Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7c363bf
Add Matrix4 and Quaternion to Core; rebuild Transform on native math
jayrulez Jul 13, 2026
047a3ff
Remove bgfx/bx/bimg and the rendering/scene stack
jayrulez Jul 13, 2026
bc5bce2
Add backend-agnostic RHI interface module (rhi)
jayrulez Jul 5, 2026
01941c3
Add Null RHI backend (rhi.null)
jayrulez Jul 5, 2026
322d4f2
Add RHI validation backend
jayrulez Jul 5, 2026
7342c39
Add Vulkan RHI backend
jayrulez Jul 5, 2026
1f1e3cd
Add DX12 RHI backend
jayrulez Jul 5, 2026
59635c2
Fix Windows build: DX12 casing + ambiguous cos in math test
jayrulez Jul 5, 2026
90d7525
Add shaders module: HLSL compilation via DXC
jayrulez Jul 5, 2026
f3a20a8
Add RHI sample framework
jayrulez Jul 5, 2026
5a88af2
Add RHI samples
jayrulez Jul 5, 2026
06bd602
Vendor DXC runtime libraries
jayrulez Jul 5, 2026
ca8006e
Add shader variant system
jayrulez Jul 5, 2026
80cf439
Add graphics render host module
jayrulez Jul 5, 2026
c0877db
Add render graph module
jayrulez Jul 5, 2026
0051742
Add Color and Color32 to core
jayrulez Jul 5, 2026
0f99e98
Remove the image loader from core
jayrulez Jul 5, 2026
3c45e15
Add image module
jayrulez Jul 5, 2026
a49c831
Add texture module
jayrulez Jul 5, 2026
b489ddb
Add packed vectors and AABB to core math
jayrulez Jul 5, 2026
aab6064
Add geometry module: runtime mesh format
jayrulez Jul 5, 2026
4181e1c
Add materials module: data-driven material model
jayrulez Jul 6, 2026
b594723
Vendor ufbx and cgltf model-loader libraries
jayrulez Jul 6, 2026
f961e55
Add model module: import representation + FBX/GLTF loaders
jayrulez Jul 6, 2026
40c5a71
Add profiler module: hierarchical CPU scope profiler
jayrulez Jul 6, 2026
8c22b93
Add easing functions to core math
jayrulez Jul 6, 2026
d25abf3
Add animation module: skeletal-animation foundation
jayrulez Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 1 addition & 10 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
[submodule "Engine/cpp/ThirdParty/bx"]
path = Engine/cpp/ThirdParty/bx
url = https://github.com/bkaradzic/bx
[submodule "Engine/cpp/ThirdParty/bimg"]
path = Engine/cpp/ThirdParty/bimg
url = https://github.com/bkaradzic/bimg
[submodule "Engine/cpp/ThirdParty/bgfx"]
path = Engine/cpp/ThirdParty/bgfx
url = https://github.com/bkaradzic/bgfx
[submodule "Engine/cpp/ThirdParty/sdl"]
[submodule "Engine/cpp/thirdparty/sdl"]
path = Engine/cpp/ThirdParty/sdl
url = https://github.com/libsdl-org/SDL
25 changes: 1 addition & 24 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ project(DraconicEngine LANGUAGES C CXX)

find_package(Git QUIET)
find_package(Threads REQUIRED)
find_package(Vulkan REQUIRED)

if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
message(STATUS "Updating submodules...")
Expand All @@ -20,36 +21,12 @@ endif()

list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

option(BUILD_BENCHMARKS "Build micro-benchmarks (nanobench)" OFF)

include(CTest)
include(Compiler)
include(Modules)
include(Shaders)

# Engine runtime (C++)
add_subdirectory(Engine/cpp)

# Samples (C++)
add_subdirectory(Samples/cpp)

# Aggregate benchmark targets: `benchmarks` builds them all, `run-benchmarks`
# builds and runs each in turn.
if(BUILD_BENCHMARKS)
get_property(DRACO_BENCH_TARGETS GLOBAL PROPERTY DRACO_BENCH_TARGETS)
if(DRACO_BENCH_TARGETS)
add_custom_target(benchmarks)
add_dependencies(benchmarks ${DRACO_BENCH_TARGETS})

