pelican-engine 0.0.0-stage → 0.3.1
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/AGENTS.md +147 -0
- package/LICENSE +22 -0
- package/README.md +129 -2
- package/distribution.json +197 -0
- package/docs/AI-WORKFLOW.md +517 -0
- package/docs/ARCHITECTURE.md +268 -0
- package/docs/CAPABILITIES.md +264 -0
- package/docs/DECISIONS.md +107 -0
- package/docs/M3-USAGE.md +192 -0
- package/docs/PELICAN-MIGRATION.md +38 -0
- package/docs/RENDER-BACKENDS.md +50 -0
- package/docs/ROADMAP.md +239 -0
- package/docs/THIRD-PARTY-NOTICES.md +151 -0
- package/docs/api/REFERENCE.md +1435 -0
- package/docs/api/index.json +1139 -0
- package/docs/npm/DISTRIBUTION.md +130 -0
- package/docs/npm/GAME-AUTHOR.md +147 -0
- package/engine.manifest.json +15 -0
- package/lib/assets/audio.js +67 -0
- package/lib/assets/errors.js +36 -0
- package/lib/assets/glb.js +277 -0
- package/lib/assets/index.js +53 -0
- package/lib/assets/jpeg.js +199 -0
- package/lib/assets/model.js +558 -0
- package/lib/assets/optimize.js +95 -0
- package/lib/assets/texture.js +187 -0
- package/lib/compiler/distribution.js +17 -0
- package/lib/compiler/extract.js +425 -0
- package/lib/compiler/graph.js +366 -0
- package/lib/compiler/index.js +574 -0
- package/lib/compiler/normalize.js +1194 -0
- package/lib/compiler/source-map.js +25 -0
- package/lib/compiler/types.js +2 -0
- package/lib/compiler/version.js +93 -0
- package/lib/experiments/simulation/index.js +2 -0
- package/lib/experiments/simulation/math.js +41 -0
- package/lib/experiments/simulation/probe.js +215 -0
- package/lib/experiments/simulation/scenes.js +192 -0
- package/lib/experiments/simulation/solver.js +346 -0
- package/lib/experiments/simulation/types.js +1 -0
- package/lib/experiments/world/adapters.js +30 -0
- package/lib/experiments/world/geometry.js +180 -0
- package/lib/experiments/world/index.js +2 -0
- package/lib/experiments/world/probe.js +288 -0
- package/lib/experiments/world/types.js +1 -0
- package/lib/experiments/world/world.js +599 -0
- package/lib/navigation/index.js +157 -0
- package/lib/physics/index.js +909 -0
- package/lib/protocol/index.js +92 -0
- package/lib/renderer/acceleration.js +609 -0
- package/lib/renderer/browser.js +2015 -0
- package/lib/renderer/bsdf.js +260 -0
- package/lib/renderer/checkpoint.js +88 -0
- package/lib/renderer/compute-executor.js +1034 -0
- package/lib/renderer/compute-texture-assets.js +49 -0
- package/lib/renderer/compute-view-contract.js +1 -0
- package/lib/renderer/denoiser.js +361 -0
- package/lib/renderer/dielectric.js +360 -0
- package/lib/renderer/embedded-image-pixels.js +368 -0
- package/lib/renderer/emissive-lights.js +67 -0
- package/lib/renderer/environment-importance.js +55 -0
- package/lib/renderer/environment.js +346 -0
- package/lib/renderer/gameplay.js +464 -0
- package/lib/renderer/geometry.js +103 -0
- package/lib/renderer/graph-expressions.js +485 -0
- package/lib/renderer/graph-tsl.js +141 -0
- package/lib/renderer/graph.js +214 -0
- package/lib/renderer/managed-texture-sampling.js +29 -0
- package/lib/renderer/material-graphs.js +297 -0
- package/lib/renderer/medium.js +348 -0
- package/lib/renderer/models.js +1053 -0
- package/lib/renderer/motion-shader.js +40 -0
- package/lib/renderer/path-shader.js +196 -0
- package/lib/renderer/pathtrace.js +324 -0
- package/lib/renderer/postprocess-contract.js +1 -0
- package/lib/renderer/postprocess.js +494 -0
- package/lib/renderer/render-buffers.js +132 -0
- package/lib/renderer/render-textures.js +155 -0
- package/lib/renderer/startup.js +50 -0
- package/lib/renderer/surface-capture.js +199 -0
- package/lib/renderer/surface-gpu.js +270 -0
- package/lib/renderer/surface-motion.js +84 -0
- package/lib/renderer/surface-ray-shader.js +91 -0
- package/lib/renderer/surface-shader.js +88 -0
- package/lib/renderer/surface-tracer.js +1720 -0
- package/lib/renderer/surface.js +1425 -0
- package/lib/renderer/temporal-filter.js +512 -0
- package/lib/renderer/texture-pixels.js +207 -0
- package/lib/renderer/traversal-nodes.js +54 -0
- package/lib/renderer/ui.js +446 -0
- package/lib/renderer/vertex-bounds.js +127 -0
- package/lib/renderer/vertex-contract.js +1 -0
- package/lib/renderer/vertex-graphs.js +197 -0
- package/lib/renderer/volume-shader.js +50 -0
- package/lib/runtime/compute-contract.js +1 -0
- package/lib/runtime/compute-service.js +674 -0
- package/lib/runtime/contracts.js +1 -0
- package/lib/runtime/error.js +37 -0
- package/lib/runtime/events.js +189 -0
- package/lib/runtime/game-services.js +772 -0
- package/lib/runtime/host.js +839 -0
- package/lib/runtime/material-parameters.js +145 -0
- package/lib/runtime/plugins.js +1 -0
- package/lib/runtime/postprocess-contract.js +1 -0
- package/lib/runtime/postprocess.js +189 -0
- package/lib/runtime/prefabs.js +13 -0
- package/lib/runtime/random.js +88 -0
- package/lib/runtime/render-buffer-contract.js +1 -0
- package/lib/runtime/render-buffers.js +243 -0
- package/lib/runtime/render-capabilities.js +17 -0
- package/lib/runtime/render-settings.js +249 -0
- package/lib/runtime/render-texture-contract.js +1 -0
- package/lib/runtime/render-textures.js +271 -0
- package/lib/runtime/resources.js +648 -0
- package/lib/runtime/scheduler.js +765 -0
- package/lib/runtime/storage.js +169 -0
- package/lib/runtime/world.js +594 -0
- package/lib/schema/index.js +24 -0
- package/lib/sdk/graph-values.js +16 -0
- package/lib/sdk/index.d.ts +800 -0
- package/lib/sdk/index.js +82 -0
- package/lib/tools/api-index.js +113 -0
- package/lib/tools/browser-session.js +89 -0
- package/lib/tools/cli.js +513 -0
- package/lib/tools/compress-dist.js +59 -0
- package/lib/tools/control-client.js +153 -0
- package/lib/tools/control-server.js +633 -0
- package/lib/tools/diagnostic-code.js +3 -0
- package/lib/tools/doctor.js +496 -0
- package/lib/tools/npm/browser-cli.js +6 -0
- package/lib/tools/npm/entry.js +40 -0
- package/lib/tools/npm/layout.js +195 -0
- package/lib/tools/setup.js +153 -0
- package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
- package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@gltf-transform/core/LICENSE.md +21 -0
- package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
- package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
- package/licenses/@img/colour/LICENSE.md +82 -0
- package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
- package/licenses/@oxc-project/types/LICENSE +22 -0
- package/licenses/@rolldown/pluginutils/LICENSE +21 -0
- package/licenses/@tweenjs/tween.js/LICENSE +23 -0
- package/licenses/@types/ndarray/LICENSE +21 -0
- package/licenses/@types/node/LICENSE +21 -0
- package/licenses/@types/stats.js/LICENSE +21 -0
- package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@types/three/LICENSE +21 -0
- package/licenses/@types/webxr/LICENSE +21 -0
- package/licenses/@webgpu/types/LICENSE +26 -0
- package/licenses/THIRD-PARTY-NOTICES.md +139 -0
- package/licenses/cwise-compiler/LICENSE +22 -0
- package/licenses/detect-libc/LICENSE +201 -0
- package/licenses/esbuild/LICENSE.md +21 -0
- package/licenses/fast-png/LICENSE +21 -0
- package/licenses/fdir/LICENSE +7 -0
- package/licenses/fflate/LICENSE +21 -0
- package/licenses/iobuffer/LICENSE +22 -0
- package/licenses/iota-array/LICENSE +22 -0
- package/licenses/is-buffer/LICENSE +21 -0
- package/licenses/ktx-parse/LICENSE +21 -0
- package/licenses/lightningcss/LICENSE +373 -0
- package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
- package/licenses/meshoptimizer/LICENSE.md +21 -0
- package/licenses/nanoid/LICENSE +20 -0
- package/licenses/ndarray/LICENSE +22 -0
- package/licenses/ndarray-lanczos/LICENSE +21 -0
- package/licenses/ndarray-ops/LICENSE +22 -0
- package/licenses/ndarray-pixels/LICENSE +21 -0
- package/licenses/ngraph.path/LICENSE +21 -0
- package/licenses/picocolors/LICENSE +15 -0
- package/licenses/picomatch/LICENSE +21 -0
- package/licenses/playwright/LICENSE +202 -0
- package/licenses/playwright/NOTICE +5 -0
- package/licenses/playwright-core/LICENSE +202 -0
- package/licenses/playwright-core/NOTICE +5 -0
- package/licenses/postcss/LICENSE +20 -0
- package/licenses/prettier/LICENSE +7 -0
- package/licenses/property-graph/LICENSE +21 -0
- package/licenses/rolldown/LICENSE +25 -0
- package/licenses/semver/LICENSE +15 -0
- package/licenses/sharp/LICENSE +191 -0
- package/licenses/source-map-js/LICENSE +28 -0
- package/licenses/three/LICENSE +21 -0
- package/licenses/tinyglobby/LICENSE +21 -0
- package/licenses/tsx/LICENSE +21 -0
- package/licenses/typescript/LICENSE.txt +55 -0
- package/licenses/undici-types/LICENSE +21 -0
- package/licenses/uniq/LICENSE +22 -0
- package/licenses/vite/LICENSE.md +2084 -0
- package/package.json +49 -4
- package/web/index.html +34 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { decode, hasPngSignature } from "fast-png";
|
|
2
|
+
import { Unzlib, zlibSync } from "fflate";
|
|
3
|
+
// Weak keys let decoded pixels die with Three's shared source.
|
|
4
|
+
const embedded = new WeakMap();
|
|
5
|
+
const MAX_BYTES = 256 * 1024 * 1024;
|
|
6
|
+
const ADAM7 = [
|
|
7
|
+
[0, 0, 8, 8],
|
|
8
|
+
[4, 0, 8, 8],
|
|
9
|
+
[0, 4, 4, 8],
|
|
10
|
+
[2, 0, 4, 4],
|
|
11
|
+
[0, 2, 2, 4],
|
|
12
|
+
[1, 0, 2, 2],
|
|
13
|
+
[0, 1, 1, 2],
|
|
14
|
+
];
|
|
15
|
+
function pngChunk(type, data) {
|
|
16
|
+
const chunk = new Uint8Array(data.length + 12);
|
|
17
|
+
const view = new DataView(chunk.buffer);
|
|
18
|
+
view.setUint32(0, data.length);
|
|
19
|
+
chunk.set(type, 4);
|
|
20
|
+
chunk.set(data, 8);
|
|
21
|
+
let crc = 0xffffffff;
|
|
22
|
+
for (let i = 4; i < chunk.length - 4; i++) {
|
|
23
|
+
crc ^= chunk[i];
|
|
24
|
+
for (let bit = 0; bit < 8; bit++)
|
|
25
|
+
crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
|
26
|
+
}
|
|
27
|
+
view.setUint32(chunk.length - 4, (crc ^ 0xffffffff) >>> 0);
|
|
28
|
+
return chunk;
|
|
29
|
+
}
|
|
30
|
+
function unpackAdam7(data, width, height, depth) {
|
|
31
|
+
const rowBytes = Math.ceil((width * depth) / 8);
|
|
32
|
+
const rows = new Uint8Array((rowBytes + 1) * height);
|
|
33
|
+
let offset = 0;
|
|
34
|
+
for (const [x0, y0, dx, dy] of ADAM7) {
|
|
35
|
+
const w = Math.max(0, Math.ceil((width - x0) / dx));
|
|
36
|
+
const h = Math.max(0, Math.ceil((height - y0) / dy));
|
|
37
|
+
if (!w || !h)
|
|
38
|
+
continue;
|
|
39
|
+
const size = Math.ceil((w * depth) / 8);
|
|
40
|
+
let previous = new Uint8Array(size);
|
|
41
|
+
for (let y = 0; y < h; y++) {
|
|
42
|
+
const filter = data[offset++];
|
|
43
|
+
if (filter > 4)
|
|
44
|
+
throw new Error("Invalid PNG filter");
|
|
45
|
+
const line = new Uint8Array(size);
|
|
46
|
+
for (let i = 0; i < size; i++) {
|
|
47
|
+
const left = i ? line[i - 1] : 0, up = previous[i], upperLeft = i ? previous[i - 1] : 0;
|
|
48
|
+
const p = left + up - upperLeft;
|
|
49
|
+
const a = Math.abs(p - left), b = Math.abs(p - up), c = Math.abs(p - upperLeft);
|
|
50
|
+
const predictor = filter === 0
|
|
51
|
+
? 0
|
|
52
|
+
: filter === 1
|
|
53
|
+
? left
|
|
54
|
+
: filter === 2
|
|
55
|
+
? up
|
|
56
|
+
: filter === 3
|
|
57
|
+
? Math.floor((left + up) / 2)
|
|
58
|
+
: a <= b && a <= c
|
|
59
|
+
? left
|
|
60
|
+
: b <= c
|
|
61
|
+
? up
|
|
62
|
+
: upperLeft;
|
|
63
|
+
line[i] = (data[offset++] + predictor) & 255;
|
|
64
|
+
}
|
|
65
|
+
for (let x = 0; x < w; x++) {
|
|
66
|
+
const value = (line[Math.floor((x * depth) / 8)] >>
|
|
67
|
+
(8 - depth - ((x * depth) % 8))) &
|
|
68
|
+
((1 << depth) - 1);
|
|
69
|
+
const targetX = x0 + x * dx;
|
|
70
|
+
rows[(y0 + y * dy) * (rowBytes + 1) + 1 + Math.floor((targetX * depth) / 8)] |= value << (8 - depth - ((targetX * depth) % 8));
|
|
71
|
+
}
|
|
72
|
+
previous = line;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
function imageUrl(image) {
|
|
78
|
+
const value = image;
|
|
79
|
+
return `${value?.src ?? ""}\n${value?.currentSrc ?? ""}`;
|
|
80
|
+
}
|
|
81
|
+
export function decodeEmbeddedPng(bytes) {
|
|
82
|
+
try {
|
|
83
|
+
if (!hasPngSignature(bytes) || bytes.length < 33)
|
|
84
|
+
throw new Error("Invalid PNG signature or header");
|
|
85
|
+
if (bytes.length > MAX_BYTES)
|
|
86
|
+
throw new Error("PNG input exceeds 256 MiB limit");
|
|
87
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
88
|
+
if (view.getUint32(8) !== 13 || view.getUint32(12) !== 0x49484452)
|
|
89
|
+
throw new Error("Invalid PNG IHDR");
|
|
90
|
+
const depth = bytes[24], color = bytes[25], interlace = bytes[28];
|
|
91
|
+
const width = view.getUint32(16), height = view.getUint32(20);
|
|
92
|
+
if (!width ||
|
|
93
|
+
!height ||
|
|
94
|
+
width > 0x7fffffff ||
|
|
95
|
+
height > 0x7fffffff ||
|
|
96
|
+
width * height * 4 > MAX_BYTES)
|
|
97
|
+
throw new Error("PNG dimensions exceed the 256 MiB RGBA limit");
|
|
98
|
+
if (![0, 2, 3, 4, 6].includes(color) ||
|
|
99
|
+
![1, 2, 4, 8, 16].includes(depth) ||
|
|
100
|
+
(depth < 8 && color !== 0 && color !== 3) ||
|
|
101
|
+
(depth === 16 && color === 3))
|
|
102
|
+
throw new Error(`Unsupported PNG color type ${color} / depth ${depth}`);
|
|
103
|
+
if (interlace !== 0 && interlace !== 1)
|
|
104
|
+
throw new Error("Invalid PNG interlace method");
|
|
105
|
+
const channels = color === 0 || color === 3 ? 1 : color === 2 ? 3 : color === 4 ? 2 : 4;
|
|
106
|
+
const rowBytes = Math.ceil((width * channels * depth) / 8);
|
|
107
|
+
let expected = (rowBytes + 1) * height;
|
|
108
|
+
if (interlace === 1) {
|
|
109
|
+
expected = 0;
|
|
110
|
+
for (const [x, y, dx, dy] of ADAM7) {
|
|
111
|
+
const w = Math.max(0, Math.ceil((width - x) / dx)), h = Math.max(0, Math.ceil((height - y) / dy));
|
|
112
|
+
if (w && h)
|
|
113
|
+
expected += (Math.ceil((w * channels * depth) / 8) + 1) * h;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (expected > MAX_BYTES)
|
|
117
|
+
throw new Error("PNG inflated data exceeds the 256 MiB limit");
|
|
118
|
+
const lowAdam7 = interlace === 1 && depth < 8;
|
|
119
|
+
const idat = [];
|
|
120
|
+
const chunks = [];
|
|
121
|
+
let ended = false, headers = 0, smallRgbTransparency;
|
|
122
|
+
for (let offset = 8; offset + 12 <= bytes.length;) {
|
|
123
|
+
const length = view.getUint32(offset);
|
|
124
|
+
if (length > bytes.length - offset - 12)
|
|
125
|
+
throw new Error("Truncated PNG chunk");
|
|
126
|
+
const type = view.getUint32(offset + 4);
|
|
127
|
+
if (lowAdam7) {
|
|
128
|
+
let crc = 0xffffffff;
|
|
129
|
+
for (let i = offset + 4; i < offset + 8 + length; i++) {
|
|
130
|
+
crc ^= bytes[i];
|
|
131
|
+
for (let bit = 0; bit < 8; bit++)
|
|
132
|
+
crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
|
133
|
+
}
|
|
134
|
+
if ((crc ^ 0xffffffff) >>> 0 !== view.getUint32(offset + 8 + length))
|
|
135
|
+
throw new Error("Invalid PNG chunk CRC");
|
|
136
|
+
chunks.push({ offset, length, type });
|
|
137
|
+
}
|
|
138
|
+
if (type === 0x49484452 && ++headers !== 1)
|
|
139
|
+
throw new Error("Multiple PNG headers");
|
|
140
|
+
if (type === 0x504c5445 && (length === 0 || length > 768))
|
|
141
|
+
throw new Error("Invalid PNG palette size");
|
|
142
|
+
if (type === 0x74524e53 && length > 256)
|
|
143
|
+
throw new Error("Invalid PNG transparency size");
|
|
144
|
+
// fast-png incorrectly rejects an RGB tRNS key when there are fewer than
|
|
145
|
+
// three pixels. Validate and remove just that chunk before decoding.
|
|
146
|
+
if (depth === 16 &&
|
|
147
|
+
color === 2 &&
|
|
148
|
+
width * height < 3 &&
|
|
149
|
+
type === 0x74524e53) {
|
|
150
|
+
if (smallRgbTransparency || length !== 6)
|
|
151
|
+
throw new Error("Invalid PNG transparency key");
|
|
152
|
+
let crc = 0xffffffff;
|
|
153
|
+
for (let i = offset + 4; i < offset + 8 + length; i++) {
|
|
154
|
+
crc ^= bytes[i];
|
|
155
|
+
for (let bit = 0; bit < 8; bit++)
|
|
156
|
+
crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
|
157
|
+
}
|
|
158
|
+
if ((crc ^ 0xffffffff) >>> 0 !== view.getUint32(offset + 8 + length))
|
|
159
|
+
throw new Error("Invalid PNG transparency CRC");
|
|
160
|
+
smallRgbTransparency = {
|
|
161
|
+
offset,
|
|
162
|
+
key: new Uint16Array([
|
|
163
|
+
view.getUint16(offset + 8),
|
|
164
|
+
view.getUint16(offset + 10),
|
|
165
|
+
view.getUint16(offset + 12),
|
|
166
|
+
]),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (type === 0x6163544c)
|
|
170
|
+
throw new Error("Animated PNG is unsupported");
|
|
171
|
+
if (depth === 16) {
|
|
172
|
+
const at = offset + 8;
|
|
173
|
+
const srgbChroma = [
|
|
174
|
+
31270, 32900, 64000, 33000, 30000, 60000, 15000, 6000,
|
|
175
|
+
];
|
|
176
|
+
if (type === 0x67414d41 &&
|
|
177
|
+
(length !== 4 || ![45455, 100000].includes(view.getUint32(at))))
|
|
178
|
+
throw new Error("16-bit PNG gAMA value is unsupported");
|
|
179
|
+
if (type === 0x6348524d &&
|
|
180
|
+
(length !== 32 ||
|
|
181
|
+
srgbChroma.some((value, i) => view.getUint32(at + 4 * i) !== value)))
|
|
182
|
+
throw new Error("16-bit PNG cHRM values are unsupported");
|
|
183
|
+
if (type === 0x73524742 && (length !== 1 || bytes[at] !== 0))
|
|
184
|
+
throw new Error("16-bit PNG sRGB intent is unsupported");
|
|
185
|
+
if (type === 0x63494350 &&
|
|
186
|
+
(length !== 4 ||
|
|
187
|
+
[1, 13, 0, 1].some((value, i) => bytes[at + i] !== value)))
|
|
188
|
+
throw new Error("16-bit PNG cICP values are unsupported");
|
|
189
|
+
}
|
|
190
|
+
// fast-png inflates ICC profiles separately without an output bound.
|
|
191
|
+
if (type === 0x69434350)
|
|
192
|
+
throw new Error("Embedded PNG ICC profiles are unsupported");
|
|
193
|
+
if (type === 0x49444154)
|
|
194
|
+
idat.push(bytes.subarray(offset + 8, offset + 8 + length));
|
|
195
|
+
if ((bytes[offset + 4] & 32) === 0 &&
|
|
196
|
+
![0x49484452, 0x504c5445, 0x49444154, 0x49454e44].includes(type))
|
|
197
|
+
throw new Error("Unknown critical PNG chunk");
|
|
198
|
+
offset += length + 12;
|
|
199
|
+
if (type === 0x49454e44) {
|
|
200
|
+
ended = true;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!ended || !idat.length)
|
|
205
|
+
throw new Error("Missing PNG IDAT or IEND");
|
|
206
|
+
// Bound streaming inflation before fast-png sees untrusted IDAT. Small
|
|
207
|
+
// compressed pushes bound temporary output even for extreme ratios.
|
|
208
|
+
let inflated = 0;
|
|
209
|
+
const inflatedData = lowAdam7 ? new Uint8Array(expected) : undefined;
|
|
210
|
+
const inflater = new Unzlib((part) => {
|
|
211
|
+
const start = inflated;
|
|
212
|
+
inflated += part.length;
|
|
213
|
+
if (inflated > expected)
|
|
214
|
+
throw new Error("PNG inflated data exceeds IHDR size");
|
|
215
|
+
inflatedData?.set(part, start);
|
|
216
|
+
});
|
|
217
|
+
for (const part of idat)
|
|
218
|
+
for (let i = 0; i < part.length; i += 512)
|
|
219
|
+
inflater.push(part.subarray(i, i + 512), false);
|
|
220
|
+
inflater.push(new Uint8Array(0), true);
|
|
221
|
+
if (inflated !== expected)
|
|
222
|
+
throw new Error("PNG inflated data does not match IHDR size");
|
|
223
|
+
let decoderBytes = bytes;
|
|
224
|
+
if (inflatedData) {
|
|
225
|
+
// fast-png 8 treats packed Adam7 samples as full bytes. Repack the
|
|
226
|
+
// validated passes as ordinary scanlines before invoking its decoder.
|
|
227
|
+
const packed = unpackAdam7(inflatedData, width, height, depth);
|
|
228
|
+
const compressed = zlibSync(packed);
|
|
229
|
+
const pieces = [bytes.subarray(0, 8)];
|
|
230
|
+
let wroteIdat = false;
|
|
231
|
+
for (const { offset, length, type } of chunks) {
|
|
232
|
+
if (type === 0x49484452) {
|
|
233
|
+
const header = bytes.slice(offset + 8, offset + 8 + length);
|
|
234
|
+
header[12] = 0;
|
|
235
|
+
pieces.push(pngChunk(bytes.subarray(offset + 4, offset + 8), header));
|
|
236
|
+
}
|
|
237
|
+
else if (type === 0x49444154) {
|
|
238
|
+
if (!wroteIdat)
|
|
239
|
+
pieces.push(pngChunk(bytes.subarray(offset + 4, offset + 8), compressed));
|
|
240
|
+
wroteIdat = true;
|
|
241
|
+
}
|
|
242
|
+
else
|
|
243
|
+
pieces.push(bytes.subarray(offset, offset + length + 12));
|
|
244
|
+
}
|
|
245
|
+
const size = pieces.reduce((sum, part) => sum + part.length, 0);
|
|
246
|
+
if (size > MAX_BYTES)
|
|
247
|
+
throw new Error("Repacked PNG exceeds the 256 MiB limit");
|
|
248
|
+
decoderBytes = new Uint8Array(size);
|
|
249
|
+
let at = 0;
|
|
250
|
+
for (const part of pieces) {
|
|
251
|
+
decoderBytes.set(part, at);
|
|
252
|
+
at += part.length;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (smallRgbTransparency) {
|
|
256
|
+
decoderBytes = new Uint8Array(bytes.length - 18);
|
|
257
|
+
decoderBytes.set(bytes.subarray(0, smallRgbTransparency.offset));
|
|
258
|
+
decoderBytes.set(bytes.subarray(smallRgbTransparency.offset + 18), smallRgbTransparency.offset);
|
|
259
|
+
}
|
|
260
|
+
const png = decode(decoderBytes, { checkCrc: true });
|
|
261
|
+
const { data, palette } = png;
|
|
262
|
+
const transparency = smallRgbTransparency?.key ?? png.transparency;
|
|
263
|
+
if (png.width !== width || png.height !== height)
|
|
264
|
+
throw new Error("Inconsistent PNG dimensions");
|
|
265
|
+
if (data.length !==
|
|
266
|
+
(depth === 16 ? width * height * channels : rowBytes * height))
|
|
267
|
+
throw new Error("Invalid decoded PNG data length");
|
|
268
|
+
if (transparency &&
|
|
269
|
+
transparency.length !== (color === 0 ? 1 : color === 2 ? 3 : 0))
|
|
270
|
+
throw new Error("Invalid PNG transparency key");
|
|
271
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
272
|
+
for (let y = 0; y < height; y++)
|
|
273
|
+
for (let x = 0; x < width; x++) {
|
|
274
|
+
const out = (y * width + x) * 4, pos = depth === 16
|
|
275
|
+
? (y * width + x) * channels
|
|
276
|
+
: y * rowBytes + x * channels;
|
|
277
|
+
if (color === 3 || color === 0) {
|
|
278
|
+
const raw = depth >= 8
|
|
279
|
+
? data[pos]
|
|
280
|
+
: (data[y * rowBytes + Math.floor((x * depth) / 8)] >>
|
|
281
|
+
(8 - depth - ((x * depth) % 8))) &
|
|
282
|
+
((1 << depth) - 1);
|
|
283
|
+
if (color === 3) {
|
|
284
|
+
const entry = palette?.[raw];
|
|
285
|
+
if (!entry || (entry.length !== 3 && entry.length !== 4))
|
|
286
|
+
throw new Error("Invalid PNG palette index");
|
|
287
|
+
rgba[out] = entry[0];
|
|
288
|
+
rgba[out + 1] = entry[1];
|
|
289
|
+
rgba[out + 2] = entry[2];
|
|
290
|
+
rgba[out + 3] = entry[3] ?? 255;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const gray = depth === 16 ? raw >>> 8 : (raw * 255) / ((1 << depth) - 1);
|
|
294
|
+
rgba[out] = rgba[out + 1] = rgba[out + 2] = gray;
|
|
295
|
+
rgba[out + 3] = transparency?.[0] === raw ? 0 : 255;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const r = data[pos], g = color === 4 ? r : data[pos + 1], b = color === 4 ? r : data[pos + 2];
|
|
300
|
+
const alpha = color === 4
|
|
301
|
+
? data[pos + 1]
|
|
302
|
+
: color === 6
|
|
303
|
+
? data[pos + 3]
|
|
304
|
+
: transparency &&
|
|
305
|
+
transparency[0] === r &&
|
|
306
|
+
transparency[1] === g &&
|
|
307
|
+
transparency[2] === b
|
|
308
|
+
? 0
|
|
309
|
+
: 255;
|
|
310
|
+
rgba[out] = depth === 16 ? r >>> 8 : r;
|
|
311
|
+
rgba[out + 1] = depth === 16 ? g >>> 8 : g;
|
|
312
|
+
rgba[out + 2] = depth === 16 ? b >>> 8 : b;
|
|
313
|
+
rgba[out + 3] =
|
|
314
|
+
depth === 16 && (color === 4 || color === 6) ? alpha >>> 8 : alpha;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
width,
|
|
319
|
+
height,
|
|
320
|
+
data: rgba,
|
|
321
|
+
channels: 4,
|
|
322
|
+
encoding: "unorm8",
|
|
323
|
+
origin: "image",
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
throw new Error(`TC_TEXTURE_PNG_UNSUPPORTED: ${error instanceof Error ? error.message : String(error)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** Associates immutable decoded content with the shared source, never userData. */
|
|
331
|
+
export function associateEmbeddedImage(texture, bytes, mime) {
|
|
332
|
+
const source = texture.source;
|
|
333
|
+
if (embedded.has(source))
|
|
334
|
+
return;
|
|
335
|
+
if (!hasPngSignature(bytes) && mime.toLowerCase() !== "image/png")
|
|
336
|
+
return;
|
|
337
|
+
const entry = { image: source.data, imageUrl: imageUrl(source.data) };
|
|
338
|
+
try {
|
|
339
|
+
entry.pixels = decodeEmbeddedPng(bytes);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
entry.error = error instanceof Error ? error.message : String(error);
|
|
343
|
+
}
|
|
344
|
+
embedded.set(source, entry);
|
|
345
|
+
}
|
|
346
|
+
export function embeddedImagePixels(texture) {
|
|
347
|
+
const source = texture.source, entry = embedded.get(source);
|
|
348
|
+
if (!entry)
|
|
349
|
+
return undefined;
|
|
350
|
+
if (entry.error)
|
|
351
|
+
throw new Error(entry.error);
|
|
352
|
+
if (source.data !== entry.image || imageUrl(source.data) !== entry.imageUrl)
|
|
353
|
+
throw new Error("TC_TEXTURE_PNG_SOURCE_CHANGED: embedded PNG source changed after decoding; reload the model");
|
|
354
|
+
if (typeof ImageBitmap !== "undefined" &&
|
|
355
|
+
source.data instanceof ImageBitmap &&
|
|
356
|
+
!source.data.width)
|
|
357
|
+
throw new Error("TC_TEXTURE_PNG_IMAGE_CLOSED: retain the image until extraction completes");
|
|
358
|
+
return entry.pixels;
|
|
359
|
+
}
|
|
360
|
+
export function releaseEmbeddedImage(texture) {
|
|
361
|
+
const source = texture.source;
|
|
362
|
+
if (embedded.has(source))
|
|
363
|
+
embedded.set(source, {
|
|
364
|
+
image: source.data,
|
|
365
|
+
imageUrl: imageUrl(source.data),
|
|
366
|
+
error: "TC_TEXTURE_PNG_SOURCE_RELEASED: model resources were disposed",
|
|
367
|
+
});
|
|
368
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Importance distribution over emitting geometry. Shader evaluates actual textured emission at each sample. */
|
|
2
|
+
export function appendEmissiveLights(snapshot, ordered, explicit) {
|
|
3
|
+
const materials = new Map(snapshot.materials.map((material) => [material.uuid, material]));
|
|
4
|
+
const emitters = [];
|
|
5
|
+
const semantic = [];
|
|
6
|
+
ordered.forEach((triangle, index) => {
|
|
7
|
+
const material = materials.get(triangle.materialUuid);
|
|
8
|
+
const color = material.emissive.map((value) => value * material.emissiveIntensity), luminance = color[0] * 0.2126 + color[1] * 0.7152 + color[2] * 0.0722;
|
|
9
|
+
if (luminance <= 0)
|
|
10
|
+
return;
|
|
11
|
+
const [a, b, c] = triangle.vertices.map((vertex) => vertex.position), e1 = b.map((value, axis) => value - a[axis]), e2 = c.map((value, axis) => value - a[axis]), cross = [
|
|
12
|
+
e1[1] * e2[2] - e1[2] * e2[1],
|
|
13
|
+
e1[2] * e2[0] - e1[0] * e2[2],
|
|
14
|
+
e1[0] * e2[1] - e1[1] * e2[0],
|
|
15
|
+
], area = Math.hypot(...cross) * 0.5;
|
|
16
|
+
if (!Number.isFinite(area) || area <= 0)
|
|
17
|
+
throw new Error("TC_TRACE_EMITTER: Invalid emissive triangle area.");
|
|
18
|
+
emitters.push({ index, area, weight: area * luminance, color });
|
|
19
|
+
const key = JSON.stringify([
|
|
20
|
+
triangle.objectUuid,
|
|
21
|
+
triangle.geometryUuid,
|
|
22
|
+
triangle.instanceId,
|
|
23
|
+
triangle.groupIndex,
|
|
24
|
+
triangle.elementOffset,
|
|
25
|
+
triangle.vertexIndices,
|
|
26
|
+
]);
|
|
27
|
+
semantic.push({
|
|
28
|
+
key,
|
|
29
|
+
value: JSON.stringify([
|
|
30
|
+
key,
|
|
31
|
+
triangle.vertices,
|
|
32
|
+
triangle.normalMatrix,
|
|
33
|
+
material,
|
|
34
|
+
]),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
const total = emitters.reduce((sum, emitter) => sum + emitter.weight, 0), output = new Float32Array(explicit.length + emitters.length * 12);
|
|
38
|
+
output.set(explicit);
|
|
39
|
+
let cumulative = 0;
|
|
40
|
+
emitters.forEach((emitter, slot) => {
|
|
41
|
+
const probability = emitter.weight / total;
|
|
42
|
+
cumulative += probability;
|
|
43
|
+
output.set([
|
|
44
|
+
emitter.index,
|
|
45
|
+
emitter.area,
|
|
46
|
+
probability,
|
|
47
|
+
4,
|
|
48
|
+
...emitter.color,
|
|
49
|
+
0,
|
|
50
|
+
slot === emitters.length - 1 ? 1 : cumulative,
|
|
51
|
+
0,
|
|
52
|
+
0,
|
|
53
|
+
0,
|
|
54
|
+
], explicit.length + slot * 12);
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
signature: JSON.stringify([
|
|
58
|
+
Array.from(explicit),
|
|
59
|
+
semantic
|
|
60
|
+
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
|
61
|
+
.map((entry) => entry.value),
|
|
62
|
+
]),
|
|
63
|
+
data: output,
|
|
64
|
+
emitters: emitters.length,
|
|
65
|
+
totalArea: emitters.reduce((sum, emitter) => sum + emitter.area, 0),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Vose alias proposals over exact spherical cell solid angles; every texel contributes to its cell's mean. */
|
|
2
|
+
export function buildEnvironmentImportance(data, width, height) {
|
|
3
|
+
if (!Number.isSafeInteger(width) ||
|
|
4
|
+
!Number.isSafeInteger(height) ||
|
|
5
|
+
width < 1 ||
|
|
6
|
+
height < 1 ||
|
|
7
|
+
data.length !== width * height * 4)
|
|
8
|
+
throw new Error("TC_ENVIRONMENT_IMPORTANCE: Invalid HDR image layout.");
|
|
9
|
+
const columns = Math.min(64, width), rows = Math.min(32, height), weights = new Float64Array(columns * rows), counts = new Uint32Array(columns * rows), solidAngles = new Float64Array(columns * rows);
|
|
10
|
+
for (let y = 0; y < height; y++)
|
|
11
|
+
for (let x = 0; x < width; x++) {
|
|
12
|
+
const index = (y * width + x) * 4;
|
|
13
|
+
for (let channel = 0; channel < 3; channel++)
|
|
14
|
+
if (!Number.isFinite(data[index + channel]) ||
|
|
15
|
+
data[index + channel] < 0)
|
|
16
|
+
throw new Error("TC_ENVIRONMENT_RADIANCE: Expected nonnegative finite HDR radiance.");
|
|
17
|
+
const cell = Math.min(rows - 1, Math.floor((y * rows) / height)) * columns +
|
|
18
|
+
Math.min(columns - 1, Math.floor((x * columns) / width));
|
|
19
|
+
weights[cell] +=
|
|
20
|
+
data[index] * 0.2126 +
|
|
21
|
+
data[index + 1] * 0.7152 +
|
|
22
|
+
data[index + 2] * 0.0722;
|
|
23
|
+
counts[cell]++;
|
|
24
|
+
}
|
|
25
|
+
let energy = 0;
|
|
26
|
+
for (let row = 0; row < rows; row++) {
|
|
27
|
+
const area = ((2 * Math.PI) / columns) *
|
|
28
|
+
(Math.cos((row * Math.PI) / rows) -
|
|
29
|
+
Math.cos(((row + 1) * Math.PI) / rows));
|
|
30
|
+
for (let column = 0; column < columns; column++) {
|
|
31
|
+
const cell = row * columns + column;
|
|
32
|
+
solidAngles[cell] = area;
|
|
33
|
+
weights[cell] = (weights[cell] / Math.max(1, counts[cell])) * area;
|
|
34
|
+
energy += weights[cell];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const entries = columns * rows, probabilities = Float64Array.from(weights, (weight) => energy > 0 ? weight / energy : 1 / entries), scaled = Float64Array.from(probabilities, (p) => p * entries), small = [], large = [], table = new Float32Array(entries * 4);
|
|
38
|
+
scaled.forEach((value, index) => (value < 1 ? small : large).push(index));
|
|
39
|
+
for (let index = 0; index < entries; index++)
|
|
40
|
+
table.set([probabilities[index], index, 1, solidAngles[index]], index * 4);
|
|
41
|
+
while (small.length && large.length) {
|
|
42
|
+
const a = small.pop(), b = large.pop();
|
|
43
|
+
table[a * 4 + 1] = b;
|
|
44
|
+
table[a * 4 + 2] = scaled[a];
|
|
45
|
+
scaled[b] = scaled[b] + scaled[a] - 1;
|
|
46
|
+
(scaled[b] < 1 ? small : large).push(b);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
width: columns,
|
|
50
|
+
height: rows,
|
|
51
|
+
table,
|
|
52
|
+
integralLuminance: energy,
|
|
53
|
+
source: [width, height],
|
|
54
|
+
};
|
|
55
|
+
}
|