4352 lines
124 KiB
C++
4352 lines
124 KiB
C++
/*
|
|
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#if !defined( INCLUDED_BRUSH_H )
|
|
#define INCLUDED_BRUSH_H
|
|
|
|
/// \file
|
|
/// \brief The brush primitive.
|
|
///
|
|
/// A collection of planes that define a convex polyhedron.
|
|
/// The Boundary-Representation of this primitive is a manifold polygonal mesh.
|
|
/// Each face polygon is represented by a list of vertices in a \c Winding.
|
|
/// Each vertex is associated with another face that is adjacent to the edge
|
|
/// formed by itself and the next vertex in the winding. This information can
|
|
/// be used to find edge-pairs and vertex-rings.
|
|
|
|
|
|
#include "debugging/debugging.h"
|
|
|
|
#include "itexdef.h"
|
|
#include "iundo.h"
|
|
#include "iselection.h"
|
|
#include "irender.h"
|
|
#include "imap.h"
|
|
#include "ibrush.h"
|
|
#include "igl.h"
|
|
#include "ifilter.h"
|
|
#include "nameable.h"
|
|
#include "moduleobserver.h"
|
|
|
|
#include <set>
|
|
|
|
#include "cullable.h"
|
|
#include "renderable.h"
|
|
#include "selectable.h"
|
|
#include "editable.h"
|
|
#include "mapfile.h"
|
|
|
|
#include "math/frustum.h"
|
|
#include "selectionlib.h"
|
|
#include "render.h"
|
|
#include "texturelib.h"
|
|
#include "container/container.h"
|
|
#include "generic/bitfield.h"
|
|
#include "signal/signalfwd.h"
|
|
|
|
#include "winding.h"
|
|
#include "brush_primit.h"
|
|
|
|
const unsigned int BRUSH_DETAIL_FLAG = 27;
|
|
const unsigned int BRUSH_DETAIL_MASK = ( 1 << BRUSH_DETAIL_FLAG );
|
|
|
|
|
|
#define BRUSH_CONNECTIVITY_DEBUG 0
|
|
#define BRUSH_DEGENERATE_DEBUG 0
|
|
|
|
#define Update_move_planepts_vertex 0
|
|
|
|
inline bool texdef_sane( const texdef_t& texdef ){
|
|
return fabs( texdef.shift[0] ) < ( 1 << 16 )
|
|
&& fabs( texdef.shift[1] ) < ( 1 << 16 );
|
|
}
|
|
|
|
inline void Winding_DrawWireframe( const Winding& winding ){
|
|
glVertexPointer( 3, GL_FLOAT, sizeof( WindingVertex ), &winding.points.data()->vertex );
|
|
glDrawArrays( GL_LINE_LOOP, 0, GLsizei( winding.numpoints ) );
|
|
}
|
|
|
|
inline void Winding_Draw( const Winding& winding, const Vector3& normal, RenderStateFlags state ){
|
|
glVertexPointer( 3, GL_FLOAT, sizeof( WindingVertex ), &winding.points.data()->vertex );
|
|
|
|
Vector3 normals[c_brush_maxFaces];
|
|
|
|
if ( ( state & RENDER_BUMP ) != 0 ) {
|
|
typedef Vector3* Vector3Iter;
|
|
for ( Vector3Iter i = normals, end = normals + winding.numpoints; i != end; ++i )
|
|
{
|
|
*i = normal;
|
|
}
|
|
if ( GlobalShaderCache().useShaderLanguage() ) {
|
|
glNormalPointer( GL_FLOAT, sizeof( Vector3 ), normals );
|
|
glVertexAttribPointerARB( c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->texcoord );
|
|
glVertexAttribPointerARB( c_attr_Tangent, 3, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->tangent );
|
|
glVertexAttribPointerARB( c_attr_Binormal, 3, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->bitangent );
|
|
}
|
|
else
|
|
{
|
|
glVertexAttribPointerARB( 11, 3, GL_FLOAT, 0, sizeof( Vector3 ), normals );
|
|
glVertexAttribPointerARB( 8, 2, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->texcoord );
|
|
glVertexAttribPointerARB( 9, 3, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->tangent );
|
|
glVertexAttribPointerARB( 10, 3, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->bitangent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( state & RENDER_LIGHTING ) {
|
|
typedef Vector3* Vector3Iter;
|
|
for ( Vector3Iter i = normals, last = normals + winding.numpoints; i != last; ++i )
|
|
{
|
|
*i = normal;
|
|
}
|
|
glNormalPointer( GL_FLOAT, sizeof( Vector3 ), normals );
|
|
}
|
|
|
|
if ( state & RENDER_TEXTURE ) {
|
|
glTexCoordPointer( 2, GL_FLOAT, sizeof( WindingVertex ), &winding.points.data()->texcoord );
|
|
}
|
|
}
|
|
#if 0
|
|
if ( state & RENDER_FILL ) {
|
|
glDrawArrays( GL_TRIANGLE_FAN, 0, GLsizei( winding.numpoints ) );
|
|
}
|
|
else
|
|
{
|
|
glDrawArrays( GL_LINE_LOOP, 0, GLsizei( winding.numpoints ) );
|
|
}
|
|
#else
|
|
glDrawArrays( GL_POLYGON, 0, GLsizei( winding.numpoints ) );
|
|
#endif
|
|
|
|
#if 0
|
|
const Winding& winding = winding;
|
|
|
|
if ( state & RENDER_FILL ) {
|
|
glBegin( GL_POLYGON );
|
|
}
|
|
else
|
|
{
|
|
glBegin( GL_LINE_LOOP );
|
|
}
|
|
|
|
if ( state & RENDER_LIGHTING ) {
|
|
glNormal3fv( normal );
|
|
}
|
|
|
|
for ( int i = 0; i < winding.numpoints; ++i )
|
|
{
|
|
if ( state & RENDER_TEXTURE ) {
|
|
glTexCoord2fv( &winding.points[i][3] );
|
|
}
|
|
glVertex3fv( winding.points[i] );
|
|
}
|
|
glEnd();
|
|
#endif
|
|
}
|
|
|
|
|
|
#include "shaderlib.h"
|
|
|
|
typedef DoubleVector3 PlanePoints[3];
|
|
|
|
inline bool planepts_equal( const PlanePoints planepts, const PlanePoints other ){
|
|
return planepts[0] == other[0] && planepts[1] == other[1] && planepts[2] == other[2];
|
|
}
|
|
|
|
inline void planepts_assign( PlanePoints planepts, const PlanePoints other ){
|
|
planepts[0] = other[0];
|
|
planepts[1] = other[1];
|
|
planepts[2] = other[2];
|
|
}
|
|
|
|
inline void planepts_quantise( PlanePoints planepts, double snap ){
|
|
vector3_snap( planepts[0], snap );
|
|
vector3_snap( planepts[1], snap );
|
|
vector3_snap( planepts[2], snap );
|
|
}
|
|
|
|
inline float vector3_max_component( const Vector3& vec3 ){
|
|
return std::max( fabsf( vec3[0] ), std::max( fabsf( vec3[1] ), fabsf( vec3[2] ) ) );
|
|
}
|
|
|
|
inline void edge_snap( Vector3& edge, double snap ){
|
|
float scale = static_cast<float>( ceil( fabs( snap / vector3_max_component( edge ) ) ) );
|
|
if ( scale > 0.0f ) {
|
|
vector3_scale( edge, scale );
|
|
}
|
|
vector3_snap( edge, snap );
|
|
}
|
|
|
|
inline void planepts_snap( PlanePoints planepts, double snap ){
|
|
Vector3 edge01( vector3_subtracted( planepts[1], planepts[0] ) );
|
|
Vector3 edge12( vector3_subtracted( planepts[2], planepts[1] ) );
|
|
Vector3 edge20( vector3_subtracted( planepts[0], planepts[2] ) );
|
|
|
|
double length_squared_01 = vector3_dot( edge01, edge01 );
|
|
double length_squared_12 = vector3_dot( edge12, edge12 );
|
|
double length_squared_20 = vector3_dot( edge20, edge20 );
|
|
|
|
vector3_snap( planepts[0], snap );
|
|
|
|
if ( length_squared_01 < length_squared_12 ) {
|
|
if ( length_squared_12 < length_squared_20 ) {
|
|
edge_snap( edge01, snap );
|
|
edge_snap( edge12, snap );
|
|
planepts[1] = vector3_added( planepts[0], edge01 );
|
|
planepts[2] = vector3_added( planepts[1], edge12 );
|
|
}
|
|
else
|
|
{
|
|
edge_snap( edge20, snap );
|
|
edge_snap( edge01, snap );
|
|
planepts[1] = vector3_added( planepts[0], edge20 );
|
|
planepts[2] = vector3_added( planepts[1], edge01 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( length_squared_01 < length_squared_20 ) {
|
|
edge_snap( edge01, snap );
|
|
edge_snap( edge12, snap );
|
|
planepts[1] = vector3_added( planepts[0], edge01 );
|
|
planepts[2] = vector3_added( planepts[1], edge12 );
|
|
}
|
|
else
|
|
{
|
|
edge_snap( edge12, snap );
|
|
edge_snap( edge20, snap );
|
|
planepts[1] = vector3_added( planepts[0], edge12 );
|
|
planepts[2] = vector3_added( planepts[1], edge20 );
|
|
}
|
|
}
|
|
}
|
|
|
|
inline PointVertex pointvertex_for_planept( const DoubleVector3& point, const Colour4b& colour ){
|
|
return PointVertex(
|
|
Vertex3f(
|
|
static_cast<float>( point.x() ),
|
|
static_cast<float>( point.y() ),
|
|
static_cast<float>( point.z() )
|
|
),
|
|
colour
|
|
);
|
|
}
|
|
|
|
inline PointVertex pointvertex_for_windingpoint( const Vector3& point, const Colour4b& colour ){
|
|
return PointVertex(
|
|
vertex3f_for_vector3( point ),
|
|
colour
|
|
);
|
|
}
|
|
|
|
inline DepthTestedPointVertex depthtested_pointvertex_for_windingpoint( const Vector3& point, const Colour4b& colour ){
|
|
return DepthTestedPointVertex(
|
|
vertex3f_for_vector3( point ),
|
|
colour
|
|
);
|
|
}
|
|
|
|
inline bool check_plane_is_integer( const PlanePoints& planePoints ){
|
|
return !float_is_integer( planePoints[0][0] )
|
|
|| !float_is_integer( planePoints[0][1] )
|
|
|| !float_is_integer( planePoints[0][2] )
|
|
|| !float_is_integer( planePoints[1][0] )
|
|
|| !float_is_integer( planePoints[1][1] )
|
|
|| !float_is_integer( planePoints[1][2] )
|
|
|| !float_is_integer( planePoints[2][0] )
|
|
|| !float_is_integer( planePoints[2][1] )
|
|
|| !float_is_integer( planePoints[2][2] );
|
|
}
|
|
|
|
inline void brush_check_shader( const char* name ){
|
|
if ( !texdef_name_valid( name ) ) {
|
|
globalErrorStream() << "brush face has invalid texture name: '" << name << "'\n";
|
|
}
|
|
}
|
|
|
|
class FaceShaderObserver
|
|
{
|
|
public:
|
|
virtual void realiseShader() = 0;
|
|
virtual void unrealiseShader() = 0;
|
|
};
|
|
|
|
class FaceShaderObserverRealise
|
|
{
|
|
public:
|
|
void operator()( FaceShaderObserver& observer ) const {
|
|
observer.realiseShader();
|
|
}
|
|
};
|
|
|
|
class FaceShaderObserverUnrealise
|
|
{
|
|
public:
|
|
void operator()( FaceShaderObserver& observer ) const {
|
|
observer.unrealiseShader();
|
|
}
|
|
};
|
|
|
|
typedef ReferencePair<FaceShaderObserver> FaceShaderObserverPair;
|
|
|
|
|
|
class ContentsFlagsValue
|
|
{
|
|
public:
|
|
ContentsFlagsValue(){
|
|
}
|
|
ContentsFlagsValue( int surfaceFlags, int contentFlags, int value, bool specified ) :
|
|
m_surfaceFlags( surfaceFlags ),
|
|
m_contentFlags( contentFlags ),
|
|
m_value( value ),
|
|
m_specified( specified ){
|
|
}
|
|
int m_surfaceFlags;
|
|
int m_contentFlags;
|
|
int m_value;
|
|
bool m_specified;
|
|
};
|
|
|
|
inline void ContentsFlagsValue_assignMasked( ContentsFlagsValue& flags, const ContentsFlagsValue& other ){
|
|
bool detail = bitfield_enabled( flags.m_contentFlags, BRUSH_DETAIL_MASK );
|
|
flags = other;
|
|
if ( detail ) {
|
|
flags.m_contentFlags = bitfield_enable( flags.m_contentFlags, BRUSH_DETAIL_MASK );
|
|
}
|
|
else
|
|
{
|
|
flags.m_contentFlags = bitfield_disable( flags.m_contentFlags, BRUSH_DETAIL_MASK );
|
|
}
|
|
}
|
|
|
|
|
|
class FaceShader : public ModuleObserver
|
|
{
|
|
public:
|
|
class SavedState
|
|
{
|
|
public:
|
|
CopiedString m_shader;
|
|
ContentsFlagsValue m_flags;
|
|
|
|
SavedState( const FaceShader& faceShader ){
|
|
m_shader = faceShader.getShader();
|
|
m_flags = faceShader.m_flags;
|
|
}
|
|
|
|
void exportState( FaceShader& faceShader ) const {
|
|
faceShader.setShader( m_shader.c_str() );
|
|
//faceShader.setFlags( m_flags ); //detail, structural flags aren't undoable with this
|
|
faceShader.m_flags = m_flags;
|
|
}
|
|
};
|
|
|
|
CopiedString m_shader;
|
|
Shader* m_state;
|
|
ContentsFlagsValue m_flags;
|
|
FaceShaderObserverPair m_observers;
|
|
bool m_instanced;
|
|
bool m_realised;
|
|
|
|
FaceShader( const char* shader, const ContentsFlagsValue& flags = ContentsFlagsValue( 0, 0, 0, false ) ) :
|
|
m_shader( shader ),
|
|
m_state( 0 ),
|
|
m_flags( flags ),
|
|
m_instanced( false ),
|
|
m_realised( false ){
|
|
captureShader();
|
|
}
|
|
~FaceShader(){
|
|
releaseShader();
|
|
}
|
|
// copy-construction not supported
|
|
FaceShader( const FaceShader& other ) = delete;
|
|
|
|
void instanceAttach(){
|
|
m_instanced = true;
|
|
m_state->incrementUsed();
|
|
}
|
|
void instanceDetach(){
|
|
m_state->decrementUsed();
|
|
m_instanced = false;
|
|
}
|
|
|
|
void captureShader(){
|
|
ASSERT_MESSAGE( m_state == 0, "shader cannot be captured" );
|
|
brush_check_shader( m_shader.c_str() );
|
|
m_state = GlobalShaderCache().capture( m_shader.c_str() );
|
|
m_state->attach( *this );
|
|
}
|
|
void releaseShader(){
|
|
ASSERT_MESSAGE( m_state != 0, "shader cannot be released" );
|
|
m_state->detach( *this );
|
|
GlobalShaderCache().release( m_shader.c_str() );
|
|
m_state = 0;
|
|
}
|
|
|
|
void realise(){
|
|
ASSERT_MESSAGE( !m_realised, "FaceTexdef::realise: already realised" );
|
|
m_realised = true;
|
|
m_observers.forEach( FaceShaderObserverRealise() );
|
|
}
|
|
void unrealise(){
|
|
ASSERT_MESSAGE( m_realised, "FaceTexdef::unrealise: already unrealised" );
|
|
m_observers.forEach( FaceShaderObserverUnrealise() );
|
|
m_realised = false;
|
|
}
|
|
|
|
void attach( FaceShaderObserver& observer ){
|
|
m_observers.attach( observer );
|
|
if ( m_realised ) {
|
|
observer.realiseShader();
|
|
}
|
|
}
|
|
|
|
void detach( FaceShaderObserver& observer ){
|
|
if ( m_realised ) {
|
|
observer.unrealiseShader();
|
|
}
|
|
m_observers.detach( observer );
|
|
}
|
|
|
|
const char* getShader() const {
|
|
return m_shader.c_str();
|
|
}
|
|
void setShader( const char* name ){
|
|
if ( m_instanced ) {
|
|
m_state->decrementUsed();
|
|
}
|
|
releaseShader();
|
|
m_shader = name;
|
|
captureShader();
|
|
if ( m_instanced ) {
|
|
m_state->incrementUsed();
|
|
}
|
|
}
|
|
ContentsFlagsValue getFlags() const {
|
|
ASSERT_MESSAGE( m_realised, "FaceShader::getFlags: flags not valid when unrealised" );
|
|
if ( !m_flags.m_specified ) {
|
|
return ContentsFlagsValue(
|
|
m_state->getTexture().surfaceFlags,
|
|
m_state->getTexture().contentFlags,
|
|
m_state->getTexture().value,
|
|
true
|
|
);
|
|
}
|
|
return m_flags;
|
|
}
|
|
void setFlags( const ContentsFlagsValue& flags ){
|
|
ASSERT_MESSAGE( m_realised, "FaceShader::setFlags: flags not valid when unrealised" );
|
|
ContentsFlagsValue_assignMasked( m_flags, flags );
|
|
}
|
|
|
|
Shader* state() const {
|
|
return m_state;
|
|
}
|
|
|
|
std::size_t width() const {
|
|
if ( m_realised ) {
|
|
return m_state->getTexture().width;
|
|
}
|
|
return 1;
|
|
}
|
|
std::size_t height() const {
|
|
if ( m_realised ) {
|
|
return m_state->getTexture().height;
|
|
}
|
|
return 1;
|
|
}
|
|
unsigned int shaderFlags() const {
|
|
if ( m_realised ) {
|
|
return m_state->getFlags();
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
class FaceTexdef : public FaceShaderObserver
|
|
{
|
|
public:
|
|
class SavedState
|
|
{
|
|
public:
|
|
TextureProjection m_projection;
|
|
|
|
SavedState( const FaceTexdef& faceTexdef ){
|
|
m_projection = faceTexdef.m_projection;
|
|
}
|
|
|
|
void exportState( FaceTexdef& faceTexdef ) const {
|
|
Texdef_Assign( faceTexdef.m_projection, m_projection );
|
|
}
|
|
};
|
|
|
|
FaceShader& m_shader;
|
|
TextureProjection m_projection;
|
|
bool m_projectionInitialised;
|
|
bool m_scaleApplied;
|
|
|
|
// not copyable
|
|
FaceTexdef( const FaceTexdef& other ) = delete;
|
|
// not assignable
|
|
FaceTexdef& operator=( const FaceTexdef& other ) = delete;
|
|
|
|
FaceTexdef(
|
|
FaceShader& shader,
|
|
const TextureProjection& projection,
|
|
bool projectionInitialised = true
|
|
) :
|
|
m_shader( shader ),
|
|
m_projection( projection ),
|
|
m_projectionInitialised( projectionInitialised ),
|
|
m_scaleApplied( false ){
|
|
m_shader.attach( *this );
|
|
}
|
|
~FaceTexdef(){
|
|
m_shader.detach( *this );
|
|
}
|
|
|
|
void addScale(){
|
|
ASSERT_MESSAGE( !m_scaleApplied, "texture scale already added" );
|
|
m_scaleApplied = true;
|
|
m_projection.m_brushprimit_texdef.addScale( m_shader.width(), m_shader.height() );
|
|
}
|
|
void removeScale(){
|
|
ASSERT_MESSAGE( m_scaleApplied, "texture scale already removed" );
|
|
m_scaleApplied = false;
|
|
m_projection.m_brushprimit_texdef.removeScale( m_shader.width(), m_shader.height() );
|
|
}
|
|
|
|
void realiseShader(){
|
|
if ( m_projectionInitialised && !m_scaleApplied ) {
|
|
addScale();
|
|
}
|
|
}
|
|
void unrealiseShader(){
|
|
if ( m_projectionInitialised && m_scaleApplied ) {
|
|
removeScale();
|
|
}
|
|
}
|
|
|
|
void setTexdef( const TextureProjection& projection, bool setBasis ){
|
|
removeScale();
|
|
Texdef_Assign( m_projection, projection, setBasis );
|
|
addScale();
|
|
}
|
|
|
|
void setTexdef( const float* hShift, const float* vShift, const float* hScale, const float* vScale, const float* rotation ){
|
|
removeScale();
|
|
Texdef_Assign( m_projection, hShift, vShift, hScale, vScale, rotation );
|
|
// if( hShift || vShift ){
|
|
// Texdef_normalise( m_projection, m_shader.width(), m_shader.height() );
|
|
// }
|
|
addScale();
|
|
}
|
|
|
|
void shift( float s, float t ){
|
|
ASSERT_MESSAGE( texdef_sane( m_projection.m_texdef ), "FaceTexdef::shift: bad texdef" );
|
|
removeScale();
|
|
Texdef_Shift( m_projection, s, t );
|
|
// Texdef_normalise( m_projection, m_shader.width(), m_shader.height() );
|
|
addScale();
|
|
}
|
|
|
|
void scale( float s, float t ){
|
|
removeScale();
|
|
Texdef_Scale( m_projection, s, t );
|
|
addScale();
|
|
}
|
|
|
|
void rotate( float angle ){
|
|
removeScale();
|
|
Texdef_Rotate( m_projection, angle );
|
|
addScale();
|
|
}
|
|
///ProjectTexture along 'direction' with parameters, defined by texdef_t
|
|
void ProjectTexture( const Plane3& plane, const texdef_t& texdef, const Vector3* direction ){
|
|
Texdef_ProjectTexture( m_projection, m_shader.width(), m_shader.height(), plane, texdef, direction );
|
|
}
|
|
///ProjectTexture along 'normal' with parameters, defined by TextureProjection
|
|
void ProjectTexture( const Plane3& plane, const TextureProjection& projection, const Vector3& normal ){
|
|
Texdef_ProjectTexture( m_projection, m_shader.width(), m_shader.height(), plane, projection, normal );
|
|
}
|
|
|
|
void Convert( TexdefTypeId in, TexdefTypeId out, const Plane3& plane ){
|
|
Texdef_Convert( in, out, plane, m_projection, m_shader.width(), m_shader.height() );
|
|
m_scaleApplied = true;
|
|
}
|
|
|
|
void fit( const Vector3& normal, const Winding& winding, float s_repeat, float t_repeat, bool only_dimension ){
|
|
Texdef_FitTexture( m_projection, m_shader.width(), m_shader.height(), normal, winding, s_repeat, t_repeat, only_dimension );
|
|
}
|
|
|
|
void emitTextureCoordinates( Winding& winding, const Vector3& normal, const Matrix4& localToWorld ){
|
|
Texdef_EmitTextureCoordinates( m_projection, m_shader.width(), m_shader.height(), winding, normal, localToWorld );
|
|
}
|
|
|
|
void transform( const Plane3& plane, const Matrix4& matrix ){
|
|
removeScale();
|
|
Texdef_transformLocked( m_projection, m_shader.width(), m_shader.height(), plane, matrix );
|
|
addScale();
|
|
}
|
|
|
|
TextureProjection normalised() const {
|
|
brushprimit_texdef_t tmp( m_projection.m_brushprimit_texdef );
|
|
tmp.removeScale( m_shader.width(), m_shader.height() );
|
|
return TextureProjection( m_projection.m_texdef, tmp, m_projection.m_basis_s, m_projection.m_basis_t );
|
|
}
|
|
#if 0 /* axial projection */
|
|
void setBasis( const Vector3& normal ){
|
|
Matrix4 basis;
|
|
Normal_GetTransform( normal, basis );
|
|
m_projection.m_basis_s = Vector3( basis.xx(), basis.yx(), basis.zx() );
|
|
m_projection.m_basis_t = Vector3( -basis.xy(), -basis.yy(), -basis.zy() );
|
|
}
|
|
#else /* face projection */
|
|
void setBasis( const Vector3& normal ){
|
|
ComputeAxisBase( normal, m_projection.m_basis_s, m_projection.m_basis_t );
|
|
}
|
|
#endif
|
|
};
|
|
|
|
inline void planepts_print( const PlanePoints& planePoints, TextOutputStream& ostream ){
|
|
ostream << "( " << planePoints[0][0] << " " << planePoints[0][1] << " " << planePoints[0][2] << " ) "
|
|
<< "( " << planePoints[1][0] << " " << planePoints[1][1] << " " << planePoints[1][2] << " ) "
|
|
<< "( " << planePoints[2][0] << " " << planePoints[2][1] << " " << planePoints[2][2] << " )";
|
|
}
|
|
|
|
|
|
inline Plane3 Plane3_applyTranslation( const Plane3& plane, const Vector3& translation ){
|
|
Plane3 tmp( plane3_translated( Plane3( plane.normal(), -plane.dist() ), translation ) );
|
|
return Plane3( tmp.normal(), -tmp.dist() );
|
|
}
|
|
|
|
inline Plane3 Plane3_applyTransform( const Plane3& plane, const Matrix4& matrix ){
|
|
/* fails for scaling */
|
|
//Plane3 tmp( plane3_transformed( Plane3( plane.normal(), -plane.dist() ), matrix ) );
|
|
//return Plane3( tmp.normal(), -tmp.dist() );
|
|
/* ok */
|
|
return plane3_transformed_affine_full( plane, matrix );
|
|
}
|
|
|
|
class FacePlane
|
|
{
|
|
PlanePoints m_planepts;
|
|
Plane3 m_planeCached;
|
|
Plane3 m_plane;
|
|
public:
|
|
Vector3 m_funcStaticOrigin;
|
|
|
|
static EBrushType m_type;
|
|
|
|
static bool isDoom3Plane(){
|
|
return FacePlane::m_type == eBrushTypeDoom3 || FacePlane::m_type == eBrushTypeQuake4;
|
|
}
|
|
|
|
class SavedState
|
|
{
|
|
public:
|
|
PlanePoints m_planepts;
|
|
Plane3 m_plane;
|
|
|
|
SavedState( const FacePlane& facePlane ){
|
|
if ( facePlane.isDoom3Plane() ) {
|
|
m_plane = facePlane.m_plane;
|
|
}
|
|
else
|
|
{
|
|
planepts_assign( m_planepts, facePlane.planePoints() );
|
|
}
|
|
}
|
|
|
|
void exportState( FacePlane& facePlane ) const {
|
|
if ( facePlane.isDoom3Plane() ) {
|
|
facePlane.m_plane = m_plane;
|
|
facePlane.updateTranslated();
|
|
}
|
|
else
|
|
{
|
|
planepts_assign( facePlane.planePoints(), m_planepts );
|
|
facePlane.MakePlane();
|
|
}
|
|
}
|
|
};
|
|
|
|
FacePlane() : m_funcStaticOrigin( 0, 0, 0 ){
|
|
}
|
|
FacePlane( const FacePlane& other ) : m_funcStaticOrigin( 0, 0, 0 ){
|
|
if ( !isDoom3Plane() ) {
|
|
planepts_assign( m_planepts, other.m_planepts );
|
|
MakePlane();
|
|
}
|
|
else
|
|
{
|
|
m_plane = other.m_plane;
|
|
updateTranslated();
|
|
}
|
|
}
|
|
FacePlane& operator=( const FacePlane& ) = default;
|
|
|
|
void MakePlane(){
|
|
if ( !isDoom3Plane() ) {
|
|
#if 0
|
|
if ( check_plane_is_integer( m_planepts ) ) {
|
|
globalErrorStream() << "non-integer planepts: ";
|
|
planepts_print( m_planepts, globalErrorStream() );
|
|
globalErrorStream() << "\n";
|
|
}
|
|
#endif
|
|
m_planeCached = plane3_for_points( m_planepts );
|
|
}
|
|
}
|
|
|
|
void reverse(){
|
|
if ( !isDoom3Plane() ) {
|
|
vector3_swap( m_planepts[0], m_planepts[2] );
|
|
MakePlane();
|
|
}
|
|
else
|
|
{
|
|
m_planeCached = plane3_flipped( m_plane );
|
|
updateSource();
|
|
}
|
|
}
|
|
void transform( const Matrix4& matrix, bool mirror ){
|
|
if ( !isDoom3Plane() ) {
|
|
|
|
#if 0
|
|
bool off = check_plane_is_integer( planePoints() );
|
|
#endif
|
|
|
|
matrix4_transform_point( matrix, m_planepts[0] );
|
|
matrix4_transform_point( matrix, m_planepts[1] );
|
|
matrix4_transform_point( matrix, m_planepts[2] );
|
|
|
|
if ( mirror ) {
|
|
reverse();
|
|
}
|
|
|
|
#if 0
|
|
if ( check_plane_is_integer( planePoints() ) ) {
|
|
if ( !off ) {
|
|
globalErrorStream() << "caused by transform\n";
|
|
}
|
|
}
|
|
#endif
|
|
MakePlane();
|
|
}
|
|
else
|
|
{
|
|
m_planeCached = Plane3_applyTransform( m_planeCached, matrix );
|
|
updateSource();
|
|
}
|
|
}
|
|
void offset( float offset ){
|
|
if ( !isDoom3Plane() ) {
|
|
Vector3 move( vector3_scaled( m_planeCached.normal(), -offset ) );
|
|
|
|
vector3_subtract( m_planepts[0], move );
|
|
vector3_subtract( m_planepts[1], move );
|
|
vector3_subtract( m_planepts[2], move );
|
|
|
|
MakePlane();
|
|
}
|
|
else
|
|
{
|
|
m_planeCached.d += offset;
|
|
updateSource();
|
|
}
|
|
}
|
|
|
|
void updateTranslated(){
|
|
m_planeCached = Plane3_applyTranslation( m_plane, m_funcStaticOrigin );
|
|
}
|
|
void updateSource(){
|
|
m_plane = Plane3_applyTranslation( m_planeCached, vector3_negated( m_funcStaticOrigin ) );
|
|
}
|
|
|
|
|
|
PlanePoints& planePoints(){
|
|
return m_planepts;
|
|
}
|
|
const PlanePoints& planePoints() const {
|
|
return m_planepts;
|
|
}
|
|
const PlanePoints& getPlanePoints(){
|
|
if( isDoom3Plane() ){
|
|
m_planepts[0] = m_planeCached.normal() * m_planeCached.dist();
|
|
ComputeAxisBase( m_planeCached.normal(), m_planepts[1], m_planepts[2] );
|
|
m_planepts[1] = m_planepts[1] + m_planepts[0];
|
|
m_planepts[2] = m_planepts[2] + m_planepts[0];
|
|
}
|
|
return m_planepts;
|
|
}
|
|
const Plane3& plane3() const {
|
|
return m_planeCached;
|
|
}
|
|
void setDoom3Plane( const Plane3& plane ){
|
|
m_plane = plane;
|
|
updateTranslated();
|
|
}
|
|
const Plane3& getDoom3Plane() const {
|
|
return m_plane;
|
|
}
|
|
|
|
void copy( const FacePlane& other ){
|
|
if ( !isDoom3Plane() ) {
|
|
planepts_assign( m_planepts, other.m_planepts );
|
|
MakePlane();
|
|
}
|
|
else
|
|
{
|
|
m_planeCached = other.m_plane;
|
|
updateSource();
|
|
}
|
|
}
|
|
void copy( const Vector3& p0, const Vector3& p1, const Vector3& p2 ){
|
|
if ( !isDoom3Plane() ) {
|
|
m_planepts[0] = p0;
|
|
m_planepts[1] = p1;
|
|
m_planepts[2] = p2;
|
|
MakePlane();
|
|
}
|
|
else
|
|
{
|
|
m_planeCached = plane3_for_points( p0, p1, p2 );
|
|
updateSource();
|
|
}
|
|
}
|
|
void copy( const PlanePoints planepoints ){
|
|
if ( !isDoom3Plane() ) {
|
|
planepts_assign( m_planepts, planepoints );
|
|
MakePlane();
|
|
}
|
|
else
|
|
{
|
|
m_planeCached = plane3_for_points( planepoints );
|
|
updateSource();
|
|
}
|
|
}
|
|
};
|
|
|
|
inline void Winding_testSelect( Winding& winding, SelectionTest& test, SelectionIntersection& best, const DoubleVector3 planepoints[3] ){
|
|
test.TestPolygon( VertexPointer( reinterpret_cast<VertexPointer::pointer>( &winding.points.data()->vertex ), sizeof( WindingVertex ) ), winding.numpoints, best, planepoints );
|
|
}
|
|
|
|
const double GRID_MIN = 0.125;
|
|
|
|
inline double quantiseInteger( double f ){
|
|
return float_to_integer( f );
|
|
}
|
|
|
|
inline double quantiseFloating( double f ){
|
|
return float_snapped( f, 1.f / ( 1 << 16 ) );
|
|
}
|
|
|
|
typedef double ( *QuantiseFunc )( double f );
|
|
|
|
class Face;
|
|
|
|
class FaceFilter
|
|
{
|
|
public:
|
|
virtual bool filter( const Face& face ) const = 0;
|
|
};
|
|
|
|
bool face_filtered( Face& face );
|
|
void add_face_filter( FaceFilter& filter, int mask, bool invert = false );
|
|
|
|
void Brush_addTextureChangedCallback( const SignalHandler& callback );
|
|
void Brush_textureChanged();
|
|
|
|
|
|
extern bool g_brush_texturelock_enabled;
|
|
extern bool g_brush_textureVertexlock_enabled;
|
|
|
|
inline TexdefTypeId BrushType_getTexdefType( EBrushType type ){
|
|
switch ( type )
|
|
{
|
|
case eBrushTypeQuake3BP:
|
|
case eBrushTypeDoom3:
|
|
case eBrushTypeQuake4:
|
|
return TEXDEFTYPEID_BRUSHPRIMITIVES;
|
|
case eBrushTypeValve220:
|
|
case eBrushTypeQuake3Valve220:
|
|
return TEXDEFTYPEID_VALVE;
|
|
default:
|
|
return TEXDEFTYPEID_QUAKE;
|
|
}
|
|
}
|
|
|
|
class FaceObserver
|
|
{
|
|
public:
|
|
virtual void planeChanged() = 0;
|
|
virtual void connectivityChanged() = 0;
|
|
virtual void shaderChanged() = 0;
|
|
virtual void evaluateTransform() = 0;
|
|
};
|
|
|
|
class Face :
|
|
public OpenGLRenderable,
|
|
public Filterable,
|
|
public Undoable,
|
|
public FaceShaderObserver
|
|
{
|
|
std::size_t m_refcount;
|
|
|
|
class SavedState : public UndoMemento
|
|
{
|
|
public:
|
|
FacePlane::SavedState m_planeState;
|
|
FaceTexdef::SavedState m_texdefState;
|
|
FaceShader::SavedState m_shaderState;
|
|
|
|
SavedState( const Face& face ) : m_planeState( face.getPlane() ), m_texdefState( face.getTexdef() ), m_shaderState( face.getShader() ){
|
|
}
|
|
|
|
void exportState( Face& face ) const {
|
|
m_planeState.exportState( face.getPlane() );
|
|
m_shaderState.exportState( face.getShader() );
|
|
m_texdefState.exportState( face.getTexdef() );
|
|
}
|
|
|
|
void release(){
|
|
delete this;
|
|
}
|
|
};
|
|
|
|
public:
|
|
static QuantiseFunc m_quantise;
|
|
static EBrushType m_type;
|
|
|
|
PlanePoints m_move_planepts;
|
|
PlanePoints m_move_planeptsTransformed;
|
|
private:
|
|
FacePlane m_plane;
|
|
FacePlane m_planeTransformed;
|
|
FaceShader m_shader;
|
|
FaceTexdef m_texdef;
|
|
TextureProjection m_texdefTransformed;
|
|
|
|
Winding m_winding;
|
|
Vector3 m_centroid;
|
|
Vector3 m_centroid_cached; //this is far not pretty hack! (invariant point for texlock in AP)
|
|
bool m_filtered;
|
|
|
|
FaceObserver* m_observer;
|
|
UndoObserver* m_undoable_observer;
|
|
MapFile* m_map;
|
|
|
|
public:
|
|
|
|
// assignment not supported
|
|
Face& operator=( const Face& other ) = delete;
|
|
// copy-construction not supported
|
|
Face( const Face& other ) = delete;
|
|
|
|
Face( FaceObserver* observer ) :
|
|
m_refcount( 0 ),
|
|
m_shader( texdef_name_default() ),
|
|
m_texdef( m_shader, TextureProjection(), false ),
|
|
m_filtered( false ),
|
|
m_observer( observer ),
|
|
m_undoable_observer( 0 ),
|
|
m_map( 0 ){
|
|
m_shader.attach( *this );
|
|
m_plane.copy( Vector3( 0, 0, 0 ), Vector3( 64, 0, 0 ), Vector3( 0, 64, 0 ) );
|
|
m_texdef.setBasis( m_plane.plane3().normal() );
|
|
planeChanged();
|
|
}
|
|
Face(
|
|
const Vector3& p0,
|
|
const Vector3& p1,
|
|
const Vector3& p2,
|
|
const char* shader,
|
|
const TextureProjection& projection,
|
|
FaceObserver* observer
|
|
) :
|
|
m_refcount( 0 ),
|
|
m_shader( shader ),
|
|
m_texdef( m_shader, projection ),
|
|
m_observer( observer ),
|
|
m_undoable_observer( 0 ),
|
|
m_map( 0 ){
|
|
m_shader.attach( *this );
|
|
m_plane.copy( p0, p1, p2 );
|
|
m_texdef.setBasis( m_plane.plane3().normal() );
|
|
planeChanged();
|
|
updateFiltered();
|
|
}
|
|
Face( const Face& other, FaceObserver* observer ) :
|
|
m_refcount( 0 ),
|
|
m_shader( other.m_shader.getShader(), other.m_shader.m_flags ),
|
|
m_texdef( m_shader, other.getTexdef().normalised() ),
|
|
m_observer( observer ),
|
|
m_undoable_observer( 0 ),
|
|
m_map( 0 ){
|
|
m_shader.attach( *this );
|
|
m_plane.copy( other.m_plane );
|
|
planepts_assign( m_move_planepts, other.m_move_planepts );
|
|
// m_texdef.setBasis( m_plane.plane3().normal() ); //don't reset basis on face clone
|
|
planeChanged();
|
|
updateFiltered();
|
|
}
|
|
~Face(){
|
|
m_shader.detach( *this );
|
|
}
|
|
|
|
void planeChanged(){
|
|
revertTransform();
|
|
m_observer->planeChanged();
|
|
}
|
|
|
|
void realiseShader(){
|
|
m_observer->shaderChanged();
|
|
}
|
|
void unrealiseShader(){
|
|
}
|
|
|
|
void instanceAttach( MapFile* map ){
|
|
m_shader.instanceAttach();
|
|
m_map = map;
|
|
m_undoable_observer = GlobalUndoSystem().observer( this );
|
|
GlobalFilterSystem().registerFilterable( *this );
|
|
}
|
|
void instanceDetach( MapFile* map ){
|
|
GlobalFilterSystem().unregisterFilterable( *this );
|
|
m_undoable_observer = 0;
|
|
GlobalUndoSystem().release( this );
|
|
m_map = 0;
|
|
m_shader.instanceDetach();
|
|
}
|
|
|
|
void render( RenderStateFlags state ) const {
|
|
Winding_Draw( m_winding, m_planeTransformed.plane3().normal(), state );
|
|
}
|
|
|
|
void updateFiltered(){
|
|
m_filtered = face_filtered( *this );
|
|
}
|
|
bool isFiltered() const {
|
|
return m_filtered;
|
|
}
|
|
|
|
void undoSave(){
|
|
if ( m_map != 0 ) {
|
|
m_map->changed();
|
|
}
|
|
if ( m_undoable_observer != 0 ) {
|
|
m_undoable_observer->save( this );
|
|
}
|
|
}
|
|
|
|
// undoable
|
|
UndoMemento* exportState() const {
|
|
return new SavedState( *this );
|
|
}
|
|
void importState( const UndoMemento* data ){
|
|
undoSave();
|
|
|
|
static_cast<const SavedState*>( data )->exportState( *this );
|
|
|
|
planeChanged();
|
|
m_observer->connectivityChanged();
|
|
texdefChanged();
|
|
m_observer->shaderChanged();
|
|
updateFiltered();
|
|
}
|
|
|
|
void IncRef(){
|
|
++m_refcount;
|
|
}
|
|
void DecRef(){
|
|
if ( --m_refcount == 0 ) {
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void flipWinding(){
|
|
m_plane.reverse();
|
|
planeChanged();
|
|
}
|
|
|
|
bool intersectVolume( const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
return volume.TestPlane( Plane3( plane3().normal(), -plane3().dist() ), localToWorld );
|
|
}
|
|
|
|
void render( Renderer& renderer, const Matrix4& localToWorld ) const {
|
|
renderer.SetState( m_shader.state(), Renderer::eFullMaterials );
|
|
renderer.addRenderable( *this, localToWorld );
|
|
}
|
|
|
|
void texdef_from_points(){
|
|
Matrix4 local2tex;
|
|
Texdef_Construct_local2tex( m_texdef.m_projection, m_shader.width(), m_shader.height(), m_plane.plane3().normal(), local2tex );
|
|
DoubleVector3 st[3];
|
|
for ( std::size_t i = 0; i < 3; ++i )
|
|
st[i] = matrix4_transformed_point( local2tex, m_move_planepts[i] );
|
|
Texdef_from_ST( m_texdefTransformed, m_move_planeptsTransformed, st, m_shader.width(), m_shader.height() );
|
|
|
|
Brush_textureChanged();
|
|
}
|
|
|
|
void transform_texdef( const Matrix4& matrix, const Vector3& invariant = g_vector3_identity ){
|
|
revertTexdef();
|
|
// Texdef_transformLocked( m_texdefTransformed, m_shader.width(), m_shader.height(), m_plane.plane3(), matrix, static_cast<Vector3>( m_plane.plane3().normal() * m_plane.plane3().dist() ) );
|
|
Texdef_transform( m_texdefTransformed, m_shader.width(), m_shader.height(), m_plane.plane3(), matrix, invariant );
|
|
EmitTextureCoordinates();
|
|
Brush_textureChanged();
|
|
}
|
|
|
|
void transform( const Matrix4& matrix, bool mirror ){
|
|
if ( g_brush_texturelock_enabled ) {
|
|
Texdef_transformLocked( m_texdefTransformed, m_shader.width(), m_shader.height(), m_plane.plane3(), matrix,
|
|
contributes() ? m_centroid_cached
|
|
: static_cast<Vector3>( m_plane.plane3().normal() * m_plane.plane3().dist() ) );
|
|
Brush_textureChanged();
|
|
}
|
|
else if( g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_VALVE ){
|
|
const DoubleVector3 from = vector3_normalised( vector3_cross( m_texdefTransformed.m_basis_s, m_texdefTransformed.m_basis_t ) );
|
|
const DoubleVector3 to = matrix4_transformed_normal( matrix, from );
|
|
Quaternion quat = quaternion_for_unit_vectors( from, to );
|
|
if( quat.w() != quat.w() ){ //handle 180` cases
|
|
if( vector3_max_abs_component_index( from ) == 2 )
|
|
quat = Quaternion( g_vector3_axis_y, 0 );
|
|
else
|
|
quat = Quaternion( g_vector3_axis_z, 0 );
|
|
}
|
|
const Matrix4 mat = matrix4_rotation_for_quaternion( quat );
|
|
m_texdefTransformed.m_basis_s = vector3_normalised( matrix4_transformed_direction( mat, m_texdefTransformed.m_basis_s ) );
|
|
m_texdefTransformed.m_basis_t = vector3_normalised( matrix4_transformed_direction( mat, m_texdefTransformed.m_basis_t ) );
|
|
}
|
|
|
|
m_planeTransformed.transform( matrix, mirror );
|
|
|
|
#if 0
|
|
ASSERT_MESSAGE( projectionaxis_for_normal( normal ) == projectionaxis_for_normal( plane3().normal() ), "bleh" );
|
|
#endif
|
|
m_observer->planeChanged();
|
|
}
|
|
|
|
void assign_planepts( const PlanePoints planepts ){
|
|
m_planeTransformed.copy( planepts );
|
|
m_observer->planeChanged();
|
|
}
|
|
|
|
/// \brief Reverts the transformable state of the brush to identity.
|
|
void revertTransform(){
|
|
m_planeTransformed = m_plane;
|
|
planepts_assign( m_move_planeptsTransformed, m_move_planepts );
|
|
m_texdefTransformed = m_texdef.m_projection;
|
|
}
|
|
void freezeTransform(){
|
|
undoSave();
|
|
m_plane = m_planeTransformed;
|
|
planepts_assign( m_move_planepts, m_move_planeptsTransformed );
|
|
m_texdef.m_projection = m_texdefTransformed;
|
|
}
|
|
|
|
void update_move_planepts_vertex( std::size_t index, PlanePoints planePoints ){
|
|
if( contributes() ){
|
|
std::size_t numpoints = getWinding().numpoints;
|
|
ASSERT_MESSAGE( index < numpoints, "update_move_planepts_vertex: invalid index" );
|
|
|
|
std::size_t opposite = Winding_Opposite( getWinding(), index );
|
|
std::size_t adjacent = Winding_wrap( getWinding(), opposite + numpoints - 1 );
|
|
planePoints[0] = getWinding()[opposite].vertex;
|
|
planePoints[1] = getWinding()[index].vertex;
|
|
planePoints[2] = getWinding()[adjacent].vertex;
|
|
// winding points are very inaccurate, so they must be quantised before using them to generate the face-plane
|
|
// planepts_quantise( planePoints, GRID_MIN );
|
|
}
|
|
}
|
|
|
|
void snapto( float snap ){
|
|
if ( contributes() ) {
|
|
#if 0
|
|
ASSERT_MESSAGE( plane3_valid( m_plane.plane3() ), "invalid plane before snap to grid" );
|
|
planepts_snap( m_plane.planePoints(), snap );
|
|
ASSERT_MESSAGE( plane3_valid( m_plane.plane3() ), "invalid plane after snap to grid" );
|
|
#else
|
|
PlanePoints planePoints;
|
|
update_move_planepts_vertex( 0, planePoints );
|
|
vector3_snap( planePoints[0], snap );
|
|
vector3_snap( planePoints[1], snap );
|
|
vector3_snap( planePoints[2], snap );
|
|
assign_planepts( planePoints );
|
|
freezeTransform();
|
|
#endif
|
|
SceneChangeNotify();
|
|
if ( !plane3_valid( m_plane.plane3() ) ) {
|
|
globalWarningStream() << "WARNING: invalid plane after snap to grid\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void testSelect( SelectionTest& test, SelectionIntersection& best ){
|
|
Winding_testSelect( m_winding, test, best, m_plane.getPlanePoints() );
|
|
}
|
|
|
|
void testSelect_centroid( SelectionTest& test, SelectionIntersection& best ) const {
|
|
test.TestPoint( m_centroid, best );
|
|
}
|
|
|
|
void shaderChanged(){
|
|
EmitTextureCoordinates();
|
|
Brush_textureChanged();
|
|
m_observer->shaderChanged();
|
|
updateFiltered();
|
|
planeChanged();
|
|
SceneChangeNotify();
|
|
}
|
|
|
|
const char* GetShader() const {
|
|
return m_shader.getShader();
|
|
}
|
|
void SetShader( const char* name ){
|
|
undoSave();
|
|
m_shader.setShader( name );
|
|
shaderChanged();
|
|
}
|
|
|
|
void revertTexdef(){
|
|
m_texdefTransformed = m_texdef.m_projection;
|
|
}
|
|
void texdefChanged(){
|
|
revertTexdef();
|
|
EmitTextureCoordinates();
|
|
Brush_textureChanged();
|
|
}
|
|
|
|
void GetTexdef( TextureProjection& projection ) const {
|
|
projection = m_texdef.normalised();
|
|
}
|
|
void SetTexdef( const TextureProjection& projection, bool setBasis = true, bool resetBasis = false ){
|
|
undoSave();
|
|
m_texdef.setTexdef( projection, setBasis );
|
|
if( resetBasis ){
|
|
m_texdef.setBasis( m_plane.plane3().normal() );
|
|
}
|
|
texdefChanged();
|
|
}
|
|
|
|
void SetTexdef( const float* hShift, const float* vShift, const float* hScale, const float* vScale, const float* rotation ){
|
|
undoSave();
|
|
m_texdef.setTexdef( hShift, vShift, hScale, vScale, rotation );
|
|
texdefChanged();
|
|
}
|
|
|
|
void GetFlags( ContentsFlagsValue& flags ) const {
|
|
flags = m_shader.getFlags();
|
|
}
|
|
void SetFlags( const ContentsFlagsValue& flags ){
|
|
undoSave();
|
|
m_shader.setFlags( flags );
|
|
m_observer->shaderChanged();
|
|
updateFiltered();
|
|
}
|
|
|
|
void ShiftTexdef( float s, float t ){
|
|
undoSave();
|
|
m_texdef.shift( s, t );
|
|
texdefChanged();
|
|
}
|
|
|
|
void ScaleTexdef( float s, float t ){
|
|
undoSave();
|
|
m_texdef.scale( s, t );
|
|
texdefChanged();
|
|
}
|
|
|
|
void RotateTexdef( float angle ){
|
|
undoSave();
|
|
m_texdef.rotate( angle );
|
|
texdefChanged();
|
|
}
|
|
|
|
void ProjectTexture( const texdef_t& texdef, const Vector3* direction ){
|
|
undoSave();
|
|
m_texdef.ProjectTexture( m_plane.plane3(), texdef, direction );
|
|
texdefChanged();
|
|
}
|
|
|
|
void ProjectTexture( const TextureProjection& projection, const Vector3& normal ){
|
|
undoSave();
|
|
m_texdef.ProjectTexture( m_plane.plane3(), projection, normal );
|
|
texdefChanged();
|
|
}
|
|
|
|
void Convert( TexdefTypeId in, TexdefTypeId out ){
|
|
m_texdef.Convert( in, out, m_plane.plane3() );
|
|
texdefChanged();
|
|
}
|
|
|
|
void FitTexture( float s_repeat, float t_repeat, bool only_dimension ){
|
|
undoSave();
|
|
m_texdef.fit( m_plane.plane3().normal(), m_winding, s_repeat, t_repeat, only_dimension );
|
|
texdefChanged();
|
|
}
|
|
|
|
void EmitTextureCoordinates(){
|
|
Texdef_EmitTextureCoordinates( m_texdefTransformed, m_shader.width(), m_shader.height(), m_winding, plane3().normal(), g_matrix4_identity );
|
|
}
|
|
|
|
|
|
const Vector3& centroid() const {
|
|
return m_centroid;
|
|
}
|
|
|
|
void construct_centroid(){
|
|
Winding_Centroid( m_winding, plane3(), m_centroid );
|
|
}
|
|
|
|
const Winding& getWinding() const {
|
|
return m_winding;
|
|
}
|
|
Winding& getWinding(){
|
|
return m_winding;
|
|
}
|
|
|
|
void cacheCentroid() {
|
|
m_centroid_cached = m_centroid;
|
|
}
|
|
|
|
const Plane3& plane3() const {
|
|
m_observer->evaluateTransform();
|
|
return m_planeTransformed.plane3();
|
|
}
|
|
const Plane3& plane3_() const {
|
|
return m_planeTransformed.plane3();
|
|
}
|
|
FacePlane& getPlane(){
|
|
return m_plane;
|
|
}
|
|
const FacePlane& getPlane() const {
|
|
return m_plane;
|
|
}
|
|
FaceTexdef& getTexdef(){
|
|
return m_texdef;
|
|
}
|
|
const FaceTexdef& getTexdef() const {
|
|
return m_texdef;
|
|
}
|
|
FaceShader& getShader(){
|
|
return m_shader;
|
|
}
|
|
const FaceShader& getShader() const {
|
|
return m_shader;
|
|
}
|
|
|
|
bool isDetail() const {
|
|
return ( m_shader.m_flags.m_contentFlags & BRUSH_DETAIL_MASK ) != 0;
|
|
}
|
|
void setDetail( bool detail ){
|
|
undoSave();
|
|
if ( detail && !isDetail() ) {
|
|
m_shader.m_flags.m_contentFlags |= BRUSH_DETAIL_MASK;
|
|
}
|
|
else if ( !detail && isDetail() ) {
|
|
m_shader.m_flags.m_contentFlags &= ~BRUSH_DETAIL_MASK;
|
|
}
|
|
m_observer->shaderChanged();
|
|
}
|
|
|
|
bool contributes() const {
|
|
return m_winding.numpoints > 2;
|
|
}
|
|
bool is_bounded() const {
|
|
for ( Winding::const_iterator i = m_winding.begin(); i != m_winding.end(); ++i )
|
|
{
|
|
if ( ( *i ).adjacent == c_brush_maxFaces ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
class FaceVertexId
|
|
{
|
|
std::size_t m_face;
|
|
std::size_t m_vertex;
|
|
|
|
public:
|
|
FaceVertexId( std::size_t face, std::size_t vertex )
|
|
: m_face( face ), m_vertex( vertex ){
|
|
}
|
|
|
|
std::size_t getFace() const {
|
|
return m_face;
|
|
}
|
|
std::size_t getVertex() const {
|
|
return m_vertex;
|
|
}
|
|
};
|
|
|
|
typedef std::size_t faceIndex_t;
|
|
|
|
struct EdgeRenderIndices
|
|
{
|
|
RenderIndex first;
|
|
RenderIndex second;
|
|
|
|
EdgeRenderIndices()
|
|
: first( 0 ), second( 0 ){
|
|
}
|
|
EdgeRenderIndices( const RenderIndex _first, const RenderIndex _second )
|
|
: first( _first ), second( _second ){
|
|
}
|
|
};
|
|
|
|
struct EdgeFaces
|
|
{
|
|
faceIndex_t first;
|
|
faceIndex_t second;
|
|
|
|
EdgeFaces()
|
|
: first( c_brush_maxFaces ), second( c_brush_maxFaces ){
|
|
}
|
|
EdgeFaces( const faceIndex_t _first, const faceIndex_t _second )
|
|
: first( _first ), second( _second ){
|
|
}
|
|
};
|
|
|
|
class RenderableWireframe : public OpenGLRenderable
|
|
{
|
|
public:
|
|
void render( RenderStateFlags state ) const {
|
|
#if 1
|
|
glVertexPointer( 3, GL_FLOAT, sizeof( DepthTestedPointVertex ), &m_vertices->vertex );
|
|
glDrawElements( GL_LINES, GLsizei( m_size << 1 ), RenderIndexTypeID, m_faceVertex.data() );
|
|
#else
|
|
glBegin( GL_LINES );
|
|
for ( std::size_t i = 0; i < m_size; ++i )
|
|
{
|
|
glVertex3fv( &m_vertices[m_faceVertex[i].first].vertex.x );
|
|
glVertex3fv( &m_vertices[m_faceVertex[i].second].vertex.x );
|
|
}
|
|
glEnd();
|
|
#endif
|
|
}
|
|
|
|
Array<EdgeRenderIndices> m_faceVertex;
|
|
std::size_t m_size;
|
|
const DepthTestedPointVertex* m_vertices;
|
|
};
|
|
|
|
class Brush;
|
|
typedef std::vector<Brush*> brush_vector_t;
|
|
|
|
class BrushFilter
|
|
{
|
|
public:
|
|
virtual bool filter( const Brush& brush ) const = 0;
|
|
};
|
|
|
|
bool brush_filtered( Brush& brush );
|
|
void add_brush_filter( BrushFilter& filter, int mask, bool invert = false );
|
|
|
|
|
|
/// \brief Returns true if 'self' takes priority when building brush b-rep.
|
|
inline bool plane3_inside( const Plane3& self, const Plane3& other, bool selfIsLater ){
|
|
if ( vector3_equal_epsilon( self.normal(), other.normal(), 0.001 ) ) {
|
|
// same plane? prefer the one with smaller index
|
|
if ( self.dist() == other.dist() ) {
|
|
return selfIsLater;
|
|
}
|
|
return self.dist() < other.dist();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
typedef SmartPointer<Face> FaceSmartPointer;
|
|
typedef std::vector<FaceSmartPointer> Faces;
|
|
|
|
/// \brief Returns the unique-id of the edge adjacent to \p faceVertex in the edge-pair for the set of \p faces.
|
|
inline FaceVertexId next_edge( const Faces& faces, FaceVertexId faceVertex ){
|
|
std::size_t adjacent_face = faces[faceVertex.getFace()]->getWinding()[faceVertex.getVertex()].adjacent;
|
|
std::size_t adjacent_vertex = Winding_FindAdjacent( faces[adjacent_face]->getWinding(), faceVertex.getFace() );
|
|
|
|
ASSERT_MESSAGE( adjacent_vertex != c_brush_maxFaces, "connectivity data invalid" );
|
|
if ( adjacent_vertex == c_brush_maxFaces ) {
|
|
return faceVertex;
|
|
}
|
|
|
|
return FaceVertexId( adjacent_face, adjacent_vertex );
|
|
}
|
|
|
|
/// \brief Returns the unique-id of the vertex adjacent to \p faceVertex in the vertex-ring for the set of \p faces.
|
|
inline FaceVertexId next_vertex( const Faces& faces, FaceVertexId faceVertex ){
|
|
FaceVertexId nextEdge = next_edge( faces, faceVertex );
|
|
return FaceVertexId( nextEdge.getFace(), Winding_next( faces[nextEdge.getFace()]->getWinding(), nextEdge.getVertex() ) );
|
|
}
|
|
|
|
class SelectableEdge
|
|
{
|
|
public:
|
|
Vector3 getEdge() const {
|
|
const Winding& winding = getFace().getWinding();
|
|
return vector3_mid( winding[m_faceVertex.getVertex()].vertex, winding[Winding_next( winding, m_faceVertex.getVertex() )].vertex );
|
|
}
|
|
|
|
Faces& m_faces;
|
|
FaceVertexId m_faceVertex;
|
|
|
|
SelectableEdge( Faces& faces, FaceVertexId faceVertex )
|
|
: m_faces( faces ), m_faceVertex( faceVertex ){
|
|
}
|
|
SelectableEdge& operator=( const SelectableEdge& other ){
|
|
m_faceVertex = other.m_faceVertex;
|
|
return *this;
|
|
}
|
|
|
|
Face& getFace() const {
|
|
return *m_faces[m_faceVertex.getFace()];
|
|
}
|
|
|
|
void testSelect( SelectionTest& test, SelectionIntersection& best ){
|
|
test.TestPoint( getEdge(), best );
|
|
}
|
|
};
|
|
|
|
class SelectableVertex
|
|
{
|
|
public:
|
|
Vector3 getVertex() const {
|
|
return getFace().getWinding()[m_faceVertex.getVertex()].vertex;
|
|
}
|
|
|
|
Faces& m_faces;
|
|
FaceVertexId m_faceVertex;
|
|
|
|
SelectableVertex( Faces& faces, FaceVertexId faceVertex )
|
|
: m_faces( faces ), m_faceVertex( faceVertex ){
|
|
}
|
|
SelectableVertex& operator=( const SelectableVertex& other ){
|
|
m_faceVertex = other.m_faceVertex;
|
|
return *this;
|
|
}
|
|
|
|
Face& getFace() const {
|
|
return *m_faces[m_faceVertex.getFace()];
|
|
}
|
|
|
|
void testSelect( SelectionTest& test, SelectionIntersection& best ){
|
|
test.TestPoint( getVertex(), best );
|
|
}
|
|
};
|
|
|
|
class BrushObserver
|
|
{
|
|
public:
|
|
virtual void reserve( std::size_t size ) = 0;
|
|
virtual void clear() = 0;
|
|
virtual void push_back( Face& face ) = 0;
|
|
virtual void pop_back() = 0;
|
|
virtual void erase( std::size_t index ) = 0;
|
|
virtual void connectivityChanged() = 0;
|
|
|
|
virtual void edge_clear() = 0;
|
|
virtual void edge_push_back( SelectableEdge& edge ) = 0;
|
|
|
|
virtual void vertex_clear() = 0;
|
|
virtual void vertex_push_back( SelectableVertex& vertex ) = 0;
|
|
|
|
virtual void vertex_select() = 0;
|
|
virtual void vertex_snap( const float snap ) = 0;
|
|
|
|
virtual void DEBUG_verify() const = 0;
|
|
};
|
|
|
|
class BrushVisitor
|
|
{
|
|
public:
|
|
virtual void visit( Face& face ) const = 0;
|
|
};
|
|
|
|
class Brush :
|
|
public TransformNode,
|
|
public Bounded,
|
|
public Cullable,
|
|
public Snappable,
|
|
public Undoable,
|
|
public FaceObserver,
|
|
public Filterable,
|
|
public Nameable,
|
|
public BrushDoom3
|
|
{
|
|
private:
|
|
scene::Node* m_node;
|
|
typedef UniqueSet<BrushObserver*> Observers;
|
|
Observers m_observers;
|
|
UndoObserver* m_undoable_observer;
|
|
MapFile* m_map;
|
|
|
|
// state
|
|
Faces m_faces;
|
|
// ----
|
|
|
|
// cached data compiled from state
|
|
Array<PointVertex> m_faceCentroidPoints;
|
|
RenderablePointArray<PointVertex> m_render_faces;
|
|
|
|
mutable Array<DepthTestedPointVertex> m_uniqueVertexPoints;
|
|
typedef std::vector<SelectableVertex> SelectableVertices;
|
|
SelectableVertices m_select_vertices;
|
|
RenderablePointArray<DepthTestedPointVertex> m_render_vertices;
|
|
RenderableDepthTestedPointArray m_render_deepvertices;
|
|
|
|
Array<PointVertex> m_uniqueEdgePoints;
|
|
typedef std::vector<SelectableEdge> SelectableEdges;
|
|
SelectableEdges m_select_edges;
|
|
RenderablePointArray<PointVertex> m_render_edges;
|
|
|
|
Array<EdgeRenderIndices> m_edge_indices;
|
|
Array<EdgeFaces> m_edge_faces;
|
|
|
|
AABB m_aabb_local;
|
|
// ----
|
|
|
|
Callback m_evaluateTransform;
|
|
Callback m_boundsChanged;
|
|
|
|
mutable bool m_planeChanged; // b-rep evaluation required
|
|
mutable bool m_transformChanged; // transform evaluation required
|
|
bool m_BRep_evaluation = false; //mutex for invalidation
|
|
// ----
|
|
|
|
public:
|
|
STRING_CONSTANT( Name, "Brush" );
|
|
|
|
Callback m_lightsChanged;
|
|
|
|
// static data
|
|
static Shader* m_state_point;
|
|
static Shader* m_state_deeppoint;
|
|
// ----
|
|
|
|
static EBrushType m_type;
|
|
static double m_maxWorldCoord;
|
|
|
|
Brush( scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged ) :
|
|
m_node( &node ),
|
|
m_undoable_observer( 0 ),
|
|
m_map( 0 ),
|
|
m_render_faces( m_faceCentroidPoints, GL_POINTS ),
|
|
m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
|
|
m_render_deepvertices( m_uniqueVertexPoints, GL_POINTS ),
|
|
m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
|
|
m_evaluateTransform( evaluateTransform ),
|
|
m_boundsChanged( boundsChanged ),
|
|
m_planeChanged( false ),
|
|
m_transformChanged( false ){
|
|
planeChanged();
|
|
}
|
|
Brush( const Brush& other, scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged ) :
|
|
m_node( &node ),
|
|
m_undoable_observer( 0 ),
|
|
m_map( 0 ),
|
|
m_render_faces( m_faceCentroidPoints, GL_POINTS ),
|
|
m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
|
|
m_render_deepvertices( m_uniqueVertexPoints, GL_POINTS ),
|
|
m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
|
|
m_evaluateTransform( evaluateTransform ),
|
|
m_boundsChanged( boundsChanged ),
|
|
m_planeChanged( false ),
|
|
m_transformChanged( false ){
|
|
copy( other );
|
|
}
|
|
Brush( const Brush& other ) :
|
|
TransformNode( other ),
|
|
Bounded( other ),
|
|
Cullable( other ),
|
|
Snappable(),
|
|
Undoable( other ),
|
|
FaceObserver( other ),
|
|
Filterable( other ),
|
|
Nameable( other ),
|
|
BrushDoom3( other ),
|
|
m_node( 0 ),
|
|
m_undoable_observer( 0 ),
|
|
m_map( 0 ),
|
|
m_render_faces( m_faceCentroidPoints, GL_POINTS ),
|
|
m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
|
|
m_render_deepvertices( m_uniqueVertexPoints, GL_POINTS ),
|
|
m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
|
|
m_planeChanged( false ),
|
|
m_transformChanged( false ){
|
|
copy( other );
|
|
}
|
|
~Brush(){
|
|
ASSERT_MESSAGE( m_observers.empty(), "Brush::~Brush: observers still attached" );
|
|
}
|
|
|
|
// assignment not supported
|
|
Brush& operator=( const Brush& other ) = delete;
|
|
|
|
void setDoom3GroupOrigin( const Vector3& origin ){
|
|
//globalOutputStream() << "func_static origin before: " << m_funcStaticOrigin << " after: " << origin << "\n";
|
|
for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
( *i )->getPlane().m_funcStaticOrigin = origin;
|
|
( *i )->getPlane().updateTranslated();
|
|
( *i )->planeChanged();
|
|
}
|
|
planeChanged();
|
|
}
|
|
|
|
void attach( BrushObserver& observer ){
|
|
for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
observer.push_back( *( *i ) );
|
|
}
|
|
|
|
for ( SelectableEdges::iterator i = m_select_edges.begin(); i != m_select_edges.end(); ++i )
|
|
{
|
|
observer.edge_push_back( *i );
|
|
}
|
|
|
|
for ( SelectableVertices::iterator i = m_select_vertices.begin(); i != m_select_vertices.end(); ++i )
|
|
{
|
|
observer.vertex_push_back( *i );
|
|
}
|
|
|
|
m_observers.insert( &observer );
|
|
}
|
|
void detach( BrushObserver& observer ){
|
|
m_observers.erase( &observer );
|
|
}
|
|
|
|
void forEachFace( const BrushVisitor& visitor ) const {
|
|
for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
visitor.visit( *( *i ) );
|
|
}
|
|
}
|
|
|
|
void forEachFace_instanceAttach( MapFile* map ) const {
|
|
for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
( *i )->instanceAttach( map );
|
|
}
|
|
}
|
|
void forEachFace_instanceDetach( MapFile* map ) const {
|
|
for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
( *i )->instanceDetach( map );
|
|
}
|
|
}
|
|
|
|
InstanceCounter m_instanceCounter;
|
|
void instanceAttach( const scene::Path& path ){
|
|
if ( ++m_instanceCounter.m_count == 1 ) {
|
|
m_map = path_find_mapfile( path.begin(), path.end() );
|
|
m_undoable_observer = GlobalUndoSystem().observer( this );
|
|
GlobalFilterSystem().registerFilterable( *this );
|
|
forEachFace_instanceAttach( m_map );
|
|
}
|
|
else
|
|
{
|
|
ASSERT_MESSAGE( path_find_mapfile( path.begin(), path.end() ) == m_map, "node is instanced across more than one file" );
|
|
}
|
|
}
|
|
void instanceDetach( const scene::Path& path ){
|
|
if ( --m_instanceCounter.m_count == 0 ) {
|
|
forEachFace_instanceDetach( m_map );
|
|
GlobalFilterSystem().unregisterFilterable( *this );
|
|
m_map = 0;
|
|
m_undoable_observer = 0;
|
|
GlobalUndoSystem().release( this );
|
|
}
|
|
}
|
|
|
|
// nameable
|
|
const char* name() const {
|
|
return "brush";
|
|
}
|
|
void attach( const NameCallback& callback ){
|
|
}
|
|
void detach( const NameCallback& callback ){
|
|
}
|
|
|
|
// filterable
|
|
void updateFiltered(){
|
|
if ( m_node != 0 ) {
|
|
if ( brush_filtered( *this ) ) {
|
|
m_node->enable( scene::Node::eFiltered );
|
|
}
|
|
else
|
|
{
|
|
m_node->disable( scene::Node::eFiltered );
|
|
}
|
|
}
|
|
}
|
|
|
|
// observer
|
|
void planeChanged(){
|
|
/* m_BRep_evaluation mutex prevents cyclic dependency:
|
|
transformModifier.set ; transformChanged() ; planeChanged() ; pivotChanged() ; sceneChangeNotify() ;
|
|
sceneRender() ; localAABB ; evaluateBRep ; buildBRep() ; evaluateTransform ; !!!problem starts here!!!! planeChanged() ; pivotChanged() ; sceneChangeNotify() ;
|
|
sceneRender() ; localAABB ; evaluateBRep ; buildBRep() ; */
|
|
if( !m_BRep_evaluation ){
|
|
m_planeChanged = true;
|
|
aabbChanged();
|
|
m_lightsChanged();
|
|
}
|
|
}
|
|
void shaderChanged(){
|
|
updateFiltered();
|
|
planeChanged(); ///isn't too much for shader changed only?
|
|
}
|
|
|
|
void evaluateBRep() const {
|
|
if ( m_planeChanged ) {
|
|
m_planeChanged = false;
|
|
const_cast<Brush*>( this )->buildBRep();
|
|
}
|
|
}
|
|
|
|
void transformChanged(){
|
|
planeChanged();
|
|
m_transformChanged = true;
|
|
}
|
|
typedef MemberCaller<Brush, &Brush::transformChanged> TransformChangedCaller;
|
|
|
|
void evaluateTransform(){
|
|
if ( m_transformChanged ) {
|
|
revertTransform();
|
|
m_evaluateTransform();
|
|
m_transformChanged = false;
|
|
}
|
|
}
|
|
const Matrix4& localToParent() const {
|
|
return g_matrix4_identity;
|
|
}
|
|
void aabbChanged(){
|
|
m_boundsChanged();
|
|
}
|
|
const AABB& localAABB() const {
|
|
evaluateBRep();
|
|
return m_aabb_local;
|
|
}
|
|
|
|
VolumeIntersectionValue intersectVolume( const VolumeTest& test, const Matrix4& localToWorld ) const {
|
|
return test.TestAABB( m_aabb_local, localToWorld );
|
|
}
|
|
|
|
void renderComponents( SelectionSystem::EComponentMode mode, Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
switch ( mode )
|
|
{
|
|
case SelectionSystem::eVertex:
|
|
{
|
|
if( GlobalOpenGL().GL_1_5() ){
|
|
if( volume.fill() ){
|
|
renderer.SetState( m_state_deeppoint, Renderer::eFullMaterials );
|
|
renderer.addRenderable( m_render_deepvertices, localToWorld );
|
|
}
|
|
else{
|
|
for( auto& p : m_uniqueVertexPoints )
|
|
p.colour = colour_vertex;
|
|
renderer.addRenderable( m_render_vertices, localToWorld );
|
|
}
|
|
}
|
|
else{
|
|
renderer.addRenderable( m_render_vertices, localToWorld );
|
|
}
|
|
}
|
|
break;
|
|
case SelectionSystem::eEdge:
|
|
renderer.addRenderable( m_render_edges, localToWorld );
|
|
break;
|
|
case SelectionSystem::eFace:
|
|
renderer.addRenderable( m_render_faces, localToWorld );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void transform( const Matrix4& matrix ){
|
|
bool mirror = matrix4_handedness( matrix ) == MATRIX4_LEFTHANDED;
|
|
|
|
for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
( *i )->transform( matrix, mirror );
|
|
}
|
|
}
|
|
void snapto( float snap ){
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->vertex_snap( snap );
|
|
}
|
|
// for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
// {
|
|
// ( *i )->snapto( snap );
|
|
// }
|
|
}
|
|
void revertTransform(){
|
|
for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
( *i )->revertTransform();
|
|
}
|
|
}
|
|
void freezeTransform(){
|
|
for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
|
|
{
|
|
( *i )->freezeTransform();
|
|
}
|
|
m_transformChanged = false;
|
|
}
|
|
|
|
class VertexModeVertex
|
|
{
|
|
public:
|
|
const Vector3 m_vertex;
|
|
Vector3 m_vertexTransformed;
|
|
const bool m_selected;
|
|
std::vector<const Face*> m_faces;
|
|
VertexModeVertex( const Vector3& vertex, const bool selected ) : m_vertex( vertex ), m_vertexTransformed( vertex ), m_selected( selected ) {
|
|
}
|
|
};
|
|
typedef std::vector<VertexModeVertex> VertexModeVertices;
|
|
|
|
|
|
VertexModeVertices m_vertexModeVertices;
|
|
bool m_vertexModeOn{false};
|
|
|
|
void vertexModeInit(){
|
|
m_vertexModeOn = true;
|
|
m_vertexModeVertices.clear();
|
|
undoSave();
|
|
}
|
|
|
|
void vertexModeFree(){
|
|
m_vertexModeOn = false;
|
|
// m_vertexModeVertices.clear(); //keep, as it may be required by buildBRep() after this call
|
|
}
|
|
|
|
void vertexModeTransform( const Matrix4& matrix );
|
|
void vertexModeBuildHull( bool allTransformed = false );
|
|
void vertexModeSnap( const float snap, bool all );
|
|
|
|
/// \brief Returns the absolute index of the \p faceVertex.
|
|
std::size_t absoluteIndex( FaceVertexId faceVertex ){
|
|
std::size_t index = 0;
|
|
for ( std::size_t i = 0; i < faceVertex.getFace(); ++i )
|
|
{
|
|
index += m_faces[i]->getWinding().numpoints;
|
|
}
|
|
return index + faceVertex.getVertex();
|
|
}
|
|
|
|
void appendFaces( const Faces& other ){
|
|
clear();
|
|
for ( Faces::const_iterator i = other.begin(); i != other.end(); ++i )
|
|
{
|
|
push_back( *i );
|
|
}
|
|
}
|
|
|
|
/// \brief The undo memento for a brush stores only the list of face references - the faces are not copied.
|
|
class BrushUndoMemento : public UndoMemento
|
|
{
|
|
public:
|
|
BrushUndoMemento( const Faces& faces ) : m_faces( faces ){
|
|
}
|
|
void release(){
|
|
delete this;
|
|
}
|
|
|
|
Faces m_faces;
|
|
};
|
|
|
|
void undoSave(){
|
|
if ( m_map != 0 ) {
|
|
m_map->changed();
|
|
}
|
|
if ( m_undoable_observer != 0 ) {
|
|
m_undoable_observer->save( this );
|
|
}
|
|
}
|
|
|
|
UndoMemento* exportState() const {
|
|
return new BrushUndoMemento( m_faces );
|
|
}
|
|
|
|
void importState( const UndoMemento* state ){
|
|
undoSave();
|
|
appendFaces( static_cast<const BrushUndoMemento*>( state )->m_faces );
|
|
planeChanged();
|
|
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->DEBUG_verify();
|
|
}
|
|
}
|
|
|
|
bool isDetail(){
|
|
return !m_faces.empty() && m_faces.front()->isDetail();
|
|
}
|
|
|
|
/// \brief Appends a copy of \p face to the end of the face list.
|
|
Face* addFace( const Face& face ){
|
|
if ( m_faces.size() == c_brush_maxFaces ) {
|
|
return 0;
|
|
}
|
|
undoSave();
|
|
push_back( FaceSmartPointer( new Face( face, this ) ) );
|
|
m_faces.back()->setDetail( isDetail() );
|
|
planeChanged();
|
|
return m_faces.back();
|
|
}
|
|
|
|
/// \brief Appends a new face constructed from the parameters to the end of the face list.
|
|
Face* addPlane( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection ){
|
|
if ( m_faces.size() == c_brush_maxFaces ) {
|
|
return 0;
|
|
}
|
|
undoSave();
|
|
push_back( FaceSmartPointer( new Face( p0, p1, p2, shader, projection, this ) ) );
|
|
m_faces.back()->setDetail( isDetail() );
|
|
planeChanged();
|
|
return m_faces.back();
|
|
}
|
|
|
|
static void constructStatic( EBrushType type ){
|
|
m_type = type;
|
|
Face::m_type = type;
|
|
FacePlane::m_type = type;
|
|
|
|
g_bp_globals.m_texdefTypeId = BrushType_getTexdefType( type );
|
|
|
|
m_state_point = GlobalShaderCache().capture( "$POINT" );
|
|
m_state_deeppoint = GlobalShaderCache().capture( "$DEEPPOINT" );
|
|
}
|
|
static void destroyStatic(){
|
|
GlobalShaderCache().release( "$POINT" );
|
|
GlobalShaderCache().release( "$DEEPPOINT" );
|
|
}
|
|
|
|
std::size_t DEBUG_size(){
|
|
return m_faces.size();
|
|
}
|
|
|
|
typedef Faces::const_iterator const_iterator;
|
|
|
|
const_iterator begin() const {
|
|
return m_faces.begin();
|
|
}
|
|
const_iterator end() const {
|
|
return m_faces.end();
|
|
}
|
|
|
|
Face* back(){
|
|
return m_faces.back();
|
|
}
|
|
const Face* back() const {
|
|
return m_faces.back();
|
|
}
|
|
void reserve( std::size_t count ){
|
|
m_faces.reserve( count );
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->reserve( count );
|
|
}
|
|
}
|
|
void push_back( Faces::value_type face ){
|
|
m_faces.push_back( face );
|
|
if ( m_instanceCounter.m_count != 0 ) {
|
|
m_faces.back()->instanceAttach( m_map );
|
|
}
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->push_back( *face );
|
|
( *i )->DEBUG_verify();
|
|
}
|
|
}
|
|
void pop_back(){
|
|
if ( m_instanceCounter.m_count != 0 ) {
|
|
m_faces.back()->instanceDetach( m_map );
|
|
}
|
|
m_faces.pop_back();
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->pop_back();
|
|
( *i )->DEBUG_verify();
|
|
}
|
|
}
|
|
void erase( std::size_t index ){
|
|
if ( m_instanceCounter.m_count != 0 ) {
|
|
m_faces[index]->instanceDetach( m_map );
|
|
}
|
|
m_faces.erase( m_faces.begin() + index );
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->erase( index );
|
|
( *i )->DEBUG_verify();
|
|
}
|
|
}
|
|
void connectivityChanged(){
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->connectivityChanged();
|
|
}
|
|
}
|
|
|
|
|
|
void clear(){
|
|
undoSave();
|
|
if ( m_instanceCounter.m_count != 0 ) {
|
|
forEachFace_instanceDetach( m_map );
|
|
}
|
|
m_faces.clear();
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->clear();
|
|
( *i )->DEBUG_verify();
|
|
}
|
|
}
|
|
std::size_t size() const {
|
|
return m_faces.size();
|
|
}
|
|
bool empty() const {
|
|
return m_faces.empty();
|
|
}
|
|
|
|
/// \brief Returns true if any face of the brush contributes to the final B-Rep.
|
|
bool hasContributingFaces() const {
|
|
for ( const_iterator i = begin(); i != end(); ++i )
|
|
{
|
|
if ( ( *i )->contributes() ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// \brief Removes faces that do not contribute to the brush. This is useful for cleaning up after CSG operations on the brush.
|
|
/// Note: removal of empty faces is not performed during direct brush manipulations, because it would make a manipulation irreversible if it created an empty face.
|
|
void removeEmptyFaces(){
|
|
evaluateBRep();
|
|
|
|
{
|
|
std::size_t i = 0;
|
|
while ( i < m_faces.size() )
|
|
{
|
|
if ( !m_faces[i]->contributes() ) {
|
|
erase( i );
|
|
planeChanged();
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Constructs \p winding from the intersection of \p plane with the other planes of the brush.
|
|
void windingForClipPlane( Winding& winding, const Plane3& plane ) const {
|
|
FixedWinding buffer[2];
|
|
bool swap = false;
|
|
|
|
// get a poly that covers an effectively infinite area
|
|
Winding_createInfinite( buffer[swap], plane, m_maxWorldCoord );
|
|
|
|
// chop the poly by all of the other faces
|
|
{
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
const Face& clip = *m_faces[i];
|
|
|
|
if ( plane3_equal( clip.plane3(), plane )
|
|
|| !plane3_valid( clip.plane3() ) || !plane_unique( i )
|
|
|| plane3_opposing( plane, clip.plane3() ) ) {
|
|
continue;
|
|
}
|
|
|
|
if( buffer[swap].points.empty() ){
|
|
//globalErrorStream() << "windingForClipPlane: about to feed empty winding to Winding_Clip\n";
|
|
break;
|
|
}
|
|
|
|
buffer[!swap].clear();
|
|
|
|
#if BRUSH_CONNECTIVITY_DEBUG
|
|
globalOutputStream() << "clip vs face: " << i << "\n";
|
|
#endif
|
|
|
|
{
|
|
// flip the plane, because we want to keep the back side
|
|
Plane3 clipPlane( vector3_negated( clip.plane3().normal() ), -clip.plane3().dist() );
|
|
Winding_Clip( buffer[swap], plane, clipPlane, i, buffer[!swap] );
|
|
}
|
|
|
|
#if BRUSH_CONNECTIVITY_DEBUG
|
|
for ( FixedWinding::Points::iterator k = buffer[!swap].points.begin(), j = buffer[!swap].points.end() - 1; k != buffer[!swap].points.end(); j = k, ++k )
|
|
{
|
|
if ( vector3_length_squared( vector3_subtracted( ( *k ).vertex, ( *j ).vertex ) ) < 1 ) {
|
|
globalOutputStream() << "v: " << std::distance( buffer[!swap].points.begin(), j ) << " tiny edge adjacent to face " << ( *j ).adjacent << "\n";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//ASSERT_MESSAGE(buffer[!swap].numpoints != 1, "created single-point winding");
|
|
|
|
swap = !swap;
|
|
}
|
|
}
|
|
|
|
Winding_forFixedWinding( winding, buffer[swap] );
|
|
|
|
#if BRUSH_CONNECTIVITY_DEBUG
|
|
Winding_printConnectivity( winding );
|
|
|
|
for ( Winding::iterator i = winding.begin(), j = winding.end() - 1; i != winding.end(); j = i, ++i )
|
|
{
|
|
if ( vector3_length_squared( vector3_subtracted( ( *i ).vertex, ( *j ).vertex ) ) < 1 ) {
|
|
globalOutputStream() << "v: " << std::distance( winding.begin(), j ) << " tiny edge adjacent to face " << ( *j ).adjacent << "\n";
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void update_wireframe( RenderableWireframe& wire, const bool* faces_visible ) const {
|
|
wire.m_faceVertex.resize( m_edge_indices.size() );
|
|
wire.m_vertices = m_uniqueVertexPoints.data();
|
|
wire.m_size = 0;
|
|
for ( std::size_t i = 0; i < m_edge_faces.size(); ++i )
|
|
{
|
|
if ( faces_visible[m_edge_faces[i].first]
|
|
|| faces_visible[m_edge_faces[i].second] ) {
|
|
wire.m_faceVertex[wire.m_size++] = m_edge_indices[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void update_faces_wireframe( Array<PointVertex>& wire, const bool* faces_visible ) const {
|
|
std::size_t count = 0;
|
|
for ( std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i )
|
|
{
|
|
if ( faces_visible[i] ) {
|
|
++count;
|
|
}
|
|
}
|
|
|
|
wire.resize( count );
|
|
Array<PointVertex>::iterator p = wire.begin();
|
|
for ( std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i )
|
|
{
|
|
if ( faces_visible[i] ) {
|
|
*p++ = m_faceCentroidPoints[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Makes this brush a deep-copy of the \p other.
|
|
void copy( const Brush& other ){
|
|
for ( Faces::const_iterator i = other.m_faces.begin(); i != other.m_faces.end(); ++i )
|
|
{
|
|
addFace( *( *i ) );
|
|
}
|
|
planeChanged();
|
|
}
|
|
|
|
/// for the only use to quickly check, if about to be transformed brush makes sense
|
|
bool contributes() const {
|
|
/* plane_unique() ripoff, calling no evaluation */
|
|
auto plane_unique_ = [this]( std::size_t index ) -> bool {
|
|
// duplicate plane
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
if ( index != i && !plane3_inside( m_faces[index]->plane3_(), m_faces[i]->plane3_(), index < i ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/* windingForClipPlane() ripoff, calling no evaluation */
|
|
auto windingForClipPlane_ = [this, &plane_unique_]( const Plane3& plane ) -> bool {
|
|
FixedWinding buffer[2];
|
|
bool swap = false;
|
|
|
|
// get a poly that covers an effectively infinite area
|
|
Winding_createInfinite( buffer[swap], plane, m_maxWorldCoord );
|
|
|
|
// chop the poly by all of the other faces
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
const Face& clip = *m_faces[i];
|
|
|
|
if ( plane3_equal( clip.plane3_(), plane )
|
|
|| !plane3_valid( clip.plane3_() ) || !plane_unique_( i )
|
|
|| plane3_opposing( plane, clip.plane3_() ) ) {
|
|
continue;
|
|
}
|
|
|
|
if( buffer[swap].points.empty() ){
|
|
break;
|
|
}
|
|
|
|
buffer[!swap].clear();
|
|
|
|
{
|
|
// flip the plane, because we want to keep the back side
|
|
Plane3 clipPlane( vector3_negated( clip.plane3_().normal() ), -clip.plane3_().dist() );
|
|
Winding_Clip( buffer[swap], plane, clipPlane, i, buffer[!swap] );
|
|
}
|
|
swap = !swap;
|
|
}
|
|
|
|
return buffer[swap].size() > 2;
|
|
};
|
|
|
|
|
|
std::size_t contributing = 0;
|
|
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
Face& f = *m_faces[i];
|
|
|
|
if ( plane3_valid( f.plane3_() ) && plane_unique_( i ) ) {
|
|
if( windingForClipPlane_( f.plane3_() ) ){
|
|
++contributing;
|
|
if( contributing > 3 )
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
void edge_push_back( FaceVertexId faceVertex ){
|
|
m_select_edges.push_back( SelectableEdge( m_faces, faceVertex ) );
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->edge_push_back( m_select_edges.back() );
|
|
}
|
|
}
|
|
void edge_clear(){
|
|
m_select_edges.clear();
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->edge_clear();
|
|
}
|
|
}
|
|
void vertex_push_back( FaceVertexId faceVertex ){
|
|
m_select_vertices.push_back( SelectableVertex( m_faces, faceVertex ) );
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->vertex_push_back( m_select_vertices.back() );
|
|
}
|
|
}
|
|
void vertex_clear(){
|
|
m_select_vertices.clear();
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
{
|
|
( *i )->vertex_clear();
|
|
}
|
|
}
|
|
|
|
/// \brief Returns true if the face identified by \p index is preceded by another plane that takes priority over it.
|
|
bool plane_unique( std::size_t index ) const {
|
|
// duplicate plane
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
if ( index != i && !plane3_inside( m_faces[index]->plane3(), m_faces[i]->plane3(), index < i ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// \brief Removes edges that are smaller than the tolerance used when generating brush windings.
|
|
void removeDegenerateEdges(){
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
Winding& winding = m_faces[i]->getWinding();
|
|
for ( Winding::iterator j = winding.begin(); j != winding.end(); )
|
|
{
|
|
std::size_t index = std::distance( winding.begin(), j );
|
|
std::size_t next = Winding_next( winding, index );
|
|
if ( Edge_isDegenerate( winding[index].vertex, winding[next].vertex ) ) {
|
|
#if BRUSH_DEGENERATE_DEBUG
|
|
globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate edge adjacent to " << winding[index].adjacent << "\n";
|
|
#endif
|
|
Winding& other = m_faces[winding[index].adjacent]->getWinding();
|
|
std::size_t adjacent = Winding_FindAdjacent( other, i );
|
|
if ( adjacent != c_brush_maxFaces ) {
|
|
other.erase( other.begin() + adjacent );
|
|
}
|
|
winding.erase( j );
|
|
}
|
|
else
|
|
{
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Invalidates faces that have only two vertices in their winding, while preserving edge-connectivity information.
|
|
void removeDegenerateFaces(){
|
|
// save adjacency info for degenerate faces
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
Winding& degen = m_faces[i]->getWinding();
|
|
|
|
if ( degen.numpoints == 2 ) {
|
|
#if BRUSH_DEGENERATE_DEBUG
|
|
globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate winding adjacent to " << degen[0].adjacent << ", " << degen[1].adjacent << "\n";
|
|
#endif
|
|
// this is an "edge" face, where the plane touches the edge of the brush
|
|
{
|
|
Winding& winding = m_faces[degen[0].adjacent]->getWinding();
|
|
std::size_t index = Winding_FindAdjacent( winding, i );
|
|
if ( index != c_brush_maxFaces ) {
|
|
#if BRUSH_DEGENERATE_DEBUG
|
|
globalOutputStream() << "Brush::buildWindings: face " << degen[0].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[1].adjacent << "\n";
|
|
#endif
|
|
winding[index].adjacent = degen[1].adjacent;
|
|
}
|
|
}
|
|
|
|
{
|
|
Winding& winding = m_faces[degen[1].adjacent]->getWinding();
|
|
std::size_t index = Winding_FindAdjacent( winding, i );
|
|
if ( index != c_brush_maxFaces ) {
|
|
#if BRUSH_DEGENERATE_DEBUG
|
|
globalOutputStream() << "Brush::buildWindings: face " << degen[1].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[0].adjacent << "\n";
|
|
#endif
|
|
winding[index].adjacent = degen[0].adjacent;
|
|
}
|
|
}
|
|
|
|
degen.resize( 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Removes edges that have the same adjacent-face as their immediate neighbour.
|
|
void removeDuplicateEdges(){
|
|
// verify face connectivity graph
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
//if(m_faces[i]->contributes())
|
|
{
|
|
Winding& winding = m_faces[i]->getWinding();
|
|
for ( std::size_t j = 0; j != winding.numpoints; )
|
|
{
|
|
std::size_t next = Winding_next( winding, j );
|
|
if ( winding[j].adjacent == winding[next].adjacent ) {
|
|
#if BRUSH_DEGENERATE_DEBUG
|
|
globalOutputStream() << "Brush::buildWindings: face " << i << ": removed duplicate edge adjacent to face " << winding[j].adjacent << "\n";
|
|
#endif
|
|
winding.erase( winding.begin() + next );
|
|
}
|
|
else
|
|
{
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Removes edges that do not have a matching pair in their adjacent-face.
|
|
void verifyConnectivityGraph(){
|
|
// verify face connectivity graph
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
//if(m_faces[i]->contributes())
|
|
{
|
|
Winding& winding = m_faces[i]->getWinding();
|
|
for ( Winding::iterator j = winding.begin(); j != winding.end(); )
|
|
{
|
|
#if BRUSH_CONNECTIVITY_DEBUG
|
|
globalOutputStream() << "Brush::buildWindings: face " << i << ": adjacent to face " << ( *j ).adjacent << "\n";
|
|
#endif
|
|
// remove unidirectional graph edges
|
|
if ( ( *j ).adjacent == c_brush_maxFaces
|
|
|| Winding_FindAdjacent( m_faces[( *j ).adjacent]->getWinding(), i ) == c_brush_maxFaces ) {
|
|
#if BRUSH_CONNECTIVITY_DEBUG
|
|
globalOutputStream() << "Brush::buildWindings: face " << i << ": removing unidirectional connectivity graph edge adjacent to face " << ( *j ).adjacent << "\n";
|
|
#endif
|
|
winding.erase( j );
|
|
}
|
|
else
|
|
{
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Returns true if the brush is a finite volume. A brush without a finite volume extends past the maximum world bounds and is not valid.
|
|
bool isBounded(){
|
|
for ( const_iterator i = begin(); i != end(); ++i )
|
|
{
|
|
if ( !( *i )->is_bounded() ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// \brief Constructs the polygon windings for each face of the brush. Also updates the brush bounding-box and face texture-coordinates.
|
|
bool buildWindings(){
|
|
|
|
{
|
|
m_aabb_local = AABB();
|
|
|
|
if( m_faces.size() )
|
|
m_faces[0]->plane3(); //force evaluateTransform() first, as m_faces is changed during vertexModeTransform
|
|
|
|
for ( std::size_t i = 0; i < m_faces.size(); ++i )
|
|
{
|
|
Face& f = *m_faces[i];
|
|
|
|
if ( !plane3_valid( f.plane3() ) || !plane_unique( i ) ) {
|
|
f.getWinding().resize( 0 );
|
|
}
|
|
else
|
|
{
|
|
#if BRUSH_CONNECTIVITY_DEBUG
|
|
globalOutputStream() << "face: " << i << "\n";
|
|
#endif
|
|
windingForClipPlane( f.getWinding(), f.plane3() );
|
|
|
|
// update brush bounds
|
|
const Winding& winding = f.getWinding();
|
|
for ( Winding::const_iterator i = winding.begin(); i != winding.end(); ++i )
|
|
{
|
|
aabb_extend_by_point_safe( m_aabb_local, ( *i ).vertex );
|
|
}
|
|
|
|
// update texture coordinates
|
|
f.EmitTextureCoordinates();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool degenerate = !isBounded();
|
|
|
|
if ( !degenerate ) {
|
|
// clean up connectivity information.
|
|
// these cleanups must be applied in a specific order.
|
|
removeDegenerateEdges();
|
|
removeDegenerateFaces();
|
|
removeDuplicateEdges();
|
|
verifyConnectivityGraph();
|
|
}
|
|
|
|
return degenerate;
|
|
}
|
|
|
|
/// \brief Constructs the face windings and updates anything that depends on them.
|
|
void buildBRep();
|
|
};
|
|
|
|
|
|
|
|
class FaceInstance;
|
|
|
|
class FaceInstanceSet
|
|
{
|
|
typedef SelectionList<FaceInstance> FaceInstances;
|
|
FaceInstances m_faceInstances;
|
|
public:
|
|
void insert( FaceInstance& faceInstance ){
|
|
m_faceInstances.append( faceInstance );
|
|
}
|
|
void erase( FaceInstance& faceInstance ){
|
|
m_faceInstances.erase( faceInstance );
|
|
}
|
|
|
|
template<typename Functor>
|
|
void foreach( Functor functor ){
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
functor( *( *i ) );
|
|
}
|
|
}
|
|
|
|
bool empty() const {
|
|
return m_faceInstances.empty();
|
|
}
|
|
FaceInstance& last() const {
|
|
return m_faceInstances.back();
|
|
}
|
|
};
|
|
|
|
extern FaceInstanceSet g_SelectedFaceInstances;
|
|
|
|
typedef std::list<std::size_t> VertexSelection;
|
|
|
|
inline VertexSelection::iterator VertexSelection_find( VertexSelection& self, std::size_t value ){
|
|
return std::find( self.begin(), self.end(), value );
|
|
}
|
|
|
|
inline VertexSelection::const_iterator VertexSelection_find( const VertexSelection& self, std::size_t value ){
|
|
return std::find( self.begin(), self.end(), value );
|
|
}
|
|
|
|
inline VertexSelection::iterator VertexSelection_insert( VertexSelection& self, std::size_t value ){
|
|
VertexSelection::iterator i = VertexSelection_find( self, value );
|
|
if ( i == self.end() ) {
|
|
self.push_back( value );
|
|
return --self.end();
|
|
}
|
|
return i;
|
|
}
|
|
inline void VertexSelection_erase( VertexSelection& self, std::size_t value ){
|
|
VertexSelection::iterator i = VertexSelection_find( self, value );
|
|
if ( i != self.end() ) {
|
|
self.erase( i );
|
|
}
|
|
}
|
|
|
|
inline bool triangle_reversed( std::size_t x, std::size_t y, std::size_t z ){
|
|
return !( ( x < y && y < z ) || ( z < x && x < y ) || ( y < z && z < x ) );
|
|
}
|
|
template<typename Element>
|
|
inline Vector3 triangle_cross( const BasicVector3<Element>& x, const BasicVector3<Element> y, const BasicVector3<Element>& z ){
|
|
return vector3_cross( y - x, z - x );
|
|
}
|
|
template<typename Element>
|
|
inline bool triangles_same_winding( const BasicVector3<Element>& x1, const BasicVector3<Element> y1, const BasicVector3<Element>& z1, const BasicVector3<Element>& x2, const BasicVector3<Element> y2, const BasicVector3<Element>& z2 ){
|
|
return vector3_dot( triangle_cross( x1, y1, z1 ), triangle_cross( x2, y2, z2 ) ) > 0;
|
|
}
|
|
|
|
|
|
class VectorLightList : public LightList
|
|
{
|
|
typedef std::vector<const RendererLight*> Lights;
|
|
Lights m_lights;
|
|
public:
|
|
void addLight( const RendererLight& light ){
|
|
m_lights.push_back( &light );
|
|
}
|
|
void clear(){
|
|
m_lights.clear();
|
|
}
|
|
void evaluateLights() const {
|
|
}
|
|
void lightsChanged() const {
|
|
}
|
|
void forEachLight( const RendererLightCallback& callback ) const {
|
|
for ( Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i )
|
|
{
|
|
callback( *( *i ) );
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class FaceInstance
|
|
{
|
|
Face* m_face;
|
|
ObservedSelectable m_selectable;
|
|
ObservedSelectable m_selectableVertices;
|
|
ObservedSelectable m_selectableEdges;
|
|
SelectionChangeCallback m_selectionChanged;
|
|
|
|
VertexSelection m_vertexSelection;
|
|
VertexSelection m_edgeSelection;
|
|
|
|
public:
|
|
mutable VectorLightList m_lights;
|
|
|
|
FaceInstance( Face& face, const SelectionChangeCallback& observer ) :
|
|
m_face( &face ),
|
|
m_selectable( SelectedChangedCaller( *this ) ),
|
|
m_selectableVertices( observer ),
|
|
m_selectableEdges( observer ),
|
|
m_selectionChanged( observer ){
|
|
}
|
|
FaceInstance( const FaceInstance& other ) :
|
|
m_face( other.m_face ),
|
|
m_selectable( SelectedChangedCaller( *this ) ),
|
|
m_selectableVertices( other.m_selectableVertices ),
|
|
m_selectableEdges( other.m_selectableEdges ),
|
|
m_selectionChanged( other.m_selectionChanged ){
|
|
}
|
|
FaceInstance& operator=( const FaceInstance& other ){
|
|
m_face = other.m_face;
|
|
return *this;
|
|
}
|
|
|
|
Face& getFace(){
|
|
return *m_face;
|
|
}
|
|
const Face& getFace() const {
|
|
return *m_face;
|
|
}
|
|
|
|
void selectedChanged( const Selectable& selectable ){
|
|
if ( selectable.isSelected() ) {
|
|
g_SelectedFaceInstances.insert( *this );
|
|
}
|
|
else
|
|
{
|
|
g_SelectedFaceInstances.erase( *this );
|
|
}
|
|
m_selectionChanged( selectable );
|
|
}
|
|
typedef MemberCaller1<FaceInstance, const Selectable&, &FaceInstance::selectedChanged> SelectedChangedCaller;
|
|
|
|
bool selectedVertices() const {
|
|
return !m_vertexSelection.empty();
|
|
}
|
|
bool selectedEdges() const {
|
|
return !m_edgeSelection.empty();
|
|
}
|
|
bool isSelected() const {
|
|
return m_selectable.isSelected();
|
|
}
|
|
|
|
bool selectedComponents() const {
|
|
return selectedVertices() || selectedEdges() || isSelected();
|
|
}
|
|
bool selectedComponents( SelectionSystem::EComponentMode mode ) const {
|
|
switch ( mode )
|
|
{
|
|
case SelectionSystem::eVertex:
|
|
return selectedVertices();
|
|
case SelectionSystem::eEdge:
|
|
return selectedEdges();
|
|
case SelectionSystem::eFace:
|
|
return isSelected();
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
void setSelected( SelectionSystem::EComponentMode mode, bool select ){
|
|
switch ( mode )
|
|
{
|
|
case SelectionSystem::eFace:
|
|
m_selectable.setSelected( select );
|
|
break;
|
|
case SelectionSystem::eVertex:
|
|
ASSERT_MESSAGE( !select, "select-all not supported" );
|
|
|
|
m_vertexSelection.clear();
|
|
m_selectableVertices.setSelected( false );
|
|
break;
|
|
case SelectionSystem::eEdge:
|
|
ASSERT_MESSAGE( !select, "select-all not supported" );
|
|
|
|
m_edgeSelection.clear();
|
|
m_selectableEdges.setSelected( false );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<typename Functor>
|
|
void SelectedVertices_foreach( Functor functor ) const {
|
|
for ( VertexSelection::const_iterator i = m_vertexSelection.begin(); i != m_vertexSelection.end(); ++i )
|
|
{
|
|
std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *i );
|
|
if ( index != c_brush_maxFaces ) {
|
|
functor( getFace().getWinding()[index].vertex );
|
|
}
|
|
}
|
|
}
|
|
template<typename Functor>
|
|
void SelectedEdges_foreach( Functor functor ) const {
|
|
for ( VertexSelection::const_iterator i = m_edgeSelection.begin(); i != m_edgeSelection.end(); ++i )
|
|
{
|
|
std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *i );
|
|
if ( index != c_brush_maxFaces ) {
|
|
const Winding& winding = getFace().getWinding();
|
|
std::size_t adjacent = Winding_next( winding, index );
|
|
functor( vector3_mid( winding[index].vertex, winding[adjacent].vertex ) );
|
|
}
|
|
}
|
|
}
|
|
template<typename Functor>
|
|
void SelectedFaces_foreach( Functor functor ) const {
|
|
if ( isSelected() ) {
|
|
functor( centroid() );
|
|
}
|
|
}
|
|
|
|
template<typename Functor>
|
|
void SelectedComponents_foreach( Functor functor ) const {
|
|
SelectedVertices_foreach( functor );
|
|
SelectedEdges_foreach( functor );
|
|
SelectedFaces_foreach( functor );
|
|
}
|
|
|
|
void iterate_selected( AABB& aabb ) const {
|
|
SelectedComponents_foreach( AABBExtendByPoint( aabb ) );
|
|
}
|
|
|
|
void gatherSelectedComponents( const Vector3Callback& callback ) const {
|
|
const Winding& winding = getFace().getWinding();
|
|
if( isSelected() )
|
|
for ( std::size_t i = 0; i != winding.numpoints; ++i )
|
|
callback( winding[i].vertex );
|
|
for ( VertexSelection::const_iterator i = m_vertexSelection.begin(); i != m_vertexSelection.end(); ++i ){
|
|
std::size_t index = Winding_FindAdjacent( winding, *i );
|
|
if ( index != c_brush_maxFaces ) {
|
|
callback( winding[index].vertex );
|
|
}
|
|
}
|
|
for ( VertexSelection::const_iterator i = m_edgeSelection.begin(); i != m_edgeSelection.end(); ++i ){
|
|
std::size_t index = Winding_FindAdjacent( winding, *i );
|
|
if ( index != c_brush_maxFaces ) {
|
|
std::size_t adjacent = Winding_next( winding, index );
|
|
callback( winding[index].vertex );
|
|
callback( winding[adjacent].vertex );
|
|
}
|
|
}
|
|
}
|
|
|
|
class RenderablePointVectorPushBack
|
|
{
|
|
RenderablePointVector& m_points;
|
|
public:
|
|
RenderablePointVectorPushBack( RenderablePointVector& points ) : m_points( points ){
|
|
}
|
|
void operator()( const Vector3& point ) const {
|
|
m_points.push_back( pointvertex_for_windingpoint( point, colour_selected ) );
|
|
}
|
|
};
|
|
|
|
void iterate_selected( RenderablePointVector& points ) const {
|
|
SelectedComponents_foreach( RenderablePointVectorPushBack( points ) );
|
|
}
|
|
|
|
bool intersectVolume( const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
return m_face->intersectVolume( volume, localToWorld );
|
|
}
|
|
|
|
void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
if ( !m_face->isFiltered() && m_face->contributes() && intersectVolume( volume, localToWorld ) ) {
|
|
renderer.PushState();
|
|
renderer.Highlight( Renderer::ePrimitiveWire );
|
|
if ( selectedComponents() ) {
|
|
renderer.Highlight( Renderer::EHighlightMode( Renderer::eFace | Renderer::eFaceWire ) );
|
|
}
|
|
m_face->render( renderer, localToWorld );
|
|
renderer.PopState();
|
|
}
|
|
}
|
|
|
|
void testSelect( SelectionTest& test, SelectionIntersection& best ) const {
|
|
if ( !m_face->isFiltered() ) {
|
|
m_face->testSelect( test, best );
|
|
}
|
|
}
|
|
void testSelect( Selector& selector, SelectionTest& test ){
|
|
SelectionIntersection best;
|
|
testSelect( test, best );
|
|
if ( best.valid() ) {
|
|
Selector_add( selector, m_selectable, best );
|
|
}
|
|
}
|
|
void testSelect_centroid( Selector& selector, SelectionTest& test ){
|
|
if ( m_face->contributes() && !m_face->isFiltered() ) {
|
|
SelectionIntersection best;
|
|
m_face->testSelect_centroid( test, best );
|
|
if ( best.valid() ) {
|
|
Selector_add( selector, m_selectable, best );
|
|
}
|
|
}
|
|
}
|
|
|
|
void addSelectable( Selector& selector ){
|
|
Selector_add( selector, m_selectable );
|
|
}
|
|
|
|
void selectReversedPlane( Selector& selector, const SelectedPlanes& selectedPlanes ){
|
|
if ( selectedPlanes.contains( plane3_flipped( getFace().plane3() ) ) ) {
|
|
Selector_add( selector, m_selectable );
|
|
}
|
|
}
|
|
|
|
bool trySelectPlane( const SelectionTest& test ){
|
|
const Vector3 projected = vector4_projected( matrix4_transformed_vector4( test.getVolume().GetViewMatrix(), Vector4( getFace().centroid(), 1 ) ) );
|
|
const Vector3 closest_point = vector4_projected( matrix4_transformed_vector4( test.getScreen2world(), Vector4( 0, 0, projected[2], 1 ) ) );
|
|
for ( Winding::const_iterator i = getFace().getWinding().begin(); i != getFace().getWinding().end(); ++i ){
|
|
if ( vector3_dot( getFace().plane3().normal(), closest_point - ( *i ).vertex ) < 0.005 ) /* epsilon to prevent almost perpendicular faces pickup */
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void transformComponents( const Matrix4& matrix ){
|
|
if ( isSelected() ) {
|
|
m_face->transform( matrix, false );
|
|
}
|
|
if ( selectedVertices() ) {
|
|
if ( m_vertexSelection.size() == 1 ) {
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
|
|
m_face->assign_planepts( m_face->m_move_planeptsTransformed );
|
|
}
|
|
else if ( m_vertexSelection.size() == 2 ) {
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
|
|
m_face->assign_planepts( m_face->m_move_planeptsTransformed );
|
|
}
|
|
else if ( m_vertexSelection.size() >= 3 ) {
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
|
|
m_face->assign_planepts( m_face->m_move_planeptsTransformed );
|
|
}
|
|
}
|
|
if ( selectedEdges() ) {
|
|
if ( m_edgeSelection.size() == 1 ) {
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
|
|
m_face->assign_planepts( m_face->m_move_planeptsTransformed );
|
|
}
|
|
else if ( m_edgeSelection.size() >= 2 ) {
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
|
|
matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
|
|
m_face->assign_planepts( m_face->m_move_planeptsTransformed );
|
|
}
|
|
}
|
|
if ( g_brush_textureVertexlock_enabled && ( selectedVertices() || selectedEdges() ) ) {
|
|
m_face->texdef_from_points();
|
|
}
|
|
}
|
|
|
|
void snapto( float snap ){
|
|
m_face->snapto( snap );
|
|
}
|
|
|
|
void snapComponents( float snap ){
|
|
if ( isSelected() ) {
|
|
snapto( snap );
|
|
}
|
|
if ( selectedVertices() ) {
|
|
vector3_snap( m_face->m_move_planepts[0], snap );
|
|
vector3_snap( m_face->m_move_planepts[1], snap );
|
|
vector3_snap( m_face->m_move_planepts[2], snap );
|
|
m_face->assign_planepts( m_face->m_move_planepts );
|
|
planepts_assign( m_face->m_move_planeptsTransformed, m_face->m_move_planepts );
|
|
m_face->freezeTransform();
|
|
}
|
|
if ( selectedEdges() ) {
|
|
vector3_snap( m_face->m_move_planepts[0], snap );
|
|
vector3_snap( m_face->m_move_planepts[1], snap );
|
|
vector3_snap( m_face->m_move_planepts[2], snap );
|
|
m_face->assign_planepts( m_face->m_move_planepts );
|
|
planepts_assign( m_face->m_move_planeptsTransformed, m_face->m_move_planepts );
|
|
m_face->freezeTransform();
|
|
}
|
|
}
|
|
#if Update_move_planepts_vertex
|
|
void update_move_planepts_vertex( std::size_t index ){
|
|
m_face->update_move_planepts_vertex( index, m_face->m_move_planepts );
|
|
}
|
|
void update_move_planepts_vertex2( std::size_t index, std::size_t other ){
|
|
if( m_face->contributes() ){
|
|
const std::size_t numpoints = m_face->getWinding().numpoints;
|
|
ASSERT_MESSAGE( index < numpoints, "select_vertex: invalid index" );
|
|
|
|
const std::size_t opposite = Winding_Opposite( m_face->getWinding(), index, other );
|
|
|
|
if ( triangle_reversed( index, other, opposite ) ) {
|
|
std::swap( index, other );
|
|
}
|
|
|
|
///! this actually happens with ON_EPSILON 1.0 / ( 1 << 8 )
|
|
ASSERT_MESSAGE(
|
|
triangles_same_winding(
|
|
m_face->getWinding()[opposite].vertex,
|
|
m_face->getWinding()[index].vertex,
|
|
m_face->getWinding()[other].vertex,
|
|
m_face->getWinding()[0].vertex,
|
|
m_face->getWinding()[1].vertex,
|
|
m_face->getWinding()[2].vertex
|
|
),
|
|
"update_move_planepts_vertex2: error"
|
|
);
|
|
|
|
m_face->m_move_planepts[0] = m_face->getWinding()[opposite].vertex;
|
|
m_face->m_move_planepts[1] = m_face->getWinding()[index].vertex;
|
|
m_face->m_move_planepts[2] = m_face->getWinding()[other].vertex;
|
|
planepts_quantise( m_face->m_move_planepts, GRID_MIN ); // winding points are very inaccurate
|
|
}
|
|
}
|
|
#endif
|
|
void update_selection_vertex(){
|
|
if ( m_vertexSelection.size() == 0 ) {
|
|
m_selectableVertices.setSelected( false );
|
|
}
|
|
else
|
|
{
|
|
m_selectableVertices.setSelected( true );
|
|
#if Update_move_planepts_vertex
|
|
if ( m_vertexSelection.size() == 1 ) {
|
|
std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_vertexSelection.begin() );
|
|
|
|
if ( index != c_brush_maxFaces ) {
|
|
update_move_planepts_vertex( index );
|
|
}
|
|
}
|
|
else if ( m_vertexSelection.size() == 2 ) {
|
|
std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_vertexSelection.begin() );
|
|
std::size_t other = Winding_FindAdjacent( getFace().getWinding(), *( ++m_vertexSelection.begin() ) );
|
|
|
|
if ( index != c_brush_maxFaces
|
|
&& other != c_brush_maxFaces ) {
|
|
update_move_planepts_vertex2( index, other );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
void select_vertex( std::size_t index, bool select ){
|
|
if ( select ) {
|
|
VertexSelection_insert( m_vertexSelection, getFace().getWinding()[index].adjacent );
|
|
}
|
|
else
|
|
{
|
|
VertexSelection_erase( m_vertexSelection, getFace().getWinding()[index].adjacent );
|
|
}
|
|
|
|
SceneChangeNotify();
|
|
update_selection_vertex();
|
|
}
|
|
|
|
bool selected_vertex( std::size_t index ) const {
|
|
return VertexSelection_find( m_vertexSelection, getFace().getWinding()[index].adjacent ) != m_vertexSelection.end();
|
|
}
|
|
|
|
void update_move_planepts_edge( std::size_t index ){
|
|
if( m_face->contributes() ){
|
|
std::size_t numpoints = m_face->getWinding().numpoints;
|
|
ASSERT_MESSAGE( index < numpoints, "select_edge: invalid index" );
|
|
|
|
std::size_t adjacent = Winding_next( m_face->getWinding(), index );
|
|
std::size_t opposite = Winding_Opposite( m_face->getWinding(), index );
|
|
m_face->m_move_planepts[0] = m_face->getWinding()[index].vertex;
|
|
m_face->m_move_planepts[1] = m_face->getWinding()[adjacent].vertex;
|
|
m_face->m_move_planepts[2] = m_face->getWinding()[opposite].vertex;
|
|
// planepts_quantise( m_face->m_move_planepts, GRID_MIN ); // winding points are very inaccurate
|
|
}
|
|
}
|
|
void update_selection_edge(){
|
|
if ( m_edgeSelection.size() == 0 ) {
|
|
m_selectableEdges.setSelected( false );
|
|
}
|
|
else
|
|
{
|
|
m_selectableEdges.setSelected( true );
|
|
|
|
if ( m_edgeSelection.size() == 1 ) {
|
|
std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_edgeSelection.begin() );
|
|
|
|
if ( index != c_brush_maxFaces ) {
|
|
update_move_planepts_edge( index );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void select_edge( std::size_t index, bool select ){
|
|
if ( select ) {
|
|
VertexSelection_insert( m_edgeSelection, getFace().getWinding()[index].adjacent );
|
|
}
|
|
else
|
|
{
|
|
VertexSelection_erase( m_edgeSelection, getFace().getWinding()[index].adjacent );
|
|
}
|
|
|
|
SceneChangeNotify();
|
|
update_selection_edge();
|
|
}
|
|
|
|
bool selected_edge( std::size_t index ) const {
|
|
return VertexSelection_find( m_edgeSelection, getFace().getWinding()[index].adjacent ) != m_edgeSelection.end();
|
|
}
|
|
|
|
const Vector3& centroid() const {
|
|
return m_face->centroid();
|
|
}
|
|
|
|
void connectivityChanged(){
|
|
// This occurs when a face is added or removed.
|
|
// The current vertex and edge selections no longer valid and must be cleared.
|
|
m_vertexSelection.clear();
|
|
m_selectableVertices.setSelected( false );
|
|
m_edgeSelection.clear();
|
|
m_selectableEdges.setSelected( false );
|
|
}
|
|
};
|
|
|
|
class BrushClipPlane : public OpenGLRenderable
|
|
{
|
|
Plane3 m_plane;
|
|
Winding m_winding;
|
|
static Shader* m_state;
|
|
public:
|
|
static void constructStatic(){
|
|
m_state = GlobalShaderCache().capture( "$CLIPPER_OVERLAY" );
|
|
}
|
|
static void destroyStatic(){
|
|
GlobalShaderCache().release( "$CLIPPER_OVERLAY" );
|
|
}
|
|
|
|
void setPlane( const Brush& brush, const Plane3& plane ){
|
|
m_plane = plane;
|
|
if ( plane3_valid( m_plane ) ) {
|
|
brush.windingForClipPlane( m_winding, m_plane );
|
|
}
|
|
else
|
|
{
|
|
m_winding.resize( 0 );
|
|
}
|
|
}
|
|
|
|
void render( RenderStateFlags state ) const {
|
|
if ( ( state & RENDER_FILL ) != 0 ) {
|
|
Winding_Draw( m_winding, m_plane.normal(), state );
|
|
}
|
|
else
|
|
{
|
|
Winding_DrawWireframe( m_winding );
|
|
|
|
// also draw a line indicating the direction of the cut
|
|
Vector3 lineverts[2];
|
|
Winding_Centroid( m_winding, m_plane, lineverts[0] );
|
|
lineverts[1] = vector3_added( lineverts[0], vector3_scaled( m_plane.normal(), Brush::m_maxWorldCoord * 4 ) );
|
|
|
|
glVertexPointer( 3, GL_FLOAT, sizeof( Vector3 ), &lineverts[0] );
|
|
glDrawArrays( GL_LINES, 0, GLsizei( 2 ) );
|
|
}
|
|
}
|
|
|
|
void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
renderer.SetState( m_state, Renderer::eWireframeOnly );
|
|
renderer.SetState( m_state, Renderer::eFullMaterials );
|
|
renderer.addRenderable( *this, localToWorld );
|
|
}
|
|
};
|
|
|
|
inline void Face_addLight( const FaceInstance& face, const Matrix4& localToWorld, const RendererLight& light ){
|
|
const Plane3& facePlane = face.getFace().plane3();
|
|
const Vector3& origin = light.aabb().origin;
|
|
Plane3 tmp( plane3_transformed( Plane3( facePlane.normal(), -facePlane.dist() ), localToWorld ) );
|
|
if ( !plane3_test_point( tmp, origin )
|
|
|| !plane3_test_point( tmp, vector3_added( origin, light.offset() ) ) ) {
|
|
face.m_lights.addLight( light );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
typedef std::vector<FaceInstance> FaceInstances;
|
|
typedef std::vector<FaceInstance*> FaceInstances_ptrs;
|
|
|
|
class EdgeInstance : public Selectable
|
|
{
|
|
FaceInstances& m_faceInstances;
|
|
SelectableEdge* m_edge;
|
|
|
|
void select_edge( bool select ){
|
|
FaceVertexId faceVertex = m_edge->m_faceVertex;
|
|
m_faceInstances[faceVertex.getFace()].select_edge( faceVertex.getVertex(), select );
|
|
faceVertex = next_edge( m_edge->m_faces, faceVertex );
|
|
m_faceInstances[faceVertex.getFace()].select_edge( faceVertex.getVertex(), select );
|
|
}
|
|
bool selected_edge() const {
|
|
FaceVertexId faceVertex = m_edge->m_faceVertex;
|
|
if ( !m_faceInstances[faceVertex.getFace()].selected_edge( faceVertex.getVertex() ) ) {
|
|
return false;
|
|
}
|
|
faceVertex = next_edge( m_edge->m_faces, faceVertex );
|
|
if ( !m_faceInstances[faceVertex.getFace()].selected_edge( faceVertex.getVertex() ) ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public:
|
|
EdgeInstance( FaceInstances& faceInstances, SelectableEdge& edge )
|
|
: m_faceInstances( faceInstances ), m_edge( &edge ){
|
|
}
|
|
EdgeInstance& operator=( const EdgeInstance& other ){
|
|
m_edge = other.m_edge;
|
|
return *this;
|
|
}
|
|
|
|
void setSelected( bool select ){
|
|
select_edge( select );
|
|
}
|
|
bool isSelected() const {
|
|
return selected_edge();
|
|
}
|
|
|
|
|
|
void testSelect( Selector& selector, SelectionTest& test ){
|
|
SelectionIntersection best;
|
|
m_edge->testSelect( test, best );
|
|
if ( best.valid() ) {
|
|
Selector_add( selector, *this, best );
|
|
}
|
|
}
|
|
void gatherComponentsHighlight( std::vector<std::vector<Vector3>>& polygons, SelectionIntersection& intersection, SelectionTest& test ) const {
|
|
SelectionIntersection best;
|
|
m_edge->testSelect( test, best );
|
|
if ( SelectionIntersection_closer( best, intersection ) ) {
|
|
intersection = best;
|
|
polygons.clear();
|
|
polygons.emplace_back( std::initializer_list<Vector3>( { m_edge->getEdge() } ) );
|
|
}
|
|
}
|
|
|
|
void bestPlaneIndirect( const SelectionTest& test, Plane3& plane, Vector3& intersection, float& dist, float& dot ) const {
|
|
const Winding& winding = m_edge->getFace().getWinding();
|
|
FaceVertexId faceVertex = m_edge->m_faceVertex;
|
|
Line line( winding[faceVertex.getVertex()].vertex, winding[Winding_next( winding, faceVertex.getVertex() )].vertex );
|
|
if( matrix4_clip_line_by_nearplane( test.getVolume().GetViewMatrix(), line ) == 2 ){
|
|
const Vector3 intersection_new = line_closest_point( line, g_vector3_identity );
|
|
const float dist_new = vector3_length_squared( intersection_new );
|
|
const float dot_new = fabs( vector3_dot( vector3_normalised( intersection_new ), vector3_normalised( line.end - line.start ) ) );
|
|
if( dist - dist_new > 1e-6f // new dist noticeably smaller
|
|
|| ( float_equal_epsilon( dist_new, dist, 1e-6f ) && dot_new < dot ) ){ // or ambiguous case. Resolve it by dot comparison
|
|
const Plane3& plane1 = m_faceInstances[faceVertex.getFace()].getFace().plane3();
|
|
faceVertex = next_edge( m_edge->m_faces, faceVertex );
|
|
const Plane3& plane2 = m_faceInstances[faceVertex.getFace()].getFace().plane3();
|
|
|
|
auto assign_plane = [&plane, &intersection, intersection_new, &dist, dist_new, &dot, dot_new]( const Plane3& plane_new ){
|
|
plane = plane_new;
|
|
intersection = intersection_new;
|
|
dist = dist_new;
|
|
dot = dot_new;
|
|
};
|
|
|
|
if( test.getVolume().fill() ){
|
|
if( plane3_distance_to_point( plane1, test.getVolume().getViewer() ) <= 0 )
|
|
assign_plane( plane1 );
|
|
else if( plane3_distance_to_point( plane2, test.getVolume().getViewer() ) <= 0 )
|
|
assign_plane( plane2 );
|
|
}
|
|
else if( fabs( vector3_length_squared( line.end - line.start ) ) > 1e-3 ){
|
|
if( fabs( vector3_dot( plane1.normal(), test.getVolume().getViewDir() ) ) < fabs( vector3_dot( plane2.normal(), test.getVolume().getViewDir() ) ) )
|
|
assign_plane( plane1 );
|
|
else
|
|
assign_plane( plane2 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class VertexInstance : public Selectable
|
|
{
|
|
FaceInstances& m_faceInstances;
|
|
SelectableVertex* m_vertex;
|
|
|
|
void select_vertex( bool select ){
|
|
FaceVertexId faceVertex = m_vertex->m_faceVertex;
|
|
do
|
|
{
|
|
m_faceInstances[faceVertex.getFace()].select_vertex( faceVertex.getVertex(), select );
|
|
faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
|
|
}
|
|
while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
|
|
}
|
|
bool selected_vertex() const {
|
|
FaceVertexId faceVertex = m_vertex->m_faceVertex;
|
|
do
|
|
{
|
|
if ( !m_faceInstances[faceVertex.getFace()].selected_vertex( faceVertex.getVertex() ) ) {
|
|
return false;
|
|
}
|
|
faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
|
|
}
|
|
while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
|
|
return true;
|
|
}
|
|
|
|
public:
|
|
VertexInstance( FaceInstances& faceInstances, SelectableVertex& vertex )
|
|
: m_faceInstances( faceInstances ), m_vertex( &vertex ){
|
|
}
|
|
VertexInstance& operator=( const VertexInstance& other ){
|
|
m_vertex = other.m_vertex;
|
|
return *this;
|
|
}
|
|
|
|
void setSelected( bool select ){
|
|
select_vertex( select );
|
|
}
|
|
bool isSelected() const {
|
|
return selected_vertex();
|
|
}
|
|
|
|
void testSelect( Selector& selector, SelectionTest& test ){
|
|
SelectionIntersection best;
|
|
m_vertex->testSelect( test, best );
|
|
if ( best.valid() ) {
|
|
Selector_add( selector, *this, best );
|
|
}
|
|
}
|
|
void gatherComponentsHighlight( std::vector<std::vector<Vector3>>& polygons, SelectionIntersection& intersection, SelectionTest& test ) const {
|
|
SelectionIntersection best;
|
|
m_vertex->testSelect( test, best );
|
|
if ( SelectionIntersection_closer( best, intersection ) ) {
|
|
intersection = best;
|
|
polygons.clear();
|
|
polygons.emplace_back( std::initializer_list<Vector3>( { m_vertex->getVertex() } ) );
|
|
}
|
|
}
|
|
|
|
void selectVerticesOfFace( const FaceInstance& faceinstance ){
|
|
FaceVertexId faceVertex = m_vertex->m_faceVertex;
|
|
do
|
|
{
|
|
if( &faceinstance == &m_faceInstances[faceVertex.getFace()] ){
|
|
setSelected( true );
|
|
return;
|
|
}
|
|
faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
|
|
}
|
|
while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
|
|
}
|
|
void gather( Brush::VertexModeVertices& vertexModeVertices ) const {
|
|
vertexModeVertices.emplace_back( m_vertex->getVertex(), isSelected() );
|
|
FaceVertexId faceVertex = m_vertex->m_faceVertex;
|
|
do
|
|
{
|
|
vertexModeVertices.back().m_faces.push_back( &m_faceInstances[faceVertex.getFace()].getFace() );
|
|
faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
|
|
}
|
|
while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
|
|
}
|
|
bool vertex_select( const Vector3& vertex ){
|
|
if( vector3_length_squared( vertex - m_vertex->getVertex() ) < ( 0.1 * 0.1 ) ){
|
|
setSelected( true );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class BrushInstanceVisitor
|
|
{
|
|
public:
|
|
virtual void visit( FaceInstance& face ) const = 0;
|
|
};
|
|
|
|
class BrushInstance :
|
|
public BrushObserver,
|
|
public scene::Instance,
|
|
public Selectable,
|
|
public Renderable,
|
|
public SelectionTestable,
|
|
public ComponentSelectionTestable,
|
|
public ComponentEditable,
|
|
public ComponentSnappable,
|
|
public PlaneSelectable,
|
|
public LightCullable
|
|
{
|
|
class TypeCasts
|
|
{
|
|
InstanceTypeCastTable m_casts;
|
|
public:
|
|
TypeCasts(){
|
|
InstanceStaticCast<BrushInstance, Selectable>::install( m_casts );
|
|
InstanceContainedCast<BrushInstance, Bounded>::install( m_casts );
|
|
InstanceContainedCast<BrushInstance, Cullable>::install( m_casts );
|
|
InstanceStaticCast<BrushInstance, Renderable>::install( m_casts );
|
|
InstanceStaticCast<BrushInstance, SelectionTestable>::install( m_casts );
|
|
InstanceStaticCast<BrushInstance, ComponentSelectionTestable>::install( m_casts );
|
|
InstanceStaticCast<BrushInstance, ComponentEditable>::install( m_casts );
|
|
InstanceStaticCast<BrushInstance, ComponentSnappable>::install( m_casts );
|
|
InstanceStaticCast<BrushInstance, PlaneSelectable>::install( m_casts );
|
|
InstanceIdentityCast<BrushInstance>::install( m_casts );
|
|
InstanceContainedCast<BrushInstance, Transformable>::install( m_casts );
|
|
}
|
|
InstanceTypeCastTable& get(){
|
|
return m_casts;
|
|
}
|
|
};
|
|
|
|
|
|
Brush& m_brush;
|
|
|
|
FaceInstances m_faceInstances;
|
|
|
|
typedef std::vector<EdgeInstance> EdgeInstances;
|
|
EdgeInstances m_edgeInstances;
|
|
typedef std::vector<VertexInstance> VertexInstances;
|
|
VertexInstances m_vertexInstances;
|
|
|
|
ObservedSelectable m_selectable;
|
|
|
|
mutable RenderableWireframe m_render_wireframe;
|
|
mutable RenderablePointVector m_render_selected;
|
|
mutable AABB m_aabb_component;
|
|
mutable Array<PointVertex> m_faceCentroidPointsCulled;
|
|
RenderablePointArray<PointVertex> m_render_faces_wireframe;
|
|
mutable bool m_viewChanged; // requires re-evaluation of view-dependent cached data
|
|
|
|
BrushClipPlane m_clipPlane;
|
|
|
|
static Shader* m_state_selpoint;
|
|
|
|
const LightList* m_lightList;
|
|
|
|
BrushTransformModifier m_transform;
|
|
public:
|
|
static Counter* m_counter;
|
|
|
|
typedef LazyStatic<TypeCasts> StaticTypeCasts;
|
|
|
|
void lightsChanged(){
|
|
m_lightList->lightsChanged();
|
|
}
|
|
typedef MemberCaller<BrushInstance, &BrushInstance::lightsChanged> LightsChangedCaller;
|
|
|
|
STRING_CONSTANT( Name, "BrushInstance" );
|
|
|
|
BrushInstance( const BrushInstance& other ) = delete; // NOT COPYABLE
|
|
BrushInstance& operator=( const BrushInstance& other ) = delete; // NOT ASSIGNABLE
|
|
|
|
BrushInstance( const scene::Path& path, scene::Instance* parent, Brush& brush ) :
|
|
Instance( path, parent, this, StaticTypeCasts::instance().get() ),
|
|
m_brush( brush ),
|
|
m_selectable( SelectedChangedCaller( *this ) ),
|
|
m_render_selected( GL_POINTS ),
|
|
m_render_faces_wireframe( m_faceCentroidPointsCulled, GL_POINTS ),
|
|
m_viewChanged( false ),
|
|
m_transform( Brush::TransformChangedCaller( m_brush ), ApplyTransformCaller( *this ) ){
|
|
m_brush.instanceAttach( Instance::path() );
|
|
m_brush.attach( *this );
|
|
m_counter->increment();
|
|
|
|
m_lightList = &GlobalShaderCache().attach( *this );
|
|
m_brush.m_lightsChanged = LightsChangedCaller( *this ); ///\todo Make this work with instancing.
|
|
|
|
Instance::setTransformChangedCallback( LightsChangedCaller( *this ) );
|
|
}
|
|
~BrushInstance(){
|
|
Instance::setTransformChangedCallback( Callback() );
|
|
|
|
m_brush.m_lightsChanged = Callback();
|
|
GlobalShaderCache().detach( *this );
|
|
|
|
m_counter->decrement();
|
|
m_brush.detach( *this );
|
|
m_brush.instanceDetach( Instance::path() );
|
|
}
|
|
|
|
Brush& getBrush(){
|
|
return m_brush;
|
|
}
|
|
const Brush& getBrush() const {
|
|
return m_brush;
|
|
}
|
|
|
|
Bounded& get( NullType<Bounded>){
|
|
return m_brush;
|
|
}
|
|
Cullable& get( NullType<Cullable>){
|
|
return m_brush;
|
|
}
|
|
Transformable& get( NullType<Transformable>){
|
|
return m_transform;
|
|
}
|
|
|
|
void selectedChanged( const Selectable& selectable ){
|
|
GlobalSelectionSystem().getObserver ( SelectionSystem::ePrimitive )( selectable );
|
|
GlobalSelectionSystem().onSelectedChanged( *this, selectable );
|
|
|
|
Instance::selectedChanged();
|
|
}
|
|
typedef MemberCaller1<BrushInstance, const Selectable&, &BrushInstance::selectedChanged> SelectedChangedCaller;
|
|
|
|
void selectedChangedComponent( const Selectable& selectable ){
|
|
GlobalSelectionSystem().getObserver ( SelectionSystem::eComponent )( selectable );
|
|
GlobalSelectionSystem().onComponentSelection( *this, selectable );
|
|
}
|
|
typedef MemberCaller1<BrushInstance, const Selectable&, &BrushInstance::selectedChangedComponent> SelectedChangedComponentCaller;
|
|
|
|
const BrushInstanceVisitor& forEachFaceInstance( const BrushInstanceVisitor& visitor ){
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
visitor.visit( *i );
|
|
}
|
|
return visitor;
|
|
}
|
|
|
|
static void constructStatic(){
|
|
m_state_selpoint = GlobalShaderCache().capture( "$SELPOINT" );
|
|
}
|
|
static void destroyStatic(){
|
|
GlobalShaderCache().release( "$SELPOINT" );
|
|
}
|
|
|
|
void clear(){
|
|
m_faceInstances.clear();
|
|
}
|
|
void reserve( std::size_t size ){
|
|
m_faceInstances.reserve( size );
|
|
}
|
|
|
|
void push_back( Face& face ){
|
|
m_faceInstances.push_back( FaceInstance( face, SelectedChangedComponentCaller( *this ) ) );
|
|
}
|
|
void pop_back(){
|
|
ASSERT_MESSAGE( !m_faceInstances.empty(), "erasing invalid element" );
|
|
m_faceInstances.pop_back();
|
|
}
|
|
void erase( std::size_t index ){
|
|
ASSERT_MESSAGE( index < m_faceInstances.size(), "erasing invalid element" );
|
|
m_faceInstances.erase( m_faceInstances.begin() + index );
|
|
}
|
|
void connectivityChanged(){
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).connectivityChanged();
|
|
}
|
|
}
|
|
|
|
void edge_clear(){
|
|
m_edgeInstances.clear();
|
|
}
|
|
void edge_push_back( SelectableEdge& edge ){
|
|
m_edgeInstances.push_back( EdgeInstance( m_faceInstances, edge ) );
|
|
}
|
|
|
|
void vertex_clear(){
|
|
m_vertexInstances.clear();
|
|
}
|
|
void vertex_push_back( SelectableVertex& vertex ){
|
|
m_vertexInstances.push_back( VertexInstance( m_faceInstances, vertex ) );
|
|
}
|
|
|
|
void vertex_select(){
|
|
bool src_selected = false;
|
|
bool dst_selected = false;
|
|
for( const auto& v : m_brush.m_vertexModeVertices )
|
|
if( v.m_selected ){
|
|
src_selected = true;
|
|
for( auto& i : m_vertexInstances )
|
|
dst_selected |= i.vertex_select( v.m_vertexTransformed );
|
|
}
|
|
if( src_selected && !dst_selected && !m_vertexInstances.empty() )
|
|
m_vertexInstances[0].setSelected( true ); //select at least something to prevent transform interruption after removing all selected vertices during vertexModeTransform
|
|
}
|
|
|
|
void vertex_snap( const float snap, bool all ){
|
|
m_brush.vertexModeInit();
|
|
m_brush.m_vertexModeVertices.reserve( m_vertexInstances.size() );
|
|
for ( const auto& i : m_vertexInstances ){
|
|
i.gather( m_brush.m_vertexModeVertices );
|
|
}
|
|
m_brush.vertexModeSnap( snap, all );
|
|
m_brush.evaluateBRep();
|
|
m_brush.vertexModeFree();
|
|
m_brush.freezeTransform();
|
|
}
|
|
|
|
void vertex_snap( const float snap ){
|
|
vertex_snap( snap, true );
|
|
}
|
|
|
|
void DEBUG_verify() const {
|
|
ASSERT_MESSAGE( m_faceInstances.size() == m_brush.DEBUG_size(), "FATAL: mismatch" );
|
|
}
|
|
|
|
bool isSelected() const {
|
|
return m_selectable.isSelected();
|
|
}
|
|
void setSelected( bool select ){
|
|
m_selectable.setSelected( select );
|
|
if ( !select && parent() ){
|
|
Selectable* sel_parent = Instance_getSelectable( *parent() );
|
|
if ( sel_parent && sel_parent->isSelected() )
|
|
sel_parent->setSelected( false );
|
|
}
|
|
}
|
|
|
|
void update_selected() const {
|
|
m_render_selected.clear();
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
if ( ( *i ).getFace().contributes() ) {
|
|
( *i ).iterate_selected( m_render_selected );
|
|
}
|
|
}
|
|
}
|
|
|
|
void evaluateViewDependent( const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
if ( m_viewChanged ) {
|
|
m_viewChanged = false;
|
|
|
|
bool faces_visible[c_brush_maxFaces];
|
|
{
|
|
bool* j = faces_visible;
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i, ++j )
|
|
{
|
|
*j = ( *i ).intersectVolume( volume, localToWorld );
|
|
}
|
|
}
|
|
|
|
m_brush.update_wireframe( m_render_wireframe, faces_visible );
|
|
m_brush.update_faces_wireframe( m_faceCentroidPointsCulled, faces_visible );
|
|
}
|
|
}
|
|
|
|
void renderComponentsSelected( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
m_brush.evaluateBRep();
|
|
|
|
update_selected();
|
|
if ( !m_render_selected.empty() ) {
|
|
renderer.Highlight( Renderer::ePrimitive, false );
|
|
renderer.SetState( m_state_selpoint, Renderer::eWireframeOnly );
|
|
renderer.SetState( m_state_selpoint, Renderer::eFullMaterials );
|
|
renderer.addRenderable( m_render_selected, localToWorld );
|
|
}
|
|
}
|
|
|
|
void renderComponents( Renderer& renderer, const VolumeTest& volume ) const {
|
|
m_brush.evaluateBRep();
|
|
|
|
const Matrix4& localToWorld = Instance::localToWorld();
|
|
|
|
renderer.SetState( m_brush.m_state_point, Renderer::eWireframeOnly );
|
|
renderer.SetState( m_brush.m_state_point, Renderer::eFullMaterials );
|
|
|
|
if ( volume.fill() && GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
|
|
evaluateViewDependent( volume, localToWorld );
|
|
renderer.addRenderable( m_render_faces_wireframe, localToWorld );
|
|
}
|
|
else
|
|
{
|
|
m_brush.renderComponents( GlobalSelectionSystem().ComponentMode(), renderer, volume, localToWorld );
|
|
}
|
|
}
|
|
|
|
void renderClipPlane( Renderer& renderer, const VolumeTest& volume ) const {
|
|
if ( GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip && isSelected() ) {
|
|
m_clipPlane.render( renderer, volume, localToWorld() );
|
|
}
|
|
}
|
|
|
|
void renderCommon( Renderer& renderer, const VolumeTest& volume ) const {
|
|
bool componentMode = GlobalSelectionSystem().Mode() == SelectionSystem::eComponent;
|
|
|
|
if ( componentMode && isSelected() ) {
|
|
renderComponents( renderer, volume );
|
|
}
|
|
|
|
if ( parentSelected() ) {
|
|
if ( !componentMode ) {
|
|
renderer.Highlight( Renderer::eFace );
|
|
}
|
|
renderer.Highlight( Renderer::ePrimitive );
|
|
}
|
|
}
|
|
|
|
void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
//renderCommon(renderer, volume);
|
|
|
|
m_lightList->evaluateLights();
|
|
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
renderer.setLights( ( *i ).m_lights );
|
|
( *i ).render( renderer, volume, localToWorld );
|
|
}
|
|
|
|
renderComponentsSelected( renderer, volume, localToWorld );
|
|
}
|
|
|
|
void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
|
|
//renderCommon(renderer, volume);
|
|
|
|
evaluateViewDependent( volume, localToWorld );
|
|
|
|
if ( m_render_wireframe.m_size != 0 ) {
|
|
renderer.addRenderable( m_render_wireframe, localToWorld );
|
|
}
|
|
|
|
renderComponentsSelected( renderer, volume, localToWorld );
|
|
}
|
|
|
|
void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
|
|
m_brush.evaluateBRep();
|
|
|
|
renderClipPlane( renderer, volume );
|
|
|
|
renderSolid( renderer, volume, localToWorld() );
|
|
}
|
|
|
|
void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
|
|
m_brush.evaluateBRep();
|
|
|
|
renderClipPlane( renderer, volume );
|
|
|
|
renderWireframe( renderer, volume, localToWorld() );
|
|
}
|
|
|
|
void viewChanged() const {
|
|
m_viewChanged = true;
|
|
}
|
|
|
|
void testSelect( Selector& selector, SelectionTest& test ){
|
|
test.BeginMesh( localToWorld() );
|
|
|
|
SelectionIntersection best;
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).testSelect( test, best );
|
|
}
|
|
if ( best.valid() ) {
|
|
selector.addIntersection( best );
|
|
}
|
|
}
|
|
|
|
bool isSelectedComponents() const {
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
if ( ( *i ).selectedComponents() ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
void setSelectedComponents( bool select, SelectionSystem::EComponentMode mode ){
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).setSelected( mode, select );
|
|
}
|
|
}
|
|
void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ){
|
|
test.BeginMesh( localToWorld() );
|
|
|
|
switch ( mode )
|
|
{
|
|
case SelectionSystem::eVertex:
|
|
{
|
|
for ( VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i )
|
|
{
|
|
( *i ).testSelect( selector, test );
|
|
}
|
|
}
|
|
break;
|
|
case SelectionSystem::eEdge:
|
|
{
|
|
for ( EdgeInstances::iterator i = m_edgeInstances.begin(); i != m_edgeInstances.end(); ++i )
|
|
{
|
|
( *i ).testSelect( selector, test );
|
|
}
|
|
}
|
|
break;
|
|
case SelectionSystem::eFace:
|
|
{
|
|
if ( test.getVolume().fill() ) {
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).testSelect( selector, test );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).testSelect_centroid( selector, test );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
void gatherComponentsHighlight( std::vector<std::vector<Vector3>>& polygons, SelectionIntersection& intersection, SelectionTest& test, SelectionSystem::EComponentMode mode ) const {
|
|
test.BeginMesh( localToWorld() );
|
|
|
|
switch ( mode )
|
|
{
|
|
case SelectionSystem::eVertex:
|
|
{
|
|
for ( const VertexInstance& i : m_vertexInstances )
|
|
{
|
|
i.gatherComponentsHighlight( polygons, intersection, test );
|
|
}
|
|
}
|
|
break;
|
|
case SelectionSystem::eEdge:
|
|
{
|
|
for ( const EdgeInstance& i : m_edgeInstances )
|
|
{
|
|
i.gatherComponentsHighlight( polygons, intersection, test );
|
|
}
|
|
}
|
|
break;
|
|
case SelectionSystem::eFace:
|
|
{
|
|
if ( test.getVolume().fill() ) {
|
|
const Plane3* plane = nullptr;
|
|
bool newint = false;
|
|
for ( const FaceInstance& i : m_faceInstances )
|
|
{
|
|
SelectionIntersection best;
|
|
i.testSelect( test, best );
|
|
if( best.valid() && intersection.equalEpsilon( best, 0.25f, 2e-6f ) ){
|
|
plane = &i.getFace().plane3();
|
|
}
|
|
else if ( SelectionIntersection_closer( best, intersection ) ) {
|
|
intersection = best;
|
|
newint = true;
|
|
plane = &i.getFace().plane3();
|
|
}
|
|
}
|
|
if( plane ){
|
|
if( newint || ( !polygons.empty() && polygons.back().size() >= 3 && !plane3_equal( *plane, plane3_for_points( polygons.back().data() ) ) ) ){
|
|
polygons.clear();
|
|
}
|
|
gatherPolygonsByPlane( *plane, polygons, false );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( const FaceInstance& i : m_faceInstances )
|
|
{
|
|
SelectionIntersection best;
|
|
i.getFace().testSelect_centroid( test, best );
|
|
if ( SelectionIntersection_closer( best, intersection ) ) {
|
|
intersection = best;
|
|
polygons.clear();
|
|
polygons.emplace_back( std::initializer_list<Vector3>( { i.getFace().centroid() } ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void invertComponentSelection( SelectionSystem::EComponentMode mode ){
|
|
switch ( mode )
|
|
{
|
|
case SelectionSystem::eVertex:
|
|
{
|
|
for ( VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i )
|
|
{
|
|
( *i ).setSelected( !( *i ).isSelected() );
|
|
}
|
|
}
|
|
break;
|
|
case SelectionSystem::eEdge:
|
|
{
|
|
for ( EdgeInstances::iterator i = m_edgeInstances.begin(); i != m_edgeInstances.end(); ++i )
|
|
{
|
|
( *i ).setSelected( !( *i ).isSelected() );
|
|
}
|
|
}
|
|
break;
|
|
case SelectionSystem::eFace:
|
|
{
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
if( !( *i ).getFace().isFiltered() )
|
|
( *i ).setSelected( mode, !( *i ).isSelected() );
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void selectPlanes( SelectionTest& test, FaceInstances_ptrs& bestInstances ){
|
|
test.BeginMesh( localToWorld() );
|
|
|
|
const Vector3 viewdir( test.getVolume().getViewDir() );
|
|
double bestDot = 1;
|
|
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
if( !( *i ).trySelectPlane( test ) ){
|
|
continue;
|
|
}
|
|
const double dot = fabs( vector3_dot( ( *i ).getFace().plane3().normal(), viewdir ) );
|
|
const double diff = bestDot - dot;
|
|
if( diff > 0.03 ){
|
|
bestDot = dot;
|
|
bestInstances.clear();
|
|
bestInstances.push_back( &( *i ) );
|
|
}
|
|
else if( fabs( diff ) <= 0.03 ){
|
|
bestInstances.push_back( &( *i ) );
|
|
}
|
|
}
|
|
}
|
|
void selectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){
|
|
FaceInstances_ptrs bestInstances;
|
|
selectPlanes( test, bestInstances );
|
|
|
|
for ( FaceInstances_ptrs::iterator i = bestInstances.begin(); i != bestInstances.end(); ++i ){
|
|
( *i )->addSelectable( selector );
|
|
selectedPlaneCallback( ( *i )->getFace().plane3() );
|
|
if( test.getVolume().fill() )
|
|
return; // select only plane in camera
|
|
}
|
|
}
|
|
void selectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes ){
|
|
for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).selectReversedPlane( selector, selectedPlanes );
|
|
}
|
|
}
|
|
|
|
void bestPlaneDirect( SelectionTest& test, Plane3& plane, SelectionIntersection& intersection ) const {
|
|
test.BeginMesh( localToWorld() );
|
|
for ( const FaceInstance& fi : m_faceInstances )
|
|
{
|
|
SelectionIntersection intersection_new;
|
|
fi.testSelect( test, intersection_new );
|
|
if( SelectionIntersection_closer( intersection_new, intersection ) ){
|
|
intersection = intersection_new;
|
|
plane = fi.getFace().plane3();
|
|
}
|
|
}
|
|
}
|
|
void bestPlaneIndirect( SelectionTest& test, Plane3& plane, Vector3& intersection, float& dist ) const {
|
|
test.BeginMesh( localToWorld() );
|
|
float dot = 1;
|
|
for ( const EdgeInstance& ei : m_edgeInstances )
|
|
{
|
|
ei.bestPlaneIndirect( test, plane, intersection, dist, dot );
|
|
}
|
|
}
|
|
void selectByPlane( const Plane3& plane ){
|
|
for ( FaceInstance& fi : m_faceInstances )
|
|
if( plane3_equal( plane, fi.getFace().plane3() ) || plane3_equal( plane, plane3_flipped( fi.getFace().plane3() ) ) )
|
|
fi.setSelected( SelectionSystem::eFace, true );
|
|
}
|
|
void gatherPolygonsByPlane( const Plane3& plane, std::vector<std::vector<Vector3>>& polygons ) const {
|
|
gatherPolygonsByPlane( plane, polygons, true );
|
|
}
|
|
void gatherPolygonsByPlane( const Plane3& plane, std::vector<std::vector<Vector3>>& polygons, const bool reversed_plane_also ) const {
|
|
for ( const FaceInstance& fi : m_faceInstances )
|
|
if( plane3_equal( plane, fi.getFace().plane3() ) || ( reversed_plane_also && plane3_equal( plane, plane3_flipped( fi.getFace().plane3() ) ) ) )
|
|
if( fi.getFace().contributes() ){
|
|
const Winding& winding = fi.getFace().getWinding();
|
|
polygons.emplace_back( winding.numpoints );
|
|
for( std::size_t i = 0; i < winding.numpoints; ++i ){
|
|
polygons.back()[i] = winding[i].vertex;
|
|
}
|
|
}
|
|
}
|
|
|
|
void selectVerticesOnPlane( const Plane3& plane ){
|
|
for ( FaceInstance& fi : m_faceInstances )
|
|
if( plane3_equal( plane, fi.getFace().plane3() ) || plane3_equal( plane, plane3_flipped( fi.getFace().plane3() ) ) )
|
|
for ( VertexInstance& vi : m_vertexInstances )
|
|
vi.selectVerticesOfFace( fi );
|
|
}
|
|
|
|
void transformComponents( const Matrix4& matrix );
|
|
|
|
const AABB& getSelectedComponentsBounds() const {
|
|
m_aabb_component = AABB();
|
|
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).iterate_selected( m_aabb_component );
|
|
}
|
|
|
|
return m_aabb_component;
|
|
}
|
|
void gatherSelectedComponents( const Vector3Callback& callback ) const {
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).gatherSelectedComponents( callback );
|
|
}
|
|
}
|
|
|
|
void insert_vertices( const Brush::VertexModeVertices& vertexModeVertices ){
|
|
if( !m_vertexInstances.empty() ){
|
|
m_brush.vertexModeInit();
|
|
m_brush.m_vertexModeVertices.reserve( m_vertexInstances.size() + 2 );
|
|
for ( const auto& i : m_vertexInstances ){
|
|
i.gather( m_brush.m_vertexModeVertices );
|
|
}
|
|
for ( const auto& i : vertexModeVertices ){
|
|
m_brush.m_vertexModeVertices.push_back( i );
|
|
if( i.m_faces.empty() )
|
|
m_brush.m_vertexModeVertices.back().m_faces.push_back( m_brush.m_vertexModeVertices[0].m_faces[0] );
|
|
}
|
|
m_transform.m_transformFrozen = false;
|
|
m_transform.setType( TRANSFORM_COMPONENT );
|
|
m_brush.transformChanged();
|
|
m_brush.evaluateBRep();
|
|
}
|
|
}
|
|
|
|
void remove_vertices(){
|
|
if( !m_vertexInstances.empty() ){
|
|
m_brush.vertexModeInit();
|
|
Brush::VertexModeVertices v;
|
|
v.reserve( m_vertexInstances.size() );
|
|
for ( const auto& i : m_vertexInstances ){
|
|
i.gather( v );
|
|
if( v.back().m_selected )
|
|
v.pop_back();
|
|
}
|
|
std::vector<bool> ok( v.size(), true );
|
|
gatherSelectedComponents( [&]( const Vector3 & value ) {
|
|
for( std::size_t i = 0; i < v.size(); ++i )
|
|
if( vector3_length_squared( v[i].m_vertex - value ) < 0.05 * 0.05 )
|
|
ok[i] = false;
|
|
} );
|
|
|
|
m_brush.m_vertexModeVertices.reserve( v.size() );
|
|
for( std::size_t i = 0; i < v.size(); ++i ){
|
|
if( ok[i] )
|
|
m_brush.m_vertexModeVertices.push_back( v[i] );
|
|
}
|
|
m_brush.vertexModeBuildHull();
|
|
m_brush.evaluateBRep();
|
|
m_brush.vertexModeFree();
|
|
m_brush.freezeTransform();
|
|
}
|
|
}
|
|
|
|
void snapComponents( float snap ){
|
|
for ( const auto& fi : m_faceInstances ){
|
|
if( fi.selectedComponents( SelectionSystem::eVertex ) ){
|
|
vertex_snap( snap, false );
|
|
return;
|
|
}
|
|
}
|
|
|
|
for ( auto& fi : m_faceInstances )
|
|
fi.snapComponents( snap );
|
|
}
|
|
void evaluateTransform(){
|
|
if( m_transform.m_transformFrozen && m_transform.isIdentity() )
|
|
return;
|
|
if( m_transform.m_transformFrozen && !m_transform.isIdentity() ){ /* new transform */
|
|
m_transform.m_transformFrozen = false;
|
|
for( auto& i : m_faceInstances )
|
|
i.getFace().cacheCentroid();
|
|
|
|
if( m_transform.getType() == TRANSFORM_COMPONENT ){
|
|
for ( const auto& i : m_faceInstances ){
|
|
if( i.selectedComponents( SelectionSystem::eVertex ) ){
|
|
m_brush.vertexModeInit();
|
|
m_brush.m_vertexModeVertices.reserve( m_vertexInstances.size() );
|
|
for ( const auto& i : m_vertexInstances ){
|
|
i.gather( m_brush.m_vertexModeVertices );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const Matrix4 matrix( m_transform.calculateTransform() );
|
|
|
|
if ( m_transform.getType() == TRANSFORM_PRIMITIVE ) {
|
|
m_brush.transform( matrix );
|
|
}
|
|
else
|
|
{
|
|
if( m_brush.m_vertexModeOn ){
|
|
m_brush.vertexModeTransform( matrix );
|
|
}
|
|
else{
|
|
const bool tmp = g_brush_texturelock_enabled;
|
|
/* do not want texture projection transformation while resizing brush */
|
|
if( GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eDrag && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive )
|
|
g_brush_texturelock_enabled = false;
|
|
transformComponents( matrix );
|
|
g_brush_texturelock_enabled = tmp;
|
|
}
|
|
}
|
|
}
|
|
void applyTransform(){
|
|
if( !m_transform.isIdentity() ){
|
|
if( m_transform.m_transformFrozen ){ //not yet unfrozen by evaluateTransform(), so evaluate
|
|
// m_brush.revertTransform();
|
|
// evaluateTransform();
|
|
m_brush.evaluateBRep();
|
|
}
|
|
m_brush.freezeTransform();
|
|
m_transform.setIdentity();
|
|
}
|
|
m_brush.vertexModeFree();
|
|
}
|
|
typedef MemberCaller<BrushInstance, &BrushInstance::applyTransform> ApplyTransformCaller;
|
|
|
|
void setClipPlane( const Plane3& plane ){
|
|
m_clipPlane.setPlane( m_brush, plane );
|
|
}
|
|
|
|
bool testLight( const RendererLight& light ) const {
|
|
return light.testAABB( worldAABB() );
|
|
}
|
|
void insertLight( const RendererLight& light ){
|
|
const Matrix4& localToWorld = Instance::localToWorld();
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
Face_addLight( *i, localToWorld, light );
|
|
}
|
|
}
|
|
void clearLights(){
|
|
for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
|
|
{
|
|
( *i ).m_lights.clear();
|
|
}
|
|
}
|
|
};
|
|
|
|
inline BrushInstance* Instance_getBrush( scene::Instance& instance ){
|
|
return InstanceTypeCast<BrushInstance>::cast( instance );
|
|
}
|
|
|
|
|
|
template<typename Functor>
|
|
class BrushSelectedVisitor : public SelectionSystem::Visitor
|
|
{
|
|
const Functor& m_functor;
|
|
public:
|
|
BrushSelectedVisitor( const Functor& functor ) : m_functor( functor ){
|
|
}
|
|
void visit( scene::Instance& instance ) const {
|
|
BrushInstance* brush = Instance_getBrush( instance );
|
|
if ( brush != 0 ) {
|
|
m_functor( *brush );
|
|
}
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_forEachSelectedBrush( const Functor& functor ){
|
|
GlobalSelectionSystem().foreachSelected( BrushSelectedVisitor<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Functor>
|
|
class BrushVisibleSelectedVisitor : public SelectionSystem::Visitor
|
|
{
|
|
const Functor& m_functor;
|
|
public:
|
|
BrushVisibleSelectedVisitor( const Functor& functor ) : m_functor( functor ){
|
|
}
|
|
void visit( scene::Instance& instance ) const {
|
|
BrushInstance* brush = Instance_getBrush( instance );
|
|
if ( brush != 0
|
|
&& instance.path().top().get().visible() ) {
|
|
m_functor( *brush );
|
|
}
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_forEachVisibleSelectedBrush( const Functor& functor ){
|
|
GlobalSelectionSystem().foreachSelected( BrushVisibleSelectedVisitor<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
class BrushForEachFace
|
|
{
|
|
const BrushInstanceVisitor& m_visitor;
|
|
public:
|
|
BrushForEachFace( const BrushInstanceVisitor& visitor ) : m_visitor( visitor ){
|
|
}
|
|
void operator()( BrushInstance& brush ) const {
|
|
brush.forEachFaceInstance( m_visitor );
|
|
}
|
|
};
|
|
|
|
template<class Functor>
|
|
class FaceInstanceVisitFace : public BrushInstanceVisitor
|
|
{
|
|
const Functor& functor;
|
|
public:
|
|
FaceInstanceVisitFace( const Functor& functor )
|
|
: functor( functor ){
|
|
}
|
|
void visit( FaceInstance& face ) const {
|
|
functor( face.getFace() );
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Brush_forEachFace( BrushInstance& brush, const Functor& functor ){
|
|
brush.forEachFaceInstance( FaceInstanceVisitFace<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
template<class Functor>
|
|
class FaceVisitAll : public BrushVisitor
|
|
{
|
|
const Functor& functor;
|
|
public:
|
|
FaceVisitAll( const Functor& functor )
|
|
: functor( functor ){
|
|
}
|
|
void visit( Face& face ) const {
|
|
functor( face );
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Brush_forEachFace( const Brush& brush, const Functor& functor ){
|
|
brush.forEachFace( FaceVisitAll<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Brush_forEachFace( Brush& brush, const Functor& functor ){
|
|
brush.forEachFace( FaceVisitAll<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
template<class Functor>
|
|
class FaceInstanceVisitAll : public BrushInstanceVisitor
|
|
{
|
|
const Functor& functor;
|
|
public:
|
|
FaceInstanceVisitAll( const Functor& functor )
|
|
: functor( functor ){
|
|
}
|
|
void visit( FaceInstance& face ) const {
|
|
functor( face );
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Brush_ForEachFaceInstance( BrushInstance& brush, const Functor& functor ){
|
|
brush.forEachFaceInstance( FaceInstanceVisitAll<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_forEachBrush( scene::Graph& graph, const Functor& functor ){
|
|
graph.traverse( InstanceWalker< InstanceApply<BrushInstance, Functor> >( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Type, typename Functor>
|
|
class InstanceIfVisible : public Functor
|
|
{
|
|
public:
|
|
InstanceIfVisible( const Functor& functor ) : Functor( functor ){
|
|
}
|
|
void operator()( scene::Instance& instance ){
|
|
if ( instance.path().top().get().visible() ) {
|
|
Functor::operator()( instance );
|
|
}
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
class BrushVisibleWalker : public scene::Graph::Walker
|
|
{
|
|
const Functor& m_functor;
|
|
public:
|
|
BrushVisibleWalker( const Functor& functor ) : m_functor( functor ){
|
|
}
|
|
bool pre( const scene::Path& path, scene::Instance& instance ) const {
|
|
if ( path.top().get().visible() ) {
|
|
BrushInstance* brush = Instance_getBrush( instance );
|
|
if ( brush != 0 ) {
|
|
m_functor( *brush );
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_forEachVisibleBrush( scene::Graph& graph, const Functor& functor ){
|
|
graph.traverse( BrushVisibleWalker<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_ForEachBrush_ForEachFace( scene::Graph& graph, const Functor& functor ){
|
|
Scene_forEachBrush( graph, BrushForEachFace( FaceInstanceVisitFace<Functor>( functor ) ) );
|
|
return functor;
|
|
}
|
|
|
|
// d1223m
|
|
template<typename Functor>
|
|
inline const Functor& Scene_ForEachBrush_ForEachFaceInstance( scene::Graph& graph, const Functor& functor ){
|
|
Scene_forEachBrush( graph, BrushForEachFace( FaceInstanceVisitAll<Functor>( functor ) ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_ForEachSelectedBrush_ForEachFace( scene::Graph& graph, const Functor& functor ){
|
|
Scene_forEachSelectedBrush( BrushForEachFace( FaceInstanceVisitFace<Functor>( functor ) ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_ForEachSelectedBrush_ForEachFaceInstance( scene::Graph& graph, const Functor& functor ){
|
|
Scene_forEachSelectedBrush( BrushForEachFace( FaceInstanceVisitAll<Functor>( functor ) ) );
|
|
return functor;
|
|
}
|
|
|
|
template<typename Functor>
|
|
class FaceVisitorWrapper
|
|
{
|
|
const Functor& functor;
|
|
public:
|
|
FaceVisitorWrapper( const Functor& functor ) : functor( functor ){
|
|
}
|
|
|
|
void operator()( FaceInstance& faceInstance ) const {
|
|
functor( faceInstance.getFace() );
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline const Functor& Scene_ForEachSelectedBrushFace( scene::Graph& graph, const Functor& functor ){
|
|
g_SelectedFaceInstances.foreach( FaceVisitorWrapper<Functor>( functor ) );
|
|
return functor;
|
|
}
|
|
|
|
|
|
#endif
|