set(BENCH_RUN_CMDS "")
foreach(BENCH_TARGET IN LISTS DRACO_BENCH_TARGETS)
list(APPEND BENCH_RUN_CMDS COMMAND $<TARGET_FILE:${BENCH_TARGET}>)
endforeach()
add_custom_target(run-benchmarks
${BENCH_RUN_CMDS}
DEPENDS ${DRACO_BENCH_TARGETS}
USES_TERMINAL
VERBATIM
)
endif()
endif()
5 changes: 1 addition & 4 deletions Engine/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
add_subdirectory(ThirdParty SYSTEM)

add_modules_library(Runtime SHARED)
# Runtime exposes the whole engine through the `draconic` module. Shell (the single
# library holding both backends) is linked PUBLIC, so a consumer only links Runtime and
# does `import draconic;`; createShell() resolves from Shell.
target_link_libraries(Runtime PUBLIC Core Shell Scene Rendering)
target_link_libraries(Runtime PUBLIC Core)

# TODO: binding library
16 changes: 16 additions & 0 deletions Engine/cpp/Runtime/Animation/AnimationModule.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// Primary module for animation - the engine's skeletal-animation foundation. Re-exports all partitions.
///
/// The animation foundation (foundation only - no rendering dependency; the renderer
/// consumes evaluated skinning matrices). Depends solely on Core (math + containers). Aggregates the
/// partitions: easing (:easing), the skeleton hierarchy (:skeleton), and the pose view (:pose).
/// The clip/sampler/player and the animation graph land in later partitions.

export module animation;

export import :easing;
export import :skeleton;
export import :pose;
export import :clip;
export import :sampler;
export import :player;
export import :graph;
213 changes: 213 additions & 0 deletions Engine/cpp/Runtime/Animation/Clip.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/// Animation clip, tracks, and keyframes (`:clip` partition).
///
/// Animation data: keyframed tracks (position/rotation/scale per bone) + timed events, grouped into
/// an AnimationClip. / AnimationTrack /
/// Keyframe / AnimationEvent. Tracks are heap-owned (UniquePtr) so GetOrCreate*Track pointers stay
/// valid as more tracks are added (matching the Beef List<AnimationTrack<T>> of references).

module;
#include <algorithm>
#include <vector>
#include <string>
#include <string_view>
#include <memory>
#include <functional>

export module animation:clip;

import core;

using namespace draco;

