Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
@@ -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();
}
};
+17
View File
@@ -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;
}
};
+17
View File
@@ -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();
};
};
+14
View File
@@ -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;
};
};
+69
View File
@@ -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;
}
};
+21
View File
@@ -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);
};
};
+75
View File
@@ -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());
}
}
};
+34
View File
@@ -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);
};
};
+29
View File
@@ -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) {
}
};
+16
View File
@@ -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; };
};
};
+42
View File
@@ -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);
}
};
+23
View File
@@ -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);
};
};
+66
View File
@@ -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
}
};
+21
View File
@@ -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);
};
};
+366
View File
@@ -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));
}
}
}
}
};
+102
View File
@@ -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);
};
};
+92
View File
@@ -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();
}
};
+30
View File
@@ -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; };
};
};
+42
View File
@@ -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();
}
};
+24
View File
@@ -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; };
};
};
+640
View File
@@ -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);
}
}
};
+122
View File
@@ -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();
};
};
+779
View File
@@ -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.");
}
};
+46
View File
@@ -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();
};
+60
View File
@@ -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();
}
};
+21
View File
@@ -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);
};
};
+253
View File
@@ -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 "";
}
};
+46
View File
@@ -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);
};