Major refactoring of the scene management system
This commit is contained in:
parent
aeffc4b453
commit
740440ca78
76 changed files with 1314 additions and 1889 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lipanimation.h"
|
||||
#include "animation.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#include "../../resource/format/binreader.h"
|
||||
|
||||
#include "lipanimation.h"
|
||||
#include "animation.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
#include "../../common/cache.h"
|
||||
|
||||
#include "lipanimation.h"
|
||||
#include "animation.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include "lipanimation.h"
|
||||
#include "animation.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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()));
|
||||
|
|
|
@ -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
|
||||
|
|
93
src/engine/scene/node/emitternode_particle.cpp
Normal file
93
src/engine/scene/node/emitternode_particle.cpp
Normal 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
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
336
src/engine/scene/node/modelnode_animation.cpp
Normal file
336
src/engine/scene/node/modelnode_animation.cpp
Normal 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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue