@flighthq/skeleton3d 0.3.1-next.407.d03a0bb → 0.3.1-next.425.a3192ab

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 CHANGED
@@ -1,2 +1,2 @@
1
- export { captureMeshSkinBindPose, cloneSkeleton3D, cloneSkeleton3DJointHierarchy, computeSkeleton3DJointMatrices, createSkeleton3D, disposeSkeleton3D, equalsSkeleton3D, getMeshSkinConservativeBounds, getMeshSkinExactBounds, getSkeleton3DJointIndexByName, getSkeleton3DJointWorldMatrix, getSkeleton3DJointWorldMatrixByName, prepareMeshSkinning, prepareScene3DSkinning, setSkeleton3DBindPose, skinMeshGeometry, skinVertices, updateMeshDeformation, updateMeshSkin, updateMeshSkinBindPoseDeformInput, validateSkeleton3D, } from './contract';
1
+ export { captureMeshSkinBindPose, cloneSkeleton3D, cloneSkeleton3DJointHierarchy, computeSkeleton3DJointMatrices, createSkeleton3D, disposeSkeleton3D, equalsSkeleton3D, getMeshSkinConservativeBounds, getMeshSkinExactBounds, getSkeleton3DJointIndexByName, getSkeleton3DJointWorldMatrix, getSkeleton3DJointWorldMatrixByName, prepareMeshSkinning, prepareScene3DSkinning, setSkeleton3DBindPose, skinMeshGeometry, skinTangents, skinVertices, updateMeshDeformation, updateMeshSkin, updateMeshSkinBindPoseDeformInput, validateSkeleton3D, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,6BAA6B,EAC7B,8BAA8B,EAC9B,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,6BAA6B,EAC7B,sBAAsB,EACtB,6BAA6B,EAC7B,6BAA6B,EAC7B,mCAAmC,EACnC,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,iCAAiC,EACjC,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,6BAA6B,EAC7B,8BAA8B,EAC9B,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,6BAA6B,EAC7B,sBAAsB,EACtB,6BAA6B,EAC7B,6BAA6B,EAC7B,mCAAmC,EACnC,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,iCAAiC,EACjC,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { captureMeshSkinBindPose, cloneSkeleton3D, cloneSkeleton3DJointHierarchy, computeSkeleton3DJointMatrices, createSkeleton3D, disposeSkeleton3D, equalsSkeleton3D, getMeshSkinConservativeBounds, getMeshSkinExactBounds, getSkeleton3DJointIndexByName, getSkeleton3DJointWorldMatrix, getSkeleton3DJointWorldMatrixByName, prepareMeshSkinning, prepareScene3DSkinning, setSkeleton3DBindPose, skinMeshGeometry, skinVertices, updateMeshDeformation, updateMeshSkin, updateMeshSkinBindPoseDeformInput, validateSkeleton3D, } from './contract';
1
+ export { captureMeshSkinBindPose, cloneSkeleton3D, cloneSkeleton3DJointHierarchy, computeSkeleton3DJointMatrices, createSkeleton3D, disposeSkeleton3D, equalsSkeleton3D, getMeshSkinConservativeBounds, getMeshSkinExactBounds, getSkeleton3DJointIndexByName, getSkeleton3DJointWorldMatrix, getSkeleton3DJointWorldMatrixByName, prepareMeshSkinning, prepareScene3DSkinning, setSkeleton3DBindPose, skinMeshGeometry, skinTangents, skinVertices, updateMeshDeformation, updateMeshSkin, updateMeshSkinBindPoseDeformInput, validateSkeleton3D, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,6BAA6B,EAC7B,8BAA8B,EAC9B,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,6BAA6B,EAC7B,sBAAsB,EACtB,6BAA6B,EAC7B,6BAA6B,EAC7B,mCAAmC,EACnC,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,iCAAiC,EACjC,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,6BAA6B,EAC7B,8BAA8B,EAC9B,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,6BAA6B,EAC7B,sBAAsB,EACtB,6BAA6B,EAC7B,6BAA6B,EAC7B,mCAAmC,EACnC,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,iCAAiC,EACjC,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
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,0BAA0B,CAAC;AAWlH,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"}
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,0BAA0B,CAAC;AAWlH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,gBAAgB,CA+D1F;AASD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC9B,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GACnC,IAAI,CA8CN;AAMD,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAsBpH"}
@@ -1,5 +1,5 @@
1
1
  import { getMeshGeometryVertexJoints0, getMeshGeometryVertexWeights0 } from '@flighthq/mesh/contract';
2
- import { skinVertices } from './skinVertices';
2
+ import { skinTangents, skinVertices } from './skinVertices';
3
3
  // Captures the de-interleaved CPU-skinning inputs for one skinned MeshGeometry: the bind-pose
4
4
  // positions/normals and the static joints0/weights0 influences, read out of the interleaved
5
5
  // `geometry.vertices` through `geometry.layout` (any offsets, any layout carrying the four
@@ -15,6 +15,10 @@ export function captureMeshSkinBindPose(geometry) {
15
15
  const normalOffset = floatOffsetForSemantic(layout, 'normal');
16
16
  const positions = new Float32Array(vertexCount * 3);
17
17
  const normals = new Float32Array(vertexCount * 3);
18
+ // Zero-length when the layout carries no tangent semantic, so the per-frame deform iterates nothing
19
+ // rather than blending meaningless zeros back into a channel the geometry does not have.
20
+ const tangentOffset = floatOffsetForSemantic(layout, 'tangent');
21
+ const tangents = new Float32Array(tangentOffset >= 0 ? vertexCount * 4 : 0);
18
22
  const joints = new Float32Array(vertexCount * 4);
19
23
  const weights = new Float32Array(vertexCount * 4);
20
24
  const joint = { w: 0, x: 0, y: 0, z: 0 };
@@ -33,6 +37,13 @@ export function captureMeshSkinBindPose(geometry) {
33
37
  normals[p + 1] = vertices[base + normalOffset + 1];
34
38
  normals[p + 2] = vertices[base + normalOffset + 2];
35
39
  }
40
+ if (tangentOffset >= 0) {
41
+ // All four floats: xyz is re-blended every frame, w is the handedness sign and rides along.
42
+ tangents[w] = vertices[base + tangentOffset];
43
+ tangents[w + 1] = vertices[base + tangentOffset + 1];
44
+ tangents[w + 2] = vertices[base + tangentOffset + 2];
45
+ tangents[w + 3] = vertices[base + tangentOffset + 3];
46
+ }
36
47
  if (getMeshGeometryVertexJoints0(joint, geometry, v)) {
37
48
  joints[w] = joint.x;
38
49
  joints[w + 1] = joint.y;
@@ -52,6 +63,8 @@ export function captureMeshSkinBindPose(geometry) {
52
63
  positions,
53
64
  skinnedNormals: new Float32Array(vertexCount * 3),
54
65
  skinnedPositions: new Float32Array(vertexCount * 3),
66
+ skinnedTangents: new Float32Array(tangents.length),
67
+ tangents,
55
68
  weights,
56
69
  };
57
70
  }
@@ -64,11 +77,15 @@ export function captureMeshSkinBindPose(geometry) {
64
77
  // the runtime slot and drives the palette is updateMeshSkin, beside this file.
65
78
  export function skinMeshGeometry(geometry, skeleton, bindPose) {
66
79
  skinVertices(bindPose.skinnedPositions, bindPose.skinnedNormals, bindPose.positions, bindPose.normals, bindPose.joints, bindPose.weights, skeleton.jointMatrices);
80
+ skinTangents(bindPose.skinnedTangents, bindPose.tangents, bindPose.joints, bindPose.weights, skeleton.jointMatrices);
67
81
  const { layout, vertices } = geometry;
68
82
  const floatsPerVertex = layout.stride / 4;
69
83
  const positionOffset = floatOffsetForSemantic(layout, 'position');
70
84
  const normalOffset = floatOffsetForSemantic(layout, 'normal');
71
- const { skinnedNormals, skinnedPositions } = bindPose;
85
+ // Skinned from the BIND POSE every frame, never from the vertices written last frame — re-skinning an
86
+ // already-skinned tangent would compound the pose instead of replacing it.
87
+ const tangentOffset = bindPose.tangents.length > 0 ? floatOffsetForSemantic(layout, 'tangent') : -1;
88
+ const { skinnedNormals, skinnedPositions, skinnedTangents } = bindPose;
72
89
  const vertexCount = (skinnedPositions.length / 3) | 0;
73
90
  for (let v = 0; v < vertexCount; v++) {
74
91
  const base = v * floatsPerVertex;
@@ -83,6 +100,14 @@ export function skinMeshGeometry(geometry, skeleton, bindPose) {
83
100
  vertices[base + normalOffset + 1] = skinnedNormals[s + 1];
84
101
  vertices[base + normalOffset + 2] = skinnedNormals[s + 2];
85
102
  }
103
+ if (tangentOffset >= 0) {
104
+ // Four floats: the blended direction, then the handedness sign exactly as it was captured.
105
+ const t = v * 4;
106
+ vertices[base + tangentOffset] = skinnedTangents[t];
107
+ vertices[base + tangentOffset + 1] = skinnedTangents[t + 1];
108
+ vertices[base + tangentOffset + 2] = skinnedTangents[t + 2];
109
+ vertices[base + tangentOffset + 3] = skinnedTangents[t + 3];
110
+ }
86
111
  }
87
112
  geometry.version++;
88
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"skinMeshGeometry.js","sourceRoot":"","sources":["../src/skinMeshGeometry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skinMeshGeometry.js","sourceRoot":"","sources":["../src/skinMeshGeometry.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
+ export declare function skinTangents(outTangents: Float32Array, tangents: Readonly<Float32Array>, joints: Readonly<ArrayLike<number>>, weights: Readonly<Float32Array>, jointMatrices: Readonly<Float32Array>): void;
1
2
  export declare function skinVertices(outPositions: Float32Array, outNormals: Float32Array, positions: Readonly<Float32Array>, normals: Readonly<Float32Array>, joints: Readonly<ArrayLike<number>>, weights: Readonly<Float32Array>, jointMatrices: Readonly<Float32Array>): void;
2
3
  //# sourceMappingURL=skinVertices.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"skinVertices.d.ts","sourceRoot":"","sources":["../src/skinVertices.ts"],"names":[],"mappings":"AAeA,wBAAgB,YAAY,CAC1B,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,YAAY,EACxB,SAAS,EAAE,QAAQ,CAAC,YAAY,CAAC,EACjC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EACnC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,aAAa,EAAE,QAAQ,CAAC,YAAY,CAAC,GACpC,IAAI,CAkDN"}
1
+ {"version":3,"file":"skinVertices.d.ts","sourceRoot":"","sources":["../src/skinVertices.ts"],"names":[],"mappings":"AAeA,wBAAgB,YAAY,CAC1B,WAAW,EAAE,YAAY,EACzB,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,EAChC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EACnC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,aAAa,EAAE,QAAQ,CAAC,YAAY,CAAC,GACpC,IAAI,CAsCN;AAiBD,wBAAgB,YAAY,CAC1B,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,YAAY,EACxB,SAAS,EAAE,QAAQ,CAAC,YAAY,CAAC,EACjC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EACnC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,aAAa,EAAE,QAAQ,CAAC,YAAY,CAAC,GACpC,IAAI,CAkDN"}
@@ -1,3 +1,53 @@
1
+ // Linear-blend-skins tangents by the same palette, 4 floats per vertex in and out.
2
+ //
3
+ // ★ `w` IS HANDEDNESS, NOT A COORDINATE, AND IS COPIED THROUGH UNTOUCHED. It records which way the
4
+ // bitangent runs (B = w * cross(N, T)), so it is a sign rather than a direction: rotating it with the
5
+ // vector, or re-deriving it after skinning, flips the bitangent across whole regions of the surface.
6
+ // That reaches the eye as inverted lighting rather than as a crash, so it is a defect that ships.
7
+ //
8
+ // ★ THE xyz TRANSFORM MUST STAY IDENTICAL TO THE ONE `skinVertices` APPLIES TO NORMALS — upper 3x3 of
9
+ // each palette matrix, no translation, no renormalization. Orthogonality of N and T survives skinning
10
+ // only because both go through the same transform; "improving" one side alone breaks N·T=0 for a
11
+ // reason that has nothing to do with the improvement. `skinVertices` and this function are checked
12
+ // against each other by a test that skins the same vector through both and requires equal results.
13
+ //
14
+ // `outTangents` may alias `tangents`: each vertex's input is read into locals before any output is
15
+ // written. Not renormalized, matching the normal path — a caller needing unit tangents does that after.
16
+ export function skinTangents(outTangents, tangents, joints, weights, jointMatrices) {
17
+ const vertexCount = (tangents.length / 4) | 0;
18
+ for (let v = 0; v < vertexCount; v++) {
19
+ const t = v * 4;
20
+ const tx = tangents[t];
21
+ const ty = tangents[t + 1];
22
+ const tz = tangents[t + 2];
23
+ const handedness = tangents[t + 3];
24
+ let otx = 0;
25
+ let oty = 0;
26
+ let otz = 0;
27
+ for (let k = 0; k < 4; k++) {
28
+ const weight = weights[t + k];
29
+ if (weight === 0)
30
+ continue;
31
+ const m = joints[t + k] * 16;
32
+ const m0 = jointMatrices[m];
33
+ const m1 = jointMatrices[m + 1];
34
+ const m2 = jointMatrices[m + 2];
35
+ const m4 = jointMatrices[m + 4];
36
+ const m5 = jointMatrices[m + 5];
37
+ const m6 = jointMatrices[m + 6];
38
+ const m8 = jointMatrices[m + 8];
39
+ const m9 = jointMatrices[m + 9];
40
+ const m10 = jointMatrices[m + 10];
41
+ otx += weight * (m0 * tx + m4 * ty + m8 * tz);
42
+ oty += weight * (m1 * tx + m5 * ty + m9 * tz);
43
+ otz += weight * (m2 * tx + m6 * ty + m10 * tz);
44
+ }
45
+ outTangents[t] = otx;
46
+ outTangents[t + 1] = oty;
47
+ outTangents[t + 2] = otz;
48
+ outTangents[t + 3] = handedness;
49
+ }
50
+ }
1
51
  // CPU linear-blend skinning (a.k.a. smooth/matrix-palette skinning). Deforms each vertex on the CPU by the
2
52
  // weighted sum of its influencing joints' palette matrices — the same math a GPU vertex shader runs, kept
3
53
  // here as a deterministic, GPU-free path for offscreen bakes, physics proxies, and jsdom tests.
@@ -1 +1 @@
1
- {"version":3,"file":"skinVertices.js","sourceRoot":"","sources":["../src/skinVertices.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skinVertices.js","sourceRoot":"","sources":["../src/skinVertices.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/skeleton3d",
3
- "version": "0.3.1-next.407.d03a0bb",
3
+ "version": "0.3.1-next.425.a3192ab",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,11 +38,11 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/entity": "0.3.1-next.407.d03a0bb",
42
- "@flighthq/geometry": "0.3.1-next.407.d03a0bb",
43
- "@flighthq/mesh": "0.3.1-next.407.d03a0bb",
44
- "@flighthq/node": "0.3.1-next.407.d03a0bb",
45
- "@flighthq/types": "0.3.1-next.407.d03a0bb"
41
+ "@flighthq/entity": "0.3.1-next.425.a3192ab",
42
+ "@flighthq/geometry": "0.3.1-next.425.a3192ab",
43
+ "@flighthq/mesh": "0.3.1-next.425.a3192ab",
44
+ "@flighthq/node": "0.3.1-next.425.a3192ab",
45
+ "@flighthq/types": "0.3.1-next.425.a3192ab"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@flighthq/scene3d": "*",
@@ -40,6 +40,8 @@ function bindPoseFor(positions: number[], jointIndices: number[]): MeshSkinBindP
40
40
  positions: new Float32Array(positions),
41
41
  skinnedNormals: new Float32Array(count * 3),
42
42
  skinnedPositions: new Float32Array(count * 3),
43
+ skinnedTangents: new Float32Array(0),
44
+ tangents: new Float32Array(0),
43
45
  weights,
44
46
  };
45
47
  }
@@ -1,4 +1,4 @@
1
- import { setVector3 } from '@flighthq/geometry/contract';
1
+ import { setQuaternion, setVector3 } from '@flighthq/geometry/contract';
2
2
  import { CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, createMeshGeometry } from '@flighthq/mesh/contract';
3
3
  import { invalidateNodeLocalTransform } from '@flighthq/node/contract';
4
4
  import { createNode3D } from '@flighthq/scene3d/contract';
@@ -15,6 +15,7 @@ function createOneVertexSkinnedGeometry(
15
15
  normal: readonly [number, number, number],
16
16
  joints: readonly [number, number, number, number],
17
17
  weights: readonly [number, number, number, number],
18
+ tangent: readonly [number, number, number, number] = [0, 0, 0, 0],
18
19
  ): MeshGeometry {
19
20
  const vertices = new Float32Array(20);
20
21
  vertices[0] = position[0];
@@ -23,6 +24,7 @@ function createOneVertexSkinnedGeometry(
23
24
  vertices[3] = normal[0];
24
25
  vertices[4] = normal[1];
25
26
  vertices[5] = normal[2];
27
+ vertices.set(tangent, 6);
26
28
  vertices.set(joints, 12);
27
29
  vertices.set(weights, 16);
28
30
  return createMeshGeometry({ layout: CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, vertices });
@@ -110,6 +112,57 @@ describe('skinMeshGeometry', () => {
110
112
  expect(geometry.vertices[4]).toBeCloseTo(1);
111
113
  expect(geometry.vertices[16]).toBe(1);
112
114
  });
115
+
116
+ it('rewrites the tangent channel in the geometry, not just the scratch', () => {
117
+ // ★ THIS IS THE ONE THAT CATCHES A MISSING WRITE-BACK. Every unit test of the blend itself passes
118
+ // while `skinMeshGeometry` never copies the result into `geometry.vertices`, because those tests
119
+ // call the blend directly. Only reading the interleaved buffer after a skin proves the channel was
120
+ // actually updated. A rotation is required to see it: under a translation a correct tangent and an
121
+ // un-skinned one are identical, so the assertion could not fail.
122
+ const geometry = createOneVertexSkinnedGeometry([1, 0, 0], [0, 1, 0], [0, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, -1]);
123
+ const joint = createNode3D();
124
+ const skeleton = createSkeleton3D([joint]);
125
+ const bindPose = captureMeshSkinBindPose(geometry);
126
+ // 90 degrees about Z.
127
+ setQuaternion(joint.rotation, 0, 0, Math.SQRT1_2, Math.SQRT1_2);
128
+ invalidateNodeLocalTransform(joint);
129
+
130
+ computeSkeleton3DJointMatrices(skeleton);
131
+ skinMeshGeometry(geometry, skeleton, bindPose);
132
+
133
+ // Tangent occupies floats 6..9: x rotates onto +y.
134
+ expect(geometry.vertices[6]).toBeCloseTo(0);
135
+ expect(geometry.vertices[7]).toBeCloseTo(1);
136
+ expect(geometry.vertices[8]).toBeCloseTo(0);
137
+ // w is handedness and must survive untouched, including the negative sign.
138
+ expect(geometry.vertices[9]).toBe(-1);
139
+ });
140
+
141
+ it('keeps the skinned tangent orthogonal to the skinned normal', () => {
142
+ // The user-visible defect: normal skinned, tangent frozen at bind pose, so N·T drifted off zero in
143
+ // every pose away from bind. Read back from the interleaved buffer, which is what the shader sees.
144
+ const geometry = createOneVertexSkinnedGeometry([1, 0, 0], [0, 1, 0], [0, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 1]);
145
+ const joint = createNode3D();
146
+ const skeleton = createSkeleton3D([joint]);
147
+ const bindPose = captureMeshSkinBindPose(geometry);
148
+ setQuaternion(joint.rotation, 0, 0, Math.SQRT1_2, Math.SQRT1_2);
149
+ invalidateNodeLocalTransform(joint);
150
+
151
+ computeSkeleton3DJointMatrices(skeleton);
152
+ skinMeshGeometry(geometry, skeleton, bindPose);
153
+
154
+ // ★ NON-DEGENERACY FIRST. A zero tangent is orthogonal to everything, so the dot product below
155
+ // passes trivially on an un-skinned or unwritten channel — which it did, while the write-back was
156
+ // in fact missing. Requiring a real length is what makes the orthogonality assertion mean anything.
157
+ const length = Math.hypot(geometry.vertices[6], geometry.vertices[7], geometry.vertices[8]);
158
+ expect(length).toBeCloseTo(1);
159
+
160
+ const dot =
161
+ geometry.vertices[3] * geometry.vertices[6] +
162
+ geometry.vertices[4] * geometry.vertices[7] +
163
+ geometry.vertices[5] * geometry.vertices[8];
164
+ expect(dot).toBeCloseTo(0);
165
+ });
113
166
  });
114
167
 
115
168
  describe('updateMeshSkinBindPoseDeformInput', () => {
@@ -1,4 +1,4 @@
1
- import { skinVertices } from './skinVertices';
1
+ import { skinTangents, skinVertices } from './skinVertices';
2
2
 
3
3
  // A column-major 4x4 identity, then variants with translation/scale, laid out as the 16-float palette entry.
4
4
  function identity(): number[] {
@@ -19,6 +19,114 @@ function translation(tx: number, ty: number, tz: number): number[] {
19
19
  return m;
20
20
  }
21
21
 
22
+ describe('skinTangents', () => {
23
+ // A 90-degree rotation about Z, column-major. A pure rotation is used deliberately: N·T = 0 survives
24
+ // skinning exactly only under a rigid transform, where transforming the tangent as a vector by M and
25
+ // the normal as a covector by M⁻ᵀ coincide. Under non-uniform scale they diverge and orthogonality is
26
+ // not something this code promises, so it is not asserted there.
27
+ // prettier-ignore
28
+ const rotateZ90 = [
29
+ 0, 1, 0, 0,
30
+ -1, 0, 0, 0,
31
+ 0, 0, 1, 0,
32
+ 0, 0, 0, 1,
33
+ ];
34
+ const singleJoint = { joints: new Float32Array([0, 0, 0, 0]), weights: new Float32Array([1, 0, 0, 0]) };
35
+
36
+ it('rotates the tangent direction with the pose', () => {
37
+ const tangents = new Float32Array([1, 0, 0, 1]);
38
+ const out = new Float32Array(4);
39
+ skinTangents(out, tangents, singleJoint.joints, singleJoint.weights, new Float32Array(rotateZ90));
40
+ expect(out[0]).toBeCloseTo(0);
41
+ expect(out[1]).toBeCloseTo(1);
42
+ expect(out[2]).toBeCloseTo(0);
43
+ });
44
+
45
+ it('carries w through UNCHANGED, because it is handedness and not a coordinate', () => {
46
+ // Rotating or re-deriving w flips the bitangent across whole regions of the surface, which reaches
47
+ // the eye as inverted lighting rather than as a crash. Both signs are checked: a transform that
48
+ // happened to preserve +1 could still destroy -1.
49
+ const positive = new Float32Array(4);
50
+ const negative = new Float32Array(4);
51
+ skinTangents(
52
+ positive,
53
+ new Float32Array([1, 0, 0, 1]),
54
+ singleJoint.joints,
55
+ singleJoint.weights,
56
+ new Float32Array(rotateZ90),
57
+ );
58
+ skinTangents(
59
+ negative,
60
+ new Float32Array([1, 0, 0, -1]),
61
+ singleJoint.joints,
62
+ singleJoint.weights,
63
+ new Float32Array(rotateZ90),
64
+ );
65
+ expect(positive[3]).toBe(1);
66
+ expect(negative[3]).toBe(-1);
67
+ });
68
+
69
+ it('keeps the tangent orthogonal to the normal it was orthogonal to', () => {
70
+ // The defect this fixes: the normal was skinned and the tangent was not, so N·T drifted away from
71
+ // zero in any pose off the bind pose. Starting orthogonal, they must stay orthogonal under a rotation.
72
+ const palette = new Float32Array(rotateZ90);
73
+ const outNormals = new Float32Array(3);
74
+ const outTangents = new Float32Array(4);
75
+ skinVertices(
76
+ new Float32Array(3),
77
+ outNormals,
78
+ new Float32Array([0, 0, 0]),
79
+ new Float32Array([0, 1, 0]),
80
+ singleJoint.joints,
81
+ singleJoint.weights,
82
+ palette,
83
+ );
84
+ skinTangents(outTangents, new Float32Array([1, 0, 0, 1]), singleJoint.joints, singleJoint.weights, palette);
85
+ const dot = outNormals[0] * outTangents[0] + outNormals[1] * outTangents[1] + outNormals[2] * outTangents[2];
86
+ expect(dot).toBeCloseTo(0);
87
+ });
88
+
89
+ it('applies the SAME transform skinVertices applies to normals', () => {
90
+ // ★ THIS IS THE ANTI-DIVERGENCE GUARD, NOT A DUPLICATE OF THE ORTHOGONALITY TEST. Orthogonality is
91
+ // preserved only because both channels go through one transform; if a later change "improves" one
92
+ // path alone the two silently stop matching, and N·T fails for a reason unrelated to that change.
93
+ // Feeding the same vector through both and demanding equal xyz is what makes that divergence loud.
94
+ const palette = new Float32Array(rotateZ90);
95
+ const vector = [0.3, -0.7, 0.5];
96
+ const outNormals = new Float32Array(3);
97
+ const outTangents = new Float32Array(4);
98
+ skinVertices(
99
+ new Float32Array(3),
100
+ outNormals,
101
+ new Float32Array([0, 0, 0]),
102
+ new Float32Array(vector),
103
+ singleJoint.joints,
104
+ singleJoint.weights,
105
+ palette,
106
+ );
107
+ skinTangents(outTangents, new Float32Array([...vector, 1]), singleJoint.joints, singleJoint.weights, palette);
108
+ expect(outTangents[0]).toBeCloseTo(outNormals[0]);
109
+ expect(outTangents[1]).toBeCloseTo(outNormals[1]);
110
+ expect(outTangents[2]).toBeCloseTo(outNormals[2]);
111
+ });
112
+
113
+ it('is safe when the output aliases the input', () => {
114
+ const tangents = new Float32Array([1, 0, 0, -1]);
115
+ skinTangents(tangents, tangents, singleJoint.joints, singleJoint.weights, new Float32Array(rotateZ90));
116
+ expect(tangents[1]).toBeCloseTo(1);
117
+ expect(tangents[3]).toBe(-1);
118
+ });
119
+
120
+ it('iterates nothing when the geometry carries no tangent channel', () => {
121
+ // A zero-length capture is how "this layout has no tangents" is expressed, so it must be a no-op
122
+ // rather than an out-of-range read.
123
+ const out = new Float32Array(0);
124
+ expect(() =>
125
+ skinTangents(out, new Float32Array(0), singleJoint.joints, singleJoint.weights, new Float32Array(rotateZ90)),
126
+ ).not.toThrow();
127
+ });
128
+ });
129
+
22
130
  describe('skinVertices', () => {
23
131
  it('passes vertices through unchanged when the only weighted joint is identity', () => {
24
132
  const positions = new Float32Array([2, 3, 4]);