Files
starruler-linux/source/game/scene/node.cpp
T
2018-07-17 14:15:37 +02:00

641 lines
14 KiB
C++

#include "scene/node.h"
#include "compat/misc.h"
#include "threads.h"
#include "main/references.h"
#include "main/logging.h"
#include "render/driver.h"
#include "obj/object.h"
#include "empire.h"
#include "physics/physics_world.h"
#include "aabbox.h"
#include "memory/AllocOnlyPool.h"
#include <map>
#include <set>
extern double frameTime_s;
extern unsigned frameNumber;
Colorf fallbackNodeColor;
Object* fallbackNodeObject;
//Produce a vaguely unique value per node, consistent per node
void shader_unique(float* values,unsigned short,void*) {
if(auto* node = scene::renderingNode)
values[0] = (float)(((node - (scene::Node*)0) >> 4) & 0xff) / 255.f;
else
values[0] = 0.5f;
}
void shader_node_color(float* colors,unsigned short,void*) {
if(auto* node = scene::renderingNode)
*(Colorf*)colors = node->color;
else
*(Colorf*)colors = fallbackNodeColor;
}
void shader_node_distance(float* distance,unsigned short,void*) {
if(auto* node = scene::renderingNode)
*distance = (float)node->sortDistance;
else
*distance = 0.f;
}
void shader_node_scale(float* scale,unsigned short,void*) {
if(auto* node = scene::renderingNode)
*scale = (float)node->abs_scale;
else
*scale = 1.f;
}
void shader_node_selected(float* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
Object* obj = node->obj;
if(obj && obj->getFlag(objSelected))
*(float*)value = 1.f;
else
*(float*)value = 0.f;
}
else
*(float*)value = 0.f;
}
void shader_obj_velocity(float* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
Object* obj = node->obj;
if(obj)
*(float*)value = (obj->velocity + obj->acceleration * (frameTime_s - obj->lastTick)).getLength();
else
*(float*)value = 0.f;
}
else if(fallbackNodeObject) {
*(float*)value = (fallbackNodeObject->velocity + fallbackNodeObject->acceleration * (frameTime_s - fallbackNodeObject->lastTick)).getLength();
}
else
*(float*)value = 0.f;
}
void shader_obj_position(float* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
Object* obj = node->obj;
if(obj) {
value[0] = obj->position.x;
value[1] = obj->position.y;
value[2] = obj->position.z;
}
else {
value[0] = 0.f;
value[1] = 0.f;
value[2] = 0.f;
}
}
else if(fallbackNodeObject) {
value[0] = fallbackNodeObject->position.x;
value[1] = fallbackNodeObject->position.y;
value[2] = fallbackNodeObject->position.z;
}
else {
value[0] = 0.f;
value[1] = 0.f;
value[2] = 0.f;
}
}
void shader_obj_rotation(float* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
Object* obj = node->obj;
if(obj) {
value[0] = obj->rotation.xyz.x;
value[1] = obj->rotation.xyz.y;
value[2] = obj->rotation.xyz.z;
value[3] = obj->rotation.w;
}
else {
value[0] = 0.f;
value[1] = 0.f;
value[2] = 0.f;
value[3] = 1.f;
}
}
else if(fallbackNodeObject) {
value[0] = fallbackNodeObject->rotation.xyz.x;
value[1] = fallbackNodeObject->rotation.xyz.y;
value[2] = fallbackNodeObject->rotation.xyz.z;
value[3] = fallbackNodeObject->rotation.w;
}
else {
value[0] = 0.f;
value[1] = 0.f;
value[2] = 0.f;
value[3] = 1.f;
}
}
void shader_node_position(float* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
value[0] = node->abs_position.x;
value[1] = node->abs_position.y;
value[2] = node->abs_position.z;
}
else if(fallbackNodeObject) {
value[0] = fallbackNodeObject->position.x;
value[1] = fallbackNodeObject->position.y;
value[2] = fallbackNodeObject->position.z;
}
else {
value[0] = 0.f;
value[1] = 0.f;
value[2] = 0.f;
}
}
void shader_node_rotation(float* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
value[0] = node->abs_rotation.xyz.x;
value[1] = node->abs_rotation.xyz.y;
value[2] = node->abs_rotation.xyz.z;
value[3] = node->abs_rotation.w;
}
else if(fallbackNodeObject) {
value[0] = fallbackNodeObject->rotation.xyz.x;
value[1] = fallbackNodeObject->rotation.xyz.y;
value[2] = fallbackNodeObject->rotation.xyz.z;
value[3] = fallbackNodeObject->rotation.w;
}
else {
value[0] = 0.f;
value[1] = 0.f;
value[2] = 0.f;
value[3] = 1.f;
}
}
void shader_obj_acceleration(float* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
Object* obj = node->obj;
if(obj)
*(float*)value = obj->acceleration.getLength();
else
*(float*)value = 0.f;
}
else if(fallbackNodeObject) {
*(float*)value = fallbackNodeObject->acceleration.getLength();
}
else
*(float*)value = 0.f;
}
void shader_emp_flag(int* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
Object* obj = node->obj;
if(obj) {
Empire* owner = obj->owner;
if(owner)
*(int*)value = (int)owner->flagID;
else
*(int*)value = 0;
}
else {
*(int*)value = 0;
}
}
else if(fallbackNodeObject) {
Empire* owner = fallbackNodeObject->owner;
if(owner)
*(int*)value = (int)owner->flagID;
else
*(int*)value = 0;
}
else
*(int*)value = 0;
}
void shader_obj_id(int* value,unsigned short,void*) {
if(auto* node = scene::renderingNode) {
Object* obj = node->obj;
if(obj)
*(int*)value = obj->id;
else
*(int*)value = 0;
}
else if(fallbackNodeObject) {
*(int*)value = fallbackNodeObject->id;
}
else
*(int*)value = 0;
}
namespace scene {
const char* typeNames[NT_ScriptBase] = {
"Misc Node",
"Culling Node",
"Particle System",
"Mesh Node",
"Mesh+Icon Node"
};
extern const char* getScriptNodeName(unsigned id);
#ifdef PROFILE_ANIMATION
volatile double nodeAnimTimes[32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
threads::atomic_int nodeAnimCounts[32];
void dumpAnimationProfile() {
for(unsigned i = 0; i < 32; ++i) {
int count = nodeAnimCounts[i];
double total = nodeAnimTimes[i];
if(count == 0)
continue;
nodeAnimTimes[i] = 0;
nodeAnimCounts[i] = 0;
const char* name = nullptr;
if(i < NT_ScriptBase)
name = typeNames[i];
else
name = getScriptNodeName(i - NT_ScriptBase);
print("%s: %.1lf ms (%.1lf us per)", name, total * 1.0e3, total * 1.0e6 / double(count));
}
}
#endif
const char* Node::getName() const {
unsigned typeIndex = (unsigned)getType();
if(typeIndex < NT_ScriptBase)
return typeNames[typeIndex];
else
return getScriptNodeName(typeIndex - NT_ScriptBase);
}
Node* renderingNode;
threads::Mutex nodeEventLock;
std::vector<NodeEvent*>* pushQueue = new std::vector<NodeEvent*>, *activeQueue = new std::vector<NodeEvent*>;
memory::AllocOnlyRegion<threads::Mutex> nodeEventMemory(2048);
void* NodeEvent::operator new(size_t bytes) {
return nodeEventMemory.alloc(bytes);
}
void NodeEvent::operator delete(void* p) {
nodeEventMemory.dealloc((unsigned char*)p);
}
void queueNodeEvent(NodeEvent* evt) {
nodeEventLock.lock();
pushQueue->push_back(evt);
nodeEventLock.release();
}
void clearNodeEvents() {
nodeEventLock.lock();
for(auto i = activeQueue->begin(), end = activeQueue->end(); i != end; ++i)
delete *i;
for(auto i = pushQueue->begin(), end = pushQueue->end(); i != end; ++i)
delete *i;
activeQueue->clear();
pushQueue->clear();
nodeEventLock.release();
}
void processNodeEvents() {
if(pushQueue->empty())
return;
nodeEventLock.lock();
std::swap(pushQueue,activeQueue);
nodeEventLock.release();
for(auto i = activeQueue->begin(), end = activeQueue->end(); i != end; ++i) {
NodeEvent* evt = *i;
evt->process();
delete evt;
}
activeQueue->clear();
}
NodeEvent::NodeEvent(Node* node) : node(node) {
if(node)
node->grab();
}
NodeEvent::~NodeEvent() {
if(node)
node->drop();
}
void Node::markForDeletion() {
//TODO: This might be unsafe if the bool is packed alongside another member
queuedDelete = true;
}
class ReparentNode : public NodeEvent {
heldPointer<Node> parent;
public:
ReparentNode(Node* reparent, Node* toParent) : NodeEvent(reparent), parent(toParent) {
}
void process() override {
node->setParent(parent);
node->rebuildTransformation();
node->sortDistance = node->abs_position.distanceTo(devices.render->cam_pos);
}
};
void Node::queueReparent(Node* newParent) {
if(newParent == 0)
newParent = devices.scene;
queueNodeEvent(new ReparentNode(this, newParent));
}
class ReparentToObject : public NodeEvent {
public:
heldPointer<Object> object;
ReparentToObject(Node* reparent, Object* obj) : NodeEvent(reparent), object(obj) {
}
void process() override {
//The object can't be destroyed here, so the node should stay valid too
//TODO: Check that assumption in all cases. (True in practice now because
//region nodes are never destroyed in game)
Node* other = object->node;
if(other) {
node->setParent(other);
node->rebuildTransformation();
node->sortDistance = node->abs_position.distanceTo(devices.render->cam_pos);
}
}
};
void Node::hintParentObject(Object* obj, bool checkDistance) {
if(!obj || !obj->node) {
//We shouldn't be parented to anything
if(parent && parent != devices.scene)
queueReparent(nullptr);
}
else {
//Check that we're completely inside this object
double maxDist = obj->radius - abs_scale;
if(!checkDistance || abs_position.distanceToSQ(obj->position) < maxDist * maxDist) {
//Accessing the random object's node pointer is
//not technically safe here, but because we're only checking
//for equality it won't break.
if(parent != obj->node)
queueNodeEvent(new ReparentToObject(this, obj));
}
else {
//We're too big to be in this node, leave
if(parent && parent != devices.scene)
queueReparent(nullptr);
}
}
if(obj)
obj->drop();
}
bool Node::getFlag(NodeFlag flag) const {
return (flags & flag) != 0;
}
void Node::setFlag(NodeFlag flag, bool val) {
if(val)
flags |= flag;
else
flags &= ~flag;
}
bool Node::isChild(Node* check) {
foreach(child, children) {
if(*child == check)
return true;
}
return false;
}
void Node::addChild(Node* child) {
child->grab();
children.push_back(child);
child->setFlag(NF_Dirty, true);
child->parent = this;
}
void Node::removeChild(Node* check) {
foreach(child, children) {
if(*child == check) {
children.erase(child);
check->parent = 0;
check->drop();
return;
}
}
}
void Node::setParent(Node* newParent) {
if(newParent == parent)
return;
Node* oldParent = parent;
if(newParent)
newParent->addChild(this);
if(oldParent)
oldParent->removeChild(this);
parent = newParent;
if(newParent && oldParent)
animate();
}
void Node::destroyTree() {
foreach(child, children)
(*child)->destroyTree();
children.clear();
parent = 0;
}
void Node::destroy() {
if(physics) {
devices.nodePhysics->unregisterItem(*physics);
physics = 0;
drop();
}
if(obj) {
obj->drop();
obj = 0;
}
animator = 0;
parent = 0;
}
void Node::flagDirty() {
if(!getFlag(NF_Dirty)) {
setFlag(NF_Dirty, true);
foreach(child, children)
(*child)->flagDirty();
}
}
bool nodeSort(scene::Node* a, scene::Node* b) {
return *a < *b;
}
void Node::animate() {
#ifdef PROFILE_ANIMATION
double start = devices.driver->getAccurateTime();
#endif
if(animator)
animator->animate(this);
if(flags & NF_Dirty)
rebuildTransformation();
if(flags & NF_AnimOnlyVisible)
frameVisible = visible && preRender(*devices.render);
else
frameVisible = preRender(*devices.render) && visible;
#ifdef PROFILE_ANIMATION
double end = devices.driver->getAccurateTime();
double duration = end - start;
auto type = getType();
nodeAnimCounts[type]++;
volatile double& curTime = nodeAnimTimes[type];
while(true) {
double prev = curTime;
double result = prev + duration;
if(threads::compare_and_swap((long long*)&curTime, *(long long*)&prev, *(long long*)&result) == *(long long*)&prev)
break;
}
#endif
if(frameVisible) {
foreach(child, children)
(*child)->animate();
if(children.size() > 1)
std::sort(children.begin(), children.end(), nodeSort);
}
}
void Node::sortChildren() {
if(children.size() > 1)
std::sort(children.begin(), children.end(), nodeSort);
}
void Node::rebuildTransformation() {
setFlag(NF_Dirty, false);
if(parent && !getFlag(NF_Independent)) {
if(getFlag(NF_ParentScale)) {
abs_position = parent->abs_position + (parent->abs_rotation * position) * parent->abs_scale;
abs_rotation = parent->abs_rotation * rotation;
abs_scale = parent->abs_scale * scale;
}
else {
abs_position = parent->abs_position + position;
abs_rotation = rotation;
abs_scale = scale;
}
}
else {
abs_position = position;
abs_rotation = rotation;
abs_scale = scale;
}
if(!getFlag(NF_NoMatrix))
abs_rotation.toTransform(transformation, abs_position, abs_scale);
if(physics) {
auto bbox = AABBoxd::fromCircle(abs_position, abs_scale);
if(physics->bound != bbox)
devices.nodePhysics->updateItem(*physics, bbox);
}
}
Node::Node() : flags(NF_Dirty | NF_Independent), lastUpdate(frameTime_s), sortDistance(0), scale(1), abs_scale(1), distanceCutoff(1.0e8),
obj(0), physics(0), visible(true), frameVisible(true), queuedDelete(false), remembered(false) {}
Node::~Node() {
destroy();
}
void Node::setObject(Object* Obj) {
if(Obj == obj)
return;
if(Obj)
Obj->grab();
if(obj)
obj->drop();
obj = Obj;
}
Object* Node::getObject() {
if(obj)
obj->grab();
return obj;
}
void Node::createPhysics() {
if(physics || !devices.nodePhysics)
return;
rebuildTransformation();
grab();
physics = devices.nodePhysics->registerItem(AABBoxd::fromCircle(abs_position, abs_scale), this);
}
bool Node::operator<(const scene::Node& other) const {
if(frameVisible && other.frameVisible) {
bool alpha = getFlag(NF_Transparent);
if(alpha != other.getFlag(NF_Transparent)) {
if(alpha)
return false;
else
return true;
}
else if(alpha) {
return sortDistance > other.sortDistance;
}
else {
return sortDistance < other.sortDistance;
}
}
else if(frameVisible) {
return true;
}
else {
return false;
}
}
void Node::_render(render::RenderDriver& driver) {
renderingNode = this;
render(driver);
if(auto cnt = children.size()) {
for(decltype(cnt) i = 0; i < cnt;) {
scene::Node* child = children[i];
if(!child->queuedDelete) {
++i;
if(child->frameVisible)
child->_render(driver);
}
else {
child->destroy();
child->drop();
children[i] = children[cnt-1];
--cnt;
}
}
if(cnt != children.size())
children.resize(cnt);
}
}
};