OzzAnimation.NET 0.17.0

dotnet add package OzzAnimation.NET --version 0.17.0
                    
NuGet\Install-Package OzzAnimation.NET -Version 0.17.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OzzAnimation.NET" Version="0.17.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OzzAnimation.NET" Version="0.17.0" />
                    
Directory.Packages.props
<PackageReference Include="OzzAnimation.NET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OzzAnimation.NET --version 0.17.0
                    
#r "nuget: OzzAnimation.NET, 0.17.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OzzAnimation.NET@0.17.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OzzAnimation.NET&version=0.17.0
                    
Install as a Cake Addin
#tool nuget:?package=OzzAnimation.NET&version=0.17.0
                    
Install as a Cake Tool

OzzAnimation.NET

NuGet Coverage

A managed C# port of the ozz-animation runtime — Guillaume Blanc's open-source skeletal animation library. Same data layout, same compression, same archive format, byte for byte; no native code, no P/Invoke, no marshalling.

var skeleton = Skeleton.Load(File.ReadAllBytes("skeleton.ozz"));
var clip     = AnimationClip.Load(File.ReadAllBytes("walk.ozz"));

var context = new SamplingContext(clip.TrackCount);
var locals  = new SoaTransforms(skeleton.JointCount);
var models  = new Matrix4x4[skeleton.JointCount];

// per frame — allocates nothing
context.Sample(clip, ratio, locals);
LocalToModel.Compute(skeleton, locals, models);

Thanks

This library exists because of ozz-animation and its author, Guillaume Blanc. The runtime data layout, the archive format, the keyframe compression, the cursor-cache sampler, the i-frame seeking, the blending model, the analytic IK solvers — all of it is his design. This repository reimplements them in C#; it invents nothing. If you find this useful, the credit belongs upstream:

ozz-animationhttps://github.com/guillaumeblanc/ozz-animation © Guillaume Blanc, MIT licensed.

Thank you for building it, and for releasing it under a licence that made this port possible.

Compatibility

Byte-compatible with ozz-animation 0.17:

Archive Tag Version
Skeleton ozz-skeleton 2
Animation ozz-animation 7
Tracks ozz-float_trackozz-quat_track 1
  • A file written by gltf2ozz loads here.
  • A file written here loads in ozz's C++ runtime.

This is enforced, not asserted. The test suite embeds archives produced by ozz-animation 0.17's own C++ builders; loading one and saving it again reproduces the file byte for byte, which pins every header count, the SoA rest-pose groups, the keyframe streams and the group-varint i-frames. A separate test samples a clip baked from a closed-form curve and checks the decoded poses against that curve, so the whole decode path is measured against ground truth rather than against itself.

Little-endian only. A big-endian archive is refused rather than byte-swapped.

Versioning tracks the ozz release this ports: 0.17.x reads and writes ozz-animation 0.17's archives, and the patch number is this port's own. A future ozz format revision would land here as a matching minor bump, so the version answers "which ozz does this speak?" without a lookup table.

What is here

This is the runtime half of ozz. ozz ships its authoring code as a separate ozz_animation_offline library, and this repository draws the same line — see Not ported.

This library ozz
Skeleton ozz::animation::Skeleton
AnimationClip ozz::animation::Animation
SamplingContext.Sample SamplingJob + its Context
SoaTransforms span<ozz::math::SoaTransform>
LocalToModel.Compute LocalToModelJob (including from / to / from_excluded)
BlendingJob BlendingJob (weighted, per-joint, additive and subtractive layers)
IKTwoBoneJob IKTwoBoneJob
IKAimJob IKAimJob
FloatTrackQuaternionTrack FloatTrackQuaternionTrack
Track<T>.Sample TrackSamplingJob
TrackTriggering.Edges TrackTriggeringJob and its iterator
MotionBlending.Blend MotionBlendingJob
SkeletonUtils skeleton_utils.h
AnimationUtils animation_utils.h

Two deliberate differences

Row-vector matrices. ozz composes model[i] = parent × local[i] in a column-vector convention. System.Numerics.Matrix4x4 is row-vector, so this library composes model[i] = local[i] × parent and stores the transpose. The transforms are the same; only the convention differs, and it is the one every other .NET library expects.

Exact instead of estimated math. Where ozz uses estimated reciprocal and inverse-square-root instructions (RcpEst, RSqrtEstNR, NormalizeEst), this uses exact division and square root. Poses differ from native ozz around the seventh decimal, never structurally.

