feat(ludic.anim): ozz-animation 0.17.0 underneath - a skin's skeleton and each clip built at load, sampled to 1e-4 of anim_mix (phase 19.1)
No bake and no new file: the skeleton comes from the skin's parents and rest pose (its own joints and their ancestors - a kit holds several rigs), a clip from anim_read_doc's channels. Built by native/build.sh from the pinned release; the Windows DLL imports KERNEL32 alone and passes there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
22d1e2d66a
commit
9a56ee9b9d
14 changed files with 419 additions and 1 deletions
23
packages/ludic.anim/native/LICENSE-ozz-animation.md
Normal file
23
packages/ludic.anim/native/LICENSE-ozz-animation.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation and
|
||||
distributed under the MIT License (MIT).
|
||||
|
||||
---
|
||||
|
||||
Copyright (c) 2020 Guillaume Blanc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
29
packages/ludic.anim/native/build.sh
Executable file
29
packages/ludic.anim/native/build.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/bin/sh
|
||||
# builds lib/<target>/ for ludic.anim: ozz-animation at a pinned tag - its base, its runtime jobs and
|
||||
# the offline builders that make a skeleton and a clip at load - and the shim over them. No CMake,
|
||||
# no glTF importer: the game reads the glTF and hands the shim its numbers. Objects go to build/native
|
||||
set -eu
|
||||
PKG="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
. "$PKG/../../tools/native/lib.sh"
|
||||
OZZ_TAG=0.17.0
|
||||
OZZ_SHA=22b4401c77a2be68acb0a8aa2e857f94dbaccfed3ab25e304a1ddea4c546e86b
|
||||
SRC="$PKG/build/src/ozz-$OZZ_TAG"
|
||||
native_fetch "$SRC" "https://github.com/guillaumeblanc/ozz-animation/archive/refs/tags/$OZZ_TAG.tar.gz" "$OZZ_SHA"
|
||||
|
||||
OBJ="$PKG/build/native/$(native_target)"
|
||||
mkdir -p "$OBJ"
|
||||
CXX="$(native_cxx)"
|
||||
FLAGS="$(native_cflags) -std=c++17 -DNDEBUG -fno-exceptions -fno-rtti -I$SRC/include -I$SRC/src"
|
||||
case "$(native_target)" in windows-*) FLAGS="$FLAGS -D_CRT_SECURE_NO_WARNINGS" ;; esac
|
||||
JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
|
||||
|
||||
# the base and the runtime whole, and of the offline library only what builds a skeleton and a clip
|
||||
( cd "$SRC" && find src/base src/animation/runtime -name '*.cc'
|
||||
for f in raw_skeleton skeleton_builder raw_animation raw_animation_utils animation_builder; do echo "src/animation/offline/$f.cc"; done
|
||||
) | while read -r f; do
|
||||
o="$OBJ/$(echo "$f" | tr '/' '_' | sed 's/\.cc$/.o/')"
|
||||
[ "$o" -nt "$SRC/$f" ] || echo "$f $o"
|
||||
done | xargs -P "$JOBS" -n 2 sh -c '"$0" '"$FLAGS"' -c "'"$SRC"'/$1" -o "$2"' "$CXX"
|
||||
"$CXX" $FLAGS -I"$PKG/native/shim" -c "$PKG/native/shim/ozz_shim.cpp" -o "$OBJ/ozz_shim.o"
|
||||
|
||||
native_link "$PKG" ludicozz "$OBJ"/*.o
|
||||
186
packages/ludic.anim/native/shim/ozz_shim.cpp
Normal file
186
packages/ludic.anim/native/shim/ozz_shim.cpp
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// ozz_shim.cpp - ludic.anim's door into ozz-animation (phase 19). It keeps the shim rules of
|
||||
// packages/README.md: int, float and opaque handles cross; no struct by value, no callback into
|
||||
// Ludic; errors are a null handle or a 0. A skeleton and a clip are built at load from the numbers
|
||||
// the package already holds (a skin's parents and rest pose, a clip's flattened channels); what a
|
||||
// frame needs is made once per actor (a context) and reused.
|
||||
#include "ozz/animation/offline/raw_skeleton.h"
|
||||
#include "ozz/animation/offline/skeleton_builder.h"
|
||||
#include "ozz/animation/offline/raw_animation.h"
|
||||
#include "ozz/animation/offline/animation_builder.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/simd_math.h"
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define OZZ_SHIM extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define OZZ_SHIM extern "C" __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
namespace oa = ozz::animation;
|
||||
namespace off = ozz::animation::offline;
|
||||
|
||||
// a skeleton, and the map between its joints (ozz orders them depth first) and the file's nodes
|
||||
struct Skel {
|
||||
ozz::unique_ptr<oa::Skeleton> s;
|
||||
std::vector<int> node_of; // per joint: the file's node
|
||||
std::vector<int> joint_of; // per node: its joint, -1 for none
|
||||
std::vector<float> rest; // per node: t3 r4 s3, what a joint no channel moves holds
|
||||
};
|
||||
struct Clip {
|
||||
ozz::unique_ptr<oa::Animation> a;
|
||||
float dur;
|
||||
};
|
||||
struct Ctx {
|
||||
const Skel *sk;
|
||||
oa::SamplingJob::Context ctx;
|
||||
std::vector<ozz::math::SoaTransform> locals;
|
||||
};
|
||||
|
||||
static void add_children(off::RawSkeleton::Joint &j, int node, int n, const int *par, const float *rest) {
|
||||
j.name = std::to_string(node).c_str();
|
||||
const float *r = rest + node * 10;
|
||||
j.transform.translation = ozz::math::Float3(r[0], r[1], r[2]);
|
||||
j.transform.rotation = ozz::math::Quaternion(r[3], r[4], r[5], r[6]);
|
||||
j.transform.scale = ozz::math::Float3(r[7], r[8], r[9]);
|
||||
for (int c = 0; c < n; c++) {
|
||||
if (par[c] != node) continue;
|
||||
j.children.emplace_back();
|
||||
add_children(j.children.back(), c, n, par, rest);
|
||||
}
|
||||
}
|
||||
|
||||
// n nodes, each's parent (-1 a root, -2 not in this skeleton) and rest pose (10 floats: translation, rotation x y z w, scale)
|
||||
OZZ_SHIM void *ozz_skel_new(int n, const int *par, const float *rest) {
|
||||
if (n <= 0 || n > oa::Skeleton::kMaxJoints) return nullptr;
|
||||
off::RawSkeleton raw;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (par[i] != -1) continue;
|
||||
raw.roots.emplace_back();
|
||||
add_children(raw.roots.back(), i, n, par, rest);
|
||||
}
|
||||
if (!raw.Validate()) return nullptr;
|
||||
off::SkeletonBuilder build;
|
||||
Skel *sk = new Skel();
|
||||
sk->s = build(raw);
|
||||
if (!sk->s) { delete sk; return nullptr; }
|
||||
int nj = sk->s->num_joints();
|
||||
sk->node_of.assign(nj, -1);
|
||||
sk->joint_of.assign(n, -1);
|
||||
auto names = sk->s->joint_names();
|
||||
for (int j = 0; j < nj; j++) {
|
||||
int node = std::atoi(names[j]);
|
||||
sk->node_of[j] = node;
|
||||
sk->joint_of[node] = j;
|
||||
}
|
||||
sk->rest.assign(rest, rest + n * 10);
|
||||
return sk;
|
||||
}
|
||||
OZZ_SHIM void ozz_skel_free(void *p) { delete static_cast<Skel *>(p); }
|
||||
OZZ_SHIM int ozz_skel_joints(void *p) { return p ? static_cast<Skel *>(p)->s->num_joints() : 0; }
|
||||
OZZ_SHIM int ozz_skel_joint_of(void *p, int node) {
|
||||
Skel *sk = static_cast<Skel *>(p);
|
||||
if (!sk || node < 0 || node >= (int)sk->joint_of.size()) return -1;
|
||||
return sk->joint_of[node];
|
||||
}
|
||||
|
||||
static float clampt(float t, float dur) { return t < 0.f ? 0.f : (t > dur ? dur : t); }
|
||||
|
||||
// a clip from ludic.anim's flattened channels (clip.ludic): a channel's keys are
|
||||
// times[coff .. coff + ckeys], its values 4 (rotation, path 0) or 3 (translation, path 1) from
|
||||
// cvoff; translations only when `positions`. A joint no channel moves holds its rest pose.
|
||||
OZZ_SHIM void *ozz_clip_new(void *skp, float dur, int nch, const int *cnode, const int *cpath, const int *ckeys,
|
||||
const int *coff, const int *cvoff, const float *times, const float *vals, int positions) {
|
||||
Skel *sk = static_cast<Skel *>(skp);
|
||||
if (!sk) return nullptr;
|
||||
off::RawAnimation raw;
|
||||
raw.duration = dur > 0.0001f ? dur : 0.0001f;
|
||||
int nj = sk->s->num_joints();
|
||||
raw.tracks.resize(nj);
|
||||
for (int ch = 0; ch < nch; ch++) {
|
||||
int node = cnode[ch];
|
||||
if (node < 0 || node >= (int)sk->joint_of.size() || sk->joint_of[node] < 0) continue;
|
||||
auto &tr = raw.tracks[sk->joint_of[node]];
|
||||
const float *v = vals + cvoff[ch];
|
||||
for (int k = 0; k < ckeys[ch]; k++) {
|
||||
float t = clampt(times[coff[ch] + k], raw.duration);
|
||||
if (k > 0 && t <= times[coff[ch] + k - 1] && t < raw.duration) continue;
|
||||
if (cpath[ch] == 0) {
|
||||
const float *q = v + k * 4;
|
||||
tr.rotations.push_back({t, ozz::math::Quaternion(q[0], q[1], q[2], q[3])});
|
||||
} else if (positions) {
|
||||
const float *p = v + k * 3;
|
||||
tr.translations.push_back({t, ozz::math::Float3(p[0], p[1], p[2])});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < nj; j++) {
|
||||
auto &tr = raw.tracks[j];
|
||||
const float *r = &sk->rest[sk->node_of[j] * 10];
|
||||
if (tr.translations.empty()) tr.translations.push_back({0.f, ozz::math::Float3(r[0], r[1], r[2])});
|
||||
if (tr.rotations.empty()) tr.rotations.push_back({0.f, ozz::math::Quaternion(r[3], r[4], r[5], r[6])});
|
||||
if (tr.scales.empty()) tr.scales.push_back({0.f, ozz::math::Float3(r[7], r[8], r[9])});
|
||||
}
|
||||
if (!raw.Validate()) return nullptr;
|
||||
off::AnimationBuilder build;
|
||||
Clip *c = new Clip();
|
||||
c->a = build(raw);
|
||||
if (!c->a) { delete c; return nullptr; }
|
||||
c->dur = raw.duration;
|
||||
return c;
|
||||
}
|
||||
OZZ_SHIM void ozz_clip_free(void *p) { delete static_cast<Clip *>(p); }
|
||||
OZZ_SHIM int ozz_clip_bytes(void *p) { return p ? (int)static_cast<Clip *>(p)->a->size() : 0; }
|
||||
|
||||
// what one actor samples with, made once
|
||||
OZZ_SHIM void *ozz_ctx_new(void *skp) {
|
||||
Skel *sk = static_cast<Skel *>(skp);
|
||||
if (!sk) return nullptr;
|
||||
Ctx *c = new Ctx();
|
||||
c->sk = sk;
|
||||
c->ctx.Resize(sk->s->num_joints());
|
||||
c->locals.resize(sk->s->num_soa_joints());
|
||||
return c;
|
||||
}
|
||||
OZZ_SHIM void ozz_ctx_free(void *p) { delete static_cast<Ctx *>(p); }
|
||||
|
||||
// clip at t seconds (wrapped on its duration), each joint's local rotation (4 per node) and
|
||||
// translation (3 per node) written at its FILE node, into the caller's buffers of n nodes
|
||||
OZZ_SHIM int ozz_sample(void *cp, void *clp, float t, float *rot, float *pos, int n) {
|
||||
Ctx *c = static_cast<Ctx *>(cp);
|
||||
Clip *cl = static_cast<Clip *>(clp);
|
||||
if (!c || !cl) return 0;
|
||||
float tt = t;
|
||||
if (cl->dur > 0.f) { tt = std::fmod(t, cl->dur); if (tt < 0.f) tt += cl->dur; }
|
||||
oa::SamplingJob job;
|
||||
job.animation = cl->a.get();
|
||||
job.context = &c->ctx;
|
||||
job.ratio = tt / cl->dur;
|
||||
job.output = ozz::make_span(c->locals);
|
||||
if (!job.Run()) return 0;
|
||||
const Skel *sk = c->sk;
|
||||
int nj = sk->s->num_joints();
|
||||
float lane[4][10];
|
||||
for (int s = 0; s * 4 < nj; s++) {
|
||||
const ozz::math::SoaTransform &x = c->locals[s];
|
||||
ozz::math::SimdFloat4 comp[7] = {x.translation.x, x.translation.y, x.translation.z,
|
||||
x.rotation.x, x.rotation.y, x.rotation.z, x.rotation.w};
|
||||
for (int k = 0; k < 7; k++) {
|
||||
float v[4];
|
||||
ozz::math::StorePtrU(comp[k], v);
|
||||
for (int l = 0; l < 4; l++) lane[l][k] = v[l];
|
||||
}
|
||||
for (int l = 0; l < 4 && s * 4 + l < nj; l++) {
|
||||
int node = sk->node_of[s * 4 + l];
|
||||
if (node < 0 || node >= n) continue;
|
||||
for (int k = 0; k < 3; k++) pos[node * 3 + k] = lane[l][k];
|
||||
for (int k = 0; k < 4; k++) rot[node * 4 + k] = lane[l][3 + k];
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue