451 lines
10 KiB
C++
451 lines
10 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_TARGETABLE_H )
|
|
#define INCLUDED_TARGETABLE_H
|
|
|
|
#include <set>
|
|
#include <map>
|
|
|
|
#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<Targetable *> 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<unsigned int>( 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 MemberCaller<TargetedEntity, void (
|
|
const char *), &TargetedEntity::targetnameChanged> TargetnameChangedCaller;
|
|
};
|
|
|
|
|
|
class TargetingEntity {
|
|
targetables_t *m_targets;
|
|
public:
|
|
TargetingEntity() :
|
|
m_targets(getTargetables(""))
|
|
{
|
|
}
|
|
|
|
void targetChanged(const char *target)
|
|
{
|
|
m_targets = getTargetables(target);
|
|
}
|
|
|
|
typedef MemberCaller<TargetingEntity, void (const char *), &TargetingEntity::targetChanged> 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<typename Functor>
|
|
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<std::size_t, TargetingEntity> TargetingEntities;
|
|
|
|
template<typename Functor>
|
|
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<const Vertex3f &>( m_worldPosition )));
|
|
m_targetLines.push_back(PointVertex(reinterpret_cast<const Vertex3f &>( worldPosition )));
|
|
}
|
|
}
|
|
};
|
|
|
|
class TargetKeys : public Entity::Observer {
|
|
TargetingEntities m_targetingEntities;
|
|
Callback<void()> 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;
|
|
}
|
|
}
|
|
if (string_equal(key, "killtarget")) {
|
|
index = -1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
void setTargetsChanged(const Callback<void()> &targetsChanged)
|
|
{
|
|
m_targetsChanged = targetsChanged;
|
|
}
|
|
|
|
void targetsChanged()
|
|
{
|
|
m_targetsChanged();
|
|
}
|
|
|
|
void insert(const char *key, EntityKeyValue &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, EntityKeyValue &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 Entity::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<void()> &targetsChanged)
|
|
{
|
|
m_targeting.setTargetsChanged(targetsChanged);
|
|
}
|
|
|
|
void targetsChanged()
|
|
{
|
|
m_targeting.targetsChanged();
|
|
}
|
|
|
|
void insert(const char *key, EntityKeyValue &value)
|
|
{
|
|
if (string_equal(key, g_targetable_nameKey)) {
|
|
value.attach(TargetedEntity::TargetnameChangedCaller(m_targeted));
|
|
}
|
|
}
|
|
|
|
void erase(const char *key, EntityKeyValue &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 vector4_to_vector3(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<TargetableInstance *> 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<RenderableConnectionLines> StaticRenderableConnectionLines;
|
|
|
|
#endif
|