Jobs are C# methods rather than structs with Validate()/Run() fields, but every ozz parameter is present, under its ozz name.

Not ported

  • The offline moduleRawSkeleton/RawAnimation, SkeletonBuilder, AnimationBuilder, AnimationOptimizer, TrackBuilder, AdditiveAnimationBuilder, MotionExtractor. Build your archives with ozz's own tools (gltf2ozz) and load them here.
  • The glTF importer and the Fbx toolchain.
  • ozz::geometry skinning jobs, and the sample framework.

Nothing above changes the archive format, so any of it could be added later without breaking files already written.

One behavioural note

A track with no keys samples to identity, not to the joint's rest pose — same as ozz. If you want unanimated joints to hold their rest transform, that is the exporter's job, and gltf2ozz does it.

Performance

Sampling is a port of ozz's SIMD SamplingJob: keys are decoded and interpolated four tracks at a time in Vector128<float> lanes, straight into a structure-of-arrays pose set with no transpose. The cursor walk stays scalar, as in ozz.

One frame of one 64-joint character — sample plus local-to-model — on an Apple M3 Max, .NET 10:

This library ozz 0.17 (C++)
Playback (steps 1/100 of the clip per frame) 1.128 µs 1.197 µs
Scrubbing (jumps to a random ratio per frame) 3.607 µs 3.201 µs
Allocated per frame 0 B 0 B

Playback is a dead heat; this run has it slightly ahead, but run-to-run drift on this machine is a few percent either way, so read the two as equal rather than as a win. Scrubbing is about 12% behind, and that gap is the deliberate trade named above: ozz seeks with estimated reciprocal and inverse-square-root instructions, this library uses exact ones.

Splitting the frame shows where the work is, and where it is worth looking:

Sampling Local-to-model Frame
Playback 690 ns 438 ns 1128 ns
Scrubbing 3175 ns 432 ns 3607 ns

Scrubbing is 88% sampler. Both runtimes walk the same keys from the same i-frames — the cost is per-key, so the lever that actually moves it is i-frame density at build time (gltf2ozz's sampling rate), not anything the runtime can do.

The other runtime jobs on the same rig, for scale (ozz's C++ is not wired up for these — the shim covers sampling only — so these are absolute numbers, not a comparison):

Job Mean
BlendingJob, two layers 175 ns
BlendingJob, two layers plus an additive one 397 ns
LocalToModel, whole skeleton 435 ns
IKTwoBoneJob 155 ns
IKAimJob 92 ns

Nothing allocates after construction, so a frame produces no GC pressure. The library is trimmable and NativeAOT-compatible.

Reproduce it:

dotnet run --project src/OzzAnimation.Benchmarks -c Release -- --filter '*'

The native rows need ozz's own C++ runtime, through the shim in bench/native:

cmake -B bench/native/build bench/native -DOZZ_ROOT=/path/to/ozz-animation -DCMAKE_BUILD_TYPE=Release
cmake --build bench/native/build --config Release
OZZ_NATIVE_LIB=$PWD/bench/native/build/libozz_shim.dylib \
  dotnet run --project src/OzzAnimation.Benchmarks -c Release -- --filter '*'

Both sides are allocation-free per frame — the shim keeps its scratch buffers in its context for the same reason this library sizes everything up front, so what is timed is the sampling and the hierarchy walk rather than an allocator.

Building

dotnet build OzzAnimation.NET.slnx
dotnet run --project src/OzzAnimation.Tests -c Release

Targets net10.0. With coverage:

dotnet run --project src/OzzAnimation.Tests -c Release -- \
  --coverage --coverage-output-format cobertura --coverage-output coverage.cobertura.xml

93 tests cover 99.7% of the library's lines. The four that remain are the byte-swapping fallbacks for a big-endian host, which no supported .NET platform is — they stay because deleting them would not make anything faster, only wrong somewhere the reinterpreting fast path does not hold. CI reports the number on every push but does not gate on it.

Releasing

The tag is the version — push one and the package ships:

git tag v0.3.0
git push origin v0.3.0

publish-nuget.yml runs the test suite, packs with the tag's version and pushes to nuget.org. The <Version> in the csproj is only the default for local builds and does not have to be bumped in lockstep; -p:Version from the tag wins.

Authentication is OIDC rather than a stored key: nuget.org must be configured to trust this repository, and the NUGET_USER repository variable must name the nuget.org account when it is not the repository owner. No API key lives in this repository.

Licence

MIT — see LICENSE, which also carries ozz-animation's own MIT notice.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.17.0 98 9/5/2026