@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/character.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
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
|
|
|
8
8
|
import { stampSemanticMetadataV1 } from './contracts';
|
|
9
|
+
import { GEOMETRY_ALLOCATION_LIMITS } from './geometry-budget';
|
|
10
|
+
import { AuthoringDiagnosticError } from './evaluator/authoring-diagnostic';
|
|
11
|
+
|
|
12
|
+
export { createSkinnedPart, type CreateSkinnedPartOptions } from './skin-binding';
|
|
9
13
|
|
|
10
14
|
export const KILN_CHARACTER_JOINT_EXTRAS_KEY = 'kilnCharacterJoint' as const;
|
|
11
15
|
export const KILN_CHARACTER_RIG_EXTRAS_KEY = 'kilnCharacterRig' as const;
|
|
@@ -637,6 +641,107 @@ export interface CharacterJointNodeEvidenceV1 {
|
|
|
637
641
|
descriptor: CharacterJointDescriptorV1;
|
|
638
642
|
}
|
|
639
643
|
|
|
644
|
+
export interface BoneChainResultV1 extends JointChainResultV1 {
|
|
645
|
+
root: THREE.Bone;
|
|
646
|
+
end: THREE.Bone;
|
|
647
|
+
nodes: THREE.Bone[];
|
|
648
|
+
byRole: ReadonlyMap<string, THREE.Bone>;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** Opt-in semantic bones. Existing createJointChain continues to make rigid pivots. */
|
|
652
|
+
export function createBoneChain(
|
|
653
|
+
chainName: string,
|
|
654
|
+
segments: readonly JointChainSegmentV1[],
|
|
655
|
+
options: CreateJointChainOptionsV1 = {},
|
|
656
|
+
): BoneChainResultV1 {
|
|
657
|
+
try {
|
|
658
|
+
return buildBoneChain(chainName, segments, options);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
if (error instanceof TypeError || error instanceof RangeError)
|
|
661
|
+
throw new AuthoringDiagnosticError('BONE_CHAIN_ARGUMENT', error.message);
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function buildBoneChain(
|
|
667
|
+
chainName: string,
|
|
668
|
+
segments: readonly JointChainSegmentV1[],
|
|
669
|
+
options: CreateJointChainOptionsV1 = {},
|
|
670
|
+
): BoneChainResultV1 {
|
|
671
|
+
if (
|
|
672
|
+
!Array.isArray(segments) ||
|
|
673
|
+
segments.length < 1 ||
|
|
674
|
+
segments.length > GEOMETRY_ALLOCATION_LIMITS.skinBones
|
|
675
|
+
)
|
|
676
|
+
throw new RangeError(
|
|
677
|
+
`createBoneChain segment limit is 1..${GEOMETRY_ALLOCATION_LIMITS.skinBones}.`,
|
|
678
|
+
);
|
|
679
|
+
if (options.parent !== undefined && !options.parent.isObject3D)
|
|
680
|
+
throw new TypeError('createBoneChain parent must be an Object3D.');
|
|
681
|
+
const parentDescriptor = options.parent
|
|
682
|
+
? readCharacterJointDescriptorV1(options.parent)
|
|
683
|
+
: undefined;
|
|
684
|
+
if (options.parentRole !== undefined && options.parentRole !== parentDescriptor?.role)
|
|
685
|
+
throw new TypeError('createBoneChain parentRole must match the declared parent joint.');
|
|
686
|
+
const roles = new Set<string>(),
|
|
687
|
+
names = new Set<string>();
|
|
688
|
+
if (options.parent) {
|
|
689
|
+
let top = options.parent;
|
|
690
|
+
while (top.parent) top = top.parent;
|
|
691
|
+
top.traverse((node) => {
|
|
692
|
+
if (node.name) names.add(node.name);
|
|
693
|
+
const descriptor = readCharacterJointDescriptorV1(node);
|
|
694
|
+
if (descriptor) roles.add(descriptor.role);
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
let parentRole = parentDescriptor?.role;
|
|
698
|
+
// Validate the complete chain before allocating nodes or parenting any of them.
|
|
699
|
+
const descriptors = segments.map((segment) => {
|
|
700
|
+
const descriptor = createCharacterJointDescriptorV1({
|
|
701
|
+
role: segment.role,
|
|
702
|
+
aliases: segment.aliases,
|
|
703
|
+
parentRole,
|
|
704
|
+
side: segment.side,
|
|
705
|
+
rest: {
|
|
706
|
+
translation: segment.offset,
|
|
707
|
+
rotation: segment.restRotation,
|
|
708
|
+
scale: segment.restScale,
|
|
709
|
+
},
|
|
710
|
+
localForwardAxis: segment.localForwardAxis,
|
|
711
|
+
localBendAxis: segment.localBendAxis,
|
|
712
|
+
endEffector: segment.endEffector,
|
|
713
|
+
contact: segment.contact,
|
|
714
|
+
});
|
|
715
|
+
const name = characterJointNodeName(chainName, descriptor.role);
|
|
716
|
+
if (roles.has(descriptor.role))
|
|
717
|
+
throw new TypeError(`createBoneChain duplicate role: ${descriptor.role}.`);
|
|
718
|
+
if (names.has(name))
|
|
719
|
+
throw new TypeError(`createBoneChain duplicate node name after sanitization: ${name}.`);
|
|
720
|
+
roles.add(descriptor.role);
|
|
721
|
+
names.add(name);
|
|
722
|
+
parentRole = descriptor.role;
|
|
723
|
+
return descriptor;
|
|
724
|
+
});
|
|
725
|
+
const nodes = descriptors.map((descriptor) => {
|
|
726
|
+
const node = new THREE.Bone();
|
|
727
|
+
node.name = characterJointNodeName(chainName, descriptor.role);
|
|
728
|
+
node.position.fromArray(descriptor.rest.translation);
|
|
729
|
+
node.quaternion.fromArray(descriptor.rest.rotation);
|
|
730
|
+
node.scale.fromArray(descriptor.rest.scale);
|
|
731
|
+
stampCharacterJointDescriptorV1(node, descriptor);
|
|
732
|
+
return node;
|
|
733
|
+
});
|
|
734
|
+
for (let i = 1; i < nodes.length; i++) nodes[i - 1]!.add(nodes[i]!);
|
|
735
|
+
if (options.parent) options.parent.add(nodes[0]!);
|
|
736
|
+
return {
|
|
737
|
+
root: nodes[0]!,
|
|
738
|
+
end: nodes[nodes.length - 1]!,
|
|
739
|
+
nodes,
|
|
740
|
+
byRole: new Map(descriptors.map((descriptor, i) => [descriptor.role, nodes[i]!])),
|
|
741
|
+
descriptors,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
640
745
|
/** Deterministic indexed paths shared by QA and diagnostic descriptors. */
|
|
641
746
|
export function collectCharacterJointNodes(root: THREE.Object3D): CharacterJointNodeEvidenceV1[] {
|
|
642
747
|
const joints: CharacterJointNodeEvidenceV1[] = [];
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
|
|
3
|
-
import type
|
|
3
|
+
import { GLB_BUFFER, type Document } from '@gltf-transform/core';
|
|
4
4
|
import { createGltfIO } from './gltf-io';
|
|
5
5
|
import { openShellExtras } from './open-shell';
|
|
6
6
|
import { rigExtrasForExport } from './rig-export';
|
|
7
7
|
import type { AuthorExtras } from './user-data-extras';
|
|
8
|
+
import { assertPortableMaterialSides } from './material-export';
|
|
9
|
+
import {
|
|
10
|
+
collectAtlasProvenance,
|
|
11
|
+
refreshAtlasDocumentProvenance,
|
|
12
|
+
type AtlasProvenanceCollection,
|
|
13
|
+
} from './atlas-provenance';
|
|
8
14
|
import {
|
|
9
15
|
KILN_SEMANTIC_EXTRAS_KEY,
|
|
10
16
|
validateSemanticMetadataV1,
|
|
@@ -60,9 +66,14 @@ export async function communitySceneDocument(
|
|
|
60
66
|
clips: THREE.AnimationClip[],
|
|
61
67
|
platform: ExportPlatform = browserPlatform,
|
|
62
68
|
authorExtras?: AuthorExtras,
|
|
69
|
+
atlas: AtlasProvenanceCollection = collectAtlasProvenance(root),
|
|
63
70
|
): Promise<Document> {
|
|
71
|
+
assertPortableMaterialSides(root);
|
|
64
72
|
const materials = new Map<THREE.Material, THREE.Material>();
|
|
73
|
+
const originalMaterials = new Map<THREE.Material, THREE.Material>();
|
|
74
|
+
const originalGeometries = new Map<THREE.Mesh, THREE.BufferGeometry>();
|
|
65
75
|
const textures = new Map<THREE.Texture, THREE.Texture>();
|
|
76
|
+
const directImages = new Map<THREE.Texture, { bytes: Uint8Array; mime: string }>();
|
|
66
77
|
const nodes = new Map<THREE.Object3D, THREE.Object3D>();
|
|
67
78
|
const encoded: Array<Promise<void>> = [];
|
|
68
79
|
function texture(source: THREE.Texture): THREE.Texture {
|
|
@@ -75,6 +86,11 @@ export async function communitySceneDocument(
|
|
|
75
86
|
textures.set(source, copy);
|
|
76
87
|
const bytes = source.userData['encoded'] as { bytes?: Uint8Array; mime?: string } | undefined;
|
|
77
88
|
if (bytes?.bytes) {
|
|
89
|
+
// Canvas stores premultiplied color and can discard hidden cutout RGB or
|
|
90
|
+
// round partial-alpha channels. Keep already-encoded direct images when
|
|
91
|
+
// the exporter does not need to flip or derive a new texture from them.
|
|
92
|
+
if (!copy.flipY && (bytes.mime === 'image/png' || bytes.mime === 'image/jpeg'))
|
|
93
|
+
directImages.set(copy, { bytes: bytes.bytes, mime: bytes.mime });
|
|
78
94
|
encoded.push(
|
|
79
95
|
(async () => {
|
|
80
96
|
copy.image = await platform.decode(bytes.bytes!);
|
|
@@ -104,6 +120,7 @@ export async function communitySceneDocument(
|
|
|
104
120
|
(copy as unknown as Record<string, unknown>)[key] = texture(value);
|
|
105
121
|
}
|
|
106
122
|
materials.set(source, copy);
|
|
123
|
+
originalMaterials.set(copy, source);
|
|
107
124
|
return copy;
|
|
108
125
|
}
|
|
109
126
|
function node(source: THREE.Object3D): THREE.Object3D {
|
|
@@ -137,6 +154,7 @@ export async function communitySceneDocument(
|
|
|
137
154
|
map: sprite.material.map ? texture(sprite.material.map) : null,
|
|
138
155
|
});
|
|
139
156
|
Object.assign(spriteMaterial.userData, authorExtras?.material(sprite.material));
|
|
157
|
+
originalMaterials.set(spriteMaterial, sprite.material);
|
|
140
158
|
copy = new THREE.Mesh(geometry, spriteMaterial);
|
|
141
159
|
THREE.Object3D.prototype.copy.call(copy, shadow, false);
|
|
142
160
|
}
|
|
@@ -172,6 +190,7 @@ export async function communitySceneDocument(
|
|
|
172
190
|
const original = source as THREE.Mesh;
|
|
173
191
|
const mesh = copy as THREE.Mesh;
|
|
174
192
|
mesh.geometry = cleanClone(original.geometry);
|
|
193
|
+
originalGeometries.set(mesh, original.geometry);
|
|
175
194
|
mesh.material = Array.isArray(original.material)
|
|
176
195
|
? original.material.map(material)
|
|
177
196
|
: material(original.material);
|
|
@@ -195,6 +214,42 @@ export async function communitySceneDocument(
|
|
|
195
214
|
}
|
|
196
215
|
await Promise.all(encoded);
|
|
197
216
|
await platform.prepare(textures.size > 0);
|
|
217
|
+
// Upstream channel-packing and normal-basis conversions use drawImage, which
|
|
218
|
+
// does not accept a DataTexture's {data,width,height} image in either host.
|
|
219
|
+
// Adapt only those detached inputs; direct color textures keep their bytes.
|
|
220
|
+
const canvasInputs = new Map<THREE.Texture, THREE.Texture>();
|
|
221
|
+
const canvasInput = (map: THREE.Texture | null): THREE.Texture | null => {
|
|
222
|
+
const raw = map?.image as { data?: Uint8Array; width: number; height: number } | undefined;
|
|
223
|
+
if (!map || !raw?.data) return map;
|
|
224
|
+
const found = canvasInputs.get(map);
|
|
225
|
+
if (found) return found;
|
|
226
|
+
const { data, width, height } = raw;
|
|
227
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
228
|
+
const context = canvas.getContext('2d');
|
|
229
|
+
if (!context) throw new Error('A 2D canvas is required to prepare export texture channels.');
|
|
230
|
+
context.putImageData(new ImageData(new Uint8ClampedArray(data), width, height), 0, 0);
|
|
231
|
+
const adapted = new THREE.Texture().copy(map);
|
|
232
|
+
adapted.source = new THREE.TextureSource(canvas);
|
|
233
|
+
canvasInputs.set(map, adapted);
|
|
234
|
+
const direct = directImages.get(map);
|
|
235
|
+
if (direct) directImages.set(adapted, direct);
|
|
236
|
+
return adapted;
|
|
237
|
+
};
|
|
238
|
+
for (const material of materials.values()) {
|
|
239
|
+
// putImageData ignores the canvas transform used by GLTFExporter for flipY.
|
|
240
|
+
// A canvas image makes that explicit orientation request reach drawImage.
|
|
241
|
+
for (const [key, value] of Object.entries(material)) {
|
|
242
|
+
if (value instanceof THREE.Texture && value.flipY && value.image?.data)
|
|
243
|
+
(material as unknown as Record<string, unknown>)[key] = canvasInput(value);
|
|
244
|
+
}
|
|
245
|
+
const standard = material as THREE.MeshStandardMaterial;
|
|
246
|
+
if (!standard.isMeshStandardMaterial) continue;
|
|
247
|
+
if (standard.metalnessMap !== standard.roughnessMap) {
|
|
248
|
+
standard.metalnessMap = canvasInput(standard.metalnessMap);
|
|
249
|
+
standard.roughnessMap = canvasInput(standard.roughnessMap);
|
|
250
|
+
}
|
|
251
|
+
standard.normalMap = canvasInput(standard.normalMap);
|
|
252
|
+
}
|
|
198
253
|
const exportClips = clips.map((clip) => {
|
|
199
254
|
const cloned = cleanClone(clip);
|
|
200
255
|
// UUID-targeted clips must point at the export copy, not the live scene.
|
|
@@ -220,7 +275,55 @@ export async function communitySceneDocument(
|
|
|
220
275
|
});
|
|
221
276
|
return cloned;
|
|
222
277
|
});
|
|
223
|
-
const
|
|
278
|
+
const preservedImages = new Map<number, { bytes: Uint8Array; mime: string }>();
|
|
279
|
+
const exporter = new GLTFExporter().register(() => ({
|
|
280
|
+
writeMesh(mesh, definition) {
|
|
281
|
+
const geometry = originalGeometries.get(mesh);
|
|
282
|
+
const binding = geometry && atlas.binding(geometry);
|
|
283
|
+
if (binding)
|
|
284
|
+
for (const primitive of definition.primitives as Array<{
|
|
285
|
+
extras?: Record<string, unknown>;
|
|
286
|
+
}>)
|
|
287
|
+
primitive.extras = { ...primitive.extras, kilnAtlasBinding: binding };
|
|
288
|
+
},
|
|
289
|
+
async writeMaterialAsync(material, definition) {
|
|
290
|
+
const source = originalMaterials.get(material);
|
|
291
|
+
if (!source) return;
|
|
292
|
+
const bag = source as unknown as Record<string, THREE.Texture | undefined>;
|
|
293
|
+
const pbr = definition.pbrMetallicRoughness as
|
|
294
|
+
| {
|
|
295
|
+
baseColorTexture?: { extras?: Record<string, unknown> };
|
|
296
|
+
metallicRoughnessTexture?: { extras?: Record<string, unknown> };
|
|
297
|
+
}
|
|
298
|
+
| undefined;
|
|
299
|
+
for (const [slot, info] of [
|
|
300
|
+
['map', pbr?.baseColorTexture],
|
|
301
|
+
['normalMap', definition.normalTexture as { extras?: Record<string, unknown> } | undefined],
|
|
302
|
+
['metalnessMap', pbr?.metallicRoughnessTexture],
|
|
303
|
+
['roughnessMap', pbr?.metallicRoughnessTexture],
|
|
304
|
+
[
|
|
305
|
+
'emissiveMap',
|
|
306
|
+
definition.emissiveTexture as { extras?: Record<string, unknown> } | undefined,
|
|
307
|
+
],
|
|
308
|
+
['aoMap', definition.occlusionTexture as { extras?: Record<string, unknown> } | undefined],
|
|
309
|
+
] as const) {
|
|
310
|
+
const texture = bag[slot],
|
|
311
|
+
ref = texture && atlas.texture(texture);
|
|
312
|
+
if (ref && info) {
|
|
313
|
+
const prior = info.extras?.['kilnAtlasReference'] as { id?: string } | undefined;
|
|
314
|
+
if (prior && prior.id !== ref.id)
|
|
315
|
+
throw new TypeError('Atlas channel packing requires one exact source identity.');
|
|
316
|
+
info.extras = { ...info.extras, kilnAtlasReference: ref };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
writeTexture(map, definition) {
|
|
321
|
+
const direct = directImages.get(map);
|
|
322
|
+
if (direct && typeof definition['source'] === 'number')
|
|
323
|
+
preservedImages.set(definition['source'], direct);
|
|
324
|
+
},
|
|
325
|
+
}));
|
|
326
|
+
const bytes = await exporter.parseAsync(copy, {
|
|
224
327
|
binary: true,
|
|
225
328
|
trs: true,
|
|
226
329
|
onlyVisible: false,
|
|
@@ -228,6 +331,37 @@ export async function communitySceneDocument(
|
|
|
228
331
|
});
|
|
229
332
|
const io = createGltfIO();
|
|
230
333
|
const json = await io.binaryToJSON(new Uint8Array(bytes as ArrayBuffer));
|
|
334
|
+
for (const [index, original] of preservedImages) {
|
|
335
|
+
const image = json.json.images?.[index];
|
|
336
|
+
if (!image) throw new Error('The glTF converter lost a direct texture image.');
|
|
337
|
+
const uri = `kiln-direct-image-${index}`;
|
|
338
|
+
image.uri = uri;
|
|
339
|
+
image.mimeType = original.mime;
|
|
340
|
+
delete image.bufferView;
|
|
341
|
+
json.resources[uri] = new Uint8Array(original.bytes);
|
|
342
|
+
}
|
|
343
|
+
// GLTFExporter includes four-byte buffer alignment in generated image views.
|
|
344
|
+
// Keep the BIN allocation aligned but end a PNG's view at its IEND chunk.
|
|
345
|
+
// Direct authored bytes above remain untouched; this is not an input decoder.
|
|
346
|
+
for (const image of json.json.images ?? []) {
|
|
347
|
+
if (image.mimeType !== 'image/png' || image.bufferView === undefined) continue;
|
|
348
|
+
const view = json.json.bufferViews![image.bufferView]!;
|
|
349
|
+
const buffer = json.resources[json.json.buffers?.[view.buffer]?.uri ?? GLB_BUFFER]!;
|
|
350
|
+
const start = view.byteOffset ?? 0;
|
|
351
|
+
const png = buffer.subarray(start, start + view.byteLength);
|
|
352
|
+
const data = new DataView(png.buffer, png.byteOffset, png.byteLength);
|
|
353
|
+
for (let offset = 8; offset + 12 <= png.length; ) {
|
|
354
|
+
const length = data.getUint32(offset),
|
|
355
|
+
end = offset + length + 12;
|
|
356
|
+
if (end > png.length) break;
|
|
357
|
+
if (data.getUint32(offset + 4) === 0x49454e44 && length === 0) {
|
|
358
|
+
if (png.length - end <= 3 && png.subarray(end).every((byte) => byte === 0))
|
|
359
|
+
view.byteLength = end;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
offset = end;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
231
365
|
// Three names texture/sampler bindings; glTF Transform models textures by
|
|
232
366
|
// image. Carry the first named binding onto an unnamed shared image so the
|
|
233
367
|
// conversion retains a useful authored resource name without duplicating it.
|
|
@@ -236,7 +370,13 @@ export async function communitySceneDocument(
|
|
|
236
370
|
const image = json.json.images?.[binding.source];
|
|
237
371
|
if (image && !image.name) image.name = binding.name;
|
|
238
372
|
}
|
|
239
|
-
|
|
373
|
+
const document = await io.readJSON(json);
|
|
374
|
+
if (atlas.table) {
|
|
375
|
+
const scene = document.getRoot().getDefaultScene() ?? document.getRoot().listScenes()[0]!;
|
|
376
|
+
scene.setExtras({ ...scene.getExtras(), kilnAtlasProvenance: atlas.table });
|
|
377
|
+
await refreshAtlasDocumentProvenance(document);
|
|
378
|
+
}
|
|
379
|
+
return document;
|
|
240
380
|
}
|
|
241
381
|
|
|
242
382
|
/** Encode the effective UV matrix in glTF's T*R*S order, including manual
|
|
@@ -1176,6 +1176,74 @@ define(
|
|
|
1176
1176
|
intents: ['create a body-plan-neutral articulated chain'],
|
|
1177
1177
|
},
|
|
1178
1178
|
);
|
|
1179
|
+
define(
|
|
1180
|
+
'createBoneChain',
|
|
1181
|
+
{
|
|
1182
|
+
...nodeFacts,
|
|
1183
|
+
axes: 'Explicit local forward/bend axes and rest transforms; body-plan neutral.',
|
|
1184
|
+
origin: 'Offsets are local to the preceding bone; first offset is under the supplied parent.',
|
|
1185
|
+
parameters: [
|
|
1186
|
+
'1..1024 descriptors; unique roles and sanitized node names across the parent hierarchy. The first parentRole is inferred from a declared parent joint; an explicit value must agree.',
|
|
1187
|
+
],
|
|
1188
|
+
preservation: [
|
|
1189
|
+
'Creates real Bones with the same versioned semantic descriptors as rigid createJointChain; leaves existing nodes untouched.',
|
|
1190
|
+
],
|
|
1191
|
+
semantics: ['Preserves declared roles, local rest frames, axes, contacts and end-effectors.'],
|
|
1192
|
+
cost: 'Linear in descriptors and existing parent hierarchy; validates before adding the chain.',
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
kind: 'assembly',
|
|
1196
|
+
references: ['src/character.ts'],
|
|
1197
|
+
tags: ['rig', 'bones', 'skinning'],
|
|
1198
|
+
aliases: ['bone chain', 'semantic bones'],
|
|
1199
|
+
intents: ['create bones for a skinned custom body plan'],
|
|
1200
|
+
related: [
|
|
1201
|
+
{ name: 'createJointChain', relation: 'alternative' },
|
|
1202
|
+
{ name: 'createSkinnedPart', relation: 'companion' },
|
|
1203
|
+
],
|
|
1204
|
+
limitations: [
|
|
1205
|
+
'Does not bind geometry, create animations, choose a body plan or generate anatomical weights.',
|
|
1206
|
+
],
|
|
1207
|
+
},
|
|
1208
|
+
);
|
|
1209
|
+
define(
|
|
1210
|
+
'createSkinnedPart',
|
|
1211
|
+
{
|
|
1212
|
+
...nodeFacts,
|
|
1213
|
+
ownership:
|
|
1214
|
+
'Owns cloned geometry, influence arrays and inverse bind matrices. Materials and caller-owned bones are shared; source geometry stays unchanged.',
|
|
1215
|
+
coordinates:
|
|
1216
|
+
'Geometry is mesh-local; supplied mesh transforms are local to the required parent. All bones must share its existing hierarchy and be in the intended rest pose.',
|
|
1217
|
+
axes: 'No forced character forward axis. Local rotation options are Euler XYZ degrees, matching createPart.',
|
|
1218
|
+
origin: 'The supplied geometry origin is unchanged.',
|
|
1219
|
+
topology: [
|
|
1220
|
+
'Creates a skinned mesh on cloned unchanged topology; no automatic solid repair or watertightness claim.',
|
|
1221
|
+
],
|
|
1222
|
+
parameters: [
|
|
1223
|
+
'1..1024 distinct named Bones; 1..262144 xyz vertices; 128 MiB total cloned attribute storage plus new influences.',
|
|
1224
|
+
'Exactly four valid indices and finite nonnegative weights per vertex. Default requires each weight <=1 and sum within 1e-5 of one; normalizeWeights:true explicitly normalizes positive finite sums. Zero sums never receive invented weights.',
|
|
1225
|
+
'Existing skin attributes, singular/nonfinite bind transforms and ambiguous bone names are rejected.',
|
|
1226
|
+
],
|
|
1227
|
+
preservation: [
|
|
1228
|
+
'Unchanged topology retains UV/color/morph attributes and material groups on an owned clone. Skeleton order matches the supplied bone array.',
|
|
1229
|
+
],
|
|
1230
|
+
semantics: ['Optional semantic metadata is stamped on the returned Mesh_name.'],
|
|
1231
|
+
cost: 'O(vertices + attributes + bones + parent hierarchy); no automatic weighting or anatomical fit computation.',
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
kind: 'assembly',
|
|
1235
|
+
references: ['src/skin-binding.ts'],
|
|
1236
|
+
tags: ['rig', 'skinning', 'weights', 'binding'],
|
|
1237
|
+
aliases: ['skin binding', 'weighted mesh'],
|
|
1238
|
+
intents: ['bind explicit vertex influences without mutating shared geometry'],
|
|
1239
|
+
related: [{ name: 'createBoneChain', relation: 'prerequisite' }],
|
|
1240
|
+
limitations: [
|
|
1241
|
+
'Requires the explicit Three exporter for skin delivery; legacy defaults remain unchanged.',
|
|
1242
|
+
'Four influences are this helper contract, not a new global restriction on direct Three.js authoring.',
|
|
1243
|
+
'Bind only after rest transforms are assembled. Reparenting or editing rest geometry afterward needs deliberate rebinding; runtime posed bounds remain the consumer responsibility.',
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
);
|
|
1179
1247
|
define(
|
|
1180
1248
|
'createVehicleFrame',
|
|
1181
1249
|
{
|
|
@@ -1427,6 +1495,39 @@ define(
|
|
|
1427
1495
|
},
|
|
1428
1496
|
);
|
|
1429
1497
|
|
|
1498
|
+
define(
|
|
1499
|
+
'compilePortableMaterialSpecV3',
|
|
1500
|
+
{
|
|
1501
|
+
...materialFacts,
|
|
1502
|
+
execution: 'async',
|
|
1503
|
+
ownership:
|
|
1504
|
+
'Creates an owned material and owned texture, Source, pixel and encoded-byte bindings; never mutates a cached resource texture.',
|
|
1505
|
+
parameters: [
|
|
1506
|
+
'Strict schemaVersion 3 pbrMetallicRoughness specification, with existing scalar PBR fields and exact slot usage checks.',
|
|
1507
|
+
'Each texture ref is a typed procedural V3 spec or approved resource ID plus a complete sampler: minFilter, magFilter, wrapS and wrapT.',
|
|
1508
|
+
'Procedural dimensions are independent width/height; colors are straight RGBA bytes. The material selects opaque, mask or blend behavior explicitly.',
|
|
1509
|
+
'Source accepts exactly one specification. Host resources are injected after verification; authored source cannot provide bytes, paths, URLs or a resolver.',
|
|
1510
|
+
],
|
|
1511
|
+
preservation: [
|
|
1512
|
+
'Existing V1/V2 recipe algorithms, hashes and material defaults are unchanged.',
|
|
1513
|
+
'Sampler and alpha state export through standard glTF material and sampler fields; runtime glow, lighting and postprocessing remain consumer choices.',
|
|
1514
|
+
],
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
...materialMetadata,
|
|
1518
|
+
references: [
|
|
1519
|
+
'src/procedural-material-v3.ts',
|
|
1520
|
+
'src/portable-material-runtime.ts',
|
|
1521
|
+
'src/portable-sampler.ts',
|
|
1522
|
+
],
|
|
1523
|
+
aliases: ['portable RGBA material', 'explicit texture sampling'],
|
|
1524
|
+
intents: ['compile cutout or pixel-textured portable materials'],
|
|
1525
|
+
limitations: [
|
|
1526
|
+
'Height-map derivation is not part of the V3 binding contract. V2 remains available for existing derive recipes.',
|
|
1527
|
+
],
|
|
1528
|
+
},
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1430
1531
|
const animationFacts = {
|
|
1431
1532
|
units:
|
|
1432
1533
|
'Keyframe times and durations are seconds; transform values are absolute, not additive offsets.',
|
|
@@ -2048,13 +2149,16 @@ define(
|
|
|
2048
2149
|
execution: 'async',
|
|
2049
2150
|
axes: 'Chart orientation is selected by xatlas, not a directional material frame.',
|
|
2050
2151
|
parameters: [
|
|
2051
|
-
'Nonempty complete triangle geometry;
|
|
2052
|
-
'
|
|
2152
|
+
'Nonempty complete triangle geometry; at most 65,536 source and output vertices, 1,000,000 triangles and a 128 MiB attribute allocation bound. Split larger meshes.',
|
|
2153
|
+
'resolution is an integer 1..4096 target, not guaranteed final dimensions; read userData.atlas.width/height. padding is an integer 0..resolution, default 2.',
|
|
2154
|
+
'useNormals defaults false (recompute); true uses and preserves source normals. Normal morphs require true and a base normal attribute.',
|
|
2053
2155
|
],
|
|
2054
2156
|
topology: ['Reindexes/splits vertices for charts; does not repair malformed source topology.'],
|
|
2055
2157
|
preservation: [
|
|
2056
|
-
'Returns
|
|
2057
|
-
'
|
|
2158
|
+
'Returns owned buffers; source remains unchanged. Seam mapping retains per-vertex attributes with 1..4 components, including colors, skin weights/indices, extra UVs and normalized/interleaved storage values.',
|
|
2159
|
+
'Preserves position/normal/color morph targets, relative-mode flag, triangle groups, draw range, name and JSON userData. Normal policy stays opt-in; zero normals are repaired.',
|
|
2160
|
+
'Replaces UV0 and atlas metadata; drops stale tangents. Recompute tangents after final UVs. Mesh materials, skeleton and animation belong to the caller, not the geometry helper.',
|
|
2161
|
+
'Rejects instanced data, unsupported morph semantics, multiple atlas pages, invalid correspondence and adapter index overflow instead of silently discarding data.',
|
|
2058
2162
|
],
|
|
2059
2163
|
cost: 'WASM charting/packing depends on mesh complexity and resolution; no general time bound.',
|
|
2060
2164
|
},
|
|
@@ -2111,17 +2215,23 @@ define(
|
|
|
2111
2215
|
...textureFacts,
|
|
2112
2216
|
ownership: 'Creates a new DataTexture with generated bytes and portable metadata.',
|
|
2113
2217
|
parameters: [
|
|
2114
|
-
'
|
|
2218
|
+
'V1/V2 preserve opaque patterns: size power-of-two 4..1024 and at most 8 layers. Explicit schemaVersion 3 uses width/height integers 4..1024, byte RGBA, at most 64 ordered operations and 64 polygon vertices.',
|
|
2219
|
+
'V3 paint/mask use integer pixel-edge shapes with binary center coverage and fixed integer source-over rounding. A mask clears outside its shape; invert keeps outside. Zero-alpha pixels are transparent black.',
|
|
2220
|
+
'V3 color-only palette colors are 1..32 RGB integers, optional fixed Bayer4 dither. Color-only stamps use bounded Xorshift32 seed/area placement; periodic wrapping is explicit and at most 256 expanded copies. Legacy-v2 layers delegate unchanged square V2 patterns with matching usage.',
|
|
2115
2221
|
'Pattern counts 1..256 and noise octaves 1..6; seeded patterns are deterministic.',
|
|
2116
2222
|
'For stripes and gradient, angleDeg rotates the direction of color variation in UV space about the image center. At 0 degrees colors vary across U: stripe bands run along V. At 90 degrees colors vary across V: bands run along U. This does not rotate the mesh UVs.',
|
|
2117
2223
|
'Stripes count is the number of alternating bands across one unit of the projected coordinate, not the number of colorA/colorB pairs. Gradient runs from its from color to its to color along that coordinate and clamps outside 0..1.',
|
|
2118
2224
|
'Unknown keys, callbacks, paths, URLs and shader source are rejected.',
|
|
2119
2225
|
],
|
|
2120
2226
|
preservation: ['Produces repeat-wrapped encoded texture content; color space follows usage.'],
|
|
2121
|
-
cost: '
|
|
2227
|
+
cost: 'V2 retains pixel/layer budgets. V3 preflights at most 1,048,576 texels, 64million work units including polygon edges, palette comparisons and periodic copies, and 64MiB transient bytes.',
|
|
2122
2228
|
},
|
|
2123
2229
|
{
|
|
2124
|
-
references: [
|
|
2230
|
+
references: [
|
|
2231
|
+
'src/procedural-texture.ts',
|
|
2232
|
+
'src/procedural-texture-v3-contract.ts',
|
|
2233
|
+
'docs/procedural-surfaces-v3.md',
|
|
2234
|
+
],
|
|
2125
2235
|
tags: ['texture', 'procedural', 'deterministic'],
|
|
2126
2236
|
aliases: ['generated texture', 'noise texture', 'layered pattern'],
|
|
2127
2237
|
intents: ['generate a repeatable texture without external image files'],
|
|
@@ -2249,6 +2359,77 @@ define(
|
|
|
2249
2359
|
],
|
|
2250
2360
|
},
|
|
2251
2361
|
);
|
|
2362
|
+
define(
|
|
2363
|
+
'textureAtlas',
|
|
2364
|
+
{
|
|
2365
|
+
...textureFacts,
|
|
2366
|
+
execution: 'async',
|
|
2367
|
+
units: 'Integer image pixel edges; half-open content rectangles and gutters.',
|
|
2368
|
+
axes: 'Image +X right and +Y down, top-left origin. Texture flipY=false.',
|
|
2369
|
+
ownership:
|
|
2370
|
+
'Creates an owned texture and immutable-by-copy source snapshots; host map capability is never exposed to authored code.',
|
|
2371
|
+
parameters: [
|
|
2372
|
+
'schemaVersion1, width/height 4..1024, explicit usage and 1..64 named regions from at most16 distinct sources.',
|
|
2373
|
+
'Each region has name, content rectangle and source; gutter defaults2 and edgeMode defaults copy-edge. Source crops and content dimensions must match without resampling.',
|
|
2374
|
+
'Source is {kind:"procedural",spec:V3} or {kind:"resource",resourceId:exactMapId,pin:{resourceId:materialId,revisionId,sha256},expected?}. Pins are host-selected immutable library revisions; no arbitrary acquisition I/O.',
|
|
2375
|
+
'Optional sampler specifies all four portable fields; default linear-mipmap-linear/linear/clamp-to-edge on both axes.',
|
|
2376
|
+
],
|
|
2377
|
+
preservation: [
|
|
2378
|
+
'Copies straight RGBA; mask-rgb-dilate changes only transparent RGB, leaving coverage unchanged. Sources and padded allocations stay separate.',
|
|
2379
|
+
],
|
|
2380
|
+
semantics: [
|
|
2381
|
+
'Named atlas metadata records compiler, recipe, layout, source, raw-pixel and domain-separated atlas hashes. It does not establish project membership.',
|
|
2382
|
+
],
|
|
2383
|
+
cost: 'Bounded full-source decode, compilation, pixel work and scratch memory admitted together before source loads.',
|
|
2384
|
+
},
|
|
2385
|
+
{
|
|
2386
|
+
references: [
|
|
2387
|
+
'src/texture-atlas-contract.ts',
|
|
2388
|
+
'src/texture-atlas-pixels.ts',
|
|
2389
|
+
'src/texture-atlas.ts',
|
|
2390
|
+
],
|
|
2391
|
+
tags: ['texture', 'atlas', 'rgba', 'ownership'],
|
|
2392
|
+
aliases: ['named texture sheet', 'foliage atlas'],
|
|
2393
|
+
intents: ['compose reusable named pixel regions without a mesh'],
|
|
2394
|
+
limitations: [
|
|
2395
|
+
'No automatic packing, rotation or resampling. Gutters reduce filtering bleed but cannot isolate every downsampled mip level.',
|
|
2396
|
+
],
|
|
2397
|
+
related: [{ name: 'remapUVToAtlas', relation: 'companion' }],
|
|
2398
|
+
},
|
|
2399
|
+
);
|
|
2400
|
+
|
|
2401
|
+
define(
|
|
2402
|
+
'remapUVToAtlas',
|
|
2403
|
+
{
|
|
2404
|
+
...ownedGeometry,
|
|
2405
|
+
units: 'Input unit UVs [0,1]; target rectangles are atlas pixels.',
|
|
2406
|
+
axes: 'Explicit top-left or bottom-left input origin; target follows top-left image coordinates.',
|
|
2407
|
+
parameters: [
|
|
2408
|
+
'Requires a compiled atlas texture or manifest and exact region name.',
|
|
2409
|
+
'inputOrigin and content-boundary/texel-center mode are both required; finite per-vertex UV0 only.',
|
|
2410
|
+
],
|
|
2411
|
+
topology: ['Preserves vertices, indices, groups, draw range and morph correspondence.'],
|
|
2412
|
+
preservation: [
|
|
2413
|
+
'Owns output attributes and metadata; UV0 is replaced. Nonidentity scale drops stale tangents with a warning.',
|
|
2414
|
+
],
|
|
2415
|
+
semantics: [
|
|
2416
|
+
'Records the named region and affine mapping without changing asset category, project membership or materials.',
|
|
2417
|
+
],
|
|
2418
|
+
cost: 'Linear UV mapping plus a bounded clone of geometry and morph buffers.',
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
references: ['src/texture-atlas.ts', 'src/uv-shapes.ts'],
|
|
2422
|
+
tags: ['uv', 'atlas', 'ownership'],
|
|
2423
|
+
aliases: ['named region UV mapping'],
|
|
2424
|
+
intents: ['bind existing UVs to a named atlas region'],
|
|
2425
|
+
limitations: ['Does not unwrap, project, resample or tile within a region.'],
|
|
2426
|
+
related: [
|
|
2427
|
+
{ name: 'textureAtlas', relation: 'prerequisite' },
|
|
2428
|
+
{ name: 'projectUV', relation: 'companion' },
|
|
2429
|
+
],
|
|
2430
|
+
},
|
|
2431
|
+
);
|
|
2432
|
+
|
|
2252
2433
|
define(
|
|
2253
2434
|
'materialBudgetAdvisory',
|
|
2254
2435
|
{
|