/* 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_TARGETABLE_H) #define INCLUDED_TARGETABLE_H #include #include #include "cullable.h" #include "renderable.h" #include "math/line.h" #include "render.h" #include "generic/callback.h" #include "selectionlib.h" #include "entitylib.h" #include "eclasslib.h" #include "stringio.h" class Targetable { public: virtual const Vector3& world_position() const = 0; }; typedef std::set targetables_t; extern const char* g_targetable_nameKey; targetables_t* getTargetables(const char* targetname); class EntityConnectionLine : public OpenGLRenderable { public: Vector3 start; Vector3 end; void render(RenderStateFlags state) const { float s1[2], s2[2]; Vector3 dir(vector3_subtracted(end, start)); double len = vector3_length(dir); vector3_scale(dir, 8.0 * (1.0 / len)); s1[0] = dir[0] - dir[1]; s1[1] = dir[0] + dir[1]; s2[0] = dir[0] + dir[1]; s2[1] = -dir[0] + dir[1]; glBegin(GL_LINES); glVertex3fv(vector3_to_array(start)); glVertex3fv(vector3_to_array(end)); len*=0.0625; // half / 8 Vector3 arrow(start); for (unsigned int i = 0, count = (len<32)? 1 : static_cast(len*0.0625); i < count; i++) { vector3_add(arrow, vector3_scaled(dir, (len<32)?len:32)); glVertex3fv(vector3_to_array(arrow)); glVertex3f(arrow[0]+s1[0], arrow[1]+s1[1], arrow[2]+dir[2]); glVertex3fv(vector3_to_array(arrow)); glVertex3f(arrow[0]+s2[0], arrow[1]+s2[1], arrow[2]+dir[2]); } glEnd(); } }; class TargetedEntity { Targetable& m_targetable; targetables_t* m_targets; void construct() { if(m_targets != 0) m_targets->insert(&m_targetable); } void destroy() { if(m_targets != 0) m_targets->erase(&m_targetable); } public: TargetedEntity(Targetable& targetable) : m_targetable(targetable), m_targets(getTargetables("")) { construct(); } ~TargetedEntity() { destroy(); } void targetnameChanged(const char* name) { destroy(); m_targets = getTargetables(name); construct(); } typedef MemberCaller1 TargetnameChangedCaller; }; class TargetingEntity { targetables_t* m_targets; public: TargetingEntity() : m_targets(getTargetables("")) { } void targetChanged(const char* target) { m_targets = getTargetables(target); } typedef MemberCaller1 TargetChangedCaller; typedef targetables_t::iterator iterator; iterator begin() const { if(m_targets == 0) { return iterator(); } return m_targets->begin(); } iterator end() const { if(m_targets == 0) { return iterator(); } return m_targets->end(); } size_t size() const { if(m_targets == 0) { return 0; } return m_targets->size(); } bool empty() const { return m_targets == 0 || m_targets->empty(); } }; template void TargetingEntity_forEach(const TargetingEntity& targets, const Functor& functor) { for(TargetingEntity::iterator i = targets.begin(); i != targets.end(); ++i) { functor((*i)->world_position()); } } typedef std::map TargetingEntities; template void TargetingEntities_forEach(const TargetingEntities& targetingEntities, const Functor& functor) { for(TargetingEntities::const_iterator i = targetingEntities.begin(); i != targetingEntities.end(); ++i) { TargetingEntity_forEach((*i).second, functor); } } class TargetLinesPushBack { RenderablePointVector& m_targetLines; const Vector3& m_worldPosition; const VolumeTest& m_volume; public: TargetLinesPushBack(RenderablePointVector& targetLines, const Vector3& worldPosition, const VolumeTest& volume) : m_targetLines(targetLines), m_worldPosition(worldPosition), m_volume(volume) { } void operator()(const Vector3& worldPosition) const { if(m_volume.TestLine(segment_for_startend(m_worldPosition, worldPosition))) { m_targetLines.push_back(PointVertex(reinterpret_cast(m_worldPosition))); m_targetLines.push_back(PointVertex(reinterpret_cast(worldPosition))); } } }; class TargetKeys : public EntityKeyValues::Observer { TargetingEntities m_targetingEntities; Callback m_targetsChanged; bool readTargetKey(const char* key, std::size_t& index) { if(string_equal_n(key, "target", 6)) { index = 0; if(string_empty(key + 6) || string_parse_size(key + 6, index)) { return true; } } return false; } public: void setTargetsChanged(const Callback& targetsChanged) { m_targetsChanged = targetsChanged; } void targetsChanged() { m_targetsChanged(); } void insert(const char* key, EntityKeyValues::Value& value) { std::size_t index; if(readTargetKey(key, index)) { TargetingEntities::iterator i = m_targetingEntities.insert(TargetingEntities::value_type(index, TargetingEntity())).first; value.attach(TargetingEntity::TargetChangedCaller((*i).second)); targetsChanged(); } } void erase(const char* key, EntityKeyValues::Value& value) { std::size_t index; if(readTargetKey(key, index)) { TargetingEntities::iterator i = m_targetingEntities.find(index); value.detach(TargetingEntity::TargetChangedCaller((*i).second)); m_targetingEntities.erase(i); targetsChanged(); } } const TargetingEntities& get() const { return m_targetingEntities; } }; class RenderableTargetingEntity { TargetingEntity& m_targets; mutable RenderablePointVector m_target_lines; public: static Shader* m_state; RenderableTargetingEntity(TargetingEntity& targets) : m_targets(targets), m_target_lines(GL_LINES) { } void compile(const VolumeTest& volume, const Vector3& world_position) const { m_target_lines.clear(); m_target_lines.reserve(m_targets.size() * 2); TargetingEntity_forEach(m_targets, TargetLinesPushBack(m_target_lines, world_position, volume)); } void render(Renderer& renderer, const VolumeTest& volume, const Vector3& world_position) const { if(!m_targets.empty()) { compile(volume, world_position); if(!m_target_lines.empty()) { renderer.addRenderable(m_target_lines, g_matrix4_identity); } } } }; class RenderableTargetingEntities { const TargetingEntities& m_targets; mutable RenderablePointVector m_target_lines; public: static Shader* m_state; RenderableTargetingEntities(const TargetingEntities& targets) : m_targets(targets), m_target_lines(GL_LINES) { } void compile(const VolumeTest& volume, const Vector3& world_position) const { m_target_lines.clear(); TargetingEntities_forEach(m_targets, TargetLinesPushBack(m_target_lines, world_position, volume)); } void render(Renderer& renderer, const VolumeTest& volume, const Vector3& world_position) const { if(!m_targets.empty()) { compile(volume, world_position); if(!m_target_lines.empty()) { renderer.addRenderable(m_target_lines, g_matrix4_identity); } } } }; class TargetableInstance : public SelectableInstance, public Targetable, public EntityKeyValues::Observer { mutable Vertex3f m_position; EntityKeyValues& m_entity; TargetKeys m_targeting; TargetedEntity m_targeted; RenderableTargetingEntities m_renderable; public: TargetableInstance( const scene::Path& path, scene::Instance* parent, void* instance, InstanceTypeCastTable& casts, EntityKeyValues& entity, Targetable& targetable ) : SelectableInstance(path, parent, instance, casts), m_entity(entity), m_targeted(targetable), m_renderable(m_targeting.get()) { m_entity.attach(*this); m_entity.attach(m_targeting); } ~TargetableInstance() { m_entity.detach(m_targeting); m_entity.detach(*this); } void setTargetsChanged(const Callback& targetsChanged) { m_targeting.setTargetsChanged(targetsChanged); } void targetsChanged() { m_targeting.targetsChanged(); } void insert(const char* key, EntityKeyValues::Value& value) { if(string_equal(key, g_targetable_nameKey)) { value.attach(TargetedEntity::TargetnameChangedCaller(m_targeted)); } } void erase(const char* key, EntityKeyValues::Value& value) { if(string_equal(key, g_targetable_nameKey)) { value.detach(TargetedEntity::TargetnameChangedCaller(m_targeted)); } } const Vector3& world_position() const { #if 1 const AABB& bounds = Instance::worldAABB(); if(aabb_valid(bounds)) { return bounds.origin; } #else const AABB& childBounds = Instance::childBounds(); if(aabb_valid(childBounds)) { return childBounds.origin; } #endif return localToWorld().t(); } void render(Renderer& renderer, const VolumeTest& volume) const { renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials); m_renderable.render(renderer, volume, world_position()); } const TargetingEntities& getTargeting() const { return m_targeting.get(); } }; class RenderableConnectionLines : public Renderable { typedef std::set TargetableInstances; TargetableInstances m_instances; public: void attach(TargetableInstance& instance) { ASSERT_MESSAGE(m_instances.find(&instance) == m_instances.end(), "cannot attach instance"); m_instances.insert(&instance); } void detach(TargetableInstance& instance) { ASSERT_MESSAGE(m_instances.find(&instance) != m_instances.end(), "cannot detach instance"); m_instances.erase(&instance); } void renderSolid(Renderer& renderer, const VolumeTest& volume) const { for(TargetableInstances::const_iterator i = m_instances.begin(); i != m_instances.end(); ++i) { if((*i)->path().top().get().visible()) { (*i)->render(renderer, volume); } } } void renderWireframe(Renderer& renderer, const VolumeTest& volume) const { renderSolid(renderer, volume); } }; typedef Static StaticRenderableConnectionLines; #endif