2020-11-17 11:16:16 +00:00
|
|
|
/*
|
|
|
|
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_ENTITYLIB_H )
|
|
|
|
#define INCLUDED_ENTITYLIB_H
|
|
|
|
|
|
|
|
#include "ireference.h"
|
|
|
|
#include "debugging/debugging.h"
|
|
|
|
|
|
|
|
#include "ientity.h"
|
|
|
|
#include "irender.h"
|
|
|
|
#include "igl.h"
|
|
|
|
#include "selectable.h"
|
|
|
|
|
|
|
|
#include "generic/callback.h"
|
|
|
|
#include "math/vector.h"
|
|
|
|
#include "math/aabb.h"
|
|
|
|
#include "undolib.h"
|
|
|
|
#include "string/pooledstring.h"
|
|
|
|
#include "generic/referencecounted.h"
|
|
|
|
#include "scenelib.h"
|
|
|
|
#include "container/container.h"
|
|
|
|
#include "eclasslib.h"
|
|
|
|
|
|
|
|
#include <list>
|
|
|
|
#include <set>
|
|
|
|
|
|
|
|
inline void arrow_draw( const Vector3& origin, const Vector3& direction_forward, const Vector3& direction_left, const Vector3& direction_up ){
|
|
|
|
Vector3 endpoint( vector3_added( origin, vector3_scaled( direction_forward, 32.0 ) ) );
|
|
|
|
|
|
|
|
Vector3 tip1( vector3_added( vector3_added( endpoint, vector3_scaled( direction_forward, -8.0 ) ), vector3_scaled( direction_up, -4.0 ) ) );
|
|
|
|
Vector3 tip2( vector3_added( tip1, vector3_scaled( direction_up, 8.0 ) ) );
|
|
|
|
Vector3 tip3( vector3_added( vector3_added( endpoint, vector3_scaled( direction_forward, -8.0 ) ), vector3_scaled( direction_left, -4.0 ) ) );
|
|
|
|
Vector3 tip4( vector3_added( tip3, vector3_scaled( direction_left, 8.0 ) ) );
|
|
|
|
|
|
|
|
glBegin( GL_LINES );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( origin ) );
|
|
|
|
glVertex3fv( vector3_to_array( endpoint ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( endpoint ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip1 ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( endpoint ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip2 ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( endpoint ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip3 ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( endpoint ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip4 ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( tip1 ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip3 ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( tip3 ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip2 ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( tip2 ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip4 ) );
|
|
|
|
|
|
|
|
glVertex3fv( vector3_to_array( tip4 ) );
|
|
|
|
glVertex3fv( vector3_to_array( tip1 ) );
|
|
|
|
|
|
|
|
glEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
class SelectionIntersection;
|
|
|
|
|
|
|
|
inline void aabb_testselect( const AABB& aabb, SelectionTest& test, SelectionIntersection& best ){
|
|
|
|
const IndexPointer::index_type indices[24] = {
|
|
|
|
2, 1, 5, 6,
|
|
|
|
1, 0, 4, 5,
|
|
|
|
0, 1, 2, 3,
|
|
|
|
3, 7, 4, 0,
|
|
|
|
3, 2, 6, 7,
|
|
|
|
7, 6, 5, 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
Vector3 points[8];
|
|
|
|
aabb_corners( aabb, points );
|
|
|
|
test.TestQuads( VertexPointer( reinterpret_cast<VertexPointer::pointer>( points ), sizeof( Vector3 ) ), IndexPointer( indices, 24 ), best );
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void aabb_draw_wire( const Vector3 points[8] ){
|
|
|
|
unsigned int indices[26] = {
|
|
|
|
0, 1, 1, 2, 2, 3, 3, 0,
|
|
|
|
4, 5, 5, 6, 6, 7, 7, 4,
|
|
|
|
0, 4, 1, 5, 2, 6, 3, 7,
|
|
|
|
// 0, 6, 1, 7, 2, 4, 3, 5 // X cross
|
|
|
|
1, 7 // diagonal line (connect mins to maxs corner)
|
|
|
|
};
|
|
|
|
#if 1
|
|
|
|
glVertexPointer( 3, GL_FLOAT, 0, points );
|
|
|
|
glDrawElements( GL_LINES, sizeof( indices ) / sizeof( indices[0] ), GL_UNSIGNED_INT, indices );
|
|
|
|
#else
|
|
|
|
glBegin( GL_LINES );
|
|
|
|
for ( std::size_t i = 0; i < sizeof( indices ) / sizeof( indices[0] ); ++i )
|
|
|
|
{
|
|
|
|
glVertex3fv( points[indices[i]] );
|
|
|
|
}
|
|
|
|
glEnd();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void aabb_draw_flatshade( const Vector3 points[8] ){
|
|
|
|
glBegin( GL_QUADS );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[0] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[2] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[1] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[5] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[6] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[1] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[1] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[0] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[4] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[5] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[2] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[0] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[1] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[2] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[3] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[3] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[0] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[3] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[7] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[4] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[4] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[3] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[2] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[6] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[7] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[5] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[7] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[6] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[5] ) );
|
|
|
|
glVertex3fv( vector3_to_array( points[4] ) );
|
|
|
|
|
|
|
|
glEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void aabb_draw_wire( const AABB& aabb ){
|
|
|
|
Vector3 points[8];
|
|
|
|
aabb_corners( aabb, points );
|
|
|
|
aabb_draw_wire( points );
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void aabb_draw_flatshade( const AABB& aabb ){
|
|
|
|
Vector3 points[8];
|
|
|
|
aabb_corners( aabb, points );
|
|
|
|
aabb_draw_flatshade( points );
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void aabb_draw_textured( const AABB& aabb ){
|
|
|
|
Vector3 points[8];
|
|
|
|
aabb_corners( aabb, points );
|
|
|
|
|
|
|
|
glBegin( GL_QUADS );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[0] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[2] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topright );
|
|
|
|
glVertex3fv( vector3_to_array( points[1] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botright );
|
|
|
|
glVertex3fv( vector3_to_array( points[5] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[6] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[1] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[1] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topright );
|
|
|
|
glVertex3fv( vector3_to_array( points[0] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botright );
|
|
|
|
glVertex3fv( vector3_to_array( points[4] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[5] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[2] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[0] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topright );
|
|
|
|
glVertex3fv( vector3_to_array( points[1] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botright );
|
|
|
|
glVertex3fv( vector3_to_array( points[2] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[3] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[3] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[0] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topright );
|
|
|
|
glVertex3fv( vector3_to_array( points[3] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botright );
|
|
|
|
glVertex3fv( vector3_to_array( points[7] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[4] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[4] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[3] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topright );
|
|
|
|
glVertex3fv( vector3_to_array( points[2] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botright );
|
|
|
|
glVertex3fv( vector3_to_array( points[6] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[7] ) );
|
|
|
|
|
|
|
|
glNormal3fv( vector3_to_array( aabb_normals[5] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[7] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_topright );
|
|
|
|
glVertex3fv( vector3_to_array( points[6] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botright );
|
|
|
|
glVertex3fv( vector3_to_array( points[5] ) );
|
|
|
|
glTexCoord2fv( aabb_texcoord_botleft );
|
|
|
|
glVertex3fv( vector3_to_array( points[4] ) );
|
|
|
|
|
|
|
|
glEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void aabb_draw_solid( const AABB& aabb, RenderStateFlags state ){
|
|
|
|
if ( state & RENDER_TEXTURE ) {
|
|
|
|
aabb_draw_textured( aabb );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aabb_draw_flatshade( aabb );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void aabb_draw( const AABB& aabb, RenderStateFlags state ){
|
|
|
|
if ( state & RENDER_FILL ) {
|
|
|
|
aabb_draw_solid( aabb, state );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aabb_draw_wire( aabb );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class RenderableSolidAABB : public OpenGLRenderable
|
|
|
|
{
|
|
|
|
const AABB& m_aabb;
|
|
|
|
public:
|
|
|
|
RenderableSolidAABB( const AABB& aabb ) : m_aabb( aabb ){
|
|
|
|
}
|
|
|
|
void render( RenderStateFlags state ) const {
|
|
|
|
aabb_draw_solid( m_aabb, state );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class RenderableWireframeAABB : public OpenGLRenderable
|
|
|
|
{
|
|
|
|
const AABB& m_aabb;
|
|
|
|
public:
|
|
|
|
RenderableWireframeAABB( const AABB& aabb ) : m_aabb( aabb ){
|
|
|
|
}
|
|
|
|
void render( RenderStateFlags state ) const {
|
|
|
|
aabb_draw_wire( m_aabb );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// \brief A key/value pair of strings.
|
|
|
|
///
|
|
|
|
/// - Notifies observers when value changes - value changes to "" on destruction.
|
|
|
|
/// - Provides undo support through the global undo system.
|
|
|
|
class KeyValue : public EntityKeyValue
|
|
|
|
{
|
|
|
|
typedef UnsortedSet<KeyObserver> KeyObservers;
|
|
|
|
|
|
|
|
std::size_t m_refcount;
|
|
|
|
KeyObservers m_observers;
|
|
|
|
CopiedString m_string;
|
|
|
|
const char* m_empty;
|
|
|
|
ObservedUndoableObject<CopiedString> m_undo;
|
|
|
|
static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged;
|
|
|
|
public:
|
|
|
|
|
|
|
|
KeyValue( const char* string, const char* empty )
|
|
|
|
: m_refcount( 0 ), m_string( string ), m_empty( empty ), m_undo( m_string, UndoImportCaller( *this ) ){
|
|
|
|
notify();
|
|
|
|
}
|
|
|
|
~KeyValue(){
|
|
|
|
ASSERT_MESSAGE( m_observers.empty(), "KeyValue::~KeyValue: observers still attached" );
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setKeyValueChangedFunc( EntityCreator::KeyValueChangedFunc func ){
|
|
|
|
m_entityKeyValueChanged = func;
|
|
|
|
}
|
|
|
|
|
|
|
|
void IncRef(){
|
|
|
|
++m_refcount;
|
|
|
|
}
|
|
|
|
void DecRef(){
|
|
|
|
if ( --m_refcount == 0 ) {
|
|
|
|
delete this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void instanceAttach( MapFile* map ){
|
|
|
|
m_undo.instanceAttach( map );
|
|
|
|
}
|
|
|
|
void instanceDetach( MapFile* map ){
|
|
|
|
m_undo.instanceDetach( map );
|
|
|
|
}
|
|
|
|
|
|
|
|
void attach( const KeyObserver& observer ){
|
|
|
|
( *m_observers.insert ( observer ) )( c_str() );
|
|
|
|
}
|
|
|
|
void detach( const KeyObserver& observer ){
|
|
|
|
observer( m_empty );
|
|
|
|
m_observers.erase( observer );
|
|
|
|
}
|
|
|
|
const char* c_str() const {
|
|
|
|
if ( string_empty( m_string.c_str() ) ) {
|
|
|
|
return m_empty;
|
|
|
|
}
|
|
|
|
return m_string.c_str();
|
|
|
|
}
|
|
|
|
void assign( const char* other ){
|
|
|
|
if ( !string_equal( m_string.c_str(), other ) ) {
|
|
|
|
m_undo.save();
|
|
|
|
m_string = other;
|
|
|
|
notify();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void notify(){
|
|
|
|
m_entityKeyValueChanged();
|
|
|
|
KeyObservers::reverse_iterator i = m_observers.rbegin();
|
|
|
|
while ( i != m_observers.rend() )
|
|
|
|
{
|
|
|
|
( *i++ )( c_str() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void importState( const CopiedString& string ){
|
|
|
|
m_string = string;
|
|
|
|
|
|
|
|
notify();
|
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
typedef MemberCaller<KeyValue, void (const CopiedString&), &KeyValue::importState> UndoImportCaller;
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/// \brief An unsorted list of key/value pairs.
|
|
|
|
///
|
|
|
|
/// - Notifies observers when a pair is inserted or removed.
|
|
|
|
/// - Provides undo support through the global undo system.
|
|
|
|
/// - New keys are appended to the end of the list.
|
2021-06-06 10:27:36 +00:00
|
|
|
#include "stream/stringstream.h"
|
2020-11-17 11:16:16 +00:00
|
|
|
class EntityKeyValues : public Entity
|
|
|
|
{
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
typedef KeyValue Value;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
static StringPool& getPool(){
|
|
|
|
return Static<StringPool, KeyContext>::instance();
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
private:
|
2021-08-04 20:23:18 +00:00
|
|
|
static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged;
|
|
|
|
static Counter* m_counter;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
EntityClass* m_eclass;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
class KeyContext {};
|
|
|
|
typedef Static<StringPool, KeyContext> KeyPool;
|
|
|
|
typedef PooledString<KeyPool> Key;
|
|
|
|
typedef SmartPointer<KeyValue> KeyValuePtr;
|
|
|
|
typedef UnsortedMap<Key, KeyValuePtr> KeyValues;
|
|
|
|
KeyValues m_keyValues;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
typedef UnsortedSet<Observer*> Observers;
|
|
|
|
Observers m_observers;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
ObservedUndoableObject<KeyValues> m_undo;
|
|
|
|
bool m_instanced;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
bool m_observerMutex;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void notifyInsert( const char* key, Value& value ){
|
|
|
|
m_observerMutex = true;
|
|
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
|
|
{
|
|
|
|
( *i )->insert( key, value );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
m_observerMutex = false;
|
|
|
|
}
|
|
|
|
void notifyErase( const char* key, Value& value ){
|
|
|
|
m_observerMutex = true;
|
|
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
|
|
|
|
{
|
|
|
|
( *i )->erase( key, value );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
m_observerMutex = false;
|
|
|
|
}
|
|
|
|
void forEachKeyValue_notifyInsert(){
|
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
notifyInsert( ( *i ).first.c_str(), *( *i ).second );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
|
|
|
void forEachKeyValue_notifyErase(){
|
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
notifyErase( ( *i ).first.c_str(), *( *i ).second );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void insert( const char* key, const KeyValuePtr& keyValue ){
|
|
|
|
KeyValues::iterator i = m_keyValues.insert( KeyValues::value_type( key, keyValue ) );
|
|
|
|
notifyInsert( key, *( *i ).second );
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
if ( m_instanced ) {
|
|
|
|
( *i ).second->instanceAttach( m_undo.map() );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
/* see if our key already exists in here */
|
|
|
|
void insert( const char* key, const char* value ){
|
|
|
|
int dupecheck = 0;
|
|
|
|
|
|
|
|
if (!strcmp(key, "classname"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "origin"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "model"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "angles"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "angle"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "alpha"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "rendermode"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "renderamt"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "rendercolor"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "velocity"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "solid"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "movetype"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "avelocity"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "skin"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "effects"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "target"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "targetname"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "killtarget"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "shadows"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "modelscale"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "scale"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "message"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "fade")) /* light keys */
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "anglescale"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "style"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "light"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "deviance"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "samples"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "filter"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "color"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "color255"))
|
|
|
|
dupecheck = 1;
|
|
|
|
else if (!strcmp(key, "extradist"))
|
|
|
|
dupecheck = 1;
|
|
|
|
|
|
|
|
KeyValues::iterator i = m_keyValues.find( key );
|
|
|
|
|
|
|
|
/* does the key already exist */
|
|
|
|
if (i != m_keyValues.end() ) {
|
|
|
|
/* re-assign only when we're a special field, else pick a new name */
|
|
|
|
if (dupecheck) {
|
|
|
|
( *i ).second->assign( value );
|
|
|
|
printf("[ENTLIB]: dupe found, setting %s to %s\n", key, value);
|
|
|
|
} else {
|
|
|
|
bool b = true;
|
|
|
|
unsigned int num = 0;
|
|
|
|
StringOutputStream new_key(64);
|
|
|
|
|
|
|
|
/* loop through and generate an enumerated variant */
|
|
|
|
do {
|
|
|
|
/* keep incrementing num until we find a free slot */
|
|
|
|
num++;
|
|
|
|
new_key.clear();
|
|
|
|
new_key << key << "#" << Unsigned(num);
|
|
|
|
i = m_keyValues.find(new_key.c_str());
|
|
|
|
|
|
|
|
if (i == m_keyValues.end()) {
|
|
|
|
insert(new_key.c_str(), value);
|
|
|
|
b = false;
|
|
|
|
}
|
|
|
|
} while (b != false);
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
m_undo.save();
|
|
|
|
insert( key, KeyValuePtr( new KeyValue( value, EntityClass_valueForKey( *m_eclass, key ) ) ) );
|
|
|
|
printf("[ENTLIB]: inserting key %s = %s\n", key, value);
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void erase( KeyValues::iterator i ){
|
|
|
|
if ( m_instanced ) {
|
|
|
|
( *i ).second->instanceDetach( m_undo.map() );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
Key key( ( *i ).first );
|
|
|
|
KeyValuePtr value( ( *i ).second );
|
|
|
|
m_keyValues.erase( i );
|
|
|
|
notifyErase( key.c_str(), *value );
|
|
|
|
}
|
|
|
|
|
|
|
|
void erase( const char* key ){
|
|
|
|
KeyValues::iterator i = m_keyValues.find( key );
|
|
|
|
if ( i != m_keyValues.end() ) {
|
|
|
|
m_undo.save();
|
|
|
|
erase( i );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
public:
|
|
|
|
bool m_isContainer;
|
|
|
|
|
|
|
|
EntityKeyValues( EntityClass* eclass ) :
|
|
|
|
m_eclass( eclass ),
|
|
|
|
m_undo( m_keyValues, UndoImportCaller( *this ) ),
|
|
|
|
m_instanced( false ),
|
|
|
|
m_observerMutex( false ),
|
|
|
|
m_isContainer( !eclass->fixedsize ){
|
|
|
|
}
|
|
|
|
EntityKeyValues( const EntityKeyValues& other ) :
|
|
|
|
Entity( other ),
|
|
|
|
m_eclass( &other.getEntityClass() ),
|
|
|
|
m_undo( m_keyValues, UndoImportCaller( *this ) ),
|
|
|
|
m_instanced( false ),
|
|
|
|
m_observerMutex( false ),
|
|
|
|
m_isContainer( other.m_isContainer ){
|
|
|
|
for ( KeyValues::const_iterator i = other.m_keyValues.begin(); i != other.m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
insert( ( *i ).first.c_str(), ( *i ).second->c_str() );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
|
|
|
~EntityKeyValues(){
|
|
|
|
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); )
|
|
|
|
{
|
|
|
|
// post-increment to allow current element to be removed safely
|
|
|
|
( *i++ )->clear();
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
ASSERT_MESSAGE( m_observers.empty(), "EntityKeyValues::~EntityKeyValues: observers still attached" );
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
static void setKeyValueChangedFunc( EntityCreator::KeyValueChangedFunc func ){
|
|
|
|
m_entityKeyValueChanged = func;
|
|
|
|
KeyValue::setKeyValueChangedFunc( func );
|
|
|
|
}
|
|
|
|
static void setCounter( Counter* counter ){
|
|
|
|
m_counter = counter;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void importState( const KeyValues& keyValues ){
|
|
|
|
for ( KeyValues::iterator i = m_keyValues.begin(); i != m_keyValues.end(); )
|
|
|
|
{
|
|
|
|
erase( i++ );
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
for ( KeyValues::const_iterator i = keyValues.begin(); i != keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
insert( ( *i ).first.c_str(), ( *i ).second );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
m_entityKeyValueChanged();
|
|
|
|
}
|
|
|
|
typedef MemberCaller<EntityKeyValues, void (const KeyValues&), &EntityKeyValues::importState> UndoImportCaller;
|
|
|
|
|
|
|
|
void attach( Observer& observer ){
|
|
|
|
ASSERT_MESSAGE( !m_observerMutex, "observer cannot be attached during iteration" );
|
|
|
|
m_observers.insert( &observer );
|
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
observer.insert( ( *i ).first.c_str(), *( *i ).second );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
|
|
|
void detach( Observer& observer ){
|
|
|
|
ASSERT_MESSAGE( !m_observerMutex, "observer cannot be detached during iteration" );
|
|
|
|
m_observers.erase( &observer );
|
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
observer.erase( ( *i ).first.c_str(), *( *i ).second );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void forEachKeyValue_instanceAttach( MapFile* map ){
|
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
( *i ).second->instanceAttach( map );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
|
|
|
void forEachKeyValue_instanceDetach( MapFile* map ){
|
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
( *i ).second->instanceDetach( map );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void instanceAttach( MapFile* map ){
|
|
|
|
if ( m_counter != 0 ) {
|
|
|
|
m_counter->increment();
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
m_instanced = true;
|
|
|
|
forEachKeyValue_instanceAttach( map );
|
|
|
|
m_undo.instanceAttach( map );
|
|
|
|
}
|
|
|
|
void instanceDetach( MapFile* map ){
|
|
|
|
if ( m_counter != 0 ) {
|
|
|
|
m_counter->decrement();
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
m_undo.instanceDetach( map );
|
|
|
|
forEachKeyValue_instanceDetach( map );
|
|
|
|
m_instanced = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// entity
|
|
|
|
EntityClass& getEntityClass() const {
|
|
|
|
return *m_eclass;
|
|
|
|
}
|
|
|
|
void forEachKeyValue( Visitor& visitor ) const {
|
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
visitor.visit( ( *i ).first.c_str(), ( *i ).second->c_str() );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
|
|
|
void setKeyValue( const char* key, const char* value ){
|
|
|
|
if ( value[0] == '\0'
|
|
|
|
/*|| string_equal(EntityClass_valueForKey(*m_eclass, key), value)*/ ) { // don't delete values equal to default
|
|
|
|
erase( key );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
insert( key, value );
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
m_entityKeyValueChanged();
|
|
|
|
}
|
|
|
|
const char* getKeyValue( const char* key ) const {
|
|
|
|
KeyValues::const_iterator i = m_keyValues.find( key );
|
|
|
|
if ( i != m_keyValues.end() ) {
|
|
|
|
return ( *i ).second->c_str();
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
return EntityClass_valueForKey( *m_eclass, key );
|
|
|
|
}
|
|
|
|
int getKeyEntries( void ) const {
|
|
|
|
int i = 0;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
|
|
|
|
{
|
|
|
|
i++;
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
2021-08-04 20:23:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool isContainer() const {
|
|
|
|
return m_isContainer;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/// \brief A Resource reference with a controlled lifetime.
|
|
|
|
/// \brief The resource is released when the ResourceReference is destroyed.
|
|
|
|
class ResourceReference
|
|
|
|
{
|
2021-08-04 20:23:18 +00:00
|
|
|
CopiedString m_name;
|
|
|
|
Resource* m_resource;
|
2020-11-17 11:16:16 +00:00
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
ResourceReference( const char* name )
|
|
|
|
: m_name( name ){
|
|
|
|
capture();
|
|
|
|
}
|
|
|
|
ResourceReference( const ResourceReference& other )
|
|
|
|
: m_name( other.m_name ){
|
|
|
|
capture();
|
|
|
|
}
|
|
|
|
ResourceReference& operator=( const ResourceReference& other ){
|
|
|
|
ResourceReference tmp( other );
|
|
|
|
tmp.swap( *this );
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
~ResourceReference(){
|
|
|
|
release();
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void capture(){
|
|
|
|
m_resource = GlobalReferenceCache().capture( m_name.c_str() );
|
|
|
|
}
|
|
|
|
void release(){
|
|
|
|
GlobalReferenceCache().release( m_name.c_str() );
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
const char* getName() const {
|
|
|
|
return m_name.c_str();
|
|
|
|
}
|
|
|
|
void setName( const char* name ){
|
|
|
|
ResourceReference tmp( name );
|
|
|
|
tmp.swap( *this );
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void swap( ResourceReference& other ){
|
|
|
|
std::swap( m_resource, other.m_resource );
|
|
|
|
std::swap( m_name, other.m_name );
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
void attach( ModuleObserver& observer ){
|
|
|
|
m_resource->attach( observer );
|
|
|
|
}
|
|
|
|
void detach( ModuleObserver& observer ){
|
|
|
|
m_resource->detach( observer );
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
Resource* get(){
|
|
|
|
return m_resource;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
namespace std
|
|
|
|
{
|
2021-08-04 20:23:18 +00:00
|
|
|
/// \brief Swaps the values of \p self and \p other.
|
|
|
|
/// Overloads std::swap.
|
|
|
|
inline void swap( ResourceReference& self, ResourceReference& other ){
|
|
|
|
self.swap( other );
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|