@instruktlabs/kiln 1.2.1 → 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 +86 -1
- package/README.md +18 -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/install.md +7 -6
- package/docs/material-library.md +9 -5
- package/docs/migration.md +16 -0
- 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/runtime.md +3 -3
- package/docs/sdk.md +55 -2
- 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/SKILL.md +1 -1
- package/skills/kiln-author-asset/references/camera-cli.md +13 -0
- package/skills/kiln-author-asset/references/geometry-recipes.md +134 -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
|
@@ -8,14 +8,36 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type * as THREE from 'three';
|
|
10
10
|
import type { TextureUsage } from "./textures.js";
|
|
11
|
+
import type { PinnedMaterialMapPortV1 } from "./pinned-material-map.js";
|
|
12
|
+
export interface PreparedPortableTextureV3 {
|
|
13
|
+
descriptor: {
|
|
14
|
+
resourceId: string;
|
|
15
|
+
usage: TextureUsage;
|
|
16
|
+
allowedSlots: readonly string[];
|
|
17
|
+
colorSpace: 'srgb' | 'linear';
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
encodedByteLength: number;
|
|
21
|
+
imageSha256: `sha256:${string}`;
|
|
22
|
+
};
|
|
23
|
+
/** Caller-owned verified PNG bytes, prepared without pixel decoding. */
|
|
24
|
+
bytes: Uint8Array;
|
|
25
|
+
/** Verified provenance only; decoding supplies actual hasAlpha metadata. */
|
|
26
|
+
userData: Record<string, unknown>;
|
|
27
|
+
}
|
|
11
28
|
import { type ApprovedTextureResourceCache } from "./material-resources.js";
|
|
12
29
|
export interface TextureResolver {
|
|
30
|
+
/** Host-only, verified immutable material closure for atlas compilation. */
|
|
31
|
+
pinnedMaterialMaps?: PinnedMaterialMapPortV1;
|
|
13
32
|
/** Host-only metadata used to reject invalid portable slot bindings before loading. */
|
|
14
33
|
describeApprovedTexture?(resourceId: unknown): {
|
|
15
34
|
usage: TextureUsage;
|
|
16
35
|
allowedSlots: readonly string[];
|
|
17
36
|
} | undefined;
|
|
18
37
|
loadApprovedTexture(resourceId: unknown): Promise<THREE.DataTexture>;
|
|
38
|
+
preparePortableTexture?(resourceId: unknown, context?: {
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}): Promise<PreparedPortableTextureV3>;
|
|
19
41
|
materialRecipe(id: unknown, overrides?: unknown): Promise<THREE.MeshStandardMaterial>;
|
|
20
42
|
}
|
|
21
43
|
export declare function createTextureResolver(cache?: ApprovedTextureResourceCache): TextureResolver;
|
package/lib/texture-resolver.js
CHANGED
|
@@ -18,6 +18,30 @@ export function createTextureResolver(cache = DEFAULT_APPROVED_TEXTURE_CACHE) {
|
|
|
18
18
|
}
|
|
19
19
|
return (await cache.load(resourceId)).texture;
|
|
20
20
|
},
|
|
21
|
+
async preparePortableTexture(resourceId, context) {
|
|
22
|
+
context?.signal?.throwIfAborted();
|
|
23
|
+
if (typeof resourceId !== 'string')
|
|
24
|
+
throw new RangeError('Unsupported approved texture resource ID.');
|
|
25
|
+
const resolved = await cache.resolveAsync(resourceId);
|
|
26
|
+
context?.signal?.throwIfAborted();
|
|
27
|
+
// resolveAsync already verifies closed ID, length, hash and bounded PNG IHDR.
|
|
28
|
+
const header = new DataView(resolved.bytes.buffer, resolved.bytes.byteOffset, resolved.bytes.byteLength);
|
|
29
|
+
return {
|
|
30
|
+
descriptor: {
|
|
31
|
+
resourceId,
|
|
32
|
+
usage: resolved.provenance.usage,
|
|
33
|
+
allowedSlots: [...resolved.descriptor.allowedSlots],
|
|
34
|
+
colorSpace: resolved.provenance.colorSpace,
|
|
35
|
+
width: header.getUint32(16),
|
|
36
|
+
height: header.getUint32(20),
|
|
37
|
+
encodedByteLength: resolved.bytes.length,
|
|
38
|
+
imageSha256: `sha256:${resolved.provenance.contentHash}`,
|
|
39
|
+
},
|
|
40
|
+
// resolveAsync already returns a caller-owned copy, never cached bytes.
|
|
41
|
+
bytes: resolved.bytes,
|
|
42
|
+
userData: { kilnTexture: { approvedResource: structuredClone(resolved.provenance) } },
|
|
43
|
+
};
|
|
44
|
+
},
|
|
21
45
|
async materialRecipe(id, overrides) {
|
|
22
46
|
if (typeof id !== 'string')
|
|
23
47
|
throw new TypeError('materialRecipe id must be a string');
|
package/lib/tools/materials.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { assertMaterialJson, materialLibraryHashSchema, materialLibraryIdSchema,
|
|
2
|
+
import { assertMaterialJson, materialLibraryHashSchema, materialLibraryIdSchema, materialLibraryPayloadAnySchema, materialLibraryPortableSpec, materialSourceSchema, } from "../material-library.js";
|
|
3
3
|
import { createMaterialRecordV1, decodeMaterialLibraryPayload } from "../material-library-node.js";
|
|
4
|
+
import { createProceduralMaterialRecord } from "../material-authoring-node.js";
|
|
4
5
|
import { createMaterialPresetDraft, listMaterialPresets } from "../material-presets.js";
|
|
5
6
|
import { nestedRecordDescription, requireActionFields } from "./actions.js";
|
|
6
7
|
const MATERIAL_ACTIONS = [
|
|
@@ -67,8 +68,8 @@ export const materialToolInput = z.strictObject({
|
|
|
67
68
|
revisionId: materialLibraryHashSchema
|
|
68
69
|
.optional()
|
|
69
70
|
.describe("get: the immutable revision; omitted, the material's only revision."),
|
|
70
|
-
draft: record(nestedRecordDescription('create-procedural:
|
|
71
|
-
payload: record(nestedRecordDescription('import: schemaVersion 1
|
|
71
|
+
draft: record(nestedRecordDescription('create-procedural: legacy layered draft, or schemaVersion 2 with explicit samplers and RGBA/atlas recipes. Exact atlas sources must exist in this library.', 'shape:material-draft')),
|
|
72
|
+
payload: record(nestedRecordDescription('import: schemaVersion 1 or 2, complete normalized records and their exact dependencies with embedded PNG bytes.', 'shape:material-import')),
|
|
72
73
|
});
|
|
73
74
|
const materialResult = (material) => ({
|
|
74
75
|
ok: true,
|
|
@@ -115,7 +116,7 @@ export function createKilnMaterialDef(library, presentation = 'grouped') {
|
|
|
115
116
|
: "kiln_material { action: 'list' }";
|
|
116
117
|
return {
|
|
117
118
|
name: 'kiln_material',
|
|
118
|
-
description: 'Manage optional immutable material resources in this workspace. presets
|
|
119
|
+
description: 'Manage optional immutable material resources in this workspace. presets lists shipped recipes; create-preset uses explicit seed, creator and license; list returns revision summaries; get returns provenance, hashes, sampling and a portable material spec. create-procedural accepts legacy layer drafts or schemaVersion 2 drafts with explicit samplers and bounded RGBA or named atlas recipes. Atlas resource sources require exact saved revisions; import requires complete normalized records and dependencies with embedded PNG bytes. Read shape:material-draft or shape:material-import through kiln_discover for full fields. No action downloads URLs or executes source. Pin the returned materialId/revisionId through per-invocation materialDependencies or project dependencies before evaluation; no project or mesh is required.',
|
|
119
120
|
inputSchema: materialToolInput,
|
|
120
121
|
actionContract: MATERIAL_CONTRACT,
|
|
121
122
|
annotations: {
|
|
@@ -177,12 +178,12 @@ export function createKilnMaterialDef(library, presentation = 'grouped') {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
case 'create-procedural': {
|
|
180
|
-
const record = await
|
|
181
|
-
const [material] = await library.import([record]);
|
|
181
|
+
const { record, dependencies } = await createProceduralMaterialRecord(input.draft, library);
|
|
182
|
+
const [material] = await library.import([record], dependencies.length ? { materials: dependencies } : undefined);
|
|
182
183
|
return materialResult(material);
|
|
183
184
|
}
|
|
184
185
|
case 'import': {
|
|
185
|
-
const records = await decodeMaterialLibraryPayload(
|
|
186
|
+
const records = await decodeMaterialLibraryPayload(materialLibraryPayloadAnySchema.parse(input.payload));
|
|
186
187
|
const materials = await library.import(records);
|
|
187
188
|
return {
|
|
188
189
|
ok: true,
|
package/lib/tools/registry.js
CHANGED
|
@@ -135,27 +135,16 @@ async function sha256Glb(bytes) {
|
|
|
135
135
|
async function renderDerivativeCell(input, context) {
|
|
136
136
|
let derivativeRoot = input.root;
|
|
137
137
|
if (input.backfaceCull === false) {
|
|
138
|
-
|
|
139
|
-
derivativeRoot
|
|
140
|
-
const mesh = node;
|
|
141
|
-
if (!mesh.isMesh)
|
|
142
|
-
return;
|
|
143
|
-
const prepare = (material) => {
|
|
144
|
-
const copy = material.clone();
|
|
145
|
-
copy.side = THREE.DoubleSide;
|
|
146
|
-
return copy;
|
|
147
|
-
};
|
|
148
|
-
mesh.material = Array.isArray(mesh.material)
|
|
149
|
-
? mesh.material.map(prepare)
|
|
150
|
-
: prepare(mesh.material);
|
|
151
|
-
});
|
|
138
|
+
const { cloneReviewScene } = await import("../views/scene-copy.js");
|
|
139
|
+
derivativeRoot = cloneReviewScene(derivativeRoot, { omitHidden: true, doubleSided: true });
|
|
152
140
|
}
|
|
153
141
|
// `withCameraVisibility` and `isolateSubtree` express isolation by clearing
|
|
154
142
|
// `mesh.visible`. The CPU rasterizer culls on that flag (see views/raster.ts),
|
|
155
143
|
// but glTF carries no per-mesh visibility, so serializing unfiltered ships the
|
|
156
144
|
// hidden geometry to the GPU service and makes isolate a silent no-op on exactly
|
|
157
145
|
// the material-faithful path it is most useful on. Prune on a copy: the caller
|
|
158
|
-
// restores `.visible` afterwards and must not observe a mutated scene.
|
|
146
|
+
// restores `.visible` afterwards and must not observe a mutated scene. Required
|
|
147
|
+
// joint ancestors survive as transform-only nodes in the derivative.
|
|
159
148
|
const { withoutHiddenMeshes } = await import("../views/camera.js");
|
|
160
149
|
derivativeRoot = withoutHiddenMeshes(derivativeRoot);
|
|
161
150
|
const rendered = await renderSceneToGLB(derivativeRoot, {
|
|
@@ -1860,7 +1849,7 @@ export async function buildProgramAssetDraft(code, context, backdrop) {
|
|
|
1860
1849
|
const dependencies = [
|
|
1861
1850
|
...(rendered.materialResourceProvenance ?? []),
|
|
1862
1851
|
...(rendered.materialLibraryDependencies ?? []).map((manifest) => ({
|
|
1863
|
-
kind:
|
|
1852
|
+
kind: `kiln.material.v${manifest.schemaVersion}`,
|
|
1864
1853
|
delivery: 'runtime',
|
|
1865
1854
|
manifest,
|
|
1866
1855
|
})),
|
package/lib/tools/review.js
CHANGED
|
@@ -177,7 +177,7 @@ export function createKilnReviewDef(context) {
|
|
|
177
177
|
const dependencies = [
|
|
178
178
|
...(evaluation.materialResourceProvenance ?? []),
|
|
179
179
|
...(evaluation.materialLibraryDependencies ?? []).map((manifest) => ({
|
|
180
|
-
kind:
|
|
180
|
+
kind: `kiln.material.v${manifest.schemaVersion}`,
|
|
181
181
|
delivery: 'runtime',
|
|
182
182
|
manifest,
|
|
183
183
|
})),
|
package/lib/uv-data.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Internal safety and correspondence boundary for the xatlasjs adapter. */
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
type Attribute = THREE.BufferAttribute | THREE.InterleavedBufferAttribute;
|
|
4
|
+
export interface AtlasMesh {
|
|
5
|
+
vertexCount: number;
|
|
6
|
+
vertex: {
|
|
7
|
+
coords1?: Float32Array | number[];
|
|
8
|
+
};
|
|
9
|
+
index?: Uint16Array | Uint32Array | number[];
|
|
10
|
+
oldIndexes: Uint16Array | Uint32Array | number[];
|
|
11
|
+
}
|
|
12
|
+
/** Reject truncated Uint16 output and reordered faces before copying protected data. */
|
|
13
|
+
export declare function validateAtlasRemap(mesh: AtlasMesh, inputIndex: Uint32Array, sourceCount: number): {
|
|
14
|
+
index: number[] | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike>;
|
|
15
|
+
uv: number[] | Float32Array<ArrayBufferLike>;
|
|
16
|
+
oldIndexes: number[] | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike>;
|
|
17
|
+
};
|
|
18
|
+
/** Copy raw storage, including normalized integers and half floats, without welding. */
|
|
19
|
+
export declare function remapUnwrapAttribute(attribute: Attribute, oldIndexes: ArrayLike<number>): THREE.BufferAttribute;
|
|
20
|
+
export declare function prepareUnwrap(geometry: THREE.BufferGeometry, opts: {
|
|
21
|
+
resolution?: number;
|
|
22
|
+
padding?: number;
|
|
23
|
+
useNormals?: boolean;
|
|
24
|
+
}): {
|
|
25
|
+
index: Uint32Array<ArrayBuffer>;
|
|
26
|
+
positions: Float32Array<ArrayBufferLike>;
|
|
27
|
+
normals: Float32Array<ArrayBufferLike> | null;
|
|
28
|
+
useNormals: boolean;
|
|
29
|
+
resolution: number;
|
|
30
|
+
padding: number;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
package/lib/uv-data.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** Internal safety and correspondence boundary for the xatlasjs adapter. */
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { GEOMETRY_ALLOCATION_LIMITS as limits } from "./geometry-budget.js";
|
|
4
|
+
/** Reject truncated Uint16 output and reordered faces before copying protected data. */
|
|
5
|
+
export function validateAtlasRemap(mesh, inputIndex, sourceCount) {
|
|
6
|
+
const count = mesh.vertexCount;
|
|
7
|
+
if (!Number.isSafeInteger(count) || count < 1 || count > limits.unwrapVertices)
|
|
8
|
+
throw new RangeError(`autoUnwrap: xatlas output exceeds the supported 1..${limits.unwrapVertices} vertices. Split the mesh before unwrapping.`);
|
|
9
|
+
if (mesh.oldIndexes.length !== count)
|
|
10
|
+
throw new Error('autoUnwrap: xatlas returned an incomplete source-vertex map.');
|
|
11
|
+
for (const oldIndex of mesh.oldIndexes)
|
|
12
|
+
if (!Number.isSafeInteger(oldIndex) || oldIndex < 0 || oldIndex >= sourceCount)
|
|
13
|
+
throw new Error('autoUnwrap: xatlas returned an invalid source-vertex map.');
|
|
14
|
+
const index = mesh.index;
|
|
15
|
+
if (!index || index.length !== inputIndex.length)
|
|
16
|
+
throw new Error('autoUnwrap: xatlas changed the triangle count; face correspondence is unsupported.');
|
|
17
|
+
for (let i = 0; i < index.length; i++) {
|
|
18
|
+
const vertex = index[i];
|
|
19
|
+
if (!Number.isSafeInteger(vertex) ||
|
|
20
|
+
vertex < 0 ||
|
|
21
|
+
vertex >= count ||
|
|
22
|
+
mesh.oldIndexes[vertex] !== inputIndex[i])
|
|
23
|
+
throw new Error('autoUnwrap: xatlas changed triangle correspondence; refusing to misassign vertex data or material groups.');
|
|
24
|
+
}
|
|
25
|
+
const uv = mesh.vertex.coords1;
|
|
26
|
+
if (!uv || uv.length !== count * 2 || !uv.every((v) => Number.isFinite(v) && v >= 0 && v <= 1))
|
|
27
|
+
throw new Error('autoUnwrap: xatlas produced no valid UV coordinates (degenerate or zero-area triangles?).');
|
|
28
|
+
return { index, uv, oldIndexes: mesh.oldIndexes };
|
|
29
|
+
}
|
|
30
|
+
function interleaved(attribute) {
|
|
31
|
+
return attribute.isInterleavedBufferAttribute === true;
|
|
32
|
+
}
|
|
33
|
+
/** Copy raw storage, including normalized integers and half floats, without welding. */
|
|
34
|
+
export function remapUnwrapAttribute(attribute, oldIndexes) {
|
|
35
|
+
const ArrayType = attribute.array.constructor;
|
|
36
|
+
const array = new ArrayType(oldIndexes.length * attribute.itemSize);
|
|
37
|
+
const stride = interleaved(attribute) ? attribute.data.stride : attribute.itemSize;
|
|
38
|
+
const offset = interleaved(attribute) ? attribute.offset : 0;
|
|
39
|
+
for (let i = 0; i < oldIndexes.length; i++) {
|
|
40
|
+
for (let c = 0; c < attribute.itemSize; c++)
|
|
41
|
+
array[i * attribute.itemSize + c] = attribute.array[oldIndexes[i] * stride + offset + c];
|
|
42
|
+
}
|
|
43
|
+
const output = attribute
|
|
44
|
+
.isFloat16BufferAttribute
|
|
45
|
+
? new THREE.Float16BufferAttribute(array, attribute.itemSize, attribute.normalized)
|
|
46
|
+
: new THREE.BufferAttribute(array, attribute.itemSize, attribute.normalized);
|
|
47
|
+
output.name = attribute.name;
|
|
48
|
+
output.setUsage(interleaved(attribute) ? attribute.data.usage : attribute.usage);
|
|
49
|
+
if (!interleaved(attribute))
|
|
50
|
+
output.gpuType = attribute.gpuType;
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
function validateAttribute(name, attribute, count) {
|
|
54
|
+
if (attribute.isInstancedBufferAttribute ||
|
|
55
|
+
(interleaved(attribute) &&
|
|
56
|
+
attribute.data
|
|
57
|
+
.isInstancedInterleavedBuffer))
|
|
58
|
+
throw new TypeError(`autoUnwrap: ${name} is per-instance data; unwrap the base geometry before instancing.`);
|
|
59
|
+
if (attribute.count !== count ||
|
|
60
|
+
!Number.isSafeInteger(attribute.itemSize) ||
|
|
61
|
+
attribute.itemSize < 1 ||
|
|
62
|
+
attribute.itemSize > 4)
|
|
63
|
+
throw new TypeError(`autoUnwrap: ${name} must be a matching per-vertex attribute with 1..4 components.`);
|
|
64
|
+
if (interleaved(attribute) &&
|
|
65
|
+
(!Number.isSafeInteger(attribute.offset) ||
|
|
66
|
+
attribute.offset < 0 ||
|
|
67
|
+
attribute.offset + attribute.itemSize > attribute.data.stride))
|
|
68
|
+
throw new TypeError(`autoUnwrap: ${name} has an invalid interleaved offset or stride.`);
|
|
69
|
+
}
|
|
70
|
+
/** Decode xyz through getters: .array alone ignores stride, normalization and half floats. */
|
|
71
|
+
function xyz(attribute, name) {
|
|
72
|
+
if (attribute.itemSize !== 3)
|
|
73
|
+
throw new TypeError(`autoUnwrap: ${name} requires three components.`);
|
|
74
|
+
const output = new Float32Array(attribute.count * 3);
|
|
75
|
+
for (let i = 0; i < attribute.count; i++) {
|
|
76
|
+
output[i * 3] = attribute.getX(i);
|
|
77
|
+
output[i * 3 + 1] = attribute.getY(i);
|
|
78
|
+
output[i * 3 + 2] = attribute.getZ(i);
|
|
79
|
+
}
|
|
80
|
+
if (!output.every(Number.isFinite))
|
|
81
|
+
throw new TypeError(`autoUnwrap: ${name} must contain finite Float32-representable values.`);
|
|
82
|
+
return output;
|
|
83
|
+
}
|
|
84
|
+
export function prepareUnwrap(geometry, opts) {
|
|
85
|
+
const resolution = opts.resolution ?? 1024;
|
|
86
|
+
const padding = opts.padding ?? 2;
|
|
87
|
+
if (!Number.isSafeInteger(resolution) || resolution < 1 || resolution > limits.unwrapResolution)
|
|
88
|
+
throw new RangeError(`autoUnwrap: resolution must be an integer from 1 to ${limits.unwrapResolution}.`);
|
|
89
|
+
if (!Number.isSafeInteger(padding) || padding < 0 || padding > resolution)
|
|
90
|
+
throw new RangeError('autoUnwrap: padding must be an integer from 0 to resolution.');
|
|
91
|
+
if (opts.useNormals !== undefined && typeof opts.useNormals !== 'boolean')
|
|
92
|
+
throw new TypeError('autoUnwrap: useNormals must be a boolean.');
|
|
93
|
+
if (geometry.isInstancedBufferGeometry)
|
|
94
|
+
throw new TypeError('autoUnwrap: unwrap the base geometry before instancing.');
|
|
95
|
+
const position = geometry.getAttribute('position');
|
|
96
|
+
if (position?.itemSize !== 3)
|
|
97
|
+
throw new TypeError('autoUnwrap: geometry requires a three-component position attribute.');
|
|
98
|
+
const count = position.count;
|
|
99
|
+
const corners = geometry.index?.count ?? count;
|
|
100
|
+
if (count === 0 || corners === 0)
|
|
101
|
+
throw new Error('autoUnwrap: geometry has no triangles. Check the operation that produced it (for example an empty CSG result).');
|
|
102
|
+
if (!Number.isSafeInteger(count) || count < 1 || count > limits.unwrapVertices)
|
|
103
|
+
throw new RangeError(`autoUnwrap: input exceeds ${limits.unwrapVertices} vertices supported by xatlasjs. Split the mesh before unwrapping.`);
|
|
104
|
+
if (!Number.isSafeInteger(corners) || corners % 3 !== 0)
|
|
105
|
+
throw new TypeError('autoUnwrap: geometry has an index count that is not a whole number of triangles.');
|
|
106
|
+
if (corners / 3 > limits.unwrapTriangles)
|
|
107
|
+
throw new RangeError(`autoUnwrap: triangle budget exceeds ${limits.unwrapTriangles}.`);
|
|
108
|
+
const attributes = Object.entries(geometry.attributes);
|
|
109
|
+
for (const [name, attribute] of attributes)
|
|
110
|
+
validateAttribute(name, attribute, count);
|
|
111
|
+
const normal = geometry.getAttribute('normal');
|
|
112
|
+
if (normal && normal.itemSize !== 3)
|
|
113
|
+
throw new TypeError('autoUnwrap: normal requires three components.');
|
|
114
|
+
const useNormals = !!opts.useNormals && !!normal;
|
|
115
|
+
for (const [name, targets] of Object.entries(geometry.morphAttributes)) {
|
|
116
|
+
if (!['position', 'normal', 'color'].includes(name))
|
|
117
|
+
throw new TypeError(`autoUnwrap: morph ${name} is unsupported; UV0 and tangents change during unwrapping.`);
|
|
118
|
+
if (name === 'normal' && targets.length && !useNormals)
|
|
119
|
+
throw new TypeError('autoUnwrap: normal morphs require base normals and useNormals: true to preserve their reference frame.');
|
|
120
|
+
for (const attribute of targets) {
|
|
121
|
+
validateAttribute(`morph ${name}`, attribute, count);
|
|
122
|
+
const base = geometry.getAttribute(name);
|
|
123
|
+
if (!base || attribute.itemSize !== base.itemSize)
|
|
124
|
+
throw new TypeError(`autoUnwrap: morph ${name} requires a matching base attribute.`);
|
|
125
|
+
attributes.push([`morph ${name}`, attribute]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Bound owned output buffers before reading attributes or entering native code.
|
|
129
|
+
const perVertexBytes = attributes.reduce((sum, [, attr]) => sum + attr.itemSize * attr.array.BYTES_PER_ELEMENT, 20);
|
|
130
|
+
if (perVertexBytes * Math.min(count + corners, limits.unwrapVertices) >
|
|
131
|
+
limits.unwrapAttributeBytes)
|
|
132
|
+
throw new RangeError(`autoUnwrap: attribute budget exceeds ${limits.unwrapAttributeBytes} bytes.`);
|
|
133
|
+
for (const [name, attribute] of attributes) {
|
|
134
|
+
const getters = [attribute.getX, attribute.getY, attribute.getZ, attribute.getW];
|
|
135
|
+
for (let i = 0; i < count; i++) {
|
|
136
|
+
for (let c = 0; c < attribute.itemSize; c++) {
|
|
137
|
+
const value = getters[c].call(attribute, i);
|
|
138
|
+
if (!Number.isFinite(value))
|
|
139
|
+
throw new TypeError(`autoUnwrap: ${name} contains non-finite values.`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (geometry.index && (geometry.index.itemSize !== 1 || geometry.index.normalized))
|
|
144
|
+
throw new TypeError('autoUnwrap: index must be a scalar, non-normalized integer attribute.');
|
|
145
|
+
const index = new Uint32Array(corners);
|
|
146
|
+
for (let i = 0; i < corners; i++) {
|
|
147
|
+
const value = geometry.index ? geometry.index.getX(i) : i;
|
|
148
|
+
if (!Number.isSafeInteger(value) || value < 0 || value >= count)
|
|
149
|
+
throw new RangeError('autoUnwrap: index references an invalid source vertex.');
|
|
150
|
+
index[i] = value;
|
|
151
|
+
}
|
|
152
|
+
for (const { start, count: length, materialIndex = 0 } of geometry.groups) {
|
|
153
|
+
if (!Number.isSafeInteger(start) ||
|
|
154
|
+
!Number.isSafeInteger(length) ||
|
|
155
|
+
start < 0 ||
|
|
156
|
+
length < 0 ||
|
|
157
|
+
start % 3 ||
|
|
158
|
+
length % 3 ||
|
|
159
|
+
start + length > corners ||
|
|
160
|
+
!Number.isSafeInteger(materialIndex) ||
|
|
161
|
+
materialIndex < 0)
|
|
162
|
+
throw new TypeError('autoUnwrap: groups must reference complete source triangles and nonnegative material indices.');
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
index,
|
|
166
|
+
positions: xyz(position, 'position'),
|
|
167
|
+
normals: useNormals ? xyz(normal, 'normal') : null,
|
|
168
|
+
useNormals,
|
|
169
|
+
resolution,
|
|
170
|
+
padding,
|
|
171
|
+
};
|
|
172
|
+
}
|
package/lib/uv-shapes.js
CHANGED
|
@@ -32,6 +32,9 @@ export function remapUV(geo, options = {}) {
|
|
|
32
32
|
values[i * 2 + 1] = nextV;
|
|
33
33
|
}
|
|
34
34
|
const cloned = geo.clone();
|
|
35
|
+
// BufferGeometry.clone() shares userData; owned UV results must not write
|
|
36
|
+
// warnings or later atlas bindings back into their source geometry.
|
|
37
|
+
cloned.userData = structuredClone(geo.userData);
|
|
35
38
|
cloned.setAttribute('uv', new THREE.Float32BufferAttribute(values, 2));
|
|
36
39
|
if ((scale[0] !== 1 || scale[1] !== 1) && cloned.hasAttribute('tangent')) {
|
|
37
40
|
cloned.deleteAttribute('tangent');
|
package/lib/uv.d.ts
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as THREE from 'three';
|
|
17
17
|
export interface AutoUnwrapOptions {
|
|
18
|
-
/**
|
|
18
|
+
/** Target atlas resolution, integer 1..4096. Actual packed dimensions may differ. Default 1024. */
|
|
19
19
|
resolution?: number;
|
|
20
20
|
/** Padding between charts in texels. Default 2. */
|
|
21
21
|
padding?: number;
|
|
22
|
-
/**
|
|
22
|
+
/** Use and preserve source normals; otherwise recompute. Required for normal morphs. Default false. */
|
|
23
23
|
useNormals?: boolean;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
@@ -29,7 +29,13 @@ export interface AutoUnwrapOptions {
|
|
|
29
29
|
* sampling, and (usually) a different vertex count than the input: xatlas
|
|
30
30
|
* duplicates vertices along UV seams so each shell gets its own chart.
|
|
31
31
|
*
|
|
32
|
-
* Returns a *new* BufferGeometry; the input is left untouched.
|
|
32
|
+
* Returns a *new* BufferGeometry; the input is left untouched. Seam remapping
|
|
33
|
+
* preserves per-vertex attributes, position/normal/color morphs, groups and JSON
|
|
34
|
+
* userData. Replaces UV0 and removes stale tangents. Normal behavior is unchanged:
|
|
35
|
+
* recompute by default, preserve source normals with useNormals: true.
|
|
36
|
+
* The current xatlasjs adapter supports at most 65,536 input AND output vertices;
|
|
37
|
+
* split larger meshes. Instancing, multi-page atlases and changed face order are
|
|
38
|
+
* rejected rather than returning corrupt attribute correspondence.
|
|
33
39
|
*
|
|
34
40
|
* @example
|
|
35
41
|
* const raw = boxGeo(1, 2, 1);
|
package/lib/uv.js
CHANGED
|
@@ -22,6 +22,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
22
22
|
return path;
|
|
23
23
|
};
|
|
24
24
|
import * as THREE from 'three';
|
|
25
|
+
import { prepareUnwrap, remapUnwrapAttribute, validateAtlasRemap } from "./uv-data.js";
|
|
25
26
|
// Lazy xatlas init — pays ~100-200ms on first call, then free.
|
|
26
27
|
let _xatlasReady = null;
|
|
27
28
|
async function getXatlas() {
|
|
@@ -48,7 +49,13 @@ async function getXatlas() {
|
|
|
48
49
|
* sampling, and (usually) a different vertex count than the input: xatlas
|
|
49
50
|
* duplicates vertices along UV seams so each shell gets its own chart.
|
|
50
51
|
*
|
|
51
|
-
* Returns a *new* BufferGeometry; the input is left untouched.
|
|
52
|
+
* Returns a *new* BufferGeometry; the input is left untouched. Seam remapping
|
|
53
|
+
* preserves per-vertex attributes, position/normal/color morphs, groups and JSON
|
|
54
|
+
* userData. Replaces UV0 and removes stale tangents. Normal behavior is unchanged:
|
|
55
|
+
* recompute by default, preserve source normals with useNormals: true.
|
|
56
|
+
* The current xatlasjs adapter supports at most 65,536 input AND output vertices;
|
|
57
|
+
* split larger meshes. Instancing, multi-page atlases and changed face order are
|
|
58
|
+
* rejected rather than returning corrupt attribute correspondence.
|
|
52
59
|
*
|
|
53
60
|
* @example
|
|
54
61
|
* const raw = boxGeo(1, 2, 1);
|
|
@@ -56,92 +63,56 @@ async function getXatlas() {
|
|
|
56
63
|
* const crate = new THREE.Mesh(unwrapped, woodPBR);
|
|
57
64
|
*/
|
|
58
65
|
export async function autoUnwrap(geometry, opts = {}) {
|
|
66
|
+
const prepared = prepareUnwrap(geometry, opts);
|
|
59
67
|
const xa = await getXatlas();
|
|
60
|
-
// xatlas needs an indexed BufferGeometry. Convert if necessary.
|
|
61
|
-
const src = geometry.index ? geometry : toIndexed(geometry);
|
|
62
|
-
const posAttr = src.getAttribute('position');
|
|
63
|
-
const normAttr = src.getAttribute('normal');
|
|
64
|
-
const idxAttr = src.getIndex();
|
|
65
|
-
if (!posAttr || !idxAttr) {
|
|
66
|
-
throw new Error('autoUnwrap: geometry requires position attribute and an index');
|
|
67
|
-
}
|
|
68
|
-
// T2.4: measured — xatlas accepts an empty mesh and returns an empty atlas
|
|
69
|
-
// without complaint, so a zero-triangle geometry used to come back with zero
|
|
70
|
-
// vertices and NO uv attribute, and the failure only surfaced later as a
|
|
71
|
-
// texture that would not appear. The realistic upstream cause is a CSG result
|
|
72
|
-
// that collapsed to nothing.
|
|
73
|
-
if (idxAttr.count === 0 || posAttr.count === 0) {
|
|
74
|
-
throw new Error('autoUnwrap: geometry has no triangles. Nothing can be unwrapped — check the operation that produced it (an empty CSG result is the usual cause).');
|
|
75
|
-
}
|
|
76
|
-
if (idxAttr.count % 3 !== 0) {
|
|
77
|
-
throw new Error(`autoUnwrap: geometry has ${idxAttr.count} indices, which is not a whole number of triangles.`);
|
|
78
|
-
}
|
|
79
68
|
xa.createAtlas();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
69
|
+
try {
|
|
70
|
+
const added = xa.addMesh(prepared.index, prepared.positions, prepared.normals, null, 'kiln-mesh', prepared.useNormals, false, 1);
|
|
71
|
+
if (!added)
|
|
72
|
+
throw new Error('autoUnwrap: xatlas.addMesh failed (non-manifold or degenerate geometry?).');
|
|
73
|
+
const result = xa.generateAtlas({}, { resolution: prepared.resolution, padding: prepared.padding }, true);
|
|
74
|
+
const mesh = result.meshes[0];
|
|
75
|
+
if (!mesh || result.meshCount !== 1)
|
|
76
|
+
throw new Error('autoUnwrap: xatlas returned no single mesh.');
|
|
77
|
+
if (result.atlasCount !== 1 ||
|
|
78
|
+
!Number.isSafeInteger(result.width) ||
|
|
79
|
+
result.width <= 0 ||
|
|
80
|
+
!Number.isSafeInteger(result.height) ||
|
|
81
|
+
result.height <= 0)
|
|
82
|
+
throw new Error('autoUnwrap: xatlas must return one valid atlas; multiple atlas pages are unsupported.');
|
|
83
|
+
const remap = validateAtlasRemap(mesh, prepared.index, geometry.getAttribute('position').count);
|
|
84
|
+
const out = new THREE.BufferGeometry();
|
|
85
|
+
for (const [name, attribute] of Object.entries(geometry.attributes)) {
|
|
86
|
+
if (name === 'uv' || name === 'tangent' || (name === 'normal' && !prepared.useNormals))
|
|
87
|
+
continue;
|
|
88
|
+
out.setAttribute(name, remapUnwrapAttribute(attribute, remap.oldIndexes));
|
|
89
|
+
}
|
|
90
|
+
for (const [name, targets] of Object.entries(geometry.morphAttributes)) {
|
|
91
|
+
// Three's declaration lists standard keys, although its runtime uses a dictionary.
|
|
92
|
+
out.morphAttributes[name] = targets.map((attribute) => remapUnwrapAttribute(attribute, remap.oldIndexes));
|
|
93
|
+
}
|
|
94
|
+
out.morphTargetsRelative = geometry.morphTargetsRelative;
|
|
95
|
+
out.setIndex(new THREE.BufferAttribute(new Uint32Array(remap.index), 1));
|
|
96
|
+
out.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(remap.uv), 2));
|
|
97
|
+
out.name = geometry.name;
|
|
98
|
+
// Triangle correspondence was checked before retaining material groups and draw range.
|
|
99
|
+
out.groups = geometry.groups.map((group) => ({ ...group }));
|
|
100
|
+
out.setDrawRange(geometry.drawRange.start, geometry.drawRange.count);
|
|
101
|
+
out.userData = JSON.parse(JSON.stringify(geometry.userData));
|
|
102
|
+
// xatlas's requested resolution is not the returned width/height (often rectangular).
|
|
103
|
+
out.userData['atlas'] = {
|
|
104
|
+
width: result.width,
|
|
105
|
+
height: result.height,
|
|
106
|
+
atlasCount: result.atlasCount,
|
|
107
|
+
};
|
|
108
|
+
if (!out.getAttribute('normal'))
|
|
109
|
+
out.computeVertexNormals();
|
|
110
|
+
repairZeroNormals(out);
|
|
111
|
+
return out;
|
|
105
112
|
}
|
|
106
|
-
|
|
107
|
-
// projection painting) can sample the atlas resolution.
|
|
108
|
-
//
|
|
109
|
-
// T2.4 — read `width`/`height`, do NOT assume `resolution` squared. `resolution`
|
|
110
|
-
// is an upper bound xatlas packs within, not the atlas it returns: a CSG'd box
|
|
111
|
-
// at resolution 1024 measured 917x1085. A baker that allocated a square
|
|
112
|
-
// `resolution x resolution` target would sample the wrong texels along one
|
|
113
|
-
// axis, which reads as a subtle uniform stretch rather than an obvious break.
|
|
114
|
-
out.userData['atlas'] = {
|
|
115
|
-
width: result.width,
|
|
116
|
-
height: result.height,
|
|
117
|
-
atlasCount: result.atlasCount,
|
|
118
|
-
};
|
|
119
|
-
if (!out.getAttribute('normal'))
|
|
120
|
-
out.computeVertexNormals();
|
|
121
|
-
repairZeroNormals(out);
|
|
122
|
-
// xatlas can pack every chart and still hand back a mesh with no coords1 when
|
|
123
|
-
// the input degenerates. Better to say so than to return a geometry whose
|
|
124
|
-
// missing `uv` only shows up as an untextured surface much later.
|
|
125
|
-
if (!out.getAttribute('uv')) {
|
|
113
|
+
finally {
|
|
126
114
|
xa.destroyAtlas();
|
|
127
|
-
throw new Error('autoUnwrap: xatlas produced no UV coordinates for this geometry (degenerate or zero-area triangles?).');
|
|
128
115
|
}
|
|
129
|
-
xa.destroyAtlas();
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Convert a non-indexed BufferGeometry to an indexed one via naive
|
|
134
|
-
* sequential indexing. Sufficient for xatlas input (it re-indexes anyway
|
|
135
|
-
* during chart generation).
|
|
136
|
-
*/
|
|
137
|
-
function toIndexed(geo) {
|
|
138
|
-
const posAttr = geo.getAttribute('position');
|
|
139
|
-
const indices = new Uint32Array(posAttr.count);
|
|
140
|
-
for (let i = 0; i < posAttr.count; i++)
|
|
141
|
-
indices[i] = i;
|
|
142
|
-
const out = geo.clone();
|
|
143
|
-
out.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
144
|
-
return out;
|
|
145
116
|
}
|
|
146
117
|
/**
|
|
147
118
|
* Replace any zero-length or non-finite normal with one derived from the faces
|
package/lib/views/camera.d.ts
CHANGED
|
@@ -97,9 +97,9 @@ export declare function withCameraVisibility<T>(root: unknown, shot: ResolvedCam
|
|
|
97
97
|
* A derivative GLB
|
|
98
98
|
* serialized from a scene with hidden nodes (authored, or isolated by
|
|
99
99
|
* {@link withCameraVisibility}) would ship them to the GPU. Every node with
|
|
100
|
-
* `visible = false`
|
|
101
|
-
*
|
|
102
|
-
* restores `.visible` itself.
|
|
100
|
+
* `visible = false` removes drawing from its subtree. Required skeleton ancestors remain
|
|
101
|
+
* as transforms without drawing payloads. Returns the root itself when nothing is hidden,
|
|
102
|
+
* else an independent pruned hierarchy/skeleton; the caller restores `.visible` itself.
|
|
103
103
|
*/
|
|
104
104
|
export declare function withoutHiddenMeshes(root: Object3D): Object3D;
|
|
105
105
|
/** Flat geometry renderer with exact orthographic/perspective projection and near/far clipping. */
|