Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/animation/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@

add_example(NAME "simple_skinning" LINK_ASSIMP)
add_example(NAME "assimp_bones" LINK_ASSIMP)
118 changes: 118 additions & 0 deletions examples/animation/assimp_bones.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include "threepp/animation/AnimationMixer.hpp"
#include "threepp/helpers/SkeletonHelper.hpp"
#include "threepp/loaders/AssimpLoader.hpp"
#include "threepp/materials/LineBasicMaterial.hpp"
#include "threepp/threepp.hpp"

using namespace threepp;

int main() {

Canvas canvas("Simple skinning", {{"aa", 8}});
GLRenderer renderer(canvas.size());
renderer.shadowMap().enabled = true;
renderer.shadowMap().type = threepp::ShadowMap::PFCSoft;

PerspectiveCamera camera(45, canvas.aspect(), 0.1, 10000);
camera.position.set(0, 6, -10);

Scene scene;
scene.background = Color(0xa0a0a0);
scene.fog = Fog(0xa0a0a0, 70, 100);

// ground

auto geometry = PlaneGeometry::create(500, 500);
auto material = MeshPhongMaterial::create({{"color", 0x999999},
{"depthWrite", false}});

auto ground = Mesh::create(geometry, material);
ground->rotation.x = -math::PI / 2;
ground->receiveShadow = true;
scene.add(ground);

auto grid = GridHelper::create(500, 100, 0x000000, 0x000000);
grid->material()->opacity = 0.2;
grid->material()->transparent = true;
scene.add(grid);

// lights

auto hemiLight = HemisphereLight::create(0xffffff, 0x444444, 0.6f);
hemiLight->position.set(0, 200, 0);
scene.add(hemiLight);

auto dirLight = DirectionalLight::create(0xffffff, 0.8f);
dirLight->position.set(0, 20, 10);
dirLight->castShadow = true;
scene.add(dirLight);

//

AssimpLoader loader;

auto soldier = loader.load("data/models/gltf/Soldier.glb");
soldier->traverseType<Mesh>([](Mesh& m) {
m.receiveShadow = true;
m.castShadow = true;
});
scene.add(soldier);
soldier->scale *= 2;
soldier->position.x = -2;

auto skeletonHelperSoldier = SkeletonHelper::create(*soldier);
skeletonHelperSoldier->material()->as<LineBasicMaterial>()->linewidth = 2;
scene.add(skeletonHelperSoldier);

//

auto stormTropper = loader.load("data/models/collada/stormtrooper/stormtrooper.dae");
stormTropper->traverseType<Mesh>([](Mesh& m) {
m.receiveShadow = true;
m.castShadow = true;

if (auto mat = m.material()->as<MaterialWithMap>()) {
mat->map->wrapS = TextureWrapping::Repeat;
mat->map->wrapT = TextureWrapping::Repeat;
}
});
scene.add(stormTropper);
stormTropper->scale *= 0.6;
stormTropper->position.x = 2;

auto skeletonHelperTrooper = SkeletonHelper::create(*stormTropper);
skeletonHelperTrooper->material()->as<LineBasicMaterial>()->linewidth = 2;
scene.add(skeletonHelperTrooper);

//

OrbitControls controls{camera, canvas};
controls.minDistance = 2;
controls.maxDistance = 20;

//

canvas.onWindowResize([&](WindowSize size) {
camera.aspect = size.aspect();
camera.updateProjectionMatrix();
renderer.setSize(size);
});

auto solderMixer = AnimationMixer(*soldier);
solderMixer.clipAction(soldier->animations.back())->setLoop(Loop::Repeat).play();

auto trooperMixer = AnimationMixer(*stormTropper);
trooperMixer.clipAction(stormTropper->animations.front())->setLoop(Loop::Repeat).play();


Clock clock;
canvas.animate([&] {
renderer.render(scene, camera);

const auto dt = clock.getDelta();

solderMixer.update(dt);
trooperMixer.update(dt);

});
}
52 changes: 15 additions & 37 deletions examples/animation/simple_skinning.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "threepp/animation/AnimationMixer.hpp"
#include "threepp/helpers/SkeletonHelper.hpp"
#include "threepp/loaders/AssimpLoader.hpp"
#include "threepp/materials/LineBasicMaterial.hpp"
Expand All @@ -13,7 +14,7 @@ int main() {
renderer.shadowMap().type = ShadowMap::PFCSoft;

PerspectiveCamera camera(45, canvas.aspect(), 0.1, 10000);
camera.position.set(0, 6, -10);
camera.position.set(0, 6, -15);

Scene scene;
scene.background = Color(0xa0a0a0);
Expand Down Expand Up @@ -50,38 +51,21 @@ int main() {

AssimpLoader loader;

auto soldier = loader.load("data/models/gltf/Soldier.glb");
soldier->traverseType<Mesh>([](Mesh& m) {
m.receiveShadow = true;
m.castShadow = true;
});
scene.add(soldier);
soldier->scale *= 2;
soldier->position.x = -2;

auto skeletonHelperSoldier = SkeletonHelper::create(*soldier);
skeletonHelperSoldier->material()->as<LineBasicMaterial>()->linewidth = 2;
scene.add(skeletonHelperSoldier);

//
auto model = loader.load("data/models/gltf/SimpleSkinning.gltf");
model->traverseType<SkinnedMesh>([](auto& m) {

auto stormTrooper = loader.load("data/models/collada/stormtrooper/stormtrooper.dae");
stormTrooper->traverseType<Mesh>([](Mesh& m) {
m.receiveShadow = true;
m.castShadow = true;

if (auto mat = m.material()->as<MaterialWithMap>()) {
mat->map->wrapS = TextureWrapping::Repeat;
mat->map->wrapT = TextureWrapping::Repeat;
}
});
scene.add(stormTrooper);
stormTrooper->scale *= 0.6;
stormTrooper->position.x = 2;
scene.add(model);

auto mixer = AnimationMixer(*model);
mixer.clipAction(AnimationClip::findByName(model->animations, "Take 01"))->play();

auto skeletonHelperTrooper = SkeletonHelper::create(*stormTrooper);
skeletonHelperTrooper->material()->as<LineBasicMaterial>()->linewidth = 2;
scene.add(skeletonHelperTrooper);
auto skeletonHelper = SkeletonHelper::create(*model);
skeletonHelper->material()->as<LineBasicMaterial>()->linewidth = 2;
scene.add(skeletonHelper);

//

Expand All @@ -97,19 +81,13 @@ int main() {
renderer.setSize(size);
});

auto& soldierBones = skeletonHelperSoldier->getBones();
auto& trooperBones = skeletonHelperTrooper->getBones();

Clock clock;
canvas.animate([&] {
renderer.render(scene, camera);
const auto dt = clock.getDelta();

auto time = clock.getElapsedTime();
for (auto i = 0; i < soldierBones.size(); i++) {
soldierBones[i]->rotation.y = std::sin(time) * 5 / float(soldierBones.size());
}
for (auto i = 0; i < trooperBones.size(); i++) {
trooperBones[i]->rotation.y = std::sin(time) * 5 / float(trooperBones.size());
}
mixer.update(dt);

renderer.render(scene, camera);
});
}
134 changes: 134 additions & 0 deletions include/threepp/animation/AnimationAction.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@

#ifndef THREEPP_ANIMATIONACTION_HPP
#define THREEPP_ANIMATIONACTION_HPP

#include "threepp/animation/AnimationClip.hpp"
#include "threepp/animation/AnimationMixer.hpp"

#include "threepp/math/Interpolant.hpp"

#include "PropertyMixer.hpp"
#include "threepp/constants.hpp"

namespace threepp {

class AnimationMixer;

class AnimationAction: public std::enable_shared_from_this<AnimationAction> {

public:
AnimationBlendMode blendMode;

AnimationAction(AnimationMixer& mixer,
const std::shared_ptr<AnimationClip>& clip,
Object3D* localRoot = nullptr,
std::optional<AnimationBlendMode> blendMode = std::nullopt);

// State & Scheduling

AnimationAction& play();

AnimationAction& stop();

AnimationAction& reset();

bool isRunning();

// return true when play has been called
bool isScheduled();

AnimationAction& startAt(float time);

AnimationAction& setLoop(Loop mode, int repetitions = -1);

// Weight

// set the weight stopping any scheduled fading
// although .enabled = false yields an effective weight of zero, this
// method does *not* change .enabled, because it would be confusing
AnimationAction& setEffectiveWeight(float weight);

// return the weight considering fading and .enabled
[[nodiscard]] float getEffectiveWeight() const;

AnimationAction& stopFading();

// Time Scale Control

// set the time scale stopping any scheduled warping
// although .paused = true yields an effective time scale of zero, this
// method does *not* change .paused, because it would be confusing
AnimationAction& setEffectiveTimeScale(float timeScale);

// return the time scale considering warping and .paused
float getEffectiveTimeScale() const;

AnimationAction& setDuration(float duration);

AnimationAction& syncWith(AnimationAction& action);

AnimationAction& halt(float duration);

AnimationAction& warp(float startTimeScale, float endTimeScale, float duration);

AnimationAction& stopWarping();


private:
AnimationMixer& _mixer;
std::shared_ptr<AnimationClip> _clip;
Object3D* _localRoot;

InterpolantSettings _interpolantSettings;
std::vector<std::shared_ptr<Interpolant>> _interpolants;

std::vector<std::shared_ptr<PropertyMixer>> _propertyBindings;

std::optional<size_t> _cacheIndex; // for the memory manager
std::optional<size_t> _byClipCacheIndex;// for the memory manager

std::shared_ptr<Interpolant> _timeScaleInterpolant;
std::shared_ptr<Interpolant> _weightInterpolant;

Loop loop{Loop::Repeat};
int _loopCount = -1;

// global mixer time when the action is to be started
// it's set back to 'null' upon start of the action
std::optional<float> _startTime;

// scaled local time of the action
// gets clamped or wrapped to 0..clip.duration according to loop
float time = 0;

float timeScale = 1;
float _effectiveTimeScale = 1;

float weight = 1;
float _effectiveWeight = 1;

int repetitions = -1;// no. of repetitions when looping

bool paused = false;// true -> zero effective time scale
bool enabled = true;// false -> zero effective weight

bool clampWhenFinished = false;// keep feeding the last frame?

bool zeroSlopeAtStart = true;// for smooth interpolation w/o separate
bool zeroSlopeAtEnd = true; // clips for start, loop and end

void _update(float time, float deltaTime, int timeDirection, int accuIndex);

float _updateWeight(float time);

float _updateTimeScale(float time);

float _updateTime(float deltaTime);

void _setEndings(bool atStart, bool atEnd, bool pingPong);

friend class AnimationMixer;
};
}// namespace threepp

#endif//THREEPP_ANIMATIONACTION_HPP
46 changes: 46 additions & 0 deletions include/threepp/animation/AnimationClip.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

#ifndef THREEPP_ANIMATIONCLIP_HPP
#define THREEPP_ANIMATIONCLIP_HPP

#include "threepp/animation/KeyframeTrack.hpp"
#include "threepp/math/MathUtils.hpp"

#include <memory>
#include <vector>

namespace threepp {

class Object3D;
class AnimationAction;

class AnimationClip {

public:
AnimationBlendMode blendMode;

explicit AnimationClip(std::string name,
float duration = 1,
const std::vector<std::shared_ptr<KeyframeTrack>>& tracks = {},
AnimationBlendMode blendMode = AnimationBlendMode::Normal);

[[nodiscard]] std::string uuid() const;

void resetDuration();

static std::shared_ptr<AnimationClip> findByName(Object3D* object, const std::string& name);
static std::shared_ptr<AnimationClip> findByName(const std::vector<std::shared_ptr<AnimationClip>>& clipArray, const std::string& name);

private:
std::string uuid_{math::generateUUID()};

std::string name;
std::vector<std::shared_ptr<KeyframeTrack>> tracks;
float duration;

friend class AnimationAction;
friend class AnimationMixer;
};

}// namespace threepp

#endif//THREEPP_ANIMATIONCLIP_HPP
Loading
Loading