@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
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { restoreGltfTextureBinding } from "./gltf-texture-binding.js";
|
|
3
|
+
import { canonicalAtlasMetadataJsonV1, canonicalizeTextureAtlasSpecV1, validateTextureAtlasManifestV1, } from "./texture-atlas-contract.js";
|
|
4
|
+
export const ATLAS_PROVENANCE_VERSION = 'kiln.atlas-provenance.v1';
|
|
5
|
+
const TABLE_ENVELOPE_BYTES = new TextEncoder().encode(JSON.stringify({ version: ATLAS_PROVENANCE_VERSION, atlases: [] })).length;
|
|
6
|
+
export const ATLAS_PROVENANCE_LIMITS = Object.freeze({
|
|
7
|
+
atlases: 128,
|
|
8
|
+
bytes: 4_194_304,
|
|
9
|
+
bindings: 1024,
|
|
10
|
+
entryBytes: 262_144,
|
|
11
|
+
encodedBytes: 16_777_216,
|
|
12
|
+
aggregateEncodedBytes: 67_108_864,
|
|
13
|
+
bakeDecodedBytes: 67_108_864,
|
|
14
|
+
});
|
|
15
|
+
function fail(message) {
|
|
16
|
+
throw new TypeError(`atlas provenance: ${message}`);
|
|
17
|
+
}
|
|
18
|
+
function field(input, key) {
|
|
19
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
20
|
+
fail('plain metadata object required.');
|
|
21
|
+
const proto = Object.getPrototypeOf(input);
|
|
22
|
+
if (proto !== null && proto !== Object.prototype)
|
|
23
|
+
fail('plain metadata prototype required.');
|
|
24
|
+
const d = Object.getOwnPropertyDescriptor(input, key);
|
|
25
|
+
if (!d)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (!d.enumerable || !('value' in d))
|
|
28
|
+
fail('metadata accessors/hidden fields are unsupported.');
|
|
29
|
+
return d.value;
|
|
30
|
+
}
|
|
31
|
+
function closed(input, keys) {
|
|
32
|
+
for (const key of Reflect.ownKeys(input))
|
|
33
|
+
if (typeof key !== 'string' || !keys.includes(key))
|
|
34
|
+
fail('unsupported metadata field.');
|
|
35
|
+
for (const key of keys)
|
|
36
|
+
field(input, key);
|
|
37
|
+
return input;
|
|
38
|
+
}
|
|
39
|
+
function normalize(input) {
|
|
40
|
+
canonicalAtlasMetadataJsonV1(input);
|
|
41
|
+
const r = closed(input, ['spec', 'manifest']);
|
|
42
|
+
const spec = canonicalizeTextureAtlasSpecV1(r['spec']);
|
|
43
|
+
return { spec, manifest: validateTextureAtlasManifestV1(r['manifest'], spec) };
|
|
44
|
+
}
|
|
45
|
+
function textureCandidates(texture) {
|
|
46
|
+
const direct = field(texture.userData, 'kilnAtlas'), resource = field(texture.userData, 'kilnMaterialResource');
|
|
47
|
+
const map = resource === undefined ? undefined : field(resource, 'map'), recipe = map === undefined ? undefined : field(map, 'recipe');
|
|
48
|
+
const candidates = [];
|
|
49
|
+
if (direct !== undefined)
|
|
50
|
+
candidates.push(direct);
|
|
51
|
+
if (recipe !== undefined && field(recipe, 'kind') === 'atlas')
|
|
52
|
+
candidates.push({ spec: field(recipe, 'spec'), manifest: field(recipe, 'manifest') });
|
|
53
|
+
return candidates;
|
|
54
|
+
}
|
|
55
|
+
function normalizeCandidates(candidates) {
|
|
56
|
+
let result;
|
|
57
|
+
for (const candidate of candidates) {
|
|
58
|
+
const p = normalize(candidate);
|
|
59
|
+
if (result && canonicalAtlasMetadataJsonV1(result) !== canonicalAtlasMetadataJsonV1(p))
|
|
60
|
+
fail('direct and saved atlas identities disagree.');
|
|
61
|
+
result = p;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
/** Reads only engine metadata data descriptors; author metadata and encoded arrays are not serialized. */
|
|
66
|
+
export function readAtlasTextureProvenance(texture) {
|
|
67
|
+
return normalizeCandidates(textureCandidates(texture));
|
|
68
|
+
}
|
|
69
|
+
function admitTable(input) {
|
|
70
|
+
const r = closed(input, ['version', 'atlases']);
|
|
71
|
+
if (r['version'] !== ATLAS_PROVENANCE_VERSION)
|
|
72
|
+
fail('unsupported version.');
|
|
73
|
+
const entries = r['atlases'];
|
|
74
|
+
if (!Array.isArray(entries) || Object.getPrototypeOf(entries) !== Array.prototype)
|
|
75
|
+
fail('dense atlas array required.');
|
|
76
|
+
const n = Object.getOwnPropertyDescriptor(entries, 'length').value;
|
|
77
|
+
if (n > ATLAS_PROVENANCE_LIMITS.atlases || Reflect.ownKeys(entries).length !== n + 1)
|
|
78
|
+
fail('atlas count/density budget exceeded.');
|
|
79
|
+
let bytes = TABLE_ENVELOPE_BYTES;
|
|
80
|
+
const admitted = [];
|
|
81
|
+
for (let i = 0; i < n; i++) {
|
|
82
|
+
const d = Object.getOwnPropertyDescriptor(entries, String(i));
|
|
83
|
+
if (!d?.enumerable || !('value' in d))
|
|
84
|
+
fail('atlas array accessors are unsupported.');
|
|
85
|
+
const json = canonicalAtlasMetadataJsonV1(d.value);
|
|
86
|
+
bytes += new TextEncoder().encode(json).length + (i > 0 ? 1 : 0);
|
|
87
|
+
if (bytes > ATLAS_PROVENANCE_LIMITS.bytes)
|
|
88
|
+
fail('aggregate metadata byte budget exceeded.');
|
|
89
|
+
admitted.push(closed(d.value, ['id', 'spec', 'manifest']));
|
|
90
|
+
}
|
|
91
|
+
return admitted;
|
|
92
|
+
}
|
|
93
|
+
export function validateAtlasProvenanceTable(input) {
|
|
94
|
+
const admitted = admitTable(input), ids = new Set(), atlases = [];
|
|
95
|
+
for (const e of admitted) {
|
|
96
|
+
const p = normalize({ spec: e['spec'], manifest: e['manifest'] });
|
|
97
|
+
if (e['id'] !== p.manifest.manifestHash || ids.has(e['id']))
|
|
98
|
+
fail('duplicate or mismatched atlas identity.');
|
|
99
|
+
ids.add(e['id']);
|
|
100
|
+
atlases.push({ id: e['id'], ...p });
|
|
101
|
+
}
|
|
102
|
+
return { version: ATLAS_PROVENANCE_VERSION, atlases };
|
|
103
|
+
}
|
|
104
|
+
function lookup(table, id) {
|
|
105
|
+
const found = table.atlases.find((a) => a.id === id);
|
|
106
|
+
if (!found)
|
|
107
|
+
fail('unknown atlas reference.');
|
|
108
|
+
return found;
|
|
109
|
+
}
|
|
110
|
+
function derivedBinding(p, regionName, origin, mode) {
|
|
111
|
+
if (!['top-left', 'bottom-left'].includes(origin) ||
|
|
112
|
+
!['content-boundary', 'texel-center'].includes(mode))
|
|
113
|
+
fail('explicit binding origin/mode required.');
|
|
114
|
+
const r = p.manifest.regions.find((r) => r.name === regionName);
|
|
115
|
+
if (!r)
|
|
116
|
+
fail('unknown named region.');
|
|
117
|
+
const { x, y, width: w, height: h } = r.content, center = mode === 'texel-center', bottom = origin === 'bottom-left';
|
|
118
|
+
return {
|
|
119
|
+
manifestHash: p.manifest.manifestHash,
|
|
120
|
+
specHash: p.manifest.specHash,
|
|
121
|
+
layoutHash: p.manifest.layoutHash,
|
|
122
|
+
region: r.name,
|
|
123
|
+
content: structuredClone(r.content),
|
|
124
|
+
allocation: structuredClone(r.allocation),
|
|
125
|
+
inputOrigin: origin,
|
|
126
|
+
mode,
|
|
127
|
+
scale: [
|
|
128
|
+
(w - (center ? 1 : 0)) / p.manifest.width,
|
|
129
|
+
((bottom ? -1 : 1) * (h - (center ? 1 : 0))) / p.manifest.height,
|
|
130
|
+
],
|
|
131
|
+
offset: [
|
|
132
|
+
(x + (center ? 0.5 : 0)) / p.manifest.width,
|
|
133
|
+
(y + (bottom ? h - (center ? 0.5 : 0) : center ? 0.5 : 0)) / p.manifest.height,
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function expandAtlasBinding(input, table) {
|
|
138
|
+
canonicalAtlasMetadataJsonV1(input);
|
|
139
|
+
const r = closed(input, ['version', 'atlasId', 'region', 'inputOrigin', 'mode']);
|
|
140
|
+
if (r['version'] !== ATLAS_PROVENANCE_VERSION)
|
|
141
|
+
fail('unsupported binding version.');
|
|
142
|
+
return derivedBinding(lookup(table, r['atlasId']), r['region'], r['inputOrigin'], r['mode']);
|
|
143
|
+
}
|
|
144
|
+
/** A geometry-wide stamp can be associated only with the exact primary atlas supplied here. */
|
|
145
|
+
export function readAtlasGeometryBinding(geometry, provenance) {
|
|
146
|
+
const stamp = field(geometry.userData, 'kilnAtlasBinding');
|
|
147
|
+
if (stamp === undefined)
|
|
148
|
+
return undefined;
|
|
149
|
+
canonicalAtlasMetadataJsonV1(stamp);
|
|
150
|
+
const r = stamp, derived = derivedBinding(provenance, r['region'], r['inputOrigin'], r['mode']);
|
|
151
|
+
if (canonicalAtlasMetadataJsonV1(stamp) !== canonicalAtlasMetadataJsonV1(derived))
|
|
152
|
+
fail('named binding identity or remap formula mismatch.');
|
|
153
|
+
return {
|
|
154
|
+
version: ATLAS_PROVENANCE_VERSION,
|
|
155
|
+
atlasId: provenance.manifest.manifestHash,
|
|
156
|
+
region: r['region'],
|
|
157
|
+
inputOrigin: r['inputOrigin'],
|
|
158
|
+
mode: r['mode'],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export function collectAtlasProvenance(root) {
|
|
162
|
+
const textures = new Map(), geometries = new Map(), candidates = new Map(), sizes = new Map(), pendingGeometry = new Map();
|
|
163
|
+
let total = TABLE_ENVELOPE_BYTES, pixelBytes = 0, bindings = 0;
|
|
164
|
+
// Entire metadata graph admission comes before canonicalizers clone manifests or exporters clone scene state.
|
|
165
|
+
root.traverse((node) => {
|
|
166
|
+
const mesh = node;
|
|
167
|
+
if (!mesh.isMesh && !node.isSprite)
|
|
168
|
+
return;
|
|
169
|
+
const meshIds = new Set(), mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
170
|
+
for (const mat of mats)
|
|
171
|
+
for (const slot of [
|
|
172
|
+
'map',
|
|
173
|
+
'normalMap',
|
|
174
|
+
'roughnessMap',
|
|
175
|
+
'metalnessMap',
|
|
176
|
+
'emissiveMap',
|
|
177
|
+
'aoMap',
|
|
178
|
+
]) {
|
|
179
|
+
const texture = mat[slot];
|
|
180
|
+
if (!(texture instanceof THREE.Texture))
|
|
181
|
+
continue;
|
|
182
|
+
let ref = textures.get(texture);
|
|
183
|
+
if (!ref) {
|
|
184
|
+
const raw = textureCandidates(texture);
|
|
185
|
+
if (!raw.length)
|
|
186
|
+
continue;
|
|
187
|
+
for (const candidate of raw) {
|
|
188
|
+
canonicalAtlasMetadataJsonV1(candidate);
|
|
189
|
+
const r = closed(candidate, ['spec', 'manifest']), id = field(r['manifest'], 'manifestHash');
|
|
190
|
+
if (typeof id !== 'string' || !/^sha256:[0-9a-f]{64}$/.test(id))
|
|
191
|
+
fail('invalid atlas identity.');
|
|
192
|
+
const bytes = new TextEncoder().encode(canonicalAtlasMetadataJsonV1({ ...r, id })).length;
|
|
193
|
+
const prior = sizes.get(id) ?? 0;
|
|
194
|
+
total += Math.max(0, bytes - prior) + (prior === 0 && sizes.size > 0 ? 1 : 0);
|
|
195
|
+
sizes.set(id, Math.max(prior, bytes));
|
|
196
|
+
if (total > ATLAS_PROVENANCE_LIMITS.bytes ||
|
|
197
|
+
sizes.size > ATLAS_PROVENANCE_LIMITS.atlases)
|
|
198
|
+
fail('aggregate metadata budget exceeded.');
|
|
199
|
+
ref ??= { version: ATLAS_PROVENANCE_VERSION, id };
|
|
200
|
+
}
|
|
201
|
+
const spec = field(raw[0], 'spec'), width = field(spec, 'width'), height = field(spec, 'height');
|
|
202
|
+
if (!Number.isSafeInteger(width) ||
|
|
203
|
+
!Number.isSafeInteger(height) ||
|
|
204
|
+
width < 1 ||
|
|
205
|
+
height < 1)
|
|
206
|
+
fail('invalid atlas image dimensions.');
|
|
207
|
+
pixelBytes += width * height * 4;
|
|
208
|
+
if (pixelBytes > ATLAS_PROVENANCE_LIMITS.bakeDecodedBytes)
|
|
209
|
+
fail('aggregate atlas bake pixel budget exceeded.');
|
|
210
|
+
candidates.set(texture, raw);
|
|
211
|
+
textures.set(texture, ref);
|
|
212
|
+
}
|
|
213
|
+
if (++bindings > ATLAS_PROVENANCE_LIMITS.bindings)
|
|
214
|
+
fail('texture binding budget exceeded.');
|
|
215
|
+
meshIds.add(ref.id);
|
|
216
|
+
}
|
|
217
|
+
if (!mesh.isMesh)
|
|
218
|
+
return;
|
|
219
|
+
const stamp = field(mesh.geometry.userData, 'kilnAtlasBinding');
|
|
220
|
+
if (stamp === undefined)
|
|
221
|
+
return;
|
|
222
|
+
if (++bindings > ATLAS_PROVENANCE_LIMITS.bindings)
|
|
223
|
+
fail('primitive binding budget exceeded.');
|
|
224
|
+
canonicalAtlasMetadataJsonV1(stamp);
|
|
225
|
+
const id = field(stamp, 'manifestHash');
|
|
226
|
+
if (typeof id !== 'string' || !meshIds.has(id))
|
|
227
|
+
fail('named binding has no matching primary atlas texture on this mesh.');
|
|
228
|
+
pendingGeometry.set(mesh.geometry, id);
|
|
229
|
+
});
|
|
230
|
+
const identities = new Map();
|
|
231
|
+
for (const [texture, raw] of candidates) {
|
|
232
|
+
const p = normalizeCandidates(raw), id = p.manifest.manifestHash, prior = identities.get(id);
|
|
233
|
+
if (prior && canonicalAtlasMetadataJsonV1(prior) !== canonicalAtlasMetadataJsonV1(p))
|
|
234
|
+
fail('conflicting atlas identity.');
|
|
235
|
+
identities.set(id, p);
|
|
236
|
+
textures.set(texture, { version: ATLAS_PROVENANCE_VERSION, id });
|
|
237
|
+
}
|
|
238
|
+
for (const [geometry, id] of pendingGeometry)
|
|
239
|
+
geometries.set(geometry, readAtlasGeometryBinding(geometry, identities.get(id)));
|
|
240
|
+
const table = identities.size
|
|
241
|
+
? {
|
|
242
|
+
version: ATLAS_PROVENANCE_VERSION,
|
|
243
|
+
atlases: [...identities].map(([id, p]) => ({ id, ...p })),
|
|
244
|
+
}
|
|
245
|
+
: undefined;
|
|
246
|
+
return { table, texture: (t) => textures.get(t), binding: (g) => geometries.get(g) };
|
|
247
|
+
}
|
|
248
|
+
function rawDocumentTable(doc) {
|
|
249
|
+
const scene = doc.getRoot().getDefaultScene() ?? doc.getRoot().listScenes()[0];
|
|
250
|
+
return scene ? field(scene.getExtras(), 'kilnAtlasProvenance') : undefined;
|
|
251
|
+
}
|
|
252
|
+
export function atlasDocumentTable(doc) {
|
|
253
|
+
const raw = rawDocumentTable(doc);
|
|
254
|
+
return raw === undefined ? undefined : validateAtlasProvenanceTable(raw);
|
|
255
|
+
}
|
|
256
|
+
export function atlasMaterialUses(doc) {
|
|
257
|
+
const uses = [];
|
|
258
|
+
for (const mat of doc.getRoot().listMaterials())
|
|
259
|
+
for (const [texture, info] of [
|
|
260
|
+
[mat.getBaseColorTexture(), mat.getBaseColorTextureInfo()],
|
|
261
|
+
[mat.getNormalTexture(), mat.getNormalTextureInfo()],
|
|
262
|
+
[mat.getMetallicRoughnessTexture(), mat.getMetallicRoughnessTextureInfo()],
|
|
263
|
+
[mat.getEmissiveTexture(), mat.getEmissiveTextureInfo()],
|
|
264
|
+
[mat.getOcclusionTexture(), mat.getOcclusionTextureInfo()],
|
|
265
|
+
])
|
|
266
|
+
if (texture && info && field(info.getExtras(), 'kilnAtlasReference') !== undefined) {
|
|
267
|
+
if (uses.length >= ATLAS_PROVENANCE_LIMITS.bindings)
|
|
268
|
+
fail('document binding budget exceeded.');
|
|
269
|
+
uses.push({ texture, info });
|
|
270
|
+
}
|
|
271
|
+
return uses;
|
|
272
|
+
}
|
|
273
|
+
async function imageHash(texture) {
|
|
274
|
+
const bytes = texture.getImage();
|
|
275
|
+
if (!bytes || bytes.byteLength > ATLAS_PROVENANCE_LIMITS.encodedBytes)
|
|
276
|
+
fail('encoded image missing or over budget.');
|
|
277
|
+
const result = await crypto.subtle.digest('SHA-256', bytes);
|
|
278
|
+
return `sha256:${[...new Uint8Array(result)].map((v) => v.toString(16).padStart(2, '0')).join('')}`;
|
|
279
|
+
}
|
|
280
|
+
function sampler(info) {
|
|
281
|
+
const value = {
|
|
282
|
+
minFilter: info.getMinFilter(),
|
|
283
|
+
magFilter: info.getMagFilter(),
|
|
284
|
+
wrapS: info.getWrapS(),
|
|
285
|
+
wrapT: info.getWrapT(),
|
|
286
|
+
};
|
|
287
|
+
if (![null, 9728, 9729, 9984, 9985, 9986, 9987].includes(value.minFilter) ||
|
|
288
|
+
![null, 9728, 9729].includes(value.magFilter) ||
|
|
289
|
+
![33071, 33648, 10497].includes(value.wrapS) ||
|
|
290
|
+
![33071, 33648, 10497].includes(value.wrapT))
|
|
291
|
+
fail('unsupported effective sampler.');
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
function reference(input, table) {
|
|
295
|
+
canonicalAtlasMetadataJsonV1(input);
|
|
296
|
+
const r = closed(input, ['version', 'id', 'effectiveSampler', 'finalMime', 'finalImageSha256']);
|
|
297
|
+
if (r['version'] !== ATLAS_PROVENANCE_VERSION)
|
|
298
|
+
fail('unsupported texture reference version.');
|
|
299
|
+
lookup(table, r['id']);
|
|
300
|
+
return r;
|
|
301
|
+
}
|
|
302
|
+
function admitEncodedImages(uses) {
|
|
303
|
+
const seen = new Set();
|
|
304
|
+
let total = 0;
|
|
305
|
+
for (const { texture } of uses) {
|
|
306
|
+
if (seen.has(texture))
|
|
307
|
+
continue;
|
|
308
|
+
seen.add(texture);
|
|
309
|
+
const bytes = texture.getImage();
|
|
310
|
+
if (!bytes || bytes.byteLength > ATLAS_PROVENANCE_LIMITS.encodedBytes)
|
|
311
|
+
fail('encoded image missing or over budget.');
|
|
312
|
+
total += bytes.byteLength;
|
|
313
|
+
if (total > ATLAS_PROVENANCE_LIMITS.aggregateEncodedBytes)
|
|
314
|
+
fail('aggregate encoded image hash budget exceeded.');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/** Export/optimization only: bind source identity to the final encoded bytes and actual per-use sampler. */
|
|
318
|
+
export async function refreshAtlasDocumentProvenance(doc) {
|
|
319
|
+
const table = atlasDocumentTable(doc), uses = atlasMaterialUses(doc);
|
|
320
|
+
if (!uses.length)
|
|
321
|
+
return;
|
|
322
|
+
if (!table)
|
|
323
|
+
fail('atlas table missing.');
|
|
324
|
+
admitEncodedImages(uses);
|
|
325
|
+
const hashes = new Map();
|
|
326
|
+
for (const { texture, info } of uses) {
|
|
327
|
+
const r = reference(field(info.getExtras(), 'kilnAtlasReference'), table);
|
|
328
|
+
let hash = hashes.get(texture);
|
|
329
|
+
if (!hash) {
|
|
330
|
+
hash = await imageHash(texture);
|
|
331
|
+
hashes.set(texture, hash);
|
|
332
|
+
}
|
|
333
|
+
info.setExtras({
|
|
334
|
+
...info.getExtras(),
|
|
335
|
+
kilnAtlasReference: {
|
|
336
|
+
version: ATLAS_PROVENANCE_VERSION,
|
|
337
|
+
id: r.id,
|
|
338
|
+
effectiveSampler: sampler(info),
|
|
339
|
+
finalMime: texture.getMimeType(),
|
|
340
|
+
finalImageSha256: hash,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Import is validation, never a repair or identity refresh. */
|
|
346
|
+
export async function validateAtlasDocumentProvenance(doc) {
|
|
347
|
+
const table = atlasDocumentTable(doc), uses = atlasMaterialUses(doc);
|
|
348
|
+
if (uses.length && !table)
|
|
349
|
+
fail('atlas table missing.');
|
|
350
|
+
if (!table) {
|
|
351
|
+
for (const mesh of doc.getRoot().listMeshes())
|
|
352
|
+
for (const primitive of mesh.listPrimitives())
|
|
353
|
+
if (field(primitive.getExtras(), 'kilnAtlasBinding') !== undefined)
|
|
354
|
+
fail('atlas table missing.');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
admitEncodedImages(uses);
|
|
358
|
+
const hashes = new Map();
|
|
359
|
+
for (const { texture, info } of uses) {
|
|
360
|
+
const r = reference(field(info.getExtras(), 'kilnAtlasReference'), table);
|
|
361
|
+
let hash = hashes.get(texture);
|
|
362
|
+
if (!hash) {
|
|
363
|
+
hash = await imageHash(texture);
|
|
364
|
+
hashes.set(texture, hash);
|
|
365
|
+
}
|
|
366
|
+
if (r.finalImageSha256 !== hash ||
|
|
367
|
+
r.finalMime !== texture.getMimeType() ||
|
|
368
|
+
canonicalAtlasMetadataJsonV1(r.effectiveSampler) !==
|
|
369
|
+
canonicalAtlasMetadataJsonV1(sampler(info)))
|
|
370
|
+
fail('final image or effective sampler identity mismatch.');
|
|
371
|
+
}
|
|
372
|
+
let bindings = 0;
|
|
373
|
+
for (const mesh of doc.getRoot().listMeshes())
|
|
374
|
+
for (const prim of mesh.listPrimitives()) {
|
|
375
|
+
const binding = field(prim.getExtras(), 'kilnAtlasBinding');
|
|
376
|
+
if (binding !== undefined) {
|
|
377
|
+
if (++bindings > ATLAS_PROVENANCE_LIMITS.bindings)
|
|
378
|
+
fail('primitive budget exceeded.');
|
|
379
|
+
expandAtlasBinding(binding, table);
|
|
380
|
+
const material = prim.getMaterial(), id = field(binding, 'atlasId');
|
|
381
|
+
const infos = material
|
|
382
|
+
? [
|
|
383
|
+
material.getBaseColorTextureInfo(),
|
|
384
|
+
material.getNormalTextureInfo(),
|
|
385
|
+
material.getMetallicRoughnessTextureInfo(),
|
|
386
|
+
material.getEmissiveTextureInfo(),
|
|
387
|
+
material.getOcclusionTextureInfo(),
|
|
388
|
+
]
|
|
389
|
+
: [];
|
|
390
|
+
if (!infos.some((info) => info &&
|
|
391
|
+
field(info.getExtras(), 'kilnAtlasReference') !== undefined &&
|
|
392
|
+
field(field(info.getExtras(), 'kilnAtlasReference'), 'id') === id))
|
|
393
|
+
fail('named binding has no matching material atlas reference.');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const validatedDeliveries = new WeakMap();
|
|
398
|
+
/** Host-only owned identity established by the validated GLB loader; no serialized skip flag. */
|
|
399
|
+
export function readValidatedAtlasDelivery(texture) {
|
|
400
|
+
const ref = validatedDeliveries.get(texture);
|
|
401
|
+
return ref ? structuredClone(ref) : undefined;
|
|
402
|
+
}
|
|
403
|
+
export function readAtlasEncodedTexture(texture) {
|
|
404
|
+
const encoded = field(texture.userData, 'encoded');
|
|
405
|
+
if (encoded === undefined)
|
|
406
|
+
return undefined;
|
|
407
|
+
const r = closed(encoded, ['mime', 'bytes']);
|
|
408
|
+
if (r['mime'] !== 'image/png' ||
|
|
409
|
+
!(r['bytes'] instanceof Uint8Array) ||
|
|
410
|
+
r['bytes'].byteLength > ATLAS_PROVENANCE_LIMITS.encodedBytes)
|
|
411
|
+
fail('bounded PNG encoding required.');
|
|
412
|
+
return { mime: 'image/png', bytes: r['bytes'] };
|
|
413
|
+
}
|
|
414
|
+
/** Called only after validateAtlasDocumentProvenance; each sampler use gets owned Three state. */
|
|
415
|
+
export function restoreAtlasTextureProvenance(texture, info, table) {
|
|
416
|
+
const r = reference(field(info.getExtras(), 'kilnAtlasReference'), table), p = lookup(table, r.id);
|
|
417
|
+
texture.userData.kilnAtlas = structuredClone({ spec: p.spec, manifest: p.manifest });
|
|
418
|
+
validatedDeliveries.set(texture, Object.freeze(structuredClone(r)));
|
|
419
|
+
restoreGltfTextureBinding(texture, info);
|
|
420
|
+
}
|
|
421
|
+
/** Compose before mergeDocuments copies image/geometry buffers or deletes the source scene. */
|
|
422
|
+
export async function mergeAtlasDocumentProvenance(target, source) {
|
|
423
|
+
const sourceRaw = rawDocumentTable(source), targetRaw = rawDocumentTable(target);
|
|
424
|
+
const union = new Map();
|
|
425
|
+
for (const raw of [targetRaw, sourceRaw])
|
|
426
|
+
if (raw !== undefined)
|
|
427
|
+
for (const entry of admitTable(raw)) {
|
|
428
|
+
const prior = union.get(entry['id']);
|
|
429
|
+
if (prior && canonicalAtlasMetadataJsonV1(prior) !== canonicalAtlasMetadataJsonV1(entry))
|
|
430
|
+
fail('conflicting atlas identity during composition.');
|
|
431
|
+
union.set(entry['id'], entry);
|
|
432
|
+
}
|
|
433
|
+
// The combined graph is admitted before any recipe/manifest cloning or source graph merge.
|
|
434
|
+
const combined = union.size
|
|
435
|
+
? { version: ATLAS_PROVENANCE_VERSION, atlases: [...union.values()] }
|
|
436
|
+
: undefined;
|
|
437
|
+
if (combined)
|
|
438
|
+
admitTable(combined);
|
|
439
|
+
admitEncodedImages([...atlasMaterialUses(target), ...atlasMaterialUses(source)]);
|
|
440
|
+
await validateAtlasDocumentProvenance(source);
|
|
441
|
+
await validateAtlasDocumentProvenance(target);
|
|
442
|
+
if (combined) {
|
|
443
|
+
const table = validateAtlasProvenanceTable(combined), scene = target.getRoot().getDefaultScene() ?? target.getRoot().listScenes()[0];
|
|
444
|
+
if (!scene)
|
|
445
|
+
fail('composition target scene missing.');
|
|
446
|
+
scene.setExtras({ ...scene.getExtras(), kilnAtlasProvenance: table });
|
|
447
|
+
}
|
|
448
|
+
}
|
package/lib/character.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Body-plan-neutral character rig contracts and
|
|
3
|
-
*
|
|
2
|
+
* Body-plan-neutral character rig contracts and rigid/bone joint-chain
|
|
3
|
+
* scaffolds. Presets are semantic data only; they do not introduce humanoid or
|
|
4
4
|
* animal geometry primitive families.
|
|
5
5
|
*/
|
|
6
6
|
import * as THREE from 'three';
|
|
7
|
+
export { createSkinnedPart, type CreateSkinnedPartOptions } from "./skin-binding.js";
|
|
7
8
|
export declare const KILN_CHARACTER_JOINT_EXTRAS_KEY: 'kilnCharacterJoint';
|
|
8
9
|
export declare const KILN_CHARACTER_RIG_EXTRAS_KEY: 'kilnCharacterRig';
|
|
9
10
|
export declare const KILN_CHARACTER_JOINT_SCHEMA_VERSION: 1;
|
|
@@ -123,6 +124,14 @@ export interface CharacterJointNodeEvidenceV1 {
|
|
|
123
124
|
nodePath: string;
|
|
124
125
|
descriptor: CharacterJointDescriptorV1;
|
|
125
126
|
}
|
|
127
|
+
export interface BoneChainResultV1 extends JointChainResultV1 {
|
|
128
|
+
root: THREE.Bone;
|
|
129
|
+
end: THREE.Bone;
|
|
130
|
+
nodes: THREE.Bone[];
|
|
131
|
+
byRole: ReadonlyMap<string, THREE.Bone>;
|
|
132
|
+
}
|
|
133
|
+
/** Opt-in semantic bones. Existing createJointChain continues to make rigid pivots. */
|
|
134
|
+
export declare function createBoneChain(chainName: string, segments: readonly JointChainSegmentV1[], options?: CreateJointChainOptionsV1): BoneChainResultV1;
|
|
126
135
|
/** Deterministic indexed paths shared by QA and diagnostic descriptors. */
|
|
127
136
|
export declare function collectCharacterJointNodes(root: THREE.Object3D): CharacterJointNodeEvidenceV1[];
|
|
128
137
|
export declare const BIPED_RIG_PRESET_V1: CharacterRigGraphV1;
|
package/lib/character.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Body-plan-neutral character rig contracts and
|
|
3
|
-
*
|
|
2
|
+
* Body-plan-neutral character rig contracts and rigid/bone joint-chain
|
|
3
|
+
* scaffolds. Presets are semantic data only; they do not introduce humanoid or
|
|
4
4
|
* animal geometry primitive families.
|
|
5
5
|
*/
|
|
6
6
|
import * as THREE from 'three';
|
|
7
7
|
import { stampSemanticMetadataV1 } from "./contracts/index.js";
|
|
8
|
+
import { GEOMETRY_ALLOCATION_LIMITS } from "./geometry-budget.js";
|
|
9
|
+
import { AuthoringDiagnosticError } from "./evaluator/authoring-diagnostic.js";
|
|
10
|
+
export { createSkinnedPart } from "./skin-binding.js";
|
|
8
11
|
export const KILN_CHARACTER_JOINT_EXTRAS_KEY = 'kilnCharacterJoint';
|
|
9
12
|
export const KILN_CHARACTER_RIG_EXTRAS_KEY = 'kilnCharacterRig';
|
|
10
13
|
export const KILN_CHARACTER_JOINT_SCHEMA_VERSION = 1;
|
|
@@ -486,6 +489,91 @@ export function createJointChain(chainName, segments, options = {}) {
|
|
|
486
489
|
descriptors,
|
|
487
490
|
};
|
|
488
491
|
}
|
|
492
|
+
/** Opt-in semantic bones. Existing createJointChain continues to make rigid pivots. */
|
|
493
|
+
export function createBoneChain(chainName, segments, options = {}) {
|
|
494
|
+
try {
|
|
495
|
+
return buildBoneChain(chainName, segments, options);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
if (error instanceof TypeError || error instanceof RangeError)
|
|
499
|
+
throw new AuthoringDiagnosticError('BONE_CHAIN_ARGUMENT', error.message);
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function buildBoneChain(chainName, segments, options = {}) {
|
|
504
|
+
if (!Array.isArray(segments) ||
|
|
505
|
+
segments.length < 1 ||
|
|
506
|
+
segments.length > GEOMETRY_ALLOCATION_LIMITS.skinBones)
|
|
507
|
+
throw new RangeError(`createBoneChain segment limit is 1..${GEOMETRY_ALLOCATION_LIMITS.skinBones}.`);
|
|
508
|
+
if (options.parent !== undefined && !options.parent.isObject3D)
|
|
509
|
+
throw new TypeError('createBoneChain parent must be an Object3D.');
|
|
510
|
+
const parentDescriptor = options.parent
|
|
511
|
+
? readCharacterJointDescriptorV1(options.parent)
|
|
512
|
+
: undefined;
|
|
513
|
+
if (options.parentRole !== undefined && options.parentRole !== parentDescriptor?.role)
|
|
514
|
+
throw new TypeError('createBoneChain parentRole must match the declared parent joint.');
|
|
515
|
+
const roles = new Set(), names = new Set();
|
|
516
|
+
if (options.parent) {
|
|
517
|
+
let top = options.parent;
|
|
518
|
+
while (top.parent)
|
|
519
|
+
top = top.parent;
|
|
520
|
+
top.traverse((node) => {
|
|
521
|
+
if (node.name)
|
|
522
|
+
names.add(node.name);
|
|
523
|
+
const descriptor = readCharacterJointDescriptorV1(node);
|
|
524
|
+
if (descriptor)
|
|
525
|
+
roles.add(descriptor.role);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
let parentRole = parentDescriptor?.role;
|
|
529
|
+
// Validate the complete chain before allocating nodes or parenting any of them.
|
|
530
|
+
const descriptors = segments.map((segment) => {
|
|
531
|
+
const descriptor = createCharacterJointDescriptorV1({
|
|
532
|
+
role: segment.role,
|
|
533
|
+
aliases: segment.aliases,
|
|
534
|
+
parentRole,
|
|
535
|
+
side: segment.side,
|
|
536
|
+
rest: {
|
|
537
|
+
translation: segment.offset,
|
|
538
|
+
rotation: segment.restRotation,
|
|
539
|
+
scale: segment.restScale,
|
|
540
|
+
},
|
|
541
|
+
localForwardAxis: segment.localForwardAxis,
|
|
542
|
+
localBendAxis: segment.localBendAxis,
|
|
543
|
+
endEffector: segment.endEffector,
|
|
544
|
+
contact: segment.contact,
|
|
545
|
+
});
|
|
546
|
+
const name = characterJointNodeName(chainName, descriptor.role);
|
|
547
|
+
if (roles.has(descriptor.role))
|
|
548
|
+
throw new TypeError(`createBoneChain duplicate role: ${descriptor.role}.`);
|
|
549
|
+
if (names.has(name))
|
|
550
|
+
throw new TypeError(`createBoneChain duplicate node name after sanitization: ${name}.`);
|
|
551
|
+
roles.add(descriptor.role);
|
|
552
|
+
names.add(name);
|
|
553
|
+
parentRole = descriptor.role;
|
|
554
|
+
return descriptor;
|
|
555
|
+
});
|
|
556
|
+
const nodes = descriptors.map((descriptor) => {
|
|
557
|
+
const node = new THREE.Bone();
|
|
558
|
+
node.name = characterJointNodeName(chainName, descriptor.role);
|
|
559
|
+
node.position.fromArray(descriptor.rest.translation);
|
|
560
|
+
node.quaternion.fromArray(descriptor.rest.rotation);
|
|
561
|
+
node.scale.fromArray(descriptor.rest.scale);
|
|
562
|
+
stampCharacterJointDescriptorV1(node, descriptor);
|
|
563
|
+
return node;
|
|
564
|
+
});
|
|
565
|
+
for (let i = 1; i < nodes.length; i++)
|
|
566
|
+
nodes[i - 1].add(nodes[i]);
|
|
567
|
+
if (options.parent)
|
|
568
|
+
options.parent.add(nodes[0]);
|
|
569
|
+
return {
|
|
570
|
+
root: nodes[0],
|
|
571
|
+
end: nodes[nodes.length - 1],
|
|
572
|
+
nodes,
|
|
573
|
+
byRole: new Map(descriptors.map((descriptor, i) => [descriptor.role, nodes[i]])),
|
|
574
|
+
descriptors,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
489
577
|
/** Deterministic indexed paths shared by QA and diagnostic descriptors. */
|
|
490
578
|
export function collectCharacterJointNodes(root) {
|
|
491
579
|
const joints = [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
-
import type
|
|
2
|
+
import { type Document } from '@gltf-transform/core';
|
|
3
3
|
import type { AuthorExtras } from "./user-data-extras.js";
|
|
4
|
+
import { type AtlasProvenanceCollection } from "./atlas-provenance.js";
|
|
4
5
|
export declare function resolveGltfExporter(value?: string | undefined): 'legacy' | 'three';
|
|
5
6
|
export interface ExportPlatform {
|
|
6
7
|
prepare(hasTextures: boolean): Promise<void>;
|
|
@@ -10,4 +11,4 @@ export interface ExportPlatform {
|
|
|
10
11
|
height: number;
|
|
11
12
|
}>;
|
|
12
13
|
}
|
|
13
|
-
export declare function communitySceneDocument(root: THREE.Object3D, clips: THREE.AnimationClip[], platform?: ExportPlatform, authorExtras?: AuthorExtras): Promise<Document>;
|
|
14
|
+
export declare function communitySceneDocument(root: THREE.Object3D, clips: THREE.AnimationClip[], platform?: ExportPlatform, authorExtras?: AuthorExtras, atlas?: AtlasProvenanceCollection): Promise<Document>;
|