@instruktlabs/kiln 1.2.2 → 1.2.3
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +74 -0
- package/README.md +7 -0
- package/dist/agent-run.mjs +6392 -1477
- package/dist/build.json +23 -23
- package/dist/cli.mjs +8453 -3407
- package/dist/evaluator-worker.mjs +16608 -12387
- package/dist/mcp-engine.mjs +6746 -1725
- package/dist/mcp-server.mjs +4 -4
- package/dist/viewer/app.js +203 -199
- package/dist/viewer/chat.html +209 -205
- package/dist/viewer/style.css +10 -0
- package/docs/atlas-provenance.md +23 -0
- package/docs/community-exporter.md +27 -0
- package/docs/export-profiles.md +20 -0
- package/docs/material-library.md +9 -5
- package/docs/procedural-surfaces-v3.md +276 -0
- package/docs/projects-and-live-review.md +1 -1
- package/docs/rendering.md +41 -0
- package/docs/sdk.md +53 -0
- package/docs/tools-operations.md +2 -2
- package/docs/tools.md +3 -3
- package/lib/asset-materials-node.d.ts +3 -3
- package/lib/asset-materials-node.js +29 -33
- package/lib/assets-node.d.ts +1 -1
- package/lib/assets-node.js +11 -4
- package/lib/assets.d.ts +4 -2
- package/lib/assets.js +61 -16
- package/lib/atlas-provenance.d.ts +86 -0
- package/lib/atlas-provenance.js +448 -0
- package/lib/character.d.ts +11 -2
- package/lib/character.js +90 -2
- package/lib/community-exporter.d.ts +3 -2
- package/lib/community-exporter.js +139 -3
- package/lib/discovery/helper-contracts.js +161 -7
- package/lib/discovery/helper-specs.js +49 -4
- package/lib/discovery/shapes.js +20 -7
- package/lib/engine-identity.d.ts +1 -1
- package/lib/engine-identity.js +1 -1
- package/lib/evaluator/authoring-diagnostic.d.ts +1 -1
- package/lib/evaluator/authoring-diagnostic.js +12 -0
- package/lib/evaluator/protocol.d.ts +2 -2
- package/lib/evaluator/protocol.js +16 -6
- package/lib/evaluator/subprocess.js +2 -2
- package/lib/exporter-node.js +9 -1
- package/lib/geometry-budget.d.ts +7 -0
- package/lib/geometry-budget.js +8 -0
- package/lib/gltf-texture-binding.d.ts +6 -0
- package/lib/gltf-texture-binding.js +54 -0
- package/lib/material-authoring-node.d.ts +6 -0
- package/lib/material-authoring-node.js +49 -0
- package/lib/material-authoring-schema.d.ts +871 -0
- package/lib/material-authoring-schema.js +310 -0
- package/lib/material-export.d.ts +5 -0
- package/lib/material-export.js +21 -0
- package/lib/material-library-node.d.ts +34 -8
- package/lib/material-library-node.js +691 -11
- package/lib/material-library.d.ts +3704 -4
- package/lib/material-library.js +272 -0
- package/lib/mcp-core.d.ts +1 -1
- package/lib/pinned-material-map.d.ts +33 -0
- package/lib/pinned-material-map.js +1 -0
- package/lib/portable-material-runtime.d.ts +5 -0
- package/lib/portable-material-runtime.js +160 -0
- package/lib/portable-sampler-runtime.d.ts +4 -0
- package/lib/portable-sampler-runtime.js +26 -0
- package/lib/portable-sampler.d.ts +11 -0
- package/lib/portable-sampler.js +42 -0
- package/lib/primitives.d.ts +15 -3
- package/lib/primitives.js +24 -4
- package/lib/procedural-material-v2.d.ts +2 -0
- package/lib/procedural-material-v2.js +2 -1
- package/lib/procedural-material-v3.d.ts +72 -0
- package/lib/procedural-material-v3.js +162 -0
- package/lib/procedural-texture-v2-compiler.d.ts +31 -0
- package/lib/procedural-texture-v2-compiler.js +172 -0
- package/lib/procedural-texture-v3-contract.d.ts +125 -0
- package/lib/procedural-texture-v3-contract.js +333 -0
- package/lib/procedural-texture-v3.d.ts +12 -0
- package/lib/procedural-texture-v3.js +165 -0
- package/lib/procedural-texture.d.ts +9 -31
- package/lib/procedural-texture.js +43 -170
- package/lib/project-bundle-node.d.ts +1 -1
- package/lib/project-bundle-node.js +70 -13
- package/lib/project-bundle.d.ts +91 -3
- package/lib/project-bundle.js +48 -7
- package/lib/qa/atlas.d.ts +21 -0
- package/lib/qa/atlas.js +474 -0
- package/lib/qa/index.d.ts +1 -0
- package/lib/qa/index.js +1 -0
- package/lib/qa/material.js +2 -0
- package/lib/qa/skin-integrity.d.ts +18 -0
- package/lib/qa/skin-integrity.js +221 -0
- package/lib/render-service-host.js +4 -7
- package/lib/render.d.ts +3 -3
- package/lib/render.js +108 -16
- package/lib/skin-binding.d.ts +20 -0
- package/lib/skin-binding.js +144 -0
- package/lib/texture-atlas-contract.d.ts +97 -0
- package/lib/texture-atlas-contract.js +414 -0
- package/lib/texture-atlas-pixels.d.ts +73 -0
- package/lib/texture-atlas-pixels.js +290 -0
- package/lib/texture-atlas.d.ts +10 -0
- package/lib/texture-atlas.js +143 -0
- package/lib/texture-bake.d.ts +3 -0
- package/lib/texture-bake.js +66 -11
- package/lib/texture-resolver.d.ts +22 -0
- package/lib/texture-resolver.js +24 -0
- package/lib/tools/materials.js +8 -7
- package/lib/tools/registry.js +5 -16
- package/lib/tools/review.js +1 -1
- package/lib/uv-data.d.ts +32 -0
- package/lib/uv-data.js +172 -0
- package/lib/uv-shapes.js +3 -0
- package/lib/uv.d.ts +9 -3
- package/lib/uv.js +53 -82
- package/lib/views/camera.d.ts +3 -3
- package/lib/views/camera.js +32 -28
- package/lib/views/face-policy.d.ts +9 -0
- package/lib/views/face-policy.js +16 -0
- package/lib/views/glb.js +218 -24
- package/lib/views/pose.d.ts +7 -1
- package/lib/views/pose.js +63 -12
- package/lib/views/raster.d.ts +6 -2
- package/lib/views/raster.js +49 -37
- package/lib/views/scene-copy.d.ts +6 -0
- package/lib/views/scene-copy.js +73 -0
- package/lib/views/scene-raster.js +2 -1
- package/lib/views/triangle-coverage.d.ts +8 -0
- package/lib/views/triangle-coverage.js +58 -0
- package/lib/workspace-node.js +7 -3
- package/lib/workspace.d.ts +2 -2
- package/package.json +3 -1
- package/plugin.json +1 -1
- package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
- package/plugins/kiln-engine/README.md +3 -3
- package/plugins/kiln-engine/package-provenance.json +6 -6
- package/plugins/kiln-engine/plugin.json +1 -1
- package/plugins/kiln-engine/runtime.json +1 -1
- package/skills/kiln-author-asset/references/geometry-recipes.md +124 -0
- package/skills/kiln-author-asset/references/projects-and-materials.md +32 -2
- package/skills/kiln-compose-scene/references/runtime-scenes.md +19 -0
- package/skills/kiln-qa-asset/references/integration-checks.md +37 -2
- package/src/asset-materials-node.ts +38 -45
- package/src/assets-node.ts +22 -5
- package/src/assets.ts +61 -20
- package/src/atlas-provenance.ts +522 -0
- package/src/character.ts +107 -2
- package/src/community-exporter.ts +143 -3
- package/src/discovery/helper-contracts.ts +188 -7
- package/src/discovery/helper-specs.ts +69 -4
- package/src/discovery/shapes.ts +25 -9
- package/src/engine-identity.ts +1 -1
- package/src/evaluator/authoring-diagnostic.ts +19 -1
- package/src/evaluator/protocol.ts +19 -9
- package/src/evaluator/subprocess.ts +2 -2
- package/src/exporter-node.ts +12 -1
- package/src/generated/mcp-manifest.json +3 -3
- package/src/geometry-budget.ts +8 -0
- package/src/gltf-texture-binding.ts +60 -0
- package/src/material-authoring-node.ts +68 -0
- package/src/material-authoring-schema.ts +343 -0
- package/src/material-export.ts +25 -0
- package/src/material-library-node.ts +840 -16
- package/src/material-library.ts +348 -5
- package/src/pinned-material-map.ts +43 -0
- package/src/portable-material-runtime.ts +200 -0
- package/src/portable-sampler-runtime.ts +40 -0
- package/src/portable-sampler.ts +54 -0
- package/src/primitives.ts +98 -4
- package/src/procedural-material-v2.ts +2 -1
- package/src/procedural-material-v3.ts +224 -0
- package/src/procedural-texture-v2-compiler.ts +234 -0
- package/src/procedural-texture-v3-contract.ts +437 -0
- package/src/procedural-texture-v3.ts +220 -0
- package/src/procedural-texture.ts +61 -229
- package/src/project-bundle-node.ts +106 -14
- package/src/project-bundle.ts +61 -18
- package/src/project-cli.ts +29 -17
- package/src/qa/atlas.ts +597 -0
- package/src/qa/index.ts +1 -0
- package/src/qa/material.ts +2 -0
- package/src/qa/skin-integrity.ts +291 -0
- package/src/render-service-host.ts +4 -6
- package/src/render.ts +133 -15
- package/src/skin-binding.ts +225 -0
- package/src/texture-atlas-contract.ts +558 -0
- package/src/texture-atlas-pixels.ts +456 -0
- package/src/texture-atlas.ts +185 -0
- package/src/texture-bake.ts +88 -10
- package/src/texture-resolver.ts +55 -0
- package/src/tools/materials.ts +15 -11
- package/src/tools/registry.ts +5 -15
- package/src/tools/review.ts +1 -1
- package/src/uv-data.ts +238 -0
- package/src/uv-shapes.ts +3 -0
- package/src/uv.ts +73 -128
- package/src/viewer/app.ts +37 -0
- package/src/viewer/live-panel.ts +57 -1
- package/src/viewer/material-preview-panel.ts +140 -0
- package/src/viewer/material-preview.ts +138 -0
- package/src/viewer/materials-panel.ts +34 -5
- package/src/viewer/navigation.ts +2 -0
- package/src/views/camera.ts +37 -40
- package/src/views/face-policy.ts +20 -0
- package/src/views/glb.ts +294 -17
- package/src/views/pose.ts +71 -13
- package/src/views/raster.ts +73 -46
- package/src/views/scene-copy.ts +86 -0
- package/src/views/scene-raster.ts +2 -1
- package/src/views/triangle-coverage.ts +78 -0
- package/src/workspace-node.ts +11 -3
- package/src/workspace.ts +2 -2
package/src/qa/material.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as THREE from 'three';
|
|
|
3
3
|
import type { KilnTextureMetadata } from '../textures';
|
|
4
4
|
import { conformancePromotionAuthorization, KILN_ENGINE_QA_OWNER, type QaRule } from './registry';
|
|
5
5
|
import type { QaContext, QaFinding } from './types';
|
|
6
|
+
import { inspectAtlasScene } from './atlas';
|
|
6
7
|
|
|
7
8
|
type StandardMaterial = THREE.MeshStandardMaterial & {
|
|
8
9
|
isMeshStandardMaterial?: boolean;
|
|
@@ -276,6 +277,7 @@ export function inspectPortableSceneMaterials(
|
|
|
276
277
|
}
|
|
277
278
|
}
|
|
278
279
|
});
|
|
280
|
+
findings.push(...inspectAtlasScene(scene).findings);
|
|
279
281
|
return findings;
|
|
280
282
|
}
|
|
281
283
|
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import type { QaFinding } from './types';
|
|
3
|
+
|
|
4
|
+
/** Source serialization invariants, independent of style, body plan or optional QA policy. */
|
|
5
|
+
export interface SkinIntegrityInspectionV1 {
|
|
6
|
+
schemaVersion: 1;
|
|
7
|
+
findings: QaFinding[];
|
|
8
|
+
meshesChecked: number;
|
|
9
|
+
verticesChecked: number;
|
|
10
|
+
truncated: boolean;
|
|
11
|
+
/** Current posed world-space bounds only; not a whole-animation envelope. */
|
|
12
|
+
bounds: { nodePath: string; min: [number, number, number]; max: [number, number, number] }[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const MAX_FINDINGS = 64;
|
|
16
|
+
const WEIGHT_TOLERANCE = 1e-5;
|
|
17
|
+
type Attribute = THREE.BufferAttribute | THREE.InterleavedBufferAttribute;
|
|
18
|
+
|
|
19
|
+
function validAttribute(a: Attribute | undefined, count: number, size: number): a is Attribute {
|
|
20
|
+
if (!a || a.itemSize !== size || a.count !== count || !Number.isSafeInteger(count) || count < 1)
|
|
21
|
+
return false;
|
|
22
|
+
if ('isInterleavedBufferAttribute' in a) {
|
|
23
|
+
return (
|
|
24
|
+
Number.isSafeInteger(a.data.stride) &&
|
|
25
|
+
a.data.stride >= size &&
|
|
26
|
+
Number.isSafeInteger(a.offset) &&
|
|
27
|
+
a.offset >= 0 &&
|
|
28
|
+
a.offset + size <= a.data.stride &&
|
|
29
|
+
(count - 1) * a.data.stride + a.offset + size <= a.array.length
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return count * size <= a.array.length;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function finiteAffine(matrix: THREE.Matrix4 | undefined): matrix is THREE.Matrix4 {
|
|
36
|
+
const e = matrix?.elements;
|
|
37
|
+
return (
|
|
38
|
+
!!e &&
|
|
39
|
+
e.length === 16 &&
|
|
40
|
+
[...e].every((v) => Number.isFinite(v) && Number.isFinite(Math.fround(v))) &&
|
|
41
|
+
// Inverting valid TRS matrices can introduce double-precision roundoff.
|
|
42
|
+
// Judge the affine row at the Float32 precision used by the exported data.
|
|
43
|
+
Math.fround(e[3]!) === 0 &&
|
|
44
|
+
Math.fround(e[7]!) === 0 &&
|
|
45
|
+
Math.fround(e[11]!) === 0 &&
|
|
46
|
+
Math.fround(e[15]!) === 1
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Read logical attributes and transforms without updating or repairing the caller's scene. */
|
|
51
|
+
export function inspectSkinIntegrity(root: THREE.Object3D): SkinIntegrityInspectionV1 {
|
|
52
|
+
const result: SkinIntegrityInspectionV1 = {
|
|
53
|
+
schemaVersion: 1,
|
|
54
|
+
findings: [],
|
|
55
|
+
meshesChecked: 0,
|
|
56
|
+
verticesChecked: 0,
|
|
57
|
+
truncated: false,
|
|
58
|
+
bounds: [],
|
|
59
|
+
};
|
|
60
|
+
const paths = new Map<THREE.Object3D, string>();
|
|
61
|
+
const pending: [THREE.Object3D, string, number][] = [[root, '', 0]];
|
|
62
|
+
while (pending.length) {
|
|
63
|
+
const [node, parent, sibling] = pending.pop()!;
|
|
64
|
+
if (paths.has(node)) continue;
|
|
65
|
+
const segment = `${(node.name || node.type).slice(0, 80)}[${sibling}]`;
|
|
66
|
+
const path = (parent ? `${parent}/${segment}` : segment).slice(-800);
|
|
67
|
+
paths.set(node, path);
|
|
68
|
+
for (let i = node.children.length - 1; i >= 0; i--) pending.push([node.children[i]!, path, i]);
|
|
69
|
+
}
|
|
70
|
+
const worlds = new Map<THREE.Object3D, THREE.Matrix4>();
|
|
71
|
+
function world(node: THREE.Object3D): THREE.Matrix4 | undefined {
|
|
72
|
+
const chain: THREE.Object3D[] = [],
|
|
73
|
+
seen = new Set<THREE.Object3D>();
|
|
74
|
+
let cursor: THREE.Object3D | null = node;
|
|
75
|
+
while (cursor && !worlds.has(cursor)) {
|
|
76
|
+
if (seen.has(cursor)) return undefined;
|
|
77
|
+
seen.add(cursor);
|
|
78
|
+
chain.push(cursor);
|
|
79
|
+
cursor = cursor.parent;
|
|
80
|
+
}
|
|
81
|
+
while (chain.length) {
|
|
82
|
+
const n = chain.pop()!;
|
|
83
|
+
const local = n.matrixAutoUpdate
|
|
84
|
+
? new THREE.Matrix4().compose(n.position, n.quaternion, n.scale)
|
|
85
|
+
: n.matrix.clone();
|
|
86
|
+
const parent = n.parent ? worlds.get(n.parent) : undefined;
|
|
87
|
+
const value =
|
|
88
|
+
n.matrixWorldAutoUpdate === false
|
|
89
|
+
? n.matrixWorld.clone()
|
|
90
|
+
: parent
|
|
91
|
+
? new THREE.Matrix4().multiplyMatrices(parent, local)
|
|
92
|
+
: local;
|
|
93
|
+
worlds.set(n, value);
|
|
94
|
+
}
|
|
95
|
+
return worlds.get(node);
|
|
96
|
+
}
|
|
97
|
+
for (const [node, nodePath] of paths) {
|
|
98
|
+
const mesh = node as THREE.SkinnedMesh;
|
|
99
|
+
if (!mesh.isSkinnedMesh) continue;
|
|
100
|
+
if (result.findings.length >= MAX_FINDINGS) {
|
|
101
|
+
result.truncated = true;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
result.meshesChecked++;
|
|
105
|
+
const start = result.findings.length;
|
|
106
|
+
const add = (
|
|
107
|
+
code: string,
|
|
108
|
+
message: string,
|
|
109
|
+
attribute?: string,
|
|
110
|
+
measurement?: QaFinding['measurement'],
|
|
111
|
+
) => {
|
|
112
|
+
if (result.findings.length >= MAX_FINDINGS) {
|
|
113
|
+
result.truncated = true;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
result.findings.push({
|
|
117
|
+
code,
|
|
118
|
+
disposition: 'block',
|
|
119
|
+
dimension: 'exportIntegrity',
|
|
120
|
+
profile: 'skin.source-integrity.v1',
|
|
121
|
+
message,
|
|
122
|
+
affected: { node: mesh.name.slice(0, 160), nodePath, ...(attribute ? { attribute } : {}) },
|
|
123
|
+
...(measurement ? { measurement } : {}),
|
|
124
|
+
repairText:
|
|
125
|
+
'Rebuild the named binding at its intended rest pose. Keep bone order, inverse bind matrices and four influence lanes aligned with every final vertex; unwrap or change topology before binding. Do not repair invalid indices by normalizing weights.',
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const skeleton = mesh.skeleton,
|
|
129
|
+
bones = skeleton?.bones;
|
|
130
|
+
if (!bones?.length || !Array.isArray(bones)) {
|
|
131
|
+
add('SKIN_SKELETON', 'Skinned mesh requires a nonempty skeleton in the exported hierarchy.');
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const unique = new Set<THREE.Bone>();
|
|
135
|
+
for (let joint = 0; joint < bones.length; joint++) {
|
|
136
|
+
const bone = bones[joint];
|
|
137
|
+
if (!bone?.isObject3D || !paths.has(bone) || unique.has(bone)) {
|
|
138
|
+
add(
|
|
139
|
+
'SKIN_JOINT_MEMBERSHIP',
|
|
140
|
+
`Joint ${joint} (${JSON.stringify(bone?.name?.slice(0, 160) ?? 'missing')}) must be a distinct node inside the exported hierarchy.`,
|
|
141
|
+
`joints[${joint}]`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (bone) unique.add(bone);
|
|
145
|
+
if (!finiteAffine(skeleton.boneInverses?.[joint])) {
|
|
146
|
+
add(
|
|
147
|
+
'SKIN_INVERSE_BIND',
|
|
148
|
+
`Joint ${joint} (${JSON.stringify(bone?.name?.slice(0, 160) ?? 'missing')}) requires a finite affine inverse bind matrix.`,
|
|
149
|
+
`boneInverses[${joint}]`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (result.truncated) break;
|
|
153
|
+
}
|
|
154
|
+
if (
|
|
155
|
+
!finiteAffine(mesh.bindMatrix) ||
|
|
156
|
+
!Number.isFinite(mesh.bindMatrix.determinant()) ||
|
|
157
|
+
mesh.bindMatrix.determinant() === 0 ||
|
|
158
|
+
![THREE.AttachedBindMode, THREE.DetachedBindMode].includes(mesh.bindMode)
|
|
159
|
+
) {
|
|
160
|
+
add(
|
|
161
|
+
'SKIN_BIND_MATRIX',
|
|
162
|
+
'Mesh requires a finite invertible affine bind matrix and an attached or detached bind mode.',
|
|
163
|
+
'bindMatrix',
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const position = mesh.geometry?.getAttribute('position'),
|
|
167
|
+
count = position?.count ?? 0;
|
|
168
|
+
const indices = mesh.geometry?.getAttribute('skinIndex'),
|
|
169
|
+
weights = mesh.geometry?.getAttribute('skinWeight');
|
|
170
|
+
if (
|
|
171
|
+
!validAttribute(position, count, 3) ||
|
|
172
|
+
!validAttribute(indices, count, 4) ||
|
|
173
|
+
indices.normalized ||
|
|
174
|
+
!validAttribute(weights, count, 4)
|
|
175
|
+
) {
|
|
176
|
+
add(
|
|
177
|
+
'SKIN_ATTRIBUTES',
|
|
178
|
+
'Skin requires xyz positions and four non-normalized joint indices and four weights per final vertex, backed by complete buffers.',
|
|
179
|
+
'skinIndex/skinWeight',
|
|
180
|
+
);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const invalid = new Map<
|
|
184
|
+
string,
|
|
185
|
+
{ count: number; first: number; last: number; attribute: string }
|
|
186
|
+
>();
|
|
187
|
+
const bad = (code: string, vertex: number, attribute: string) => {
|
|
188
|
+
const row = invalid.get(code);
|
|
189
|
+
if (row) {
|
|
190
|
+
if (row.last !== vertex) row.count++;
|
|
191
|
+
row.last = vertex;
|
|
192
|
+
} else invalid.set(code, { count: 1, first: vertex, last: vertex, attribute });
|
|
193
|
+
};
|
|
194
|
+
for (let vertex = 0; vertex < count; vertex++) {
|
|
195
|
+
let sum = 0;
|
|
196
|
+
for (let lane = 0; lane < 4; lane++) {
|
|
197
|
+
const index = indices.getComponent(vertex, lane),
|
|
198
|
+
weight = weights.getComponent(vertex, lane);
|
|
199
|
+
if (!Number.isInteger(index) || index < 0 || index >= bones.length)
|
|
200
|
+
bad('SKIN_JOINT_INDEX', vertex, 'skinIndex');
|
|
201
|
+
if (!Number.isFinite(weight) || weight < 0 || weight > 1)
|
|
202
|
+
bad('SKIN_WEIGHT_VALUE', vertex, 'skinWeight');
|
|
203
|
+
sum += weight;
|
|
204
|
+
}
|
|
205
|
+
if (!Number.isFinite(sum) || Math.abs(sum - 1) > WEIGHT_TOLERANCE)
|
|
206
|
+
bad('SKIN_WEIGHT_SUM', vertex, 'skinWeight');
|
|
207
|
+
}
|
|
208
|
+
result.verticesChecked += count;
|
|
209
|
+
for (const [code, row] of invalid) {
|
|
210
|
+
const why =
|
|
211
|
+
code === 'SKIN_JOINT_INDEX'
|
|
212
|
+
? 'indices must address the skeleton, including zero-weight lanes'
|
|
213
|
+
: code === 'SKIN_WEIGHT_SUM'
|
|
214
|
+
? `weights must sum to one within ${WEIGHT_TOLERANCE}`
|
|
215
|
+
: 'weights must be finite values in [0,1]';
|
|
216
|
+
add(
|
|
217
|
+
code,
|
|
218
|
+
`Invalid ${row.attribute} at vertex range ${row.first}..${row.last} (${row.count} affected vertices): ${why}.`,
|
|
219
|
+
row.attribute,
|
|
220
|
+
{
|
|
221
|
+
name: 'invalidSkinVertices',
|
|
222
|
+
actual: row.count,
|
|
223
|
+
expected: 0,
|
|
224
|
+
breakdown: { firstVertex: row.first, lastVertex: row.last },
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (result.findings.length !== start) continue;
|
|
229
|
+
const meshWorld = world(mesh);
|
|
230
|
+
if (!finiteAffine(meshWorld)) {
|
|
231
|
+
add('SKIN_POSE_TRANSFORM', 'Current mesh world transform must be finite and affine.');
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const inverse =
|
|
235
|
+
mesh.bindMode === THREE.AttachedBindMode
|
|
236
|
+
? meshWorld.clone().invert()
|
|
237
|
+
: mesh.bindMatrix.clone().invert();
|
|
238
|
+
const matrices: THREE.Matrix4[] = [];
|
|
239
|
+
for (let joint = 0; joint < bones.length; joint++) {
|
|
240
|
+
const boneWorld = world(bones[joint]!);
|
|
241
|
+
if (!finiteAffine(boneWorld)) {
|
|
242
|
+
add(
|
|
243
|
+
'SKIN_POSE_TRANSFORM',
|
|
244
|
+
`Current transform for joint ${joint} must be finite and affine.`,
|
|
245
|
+
`joints[${joint}]`,
|
|
246
|
+
);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
matrices.push(
|
|
250
|
+
new THREE.Matrix4()
|
|
251
|
+
.multiplyMatrices(boneWorld, skeleton.boneInverses[joint]!)
|
|
252
|
+
.multiply(mesh.bindMatrix),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (result.findings.length !== start) continue;
|
|
256
|
+
const base = new THREE.Vector3(),
|
|
257
|
+
posed = new THREE.Vector3(),
|
|
258
|
+
temp = new THREE.Vector3(),
|
|
259
|
+
box = new THREE.Box3();
|
|
260
|
+
for (let vertex = 0; vertex < count; vertex++) {
|
|
261
|
+
// Mesh's implementation evaluates morphs but does not invoke the unsafe skin path.
|
|
262
|
+
THREE.Mesh.prototype.getVertexPosition.call(mesh, vertex, base);
|
|
263
|
+
posed.set(0, 0, 0);
|
|
264
|
+
for (let lane = 0; lane < 4; lane++) {
|
|
265
|
+
const weight = weights.getComponent(vertex, lane);
|
|
266
|
+
if (weight !== 0)
|
|
267
|
+
posed.addScaledVector(
|
|
268
|
+
temp.copy(base).applyMatrix4(matrices[indices.getComponent(vertex, lane)]!),
|
|
269
|
+
weight,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
posed.applyMatrix4(inverse).applyMatrix4(meshWorld);
|
|
273
|
+
if (
|
|
274
|
+
!Number.isFinite(Math.fround(posed.x)) ||
|
|
275
|
+
!Number.isFinite(Math.fround(posed.y)) ||
|
|
276
|
+
!Number.isFinite(Math.fround(posed.z))
|
|
277
|
+
) {
|
|
278
|
+
add(
|
|
279
|
+
'SKIN_POSED_VERTEX',
|
|
280
|
+
`Current deformed vertex ${vertex} is not finite and Float32-representable. Check positions, morphs and weighted joint transforms.`,
|
|
281
|
+
`position[${vertex}]`,
|
|
282
|
+
);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
box.expandByPoint(posed);
|
|
286
|
+
}
|
|
287
|
+
if (result.findings.length === start)
|
|
288
|
+
result.bounds.push({ nodePath, min: box.min.toArray(), max: box.max.toArray() });
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
@@ -248,7 +248,6 @@ export async function startLocalRenderService(
|
|
|
248
248
|
stderr = `${stderr}${chunk.toString()}`.slice(-2_000);
|
|
249
249
|
});
|
|
250
250
|
const deadline = Date.now() + budget;
|
|
251
|
-
let exitedAt: number | undefined;
|
|
252
251
|
try {
|
|
253
252
|
while (Date.now() < deadline) {
|
|
254
253
|
const fresh = await inspectLocalRenderService(url, dir, 1_000);
|
|
@@ -258,11 +257,10 @@ export async function startLocalRenderService(
|
|
|
258
257
|
const exited = windowsLaunch
|
|
259
258
|
? windowsLaunch.hasExited()
|
|
260
259
|
: running.exitCode !== null || running.signalCode !== null;
|
|
261
|
-
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
if (spawnError || (exitedAt !== undefined && Date.now() - exitedAt >= 1500))
|
|
260
|
+
// A competing child can win the socket while this one exits after losing
|
|
261
|
+
// its bind. An unknown listener gets the same startup deadline as our child;
|
|
262
|
+
// only verified health permits joining. An exited child with no listener fails.
|
|
263
|
+
if (spawnError || (exited && fresh.kind === 'absent'))
|
|
266
264
|
throw new Error(
|
|
267
265
|
`render service exited during startup${spawnError ? `: ${spawnError.message}` : ''}${stderr.trim() ? `: ${stderr.trim()}` : windowsLaunch ? '; launch the renderer manually to inspect native-driver errors' : ''}`,
|
|
268
266
|
);
|