Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
#include "anim_group.h"
|
||||
#include "scene/node.h"
|
||||
#include "obj/obj_group.h"
|
||||
#include "obj/object.h"
|
||||
#include "empire.h"
|
||||
|
||||
extern double frameLen_s, frameTime_s;
|
||||
|
||||
namespace scene {
|
||||
void GroupAnim::animate(Node* node) {
|
||||
if(group && group->getObjectCount() != 0) {
|
||||
double tickTime = std::min(node->sortDistance * 3e-5, 3.0);
|
||||
|
||||
Object* obj = group->getOwner();
|
||||
if(node->obj != obj)
|
||||
node->setObject(obj);
|
||||
|
||||
if(obj) {
|
||||
if(Empire* owner = obj->owner)
|
||||
node->color = owner->color;
|
||||
|
||||
if(obj->lastTick > node->lastUpdate + tickTime) {
|
||||
node->position = node->position.interpolate(group->getCenter(), frameLen_s/(obj->lastTick - node->lastUpdate));
|
||||
node->rotation = node->rotation.slerp(group->formationFacing, frameLen_s/(obj->lastTick - node->lastUpdate));
|
||||
|
||||
node->lastUpdate = frameTime_s;
|
||||
node->rebuildTransformation();
|
||||
node->visible = obj->isVisibleTo(Empire::getPlayerEmpire());
|
||||
}
|
||||
else {
|
||||
node->position = group->getCenter();
|
||||
node->rotation = group->formationFacing;
|
||||
node->rebuildTransformation();
|
||||
node->visible = obj->isVisibleTo(Empire::getPlayerEmpire());
|
||||
}
|
||||
}
|
||||
else {
|
||||
node->visible = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
node->markForDeletion();
|
||||
if(group) {
|
||||
group->drop();
|
||||
group = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupAnim::~GroupAnim() {
|
||||
if(group)
|
||||
group->drop();
|
||||
}
|
||||
|
||||
GroupAnim::GroupAnim(ObjectGroup* Group) : group(Group) {
|
||||
group->grab();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "animator.h"
|
||||
|
||||
class ObjectGroup;
|
||||
|
||||
namespace scene {
|
||||
|
||||
class GroupAnim : public Animator {
|
||||
ObjectGroup* group;
|
||||
public:
|
||||
void animate(Node* node);
|
||||
|
||||
GroupAnim(ObjectGroup* group);
|
||||
~GroupAnim();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "anim_linear.h"
|
||||
#include "scene/node.h"
|
||||
|
||||
extern double frameTime_s;
|
||||
|
||||
namespace scene {
|
||||
|
||||
LinearMotionAnim::LinearMotionAnim(const vec3d& Velocity) : velocity(Velocity) {}
|
||||
|
||||
void LinearMotionAnim::animate(Node* node) {
|
||||
node->position += velocity * (frameTime_s - node->lastUpdate);
|
||||
node->rebuildTransformation();
|
||||
node->lastUpdate = frameTime_s;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "animator.h"
|
||||
#include "vec3.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
class LinearMotionAnim : public Animator {
|
||||
public:
|
||||
vec3d velocity;
|
||||
|
||||
void animate(Node* node);
|
||||
|
||||
LinearMotionAnim(const vec3d& Velocity);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "anim_node_sync.h"
|
||||
#include "scene/node.h"
|
||||
#include "obj/object.h"
|
||||
#include "empire.h"
|
||||
#include "main/references.h"
|
||||
|
||||
extern double frameLen_s, frameTime_s;
|
||||
|
||||
namespace scene {
|
||||
|
||||
void NodeSyncAnimator::animate(Node* node) {
|
||||
Object* obj = node->obj;
|
||||
if(obj) {
|
||||
auto* player = Empire::getPlayerEmpire();
|
||||
|
||||
bool vis = false;
|
||||
|
||||
vis = obj->isVisibleTo(player) && obj->position.distanceToSQ(devices.render->cam_pos) < (node->distanceCutoff * obj->radius * obj->radius);
|
||||
bool wasVisible = node->visible;
|
||||
|
||||
if(!vis && node->getFlag(NF_Memorable) && (node->remembered || obj->isKnownTo(player))) {
|
||||
vis = true;
|
||||
node->remembered = true;
|
||||
}
|
||||
else if(vis) {
|
||||
node->remembered = false;
|
||||
}
|
||||
|
||||
if(!vis && !wasVisible && node->getFlag(NF_AnimOnlyVisible)) {
|
||||
node->visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if(obj->isFocus()) {
|
||||
//TODO: This should be handled better/centrally
|
||||
obj->setFlag(objFocus, false);
|
||||
}
|
||||
|
||||
double interpPct = 1.0;
|
||||
if(vis && wasVisible) {
|
||||
if(obj->lastTick > node->lastUpdate + 1.0/60.0) {
|
||||
interpPct = (frameTime_s - node->lastUpdate)/(obj->lastTick - node->lastUpdate);
|
||||
node->position = node->position.interpolate(obj->position, interpPct);
|
||||
}
|
||||
else {
|
||||
//Interpolate to a predicted position based on physics data
|
||||
double tDiff = frameTime_s - obj->lastTick;
|
||||
vec3d predicted = obj->position + (obj->velocity + obj->acceleration * (tDiff * 0.5)) * tDiff;
|
||||
//node->position = node->position.interpolate(predicted, (frameTime_s - node->lastUpdate) / tDiff);
|
||||
node->position = predicted;
|
||||
interpPct = tDiff / (node->lastUpdate - obj->lastTick);
|
||||
}
|
||||
}
|
||||
else {
|
||||
double tDiff = frameTime_s - obj->lastTick;
|
||||
node->position = obj->position + (obj->velocity + obj->acceleration * (tDiff * 0.5)) * tDiff;
|
||||
}
|
||||
node->lastUpdate = frameTime_s;
|
||||
|
||||
node->visible = vis;
|
||||
|
||||
//Some updates only need performed for visible objects
|
||||
if(vis || !node->getFlag(NF_AnimOnlyVisible)) {
|
||||
if(!node->remembered && !node->getFlag(NF_CustomColor))
|
||||
if(Empire* owner = obj->owner)
|
||||
node->color = owner->color;
|
||||
|
||||
if(wasVisible)
|
||||
node->rotation = node->rotation.slerp(obj->rotation, interpPct);
|
||||
else
|
||||
node->rotation = obj->rotation;
|
||||
|
||||
node->rebuildTransformation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeSyncAnimator syncAnimator;
|
||||
|
||||
NodeSyncAnimator* NodeSyncAnimator::getSingleton() {
|
||||
return &syncAnimator;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "animator.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
class NodeSyncAnimator : public Animator {
|
||||
public:
|
||||
void animate(Node* node);
|
||||
|
||||
static NodeSyncAnimator* getSingleton();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "anim_projectile.h"
|
||||
#include "scene/node.h"
|
||||
#include "scene/beam_node.h"
|
||||
#include <stdio.h>
|
||||
|
||||
extern double frameTime_s;
|
||||
|
||||
namespace scene {
|
||||
|
||||
ProjectileAnim::ProjectileAnim(const vec3d& Velocity) : velocity(Velocity) {
|
||||
}
|
||||
|
||||
void ProjectileAnim::animate(Node* node) {
|
||||
node->position += velocity * (frameTime_s - node->lastUpdate);
|
||||
node->rebuildTransformation();
|
||||
node->lastUpdate = frameTime_s;
|
||||
}
|
||||
|
||||
void BeamAnim::animate(Node* node) {
|
||||
BeamNode* beam = (BeamNode*)node;
|
||||
if(beam->endPosition.zero())
|
||||
return;
|
||||
node->visible = true;
|
||||
|
||||
if(follow) {
|
||||
node->position = follow->abs_position + offset;
|
||||
node->rebuildTransformation();
|
||||
}
|
||||
|
||||
float curLength = beam->abs_position.distanceTo(beam->endPosition);
|
||||
beam->uvLength = curLength / length;
|
||||
}
|
||||
|
||||
BeamAnim::BeamAnim(Node* Follow, vec3d Offset, float range) : follow(Follow), offset(Offset), length(range) {
|
||||
if(Follow)
|
||||
Follow->grab();
|
||||
}
|
||||
|
||||
BeamAnim::~BeamAnim() {
|
||||
if(follow)
|
||||
follow->drop();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "animator.h"
|
||||
#include "vec3.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
class ProjectileAnim : public Animator {
|
||||
public:
|
||||
vec3d velocity;
|
||||
|
||||
void animate(Node* node);
|
||||
|
||||
ProjectileAnim(const vec3d& Velocity);
|
||||
};
|
||||
|
||||
class BeamAnim : public Animator {
|
||||
scene::Node* follow;
|
||||
vec3d offset;
|
||||
float length;
|
||||
public:
|
||||
void animate(Node* node);
|
||||
BeamAnim(Node* Follow, vec3d Offset, float range);
|
||||
~BeamAnim();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "util/refcount.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
class Node;
|
||||
|
||||
class Animator : public AtomicRefCounted {
|
||||
public:
|
||||
|
||||
virtual void animate(Node* node) = 0;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "beam_node.h"
|
||||
#include "render/driver.h"
|
||||
#include "aabbox.h"
|
||||
#include "frustum.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
|
||||
extern double pixelSizeRatio;
|
||||
|
||||
namespace scene {
|
||||
|
||||
BeamNode::BeamNode(const render::RenderState* Material, float Width, const vec3d& startPoint, const vec3d& endPoint, bool StaticSize)
|
||||
: material(Material), width(Width), endPosition(endPoint), staticSize(StaticSize), uvLength(1.f)
|
||||
{
|
||||
position = abs_position = startPoint;
|
||||
setFlag(NF_NoMatrix, true);
|
||||
if(Material->baseMat != render::MAT_Solid)
|
||||
setFlag(NF_Transparent, true);
|
||||
}
|
||||
|
||||
bool BeamNode::preRender(render::RenderDriver& driver) {
|
||||
auto& cam_pos = driver.cam_pos;
|
||||
|
||||
if(!visible)
|
||||
return false;
|
||||
|
||||
line3dd line(abs_position, endPosition);
|
||||
AABBoxd box(line);
|
||||
|
||||
if(box.overlaps(driver.getViewFrustum().bound)) {
|
||||
sortDistance = line.getClosestPoint(cam_pos, false).distanceTo(cam_pos);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BeamNode::render(render::RenderDriver& driver) {
|
||||
auto& cam_pos = driver.cam_pos;
|
||||
|
||||
double size = width * abs_scale;
|
||||
if(staticSize)
|
||||
size *= sortDistance / pixelSizeRatio;
|
||||
|
||||
vec3d offset = (endPosition - abs_position).cross(driver.cam_facing).normalized(size);
|
||||
|
||||
auto* buffer = render::VertexBufferTCV::fetch(material);
|
||||
auto* verts = buffer->request(1, render::PT_Quads);
|
||||
|
||||
Color col = color;
|
||||
|
||||
verts[0].pos = vec3f(abs_position + offset - cam_pos);
|
||||
verts[0].uv = vec2f(0,0);
|
||||
verts[0].col = col;
|
||||
|
||||
verts[1].pos = vec3f(endPosition + offset - cam_pos);
|
||||
verts[1].uv = vec2f(uvLength,0);
|
||||
verts[1].col = col;
|
||||
|
||||
verts[2].pos = vec3f(endPosition - offset - cam_pos);
|
||||
verts[2].uv = vec2f(uvLength,1);
|
||||
verts[2].col = col;
|
||||
|
||||
verts[3].pos = vec3f(abs_position - offset - cam_pos);
|
||||
verts[3].uv = vec2f(0,1);
|
||||
verts[3].col = col;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
|
||||
namespace render {
|
||||
struct RenderState;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
class BeamNode : public Node {
|
||||
const render::RenderState* material;
|
||||
public:
|
||||
float width, uvLength;
|
||||
vec3d endPosition;
|
||||
bool staticSize;
|
||||
|
||||
BeamNode(const render::RenderState* Material, float Width, const vec3d& startPoint, const vec3d& endPoint, bool StaticSize = false);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "scene/billboard_node.h"
|
||||
#include "render/driver.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
BillboardNode::BillboardNode(const render::RenderState* Material, double Width)
|
||||
: material(Material), width(Width)
|
||||
{
|
||||
setFlag(NF_NoMatrix, true);
|
||||
if(Material->baseMat != render::MAT_Solid)
|
||||
setFlag(NF_Transparent, true);
|
||||
}
|
||||
|
||||
void BillboardNode::setWidth(double Width) {
|
||||
if(width != Width)
|
||||
width = Width;
|
||||
}
|
||||
|
||||
bool BillboardNode::preRender(render::RenderDriver& driver) {
|
||||
auto fromCamera = abs_position - driver.cam_pos;
|
||||
if(fromCamera.dot(driver.cam_facing) > 0) {
|
||||
sortDistance = fromCamera.getLength();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BillboardNode::render(render::RenderDriver& driver) {
|
||||
driver.switchToRenderState(*material);
|
||||
driver.drawBillboard(abs_position, width * abs_scale);
|
||||
}
|
||||
|
||||
SpriteNode::SpriteNode(const render::Sprite& sprt, double Width)
|
||||
: sprite(sprt), width(Width)
|
||||
{
|
||||
setFlag(NF_NoMatrix, true);
|
||||
if(sprt.mat) {
|
||||
if(sprt.mat->baseMat != render::MAT_Solid)
|
||||
setFlag(NF_Transparent, true);
|
||||
}
|
||||
else if(sprt.sheet) {
|
||||
if(sprt.sheet->material.baseMat != render::MAT_Solid)
|
||||
setFlag(NF_Transparent, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteNode::setWidth(double Width) {
|
||||
if(width != Width)
|
||||
width = Width;
|
||||
}
|
||||
|
||||
bool SpriteNode::preRender(render::RenderDriver& driver) {
|
||||
auto fromCamera = abs_position - driver.cam_pos;
|
||||
if(fromCamera.dot(driver.cam_facing) > 0) {
|
||||
sortDistance = fromCamera.getLength();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteNode::render(render::RenderDriver& driver) {
|
||||
if(sprite.mat) {
|
||||
driver.switchToRenderState(*sprite.mat);
|
||||
driver.drawBillboard(abs_position, width * abs_scale);
|
||||
}
|
||||
else {
|
||||
driver.drawBillboard(abs_position, width * abs_scale, sprite.sheet->material, sprite.sheet->getSource(sprite.index), 0, Color());
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
#include "render/spritesheet.h"
|
||||
|
||||
namespace render {
|
||||
struct RenderState;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
class BillboardNode : public Node {
|
||||
const render::RenderState* material;
|
||||
double width;
|
||||
public:
|
||||
BillboardNode(const render::RenderState* Material, double Width);
|
||||
void setWidth(double Width);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
class SpriteNode : public Node {
|
||||
public:
|
||||
render::Sprite sprite;
|
||||
double width;
|
||||
|
||||
SpriteNode(const render::Sprite& sprt, double Width);
|
||||
void setWidth(double Width);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "culling_node.h"
|
||||
#include "main/references.h"
|
||||
#include "render/driver.h"
|
||||
#include "frustum.h"
|
||||
#include "obj/object.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
CullingNode::CullingNode(vec3d pos, double rad) {
|
||||
position = pos;
|
||||
scale = rad;
|
||||
setFlag(NF_Dirty, true);
|
||||
setFlag(NF_Transparent, true);
|
||||
setFlag(NF_NoMatrix, true);
|
||||
}
|
||||
|
||||
bool CullingNode::preRender(render::RenderDriver& driver) {
|
||||
if(!driver.getViewFrustum().overlaps(abs_position,abs_scale))
|
||||
return false;
|
||||
else {
|
||||
sortDistance = driver.cam_pos.distanceTo(abs_position);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void CullingNode::render(render::RenderDriver& driver) {
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
class CullingNode : public Node {
|
||||
public:
|
||||
CullingNode(vec3d position, double radius);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
|
||||
NodeType getType() const override { return NT_Culling; };
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "frame_line.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include <algorithm>
|
||||
|
||||
extern double frameLen_s;
|
||||
|
||||
namespace scene {
|
||||
FrameLineNode::FrameLineNode(const render::RenderState& material, float duration) : mat(material), interpDuration(duration), age(0), life(1000.f), fadeAge(1000.f) {
|
||||
setFlag(NF_NoMatrix, true);
|
||||
}
|
||||
|
||||
bool FrameLineNode::preRender(render::RenderDriver& driver) {
|
||||
if(frameLen_s > 0) {
|
||||
age += (float)frameLen_s;
|
||||
endPos = abs_position.interpolate(prevPos, interpDuration / frameLen_s);
|
||||
prevPos = abs_position;
|
||||
}
|
||||
|
||||
vec3d off = abs_position - driver.cam_pos;
|
||||
sortDistance = off.getLength();
|
||||
return off.dot(driver.cam_facing) > 0;
|
||||
}
|
||||
|
||||
void FrameLineNode::render(render::RenderDriver& driver) {
|
||||
auto& camPos = driver.cam_pos;
|
||||
|
||||
auto* verts = render::VertexBufferTCV::fetch(&mat)->request(1, render::PT_Lines);
|
||||
|
||||
float alpha = age > fadeAge ? 1.f - std::min((age-fadeAge)/(life-fadeAge), 1.f) : 1.f;
|
||||
|
||||
verts[0].col = startCol;
|
||||
verts[0].col.a *= alpha;
|
||||
verts[0].pos = vec3f(abs_position - camPos);
|
||||
verts[0].uv = vec2f(0,0);
|
||||
|
||||
verts[1].col = endCol;
|
||||
verts[1].col.a *= alpha;
|
||||
verts[1].pos = vec3f(endPos - camPos);
|
||||
verts[1].uv = vec2f(1,0);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "node.h"
|
||||
#include "render/render_state.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
class FrameLineNode : public Node {
|
||||
//Stored previous positions so a trail can be rendered
|
||||
|
||||
const render::RenderState& mat;
|
||||
|
||||
public:
|
||||
vec3d endPos, prevPos;
|
||||
float interpDuration;
|
||||
Color startCol, endCol;
|
||||
float age, life, fadeAge;
|
||||
|
||||
FrameLineNode(const render::RenderState& material, float duration);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "icon_node.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/spritesheet.h"
|
||||
#include "obj/object.h"
|
||||
#include "frustum.h"
|
||||
|
||||
const double MinRenderDistanceFactor = 5000.0;
|
||||
|
||||
namespace scene {
|
||||
|
||||
|
||||
IconNode::IconNode(const render::SpriteSheet* spriteSheet, unsigned spriteIndex) : sheet(spriteSheet), index(spriteIndex) {
|
||||
setFlag(NF_NoMatrix, true);
|
||||
if(spriteSheet->material.baseMat != render::MAT_Solid)
|
||||
setFlag(NF_Transparent, true);
|
||||
}
|
||||
|
||||
bool IconNode::preRender(render::RenderDriver& driver) {
|
||||
auto fromCamera = abs_position + (driver.cam_facing * abs_scale) - driver.cam_pos;
|
||||
sortDistance = fromCamera.getLength();
|
||||
|
||||
if(sortDistance < MinRenderDistanceFactor * abs_scale || !driver.getViewFrustum().overlaps(abs_position,abs_scale))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void IconNode::render(render::RenderDriver& driver) {
|
||||
double iconScale = sortDistance * abs_scale * 0.25;
|
||||
bool selected = false;
|
||||
if(obj)
|
||||
selected = obj->getFlag(objSelected);
|
||||
if(selected)
|
||||
iconScale *= 1.1;
|
||||
|
||||
//Calculate best-fit facing of the 2D sprite (Assuming the sprite is facing +x)
|
||||
auto& cam_up = driver.cam_up;
|
||||
auto& cam_facing = driver.cam_facing;
|
||||
|
||||
double rot;
|
||||
|
||||
vec3d obj_facing = rotation * vec3d::front();
|
||||
double alongDot = cam_facing.dot(obj_facing);
|
||||
|
||||
//If it's facing along the camera vector, we can't get an accurate angle
|
||||
if(alongDot > -0.999 && alongDot < 0.999) {
|
||||
obj_facing -= cam_facing * alongDot;
|
||||
obj_facing.normalize();
|
||||
|
||||
vec3d cam_right = cam_up.cross(cam_facing);
|
||||
rot = acos(cam_right.dot(obj_facing));
|
||||
if(cam_up.dot(obj_facing) < 0)
|
||||
rot = -rot;
|
||||
}
|
||||
else {
|
||||
//Point up when going away, down when coming toward
|
||||
rot = alongDot > 0 ? pi * 0.5 : pi * -0.5;
|
||||
}
|
||||
|
||||
driver.drawBillboard(abs_position, iconScale,
|
||||
sheet->material, sheet->getSource(index), rot);
|
||||
|
||||
//TODO: Draw other icons based on group unit count
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
|
||||
namespace render {
|
||||
class SpriteSheet;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
class IconNode : public Node {
|
||||
public:
|
||||
const render::SpriteSheet* sheet;
|
||||
unsigned index;
|
||||
|
||||
IconNode(const render::SpriteSheet* spriteSheet, unsigned spriteIndex);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,366 @@
|
||||
#include "line_trail_node.h"
|
||||
#include "render/driver.h"
|
||||
#include "compat/gl.h"
|
||||
#include "main/references.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
|
||||
extern double frameLen_s;
|
||||
extern double pixelSizeRatio;
|
||||
|
||||
namespace render {
|
||||
extern const RenderMesh* lastRenderedMesh;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
LineTrailNode::LineTrailNode(const render::RenderState& material) :
|
||||
storedPositions(0), firstIndex(1), stored_s(0), lineLen_s(1), qualitySteps(1), mat(material)
|
||||
{
|
||||
setFlag(NF_NoMatrix, true);
|
||||
setFlag(NF_Independent, false);
|
||||
}
|
||||
|
||||
bool LineTrailNode::preRender(render::RenderDriver& driver) {
|
||||
rebuildTransformation();
|
||||
|
||||
stored_s += frameLen_s;
|
||||
double step = lineLen_s / double(LINE_POS_COUNT-1);
|
||||
|
||||
if(stored_s >= step) {
|
||||
stored_s -= step;
|
||||
|
||||
--firstIndex;
|
||||
if(firstIndex >= LINE_POS_COUNT)
|
||||
firstIndex = LINE_POS_COUNT - 1;
|
||||
if(storedPositions < LINE_POS_COUNT)
|
||||
++storedPositions;
|
||||
|
||||
prevPositions[firstIndex] = abs_position;
|
||||
}
|
||||
|
||||
sortDistance = abs_position.distanceTo(driver.cam_pos);
|
||||
|
||||
return sortDistance < (3500.0 * pixelSizeRatio);
|
||||
}
|
||||
|
||||
void LineTrailNode::render(render::RenderDriver& driver) {
|
||||
auto* vertBuffer = render::VertexBufferTCV::fetch(&mat);
|
||||
auto* v = vertBuffer->request(storedPositions + 1, render::PT_LineStrip);
|
||||
float startPct = (float)(stored_s / lineLen_s);
|
||||
|
||||
auto& camPos = driver.cam_pos;
|
||||
|
||||
v[0].pos = vec3f(abs_position - camPos);
|
||||
v[0].col = startCol;
|
||||
v[0].uv = vec2f(0, 0);
|
||||
|
||||
for(unsigned i = 0; i < storedPositions; ++i) {
|
||||
auto& vert = v[i+1];
|
||||
unsigned index = (firstIndex + i) % LINE_POS_COUNT;
|
||||
vert.pos = vec3f(prevPositions[index] - camPos);
|
||||
|
||||
float pct = startPct + (float(i) / float(LINE_POS_COUNT-1));
|
||||
|
||||
vert.col = startCol.getInterpolated(endCol, pct);
|
||||
|
||||
vert.uv = vec2f(pct, float(index) / float(LINE_POS_COUNT));
|
||||
}
|
||||
}
|
||||
|
||||
ProjectileBatch::ProjectileBatch() {
|
||||
setFlag(NF_Transparent, true);
|
||||
sortDistance = 0.0;
|
||||
}
|
||||
|
||||
ProjectileBatch::~ProjectileBatch() {
|
||||
for(auto i = projectiles.begin(), end = projectiles.end(); i != end; ++i) {
|
||||
auto* projs = i->second;
|
||||
if(!projs)
|
||||
continue;
|
||||
|
||||
for(unsigned ind = 0, cnt = projs->size(); ind != cnt; ++ind) {
|
||||
auto& proj = projs->at(ind);
|
||||
proj.kill->drop();
|
||||
}
|
||||
|
||||
delete projs;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectileBatch::registerProj(const render::RenderState& mat, ProjEffect& eff) {
|
||||
auto*& projs = projectiles[&mat];
|
||||
if(!projs)
|
||||
projs = new std::vector<ProjEffect>;
|
||||
projs->push_back(eff);
|
||||
}
|
||||
|
||||
bool ProjectileBatch::preRender(render::RenderDriver& driver) {
|
||||
if(projectiles.empty())
|
||||
return false;
|
||||
float time = (float)frameLen_s;
|
||||
|
||||
for(auto i = projectiles.begin(), end = projectiles.end(); i != end; ++i) {
|
||||
auto* projs = i->second;
|
||||
if(!projs || projs->empty())
|
||||
continue;
|
||||
|
||||
auto* pProj = &(*projs)[0];
|
||||
unsigned copyOff = 0;
|
||||
for(unsigned ind = 0, cnt = projs->size(); ind != cnt; ++ind) {
|
||||
auto& proj = pProj[ind];
|
||||
proj.life -= time;
|
||||
if(proj.life < 0 || **proj.kill) {
|
||||
copyOff += 1;
|
||||
proj.kill->drop();
|
||||
}
|
||||
else {
|
||||
proj.pos += vec3d(proj.dir * (proj.speed * time));
|
||||
if(copyOff)
|
||||
(*projs)[ind - copyOff] = proj;
|
||||
}
|
||||
}
|
||||
|
||||
if(copyOff)
|
||||
projs->resize(projs->size() - copyOff);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProjectileBatch::render(render::RenderDriver& driver) {
|
||||
auto& camPos = driver.cam_pos;
|
||||
|
||||
for(auto i = projectiles.begin(), end = projectiles.end(); i != end; ++i) {
|
||||
auto* projs = i->second;
|
||||
if(!projs)
|
||||
continue;
|
||||
|
||||
unsigned count = projs->size();
|
||||
if(count == 0)
|
||||
continue;
|
||||
|
||||
if(projs->begin()->line) {
|
||||
//Draw out projectiles in batches of 128
|
||||
unsigned start = 0;
|
||||
while(start < count) {
|
||||
unsigned drawCount;
|
||||
if(start + 128 <= count)
|
||||
drawCount = 128;
|
||||
else
|
||||
drawCount = count - start;
|
||||
|
||||
auto* buffer = render::VertexBufferTCV::fetch(i->first);
|
||||
auto* verts = buffer->request(drawCount, render::PT_Lines);
|
||||
|
||||
for(unsigned p = start; p < start + drawCount; ++p, verts += 2) {
|
||||
auto& proj = (*projs)[p];
|
||||
float alpha = std::min(1.f, proj.life / proj.fadeStart);
|
||||
|
||||
verts[0].pos = vec3f(proj.pos - camPos);
|
||||
verts[0].uv = vec2f(0,0);
|
||||
verts[0].col = proj.start;
|
||||
verts[0].col.a *= alpha;
|
||||
|
||||
verts[1].pos = vec3f(proj.pos - vec3d(proj.dir * proj.length) - camPos);
|
||||
verts[1].uv = vec2f(1,0);
|
||||
verts[1].col = proj.end;
|
||||
verts[1].col.a *= alpha;
|
||||
}
|
||||
|
||||
start += drawCount;
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& cam_pos = driver.cam_pos;
|
||||
double aspect = 1.0;
|
||||
if(i->first->textures[0]) {
|
||||
auto size = i->first->textures[0]->size;
|
||||
if(size.x > 0 && size.y > 0)
|
||||
aspect = (float)size.y / (float)size.x;
|
||||
}
|
||||
|
||||
//Draw out projectiles in batches of 64
|
||||
unsigned start = 0;
|
||||
while(start < count) {
|
||||
unsigned drawCount;
|
||||
if(start + 64 <= count)
|
||||
drawCount = 64;
|
||||
else
|
||||
drawCount = count - start;
|
||||
|
||||
auto* buffer = render::VertexBufferTCV::fetch(i->first);
|
||||
auto* verts = buffer->request(drawCount, render::PT_Quads);
|
||||
|
||||
for(unsigned p = start; p < start + drawCount; ++p, verts += 4) {
|
||||
auto& proj = (*projs)[p];
|
||||
float alpha = std::min(1.f, proj.life / proj.fadeStart);
|
||||
|
||||
double size = aspect * proj.length * 0.5;
|
||||
|
||||
vec3d end = proj.pos - vec3d(proj.dir * proj.length);
|
||||
|
||||
vec3d offset = vec3d(proj.pos - end).cross(driver.cam_facing).normalized(size);
|
||||
|
||||
Color col = color;
|
||||
|
||||
verts[0].pos = vec3f(end + offset - cam_pos);
|
||||
verts[0].uv = vec2f(0,0);
|
||||
verts[0].col = proj.end;
|
||||
verts[0].col.a *= alpha;
|
||||
|
||||
verts[1].pos = vec3f(proj.pos + offset - cam_pos);
|
||||
verts[1].uv = vec2f(1.f,0);
|
||||
verts[1].col = proj.start;
|
||||
verts[1].col.a *= alpha;
|
||||
|
||||
verts[2].pos = vec3f(proj.pos - offset - cam_pos);
|
||||
verts[2].uv = vec2f(1.f,1.f);
|
||||
verts[2].col = proj.start;
|
||||
verts[2].col.a *= alpha;
|
||||
|
||||
verts[3].pos = vec3f(end - offset - cam_pos);
|
||||
verts[3].uv = vec2f(0,1.f);
|
||||
verts[3].col = proj.end;
|
||||
verts[3].col.a *= alpha;
|
||||
}
|
||||
|
||||
start += drawCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MissileBatch::MissileBatch() {
|
||||
setFlag(NF_Transparent, true);
|
||||
sortDistance = 0.0;
|
||||
}
|
||||
|
||||
MissileBatch::~MissileBatch() {
|
||||
for(auto i = missiles.begin(), end = missiles.end(); i != end; ++i) {
|
||||
auto* msls = i->second;
|
||||
if(!msls)
|
||||
continue;
|
||||
|
||||
for(unsigned ind = 0, cnt = msls->size(); ind != cnt; ++ind) {
|
||||
auto& missile = msls->at(ind);
|
||||
missile.track->drop();
|
||||
}
|
||||
|
||||
delete msls;
|
||||
}
|
||||
}
|
||||
|
||||
void MissileBatch::registerProj(const render::RenderState& mat, const render::RenderState& trail, MissileTrail& eff) {
|
||||
eff.lineStart = eff.lineCount = 0;
|
||||
eff.lineProgress = eff.startProgress = 0;
|
||||
MissileTrailMats mats = {&mat, &trail};
|
||||
auto*& msls = missiles[mats];
|
||||
if(!msls)
|
||||
msls = new std::vector<MissileTrail>;
|
||||
msls->push_back(eff);
|
||||
}
|
||||
|
||||
bool MissileBatch::preRender(render::RenderDriver& driver) {
|
||||
if(missiles.empty())
|
||||
return false;
|
||||
if(frameLen_s == 0)
|
||||
return true;
|
||||
|
||||
double gameTime = devices.driver->getGameTime();
|
||||
|
||||
for(auto i = missiles.begin(), end = missiles.end(); i != end; ++i) {
|
||||
auto* msls = i->second;
|
||||
if(!msls)
|
||||
continue;
|
||||
|
||||
unsigned copyOff = 0;
|
||||
for(unsigned ind = 0, cnt = msls->size(); ind != cnt; ++ind) {
|
||||
auto& missile = (*msls)[ind];
|
||||
auto& track = **missile.track;
|
||||
if(track.aliveUntil >= 0 && gameTime > track.aliveUntil) {
|
||||
if((int)missile.lineCount <= (int)missile.startProgress) {
|
||||
copyOff += 1;
|
||||
missile.track->drop();
|
||||
}
|
||||
else {
|
||||
missile.startProgress += frameLen_s * float(LINE_POS_COUNT) / missile.length;
|
||||
|
||||
if(copyOff)
|
||||
(*msls)[ind - copyOff] = missile;
|
||||
}
|
||||
}
|
||||
else {
|
||||
missile.pos = (missile.pos).interpolate(track.pos + vec3d(track.vel) * (gameTime - track.lastUpdate), std::min(frameLen_s / 0.25, 1.0));
|
||||
missile.lastUpdate = gameTime;
|
||||
|
||||
missile.lineProgress += frameLen_s * float(LINE_POS_COUNT) / missile.length;
|
||||
while(missile.lineProgress >= 1.f) {
|
||||
missile.lineProgress -= 1.f;
|
||||
missile.lineCount += 1;
|
||||
missile.lineStart = (missile.lineStart + LINE_POS_COUNT - 1) % LINE_POS_COUNT;
|
||||
if(missile.lineCount > LINE_POS_COUNT)
|
||||
missile.lineCount = LINE_POS_COUNT;
|
||||
missile.trail[missile.lineStart] = missile.pos;
|
||||
}
|
||||
|
||||
if(copyOff)
|
||||
(*msls)[ind - copyOff] = missile;
|
||||
}
|
||||
}
|
||||
|
||||
if(copyOff)
|
||||
msls->resize(msls->size() - copyOff);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MissileBatch::render(render::RenderDriver& driver) {
|
||||
double gameTime = devices.driver->getGameTime();
|
||||
|
||||
for(auto i = missiles.begin(), end = missiles.end(); i != end; ++i) {
|
||||
auto* msls = i->second;
|
||||
if(!msls)
|
||||
continue;
|
||||
|
||||
//Render all sprites
|
||||
for(unsigned ind = 0, cnt = msls->size(); ind != cnt; ++ind) {
|
||||
auto& missile = (*msls)[ind];
|
||||
double until = missile.track->data.aliveUntil;
|
||||
if(until < 0 || until > gameTime)
|
||||
devices.render->drawBillboard(missile.pos, missile.size, *i->first.sprite, 0.0, &missile.color);
|
||||
}
|
||||
|
||||
auto& cam_pos = driver.cam_pos;
|
||||
|
||||
//Then render all trails
|
||||
for(unsigned ind = 0, cnt = msls->size(); ind != cnt; ++ind) {
|
||||
auto& missile = (*msls)[ind];
|
||||
if(missile.lineCount == 0)
|
||||
continue;
|
||||
|
||||
auto* vertBuffer = render::VertexBufferTCV::fetch(i->first.trail);
|
||||
auto* v = vertBuffer->request(missile.lineCount + 1, render::PT_LineStrip);
|
||||
float startPct = (missile.startProgress + missile.lineProgress) * missile.length / float(LINE_POS_COUNT);
|
||||
|
||||
v[0].pos = vec3f(missile.pos - cam_pos);
|
||||
v[0].col = missile.start;
|
||||
v[0].uv = vec2f(missile.startProgress * missile.length / float(LINE_POS_COUNT), 0);
|
||||
|
||||
for(unsigned i = 0; i < missile.lineCount; ++i) {
|
||||
auto& vert = v[i+1];
|
||||
unsigned index = (missile.lineStart + i) % LINE_POS_COUNT;
|
||||
vert.pos = vec3f(missile.trail[index] - cam_pos);
|
||||
|
||||
float pct = startPct + (float(i) / float(LINE_POS_COUNT-1));
|
||||
if(pct > 1.f)
|
||||
pct = 1.f;
|
||||
|
||||
vert.col = missile.start.getInterpolated(missile.end, pct);
|
||||
|
||||
vert.uv = vec2f(pct, float(index) / float(LINE_POS_COUNT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "node.h"
|
||||
#include "color.h"
|
||||
#include "render/render_state.h"
|
||||
#include "threads.h"
|
||||
#include "design/projectiles.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
struct MissileTrailMats {
|
||||
const render::RenderState* sprite, *trail;
|
||||
|
||||
bool operator==(const MissileTrailMats& other) const {
|
||||
return sprite == other.sprite && trail == other.trail;
|
||||
}
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template<>
|
||||
struct hash<MissileTrailMats> {
|
||||
size_t operator()(const MissileTrailMats& trail) const {
|
||||
return (size_t)trail.sprite ^ (size_t)trail.trail;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
#define LINE_POS_COUNT 16
|
||||
|
||||
class LineTrailNode : public Node {
|
||||
//Stored previous positions so a trail can be rendered
|
||||
vec3d prevPositions[LINE_POS_COUNT];
|
||||
unsigned storedPositions;
|
||||
unsigned firstIndex;
|
||||
|
||||
//The number of seconds since a position has been stored
|
||||
double stored_s;
|
||||
|
||||
//Number of steps to process (higher is lower quality)
|
||||
unsigned qualitySteps;
|
||||
|
||||
const render::RenderState& mat;
|
||||
|
||||
public:
|
||||
//The number of seconds the trail lasts
|
||||
double lineLen_s;
|
||||
Color startCol, endCol;
|
||||
|
||||
LineTrailNode(const render::RenderState& material);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
class ProjectileBatch : public Node {
|
||||
public:
|
||||
struct ProjEffect {
|
||||
threads::SharedData<bool>* kill;
|
||||
vec3d pos;
|
||||
vec3f dir;
|
||||
Color start, end;
|
||||
float length, life, fadeStart, speed;
|
||||
bool line;
|
||||
};
|
||||
private:
|
||||
std::unordered_map<const render::RenderState*,std::vector<ProjEffect>*> projectiles;
|
||||
public:
|
||||
ProjectileBatch();
|
||||
~ProjectileBatch();
|
||||
|
||||
void registerProj(const render::RenderState& mat, ProjEffect& eff);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
|
||||
class MissileBatch : public Node {
|
||||
public:
|
||||
struct MissileTrail {
|
||||
threads::SharedData<MissileData>* track;
|
||||
double lastUpdate;
|
||||
vec3d trail[LINE_POS_COUNT];
|
||||
vec3d pos;
|
||||
float length, size;
|
||||
Color start, end, color;
|
||||
float lineProgress, startProgress;
|
||||
unsigned lineStart, lineCount;
|
||||
};
|
||||
private:
|
||||
std::unordered_map<MissileTrailMats,std::vector<MissileTrail>*> missiles;
|
||||
public:
|
||||
MissileBatch();
|
||||
~MissileBatch();
|
||||
|
||||
void registerProj(const render::RenderState& mat, const render::RenderState& trail, MissileTrail& eff);
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "scene/mesh_icon_node.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/spritesheet.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include "constants.h"
|
||||
#include "frustum.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
//Maximum distance away from the camera something can render (according to its scale)
|
||||
const double MaxRenderDistFactor = 8000.0;
|
||||
const double IconRenderDistFactor = 1000.0;
|
||||
const double BothRenderDistFactor = 500.0;
|
||||
bool MeshIconNode::render3DIcons = true;
|
||||
|
||||
MeshIconNode::MeshIconNode(
|
||||
const render::RenderMesh* Mesh, const render::RenderState* Material,
|
||||
const render::SpriteSheet* Sheet, unsigned Index)
|
||||
: mesh(Mesh), material(Material), iconSheet(Sheet), iconIndex(Index) {
|
||||
if(Material->baseMat != render::MAT_Solid || (Sheet && Sheet->material.baseMat != render::MAT_Solid))
|
||||
setFlag(NF_Transparent, true);
|
||||
setFlag(NF_AnimOnlyVisible, true);
|
||||
}
|
||||
|
||||
bool MeshIconNode::preRender(render::RenderDriver& driver) {
|
||||
auto fromCamera = abs_position + (driver.cam_facing * abs_scale) - driver.cam_pos;
|
||||
sortDistance = fromCamera.getLength();
|
||||
|
||||
if(sortDistance > MaxRenderDistFactor * abs_scale || !driver.getViewFrustum().overlaps(abs_position,abs_scale))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshIconNode::render(render::RenderDriver& driver) {
|
||||
if(render3DIcons) {
|
||||
double scaleMod = pow(abs_scale, 0.3);
|
||||
if(iconSheet) {
|
||||
if(sortDistance > BothRenderDistFactor * scaleMod) {
|
||||
double iconScale = sqrt(sortDistance / abs_scale) * abs_scale * 0.25;
|
||||
|
||||
//Calculate best-fit facing of the 2D sprite (Assuming the sprite is facing +x)
|
||||
auto& cam_facing = driver.cam_facing;
|
||||
auto& cam_up = driver.cam_up;
|
||||
|
||||
double rot;
|
||||
|
||||
vec3d obj_facing = rotation * vec3d::front();
|
||||
double alongDot = cam_facing.dot(obj_facing);
|
||||
|
||||
//If it's facing along the camera vector, we can't get an accurate angle
|
||||
if(alongDot > -0.9999 && alongDot < 0.9999) {
|
||||
obj_facing -= cam_facing * alongDot;
|
||||
obj_facing.normalize();
|
||||
|
||||
vec3d cam_right = cam_facing.cross(cam_up).normalized();
|
||||
rot = acos(cam_right.dot(obj_facing));
|
||||
if(cam_right.cross(cam_facing).dot(obj_facing) < 0)
|
||||
rot = -rot;
|
||||
}
|
||||
else {
|
||||
//Point up when going away, down when coming toward
|
||||
rot = alongDot > 0 ? pi * 0.5 : pi * -0.5;
|
||||
}
|
||||
|
||||
color.a = std::min(1.0, (sortDistance / scaleMod - BothRenderDistFactor) / (IconRenderDistFactor - BothRenderDistFactor));
|
||||
|
||||
Color c = color;
|
||||
|
||||
driver.drawBillboard(abs_position, iconScale,
|
||||
iconSheet->material, iconSheet->getSource(iconIndex), rot, c);
|
||||
|
||||
if(sortDistance > IconRenderDistFactor * scaleMod)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(sortDistance > IconRenderDistFactor * scaleMod) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(material->baseMat != render::MAT_Solid)
|
||||
render::renderVertexBuffers();
|
||||
|
||||
driver.setTransformation(transformation);
|
||||
driver.switchToRenderState(*material);
|
||||
mesh->selectLOD(sortDistance / abs_scale)->render();
|
||||
driver.resetTransformation();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
|
||||
namespace render {
|
||||
struct RenderState;
|
||||
class RenderMesh;
|
||||
class SpriteSheet;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
class MeshIconNode : public Node {
|
||||
public:
|
||||
static bool render3DIcons;
|
||||
const render::RenderMesh* mesh;
|
||||
const render::RenderState* material;
|
||||
const render::SpriteSheet* iconSheet;
|
||||
unsigned iconIndex;
|
||||
|
||||
MeshIconNode(
|
||||
const render::RenderMesh* mesh, const render::RenderState* material,
|
||||
const render::SpriteSheet* sheet, unsigned index);
|
||||
|
||||
bool preRender(render::RenderDriver& driver) override;
|
||||
void render(render::RenderDriver& driver) override;
|
||||
|
||||
NodeType getType() const override { return NT_MeshIconNode; };
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "scene/mesh_node.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include "frustum.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "aabbox.h"
|
||||
#include "main/references.h"
|
||||
|
||||
namespace scene {
|
||||
|
||||
//Maximum distance away from the camera something can render (according to its scale)
|
||||
const double MaxRenderDistFactor = 1400.0;
|
||||
|
||||
MeshNode::MeshNode(const render::RenderMesh* Mesh, const render::RenderState* Material)
|
||||
: mesh(Mesh), material(Material) {
|
||||
if(Material->baseMat != render::MAT_Solid)
|
||||
setFlag(NF_Transparent, true);
|
||||
setFlag(NF_AnimOnlyVisible, true);
|
||||
}
|
||||
|
||||
bool MeshNode::preRender(render::RenderDriver& driver) {
|
||||
auto fromCamera = abs_position + (driver.cam_facing * abs_scale) - driver.cam_pos;
|
||||
sortDistance = fromCamera.getLength();
|
||||
|
||||
if(sortDistance > MaxRenderDistFactor * abs_scale || !driver.getViewFrustum().overlaps(abs_position,abs_scale))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshNode::render(render::RenderDriver& driver) {
|
||||
if(material->baseMat != render::MAT_Solid)
|
||||
render::renderVertexBuffers();
|
||||
|
||||
driver.setTransformation(transformation);
|
||||
driver.switchToRenderState(*material);
|
||||
mesh->selectLOD(sortDistance / abs_scale)->render();
|
||||
driver.resetTransformation();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
|
||||
namespace render {
|
||||
struct RenderState;
|
||||
class RenderMesh;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
class MeshNode : public Node {
|
||||
public:
|
||||
const render::RenderMesh* mesh;
|
||||
const render::RenderState* material;
|
||||
|
||||
MeshNode(const render::RenderMesh* mesh, const render::RenderState* material);
|
||||
|
||||
bool preRender(render::RenderDriver& driver) override;
|
||||
void render(render::RenderDriver& driver) override;
|
||||
|
||||
NodeType getType() const override { return NT_MeshNode; };
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,640 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
#include "vec3.h"
|
||||
#include "matrix.h"
|
||||
#include "quaternion.h"
|
||||
#include "util/refcount.h"
|
||||
#include "color.h"
|
||||
#include "animation/animator.h"
|
||||
#include <vector>
|
||||
|
||||
class Object;
|
||||
struct PhysicsItem;
|
||||
|
||||
namespace render {
|
||||
class RenderDriver;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
enum NodeFlag {
|
||||
NF_Dirty = 0x1,
|
||||
NF_Transparent = 0x2,
|
||||
NF_ParentScale = 0x4,
|
||||
NF_FixedSize = 0x8,
|
||||
NF_NoCulling = 0x10,
|
||||
NF_Independent = 0x20,
|
||||
NF_AnimOnlyVisible = 0x40,
|
||||
NF_Memorable = 0x80,
|
||||
NF_NoMatrix = 0x100,
|
||||
NF_CustomColor = 0x200,
|
||||
};
|
||||
|
||||
extern Node* renderingNode;
|
||||
|
||||
class NodeEvent {
|
||||
public:
|
||||
Node* node;
|
||||
|
||||
NodeEvent(Node* node);
|
||||
virtual void process() = 0;
|
||||
virtual ~NodeEvent();
|
||||
|
||||
void* operator new(size_t bytes);
|
||||
void operator delete(void* p);
|
||||
};
|
||||
|
||||
void queueNodeEvent(NodeEvent* evt);
|
||||
void processNodeEvents();
|
||||
void clearNodeEvents();
|
||||
|
||||
//#define PROFILE_ANIMATION
|
||||
#ifdef PROFILE_ANIMATION
|
||||
void dumpAnimationProfile();
|
||||
#endif
|
||||
|
||||
enum NodeType {
|
||||
NT_System,
|
||||
NT_Culling,
|
||||
NT_ParticleSystem,
|
||||
NT_MeshNode,
|
||||
NT_MeshIconNode,
|
||||
NT_ScriptBase,
|
||||
};
|
||||
|
||||
class Node : public AtomicRefCounted {
|
||||
protected:
|
||||
int flags;
|
||||
public:
|
||||
bool visible, frameVisible, queuedDelete, remembered;
|
||||
double lastUpdate, sortDistance;
|
||||
Matrix transformation;
|
||||
vec3d position, abs_position;
|
||||
double scale, abs_scale;
|
||||
quaterniond rotation, abs_rotation;
|
||||
double distanceCutoff;
|
||||
heldPointer<Animator> animator;
|
||||
Colorf color;
|
||||
Object* obj;
|
||||
PhysicsItem* physics;
|
||||
|
||||
void setObject(Object* Obj);
|
||||
Object* getObject();
|
||||
|
||||
std::vector<Node*> children;
|
||||
heldPointer<Node> parent;
|
||||
|
||||
bool getFlag(NodeFlag flag) const;
|
||||
void setFlag(NodeFlag flag, bool val);
|
||||
void flagDirty();
|
||||
|
||||
virtual NodeType getType() const { return NT_System; };
|
||||
const char* getName() const;
|
||||
|
||||
void markForDeletion();
|
||||
virtual void destroy();
|
||||
|
||||
void queueReparent(Node* newParent);
|
||||
void hintParentObject(Object* obj, bool checkDistance = true);
|
||||
|
||||
void rebuildTransformation();
|
||||
|
||||
bool isChild(Node* child);
|
||||
void addChild(Node* child);
|
||||
void removeChild(Node* child);
|
||||
void setParent(Node* parent);
|
||||
void destroyTree();
|
||||
void animate();
|
||||
//Used to sort of nodes of the root node, as that node is not responsible for animating its children
|
||||
void sortChildren();
|
||||
|
||||
void createPhysics();
|
||||
|
||||
virtual bool preRender(render::RenderDriver& driver) { return true; }
|
||||
virtual void render(render::RenderDriver& driver) { }
|
||||
void _render(render::RenderDriver& driver);
|
||||
|
||||
bool operator<(const scene::Node& other) const;
|
||||
|
||||
Node();
|
||||
virtual ~Node();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,779 @@
|
||||
#include "particle_system.h"
|
||||
#include "render/render_state.h"
|
||||
#include "main/references.h"
|
||||
#include "util/random.h"
|
||||
#include "scripts/binds.h"
|
||||
#include "ISound.h"
|
||||
#include "constants.h"
|
||||
#include "files.h"
|
||||
#include "memory/AllocOnlyPool.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <stdint.h>
|
||||
|
||||
extern double frameLen_s, frameTime_s;
|
||||
const uint32_t fileIdentifier = (uint32_t)(('S' << 24) | ('R' << 16) | ('2' << 8) | 'P');
|
||||
const uint8_t currentVersion = 3;
|
||||
|
||||
struct BinaryFile {
|
||||
FILE* file;
|
||||
|
||||
BinaryFile(const char* filename, const char* mode) : file(fopen(filename, mode)) {}
|
||||
|
||||
~BinaryFile() {
|
||||
if(file)
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
bool open() const {
|
||||
return file != nullptr;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
BinaryFile& operator<<(const T& data) {
|
||||
fwrite(&data, sizeof(T), 1, file);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
BinaryFile& operator>>(T& data) {
|
||||
fread(&data, sizeof(T), 1, file);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void read(char* buffer, unsigned bytes) {
|
||||
fread(buffer, bytes, 1, file);
|
||||
}
|
||||
|
||||
void write(const char* buffer, unsigned bytes) {
|
||||
fwrite(buffer, bytes, 1, file);
|
||||
}
|
||||
|
||||
void write(const std::string& str) {
|
||||
uint8_t len = (uint8_t)str.size();
|
||||
*this << len;
|
||||
write(str.c_str(), len);
|
||||
}
|
||||
|
||||
void read(std::string& str) {
|
||||
uint8_t len;
|
||||
*this >> len;
|
||||
char buffer[255];
|
||||
read(buffer, len);
|
||||
str.assign(buffer,len);
|
||||
}
|
||||
};
|
||||
|
||||
struct RandRange {
|
||||
float min, max;
|
||||
|
||||
float get() const {
|
||||
return randomf(min,max);
|
||||
}
|
||||
|
||||
void set(float low, float hi) {
|
||||
min = low;
|
||||
max = hi;
|
||||
}
|
||||
|
||||
RandRange() : min(0), max(0) {}
|
||||
RandRange(const RandRange& other) : min(other.min), max(other.max) {}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
T interp(const T& a, const T& b, float percent) {
|
||||
return a + (b - a) * percent;
|
||||
}
|
||||
|
||||
Color interp(Color a, Color b, float percent) {
|
||||
return a.getInterpolated(b, percent);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
struct BezierCurve {
|
||||
std::vector<T> values;
|
||||
T def;
|
||||
|
||||
T interp(float percent) const {
|
||||
unsigned count = (unsigned)values.size();
|
||||
if(count == 0)
|
||||
return def;
|
||||
if(count == 1)
|
||||
return values[0];
|
||||
if(count == 2)
|
||||
return ::interp(values[0], values[1], percent);
|
||||
if(count > 100)
|
||||
count = 100;
|
||||
|
||||
//Need additional space for interpolated values
|
||||
T buffer[512];
|
||||
|
||||
T* dest = &buffer[0];
|
||||
|
||||
--count;
|
||||
for(unsigned i = 0; i < count; ++i)
|
||||
dest[i] = ::interp(values[i], values[i+1], percent);
|
||||
|
||||
//Each iteration, swap dest and source, reduce the next count by one, and interp between previous results
|
||||
//When count reaches 0, we have the final value
|
||||
T* source = dest + count;
|
||||
|
||||
while(--count != 0) {
|
||||
std::swap(source, dest);
|
||||
for(unsigned i = 0; i < count; ++i)
|
||||
dest[i] = ::interp(source[i], source[i+1], percent);
|
||||
}
|
||||
|
||||
return dest[0];
|
||||
}
|
||||
|
||||
void save(BinaryFile& file) const {
|
||||
uint8_t count = (uint8_t)values.size();
|
||||
file << count;
|
||||
|
||||
for(uint8_t i = 0; i < count; ++i)
|
||||
file << values[i];
|
||||
}
|
||||
|
||||
void load(BinaryFile& file) {
|
||||
uint8_t count;
|
||||
file >> count;
|
||||
values.resize(count);
|
||||
for(uint8_t i = 0; i < count; ++i)
|
||||
file >> values[i];
|
||||
}
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
struct ParticleFlowDesc {
|
||||
std::vector<const render::RenderState*> materials;
|
||||
std::vector<std::string> matNames;
|
||||
BezierCurve<Color> color;
|
||||
BezierCurve<float> size;
|
||||
float start, end;
|
||||
float rate;
|
||||
RandRange cone, spawnDist, scale, life, speed;
|
||||
|
||||
std::string sfx_start;
|
||||
const resource::Sound* sound_start;
|
||||
|
||||
bool flat;
|
||||
|
||||
ParticleFlowDesc() : start(0.f), end(0.f), rate(1.f), sound_start(nullptr), flat(false) {
|
||||
size.def = 1.f;
|
||||
cone.set(0.f, pi);
|
||||
life.set(1.f, 1.f);
|
||||
scale.set(1.f, 1.f);
|
||||
}
|
||||
|
||||
unsigned getColorCount() const {
|
||||
return color.values.size();
|
||||
}
|
||||
|
||||
Color getColor(int index) const {
|
||||
if(index < 0 || index >= (int)color.values.size())
|
||||
return color.def;
|
||||
else
|
||||
return color.values[index];
|
||||
}
|
||||
|
||||
void setColor(int index, const Color& col) {
|
||||
if(index < 0 || index >= (int)color.values.size())
|
||||
color.def = col;
|
||||
else
|
||||
color.values[index] = col;
|
||||
}
|
||||
|
||||
void addColor(unsigned index, const Color& col) {
|
||||
if(index >= color.values.size())
|
||||
color.values.push_back(col);
|
||||
else
|
||||
color.values.insert(color.values.begin() + index, col);
|
||||
}
|
||||
|
||||
void removeColor(unsigned index) {
|
||||
if(index < color.values.size())
|
||||
color.values.erase(color.values.begin() + index);
|
||||
}
|
||||
|
||||
unsigned getSizeCount() const {
|
||||
return size.values.size();
|
||||
}
|
||||
|
||||
float getSize(int index) const {
|
||||
if(index < 0 || index >= (int)size.values.size())
|
||||
return size.def;
|
||||
else
|
||||
return size.values[index];
|
||||
}
|
||||
|
||||
void setSize(int index, float Size) {
|
||||
if(index < 0 || index >= (int)size.values.size())
|
||||
size.def = Size;
|
||||
else
|
||||
size.values[index] = Size;
|
||||
}
|
||||
|
||||
void addSize(unsigned index, float Size) {
|
||||
if(index >= size.values.size())
|
||||
size.values.push_back(Size);
|
||||
else
|
||||
size.values.insert(size.values.begin() + index, Size);
|
||||
}
|
||||
|
||||
void removeSize(unsigned index) {
|
||||
if(index < size.values.size())
|
||||
size.values.erase(size.values.begin() + index);
|
||||
}
|
||||
|
||||
unsigned getMatCount() const {
|
||||
return matNames.size();
|
||||
}
|
||||
|
||||
std::string getMatName(unsigned index) const {
|
||||
if(index >= matNames.size())
|
||||
return "";
|
||||
else
|
||||
return matNames[index];
|
||||
}
|
||||
|
||||
void setMatName(unsigned index, const std::string& name) {
|
||||
if(index < matNames.size()) {
|
||||
matNames[index] = name;
|
||||
materials[index] = &devices.library.getMaterial(name);
|
||||
}
|
||||
}
|
||||
|
||||
void addMat(const std::string& name) {
|
||||
matNames.push_back(name);
|
||||
materials.push_back(&devices.library.getMaterial(name));
|
||||
}
|
||||
|
||||
void removeMat(unsigned index) {
|
||||
if(index >= matNames.size())
|
||||
return;
|
||||
matNames.erase(matNames.begin() + index);
|
||||
materials.erase(materials.begin() + index);
|
||||
}
|
||||
|
||||
void save(BinaryFile& file) const {
|
||||
file << start << end << rate;
|
||||
file << cone << spawnDist;
|
||||
file << scale.min << scale.max;
|
||||
file << life.min << life.max;
|
||||
file << speed.min << speed.max;
|
||||
color.save(file);
|
||||
size.save(file);
|
||||
|
||||
file << flat;
|
||||
|
||||
uint8_t count = (uint8_t)matNames.size();
|
||||
file << count;
|
||||
|
||||
for(uint8_t i = 0; i < count; ++i)
|
||||
file.write(matNames[i]);
|
||||
file.write(sfx_start);
|
||||
}
|
||||
|
||||
void load(BinaryFile& file, unsigned version) {
|
||||
file >> start >> end >> rate;
|
||||
if(version >= 1) {
|
||||
file >> cone >> spawnDist;
|
||||
}
|
||||
else {
|
||||
file >> cone.max;
|
||||
}
|
||||
file >> scale.min >> scale.max;
|
||||
file >> life.min >> life.max;
|
||||
file >> speed.min >> speed.max;
|
||||
color.load(file);
|
||||
size.load(file);
|
||||
|
||||
if(version >= 3)
|
||||
file >> flat;
|
||||
|
||||
uint8_t count = 0;
|
||||
file >> count;
|
||||
matNames.resize(count);
|
||||
materials.resize(count);
|
||||
|
||||
for(uint8_t i = 0; i < count; ++i) {
|
||||
file.read(matNames[i]);
|
||||
//TODO: Defer matching names with materials
|
||||
materials[i] = &devices.library.getMaterial(matNames[i]);
|
||||
}
|
||||
|
||||
if(version > 1) {
|
||||
file.read(sfx_start);
|
||||
sound_start = devices.library.getSound(sfx_start);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Particle {
|
||||
const render::RenderState* mat;
|
||||
Particle* next;
|
||||
quaternionf rot;
|
||||
vec3d pos;
|
||||
vec3f vel;
|
||||
float scale, life, age;
|
||||
float rotation;
|
||||
|
||||
float frame_scale;
|
||||
Color frame_color;
|
||||
|
||||
bool update(float time, const ParticleFlowDesc* flow) {
|
||||
age += time;
|
||||
if(age >= life)
|
||||
return true;
|
||||
|
||||
pos += vec3d(vel * time);
|
||||
float percent = age / life;
|
||||
frame_scale = scale * flow->size.interp(percent);
|
||||
frame_color = flow->color.interp(percent);
|
||||
return false;
|
||||
}
|
||||
|
||||
Particle* updateChain(float time, const ParticleFlowDesc* flow) {
|
||||
Particle* cur = this, *prev = nullptr, *head = nullptr;
|
||||
while(cur) {
|
||||
if(cur->update(time, flow)) {
|
||||
auto* deleteParticle = cur;
|
||||
cur = cur->next;
|
||||
if(prev)
|
||||
prev->next = cur;
|
||||
delete deleteParticle;
|
||||
}
|
||||
else {
|
||||
if(!head)
|
||||
head = cur;
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
Particle(const ParticleFlowDesc* flow, const vec3d& position, const vec3d& velocity, const quaterniond& rot, float Scale, float Life, float timeAdvance)
|
||||
: age(0), life(Life), next(0), pos(position)
|
||||
{
|
||||
scale = flow->scale.get() * Scale;
|
||||
rotation = (float)randomd(0,twopi);
|
||||
if(!flow->materials.empty())
|
||||
mat = flow->materials[randomi(0,(int)flow->materials.size() - 1)];
|
||||
else
|
||||
mat = &devices.library.getErrorMaterial();
|
||||
|
||||
vec3d from = vec3d::front();
|
||||
vec3d perpRight = from.cross(vec3d::right());
|
||||
vec3d perpUp = from.cross(perpRight);
|
||||
|
||||
double perpAngle = randomd() * twopi;
|
||||
vec3d perp = (perpRight * cos(perpAngle) + perpUp * sin(perpAngle)).normalized();
|
||||
|
||||
//Slerp to the perpendicular vector based on the actual chosen spread angle
|
||||
double angle = flow->cone.get();
|
||||
vec3d dir;
|
||||
if(angle < pi * 0.5)
|
||||
dir = from.slerp(perp, angle / (pi * 0.5));
|
||||
else
|
||||
dir = perp.slerp(-from, (angle - pi*0.5) / (pi * 0.5));
|
||||
|
||||
dir = rot * dir;
|
||||
if(flow->flat)
|
||||
this->rot = quaternionf::fromImpliedTransform(vec3f::up(), vec3f(dir));
|
||||
|
||||
vel = vec3f((dir * (flow->speed.get() * Scale)) + velocity);
|
||||
pos += dir * (flow->spawnDist.get() * Scale);
|
||||
|
||||
update(timeAdvance, flow);
|
||||
}
|
||||
|
||||
static Particle* create(const ParticleFlowDesc* flow, const vec3d& position, const vec3d& velocity, const quaterniond& rot, float Scale, float timeAdvance) {
|
||||
float life = flow->life.get();
|
||||
if(timeAdvance >= life)
|
||||
return nullptr;
|
||||
return new Particle(flow, position, velocity, rot, Scale, life, timeAdvance);
|
||||
}
|
||||
};
|
||||
|
||||
struct ParticleSystemDesc {
|
||||
std::vector<ParticleFlowDesc*> flows;
|
||||
|
||||
ParticleFlowDesc* getFlow(unsigned index) {
|
||||
if(index < (unsigned)flows.size())
|
||||
return flows[index];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned getFlowCount() const {
|
||||
return (unsigned)flows.size();
|
||||
}
|
||||
|
||||
void removeFlow(unsigned index) {
|
||||
//TODO: Totally unsafe
|
||||
if(index < (unsigned)flows.size()) {
|
||||
delete flows[index];
|
||||
flows.erase(flows.begin() + index);
|
||||
}
|
||||
}
|
||||
|
||||
ParticleFlowDesc* addFlow() {
|
||||
ParticleFlowDesc* desc = new ParticleFlowDesc();
|
||||
flows.push_back(desc);
|
||||
return desc;
|
||||
}
|
||||
|
||||
ParticleFlowDesc* copyFlow(ParticleFlowDesc* flow) {
|
||||
if(!flow)
|
||||
return nullptr;
|
||||
ParticleFlowDesc* desc = new ParticleFlowDesc(*flow);
|
||||
flows.push_back(desc);
|
||||
return desc;
|
||||
}
|
||||
|
||||
void save(const char* filename) const {
|
||||
BinaryFile file(filename, "wb");
|
||||
if(!file.open())
|
||||
return;
|
||||
|
||||
//Identifier and version
|
||||
file << fileIdentifier << currentVersion;
|
||||
|
||||
file << (uint16_t)flows.size();
|
||||
|
||||
for(uint16_t i = 0, cnt = (uint16_t)flows.size(); i < cnt; ++i)
|
||||
flows[i]->save(file);
|
||||
}
|
||||
|
||||
void load(const char* filename) {
|
||||
BinaryFile file(filename, "rb");
|
||||
if(!file.open())
|
||||
return;
|
||||
|
||||
uint32_t identifier;
|
||||
uint8_t version;
|
||||
file >> identifier >> version;
|
||||
if(identifier != fileIdentifier || version > currentVersion)
|
||||
return;
|
||||
|
||||
uint16_t flowCount = 0;
|
||||
file >> flowCount;
|
||||
flows.resize(flowCount);
|
||||
for(uint16_t i = 0; i < flowCount; ++i) {
|
||||
flows[i] = new ParticleFlowDesc();
|
||||
flows[i]->load(file, version);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ParticleSystemDesc* loadParticleSystem(const std::string& filename) {
|
||||
ParticleSystemDesc* system = new ParticleSystemDesc();
|
||||
system->load(filename.c_str());
|
||||
return system;
|
||||
}
|
||||
|
||||
ParticleSystemDesc* createDummyParticleSystem() {
|
||||
return new ParticleSystemDesc();
|
||||
}
|
||||
|
||||
ParticleSystem* playParticleSystem(const ParticleSystemDesc* desc, Node* parent, const vec3d& pos, const quaterniond& rot, const vec3d& vel, float scale, float delay) {
|
||||
if(desc == 0)
|
||||
return 0;
|
||||
|
||||
ParticleSystem* sys = new ParticleSystem(desc);
|
||||
|
||||
sys->position = pos;
|
||||
sys->vel = vel;
|
||||
sys->rot = rot;
|
||||
sys->scale = scale;
|
||||
sys->delay = delay;
|
||||
|
||||
if(parent) {
|
||||
sys->setFlag(NF_Independent, false);
|
||||
sys->queueReparent(parent);
|
||||
}
|
||||
else {
|
||||
sys->queueReparent(devices.scene);
|
||||
}
|
||||
|
||||
return sys;
|
||||
}
|
||||
|
||||
ParticleSystem::ParticleSystem(const ParticleSystemDesc* system) : age(0.f), delay(0.f), scale(1.f), lastUpdate(frameTime_s) {
|
||||
setFlag(NF_NoMatrix, true);
|
||||
setFlag(NF_Transparent, true);
|
||||
flows.resize(system->flows.size());
|
||||
for(size_t i = 0, cnt = flows.size(); i < cnt; ++i)
|
||||
flows[i].flow = system->flows[i];
|
||||
}
|
||||
|
||||
ParticleSystem::~ParticleSystem() {
|
||||
for(size_t i = 0, cnt = flows.size(); i < cnt; ++i) {
|
||||
auto* particle = flows[i].list;
|
||||
while(particle) {
|
||||
auto* next = particle->next;
|
||||
delete particle;
|
||||
particle = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ParticleSystem::preRender(render::RenderDriver& driver) {
|
||||
float time = (float)(frameTime_s - lastUpdate);
|
||||
if(delay > 0) {
|
||||
delay -= time;
|
||||
if(delay > 0)
|
||||
return true;
|
||||
}
|
||||
if(time > 0) {
|
||||
lastUpdate = frameTime_s;
|
||||
rebuildTransformation();
|
||||
age += time;
|
||||
|
||||
bool alive = false;
|
||||
|
||||
for(size_t i = 0, cnt = flows.size(); i != cnt; ++i) {
|
||||
auto& flowData = flows[i];
|
||||
auto* flow = flowData.flow;
|
||||
|
||||
if(age >= flow->start) {
|
||||
if(!flowData.started) {
|
||||
if(flow->sound_start) {
|
||||
//At the start of a flow, play its start sound (if the flow isn't already over due)
|
||||
if(auto* sound = flow->sound_start->play3D(abs_position,false,true,false)) {
|
||||
int msOffset = (int)(1000.0 * (age - flow->start));
|
||||
//Avoid offsetting the sound unless it's at least a few frames off
|
||||
if(msOffset > 64)
|
||||
sound->setPlayPosition(msOffset);
|
||||
double base_dist = abs_scale * (flow->life.max * flow->speed.max + flow->scale.max);
|
||||
sound->setMinDistance(base_dist);
|
||||
sound->setMaxDistance(base_dist * 128.f);
|
||||
sound->setVolume(base_dist);
|
||||
sound->resume();
|
||||
sound->drop();
|
||||
}
|
||||
}
|
||||
|
||||
flowData.started = true;
|
||||
}
|
||||
|
||||
unsigned make = 0;
|
||||
float overtime = age - flow->end;
|
||||
if(overtime < 0.f) {
|
||||
alive = true;
|
||||
|
||||
flowData.progress += flow->rate * time;
|
||||
float iPart;
|
||||
flowData.progress = std::modf(flowData.progress + (flow->rate * time), &iPart);
|
||||
make = (unsigned)iPart;
|
||||
}
|
||||
else if(flowData.progress > 0.f) {
|
||||
//If we had enough time in our dying moment to create a particle, do so
|
||||
//Handles cases of very short lived flows that only generate one particle or very few
|
||||
float iPart;
|
||||
std::modf(flowData.progress + flow->rate * (time - overtime), &iPart);
|
||||
if(iPart > 0)
|
||||
make = (unsigned)iPart;
|
||||
flowData.progress = 0.f;
|
||||
}
|
||||
|
||||
if(flowData.list)
|
||||
flowData.list = flowData.list->updateChain(time, flow);
|
||||
|
||||
if(make > 0) {
|
||||
float tStep = time / (float)make;
|
||||
float tOff = tStep;
|
||||
|
||||
if(time > flow->life.max) {
|
||||
//Skip generating particles that definitely won't survive
|
||||
// Special case for long-duration particle systems that may spend long periods invisible
|
||||
int skip = (int)((time - flow->life.max) / tStep);
|
||||
tOff += (float)skip * tStep;
|
||||
make -= (unsigned)skip;
|
||||
}
|
||||
|
||||
quaterniond totRot = abs_rotation * rot;
|
||||
|
||||
while(make--) {
|
||||
Particle* particle = Particle::create(flow, abs_position, vel, totRot, scale, tOff);
|
||||
tOff += tStep;
|
||||
|
||||
if(particle) {
|
||||
if(flowData.list)
|
||||
particle->next = flowData.list;
|
||||
flowData.list = particle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(flowData.list)
|
||||
alive = true;
|
||||
}
|
||||
else if(age < flow->end) {
|
||||
alive = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!alive) {
|
||||
markForDeletion();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sortDistance = devices.render->cam_pos.distanceTo(abs_position);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParticleSystem::end() {
|
||||
for(size_t i = 0, cnt = flows.size(); i != cnt; ++i)
|
||||
age = std::max(age, flows[i].flow->end);
|
||||
}
|
||||
|
||||
void ParticleSystem::render(render::RenderDriver& driver) {
|
||||
for(size_t i = 0, cnt = flows.size(); i != cnt; ++i) {
|
||||
auto& flowData = flows[i];
|
||||
Particle* particle = flowData.list;
|
||||
|
||||
if(!flowData.flow->flat) {
|
||||
while(particle) {
|
||||
devices.render->drawBillboard(particle->pos, particle->frame_scale * 2.f, *particle->mat, particle->rotation, &particle->frame_color);
|
||||
particle = particle->next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while(particle) {
|
||||
vec3f up = particle->rot * vec3f::front(particle->frame_scale);
|
||||
vec3f right = particle->rot * vec3f::right(particle->frame_scale);
|
||||
vec3f ur = up + right, ul = up - right;
|
||||
|
||||
double st = sin(particle->rotation), ct = cos(particle->rotation);
|
||||
vec3f upLeft = (ul * ct) - (ur * st);
|
||||
vec3f upRight = (ur * ct) + (ul * st);
|
||||
|
||||
vec3f center = vec3f(particle->pos - devices.render->cam_pos);
|
||||
auto* buffer = render::VertexBufferTCV::fetch(particle->mat);
|
||||
auto* verts = buffer->request(1, render::PT_Quads);
|
||||
|
||||
auto* vert = &verts[0];
|
||||
vert->pos = center + upLeft;
|
||||
vert->col = particle->frame_color;
|
||||
vert->uv = vec2f(0.f, 0.f);
|
||||
|
||||
vert = &verts[1];
|
||||
vert->pos = center + upRight;
|
||||
vert->col = particle->frame_color;
|
||||
vert->uv = vec2f(1.f, 0.f);
|
||||
|
||||
vert = &verts[2];
|
||||
vert->pos = center - upLeft;
|
||||
vert->col = particle->frame_color;
|
||||
vert->uv = vec2f(1.f, 1.f);
|
||||
|
||||
vert = &verts[3];
|
||||
vert->pos = center - upRight;
|
||||
vert->col = particle->frame_color;
|
||||
vert->uv = vec2f(0.f, 1.f);
|
||||
|
||||
particle = particle->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
namespace scripts {
|
||||
|
||||
void saveParticleSystem(scene::ParticleSystemDesc* system, const std::string& filename) {
|
||||
//TODO: Check that they aren't hacking
|
||||
system->save(filename.c_str());
|
||||
}
|
||||
|
||||
scene::ParticleSystemDesc* copyParticleSystem(scene::ParticleSystemDesc* system) {
|
||||
auto* ps = new scene::ParticleSystemDesc();
|
||||
ps->flows.resize(system->flows.size());
|
||||
for(size_t i = 0, cnt = system->flows.size(); i < cnt; ++i) {
|
||||
ps->flows[i] = new scene::ParticleFlowDesc(*system->flows[i]);
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
scene::ParticleSystemDesc* makeParticleSystem() {
|
||||
return new scene::ParticleSystemDesc();
|
||||
}
|
||||
|
||||
std::string flowGetStartSound(const scene::ParticleFlowDesc* desc) {
|
||||
return desc->sfx_start;
|
||||
}
|
||||
|
||||
void flowSetStartSound(scene::ParticleFlowDesc* desc, const std::string& sfx) {
|
||||
desc->sfx_start = sfx;
|
||||
desc->sound_start = devices.library.getSound(sfx);
|
||||
}
|
||||
|
||||
void RegisterParticleSystemBinds() {
|
||||
ClassBind rr("Range", asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS | asOBJ_APP_CLASS_ALLFLOATS, sizeof(RandRange));
|
||||
rr.addMember("float min", offsetof(RandRange,min));
|
||||
rr.addMember("float max", offsetof(RandRange,max));
|
||||
|
||||
//TODO: Leaks, leaks everywhere
|
||||
ClassBind ps("ParticleSystem", asOBJ_REF | asOBJ_NOCOUNT);
|
||||
ClassBind flow("ParticleFlow", asOBJ_REF | asOBJ_NOCOUNT);
|
||||
|
||||
ps.addFactory("ParticleSystem@ f()", asFUNCTION(makeParticleSystem));
|
||||
|
||||
ps.addMethod("ParticleFlow@ createFlow()", asMETHOD(scene::ParticleSystemDesc,addFlow));
|
||||
ps.addMethod("ParticleFlow@ duplicateFlow(ParticleFlow@ flow)", asMETHOD(scene::ParticleSystemDesc,copyFlow));
|
||||
ps.addMethod("void removeFlow(uint index)", asMETHOD(scene::ParticleSystemDesc,removeFlow));
|
||||
ps.addMethod("ParticleFlow@ get_flows(uint index)", asMETHOD(scene::ParticleSystemDesc,getFlow));
|
||||
ps.addMethod("uint get_flowCount() const", asMETHOD(scene::ParticleSystemDesc,getFlowCount));
|
||||
ps.addExternMethod("void save(const string& in filename) const", asFUNCTION(saveParticleSystem));
|
||||
ps.addExternMethod("ParticleSystem@ duplicate() const", asFUNCTION(copyParticleSystem));
|
||||
|
||||
flow.addExternMethod("string get_soundStart() const", asFUNCTION(flowGetStartSound));
|
||||
flow.addExternMethod("void set_soundStart(const string& sfx)", asFUNCTION(flowSetStartSound));
|
||||
|
||||
flow.addMethod("string get_materials(uint index) const", asMETHOD(scene::ParticleFlowDesc,getMatName));
|
||||
flow.addMethod("void set_materials(uint index, const string& id)", asMETHOD(scene::ParticleFlowDesc,setMatName));
|
||||
flow.addMethod("uint get_materialCount() const", asMETHOD(scene::ParticleFlowDesc,getMatCount));
|
||||
flow.addMethod("void addMaterial(const string& id)", asMETHOD(scene::ParticleFlowDesc,addMat));
|
||||
flow.addMethod("void removeMaterial(uint index)", asMETHOD(scene::ParticleFlowDesc,removeMat));
|
||||
|
||||
flow.addMethod("uint get_colorCount() const", asMETHOD(scene::ParticleFlowDesc,getColorCount));
|
||||
flow.addMethod("Color get_colors(int index) const", asMETHOD(scene::ParticleFlowDesc,getColor));
|
||||
flow.addMethod("void set_colors(int index, const Color& col)", asMETHOD(scene::ParticleFlowDesc,setColor));
|
||||
flow.addMethod("void removeColor(uint index)", asMETHOD(scene::ParticleFlowDesc,removeColor));
|
||||
flow.addMethod("void addColor(uint index, const Color& col) const", asMETHOD(scene::ParticleFlowDesc,addColor));
|
||||
|
||||
flow.addMethod("uint get_sizeCount() const", asMETHOD(scene::ParticleFlowDesc,getSizeCount));
|
||||
flow.addMethod("float get_sizes(int i) const", asMETHOD(scene::ParticleFlowDesc,getSize));
|
||||
flow.addMethod("void set_sizes(int i, float size)", asMETHOD(scene::ParticleFlowDesc,setSize));
|
||||
flow.addMethod("void removeSize(uint index)", asMETHOD(scene::ParticleFlowDesc,removeSize));
|
||||
flow.addMethod("void addSize(uint index, float size) const", asMETHOD(scene::ParticleFlowDesc,addSize));
|
||||
|
||||
flow.addMember("float start", offsetof(scene::ParticleFlowDesc,start))
|
||||
doc("Second offset from when the particle system starts to begin this flow.");
|
||||
flow.addMember("float end", offsetof(scene::ParticleFlowDesc,end))
|
||||
doc("Second offset from when the particle system starts to end this flow.");
|
||||
flow.addMember("float rate", offsetof(scene::ParticleFlowDesc,rate))
|
||||
doc("Particles to generate per second.");
|
||||
flow.addMember("Range cone", offsetof(scene::ParticleFlowDesc,cone))
|
||||
doc("Radian spread of particule emission cone.");
|
||||
flow.addMember("Range spawnDist", offsetof(scene::ParticleFlowDesc,spawnDist))
|
||||
doc("Radius at which to emit particles.");
|
||||
flow.addMember("Range scale", offsetof(scene::ParticleFlowDesc,scale))
|
||||
doc("Range of possible particle scales.");
|
||||
flow.addMember("Range life", offsetof(scene::ParticleFlowDesc,life))
|
||||
doc("Range of possible particle durations (in seconds).");
|
||||
flow.addMember("Range speed", offsetof(scene::ParticleFlowDesc,speed))
|
||||
doc("Range of possible particle speeds.");
|
||||
flow.addMember("bool flat", offsetof(scene::ParticleFlowDesc,flat))
|
||||
doc("Whether particles should face the direction they are moving, rather than the camera.");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include "node.h"
|
||||
#include <string>
|
||||
|
||||
namespace scene {
|
||||
|
||||
struct ParticleSystemDesc;
|
||||
struct ParticleFlowDesc;
|
||||
struct Particle;
|
||||
|
||||
struct FlowData {
|
||||
const ParticleFlowDesc* flow;
|
||||
Particle* list;
|
||||
float progress;
|
||||
bool started;
|
||||
|
||||
FlowData() : flow(0), list(0), progress(1.f), started(false) {}
|
||||
};
|
||||
|
||||
class ParticleSystem : public Node {
|
||||
std::vector<FlowData> flows;
|
||||
public:
|
||||
vec3d vel;
|
||||
quaterniond rot;
|
||||
float scale;
|
||||
float age;
|
||||
float delay;
|
||||
double lastUpdate;
|
||||
|
||||
ParticleSystem(const ParticleSystemDesc* system);
|
||||
~ParticleSystem();
|
||||
|
||||
//Stops streaming new particles
|
||||
void end();
|
||||
|
||||
bool preRender(render::RenderDriver& driver) override;
|
||||
void render(render::RenderDriver& driver) override;
|
||||
|
||||
NodeType getType() const override { return NT_ParticleSystem; };
|
||||
};
|
||||
|
||||
ParticleSystem* playParticleSystem(const ParticleSystemDesc* desc, Node* parent, const vec3d& pos, const quaterniond& rot, const vec3d& vel, float scale = 1.f, float delay = 0.f);
|
||||
ParticleSystemDesc* loadParticleSystem(const std::string& filename);
|
||||
ParticleSystemDesc* createDummyParticleSystem();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "scene/plane_node.h"
|
||||
#include "render/driver.h"
|
||||
|
||||
void shader_plane_minrad(float* value,unsigned short,void*) {
|
||||
if(auto* node = dynamic_cast<scene::PlaneNode*>(scene::renderingNode))
|
||||
*value = (float)node->minRad;
|
||||
else
|
||||
*value = 0.f;
|
||||
}
|
||||
|
||||
void shader_plane_maxrad(float* value,unsigned short,void*) {
|
||||
if(auto* node = dynamic_cast<scene::PlaneNode*>(scene::renderingNode))
|
||||
*value = (float)node->maxRad;
|
||||
else
|
||||
*value = 0.f;
|
||||
}
|
||||
|
||||
namespace scene {
|
||||
PlaneNode::PlaneNode(const render::RenderState* Material, double Size)
|
||||
: material(Material), minRad(-3.2f), maxRad(3.2f) {
|
||||
setFlag(NF_NoMatrix, true);
|
||||
if(Material->baseMat != render::MAT_Solid)
|
||||
setFlag(NF_Transparent, true);
|
||||
scale = Size;
|
||||
}
|
||||
|
||||
bool PlaneNode::preRender(render::RenderDriver& driver) {
|
||||
auto fromCamera = abs_position - driver.cam_pos;
|
||||
if(fromCamera.dot(driver.cam_facing) > 0) {
|
||||
sortDistance = fromCamera.getLength();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PlaneNode::render(render::RenderDriver& driver) {
|
||||
driver.setTransformation(transformation);
|
||||
driver.switchToRenderState(*material);
|
||||
|
||||
vec3d verts[4];
|
||||
vec3d vsize = vec3d(scale, 0, scale);
|
||||
|
||||
vec3d topLeft = abs_position - vsize;
|
||||
vec3d botRight = abs_position + vsize;
|
||||
|
||||
verts[0] = topLeft;
|
||||
verts[1] = topLeft + vec3d(vsize.x * 2, 0, 0);
|
||||
verts[2] = botRight;
|
||||
verts[3] = botRight - vec3d(vsize.x * 2, 0, 0);
|
||||
|
||||
vec2f tcs[4] = { vec2f(0,0), vec2f(1,0), vec2f(1,1), vec2f(0,1) };
|
||||
Color colors[4] = { color, color, color, color };
|
||||
|
||||
driver.drawQuad(verts, tcs, colors);
|
||||
driver.resetTransformation();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
|
||||
namespace render {
|
||||
struct RenderState;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
class PlaneNode : public Node {
|
||||
public:
|
||||
const render::RenderState* material;
|
||||
float minRad, maxRad;
|
||||
|
||||
PlaneNode(const render::RenderState* material, double size);
|
||||
|
||||
virtual bool preRender(render::RenderDriver& driver);
|
||||
virtual void render(render::RenderDriver& driver);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,253 @@
|
||||
#include "scripted_node.h"
|
||||
#include "angelscript.h"
|
||||
#include "scripts/manager.h"
|
||||
#include "scripts/script_components.h"
|
||||
#include "scripts/generic_call.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "util/format.h"
|
||||
#include "str_util.h"
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include "threads.h"
|
||||
#include "frustum.h"
|
||||
|
||||
std::unordered_map<std::string, scene::scriptNodeType*> nodeTypes;
|
||||
static unsigned nextScriptNodeID = 0;
|
||||
|
||||
namespace scene {
|
||||
scriptNodeType::scriptNodeType(const std::string& Name, const std::string& ident)
|
||||
: id(nextScriptNodeID++), name(Name), identifier(ident), factory(0), preRender(0), render(0) {}
|
||||
|
||||
void scriptNodeType::bind() {
|
||||
asITypeInfo* type = 0;
|
||||
|
||||
std::vector<std::string> parts;
|
||||
split(identifier, parts, "::");
|
||||
if(parts.size() == 2)
|
||||
type = devices.scripts.client->getClass(parts[0].c_str(),parts[1].c_str());
|
||||
|
||||
if(type) {
|
||||
factory = type->GetFactoryByDecl(format("$1@ $1(Node&)", type->GetName()).c_str());
|
||||
preRender = type->GetMethodByDecl("bool preRender(Node&)",false);
|
||||
render = type->GetMethodByDecl("void render(Node&)",false);
|
||||
|
||||
for(unsigned i = 0; i < methods.size(); ++i) {
|
||||
auto* method = methods[i];
|
||||
|
||||
auto descContaining = method->desc;
|
||||
descContaining.prepend(scripts::GT_Node_Ref);
|
||||
|
||||
method->func = type->GetMethodByDecl(descContaining.declaration().c_str(),false);
|
||||
if(method->func) {
|
||||
method->desc = descContaining;
|
||||
method->passContaining = true;
|
||||
}
|
||||
else {
|
||||
method->func = type->GetMethodByDecl(method->desc.declaration().c_str(),false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
error("'%s' was not a valid script class identifier", identifier.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ScriptedNode::ScriptedNode(scriptNodeType* nodeType) : scriptObject(0), errors(0), type(*nodeType) {
|
||||
if(asIScriptFunction* func = type.factory) {
|
||||
auto call = devices.scripts.client->call(func);
|
||||
call.push(this);
|
||||
call.call(scriptObject);
|
||||
if(scriptObject)
|
||||
scriptObject->AddRef();
|
||||
else
|
||||
throw "Failed to create script node";
|
||||
}
|
||||
else {
|
||||
throw "Script object has no acceptable factory";
|
||||
}
|
||||
}
|
||||
|
||||
ScriptedNode* ScriptedNode::create(const std::string& type) {
|
||||
try {
|
||||
auto entry = nodeTypes.find(type);
|
||||
if(entry != nodeTypes.end())
|
||||
return new ScriptedNode(entry->second);
|
||||
else
|
||||
error("Error creating script node '%s': No such node type", type.c_str());
|
||||
}
|
||||
catch(const char* err) {
|
||||
error("Error creating script node '%s': %s", type.c_str(), err);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScriptedNode::destroy() {
|
||||
if(scriptObject) {
|
||||
scriptObject->Release();
|
||||
scriptObject = 0;
|
||||
}
|
||||
|
||||
Node::destroy();
|
||||
}
|
||||
|
||||
ScriptedNode::~ScriptedNode() {
|
||||
if(scriptObject)
|
||||
scriptObject->Release();
|
||||
}
|
||||
|
||||
NodeType ScriptedNode::getType() const {
|
||||
return (NodeType)(NT_ScriptBase + type.id);
|
||||
}
|
||||
|
||||
bool ScriptedNode::preRender(render::RenderDriver& driver) {
|
||||
if(!scriptObject)
|
||||
return false;
|
||||
|
||||
sortDistance = abs_position.distanceTo(driver.cam_pos);
|
||||
|
||||
//Call scripted pre-render if available
|
||||
if(asIScriptFunction* func = type.preRender) {
|
||||
auto call = devices.scripts.client->call(func);
|
||||
call.setObject(scriptObject);
|
||||
call.push(this);
|
||||
|
||||
bool ret = false;
|
||||
if(!call.call(ret)) {
|
||||
if(++errors >= 3) {
|
||||
markForDeletion();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!ret)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!visible)
|
||||
return false;
|
||||
if(!getFlag(NF_NoCulling) && !driver.getViewFrustum().overlaps(abs_position, abs_scale))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScriptedNode::render(render::RenderDriver& driver) {
|
||||
if(!scriptObject)
|
||||
return;
|
||||
if(asIScriptFunction* func = type.render) {
|
||||
auto call = devices.scripts.client->call(func);
|
||||
call.setObject(scriptObject);
|
||||
call.push(this);
|
||||
|
||||
if(!call.call()) {
|
||||
if(++errors >= 3) {
|
||||
markForDeletion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadScriptNodeTypes(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
skipBOM(file);
|
||||
char name[80], script[80], bracket;
|
||||
|
||||
bool inType = false;
|
||||
scriptNodeType* type = 0;
|
||||
|
||||
while(true) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
if(file.fail())
|
||||
break;
|
||||
|
||||
std::string parse = line.substr(0, line.find("//"));
|
||||
if(parse.find_first_not_of(" \t\n\r") == std::string::npos)
|
||||
continue;
|
||||
|
||||
if(!inType) {
|
||||
int args = sscanf(parse.c_str(), "Node %79s from %79s {", name, script);
|
||||
if(args != 2) {
|
||||
error("Unrecognized line '%s'", line.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string typeName = name;
|
||||
|
||||
if(nodeTypes.find(typeName) != nodeTypes.end()) {
|
||||
error("Duplicate node type '%s'", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
type = new scriptNodeType(name, script);
|
||||
nodeTypes[name] = type;
|
||||
|
||||
inType = true;
|
||||
}
|
||||
else if(type) {
|
||||
int args = sscanf(parse.c_str(), " %c", &bracket);
|
||||
if(args == 1 && bracket == '}') {
|
||||
inType = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
parse = trim(parse);
|
||||
|
||||
if(parse[parse.size() - 1] == ':') {
|
||||
parse[parse.size() - 1] = ' ';
|
||||
continue;
|
||||
}
|
||||
|
||||
//Parse declaration
|
||||
scripts::GenericCallDesc desc;
|
||||
desc.parse(parse, true, false);
|
||||
|
||||
if(desc.returnType.type) {
|
||||
error("Error: '%s': node functions may not return a value.", parse.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
//Create method desc
|
||||
scripts::WrappedMethod* m = new scripts::WrappedMethod();
|
||||
m->index = (unsigned)type->methods.size();
|
||||
m->compId = 0;
|
||||
m->local = true;
|
||||
m->server = true;
|
||||
m->shadow = true;
|
||||
m->wrapped = desc;
|
||||
m->restricted = false;
|
||||
|
||||
m->desc.append(desc);
|
||||
m->desc.name = desc.name;
|
||||
m->desc.constFunction = false;
|
||||
m->desc.returnType = desc.returnType;
|
||||
m->desc.returnsArray = m->wrapped.returnsArray;
|
||||
|
||||
type->methods.push_back(m);
|
||||
}
|
||||
else {
|
||||
if(parse.find('}') != std::string::npos)
|
||||
inType = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bindScriptNodeTypes() {
|
||||
foreach(it, nodeTypes)
|
||||
it->second->bind();
|
||||
}
|
||||
|
||||
void clearScriptNodeTypes() {
|
||||
foreach(it, nodeTypes)
|
||||
delete it->second;
|
||||
nodeTypes.clear();
|
||||
nextScriptNodeID = 0;
|
||||
}
|
||||
|
||||
const char* getScriptNodeName(unsigned id) {
|
||||
foreach(it, nodeTypes)
|
||||
if(it->second->id == id)
|
||||
return it->first.c_str();
|
||||
return "";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
#include <string>
|
||||
|
||||
class asIScriptObject;
|
||||
class asIScriptFunction;
|
||||
|
||||
namespace scripts {
|
||||
struct WrappedMethod;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
|
||||
struct scriptNodeType {
|
||||
unsigned id;
|
||||
std::string name, identifier;
|
||||
asIScriptFunction *factory, *preRender, *render;
|
||||
std::vector<scripts::WrappedMethod*> methods;
|
||||
|
||||
scriptNodeType(const std::string& name, const std::string& ident);
|
||||
void bind();
|
||||
};
|
||||
|
||||
class ScriptedNode : public Node {
|
||||
unsigned errors;
|
||||
|
||||
~ScriptedNode();
|
||||
|
||||
bool preRender(render::RenderDriver& driver);
|
||||
void render(render::RenderDriver& driver);
|
||||
void destroy();
|
||||
NodeType getType() const override;
|
||||
|
||||
public:
|
||||
asIScriptObject* scriptObject;
|
||||
scriptNodeType& type;
|
||||
|
||||
ScriptedNode(scriptNodeType* nodeType);
|
||||
static ScriptedNode* create(const std::string& type);
|
||||
};
|
||||
|
||||
void loadScriptNodeTypes(const std::string& filename);
|
||||
void bindScriptNodeTypes();
|
||||
void clearScriptNodeTypes();
|
||||
const char* getScriptNodeName(unsigned id);
|
||||
};
|
||||
Reference in New Issue
Block a user