@flighthq/skeleton3d 0.2.1-next.451.d9a4a4e → 0.2.1-next.530.a223ca9
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/skeleton3d.d.ts +1 -0
- package/dist/skeleton3d.d.ts.map +1 -1
- package/dist/skeleton3d.js +37 -9
- package/dist/skeleton3d.js.map +1 -1
- package/dist/skinMeshGeometry.d.ts +1 -0
- package/dist/skinMeshGeometry.d.ts.map +1 -1
- package/dist/skinMeshGeometry.js +38 -12
- package/dist/skinMeshGeometry.js.map +1 -1
- package/dist/updateMeshDeformation.d.ts +3 -0
- package/dist/updateMeshDeformation.d.ts.map +1 -0
- package/dist/updateMeshDeformation.js +12 -0
- package/dist/updateMeshDeformation.js.map +1 -0
- package/dist/updateMeshSkin.d.ts.map +1 -1
- package/dist/updateMeshSkin.js +8 -2
- package/dist/updateMeshSkin.js.map +1 -1
- package/package.json +7 -5
- package/src/getMeshSkinBounds.test.ts +3 -2
- package/src/skeleton3d.test.ts +44 -7
- package/src/skinMeshGeometry.test.ts +57 -2
- package/src/updateMeshDeformation.test.ts +63 -0
- package/src/updateMeshSkin.test.ts +2 -0
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC"}
|
package/dist/skeleton3d.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Matrix4Like, SceneNode, Skeleton3D, Skeleton3DValidationDiagnostic } from '@flighthq/types';
|
|
2
2
|
export declare function cloneSkeleton3D(skeleton: Readonly<Skeleton3D>): Skeleton3D;
|
|
3
|
+
export declare function cloneSkeleton3DJointHierarchy(skeleton: Readonly<Skeleton3D>, cloneJoint: (joint: Readonly<SceneNode>, jointIndex: number) => SceneNode): Skeleton3D;
|
|
3
4
|
export declare function computeSkeleton3DJointMatrices(skeleton: Readonly<Skeleton3D>): void;
|
|
4
5
|
export declare function createSkeleton3D(joints: SceneNode[], inverseBindMatrices?: Float32Array, names?: readonly string[] | null): Skeleton3D;
|
|
5
6
|
export declare function disposeSkeleton3D(skeleton: Skeleton3D): void;
|
package/dist/skeleton3d.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skeleton3d.d.ts","sourceRoot":"","sources":["../src/skeleton3d.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skeleton3d.d.ts","sourceRoot":"","sources":["../src/skeleton3d.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAC;AAE1G,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,CAQ1E;AAOD,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC9B,UAAU,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,MAAM,KAAK,SAAS,GACxE,UAAU,CAsBZ;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAQnF;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,mBAAmB,CAAC,EAAE,YAAY,EAClC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAC/B,UAAU,CAUZ;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CAG5D;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,OAAO,CAe1F;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIlG;AAED,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC9B,UAAU,EAAE,MAAM,GACjB,OAAO,CAKT;AAED,wBAAgB,mCAAmC,CACjD,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC9B,IAAI,EAAE,MAAM,GACX,OAAO,CAET;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAM1E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,8BAA8B,GAAG,IAAI,CAWxG"}
|
package/dist/skeleton3d.js
CHANGED
|
@@ -1,17 +1,45 @@
|
|
|
1
|
+
import { createEntity } from '@flighthq/entity';
|
|
1
2
|
import { copyMatrix4, createMatrix4, inverseMatrix4, multiplyMatrix4 } from '@flighthq/geometry';
|
|
2
|
-
import { getNodeWorldMatrix4 } from '@flighthq/node';
|
|
3
|
+
import { addNodeChild, getNodeParent, getNodeWorldMatrix4 } from '@flighthq/node';
|
|
3
4
|
export function cloneSkeleton3D(skeleton) {
|
|
4
|
-
const clone = {
|
|
5
|
+
const clone = createEntity({
|
|
5
6
|
inverseBindMatrices: new Float32Array(skeleton.inverseBindMatrices),
|
|
6
7
|
jointMatrices: new Float32Array(skeleton.jointMatrices),
|
|
7
8
|
joints: skeleton.joints.slice(),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
clone.names = skeleton.names.slice();
|
|
11
|
-
else if (skeleton.names === null)
|
|
12
|
-
clone.names = null;
|
|
9
|
+
names: skeleton.names === undefined ? undefined : skeleton.names === null ? null : skeleton.names.slice(),
|
|
10
|
+
});
|
|
13
11
|
return clone;
|
|
14
12
|
}
|
|
13
|
+
// Clones a skeleton and its joint-to-joint hierarchy without assuming that every SceneNode kind is
|
|
14
|
+
// generically cloneable. `cloneJoint` is the one narrow policy seam: it must return a fresh detached
|
|
15
|
+
// node carrying the source joint's local pose/data. Parent links whose parent is another skeleton
|
|
16
|
+
// joint are rebuilt over the cloned nodes; a parent outside the joint set is intentionally left to
|
|
17
|
+
// the caller's surrounding scene clone. Buffers, names, joint nodes, and hierarchy are all independent.
|
|
18
|
+
export function cloneSkeleton3DJointHierarchy(skeleton, cloneJoint) {
|
|
19
|
+
const sourceJoints = skeleton.joints;
|
|
20
|
+
const joints = new Array(sourceJoints.length);
|
|
21
|
+
const clonesBySource = new Map();
|
|
22
|
+
for (let i = 0; i < sourceJoints.length; i++) {
|
|
23
|
+
const source = sourceJoints[i];
|
|
24
|
+
const clone = cloneJoint(source, i);
|
|
25
|
+
joints[i] = clone;
|
|
26
|
+
clonesBySource.set(source, clone);
|
|
27
|
+
}
|
|
28
|
+
for (let i = 0; i < sourceJoints.length; i++) {
|
|
29
|
+
const sourceParent = getNodeParent(sourceJoints[i]);
|
|
30
|
+
if (sourceParent === null)
|
|
31
|
+
continue;
|
|
32
|
+
const parentClone = clonesBySource.get(sourceParent);
|
|
33
|
+
if (parentClone !== undefined)
|
|
34
|
+
addNodeChild(parentClone, joints[i]);
|
|
35
|
+
}
|
|
36
|
+
return createEntity({
|
|
37
|
+
inverseBindMatrices: new Float32Array(skeleton.inverseBindMatrices),
|
|
38
|
+
jointMatrices: new Float32Array(skeleton.jointMatrices),
|
|
39
|
+
joints,
|
|
40
|
+
names: skeleton.names === undefined ? undefined : skeleton.names === null ? null : skeleton.names.slice(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
15
43
|
export function computeSkeleton3DJointMatrices(skeleton) {
|
|
16
44
|
const { inverseBindMatrices, jointMatrices, joints } = skeleton;
|
|
17
45
|
for (let j = 0; j < joints.length; j++) {
|
|
@@ -24,12 +52,12 @@ export function computeSkeleton3DJointMatrices(skeleton) {
|
|
|
24
52
|
}
|
|
25
53
|
export function createSkeleton3D(joints, inverseBindMatrices, names) {
|
|
26
54
|
const count = joints.length;
|
|
27
|
-
const skeleton = {
|
|
55
|
+
const skeleton = createEntity({
|
|
28
56
|
inverseBindMatrices: inverseBindMatrices ?? new Float32Array(count * 16),
|
|
29
57
|
jointMatrices: new Float32Array(count * 16),
|
|
30
58
|
joints,
|
|
31
59
|
names: names ?? null,
|
|
32
|
-
};
|
|
60
|
+
});
|
|
33
61
|
if (inverseBindMatrices === undefined)
|
|
34
62
|
setSkeleton3DBindPose(skeleton);
|
|
35
63
|
return skeleton;
|
package/dist/skeleton3d.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skeleton3d.js","sourceRoot":"","sources":["../src/skeleton3d.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"skeleton3d.js","sourceRoot":"","sources":["../src/skeleton3d.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MeshGeometry, MeshSkinBindPose, Skeleton3D } from '@flighthq/types';
|
|
2
2
|
export declare function captureMeshSkinBindPose(geometry: Readonly<MeshGeometry>): MeshSkinBindPose;
|
|
3
3
|
export declare function skinMeshGeometry(geometry: MeshGeometry, skeleton: Readonly<Skeleton3D>, bindPose: Readonly<MeshSkinBindPose>): void;
|
|
4
|
+
export declare function updateMeshSkinBindPoseDeformInput(bindPose: MeshSkinBindPose, geometry: Readonly<MeshGeometry>): void;
|
|
4
5
|
//# sourceMappingURL=skinMeshGeometry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skinMeshGeometry.d.ts","sourceRoot":"","sources":["../src/skinMeshGeometry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skinMeshGeometry.d.ts","sourceRoot":"","sources":["../src/skinMeshGeometry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAyB,MAAM,iBAAiB,CAAC;AAWzG,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,gBAAgB,CAkD1F;AASD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC9B,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GACnC,IAAI,CAkCN;AAMD,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAsBpH"}
|
package/dist/skinMeshGeometry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getMeshGeometryVertexJoints0, getMeshGeometryVertexWeights0 } from '@flighthq/mesh';
|
|
1
2
|
import { skinVertices } from './skinVertices';
|
|
2
3
|
// Captures the de-interleaved CPU-skinning inputs for one skinned MeshGeometry: the bind-pose
|
|
3
4
|
// positions/normals and the static joints0/weights0 influences, read out of the interleaved
|
|
@@ -12,12 +13,12 @@ export function captureMeshSkinBindPose(geometry) {
|
|
|
12
13
|
const vertexCount = floatsPerVertex > 0 ? (vertices.length / floatsPerVertex) | 0 : 0;
|
|
13
14
|
const positionOffset = floatOffsetForSemantic(layout, 'position');
|
|
14
15
|
const normalOffset = floatOffsetForSemantic(layout, 'normal');
|
|
15
|
-
const jointsOffset = floatOffsetForSemantic(layout, 'joints0');
|
|
16
|
-
const weightsOffset = floatOffsetForSemantic(layout, 'weights0');
|
|
17
16
|
const positions = new Float32Array(vertexCount * 3);
|
|
18
17
|
const normals = new Float32Array(vertexCount * 3);
|
|
19
18
|
const joints = new Float32Array(vertexCount * 4);
|
|
20
19
|
const weights = new Float32Array(vertexCount * 4);
|
|
20
|
+
const joint = { w: 0, x: 0, y: 0, z: 0 };
|
|
21
|
+
const weight = { w: 0, x: 0, y: 0, z: 0 };
|
|
21
22
|
for (let v = 0; v < vertexCount; v++) {
|
|
22
23
|
const base = v * floatsPerVertex;
|
|
23
24
|
const p = v * 3;
|
|
@@ -32,17 +33,17 @@ export function captureMeshSkinBindPose(geometry) {
|
|
|
32
33
|
normals[p + 1] = vertices[base + normalOffset + 1];
|
|
33
34
|
normals[p + 2] = vertices[base + normalOffset + 2];
|
|
34
35
|
}
|
|
35
|
-
if (
|
|
36
|
-
joints[w] =
|
|
37
|
-
joints[w + 1] =
|
|
38
|
-
joints[w + 2] =
|
|
39
|
-
joints[w + 3] =
|
|
36
|
+
if (getMeshGeometryVertexJoints0(joint, geometry, v)) {
|
|
37
|
+
joints[w] = joint.x;
|
|
38
|
+
joints[w + 1] = joint.y;
|
|
39
|
+
joints[w + 2] = joint.z;
|
|
40
|
+
joints[w + 3] = joint.w;
|
|
40
41
|
}
|
|
41
|
-
if (
|
|
42
|
-
weights[w] =
|
|
43
|
-
weights[w + 1] =
|
|
44
|
-
weights[w + 2] =
|
|
45
|
-
weights[w + 3] =
|
|
42
|
+
if (getMeshGeometryVertexWeights0(weight, geometry, v)) {
|
|
43
|
+
weights[w] = weight.x;
|
|
44
|
+
weights[w + 1] = weight.y;
|
|
45
|
+
weights[w + 2] = weight.z;
|
|
46
|
+
weights[w + 3] = weight.w;
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
return {
|
|
@@ -85,6 +86,31 @@ export function skinMeshGeometry(geometry, skeleton, bindPose) {
|
|
|
85
86
|
}
|
|
86
87
|
geometry.version++;
|
|
87
88
|
}
|
|
89
|
+
// Refreshes only the deformable position/normal input of an existing skin bind pose from the
|
|
90
|
+
// geometry's current vertices. Static joints/weights and output scratch remain untouched. This is
|
|
91
|
+
// the allocation-free bridge for composed morph-then-skin deformation: morph writes its result into
|
|
92
|
+
// the geometry, this copies that result into skin input, then skinMeshGeometry applies the palette.
|
|
93
|
+
export function updateMeshSkinBindPoseDeformInput(bindPose, geometry) {
|
|
94
|
+
const { layout, vertices } = geometry;
|
|
95
|
+
const floatsPerVertex = layout.stride / 4;
|
|
96
|
+
const vertexCount = floatsPerVertex > 0 ? Math.min((vertices.length / floatsPerVertex) | 0, bindPose.positions.length / 3) : 0;
|
|
97
|
+
const positionOffset = floatOffsetForSemantic(layout, 'position');
|
|
98
|
+
const normalOffset = floatOffsetForSemantic(layout, 'normal');
|
|
99
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
100
|
+
const source = v * floatsPerVertex;
|
|
101
|
+
const target = v * 3;
|
|
102
|
+
if (positionOffset >= 0) {
|
|
103
|
+
bindPose.positions[target] = vertices[source + positionOffset];
|
|
104
|
+
bindPose.positions[target + 1] = vertices[source + positionOffset + 1];
|
|
105
|
+
bindPose.positions[target + 2] = vertices[source + positionOffset + 2];
|
|
106
|
+
}
|
|
107
|
+
if (normalOffset >= 0) {
|
|
108
|
+
bindPose.normals[target] = vertices[source + normalOffset];
|
|
109
|
+
bindPose.normals[target + 1] = vertices[source + normalOffset + 1];
|
|
110
|
+
bindPose.normals[target + 2] = vertices[source + normalOffset + 2];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
88
114
|
// The float (not byte) offset of a semantic within an interleaved vertex record, or -1 when the
|
|
89
115
|
// layout does not carry it. byteOffset is a multiple of 4 for the float32 channels skinning reads.
|
|
90
116
|
function floatOffsetForSemantic(layout, semantic) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skinMeshGeometry.js","sourceRoot":"","sources":["../src/skinMeshGeometry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skinMeshGeometry.js","sourceRoot":"","sources":["../src/skinMeshGeometry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateMeshDeformation.d.ts","sourceRoot":"","sources":["../src/updateMeshDeformation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAS5C,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAGhE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { updateMeshMorph } from '@flighthq/scene';
|
|
2
|
+
import { updateMeshSkin } from './updateMeshSkin';
|
|
3
|
+
// Applies a mesh's CPU deformation in the one valid composition order: morph first, then skin. Each
|
|
4
|
+
// deformer remains a standalone primitive and is a no-op when absent; this small composition prevents
|
|
5
|
+
// callers from freezing the first morph into skin's captured input or accidentally reversing the two.
|
|
6
|
+
// Backends using GPU skinning should continue to update morph CPU-side and upload the joint palette,
|
|
7
|
+
// rather than calling this CPU skin path and then skinning again in the shader.
|
|
8
|
+
export function updateMeshDeformation(mesh) {
|
|
9
|
+
updateMeshMorph(mesh);
|
|
10
|
+
updateMeshSkin(mesh);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=updateMeshDeformation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateMeshDeformation.js","sourceRoot":"","sources":["../src/updateMeshDeformation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,oGAAoG;AACpG,sGAAsG;AACtG,sGAAsG;AACtG,qGAAqG;AACrG,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CAAC,IAAoB;IACxD,eAAe,CAAC,IAAI,CAAC,CAAC;IACtB,cAAc,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateMeshSkin.d.ts","sourceRoot":"","sources":["../src/updateMeshSkin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAiB5C,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"updateMeshSkin.d.ts","sourceRoot":"","sources":["../src/updateMeshSkin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAiB5C,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAmBzD"}
|
package/dist/updateMeshSkin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getMeshGeometrySkinBindPose, setMeshGeometrySkinBindPose } from '@flighthq/mesh';
|
|
1
|
+
import { getMeshGeometrySkinBindPose, refreshMeshGeometryBounds, setMeshGeometrySkinBindPose } from '@flighthq/mesh';
|
|
2
2
|
import { computeSkeleton3DJointMatrices } from './skeleton3d';
|
|
3
|
-
import { captureMeshSkinBindPose, skinMeshGeometry } from './skinMeshGeometry';
|
|
3
|
+
import { captureMeshSkinBindPose, skinMeshGeometry, updateMeshSkinBindPoseDeformInput } from './skinMeshGeometry';
|
|
4
4
|
// Deforms a skinned mesh into its geometry for the current joint pose — the explicit per-frame
|
|
5
5
|
// skinning call. Run it after the pose is set (an animation clip applied to the joint nodes, or
|
|
6
6
|
// manual joint transforms) and before rendering: it recomputes the skeleton's palette from the
|
|
@@ -24,6 +24,12 @@ export function updateMeshSkin(mesh) {
|
|
|
24
24
|
bindPose = captureMeshSkinBindPose(geometry);
|
|
25
25
|
setMeshGeometrySkinBindPose(geometry, bindPose);
|
|
26
26
|
}
|
|
27
|
+
else if (mesh.morph != null) {
|
|
28
|
+
// The composed update has just written the current morph result into geometry. Refresh only
|
|
29
|
+
// skin's deform input so later morph-weight changes are not frozen into the first captured pose.
|
|
30
|
+
updateMeshSkinBindPoseDeformInput(bindPose, geometry);
|
|
31
|
+
}
|
|
27
32
|
skinMeshGeometry(geometry, skin.skeleton, bindPose);
|
|
33
|
+
refreshMeshGeometryBounds(geometry);
|
|
28
34
|
}
|
|
29
35
|
//# sourceMappingURL=updateMeshSkin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateMeshSkin.js","sourceRoot":"","sources":["../src/updateMeshSkin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"updateMeshSkin.js","sourceRoot":"","sources":["../src/updateMeshSkin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAGrH,OAAO,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,iCAAiC,EAAE,MAAM,oBAAoB,CAAC;AAElH,+FAA+F;AAC/F,gGAAgG;AAChG,+FAA+F;AAC/F,0FAA0F;AAC1F,8FAA8F;AAC9F,oGAAoG;AACpG,oGAAoG;AACpG,iGAAiG;AACjG,EAAE;AACF,sGAAsG;AACtG,sGAAsG;AACtG,wFAAwF;AACxF,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO;IAEzB,8BAA8B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAE9C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,IAAI,QAAQ,GAAG,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IACrD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,QAAQ,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAC7C,2BAA2B,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC9B,4FAA4F;QAC5F,iGAAiG;QACjG,iCAAiC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACpD,yBAAyB,CAAC,QAAQ,CAAC,CAAC;AACtC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/skeleton3d",
|
|
3
|
-
"version": "0.2.1-next.
|
|
3
|
+
"version": "0.2.1-next.530.a223ca9",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@flighthq/
|
|
36
|
-
"@flighthq/
|
|
37
|
-
"@flighthq/
|
|
38
|
-
"@flighthq/
|
|
35
|
+
"@flighthq/entity": "0.2.1-next.530.a223ca9",
|
|
36
|
+
"@flighthq/geometry": "0.2.1-next.530.a223ca9",
|
|
37
|
+
"@flighthq/mesh": "0.2.1-next.530.a223ca9",
|
|
38
|
+
"@flighthq/node": "0.2.1-next.530.a223ca9",
|
|
39
|
+
"@flighthq/scene": "0.2.1-next.530.a223ca9",
|
|
40
|
+
"@flighthq/types": "0.2.1-next.530.a223ca9"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@flighthq/scene": "*",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createEntity } from '@flighthq/entity';
|
|
1
2
|
import { createAabb } from '@flighthq/geometry';
|
|
2
3
|
import type { MeshSkinBindPose, Skeleton3D } from '@flighthq/types';
|
|
3
4
|
|
|
@@ -44,11 +45,11 @@ function bindPoseFor(positions: number[], jointIndices: number[]): MeshSkinBindP
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function skeletonFor(palette: number[]): Skeleton3D {
|
|
47
|
-
return {
|
|
48
|
+
return createEntity({
|
|
48
49
|
inverseBindMatrices: new Float32Array(palette.length),
|
|
49
50
|
jointMatrices: new Float32Array(palette),
|
|
50
51
|
joints: [],
|
|
51
|
-
};
|
|
52
|
+
});
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
describe('getMeshSkinConservativeBounds', () => {
|
package/src/skeleton3d.test.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createMatrix4, getMatrix4Element, setVector3 } from '@flighthq/geometry';
|
|
2
|
-
import { invalidateNodeLocalTransform } from '@flighthq/node';
|
|
2
|
+
import { addNodeChild, getNodeParent, invalidateNodeLocalTransform, setNodeTransform3D } from '@flighthq/node';
|
|
3
3
|
import { createSceneNode } from '@flighthq/scene';
|
|
4
|
-
import
|
|
4
|
+
import { EntityRuntimeKey } from '@flighthq/types';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
cloneSkeleton3D,
|
|
8
|
+
cloneSkeleton3DJointHierarchy,
|
|
8
9
|
computeSkeleton3DJointMatrices,
|
|
9
10
|
createSkeleton3D,
|
|
10
11
|
disposeSkeleton3D,
|
|
@@ -33,6 +34,7 @@ describe('cloneSkeleton3D', () => {
|
|
|
33
34
|
expect(Array.from(clone.inverseBindMatrices)).toEqual(Array.from(skeleton.inverseBindMatrices));
|
|
34
35
|
expect(clone.names).toEqual(['root']);
|
|
35
36
|
expect(clone.names).not.toBe(skeleton.names);
|
|
37
|
+
expect(EntityRuntimeKey in clone).toBe(true);
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
it('preserves a null names field', () => {
|
|
@@ -41,6 +43,42 @@ describe('cloneSkeleton3D', () => {
|
|
|
41
43
|
});
|
|
42
44
|
});
|
|
43
45
|
|
|
46
|
+
describe('cloneSkeleton3DJointHierarchy', () => {
|
|
47
|
+
it('rebuilds joint parentage over fresh nodes and isolates pose updates', () => {
|
|
48
|
+
const root = createSceneNode();
|
|
49
|
+
const child = createSceneNode();
|
|
50
|
+
setVector3(root.position, 1, 0, 0);
|
|
51
|
+
setVector3(child.position, 0, 2, 0);
|
|
52
|
+
invalidateNodeLocalTransform(root);
|
|
53
|
+
invalidateNodeLocalTransform(child);
|
|
54
|
+
addNodeChild(root, child);
|
|
55
|
+
const source = createSkeleton3D([root, child], undefined, ['root', 'child']);
|
|
56
|
+
|
|
57
|
+
const clone = cloneSkeleton3DJointHierarchy(source, (joint) => {
|
|
58
|
+
const out = createSceneNode(joint.kind, joint);
|
|
59
|
+
setNodeTransform3D(out, joint);
|
|
60
|
+
return out;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(clone.joints[0]).not.toBe(root);
|
|
64
|
+
expect(clone.joints[1]).not.toBe(child);
|
|
65
|
+
expect(getNodeParent(clone.joints[0])).toBeNull();
|
|
66
|
+
expect(getNodeParent(clone.joints[1])).toBe(clone.joints[0]);
|
|
67
|
+
expect(getNodeParent(child)).toBe(root);
|
|
68
|
+
expect(clone.inverseBindMatrices).not.toBe(source.inverseBindMatrices);
|
|
69
|
+
expect(clone.names).toEqual(['root', 'child']);
|
|
70
|
+
|
|
71
|
+
setVector3(clone.joints[0].position, 6, 0, 0);
|
|
72
|
+
invalidateNodeLocalTransform(clone.joints[0]);
|
|
73
|
+
computeSkeleton3DJointMatrices(source);
|
|
74
|
+
computeSkeleton3DJointMatrices(clone);
|
|
75
|
+
expect(source.jointMatrices[12]).toBeCloseTo(0);
|
|
76
|
+
expect(clone.jointMatrices[12]).toBeCloseTo(5);
|
|
77
|
+
expect(source.jointMatrices[28]).toBeCloseTo(0);
|
|
78
|
+
expect(clone.jointMatrices[28]).toBeCloseTo(5);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
44
82
|
describe('computeSkeleton3DJointMatrices', () => {
|
|
45
83
|
it('encodes the joint delta from its bind pose', () => {
|
|
46
84
|
const joint = createSceneNode();
|
|
@@ -68,11 +106,9 @@ describe('computeSkeleton3DJointMatrices', () => {
|
|
|
68
106
|
const shared = new Float32Array(32);
|
|
69
107
|
shared.set(temp.inverseBindMatrices);
|
|
70
108
|
|
|
71
|
-
const aliasSkeleton
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
joints: [joint0, joint1],
|
|
75
|
-
};
|
|
109
|
+
const aliasSkeleton = createSkeleton3D([joint0, joint1]);
|
|
110
|
+
aliasSkeleton.inverseBindMatrices = shared;
|
|
111
|
+
aliasSkeleton.jointMatrices = shared;
|
|
76
112
|
|
|
77
113
|
computeSkeleton3DJointMatrices(aliasSkeleton);
|
|
78
114
|
|
|
@@ -131,6 +167,7 @@ describe('createSkeleton3D', () => {
|
|
|
131
167
|
const skeleton = createSkeleton3D([createSceneNode(), createSceneNode()]);
|
|
132
168
|
expect(skeleton.jointMatrices.length).toBe(32);
|
|
133
169
|
expect(skeleton.inverseBindMatrices.length).toBe(32);
|
|
170
|
+
expect(EntityRuntimeKey in skeleton).toBe(true);
|
|
134
171
|
});
|
|
135
172
|
});
|
|
136
173
|
|
|
@@ -2,11 +2,11 @@ import { setVector3 } from '@flighthq/geometry';
|
|
|
2
2
|
import { CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, createMeshGeometry } from '@flighthq/mesh';
|
|
3
3
|
import { invalidateNodeLocalTransform } from '@flighthq/node';
|
|
4
4
|
import { createSceneNode } from '@flighthq/scene';
|
|
5
|
-
import type { MeshGeometry } from '@flighthq/types';
|
|
5
|
+
import type { MeshGeometry, VertexAttributeLayout } from '@flighthq/types';
|
|
6
6
|
import { describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { computeSkeleton3DJointMatrices, createSkeleton3D } from './skeleton3d';
|
|
9
|
-
import { captureMeshSkinBindPose, skinMeshGeometry } from './skinMeshGeometry';
|
|
9
|
+
import { captureMeshSkinBindPose, skinMeshGeometry, updateMeshSkinBindPoseDeformInput } from './skinMeshGeometry';
|
|
10
10
|
|
|
11
11
|
// Builds a single-vertex geometry in the canonical skinned layout (20 floats): position, normal,
|
|
12
12
|
// tangent(0), uv0(0), joints0, weights0.
|
|
@@ -41,6 +41,37 @@ describe('captureMeshSkinBindPose', () => {
|
|
|
41
41
|
expect(bindPose.skinnedPositions.length).toBe(3);
|
|
42
42
|
expect(bindPose.skinnedNormals.length).toBe(3);
|
|
43
43
|
});
|
|
44
|
+
|
|
45
|
+
it('decodes packed joint and weight channels through the shared mesh attribute contract', () => {
|
|
46
|
+
const layout: VertexAttributeLayout = {
|
|
47
|
+
attributes: [
|
|
48
|
+
{ byteOffset: 0, format: 'float32x3', semantic: 'position' },
|
|
49
|
+
{ byteOffset: 12, format: 'float32x3', semantic: 'normal' },
|
|
50
|
+
{ byteOffset: 24, format: 'uint8x4', semantic: 'joints0' },
|
|
51
|
+
{ byteOffset: 28, format: 'unorm8x4', semantic: 'weights0' },
|
|
52
|
+
],
|
|
53
|
+
stride: 32,
|
|
54
|
+
};
|
|
55
|
+
const vertices = new Float32Array(8);
|
|
56
|
+
vertices.set([1, 2, 3, 0, 1, 0]);
|
|
57
|
+
const view = new DataView(vertices.buffer);
|
|
58
|
+
view.setUint8(24, 3);
|
|
59
|
+
view.setUint8(25, 2);
|
|
60
|
+
view.setUint8(26, 1);
|
|
61
|
+
view.setUint8(27, 0);
|
|
62
|
+
view.setUint8(28, 128);
|
|
63
|
+
view.setUint8(29, 64);
|
|
64
|
+
view.setUint8(30, 32);
|
|
65
|
+
view.setUint8(31, 31);
|
|
66
|
+
|
|
67
|
+
const bindPose = captureMeshSkinBindPose(createMeshGeometry({ layout, vertices }));
|
|
68
|
+
|
|
69
|
+
expect(Array.from(bindPose.joints)).toEqual([3, 2, 1, 0]);
|
|
70
|
+
expect(bindPose.weights[0]).toBeCloseTo(128 / 255);
|
|
71
|
+
expect(bindPose.weights[1]).toBeCloseTo(64 / 255);
|
|
72
|
+
expect(bindPose.weights[2]).toBeCloseTo(32 / 255);
|
|
73
|
+
expect(bindPose.weights[3]).toBeCloseTo(31 / 255);
|
|
74
|
+
});
|
|
44
75
|
});
|
|
45
76
|
|
|
46
77
|
describe('skinMeshGeometry', () => {
|
|
@@ -80,3 +111,27 @@ describe('skinMeshGeometry', () => {
|
|
|
80
111
|
expect(geometry.vertices[16]).toBe(1);
|
|
81
112
|
});
|
|
82
113
|
});
|
|
114
|
+
|
|
115
|
+
describe('updateMeshSkinBindPoseDeformInput', () => {
|
|
116
|
+
it('refreshes positions and normals without replacing static influences or scratch', () => {
|
|
117
|
+
const geometry = createOneVertexSkinnedGeometry([1, 2, 3], [0, 1, 0], [2, 0, 0, 0], [1, 0, 0, 0]);
|
|
118
|
+
const bindPose = captureMeshSkinBindPose(geometry);
|
|
119
|
+
const joints = bindPose.joints;
|
|
120
|
+
const weights = bindPose.weights;
|
|
121
|
+
const scratch = bindPose.skinnedPositions;
|
|
122
|
+
geometry.vertices[0] = 4;
|
|
123
|
+
geometry.vertices[1] = 5;
|
|
124
|
+
geometry.vertices[2] = 6;
|
|
125
|
+
geometry.vertices[3] = 1;
|
|
126
|
+
geometry.vertices[4] = 0;
|
|
127
|
+
geometry.vertices[5] = 0;
|
|
128
|
+
|
|
129
|
+
updateMeshSkinBindPoseDeformInput(bindPose, geometry);
|
|
130
|
+
|
|
131
|
+
expect(Array.from(bindPose.positions)).toEqual([4, 5, 6]);
|
|
132
|
+
expect(Array.from(bindPose.normals)).toEqual([1, 0, 0]);
|
|
133
|
+
expect(bindPose.joints).toBe(joints);
|
|
134
|
+
expect(bindPose.weights).toBe(weights);
|
|
135
|
+
expect(bindPose.skinnedPositions).toBe(scratch);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { setVector3 } from '@flighthq/geometry';
|
|
2
|
+
import { CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, createMeshGeometry } from '@flighthq/mesh';
|
|
3
|
+
import { invalidateNodeLocalTransform } from '@flighthq/node';
|
|
4
|
+
import { createMesh, createSceneNode } from '@flighthq/scene';
|
|
5
|
+
import type { MeshMorph } from '@flighthq/types';
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { createSkeleton3D } from './skeleton3d';
|
|
9
|
+
import { updateMeshDeformation } from './updateMeshDeformation';
|
|
10
|
+
|
|
11
|
+
describe('updateMeshDeformation', () => {
|
|
12
|
+
it('recomposes changed morph weights before CPU skinning on every frame', () => {
|
|
13
|
+
const vertices = new Float32Array(20);
|
|
14
|
+
vertices[0] = 1;
|
|
15
|
+
vertices[4] = 1;
|
|
16
|
+
vertices[16] = 1;
|
|
17
|
+
const mesh = createMesh(createMeshGeometry({ layout: CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, vertices }), []);
|
|
18
|
+
const morph: MeshMorph = {
|
|
19
|
+
targets: [{ normalDeltas: null, positionDeltas: new Float32Array([2, 0, 0]), tangentDeltas: null }],
|
|
20
|
+
weights: new Float32Array([1]),
|
|
21
|
+
};
|
|
22
|
+
mesh.morph = morph;
|
|
23
|
+
const joint = createSceneNode();
|
|
24
|
+
mesh.skin = { skeleton: createSkeleton3D([joint]) };
|
|
25
|
+
setVector3(joint.position, 0, 5, 0);
|
|
26
|
+
invalidateNodeLocalTransform(joint);
|
|
27
|
+
|
|
28
|
+
updateMeshDeformation(mesh);
|
|
29
|
+
expect(Array.from(mesh.geometry.vertices.slice(0, 3))).toEqual([3, 5, 0]);
|
|
30
|
+
|
|
31
|
+
morph.weights[0] = 3;
|
|
32
|
+
updateMeshDeformation(mesh);
|
|
33
|
+
expect(Array.from(mesh.geometry.vertices.slice(0, 3))).toEqual([7, 5, 0]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('preserves the standalone morph-only and skin-only cases', () => {
|
|
37
|
+
const morphVertices = new Float32Array(20);
|
|
38
|
+
morphVertices[16] = 1;
|
|
39
|
+
const morphMesh = createMesh(
|
|
40
|
+
createMeshGeometry({ layout: CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, vertices: morphVertices }),
|
|
41
|
+
[],
|
|
42
|
+
);
|
|
43
|
+
morphMesh.morph = {
|
|
44
|
+
targets: [{ normalDeltas: null, positionDeltas: new Float32Array([2, 0, 0]), tangentDeltas: null }],
|
|
45
|
+
weights: new Float32Array([1]),
|
|
46
|
+
};
|
|
47
|
+
updateMeshDeformation(morphMesh);
|
|
48
|
+
expect(morphMesh.geometry.vertices[0]).toBe(2);
|
|
49
|
+
|
|
50
|
+
const skinVertices = new Float32Array(20);
|
|
51
|
+
skinVertices[16] = 1;
|
|
52
|
+
const skinMesh = createMesh(
|
|
53
|
+
createMeshGeometry({ layout: CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, vertices: skinVertices }),
|
|
54
|
+
[],
|
|
55
|
+
);
|
|
56
|
+
const joint = createSceneNode();
|
|
57
|
+
skinMesh.skin = { skeleton: createSkeleton3D([joint]) };
|
|
58
|
+
setVector3(joint.position, 0, 2, 0);
|
|
59
|
+
invalidateNodeLocalTransform(joint);
|
|
60
|
+
updateMeshDeformation(skinMesh);
|
|
61
|
+
expect(skinMesh.geometry.vertices[1]).toBe(2);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -34,6 +34,8 @@ describe('updateMeshSkin', () => {
|
|
|
34
34
|
updateMeshSkin(mesh);
|
|
35
35
|
|
|
36
36
|
expect(mesh.geometry.vertices[1]).toBeCloseTo(5);
|
|
37
|
+
expect(mesh.geometry.bounds?.min.y).toBeCloseTo(5);
|
|
38
|
+
expect(mesh.geometry.bounds?.max.y).toBeCloseTo(5);
|
|
37
39
|
expect(mesh.geometry.version).toBe(versionBefore + 1);
|
|
38
40
|
expect(getMeshGeometrySkinBindPose(mesh.geometry)).not.toBeNull();
|
|
39
41
|
});
|