auto str = StringOutputStream()(bla) use form was not doing copy elision or move, but copy
1938 lines
54 KiB
C++
1938 lines
54 KiB
C++
/*
|
|
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
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
/// \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 <set>
|
|
#include <limits>
|
|
|
|
#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"
|
|
|
|
#include "brush_primit.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 class EPatchCap
|
|
{
|
|
Bevel,
|
|
EndCap,
|
|
IBevel,
|
|
IEndCap,
|
|
Cylinder,
|
|
};
|
|
|
|
enum class EPatchPrefab
|
|
{
|
|
Plane,
|
|
Bevel,
|
|
EndCap,
|
|
Cylinder,
|
|
DenseCylinder,
|
|
VeryDenseCylinder,
|
|
SqCylinder,
|
|
Cone,
|
|
Sphere,
|
|
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<std::size_t>::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<BezierCurveTree*>& curveTrees ){
|
|
for ( Array<BezierCurveTree*>::iterator i = curveTrees.begin(); i != curveTrees.end(); ++i )
|
|
{
|
|
BezierCurveTree_Delete( *i );
|
|
}
|
|
}
|
|
|
|
inline void PatchControlArray_invert( Array<PatchControl>& ctrl, std::size_t width, std::size_t height ){
|
|
Array<PatchControl> 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<ArbitraryMeshVertex> m_vertices;
|
|
Array<RenderIndex> m_indices;
|
|
std::size_t m_numStrips;
|
|
std::size_t m_lenStrips;
|
|
|
|
Array<std::size_t> m_arrayWidth;
|
|
std::size_t m_nArrayWidth;
|
|
Array<std::size_t> m_arrayHeight;
|
|
std::size_t m_nArrayHeight;
|
|
|
|
Array<BezierCurveTree*> m_curveTreeU;
|
|
Array<BezierCurveTree*> 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
|
|
gl().glVertexPointer( 3, GL_FLOAT, 0, 0 );
|
|
gl().glDrawArrays( GL_TRIANGLE_FAN, 0, 0 );
|
|
#endif
|
|
|
|
std::size_t n = 0;
|
|
gl().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 )
|
|
{
|
|
gl().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] ) ) {
|
|
gl().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 )
|
|
{
|
|
gl().glVertexPointer( 3, GL_FLOAT, GLsizei( n ), &p->vertex );
|
|
gl().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] ) ) {
|
|
gl().glVertexPointer( 3, GL_FLOAT, GLsizei( n ), &( m_tess.m_vertices.data() + ( m_tess.m_curveTreeU[i]->index ) )->vertex );
|
|
gl().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 {
|
|
gl().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 )
|
|
{
|
|
gl().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 ) {
|
|
gl().glNormalPointer( GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->normal );
|
|
gl().glVertexAttribPointer( c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->texcoord );
|
|
gl().glVertexAttribPointer( c_attr_Tangent, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->tangent );
|
|
gl().glVertexAttribPointer( c_attr_Binormal, 3, GL_FLOAT, 0, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->bitangent );
|
|
}
|
|
else
|
|
{
|
|
gl().glNormalPointer( GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->normal );
|
|
gl().glTexCoordPointer( 2, GL_FLOAT, sizeof( ArbitraryMeshVertex ), &m_tess.m_vertices.data()->texcoord );
|
|
}
|
|
gl().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 )
|
|
{
|
|
gl().glDrawElements( GL_QUAD_STRIP, GLsizei( m_tess.m_lenStrips ), RenderIndexTypeID, strip_indices );
|
|
}
|
|
}
|
|
|
|
#if defined( _DEBUG ) && !defined( _DEBUG_QUICKER )
|
|
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<xml_state_t> m_xml_state;
|
|
|
|
typedef Array<PatchControl> 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<Observer*> 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<PointVertex> 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 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 {
|
|
const_cast<Patch*>( this )->evaluateTransform(); //experimental! fixing extra sceneChangeNotify call during scene rendering
|
|
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_boundsChanged(); //experimental! fixing extra sceneChangeNotify call during scene rendering
|
|
m_lightsChanged();
|
|
SceneChangeNotify();
|
|
}
|
|
typedef MemberCaller<Patch, &Patch::transformChanged> TransformChangedCaller;
|
|
|
|
void evaluateTransform(){
|
|
if ( m_transformChanged ) {
|
|
//m_transformChanged = false;
|
|
revertTransform();
|
|
m_evaluateTransform();
|
|
m_transformChanged = false; //experimental! fixing extra sceneChangeNotify call during scene rendering
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
const Shader* getShader(){
|
|
ASSERT_MESSAGE( m_state != 0, "patch shader is not realised" );
|
|
return m_state;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Same as above, just for const arguments
|
|
const PatchControlArray& getControlPoints() const {
|
|
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();
|
|
Vector3 Calculate_AvgNormal() const;
|
|
void Calculate_AvgAxes( Vector3& wDir, Vector3& hDir ) const;
|
|
void ProjectTexture( TextureProjection projection, const Vector3& normal );
|
|
void ProjectTexture( const texdef_t& texdef, const Vector3* direction );
|
|
void createThickenedOpposite(const Patch& sourcePatch, const float thickness, const int axis, bool& no12, bool& no34 );
|
|
void createThickenedWall(const Patch& sourcePatch, const Patch& targetPatch, const int wallIndex);
|
|
|
|
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<const SavedState*>( 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 ( !texdef_name_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
|
|
{
|
|
patch.SetShader( StringStream<64>( GlobalTexturePrefix_get(), texture ) );
|
|
}
|
|
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<PatchInstance, Selectable>::install( m_casts );
|
|
InstanceContainedCast<PatchInstance, Bounded>::install( m_casts );
|
|
InstanceContainedCast<PatchInstance, Cullable>::install( m_casts );
|
|
InstanceStaticCast<PatchInstance, Renderable>::install( m_casts );
|
|
InstanceStaticCast<PatchInstance, SelectionTestable>::install( m_casts );
|
|
InstanceStaticCast<PatchInstance, ComponentSelectionTestable>::install( m_casts );
|
|
InstanceStaticCast<PatchInstance, ComponentEditable>::install( m_casts );
|
|
InstanceStaticCast<PatchInstance, ComponentSnappable>::install( m_casts );
|
|
InstanceStaticCast<PatchInstance, PlaneSelectable>::install( m_casts );
|
|
InstanceIdentityCast<PatchInstance>::install( m_casts );
|
|
InstanceContainedCast<PatchInstance, Transformable>::install( m_casts );
|
|
}
|
|
InstanceTypeCastTable& get(){
|
|
return m_casts;
|
|
}
|
|
};
|
|
|
|
|
|
Patch& m_patch;
|
|
typedef std::vector<PatchControlInstance> 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:
|
|
static Counter* m_counter;
|
|
|
|
typedef LazyStatic<TypeCasts> StaticTypeCasts;
|
|
|
|
void lightsChanged(){
|
|
m_lightList->lightsChanged();
|
|
}
|
|
typedef MemberCaller<PatchInstance, &PatchInstance::lightsChanged> 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_counter->increment();
|
|
|
|
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_counter->decrement();
|
|
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<PatchInstance, const Selectable&, &PatchInstance::selectedChanged> SelectedChangedCaller;
|
|
|
|
void selectedChangedComponent( const Selectable& selectable ){
|
|
GlobalSelectionSystem().getObserver ( SelectionSystem::eComponent )( selectable );
|
|
GlobalSelectionSystem().onComponentSelection( *this, selectable );
|
|
}
|
|
typedef MemberCaller1<PatchInstance, const Selectable&, &PatchInstance::selectedChangedComponent> SelectedChangedComponentCaller;
|
|
|
|
Patch& getPatch(){
|
|
return m_patch;
|
|
}
|
|
Bounded& get( NullType<Bounded>){
|
|
return m_patch;
|
|
}
|
|
Cullable& get( NullType<Cullable>){
|
|
return m_patch;
|
|
}
|
|
Transformable& get( NullType<Transformable>){
|
|
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 );
|
|
if ( !select && parent() ){
|
|
Selectable* sel_parent = Instance_getSelectable( *parent() );
|
|
if ( sel_parent && sel_parent->isSelected() )
|
|
sel_parent->setSelected( false );
|
|
}
|
|
}
|
|
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<Vertex3f&>( ( *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 );
|
|
renderer.Highlight( Renderer::ePrimitiveWire );
|
|
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 );
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
void gatherComponentsHighlight( std::vector<std::vector<Vector3>>& polygons, SelectionIntersection& intersection, SelectionTest& test, SelectionSystem::EComponentMode mode ) const {
|
|
test.BeginMesh( localToWorld() );
|
|
|
|
switch ( mode )
|
|
{
|
|
case SelectionSystem::eVertex:
|
|
{
|
|
for ( const PatchControlInstance& pci : m_ctrl_instances )
|
|
{
|
|
SelectionIntersection best;
|
|
test.TestPoint( pci.m_ctrl->m_vertex, best );
|
|
if ( SelectionIntersection_closer( best, intersection ) ) {
|
|
intersection = best;
|
|
polygons.clear();
|
|
polygons.emplace_back( std::initializer_list<Vector3>( { pci.m_ctrl->m_vertex } ) );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 gatherSelectedComponents( const Vector3Callback& callback ) const {
|
|
for ( PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i )
|
|
{
|
|
if ( ( *i ).m_selectable.isSelected() ) {
|
|
callback( ( *i ).m_ctrl->m_vertex );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool selectedVertices() 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 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( matrix.t().vec3() ) );
|
|
}
|
|
}
|
|
|
|
void invertComponentSelection(){
|
|
for ( PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i )
|
|
{
|
|
( *i ).m_selectable.setSelected( !( *i ).m_selectable.isSelected() );
|
|
}
|
|
}
|
|
|
|
|
|
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 bestPlaneDirect( SelectionTest& test, Plane3& plane, SelectionIntersection& intersection ) const {
|
|
test.BeginMesh( localToWorld() );
|
|
m_dragPlanes.bestPlaneDirect( m_patch.localAABB(), test, plane, intersection );
|
|
}
|
|
void bestPlaneIndirect( SelectionTest& test, Plane3& plane, Vector3& intersection, float& dist ) const {
|
|
test.BeginMesh( localToWorld() );
|
|
m_dragPlanes.bestPlaneIndirect( m_patch.localAABB(), test, plane, intersection, dist );
|
|
}
|
|
void selectByPlane( const Plane3& plane ){
|
|
m_dragPlanes.selectByPlane( m_patch.localAABB(), plane );
|
|
}
|
|
void gatherPolygonsByPlane( const Plane3& plane, std::vector<std::vector<Vector3>>& polygons ) const {
|
|
m_dragPlanes.gatherPolygonsByPlane( m_patch.localAABB(), plane, polygons );
|
|
}
|
|
|
|
|
|
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<PatchInstance, &PatchInstance::applyTransform> ApplyTransformCaller;
|
|
|
|
|
|
bool testLight( const RendererLight& light ) const {
|
|
return light.testAABB( worldAABB() );
|
|
}
|
|
};
|
|
|
|
|
|
template<typename TokenImporter, typename TokenExporter>
|
|
class PatchNode :
|
|
public scene::Node::Symbiot,
|
|
public scene::Instantiable,
|
|
public scene::Cloneable
|
|
{
|
|
typedef PatchNode<TokenImporter, TokenExporter> Self;
|
|
|
|
class TypeCasts
|
|
{
|
|
InstanceTypeCastTable m_casts;
|
|
public:
|
|
TypeCasts(){
|
|
NodeStaticCast<PatchNode, scene::Instantiable>::install( m_casts );
|
|
NodeStaticCast<PatchNode, scene::Cloneable>::install( m_casts );
|
|
NodeContainedCast<PatchNode, Snappable>::install( m_casts );
|
|
NodeContainedCast<PatchNode, TransformNode>::install( m_casts );
|
|
NodeContainedCast<PatchNode, Patch>::install( m_casts );
|
|
NodeContainedCast<PatchNode, XMLImporter>::install( m_casts );
|
|
NodeContainedCast<PatchNode, XMLExporter>::install( m_casts );
|
|
NodeContainedCast<PatchNode, MapImporter>::install( m_casts );
|
|
NodeContainedCast<PatchNode, MapExporter>::install( m_casts );
|
|
NodeContainedCast<PatchNode, Nameable>::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<TypeCasts> StaticTypeCasts;
|
|
|
|
Snappable& get( NullType<Snappable>){
|
|
return m_patch;
|
|
}
|
|
TransformNode& get( NullType<TransformNode>){
|
|
return m_patch;
|
|
}
|
|
Patch& get( NullType<Patch>){
|
|
return m_patch;
|
|
}
|
|
XMLImporter& get( NullType<XMLImporter>){
|
|
return m_patch;
|
|
}
|
|
XMLExporter& get( NullType<XMLExporter>){
|
|
return m_patch;
|
|
}
|
|
MapImporter& get( NullType<MapImporter>){
|
|
return m_importMap;
|
|
}
|
|
MapExporter& get( NullType<MapExporter>){
|
|
return m_exportMap;
|
|
}
|
|
Nameable& get( NullType<Nameable>){
|
|
return m_patch;
|
|
}
|
|
|
|
PatchNode( bool patchDef3 = false ) :
|
|
m_node( this, this, StaticTypeCasts::instance().get() ),
|
|
m_patch( m_node, InstanceSetEvaluateTransform<PatchInstance>::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<PatchInstance>::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<PatchTokenImporter, PatchTokenExporter> PatchNodeQuake3;
|
|
typedef PatchNode<PatchDoom3TokenImporter, PatchDoom3TokenExporter> PatchNodeDoom3;
|
|
|
|
inline Patch* Node_getPatch( scene::Node& node ){
|
|
return NodeTypeCast<Patch>::cast( node );
|
|
}
|
|
|
|
inline PatchInstance* Instance_getPatch( scene::Instance& instance ){
|
|
return InstanceTypeCast<PatchInstance>::cast( instance );
|
|
}
|
|
|
|
template<typename Functor>
|
|
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<typename Functor>
|
|
inline void Scene_forEachSelectedPatch( const Functor& functor ){
|
|
GlobalSelectionSystem().foreachSelected( PatchSelectedVisitor<Functor>( functor ) );
|
|
}
|
|
|
|
|
|
template<typename Functor>
|
|
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<typename Functor>
|
|
inline void Scene_forEachVisibleSelectedPatchInstance( const Functor& functor ){
|
|
GlobalSelectionSystem().foreachSelected( PatchVisibleSelectedVisitor<Functor>( functor ) );
|
|
}
|
|
|
|
template<typename Functor>
|
|
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 );
|
|
}
|
|
}
|
|
else{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline void Scene_forEachVisiblePatch( const Functor& functor ){
|
|
GlobalSceneGraph().traverse( PatchForEachWalker<Functor>( functor ) );
|
|
}
|
|
|
|
template<typename Functor>
|
|
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_isSelected( instance ) ) {
|
|
m_functor( *patch );
|
|
}
|
|
}
|
|
else{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline void Scene_forEachVisibleSelectedPatch( const Functor& functor ){
|
|
GlobalSceneGraph().traverse( PatchForEachSelectedWalker<Functor>( functor ) );
|
|
}
|
|
|
|
template<typename Functor>
|
|
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 );
|
|
}
|
|
}
|
|
else{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template<typename Functor>
|
|
inline void Scene_forEachVisiblePatchInstance( const Functor& functor ){
|
|
GlobalSceneGraph().traverse( PatchForEachInstanceWalker<Functor>( functor ) );
|
|
}
|