Major refactoring of the scene management system

This commit is contained in:
Vsevolod Kremianskii 2021-05-14 23:31:05 +07:00
parent aeffc4b453
commit 740440ca78
76 changed files with 1314 additions and 1889 deletions

View file

@ -163,7 +163,7 @@ set(GRAPHICS_HEADERS
src/engine/graphics/font.h
src/engine/graphics/fonts.h
src/engine/graphics/framebuffer.h
src/engine/graphics/lip/lipanimation.h
src/engine/graphics/lip/animation.h
src/engine/graphics/lip/lipreader.h
src/engine/graphics/lip/lips.h
src/engine/graphics/lip/lipwriter.h
@ -205,7 +205,7 @@ set(GRAPHICS_SOURCES
src/engine/graphics/font.cpp
src/engine/graphics/fonts.cpp
src/engine/graphics/framebuffer.cpp
src/engine/graphics/lip/lipanimation.cpp
src/engine/graphics/lip/animation.cpp
src/engine/graphics/lip/lipreader.cpp
src/engine/graphics/lip/lips.cpp
src/engine/graphics/lip/lipwriter.cpp
@ -289,36 +289,33 @@ set_target_properties(libvideo PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINAR
## libscene static library
set(SCENE_HEADERS
src/engine/scene/animation/eventlistener.h
src/engine/scene/animation/channel.h
src/engine/scene/animation/properties.h
src/engine/scene/animation/scenenodeanimator.h
src/engine/scene/animation/scenenodestate.h
src/engine/scene/grasscluster.h
src/engine/scene/animeventlistener.h
src/engine/scene/animproperties.h
src/engine/scene/node/cameranode.h
src/engine/scene/node/dummynode.h
src/engine/scene/node/emitternode.h
src/engine/scene/node/grassnode.h
src/engine/scene/node/lightnode.h
src/engine/scene/node/meshnode.h
src/engine/scene/node/modelnode.h
src/engine/scene/node/modelnodescenenode.h
src/engine/scene/node/scenenode.h
src/engine/scene/particle.h
src/engine/scene/pipeline/control.h
src/engine/scene/pipeline/world.h
src/engine/scene/scenegraph.h
src/engine/scene/types.h)
set(SCENE_SOURCES
src/engine/scene/animation/channel.cpp
src/engine/scene/animation/scenenodeanimator.cpp
src/engine/scene/node/cameranode.cpp
src/engine/scene/node/emitternode.cpp
src/engine/scene/node/emitternode_particle.cpp
src/engine/scene/node/grassnode.cpp
src/engine/scene/node/lightnode.cpp
src/engine/scene/node/meshnode.cpp
src/engine/scene/node/modelnode.cpp
src/engine/scene/node/modelnode_animation.cpp
src/engine/scene/node/modelnodescenenode.cpp
src/engine/scene/node/scenenode.cpp
src/engine/scene/particle.cpp
src/engine/scene/pipeline/control.cpp
src/engine/scene/pipeline/world.cpp
src/engine/scene/scenegraph.cpp)

View file

@ -380,7 +380,6 @@ void ActionExecutor::executeJumpToLocation(const shared_ptr<Object> &actor, Loca
void ActionExecutor::executePlayAnimation(const shared_ptr<Object> &actor, const shared_ptr<PlayAnimationAction> &action, float dt) {
AnimationProperties properties;
properties.flags = AnimationFlags::propagateHead;
properties.speed = action->speed();
auto spatial = static_pointer_cast<SpatialObject>(actor);

View file

@ -21,8 +21,10 @@
#include <boost/format.hpp>
#include "../../graphics/types.h"
#include "../../graphics/model/models.h"
#include "../../resource/resources.h"
#include "../../scene/node/cameranode.h"
using namespace std;
@ -33,14 +35,14 @@ namespace reone {
namespace game {
AnimatedCamera::AnimatedCamera(SceneGraph *sceneGraph, float aspect) : _sceneGraph(sceneGraph), _aspect(aspect) {
AnimatedCamera::AnimatedCamera(float aspect, SceneGraph *sceneGraph) : _sceneGraph(sceneGraph), _aspect(aspect) {
_sceneGraph = sceneGraph;
_sceneNode = make_shared<CameraSceneNode>(_sceneGraph, glm::mat4(1.0f), _aspect, _zNear, _zFar);
_sceneNode = make_shared<CameraSceneNode>("", glm::mat4(1.0f), _sceneGraph);
updateProjection();
}
void AnimatedCamera::updateProjection() {
glm::mat4 projection(glm::perspective(glm::radians(_fovy), _aspect, _zNear, _zFar));
glm::mat4 projection(glm::perspective(glm::radians(_fovy), _aspect, kDefaultClipPlaneNear, kDefaultClipPlaneFar));
_sceneNode->setProjection(projection);
}
@ -64,12 +66,12 @@ static const string &getAnimationName(int animNumber) {
void AnimatedCamera::playAnimation(int animNumber) {
if (_model) {
_model->animator().playAnimation(getAnimationName(animNumber));
_model->playAnimation(getAnimationName(animNumber));
}
}
bool AnimatedCamera::isAnimationFinished() const {
return _model ? _model->animator().isAnimationFinished() : false;
return _model ? _model->isAnimationFinished() : false;
}
void AnimatedCamera::setModel(const shared_ptr<Model> &model) {
@ -77,7 +79,7 @@ void AnimatedCamera::setModel(const shared_ptr<Model> &model) {
(!_model && !model)) return;
if (model) {
_model = make_unique<ModelSceneNode>(ModelUsage::Other, model, _sceneGraph);
_model = make_unique<ModelSceneNode>(model, ModelUsage::Camera, _sceneGraph);
_model->attach("camerahook", _sceneNode);
} else {
_model.reset();

View file

@ -34,7 +34,7 @@ const float kDefaultAnimCamFOV = 55.0f;
class AnimatedCamera : public Camera {
public:
AnimatedCamera(scene::SceneGraph *sceneGraph, float aspect);
AnimatedCamera(float aspect, scene::SceneGraph *sceneGraph);
void update(float dt) override;
@ -50,8 +50,6 @@ private:
float _aspect { 1.0f };
std::unique_ptr<scene::ModelSceneNode> _model;
float _fovy { kDefaultAnimCamFOV };
float _zNear { 0.1f };
float _zFar { 10000.0f };
void updateProjection();
};

View file

@ -28,9 +28,9 @@ namespace reone {
namespace game {
DialogCamera::DialogCamera(SceneGraph *sceneGraph, const CameraStyle &style, float aspect, float zNear, float zFar) {
glm::mat4 projection(glm::perspective(glm::radians(style.viewAngle), aspect, zNear, zFar));
_sceneNode = make_shared<CameraSceneNode>(sceneGraph, projection, aspect, zNear, zFar);
DialogCamera::DialogCamera(float aspect, const CameraStyle &style, SceneGraph *sceneGraph) {
glm::mat4 projection(glm::perspective(glm::radians(style.viewAngle), aspect, kDefaultClipPlaneNear, kDefaultClipPlaneFar));
_sceneNode = make_shared<CameraSceneNode>("", move(projection), sceneGraph);
}
void DialogCamera::setSpeakerPosition(const glm::vec3 &position) {

View file

@ -40,7 +40,7 @@ public:
ListenerFar
};
DialogCamera(scene::SceneGraph *sceneGraph, const CameraStyle &style, float aspect, float zNear = 0.1f, float zFar = 10000.0f);
DialogCamera(float aspect, const CameraStyle &style, scene::SceneGraph *sceneGraph);
void setSpeakerPosition(const glm::vec3 &position);
void setListenerPosition(const glm::vec3 &position);

View file

@ -19,8 +19,11 @@
#include "glm/ext.hpp"
#include "../../graphics/types.h"
using namespace std;
using namespace reone::graphics;
using namespace reone::scene;
namespace reone {
@ -30,9 +33,9 @@ namespace game {
static constexpr float kMovementSpeed = 4.0f;
static constexpr float kMouseMultiplier = glm::pi<float>() / 4000.0f;
FirstPersonCamera::FirstPersonCamera(SceneGraph *sceneGraph, float aspect, float fovy, float zNear, float zFar) {
glm::mat4 projection(glm::perspective(fovy, aspect, zNear, zFar));
_sceneNode = make_unique<CameraSceneNode>(sceneGraph, projection, aspect, zNear, zFar);
FirstPersonCamera::FirstPersonCamera(float aspect, float fovy, SceneGraph *sceneGraph) {
glm::mat4 projection(glm::perspective(fovy, aspect, kDefaultClipPlaneNear, kDefaultClipPlaneFar));
_sceneNode = make_unique<CameraSceneNode>("", move(projection), sceneGraph);
}
bool FirstPersonCamera::handle(const SDL_Event &event) {

View file

@ -25,7 +25,7 @@ namespace game {
class FirstPersonCamera : public Camera {
public:
FirstPersonCamera(scene::SceneGraph *sceneGraph, float aspect, float fovy, float zNear = 0.1f, float zFar = 10000.0f);
FirstPersonCamera(float aspect, float fovy, scene::SceneGraph *sceneGraph);
bool handle(const SDL_Event &event) override;
void update(float dt) override;

View file

@ -23,22 +23,19 @@
using namespace std;
using namespace reone::graphics;
using namespace reone::scene;
namespace reone {
namespace game {
static constexpr float kNearPlane = 0.1f;
static constexpr float kFarPlane = 10000.0f;
StaticCamera::StaticCamera(SceneGraph *sceneGraph, float aspect) : _aspect(aspect) {
_sceneNode = make_unique<CameraSceneNode>(sceneGraph, glm::mat4(1.0f), aspect, kNearPlane, kFarPlane);
StaticCamera::StaticCamera(float aspect, SceneGraph *sceneGraph) : _aspect(aspect) {
_sceneNode = make_unique<CameraSceneNode>("", glm::mat4(1.0f), sceneGraph);
}
void StaticCamera::setObject(const PlaceableCamera &object) {
glm::mat4 projection(glm::perspective(glm::radians(object.fieldOfView()), _aspect, kNearPlane, kFarPlane));
glm::mat4 projection(glm::perspective(glm::radians(object.fieldOfView()), _aspect, kDefaultClipPlaneNear, kDefaultClipPlaneFar));
_sceneNode->setLocalTransform(object.transform());
_sceneNode->setProjection(projection);
}

View file

@ -27,7 +27,7 @@ class PlaceableCamera;
class StaticCamera : public Camera {
public:
StaticCamera(scene::SceneGraph *sceneGraph, float aspect);
StaticCamera(float aspect, scene::SceneGraph *sceneGraph);
void setObject(const PlaceableCamera &object);

View file

@ -23,6 +23,7 @@
using namespace std;
using namespace reone::graphics;
using namespace reone::scene;
namespace reone {
@ -34,9 +35,9 @@ static constexpr float kMaxRotationSpeed = 2.5f;
static constexpr float kRotationAcceleration = 1.0f;
static constexpr float kMouseRotationSpeed = 0.001f;
ThirdPersonCamera::ThirdPersonCamera(Game *game, SceneGraph *sceneGraph, float aspect, const CameraStyle &style, float zNear, float zFar) : _game(game) {
glm::mat4 projection(glm::perspective(glm::radians(style.viewAngle), aspect, zNear, zFar));
_sceneNode = make_unique<CameraSceneNode>(sceneGraph, projection, aspect, zNear, zFar);
ThirdPersonCamera::ThirdPersonCamera(float aspect, const CameraStyle &style, Game *game, SceneGraph *sceneGraph) : _game(game) {
glm::mat4 projection(glm::perspective(glm::radians(style.viewAngle), aspect, kDefaultClipPlaneNear, kDefaultClipPlaneFar));
_sceneNode = make_unique<CameraSceneNode>("", move(projection), sceneGraph);
_style = style;
}

View file

@ -32,7 +32,7 @@ class Game;
class ThirdPersonCamera : public Camera {
public:
ThirdPersonCamera(Game *game, scene::SceneGraph *sceneGraph, float aspect, const CameraStyle &style, float zNear = 0.1f, float zFar = 10000.0f);
ThirdPersonCamera(float aspect, const CameraStyle &style, Game *game, scene::SceneGraph *sceneGraph);
bool handle(const SDL_Event &event) override;
void update(float dt) override;

View file

@ -22,6 +22,7 @@
#include "combat.h"
#include "glm/gtx/euler_angles.hpp"
#include "glm/gtx/transform.hpp"
#include "../game.h"
@ -38,38 +39,42 @@ static constexpr char kModelEventDetonate[] = "detonate";
static constexpr float kProjectileSpeed = 16.0f;
void Combat::fireProjectile(const shared_ptr<Creature> &attacker, const shared_ptr<SpatialObject> &target, Round &round) {
auto attackerModel = static_pointer_cast<ModelSceneNode>(attacker->sceneNode());
auto targetModel = static_pointer_cast<ModelSceneNode>(target->sceneNode());
if (!attackerModel || !targetModel) return;
shared_ptr<Item> weapon(attacker->getEquippedItem(InventorySlot::rightWeapon));
if (!weapon) return;
shared_ptr<Item::AmmunitionType> ammunitionType(weapon->ammunitionType());
if (!ammunitionType) return;
shared_ptr<ModelSceneNode> weaponModel(attacker->getModelSceneNode()->getAttachedModel("rhand"));
auto weaponModel = static_pointer_cast<ModelSceneNode>(attackerModel->getAttachment("rhand"));
if (!weaponModel) return;
// Determine projectile position
glm::vec3 projectilePos, bulletHookPos;
if (weaponModel->getNodeAbsolutePosition("bullethook", bulletHookPos)) {
projectilePos = weaponModel->absoluteTransform() * glm::vec4(bulletHookPos, 1.0f);
glm::vec3 projectilePos;
shared_ptr<ModelNode> bulletHook(weaponModel->model()->getNodeByName("bullethook"));
if (bulletHook) {
projectilePos = weaponModel->absoluteTransform() * glm::vec4(bulletHook->restPosition(), 1.0f);
} else {
projectilePos = weaponModel->absoluteTransform()[3];
projectilePos = weaponModel->getOrigin();
}
// Determine projectile direction
shared_ptr<ModelSceneNode> targetModel(target->getModelSceneNode());
glm::vec3 projectileTarget, impactPos;
if (targetModel->getNodeAbsolutePosition("impact", impactPos)) {
projectileTarget = targetModel->absoluteTransform() * glm::vec4(impactPos, 1.0f);
glm::vec3 projectileTarget;
shared_ptr<ModelNode> impact(targetModel->model()->getNodeByName("impact"));
if (impact) {
projectileTarget = targetModel->absoluteTransform() * glm::vec4(impact->restPosition(), 1.0f);
} else {
projectileTarget = targetModel->absoluteTransform()[3];
projectileTarget = targetModel->getOrigin();
}
round.projectileDir = glm::normalize(projectileTarget - projectilePos);
// Create and add a projectile to the scene graph
round.projectile = make_shared<ModelSceneNode>(ModelUsage::Projectile, ammunitionType->model, &_game->sceneGraph());
round.projectile = make_shared<ModelSceneNode>(ammunitionType->model, ModelUsage::Projectile, &_game->sceneGraph());
round.projectile->signalEvent(kModelEventDetonate);
round.projectile->setPosition(projectilePos);
round.projectile->setProjectileSpeed(kProjectileSpeed);
round.projectile->setLocalTransform(glm::translate(projectilePos));
_game->sceneGraph().addRoot(round.projectile);
// Play shot sound, if any

View file

@ -122,7 +122,8 @@ void Console::cmdListAnim(vector<string> tokens) {
substr = tokens[1];
}
vector<string> anims(object->getModelSceneNode()->model()->getAnimationNames());
auto model = static_pointer_cast<ModelSceneNode>(object->sceneNode());
vector<string> anims(model->model()->getAnimationNames());
sort(anims.begin(), anims.end());
for (auto &anim : anims) {
@ -146,7 +147,8 @@ void Console::cmdPlayAnim(vector<string> tokens) {
return;
}
}
object->getModelSceneNode()->animator().playAnimation(tokens[1], AnimationProperties::fromFlags(AnimationFlags::loop));
auto model = static_pointer_cast<ModelSceneNode>(object->sceneNode());
model->playAnimation(tokens[1], AnimationProperties::fromFlags(AnimationFlags::loop));
}
void Console::cmdKill(vector<string> tokens) {

View file

@ -729,7 +729,7 @@ void Game::updateSceneGraph(float dt) {
shared_ptr<SceneNode> lightingRefNode;
shared_ptr<Creature> partyLeader(_party.getLeader());
if (partyLeader && _cameraType == CameraType::ThirdPerson) {
lightingRefNode = partyLeader->getModelSceneNode();
lightingRefNode = partyLeader->sceneNode();
} else {
lightingRefNode = camera->sceneNode();
}

View file

@ -377,11 +377,11 @@ shared_ptr<ModelSceneNode> CharacterGeneration::getCharacterModel(SceneGraph &sc
creature->setAppearance(_character.appearance);
creature->equip("g_a_clothes01");
creature->loadAppearance();
creature->getModelSceneNode()->setCullable(false);
creature->sceneNode()->setCullable(false);
creature->updateModelAnimation();
auto model = make_shared<ModelSceneNode>(ModelUsage::GUI, Models::instance().get("cgbody_light"), &sceneGraph);
model->attach("cgbody_light", creature->getModelSceneNode());
auto model = make_shared<ModelSceneNode>(Models::instance().get("cgbody_light"), ModelUsage::GUI, &sceneGraph);
model->attach("cgbody_light", creature->sceneNode());
return move(model);
}

View file

@ -148,11 +148,11 @@ shared_ptr<ModelSceneNode> ClassSelection::getCharacterModel(int appearance, Sce
character->setAppearance(appearance);
character->equip("g_a_clothes01");
character->loadAppearance();
character->getModelSceneNode()->setCullable(false);
character->sceneNode()->setCullable(false);
character->updateModelAnimation();
auto model = make_shared<ModelSceneNode>(ModelUsage::GUI, Models::instance().get("cgbody_light"), &sceneGraph);
model->attach("cgbody_light", character->getModelSceneNode());
auto model = make_shared<ModelSceneNode>(Models::instance().get("cgbody_light"), ModelUsage::GUI, &sceneGraph);
model->attach("cgbody_light", character->sceneNode());
return move(model);
}

View file

@ -106,17 +106,17 @@ shared_ptr<ModelSceneNode> PortraitSelection::getCharacterModel(SceneGraph &scen
creature->setAppearance(getAppearanceFromCurrentPortrait());
creature->equip("g_a_clothes01");
creature->loadAppearance();
creature->getModelSceneNode()->setCullable(false);
creature->sceneNode()->setCullable(false);
creature->updateModelAnimation();
// Attach creature model to the root scene node
shared_ptr<ModelSceneNode> creatureModel(creature->getModelSceneNode());
glm::vec3 headPosition;
if (creatureModel->getNodeAbsolutePosition("camerahook", headPosition)) {
creature->setPosition(glm::vec3(0.0f, 0.0f, -headPosition.z));
auto creatureModel = static_pointer_cast<ModelSceneNode>(creature->sceneNode());
shared_ptr<ModelNode> cameraHook(creatureModel->model()->getNodeByName("camerahook"));
if (cameraHook) {
creature->setPosition(glm::vec3(0.0f, 0.0f, -cameraHook->restPosition().z));
}
auto model = make_shared<ModelSceneNode>(ModelUsage::GUI, Models::instance().get("cghead_light"), &sceneGraph);
auto model = make_shared<ModelSceneNode>(Models::instance().get("cghead_light"), ModelUsage::GUI, &sceneGraph);
model->attach("cghead_light", creatureModel);

View file

@ -19,7 +19,7 @@
#include "../../audio/soundhandle.h"
#include "../../common/timer.h"
#include "../../graphics/lip/lipanimation.h"
#include "../../graphics/lip/animation.h"
#include "../../graphics/model/model.h"
#include "../dialog.h"

View file

@ -250,16 +250,15 @@ void DialogGUI::updateCamera() {
}
glm::vec3 DialogGUI::getTalkPosition(const SpatialObject &object) const {
shared_ptr<ModelSceneNode> model(object.getModelSceneNode());
if (model) {
glm::vec3 hookPosition(0.0f);
if (model->getNodeAbsolutePosition("talkdummy", hookPosition)) {
return object.position() + hookPosition;
}
return model->getWorldCenterAABB();
}
auto model = static_pointer_cast<ModelSceneNode>(object.sceneNode());
if (!model) return object.position();
return object.position();
shared_ptr<ModelNode> talkDummy(model->model()->getNodeByName("talkdummy"));
return talkDummy ?
(object.transform() * talkDummy->absoluteTransform())[3] :
model->getWorldCenterOfAABB();
}
DialogCamera::Variant DialogGUI::getRandomCameraVariant() const {
@ -287,7 +286,6 @@ void DialogGUI::updateParticipantAnimations() {
shared_ptr<Animation> animation(participant.model->getAnimation(animName));
if (animation) {
AnimationProperties properties;
properties.flags = AnimationFlags::propagateHead;
properties.scale = 1.0f;
participant.creature->playAnimation(animation, move(properties));
}
@ -304,7 +302,7 @@ void DialogGUI::updateParticipantAnimations() {
}
AnimationType animType = getStuntAnimationType(anim.animation);
if (animType != AnimationType::Invalid) {
participant->playAnimation(animType, AnimationProperties::fromFlags(AnimationFlags::propagateHead));
participant->playAnimation(animType);
}
}
}

View file

@ -180,8 +180,8 @@ shared_ptr<ModelSceneNode> CharacterMenu::getSceneModel(SceneGraph &sceneGraph)
character->loadAppearance();
character->updateModelAnimation();
auto sceneModel = make_shared<ModelSceneNode>(ModelUsage::GUI, Models::instance().get("charmain_light"), &sceneGraph);
sceneModel->attach("charmain_light", character->getModelSceneNode());
auto sceneModel = make_shared<ModelSceneNode>(Models::instance().get("charmain_light"), ModelUsage::GUI, &sceneGraph);
sceneModel->attach("charmain_light", character->sceneNode());
return move(sceneModel);
}

View file

@ -127,9 +127,8 @@ void MainMenu::setup3DView() {
}
shared_ptr<ModelSceneNode> MainMenu::getKotorModel(SceneGraph &sceneGraph) {
auto model = make_shared<ModelSceneNode>(ModelUsage::GUI, Models::instance().get("mainmenu"), &sceneGraph);
model->animator().playAnimation("default", AnimationProperties::fromFlags(AnimationFlags::loop));
auto model = make_shared<ModelSceneNode>(Models::instance().get("mainmenu"), ModelUsage::GUI, &sceneGraph);
model->playAnimation("default", AnimationProperties::fromFlags(AnimationFlags::loop));
return move(model);
}

View file

@ -117,17 +117,17 @@ void Area::loadLYT() {
if (!model) continue;
glm::vec3 position(lytRoom.position.x, lytRoom.position.y, lytRoom.position.z);
shared_ptr<Walkmesh> walkmesh(Walkmeshes::instance().get(lytRoom.name, ResourceType::Wok));
auto sceneNode = make_shared<ModelSceneNode>(ModelUsage::Room, model, &_game->sceneGraph());
sceneNode->setWalkmesh(walkmesh);
auto sceneNode = make_shared<ModelSceneNode>(model, ModelUsage::Room, &_game->sceneGraph());
sceneNode->setLocalTransform(glm::translate(glm::mat4(1.0f), position));
for (auto &anim : model->getAnimationNames()) {
if (boost::starts_with(anim, "animloop")) {
sceneNode->animator().playAnimation(anim, AnimationProperties::fromFlags(AnimationFlags::loopOverlay));
sceneNode->playAnimation(anim, AnimationProperties::fromFlags(AnimationFlags::loopOverlay));
}
}
shared_ptr<Walkmesh> walkmesh(Walkmeshes::instance().get(lytRoom.name, ResourceType::Wok));
auto room = make_unique<Room>(lytRoom.name, position, sceneNode, walkmesh);
_rooms.insert(make_pair(room->name(), move(room)));
}
@ -181,20 +181,20 @@ void Area::initCameras(const glm::vec3 &entryPosition, float entryFacing) {
SceneGraph *sceneGraph = &_game->sceneGraph();
_firstPersonCamera = make_unique<FirstPersonCamera>(sceneGraph, _cameraAspect, glm::radians(kDefaultFieldOfView));
_firstPersonCamera = make_unique<FirstPersonCamera>(_cameraAspect, glm::radians(kDefaultFieldOfView), sceneGraph);
_firstPersonCamera->setPosition(position);
_firstPersonCamera->setFacing(entryFacing);
_thirdPersonCamera = make_unique<ThirdPersonCamera>(_game, sceneGraph, _cameraAspect, _camStyleDefault);
_thirdPersonCamera = make_unique<ThirdPersonCamera>(_cameraAspect, _camStyleDefault, _game, sceneGraph);
_thirdPersonCamera->setFindObstacle(bind(&Area::getCameraObstacle, this, _1, _2, _3));
_thirdPersonCamera->setTargetPosition(position);
_thirdPersonCamera->setFacing(entryFacing);
_dialogCamera = make_unique<DialogCamera>(sceneGraph, _camStyleDefault, _cameraAspect);
_dialogCamera = make_unique<DialogCamera>(_cameraAspect, _camStyleDefault, sceneGraph);
_dialogCamera->setFindObstacle(bind(&Area::getCameraObstacle, this, _1, _2, _3));
_animatedCamera = make_unique<AnimatedCamera>(sceneGraph, _cameraAspect);
_staticCamera = make_unique<StaticCamera>(sceneGraph, _cameraAspect);
_animatedCamera = make_unique<AnimatedCamera>(_cameraAspect, sceneGraph);
_staticCamera = make_unique<StaticCamera>(_cameraAspect, sceneGraph);
}
void Area::add(const shared_ptr<SpatialObject> &object) {
@ -232,7 +232,7 @@ void Area::doDestroyObject(uint32_t objectId) {
}
}
{
shared_ptr<ModelSceneNode> sceneNode(object->getModelSceneNode());
auto sceneNode = object->sceneNode();
if (sceneNode) {
_game->sceneGraph().removeRoot(sceneNode);
}
@ -358,10 +358,12 @@ bool Area::handleKeyDown(const SDL_KeyboardEvent &event) {
}
void Area::printDebugInfo(const SpatialObject &object) {
auto model = static_pointer_cast<ModelSceneNode>(object.sceneNode());
ostringstream ss;
ss << boost::format("tag='%s'") % object.tag();
ss << boost::format(",pos=[%0.2f,%0.2f,%0.2f]") % object.position().x % object.position().y % object.position().z;
ss << boost::format(",model='%s'") % object.getModelSceneNode()->getName();
ss << boost::format(",model='%s'") % model->model()->name();
debug("Selected object: " + ss.str());
}
@ -528,7 +530,7 @@ void Area::fill(SceneGraph &sceneGraph) {
shared_ptr<ModelNode> aabbNode(sceneNode->model()->getAABBNode());
if (aabbNode && _grass.texture) {
glm::mat4 aabbTransform(glm::translate(aabbNode->absoluteTransform(), room.second->position()));
auto grass = make_shared<GrassSceneNode>(&sceneGraph, glm::vec2(_grass.quadSize), _grass.texture, aabbNode->mesh()->lightmap);
auto grass = make_shared<GrassSceneNode>(room.first, glm::vec2(_grass.quadSize), _grass.texture, aabbNode->mesh()->lightmap, &sceneGraph);
for (auto &material : Surfaces::instance().getGrassSurfaceIndices()) {
for (auto &face : aabbNode->getFacesByMaterial(material)) {
vector<glm::vec3> vertices(aabbNode->mesh()->mesh->getTriangleCoords(face));
@ -537,7 +539,7 @@ void Area::fill(SceneGraph &sceneGraph) {
glm::vec3 baryPosition(getRandomBarycentric());
glm::vec3 position(aabbTransform * glm::vec4(barycentricToCartesian(vertices[0], vertices[1], vertices[2], baryPosition), 1.0f));
glm::vec2 lightmapUV(aabbNode->mesh()->mesh->getTriangleTexCoords2(face, baryPosition));
GrassCluster cluster;
GrassSceneNode::Cluster cluster;
cluster.position = move(position);
cluster.variant = getRandomGrassVariant();
cluster.lightmapUV = move(lightmapUV);
@ -644,13 +646,14 @@ void Area::update3rdPersonCameraTarget() {
shared_ptr<SpatialObject> partyLeader(_game->party().getLeader());
if (!partyLeader) return;
glm::vec3 position;
glm::vec3 position(partyLeader->position());
if (partyLeader->getModelSceneNode()->getNodeAbsolutePosition("camerahook", position)) {
position += partyLeader->position();
} else {
position = partyLeader->position();
auto model = static_pointer_cast<ModelSceneNode>(partyLeader->sceneNode());
shared_ptr<ModelNode> cameraHook(model->model()->getNodeByName("camerahook"));
if (cameraHook) {
position += cameraHook->restPosition();
}
_thirdPersonCamera->setTargetPosition(position);
}
@ -832,7 +835,7 @@ shared_ptr<Object> Area::createObject(ObjectType type, const string &blueprintRe
auto spatial = dynamic_pointer_cast<SpatialObject>(object);
if (spatial) {
add(spatial);
auto model = spatial->getModelSceneNode();
auto model = spatial->sceneNode();
if (model) {
_game->sceneGraph().addRoot(model);
}

View file

@ -39,7 +39,7 @@ bool Area::testElevationAt(const glm::vec2 &point, float &z, int &material, Room
// Test non-walkable faces of object walkmeshes
for (auto &o : _objects) {
shared_ptr<ModelSceneNode> model(o->getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(o->sceneNode());
shared_ptr<Walkmesh> walkmesh(o->getWalkmesh());
if (!model || !walkmesh) continue;
@ -94,7 +94,7 @@ shared_ptr<SpatialObject> Area::getObjectAt(int x, int y) const {
// Skip non-selectable objects and party leader
if (!o->isSelectable() || o == partyLeader) continue;
shared_ptr<ModelSceneNode> model(o->getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(o->sceneNode());
if (!model) continue;
// Distance to object must not exceed maximum collision distance
@ -124,7 +124,7 @@ bool Area::getCameraObstacle(const glm::vec3 &start, const glm::vec3 &end, glm::
for (auto &o : _objects) {
if (o->type() != ObjectType::Door) continue;
shared_ptr<ModelSceneNode> model(o->getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(o->sceneNode());
if (!model) continue;
// Distance to object must not exceed maximum collision distance
@ -202,7 +202,7 @@ bool Area::isInLineOfSight(const Creature &subject, const SpatialObject &target)
for (auto &o : _objects) {
if (o->type() != ObjectType::Door) continue;
shared_ptr<ModelSceneNode> model(o->getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(o->sceneNode());
if (!model) continue;
// Distance to object must not exceed maximum collision distance

View file

@ -56,7 +56,6 @@ namespace game {
static constexpr int kStrRefRemains = 38151;
static string g_headHookNode("headhook");
static string g_talkDummyNode("talkdummy");
Creature::Creature(
@ -117,7 +116,6 @@ void Creature::updateModel() {
if (model) {
model->setCullable(true);
model->setDrawDistance(32.0f);
_headModel = model->getAttachedModel(g_headHookNode);
if (!_stunt) {
model->setLocalTransform(_transform);
}
@ -158,11 +156,11 @@ void Creature::update(float dt) {
}
void Creature::updateModelAnimation() {
shared_ptr<ModelSceneNode> model(getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
if (!model) return;
if (_animFireForget) {
if (!model->animator().isAnimationFinished()) return;
if (!model->isAnimationFinished()) return;
_animFireForget = false;
_animDirty = true;
@ -197,20 +195,10 @@ void Creature::updateModelAnimation() {
}
if (talkAnim) {
int addFlags = _lipAnimation ? AnimationFlags::syncLipAnim : 0;
if (_headModel) {
model->animator().playAnimation(anim, AnimationProperties::fromFlags(AnimationFlags::loop));
_headModel->animator().playAnimation(anim, AnimationProperties::fromFlags(AnimationFlags::loop));
_headModel->animator().playAnimation(talkAnim, AnimationProperties::fromFlags(AnimationFlags::loopOverlay | addFlags), _lipAnimation);
} else {
model->animator().playAnimation(anim, AnimationProperties::fromFlags(AnimationFlags::loop));
model->animator().playAnimation(talkAnim, AnimationProperties::fromFlags(AnimationFlags::loopOverlay | addFlags), _lipAnimation);
}
model->playAnimation(anim, nullptr, AnimationProperties::fromFlags(AnimationFlags::loopOverlay | AnimationFlags::propagate));
model->playAnimation(talkAnim, _lipAnimation, AnimationProperties::fromFlags(AnimationFlags::loopOverlay | AnimationFlags::propagate));
} else {
model->animator().playAnimation(anim, AnimationProperties::fromFlags(AnimationFlags::loopBlend));
if (_headModel) {
_headModel->animator().playAnimation(anim, AnimationProperties::fromFlags(AnimationFlags::loopBlend));
}
model->playAnimation(anim, nullptr, AnimationProperties::fromFlags(AnimationFlags::loopBlend | AnimationFlags::propagate));
}
_animDirty = false;
@ -255,20 +243,12 @@ void Creature::playAnimation(const string &name, AnimationProperties properties,
bool fireForget = !(properties.flags & AnimationFlags::loop);
doPlayAnimation(fireForget, [&]() {
shared_ptr<ModelSceneNode> model(getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
if (!model) return;
_animAction = actionToComplete;
// Extract "propagate to head model" flag
bool propagateHead = properties.flags & AnimationFlags::propagateHead;
properties.flags &= ~AnimationFlags::propagateHead;
model->animator().playAnimation(name, properties);
if (propagateHead && _headModel) {
_headModel->animator().playAnimation(name, move(properties));
}
model->playAnimation(name, properties);
});
}
@ -286,18 +266,10 @@ void Creature::playAnimation(const shared_ptr<Animation> &anim, AnimationPropert
bool fireForget = !(properties.flags & AnimationFlags::loop);
doPlayAnimation(fireForget, [&]() {
shared_ptr<ModelSceneNode> model(getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
if (!model) return;
// Extract propagate to head model flag
bool propagateHead = properties.flags & AnimationFlags::propagateHead;
properties.flags &= ~AnimationFlags::propagateHead;
model->animator().playAnimation(anim, properties);
if (propagateHead && _headModel) {
_headModel->animator().playAnimation(anim, move(properties));
}
model->playAnimation(anim, nullptr, properties);
});
}
@ -332,13 +304,12 @@ bool Creature::equip(int slot, const shared_ptr<Item> &item) {
if (_sceneNode) {
updateModel();
shared_ptr<ModelSceneNode> model(getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
if (model) {
if (slot == InventorySlot::rightWeapon) {
shared_ptr<ModelSceneNode> weapon(model->getAttachedModel("rhand"));
auto weapon = static_pointer_cast<ModelSceneNode>(model->getAttachment("rhand"));
if (weapon && weapon->model()->classification() == Model::Classification::Lightsaber) {
weapon->animator().setDefaultAnimation("powered", AnimationProperties::fromFlags(AnimationFlags::loop));
weapon->animator().playAnimation("powerup");
weapon->playAnimation("powerup");
}
}
}
@ -415,18 +386,15 @@ void Creature::clearPath() {
}
glm::vec3 Creature::getSelectablePosition() const {
shared_ptr<ModelSceneNode> model(getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
if (!model) return _position;
if (_dead) return model->getWorldCenterOfAABB();
if (_dead) return model->getWorldCenterAABB();
shared_ptr<ModelNode> talkDummy(model->model()->getNodeByName(g_talkDummyNode));
glm::vec3 position;
if (model->getNodeAbsolutePosition(g_talkDummyNode, position)) {
return model->absoluteTransform() * glm::vec4(position, 1.0f);
}
return model->getWorldCenterAABB();
return talkDummy ?
(_transform * talkDummy->absoluteTransform())[3] :
model->getWorldCenterOfAABB();
}
float Creature::getAttackRange() const {

View file

@ -22,11 +22,12 @@
#include <set>
#include "../../audio/stream.h"
#include "../../graphics/lip/lipanimation.h"
#include "../../graphics/lip/animation.h"
#include "../../resource/format/2dareader.h"
#include "../../resource/format/gffreader.h"
#include "../../resource/types.h"
#include "../../scene/animation/eventlistener.h"
#include "../../scene/animeventlistener.h"
#include "../../scene/node/modelnode.h"
#include "../../script/types.h"
#include "../d20/attributes.h"
@ -226,7 +227,6 @@ private:
Gender _gender { Gender::Male };
int _appearance { 0 };
ModelType _modelType { ModelType::Creature };
std::shared_ptr<scene::ModelSceneNode> _headModel;
std::shared_ptr<graphics::Texture> _portrait;
std::map<int, std::shared_ptr<Item>> _equipment;
std::shared_ptr<Path> _path;

View file

@ -42,15 +42,17 @@ namespace game {
static const string g_headHookNode("headhook");
static const string g_maskHookNode("gogglehook");
static const string g_rightHandNode("rhand");
static const string g_leftHandNode("lhand");
shared_ptr<ModelSceneNode> Creature::buildModel() {
string modelName(getBodyModelName());
if (modelName.empty()) return nullptr;
string bodyModelName(getBodyModelName());
if (bodyModelName.empty()) return nullptr;
shared_ptr<Model> model(Models::instance().get(modelName));
if (!model) return nullptr;
shared_ptr<Model> bodyModel(Models::instance().get(bodyModelName));
if (!bodyModel) return nullptr;
auto modelSceneNode = make_unique<ModelSceneNode>(ModelUsage::Creature, model, _sceneGraph, set<string>(), this);
auto bodySceneNode = make_unique<ModelSceneNode>(bodyModel, ModelUsage::Creature, _sceneGraph, this);
// Body texture
@ -58,7 +60,7 @@ shared_ptr<ModelSceneNode> Creature::buildModel() {
if (!bodyTextureName.empty()) {
shared_ptr<Texture> texture(Textures::instance().get(bodyTextureName, TextureUsage::Diffuse));
if (texture) {
modelSceneNode->setDiffuseTexture(texture);
bodySceneNode->setDiffuseTexture(texture);
}
}
@ -76,34 +78,42 @@ shared_ptr<ModelSceneNode> Creature::buildModel() {
if (!headModelName.empty()) {
shared_ptr<Model> headModel(Models::instance().get(headModelName));
if (headModel) {
shared_ptr<ModelSceneNode> headSceneNode(modelSceneNode->attach(g_headHookNode, headModel, ModelUsage::Creature));
if (headSceneNode && maskModel) {
headSceneNode->attach(g_maskHookNode, maskModel, ModelUsage::Equipment);
shared_ptr<ModelNode> headHook(bodyModel->getNodeByName(g_headHookNode));
if (headHook) {
auto headSceneNode = make_shared<ModelSceneNode>(headModel, ModelUsage::Creature, _sceneGraph, this);
headSceneNode->setInanimateNodes(bodyModel->getAncestorNodes(headHook->id()));
bodySceneNode->attach(headHook->id(), headSceneNode);
if (maskModel) {
auto maskSceneNode = make_shared<ModelSceneNode>(maskModel, ModelUsage::Equipment, _sceneGraph, this);
headSceneNode->attach(g_maskHookNode, maskSceneNode);
}
}
}
}
// Left weapon
string leftWeaponModelName(getWeaponModelName(InventorySlot::leftWeapon));
if (!leftWeaponModelName.empty()) {
shared_ptr<Model> leftWeaponModel(Models::instance().get(leftWeaponModelName));
if (leftWeaponModel) {
modelSceneNode->attach("lhand", leftWeaponModel, ModelUsage::Equipment);
}
}
// Right weapon
string rightWeaponModelName(getWeaponModelName(InventorySlot::rightWeapon));
if (!rightWeaponModelName.empty()) {
shared_ptr<Model> rightWeaponModel(Models::instance().get(rightWeaponModelName));
if (rightWeaponModel) {
modelSceneNode->attach("rhand", rightWeaponModel, ModelUsage::Equipment);
shared_ptr<Model> weaponModel(Models::instance().get(rightWeaponModelName));
if (weaponModel) {
auto weaponSceneNode = make_shared<ModelSceneNode>(weaponModel, ModelUsage::Equipment, _sceneGraph, this);
bodySceneNode->attach(g_rightHandNode, move(weaponSceneNode));
}
}
return move(modelSceneNode);
// Left weapon
string leftWeaponModelName(getWeaponModelName(InventorySlot::leftWeapon));
if (!leftWeaponModelName.empty()) {
shared_ptr<Model> weaponModel(Models::instance().get(leftWeaponModelName));
if (weaponModel) {
auto weaponSceneNode = make_shared<ModelSceneNode>(weaponModel, ModelUsage::Equipment, _sceneGraph, this);
bodySceneNode->attach(g_leftHandNode, move(weaponSceneNode));
}
}
return move(bodySceneNode);
}
string Creature::getBodyModelName() const {

View file

@ -68,7 +68,7 @@ void Door::loadFromBlueprint(const string &resRef) {
shared_ptr<TwoDA> doors(Resources::instance().get2DA("genericdoors"));
string modelName(boost::to_lower_copy(doors->getString(_genericType, "modelname")));
auto model = make_unique<ModelSceneNode>(ModelUsage::Door, Models::instance().get(modelName), _sceneGraph);
auto model = make_unique<ModelSceneNode>(Models::instance().get(modelName), ModelUsage::Door, _sceneGraph);
model->setCullable(true);
model->setDrawDistance(FLT_MAX);
_sceneNode = move(model);
@ -93,19 +93,19 @@ bool Door::isSelectable() const {
}
void Door::open(const shared_ptr<Object> &triggerrer) {
shared_ptr<ModelSceneNode> model(getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
if (model) {
model->animator().setDefaultAnimation("opened1", AnimationProperties::fromFlags(AnimationFlags::loop));
model->animator().playAnimation("opening1");
//model->setDefaultAnimation("opened1", AnimationProperties::fromFlags(AnimationFlags::loop));
model->playAnimation("opening1");
}
_open = true;
}
void Door::close(const shared_ptr<Object> &triggerrer) {
shared_ptr<ModelSceneNode> model(getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
if (model) {
model->animator().setDefaultAnimation("closed", AnimationProperties::fromFlags(AnimationFlags::loop));
model->animator().playAnimation("closing1");
//model->setDefaultAnimation("closed", AnimationProperties::fromFlags(AnimationFlags::loop));
model->playAnimation("closing1");
}
_open = false;
}

View file

@ -62,7 +62,7 @@ void Placeable::loadFromBlueprint(const string &resRef) {
shared_ptr<TwoDA> placeables(Resources::instance().get2DA("placeables"));
string modelName(boost::to_lower_copy(placeables->getString(_appearance, "modelname")));
auto model = make_shared<ModelSceneNode>(ModelUsage::Placeable, Models::instance().get(modelName), _sceneGraph);
auto model = make_shared<ModelSceneNode>(Models::instance().get(modelName), ModelUsage::Placeable, _sceneGraph);
model->setCullable(true);
model->setDrawDistance(64.0f);
_sceneNode = move(model);

View file

@ -118,10 +118,9 @@ float SpatialObject::getDistanceTo2(const SpatialObject &other) const {
}
bool SpatialObject::contains(const glm::vec3 &point) const {
auto model = getModelSceneNode();
if (!model) return false;
if (!_sceneNode) return false;
const AABB &aabb = model->model()->aabb();
const AABB &aabb = _sceneNode->aabb();
return (aabb * _transform).contains(point);
}
@ -133,13 +132,15 @@ void SpatialObject::face(const SpatialObject &other) {
}
void SpatialObject::face(const glm::vec3 &point) {
if (point == _position) return;
glm::vec2 dir(glm::normalize(point - _position));
_orientation = glm::quat(glm::vec3(0.0f, 0.0f, -glm::atan(dir.x, dir.y)));
updateTransform();
}
void SpatialObject::faceAwayFrom(const SpatialObject &other) {
if (_id == other._id) return;
if (_id == other._id || _position == other.position()) return;
glm::vec2 dir(glm::normalize(_position - other.position()));
_orientation = glm::quat(glm::vec3(0.0f, 0.0f, -glm::atan(dir.x, dir.y)));
@ -214,19 +215,13 @@ bool SpatialObject::isSelectable() const {
return false;
}
shared_ptr<ModelSceneNode> SpatialObject::getModelSceneNode() const {
if (!_sceneNode || _sceneNode->type() != SceneNodeType::Model) return nullptr;
return static_pointer_cast<ModelSceneNode>(_sceneNode);
}
shared_ptr<Walkmesh> SpatialObject::getWalkmesh() const {
return nullptr;
}
glm::vec3 SpatialObject::getSelectablePosition() const {
auto model = getModelSceneNode();
return model ? model->getWorldCenterAABB() : _position;
auto model = static_pointer_cast<ModelSceneNode>(_sceneNode);
return model ? model->getWorldCenterOfAABB() : _position;
}
void SpatialObject::setRoom(Room *room) {

View file

@ -23,11 +23,13 @@
#include "object.h"
#include "glm/gtc/quaternion.hpp"
#include "glm/mat4x4.hpp"
#include "glm/vec3.hpp"
#include "../../graphics/walkmesh/walkmesh.h"
#include "../../scene/node/modelnode.h"
#include "../../scene/animproperties.h"
#include "../../scene/node/scenenode.h"
#include "../action/playanimation.h"
#include "../enginetype/effect.h"
@ -66,7 +68,6 @@ public:
virtual glm::vec3 getSelectablePosition() const;
float getFacing() const { return glm::eulerAngles(_orientation).z; }
std::shared_ptr<scene::ModelSceneNode> getModelSceneNode() const;
virtual std::shared_ptr<graphics::Walkmesh> getWalkmesh() const;
ObjectFactory &objectFactory() { return *_objectFactory; }

View file

@ -90,7 +90,7 @@ vector<shared_ptr<SpatialObject>> ObjectSelector::getSelectableObjects() const {
for (auto &object : _area->objects()) {
if (!object->isSelectable() || object.get() == partyLeader.get()) continue;
shared_ptr<ModelSceneNode> model(object->getModelSceneNode());
auto model = static_pointer_cast<ModelSceneNode>(object->sceneNode());
if (!model || !model->isVisible()) continue;
float dist2 = object->getDistanceTo2(origin);

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "lipanimation.h"
#include "animation.h"
#include <stdexcept>

View file

@ -19,7 +19,7 @@
#include "../../resource/format/binreader.h"
#include "lipanimation.h"
#include "animation.h"
namespace reone {

View file

@ -21,7 +21,7 @@
#include "../../common/cache.h"
#include "lipanimation.h"
#include "animation.h"
namespace reone {

View file

@ -21,7 +21,7 @@
#include <boost/filesystem/path.hpp>
#include "lipanimation.h"
#include "animation.h"
namespace reone {

View file

@ -17,7 +17,7 @@
#include "animation.h"
#include <queue>
#include <stack>
#include "../../common/collectionutil.h"
@ -32,7 +32,7 @@ Animation::Animation(
float length,
float transitionTime,
shared_ptr<ModelNode> rootNode,
vector<Event> &&events
vector<Event> events
) :
_name(move(name)),
_length(length),
@ -44,13 +44,14 @@ Animation::Animation(
}
void Animation::fillNodeByName() {
queue<shared_ptr<ModelNode>> nodes;
stack<shared_ptr<ModelNode>> nodes;
nodes.push(_rootNode);
while (!nodes.empty()) {
shared_ptr<ModelNode> node(nodes.front());
shared_ptr<ModelNode> node(nodes.top());
nodes.pop();
_nodeById.insert(make_pair(node->id(), node));
_nodeByName.insert(make_pair(node->name(), node));
for (auto &child : node->children()) {
@ -59,6 +60,10 @@ void Animation::fillNodeByName() {
}
}
shared_ptr<ModelNode> Animation::getNodeById(uint16_t nodeId) const {
return getFromLookupOrNull(_nodeById, nodeId);
}
shared_ptr<ModelNode> Animation::getNodeByName(const string &name) const {
return getFromLookupOrNull(_nodeByName, name);
}

View file

@ -27,8 +27,6 @@ namespace reone {
namespace graphics {
class MdlReader;
class Animation : boost::noncopyable {
public:
struct Event {
@ -41,8 +39,9 @@ public:
float length,
float transitionTime,
std::shared_ptr<ModelNode> rootNode,
std::vector<Event> &&events);
std::vector<Event> events);
std::shared_ptr<ModelNode> getNodeById(uint16_t nodeId) const;
std::shared_ptr<ModelNode> getNodeByName(const std::string &name) const;
const std::string &name() const { return _name; }
@ -58,6 +57,7 @@ private:
std::shared_ptr<ModelNode> _rootNode;
std::vector<Event> _events;
std::unordered_map<uint16_t, std::shared_ptr<ModelNode>> _nodeById;
std::unordered_map<std::string, std::shared_ptr<ModelNode>> _nodeByName;
void fillNodeByName();

View file

@ -40,7 +40,7 @@ static constexpr int kMdlDataOffset = 12;
static constexpr uint32_t kFunctionPtrTslPC = 4285200;
static constexpr uint32_t kFunctionPtrTslXbox = 4285872;
// Classificaiton
// Classification
static unordered_map<uint8_t, Model::Classification> g_classifications {
{ 0, Model::Classification::Other },
@ -712,7 +712,12 @@ unique_ptr<Animation> MdlReader::readAnimation(uint32_t offset) {
sort(events.begin(), events.end(), [](auto &left, auto &right) { return left.time < right.time; });
}
return make_unique<Animation>(move(name), length, transitionTime, move(rootNode), move(events));
return make_unique<Animation>(
move(name),
length,
transitionTime,
move(rootNode),
move(events));
}
} // namespace graphics

View file

@ -100,6 +100,33 @@ void Model::addAnimation(shared_ptr<Animation> animation) {
_animations.insert(make_pair(animation->name(), move(animation)));
}
shared_ptr<ModelNode> Model::getNodeById(uint16_t nodeId) const {
return getFromLookupOrNull(_nodeById, nodeId);
}
shared_ptr<ModelNode> Model::getNodeByName(const string &name) const {
return getFromLookupOrNull(_nodeByName, name);
}
shared_ptr<ModelNode> Model::getAABBNode() const {
for (auto &node : _nodeById) {
if (node.second->isAABBMesh()) return node.second;
}
return nullptr;
}
shared_ptr<Animation> Model::getAnimation(const string &name) const {
auto maybeAnim = _animations.find(name);
if (maybeAnim != _animations.end()) return maybeAnim->second;
shared_ptr<Animation> anim;
if (_superModel) {
anim = _superModel->getAnimation(name);
}
return move(anim);
}
vector<string> Model::getAnimationNames() const {
vector<string> result;
@ -116,30 +143,17 @@ vector<string> Model::getAnimationNames() const {
return move(result);
}
shared_ptr<Animation> Model::getAnimation(const string &name) const {
auto maybeAnim = _animations.find(name);
if (maybeAnim != _animations.end()) return maybeAnim->second;
set<uint16_t> Model::getAncestorNodes(uint16_t parentId) const {
set<uint16_t> result;
shared_ptr<Animation> anim;
if (_superModel) {
anim = _superModel->getAnimation(name);
}
if (!anim) {
debug(boost::format("Model animation not found: '%s' '%s'") % name % _name, 2);
auto maybeParent = _nodeById.find(parentId);
if (maybeParent != _nodeById.end()) {
for (const ModelNode *node = maybeParent->second->parent(); node; node = node->parent()) {
result.insert(node->id());
}
}
return move(anim);
}
shared_ptr<ModelNode> Model::getNodeByName(const string &name) const {
return getFromLookupOrNull(_nodeByName, name);
}
shared_ptr<ModelNode> Model::getAABBNode() const {
for (auto &node : _nodeById) {
if (node.second->isAABBMesh()) return node.second;
}
return nullptr;
return move(result);
}
} // namespace graphics

View file

@ -17,6 +17,7 @@
#pragma once
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
@ -63,10 +64,12 @@ public:
bool isAffectedByFog() const { return _affectedByFog; }
std::vector<std::string> getAnimationNames() const;
std::shared_ptr<Animation> getAnimation(const std::string &name) const;
std::shared_ptr<ModelNode> getNodeById(uint16_t nodeId) const;
std::shared_ptr<ModelNode> getNodeByName(const std::string &name) const;
std::shared_ptr<ModelNode> getAABBNode() const;
std::shared_ptr<Animation> getAnimation(const std::string &name) const;
std::vector<std::string> getAnimationNames() const;
std::set<uint16_t> getAncestorNodes(uint16_t parentId) const;
const std::string &name() const { return _name; }
Classification classification() const { return _classification; }

View file

@ -155,36 +155,14 @@ public:
Lighten
};
template <class T>
struct Constraints {
T start;
T mid;
T end;
};
UpdateMode updateMode { UpdateMode::Invalid };
RenderMode renderMode { RenderMode::Invalid };
BlendMode blendMode { BlendMode::Invalid };
int renderOrder { 0 };
std::shared_ptr<Texture> texture;
int gridWidth { 0 };
int gridHeight { 0 };
int frameStart { 0 };
int frameEnd { 0 };
glm::vec2 size { 0.0f };
float birthrate { 0.0f }; /**< rate of particle birth per second */
float lifeExpectancy { 0.0f }; /**< life of each particle in seconds */
float velocity { 0.0f };
float randomVelocity { 0.0f };
float spread { 0.0f };
int renderOrder { 0 };
bool loop { false };
float fps { 0.0f };
Constraints<float> particleSize;
Constraints<glm::vec3> color;
Constraints<float> alpha;
};
struct Reference {
@ -225,6 +203,10 @@ public:
// Specialization
bool isMesh() const { return static_cast<bool>(_mesh); }
bool isLight() const { return static_cast<bool>(_light); }
bool isEmitter() const { return static_cast<bool>(_emitter); }
bool isReference() const { return static_cast<bool>(_reference); }
bool isSkinMesh() const { return _mesh && _mesh->skin; }
bool isDanglyMesh() const { return _mesh && _mesh->danglyMesh; }
bool isAABBMesh() const { return _mesh && _mesh->aabbTree; }

View file

@ -45,10 +45,6 @@ uint32_t getInternalPixelFormatGL(PixelFormat format) {
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
case PixelFormat::Depth:
return GL_DEPTH_COMPONENT;
case PixelFormat::RG16F:
return GL_RG16F;
case PixelFormat::RGB16F:
return GL_RGB16F;
default:
throw logic_error("Unsupported pixel format: " + to_string(static_cast<int>(format)));
}

View file

@ -181,8 +181,6 @@ void Texture::fillTarget(uint32_t target, int level, int width, int height, cons
case PixelFormat::BGR:
case PixelFormat::BGRA:
case PixelFormat::Depth:
case PixelFormat::RG16F:
case PixelFormat::RGB16F:
glTexImage2D(target, level, getInternalPixelFormatGL(_pixelFormat), width, height, 0, getPixelFormatGL(), getPixelTypeGL(), pixels);
break;
default:
@ -195,7 +193,6 @@ uint32_t Texture::getPixelFormatGL() const {
case PixelFormat::Grayscale:
return GL_RED;
case PixelFormat::RGB:
case PixelFormat::RGB16F:
return GL_RGB;
case PixelFormat::RGBA:
case PixelFormat::DXT1:
@ -207,8 +204,6 @@ uint32_t Texture::getPixelFormatGL() const {
return GL_BGRA;
case PixelFormat::Depth:
return GL_DEPTH_COMPONENT;
case PixelFormat::RG16F:
return GL_RG;
default:
throw logic_error("Unsupported pixel format: " + to_string(static_cast<int>(_pixelFormat)));
}
@ -223,8 +218,6 @@ uint32_t Texture::getPixelTypeGL() const {
case PixelFormat::BGRA:
return GL_UNSIGNED_BYTE;
case PixelFormat::Depth:
case PixelFormat::RG16F:
case PixelFormat::RGB16F:
return GL_FLOAT;
default:
throw logic_error("Unsupported pixel format: " + to_string(static_cast<int>(_pixelFormat)));

View file

@ -24,6 +24,10 @@ namespace reone {
namespace graphics {
constexpr int kNumCubeFaces = 6;
constexpr float kDefaultClipPlaneNear = 0.1f;
constexpr float kDefaultClipPlaneFar = 10000.0f;
constexpr int kMaxBones = 128;
constexpr int kMaxLights = 8;
constexpr int kMaxParticles = 32;
@ -39,10 +43,7 @@ enum class PixelFormat {
BGRA,
DXT1,
DXT5,
Depth,
RG16F,
RGB16F
Depth
};
/**

View file

@ -51,13 +51,16 @@ unique_ptr<SceneGraph> SceneBuilder::build() {
_modelScale + _modelOffset.y,
_zNear, _zFar));
auto camera = make_shared<CameraSceneNode>(scene.get(), projection, _aspect, _zNear, _zFar);
auto camera = make_shared<CameraSceneNode>("", projection, scene.get());
if (_cameraNodeName.empty()) {
camera->setLocalTransform(_cameraTransform);
} else {
MeshSceneNode *modelNode = model->getModelNode(_cameraNodeName);
if (modelNode) {
camera->setLocalTransform(modelNode->absoluteTransform() * _cameraTransform);
shared_ptr<ModelNode> refModelNode(model->model()->getNodeByName(_cameraNodeName));
if (refModelNode) {
shared_ptr<ModelNodeSceneNode> refSceneNode(model->getNodeById(refModelNode->id()));
if (refSceneNode) {
camera->setLocalTransform(refModelNode->absoluteTransform() * _cameraTransform);
}
}
}

View file

@ -1,238 +0,0 @@
/*
* 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 "channel.h"
#include <stdexcept>
#include "glm/common.hpp"
#include "../node/meshnode.h"
#include "../node/modelnode.h"
#include "../types.h"
using namespace std;
using namespace reone::graphics;
namespace reone {
namespace scene {
AnimationChannel::AnimationChannel(ModelSceneNode *sceneNode, set<string> ignoreNodes) :
_sceneNode(sceneNode),
_ignoreNodes(move(ignoreNodes)) {
if (!sceneNode) {
throw invalid_argument("sceneNode must not be null");
}
}
void AnimationChannel::reset() {
_animation.reset();
_lipAnimation.reset();
_time = 0.0f;
_freeze = false;
_finished = false;
}
void AnimationChannel::reset(shared_ptr<Animation> anim, AnimationProperties properties, shared_ptr<LipAnimation> lipAnim) {
if (!anim) {
throw invalid_argument("anim must not be null");
}
_animation = move(anim);
_properties = move(properties);
_lipAnimation = move(lipAnim);
_time = 0.0f;
_freeze = false;
_finished = false;
}
void AnimationChannel::update(float dt, bool visible) {
if (!_animation || _freeze || _finished) return;
float newTime, length;
if (_properties.flags & AnimationFlags::syncLipAnim) {
length = _lipAnimation->length();
newTime = glm::min(_time + dt, length);
} else {
length = _animation->length();
newTime = glm::min(_time + _properties.speed * dt, length);
// Signal animation events between the previous time and the current time
for (auto &event : _animation->events()) {
if (event.time > _time && event.time <= newTime) {
_sceneNode->signalEvent(event.name);
}
}
}
if (visible) {
_stateById.clear();
computeSceneNodeStates(*_animation->rootNode());
}
_time = newTime;
if (_time == length) {
bool loop = _properties.flags & AnimationFlags::loop;
bool syncLipAnim = _properties.flags & AnimationFlags::syncLipAnim;
if (loop && !syncLipAnim) {
_time = 0.0f;
} else {
_finished = true;
}
}
}
void AnimationChannel::computeSceneNodeStates(const ModelNode &animNode) {
if (_ignoreNodes.count(animNode.name()) == 0) {
MeshSceneNode *modelNodeSceneNode = _sceneNode->getModelNode(animNode.name());
if (modelNodeSceneNode) {
const ModelNode *modelNode = modelNodeSceneNode->modelNode();
bool transformChanged = false;
float scale = 1.0f;
glm::vec3 position(modelNode->restPosition());
glm::quat orientation(modelNode->restOrientation());
if (_properties.flags & AnimationFlags::syncLipAnim) {
uint8_t leftFrameIdx, rightFrameIdx;
float factor;
if (_lipAnimation->getKeyframes(_time, leftFrameIdx, rightFrameIdx, factor)) {
float animScale;
if (animNode.getScale(leftFrameIdx, rightFrameIdx, factor, animScale)) {
scale = animScale;
transformChanged = true;
}
glm::vec3 animPosiiton;
if (animNode.getPosition(leftFrameIdx, rightFrameIdx, factor, animPosiiton)) {
position += _properties.scale * animPosiiton;
transformChanged = true;
}
glm::quat animOrientation;
if (animNode.getOrientation(leftFrameIdx, rightFrameIdx, factor, animOrientation)) {
orientation = move(animOrientation);
transformChanged = true;
}
}
} else {
float animScale;
if (animNode.scale().getByTime(_time, animScale)) {
scale = animScale;
transformChanged = true;
}
glm::vec3 animPosition;
if (animNode.position().getByTime(_time, animPosition)) {
position += _properties.scale * animPosition;
transformChanged = true;
}
glm::quat animOrientation;
if (animNode.orientation().getByTime(_time, animOrientation)) {
orientation = move(animOrientation);
transformChanged = true;
}
}
SceneNodeState state;
float alpha;
if (animNode.alpha().getByTime(_time, alpha)) {
state.flags |= SceneNodeStateFlags::alpha;
state.alpha = alpha;
}
glm::vec3 selfIllumColor;
if (animNode.selfIllumColor().getByTime(_time, selfIllumColor)) {
state.flags |= SceneNodeStateFlags::selfIllum;
state.selfIllumColor = move(selfIllumColor);
}
glm::vec3 lightColor;
if (animNode.color().getByTime(_time, lightColor)) {
state.flags |= SceneNodeStateFlags::lightColor;
state.lightColor = move(lightColor);
}
float lightMultiplier;
if (animNode.multiplier().getByTime(_time, lightMultiplier)) {
state.flags |= SceneNodeStateFlags::lightMultiplier;
state.lightMultiplier = lightMultiplier;
}
float lightRadius;
if (animNode.radius().getByTime(_time, lightRadius)) {
state.flags |= SceneNodeStateFlags::lightRadius;
state.lightRadius = lightRadius;
}
if (transformChanged) {
glm::mat4 transform(1.0f);
transform = glm::scale(transform, glm::vec3(scale));
transform = glm::translate(transform, position);
transform *= glm::mat4_cast(orientation);
state.flags |= SceneNodeStateFlags::transform;
state.transform = move(transform);
}
_stateById.insert(make_pair(modelNode->id(), move(state)));
}
}
for (auto &child : animNode.children()) {
computeSceneNodeStates(*child);
}
}
void AnimationChannel::freeze() {
_freeze = true;
}
bool AnimationChannel::isSameAnimation(const Animation &anim, const AnimationProperties &properties, shared_ptr<LipAnimation> lipAnim) const {
return _animation.get() == &anim && _properties == properties && _lipAnimation == lipAnim;
}
bool AnimationChannel::isActive() const {
return _animation && !_finished;
}
bool AnimationChannel::isPastTransitionTime() const {
return _animation && _time > _animation->transitionTime();
}
bool AnimationChannel::isFinished() const {
return _animation && _finished;
}
float AnimationChannel::getTransitionTime() const {
return _animation ? _animation->transitionTime() : 0.0f;
}
bool AnimationChannel::getSceneNodeStateById(uint16_t nodeId, SceneNodeState &state) const {
auto maybeState = _stateById.find(nodeId);
if (maybeState != _stateById.end()) {
state = maybeState->second;
return true;
}
return false;
}
string AnimationChannel::getAnimationName() const {
return _animation ? _animation->name() : "";
}
void AnimationChannel::setTime(float time) {
_time = time;
}
} // namespace scene
} // namespace reone

View file

@ -1,107 +0,0 @@
/*
* 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 <cstdint>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include "../../graphics/lip/lipanimation.h"
#include "../../graphics/model/animation.h"
#include "../../graphics/model/model.h"
#include "../../graphics/model/modelnode.h"
#include "properties.h"
#include "scenenodestate.h"
namespace reone {
namespace scene {
class ModelSceneNode;
/**
* Represents a single animation being played on a model. Multiple animation
* channels can be blended or overlayed by SceneNodeAnimator.
*
* @see SceneNodeAnimator
*/
class AnimationChannel {
public:
/**
* @param sceneNode scene node to apply animations to
* @param skipNodes list of model node names to ignore
*/
AnimationChannel(ModelSceneNode *sceneNode, std::set<std::string> ignoreNodes);
void reset();
void reset(std::shared_ptr<graphics::Animation> anim, AnimationProperties properties, std::shared_ptr<graphics::LipAnimation> lipAnim = nullptr);
/**
* @param dt frame delta time
* @param visible whether the animated scene node is visible
*/
void update(float dt, bool visible = true);
void freeze();
bool isSameAnimation(const graphics::Animation &anim, const AnimationProperties &properties, std::shared_ptr<graphics::LipAnimation> lipAnim = nullptr) const;
/**
* @return true if this animation channel contains an animation that is not finished, false otherwise
*/
bool isActive() const;
/**
* @return true if this animation channel contains that is past transition time, false otherwise
*/
bool isPastTransitionTime() const;
/**
* @return true if this animation channel contains an animation that is finished, false otherwise
*/
bool isFinished() const;
float getTransitionTime() const;
bool getSceneNodeStateById(uint16_t nodeId, SceneNodeState &state) const;
std::string getAnimationName() const;
float time() const { return _time; }
void setTime(float time);
private:
ModelSceneNode *_sceneNode;
std::set<std::string> _ignoreNodes;
std::shared_ptr<graphics::Animation> _animation;
AnimationProperties _properties;
std::shared_ptr<graphics::LipAnimation> _lipAnimation;
float _time { 0.0f };
bool _freeze { false };
bool _finished { false };
std::unordered_map<uint16_t, SceneNodeState> _stateById;
void computeSceneNodeStates(const graphics::ModelNode &animNode);
};
} // namespace scene
} // namespace reone

View file

@ -1,328 +0,0 @@
/*
* 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 "scenenodeanimator.h"
#include <stdexcept>
#include "../node/lightnode.h"
#include "../node/meshnode.h"
#include "../node/modelnode.h"
#include "../types.h"
using namespace std;
using namespace reone::graphics;
namespace reone {
namespace scene {
static constexpr float kTransitionDuration = 0.25f;
SceneNodeAnimator::SceneNodeAnimator(ModelSceneNode *sceneNode, set<string> ignoreNodes) :
_sceneNode(sceneNode),
_ignoreNodes(ignoreNodes) {
if (!sceneNode) {
throw invalid_argument("sceneNode must not be null");
}
for (int i = 0; i < kChannelCount; ++i) {
_channels.push_back(AnimationChannel(sceneNode, ignoreNodes));
}
}
void SceneNodeAnimator::update(float dt, bool visible) {
// Regardless of the composition mode, when there is no active animation on
// the first channel, start the default animation
if (!_channels[0].isActive()) {
playDefaultAnimation();
return;
}
// In the Blend mode, if the animation on the first channel is past
// transition time, stop animation on the second channel
if (isInTransition() && _channels[0].isPastTransitionTime()) {
_channels[1].reset();
_transition = false;
}
// Update animation channels
for (auto &channel : _channels) {
channel.update(dt, visible);
}
if (visible) {
// Compute and apply node states to the managed model
_stateById.clear();
computeSceneNodeStates(*_sceneNode->model()->rootNode());
applySceneNodeStates(*_sceneNode->model()->rootNode());
}
}
void SceneNodeAnimator::playDefaultAnimation() {
if (!_defaultAnimName.empty()) {
playAnimation(_defaultAnimName, _defaultAnimProperties);
}
}
bool SceneNodeAnimator::isInTransition() const {
return _compositionMode == CompositionMode::Blend && _transition;
}
void SceneNodeAnimator::computeSceneNodeStates(ModelNode &modelNode, glm::mat4 parentTransform) {
if (modelNode.isSkinMesh()) return;
SceneNodeState state;
state.flags |= SceneNodeStateFlags::transform;
glm::mat4 localTransform(modelNode.localTransform());
if (isInTransition()) {
float delta = 1.0f - (_channels[0].getTransitionTime() - _channels[0].time()) / _channels[0].getTransitionTime();
// In the Blend mode, blend animations on the first two channels (only transforms)
SceneNodeState channel0State, channel1State;
bool hasChannel0State = _channels[0].getSceneNodeStateById(modelNode.id(), channel0State);
bool hasChannel1State = _channels[1].getSceneNodeStateById(modelNode.id(), channel1State);
if (hasChannel0State && (channel0State.flags & SceneNodeStateFlags::transform) &&
hasChannel1State && (channel1State.flags & SceneNodeStateFlags::transform)) {
glm::quat orientation0(glm::toQuat(channel0State.transform));
glm::quat orientation1(glm::toQuat(channel1State.transform));
localTransform = glm::translate(glm::mat4(1.0f), glm::vec3(channel0State.transform[3]));
localTransform *= glm::mat4_cast(glm::slerp(orientation1, orientation0, delta));
} else if (hasChannel0State && (channel0State.flags & SceneNodeStateFlags::transform)) {
localTransform = move(channel0State.transform);
} else if (hasChannel1State && (channel1State.flags & SceneNodeStateFlags::transform)) {
localTransform = move(channel1State.transform);
}
if (hasChannel0State) {
if (channel0State.flags & SceneNodeStateFlags::alpha) {
state.flags |= SceneNodeStateFlags::alpha;
state.alpha = channel0State.alpha;
}
if (channel0State.flags & SceneNodeStateFlags::selfIllum) {
state.flags |= SceneNodeStateFlags::selfIllum;
state.selfIllumColor = channel0State.selfIllumColor;
}
if (channel0State.flags & SceneNodeStateFlags::lightColor) {
state.flags |= SceneNodeStateFlags::lightColor;
state.lightColor = channel0State.lightColor;
}
if (channel0State.flags & SceneNodeStateFlags::lightMultiplier) {
state.flags |= SceneNodeStateFlags::lightMultiplier;
state.lightMultiplier = channel0State.lightMultiplier;
}
if (channel0State.flags & SceneNodeStateFlags::lightRadius) {
state.flags |= SceneNodeStateFlags::lightRadius;
state.lightRadius = channel0State.lightRadius;
}
}
} else if (_compositionMode == CompositionMode::Overlay) {
// In the Overlay mode, for each state component select the first animation channel to have state for the given node
int componentsLeft = SceneNodeStateFlags::all;
for (int i = kChannelCount - 1; i >= 0; --i) {
SceneNodeState channelState;
if (_channels[i].isActive() && _channels[i].getSceneNodeStateById(modelNode.id(), channelState)) {
if ((channelState.flags & SceneNodeStateFlags::transform) && (componentsLeft & SceneNodeStateFlags::transform)) {
localTransform = move(channelState.transform);
componentsLeft &= ~SceneNodeStateFlags::transform;
}
if ((channelState.flags & SceneNodeStateFlags::alpha) && (componentsLeft & SceneNodeStateFlags::alpha)) {
state.flags |= SceneNodeStateFlags::alpha;
state.alpha = channelState.alpha;
componentsLeft &= ~SceneNodeStateFlags::alpha;
}
if ((channelState.flags & SceneNodeStateFlags::selfIllum) && (componentsLeft & SceneNodeStateFlags::selfIllum)) {
state.flags |= SceneNodeStateFlags::selfIllum;
state.selfIllumColor = move(channelState.selfIllumColor);
componentsLeft &= ~SceneNodeStateFlags::selfIllum;
}
if ((channelState.flags & SceneNodeStateFlags::lightColor) && (componentsLeft & SceneNodeStateFlags::lightColor)) {
state.flags |= SceneNodeStateFlags::lightColor;
state.lightColor = move(channelState.lightColor);
componentsLeft &= ~SceneNodeStateFlags::lightColor;
}
if ((channelState.flags & SceneNodeStateFlags::lightMultiplier) && (componentsLeft & SceneNodeStateFlags::lightMultiplier)) {
state.flags |= SceneNodeStateFlags::lightMultiplier;
state.lightMultiplier = channelState.lightMultiplier;
componentsLeft &= ~SceneNodeStateFlags::lightMultiplier;
}
if ((channelState.flags & SceneNodeStateFlags::lightRadius) && (componentsLeft & SceneNodeStateFlags::lightRadius)) {
state.flags |= SceneNodeStateFlags::lightRadius;
state.lightRadius = channelState.lightRadius;
componentsLeft &= ~SceneNodeStateFlags::lightRadius;
}
}
}
} else {
// Otherwise, select animation on the first channel
SceneNodeState channelState;
if (_channels[0].getSceneNodeStateById(modelNode.id(), channelState)) {
if (channelState.flags & SceneNodeStateFlags::transform) {
localTransform = move(channelState.transform);
}
if (channelState.flags & SceneNodeStateFlags::alpha) {
state.flags |= SceneNodeStateFlags::alpha;
state.alpha = channelState.alpha;
}
if (channelState.flags & SceneNodeStateFlags::selfIllum) {
state.flags |= SceneNodeStateFlags::selfIllum;
state.selfIllumColor = move(channelState.selfIllumColor);
}
if (channelState.flags & SceneNodeStateFlags::lightColor) {
state.flags |= SceneNodeStateFlags::lightColor;
state.lightColor = move(channelState.lightColor);
}
if (channelState.flags & SceneNodeStateFlags::lightMultiplier) {
state.flags |= SceneNodeStateFlags::lightMultiplier;
state.lightMultiplier = channelState.lightMultiplier;
}
if (channelState.flags & SceneNodeStateFlags::lightRadius) {
state.flags |= SceneNodeStateFlags::lightRadius;
state.lightRadius = channelState.lightRadius;
}
}
}
glm::mat4 absTransform(parentTransform * localTransform);
state.transform = absTransform;
_stateById.insert(make_pair(modelNode.id(), move(state)));
for (auto &child : modelNode.children()) {
computeSceneNodeStates(*child, absTransform);
}
}
void SceneNodeAnimator::applySceneNodeStates(ModelNode &modelNode) {
// Do not apply transforms to skinned model nodes
if (modelNode.isSkinMesh()) return;
auto maybeState = _stateById.find(modelNode.id());
if (maybeState != _stateById.end()) {
const SceneNodeState &state = maybeState->second;
MeshSceneNode *sceneNode = _sceneNode->getModelNodeById(modelNode.id());
if (state.flags & SceneNodeStateFlags::transform) {
sceneNode->setLocalTransform(state.transform);
sceneNode->setBoneTransform(state.transform * modelNode.absoluteTransformInverse());
}
if (state.flags & SceneNodeStateFlags::alpha) {
sceneNode->setAlpha(state.alpha);
}
if (state.flags & SceneNodeStateFlags::selfIllum) {
sceneNode->setSelfIllumColor(state.selfIllumColor);
}
LightSceneNode *light = _sceneNode->getLightNodeById(modelNode.id());
if (light) {
if (state.flags & SceneNodeStateFlags::lightColor) {
light->setColor(state.lightColor);
}
if (state.flags & SceneNodeStateFlags::lightMultiplier) {
light->setMultiplier(state.lightMultiplier);
}
if (state.flags & SceneNodeStateFlags::lightRadius) {
light->setRadius(state.lightRadius);
}
}
}
for (auto &child : modelNode.children()) {
applySceneNodeStates(*child);
}
}
void SceneNodeAnimator::playAnimation(const string &name, AnimationProperties properties) {
shared_ptr<Model> model(_sceneNode->model());
shared_ptr<Animation> anim(model->getAnimation(name));
if (anim) {
playAnimation(move(anim), move(properties));
}
}
void SceneNodeAnimator::playAnimation(shared_ptr<Animation> anim, AnimationProperties properties, shared_ptr<LipAnimation> lipAnim) {
if (!anim) return;
_compositionMode = determineCompositionMode(properties.flags);
// Clear composition flags
properties.flags &= ~(AnimationFlags::blend | AnimationFlags::overlay);
// If scale is 0.0, replace it with models scale
if (properties.scale == 0.0f) {
properties.scale = _sceneNode->model()->animationScale();
}
switch (_compositionMode) {
case CompositionMode::Mono:
if (!_channels[0].isSameAnimation(*anim, properties, lipAnim)) {
// Play the specified animation on the first channel and stop animation on other channels
_channels[0].reset(anim, properties, lipAnim);
for (int i = 1; i < kChannelCount; ++i) {
_channels[i].reset();
}
}
break;
case CompositionMode::Blend:
if (!_channels[0].isSameAnimation(*anim, properties, lipAnim)) {
if (_channels[0].isActive()) {
// Play the specified animation on the first channel - previous animation is moved onto the second channel and is freezed
_channels[1] = _channels[0];
_channels[0].reset(anim, properties, lipAnim);
_channels[0].setTime(glm::max(0.0f, anim->transitionTime() - kTransitionDuration));
_channels[1].freeze();
_transition = true;
} else {
_channels[0].reset(anim, properties, lipAnim);
_transition = false;
}
}
break;
case CompositionMode::Overlay:
// Play the specified animation on the first vacant channel, if any
for (int i = 0; i < kChannelCount; ++i) {
if (!_channels[i].isActive()) {
_channels[i].reset(anim, properties, lipAnim);
break;
}
}
break;
default:
break;
}
}
SceneNodeAnimator::CompositionMode SceneNodeAnimator::determineCompositionMode(int flags) const {
return (flags & AnimationFlags::blend) ?
CompositionMode::Blend :
((flags & AnimationFlags::overlay) ? CompositionMode::Overlay : CompositionMode::Mono);
}
bool SceneNodeAnimator::isAnimationFinished() const {
return _channels[0].isFinished();
}
void SceneNodeAnimator::setDefaultAnimation(string name, AnimationProperties properties) {
_defaultAnimName = move(name);
_defaultAnimProperties = move(properties);
}
} // namespace scene
} // namespace reone

View file

@ -1,107 +0,0 @@
/*
* 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 <cstdint>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include "glm/mat4x4.hpp"
#include "../../graphics/model/animation.h"
#include "../../graphics/model/modelnode.h"
#include "channel.h"
#include "properties.h"
#include "scenenodestate.h"
namespace reone {
namespace scene {
class ModelSceneNode;
/**
* Responsible for applying animations to model scene nodes. Manages up to
* kChannelCount animation channels, which it either blends or overlays depending
* on the composition mode.
*/
class SceneNodeAnimator {
public:
/**
* @param sceneNode scene node to apply animations to
* @param skipNodes names of model nodes to ignore
*/
SceneNodeAnimator(ModelSceneNode *sceneNode, std::set<std::string> ignoreNodes);
/**
* @param dt frame delta time
* @param visible whether the animated scene node is visible
*/
void update(float dt, bool visible = true);
void playAnimation(const std::string &name, AnimationProperties properties = AnimationProperties());
void playAnimation(std::shared_ptr<graphics::Animation> anim, AnimationProperties properties = AnimationProperties(), std::shared_ptr<graphics::LipAnimation> lipAnim = nullptr);
void playDefaultAnimation();
bool isAnimationFinished() const;
void setDefaultAnimation(std::string name, AnimationProperties properties = AnimationProperties());
private:
static constexpr int kChannelCount = 8;
enum class CompositionMode {
Mono, /**< only animation on the first channel is being played */
Overlay, /**< animations on all channels play simultaneously */
Blend /**< animation on the second channel is transitioned into animation on the first channel */
};
struct NodeState {
glm::mat4 transform { 1.0f };
float alpha { 1.0f };
glm::vec3 selfIllumColor { 0.0f };
};
ModelSceneNode *_sceneNode;
std::set<std::string> _ignoreNodes;
std::vector<AnimationChannel> _channels;
CompositionMode _compositionMode { CompositionMode::Overlay };
bool _transition { false }; /**< is there an animation transition going on? */
std::unordered_map<uint16_t, SceneNodeState> _stateById;
std::string _defaultAnimName;
AnimationProperties _defaultAnimProperties;
void computeSceneNodeStates(graphics::ModelNode &modelNode, glm::mat4 parentTransform = glm::mat4(1.0f));
void applySceneNodeStates(graphics::ModelNode &modelNode);
bool isInTransition() const;
/**
* Determines the composition mode by animation flags.
*/
CompositionMode determineCompositionMode(int flags) const;
};
} // namespace scene
} // namespace reone

View file

@ -27,27 +27,21 @@ namespace reone {
namespace scene {
CameraSceneNode::CameraSceneNode(SceneGraph *sceneGraph, glm::mat4 projection, float aspect, float nearPlane, float farPlane) :
SceneNode(SceneNodeType::Camera, sceneGraph),
_projection(projection),
_aspect(aspect),
_nearPlane(nearPlane),
_farPlane(farPlane) {
updateFrustum();
CameraSceneNode::CameraSceneNode(string name, glm::mat4 projection, SceneGraph *sceneGraph) :
SceneNode(move(name), SceneNodeType::Camera, sceneGraph),
_projection(projection) {
}
void CameraSceneNode::updateAbsoluteTransform() {
SceneNode::updateAbsoluteTransform();
updateView();
updateFrustum();
void CameraSceneNode::onAbsoluteTransformChanged() {
computeView();
computeFrustumPlanes();
}
void CameraSceneNode::updateView() {
_view = glm::inverse(_absoluteTransform);
void CameraSceneNode::computeView() {
_view = _absTransformInv;
}
void CameraSceneNode::updateFrustum() {
void CameraSceneNode::computeFrustumPlanes() {
// Implementation of http://www.cs.otago.ac.nz/postgrads/alexis/planeExtraction.pdf
glm::mat4 vp(_projection * _view);
@ -111,9 +105,9 @@ bool CameraSceneNode::isInFrustum(const SceneNode &other) const {
isInFrustum(other.absoluteTransform()[3]);
}
void CameraSceneNode::setProjection(const glm::mat4 &projection) {
_projection = projection;
updateFrustum();
void CameraSceneNode::setProjection(glm::mat4 projection) {
_projection = move(projection);
computeFrustumPlanes();
}
} // namespace scene

View file

@ -27,7 +27,7 @@ namespace scene {
class CameraSceneNode : public SceneNode {
public:
CameraSceneNode(SceneGraph *sceneGraph, glm::mat4 projection, float aspect, float nearPlane, float farPlane);
CameraSceneNode(std::string name, glm::mat4 projection, SceneGraph *sceneGraph);
bool isInFrustum(const glm::vec3 &point) const;
bool isInFrustum(const graphics::AABB &aabb) const;
@ -35,18 +35,12 @@ public:
const glm::mat4 &projection() const { return _projection; }
const glm::mat4 &view() const { return _view; }
float aspect() const { return _aspect; }
float nearPlane() const { return _nearPlane; }
float farPlane() const { return _farPlane; }
void setProjection(const glm::mat4 &projection);
void setProjection(glm::mat4 projection);
private:
glm::mat4 _projection { 1.0f };
glm::mat4 _view { 1.0f };
float _aspect { 1.0f };
float _nearPlane { 0.0f };
float _farPlane { 0.0f };
// Frustum planes
@ -59,10 +53,10 @@ private:
// END Frustum planes
void updateAbsoluteTransform() override;
void computeView();
void computeFrustumPlanes();
void updateView();
void updateFrustum();
void onAbsoluteTransformChanged() override;
};
} // namespace scene

View file

@ -17,17 +17,16 @@
#pragma once
#include "glm/vec2.hpp"
#include "glm/vec3.hpp"
#include "modelnodescenenode.h"
namespace reone {
namespace scene {
struct GrassCluster {
glm::vec3 position { 0.0f };
glm::vec2 lightmapUV { 0.0f };
int variant { 0 };
class DummySceneNode : public ModelNodeSceneNode {
public:
DummySceneNode(std::shared_ptr<graphics::ModelNode> modelNode, SceneGraph *sceneGraph) : ModelNodeSceneNode(std::move(modelNode), SceneNodeType::Dummy, sceneGraph) {
}
};
} // namespace scene

View file

@ -42,25 +42,36 @@ namespace reone {
namespace scene {
static constexpr float kMotionBlurStrength = 0.25f;
static constexpr float kProjectileSpeed = 16.0f;
EmitterSceneNode::EmitterSceneNode(const ModelSceneNode *modelSceneNode, const shared_ptr<ModelNode::Emitter> &emitter, SceneGraph *sceneGraph) :
SceneNode(SceneNodeType::Emitter, sceneGraph),
_modelSceneNode(modelSceneNode),
_emitter(emitter) {
EmitterSceneNode::EmitterSceneNode(const ModelSceneNode *model, shared_ptr<ModelNode> modelNode, SceneGraph *sceneGraph) :
ModelNodeSceneNode(modelNode, SceneNodeType::Emitter, sceneGraph),
_model(model) {
if (!modelSceneNode) {
throw invalid_argument("modelSceneNode must not be null");
}
if (!emitter) {
throw invalid_argument("emitter must not be null");
if (!model) {
throw invalid_argument("model must not be null");
}
_particleSize.start = modelNode->sizeStart().getByFrameOrElse(0, 1.0f);
_particleSize.end = modelNode->sizeEnd().getByFrameOrElse(0, 1.0f);
_color.start = modelNode->colorStart().getByFrameOrElse(0, glm::vec3(1.0f));
_color.mid = modelNode->colorMid().getByFrameOrElse(0, glm::vec3(1.0f));
_color.end = modelNode->colorEnd().getByFrameOrElse(0, glm::vec3(1.0f));
_alpha.start = modelNode->alphaStart().getByFrameOrElse(0, 1.0f);
_alpha.mid = modelNode->alphaMid().getByFrameOrElse(0, 1.0f);
_alpha.end = modelNode->alphaEnd().getByFrameOrElse(0, 1.0f);
_frameStart = modelNode->frameStart().getByFrameOrElse(0, 0.0f);
_frameEnd = modelNode->frameEnd().getByFrameOrElse(0, 0.0f);
_size.x = modelNode->xSize().getByFrameOrElse(0, 1.0f);
_size.y = modelNode->xSize().getByFrameOrElse(0, 1.0f);
_birthrate = modelNode->birthrate().getByFrameOrElse(0, 0.0f);
_lifeExpectancy = modelNode->lifeExp().getByFrameOrElse(0, 0.0f);
_velocity = modelNode->velocity().getByFrameOrElse(0, 0.0f);
_randomVelocity = modelNode->randVel().getByFrameOrElse(0, 0.0f);
_spread = modelNode->spread().getByFrameOrElse(0, 0.0f);
_fps = modelNode->fps().getByFrameOrElse(0, 0.0f);
init();
}
void EmitterSceneNode::init() {
if (_emitter->birthrate != 0) {
_birthInterval = 1.0f / static_cast<float>(_emitter->birthrate);
if (_birthrate != 0.0f) {
_birthInterval = 1.0f / static_cast<float>(_birthrate);
}
}
@ -72,14 +83,14 @@ void EmitterSceneNode::update(float dt) {
spawnParticles(dt);
for (auto &particle : _particles) {
particle->update(dt);
updateParticle(*particle, dt);
}
}
void EmitterSceneNode::removeExpiredParticles(float dt) {
for (auto it = _particles.begin(); it != _particles.end(); ) {
auto &particle = (*it);
if (particle->isExpired()) {
if (isParticleExpired(*particle)) {
it = _particles.erase(it);
} else {
++it;
@ -88,9 +99,11 @@ void EmitterSceneNode::removeExpiredParticles(float dt) {
}
void EmitterSceneNode::spawnParticles(float dt) {
switch (_emitter->updateMode) {
shared_ptr<ModelNode::Emitter> emitter(_modelNode->emitter());
switch (emitter->updateMode) {
case ModelNode::Emitter::UpdateMode::Fountain:
if (_emitter->birthrate != 0.0f) {
if (_birthrate != 0.0f) {
if (_birthTimer.advance(dt)) {
if (_particles.size() < kMaxParticles) {
doSpawnParticle();
@ -100,7 +113,7 @@ void EmitterSceneNode::spawnParticles(float dt) {
}
break;
case ModelNode::Emitter::UpdateMode::Single:
if (!_spawned || (_particles.empty() && _emitter->loop)) {
if (!_spawned || (_particles.empty() && emitter->loop)) {
doSpawnParticle();
_spawned = true;
}
@ -111,49 +124,54 @@ void EmitterSceneNode::spawnParticles(float dt) {
}
void EmitterSceneNode::doSpawnParticle() {
float halfW = 0.005f * _emitter->size.x;
float halfH = 0.005f * _emitter->size.y;
shared_ptr<ModelNode::Emitter> emitter(_modelNode->emitter());
float halfW = 0.005f * _size.x;
float halfH = 0.005f * _size.y;
glm::vec3 position(random(-halfW, halfW), random(-halfH, halfH), 0.0f);
float sign;
if (_emitter->spread > glm::pi<float>() && random(0, 1) != 0) {
if (_spread > glm::pi<float>() && random(0, 1) != 0) {
sign = -1.0f;
} else {
sign = 1.0f;
}
float velocity = sign * (_emitter->velocity + random(0.0f, _emitter->randomVelocity));
float velocity = sign * (_velocity + random(0.0f, _randomVelocity));
auto particle = make_shared<Particle>(position, velocity, this);
auto particle = make_shared<Particle>();
particle->position = move(position);
particle->velocity = velocity;
particle->emitter = this;
_particles.push_back(particle);
}
void EmitterSceneNode::drawParticles(const vector<Particle *> &particles) {
if (particles.empty()) return;
shared_ptr<Texture> texture(_emitter->texture);
shared_ptr<ModelNode::Emitter> emitter(_modelNode->emitter());
shared_ptr<Texture> texture(emitter->texture);
if (!texture) return;
ShaderUniforms uniforms(_sceneGraph->uniformsPrototype());
uniforms.combined.featureMask |= UniformFeatureFlags::particles;
uniforms.particles->gridSize = glm::vec2(_emitter->gridWidth, _emitter->gridHeight);
uniforms.particles->render = static_cast<int>(_emitter->renderMode);
uniforms.particles->gridSize = glm::vec2(emitter->gridWidth, emitter->gridHeight);
uniforms.particles->render = static_cast<int>(emitter->renderMode);
for (size_t i = 0; i < particles.size(); ++i) {
const Particle &particle = *particles[i];
glm::mat4 transform(_absoluteTransform);
transform = glm::translate(transform, particles[i]->position());
if (_emitter->renderMode == ModelNode::Emitter::RenderMode::MotionBlur) {
transform = glm::scale(transform, glm::vec3((1.0f + kMotionBlurStrength * _modelSceneNode->projectileSpeed()) * particle.size(), particle.size(), particle.size()));
glm::mat4 transform(_absTransform);
transform = glm::translate(transform, particles[i]->position);
if (emitter->renderMode == ModelNode::Emitter::RenderMode::MotionBlur) {
transform = glm::scale(transform, glm::vec3((1.0f + kMotionBlurStrength * kProjectileSpeed) * particle.size, particle.size, particle.size));
} else {
transform = glm::scale(transform, glm::vec3(particle.size()));
transform = glm::scale(transform, glm::vec3(particle.size));
}
uniforms.particles->particles[i].transform = move(transform);
uniforms.particles->particles[i].color = glm::vec4(particle.color(), 1.0f);
uniforms.particles->particles[i].size = glm::vec2(particle.size());
uniforms.particles->particles[i].alpha = particle.alpha();
uniforms.particles->particles[i].frame = particle.frame();
uniforms.particles->particles[i].color = glm::vec4(particle.color, 1.0f);
uniforms.particles->particles[i].size = glm::vec2(particle.size);
uniforms.particles->particles[i].alpha = particle.alpha;
uniforms.particles->particles[i].frame = particle.frame;
}
Shaders::instance().activate(ShaderProgram::ParticleParticle, uniforms);
@ -161,7 +179,7 @@ void EmitterSceneNode::drawParticles(const vector<Particle *> &particles) {
StateManager::instance().setActiveTextureUnit(TextureUnits::diffuse);
texture->bind();
bool lighten = _emitter->blendMode == ModelNode::Emitter::BlendMode::Lighten;
bool lighten = emitter->blendMode == ModelNode::Emitter::BlendMode::Lighten;
if (lighten) {
StateManager::instance().withLightenBlending([&particles]() {
Meshes::instance().getBillboard()->drawInstanced(static_cast<int>(particles.size()));

View file

@ -17,8 +17,6 @@
#pragma once
#include "scenenode.h"
#include <vector>
#include "glm/vec3.hpp"
@ -26,7 +24,7 @@
#include "../../common/timer.h"
#include "../../graphics/model/modelnode.h"
#include "../particle.h"
#include "modelnodescenenode.h"
namespace reone {
@ -34,9 +32,28 @@ namespace scene {
class ModelSceneNode;
class EmitterSceneNode : public SceneNode {
class EmitterSceneNode : public ModelNodeSceneNode {
public:
EmitterSceneNode(const ModelSceneNode *modelSceneNode, const std::shared_ptr<graphics::ModelNode::Emitter> &emitter, SceneGraph *sceneGraph);
struct Particle {
glm::vec3 position { 0.0f };
float velocity { 0.0f };
EmitterSceneNode *emitter { nullptr };
float animLength { 0.0f };
float lifetime { 0.0f };
int frame { 0 };
float size { 1.0f };
glm::vec3 color { 1.0f };
float alpha { 1.0f };
};
template <class T>
struct Constraints {
T start;
T mid;
T end;
};
EmitterSceneNode(const ModelSceneNode *model, std::shared_ptr<graphics::ModelNode> modelNode, SceneGraph *sceneGraph);
void update(float dt) override;
@ -49,23 +66,43 @@ public:
void detonate();
std::shared_ptr<graphics::ModelNode::Emitter> emitter() const { return _emitter; }
const std::vector<std::shared_ptr<Particle>> &particles() const { return _particles; }
private:
const ModelSceneNode *_modelSceneNode;
std::shared_ptr<graphics::ModelNode::Emitter> _emitter;
const ModelSceneNode *_model;
Constraints<float> _particleSize;
Constraints<glm::vec3> _color;
Constraints<float> _alpha;
int _frameStart { 0 };
int _frameEnd { 0 };
glm::vec2 _size { 0.0f };
float _birthrate { 0.0f }; /**< rate of particle birth per second */
float _lifeExpectancy { 0.0f }; /**< life of each particle in seconds */
float _velocity { 0.0f };
float _randomVelocity { 0.0f };
float _spread { 0.0f };
float _fps { 0.0f };
float _birthInterval { 0.0f };
Timer _birthTimer;
std::vector<std::shared_ptr<Particle>> _particles;
bool _spawned { false };
void init();
void spawnParticles(float dt);
void removeExpiredParticles(float dt);
void doSpawnParticle();
// Particles
void initParticle(Particle &particle);
void updateParticle(Particle &particle, float dt);
void updateParticleAnimation(Particle &particle, float dt);
bool isParticleExpired(Particle &particle) const;
// END Particles
};
} // namespace scene

View file

@ -0,0 +1,93 @@
/*
* 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 "emitternode.h"
using namespace std;
using namespace reone::graphics;
namespace reone {
namespace scene {
void EmitterSceneNode::initParticle(Particle &particle) {
shared_ptr<ModelNode::Emitter> emitter(_modelNode->emitter());
if (_fps > 0) {
particle.animLength = (_frameEnd - _frameStart + 1) / static_cast<float>(_fps);
}
particle.frame = _frameStart;
}
void EmitterSceneNode::updateParticle(Particle &particle, float dt) {
shared_ptr<ModelNode::Emitter> emitter(_modelNode->emitter());
if (_lifeExpectancy != -1) {
particle.lifetime = glm::min(particle.lifetime + dt, static_cast<float>(_lifeExpectancy));
} else if (particle.lifetime == particle.animLength) {
particle.lifetime = 0.0f;
} else {
particle.lifetime = glm::min(particle.lifetime + dt, particle.animLength);
}
if (!isParticleExpired(particle)) {
particle.position.z += particle.velocity * dt;
updateParticleAnimation(particle, dt);
}
}
template <class T>
static T interpolateConstraints(const EmitterSceneNode::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 EmitterSceneNode::updateParticleAnimation(Particle &particle, float dt) {
shared_ptr<ModelNode::Emitter> emitter(_modelNode->emitter());
float maturity;
if (_lifeExpectancy != -1) {
maturity = particle.lifetime / static_cast<float>(_lifeExpectancy);
} else if (particle.animLength > 0.0f) {
maturity = particle.lifetime / particle.animLength;
} else {
maturity = 0.0f;
}
particle.frame = static_cast<int>(glm::ceil(_frameStart + maturity * (_frameEnd - _frameStart)));
particle.size = interpolateConstraints(_particleSize, maturity);
particle.color = interpolateConstraints(_color, maturity);
particle.alpha = interpolateConstraints(_alpha, maturity);
}
bool EmitterSceneNode::isParticleExpired(Particle &particle) const {
shared_ptr<ModelNode::Emitter> emitter(_modelNode->emitter());
return _lifeExpectancy != -1 && particle.lifetime >= _lifeExpectancy;
}
} // namespace scene
} // namespace reone

View file

@ -33,8 +33,8 @@ namespace reone {
namespace scene {
GrassSceneNode::GrassSceneNode(SceneGraph *graph, glm::vec2 quadSize, shared_ptr<Texture> texture, shared_ptr<Texture> lightmap) :
SceneNode(SceneNodeType::Grass, graph),
GrassSceneNode::GrassSceneNode(string name, glm::vec2 quadSize, shared_ptr<Texture> texture, shared_ptr<Texture> lightmap, SceneGraph *graph) :
SceneNode(move(name), SceneNodeType::Grass, graph),
_quadSize(move(quadSize)),
_texture(texture),
_lightmap(move(lightmap)) {
@ -42,18 +42,17 @@ GrassSceneNode::GrassSceneNode(SceneGraph *graph, glm::vec2 quadSize, shared_ptr
if (!texture) {
throw invalid_argument("texture must not be null");
}
_transparent = true;
}
void GrassSceneNode::clear() {
_clusters.clear();
}
void GrassSceneNode::addCluster(GrassCluster cluster) {
void GrassSceneNode::addCluster(Cluster cluster) {
_clusters.push_back(move(cluster));
}
void GrassSceneNode::drawClusters(const vector<GrassCluster> &clusters) {
void GrassSceneNode::drawClusters(const vector<Cluster> &clusters) {
StateManager::instance().setActiveTextureUnit(TextureUnits::diffuse);
_texture->bind();

View file

@ -19,32 +19,34 @@
#include "../../graphics/texture/texture.h"
#include "../grasscluster.h"
#include "scenenode.h"
namespace reone {
namespace scene {
class CameraSceneNode;
class GrassSceneNode : public SceneNode {
public:
GrassSceneNode(SceneGraph *graph, glm::vec2 quadSize, std::shared_ptr<graphics::Texture> texture, std::shared_ptr<graphics::Texture> lightmap = nullptr);
struct Cluster {
glm::vec3 position { 0.0f };
glm::vec2 lightmapUV { 0.0f };
int variant { 0 };
};
GrassSceneNode(std::string name, glm::vec2 quadSize, std::shared_ptr<graphics::Texture> texture, std::shared_ptr<graphics::Texture> lightmap, SceneGraph *graph);
void clear();
void addCluster(GrassCluster cluster);
void addCluster(Cluster cluster);
void drawClusters(const std::vector<GrassCluster> &clusters);
void drawClusters(const std::vector<Cluster> &clusters);
const std::vector<GrassCluster> &clusters() const { return _clusters; }
const std::vector<Cluster> &clusters() const { return _clusters; }
private:
glm::vec2 _quadSize { 0.0f };
std::shared_ptr<graphics::Texture> _texture;
std::shared_ptr<graphics::Texture> _lightmap;
std::vector<GrassCluster> _clusters;
std::vector<Cluster> _clusters;
};
} // namespace scene

View file

@ -17,6 +17,8 @@
#include "lightnode.h"
#include <stdexcept>
#include "glm/ext.hpp"
#include "../../graphics/mesh/meshes.h"
@ -36,9 +38,18 @@ namespace reone {
namespace scene {
LightSceneNode::LightSceneNode(int priority, SceneGraph *sceneGraph) :
SceneNode(SceneNodeType::Light, sceneGraph),
_priority(priority) {
static constexpr float kMinDirectionalLightRadius = 1000.0f;
LightSceneNode::LightSceneNode(const ModelSceneNode *model, shared_ptr<ModelNode> modelNode, SceneGraph *sceneGraph) :
ModelNodeSceneNode(modelNode, SceneNodeType::Light, sceneGraph),
_model(model) {
if (!model) {
throw invalid_argument("model must not be null");
}
_radius = modelNode->radius().getByFrameOrElse(0, 1.0f);
_multiplier = modelNode->multiplier().getByFrameOrElse(0, 1.0f);
_color = modelNode->color().getByFrameOrElse(0, glm::vec3(1.0f));
}
void LightSceneNode::drawLensFlares(const ModelNode::LensFlare &flare) {
@ -48,7 +59,7 @@ void LightSceneNode::drawLensFlares(const ModelNode::LensFlare &flare) {
StateManager::instance().setActiveTextureUnit(TextureUnits::diffuse);
flare.texture->bind();
glm::vec4 lightPos(_absoluteTransform[3]);
glm::vec4 lightPos(_absTransform[3]);
glm::vec4 lightPosNdc(camera->projection() * camera->view() * lightPos);
float w = _sceneGraph->options().width;
@ -79,6 +90,10 @@ void LightSceneNode::drawLensFlares(const ModelNode::LensFlare &flare) {
});
}
bool LightSceneNode::isDirectional() const {
return _radius >= kMinDirectionalLightRadius;
}
} // namespace scene
} // namespace reone

View file

@ -17,58 +17,38 @@
#pragma once
#include "../types.h"
#include "../../graphics/model/modelnode.h"
#include "scenenode.h"
#include "modelnodescenenode.h"
namespace reone {
namespace scene {
class LightSceneNode : public SceneNode {
class ModelSceneNode;
class LightSceneNode : public ModelNodeSceneNode {
public:
LightSceneNode(int priority, SceneGraph *sceneGraph);
LightSceneNode(const ModelSceneNode *model, std::shared_ptr<graphics::ModelNode> modelNode, SceneGraph *sceneGraph);
void drawLensFlares(const graphics::ModelNode::LensFlare &flare);
bool isShadow() const { return _shadow; }
bool isAmbientOnly() const { return _ambientOnly; }
bool isDirectional() const { return _directional; }
bool isDirectional() const;
const glm::vec3 &color() const { return _color; }
int priority() const { return _priority; }
float multiplier() const { return _multiplier; }
float radius() const { return _radius; }
float flareRadius() const { return _flareRadius; }
const std::vector<graphics::ModelNode::LensFlare> &flares() const { return _flares; }
float multiplier() const { return _multiplier; }
const glm::vec3 &color() const { return _color; }
void setColor(glm::vec3 color) { _color = std::move(color); }
void setMultiplier(float multiplier) { _multiplier = multiplier; }
void setRadius(float radius) { _radius = radius; }
void setShadow(bool shadow) { _shadow = shadow; }
void setAmbientOnly(bool ambientOnly) { _ambientOnly = ambientOnly; }
void setDirectional(bool directional) { _directional = directional; }
void setFlareRadius(float radius) { _flareRadius = radius; }
void setFlares(std::vector<graphics::ModelNode::LensFlare> flares) { _flares = std::move(flares); }
void setMultiplier(float multiplier) { _multiplier = multiplier; }
void setColor(glm::vec3 color) { _color = std::move(color); }
private:
int _priority;
const ModelSceneNode *_model;
glm::vec3 _color { 1.0f };
float _multiplier { 1.0f };
float _radius { 1.0f };
bool _shadow { false };
bool _ambientOnly { false };
bool _directional { false };
// Light flares
float _flareRadius { 0.0f };
std::vector<graphics::ModelNode::LensFlare> _flares;
// END Light flares
float _radius { 0.0f };
float _multiplier { 0.0f };
glm::vec3 _color { 0.0f };
};
} // namespace scene

View file

@ -49,23 +49,16 @@ static constexpr float kUvAnimationSpeed = 250.0f;
static bool g_debugWalkmesh = false;
MeshSceneNode::MeshSceneNode(SceneGraph *sceneGraph, const ModelSceneNode *modelSceneNode, ModelNode *modelNode) :
SceneNode(SceneNodeType::ModelNode, sceneGraph),
_modelSceneNode(modelSceneNode),
_modelNode(modelNode) {
MeshSceneNode::MeshSceneNode(const ModelSceneNode *model, shared_ptr<ModelNode> modelNode, SceneGraph *sceneGraph) :
ModelNodeSceneNode(modelNode, SceneNodeType::Mesh, sceneGraph),
_model(model) {
if (!modelSceneNode) {
throw invalid_argument("modelSceneNode must not be null");
}
if (!modelNode) {
throw invalid_argument("modelNode must not be null");
}
if (_modelNode->alpha().getNumFrames() > 0) {
_alpha = _modelNode->alpha().getByFrame(0);
}
if (_modelNode->selfIllumColor().getNumFrames() > 0) {
_selfIllumColor = _modelNode->selfIllumColor().getByFrame(0);
if (!model) {
throw invalid_argument("model must not be null");
}
_alpha = _modelNode->alpha().getByFrameOrElse(0, 1.0f);
_selfIllumColor = _modelNode->selfIllumColor().getByFrameOrElse(0, glm::vec3(0.0f));
initTextures();
}
@ -82,15 +75,20 @@ void MeshSceneNode::initTextures() {
}
void MeshSceneNode::refreshMaterial() {
if (!_textures.diffuse) return;
_material = Material();
shared_ptr<Material> material(Materials::instance().get(_textures.diffuse->name()));
if (material) {
_material = *material;
if (_textures.diffuse) {
shared_ptr<Material> material(Materials::instance().get(_textures.diffuse->name()));
if (material) {
_material = *material;
}
}
}
void MeshSceneNode::refreshAdditionalTextures() {
_textures.envmap.reset();
_textures.bumpmap.reset();
if (!_textures.diffuse) return;
const Texture::Features &features = _textures.diffuse->features();
@ -105,58 +103,65 @@ void MeshSceneNode::refreshAdditionalTextures() {
}
void MeshSceneNode::update(float dt) {
SceneNode::update(dt);
shared_ptr<ModelNode::TriangleMesh> mesh(_modelNode->mesh());
if (mesh) {
// UV animation
if (mesh->uvAnimation.dir.x != 0.0f || mesh->uvAnimation.dir.y != 0.0f) {
_uvOffset += kUvAnimationSpeed * mesh->uvAnimation.dir * dt;
_uvOffset -= glm::floor(_uvOffset);
}
updateUVAnimation(dt, *mesh);
updateBumpmapAnimation(dt, *mesh);
updateDanglyMeshAnimation(dt, *mesh);
}
}
// Bumpmap UV animation
if (_textures.bumpmap) {
const Texture::Features &features = _textures.bumpmap->features();
if (features.procedureType == Texture::ProcedureType::Cycle) {
int frameCount = features.numX * features.numY;
float length = frameCount / static_cast<float>(features.fps);
_bumpmapTime = glm::min(_bumpmapTime + dt, length);
_bumpmapFrame = static_cast<int>(glm::round((frameCount - 1) * (_bumpmapTime / length)));
if (_bumpmapTime == length) {
_bumpmapTime = 0.0f;
}
void MeshSceneNode::updateUVAnimation(float dt, const ModelNode::TriangleMesh &mesh) {
if (mesh.uvAnimation.dir.x != 0.0f || mesh.uvAnimation.dir.y != 0.0f) {
_uvOffset += kUvAnimationSpeed * mesh.uvAnimation.dir * dt;
_uvOffset -= glm::floor(_uvOffset);
}
}
void MeshSceneNode::updateBumpmapAnimation(float dt, const ModelNode::TriangleMesh &mesh) {
if (!_textures.bumpmap) return;
const Texture::Features &features = _textures.bumpmap->features();
if (features.procedureType == Texture::ProcedureType::Cycle) {
int frameCount = features.numX * features.numY;
float length = frameCount / static_cast<float>(features.fps);
_bumpmapTime = glm::min(_bumpmapTime + dt, length);
_bumpmapFrame = static_cast<int>(glm::round((frameCount - 1) * (_bumpmapTime / length)));
if (_bumpmapTime == length) {
_bumpmapTime = 0.0f;
}
}
}
void MeshSceneNode::updateDanglyMeshAnimation(float dt, const ModelNode::TriangleMesh &mesh) {
shared_ptr<ModelNode::DanglyMesh> danglyMesh(mesh.danglyMesh);
if (!danglyMesh) return;
bool forceApplied = glm::length2(_danglymeshAnimation.force) > 0.0f;
if (forceApplied) {
// When force is applied, stride in the opposite direction from the applied force
glm::vec3 strideDir(-_danglymeshAnimation.force);
glm::vec3 maxStride(danglyMesh->displacement);
_danglymeshAnimation.stride = glm::clamp(_danglymeshAnimation.stride + danglyMesh->period * strideDir * dt, -maxStride, maxStride);
} else {
// When force is not applied, gradually nullify stride
float strideMag2 = glm::length2(_danglymeshAnimation.stride);
if (strideMag2 > 0.0f) {
glm::vec3 strideDir(-_danglymeshAnimation.stride);
_danglymeshAnimation.stride += danglyMesh->period * strideDir * dt;
if ((strideDir.x > 0.0f && _danglymeshAnimation.stride.x > 0.0f) || (strideDir.x < 0.0f && _danglymeshAnimation.stride.x < 0.0f)) {
_danglymeshAnimation.stride.x = 0.0f;
}
}
// Danglymesh animation
shared_ptr<ModelNode::DanglyMesh> danglyMesh(mesh->danglyMesh);
if (danglyMesh) {
bool forceApplied = glm::length2(_danglymeshAnimation.force) > 0.0f;
if (forceApplied) {
// When force is applied, stride in the opposite direction from the applied force
glm::vec3 strideDir(-_danglymeshAnimation.force);
glm::vec3 maxStride(danglyMesh->displacement);
_danglymeshAnimation.stride = glm::clamp(_danglymeshAnimation.stride + danglyMesh->period * strideDir * dt, -maxStride, maxStride);
} else {
// When force is not applied, gradually nullify stride
float strideMag2 = glm::length2(_danglymeshAnimation.stride);
if (strideMag2 > 0.0f) {
glm::vec3 strideDir(-_danglymeshAnimation.stride);
_danglymeshAnimation.stride += danglyMesh->period * strideDir * dt;
if ((strideDir.x > 0.0f && _danglymeshAnimation.stride.x > 0.0f) || (strideDir.x < 0.0f && _danglymeshAnimation.stride.x < 0.0f)) {
_danglymeshAnimation.stride.x = 0.0f;
}
if ((strideDir.y > 0.0f && _danglymeshAnimation.stride.y > 0.0f) || (strideDir.y < 0.0f && _danglymeshAnimation.stride.y < 0.0f)) {
_danglymeshAnimation.stride.y = 0.0f;
}
if ((strideDir.z > 0.0f && _danglymeshAnimation.stride.z > 0.0f) || (strideDir.z < 0.0f && _danglymeshAnimation.stride.z < 0.0f)) {
_danglymeshAnimation.stride.z = 0.0f;
}
}
if ((strideDir.y > 0.0f && _danglymeshAnimation.stride.y > 0.0f) || (strideDir.y < 0.0f && _danglymeshAnimation.stride.y < 0.0f)) {
_danglymeshAnimation.stride.y = 0.0f;
}
if ((strideDir.z > 0.0f && _danglymeshAnimation.stride.z > 0.0f) || (strideDir.z < 0.0f && _danglymeshAnimation.stride.z < 0.0f)) {
_danglymeshAnimation.stride.z = 0.0f;
}
}
}
SceneNode::update(dt);
}
bool MeshSceneNode::shouldRender() const {
@ -184,7 +189,7 @@ bool MeshSceneNode::isTransparent() const {
if (!mesh) return false; // Meshless nodes are opaque
// Character models are opaque
if (_modelSceneNode->model()->classification() == Model::Classification::Character) return false;
if (_model->model()->classification() == Model::Classification::Character) return false;
// Model nodes with alpha less than 1.0 are transparent
if (_alpha < 1.0f) return true;
@ -233,8 +238,8 @@ void MeshSceneNode::drawSingle(bool shadowPass) {
if (isFeatureEnabled(Feature::HDR)) {
uniforms.combined.featureMask |= UniformFeatureFlags::hdr;
}
uniforms.combined.general.model = _absoluteTransform;
uniforms.combined.general.alpha = _modelSceneNode->alpha() * _alpha;
uniforms.combined.general.model = _absTransform;
uniforms.combined.general.alpha = _alpha;
uniforms.combined.general.ambientColor = glm::vec4(_sceneGraph->ambientLightColor(), 1.0f);
ShaderProgram program;
@ -278,7 +283,7 @@ void MeshSceneNode::drawSingle(bool shadowPass) {
uniforms.combined.bumpmaps.frame = _bumpmapFrame;
}
bool receivesShadows = isReceivingShadows(*_modelSceneNode, *this);
bool receivesShadows = isReceivingShadows(*_model, *this);
if (receivesShadows) {
uniforms.combined.featureMask |= UniformFeatureFlags::shadows;
}
@ -290,8 +295,8 @@ void MeshSceneNode::drawSingle(bool shadowPass) {
if (i < static_cast<int>(mesh->skin->boneNodeId.size())) {
uint16_t nodeId = mesh->skin->boneNodeId[i];
if (nodeId != 0xffff) {
MeshSceneNode *bone = _modelSceneNode->getModelNodeById(nodeId);
if (bone) {
shared_ptr<ModelNodeSceneNode> bone(_model->getNodeById(nodeId));
if (bone && bone->type() == SceneNodeType::Mesh) {
uniforms.skeletal->bones[i] = _modelNode->absoluteTransformInverse() * bone->boneTransform() * _modelNode->absoluteTransform();
}
}
@ -338,7 +343,7 @@ void MeshSceneNode::drawSingle(bool shadowPass) {
}
}
if (_sceneGraph->isFogEnabled() && _modelSceneNode->model()->isAffectedByFog()) {
if (_sceneGraph->isFogEnabled() && _model->model()->isAffectedByFog()) {
uniforms.combined.featureMask |= UniformFeatureFlags::fog;
uniforms.combined.general.fogNear = _sceneGraph->fogNear();
uniforms.combined.general.fogFar = _sceneGraph->fogFar();
@ -401,7 +406,7 @@ void MeshSceneNode::drawSingle(bool shadowPass) {
}
bool MeshSceneNode::isLightingEnabled() const {
if (!isLightingEnabledByUsage(_modelSceneNode->usage())) return false;
if (!isLightingEnabledByUsage(_model->usage())) return false;
// Lighting is disabled for lightmapped models, unless dynamic room lighting is enabled
if (_textures.lightmap && !isFeatureEnabled(Feature::DynamicRoomLighting)) return false;
@ -418,14 +423,10 @@ bool MeshSceneNode::isLightingEnabled() const {
void MeshSceneNode::setAppliedForce(glm::vec3 force) {
if (_modelNode->isDanglyMesh()) {
// Convert force from world to object space
_danglymeshAnimation.force = _absoluteTransformInv * glm::vec4(force, 0.0f);
_danglymeshAnimation.force = _absTransformInv * glm::vec4(force, 0.0f);
}
}
glm::vec3 MeshSceneNode::getOrigin() const {
return _absoluteTransform * glm::vec4(_modelNode->mesh()->mesh->aabb().center(), 1.0f);
}
void MeshSceneNode::setDiffuseTexture(const shared_ptr<Texture> &texture) {
_textures.diffuse = texture;
refreshMaterial();

View file

@ -17,7 +17,7 @@
#pragma once
#include "scenenode.h"
#include "modelnodescenenode.h"
#include "../../graphics/material.h"
#include "../../graphics/model/model.h"
@ -29,32 +29,25 @@ namespace scene {
class ModelSceneNode;
class MeshSceneNode : public SceneNode {
class MeshSceneNode : public ModelNodeSceneNode {
public:
MeshSceneNode(SceneGraph *sceneGraph, const ModelSceneNode *modelSceneNode, graphics::ModelNode *modelNode);
MeshSceneNode(const ModelSceneNode *model, std::shared_ptr<graphics::ModelNode> modelNode, SceneGraph *sceneGraph);
void update(float dt) override;
void drawSingle(bool shadowPass);
void setAppliedForce(glm::vec3 force);
bool shouldRender() const;
bool shouldCastShadows() const;
glm::vec3 getOrigin() const override;
bool isTransparent() const override;
bool isTransparent() const;
bool isSelfIlluminated() const;
const ModelSceneNode *modelSceneNode() const { return _modelSceneNode; }
const graphics::ModelNode *modelNode() const { return _modelNode; }
const glm::mat4 &boneTransform() const { return _boneTransform; }
const ModelSceneNode *model() const { return _model; }
void setBoneTransform(glm::mat4 transform) { _boneTransform = std::move(transform); }
void setDiffuseTexture(const std::shared_ptr<graphics::Texture> &texture);
void setAlpha(float alpha) { _alpha = alpha; }
void setSelfIllumColor(glm::vec3 color) { _selfIllumColor = std::move(color); }
void setAppliedForce(glm::vec3 force);
private:
struct NodeTextures {
@ -69,17 +62,15 @@ private:
glm::vec3 stride { 0.0f }; /**< how far have vertices traveled from the rest position? */
} _danglymeshAnimation;
const ModelSceneNode *_modelSceneNode;
const graphics::ModelNode *_modelNode;
const ModelSceneNode *_model;
graphics::Material _material;
glm::mat4 _animTransform { 1.0f };
glm::mat4 _boneTransform { 1.0f };
glm::vec2 _uvOffset { 0.0f };
float _bumpmapTime { 0.0f };
int _bumpmapFrame { 0 };
float _alpha { 1.0f };
glm::vec3 _selfIllumColor { 0.0f };
bool _transparent { false };
void initTextures();
@ -87,6 +78,14 @@ private:
void refreshAdditionalTextures();
bool isLightingEnabled() const;
// Animation
void updateUVAnimation(float dt, const graphics::ModelNode::TriangleMesh &mesh);
void updateBumpmapAnimation(float dt, const graphics::ModelNode::TriangleMesh &mesh);
void updateDanglyMeshAnimation(float dt, const graphics::ModelNode::TriangleMesh &mesh);
// END Animation
};
} // namespace scene

View file

@ -15,327 +15,173 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stack>
#include <stdexcept>
#include "../../common/collectionutil.h"
#include "../../common/log.h"
#include "../../graphics/featureutil.h"
#include "../../graphics/mesh/meshes.h"
#include "../../resource/resources.h"
#include "../scenegraph.h"
#include "../types.h"
#include "cameranode.h"
#include "emitternode.h"
#include "lightnode.h"
#include "meshnode.h"
#include "modelnode.h"
using namespace std;
using namespace std::placeholders;
using namespace reone::graphics;
using namespace reone::resource;
namespace reone {
namespace scene {
const float kMinDirectionalLightRadius = 1000.0f;
static bool g_debugAABB = false;
ModelSceneNode::ModelSceneNode(
shared_ptr<Model> model,
ModelUsage usage,
const shared_ptr<Model> &model,
SceneGraph *sceneGraph,
set<string> ignoreNodes,
IAnimationEventListener *animEventListener
) :
SceneNode(SceneNodeType::Model, sceneGraph),
_animEventListener(animEventListener),
_usage(usage),
SceneNode(model->name(), SceneNodeType::Model, sceneGraph),
_model(model),
_animator(this, ignoreNodes) {
initModelNodes();
_usage(usage),
_animEventListener(animEventListener) {
if (!model) {
throw invalid_argument("model must not be null");
}
_volumetric = true;
}
static bool validateEmitter(const ModelNode::Emitter &emitter) {
switch (emitter.updateMode) {
case ModelNode::Emitter::UpdateMode::Fountain:
case ModelNode::Emitter::UpdateMode::Single:
case ModelNode::Emitter::UpdateMode::Explosion:
break;
default:
warn("validateEmitter: unsupported update mode: " + to_string(static_cast<int>(emitter.updateMode)));
return false;
}
switch (emitter.renderMode) {
case ModelNode::Emitter::RenderMode::Normal:
case ModelNode::Emitter::RenderMode::BillboardToWorldZ:
case ModelNode::Emitter::RenderMode::MotionBlur:
case ModelNode::Emitter::RenderMode::BillboardToLocalZ:
case ModelNode::Emitter::RenderMode::AlignedToParticleDir:
break;
default:
warn("validateEmitter: unsupported render mode: " + to_string(static_cast<int>(emitter.renderMode)));
return false;
}
switch (emitter.blendMode) {
case ModelNode::Emitter::BlendMode::Normal:
case ModelNode::Emitter::BlendMode::Lighten:
break;
default:
warn("validateEmitter: unsupported blend mode: " + to_string(static_cast<int>(emitter.blendMode)));
return false;
}
return true;
}
void ModelSceneNode::initModelNodes() {
shared_ptr<MeshSceneNode> rootNode(getModelNodeSceneNode(*_model->rootNode()));
addChild(rootNode);
stack<MeshSceneNode *> nodes;
nodes.push(rootNode.get());
while (!nodes.empty()) {
MeshSceneNode *sceneNode = nodes.top();
nodes.pop();
const ModelNode *modelNode = sceneNode->modelNode();
_modelNodeById.insert(make_pair(modelNode->id(), sceneNode));
for (auto &child : modelNode->children()) {
shared_ptr<MeshSceneNode> childNode(getModelNodeSceneNode(*child));
addChild(childNode);
nodes.push(childNode.get());
shared_ptr<ModelNode::Light> light(child->light());
if (light) {
// Light is considered directional if its radius exceeds a certain threshold
float radius = child->radius().getByFrameOrElse(0, 1.0f);
bool directional = radius >= kMinDirectionalLightRadius;
auto lightNode = make_shared<LightSceneNode>(light->priority, _sceneGraph);
lightNode->setColor(child->color().getByFrameOrElse(0, glm::vec3(1.0f)));
lightNode->setMultiplier(child->multiplier().getByFrameOrElse(0, 1.0f));
lightNode->setRadius(radius);
lightNode->setShadow(light->shadow);
lightNode->setAmbientOnly(light->ambientOnly);
lightNode->setDirectional(directional);
lightNode->setFlareRadius(light->flareRadius);
lightNode->setFlares(light->flares);
childNode->addChild(lightNode);
_lightNodeById.insert(make_pair(modelNode->id(), lightNode.get()));
}
shared_ptr<ModelNode::Emitter> emitter(child->emitter());
if (emitter && validateEmitter(*emitter)) {
auto emitterNode = make_shared<EmitterSceneNode>(this, emitter, _sceneGraph);
childNode->addChild(emitterNode);
_emitters.push_back(emitterNode);
}
// If model node is a reference, attach the model it contains to the model nodes scene node
shared_ptr<ModelNode::Reference> reference(child->reference());
if (reference) {
attach(*childNode, reference->model, _usage);
}
}
}
buildNodeTree(_model->rootNode(), this);
computeAABB();
}
unique_ptr<MeshSceneNode> ModelSceneNode::getModelNodeSceneNode(ModelNode &node) const {
auto sceneNode = make_unique<MeshSceneNode>(_sceneGraph, this, &node);
sceneNode->setLocalTransform(node.absoluteTransform());
return move(sceneNode);
void ModelSceneNode::buildNodeTree(shared_ptr<ModelNode> node, SceneNode *parent) {
// Convert model node to scene node
shared_ptr<ModelNodeSceneNode> sceneNode;
if (node->isMesh()) {
sceneNode = newMeshSceneNode(node);
} else if (node->isLight()) {
sceneNode = newLightSceneNode(node);
} else if (node->isEmitter()) {
sceneNode = newEmitterSceneNode(node);
} else {
sceneNode = newDummySceneNode(node);
}
if (node->isSkinMesh()) {
// Reparent skin meshes to prevent animation being applied twice
glm::mat4 transform(node->parent()->absoluteTransform() * node->localTransform());
sceneNode->setLocalTransform(move(transform));
addChild(sceneNode);
} else {
sceneNode->setLocalTransform(node->localTransform());
parent->addChild(sceneNode);
}
_nodeById.insert(make_pair(node->id(), sceneNode));
if (node->isReference()) {
auto model = make_shared<ModelSceneNode>(node->reference()->model, _usage, _sceneGraph, _animEventListener);
attach(node->id(), move(model));
}
for (auto &child : node->children()) {
buildNodeTree(child, sceneNode.get());
}
}
void ModelSceneNode::update(float dt) {
_animator.update(dt, !_culled);
// Optimization: skip invisible models
if (!_visible) return;
SceneNode::update(dt);
}
void ModelSceneNode::draw() {
if (g_debugAABB) {
glm::mat4 transform(_absoluteTransform * _aabb.transform());
ShaderUniforms uniforms(_sceneGraph->uniformsPrototype());
uniforms.combined.general.model = move(transform);
Shaders::instance().activate(ShaderProgram::ModelColor, uniforms);
Meshes::instance().getAABB()->draw();
}
}
shared_ptr<ModelSceneNode> ModelSceneNode::attach(const string &parent, const shared_ptr<Model> &model, ModelUsage usage) {
MeshSceneNode *parentNode = getModelNode(parent);
return parentNode ? attach(*parentNode, model, usage) : nullptr;
}
shared_ptr<ModelSceneNode> ModelSceneNode::attach(MeshSceneNode &parent, const shared_ptr<Model> &model, ModelUsage usage) {
const ModelNode *parentModelNode = parent.modelNode();
uint16_t parentId = parentModelNode->id();
auto maybeAttached = _attachedModels.find(parentId);
if (maybeAttached != _attachedModels.end()) {
parent.removeChild(*maybeAttached->second);
_attachedModels.erase(maybeAttached);
}
if (model) {
set<string> ignoreNodes;
for (const ModelNode *node = parentModelNode; node; node = node->parent()) {
ignoreNodes.insert(node->name());
}
auto modelNode = make_shared<ModelSceneNode>(usage, model, _sceneGraph, ignoreNodes);
parent.addChild(modelNode);
return _attachedModels.insert(make_pair(parentId, move(modelNode))).first->second;
}
return nullptr;
}
MeshSceneNode *ModelSceneNode::getModelNode(const string &name) const {
shared_ptr<ModelNode> modelNode(_model->getNodeByName(name));
if (!modelNode) return nullptr;
return getFromLookupOrNull(_modelNodeById, modelNode->id());
}
MeshSceneNode *ModelSceneNode::getModelNodeById(uint16_t nodeId) const {
return getFromLookupOrNull(_modelNodeById, nodeId);
}
LightSceneNode *ModelSceneNode::getLightNodeById(uint16_t nodeId) const {
return getFromLookupOrNull(_lightNodeById, nodeId);
}
shared_ptr<ModelSceneNode> ModelSceneNode::getAttachedModel(const string &parent) const {
shared_ptr<ModelNode> parentModelNode(_model->getNodeByName(parent));
if (!parentModelNode) return nullptr;
return getFromLookupOrNull(_attachedModels, parentModelNode->id());
}
void ModelSceneNode::attach(const string &parent, const shared_ptr<SceneNode> &node) {
shared_ptr<ModelNode> parentModelNode(_model->getNodeByName(parent));
if (!parentModelNode) {
warn(boost::format("Scene node %s: model node not found: %s") % _model->name() % parent);
return;
}
uint16_t parentId = parentModelNode->id();
auto maybeNode = _modelNodeById.find(parentId);
if (maybeNode != _modelNodeById.end()) {
maybeNode->second->addChild(node);
}
}
bool ModelSceneNode::getNodeAbsolutePosition(const string &name, glm::vec3 &position) const {
shared_ptr<ModelNode> node(_model->getNodeByName(name));
if (!node) {
shared_ptr<Model> superModel(_model->superModel());
if (superModel) {
node = superModel->getNodeByName(name);
}
}
if (!node) return false;
position = node->absoluteTransform()[3];
return true;
}
glm::vec3 ModelSceneNode::getWorldCenterAABB() const {
return _absoluteTransform * glm::vec4(_aabb.center(), 1.0f);
}
const string &ModelSceneNode::getName() const {
return _model->name();
}
void ModelSceneNode::setDiffuseTexture(const shared_ptr<Texture> &texture) {
for (auto &child : _children) {
if (child->type() == SceneNodeType::ModelNode) {
static_pointer_cast<MeshSceneNode>(child)->setDiffuseTexture(texture);
}
}
}
void ModelSceneNode::setVisible(bool visible) {
if (_visible == visible) return;
_visible = visible;
for (auto &attached : _attachedModels) {
attached.second->setVisible(visible);
}
}
void ModelSceneNode::setAlpha(float alpha) {
_alpha = alpha;
for (auto &attached : _attachedModels) {
attached.second->setAlpha(alpha);
}
updateAnimations(dt);
}
void ModelSceneNode::computeAABB() {
_aabb.reset();
stack<SceneNode *> nodes;
nodes.push(this);
while (!nodes.empty()) {
SceneNode *node = nodes.top();
nodes.pop();
if (node->type() == SceneNodeType::ModelNode) {
auto modelNode = static_cast<MeshSceneNode *>(node);
shared_ptr<ModelNode::TriangleMesh> mesh(modelNode->modelNode()->mesh());
if (mesh) {
_aabb.expand(mesh->mesh->aabb() * node->localTransform());
}
}
for (auto &child : node->children()) {
nodes.push(child.get());
for (auto &node : _nodeById) {
if (node.second->type() == SceneNodeType::Mesh) {
shared_ptr<ModelNode> modelNode(node.second->modelNode());
AABB modelSpaceAABB(modelNode->mesh()->mesh->aabb() * modelNode->absoluteTransform());
_aabb.expand(modelSpaceAABB);
}
}
}
unique_ptr<DummySceneNode> ModelSceneNode::newDummySceneNode(shared_ptr<ModelNode> node) const {
return make_unique<DummySceneNode>(node, _sceneGraph);
}
unique_ptr<MeshSceneNode> ModelSceneNode::newMeshSceneNode(shared_ptr<ModelNode> node) const {
return make_unique<MeshSceneNode>(this, node, _sceneGraph);
}
unique_ptr<LightSceneNode> ModelSceneNode::newLightSceneNode(shared_ptr<ModelNode> node) const {
return make_unique<LightSceneNode>(this, node, _sceneGraph);
}
unique_ptr<EmitterSceneNode> ModelSceneNode::newEmitterSceneNode(shared_ptr<ModelNode> node) const {
return make_unique<EmitterSceneNode>(this, node, _sceneGraph);
}
void ModelSceneNode::signalEvent(const string &name) {
debug(boost::format("Animation event signalled: %s %s") % _model->name() % name, 3);
debug(boost::format("Model '%s': event '%s' signalled") % _model->name() % name, 3);
if (name == "detonate") {
for (auto &emitter : _emitters) {
emitter->detonate();
for (auto &node : _nodeById) {
if (node.second->type() == SceneNodeType::Emitter) {
static_pointer_cast<EmitterSceneNode>(node.second)->detonate();
}
}
} else if (_animEventListener) {
_animEventListener->onEventSignalled(name);
}
}
void ModelSceneNode::setAppliedForce(glm::vec3 force) {
for (auto &nodePair : _modelNodeById) {
nodePair.second->setAppliedForce(force);
void ModelSceneNode::attach(uint16_t parentId, shared_ptr<SceneNode> node) {
auto maybeParent = _nodeById.find(parentId);
if (maybeParent == _nodeById.end()) return;
shared_ptr<ModelNodeSceneNode> parent(maybeParent->second);
parent->addChild(node);
_attachmentByNodeId.insert(make_pair(parentId, node));
}
void ModelSceneNode::attach(const string &parentName, shared_ptr<SceneNode> node) {
auto parent = _model->getNodeByName(parentName);
if (parent) {
attach(parent->id(), node);
}
for (auto &attached : _attachedModels) {
attached.second->setAppliedForce(force);
}
shared_ptr<ModelNodeSceneNode> ModelSceneNode::getNodeById(uint16_t nodeId) const {
return getFromLookupOrNull(_nodeById, nodeId);
}
shared_ptr<SceneNode> ModelSceneNode::getAttachment(const string &parentName) const {
auto parent = _model->getNodeByName(parentName);
return parent ? getFromLookupOrNull(_attachmentByNodeId, parent->id()) : nullptr;
}
void ModelSceneNode::setDiffuseTexture(shared_ptr<Texture> texture) {
for (auto &child : _children) {
if (child->type() == SceneNodeType::Mesh) {
static_pointer_cast<MeshSceneNode>(child)->setDiffuseTexture(texture);
}
}
}
void ModelSceneNode::setAppliedForce(glm::vec3 force) {
for (auto &node : _nodeById) {
if (node.second->type() == SceneNodeType::Mesh) {
static_pointer_cast<MeshSceneNode>(node.second)->setAppliedForce(force);
}
}
for (auto &attachment : _attachmentByNodeId) {
if (attachment.second->type() == SceneNodeType::Model) {
static_pointer_cast<ModelSceneNode>(attachment.second)->setAppliedForce(force);
}
}
}

View file

@ -17,91 +17,148 @@
#pragma once
#include <cstdint>
#include <set>
#include <unordered_map>
#include "../../graphics/lip/animation.h"
#include "../../graphics/model/model.h"
#include "../../graphics/shader/shaders.h"
#include "../../graphics/walkmesh/walkmesh.h"
#include "../animation/eventlistener.h"
#include "../animation/scenenodeanimator.h"
#include "../animeventlistener.h"
#include "../animproperties.h"
#include "../types.h"
#include "scenenode.h"
#include "dummynode.h"
#include "emitternode.h"
#include "lightnode.h"
#include "meshnode.h"
namespace reone {
namespace scene {
class EmitterSceneNode;
class LightSceneNode;
class MeshSceneNode;
constexpr float kDefaultDrawDistance = 1024.0f;
constexpr int kNumAnimationChannels = 8;
class ModelSceneNode : public SceneNode {
public:
ModelSceneNode(
std::shared_ptr<graphics::Model> model,
ModelUsage usage,
const std::shared_ptr<graphics::Model> &model,
SceneGraph *sceneGraph,
std::set<std::string> ignoreNodes = std::set<std::string>(),
IAnimationEventListener *animEventListener = nullptr);
void update(float dt) override;
void draw() override;
void computeAABB();
void signalEvent(const std::string &name);
std::shared_ptr<ModelNodeSceneNode> getNodeById(uint16_t nodeId) const;
std::shared_ptr<graphics::Model> model() const { return _model; }
ModelUsage usage() const { return _usage; }
float drawDistance() const { return _drawDistance; }
void setDrawDistance(float distance) { _drawDistance = distance; }
void setDiffuseTexture(std::shared_ptr<graphics::Texture> texture);
void setAppliedForce(glm::vec3 force);
MeshSceneNode *getModelNode(const std::string &name) const;
MeshSceneNode *getModelNodeById(uint16_t nodeId) const;
LightSceneNode *getLightNodeById(uint16_t nodeId) const;
std::shared_ptr<ModelSceneNode> getAttachedModel(const std::string &parent) const;
bool getNodeAbsolutePosition(const std::string &name, glm::vec3 &position) const;
glm::vec3 getWorldCenterAABB() const;
const std::string &getName() const;
// Animation
ModelUsage usage() const { return _usage; }
std::shared_ptr<graphics::Model> model() const { return _model; }
std::shared_ptr<graphics::Walkmesh> walkmesh() const { return _walkmesh; }
float alpha() const { return _alpha; }
float projectileSpeed() const { return _projectileSpeed; }
SceneNodeAnimator &animator() { return _animator; }
void playAnimation(const std::string &name, AnimationProperties properties = AnimationProperties());
void playAnimation(std::shared_ptr<graphics::Animation> anim, std::shared_ptr<graphics::LipAnimation> lipAnim = nullptr, AnimationProperties properties = AnimationProperties());
void setVisible(bool visible) override;
void setDiffuseTexture(const std::shared_ptr<graphics::Texture> &texture);
void setAlpha(float alpha);
void setProjectileSpeed(float speed) { _projectileSpeed = speed; }
void setWalkmesh(std::shared_ptr<graphics::Walkmesh> walkmesh) { _walkmesh = std::move(walkmesh); }
bool isAnimationFinished() const;
void setInanimateNodes(std::set<uint16_t> nodes) { _inanimateNodes = std::move(nodes); }
// END Animation
// Attachments
std::shared_ptr<ModelSceneNode> attach(const std::string &parent, const std::shared_ptr<graphics::Model> &model, ModelUsage usage);
std::shared_ptr<ModelSceneNode> attach(MeshSceneNode &parent, const std::shared_ptr<graphics::Model> &model, ModelUsage usage);
void attach(const std::string &parent, const std::shared_ptr<SceneNode> &node);
void attach(uint16_t parentId, std::shared_ptr<SceneNode> node);
void attach(const std::string &parentName, std::shared_ptr<SceneNode> node);
std::shared_ptr<SceneNode> getAttachment(const std::string &parentName) const;
// END Attachments
private:
enum class AnimationBlendMode {
Single,
Blend,
Overlay
};
struct AnimationStateFlags {
static constexpr int transform = 1;
static constexpr int alpha = 2;
static constexpr int selfIllumColor = 4;
};
struct AnimationState {
int flags { 0 };
glm::mat4 transform { 1.0f };
float alpha { 0.0f };
glm::vec3 selfIllumColor { 0.0f };
};
struct AnimationChannel {
std::shared_ptr<graphics::Animation> anim;
std::shared_ptr<graphics::LipAnimation> lipAnim;
AnimationProperties properties;
float time { 0.0f };
std::unordered_map<uint16_t, AnimationState> stateById;
// Flags
bool finished { false }; /**< channel contains a fire-and-forget animation that has finished playing */
bool transition { false }; /**< when computing states, use animation transition time as channel time */
bool freeze { false }; /**< channel time is not to be updated */
// END Flags
};
std::shared_ptr<graphics::Model> _model;
ModelUsage _usage;
IAnimationEventListener *_animEventListener;
ModelUsage _usage;
std::shared_ptr<graphics::Model> _model;
std::shared_ptr<graphics::Walkmesh> _walkmesh;
SceneNodeAnimator _animator;
float _drawDistance { kDefaultDrawDistance };
std::unordered_map<uint16_t, MeshSceneNode *> _modelNodeById;
std::unordered_map<uint16_t, LightSceneNode *> _lightNodeById;
std::vector<std::shared_ptr<EmitterSceneNode>> _emitters;
std::unordered_map<uint16_t, std::shared_ptr<ModelSceneNode>> _attachedModels;
bool _visible { true };
float _alpha { 1.0f };
float _projectileSpeed { 0.0f };
// Lookups
void initModelNodes();
std::unordered_map<uint16_t, std::shared_ptr<ModelNodeSceneNode>> _nodeById;
std::unordered_map<uint16_t, std::shared_ptr<SceneNode>> _attachmentByNodeId;
std::unique_ptr<MeshSceneNode> getModelNodeSceneNode(graphics::ModelNode &node) const;
// END Lookups
// Animation
AnimationChannel _animChannels[kNumAnimationChannels];
AnimationBlendMode _animBlendMode { AnimationBlendMode::Single };
std::set<uint16_t> _inanimateNodes; /**< node identifiers that are not to be animated */
// END Animation
void buildNodeTree(std::shared_ptr<graphics::ModelNode> node, SceneNode *parent);
std::unique_ptr<DummySceneNode> newDummySceneNode(std::shared_ptr<graphics::ModelNode> node) const;
std::unique_ptr<MeshSceneNode> newMeshSceneNode(std::shared_ptr<graphics::ModelNode> node) const;
std::unique_ptr<LightSceneNode> newLightSceneNode(std::shared_ptr<graphics::ModelNode> node) const;
std::unique_ptr<EmitterSceneNode> newEmitterSceneNode(std::shared_ptr<graphics::ModelNode> node) const;
// Animation
void resetAnimationChannel(AnimationChannel &channel, std::shared_ptr<graphics::Animation> anim = nullptr, std::shared_ptr<graphics::LipAnimation> lipAnim = nullptr, AnimationProperties properties = AnimationProperties());
void updateAnimations(float dt);
void updateAnimationChannel(AnimationChannel &channel, float dt);
void computeAnimationStates(AnimationChannel &channel, float time, const graphics::ModelNode &modelNode);
void applyAnimationStates(const graphics::ModelNode &modelNode);
void computeBoneTransforms();
static AnimationBlendMode getAnimationBlendMode(int flags);
// END Animation
};
} // namespace scene

View file

@ -0,0 +1,336 @@
/*
* 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 "modelnode.h"
#include "glm/gtx/matrix_decompose.hpp"
#include "glm/gtx/transform.hpp"
using namespace std;
using namespace reone::graphics;
namespace reone {
namespace scene {
void ModelSceneNode::playAnimation(const string &name, AnimationProperties properties) {
shared_ptr<Animation> anim(_model->getAnimation(name));
if (anim) {
playAnimation(anim, nullptr, move(properties));
}
}
void ModelSceneNode::playAnimation(shared_ptr<Animation> anim, shared_ptr<LipAnimation> lipAnim, AnimationProperties properties) {
if (properties.scale == 0.0f) {
properties.scale = _model->animationScale();
}
AnimationBlendMode blendMode = getAnimationBlendMode(properties.flags);
switch (blendMode) {
case AnimationBlendMode::Single:
// In Single mode, add animation to the first channel
resetAnimationChannel(_animChannels[0], anim, lipAnim, properties);
break;
case AnimationBlendMode::Blend: {
// In Blend mode, if there is an unfinished animation in the first
// channel, move it into the second channel and initiate transition
bool transition = false;
if (_animChannels[0].anim && !_animChannels[0].finished) {
_animChannels[1] = _animChannels[0];
_animChannels[1].transition = false;
_animChannels[1].freeze = true;
transition = true;
}
// Add animation to the first channel
resetAnimationChannel(_animChannels[0], anim, lipAnim, properties);
_animChannels[0].transition = transition;
break;
}
case AnimationBlendMode::Overlay:
// In Overlay mode, add animation to the first channel and move all
// other channels down the stack. If current mode is not Overlay,
// reset all other channels.
if (_animBlendMode == AnimationBlendMode::Overlay) {
for (int i = kNumAnimationChannels - 1; i > 0; --i) {
_animChannels[i] = _animChannels[i - 1];
}
} else {
for (int i = 1; i < kNumAnimationChannels; ++i) {
resetAnimationChannel(_animChannels[i]);
}
}
resetAnimationChannel(_animChannels[0], anim, lipAnim, properties);
break;
default:
break;
}
_animBlendMode = blendMode;
// Optionally propagate animation to attachments
if (properties.flags & AnimationFlags::propagate) {
for (auto &attachment : _attachmentByNodeId) {
if (attachment.second->type() == SceneNodeType::Model) {
static_pointer_cast<ModelSceneNode>(attachment.second)->playAnimation(anim, lipAnim, properties);
}
}
}
}
ModelSceneNode::AnimationBlendMode ModelSceneNode::getAnimationBlendMode(int flags) {
return (flags & AnimationFlags::blend) ?
AnimationBlendMode::Blend :
((flags & AnimationFlags::overlay) ? AnimationBlendMode::Overlay : AnimationBlendMode::Single);
}
void ModelSceneNode::resetAnimationChannel(AnimationChannel &channel, shared_ptr<Animation> anim, shared_ptr<LipAnimation> lipAnim, AnimationProperties properties) {
channel.anim = move(anim);
channel.lipAnim = move(lipAnim);
channel.properties = move(properties);
channel.time = 0.0f;
channel.stateById.clear();
channel.finished = false;
channel.transition = false;
channel.freeze = false;
}
void ModelSceneNode::updateAnimations(float dt) {
for (auto &channel : _animChannels) {
updateAnimationChannel(channel, dt);
}
// Apply states and compute bone transforms only when this model is not culled
if (!_culled) {
applyAnimationStates(*_model->rootNode());
computeBoneTransforms();
}
}
void ModelSceneNode::updateAnimationChannel(AnimationChannel &channel, float dt) {
// Do not update if there is no animation, freezed or a finished animation
// in the channel
if (!channel.anim || channel.freeze || channel.finished) return;
// Take length from the lip animation, if any
float length = channel.lipAnim ? channel.lipAnim->length() : channel.anim->length();
// Advance time
channel.time = glm::min(length, channel.time + channel.properties.speed * dt);
// Clear transition flag if past transition time
if (channel.transition && channel.time >= channel.anim->transitionTime()) {
channel.transition = false;
}
// Signal events between previous and current time
for (auto &event : channel.anim->events()) {
if (event.time > channel.time && event.time <= channel.time) {
signalEvent(event.name);
}
}
// Loop or finish playing the animation
if (channel.time == length) {
bool loop = channel.properties.flags & AnimationFlags::loop;
if (loop) {
channel.time = 0.0f;
} else {
channel.finished = true;
}
}
// Compute animation states only when this model is not culled
if (!_culled) {
float time = channel.transition ? channel.anim->transitionTime() : channel.time;
channel.stateById.clear();
computeAnimationStates(channel, time, *_model->rootNode());
}
}
void ModelSceneNode::computeAnimationStates(AnimationChannel &channel, float time, const ModelNode &modelNode) {
shared_ptr<ModelNode> animNode(channel.anim->getNodeById(modelNode.id()));
if (animNode && _inanimateNodes.count(modelNode.id()) == 0) {
AnimationState state;
state.flags = 0;
glm::vec3 position(modelNode.restPosition());
glm::quat orientation(modelNode.restOrientation());
float scale = 1.0f;
if (channel.lipAnim) {
uint8_t leftShape, rightShape;
float factor;
if (channel.lipAnim->getKeyframes(time, leftShape, rightShape, factor)) {
glm::vec3 animPosition;
if (animNode->getPosition(leftShape, rightShape, factor, animPosition)) {
position += channel.properties.scale * animPosition;
state.flags |= AnimationStateFlags::transform;
}
glm::quat animOrientation;
if (animNode->getOrientation(leftShape, rightShape, factor, animOrientation)) {
orientation = move(animOrientation);
state.flags |= AnimationStateFlags::transform;
}
float animScale;
if (animNode->getScale(leftShape, rightShape, factor, animScale)) {
scale = animScale;
state.flags |= AnimationStateFlags::transform;
}
}
} else {
glm::vec3 animPosition;
if (animNode->position().getByTime(time, animPosition)) {
position += channel.properties.scale * animPosition;
state.flags |= AnimationStateFlags::transform;
}
glm::quat animOrientation;
if (animNode->orientation().getByTime(time, animOrientation)) {
orientation = move(animOrientation);
state.flags |= AnimationStateFlags::transform;
}
float animScale;
if (animNode->scale().getByTime(time, animScale)) {
scale = animScale;
state.flags |= AnimationStateFlags::transform;
}
}
if (state.flags & AnimationStateFlags::transform) {
state.transform *= glm::scale(glm::vec3(scale));
state.transform *= glm::translate(position);
state.transform *= glm::mat4_cast(orientation);
}
float animAlpha;
if (animNode->alpha().getByTime(time, animAlpha)) {
state.flags |= AnimationStateFlags::alpha;
state.alpha = animAlpha;
}
glm::vec3 animSelfIllum;
if (animNode->selfIllumColor().getByTime(time, animSelfIllum)) {
state.flags |= AnimationStateFlags::selfIllumColor;
state.selfIllumColor = move(animSelfIllum);
}
channel.stateById.insert(make_pair(modelNode.id(), move(state)));
}
for (auto &child : modelNode.children()) {
computeAnimationStates(channel, time, *child);
}
}
void ModelSceneNode::applyAnimationStates(const ModelNode &modelNode) {
auto maybeSceneNode = _nodeById.find(modelNode.id());
if (maybeSceneNode != _nodeById.end()) {
shared_ptr<SceneNode> sceneNode(maybeSceneNode->second);
AnimationState combined;
switch (_animBlendMode) {
case AnimationBlendMode::Single:
case AnimationBlendMode::Blend: {
bool blend = _animBlendMode == AnimationBlendMode::Blend && _animChannels[0].transition;
auto state1 = _animChannels[0].stateById.count(modelNode.id()) > 0 ? _animChannels[0].stateById[modelNode.id()] : AnimationState();
auto state2 = _animChannels[1].stateById.count(modelNode.id()) > 0 ? _animChannels[1].stateById[modelNode.id()] : AnimationState();
if (blend && state1.flags & AnimationStateFlags::transform && state2.flags & AnimationStateFlags::transform) {
float factor = glm::min(1.0f, _animChannels[0].time / _animChannels[0].anim->transitionTime());
glm::vec3 scale1, scale2, translation1, translation2, skew;
glm::quat orientation1, orientation2;
glm::vec4 perspective;
glm::decompose(state1.transform, scale1, orientation1, translation1, skew, perspective);
glm::decompose(state2.transform, scale2, orientation2, translation2, skew, perspective);
combined.flags |= AnimationStateFlags::transform;
combined.transform *= glm::scale(glm::mix(scale2, scale1, factor));
combined.transform *= glm::translate(glm::mix(translation2, translation1, factor));
combined.transform *= glm::mat4_cast(glm::slerp(orientation2, orientation1, factor));
} else if (state1.flags & AnimationStateFlags::transform) {
combined.flags |= AnimationStateFlags::transform;
combined.transform = state1.transform;
}
if (state1.flags & AnimationStateFlags::alpha) {
combined.flags |= AnimationStateFlags::alpha;
combined.alpha = state1.alpha;
}
if (state1.flags & AnimationStateFlags::selfIllumColor) {
combined.flags |= AnimationStateFlags::selfIllumColor;
combined.selfIllumColor = state1.selfIllumColor;
}
break;
}
case AnimationBlendMode::Overlay:
for (int i = 0; i < kNumAnimationChannels; ++i) {
auto maybeState = _animChannels[i].stateById.find(modelNode.id());
if (maybeState != _animChannels[i].stateById.end()) {
const AnimationState &state = maybeState->second;
if ((state.flags & AnimationStateFlags::transform) && !(combined.flags & AnimationStateFlags::transform)) {
combined.flags |= AnimationStateFlags::transform;
combined.transform = state.transform;
}
if ((state.flags & AnimationStateFlags::alpha) && !(combined.flags & AnimationStateFlags::alpha)) {
combined.flags |= AnimationStateFlags::alpha;
combined.alpha = state.alpha;
}
if ((state.flags & AnimationStateFlags::selfIllumColor) && !(combined.flags & AnimationStateFlags::selfIllumColor)) {
combined.flags |= AnimationStateFlags::selfIllumColor;
combined.selfIllumColor = state.selfIllumColor;
}
}
}
break;
default:
break;
}
if (combined.flags & AnimationStateFlags::transform) {
sceneNode->setLocalTransform(combined.transform);
} else if (!modelNode.isSkinMesh()) {
sceneNode->setLocalTransform(modelNode.localTransform());
}
if (combined.flags & AnimationStateFlags::alpha) {
static_pointer_cast<MeshSceneNode>(sceneNode)->setAlpha(combined.alpha);
}
if (combined.flags & AnimationStateFlags::selfIllumColor) {
static_pointer_cast<MeshSceneNode>(sceneNode)->setSelfIllumColor(combined.selfIllumColor);
}
}
for (auto &child : modelNode.children()) {
applyAnimationStates(*child);
}
}
void ModelSceneNode::computeBoneTransforms() {
for (auto &node : _nodeById) {
glm::mat4 transform(1.0f);
transform = node.second->absoluteTransform() * node.second->modelNode()->absoluteTransformInverse(); // make relative to the rest pose (world space)
transform = _absTransformInv * transform; // world space to model space
node.second->setBoneTransform(move(transform));
}
}
bool ModelSceneNode::isAnimationFinished() const {
return _animChannels[0].anim && _animChannels[0].finished;
}
} // namespace scene
} // namespace reone

View file

@ -15,29 +15,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "modelnodescenenode.h"
#include "glm/mat4x4.hpp"
#include "glm/vec3.hpp"
#include <stdexcept>
#include "../types.h"
using namespace std;
using namespace reone::graphics;
namespace reone {
namespace scene {
/**
* @see SceneNodeStateFlags
*/
struct SceneNodeState {
int flags { 0 };
glm::mat4 transform { 1.0f };
float alpha { 1.0f };
glm::vec3 selfIllumColor { 0.0f };
glm::vec3 lightColor { 1.0f };
float lightMultiplier { 1.0f };
float lightRadius { 1.0f };
};
ModelNodeSceneNode::ModelNodeSceneNode(shared_ptr<ModelNode> modelNode, SceneNodeType type, SceneGraph *sceneGraph) :
SceneNode(modelNode->name(), type, sceneGraph),
_modelNode(modelNode) {
if (!modelNode) {
throw invalid_argument("modelNode must not be null");
}
}
} // namespace scene

View file

@ -17,44 +17,28 @@
#pragma once
#include "glm/vec3.hpp"
#include "../../graphics/model/modelnode.h"
#include "scenenode.h"
namespace reone {
namespace scene {
class EmitterSceneNode;
class Particle {
class ModelNodeSceneNode : public SceneNode {
public:
Particle(glm::vec3 position, float velocity, EmitterSceneNode *emitter);
std::shared_ptr<graphics::ModelNode> modelNode() const { return _modelNode; }
const glm::mat4 &boneTransform() const { return _boneTransform; }
void update(float dt);
void setBoneTransform(glm::mat4 transform) { _boneTransform = std::move(transform); }
bool isExpired() const;
protected:
std::shared_ptr<graphics::ModelNode> _modelNode;
EmitterSceneNode *emitter() const { return _emitter; }
const glm::vec3 &position() const { return _position; }
float size() const { return _size; }
int frame() const { return _frame; }
const glm::vec3 &color() const { return _color; }
float alpha() const { return _alpha; }
ModelNodeSceneNode(std::shared_ptr<graphics::ModelNode> modelNode, SceneNodeType type, SceneGraph *sceneGraph);
private:
glm::vec3 _position;
float _velocity;
EmitterSceneNode *_emitter;
float _animLength { 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);
glm::mat4 _boneTransform { 1.0f }; /**< model space transform relative to the rest pose */
};
} // namespace scene

View file

@ -17,34 +17,50 @@
#include "scenenode.h"
#include <algorithm>
#include "glm/gtx/norm.hpp"
#include "../scenegraph.h"
using namespace std;
namespace reone {
namespace scene {
SceneNode::SceneNode(SceneNodeType type, SceneGraph *sceneGraph) : _type(type), _sceneGraph(sceneGraph) {
SceneNode::SceneNode(string name, SceneNodeType type, SceneGraph *sceneGraph) :
_name(move(name)),
_type(type),
_sceneGraph(sceneGraph) {
}
void SceneNode::addChild(const shared_ptr<SceneNode> &node) {
node->setParent(this);
void SceneNode::addChild(shared_ptr<SceneNode> node) {
node->_parent = this;
node->computeAbsoluteTransforms();
_children.push_back(node);
}
void SceneNode::computeAbsoluteTransforms() {
if (_parent) {
_absTransform = _parent->_absTransform * _localTransform;
} else {
_absTransform = _localTransform;
}
_absTransformInv = glm::inverse(_absTransform);
for (auto &child : _children) {
child->computeAbsoluteTransforms();
}
onAbsoluteTransformChanged();
}
void SceneNode::removeChild(SceneNode &node) {
auto maybeChild = find_if(
_children.begin(),
_children.end(),
[&node](const shared_ptr<SceneNode> &n) { return n.get() == &node; });
[&node](auto &n) { return n.get() == &node; });
if (maybeChild != _children.end()) {
node.setParent(nullptr);
node._parent = nullptr;
node.computeAbsoluteTransforms();
_children.erase(maybeChild);
}
}
@ -62,7 +78,7 @@ void SceneNode::draw() {
}
glm::vec3 SceneNode::getOrigin() const {
return glm::vec3(_absoluteTransform[3]);
return glm::vec3(_absTransform[3]);
}
float SceneNode::getDistanceTo(const glm::vec3 &point) const {
@ -81,36 +97,13 @@ float SceneNode::getDistanceTo2(const SceneNode &other) const {
return glm::distance2(getOrigin(), other.getOrigin());
}
void SceneNode::setParent(const SceneNode *parent) {
_parent = parent;
updateAbsoluteTransform();
glm::vec3 SceneNode::getWorldCenterOfAABB() const {
return _absTransform * glm::vec4(_aabb.center(), 1.0f);
}
void SceneNode::updateAbsoluteTransform() {
_absoluteTransform = _parent ? _parent->_absoluteTransform : glm::mat4(1.0f);
_absoluteTransform *= _localTransform;
_absoluteTransformInv = glm::inverse(_absoluteTransform);
for (auto &child : _children) {
child->updateAbsoluteTransform();
}
}
void SceneNode::setLocalTransform(const glm::mat4 &transform) {
_localTransform = transform;
updateAbsoluteTransform();
}
void SceneNode::setPosition(glm::vec3 position) {
setLocalTransform(glm::translate(glm::mat4(1.0f), position));
}
void SceneNode::setVisible(bool visible) {
_visible = visible;
}
void SceneNode::setTransparent(bool transparent) {
_transparent = transparent;
void SceneNode::setLocalTransform(glm::mat4 transform) {
_localTransform = move(transform);
computeAbsoluteTransforms();
}
} // namespace scene

View file

@ -18,11 +18,13 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <boost/noncopyable.hpp>
#include "glm/mat4x4.hpp"
#include "glm/vec3.hpp"
#include "../../graphics/aabb.h"
@ -32,96 +34,96 @@ namespace reone {
namespace scene {
constexpr float kDefaultDrawDistance = 1024.0f;
class SceneGraph;
class SceneNode : boost::noncopyable {
public:
void addChild(const std::shared_ptr<SceneNode> &node);
void addChild(std::shared_ptr<SceneNode> node);
void removeChild(SceneNode &node);
virtual void update(float dt);
virtual void draw();
bool isVisible() const { return _visible; }
virtual bool isTransparent() const { return _transparent; }
bool isVolumetric() const { return _volumetric; }
bool isCullable() const { return _cullable; }
bool isCulled() const { return _culled; }
bool isVolumetric() const { return _volumetric; }
virtual glm::vec3 getOrigin() const;
glm::vec3 getOrigin() const;
/**
* @return distance from the origin of this node to the point
* @return distance from the origin of this node to the specified point
*/
float getDistanceTo(const glm::vec3 &point) const;
/**
* @return squared distance from the origin of this node to the point
* @return squared distance from the origin of this node to the specified point
*/
float getDistanceTo2(const glm::vec3 &point) const;
/**
* @return shortest distance from the origin of this node to the other node
* @return distance between origins of this and the specified node
*/
float getDistanceTo(const SceneNode &other) const;
/**
* @return shortest distance (squared) from the origin of this node to the other node
* @return squared distance between origins of this and the specified node
*/
float getDistanceTo2(const SceneNode &other) const;
glm::vec3 getWorldCenterOfAABB() const;
const std::string &name() const { return _name; }
SceneNodeType type() const { return _type; }
const SceneNode *parent() const { return _parent; }
const glm::mat4 &localTransform() const { return _localTransform; }
const glm::mat4 &absoluteTransform() const { return _absoluteTransform; }
const glm::mat4 &absoluteTransformInverse() const { return _absoluteTransformInv; }
const graphics::AABB &aabb() const { return _aabb; }
const std::vector<std::shared_ptr<SceneNode>> &children() const { return _children; }
float drawDistance() const { return _drawDistance; }
const graphics::AABB &aabb() const { return _aabb; }
void setParent(const SceneNode *parent);
virtual void setLocalTransform(const glm::mat4 &transform);
void setPosition(glm::vec3 position);
virtual void setVisible(bool visible);
void setTransparent(bool transparent);
void setDrawDistance(float distance) { _drawDistance = distance; }
void setVisible(bool visible) { _visible = visible; }
void setCullable(bool cullable) { _cullable = cullable; }
void setCulled(bool culled) { _culled = culled; }
// Transformations
const glm::mat4 &localTransform() const { return _localTransform; }
const glm::mat4 &absoluteTransform() const { return _absTransform; }
const glm::mat4 &absoluteTransformInverse() const { return _absTransformInv; }
void setLocalTransform(glm::mat4 transform);
// END Transformations
protected:
std::string _name;
SceneNodeType _type;
SceneGraph *_sceneGraph;
graphics::AABB _aabb;
float _drawDistance { kDefaultDrawDistance };
bool _visible { true };
bool _transparent { false };
bool _volumetric { false }; /**< does this model have a bounding box, or is it a point? */
bool _cullable { false }; /**< can this model be frustum- or distance-culled? */
bool _culled { false }; /**< has this model been frustum- or distance-culled? */
// Relations
const SceneNode *_parent { nullptr };
std::vector<std::shared_ptr<SceneNode>> _children;
// END Relations
// Transformation matrices
// Transformations
glm::mat4 _localTransform { 1.0f };
glm::mat4 _absoluteTransform { 1.0f };
glm::mat4 _absoluteTransformInv { 1.0f };
glm::mat4 _absTransform { 1.0f };
glm::mat4 _absTransformInv { 1.0f };
// END Transformation matrices
// END Transformations
SceneNode(SceneNodeType type, SceneGraph *sceneGraph);
// Flags
virtual void updateAbsoluteTransform();
bool _visible { true };
bool _cullable { false }; /**< can this scene node be frustum- or distance-culled? */
bool _culled { false }; /**< has this scene node been frustum- or distance-culled? */
bool _volumetric { false }; /**< does this scene node have a bounding box? */
// END Flags
SceneNode(std::string name, SceneNodeType type, SceneGraph *sceneGraph);
void computeAbsoluteTransforms();
virtual void onAbsoluteTransformChanged() { }
};
} // namespace scene

View file

@ -1,110 +0,0 @@
/*
* 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 "particle.h"
#include <stdexcept>
#include "node/emitternode.h"
using namespace std;
using namespace reone::graphics;
namespace reone {
namespace scene {
Particle::Particle(glm::vec3 position, float velocity, EmitterSceneNode *emitter) :
_position(position),
_velocity(velocity),
_emitter(emitter) {
if (!emitter) {
throw invalid_argument("emitter must not be null");
}
init();
}
void Particle::init() {
shared_ptr<ModelNode::Emitter> emitter(_emitter->emitter());
if (emitter->fps > 0) {
_animLength = (emitter->frameEnd - emitter->frameStart + 1) / static_cast<float>(emitter->fps);
}
_renderOrder = emitter->renderOrder;
_frame = emitter->frameStart;
}
void Particle::update(float dt) {
shared_ptr<ModelNode::Emitter> emitter(_emitter->emitter());
if (emitter->lifeExpectancy != -1) {
_lifetime = glm::min(_lifetime + dt, static_cast<float>(emitter->lifeExpectancy));
} else if (_lifetime == _animLength) {
_lifetime = 0.0f;
} else {
_lifetime = glm::min(_lifetime + dt, _animLength);
}
if (!isExpired()) {
_position.z += _velocity * dt;
updateAnimation(dt);
}
}
template <class T>
static T interpolateConstraints(const ModelNode::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 Particle::updateAnimation(float dt) {
shared_ptr<ModelNode::Emitter> emitter(_emitter->emitter());
float maturity;
if (emitter->lifeExpectancy != -1) {
maturity = _lifetime / static_cast<float>(emitter->lifeExpectancy);
} else if (_animLength > 0.0f) {
maturity = _lifetime / _animLength;
} else {
maturity = 0.0f;
}
_frame = static_cast<int>(glm::ceil(emitter->frameStart + maturity * (emitter->frameEnd - emitter->frameStart)));
_size = interpolateConstraints(emitter->particleSize, maturity);
_color = interpolateConstraints(emitter->color, maturity);
_alpha = interpolateConstraints(emitter->alpha, maturity);
}
bool Particle::isExpired() const {
shared_ptr<ModelNode::Emitter> emitter(_emitter->emitter());
return emitter->lifeExpectancy != -1 && _lifetime >= emitter->lifeExpectancy;
}
} // namespace scene
} // namespace reone

View file

@ -76,12 +76,16 @@ void SceneGraph::update(float dt) {
void SceneGraph::cullRoots() {
for (auto &root : _roots) {
bool culled =
!root->isVisible() ||
root->getDistanceTo2(*_activeCamera) > root->drawDistance() * root->drawDistance() ||
(root->isCullable() && !_activeCamera->isInFrustum(*root));
if (root->type() != SceneNodeType::Model) continue;
root->setCulled(culled);
auto modelRoot = static_pointer_cast<ModelSceneNode>(root);
bool culled =
!modelRoot->isVisible() ||
modelRoot->getDistanceTo2(*_activeCamera) > modelRoot->drawDistance() * modelRoot->drawDistance() ||
(modelRoot->isCullable() && !_activeCamera->isInFrustum(*root));
modelRoot->setCulled(culled);
}
}
@ -89,7 +93,7 @@ void SceneGraph::updateLighting() {
_closestLights.clear();
if (_lightingRefNode) {
getLightsAt(*_lightingRefNode, _closestLights, kMaxLights, [](auto &light) { return !light.isAmbientOnly(); });
getLightsAt(*_lightingRefNode, _closestLights, kMaxLights, [](auto &light) { return !light.modelNode()->light()->ambientOnly; });
}
}
@ -127,7 +131,7 @@ void SceneGraph::refreshFromSceneNode(const std::shared_ptr<SceneNode> &node) {
}
break;
}
case SceneNodeType::ModelNode: {
case SceneNodeType::Mesh: {
// For model nodes, determine whether they should be rendered and cast shadows
auto modelNode = static_pointer_cast<MeshSceneNode>(node);
if (modelNode->shouldRender()) {
@ -168,7 +172,7 @@ void SceneGraph::refreshShadowLight() {
if (_lightingRefNode) {
vector<LightSceneNode *> lights;
getLightsAt(*_lightingRefNode, lights, 1, [](auto &light) { return light.isShadow(); });
getLightsAt(*_lightingRefNode, lights, 1, [](auto &light) { return light.modelNode()->light()->shadow; });
if (!lights.empty()) {
nextShadowLight = lights.front();
@ -221,11 +225,11 @@ void SceneGraph::prepareParticles() {
static glm::vec4 viewport(-1.0f, -1.0f, 1.0f, 1.0f);
// Extract particles from all emitters, sort them by depth
vector<pair<Particle *, float>> particlesZ;
vector<pair<EmitterSceneNode::Particle *, float>> particlesZ;
for (auto &emitter : _emitters) {
glm::mat4 modelView(_activeCamera->view() * emitter->absoluteTransform());
for (auto &particle : emitter->particles()) {
glm::vec3 screen(glm::project(particle->position(), modelView, _activeCamera->projection(), viewport));
glm::vec3 screen(glm::project(particle->position, modelView, _activeCamera->projection(), viewport));
if (screen.z >= 0.5f && glm::abs(screen.x) <= 1.0f && glm::abs(screen.y) <= 1.0f) {
particlesZ.push_back(make_pair(particle.get(), screen.z));
}
@ -238,18 +242,18 @@ void SceneGraph::prepareParticles() {
// Map (particle, Z) pairs to (emitter, particles)
_particles.clear();
EmitterSceneNode *emitter = nullptr;
vector<Particle *> emitterParticles;
vector<EmitterSceneNode::Particle *> emitterParticles;
for (auto &pair : particlesZ) {
if (pair.first->emitter() != emitter) {
if (pair.first->emitter != emitter) {
flushEmitterParticles(emitter, emitterParticles);
emitter = pair.first->emitter();
emitter = pair.first->emitter;
}
emitterParticles.push_back(pair.first);
}
flushEmitterParticles(emitter, emitterParticles);
}
void SceneGraph::flushEmitterParticles(EmitterSceneNode *emitter, vector<Particle *> &particles) {
void SceneGraph::flushEmitterParticles(EmitterSceneNode *emitter, vector<EmitterSceneNode::Particle *> &particles) {
if (emitter && !particles.empty()) {
_particles.push_back(make_pair(emitter, particles));
particles.clear();
@ -266,8 +270,8 @@ void SceneGraph::prepareGrass() {
float grassDistance2 = kMaxGrassDistance * kMaxGrassDistance;
for (auto &node : _grass) {
vector<GrassCluster> clusters;
vector<pair<GrassCluster, float>> clustersZ;
vector<GrassSceneNode::Cluster> clusters;
vector<pair<GrassSceneNode::Cluster, float>> clustersZ;
for (auto &cluster : node->clusters()) {
float distance2 = glm::distance2(cameraPos, cluster.position);
if (distance2 <= grassDistance2) {
@ -303,25 +307,11 @@ void SceneGraph::draw(bool shadowPass) {
return;
}
// Render opaque roots
for (auto &root : _roots) {
if (!root->isTransparent()) {
root->draw();
}
}
// Render opaque meshes
for (auto &mesh : _opaqueMeshes) {
mesh->drawSingle(false);
}
// Render transparent roots
for (auto &root : _roots) {
if (root->isTransparent()) {
root->draw();
}
}
// Render transparent meshes
for (auto &mesh : _transparentMeshes) {
mesh->drawSingle(false);
@ -340,10 +330,11 @@ void SceneGraph::draw(bool shadowPass) {
// Render lens flares
for (auto &light : _lights) {
// Ignore lights that are too far away or outside of camera frustum
if (_activeCamera->getDistanceTo2(*light) > light->flareRadius() * light->flareRadius() ||
float flareRadius = light->modelNode()->light()->flareRadius;
if (_activeCamera->getDistanceTo2(*light) > flareRadius * flareRadius ||
!_activeCamera->isInFrustum(light->absoluteTransform()[3])) continue;
for (auto &flare : light->flares()) {
for (auto &flare : light->modelNode()->light()->flares) {
light->drawLensFlares(flare);
}
}
@ -372,8 +363,11 @@ void SceneGraph::getLightsAt(
// Sort lights by priority and radius
sort(lights.begin(), lights.end(), [&distances](LightSceneNode *left, LightSceneNode *right) {
if (left->priority() < right->priority()) return true;
if (left->priority() > right->priority()) return false;
int leftPriority = left->modelNode()->light()->priority;
int rightPriority = right->modelNode()->light()->priority;
if (leftPriority < rightPriority) return true;
if (leftPriority > rightPriority) return false;
float leftDistance = distances.find(left)->second;
float rightDistance = distances.find(right)->second;

View file

@ -29,20 +29,16 @@
#include "../graphics/shader/shaders.h"
#include "../graphics/types.h"
#include "grasscluster.h"
#include "node/cameranode.h"
#include "node/emitternode.h"
#include "node/grassnode.h"
#include "node/lightnode.h"
#include "node/meshnode.h"
namespace reone {
namespace scene {
class CameraSceneNode;
class EmitterSceneNode;
class GrassSceneNode;
class LightSceneNode;
class MeshSceneNode;
class Particle;
class SceneNode;
/**
* Responsible for managing drawable objects and their relations.
*
@ -125,8 +121,8 @@ private:
std::vector<LightSceneNode *> _lights;
std::vector<EmitterSceneNode *> _emitters;
std::vector<GrassSceneNode *> _grass;
std::vector<std::pair<EmitterSceneNode *, std::vector<Particle *>>> _particles;
std::vector<std::pair<GrassSceneNode *, std::vector<GrassCluster>>> _grassClusters;
std::vector<std::pair<EmitterSceneNode *, std::vector<EmitterSceneNode::Particle *>>> _particles;
std::vector<std::pair<GrassSceneNode *, std::vector<GrassSceneNode::Cluster>>> _grassClusters;
uint32_t _textureId { 0 };
bool _updateRoots { true };
@ -164,7 +160,7 @@ private:
void prepareParticles();
void prepareGrass();
inline void flushEmitterParticles(EmitterSceneNode *emitter, std::vector<Particle *> &particles);
inline void flushEmitterParticles(EmitterSceneNode *emitter, std::vector<EmitterSceneNode::Particle *> &particles);
};
} // namespace scene

View file

@ -22,10 +22,10 @@ namespace reone {
namespace scene {
enum class SceneNodeType {
Mesh,
Model,
ModelNode,
Dummy,
Camera,
Model,
Mesh,
Light,
Emitter,
Grass
@ -39,15 +39,14 @@ enum class ModelUsage {
Door,
Equipment,
Projectile,
Other
Camera
};
struct AnimationFlags {
static constexpr int loop = 1;
static constexpr int blend = 2; /**< blend previous animation into the next one */
static constexpr int overlay = 4; /**< overlay next animation on top of the previous one */
static constexpr int propagateHead = 8; /**< propagate animation to the head model, if any */
static constexpr int syncLipAnim = 0x10; /**< animation must be synchronized with the lip animation */
static constexpr int propagate = 8; /**< propagate animation to attached models */
static constexpr int loopOverlay = loop | overlay;
static constexpr int loopBlend = loop | blend;