feat: Properly implement fountain emitters

This commit is contained in:
Vsevolod Kremianskii 2021-01-11 17:23:39 +07:00
parent 0f26b589ff
commit b585bb79a8
14 changed files with 392 additions and 128 deletions

View file

@ -257,6 +257,7 @@ set(SCENE_HEADERS
src/scene/node/lightnode.h
src/scene/node/modelnodescenenode.h
src/scene/node/modelscenenode.h
src/scene/node/particlenode.h
src/scene/node/scenenode.h
src/scene/pipeline/control.h
src/scene/pipeline/world.h
@ -270,6 +271,7 @@ set(SCENE_SOURCES
src/scene/node/lightnode.cpp
src/scene/node/modelnodescenenode.cpp
src/scene/node/modelscenenode.cpp
src/scene/node/particlenode.cpp
src/scene/node/scenenode.cpp
src/scene/pipeline/control.cpp
src/scene/pipeline/world.cpp

View file

@ -40,9 +40,16 @@ public:
BillboardToWorldZ
};
public:
template <class T>
struct Constraints {
T start;
T mid;
T end;
};
UpdateType updateType() const { return _updateType; }
RenderType renderType() const { return _renderType; }
int renderOrder() const { return _renderOrder; }
std::shared_ptr<Texture> texture() const { return _texture; }
int gridWidth() const { return _gridWidth; }
@ -51,7 +58,9 @@ public:
int frameEnd() const { return _frameEnd; }
const glm::vec2 &size() const { return _size; }
float sizeStart() const { return _sizeStart; }
const Constraints<float> &particleSize() const { return _particleSize; }
const Constraints<glm::vec3> &color() const { return _color; }
const Constraints<float> &alpha() const { return _alpha; }
int birthrate() const { return _birthrate; }
int lifeExpectancy() const { return _lifeExpectancy; }
float velocity() const { return _velocity; }
@ -60,6 +69,7 @@ public:
private:
UpdateType _updateType { UpdateType::Invalid };
RenderType _renderType { RenderType::Invalid };
int _renderOrder { 0 };
std::shared_ptr<Texture> _texture;
int _gridWidth { 0 };
@ -68,12 +78,15 @@ private:
int _frameEnd { 0 };
glm::vec2 _size { 0.0f };
float _sizeStart { 0.0f };
int _birthrate { 0 }; /**< rate of particle birth per second */
int _lifeExpectancy { 0 }; /**< life of each particle in seconds */
float _velocity { 0.0f };
float _randomVelocity { 0.0f };
Constraints<float> _particleSize;
Constraints<glm::vec3> _color;
Constraints<float> _alpha;
friend class MdlFile;
};

View file

