/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined( INCLUDED_PATCH_H ) #define INCLUDED_PATCH_H /// \file /// \brief The patch primitive. /// /// A 2-dimensional matrix of vertices that define a quadratic bezier surface. /// The Boundary-Representation of this primitive is a triangle mesh. /// The surface is recursively tesselated until the angle between each triangle /// edge is smaller than a specified tolerance. #include "nameable.h" #include "ifilter.h" #include "imap.h" #include "ipatch.h" #include "cullable.h" #include "renderable.h" #include "editable.h" #include "selectable.h" #include "debugging/debugging.h" #include #include #include "math/frustum.h" #include "string/string.h" #include "stream/stringstream.h" #include "stream/textstream.h" #include "xml/xmlelement.h" #include "scenelib.h" #include "transformlib.h" #include "instancelib.h" #include "selectionlib.h" #include "traverselib.h" #include "render.h" #include "stringio.h" #include "shaderlib.h" #include "generic/callback.h" #include "signal/signalfwd.h" #include "texturelib.h" #include "xml/ixml.h" #include "dragplanes.h" enum EPatchType { ePatchTypeQuake3, ePatchTypeDoom3, }; extern int g_PatchSubdivideThreshold; #define MIN_PATCH_WIDTH 3 #define MIN_PATCH_HEIGHT 3 extern std::size_t MAX_PATCH_WIDTH; extern std::size_t MAX_PATCH_HEIGHT; #define MAX_PATCH_ROWCTRL ( ( ( MAX_PATCH_WIDTH - 1 ) - 1 ) / 2 ) #define MAX_PATCH_COLCTRL ( ( ( MAX_PATCH_HEIGHT - 1 ) - 1 ) / 2 ) enum EPatchCap { eCapBevel, eCapEndCap, eCapIBevel, eCapIEndCap, eCapCylinder, }; enum EPatchPrefab { ePlane, eBevel, eEndCap, eCylinder, eDenseCylinder, eVeryDenseCylinder, eSqCylinder, eCone, eSphere, eXactCylinder, eXactSphere, eXactCone, }; enum EMatrixMajor { ROW, COL, }; struct BezierCurve { Vector3 crd; Vector3 left; Vector3 right; }; const std::size_t BEZIERCURVETREE_MAX_INDEX = std::size_t( 1 ) << ( std::numeric_limits::digits - 1 ); struct BezierCurveTree { std::size_t index; BezierCurveTree* left; BezierCurveTree* right; }; inline bool BezierCurveTree_isLeaf( const BezierCurveTree* node ){ return node->left == 0 && node->right == 0; } void BezierCurveTree_Delete( BezierCurveTree *pCurve ); inline VertexPointer vertexpointer_arbitrarymeshvertex( const ArbitraryMeshVertex* array ){ return VertexPointer( VertexPointer::pointer( &array->vertex ), sizeof( ArbitraryMeshVertex ) ); } typedef PatchControl* PatchControlIter; typedef const PatchControl* PatchControlConstIter; inline void copy_ctrl( PatchControlIter ctrl, PatchControlConstIter begin, PatchControlConstIter end ){ std::copy( begin, end, ctrl ); } const Colour4b colour_corner( 0, 255, 0, 255 ); const Colour4b colour_inside( 255, 0, 255, 255 ); class Patch; class PatchFilter { public: virtual bool filter( const Patch& patch ) const = 0; }; bool patch_filtered( Patch& patch ); void add_patch_filter( PatchFilter& filter, int mask, bool invert = false ); void Patch_addTextureChangedCallback( const SignalHandler& handler ); void Patch_textureChanged(); inline void BezierCurveTreeArray_deleteAll( Array& curveTrees ){ for ( Array::iterator i = curveTrees.begin(); i != curveTrees.end(); ++i ) { BezierCurveTree_Delete( *i ); } } inline void PatchControlArray_invert( Array& ctrl, std::size_t width, std::size_t height ){ Array tmp( width ); PatchControlIter from = ctrl.data() + ( width * ( height - 1 ) ); PatchControlIter to = ctrl.data(); for ( std::size_t h = 0; h != ( ( height - 1 ) >> 1 ); ++h, to += width, from -= width ) { copy_ctrl( tmp.data(), to, to + width ); copy_ctrl( to, from, from + width ); copy_ctrl( from, tmp.data(), tmp.data() + width ); } } class PatchTesselation { public: PatchTesselation() : m_numStrips( 0 ), m_lenStrips( 0 ), m_nArrayWidth( 0 ), m_nArrayHeight( 0 ){ } Array m_vertices; Array m_indices; std::size_t m_numStrips; std::size_t m_lenStrips; Array m_arrayWidth; std::size_t m_nArrayWidth; Array m_arrayHeight; std::size_t m_nArrayHeight; Array m_curveTreeU; Array m_curveTreeV; }; class RenderablePatchWireframe : public OpenGLRenderable { PatchTesselation& m_tess; public: RenderablePatchWireframe( PatchTesselation& tess ) : m_tess( tess ){ } void render( RenderStateFlags state ) const { { #if NV_DRIVER_BUG glVertexPointer( 3, GL_FLOAT, 0, 0 ); glDrawArrays( GL_TRIANGLE_FAN, 0, 0 ); #endif std::size_t n = 0; glVertexPointer( 3, GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->vertex ); for ( std::size_t i = 0; i <= m_tess.m_curveTreeV.size(); ++i ) { glDrawArrays( GL_LINE_STRIP, GLint( n ), GLsizei( m_tess.m_nArrayWidth ) ); if ( i == m_tess.m_curveTreeV.size() ) { break; } if ( !BezierCurveTree_isLeaf( m_tess.m_curveTreeV[i] ) ) { glDrawArrays( GL_LINE_STRIP, GLint( m_tess.m_curveTreeV[i]->index ), GLsizei( m_tess.m_nArrayWidth ) ); } n += ( m_tess.m_arrayHeight[i] * m_tess.m_nArrayWidth ); } } { const ArbitraryMeshVertex* p = m_tess.m_vertices.data(); std::size_t n = m_tess.m_nArrayWidth * sizeof( ArbitraryMeshVertex ); for ( std::size_t i = 0; i <= m_tess.m_curveTreeU.size(); ++i ) { glVertexPointer( 3, GL_FLOAT, GLsizei( n ), &p->vertex ); glDrawArrays( GL_LINE_STRIP, 0, GLsizei( m_tess.m_nArrayHeight ) ); if ( i == m_tess.m_curveTreeU.size() ) { break; } if ( !BezierCurveTree_isLeaf( m_tess.m_curveTreeU[i] ) ) { glVertexPointer( 3, GL_FLOAT, GLsizei( n ), &( m_tess.m_vertices.data() + ( m_tess.m_curveTreeU[i]->index ) )->vertex ); glDrawArrays( GL_LINE_STRIP, 0, GLsizei( m_tess.m_nArrayHeight ) ); } p += m_tess.m_arrayWidth[i]; } } } }; class RenderablePatchFixedWireframe : public OpenGLRenderable { PatchTesselation& m_tess; public: RenderablePatchFixedWireframe( PatchTesselation& tess ) : m_tess( tess ){ } void render( RenderStateFlags state ) const { glVertexPointer( 3, GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->vertex ); const RenderIndex* strip_indices = m_tess.m_indices.data(); for ( std::size_t i = 0; i < m_tess.m_numStrips; i++, strip_indices += m_tess.m_lenStrips ) { glDrawElements( GL_QUAD_STRIP, GLsizei( m_tess.m_lenStrips ), RenderIndexTypeID, strip_indices ); } } }; class RenderablePatchSolid : public OpenGLRenderable { PatchTesselation& m_tess; public: RenderablePatchSolid( PatchTesselation& tess ) : m_tess( tess ){ } void RenderNormals() const; void render( RenderStateFlags state ) const { #if 0 if ( ( state & RENDER_FILL ) == 0 ) { RenderablePatchWireframe( m_tess ).render( state ); } else #endif { if ( ( state & RENDER_BUMP ) != 0 ) { if ( GlobalShaderCache().useShaderLanguage() ) { glNormalPointer( GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->normal ); glVertexAttribPointerARB( c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->texcoord ); glVertexAttribPointerARB( c_attr_Tangent, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->tangent ); glVertexAttribPointerARB( c_attr_Binormal, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->bitangent ); } else { glVertexAttribPointerARB( 11, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->normal ); glVertexAttribPointerARB( 8, 2, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->texcoord ); glVertexAttribPointerARB( 9, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->tangent ); glVertexAttribPointerARB( 10, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->bitangent ); } } else { glNormalPointer( GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->normal ); glTexCoordPointer( 2, GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->texcoord ); } glVertexPointer( 3, GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->vertex ); const RenderIndex* strip_indices = m_tess.m_indices.data(); for ( std::size_t i = 0; i < m_tess.m_numStrips; i++, strip_indices += m_tess.m_lenStrips ) { glDrawElements( GL_QUAD_STRIP, GLsizei( m_tess.m_lenStrips ), RenderIndexTypeID, strip_indices ); } } #if defined( _DEBUG ) RenderNormals(); #endif } }; // parametric surface defined by quadratic bezier control curves class Patch : public XMLImporter, public XMLExporter, public TransformNode, public Bounded, public Cullable, public Snappable, public Undoable, public Filterable, public Nameable { class xml_state_t { public: enum EState { eDefault, ePatch, eMatrix, eShader, }; xml_state_t( EState state ) : m_state( state ) {} EState state() const { return m_state; } const char* content() const { return m_content.c_str(); } std::size_t write( const char* buffer, std::size_t length ){ return m_content.write( buffer, length ); } private: EState m_state; StringOutputStream m_content; }; std::vector m_xml_state; typedef Array PatchControlArray; class SavedState : public UndoMemento { public: SavedState( std::size_t width, std::size_t height, const PatchControlArray& ctrl, const char* shader, bool patchDef3, std::size_t subdivisions_x, std::size_t subdivisions_y ) : m_width( width ), m_height( height ), m_shader( shader ), m_ctrl( ctrl ), m_patchDef3( patchDef3 ), m_subdivisions_x( subdivisions_x ), m_subdivisions_y( subdivisions_y ){ } void release(){ delete this; } std::size_t m_width, m_height; CopiedString m_shader; PatchControlArray m_ctrl; bool m_patchDef3; std::size_t m_subdivisions_x; std::size_t m_subdivisions_y; }; public: class Observer { public: virtual void allocate( std::size_t size ) = 0; }; private: typedef UniqueSet Observers; Observers m_observers; scene::Node* m_node; AABB m_aabb_local; // local bbox CopiedString m_shader; Shader* m_state; std::size_t m_width; std::size_t m_height; public: bool m_patchDef3; std::size_t m_subdivisions_x; std::size_t m_subdivisions_y; private: UndoObserver* m_undoable_observer; MapFile* m_map; // dynamically allocated array of control points, size is m_width*m_height PatchControlArray m_ctrl; PatchControlArray m_ctrlTransformed; PatchTesselation m_tess; RenderablePatchSolid m_render_solid; RenderablePatchWireframe m_render_wireframe; RenderablePatchFixedWireframe m_render_wireframe_fixed; static Shader* m_state_ctrl; static Shader* m_state_lattice; VertexBuffer m_ctrl_vertices; RenderableVertexBuffer m_render_ctrl; IndexBuffer m_lattice_indices; RenderableIndexBuffer m_render_lattice; bool m_bOverlay; bool m_transformChanged; Callback m_evaluateTransform; Callback m_boundsChanged; void construct(){ m_bOverlay = false; m_width = m_height = 0; m_patchDef3 = false; m_subdivisions_x = 0; m_subdivisions_y = 0; check_shader(); captureShader(); m_xml_state.push_back( xml_state_t::eDefault ); } public: Callback m_lightsChanged; static int m_CycleCapIndex; // = 0; static EPatchType m_type; STRING_CONSTANT( Name, "Patch" ); Patch( scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged ) : m_node( &node ), m_shader( texdef_name_default() ), m_state( 0 ), m_undoable_observer( 0 ), m_map( 0 ), m_render_solid( m_tess ), m_render_wireframe( m_tess ), m_render_wireframe_fixed( m_tess ), m_render_ctrl( GL_POINTS, m_ctrl_vertices ), m_render_lattice( GL_LINES, m_lattice_indices, m_ctrl_vertices ), m_transformChanged( false ), m_evaluateTransform( evaluateTransform ), m_boundsChanged( boundsChanged ){ construct(); } Patch( const Patch& other, scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged ) : m_node( &node ), m_shader( texdef_name_default() ), m_state( 0 ), m_undoable_observer( 0 ), m_map( 0 ), m_render_solid( m_tess ), m_render_wireframe( m_tess ), m_render_wireframe_fixed( m_tess ), m_render_ctrl( GL_POINTS, m_ctrl_vertices ), m_render_lattice( GL_LINES, m_lattice_indices, m_ctrl_vertices ), m_transformChanged( false ), m_evaluateTransform( evaluateTransform ), m_boundsChanged( boundsChanged ){ construct(); m_patchDef3 = other.m_patchDef3; m_subdivisions_x = other.m_subdivisions_x; m_subdivisions_y = other.m_subdivisions_y; setDims( other.m_width, other.m_height ); copy_ctrl( m_ctrl.data(), other.m_ctrl.data(), other.m_ctrl.data() + ( m_width * m_height ) ); SetShader( other.m_shader.c_str() ); controlPointsChanged(); } Patch( const Patch& other ) : XMLImporter( other ), XMLExporter( other ), TransformNode( other ), Bounded( other ), Cullable( other ), Snappable(), Undoable( other ), Filterable( other ), Nameable( other ), m_state( 0 ), m_undoable_observer( 0 ), m_map( 0 ), m_render_solid( m_tess ), m_render_wireframe( m_tess ), m_render_wireframe_fixed( m_tess ), m_render_ctrl( GL_POINTS, m_ctrl_vertices ), m_render_lattice( GL_LINES, m_lattice_indices, m_ctrl_vertices ), m_transformChanged( false ), m_evaluateTransform( other.m_evaluateTransform ), m_boundsChanged( other.m_boundsChanged ){ m_bOverlay = false; m_patchDef3 = other.m_patchDef3; m_subdivisions_x = other.m_subdivisions_x; m_subdivisions_y = other.m_subdivisions_y; setDims( other.m_width, other.m_height ); copy_ctrl( m_ctrl.data(), other.m_ctrl.data(), other.m_ctrl.data() + ( m_width * m_height ) ); SetShader( other.m_shader.c_str() ); controlPointsChanged(); } ~Patch(){ BezierCurveTreeArray_deleteAll( m_tess.m_curveTreeU ); BezierCurveTreeArray_deleteAll( m_tess.m_curveTreeV ); releaseShader(); ASSERT_MESSAGE( m_observers.empty(), "Patch::~Patch: observers still attached" ); } InstanceCounter m_instanceCounter; void instanceAttach( const scene::Path& path ){ if ( ++m_instanceCounter.m_count == 1 ) { m_state->incrementUsed(); m_map = path_find_mapfile( path.begin(), path.end() ); m_undoable_observer = GlobalUndoSystem().observer( this ); GlobalFilterSystem().registerFilterable( *this ); } 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 ) { m_map = 0; m_undoable_observer = 0; GlobalUndoSystem().release( this ); GlobalFilterSystem().unregisterFilterable( *this ); m_state->decrementUsed(); } } const char* name() const { return "patch"; } void attach( const NameCallback& callback ){ } void detach( const NameCallback& callback ){ } void attach( Observer* observer ){ observer->allocate( m_width * m_height ); m_observers.insert( observer ); } void detach( Observer* observer ){ m_observers.erase( observer ); } void updateFiltered(){ if ( m_node != 0 ) { if ( patch_filtered( *this ) ) { m_node->enable( scene::Node::eFiltered ); } else { m_node->disable( scene::Node::eFiltered ); } } } void onAllocate( std::size_t size ){ for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i ) { ( *i )->allocate( size ); } } const Matrix4& localToParent() const { return g_matrix4_identity; } const AABB& localAABB() const { return m_aabb_local; } VolumeIntersectionValue intersectVolume( const VolumeTest& test, const Matrix4& localToWorld ) const { return test.TestAABB( m_aabb_local, localToWorld ); } void render_solid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const { renderer.SetState( m_state, Renderer::eFullMaterials ); renderer.addRenderable( m_render_solid, localToWorld ); } void render_wireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const { renderer.SetState( m_state, Renderer::eFullMaterials ); if ( m_patchDef3 ) { renderer.addRenderable( m_render_wireframe_fixed, localToWorld ); } else { renderer.addRenderable( m_render_wireframe, localToWorld ); } } void render_component( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const { renderer.SetState( m_state_lattice, Renderer::eWireframeOnly ); renderer.SetState( m_state_lattice, Renderer::eFullMaterials ); renderer.addRenderable( m_render_lattice, localToWorld ); renderer.SetState( m_state_ctrl, Renderer::eWireframeOnly ); renderer.SetState( m_state_ctrl, Renderer::eFullMaterials ); renderer.addRenderable( m_render_ctrl, localToWorld ); } void testSelect( Selector& selector, SelectionTest& test ){ SelectionIntersection best; IndexPointer::index_type* pIndex = m_tess.m_indices.data(); for ( std::size_t s = 0; s < m_tess.m_numStrips; s++ ) { test.TestQuadStrip( vertexpointer_arbitrarymeshvertex( m_tess.m_vertices.data() ), IndexPointer( pIndex, m_tess.m_lenStrips ), best ); pIndex += m_tess.m_lenStrips; } if ( best.valid() ) { selector.addIntersection( best ); } } void transform( const Matrix4& matrix ){ for ( PatchControlIter i = m_ctrlTransformed.data(); i != m_ctrlTransformed.data() + m_ctrlTransformed.size(); ++i ) { matrix4_transform_point( matrix, ( *i ).m_vertex ); } if ( matrix4_handedness( matrix ) == MATRIX4_LEFTHANDED ) { PatchControlArray_invert( m_ctrlTransformed, m_width, m_height ); } UpdateCachedData(); } void transformChanged(){ m_transformChanged = true; m_lightsChanged(); SceneChangeNotify(); } typedef MemberCaller TransformChangedCaller; void evaluateTransform(){ if ( m_transformChanged ) { m_transformChanged = false; revertTransform(); m_evaluateTransform(); } } void revertTransform(){ m_ctrlTransformed = m_ctrl; } void freezeTransform(){ undoSave(); evaluateTransform(); ASSERT_MESSAGE( m_ctrlTransformed.size() == m_ctrl.size(), "Patch::freeze: size mismatch" ); std::copy( m_ctrlTransformed.begin(), m_ctrlTransformed.end(), m_ctrl.begin() ); } void controlPointsChanged(){ transformChanged(); evaluateTransform(); UpdateCachedData(); } bool isValid() const; void snapto( float snap ){ undoSave(); for ( PatchControlIter i = m_ctrl.data(); i != m_ctrl.data() + m_ctrl.size(); ++i ) { vector3_snap( ( *i ).m_vertex, snap ); } controlPointsChanged(); } void RenderDebug( RenderStateFlags state ) const; void RenderNormals( RenderStateFlags state ) const; void pushElement( const XMLElement& element ){ switch ( m_xml_state.back().state() ) { case xml_state_t::eDefault: ASSERT_MESSAGE( string_equal( element.name(), "patch" ), "parse error" ); m_xml_state.push_back( xml_state_t::ePatch ); break; case xml_state_t::ePatch: if ( string_equal( element.name(), "matrix" ) ) { setDims( atoi( element.attribute( "width" ) ), atoi( element.attribute( "height" ) ) ); m_xml_state.push_back( xml_state_t::eMatrix ); } else if ( string_equal( element.name(), "shader" ) ) { m_xml_state.push_back( xml_state_t::eShader ); } break; default: ERROR_MESSAGE( "parse error" ); } } void popElement( const char* name ){ switch ( m_xml_state.back().state() ) { case xml_state_t::eDefault: ERROR_MESSAGE( "parse error" ); break; case xml_state_t::ePatch: break; case xml_state_t::eMatrix: { StringTokeniser content( m_xml_state.back().content() ); for ( PatchControlIter i = m_ctrl.data(), end = m_ctrl.data() + m_ctrl.size(); i != end; ++i ) { ( *i ).m_vertex[0] = string_read_float( content.getToken() ); ( *i ).m_vertex[1] = string_read_float( content.getToken() ); ( *i ).m_vertex[2] = string_read_float( content.getToken() ); ( *i ).m_texcoord[0] = string_read_float( content.getToken() ); ( *i ).m_texcoord[1] = string_read_float( content.getToken() ); } controlPointsChanged(); } break; case xml_state_t::eShader: { SetShader( m_xml_state.back().content() ); } break; default: ERROR_MESSAGE( "parse error" ); } ASSERT_MESSAGE( !m_xml_state.empty(), "popping empty stack" ); m_xml_state.pop_back(); } std::size_t write( const char* buffer, std::size_t length ){ switch ( m_xml_state.back().state() ) { case xml_state_t::eDefault: break; case xml_state_t::ePatch: break; case xml_state_t::eMatrix: case xml_state_t::eShader: return m_xml_state.back().write( buffer, length ); break; default: ERROR_MESSAGE( "parse error" ); } return length; } void exportXML( XMLImporter& importer ){ StaticElement patchElement( "patch" ); importer.pushElement( patchElement ); { const StaticElement element( "shader" ); importer.pushElement( element ); importer.write( m_shader.c_str(), strlen( m_shader.c_str() ) ); importer.popElement( element.name() ); } { char width[16], height[16]; sprintf( width, "%u", Unsigned( m_width ) ); sprintf( height, "%u", Unsigned( m_height ) ); StaticElement element( "matrix" ); element.insertAttribute( "width", width ); element.insertAttribute( "height", height ); importer.pushElement( element ); { for ( PatchControlIter i = m_ctrl.data(), end = m_ctrl.data() + m_ctrl.size(); i != end; ++i ) { importer << ( *i ).m_vertex[0] << ' ' << ( *i ).m_vertex[1] << ' ' << ( *i ).m_vertex[2] << ' ' << ( *i ).m_texcoord[0] << ' ' << ( *i ).m_texcoord[1]; } } importer.popElement( element.name() ); } importer.popElement( patchElement.name() ); } void UpdateCachedData(); const char *GetShader() const { return m_shader.c_str(); } void SetShader( const char* name ){ ASSERT_NOTNULL( name ); if ( shader_equal( m_shader.c_str(), name ) ) { return; } undoSave(); if ( m_instanceCounter.m_count != 0 ) { m_state->decrementUsed(); } releaseShader(); m_shader = name; captureShader(); if ( m_instanceCounter.m_count != 0 ) { m_state->incrementUsed(); } check_shader(); Patch_textureChanged(); } int getShaderFlags() const { if ( m_state != 0 ) { return m_state->getFlags(); } return 0; } typedef PatchControl* iterator; typedef const PatchControl* const_iterator; iterator begin(){ return m_ctrl.data(); } const_iterator begin() const { return m_ctrl.data(); } iterator end(){ return m_ctrl.data() + m_ctrl.size(); } const_iterator end() const { return m_ctrl.data() + m_ctrl.size(); } PatchControlArray& getControlPoints(){ return m_ctrl; } PatchControlArray& getControlPointsTransformed(){ return m_ctrlTransformed; } void setDims( std::size_t w, std::size_t h ); std::size_t getWidth() const { return m_width; } std::size_t getHeight() const { return m_height; } PatchControl& ctrlAt( std::size_t row, std::size_t col ){ return m_ctrl[row * m_width + col]; } const PatchControl& ctrlAt( std::size_t row, std::size_t col ) const { return m_ctrl[row * m_width + col]; } void ConstructPrefab( const AABB& aabb, EPatchPrefab eType, int axis, std::size_t width = 3, std::size_t height = 3 ); void constructPlane( const AABB& aabb, int axis, std::size_t width, std::size_t height ); void InvertMatrix(); void TransposeMatrix(); void Redisperse( EMatrixMajor mt ); void Smooth( EMatrixMajor mt ); void InsertRemove( bool bInsert, bool bColumn, bool bFirst ); Patch* MakeCap( Patch* patch, EPatchCap eType, EMatrixMajor mt, bool bFirst ); void ConstructSeam( EPatchCap eType, Vector3* p, std::size_t width ); void FlipTexture( int nAxis ); void TranslateTexture( float s, float t ); void ScaleTexture( float s, float t ); void RotateTexture( float angle ); void SetTextureRepeat( float s, float t ); // call with s=1 t=1 for FIT void CapTexture(); void NaturalTexture(); void ProjectTexture( int nAxis ); void undoSave(){ if ( m_map != 0 ) { m_map->changed(); } if ( m_undoable_observer != 0 ) { m_undoable_observer->save( this ); } } UndoMemento* exportState() const { return new SavedState( m_width, m_height, m_ctrl, m_shader.c_str(), m_patchDef3, m_subdivisions_x, m_subdivisions_y ); } void importState( const UndoMemento* state ){ undoSave(); const SavedState& other = *( static_cast( state ) ); // begin duplicate of SavedState copy constructor, needs refactoring // copy construct { m_width = other.m_width; m_height = other.m_height; SetShader( other.m_shader.c_str() ); m_ctrl = other.m_ctrl; onAllocate( m_ctrl.size() ); m_patchDef3 = other.m_patchDef3; m_subdivisions_x = other.m_subdivisions_x; m_subdivisions_y = other.m_subdivisions_y; } // end duplicate code Patch_textureChanged(); controlPointsChanged(); } static void constructStatic( EPatchType type ){ Patch::m_type = type; Patch::m_state_ctrl = GlobalShaderCache().capture( "$POINT" ); Patch::m_state_lattice = GlobalShaderCache().capture( "$LATTICE" ); } static void destroyStatic(){ GlobalShaderCache().release( "$LATTICE" ); GlobalShaderCache().release( "$POINT" ); } private: void captureShader(){ m_state = GlobalShaderCache().capture( m_shader.c_str() ); } void releaseShader(){ GlobalShaderCache().release( m_shader.c_str() ); } void check_shader(){ if ( !shader_valid( GetShader() ) ) { globalErrorStream() << "patch has invalid texture name: '" << GetShader() << "'\n"; } } void InsertPoints( EMatrixMajor mt, bool bFirst ); void RemovePoints( EMatrixMajor mt, bool bFirst ); void AccumulateBBox(); void TesselateSubMatrixFixed( ArbitraryMeshVertex * vertices, std::size_t strideX, std::size_t strideY, unsigned int nFlagsX, unsigned int nFlagsY, PatchControl * subMatrix[3][3] ); // uses binary trees representing bezier curves to recursively tesselate a bezier sub-patch void TesselateSubMatrix( const BezierCurveTree *BX, const BezierCurveTree *BY, std::size_t offStartX, std::size_t offStartY, std::size_t offEndX, std::size_t offEndY, std::size_t nFlagsX, std::size_t nFlagsY, Vector3& left, Vector3& mid, Vector3& right, Vector2& texLeft, Vector2& texMid, Vector2& texRight, bool bTranspose ); // tesselates the entire surface void BuildTesselationCurves( EMatrixMajor major ); void accumulateVertexTangentSpace( std::size_t index, Vector3 tangentX[6], Vector3 tangentY[6], Vector2 tangentS[6], Vector2 tangentT[6], std::size_t index0, std::size_t index1 ); void BuildVertexArray(); }; inline bool Patch_importHeader( Patch& patch, Tokeniser& tokeniser ){ tokeniser.nextLine(); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "{" ) ); return true; } inline bool Patch_importShader( Patch& patch, Tokeniser& tokeniser ){ // parse shader name tokeniser.nextLine(); const char* texture = tokeniser.getToken(); if ( texture == 0 ) { Tokeniser_unexpectedError( tokeniser, texture, "#texture-name" ); return false; } if ( string_equal( texture, "NULL" ) ) { patch.SetShader( texdef_name_default() ); } else { StringOutputStream shader( string_length( GlobalTexturePrefix_get() ) + string_length( texture ) ); shader << GlobalTexturePrefix_get() << texture; patch.SetShader( shader.c_str() ); } return true; } inline bool PatchDoom3_importShader( Patch& patch, Tokeniser& tokeniser ){ // parse shader name tokeniser.nextLine(); const char *shader = tokeniser.getToken(); if ( shader == 0 ) { Tokeniser_unexpectedError( tokeniser, shader, "#shader-name" ); return false; } if ( string_equal( shader, "_emptyname" ) ) { shader = texdef_name_default(); } patch.SetShader( shader ); return true; } inline bool Patch_importParams( Patch& patch, Tokeniser& tokeniser ){ tokeniser.nextLine(); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); // parse matrix dimensions { std::size_t c, r; RETURN_FALSE_IF_FAIL( Tokeniser_getSize( tokeniser, c ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getSize( tokeniser, r ) ); patch.setDims( c, r ); } if ( patch.m_patchDef3 ) { RETURN_FALSE_IF_FAIL( Tokeniser_getSize( tokeniser, patch.m_subdivisions_x ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getSize( tokeniser, patch.m_subdivisions_y ) ); } // ignore contents/flags/value int tmp; RETURN_FALSE_IF_FAIL( Tokeniser_getInteger( tokeniser, tmp ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getInteger( tokeniser, tmp ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getInteger( tokeniser, tmp ) ); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); return true; } inline bool Patch_importMatrix( Patch& patch, Tokeniser& tokeniser ){ // parse matrix tokeniser.nextLine(); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); { for ( std::size_t c = 0; c < patch.getWidth(); c++ ) { tokeniser.nextLine(); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); for ( std::size_t r = 0; r < patch.getHeight(); r++ ) { RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, patch.ctrlAt( r,c ).m_vertex[0] ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, patch.ctrlAt( r,c ).m_vertex[1] ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, patch.ctrlAt( r,c ).m_vertex[2] ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, patch.ctrlAt( r,c ).m_texcoord[0] ) ); RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, patch.ctrlAt( r,c ).m_texcoord[1] ) ); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); } RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); } } tokeniser.nextLine(); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) ); return true; } inline bool Patch_importFooter( Patch& patch, Tokeniser& tokeniser ){ patch.controlPointsChanged(); tokeniser.nextLine(); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "}" ) ); tokeniser.nextLine(); RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "}" ) ); return true; } class PatchTokenImporter : public MapImporter { Patch& m_patch; public: PatchTokenImporter( Patch& patch ) : m_patch( patch ){ } bool importTokens( Tokeniser& tokeniser ){ RETURN_FALSE_IF_FAIL( Patch_importHeader( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( Patch_importShader( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( Patch_importParams( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( Patch_importMatrix( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( Patch_importFooter( m_patch, tokeniser ) ); return true; } }; class PatchDoom3TokenImporter : public MapImporter { Patch& m_patch; public: PatchDoom3TokenImporter( Patch& patch ) : m_patch( patch ){ } bool importTokens( Tokeniser& tokeniser ){ RETURN_FALSE_IF_FAIL( Patch_importHeader( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( PatchDoom3_importShader( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( Patch_importParams( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( Patch_importMatrix( m_patch, tokeniser ) ); RETURN_FALSE_IF_FAIL( Patch_importFooter( m_patch, tokeniser ) ); return true; } }; inline void Patch_exportHeader( const Patch& patch, TokenWriter& writer ){ writer.writeToken( "{" ); writer.nextLine(); writer.writeToken( patch.m_patchDef3 ? "patchDef3" : "patchDef2" ); writer.nextLine(); writer.writeToken( "{" ); writer.nextLine(); } inline void Patch_exportShader( const Patch& patch, TokenWriter& writer ){ // write shader name if ( *( shader_get_textureName( patch.GetShader() ) ) == '\0' ) { writer.writeToken( "NULL" ); } else { writer.writeToken( shader_get_textureName( patch.GetShader() ) ); } writer.nextLine(); } inline void PatchDoom3_exportShader( const Patch& patch, TokenWriter& writer ){ // write shader name if ( *( shader_get_textureName( patch.GetShader() ) ) == '\0' ) { writer.writeString( "_emptyname" ); } else { writer.writeString( patch.GetShader() ); } writer.nextLine(); } inline void Patch_exportParams( const Patch& patch, TokenWriter& writer ){ // write matrix dimensions writer.writeToken( "(" ); writer.writeUnsigned( patch.getWidth() ); writer.writeUnsigned( patch.getHeight() ); if ( patch.m_patchDef3 ) { writer.writeUnsigned( patch.m_subdivisions_x ); writer.writeUnsigned( patch.m_subdivisions_y ); } writer.writeInteger( 0 ); writer.writeInteger( 0 ); writer.writeInteger( 0 ); writer.writeToken( ")" ); writer.nextLine(); } inline void Patch_exportMatrix( const Patch& patch, TokenWriter& writer ){ // write matrix writer.writeToken( "(" ); writer.nextLine(); for ( std::size_t c = 0; c < patch.getWidth(); c++ ) { writer.writeToken( "(" ); for ( std::size_t r = 0; r < patch.getHeight(); r++ ) { writer.writeToken( "(" ); writer.writeFloat( patch.ctrlAt( r,c ).m_vertex[0] ); writer.writeFloat( patch.ctrlAt( r,c ).m_vertex[1] ); writer.writeFloat( patch.ctrlAt( r,c ).m_vertex[2] ); writer.writeFloat( patch.ctrlAt( r,c ).m_texcoord[0] ); writer.writeFloat( patch.ctrlAt( r,c ).m_texcoord[1] ); writer.writeToken( ")" ); } writer.writeToken( ")" ); writer.nextLine(); } writer.writeToken( ")" ); writer.nextLine(); } inline void Patch_exportFooter( const Patch& patch, TokenWriter& writer ){ writer.writeToken( "}" ); writer.nextLine(); writer.writeToken( "}" ); writer.nextLine(); } class PatchTokenExporter : public MapExporter { const Patch& m_patch; public: PatchTokenExporter( Patch& patch ) : m_patch( patch ){ } void exportTokens( TokenWriter& writer ) const { Patch_exportHeader( m_patch, writer ); Patch_exportShader( m_patch, writer ); Patch_exportParams( m_patch, writer ); Patch_exportMatrix( m_patch, writer ); Patch_exportFooter( m_patch, writer ); } }; class PatchDoom3TokenExporter : public MapExporter { const Patch& m_patch; public: PatchDoom3TokenExporter( Patch& patch ) : m_patch( patch ){ } void exportTokens( TokenWriter& writer ) const { Patch_exportHeader( m_patch, writer ); PatchDoom3_exportShader( m_patch, writer ); Patch_exportParams( m_patch, writer ); Patch_exportMatrix( m_patch, writer ); Patch_exportFooter( m_patch, writer ); } }; class PatchControlInstance { public: PatchControl* m_ctrl; ObservedSelectable m_selectable; PatchControlInstance( PatchControl* ctrl, const SelectionChangeCallback& observer ) : m_ctrl( ctrl ), m_selectable( observer ){ } void testSelect( Selector& selector, SelectionTest& test ){ SelectionIntersection best; test.TestPoint( m_ctrl->m_vertex, best ); if ( best.valid() ) { Selector_add( selector, m_selectable, best ); } } void snapto( float snap ){ vector3_snap( m_ctrl->m_vertex, snap ); } }; class PatchInstance : public Patch::Observer, 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; } }; Patch& m_patch; typedef std::vector PatchControlInstances; PatchControlInstances m_ctrl_instances; ObservedSelectable m_selectable; DragPlanes m_dragPlanes; mutable RenderablePointVector m_render_selected; mutable AABB m_aabb_component; static Shader* m_state_selpoint; const LightList* m_lightList; TransformModifier m_transform; public: typedef LazyStatic StaticTypeCasts; void lightsChanged(){ m_lightList->lightsChanged(); } typedef MemberCaller LightsChangedCaller; STRING_CONSTANT( Name, "PatchInstance" ); PatchInstance( const scene::Path& path, scene::Instance* parent, Patch& patch ) : Instance( path, parent, this, StaticTypeCasts::instance().get() ), m_patch( patch ), m_selectable( SelectedChangedCaller( *this ) ), m_dragPlanes( SelectedChangedComponentCaller( *this ) ), m_render_selected( GL_POINTS ), m_transform( Patch::TransformChangedCaller( m_patch ), ApplyTransformCaller( *this ) ){ m_patch.instanceAttach( Instance::path() ); m_patch.attach( this ); m_lightList = &GlobalShaderCache().attach( *this ); m_patch.m_lightsChanged = LightsChangedCaller( *this ); Instance::setTransformChangedCallback( LightsChangedCaller( *this ) ); } ~PatchInstance(){ Instance::setTransformChangedCallback( Callback() ); m_patch.m_lightsChanged = Callback(); GlobalShaderCache().detach( *this ); m_patch.detach( this ); m_patch.instanceDetach( Instance::path() ); } 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; Patch& getPatch(){ return m_patch; } Bounded& get( NullType){ return m_patch; } Cullable& get( NullType){ return m_patch; } Transformable& get( NullType){ return m_transform; } static void constructStatic(){ m_state_selpoint = GlobalShaderCache().capture( "$SELPOINT" ); } static void destroyStatic(){ GlobalShaderCache().release( "$SELPOINT" ); } void allocate( std::size_t size ){ m_ctrl_instances.clear(); m_ctrl_instances.reserve( size ); for ( Patch::iterator i = m_patch.begin(); i != m_patch.end(); ++i ) { m_ctrl_instances.push_back( PatchControlInstance( &( *i ), SelectedChangedComponentCaller( *this ) ) ); } } void setSelected( bool select ){ m_selectable.setSelected( select ); } bool isSelected() const { return m_selectable.isSelected(); } void update_selected() const { m_render_selected.clear(); Patch::iterator ctrl = m_patch.getControlPointsTransformed().begin(); for ( PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i, ++ctrl ) { if ( ( *i ).m_selectable.isSelected() ) { const Colour4b colour_selected( 0, 0, 255, 255 ); m_render_selected.push_back( PointVertex( reinterpret_cast( ( *ctrl ).m_vertex ), colour_selected ) ); } } } #if 0 void render( Renderer& renderer, const VolumeTest& volume ) const { if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent && m_selectable.isSelected() ) { renderer.Highlight( Renderer::eFace, false ); m_patch.render( renderer, volume, localToWorld() ); if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eVertex ) { renderer.Highlight( Renderer::ePrimitive, false ); m_patch.render_component( renderer, volume, localToWorld() ); renderComponentsSelected( renderer, volume ); } } else{ m_patch.render( renderer, volume, localToWorld() ); } } #endif void renderSolid( Renderer& renderer, const VolumeTest& volume ) const { m_patch.evaluateTransform(); renderer.setLights( *m_lightList ); m_patch.render_solid( renderer, volume, localToWorld() ); renderComponentsSelected( renderer, volume ); } void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const { m_patch.evaluateTransform(); m_patch.render_wireframe( renderer, volume, localToWorld() ); renderComponentsSelected( renderer, volume ); } void renderComponentsSelected( Renderer& renderer, const VolumeTest& volume ) const { m_patch.evaluateTransform(); 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_patch.evaluateTransform(); if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eVertex ) { m_patch.render_component( renderer, volume, localToWorld() ); } } void testSelect( Selector& selector, SelectionTest& test ){ test.BeginMesh( localToWorld(), true ); m_patch.testSelect( selector, test ); } void selectCtrl( bool select ){ for ( PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i ) { ( *i ).m_selectable.setSelected( select ); } } bool isSelectedComponents() const { for ( PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i ) { if ( ( *i ).m_selectable.isSelected() ) { return true; } } return false; } void setSelectedComponents( bool select, SelectionSystem::EComponentMode mode ){ if ( mode == SelectionSystem::eVertex ) { selectCtrl( select ); } else if ( mode == SelectionSystem::eFace ) { m_dragPlanes.setSelected( select ); } } const AABB& getSelectedComponentsBounds() const { m_aabb_component = AABB(); for ( PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i ) { if ( ( *i ).m_selectable.isSelected() ) { aabb_extend_by_point_safe( m_aabb_component, ( *i ).m_ctrl->m_vertex ); } } return m_aabb_component; } void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ){ test.BeginMesh( localToWorld() ); switch ( mode ) { case SelectionSystem::eVertex: { for ( PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i ) { ( *i ).testSelect( selector, test ); } } break; default: break; } } bool selectedVertices(){ for ( PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i ) { if ( ( *i ).m_selectable.isSelected() ) { return true; } } return false; } void transformComponents( const Matrix4& matrix ){ if ( selectedVertices() ) { PatchControlIter ctrl = m_patch.getControlPointsTransformed().begin(); for ( PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i, ++ctrl ) { if ( ( *i ).m_selectable.isSelected() ) { matrix4_transform_point( matrix, ( *ctrl ).m_vertex ); } } m_patch.UpdateCachedData(); } if ( m_dragPlanes.isSelected() ) { // this should only be true when the transform is a pure translation. m_patch.transform( m_dragPlanes.evaluateTransform( vector4_to_vector3( matrix.t() ) ) ); } } void selectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){ test.BeginMesh( localToWorld() ); m_dragPlanes.selectPlanes( m_patch.localAABB(), selector, test, selectedPlaneCallback ); } void selectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes ){ m_dragPlanes.selectReversedPlanes( m_patch.localAABB(), selector, selectedPlanes ); } void snapComponents( float snap ){ if ( selectedVertices() ) { m_patch.undoSave(); for ( PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i ) { if ( ( *i ).m_selectable.isSelected() ) { ( *i ).snapto( snap ); } } m_patch.controlPointsChanged(); } } void evaluateTransform(){ Matrix4 matrix( m_transform.calculateTransform() ); if ( m_transform.getType() == TRANSFORM_PRIMITIVE ) { m_patch.transform( matrix ); } else { transformComponents( matrix ); } } void applyTransform(){ m_patch.revertTransform(); evaluateTransform(); m_patch.freezeTransform(); } typedef MemberCaller ApplyTransformCaller; bool testLight( const RendererLight& light ) const { return light.testAABB( worldAABB() ); } }; template class PatchNode : public scene::Node::Symbiot, public scene::Instantiable, public scene::Cloneable { typedef PatchNode Self; class TypeCasts { InstanceTypeCastTable m_casts; public: TypeCasts(){ NodeStaticCast::install( m_casts ); NodeStaticCast::install( m_casts ); NodeContainedCast::install( m_casts ); NodeContainedCast::install( m_casts ); NodeContainedCast::install( m_casts ); NodeContainedCast::install( m_casts ); NodeContainedCast::install( m_casts ); NodeContainedCast::install( m_casts ); NodeContainedCast::install( m_casts ); NodeContainedCast::install( m_casts ); } InstanceTypeCastTable& get(){ return m_casts; } }; scene::Node m_node; InstanceSet m_instances; Patch m_patch; TokenImporter m_importMap; TokenExporter m_exportMap; public: typedef LazyStatic StaticTypeCasts; Snappable& get( NullType){ return m_patch; } TransformNode& get( NullType){ return m_patch; } Patch& get( NullType){ return m_patch; } XMLImporter& get( NullType){ return m_patch; } XMLExporter& get( NullType){ return m_patch; } MapImporter& get( NullType){ return m_importMap; } MapExporter& get( NullType){ return m_exportMap; } Nameable& get( NullType){ return m_patch; } PatchNode( bool patchDef3 = false ) : m_node( this, this, StaticTypeCasts::instance().get() ), m_patch( m_node, InstanceSetEvaluateTransform::Caller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ) ), m_importMap( m_patch ), m_exportMap( m_patch ){ m_patch.m_patchDef3 = patchDef3; } PatchNode( const PatchNode& other ) : scene::Node::Symbiot( other ), scene::Instantiable( other ), scene::Cloneable( other ), m_node( this, this, StaticTypeCasts::instance().get() ), m_patch( other.m_patch, m_node, InstanceSetEvaluateTransform::Caller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ) ), m_importMap( m_patch ), m_exportMap( m_patch ){ } void release(){ delete this; } scene::Node& node(){ return m_node; } Patch& get(){ return m_patch; } const Patch& get() const { return m_patch; } scene::Node& clone() const { return ( new PatchNode( *this ) )->node(); } scene::Instance* create( const scene::Path& path, scene::Instance* parent ){ return new PatchInstance( path, parent, m_patch ); } void forEachInstance( const scene::Instantiable::Visitor& visitor ){ m_instances.forEachInstance( visitor ); } void insert( scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance ){ m_instances.insert( observer, path, instance ); } scene::Instance* erase( scene::Instantiable::Observer* observer, const scene::Path& path ){ return m_instances.erase( observer, path ); } }; typedef PatchNode PatchNodeQuake3; typedef PatchNode PatchNodeDoom3; inline Patch* Node_getPatch( scene::Node& node ){ return NodeTypeCast::cast( node ); } inline PatchInstance* Instance_getPatch( scene::Instance& instance ){ return InstanceTypeCast::cast( instance ); } template class PatchSelectedVisitor : public SelectionSystem::Visitor { const Functor& m_functor; public: PatchSelectedVisitor( const Functor& functor ) : m_functor( functor ){ } void visit( scene::Instance& instance ) const { PatchInstance* patch = Instance_getPatch( instance ); if ( patch != 0 ) { m_functor( *patch ); } } }; template inline void Scene_forEachSelectedPatch( const Functor& functor ){ GlobalSelectionSystem().foreachSelected( PatchSelectedVisitor( functor ) ); } template class PatchVisibleSelectedVisitor : public SelectionSystem::Visitor { const Functor& m_functor; public: PatchVisibleSelectedVisitor( const Functor& functor ) : m_functor( functor ){ } void visit( scene::Instance& instance ) const { PatchInstance* patch = Instance_getPatch( instance ); if ( patch != 0 && instance.path().top().get().visible() ) { m_functor( *patch ); } } }; template inline void Scene_forEachVisibleSelectedPatchInstance( const Functor& functor ){ GlobalSelectionSystem().foreachSelected( PatchVisibleSelectedVisitor( functor ) ); } template class PatchForEachWalker : public scene::Graph::Walker { const Functor& m_functor; public: PatchForEachWalker( const Functor& functor ) : m_functor( functor ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { if ( path.top().get().visible() ) { Patch* patch = Node_getPatch( path.top() ); if ( patch != 0 ) { m_functor( *patch ); } } return true; } }; template inline void Scene_forEachVisiblePatch( const Functor& functor ){ GlobalSceneGraph().traverse( PatchForEachWalker( functor ) ); } template class PatchForEachSelectedWalker : public scene::Graph::Walker { const Functor& m_functor; public: PatchForEachSelectedWalker( const Functor& functor ) : m_functor( functor ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { if ( path.top().get().visible() ) { Patch* patch = Node_getPatch( path.top() ); if ( patch != 0 && Instance_getSelectable( instance )->isSelected() ) { m_functor( *patch ); } } return true; } }; template inline void Scene_forEachVisibleSelectedPatch( const Functor& functor ){ GlobalSceneGraph().traverse( PatchForEachSelectedWalker( functor ) ); } template class PatchForEachInstanceWalker : public scene::Graph::Walker { const Functor& m_functor; public: PatchForEachInstanceWalker( const Functor& functor ) : m_functor( functor ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { if ( path.top().get().visible() ) { PatchInstance* patch = Instance_getPatch( instance ); if ( patch != 0 ) { m_functor( *patch ); } } return true; } }; template inline void Scene_forEachVisiblePatchInstance( const Functor& functor ){ GlobalSceneGraph().traverse( PatchForEachInstanceWalker( functor ) ); } #endif