export namespace draco::animation {

// Event callback: (event name, event time in seconds). The engine's move-only delegate. Defined here
// (with AnimationEvent) so both the player and the graph's IAnimationStateNode can use it.
using AnimationEventHandler = std::function<void(std::u8string_view, f32)>;

// Keyframe interpolation mode.
enum class InterpolationMode { Step, Linear, CubicSpline };

// A keyframed value at a time (tangents used only for cubic spline).
template <typename T>
struct Keyframe {
f32 time = 0.0f;
T value = {};
T inTangent = {};
T outTangent= {};

Keyframe() = default;
Keyframe(f32 t, const T& v) : time(t), value(v) {}
Keyframe(f32 t, const T& v, const T& in, const T& out) : time(t), value(v), inTangent(in), outTangent(out) {}
};

// The keyframe interval surrounding a sample time + the interpolation factor.
struct KeyframeLookup { i32 prev = -1; i32 next = -1; f32 t = 0.0f; };

// Keyframes for one property of one bone.
template <typename T>
class AnimationTrack {
public:
i32 boneIndex = 0;
InterpolationMode interpolation = InterpolationMode::Linear;

[[nodiscard]] std::vector<Keyframe<T>>& keyframes() noexcept { return m_keyframes; }
[[nodiscard]] const std::vector<Keyframe<T>>& keyframes() const noexcept { return m_keyframes; }

void addKeyframe(f32 time, const T& value) { m_keyframes.push_back(Keyframe<T>{ time, value }); }
void addKeyframe(f32 time, const T& value, const T& inTan, const T& outTan) {
m_keyframes.push_back(Keyframe<T>{ time, value, inTan, outTan });
}

// Stable insertion sort by time (keyframes are typically already time-ordered from import).
void sortKeyframes() {
for (usize i = 1; i < m_keyframes.size(); ++i) {
Keyframe<T> key = m_keyframes[i];
usize j = i;
while (j > 0 && m_keyframes[j - 1].time > key.time) { m_keyframes[j] = m_keyframes[j - 1]; --j; }
m_keyframes[j] = key;
}
}

// Find the keyframe interval (prev,next) + factor t for `time`. Clamps to ends.
[[nodiscard]] KeyframeLookup findKeyframes(f32 time) const {
const i32 count = static_cast<i32>(m_keyframes.size());
if (count == 0) { return KeyframeLookup{ -1, -1, 0.0f }; }
if (count == 1) { return KeyframeLookup{ 0, 0, 0.0f }; }
if (time <= m_keyframes[0].time) { return KeyframeLookup{ 0, 0, 0.0f }; }
if (time >= m_keyframes[static_cast<usize>(count - 1)].time) { return KeyframeLookup{ count - 1, count - 1, 0.0f }; }

i32 low = 0, high = count - 1;
while (low < high - 1) {
const i32 mid = (low + high) / 2;
if (m_keyframes[static_cast<usize>(mid)].time <= time) { low = mid; } else { high = mid; }
}
const f32 duration = m_keyframes[static_cast<usize>(high)].time - m_keyframes[static_cast<usize>(low)].time;
const f32 t = (duration > 0.0f) ? (time - m_keyframes[static_cast<usize>(low)].time) / duration : 0.0f;
return KeyframeLookup{ low, high, t };
}

private:
std::vector<Keyframe<T>> m_keyframes;
};

// An event placed at a time in a clip; fires when playback crosses it.
struct AnimationEvent {
f32 time = 0.0f;
std::u8string name;
AnimationEvent() = default;
AnimationEvent(f32 t, std::u8string_view n) : time(t), name(n) {}
};

// All tracks + events for one animation. A resource product (Object) so a cooked AnimationClipSource
// can build into it via the resource system.
class AnimationClip {
public:
AnimationClip() = default;
explicit AnimationClip(std::u8string_view name, f32 duration = 0.0f, bool isLooping = false)
: duration(duration), isLooping(isLooping), m_name(name) {}

AnimationClip(const AnimationClip&) = delete;
AnimationClip& operator=(const AnimationClip&) = delete;

f32 duration = 0.0f;
bool isLooping = false;

[[nodiscard]] std::u8string& name() noexcept { return m_name; }
[[nodiscard]] const std::u8string& name() const noexcept { return m_name; }

using Vec3Track = AnimationTrack<math::Vector3>;
using QuatTrack = AnimationTrack<math::Quaternion>;

[[nodiscard]] std::vector<std::unique_ptr<Vec3Track>>& positionTracks() noexcept { return m_positionTracks; }
[[nodiscard]] std::vector<std::unique_ptr<QuatTrack>>& rotationTracks() noexcept { return m_rotationTracks; }
[[nodiscard]] std::vector<std::unique_ptr<Vec3Track>>& scaleTracks() noexcept { return m_scaleTracks; }
[[nodiscard]] std::vector<AnimationEvent>& events() noexcept { return m_events; }
[[nodiscard]] const std::vector<std::unique_ptr<Vec3Track>>& positionTracks() const noexcept { return m_positionTracks; }
[[nodiscard]] const std::vector<std::unique_ptr<QuatTrack>>& rotationTracks() const noexcept { return m_rotationTracks; }
[[nodiscard]] const std::vector<std::unique_ptr<Vec3Track>>& scaleTracks() const noexcept { return m_scaleTracks; }
[[nodiscard]] const std::vector<AnimationEvent>& events() const noexcept { return m_events; }

[[nodiscard]] Vec3Track* getOrCreatePositionTrack(i32 boneIndex) { return getOrCreate(m_positionTracks, boneIndex); }
[[nodiscard]] QuatTrack* getOrCreateRotationTrack(i32 boneIndex) { return getOrCreate(m_rotationTracks, boneIndex); }
[[nodiscard]] Vec3Track* getOrCreateScaleTrack(i32 boneIndex) { return getOrCreate(m_scaleTracks, boneIndex); }

void sortAllKeyframes() {
for (auto& t : m_positionTracks) { t->sortKeyframes(); }
for (auto& t : m_rotationTracks) { t->sortKeyframes(); }
for (auto& t : m_scaleTracks) { t->sortKeyframes(); }
}

void addEvent(f32 time, std::u8string_view name) { m_events.push_back(AnimationEvent{ time, name }); }

void sortEvents() {
for (usize i = 1; i < m_events.size(); ++i) {
AnimationEvent key = static_cast<AnimationEvent&&>(m_events[i]);
usize j = i;
while (j > 0 && m_events[j - 1].time > key.time) { m_events[j] = static_cast<AnimationEvent&&>(m_events[j - 1]); --j; }
m_events[j] = static_cast<AnimationEvent&&>(key);
}
}

// Fire events crossed in (prevTime, currentTime]. currentTime may exceed Duration when looping.
// `handler` is any callable (std::u8string_view name, f32 time).
template <typename Handler>
void fireEvents(f32 prevTime, f32 currentTime, Handler&& handler) const {
if (m_events.empty() || duration <= 0.0f) { return; }

if (currentTime > prevTime && currentTime <= duration) {
for (const AnimationEvent& e : m_events) {
if (e.time > prevTime && e.time <= currentTime) { handler(std::u8string_view{ e.name }, e.time); }
}
} else if (currentTime > duration) {
if (isLooping) {
for (const AnimationEvent& e : m_events) {
if (e.time > prevTime && e.time <= duration) { handler(std::u8string_view{ e.name }, e.time); }
}
f32 wrapped = currentTime;
while (wrapped >= duration) { wrapped -= duration; }
for (const AnimationEvent& e : m_events) {
if (e.time <= wrapped) { handler(std::u8string_view{ e.name }, e.time); }
}
} else {
for (const AnimationEvent& e : m_events) {
if (e.time > prevTime && e.time <= duration) { handler(std::u8string_view{ e.name }, e.time); }
}
}
}
}

// Duration = latest keyframe time across all tracks.
void computeDuration() {
duration = 0.0f;
for (auto& t : m_positionTracks) { if (!t->keyframes().empty()) { duration = std::max(duration, t->keyframes()[t->keyframes().size() - 1].time); } }
for (auto& t : m_rotationTracks) { if (!t->keyframes().empty()) { duration = std::max(duration, t->keyframes()[t->keyframes().size() - 1].time); } }
for (auto& t : m_scaleTracks) { if (!t->keyframes().empty()) { duration = std::max(duration, t->keyframes()[t->keyframes().size() - 1].time); } }
}

// Reset in place (resource hot-reload keeps outside references valid).
void clearForReload() {
m_positionTracks.clear(); m_rotationTracks.clear(); m_scaleTracks.clear(); m_events.clear();
duration = 0.0f; isLooping = false; m_name.clear();
}

private:
template <typename Track>
[[nodiscard]] Track* getOrCreate(std::vector<std::unique_ptr<Track>>& tracks, i32 boneIndex) {
for (auto& t : tracks) { if (t->boneIndex == boneIndex) { return t.get(); } }
std::unique_ptr<Track> track = std::make_unique<Track>();
track->boneIndex = boneIndex;
Track* raw = track.get();
tracks.push_back(static_cast<std::unique_ptr<Track>&&>(track));
return raw;
}

std::u8string m_name;
std::vector<std::unique_ptr<Vec3Track>> m_positionTracks;
std::vector<std::unique_ptr<QuatTrack>> m_rotationTracks;
std::vector<std::unique_ptr<Vec3Track>> m_scaleTracks;
std::vector<AnimationEvent> m_events;
};


} // namespace draco::animation
Loading
Loading