From cff7986cde2ff911155673ed7c8d15f293e0be76 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Mon, 18 May 2026 03:09:26 +0200 Subject: [PATCH] feat: add load_textures + extend load_rig with asset_aliases puppet.load_textures(rig, asset_aliases): - Resolves bone.texture atlas-ids to texture-handles via the asset- lib indirection. Reads asset_aliases[rig.asset_pack] to get the asset-lib id, loads the lib's atlas.json, looks up the atlas-entry by id, then calls engine.asset.load_texture on the resolved file path. - Resolves bone.anchor from the atlas entry unless the bone has an explicit anchor override. - Raises clear errors when alias/atlas-pack/atlas-id is missing. puppet.load_rig(path, asset_aliases?) now optionally calls load_textures after build_rig if asset_aliases is supplied. README updated with new API entries (load_rig signature, load_textures, bone_world_transform, write_bone_test_only) and CHANGELOG v0.2.0 entry. --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++---- init.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 48dbb02..7fde3a0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ tracks, rest pose, keyframe animations, procedural animations (Lua-side), and look-at constraint. No footplant IK in v0.1 (deferred). -**Version:** 0.1.0 +**Version:** 0.2.0 **Lib-ID:** lib-core.puppet **Requires:** (none — pure Lua + engine.render.*) **Tags:** animation, skeleton, puppet, character @@ -72,10 +72,37 @@ Keyframe-layer controls. Animation must be registered via Binds a built animation (from `build_animation`) to a puppet instance, making it available to `play`. -### `puppet.load_rig(path)`, `puppet.load_animation(path)` +### `puppet.load_rig(path, asset_aliases?)` -Convenience wrappers: read JSON file via `engine.asset.read_file` + -parse + call `build_rig` / `build_animation`. +Loads a rig from a JSON file and optionally resolves its bone textures +via `puppet.load_textures`. Pass the module's `asset_aliases` table +(from `engine.module.asset_aliases()`) to enable sprite-mode rendering; +omit for colored-rect-only rigs. + +### `puppet.load_textures(rig, asset_aliases)` + +For each bone with a `texture` field, resolves the atlas-id to a +texture-handle via the asset-lib indirection: read `asset_aliases[rig.asset_pack]` +to get the lib-id, load `/assets/atlas.json`, look up the +atlas-entry by id, and call `engine.asset.load_texture` on the file +path. Also resolves bone anchors from the atlas (unless bone has an +explicit `anchor` field). + +### `puppet.bone_world_transform(handle, bone_id)` + +Returns the bone's cached world-transform `(x, y, angle)` from the +last `puppet.update` tick. Used by tests and by modules needing world- +space queries (e.g., for spawning effects at a bone position). + +### `puppet.write_bone_test_only(handle, bone_id, angle_rad)` + +Test-setup helper: directly write a bone angle bypassing the track- +conflict-guard. Not for production use; modules should write via +procedural callbacks. + +### `puppet.load_animation(path, rig)` + +Convenience wrapper: reads a JSON file and calls `build_animation`. ### `puppet.position(p)`, `puppet.facing(p)`, `puppet.move_to(p, x, y)` @@ -98,6 +125,15 @@ ownership at write time. ## CHANGELOG +### v0.2.0 +- Rig-format extension (texture, z_order, scale, anchor, asset_pack + fields on bones; rig.bones_z_sorted for render-order) +- Multi-level parent-chain transform with depth-first update pipeline +- Sprite-mode render via engine.render.draw_sprite_transform +- New API: load_textures, bone_world_transform, write_bone_test_only +- All v0.1.0 rigs continue to work (texture-less bones fall back to + colored-rect render) + ### v0.1.0 - Initial release: skeleton + bones + tracks + rest + keyframe + procedural + look-at constraint. Footplant IK deferred. diff --git a/init.lua b/init.lua index 697d7be..cedb6c5 100644 --- a/init.lua +++ b/init.lua @@ -576,9 +576,55 @@ end -- ==================================================================== -- engine.asset.load_json(path) reads the file and parses JSON in one -- call, returning a Lua table directly. No separate decode step needed. -function M.load_rig(path) + +-- ==================================================================== +-- Resolve atlas-ids to texture-handles via asset-lib indirection. +-- asset_aliases: { [alias-key] = asset-lib-id } from module's manifest. +-- ==================================================================== +function M.load_textures(rig, asset_aliases) + if rig.asset_pack == nil then return end -- no-texture rig, nothing to do + local lib_id = asset_aliases[rig.asset_pack] + if lib_id == nil then + error("puppet.load_textures: asset-pack alias '" .. rig.asset_pack + .. "' not found in module asset_aliases") + end + local atlas_path = lib_id .. "/assets/atlas.json" + local atlas = engine.asset.load_json(atlas_path) + local pack = atlas[rig.asset_pack] + if pack == nil then + error("puppet.load_textures: asset_pack '" .. rig.asset_pack + .. "' not declared in atlas of '" .. lib_id .. "'") + end + for _, b in pairs(rig.bones_by_id) do + if b.texture then + local entry = pack[b.texture] + if entry == nil then + error("puppet.load_textures: atlas-id '" .. b.texture + .. "' not in asset_pack '" .. rig.asset_pack + .. "' of '" .. lib_id .. "'") + end + local tex_path = lib_id .. "/assets/" .. entry.file + b.texture_handle = engine.asset.load_texture(tex_path) + -- Resolve anchor: bone.anchor override beats atlas-default. + if b.anchor == nil then + b.anchor = entry.anchor or { 0, 0 } + end + end + end +end + +-- ==================================================================== +-- Load a rig from a JSON file and optionally resolve textures. +-- asset_aliases is the module-level alias map (from manifest.module); +-- pass engine.module.asset_aliases() in module init.lua. +-- ==================================================================== +function M.load_rig(path, asset_aliases) local rig_table = engine.asset.load_json(path) - return M.build_rig(rig_table) + local rig = M.build_rig(rig_table) + if asset_aliases then + M.load_textures(rig, asset_aliases) + end + return rig end function M.load_animation(path, rig)