/* 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 */ #pragma once /// \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 #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 ){ gl().glVertexPointer( 3, GL_DOUBLE, sizeof( WindingVertex ), &winding.points.data()->vertex ); gl().glDrawArrays( GL_LINE_LOOP, 0, GLsizei( winding.numpoints ) ); } inline void Winding_Draw( const Winding& winding, const Vector3& normal, RenderStateFlags state ){ gl().glVertexPointer( 3, GL_DOUBLE, 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; } gl().glNormalPointer( GL_FLOAT, sizeof( Vector3 ), normals ); gl().glVertexAttribPointer( c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->texcoord ); gl().glVertexAttribPointer( c_attr_Tangent, 3, GL_FLOAT, 0, sizeof( WindingVertex ), &winding.points.data()->tangent ); gl().glVertexAttribPointer( c_attr_Binormal, 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; } gl().glNormalPointer( GL_FLOAT, sizeof( Vector3 ), normals ); } if ( state & RENDER_TEXTURE ) { gl().glTexCoordPointer( 2, GL_FLOAT, sizeof( WindingVertex ), &winding.points.data()->texcoord ); } } #if 0 if ( state & RENDER_FILL ) { gl().glDrawArrays( GL_TRIANGLE_FAN, 0, GLsizei( winding.numpoints ) ); } else { gl().glDrawArrays( GL_LINE_LOOP, 0, GLsizei( winding.numpoints ) ); } #else gl().glDrawArrays( GL_POLYGON, 0, GLsizei( winding.numpoints ) ); #endif #if 0 const Winding& winding = winding; if ( state & RENDER_FILL ) { gl().glBegin( GL_POLYGON ); } else { gl().glBegin( GL_LINE_LOOP ); } if ( state & RENDER_LIGHTING ) { gl().glNormal3fv( normal ); } for ( int i = 0; i < winding.numpoints; ++i ) { if ( state & RENDER_TEXTURE ) { gl().glTexCoord2fv( &winding.points[i][3] ); } gl().glVertex3fv( winding.points[i] ); } gl().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( 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( point.x() ), static_cast( point.y() ), static_cast( 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; }; typedef ReferencePair 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( []( FaceShaderObserver& observer ){ observer.realiseShader(); } ); } void unrealise(){ ASSERT_MESSAGE( m_realised, "FaceTexdef::unrealise: already unrealised" ); m_observers.forEach( []( FaceShaderObserver& observer ){ observer.unrealiseShader(); } ); 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 DoubleVector3& p0, const DoubleVector3& p1, const DoubleVector3& 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( &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 ){ #if 0 return float_snapped( f, 1.f / ( 1 << 16 ) ); #else return f; #endif } 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 eBrushTypeQuake2BP: case eBrushTypeDoom3: case eBrushTypeQuake4: return TEXDEFTYPEID_BRUSHPRIMITIVES; case eBrushTypeValve220: case eBrushTypeQuake2Valve220: 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( DoubleVector3( 0, 0, 0 ), DoubleVector3( 64, 0, 0 ), DoubleVector3( 0, 64, 0 ) ); m_texdef.setBasis( m_plane.plane3().normal() ); planeChanged(); } Face( const DoubleVector3& p0, const DoubleVector3& p1, const DoubleVector3& 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( 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( 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( 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(); Brush_textureChanged(); 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 gl().glVertexPointer( 3, GL_FLOAT, sizeof( DepthTestedPointVertex ), &m_vertices->vertex ); gl().glDrawElements( GL_LINES, GLsizei( m_size << 1 ), RenderIndexTypeID, m_faceVertex.data() ); #else gl().glBegin( GL_LINES ); for ( std::size_t i = 0; i < m_size; ++i ) { gl().glVertex3fv( &m_vertices[m_faceVertex[i].first].vertex.x ); gl().glVertex3fv( &m_vertices[m_faceVertex[i].second].vertex.x ); } gl().glEnd(); #endif } Array m_faceVertex; std::size_t m_size; const DepthTestedPointVertex* m_vertices; }; class Brush; typedef std::vector 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 FaceSmartPointer; typedef std::vector 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: DoubleVector3 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 Observers; Observers m_observers; UndoObserver* m_undoable_observer; MapFile* m_map; // state Faces m_faces; // ---- // cached data compiled from state Array m_faceCentroidPoints; RenderablePointArray m_render_faces; mutable Array m_uniqueVertexPoints; typedef std::vector SelectableVertices; SelectableVertices m_select_vertices; RenderablePointArray m_render_vertices; RenderableDepthTestedPointArray m_render_deepvertices; Array m_uniqueEdgePoints; typedef std::vector SelectableEdges; SelectableEdges m_select_edges; RenderablePointArray m_render_edges; Array m_edge_indices; Array 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( this )->buildBRep(); } } void transformChanged(){ planeChanged(); m_transformChanged = true; } typedef MemberCaller 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( 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 ); } } 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 DoubleVector3 m_vertex; DoubleVector3 m_vertexTransformed; const bool m_selected; std::vector m_faces; VertexModeVertex( const DoubleVector3& vertex, const bool selected ) : m_vertex( vertex ), m_vertexTransformed( vertex ), m_selected( selected ) { } }; typedef std::vector 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( 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 DoubleVector3& p0, const DoubleVector3& p1, const DoubleVector3& 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& 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::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 FaceInstances; FaceInstances m_faceInstances; public: void insert( FaceInstance& faceInstance ){ m_faceInstances.append( faceInstance ); } void erase( FaceInstance& faceInstance ){ m_faceInstances.erase( faceInstance ); } template 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 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 inline Vector3 triangle_cross( const BasicVector3& x, const BasicVector3 y, const BasicVector3& z ){ return vector3_cross( y - x, z - x ); } template inline bool triangles_same_winding( const BasicVector3& x1, const BasicVector3 y1, const BasicVector3& z1, const BasicVector3& x2, const BasicVector3 y2, const BasicVector3& z2 ){ return vector3_dot( triangle_cross( x1, y1, z1 ), triangle_cross( x2, y2, z2 ) ) > 0; } class VectorLightList : public LightList { typedef std::vector 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 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 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 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 void SelectedFaces_foreach( Functor functor ) const { if ( isSelected() ) { functor( centroid() ); } } template 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 ) ); gl().glVertexPointer( 3, GL_FLOAT, sizeof( Vector3 ), &lineverts[0] ); gl().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 FaceInstances; typedef std::vector 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>& 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( { 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>& 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( { 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 DoubleVector3& 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::install( m_casts ); InstanceContainedCast::install( m_casts ); InstanceContainedCast::install( m_casts ); InstanceStaticCast::install( m_casts ); InstanceStaticCast::install( m_casts ); InstanceStaticCast::install( m_casts ); InstanceStaticCast::install( m_casts ); InstanceStaticCast::install( m_casts ); InstanceStaticCast::install( m_casts ); InstanceIdentityCast::install( m_casts ); InstanceContainedCast::install( m_casts ); } InstanceTypeCastTable& get(){ return m_casts; } }; Brush& m_brush; FaceInstances m_faceInstances; typedef std::vector EdgeInstances; EdgeInstances m_edgeInstances; typedef std::vector VertexInstances; VertexInstances m_vertexInstances; ObservedSelectable m_selectable; mutable RenderableWireframe m_render_wireframe; mutable RenderablePointVector m_render_selected; mutable AABB m_aabb_component; mutable Array m_faceCentroidPointsCulled; RenderablePointArray 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 StaticTypeCasts; void lightsChanged(){ m_lightList->lightsChanged(); } typedef MemberCaller 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){ return m_brush; } Cullable& get( NullType){ return m_brush; } Transformable& get( NullType){ return m_transform; } void selectedChanged( const Selectable& selectable ){ GlobalSelectionSystem().getObserver ( SelectionSystem::ePrimitive )( selectable ); GlobalSelectionSystem().onSelectedChanged( *this, selectable ); Instance::selectedChanged(); } typedef MemberCaller1 SelectedChangedCaller; void selectedChangedComponent( const Selectable& selectable ){ GlobalSelectionSystem().getObserver ( SelectionSystem::eComponent )( selectable ); GlobalSelectionSystem().onComponentSelection( *this, selectable ); } typedef MemberCaller1 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>& 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( { 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>& polygons ) const { gatherPolygonsByPlane( plane, polygons, true ); } void gatherPolygonsByPlane( const Plane3& plane, std::vector>& 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 ok( v.size(), true ); gatherSelectedComponents( [&]( const DoubleVector3 & 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 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::cast( instance ); } template 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 inline const Functor& Scene_forEachSelectedBrush( const Functor& functor ){ GlobalSelectionSystem().foreachSelected( BrushSelectedVisitor( functor ) ); return functor; } template 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 inline const Functor& Scene_forEachVisibleSelectedBrush( const Functor& functor ){ GlobalSelectionSystem().foreachSelected( BrushVisibleSelectedVisitor( 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 FaceInstanceVisitFace : public BrushInstanceVisitor { const Functor& functor; public: FaceInstanceVisitFace( const Functor& functor ) : functor( functor ){ } void visit( FaceInstance& face ) const { functor( face.getFace() ); } }; template inline const Functor& Brush_forEachFace( BrushInstance& brush, const Functor& functor ){ brush.forEachFaceInstance( FaceInstanceVisitFace( functor ) ); return functor; } template class FaceVisitAll : public BrushVisitor { const Functor& functor; public: FaceVisitAll( const Functor& functor ) : functor( functor ){ } void visit( Face& face ) const { functor( face ); } }; template inline const Functor& Brush_forEachFace( const Brush& brush, const Functor& functor ){ brush.forEachFace( FaceVisitAll( functor ) ); return functor; } template inline const Functor& Brush_forEachFace( Brush& brush, const Functor& functor ){ brush.forEachFace( FaceVisitAll( functor ) ); return functor; } template class FaceInstanceVisitAll : public BrushInstanceVisitor { const Functor& functor; public: FaceInstanceVisitAll( const Functor& functor ) : functor( functor ){ } void visit( FaceInstance& face ) const { functor( face ); } }; template inline const Functor& Brush_ForEachFaceInstance( BrushInstance& brush, const Functor& functor ){ brush.forEachFaceInstance( FaceInstanceVisitAll( functor ) ); return functor; } template inline const Functor& Scene_forEachBrush( scene::Graph& graph, const Functor& functor ){ graph.traverse( InstanceWalker< InstanceApply >( functor ) ); return functor; } template 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 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 inline const Functor& Scene_forEachVisibleBrush( scene::Graph& graph, const Functor& functor ){ graph.traverse( BrushVisibleWalker( functor ) ); return functor; } template inline const Functor& Scene_ForEachBrush_ForEachFace( scene::Graph& graph, const Functor& functor ){ Scene_forEachBrush( graph, BrushForEachFace( FaceInstanceVisitFace( functor ) ) ); return functor; } // d1223m template inline const Functor& Scene_ForEachBrush_ForEachFaceInstance( scene::Graph& graph, const Functor& functor ){ Scene_forEachBrush( graph, BrushForEachFace( FaceInstanceVisitAll( functor ) ) ); return functor; } template inline const Functor& Scene_ForEachSelectedBrush_ForEachFace( scene::Graph& graph, const Functor& functor ){ Scene_forEachSelectedBrush( BrushForEachFace( FaceInstanceVisitFace( functor ) ) ); return functor; } template inline const Functor& Scene_ForEachSelectedBrush_ForEachFaceInstance( scene::Graph& graph, const Functor& functor ){ Scene_forEachSelectedBrush( BrushForEachFace( FaceInstanceVisitAll( functor ) ) ); return functor; } template class FaceVisitorWrapper { const Functor& functor; public: FaceVisitorWrapper( const Functor& functor ) : functor( functor ){ } void operator()( FaceInstance& faceInstance ) const { functor( faceInstance.getFace() ); } }; template inline const Functor& Scene_ForEachSelectedBrushFace( scene::Graph& graph, const Functor& functor ){ g_SelectedFaceInstances.foreach( FaceVisitorWrapper( functor ) ); return functor; }