@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/lib/primitives.js
CHANGED
|
@@ -12,7 +12,7 @@ import * as THREE from 'three';
|
|
|
12
12
|
import { AuthoringDiagnosticError } from "./evaluator/authoring-diagnostic.js";
|
|
13
13
|
import { assertDimension, assertPrimitiveSegments, assertPrimitiveGrid, assertFiniteTriple, GEOMETRY_ALLOCATION_LIMITS, } from "./geometry-budget.js";
|
|
14
14
|
import { buildWallPanels } from "./wall-panels.js";
|
|
15
|
-
import { createJointChain } from "./character.js";
|
|
15
|
+
import { createBoneChain, createJointChain, createSkinnedPart } from "./character.js";
|
|
16
16
|
import { describeAssembly, replicateAssembly } from "./assembly.js";
|
|
17
17
|
import { defineLod } from "./lod.js";
|
|
18
18
|
export { defineLod } from "./lod.js";
|
|
@@ -37,7 +37,8 @@ import * as profile from "./profile.js";
|
|
|
37
37
|
import * as solids from "./solids.js";
|
|
38
38
|
import * as proceduralTextures from "./procedural-texture.js";
|
|
39
39
|
import * as textures from "./textures.js";
|
|
40
|
-
import {
|
|
40
|
+
import { textureAtlas, remapUVToAtlas } from "./texture-atlas.js";
|
|
41
|
+
import { compilePortableMaterialSpecV2, compilePortableMaterialSpecV3, } from "./portable-material-runtime.js";
|
|
41
42
|
import { createVehicleFrame, createWheelAssembly, createWheelGeometrySet } from "./vehicle.js";
|
|
42
43
|
import { stampSemanticMetadataV1, } from "./contracts/index.js";
|
|
43
44
|
import * as uv from "./uv.js";
|
|
@@ -45,11 +46,17 @@ import * as uvShapes from "./uv-shapes.js";
|
|
|
45
46
|
import { projectUV } from "./uv-project.js";
|
|
46
47
|
export { projectUV } from "./uv-project.js";
|
|
47
48
|
export { remapUV } from "./uv-shapes.js";
|
|
49
|
+
export { textureAtlas, remapUVToAtlas } from "./texture-atlas.js";
|
|
50
|
+
export { TextureAtlasError, TEXTURE_ATLAS_COMPILER_ID, TEXTURE_ATLAS_LIMITS, canonicalizeTextureAtlasSpecV1, canonicalTextureAtlasJsonV1, hashTextureAtlasSpecV1, } from "./texture-atlas-contract.js";
|
|
51
|
+
export { compileTextureAtlasSpecV1 } from "./texture-atlas-pixels.js";
|
|
48
52
|
export { createGableEndPanel, createGableRoof, createGableShell, createRoofSurfaceLayout, } from "./architecture.js";
|
|
49
|
-
export { createJointChain } from "./character.js";
|
|
53
|
+
export { createBoneChain, createJointChain, createSkinnedPart } from "./character.js";
|
|
50
54
|
export { createVehicleFrame, createWheelAssembly, createWheelGeometrySet, } from "./vehicle.js";
|
|
51
55
|
export { materialRecipe } from "./material-recipe-runtime.js";
|
|
52
|
-
export { compilePortableMaterialSpecV2 } from "./portable-material-runtime.js";
|
|
56
|
+
export { compilePortableMaterialSpecV2, compilePortableMaterialSpecV3, } from "./portable-material-runtime.js";
|
|
57
|
+
export { canonicalizePortableMaterialSpecV3, PortableMaterialV3Error, PORTABLE_MATERIAL_V3_LIMITS, } from "./procedural-material-v3.js";
|
|
58
|
+
export { canonicalizePortableSamplerV1 } from "./portable-sampler.js";
|
|
59
|
+
export { compileProceduralTextureSpecV3, canonicalizeProceduralTextureSpecV3, canonicalProceduralTextureJsonV3, hashProceduralTextureSpecV3, PROCEDURAL_TEXTURE_V3_COMPILER_ID, PROCEDURAL_TEXTURE_SPEC_VERSION_V3, MAX_PROCEDURAL_V3_EDGE, MAX_PROCEDURAL_V3_TEXELS, MAX_PROCEDURAL_V3_OPERATIONS, MAX_PROCEDURAL_V3_VERTICES, MAX_PROCEDURAL_V3_STAMPS, MAX_PROCEDURAL_V3_PALETTE, MAX_PROCEDURAL_V3_WORK, MAX_PROCEDURAL_V3_TRANSIENT_BYTES, } from "./procedural-texture-v3.js";
|
|
53
60
|
export { MAX_PORTABLE_MATERIAL_TEXELS, MAX_PORTABLE_MATERIAL_TEXTURES, MAX_PROCEDURAL_LAYERS, MAX_PROCEDURAL_SIZE, PORTABLE_MATERIAL_SPEC_VERSION, PROCEDURAL_TEXTURE_SPEC_VERSION, ProceduralTextureError, canonicalProceduralTextureJsonV2, canonicalizePortableMaterialSpecV2, canonicalizeProceduralTextureSpecV2, compileProceduralTextureSpecV2, hashProceduralTextureSpecV2, migrateProceduralTextureSpecV1, } from "./procedural-texture.js";
|
|
54
61
|
function vectorToTuple(v) {
|
|
55
62
|
return [v.x, v.y, v.z];
|
|
@@ -1429,6 +1436,8 @@ export function buildSandboxGlobals(usage, options = {}) {
|
|
|
1429
1436
|
replicateAssembly: wrap('replicateAssembly', replicateAssembly),
|
|
1430
1437
|
createPivot: wrap('createPivot', createPivot),
|
|
1431
1438
|
createJointChain: wrap('createJointChain', createJointChain),
|
|
1439
|
+
createBoneChain: wrap('createBoneChain', createBoneChain),
|
|
1440
|
+
createSkinnedPart: wrap('createSkinnedPart', createSkinnedPart),
|
|
1432
1441
|
createVehicleFrame: wrap('createVehicleFrame', createVehicleFrame),
|
|
1433
1442
|
createWheelGeometrySet: wrap('createWheelGeometrySet', createWheelGeometrySet),
|
|
1434
1443
|
createWheelAssembly: wrap('createWheelAssembly', createWheelAssembly),
|
|
@@ -1477,6 +1486,11 @@ export function buildSandboxGlobals(usage, options = {}) {
|
|
|
1477
1486
|
return Promise.reject(new TypeError('compilePortableMaterialSpecV2 accepts exactly one material spec'));
|
|
1478
1487
|
return compilePortableMaterialSpecV2(args[0], { resolver: options.textureResolver });
|
|
1479
1488
|
}),
|
|
1489
|
+
compilePortableMaterialSpecV3: wrap('compilePortableMaterialSpecV3', (...args) => {
|
|
1490
|
+
if (args.length !== 1)
|
|
1491
|
+
return Promise.reject(new TypeError('compilePortableMaterialSpecV3 accepts exactly one material spec'));
|
|
1492
|
+
return compilePortableMaterialSpecV3(args[0], { resolver: options.textureResolver });
|
|
1493
|
+
}),
|
|
1480
1494
|
basicMaterial: wrap('basicMaterial', basicMaterial),
|
|
1481
1495
|
glassMaterial: wrap('glassMaterial', glassMaterial),
|
|
1482
1496
|
lambertMaterial: wrap('lambertMaterial', lambertMaterial),
|
|
@@ -1529,6 +1543,7 @@ export function buildSandboxGlobals(usage, options = {}) {
|
|
|
1529
1543
|
// Shape-aware unwraps (sync — preserve built-in directional UVs)
|
|
1530
1544
|
projectUV: wrap('projectUV', projectUV),
|
|
1531
1545
|
remapUV: wrap('remapUV', uvShapes.remapUV),
|
|
1546
|
+
remapUVToAtlas: wrap('remapUVToAtlas', remapUVToAtlas),
|
|
1532
1547
|
// Parametric primitives
|
|
1533
1548
|
gearGeo: wrapGeo('gearGeo', gears.gearGeo),
|
|
1534
1549
|
bladeGeo: wrapGeo('bladeGeo', gears.bladeGeo),
|
|
@@ -1537,6 +1552,11 @@ export function buildSandboxGlobals(usage, options = {}) {
|
|
|
1537
1552
|
loadApprovedTexture: wrap('loadApprovedTexture', loadApprovedTexture),
|
|
1538
1553
|
// Procedural textures (sync — no I/O, so no await)
|
|
1539
1554
|
proceduralTexture: wrap('proceduralTexture', proceduralTextures.proceduralTexture),
|
|
1555
|
+
textureAtlas: wrap('textureAtlas', (...args) => {
|
|
1556
|
+
if (args.length !== 1)
|
|
1557
|
+
return Promise.reject(new TypeError('textureAtlas accepts exactly one atlas spec'));
|
|
1558
|
+
return textureAtlas(args[0], { resources: options.textureResolver?.pinnedMaterialMaps });
|
|
1559
|
+
}),
|
|
1540
1560
|
normalMapFromHeight: wrap('normalMapFromHeight', proceduralTextures.normalMapFromHeight),
|
|
1541
1561
|
pbrMaterial: wrap('pbrMaterial', textures.pbrMaterial),
|
|
1542
1562
|
foliageMaterial: wrap('foliageMaterial', textures.foliageMaterial),
|
|
@@ -184,6 +184,8 @@ export declare class ProceduralTextureError extends AuthoringDiagnosticError {
|
|
|
184
184
|
export declare function migrateProceduralTextureSpecV1(input: unknown): ProceduralTextureSpecV2;
|
|
185
185
|
export declare function canonicalizeProceduralTextureSpecV2(input: unknown): CanonicalProceduralTextureSpecV2;
|
|
186
186
|
export declare function canonicalProceduralTextureJsonV2(input: unknown): string;
|
|
187
|
+
/** Browser-safe SHA-256 shared by versioned recipe contracts. */
|
|
188
|
+
export declare function sha256Hex(text: string): string;
|
|
187
189
|
export declare function hashProceduralTextureSpecV2(input: unknown): `sha256:${string}`;
|
|
188
190
|
export declare function canonicalizePortableMaterialSpecV2(input: unknown): CanonicalPortableMaterialSpecV2;
|
|
189
191
|
export {};
|
|
@@ -294,7 +294,8 @@ const SHA256_K = new Uint32Array([
|
|
|
294
294
|
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
295
295
|
]);
|
|
296
296
|
const rotateRight = (value, bits) => (value >>> bits) | (value << (32 - bits));
|
|
297
|
-
|
|
297
|
+
/** Browser-safe SHA-256 shared by versioned recipe contracts. */
|
|
298
|
+
export function sha256Hex(text) {
|
|
298
299
|
const source = new TextEncoder().encode(text);
|
|
299
300
|
const bitLength = source.length * 8;
|
|
300
301
|
const paddedLength = Math.ceil((source.length + 9) / 64) * 64;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Strict opt-in PBR material data. Pixel/byte loading is outside this module. */
|
|
2
|
+
import { ProceduralTextureError } from "./procedural-material-v2.js";
|
|
3
|
+
import { type CanonicalProceduralTextureSpecV3, type ProceduralTextureSpecV3 } from "./procedural-texture-v3-contract.js";
|
|
4
|
+
import { type CanonicalPortableSamplerV1, type PortableSamplerV1 } from "./portable-sampler.js";
|
|
5
|
+
import type { MaterialMapSlotV1 } from "./pinned-material-map.js";
|
|
6
|
+
export declare const PORTABLE_MATERIAL_V3_LIMITS: Readonly<{
|
|
7
|
+
maxBindings: 5;
|
|
8
|
+
maxTexels: number;
|
|
9
|
+
maxEncodedBytes: number;
|
|
10
|
+
maxWork: 64000000;
|
|
11
|
+
maxTransientBytes: number;
|
|
12
|
+
maxJsonBytes: number;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const PORTABLE_MATERIAL_SLOT_USAGE: {
|
|
15
|
+
readonly baseColor: 'albedo';
|
|
16
|
+
readonly normal: 'normal';
|
|
17
|
+
readonly metallicRoughness: 'metallicRoughness';
|
|
18
|
+
readonly emissive: 'emissive';
|
|
19
|
+
readonly occlusion: 'occlusion';
|
|
20
|
+
};
|
|
21
|
+
export type PortableTextureRefV3 = {
|
|
22
|
+
kind: 'procedural';
|
|
23
|
+
spec: ProceduralTextureSpecV3;
|
|
24
|
+
sampler: PortableSamplerV1;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'resource';
|
|
27
|
+
resourceId: string;
|
|
28
|
+
sampler: PortableSamplerV1;
|
|
29
|
+
};
|
|
30
|
+
export type CanonicalPortableTextureRefV3 = {
|
|
31
|
+
kind: 'procedural';
|
|
32
|
+
spec: CanonicalProceduralTextureSpecV3;
|
|
33
|
+
sampler: CanonicalPortableSamplerV1;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'resource';
|
|
36
|
+
resourceId: string;
|
|
37
|
+
sampler: CanonicalPortableSamplerV1;
|
|
38
|
+
};
|
|
39
|
+
export interface PortableMaterialSpecV3 {
|
|
40
|
+
schemaVersion: 3;
|
|
41
|
+
model: 'pbrMetallicRoughness';
|
|
42
|
+
name?: string;
|
|
43
|
+
baseColor?: number;
|
|
44
|
+
roughness?: number;
|
|
45
|
+
metalness?: number;
|
|
46
|
+
emissive?: number;
|
|
47
|
+
emissiveIntensity?: number;
|
|
48
|
+
alphaMode?: 'opaque' | 'mask' | 'blend';
|
|
49
|
+
alphaCutoff?: number;
|
|
50
|
+
doubleSided?: boolean;
|
|
51
|
+
textures?: Partial<Record<MaterialMapSlotV1, PortableTextureRefV3>>;
|
|
52
|
+
}
|
|
53
|
+
export interface CanonicalPortableMaterialSpecV3 extends Omit<PortableMaterialSpecV3, 'textures' | 'roughness' | 'metalness' | 'emissiveIntensity' | 'alphaMode' | 'alphaCutoff' | 'doubleSided'> {
|
|
54
|
+
roughness: number;
|
|
55
|
+
metalness: number;
|
|
56
|
+
emissiveIntensity: number;
|
|
57
|
+
alphaMode: 'opaque' | 'mask' | 'blend';
|
|
58
|
+
alphaCutoff: number;
|
|
59
|
+
doubleSided: boolean;
|
|
60
|
+
textures: Partial<Record<MaterialMapSlotV1, CanonicalPortableTextureRefV3>>;
|
|
61
|
+
}
|
|
62
|
+
export declare class PortableMaterialV3Error extends ProceduralTextureError {
|
|
63
|
+
constructor(message: string);
|
|
64
|
+
}
|
|
65
|
+
export interface PortableMaterialAdmissionV3 {
|
|
66
|
+
spec: CanonicalPortableMaterialSpecV3;
|
|
67
|
+
texels: number;
|
|
68
|
+
estimatedWorkUnits: number;
|
|
69
|
+
conservativePixelAllocationBytes: number;
|
|
70
|
+
}
|
|
71
|
+
export declare function admitPortableMaterialSpecV3(input: unknown): PortableMaterialAdmissionV3;
|
|
72
|
+
export declare function canonicalizePortableMaterialSpecV3(input: unknown): CanonicalPortableMaterialSpecV3;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/** Strict opt-in PBR material data. Pixel/byte loading is outside this module. */
|
|
2
|
+
import { canonicalizePortableMaterialSpecV2, ProceduralTextureError, } from "./procedural-material-v2.js";
|
|
3
|
+
import { admitProceduralTextureSpecV3, } from "./procedural-texture-v3-contract.js";
|
|
4
|
+
import { canonicalizePortableSamplerV1, } from "./portable-sampler.js";
|
|
5
|
+
export const PORTABLE_MATERIAL_V3_LIMITS = Object.freeze({
|
|
6
|
+
maxBindings: 5,
|
|
7
|
+
maxTexels: 4 * 1024 * 1024,
|
|
8
|
+
maxEncodedBytes: 16 * 1024 * 1024,
|
|
9
|
+
maxWork: 64_000_000,
|
|
10
|
+
maxTransientBytes: 64 * 1024 * 1024,
|
|
11
|
+
maxJsonBytes: 1024 * 1024,
|
|
12
|
+
});
|
|
13
|
+
export const PORTABLE_MATERIAL_SLOT_USAGE = {
|
|
14
|
+
baseColor: 'albedo',
|
|
15
|
+
normal: 'normal',
|
|
16
|
+
metallicRoughness: 'metallicRoughness',
|
|
17
|
+
emissive: 'emissive',
|
|
18
|
+
occlusion: 'occlusion',
|
|
19
|
+
};
|
|
20
|
+
export class PortableMaterialV3Error extends ProceduralTextureError {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(`portableMaterial V3: ${message}`, 'PORTABLE_MATERIAL_V3_CONTRACT');
|
|
23
|
+
this.name = 'PortableMaterialV3Error';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function fail(message) {
|
|
27
|
+
throw new PortableMaterialV3Error(message);
|
|
28
|
+
}
|
|
29
|
+
/** Bound untrusted data before reading any accessor or making canonical copies. */
|
|
30
|
+
function assertJson(input) {
|
|
31
|
+
let nodes = 0;
|
|
32
|
+
const visit = (value, depth) => {
|
|
33
|
+
if (++nodes > 65536 || depth > 16)
|
|
34
|
+
fail('JSON size/depth budget exceeded.');
|
|
35
|
+
if (value === null || typeof value === 'boolean')
|
|
36
|
+
return;
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
if (value.length > PORTABLE_MATERIAL_V3_LIMITS.maxJsonBytes)
|
|
39
|
+
fail('string budget exceeded.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
43
|
+
return;
|
|
44
|
+
if (!value || typeof value !== 'object')
|
|
45
|
+
fail('only plain JSON data is supported.');
|
|
46
|
+
const array = Array.isArray(value), proto = Object.getPrototypeOf(value);
|
|
47
|
+
if (array ? proto !== Array.prototype : proto !== Object.prototype && proto !== null)
|
|
48
|
+
fail('only plain JSON prototypes are supported.');
|
|
49
|
+
const descriptors = Object.getOwnPropertyDescriptors(value), keys = Reflect.ownKeys(value);
|
|
50
|
+
if (keys.length > 1025)
|
|
51
|
+
fail('field/array budget exceeded.');
|
|
52
|
+
const length = array ? descriptors['length']?.value : undefined;
|
|
53
|
+
if (array && (length > 1024 || keys.length !== length + 1))
|
|
54
|
+
fail('arrays must be bounded dense JSON data.');
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
if (array && key === 'length')
|
|
57
|
+
continue;
|
|
58
|
+
if (typeof key !== 'string' || ['__proto__', 'prototype', 'constructor'].includes(key))
|
|
59
|
+
fail('forbidden JSON field.');
|
|
60
|
+
const d = descriptors[key];
|
|
61
|
+
if (!d.enumerable || !('value' in d))
|
|
62
|
+
fail('JSON accessors/hidden fields are unsupported.');
|
|
63
|
+
if (array && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= length))
|
|
64
|
+
fail('invalid array key.');
|
|
65
|
+
visit(d.value, depth + 1);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
visit(input, 0);
|
|
69
|
+
if (new TextEncoder().encode(JSON.stringify(input)).length >
|
|
70
|
+
PORTABLE_MATERIAL_V3_LIMITS.maxJsonBytes)
|
|
71
|
+
fail('JSON byte budget exceeded.');
|
|
72
|
+
}
|
|
73
|
+
function record(value, keys) {
|
|
74
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
75
|
+
fail('expected a JSON object.');
|
|
76
|
+
const result = value;
|
|
77
|
+
if (Object.keys(result).some((key) => !keys.includes(key)))
|
|
78
|
+
fail('unknown field in material or texture reference.');
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
function admitMaterial(input) {
|
|
82
|
+
assertJson(input);
|
|
83
|
+
const scalarKeys = [
|
|
84
|
+
'baseColor',
|
|
85
|
+
'roughness',
|
|
86
|
+
'metalness',
|
|
87
|
+
'emissive',
|
|
88
|
+
'emissiveIntensity',
|
|
89
|
+
'alphaMode',
|
|
90
|
+
'alphaCutoff',
|
|
91
|
+
'doubleSided',
|
|
92
|
+
];
|
|
93
|
+
const r = record(input, ['schemaVersion', 'model', 'name', 'textures', ...scalarKeys]);
|
|
94
|
+
if (r['schemaVersion'] !== 3 || r['model'] !== 'pbrMetallicRoughness')
|
|
95
|
+
fail('requires schemaVersion 3 and model pbrMetallicRoughness.');
|
|
96
|
+
const name = r['name'];
|
|
97
|
+
if (name !== undefined && (typeof name !== 'string' || name.length < 1 || name.length > 200))
|
|
98
|
+
fail('name requires 1..200 characters.');
|
|
99
|
+
const common = canonicalizePortableMaterialSpecV2({
|
|
100
|
+
schemaVersion: 2,
|
|
101
|
+
model: 'pbrMetallicRoughness',
|
|
102
|
+
...Object.fromEntries(scalarKeys.filter((k) => r[k] !== undefined).map((k) => [k, r[k]])),
|
|
103
|
+
});
|
|
104
|
+
const refs = record(r['textures'] ?? {}, Object.keys(PORTABLE_MATERIAL_SLOT_USAGE));
|
|
105
|
+
const textures = {};
|
|
106
|
+
let texels = 0, work = 0, bytes = 0;
|
|
107
|
+
for (const [key, value] of Object.entries(refs)) {
|
|
108
|
+
const slot = key;
|
|
109
|
+
const ref = record(value, ['kind', 'spec', 'resourceId', 'sampler']);
|
|
110
|
+
const sampler = canonicalizePortableSamplerV1(ref['sampler']);
|
|
111
|
+
if (ref['kind'] === 'procedural') {
|
|
112
|
+
if (ref['resourceId'] !== undefined)
|
|
113
|
+
fail('procedural references cannot carry a resource ID.');
|
|
114
|
+
const admitted = admitProceduralTextureSpecV3(ref['spec']);
|
|
115
|
+
if (admitted.spec.usage !== PORTABLE_MATERIAL_SLOT_USAGE[slot])
|
|
116
|
+
fail(`${slot} requires ${PORTABLE_MATERIAL_SLOT_USAGE[slot]} data.`);
|
|
117
|
+
texels += admitted.spec.width * admitted.spec.height;
|
|
118
|
+
work += admitted.estimatedWorkUnits;
|
|
119
|
+
bytes += admitted.conservativePixelAllocationBytes;
|
|
120
|
+
textures[slot] = { kind: 'procedural', spec: admitted.spec, sampler };
|
|
121
|
+
}
|
|
122
|
+
else if (ref['kind'] === 'resource') {
|
|
123
|
+
if (ref['spec'] !== undefined ||
|
|
124
|
+
typeof ref['resourceId'] !== 'string' ||
|
|
125
|
+
ref['resourceId'].length > 128 ||
|
|
126
|
+
!/^kiln\.[a-z0-9][a-z0-9._-]+$/.test(ref['resourceId']))
|
|
127
|
+
fail('resource requires only a bounded closed kiln.* ID and sampler.');
|
|
128
|
+
textures[slot] = { kind: 'resource', resourceId: ref['resourceId'], sampler };
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
fail('texture kind must be procedural or resource.');
|
|
132
|
+
}
|
|
133
|
+
if (texels > PORTABLE_MATERIAL_V3_LIMITS.maxTexels ||
|
|
134
|
+
work > PORTABLE_MATERIAL_V3_LIMITS.maxWork ||
|
|
135
|
+
bytes > PORTABLE_MATERIAL_V3_LIMITS.maxTransientBytes)
|
|
136
|
+
fail('aggregate texel/work/allocation budget exceeded.');
|
|
137
|
+
const { schemaVersion: _, textures: __, ...parameters } = common;
|
|
138
|
+
return {
|
|
139
|
+
spec: {
|
|
140
|
+
schemaVersion: 3,
|
|
141
|
+
...parameters,
|
|
142
|
+
...(name !== undefined ? { name: name } : {}),
|
|
143
|
+
textures,
|
|
144
|
+
},
|
|
145
|
+
texels,
|
|
146
|
+
estimatedWorkUnits: work,
|
|
147
|
+
conservativePixelAllocationBytes: bytes,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export function admitPortableMaterialSpecV3(input) {
|
|
151
|
+
try {
|
|
152
|
+
return admitMaterial(input);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error instanceof PortableMaterialV3Error)
|
|
156
|
+
throw error;
|
|
157
|
+
throw new PortableMaterialV3Error(error instanceof ProceduralTextureError ? error.message : 'Invalid bounded material data.');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function canonicalizePortableMaterialSpecV3(input) {
|
|
161
|
+
return admitPortableMaterialSpecV3(input).spec;
|
|
162
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Legacy V1/V2 pixel compiler. Extraction preserves arithmetic and recipe identity. */
|
|
2
|
+
import { type CanonicalProceduralTextureSpecV2 } from "./procedural-material-v2.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build a tiling texture from a bounded, deterministic layer stack.
|
|
5
|
+
*
|
|
6
|
+
* The result is a plain RGBA `DataTexture`; `renderSceneToGLB` bakes it to PNG
|
|
7
|
+
* and embeds it, so nothing else has to be done to ship it.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const bark = proceduralTexture({
|
|
11
|
+
* schemaVersion: 2,
|
|
12
|
+
* size: 256,
|
|
13
|
+
* usage: 'albedo',
|
|
14
|
+
* name: 'Bark',
|
|
15
|
+
* layers: [
|
|
16
|
+
* { op: 'solid', color: 0x5a4632 },
|
|
17
|
+
* { op: 'noise', colorA: 0x3d2f21, colorB: 0x7a6248, scale: 6, octaves: 4, blend: 'overlay' },
|
|
18
|
+
* { op: 'stripes', colorA: 0x000000, colorB: 0xffffff, count: 24, angleDeg: 90,
|
|
19
|
+
* blend: 'multiply', opacity: 0.15 },
|
|
20
|
+
* ],
|
|
21
|
+
* });
|
|
22
|
+
* const trunk = createPart('Trunk', cylinderGeo(0.3, 0.3, 3), pbrMaterial({ albedo: bark }), { parent: root });
|
|
23
|
+
*/
|
|
24
|
+
export interface CompiledProceduralTextureV2 {
|
|
25
|
+
spec: CanonicalProceduralTextureSpecV2;
|
|
26
|
+
pixels: Uint8Array;
|
|
27
|
+
canonicalJson: string;
|
|
28
|
+
recipeHash: `sha256:${string}`;
|
|
29
|
+
}
|
|
30
|
+
/** Validate, canonicalize, and compile one texture without constructing Three.js state. */
|
|
31
|
+
export declare function compileProceduralTextureSpecV2(input: unknown): CompiledProceduralTextureV2;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** Legacy V1/V2 pixel compiler. Extraction preserves arithmetic and recipe identity. */
|
|
2
|
+
import { canonicalProceduralTextureJsonV2, canonicalizeProceduralTextureSpecV2, hashProceduralTextureSpecV2, } from "./procedural-material-v2.js";
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// Deterministic value noise
|
|
5
|
+
// -----------------------------------------------------------------------------
|
|
6
|
+
/**
|
|
7
|
+
* Integer hash of a lattice coordinate. Not a cryptographic hash — it only has
|
|
8
|
+
* to decorrelate neighbouring integers and return the same value forever.
|
|
9
|
+
*/
|
|
10
|
+
function hash2(x, y, seed) {
|
|
11
|
+
let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 1274126177)) | 0;
|
|
12
|
+
h = h ^ (h >>> 13);
|
|
13
|
+
h = Math.imul(h, 1274126177) | 0;
|
|
14
|
+
h = h ^ (h >>> 16);
|
|
15
|
+
return (h >>> 0) / 4294967296;
|
|
16
|
+
}
|
|
17
|
+
const smoothstep = (t) => t * t * (3 - 2 * t);
|
|
18
|
+
/**
|
|
19
|
+
* Value noise on a lattice that wraps at `period`, so the result tiles.
|
|
20
|
+
* `period` is in lattice cells, and both axes use the same one.
|
|
21
|
+
*/
|
|
22
|
+
function valueNoise(u, v, period, seed) {
|
|
23
|
+
const x = u * period;
|
|
24
|
+
const y = v * period;
|
|
25
|
+
const xi = Math.floor(x);
|
|
26
|
+
const yi = Math.floor(y);
|
|
27
|
+
const xf = x - xi;
|
|
28
|
+
const yf = y - yi;
|
|
29
|
+
// Wrapping the lattice index (not the sample point) is what makes it tile:
|
|
30
|
+
// the cell to the right of the last one is the first one again.
|
|
31
|
+
const wrap = (n) => ((n % period) + period) % period;
|
|
32
|
+
const x0 = wrap(xi);
|
|
33
|
+
const y0 = wrap(yi);
|
|
34
|
+
const x1 = wrap(xi + 1);
|
|
35
|
+
const y1 = wrap(yi + 1);
|
|
36
|
+
const sx = smoothstep(xf);
|
|
37
|
+
const sy = smoothstep(yf);
|
|
38
|
+
const n00 = hash2(x0, y0, seed);
|
|
39
|
+
const n10 = hash2(x1, y0, seed);
|
|
40
|
+
const n01 = hash2(x0, y1, seed);
|
|
41
|
+
const n11 = hash2(x1, y1, seed);
|
|
42
|
+
const top = n00 + sx * (n10 - n00);
|
|
43
|
+
const bottom = n01 + sx * (n11 - n01);
|
|
44
|
+
return top + sy * (bottom - top);
|
|
45
|
+
}
|
|
46
|
+
function fbm(u, v, period, octaves, seed) {
|
|
47
|
+
let sum = 0;
|
|
48
|
+
let amplitude = 1;
|
|
49
|
+
let total = 0;
|
|
50
|
+
let p = period;
|
|
51
|
+
for (let o = 0; o < octaves; o++) {
|
|
52
|
+
sum += valueNoise(u, v, p, seed + o * 101) * amplitude;
|
|
53
|
+
total += amplitude;
|
|
54
|
+
amplitude *= 0.5;
|
|
55
|
+
// Doubling the period keeps every octave tileable at the base period.
|
|
56
|
+
p *= 2;
|
|
57
|
+
}
|
|
58
|
+
return total > 0 ? sum / total : 0;
|
|
59
|
+
}
|
|
60
|
+
// -----------------------------------------------------------------------------
|
|
61
|
+
// Layer evaluation
|
|
62
|
+
// -----------------------------------------------------------------------------
|
|
63
|
+
const rgbOf = (color) => [
|
|
64
|
+
(color >> 16) & 0xff,
|
|
65
|
+
(color >> 8) & 0xff,
|
|
66
|
+
color & 0xff,
|
|
67
|
+
];
|
|
68
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
69
|
+
/** Rotate normalized coords about the center so angled patterns stay centered. */
|
|
70
|
+
function project(u, v, angleDeg) {
|
|
71
|
+
const a = (angleDeg * Math.PI) / 180;
|
|
72
|
+
return (u - 0.5) * Math.cos(a) + (v - 0.5) * Math.sin(a) + 0.5;
|
|
73
|
+
}
|
|
74
|
+
/** Evaluate one layer at a normalized coordinate, returning 0-255 RGB. */
|
|
75
|
+
function evalLayer(layer, u, v) {
|
|
76
|
+
switch (layer.op) {
|
|
77
|
+
case 'solid':
|
|
78
|
+
return rgbOf(layer.color);
|
|
79
|
+
case 'checker': {
|
|
80
|
+
const cx = Math.floor(u * layer.squares);
|
|
81
|
+
const cy = Math.floor(v * layer.squares);
|
|
82
|
+
return rgbOf((cx + cy) % 2 === 0 ? layer.colorA : layer.colorB);
|
|
83
|
+
}
|
|
84
|
+
case 'stripes': {
|
|
85
|
+
const t = project(u, v, layer.angleDeg);
|
|
86
|
+
return rgbOf(Math.floor(t * layer.count) % 2 === 0 ? layer.colorA : layer.colorB);
|
|
87
|
+
}
|
|
88
|
+
case 'gradient': {
|
|
89
|
+
const t = Math.min(1, Math.max(0, project(u, v, layer.angleDeg)));
|
|
90
|
+
const from = rgbOf(layer.from);
|
|
91
|
+
const to = rgbOf(layer.to);
|
|
92
|
+
return [lerp(from[0], to[0], t), lerp(from[1], to[1], t), lerp(from[2], to[2], t)];
|
|
93
|
+
}
|
|
94
|
+
case 'bricks': {
|
|
95
|
+
const row = Math.floor(v * layer.rows);
|
|
96
|
+
// Offset alternate rows, wrapping into [0,1) so the pattern still tiles.
|
|
97
|
+
const shifted = (u + (row % 2 === 0 ? 0 : layer.stagger / layer.cols) + 1) % 1;
|
|
98
|
+
const inCol = (shifted * layer.cols) % 1;
|
|
99
|
+
const inRow = (v * layer.rows) % 1;
|
|
100
|
+
const isMortar = inCol < layer.mortarWidth ||
|
|
101
|
+
inCol > 1 - layer.mortarWidth ||
|
|
102
|
+
inRow < layer.mortarWidth ||
|
|
103
|
+
inRow > 1 - layer.mortarWidth;
|
|
104
|
+
return rgbOf(isMortar ? layer.mortar : layer.brick);
|
|
105
|
+
}
|
|
106
|
+
case 'noise': {
|
|
107
|
+
const n = fbm(u, v, layer.scale, layer.octaves, layer.seed);
|
|
108
|
+
const a = rgbOf(layer.colorA);
|
|
109
|
+
const b = rgbOf(layer.colorB);
|
|
110
|
+
return [lerp(a[0], b[0], n), lerp(a[1], b[1], n), lerp(a[2], b[2], n)];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function blendChannel(mode, base, top) {
|
|
115
|
+
const b = base / 255;
|
|
116
|
+
const t = top / 255;
|
|
117
|
+
let out;
|
|
118
|
+
switch (mode) {
|
|
119
|
+
case 'multiply':
|
|
120
|
+
out = b * t;
|
|
121
|
+
break;
|
|
122
|
+
case 'screen':
|
|
123
|
+
out = 1 - (1 - b) * (1 - t);
|
|
124
|
+
break;
|
|
125
|
+
case 'overlay':
|
|
126
|
+
out = b < 0.5 ? 2 * b * t : 1 - 2 * (1 - b) * (1 - t);
|
|
127
|
+
break;
|
|
128
|
+
default:
|
|
129
|
+
out = t;
|
|
130
|
+
}
|
|
131
|
+
return out * 255;
|
|
132
|
+
}
|
|
133
|
+
/** Validate, canonicalize, and compile one texture without constructing Three.js state. */
|
|
134
|
+
export function compileProceduralTextureSpecV2(input) {
|
|
135
|
+
const spec = canonicalizeProceduralTextureSpecV2(input);
|
|
136
|
+
const { size, layers } = spec;
|
|
137
|
+
const data = new Uint8Array(size * size * 4);
|
|
138
|
+
for (let y = 0; y < size; y++) {
|
|
139
|
+
// Sample at texel centers so a pattern with N divisions lands on exact
|
|
140
|
+
// boundaries instead of drifting half a texel.
|
|
141
|
+
const v = (y + 0.5) / size;
|
|
142
|
+
for (let x = 0; x < size; x++) {
|
|
143
|
+
const u = (x + 0.5) / size;
|
|
144
|
+
let r = 0;
|
|
145
|
+
let g = 0;
|
|
146
|
+
let b = 0;
|
|
147
|
+
for (const [i, layer] of layers.entries()) {
|
|
148
|
+
const [lr, lg, lb] = evalLayer(layer, u, v);
|
|
149
|
+
if (i === 0) {
|
|
150
|
+
r = lr;
|
|
151
|
+
g = lg;
|
|
152
|
+
b = lb;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
r = lerp(r, blendChannel(layer.blend, r, lr), layer.opacity);
|
|
156
|
+
g = lerp(g, blendChannel(layer.blend, g, lg), layer.opacity);
|
|
157
|
+
b = lerp(b, blendChannel(layer.blend, b, lb), layer.opacity);
|
|
158
|
+
}
|
|
159
|
+
const o = (y * size + x) * 4;
|
|
160
|
+
data[o] = Math.max(0, Math.min(255, Math.round(r)));
|
|
161
|
+
data[o + 1] = Math.max(0, Math.min(255, Math.round(g)));
|
|
162
|
+
data[o + 2] = Math.max(0, Math.min(255, Math.round(b)));
|
|
163
|
+
data[o + 3] = 255;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
spec,
|
|
168
|
+
pixels: data,
|
|
169
|
+
canonicalJson: canonicalProceduralTextureJsonV2(spec),
|
|
170
|
+
recipeHash: hashProceduralTextureSpecV2(spec),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** Bounded JSON and identity contract; no pixel allocations or Three.js dependency. */
|
|
2
|
+
import { type CanonicalProceduralTextureSpecV2, type ProceduralTextureSpecV2 } from "./procedural-material-v2.js";
|
|
3
|
+
import { type TextureUsage } from "./texture-contract.js";
|
|
4
|
+
export declare const PROCEDURAL_TEXTURE_V3_COMPILER_ID: 'kiln.procedural-rgba.v3.1';
|
|
5
|
+
export declare const PROCEDURAL_TEXTURE_SPEC_VERSION_V3: 3;
|
|
6
|
+
export declare const MAX_PROCEDURAL_V3_EDGE = 1024;
|
|
7
|
+
export declare const MAX_PROCEDURAL_V3_TEXELS = 1048576;
|
|
8
|
+
export declare const MAX_PROCEDURAL_V3_OPERATIONS = 64;
|
|
9
|
+
export declare const MAX_PROCEDURAL_V3_VERTICES = 64;
|
|
10
|
+
export declare const MAX_PROCEDURAL_V3_STAMPS = 256;
|
|
11
|
+
export declare const MAX_PROCEDURAL_V3_PALETTE = 32;
|
|
12
|
+
export declare const MAX_PROCEDURAL_V3_WORK = 64000000;
|
|
13
|
+
export declare const MAX_PROCEDURAL_V3_TRANSIENT_BYTES: number;
|
|
14
|
+
export type RgbaV3 = [number, number, number, number];
|
|
15
|
+
export type PixelPointV3 = [number, number];
|
|
16
|
+
export interface PixelRectangleV3 {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
}
|
|
22
|
+
export type ProceduralShapeV3 = (PixelRectangleV3 & {
|
|
23
|
+
kind: 'rectangle';
|
|
24
|
+
}) | (PixelRectangleV3 & {
|
|
25
|
+
kind: 'ellipse';
|
|
26
|
+
}) | {
|
|
27
|
+
kind: 'polygon';
|
|
28
|
+
points: PixelPointV3[];
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'line';
|
|
31
|
+
from: PixelPointV3;
|
|
32
|
+
to: PixelPointV3;
|
|
33
|
+
width: number;
|
|
34
|
+
};
|
|
35
|
+
export type ProceduralOperationV3 = {
|
|
36
|
+
op: 'paint';
|
|
37
|
+
shape: ProceduralShapeV3;
|
|
38
|
+
rgba: RgbaV3;
|
|
39
|
+
opacity?: number;
|
|
40
|
+
} | {
|
|
41
|
+
op: 'mask';
|
|
42
|
+
shape: ProceduralShapeV3;
|
|
43
|
+
alpha?: number;
|
|
44
|
+
invert?: boolean;
|
|
45
|
+
} | {
|
|
46
|
+
op: 'stamps';
|
|
47
|
+
shape: ProceduralShapeV3;
|
|
48
|
+
rgba: RgbaV3;
|
|
49
|
+
opacity?: number;
|
|
50
|
+
count: number;
|
|
51
|
+
seed: number;
|
|
52
|
+
area: PixelRectangleV3;
|
|
53
|
+
periodic?: boolean;
|
|
54
|
+
} | {
|
|
55
|
+
op: 'legacy-v2';
|
|
56
|
+
spec: ProceduralTextureSpecV2;
|
|
57
|
+
alpha?: number;
|
|
58
|
+
opacity?: number;
|
|
59
|
+
};
|
|
60
|
+
export interface ProceduralTextureSpecV3 {
|
|
61
|
+
schemaVersion: 3;
|
|
62
|
+
width: number;
|
|
63
|
+
height: number;
|
|
64
|
+
usage?: TextureUsage;
|
|
65
|
+
name?: string;
|
|
66
|
+
background?: RgbaV3;
|
|
67
|
+
operations: ProceduralOperationV3[];
|
|
68
|
+
palette?: {
|
|
69
|
+
colors: number[];
|
|
70
|
+
dither?: 'none' | 'bayer4';
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export type CanonicalProceduralOperationV3 = {
|
|
74
|
+
op: 'paint';
|
|
75
|
+
shape: ProceduralShapeV3;
|
|
76
|
+
rgba: RgbaV3;
|
|
77
|
+
opacity: number;
|
|
78
|
+
} | {
|
|
79
|
+
op: 'mask';
|
|
80
|
+
shape: ProceduralShapeV3;
|
|
81
|
+
alpha: number;
|
|
82
|
+
invert: boolean;
|
|
83
|
+
} | {
|
|
84
|
+
op: 'stamps';
|
|
85
|
+
shape: ProceduralShapeV3;
|
|
86
|
+
rgba: RgbaV3;
|
|
87
|
+
opacity: number;
|
|
88
|
+
count: number;
|
|
89
|
+
seed: number;
|
|
90
|
+
area: PixelRectangleV3;
|
|
91
|
+
periodic: boolean;
|
|
92
|
+
} | {
|
|
93
|
+
op: 'legacy-v2';
|
|
94
|
+
spec: CanonicalProceduralTextureSpecV2;
|
|
95
|
+
alpha: number;
|
|
96
|
+
opacity: number;
|
|
97
|
+
};
|
|
98
|
+
export interface CanonicalProceduralTextureSpecV3 {
|
|
99
|
+
schemaVersion: 3;
|
|
100
|
+
width: number;
|
|
101
|
+
height: number;
|
|
102
|
+
usage: TextureUsage;
|
|
103
|
+
name?: string;
|
|
104
|
+
background: RgbaV3;
|
|
105
|
+
operations: CanonicalProceduralOperationV3[];
|
|
106
|
+
palette?: {
|
|
107
|
+
colors: number[];
|
|
108
|
+
dither: 'none' | 'bayer4';
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export declare function proceduralShapeBoundsV3(s: ProceduralShapeV3): [number, number, number, number];
|
|
112
|
+
export declare function proceduralShapeCostV3(s: ProceduralShapeV3): number;
|
|
113
|
+
export declare function proceduralClippedBoundsV3(s: ProceduralShapeV3, width: number, height: number, dx?: number, dy?: number): [number, number, number, number];
|
|
114
|
+
/** Internal composition admission; byte accounting is total RGBA allocations, not live peak/RSS. */
|
|
115
|
+
export interface ProceduralTextureAdmissionV3 {
|
|
116
|
+
spec: CanonicalProceduralTextureSpecV3;
|
|
117
|
+
canonicalJson: string;
|
|
118
|
+
estimatedWorkUnits: number;
|
|
119
|
+
conservativePixelAllocationBytes: number;
|
|
120
|
+
}
|
|
121
|
+
/** Owns the single validation/accounting pass shared by standalone and atlas compilation. */
|
|
122
|
+
export declare function admitProceduralTextureSpecV3(input: unknown): ProceduralTextureAdmissionV3;
|
|
123
|
+
export declare function canonicalizeProceduralTextureSpecV3(input: unknown): CanonicalProceduralTextureSpecV3;
|
|
124
|
+
export declare function canonicalProceduralTextureJsonV3(input: unknown): string;
|
|
125
|
+
export declare function hashProceduralTextureSpecV3(input: unknown): `sha256:${string}`;
|