feat: Properly implement fountain emitters
This commit is contained in:
parent
0f26b589ff
commit
b585bb79a8
14 changed files with 392 additions and 128 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
122
src/scene/node/particlenode.cpp
Normal file
122
src/scene/node/particlenode.cpp
Normal 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
|
59
src/scene/node/particlenode.h
Normal file
59
src/scene/node/particlenode.h
Normal 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
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue