@forgeax/engine-skinning 0.0.0-dev.8d955ade1c79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +24 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/binding.unit.test.d.ts +2 -0
- package/dist/__tests__/binding.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/plugin.integration.test.d.ts +2 -0
- package/dist/__tests__/plugin.integration.test.d.ts.map +1 -0
- package/dist/__tests__/skin-error-code-owner.test-d.d.ts +27 -0
- package/dist/__tests__/skin-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/assets/skin-decoder.d.ts +4 -0
- package/dist/assets/skin-decoder.d.ts.map +1 -0
- package/dist/errors.d.ts +232 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +375 -0
- package/dist/index.mjs.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/resolve-skin-joints.d.ts +10 -0
- package/dist/resolve-skin-joints.d.ts.map +1 -0
- package/dist/skin.d.ts +5 -0
- package/dist/skin.d.ts.map +1 -0
- package/package.json +57 -0
- package/src/__tests__/binding.unit.test.ts +33 -0
- package/src/__tests__/errors.unit.test.ts +13 -0
- package/src/__tests__/plugin.integration.test.ts +23 -0
- package/src/__tests__/skin-error-code-owner.test-d.ts +91 -0
- package/src/assets/skin-decoder.ts +69 -0
- package/src/errors.ts +356 -0
- package/src/index.ts +15 -0
- package/src/plugin.ts +25 -0
- package/src/resolve-skin-joints.ts +24 -0
- package/src/skin.ts +68 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { skeletonContribution, skinContribution } from './assets/skin-decoder';
|
|
2
|
+
export {
|
|
3
|
+
JointCountMismatchError,
|
|
4
|
+
JointEntityDanglingError,
|
|
5
|
+
SkeletonResolveFailedError,
|
|
6
|
+
type SkinError,
|
|
7
|
+
type SkinErrorCode,
|
|
8
|
+
SkinInstancesCoexistForbiddenError,
|
|
9
|
+
SkinJointCountExceededError,
|
|
10
|
+
SkinJointDespawnedError,
|
|
11
|
+
SkinJointPathUnresolvedError,
|
|
12
|
+
} from './errors';
|
|
13
|
+
export { skinningPlugin } from './plugin';
|
|
14
|
+
export { resolveSkinJoints } from './resolve-skin-joints';
|
|
15
|
+
export { Skin } from './skin';
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Component, World } from '@forgeax/engine-ecs';
|
|
2
|
+
import type { Plugin } from '@forgeax/engine-plugin';
|
|
3
|
+
import { Skin } from './skin';
|
|
4
|
+
|
|
5
|
+
const SKINNING_COMPONENTS: readonly Component[] = [Skin];
|
|
6
|
+
|
|
7
|
+
function registerSkinningComponents(world: World): () => void {
|
|
8
|
+
const leases = SKINNING_COMPONENTS.map((component) =>
|
|
9
|
+
world.components.register(component).unwrap(),
|
|
10
|
+
);
|
|
11
|
+
return () => {
|
|
12
|
+
for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Install skeletal binding components in a World that consumes skinned scenes. */
|
|
17
|
+
export function skinningPlugin(): Plugin {
|
|
18
|
+
return {
|
|
19
|
+
name: 'skinning',
|
|
20
|
+
inject: ['world'],
|
|
21
|
+
apply(ctx) {
|
|
22
|
+
ctx.effect(() => registerSkinningComponents(ctx.world), 'skinning/components');
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EntityHandle } from '@forgeax/engine-ecs';
|
|
2
|
+
import { type SkinError, SkinJointPathUnresolvedError } from './errors.js';
|
|
3
|
+
|
|
4
|
+
export function resolveSkinJoints(
|
|
5
|
+
jointPaths: readonly string[],
|
|
6
|
+
names: ReadonlyMap<string, EntityHandle>,
|
|
7
|
+
skinEntity: EntityHandle,
|
|
8
|
+
): { ok: true; value: Uint32Array } | { ok: false; error: SkinError } {
|
|
9
|
+
const joints: number[] = [];
|
|
10
|
+
for (const path of jointPaths) {
|
|
11
|
+
const segments = path.split('/').filter(Boolean);
|
|
12
|
+
if (segments.length === 0) continue;
|
|
13
|
+
const failedAtIndex = segments.length - 1;
|
|
14
|
+
const entity = names.get(segments[failedAtIndex] ?? '');
|
|
15
|
+
if (entity === undefined) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
error: new SkinJointPathUnresolvedError(skinEntity as number, segments, failedAtIndex),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
joints.push(entity as number);
|
|
22
|
+
}
|
|
23
|
+
return { ok: true, value: new Uint32Array(joints) };
|
|
24
|
+
}
|
package/src/skin.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @forgeax/engine-runtime - Skin component (skeleton handle + joint Entity slots).
|
|
2
|
+
//
|
|
3
|
+
// Schema: { skeleton: 'shared<SkeletonAsset>', joints: 'array<entity>' }.
|
|
4
|
+
//
|
|
5
|
+
// `skeleton` carries the immutable SkeletonAsset handle (IBM + jointCount);
|
|
6
|
+
// `joints` carries the live Entity[] resolved at post-spawn time from the
|
|
7
|
+
// SkinAsset.jointPaths via Name-component BFS/DFS lookup. The joint list is
|
|
8
|
+
// consumed by advanceAnimationPlayer (write target) and render-system-extract
|
|
9
|
+
// (CPU palette pre-multiply source).
|
|
10
|
+
//
|
|
11
|
+
// Naming: single-semantic component drops the 'Component' suffix
|
|
12
|
+
// (AGENTS.md §Component naming rule #1). `joints` field takes the holder's
|
|
13
|
+
// perspective (AGENTS.md §Component naming rule #3).
|
|
14
|
+
//
|
|
15
|
+
// Component registered alongside MeshFilter / MeshRenderer / Transform as
|
|
16
|
+
// a sibling on the same entity (AC-13 / AC-37). Skin + Instances coexistence
|
|
17
|
+
// on the same entity is forbidden (M2 fail-fast 'skin-instances-coexist-forbidden').
|
|
18
|
+
//
|
|
19
|
+
// Decision anchors:
|
|
20
|
+
// - requirements AC-13 (Skin sibling to MeshFilter / MeshRenderer)
|
|
21
|
+
// - requirements AC-15 (joint Entity slots, no marker component)
|
|
22
|
+
// - requirements AC-37 (no Component suffix)
|
|
23
|
+
// - plan-strategy D-10 (SkinPaletteSlice naming + Skin x Instances fail-fast)
|
|
24
|
+
// - charter P3 (explicit failure: joint despawn fail-fast)
|
|
25
|
+
// - schema vocab 'shared<SkeletonAsset>' v1 missing item #4 alignment
|
|
26
|
+
//
|
|
27
|
+
// ## Transform contract (post-bug-20260615 fix)
|
|
28
|
+
//
|
|
29
|
+
// **Old (buggy) implicit contract (pre-bug-20260615):** The Skin entity's
|
|
30
|
+
// Transform.world was double-applied during skinning -- the shader computed
|
|
31
|
+
// `world = meshes[0].worldFromLocal x palette x pos`, so any non-identity
|
|
32
|
+
// Transform on the Skin entity (or its non-joint ancestors) caused doubled
|
|
33
|
+
// motion (translation 2x, rotation 2x). Holders had to manually pin the Skin
|
|
34
|
+
// entity's Transform to identity to avoid doubled motion. This contract was
|
|
35
|
+
// undocumented and easy to violate.
|
|
36
|
+
//
|
|
37
|
+
// **New explicit contract (post-bug-20260615 fix):** An entity carrying `Skin`
|
|
38
|
+
// has its own `Transform` ignored at render time; the world transform is
|
|
39
|
+
// determined entirely by the joints' world matrices fed through the palette:
|
|
40
|
+
//
|
|
41
|
+
// palette[i] = jointWorld_i x IBM_i
|
|
42
|
+
// shader: world = palette[i] x pos
|
|
43
|
+
//
|
|
44
|
+
// No additional left-multiply by `meshes[0].worldFromLocal` or `instanceLocal`.
|
|
45
|
+
// To move the rig, parent the joint root (or any common ancestor of the joints
|
|
46
|
+
// in `Skin.joints[]`) to your driving entity -- moving the Skin entity itself
|
|
47
|
+
// has no rendering effect. This aligns with glTF 2.0 SSkins Implementation
|
|
48
|
+
// Note: "the transform of the node that the mesh is attached to must be
|
|
49
|
+
// ignored when performing skinning."
|
|
50
|
+
//
|
|
51
|
+
// Full pipeline documentation: packages/runtime/README.md
|
|
52
|
+
// SSkinPaletteAllocator.
|
|
53
|
+
//
|
|
54
|
+
// Fix commits:
|
|
55
|
+
// - M0 (red): 15425c2b -- parented skin double-transform unit test
|
|
56
|
+
// - M1 (green): 2ad509b7 -- shader Plan A: drop meshes[0] left-multiply
|
|
57
|
+
// - M2 (cleanup): 4118e463 -- extract.ts joint read -> world.get API
|
|
58
|
+
// - M3 (demo): 94d7db66 -- hello-skin parented Fox under non-identity rig
|
|
59
|
+
// - M4 (baseline): b6ddf46d -- palette-hash counter-proof + submodule pointer
|
|
60
|
+
|
|
61
|
+
import { defineComponent } from '@forgeax/engine-ecs';
|
|
62
|
+
|
|
63
|
+
export const Skin = defineComponent('Skin', {
|
|
64
|
+
// The renderer owns the live skeleton asset/palette binding; joint entity
|
|
65
|
+
// relationships remain the portable simulation-side pose contract.
|
|
66
|
+
skeleton: { type: 'shared<SkeletonAsset>' },
|
|
67
|
+
joints: { type: 'array<entity>' },
|
|
68
|
+
});
|