Skip to content

Commit ad4143e

Browse files
authored
Refactor Node hierarchy, implement animations (#74)
* Refactor Node hierarchy; add animations * CI: update repos before installing packages * Replace class Node with struct Node * Cleanup * Fix animation timeline - Per BoxAnimated in GLTF samples, each animation must have a single timeline, and channels shorter than the maximum duration stay locked at the last keyframe until the timeline resets. * Incorporate Step interpolation
1 parent a6c1813 commit ad4143e

File tree

22 files changed

+325
-227
lines changed

22 files changed

+325
-227
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
steps:
77
- uses: actions/checkout@v2
88
- name: init
9-
run: sudo apt install -yqq ninja-build xorg-dev g++-11
9+
run: sudo apt update -yqq && sudo apt install -yqq ninja-build xorg-dev g++-11
1010
- name: configure gcc
1111
run: cmake -S . --preset=default -B build -DCMAKE_CXX_COMPILER=g++-11 -DFACADE_BUILD_SHADERS=OFF
1212
- name: configure clang

lib/engine/include/facade/engine/editor/drag_drop_id.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,14 @@ void make_id_slot(Id<T>& out_id, char const* label, char const* payload_name) {
9595
template <typename T>
9696
void make_id_slot(Node& out_node, char const* label, char const* payload_name) {
9797
auto oid = std::optional<Id<T>>{};
98-
auto* id = out_node.find<Id<T>>();
98+
auto id = out_node.find<T>();
9999
if (id) { oid = *id; }
100100
make_id_slot(oid, label, payload_name, {true});
101101
if (id) {
102102
if (!oid) {
103-
out_node.detach<Id<T>>();
103+
out_node.detach<T>();
104104
} else {
105-
*id = *oid;
105+
out_node.attach<T>(*oid);
106106
}
107107
} else {
108108
if (oid) { out_node.attach(*oid); }

lib/engine/include/facade/engine/editor/scene_tree.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22
#include <facade/engine/editor/common.hpp>
33
#include <facade/scene/id.hpp>
44
#include <facade/util/fixed_string.hpp>
5+
#include <optional>
56

67
namespace facade {
78
class Scene;
8-
class Node;
9+
struct Node;
910

1011
namespace editor {
1112
///
1213
/// \brief Target Node to inspect (driven by SceneTree)
1314
///
1415
struct InspectNode {
1516
FixedString<128> name{"[Node]###Node"};
16-
Id<Node> id{};
17+
std::optional<Id<Node>> id{};
1718

18-
explicit operator bool() const { return id > Id<Node>{}; }
19+
explicit operator bool() const { return id.has_value(); }
1920
};
2021

2122
///

lib/engine/include/facade/engine/scene_renderer.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class SceneRenderer {
1818
std::span<glm::mat4x4 const> make_instances(Node const& node, glm::mat4x4 const& parent) const;
1919

2020
void render(Renderer& renderer, vk::CommandBuffer cb, Skybox const& skybox) const;
21-
void render(Renderer& renderer, vk::CommandBuffer cb, Node const& node, glm::mat4 const& parent = matrix_identity_v) const;
21+
void render(Renderer& renderer, vk::CommandBuffer cb, Node const& node, glm::mat4 parent = matrix_identity_v) const;
2222

2323
Gfx m_gfx;
2424
Sampler m_sampler;

lib/engine/src/editor/inspector.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ void ResourceInspector::edit(Mesh& out_mesh, Id<Mesh> id) const {
9898

9999
void SceneInspector::camera() const {
100100
auto& node = m_scene.camera();
101-
auto* id = node.find<Id<Camera>>();
101+
auto id = node.find<Camera>();
102102
assert(id);
103103

104104
if (auto combo = Combo{"Select", FixedString{"{} ({})", node.name, *id}.c_str()}) {
105-
for (auto const& cid : m_scene.cameras()) {
106-
auto const* node = m_scene.find(cid);
105+
for (auto cid : m_scene.cameras()) {
106+
auto const* node = m_resources.nodes.find(cid);
107107
assert(node);
108-
auto const* cam = node->find<Id<Camera>>();
108+
auto cam = node->find<Camera>();
109109
assert(cam);
110110
if (combo.item(FixedString{"{} ({})", node->name, *cam}.c_str(), {cid == *id})) {
111111
if (!m_scene.select_camera(cid)) { logger::warn("[Inspector] Failed to select camera: [{}]", cid); }
@@ -189,7 +189,7 @@ void SceneInspector::instances(Node& out_node, Bool unified_scaling) const {
189189
}
190190

191191
void SceneInspector::mesh(Node& out_node) const {
192-
auto* mesh_id = out_node.find<Id<Mesh>>();
192+
auto mesh_id = out_node.find<Mesh>();
193193
if (auto tn = TreeNode{"Mesh", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed}) {
194194
auto name = FixedString<128>{};
195195
if (mesh_id) { name = FixedString<128>{"{} ({})", m_resources.meshes.find(*mesh_id)->name, *mesh_id}; }
@@ -198,7 +198,7 @@ void SceneInspector::mesh(Node& out_node) const {
198198
}
199199

200200
void SceneInspector::node(Id<Node> node_id, Bool& out_unified_scaling) const {
201-
auto* node = m_scene.find(node_id);
201+
auto* node = m_resources.nodes.find(node_id);
202202
if (!node) { return; }
203203
transform(*node, out_unified_scaling);
204204
instances(*node, out_unified_scaling);

lib/engine/src/editor/scene_tree.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@ namespace {
77
FixedString<128> node_name(Node const& node) {
88
auto ret = FixedString<128>{node.name};
99
if (ret.empty()) { ret = "(Unnamed)"; }
10-
ret += FixedString{" ({})", node.id()};
10+
ret += FixedString{" ({})", node.self};
1111
return ret;
1212
}
1313

1414
InspectNode inspect(Node const& node) {
1515
auto ret = InspectNode{};
16-
ret.id = node.id();
16+
ret.id = node.self;
1717
ret.name = node_name(node);
1818
ret.name += FixedString{"###Node"};
1919
return ret;
2020
}
2121

22-
void walk(Node const& camera, Node& node, InspectNode& inout) {
22+
void walk(Scene& out_scene, Node& node, InspectNode& inout) {
2323
auto flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
24-
if (node.id() == inout.id) { flags |= ImGuiTreeNodeFlags_Selected; }
25-
if (node.children().empty()) {
24+
if (node.self == inout.id) { flags |= ImGuiTreeNodeFlags_Selected; }
25+
if (node.children.empty()) {
2626
flags |= ImGuiTreeNodeFlags_Leaf;
27-
if (&node == &camera && node.attachment_count() == 1) { return; }
27+
if (&node == &out_scene.camera() && node.attachments.size() == 1) { return; }
2828
}
2929
auto tn = editor::TreeNode{node_name(node).c_str(), flags};
3030
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
@@ -33,15 +33,17 @@ void walk(Node const& camera, Node& node, InspectNode& inout) {
3333
inout = {};
3434
}
3535
if (tn) {
36-
for (auto& child : node.children()) { walk(camera, child, inout); }
36+
for (auto child : node.children) { walk(out_scene, *out_scene.resources().nodes.find(child), inout); }
3737
}
3838
}
3939
} // namespace
4040

4141
bool SceneTree::render(NotClosed<Window>, InspectNode& inout) const {
4242
auto const in = inout.id;
43-
if (!m_scene.find(in)) { inout = {}; }
44-
for (auto& root : m_scene.roots()) { walk(m_scene.camera(), root, inout); }
43+
auto& nodes = m_scene.resources().nodes;
44+
auto* node = in ? nodes.find(*inout.id) : nullptr;
45+
if (!node) { inout = {}; }
46+
for (auto id : m_scene.roots()) { walk(m_scene, *nodes.find(id), inout); }
4547
return inout.id != in;
4648
}
4749
} // namespace facade::editor

lib/engine/src/engine.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ auto Engine::poll() -> State const& {
291291
// ImGui wants all widget calls within BeginFrame() / EndFrame(), so begin here
292292
m_impl->window.gui->new_frame();
293293
m_impl->window.window.get().glfw->poll_events();
294-
return m_impl.get()->window.window.get().state();
294+
auto const& ret = m_impl.get()->window.window.get().state();
295+
m_impl->scene.tick(ret.dt);
296+
return ret;
295297
}
296298

297299
void Engine::render() {

lib/engine/src/scene_renderer.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ void SceneRenderer::render(Scene const& scene, Ptr<Skybox const> skybox, Rendere
2828
m_scene = &scene;
2929
write_view(renderer.framebuffer_extent());
3030
if (skybox) { render(renderer, cb, *skybox); }
31-
for (auto const& node : m_scene->roots()) { render(renderer, cb, node); }
31+
for (auto const& node : m_scene->roots()) { render(renderer, cb, m_scene->resources().nodes[node]); }
3232
}
3333

3434
void SceneRenderer::write_view(glm::vec2 const extent) {
3535
auto const& cam_node = m_scene->camera();
36-
auto const* cam_id = cam_node.find<Id<Camera>>();
36+
auto const cam_id = cam_node.find<Camera>();
3737
assert(cam_id);
3838
auto const& cam = m_scene->resources().cameras[*cam_id];
3939
struct {
@@ -83,9 +83,9 @@ void SceneRenderer::render(Renderer& renderer, vk::CommandBuffer cb, Skybox cons
8383
renderer.draw(pipeline, skybox.static_mesh(), {&instance, 1});
8484
}
8585

86-
void SceneRenderer::render(Renderer& renderer, vk::CommandBuffer cb, Node const& node, glm::mat4 const& parent) const {
86+
void SceneRenderer::render(Renderer& renderer, vk::CommandBuffer cb, Node const& node, glm::mat4 parent) const {
8787
auto const& resources = m_scene->resources();
88-
if (auto const* mesh_id = node.find<Id<Mesh>>()) {
88+
if (auto mesh_id = node.find<Mesh>()) {
8989
static auto const s_default_material = Material{std::make_unique<LitMaterial>()};
9090
auto const& mesh = resources.meshes[*mesh_id];
9191
for (auto const& primitive : mesh.primitives) {
@@ -103,6 +103,7 @@ void SceneRenderer::render(Renderer& renderer, vk::CommandBuffer cb, Node const&
103103
}
104104
}
105105

106-
for (auto const& child : node.m_children) { render(renderer, cb, child, parent * node.transform.matrix()); }
106+
parent = parent * node.transform.matrix();
107+
for (auto const& id : node.children) { render(renderer, cb, m_scene->resources().nodes[id], parent); }
107108
}
108109
} // namespace facade

lib/ext/src.zip

13 Bytes
Binary file not shown.

lib/scene/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ if(${${target_prefix_upper}_PCH})
3333
endif()
3434

3535
target_sources(${PROJECT_NAME} PRIVATE
36+
include/${target_prefix}/scene/animation.hpp
37+
include/${target_prefix}/scene/animator.hpp
3638
include/${target_prefix}/scene/camera.hpp
3739
include/${target_prefix}/scene/fly_cam.hpp
3840
include/${target_prefix}/scene/gltf_loader.hpp
3941
include/${target_prefix}/scene/id.hpp
42+
include/${target_prefix}/scene/interpolator.hpp
4043
include/${target_prefix}/scene/lights.hpp
4144
include/${target_prefix}/scene/load_status.hpp
4245
include/${target_prefix}/scene/material.hpp
@@ -47,6 +50,7 @@ target_sources(${PROJECT_NAME} PRIVATE
4750
include/${target_prefix}/scene/scene_resources.hpp
4851
include/${target_prefix}/scene/scene.hpp
4952

53+
src/animator.cpp
5054
src/camera.cpp
5155
src/gltf_loader.cpp
5256
src/material.cpp
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
#include <facade/scene/animator.hpp>
3+
#include <facade/scene/id.hpp>
4+
5+
namespace facade {
6+
struct Animation {
7+
Animator animator{};
8+
Id<Node> target{};
9+
};
10+
} // namespace facade
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
#include <facade/scene/interpolator.hpp>
3+
4+
namespace facade {
5+
struct Node;
6+
7+
struct Animator {
8+
Interpolator<glm::vec3> translation{};
9+
Interpolator<glm::quat> rotation{};
10+
Interpolator<glm::vec3> scale{};
11+
12+
float elapsed{};
13+
14+
void update(Node& out_node, float dt);
15+
};
16+
} // namespace facade
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#pragma once
2+
#include <facade/util/logger.hpp>
3+
#include <facade/util/transform.hpp>
4+
#include <optional>
5+
6+
namespace facade {
7+
constexpr glm::vec3 lerp(glm::vec3 const& a, glm::vec3 const& b, float const t) {
8+
return {std::lerp(a.x, b.x, t), std::lerp(a.y, b.y, t), std::lerp(a.z, b.z, t)};
9+
}
10+
11+
inline glm::quat lerp(glm::quat const& a, glm::quat const& b, float const t) { return glm::slerp(a, b, t); }
12+
13+
enum class Interpolation {
14+
eLinear,
15+
eStep,
16+
eCubic,
17+
};
18+
19+
template <typename T>
20+
struct Interpolator {
21+
struct Keyframe {
22+
T value{};
23+
float timestamp{};
24+
};
25+
26+
std::vector<Keyframe> keyframes{};
27+
Interpolation interpolation{};
28+
29+
float duration() const { return keyframes.empty() ? 0.0f : keyframes.back().timestamp; }
30+
31+
std::optional<std::size_t> index_for(float time) const {
32+
if (keyframes.empty()) { return {}; }
33+
34+
auto const it = std::lower_bound(keyframes.begin(), keyframes.end(), time, [](Keyframe const& k, float time) { return k.timestamp < time; });
35+
if (it == keyframes.end()) { return {}; }
36+
return static_cast<std::size_t>(it - keyframes.begin());
37+
}
38+
39+
std::optional<T> operator()(float elapsed) const {
40+
if (keyframes.empty()) { return {}; }
41+
42+
auto const i_next = index_for(elapsed);
43+
if (!i_next) { return keyframes.back().value; }
44+
45+
assert(*i_next < keyframes.size());
46+
auto const& next = keyframes[*i_next];
47+
assert(elapsed <= next.timestamp);
48+
if (*i_next == 0) { return next.value; }
49+
50+
auto const& prev = keyframes[*i_next - 1];
51+
assert(prev.timestamp < elapsed);
52+
if (interpolation == Interpolation::eStep) { return prev.value; }
53+
54+
auto const t = (elapsed - prev.timestamp) / (next.timestamp - prev.timestamp);
55+
using facade::lerp;
56+
using std::lerp;
57+
return lerp(prev.value, next.value, t);
58+
}
59+
};
60+
} // namespace facade

0 commit comments

Comments
 (0)