/*
- Copyright (C) 1999-2007 id Software, Inc. and contributors.
- For a list of contributors, see the accompanying CONTRIBUTORS file.
+ Copyright (C) 2001-2006, William Joseph.
+ All Rights Reserved.
This file is part of GtkRadiant.
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+///\file
+///\brief Represents the Quake3 misc_model entity.
+///
+/// This entity displays the model specified in its "model" key.
+/// The "origin", "angles" and "modelscale*" keys directly control the entity's local-to-parent transform.
+
+#include "cullable.h"
+#include "renderable.h"
+#include "editable.h"
+
+#include "selectionlib.h"
+#include "instancelib.h"
+#include "transformlib.h"
+#include "traverselib.h"
+#include "entitylib.h"
+#include "eclasslib.h"
+#include "render.h"
+#include "pivot.h"
+
+#include "targetable.h"
+#include "origin.h"
+#include "angles.h"
+#include "scale.h"
+#include "model.h"
+#include "filters.h"
+#include "namedentity.h"
+#include "keyobservers.h"
+#include "namekeys.h"
-#include "entity_entitymodel.h"
#include "entity.h"
-//
-// CEntityMiscModel implementation
-//
-
-CEntityMiscModel::CEntityMiscModel ( entity_t *e ){
- refCount = 1;
- m_entity = e;
- m_model = NULL;
- VectorSet( m_translate, 0,0,0 );
- VectorSet( m_euler, 0,0,0 );
- VectorSet( m_scale, 1,1,1 );
- VectorSet( m_pivot, 0,0,0 );
- m4x4_identity( m_transform );
- m4x4_identity( m_inverse_transform );
-}
-
-CEntityMiscModel::~CEntityMiscModel (){
- if ( m_cachereq.GetBuffer()[0] != ':'
- && m_version.c_str()[0] != '\0' ) {
- GetModelCache()->DeleteByID( m_cachereq.GetBuffer(), m_version.c_str() );
- }
+class MiscModel :
+ public Snappable
+{
+EntityKeyValues m_entity;
+KeyObserverMap m_keyObservers;
+MatrixTransform m_transform;
+
+OriginKey m_originKey;
+Vector3 m_origin;
+AnglesKey m_anglesKey;
+Vector3 m_angles;
+ScaleKey m_scaleKey;
+Vector3 m_scale;
+
+SingletonModel m_model;
+
+ClassnameFilter m_filter;
+NamedEntity m_named;
+NameKeys m_nameKeys;
+RenderablePivot m_renderOrigin;
+RenderableNamedEntity m_renderName;
+
+Callback m_transformChanged;
+Callback m_evaluateTransform;
+
+void construct(){
+ m_keyObservers.insert( "classname", ClassnameFilter::ClassnameChangedCaller( m_filter ) );
+ m_keyObservers.insert( Static<KeyIsName>::instance().m_nameKey, NamedEntity::IdentifierChangedCaller( m_named ) );
+ m_keyObservers.insert( "model", SingletonModel::ModelChangedCaller( m_model ) );
+ m_keyObservers.insert( "origin", OriginKey::OriginChangedCaller( m_originKey ) );
+ m_keyObservers.insert( "angle", AnglesKey::AngleChangedCaller( m_anglesKey ) );
+ m_keyObservers.insert( "angles", AnglesKey::AnglesChangedCaller( m_anglesKey ) );
+ m_keyObservers.insert( "modelscale", ScaleKey::UniformScaleChangedCaller( m_scaleKey ) );
+ m_keyObservers.insert( "modelscale_vec", ScaleKey::ScaleChangedCaller( m_scaleKey ) );
}
-
-// IRender
-
-void CEntityMiscModel::Draw( int state, int rflags ) const {
- // push the current modelview matrix
- // FIXME: put in a check for stack recursion depth..
- // or avoid recursion of opengl matrix stack
- g_QglTable.m_pfn_qglPushMatrix();
- // apply the parent-to-local transform
- g_QglTable.m_pfn_qglMultMatrixf( m_transform );
-
- pivot_draw( m_pivot );
-
- // draw children
- if ( m_model && m_model->pRender ) {
- m_model->pRender->Draw( state, rflags );
- }
-
- g_QglTable.m_pfn_qglPopMatrix();
+void updateTransform(){
+ m_transform.localToParent() = g_matrix4_identity;
+ matrix4_transform_by_euler_xyz_degrees( m_transform.localToParent(), m_origin, m_angles, m_scale );
+ m_transformChanged();
}
-// ISelect
+// vc 2k5 compiler fix
+#if _MSC_VER >= 1400
+public:
+#endif
-bool CEntityMiscModel::TestRay( const ray_t *ray, vec_t *dist ) const {
- vec_t dist_start = *dist;
- vec_t dist_local = *dist;
- ray_t ray_local = *ray;
+void originChanged(){
+ m_origin = m_originKey.m_origin;
+ updateTransform();
+}
+typedef MemberCaller<MiscModel, &MiscModel::originChanged> OriginChangedCaller;
+void anglesChanged(){
+ m_angles = m_anglesKey.m_angles;
+ updateTransform();
+}
+typedef MemberCaller<MiscModel, &MiscModel::anglesChanged> AnglesChangedCaller;
+void scaleChanged(){
+ m_scale = m_scaleKey.m_scale;
+ updateTransform();
+}
+typedef MemberCaller<MiscModel, &MiscModel::scaleChanged> ScaleChangedCaller;
+public:
+
+MiscModel( EntityClass* eclass, scene::Node& node, const Callback& transformChanged, const Callback& evaluateTransform ) :
+ m_entity( eclass ),
+ m_originKey( OriginChangedCaller( *this ) ),
+ m_origin( ORIGINKEY_IDENTITY ),
+ m_anglesKey( AnglesChangedCaller( *this ) ),
+ m_angles( ANGLESKEY_IDENTITY ),
+ m_scaleKey( ScaleChangedCaller( *this ) ),
+ m_scale( SCALEKEY_IDENTITY ),
+ m_filter( m_entity, node ),
+ m_named( m_entity ),
+ m_nameKeys( m_entity ),
+ m_renderName( m_named, g_vector3_identity ),
+ m_transformChanged( transformChanged ),
+ m_evaluateTransform( evaluateTransform ){
+ construct();
+}
+MiscModel( const MiscModel& other, scene::Node& node, const Callback& transformChanged, const Callback& evaluateTransform ) :
+ m_entity( other.m_entity ),
+ m_originKey( OriginChangedCaller( *this ) ),
+ m_origin( ORIGINKEY_IDENTITY ),
+ m_anglesKey( AnglesChangedCaller( *this ) ),
+ m_angles( ANGLESKEY_IDENTITY ),
+ m_scaleKey( ScaleChangedCaller( *this ) ),
+ m_scale( SCALEKEY_IDENTITY ),
+ m_filter( m_entity, node ),
+ m_named( m_entity ),
+ m_nameKeys( m_entity ),
+ m_renderName( m_named, g_vector3_identity ),
+ m_transformChanged( transformChanged ),
+ m_evaluateTransform( evaluateTransform ){
+ construct();
+}
- if ( aabb_test_ray( &m_BBox, ray ) == 0 ) {
- return false;
+InstanceCounter m_instanceCounter;
+void instanceAttach( const scene::Path& path ){
+ if ( ++m_instanceCounter.m_count == 1 ) {
+ m_filter.instanceAttach();
+ m_entity.instanceAttach( path_find_mapfile( path.begin(), path.end() ) );
+ m_entity.attach( m_keyObservers );
}
-
- ray_transform( &ray_local, m_inverse_transform );
-
- if ( m_model && m_model->pSelect ) {
- if ( m_model->pSelect->TestRay( &ray_local, &dist_local ) ) {
- *dist = dist_local;
- }
+}
+void instanceDetach( const scene::Path& path ){
+ if ( --m_instanceCounter.m_count == 0 ) {
+ m_entity.detach( m_keyObservers );
+ m_entity.instanceDetach( path_find_mapfile( path.begin(), path.end() ) );
+ m_filter.instanceDetach();
}
- else{ *dist = dist_local; }
-
- return *dist < dist_start;
}
-
-//IEdit
-
-void CEntityMiscModel::Translate( const vec3_t translation ){
- VectorIncrement( translation, m_translate );
- UpdateCachedData();
+EntityKeyValues& getEntity(){
+ return m_entity;
+}
+const EntityKeyValues& getEntity() const {
+ return m_entity;
}
-void CEntityMiscModel::Rotate( const vec3_t pivot, const vec3_t rotation ){
- m4x4_t rotation_matrix;
-
- m4x4_identity( rotation_matrix );
- m4x4_pivoted_rotate_by_vec3( rotation_matrix, rotation, eXYZ, pivot );
- m4x4_transform_point( rotation_matrix, m_translate );
-
- VectorIncrement( rotation, m_euler );
+scene::Traversable& getTraversable(){
+ return m_model.getTraversable();
+}
+Namespaced& getNamespaced(){
+ return m_nameKeys;
+}
+Nameable& getNameable(){
+ return m_named;
+}
+TransformNode& getTransformNode(){
+ return m_transform;
+}
- UpdateCachedData();
+void attach( scene::Traversable::Observer* observer ){
+ m_model.attach( observer );
+}
+void detach( scene::Traversable::Observer* observer ){
+ m_model.detach( observer );
}
-void CEntityMiscModel::OnKeyValueChanged( entity_t *e, const char *key, const char* value ){
- if ( strcmp( key, "model" ) == 0 ) {
- SetName( value );
- }
- else if ( strcmp( key, "_frame" ) == 0 ) {
- SetName( ValueForKey( e, "model" ) );
- }
- else if ( strcmp( key, "angle" ) == 0 ) {
- VectorSet( m_euler, 0.f, 0.f, 0.f );
- m_euler[2] = atof( value );
- UpdateCachedData();
- }
- else if ( strcmp( key, "angles" ) == 0 ) {
- VectorSet( m_euler, 0.f, 0.f, 0.f );
- if ( value[0] != '\0' ) {
- sscanf( value, "%f %f %f", &m_euler[1], &m_euler[2], &m_euler[0] );
- }
- UpdateCachedData();
- }
- else if ( strcmp( key, "modelscale" ) == 0 || strcmp( key,"modelscale_vec" ) == 0 ) {
- const char *s;
- VectorSet( m_scale, 1.f, 1.f, 1.f );
- s = ValueForKey( e,"modelscale" );
- if ( s[0] != '\0' ) {
- float f = atof( s );
- if ( f != 0 ) {
- VectorSet( m_scale, f, f, f );
- }
- else{
- Sys_FPrintf( SYS_WRN, "WARNING: ignoring 0 modelscale key\n" );
- }
- }
- s = ValueForKey( e,"modelscale_vec" );
- if ( s[0] != '\0' ) {
- sscanf( s, "%f %f %f", &m_scale[0], &m_scale[1], &m_scale[2] );
- if ( m_scale[0] == 0.0 && m_scale[1] == 0.0 && m_scale[2] == 0.0 ) {
- VectorSet( m_scale, 1,1,1 );
- Sys_FPrintf( SYS_WRN, "WARNING: ignoring 0 0 0 modelscale_vec key\n" );
- }
- }
- UpdateCachedData();
+void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected ) const {
+ if ( selected ) {
+ m_renderOrigin.render( renderer, volume, localToWorld );
}
- else if ( strcmp( key, "origin" ) == 0 ) {
- sscanf( value, "%f %f %f", &m_translate[0], &m_translate[1], &m_translate[2] );
- UpdateCachedData();
- }
- else if ( strncmp( key,"_remap",6 ) == 0 ) {
- SetName( ValueForKey( e, "model" ) );
+
+ renderer.SetState( m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly );
+}
+void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected ) const {
+ renderSolid( renderer, volume, localToWorld, selected );
+ if ( g_showNames && !string_equal( m_named.name(), "misc_model" ) ) {
+ renderer.addRenderable( m_renderName, localToWorld );
}
}
-//
-// CEntityMiscModel
-//
-
-// private:
+void translate( const Vector3& translation ){
+ m_origin = origin_translated( m_origin, translation );
+}
+void rotate( const Quaternion& rotation ){
+ m_angles = angles_rotated( m_angles, rotation );
+}
+void scale( const Vector3& scaling ){
+ m_scale = scale_scaled( m_scale, scaling );
+}
+void snapto( float snap ){
+ m_originKey.m_origin = origin_snapped( m_originKey.m_origin, snap );
+ m_originKey.write( &m_entity );
+}
+void revertTransform(){
+ m_origin = m_originKey.m_origin;
+ m_angles = m_anglesKey.m_angles;
+ m_scale = m_scaleKey.m_scale;
+}
+void freezeTransform(){
+ m_originKey.m_origin = m_origin;
+ m_originKey.write( &m_entity );
+ m_anglesKey.m_angles = m_angles;
+ m_anglesKey.write( &m_entity );
+ m_scaleKey.m_scale = m_scale;
+ m_scaleKey.write( &m_entity );
+}
+void transformChanged(){
+ revertTransform();
+ m_evaluateTransform();
+ updateTransform();
+}
+typedef MemberCaller<MiscModel, &MiscModel::transformChanged> TransformChangedCaller;
+};
+
+class MiscModelInstance : public TargetableInstance, public TransformModifier, public Renderable
+{
+class TypeCasts
+{
+InstanceTypeCastTable m_casts;
+public:
+TypeCasts(){
+ m_casts = TargetableInstance::StaticTypeCasts::instance().get();
+ InstanceStaticCast<MiscModelInstance, Renderable>::install( m_casts );
+ InstanceStaticCast<MiscModelInstance, Transformable>::install( m_casts );
+ InstanceIdentityCast<MiscModelInstance>::install( m_casts );
+}
+InstanceTypeCastTable& get(){
+ return m_casts;
+}
+};
-void CEntityMiscModel::BuildCacheRequestString( const char *name ){
- bool hasRemaps = false;
+MiscModel& m_contained;
+public:
+typedef LazyStatic<TypeCasts> StaticTypeCasts;
- m_cachereq.Format( "%s:%i", name, IntForKey( m_entity,"_frame" ) );
+STRING_CONSTANT( Name, "MiscModelInstance" );
- for ( epair_t* ep = m_entity->epairs ; ep ; ep = ep->next )
- {
- if ( strncmp( ep->key,"_remap",6 ) == 0 ) {
- if ( !hasRemaps ) {
- hasRemaps = true;
- m_cachereq += "?";
- }
- else {
- m_cachereq += "&";
- }
- m_cachereq += ep->value;
- }
- }
+MiscModelInstance( const scene::Path& path, scene::Instance* parent, MiscModel& miscmodel ) :
+ TargetableInstance( path, parent, this, StaticTypeCasts::instance().get(), miscmodel.getEntity(), *this ),
+ TransformModifier( MiscModel::TransformChangedCaller( miscmodel ), ApplyTransformCaller( *this ) ),
+ m_contained( miscmodel ){
+ m_contained.instanceAttach( Instance::path() );
+ StaticRenderableConnectionLines::instance().attach( *this );
}
-
-void CEntityMiscModel::SetName( const char *name ){
- Str m_oldcachereq = m_cachereq;
-
- if ( name[0] == '\0' ) {
- return;
+~MiscModelInstance(){
+ StaticRenderableConnectionLines::instance().detach( *this );
+ m_contained.instanceDetach( Instance::path() );
+}
+void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
+ m_contained.renderSolid( renderer, volume, Instance::localToWorld(), getSelectable().isSelected() );
+}
+void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
+ m_contained.renderWireframe( renderer, volume, Instance::localToWorld(), getSelectable().isSelected() );
+}
+void evaluateTransform(){
+ if ( getType() == TRANSFORM_PRIMITIVE ) {
+ m_contained.translate( getTranslation() );
+ m_contained.rotate( getRotation() );
+ m_contained.scale( getScale() );
}
+}
+void applyTransform(){
+ m_contained.revertTransform();
+ evaluateTransform();
+ m_contained.freezeTransform();
+}
+typedef MemberCaller<MiscModelInstance, &MiscModelInstance::applyTransform> ApplyTransformCaller;
+};
+
+class MiscModelNode :
+ public scene::Node::Symbiot,
+ public scene::Instantiable,
+ public scene::Cloneable,
+ public scene::Traversable::Observer
+{
+class TypeCasts
+{
+NodeTypeCastTable m_casts;
+public:
+TypeCasts(){
+ NodeStaticCast<MiscModelNode, scene::Instantiable>::install( m_casts );
+ NodeStaticCast<MiscModelNode, scene::Cloneable>::install( m_casts );
+ NodeContainedCast<MiscModelNode, scene::Traversable>::install( m_casts );
+ NodeContainedCast<MiscModelNode, Snappable>::install( m_casts );
+ NodeContainedCast<MiscModelNode, TransformNode>::install( m_casts );
+ NodeContainedCast<MiscModelNode, Entity>::install( m_casts );
+ NodeContainedCast<MiscModelNode, Nameable>::install( m_casts );
+ NodeContainedCast<MiscModelNode, Namespaced>::install( m_casts );
+}
+NodeTypeCastTable& get(){
+ return m_casts;
+}
+};
- BuildCacheRequestString( name );
- if ( strcmp( m_oldcachereq, m_cachereq ) == 0 ) {
- return;
- }
+scene::Node m_node;
+InstanceSet m_instances;
+MiscModel m_contained;
- if ( m_cachereq.GetBuffer()[0] != ':'
- && m_version.c_str()[0] != '\0' ) {
- GetModelCache()->DeleteByID( m_cachereq.GetBuffer(), m_version.c_str() );
- }
+void construct(){
+ m_contained.attach( this );
+}
+void destroy(){
+ m_contained.detach( this );
+}
- m_model = NULL;
+public:
+typedef LazyStatic<TypeCasts> StaticTypeCasts;
- if ( name[0] != '\0' ) {
- const char* dot = strrchr( name, '.' );
- if ( dot != NULL ) {
- m_version = ++dot;
- m_model = GetModelCache()->GetByID( m_cachereq.GetBuffer(), m_version.c_str() );
- }
- }
+scene::Traversable& get( NullType<scene::Traversable>){
+ return m_contained.getTraversable();
+}
+Snappable& get( NullType<Snappable>){
+ return m_contained;
+}
+TransformNode& get( NullType<TransformNode>){
+ return m_contained.getTransformNode();
+}
+Entity& get( NullType<Entity>){
+ return m_contained.getEntity();
+}
+Nameable& get( NullType<Nameable>){
+ return m_contained.getNameable();
+}
+Namespaced& get( NullType<Namespaced>){
+ return m_contained.getNamespaced();
+}
- UpdateCachedData();
+MiscModelNode( EntityClass* eclass ) :
+ m_node( this, this, StaticTypeCasts::instance().get() ),
+ m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<MiscModelInstance>::Caller( m_instances ) ){
+ construct();
+}
+MiscModelNode( const MiscModelNode& other ) :
+ scene::Node::Symbiot( other ),
+ scene::Instantiable( other ),
+ scene::Cloneable( other ),
+ scene::Traversable::Observer( other ),
+ m_node( this, this, StaticTypeCasts::instance().get() ),
+ m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<MiscModelInstance>::Caller( m_instances ) ){
+ construct();
+}
+~MiscModelNode(){
+ destroy();
}
+void release(){
+ delete this;
+}
+scene::Node& node(){
+ return m_node;
+}
-void CEntityMiscModel::UpdateCachedData(){
- aabb_t aabb_temp;
+scene::Node& clone() const {
+ return ( new MiscModelNode( *this ) )->node();
+}
- m4x4_identity( m_transform );
- m4x4_pivoted_transform_by_vec3( m_transform, m_translate, m_euler, eXYZ, m_scale, m_pivot );
- memcpy( m_inverse_transform, m_transform, sizeof( m4x4_t ) );
- m4x4_invert( m_inverse_transform );
+void insert( scene::Node& child ){
+ m_instances.insert( child );
+}
+void erase( scene::Node& child ){
+ m_instances.erase( child );
+}
- aabb_clear( &aabb_temp );
+scene::Instance* create( const scene::Path& path, scene::Instance* parent ){
+ return new MiscModelInstance( path, parent, m_contained );
+}
+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 );
+}
+};
- if ( m_model && m_model->pRender ) {
- aabb_extend_by_aabb( &aabb_temp, m_model->pRender->GetAABB() );
- }
- else{
- VectorSet( aabb_temp.extents, 8, 8, 8 );
- }
+scene::Node& New_MiscModel( EntityClass* eclass ){
+ return ( new MiscModelNode( eclass ) )->node();
+}
- aabb_for_transformed_aabb( &m_BBox, &aabb_temp, m_transform );
+void MiscModel_construct(){
+}
+void MiscModel_destroy(){
}