worldspawn/libs/entitylib.h
Marco Hladik 826d115cb1 Gracefully deal with entity attributes of the same name. This is needed for
the Input/Output system. We append numerations separated by a '#' character
and strip the when saving the map. The editor will intelligently pick a
name for us.
2021-06-06 12:27:36 +02:00

754 lines
21 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
*/
#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();
}
typedef MemberCaller<KeyValue, void(const CopiedString&), &KeyValue::importState> UndoImportCaller;
};
/// \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.
#include "stream/stringstream.h"
class EntityKeyValues : public Entity
{
public:
typedef KeyValue Value;
static StringPool& getPool(){
return Static<StringPool, KeyContext>::instance();
}
private:
static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged;
static Counter* m_counter;
EntityClass* m_eclass;
class KeyContext {};
typedef Static<StringPool, KeyContext> KeyPool;
typedef PooledString<KeyPool> Key;
typedef SmartPointer<KeyValue> KeyValuePtr;
typedef UnsortedMap<Key, KeyValuePtr> KeyValues;
KeyValues m_keyValues;
typedef UnsortedSet<Observer*> Observers;
Observers m_observers;
ObservedUndoableObject<KeyValues> m_undo;
bool m_instanced;
bool m_observerMutex;
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 );
}
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 );
}
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 );
}
}
void forEachKeyValue_notifyErase(){
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
{
notifyErase( ( *i ).first.c_str(), *( *i ).second );
}
}
void insert( const char* key, const KeyValuePtr& keyValue ){
KeyValues::iterator i = m_keyValues.insert( KeyValues::value_type( key, keyValue ) );
notifyInsert( key, *( *i ).second );
if ( m_instanced ) {
( *i ).second->instanceAttach( m_undo.map() );
}
}
/* 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;
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);
}
}
else
{
m_undo.save();
insert( key, KeyValuePtr( new KeyValue( value, EntityClass_valueForKey( *m_eclass, key ) ) ) );
printf("[ENTLIB]: inserting key %s = %s\n", key, value);
}
}
void erase( KeyValues::iterator i ){
if ( m_instanced ) {
( *i ).second->instanceDetach( m_undo.map() );
}
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 );
}
}
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() );
}
}
~EntityKeyValues(){
for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); )
{
// post-increment to allow current element to be removed safely
( *i++ )->clear();
}
ASSERT_MESSAGE( m_observers.empty(), "EntityKeyValues::~EntityKeyValues: observers still attached" );
}
static void setKeyValueChangedFunc( EntityCreator::KeyValueChangedFunc func ){
m_entityKeyValueChanged = func;
KeyValue::setKeyValueChangedFunc( func );
}
static void setCounter( Counter* counter ){
m_counter = counter;
}
void importState( const KeyValues& keyValues ){
for ( KeyValues::iterator i = m_keyValues.begin(); i != m_keyValues.end(); )
{
erase( i++ );
}
for ( KeyValues::const_iterator i = keyValues.begin(); i != keyValues.end(); ++i )
{
insert( ( *i ).first.c_str(), ( *i ).second );
}
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 );
}
}
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 );
}
}
void forEachKeyValue_instanceAttach( MapFile* map ){
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
{
( *i ).second->instanceAttach( map );
}
}
void forEachKeyValue_instanceDetach( MapFile* map ){
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
{
( *i ).second->instanceDetach( map );
}
}
void instanceAttach( MapFile* map ){
if ( m_counter != 0 ) {
m_counter->increment();
}
m_instanced = true;
forEachKeyValue_instanceAttach( map );
m_undo.instanceAttach( map );
}
void instanceDetach( MapFile* map ){
if ( m_counter != 0 ) {
m_counter->decrement();
}
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() );
}
}
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 );
}
else
{
insert( key, value );
}
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();
}
return EntityClass_valueForKey( *m_eclass, key );
}
int getKeyEntries( void ) const {
int i = 0;
for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i )
{
i++;
}
}
bool isContainer() const {
return m_isContainer;
}
};
/// \brief A Resource reference with a controlled lifetime.
/// \brief The resource is released when the ResourceReference is destroyed.
class ResourceReference
{
CopiedString m_name;
Resource* m_resource;
public:
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();
}
void capture(){
m_resource = GlobalReferenceCache().capture( m_name.c_str() );
}
void release(){
GlobalReferenceCache().release( m_name.c_str() );
}
const char* getName() const {
return m_name.c_str();
}
void setName( const char* name ){
ResourceReference tmp( name );
tmp.swap( *this );
}
void swap( ResourceReference& other ){
std::swap( m_resource, other.m_resource );
std::swap( m_name, other.m_name );
}
void attach( ModuleObserver& observer ){
m_resource->attach( observer );
}
void detach( ModuleObserver& observer ){
m_resource->detach( observer );
}
Resource* get(){
return m_resource;
}
};
namespace std
{
/// \brief Swaps the values of \p self and \p other.
/// Overloads std::swap.
inline void swap( ResourceReference& self, ResourceReference& other ){
self.swap( other );
}
}
#endif