@ -24,10 +24,10 @@ namespace reone {
namespace render {
static vector<float> g_vertices = {
-0.5f, 0.0f, -0.5f, 0.0f, 0.0f,
0.5f, 0.0f, -0.5f, 1.0f, 0.0f,
0.5f, 0.0f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.0f, 0.5f, 0.0f, 1.0f
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
static vector<uint16_t> g_indices = {

View file

@ -58,6 +58,8 @@ enum class ControllerType {
Position = 8,
Orientation = 20,
Color = 76,
AlphaEnd = 80,
AlphaStart = 84,
Radius_Birthrate = 88,
SelfIllumColor = 100,
FrameEnd = 108,
@ -66,9 +68,15 @@ enum class ControllerType {
Alpha = 132,
Multiplier_RandomVelocity = 140,
SizeStart = 144,
SizeEnd = 148,
Velocity = 168,
SizeX = 172,
SizeY = 176
SizeY = 176,
AlphaMid = 216,
SizeMid = 232,
ColorMid = 284,
ColorEnd = 380,
ColorStart = 392
};
MdlFile::MdlFile(GameVersion version) : BinaryFile(kSignatureSize, kSignature), _version(version) {
@ -320,11 +328,6 @@ void MdlFile::readControllers(uint32_t keyCount, uint32_t keyOffset, const vecto
readRandomVelocityController(dataIndex, data, node);
}
break;
case ControllerType::SizeStart:
if (node._flags & kNodeHasEmitter) {
readSizeStartController(dataIndex, data, node);
}
break;
case ControllerType::Velocity:
if (node._flags & kNodeHasEmitter) {
readVelocityController(dataIndex, data, node);
@ -340,6 +343,55 @@ void MdlFile::readControllers(uint32_t keyCount, uint32_t keyOffset, const vecto
readSizeYController(dataIndex, data, node);
}
break;
case ControllerType::SizeStart:
if (node._flags & kNodeHasEmitter) {
readSizeStartController(dataIndex, data, node);
}
break;
case ControllerType::SizeMid:
if (node._flags & kNodeHasEmitter) {
readSizeMidController(dataIndex, data, node);
}
break;
case ControllerType::SizeEnd:
if (node._flags & kNodeHasEmitter) {
readSizeEndController(dataIndex, data, node);
}
break;
case ControllerType::ColorStart:
if (node._flags & kNodeHasEmitter) {
readColorStartController(dataIndex, data, node);
}
break;
case ControllerType::ColorMid:
if (node._flags & kNodeHasEmitter) {
readColorMidController(dataIndex, data, node);
}
break;
case ControllerType::ColorEnd:
if (node._flags & kNodeHasEmitter) {
readColorEndController(dataIndex, data, node);
}
break;
case ControllerType::AlphaStart:
if (node._flags & kNodeHasEmitter) {
readAlphaStartController(dataIndex, data, node);
}
break;
case ControllerType::AlphaMid:
if (node._flags & kNodeHasEmitter) {
readAlphaMidController(dataIndex, data, node);
}
break;
case ControllerType::AlphaEnd:
if (node._flags & kNodeHasEmitter) {
readAlphaEndController(dataIndex, data, node);
}
break;
default:
debug(boost::format("MDL: unsupported controller type: \"%s\" %d") % _name % static_cast<int>(type), 3);
break;
@ -459,7 +511,45 @@ void MdlFile::readLifeExpectancyController(uint16_t dataIndex, const vector<floa
}
void MdlFile::readSizeStartController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_sizeStart = data[dataIndex];
node._emitter->_particleSize.start = data[dataIndex];
}
void MdlFile::readSizeMidController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_particleSize.mid = data[dataIndex];
}
void MdlFile::readSizeEndController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_particleSize.end = data[dataIndex];
}
void MdlFile::readColorStartController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_color.start.r = data[dataIndex + 0];
node._emitter->_color.start.g = data[dataIndex + 1];
node._emitter->_color.start.b = data[dataIndex + 2];
}
void MdlFile::readColorMidController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_color.mid.r = data[dataIndex + 0];
node._emitter->_color.mid.g = data[dataIndex + 1];
node._emitter->_color.mid.b = data[dataIndex + 2];
}
void MdlFile::readColorEndController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_color.end.r = data[dataIndex + 0];
node._emitter->_color.end.g = data[dataIndex + 1];
node._emitter->_color.end.b = data[dataIndex + 2];
}
void MdlFile::readAlphaStartController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_alpha.start = data[dataIndex];
}
void MdlFile::readAlphaMidController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_alpha.mid = data[dataIndex];
}
void MdlFile::readAlphaEndController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
node._emitter->_alpha.end = data[dataIndex];
}
void MdlFile::readSizeXController(uint16_t dataIndex, const vector<float> &data, ModelNode &node) {
@ -733,7 +823,11 @@ void MdlFile::readEmitter(ModelNode &node) {
node._emitter->_texture = Textures::instance().get(boost::to_lower_copy(readCString(32)), TextureType::Diffuse);
ignore(56);
ignore(24);
node._emitter->_renderOrder = readUint16();
ignore(30);
}
} // namespace render

View file

@ -61,8 +61,14 @@ private:
// Controllers
void readAlphaController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readAlphaEndController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readAlphaMidController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readAlphaStartController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readBirthrateController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readColorController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readColorEndController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readColorMidController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readColorStartController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readFrameEndController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readFrameStartController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readLifeExpectancyController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
@ -72,6 +78,8 @@ private:
void readRadiusController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readRandomVelocityController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readSelfIllumColorController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readSizeEndController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readSizeMidController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readSizeStartController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readSizeXController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);
void readSizeYController(uint16_t dataIndex, const std::vector<float> &data, render::ModelNode &node);

View file

@ -73,6 +73,8 @@ layout(std140) uniform General {
uniform vec2 uBlurResolution;
uniform vec2 uBlurDirection;
uniform vec2 uBillboardGridSize;
uniform vec2 uBillboardSize;
uniform vec4 uParticleCenter;
uniform int uBillboardFrame;
};
@ -162,6 +164,29 @@ void main() {
}
)END";
static const GLchar kSourceVertexBillboard[] = R"END(
uniform mat4 uProjection;
uniform mat4 uView;
layout(location = 0) in vec3 aPosition;
layout(location = 2) in vec2 aTexCoords;
out vec2 fragTexCoords;
void main() {
vec3 cameraRight = vec3(uView[0][0], uView[1][0], uView[2][0]);
vec3 cameraUp = vec3(uView[0][1], uView[1][1], uView[2][1]);
vec3 position =
uParticleCenter.xyz +
cameraRight * aPosition.x * uBillboardSize.x +
cameraUp * aPosition.y * uBillboardSize.y;
gl_Position = uProjection * uView * vec4(position, 1.0);
fragTexCoords = aTexCoords;
}
)END";
static const GLchar kSourceGeometryDepth[] = R"END(
const int NUM_CUBE_FACES = 6;
@ -398,7 +423,8 @@ void main() {
texCoords.x += oneOverGridX * (uBillboardFrame % int(uBillboardGridSize.x));
}
fragColor = texture(uTexture, texCoords);
vec4 textureSample = texture(uTexture, texCoords);
fragColor = vec4(uColor.rgb * textureSample.rgb, uAlpha * textureSample.a);
}
)END";
@ -430,6 +456,7 @@ void Shaders::initGL() {
initShader(ShaderName::VertexGUI, GL_VERTEX_SHADER, kSourceVertexGUI);
initShader(ShaderName::VertexModel, GL_VERTEX_SHADER, kSourceVertexModel);
initShader(ShaderName::VertexDepth, GL_VERTEX_SHADER, kSourceVertexDepth);
initShader(ShaderName::VertexBillboard, GL_VERTEX_SHADER, kSourceVertexBillboard);
initShader(ShaderName::GeometryDepth, GL_GEOMETRY_SHADER, kSourceGeometryDepth);
initShader(ShaderName::FragmentWhite, GL_FRAGMENT_SHADER, kSourceFragmentWhite);
initShader(ShaderName::FragmentGUI, GL_FRAGMENT_SHADER, kSourceFragmentGUI);
@ -447,7 +474,7 @@ void Shaders::initGL() {
initProgram(ShaderProgram::GUIDebugShadows, 2, ShaderName::VertexGUI, ShaderName::FragmentDebugShadows);
initProgram(ShaderProgram::ModelWhite, 2, ShaderName::VertexModel, ShaderName::FragmentWhite);
initProgram(ShaderProgram::ModelModel, 2, ShaderName::VertexModel, ShaderName::FragmentModel);
initProgram(ShaderProgram::ModelBillboard, 2, ShaderName::VertexModel, ShaderName::FragmentBillboard);
initProgram(ShaderProgram::BillboardBillboard, 2, ShaderName::VertexBillboard, ShaderName::FragmentBillboard);
initProgram(ShaderProgram::DepthDepth, 3, ShaderName::VertexDepth, ShaderName::GeometryDepth, ShaderName::FragmentDepth);
glGenBuffers(1, &_generalUbo);

View file

@ -44,7 +44,7 @@ enum class ShaderProgram {
GUIDebugShadows,
ModelWhite,
ModelModel,
ModelBillboard,
BillboardBillboard,
DepthDepth
};
@ -90,8 +90,10 @@ struct GeneralUniforms {
glm::vec2 blurResolution { 0.0f };
glm::vec2 blurDirection { 0.0f };
glm::vec2 billboardGridSize { 0.0f };
glm::vec2 billboardSize { 0.0f };
glm::vec4 particleCenter { 0.0f };
int billboardFrame { 0 };
char padding3[4];
char padding3[12];
};
struct SkeletalUniforms {
@ -140,6 +142,7 @@ private:
VertexGUI,
VertexModel,
VertexDepth,
VertexBillboard,
FragmentWhite,
FragmentGUI,
FragmentModel,

View file

@ -17,20 +17,11 @@
#include "emitternode.h"
#include <algorithm>
#include <stdexcept>
#include "glm/gtx/euler_angles.hpp"
#include "glm/gtx/matrix_decompose.hpp"
#include "../../common/random.h"
#include "../../render/mesh/billboard.h"
#include "../../render/shaders.h"
#include "../../render/util.h"
#include "../scenegraph.h"
#include "cameranode.h"
#include "particlenode.h"
using namespace std;
@ -41,16 +32,11 @@ namespace reone {
namespace scene {
constexpr int kMaxParticleCount = 16;
constexpr float kParticleFrameInterval = 0.2f;
EmitterSceneNode::EmitterSceneNode(const shared_ptr<ModelNode> &modelNode, const shared_ptr<Emitter> &emitter, SceneGraph *sceneGraph) :
EmitterSceneNode::EmitterSceneNode(const shared_ptr<Emitter> &emitter, SceneGraph *sceneGraph) :
SceneNode(sceneGraph),
_modelNode(modelNode),
_emitter(emitter) {
if (!modelNode) {
throw invalid_argument("modelNode must not be null");
}
if (!emitter) {
throw invalid_argument("emitter must not be null");
}
@ -64,6 +50,7 @@ void EmitterSceneNode::init() {
void EmitterSceneNode::update(float dt) {
if (_emitter->updateType() != Emitter::UpdateType::Fountain) return;
if (_emitter->renderType() != Emitter::RenderType::Normal) return;
spawnParticles(dt);
updateParticles(dt);
@ -76,82 +63,32 @@ void EmitterSceneNode::spawnParticles(float dt) {
if (_particles.size() < kMaxParticleCount) {
float halfW = 0.005f * _emitter->size().x;
float halfH = 0.005f * _emitter->size().y;
glm::vec3 position(random(-halfW, halfW), random(-halfH, halfH), 0.0f);
auto particle = make_shared<Particle>();
particle->position = _absoluteTransform * glm::vec4(random(-halfW, halfW), random(-halfH, halfH), 0.0f, 1.0f);
particle->frame = _emitter->frameStart();
particle->frameTimer.reset(kParticleFrameInterval);
particle->normal = getPlaneNormal();
particle->velocity = (_emitter->velocity() + random(0.0f, _emitter->randomVelocity()));
particle->lifetime = _emitter->lifeExpectancy();
float velocity = (_emitter->velocity() + random(0.0f, _emitter->randomVelocity()));
_particles.push_back(move(particle));
auto particle = make_shared<ParticleSceneNode>(position, velocity, _emitter, _sceneGraph);
_particles.push_back(particle);
addChild(particle);
}
_birthTimer.reset(_birthInterval);
}
}
glm::vec3 EmitterSceneNode::getPlaneNormal() const {
static glm::vec3 up(0.0f, 0.0f, 1.0f);
glm::vec3 scale, translation, skew;
glm::quat orientation;
glm::vec4 perspective;
glm::decompose(_modelNode->absoluteTransform(), scale, orientation, translation, skew, perspective);
return glm::normalize(orientation * up);
}
void EmitterSceneNode::updateParticles(float dt) {
for (auto it = _particles.begin(); it != _particles.end(); ) {
auto &particle = (*it);
particle->update(dt);
// Expire the particle
particle->lifetime = glm::max(0.0f, particle->lifetime - dt);
if (particle->lifetime == 0.0f) {
if (particle->isExpired()) {
removeChild(*particle);
it = _particles.erase(it);
} else {
// Update the particle position
particle->position += particle->velocity * particle->normal * dt; // TODO: use velocity?
// Animate the particle
particle->frameTimer.update(dt);
if (particle->frameTimer.hasTimedOut()) {
++particle->frame;
if (particle->frame > _emitter->frameEnd()) {
particle->frame = _emitter->frameStart();
}
particle->frameTimer.reset(kParticleFrameInterval);
}
++it;
}
}
}
void EmitterSceneNode::renderSingle(bool shadowPass) const {
if (shadowPass) return;
for (auto &particle : _particles) {
glm::mat4 transform(1.0f);
transform = glm::translate(transform, particle->position);
transform = glm::scale(transform, glm::vec3(_emitter->sizeStart()));
LocalUniforms locals;
locals.general.model = move(transform);
locals.general.billboardGridSize = glm::vec2(_emitter->gridWidth(), _emitter->gridHeight());
locals.general.billboardFrame = particle->frame;
Shaders::instance().activate(ShaderProgram::ModelBillboard, locals);
setActiveTextureUnit(0);
_emitter->texture()->bind();
BillboardMesh::instance().renderTriangles();
}
}
} // namespace scene
} // namespace reone

View file

@ -24,48 +24,33 @@
#include "glm/vec3.hpp"
#include "../../common/timer.h"
#include "../../render/emitter.h"
#include "../../render/model/modelnode.h"
namespace reone {
namespace scene {
class ParticleSceneNode;
class EmitterSceneNode : public SceneNode {
public:
EmitterSceneNode(const std::shared_ptr<render::ModelNode> &modelNode, const std::shared_ptr<render::Emitter> &emitter, SceneGraph *sceneGraph);
EmitterSceneNode(const std::shared_ptr<render::Emitter> &emitter, SceneGraph *sceneGraph);
void update(float dt);
void renderSingle(bool shadowPass) const override;
std::shared_ptr<render::Emitter> emitter() const { return _emitter; }
private:
struct Particle {
glm::vec3 position { 0.0f };
float cameraDistance { 0.0f };
int frame { 0 };
Timer frameTimer;
glm::vec3 normal { 0.0f };
float lifetime { 0.0f };
float velocity { 0.0f };
};
std::shared_ptr<render::ModelNode> _modelNode;
std::shared_ptr<render::Emitter> _emitter;
float _birthInterval { 0.0f };
Timer _birthTimer;
std::vector<std::shared_ptr<Particle>> _particles;
std::vector<std::shared_ptr<ParticleSceneNode>> _particles;
void init();
void spawnParticles(float dt);
void updateParticles(float dt);
glm::vec3 getPlaneNormal() const;
};
} // namespace scene

View file

@ -76,7 +76,7 @@ void ModelSceneNode::initModelNodes() {
shared_ptr<Emitter> emitter(child->emitter());
if (emitter) {
auto emitterNode = make_shared<EmitterSceneNode>(child, emitter, _sceneGraph);
auto emitterNode = make_shared<EmitterSceneNode>(emitter, _sceneGraph);
childNode->addChild(emitterNode);
_emitters.push_back(emitterNode);
}

View file

@ -0,0 +1,122 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "particlenode.h"
#include <stdexcept>
#include "glm/common.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "../../render/mesh/billboard.h"
#include "../../render/shaders.h"
#include "../../render/util.h"
using namespace std;
using namespace reone::render;
namespace reone {
namespace scene {
ParticleSceneNode::ParticleSceneNode(glm::vec3 position, float velocity, const shared_ptr<Emitter> &emitter, SceneGraph *sceneGraph) :
SceneNode(sceneGraph),
_position(position),
_velocity(velocity),
_emitter(emitter) {
if (!emitter) {
throw invalid_argument("emitter must not be null");
}
init();
}
void ParticleSceneNode::init() {
_renderOrder = _emitter->renderOrder();
_frame = _emitter->frameStart();
updateLocalTransform();
}
void ParticleSceneNode::updateLocalTransform() {
setLocalTransform(glm::translate(glm::mat4(1.0f), _position));
}
void ParticleSceneNode::update(float dt) {
_lifetime = glm::min(_lifetime + dt, static_cast<float>(_emitter->lifeExpectancy()));
if (_lifetime < _emitter->lifeExpectancy()) {
_position.z += _velocity * dt;
updateLocalTransform();
}
updateAnimation(dt);
}
template <class T>
static T interpolateConstraints(const Emitter::Constraints<T> &constraints, float t) {
T result;
if (t < 0.5f) {
float tt = 2.0f * t;
result = (1.0f - tt) * constraints.start + tt * constraints.mid;
} else {
float tt = 2.0f * (t - 0.5f);
result = (1.0f - tt) * constraints.mid + tt * constraints.end;
}
return result;
}
void ParticleSceneNode::updateAnimation(float dt) {
float maturity = _lifetime / static_cast<float>(_emitter->lifeExpectancy());
_frame = glm::ceil(_emitter->frameStart() + maturity * (_emitter->frameEnd() - _emitter->frameStart()));
_size = interpolateConstraints(_emitter->particleSize(), maturity);
_color = interpolateConstraints(_emitter->color(), maturity);
_alpha = interpolateConstraints(_emitter->alpha(), maturity);
}
void ParticleSceneNode::renderSingle(bool shadowPass) const {
if (shadowPass) return;
LocalUniforms locals;
locals.general.color = glm::vec4(_color, 1.0f);
locals.general.alpha = _alpha;
locals.general.billboardGridSize = glm::vec2(_emitter->gridWidth(), _emitter->gridHeight());
locals.general.billboardSize = glm::vec2(_size);
locals.general.particleCenter = _absoluteTransform[3];
locals.general.billboardFrame = _frame;
Shaders::instance().activate(ShaderProgram::BillboardBillboard, locals);
setActiveTextureUnit(0);
_emitter->texture()->bind();
BillboardMesh::instance().renderTriangles();
}
bool ParticleSceneNode::isExpired() const {
return _lifetime >= _emitter->lifeExpectancy();
}
int ParticleSceneNode::renderOrder() const {
return _renderOrder;
}
} // namespace scene
} // namespace reone

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "scenenode.h"
#include "../../render/emitter.h"
namespace reone {
namespace scene {
class ParticleSceneNode : public SceneNode {
public:
ParticleSceneNode(glm::vec3 position, float velocity, const std::shared_ptr<render::Emitter> &emitter, SceneGraph *sceneGraph);
void update(float dt);
void renderSingle(bool shadowPass) const override;
bool isExpired() const;
int renderOrder() const;
private:
std::shared_ptr<render::Emitter> _emitter;
glm::vec3 _position { 0.0f };
float _velocity { 0.0f };
int _renderOrder { 0 };
float _lifetime { 0.0f };
int _frame { 0 };
float _size { 1.0f };
glm::vec3 _color { 1.0f };
float _alpha { 1.0f };
void init();
void updateAnimation(float dt);
void updateLocalTransform();
};
} // namespace scene
} // namespace reone

View file

@ -23,10 +23,10 @@
#include "../render/mesh/quad.h"
#include "node/cameranode.h"
#include "node/emitternode.h"
#include "node/lightnode.h"
#include "node/modelnodescenenode.h"
#include "node/modelscenenode.h"
#include "node/particlenode.h"
using namespace std;
@ -36,7 +36,7 @@ namespace reone {
namespace scene {
static bool g_emittersEnabled = false;
static bool g_emittersEnabled = true;
SceneGraph::SceneGraph(const GraphicsOptions &opts) : _opts(opts) {
}
@ -62,7 +62,7 @@ void SceneGraph::build() {
void SceneGraph::prepareFrame() {
if (!_activeCamera) return;
refreshMeshesLightsAndEmitters();
refreshNodeLists();
refreshShadowLight();
for (auto &root : _roots) {
@ -71,9 +71,10 @@ void SceneGraph::prepareFrame() {
modelNode->updateLighting();
}
}
// Sort transparent meshes
unordered_map<SceneNode *, float> cameraDistances;
glm::vec3 cameraPosition(_activeCamera->absoluteTransform()[3]);
for (auto &mesh : _transparentMeshes) {
cameraDistances.insert(make_pair(mesh, mesh->distanceTo(cameraPosition)));
}
@ -89,14 +90,27 @@ void SceneGraph::prepareFrame() {
return leftDistance > rightDistance;
});
// Sort particles
unordered_map<SceneNode *, float> particlesZ;
for (auto &particle : _particles) {
glm::vec4 screen(_activeCamera->projection() * _activeCamera->view() * particle->absoluteTransform()[3]);
screen /= screen.w;
particlesZ.insert(make_pair(particle, screen.z));
}
sort(_particles.begin(), _particles.end(), [&particlesZ](auto &left, auto &right) {
float leftZ = particlesZ.find(left)->second;
float rightZ = particlesZ.find(right)->second;
return leftZ > rightZ;
});
}
void SceneGraph::refreshMeshesLightsAndEmitters() {
void SceneGraph::refreshNodeLists() {
_opaqueMeshes.clear();
_transparentMeshes.clear();
_shadowMeshes.clear();
_lights.clear();
_emitters.clear();
_particles.clear();
for (auto &root : _roots) {
stack<SceneNode *> nodes;
@ -128,9 +142,9 @@ void SceneGraph::refreshMeshesLightsAndEmitters() {
if (light) {
_lights.push_back(light);
} else if (g_emittersEnabled) {
auto emitter = dynamic_cast<EmitterSceneNode *>(node);
if (emitter) {
_emitters.push_back(emitter);
auto particle = dynamic_cast<ParticleSceneNode *>(node);
if (particle) {
_particles.push_back(particle);
}
}
}
@ -188,12 +202,12 @@ void SceneGraph::renderNoGlobalUniforms(bool shadowPass) const {
for (auto &mesh : _opaqueMeshes) {
mesh->renderSingle(false);
}
for (auto &emitter : _emitters) {
emitter->renderSingle(false);
}
for (auto &mesh : _transparentMeshes) {
mesh->renderSingle(false);
}
for (auto &particle : _particles) {
particle->renderSingle(false);
}
}
void SceneGraph::getLightsAt(const glm::vec3 &position, vector<LightSceneNode *> &lights) const {

View file

@ -30,9 +30,9 @@ namespace reone {
namespace scene {
class CameraSceneNode;
class EmitterSceneNode;
class LightSceneNode;
class ModelNodeSceneNode;
class ParticleSceneNode;
class SceneNode;
class SceneGraph {
@ -75,7 +75,7 @@ private:
std::vector<ModelNodeSceneNode *> _transparentMeshes;
std::vector<ModelNodeSceneNode *> _shadowMeshes;
std::vector<LightSceneNode *> _lights;
std::vector<EmitterSceneNode *> _emitters;
std::vector<ParticleSceneNode *> _particles;
std::shared_ptr<CameraSceneNode> _activeCamera;
glm::vec3 _ambientLightColor { 0.5f };
uint32_t _textureId { 0 };
@ -86,7 +86,7 @@ private:
SceneGraph(const SceneGraph &) = delete;
SceneGraph &operator=(const SceneGraph &) = delete;
void refreshMeshesLightsAndEmitters();
void refreshNodeLists();
void refreshShadowLight();
};