@oeave/bakery3 0.0.0-stage → 0.1.0

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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +562 -2
  3. package/dist/animation-B1h0Ryvj.d.ts +97 -0
  4. package/dist/bake/index.d.ts +369 -0
  5. package/dist/bake/index.js +9 -0
  6. package/dist/bake/index.js.map +1 -0
  7. package/dist/bake-DaOlenyx.d.ts +1357 -0
  8. package/dist/catalog/index.d.ts +100 -0
  9. package/dist/catalog/index.js +154 -0
  10. package/dist/catalog/index.js.map +1 -0
  11. package/dist/catalog.gen-BM-aNf7n.d.ts +733 -0
  12. package/dist/chunk-25ZXSVQ3.js +1107 -0
  13. package/dist/chunk-25ZXSVQ3.js.map +1 -0
  14. package/dist/chunk-3G5QL4F4.js +145 -0
  15. package/dist/chunk-3G5QL4F4.js.map +1 -0
  16. package/dist/chunk-4E5VV4QY.js +12 -0
  17. package/dist/chunk-4E5VV4QY.js.map +1 -0
  18. package/dist/chunk-54WJDN5R.js +3286 -0
  19. package/dist/chunk-54WJDN5R.js.map +1 -0
  20. package/dist/chunk-6NYR73Y7.js +832 -0
  21. package/dist/chunk-6NYR73Y7.js.map +1 -0
  22. package/dist/chunk-6YVQANMW.js +1463 -0
  23. package/dist/chunk-6YVQANMW.js.map +1 -0
  24. package/dist/chunk-AF5BIELI.js +70 -0
  25. package/dist/chunk-AF5BIELI.js.map +1 -0
  26. package/dist/chunk-APBOWWSV.js +92 -0
  27. package/dist/chunk-APBOWWSV.js.map +1 -0
  28. package/dist/chunk-ARMTTZXZ.js +1157 -0
  29. package/dist/chunk-ARMTTZXZ.js.map +1 -0
  30. package/dist/chunk-CX4WTVGH.js +4492 -0
  31. package/dist/chunk-CX4WTVGH.js.map +1 -0
  32. package/dist/chunk-HQTUFVMM.js +1412 -0
  33. package/dist/chunk-HQTUFVMM.js.map +1 -0
  34. package/dist/chunk-MD2U6S4N.js +1952 -0
  35. package/dist/chunk-MD2U6S4N.js.map +1 -0
  36. package/dist/chunk-S4AJTLLN.js +23 -0
  37. package/dist/chunk-S4AJTLLN.js.map +1 -0
  38. package/dist/chunk-TGFNQBKC.js +551 -0
  39. package/dist/chunk-TGFNQBKC.js.map +1 -0
  40. package/dist/chunk-V3QINEEG.js +87 -0
  41. package/dist/chunk-V3QINEEG.js.map +1 -0
  42. package/dist/chunk-W6WQ5UP7.js +91 -0
  43. package/dist/chunk-W6WQ5UP7.js.map +1 -0
  44. package/dist/chunk-WBIAFIHV.js +346 -0
  45. package/dist/chunk-WBIAFIHV.js.map +1 -0
  46. package/dist/chunk-ZEBVAIJJ.js +156 -0
  47. package/dist/chunk-ZEBVAIJJ.js.map +1 -0
  48. package/dist/devtools/index.d.ts +536 -0
  49. package/dist/devtools/index.js +15 -0
  50. package/dist/devtools/index.js.map +1 -0
  51. package/dist/environments/index.d.ts +93 -0
  52. package/dist/environments/index.js +382 -0
  53. package/dist/environments/index.js.map +1 -0
  54. package/dist/hotspots/index.d.ts +111 -0
  55. package/dist/hotspots/index.js +285 -0
  56. package/dist/hotspots/index.js.map +1 -0
  57. package/dist/index.d.ts +944 -0
  58. package/dist/index.js +15 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/node/index.cjs +5115 -0
  61. package/dist/node/index.cjs.map +1 -0
  62. package/dist/node/index.d.cts +4862 -0
  63. package/dist/node/index.d.ts +576 -0
  64. package/dist/node/index.js +1279 -0
  65. package/dist/node/index.js.map +1 -0
  66. package/dist/prepare-C5qVvaw4.d.ts +112 -0
  67. package/dist/presets/index.d.ts +526 -0
  68. package/dist/presets/index.js +14 -0
  69. package/dist/presets/index.js.map +1 -0
  70. package/dist/r3f/index.d.ts +159 -0
  71. package/dist/r3f/index.js +592 -0
  72. package/dist/r3f/index.js.map +1 -0
  73. package/dist/room-S5TLCK4F.js +9 -0
  74. package/dist/room-S5TLCK4F.js.map +1 -0
  75. package/dist/rooms.gen-vhj6sFW5.d.ts +1434 -0
  76. package/dist/session-YCQS4MRH.js +9 -0
  77. package/dist/session-YCQS4MRH.js.map +1 -0
  78. package/dist/shapes/index.d.ts +222 -0
  79. package/dist/shapes/index.js +836 -0
  80. package/dist/shapes/index.js.map +1 -0
  81. package/dist/testRun-BIPXaVPb.d.ts +1130 -0
  82. package/dist/timeline-ChwgD7bT.d.ts +470 -0
  83. package/dist/tsl/index.d.ts +165 -0
  84. package/dist/tsl/index.js +310 -0
  85. package/dist/tsl/index.js.map +1 -0
  86. package/package.json +170 -4
@@ -0,0 +1,1412 @@
1
+ import { Bakery3Error } from './chunk-3G5QL4F4.js';
2
+ import { Box3, Vector3, LinearSRGBColorSpace, BufferAttribute, BufferGeometry, PlaneGeometry, MeshBasicMaterial, DoubleSide, Mesh, MeshStandardMaterial, Vector2, MultiplyBlending } from 'three';
3
+
4
+ // src/capture/ids.ts
5
+ var ID_KEY = "bakery3Id";
6
+ function idOf(target) {
7
+ const declared = target.userData?.[ID_KEY];
8
+ return typeof declared === "string" && declared.length > 0 ? declared : target.uuid;
9
+ }
10
+ var clonedFrom = /* @__PURE__ */ new WeakMap();
11
+ function markClone(clone, source) {
12
+ clonedFrom.set(clone, clonedFrom.get(source) ?? source);
13
+ }
14
+ var sourceOf = (material) => clonedFrom.get(material) ?? material;
15
+ function isDeclared(target) {
16
+ const declared = target.userData?.[ID_KEY];
17
+ return typeof declared === "string" && declared.length > 0 && declared !== target.uuid;
18
+ }
19
+ function assertUniqueIds(root) {
20
+ const objects = /* @__PURE__ */ new Map();
21
+ const materials = /* @__PURE__ */ new Map();
22
+ root.traverse((object) => {
23
+ if (isDeclared(object)) {
24
+ const id = idOf(object);
25
+ const first = objects.get(id);
26
+ if (first && first !== object)
27
+ throw duplicate("object", id, [scenePath(first), scenePath(object)]);
28
+ objects.set(id, object);
29
+ }
30
+ const mesh = object;
31
+ if (!mesh.isMesh || !mesh.material) return;
32
+ const list = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
33
+ for (const material of list) {
34
+ if (!material || !isDeclared(material)) continue;
35
+ const id = idOf(material);
36
+ const first = materials.get(id);
37
+ if (first && sourceOf(first.material) !== sourceOf(material)) {
38
+ throw duplicate("material", id, [
39
+ `${scenePath(first.owner)} \u2192 ${first.material.name || first.material.type}`,
40
+ `${scenePath(object)} \u2192 ${material.name || material.type}`
41
+ ]);
42
+ }
43
+ materials.set(id, { material, owner: object });
44
+ }
45
+ });
46
+ }
47
+ function scenePath(object) {
48
+ const parts = [];
49
+ let current = object;
50
+ while (current && current.parent) {
51
+ parts.unshift(`<${current.name || current.type}>`);
52
+ current = current.parent;
53
+ }
54
+ return parts.join("/");
55
+ }
56
+ function duplicate(kind, id, where) {
57
+ return new Bakery3Error({
58
+ code: "InvalidRequest",
59
+ message: `Two ${kind}s declare userData.bakery3Id "${id}".`,
60
+ why: `Ids are how the renderer matches your live ${kind}s to the ones in the exported scene. With two claiming "${id}", one of them would silently take the other's transform, visibility or lightmap.`,
61
+ fix: "Give one of them a different userData.bakery3Id, or remove it and let the SDK use the uuid.",
62
+ path: where.join(" and ")
63
+ });
64
+ }
65
+
66
+ // src/capture/scene.ts
67
+ function readUserData(object) {
68
+ const data = object.userData?.bakery3;
69
+ return data && typeof data === "object" ? data : {};
70
+ }
71
+ function stampIds(root) {
72
+ assertUniqueIds(root);
73
+ const touched = [];
74
+ const stamp = (target) => {
75
+ if (target.userData?.[ID_KEY] !== void 0) return;
76
+ target.userData = target.userData ?? {};
77
+ target.userData[ID_KEY] = target.uuid;
78
+ touched.push(target);
79
+ };
80
+ root.traverse((object) => {
81
+ stamp(object);
82
+ const mesh = object;
83
+ if (!mesh.isMesh || !mesh.material) return;
84
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
85
+ for (const material of materials) if (material) stamp(material);
86
+ });
87
+ return () => {
88
+ for (const target of touched) delete target.userData[ID_KEY];
89
+ };
90
+ }
91
+ function shouldExclude(object) {
92
+ for (let node = object; node; node = node.parent) {
93
+ if (excludedItself(node)) return true;
94
+ }
95
+ return false;
96
+ }
97
+ function excludedItself(object) {
98
+ if (readUserData(object).exclude) return true;
99
+ const roomPreset = object.userData?.bakery3Preset?.roomPreset;
100
+ if (roomPreset) return !roomPreset.standIn;
101
+ if (/Helper$/.test(object.type)) return true;
102
+ if (object.type === "TransformControls") return true;
103
+ return false;
104
+ }
105
+ function captureRuntime(root, productState) {
106
+ const hiddenObjectIds = [];
107
+ const instances = [];
108
+ const morphs = [];
109
+ const poses = [];
110
+ root.traverse((object) => {
111
+ if (shouldExclude(object)) return;
112
+ if (!object.visible && !readUserData(object).renderOnly) {
113
+ hiddenObjectIds.push(idOf(object));
114
+ return;
115
+ }
116
+ const mesh = object;
117
+ if (mesh.isInstancedMesh) {
118
+ instances.push({
119
+ objectId: idOf(object),
120
+ count: mesh.count,
121
+ matrices: Array.from(mesh.instanceMatrix.array)
122
+ });
123
+ }
124
+ if (mesh.isMesh && mesh.morphTargetInfluences?.length) {
125
+ if (mesh.morphTargetInfluences.some((v) => v > 1e-6)) {
126
+ morphs.push({
127
+ objectId: idOf(object),
128
+ influences: Array.from(mesh.morphTargetInfluences)
129
+ });
130
+ }
131
+ }
132
+ if (mesh.isSkinnedMesh && mesh.skeleton) {
133
+ poses.push({
134
+ objectId: idOf(object),
135
+ boneMatrices: mesh.skeleton.bones.map((bone) => {
136
+ bone.updateMatrixWorld();
137
+ return Array.from(bone.matrixWorld.elements);
138
+ })
139
+ });
140
+ }
141
+ });
142
+ return {
143
+ ...hiddenObjectIds.length ? { hiddenObjectIds } : {},
144
+ ...instances.length ? { instances } : {},
145
+ ...morphs.length ? { morphs } : {},
146
+ ...poses.length ? { poses } : {},
147
+ ...productState ? { productState } : {}
148
+ };
149
+ }
150
+ function captureLights(root) {
151
+ const lights = [];
152
+ root.traverse((object) => {
153
+ const light = object;
154
+ if (!light.isLight || shouldExclude(object) || !object.visible && !readUserData(object).renderOnly)
155
+ return;
156
+ light.updateMatrixWorld();
157
+ const position = worldPosition(light);
158
+ const color = `#${light.color.getHexString()}`;
159
+ const base = {
160
+ objectId: idOf(object),
161
+ color,
162
+ intensity: light.intensity,
163
+ castShadow: light.castShadow ?? false
164
+ };
165
+ if (light.isAmbientLight) {
166
+ return;
167
+ }
168
+ if (light.isDirectionalLight) {
169
+ light.target?.updateMatrixWorld();
170
+ const angleDeg = readUserData(object).angleDeg;
171
+ lights.push({
172
+ ...base,
173
+ type: "directional",
174
+ position,
175
+ target: light.target ? worldPosition(light.target) : [0, 0, 0],
176
+ ...typeof angleDeg === "number" && angleDeg > 0 ? { angleDeg } : {}
177
+ });
178
+ return;
179
+ }
180
+ if (light.isSpotLight) {
181
+ light.target?.updateMatrixWorld();
182
+ lights.push({
183
+ ...base,
184
+ type: "spot",
185
+ position,
186
+ target: light.target ? worldPosition(light.target) : [0, 0, 0],
187
+ angle: light.angle,
188
+ penumbra: light.penumbra,
189
+ distance: light.distance,
190
+ decay: light.decay
191
+ });
192
+ return;
193
+ }
194
+ if (light.isPointLight) {
195
+ lights.push({
196
+ ...base,
197
+ type: "point",
198
+ position,
199
+ distance: light.distance,
200
+ decay: light.decay
201
+ });
202
+ return;
203
+ }
204
+ if (light.isRectAreaLight) {
205
+ const facing = worldZ(light);
206
+ lights.push({
207
+ ...base,
208
+ type: "area",
209
+ position,
210
+ target: [
211
+ position[0] - facing[0],
212
+ position[1] - facing[1],
213
+ position[2] - facing[2]
214
+ ],
215
+ // three's RectAreaLight defaults are 10x10. Falling back to 1x1 would
216
+ // silently make it a hundred times dimmer than the browser drew it.
217
+ size: { width: light.width ?? 10, height: light.height ?? 10 }
218
+ });
219
+ return;
220
+ }
221
+ if (light.isHemisphereLight) {
222
+ lights.push({
223
+ ...base,
224
+ type: "hemisphere",
225
+ position,
226
+ groundColor: `#${light.groundColor?.getHexString() ?? "000000"}`
227
+ });
228
+ }
229
+ });
230
+ return lights;
231
+ }
232
+ function captureEnvironmentSettings(scene) {
233
+ const anyScene = scene;
234
+ return {
235
+ ...anyScene.environmentIntensity !== void 0 ? { intensity: anyScene.environmentIntensity } : {},
236
+ ...anyScene.environmentRotation ? {
237
+ rotation: [
238
+ anyScene.environmentRotation.x,
239
+ anyScene.environmentRotation.y,
240
+ anyScene.environmentRotation.z
241
+ ]
242
+ } : {}
243
+ };
244
+ }
245
+ function captureFog(scene) {
246
+ const fog = scene.fog;
247
+ if (!fog) return {};
248
+ if (fog.isFogExp2) {
249
+ const density = fog.density ?? 0;
250
+ if (!(density > 0) || !Number.isFinite(density)) return {};
251
+ return {
252
+ fog: { color: `#${fog.color?.getHexString() ?? "ffffff"}`, density },
253
+ issue: {
254
+ code: "fog_traced_as_volume",
255
+ severity: "info",
256
+ message: "The scene has fog. The render traces it as a volume that scatters light, so lights glow and throw beams in it; the viewport only fades surfaces with distance.",
257
+ docs: "https://bakery3.com/docs/environment#fog"
258
+ }
259
+ };
260
+ }
261
+ if (fog.isFog) {
262
+ return {
263
+ issue: {
264
+ code: "fog_linear_unsupported",
265
+ severity: "warning",
266
+ message: "The scene has a linear `Fog`, which is not rendered: a near and a far plane describe no volume. Use `FogExp2` to have the fog in the render.",
267
+ docs: "https://bakery3.com/docs/environment#fog"
268
+ }
269
+ };
270
+ }
271
+ return {};
272
+ }
273
+ function worldPosition(object) {
274
+ const e = object.matrixWorld.elements;
275
+ return [e[12], e[13], e[14]];
276
+ }
277
+ function worldZ(object) {
278
+ const e = object.matrixWorld.elements;
279
+ const length = Math.hypot(e[8], e[9], e[10]) || 1;
280
+ return [e[8] / length, e[9] / length, e[10] / length];
281
+ }
282
+
283
+ // src/bake/candidates.ts
284
+ function bakeCandidates(options) {
285
+ const candidates = [];
286
+ options.scene.traverse((object) => {
287
+ if (shouldExclude(object)) return;
288
+ const mesh = object;
289
+ if (!mesh.isMesh) return;
290
+ const base = {
291
+ objectId: idOf(object),
292
+ object: mesh,
293
+ name: object.name || object.type,
294
+ path: scenePath(object)
295
+ };
296
+ const refusal = refuse(mesh);
297
+ if (refusal) {
298
+ candidates.push({
299
+ ...base,
300
+ bakeable: false,
301
+ reason: refusal,
302
+ defaultInclude: false
303
+ });
304
+ return;
305
+ }
306
+ const advisory = advise(mesh);
307
+ candidates.push({
308
+ ...base,
309
+ bakeable: true,
310
+ ...advisory ? { reason: advisory } : {},
311
+ defaultInclude: !advisory
312
+ });
313
+ });
314
+ return candidates;
315
+ }
316
+ function defaultIncludeIds(candidates) {
317
+ return candidates.filter((candidate) => candidate.bakeable && candidate.defaultInclude).map((candidate) => candidate.objectId);
318
+ }
319
+ function hasLightMapSlot(material) {
320
+ return "lightMap" in material;
321
+ }
322
+ function materialsOf(mesh) {
323
+ const material = mesh.material;
324
+ if (!material) return [];
325
+ return (Array.isArray(material) ? material : [material]).filter(Boolean);
326
+ }
327
+ function refuse(mesh) {
328
+ if (mesh.isInstancedMesh) {
329
+ return {
330
+ code: "BakeInstancedMesh",
331
+ message: `"${mesh.name || mesh.type}" is an InstancedMesh, so it cannot take a lightmap.`,
332
+ why: "Every instance shares one geometry and one material, so it would also share one lightmap: the chair in the corner would be lit like the chair by the window.",
333
+ fix: "Convert the instances you want baked into individual meshes, or leave them lit in realtime on top of the baked room."
334
+ };
335
+ }
336
+ if (mesh.isBatchedMesh) {
337
+ return {
338
+ code: "BakeBatchedMesh",
339
+ message: `"${mesh.name || mesh.type}" is a BatchedMesh, so it cannot take a lightmap.`,
340
+ why: "A BatchedMesh packs many objects into one draw with one material; a per-object lightmap has nowhere to attach.",
341
+ fix: "Bake the geometries as individual meshes, or leave this one lit in realtime."
342
+ };
343
+ }
344
+ if (mesh.isSkinnedMesh) {
345
+ return {
346
+ code: "BakeSkinnedMesh",
347
+ message: `"${mesh.name || mesh.type}" is skinned, so its lighting cannot be baked.`,
348
+ why: "A lightmap is fixed to the surface in its bind pose. The moment the skeleton moves, the baked shadows stay where the mesh used to be.",
349
+ fix: "Leave animated characters lit in realtime. They will still receive the baked room through your own lights."
350
+ };
351
+ }
352
+ const materials = materialsOf(mesh);
353
+ if (!materials.length) {
354
+ return {
355
+ code: "BakeNoMaterial",
356
+ message: `"${mesh.name || mesh.type}" has no material.`,
357
+ why: "A lightmap is a material slot; without a material there is nothing to set.",
358
+ fix: "Give the mesh a MeshStandardMaterial (or any material with a lightMap slot)."
359
+ };
360
+ }
361
+ const unsupported = materials.filter(
362
+ (material) => !hasLightMapSlot(material)
363
+ );
364
+ if (unsupported.length) {
365
+ const names = unsupported.map((material) => `${material.name || material.type}`).join(", ");
366
+ return {
367
+ code: "BakeNoLightMapSlot",
368
+ message: `"${mesh.name || mesh.type}" uses ${names}, which has no lightMap slot.`,
369
+ why: "three multiplies a lightmap into irradiance inside its own shading model. A raw ShaderMaterial writes its own color, so there is nothing to multiply into.",
370
+ fix: "Use MeshStandardMaterial / MeshPhysicalMaterial (or their Node variants), or sample the lightmap yourself in your shader."
371
+ };
372
+ }
373
+ return void 0;
374
+ }
375
+ function advise(mesh) {
376
+ const userData = mesh.userData?.bakery3 ?? {};
377
+ if (userData.bake === false) {
378
+ return {
379
+ code: "BakeOptedOut",
380
+ message: `"${mesh.name || mesh.type}" is marked userData.bakery3.bake = false.`,
381
+ why: "You asked for this one to stay out of bakes.",
382
+ fix: "Remove the flag, or tick the box to include it this once."
383
+ };
384
+ }
385
+ if (!mesh.visible || hasHiddenAncestor(mesh)) {
386
+ return {
387
+ code: "BakeObjectHidden",
388
+ message: `"${mesh.name || mesh.type}" is hidden right now.`,
389
+ why: "Hidden objects are hidden in the bake too, so its lightmap would come back black, and the render it is hidden from does not need one.",
390
+ fix: "Make it visible before baking if this variant is one you ship."
391
+ };
392
+ }
393
+ const glassy = materialsOf(mesh).filter(isTransmissive);
394
+ if (glassy.length) {
395
+ return {
396
+ code: "BakeTransmissiveMaterial",
397
+ message: `"${mesh.name || mesh.type}" is transparent or transmissive.`,
398
+ why: "A lightmap only modulates diffuse irradiance. Glass is almost entirely refraction and specular, so the baked map costs texels and changes nothing you can see.",
399
+ fix: "Leave it out: it will still refract the baked room around it. Tick the box if it is a frosted surface with real diffuse."
400
+ };
401
+ }
402
+ return void 0;
403
+ }
404
+ function hasHiddenAncestor(object) {
405
+ for (let node = object.parent; node; node = node.parent) {
406
+ if (!node.visible) return true;
407
+ }
408
+ return false;
409
+ }
410
+ function isTransmissive(material) {
411
+ const surface = material;
412
+ if ((surface.transmission ?? 0) > 0) return true;
413
+ if (material.transparent && (surface.opacity ?? 1) < 1) return true;
414
+ return false;
415
+ }
416
+ function shadowMesh(shadow, texture) {
417
+ const geometry = new PlaneGeometry(shadow.width, shadow.height);
418
+ geometry.rotateX(-Math.PI / 2);
419
+ const material = new MeshBasicMaterial({
420
+ map: texture,
421
+ blending: MultiplyBlending,
422
+ // Required: both renderers refuse MultiplyBlending without it and draw
423
+ // the plane opaque instead, so the ratio, near 1 almost everywhere, lands
424
+ // as a white square. With it the blend is `src·dst + dst·(1 − a)`, and
425
+ // the ratio has no alpha channel (a = 1), so it is exactly
426
+ // `floor · ratio`.
427
+ premultipliedAlpha: true,
428
+ transparent: true,
429
+ // It contributes nothing to depth: it is a mark on a surface that is
430
+ // already there, and writing depth would make it occlude the product
431
+ // standing on it.
432
+ depthWrite: false,
433
+ // Lit by nothing, tone mapped by nothing: see the header.
434
+ toneMapped: false,
435
+ side: DoubleSide
436
+ });
437
+ const mesh = new Mesh(geometry, material);
438
+ mesh.position.set(
439
+ shadow.center[0],
440
+ shadow.center[1] + shadow.lift,
441
+ shadow.center[2]
442
+ );
443
+ mesh.renderOrder = 1;
444
+ mesh.castShadow = false;
445
+ mesh.receiveShadow = false;
446
+ mesh.name = "bakery3 shadow";
447
+ mesh.userData.bakery3 = { exclude: true, shadow: true };
448
+ return mesh;
449
+ }
450
+ function configureShadow(texture) {
451
+ texture.colorSpace = LinearSRGBColorSpace;
452
+ texture.flipY = false;
453
+ texture.generateMipmaps = false;
454
+ texture.needsUpdate = true;
455
+ return texture;
456
+ }
457
+ function shadowTouch(scene, shadow, texture) {
458
+ const mesh = shadowMesh(shadow, texture);
459
+ return {
460
+ mesh,
461
+ on() {
462
+ scene.add(mesh);
463
+ },
464
+ off() {
465
+ scene.remove(mesh);
466
+ },
467
+ dispose() {
468
+ mesh.geometry.dispose();
469
+ mesh.material.dispose();
470
+ }
471
+ };
472
+ }
473
+
474
+ // src/bake/shading.ts
475
+ var HOOKS = [
476
+ "onBeforeCompile",
477
+ "customProgramCacheKey",
478
+ "setupLightMap",
479
+ "setupLightingModel",
480
+ "_bakery3LightMapNode"
481
+ ];
482
+ function bakedShadingTouch(material) {
483
+ const previous = /* @__PURE__ */ new Map();
484
+ let installed = false;
485
+ const target = material;
486
+ return {
487
+ on() {
488
+ if (installed) return;
489
+ for (const key of HOOKS)
490
+ previous.set(key, Object.getOwnPropertyDescriptor(target, key));
491
+ installBakedShading(material);
492
+ installed = true;
493
+ },
494
+ off() {
495
+ if (!installed) return;
496
+ for (const key of HOOKS) {
497
+ const descriptor = previous.get(key);
498
+ if (descriptor) Object.defineProperty(target, key, descriptor);
499
+ else delete target[key];
500
+ }
501
+ installed = false;
502
+ }
503
+ };
504
+ }
505
+ var ANCHOR = "#include <lights_fragment_maps>";
506
+ var FALLBACK_ANCHOR = "#include <lights_fragment_end>";
507
+ var REPLACE_DIFFUSE_GLSL = `
508
+ #if defined( USE_LIGHTMAP ) && defined( RE_IndirectDiffuse )
509
+ // Bakery3: the lightmap is the whole diffuse irradiance (direct, ambient
510
+ // and environment included), so nothing else may add diffuse. Specular from
511
+ // the lamps and the environment stays live.
512
+ {
513
+ vec3 bakery3Irradiance = texture2D( lightMap, vLightMapUv ).rgb * lightMapIntensity;
514
+ reflectedLight.directDiffuse = vec3( 0.0 );
515
+ #if defined( STANDARD )
516
+ irradiance = vec3( 0.0 );
517
+ iblIrradiance = bakery3Irradiance;
518
+ #else
519
+ irradiance = bakery3Irradiance;
520
+ #endif
521
+ }
522
+ #endif
523
+ `;
524
+ function patchFragmentShader(source) {
525
+ if (source.includes(ANCHOR))
526
+ return source.replace(ANCHOR, `${ANCHOR}
527
+ ${REPLACE_DIFFUSE_GLSL}`);
528
+ if (source.includes(FALLBACK_ANCHOR)) {
529
+ return source.replace(
530
+ FALLBACK_ANCHOR,
531
+ `${REPLACE_DIFFUSE_GLSL}
532
+ ${FALLBACK_ANCHOR}`
533
+ );
534
+ }
535
+ return source;
536
+ }
537
+ var CACHE_KEY_TAG = "|bakery3:lightmap-replace";
538
+ function installBakedShading(material) {
539
+ const target = material;
540
+ const previousCompile = target.onBeforeCompile;
541
+ const ownCacheKey = Object.prototype.hasOwnProperty.call(
542
+ material,
543
+ "customProgramCacheKey"
544
+ ) ? target.customProgramCacheKey : null;
545
+ target.onBeforeCompile = function(shader, renderer) {
546
+ previousCompile.call(this, shader, renderer);
547
+ shader.fragmentShader = patchFragmentShader(shader.fragmentShader);
548
+ };
549
+ target.customProgramCacheKey = function() {
550
+ const base = ownCacheKey ? ownCacheKey.call(this) : previousCompile.toString();
551
+ return `${base}${CACHE_KEY_TAG}`;
552
+ };
553
+ const node = material;
554
+ node.setupLightMap = function(builder) {
555
+ const proto = Object.getPrototypeOf(this);
556
+ const irradianceNode = proto.setupLightMap ? proto.setupLightMap.call(this, builder) : null;
557
+ this._bakery3LightMapNode = irradianceNode?.node ?? null;
558
+ return irradianceNode;
559
+ };
560
+ node.setupLightingModel = function(builder) {
561
+ const proto = Object.getPrototypeOf(this);
562
+ const model = proto.setupLightingModel ? proto.setupLightingModel.call(this, builder) : null;
563
+ if (!model) return model;
564
+ return bakedLightingModel(model, this._bakery3LightMapNode ?? null);
565
+ };
566
+ }
567
+ function bakedLightingModel(model, lightMap) {
568
+ const wrapped = Object.create(model);
569
+ if (model.direct) {
570
+ const direct = model.direct;
571
+ wrapped.direct = function(input, builder) {
572
+ return direct.call(
573
+ this,
574
+ { ...input, reflectedLight: withoutDiffuse(input.reflectedLight) },
575
+ builder
576
+ );
577
+ };
578
+ }
579
+ if (model.directRectArea) {
580
+ const directRectArea = model.directRectArea;
581
+ wrapped.directRectArea = function(input, builder) {
582
+ return directRectArea.call(
583
+ this,
584
+ { ...input, reflectedLight: withoutDiffuse(input.reflectedLight) },
585
+ builder
586
+ );
587
+ };
588
+ }
589
+ if (model.indirect) {
590
+ const indirect = model.indirect;
591
+ wrapped.indirect = function(builder) {
592
+ const context = builder.context;
593
+ if (lightMap && context.irradiance && context.iblIrradiance) {
594
+ const zero = context.irradiance.mul(0);
595
+ if (typeof model.computeMultiscattering === "function") {
596
+ context.iblIrradiance.assign(lightMap);
597
+ context.irradiance.assign(zero);
598
+ } else {
599
+ context.irradiance.assign(lightMap);
600
+ }
601
+ }
602
+ return indirect.call(this, builder);
603
+ };
604
+ }
605
+ return wrapped;
606
+ }
607
+ function withoutDiffuse(reflectedLight) {
608
+ return {
609
+ ...reflectedLight,
610
+ directDiffuse: reflectedLight.directDiffuse.mul(0).toVar()
611
+ };
612
+ }
613
+
614
+ // src/bake/proxies.ts
615
+ function buildProxyTouches(options) {
616
+ const touches = [];
617
+ const applied = [];
618
+ const replaced = [];
619
+ const skipped = [];
620
+ for (const { proxy, textures } of options.proxies) {
621
+ const stoodFor = proxy.replaces.flatMap((id) => options.byId.get(id) ?? []);
622
+ if (!stoodFor.length) {
623
+ skipped.push({
624
+ objectId: `simplify[${proxy.index}]`,
625
+ ...proxy.name ? { name: proxy.name } : {},
626
+ code: "BakeStandInUnmatched",
627
+ message: `None of the objects "${proxy.name ?? `stand-in ${proxy.index}`}" replaces are in the scene.`,
628
+ why: "A stand-in goes in where the objects it was made from come out, and they are gone.",
629
+ fix: "Bake again against the scene as it stands."
630
+ });
631
+ continue;
632
+ }
633
+ const mesh = proxy.kind === "billboard" && textures.kind === "billboard" ? billboardMesh(proxy, textures.map) : proxy.kind === "mesh" && textures.kind === "mesh" ? standInMesh(proxy, textures, options.intensity, options.shading) : proxy.kind === "decimated" && textures.kind === "decimated" ? decimatedMesh(proxy, textures.light, stoodFor[0], options) : null;
634
+ if (!mesh) continue;
635
+ mesh.name = `bakery3 stand-in ${proxy.index}${proxy.name ? ` (${proxy.name})` : ""}`;
636
+ mesh.userData.bakery3 = { exclude: true, standIn: proxy.index };
637
+ touches.push(swapTouch(options.scene, mesh, stoodFor));
638
+ applied.push(proxy.index);
639
+ replaced.push(...proxy.replaces.filter((id) => options.byId.has(id)));
640
+ }
641
+ return { touches, applied, replaced, skipped };
642
+ }
643
+ function standInMesh(proxy, textures, intensity, shading) {
644
+ const geometry = new BufferGeometry();
645
+ const source = proxy.geometry;
646
+ geometry.setAttribute(
647
+ "position",
648
+ new BufferAttribute(new Float32Array(source.position), 3)
649
+ );
650
+ geometry.setAttribute(
651
+ "normal",
652
+ new BufferAttribute(new Float32Array(source.normal), 3)
653
+ );
654
+ const uv = new Float32Array(source.uv);
655
+ geometry.setAttribute("uv", new BufferAttribute(uv, 2));
656
+ geometry.setAttribute("uv1", new BufferAttribute(uv, 2));
657
+ if (source.tangent)
658
+ geometry.setAttribute(
659
+ "tangent",
660
+ new BufferAttribute(new Float32Array(source.tangent), 4)
661
+ );
662
+ const count = source.position.length / 3;
663
+ geometry.setIndex(
664
+ new BufferAttribute(
665
+ count > 65535 ? new Uint32Array(source.index) : new Uint16Array(source.index),
666
+ 1
667
+ )
668
+ );
669
+ geometry.computeBoundingSphere();
670
+ geometry.computeBoundingBox();
671
+ const material = new MeshStandardMaterial({
672
+ map: textures.color,
673
+ normalMap: textures.normal,
674
+ normalScale: new Vector2(1, 1),
675
+ roughness: proxy.roughness,
676
+ metalness: proxy.metalness,
677
+ lightMap: textures.light,
678
+ lightMapIntensity: intensity
679
+ });
680
+ if (shading === "replace") bakedShadingTouch(material).on();
681
+ return new Mesh(geometry, material);
682
+ }
683
+ function decimatedMesh(proxy, light, replaced, options) {
684
+ const geometry = new BufferGeometry();
685
+ const source = proxy.geometry;
686
+ geometry.setAttribute(
687
+ "position",
688
+ new BufferAttribute(new Float32Array(source.position), 3)
689
+ );
690
+ geometry.setAttribute(
691
+ "normal",
692
+ new BufferAttribute(new Float32Array(source.normal), 3)
693
+ );
694
+ geometry.setAttribute(
695
+ "uv",
696
+ new BufferAttribute(new Float32Array(source.uv), 2)
697
+ );
698
+ if (source.uv1)
699
+ geometry.setAttribute(
700
+ "uv1",
701
+ new BufferAttribute(new Float32Array(source.uv1), 2)
702
+ );
703
+ if (source.color)
704
+ geometry.setAttribute(
705
+ "color",
706
+ new BufferAttribute(new Float32Array(source.color), 3)
707
+ );
708
+ const count = source.position.length / 3;
709
+ geometry.setIndex(
710
+ new BufferAttribute(
711
+ count > 65535 ? new Uint32Array(source.index) : new Uint16Array(source.index),
712
+ 1
713
+ )
714
+ );
715
+ for (const group of source.groups ?? [])
716
+ geometry.addGroup(group.start, group.count, group.materialIndex);
717
+ geometry.computeBoundingSphere();
718
+ geometry.computeBoundingBox();
719
+ let lightmap = light;
720
+ if (proxy.lightmapChannel !== light.channel) {
721
+ lightmap = light.clone();
722
+ lightmap.channel = proxy.lightmapChannel;
723
+ lightmap.needsUpdate = true;
724
+ }
725
+ const probed = options.probe && options.probe.meshes.has(replaced) ? options.probe.texture : void 0;
726
+ const dress = (material2) => {
727
+ const clone = material2.clone();
728
+ if (clone.vertexColors && !source.color) {
729
+ clone.vertexColors = false;
730
+ }
731
+ if ("lightMap" in clone) {
732
+ clone.lightMap = lightmap;
733
+ clone.lightMapIntensity = options.intensity;
734
+ if (probed && "envMap" in clone) clone.envMap = probed;
735
+ if (options.shading === "replace")
736
+ bakedShadingTouch(clone).on();
737
+ }
738
+ return clone;
739
+ };
740
+ const material = Array.isArray(replaced.material) ? replaced.material.map(dress) : dress(replaced.material);
741
+ const mesh = new Mesh(geometry, material);
742
+ mesh.userData.bakery3LightmapView = lightmap === light ? void 0 : lightmap;
743
+ return mesh;
744
+ }
745
+ function billboardMesh(proxy, map) {
746
+ const geometry = new PlaneGeometry(proxy.width, proxy.height);
747
+ const material = new MeshBasicMaterial({
748
+ map,
749
+ // A cutout, not a blend: a blended plant sorts against the glass behind
750
+ // it and loses, and the edge of a leaf is a hard edge anyway.
751
+ alphaTest: 0.5,
752
+ side: DoubleSide
753
+ });
754
+ const mesh = new Mesh(geometry, material);
755
+ mesh.position.set(proxy.center[0], proxy.center[1], proxy.center[2]);
756
+ mesh.castShadow = false;
757
+ mesh.receiveShadow = false;
758
+ mesh.frustumCulled = false;
759
+ const eye = new Vector3();
760
+ const here = new Vector3();
761
+ mesh.onBeforeRender = (_renderer, _scene, camera) => {
762
+ eye.setFromMatrixPosition(camera.matrixWorld);
763
+ here.setFromMatrixPosition(mesh.matrixWorld);
764
+ const turn = Math.atan2(eye.x - here.x, eye.z - here.z);
765
+ if (Math.abs(turn - mesh.rotation.y) < 1e-4) return;
766
+ mesh.rotation.set(0, turn, 0);
767
+ mesh.updateMatrix();
768
+ mesh.updateMatrixWorld(true);
769
+ };
770
+ return mesh;
771
+ }
772
+ function swapTouch(scene, mesh, stoodFor) {
773
+ const was = /* @__PURE__ */ new Map();
774
+ return {
775
+ on() {
776
+ for (const object of stoodFor) {
777
+ was.set(object, object.visible);
778
+ object.visible = false;
779
+ }
780
+ scene.add(mesh);
781
+ },
782
+ off() {
783
+ scene.remove(mesh);
784
+ for (const [object, visible] of was) object.visible = visible;
785
+ was.clear();
786
+ },
787
+ dispose() {
788
+ mesh.geometry.dispose();
789
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material])
790
+ material.dispose();
791
+ mesh.userData.bakery3LightmapView?.dispose();
792
+ }
793
+ };
794
+ }
795
+
796
+ // src/bake/apply.ts
797
+ var bakeWritten = /* @__PURE__ */ new WeakSet();
798
+ function isBakeWrittenUv(attribute) {
799
+ return Boolean(attribute && bakeWritten.has(attribute));
800
+ }
801
+ function buildPatch(options) {
802
+ const { scene, intensity } = options;
803
+ const shading = options.shading ?? "replace";
804
+ const byId = indexObjects(scene);
805
+ const geometryUsers = /* @__PURE__ */ new Map();
806
+ const materialUsers = /* @__PURE__ */ new Map();
807
+ scene.traverse((object) => {
808
+ const mesh = object;
809
+ if (!mesh.isMesh) return;
810
+ if (mesh.geometry) {
811
+ const users = geometryUsers.get(mesh.geometry) ?? [];
812
+ users.push(mesh);
813
+ geometryUsers.set(mesh.geometry, users);
814
+ }
815
+ for (const material of materialsOf(mesh)) {
816
+ const users = materialUsers.get(material) ?? /* @__PURE__ */ new Set();
817
+ users.add(mesh);
818
+ materialUsers.set(material, users);
819
+ }
820
+ });
821
+ const uvById = /* @__PURE__ */ new Map();
822
+ for (const entry of options.objects)
823
+ uvById.set(entry.baked.objectId, entry.baked.uv);
824
+ const touches = [];
825
+ const uvWrites = /* @__PURE__ */ new Map();
826
+ const applied = [];
827
+ const unmatched = [];
828
+ const skipped = [];
829
+ const meshes = /* @__PURE__ */ new Set();
830
+ for (const { baked, texture } of options.objects) {
831
+ const found = byId.get(baked.objectId);
832
+ if (!found || found.length === 0) {
833
+ unmatched.push(baked.objectId);
834
+ continue;
835
+ }
836
+ if (found.length > 1) {
837
+ skipped.push({
838
+ objectId: baked.objectId,
839
+ ...baked.name ? { name: baked.name } : {},
840
+ code: "BakeAmbiguousObjectId",
841
+ message: `${found.length} objects in the scene answer to the id "${baked.objectId}".`,
842
+ why: "Applying the lightmap to one of them would be a coin toss, and applying it to all of them would light the wrong surfaces.",
843
+ fix: "Give each object a distinct userData.bakery3Id."
844
+ });
845
+ continue;
846
+ }
847
+ const object = found[0];
848
+ const mesh = object;
849
+ if (!mesh.isMesh || mesh.isInstancedMesh || mesh.isSkinnedMesh) {
850
+ skipped.push({
851
+ objectId: baked.objectId,
852
+ ...baked.name ? { name: baked.name } : {},
853
+ code: "BakeObjectChanged",
854
+ message: `"${baked.name ?? baked.objectId}" is a ${object.type} now, not a plain mesh.`,
855
+ why: "The bake was made against a mesh that could take a lightmap. Whatever is under that id today cannot.",
856
+ fix: "Bake again against the scene as it stands."
857
+ });
858
+ continue;
859
+ }
860
+ const plan = planGeometry({
861
+ mesh,
862
+ uv: baked.uv,
863
+ geometryUsers,
864
+ uvById,
865
+ objectId: baked.objectId,
866
+ ...baked.name !== void 0 ? { name: baked.name } : {}
867
+ });
868
+ if ("skip" in plan) {
869
+ skipped.push(plan.skip);
870
+ continue;
871
+ }
872
+ let lightmap = texture;
873
+ if (plan.kind === "in-place") {
874
+ if (!uvWrites.has(mesh.geometry)) {
875
+ uvWrites.set(mesh.geometry, true);
876
+ touches.push(uvAttributeTouch(mesh.geometry, plan.attribute));
877
+ }
878
+ } else if (plan.kind === "channel") {
879
+ lightmap = onChannel(texture, plan.channel, touches);
880
+ } else {
881
+ touches.push(geometrySwapTouch(mesh, plan.geometry));
882
+ }
883
+ touches.push(
884
+ ...materialTouches(mesh, lightmap, intensity, materialUsers, shading)
885
+ );
886
+ meshes.add(mesh);
887
+ applied.push(baked.objectId);
888
+ }
889
+ const probed = [];
890
+ if (options.probe) {
891
+ const targets = new Set(options.probe.meshes);
892
+ const written = /* @__PURE__ */ new Set();
893
+ for (const mesh of options.probe.meshes) {
894
+ const probeTouchesFor = probeTouches(
895
+ mesh,
896
+ options.probe.texture,
897
+ materialUsers,
898
+ targets,
899
+ written
900
+ );
901
+ if (!probeTouchesFor.length) continue;
902
+ touches.push(...probeTouchesFor);
903
+ meshes.add(mesh);
904
+ probed.push(idOf(mesh));
905
+ }
906
+ }
907
+ const standIns = buildProxyTouches({
908
+ scene,
909
+ proxies: options.proxies ?? [],
910
+ byId,
911
+ intensity,
912
+ shading,
913
+ ...options.probe ? {
914
+ probe: {
915
+ texture: options.probe.texture,
916
+ meshes: new Set(options.probe.meshes)
917
+ }
918
+ } : {}
919
+ });
920
+ touches.push(...standIns.touches);
921
+ skipped.push(...standIns.skipped);
922
+ if (options.shadow) {
923
+ touches.push(
924
+ shadowTouch(scene, options.shadow.shadow, options.shadow.texture)
925
+ );
926
+ }
927
+ return {
928
+ patch: makePatch(touches, meshes.size + standIns.applied.length),
929
+ applied,
930
+ unmatched,
931
+ skipped,
932
+ probed,
933
+ standIns: standIns.applied,
934
+ replaced: standIns.replaced,
935
+ shadow: Boolean(options.shadow)
936
+ };
937
+ }
938
+ function probeTouches(mesh, texture, materialUsers, targets, written) {
939
+ const original = mesh.material;
940
+ const slots = materialsOf(mesh).filter((material) => "envMap" in material);
941
+ if (!slots.length) return [];
942
+ const touches = [];
943
+ const clones = [];
944
+ const mapped = /* @__PURE__ */ new Map();
945
+ let swapped = false;
946
+ for (const material of slots) {
947
+ if (mapped.has(material)) continue;
948
+ const users = materialUsers.get(material) ?? /* @__PURE__ */ new Set([mesh]);
949
+ const allTargets = [...users].every((user) => targets.has(user));
950
+ if (allTargets) {
951
+ mapped.set(material, material);
952
+ if (!written.has(material)) {
953
+ written.add(material);
954
+ touches.push(inPlaceProbeTouch(material, texture));
955
+ }
956
+ continue;
957
+ }
958
+ const clone = material.clone();
959
+ markClone(clone, material);
960
+ clone.envMap = texture;
961
+ clone.needsUpdate = true;
962
+ mapped.set(material, clone);
963
+ clones.push(clone);
964
+ swapped = true;
965
+ }
966
+ if (swapped) {
967
+ const applied = Array.isArray(original) ? original.map((material) => mapped.get(material) ?? material) : mapped.get(original) ?? original;
968
+ touches.push(materialSwapTouch(mesh, applied, clones));
969
+ }
970
+ return touches;
971
+ }
972
+ function inPlaceProbeTouch(material, texture) {
973
+ const previous = material.envMap;
974
+ return {
975
+ on() {
976
+ material.envMap = texture;
977
+ material.needsUpdate = true;
978
+ },
979
+ off() {
980
+ material.envMap = previous;
981
+ material.needsUpdate = true;
982
+ },
983
+ dispose() {
984
+ }
985
+ };
986
+ }
987
+ function makePatch(touches, meshes) {
988
+ let active = false;
989
+ let disposed = false;
990
+ return {
991
+ get active() {
992
+ return active;
993
+ },
994
+ get meshes() {
995
+ return meshes;
996
+ },
997
+ on() {
998
+ if (active || disposed) return;
999
+ for (const touch of touches) touch.on();
1000
+ active = true;
1001
+ },
1002
+ off() {
1003
+ if (!active) return;
1004
+ for (let i = touches.length - 1; i >= 0; i--) touches[i].off();
1005
+ active = false;
1006
+ },
1007
+ dispose() {
1008
+ if (disposed) return;
1009
+ if (active) {
1010
+ for (let i = touches.length - 1; i >= 0; i--) touches[i].off();
1011
+ active = false;
1012
+ }
1013
+ for (const touch of touches) touch.dispose();
1014
+ disposed = true;
1015
+ }
1016
+ };
1017
+ }
1018
+ function uvAttributeTouch(geometry, attribute) {
1019
+ const previous = geometry.getAttribute("uv1");
1020
+ return {
1021
+ on() {
1022
+ geometry.setAttribute("uv1", attribute);
1023
+ },
1024
+ off() {
1025
+ if (previous) geometry.setAttribute("uv1", previous);
1026
+ else geometry.deleteAttribute("uv1");
1027
+ },
1028
+ dispose() {
1029
+ }
1030
+ };
1031
+ }
1032
+ function geometrySwapTouch(mesh, rebuilt) {
1033
+ const previous = mesh.geometry;
1034
+ return {
1035
+ on() {
1036
+ mesh.geometry = rebuilt;
1037
+ },
1038
+ off() {
1039
+ mesh.geometry = previous;
1040
+ },
1041
+ dispose() {
1042
+ rebuilt.dispose();
1043
+ }
1044
+ };
1045
+ }
1046
+ function onChannel(texture, channel, touches) {
1047
+ if (texture.channel === channel) return texture;
1048
+ const view = texture.clone();
1049
+ view.channel = channel;
1050
+ view.needsUpdate = true;
1051
+ touches.push({ on() {
1052
+ }, off() {
1053
+ }, dispose: () => view.dispose() });
1054
+ return view;
1055
+ }
1056
+ function materialTouches(mesh, texture, intensity, materialUsers, shading) {
1057
+ const original = mesh.material;
1058
+ const slots = materialsOf(mesh);
1059
+ const touches = [];
1060
+ const clones = [];
1061
+ const mapped = /* @__PURE__ */ new Map();
1062
+ let swapped = false;
1063
+ for (const material of slots) {
1064
+ if (mapped.has(material)) continue;
1065
+ const shared = (materialUsers.get(material)?.size ?? 1) > 1;
1066
+ if (!shared) {
1067
+ mapped.set(material, material);
1068
+ touches.push(
1069
+ inPlaceMaterialTouch(
1070
+ material,
1071
+ texture,
1072
+ intensity,
1073
+ shading
1074
+ )
1075
+ );
1076
+ continue;
1077
+ }
1078
+ const clone = material.clone();
1079
+ markClone(clone, material);
1080
+ clone.lightMap = texture;
1081
+ clone.lightMapIntensity = intensity;
1082
+ if (shading === "replace") bakedShadingTouch(clone).on();
1083
+ clone.needsUpdate = true;
1084
+ mapped.set(material, clone);
1085
+ clones.push(clone);
1086
+ swapped = true;
1087
+ }
1088
+ if (swapped) {
1089
+ const applied = Array.isArray(original) ? original.map((material) => mapped.get(material) ?? material) : mapped.get(original) ?? original;
1090
+ touches.push(materialSwapTouch(mesh, applied, clones));
1091
+ }
1092
+ return touches;
1093
+ }
1094
+ function inPlaceMaterialTouch(material, texture, intensity, shading) {
1095
+ const previousMap = material.lightMap;
1096
+ const previousIntensity = material.lightMapIntensity;
1097
+ const shadingTouch = shading === "replace" ? bakedShadingTouch(material) : null;
1098
+ return {
1099
+ on() {
1100
+ material.lightMap = texture;
1101
+ material.lightMapIntensity = intensity;
1102
+ shadingTouch?.on();
1103
+ material.needsUpdate = true;
1104
+ },
1105
+ off() {
1106
+ shadingTouch?.off();
1107
+ material.lightMap = previousMap;
1108
+ material.lightMapIntensity = previousIntensity;
1109
+ material.needsUpdate = true;
1110
+ },
1111
+ dispose() {
1112
+ }
1113
+ };
1114
+ }
1115
+ function materialSwapTouch(mesh, applied, clones) {
1116
+ const previous = mesh.material;
1117
+ return {
1118
+ on() {
1119
+ mesh.material = applied;
1120
+ },
1121
+ off() {
1122
+ mesh.material = previous;
1123
+ },
1124
+ dispose() {
1125
+ for (const clone of clones) clone.dispose();
1126
+ }
1127
+ };
1128
+ }
1129
+ function planGeometry(input) {
1130
+ const { mesh, uv, objectId } = input;
1131
+ const geometry = mesh.geometry;
1132
+ const skip = (code, message, why, fix) => ({
1133
+ skip: {
1134
+ objectId,
1135
+ ...input.name !== void 0 ? { name: input.name } : {},
1136
+ code,
1137
+ message,
1138
+ why,
1139
+ fix
1140
+ }
1141
+ });
1142
+ if (!geometry) {
1143
+ return skip(
1144
+ "BakeNoGeometry",
1145
+ `"${input.name ?? objectId}" has no geometry to put a second UV set on.`,
1146
+ "The mesh that was baked is not the mesh that is in the scene now.",
1147
+ "Bake again against the scene as it stands."
1148
+ );
1149
+ }
1150
+ const position = geometry.getAttribute("position");
1151
+ if (!position) {
1152
+ return skip(
1153
+ "BakeNoPosition",
1154
+ `"${input.name ?? objectId}" has no position attribute.`,
1155
+ "Without vertices there is nothing for the unwrap to index.",
1156
+ "Bake again against the scene as it stands."
1157
+ );
1158
+ }
1159
+ if ("channel" in uv) {
1160
+ const name = uv.channel === 0 ? "uv" : "uv1";
1161
+ const attribute2 = geometry.getAttribute(name);
1162
+ if (!attribute2 || attribute2.count !== position.count) {
1163
+ return skip(
1164
+ "BakeUvChannelMissing",
1165
+ `The lightmap for "${input.name ?? objectId}" is laid on its own \`${name}\`, and the mesh ${attribute2 ? "has a different number of them now" : "no longer has one"}.`,
1166
+ "The bake reused the uv set the mesh was captured with instead of unwrapping it, so that set is what the lightmap lines up with.",
1167
+ "Bake again against the scene as it stands."
1168
+ );
1169
+ }
1170
+ return { kind: "channel", channel: uv.channel };
1171
+ }
1172
+ if ("topology" in uv) {
1173
+ const { vertexSource, index, values: values2 } = uv.topology;
1174
+ if (values2.length !== vertexSource.length * 2) {
1175
+ return skip(
1176
+ "BakeUvLengthMismatch",
1177
+ `The unwrap for "${input.name ?? objectId}" has ${values2.length / 2} UVs for ${vertexSource.length} vertices.`,
1178
+ "The bundle is internally inconsistent, which means the render worker and this SDK disagree about the format.",
1179
+ "Update @oeave/bakery3, or bake again."
1180
+ );
1181
+ }
1182
+ for (const source of vertexSource) {
1183
+ if (!Number.isInteger(source) || source < 0 || source >= position.count) {
1184
+ return skip(
1185
+ "BakeTopologyOutOfRange",
1186
+ `The rebuild for "${input.name ?? objectId}" refers to vertex ${source}, and the live geometry has ${position.count}.`,
1187
+ "The geometry changed between the bake and now: a different subdivision, a different loader, or an edit.",
1188
+ "Bake again against the scene as it stands."
1189
+ );
1190
+ }
1191
+ }
1192
+ if (Array.isArray(mesh.material) && geometry.groups.length > 1) {
1193
+ return skip(
1194
+ "BakeTopologyGroupsUnsupported",
1195
+ `"${input.name ?? objectId}" needs a geometry rebuild and has ${geometry.groups.length} material groups.`,
1196
+ "A rebuild renumbers the triangle index, and this version cannot prove which of your groups each new triangle belongs to. Guessing would assign the wrong material to part of the surface.",
1197
+ "Split the mesh into one mesh per material, or bake it at a size whose unwrap does not need vertex splits."
1198
+ );
1199
+ }
1200
+ if (geometry.drawRange.start !== 0 || Number.isFinite(geometry.drawRange.count)) {
1201
+ return skip(
1202
+ "BakeDrawRangeUnsupported",
1203
+ `"${input.name ?? objectId}" needs a geometry rebuild and draws only part of its index.`,
1204
+ "The rebuild renumbers the index, so the partial draw range would point at different triangles.",
1205
+ "Clear setDrawRange() before baking, or split the mesh."
1206
+ );
1207
+ }
1208
+ return {
1209
+ kind: "rebuild",
1210
+ geometry: rebuildGeometry(geometry, uv.topology)
1211
+ };
1212
+ }
1213
+ const values = uv.values;
1214
+ if (values.length !== position.count * 2) {
1215
+ return skip(
1216
+ "BakeVertexCountMismatch",
1217
+ `The bake has ${values.length / 2} UVs for "${input.name ?? objectId}", and the live geometry has ${position.count} vertices.`,
1218
+ "The geometry changed between baking and applying, so the unwrap no longer describes this surface.",
1219
+ "Bake again against the scene as it stands."
1220
+ );
1221
+ }
1222
+ const attribute = new BufferAttribute(new Float32Array(values), 2);
1223
+ bakeWritten.add(attribute);
1224
+ const sharers = (input.geometryUsers.get(geometry) ?? []).filter(
1225
+ (other) => other !== mesh
1226
+ );
1227
+ const conflicted = sharers.some((other) => {
1228
+ const otherUv = input.uvById.get(idOf(other));
1229
+ if (!otherUv) return false;
1230
+ if ("channel" in otherUv) return otherUv.channel === 1;
1231
+ if ("topology" in otherUv) return true;
1232
+ return !sameValues(otherUv.values, values);
1233
+ });
1234
+ if (!conflicted) return { kind: "in-place", attribute };
1235
+ const copy = geometry.clone();
1236
+ copy.setAttribute("uv1", attribute);
1237
+ return { kind: "rebuild", geometry: copy };
1238
+ }
1239
+ function sameValues(a, b) {
1240
+ if (a.length !== b.length) return false;
1241
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
1242
+ return true;
1243
+ }
1244
+ function rebuildGeometry(source, topology) {
1245
+ const { vertexSource, index, values } = topology;
1246
+ const target = new BufferGeometry();
1247
+ target.name = source.name;
1248
+ target.userData = { ...source.userData };
1249
+ for (const [name, attribute] of Object.entries(source.attributes)) {
1250
+ target.setAttribute(
1251
+ name,
1252
+ gather(attribute, vertexSource)
1253
+ );
1254
+ }
1255
+ const morphs = target.morphAttributes;
1256
+ for (const [name, list] of Object.entries(
1257
+ source.morphAttributes
1258
+ )) {
1259
+ morphs[name] = list.map((attribute) => gather(attribute, vertexSource));
1260
+ }
1261
+ target.morphTargetsRelative = source.morphTargetsRelative;
1262
+ const lightmapUv = new BufferAttribute(new Float32Array(values), 2);
1263
+ bakeWritten.add(lightmapUv);
1264
+ target.setAttribute("uv1", lightmapUv);
1265
+ const IndexArray = vertexSource.length > 65535 ? Uint32Array : Uint16Array;
1266
+ target.setIndex(new BufferAttribute(IndexArray.from(index), 1));
1267
+ const group = source.groups[0];
1268
+ if (group) target.addGroup(0, index.length, group.materialIndex);
1269
+ return target;
1270
+ }
1271
+ function gather(attribute, vertexSource) {
1272
+ const itemSize = attribute.itemSize;
1273
+ const SourceArray = attribute.array.constructor;
1274
+ const aligned = SourceArray.BYTES_PER_ELEMENT * itemSize % 4 === 0;
1275
+ const ArrayType = aligned ? SourceArray : Float32Array;
1276
+ const out = new BufferAttribute(
1277
+ new ArrayType(vertexSource.length * itemSize),
1278
+ itemSize,
1279
+ aligned && attribute.normalized
1280
+ );
1281
+ out.name = attribute.name;
1282
+ for (let i = 0; i < vertexSource.length; i++) {
1283
+ const source = vertexSource[i];
1284
+ for (let component = 0; component < itemSize; component++) {
1285
+ out.setComponent(i, component, attribute.getComponent(source, component));
1286
+ }
1287
+ }
1288
+ return out;
1289
+ }
1290
+ function indexObjects(scene) {
1291
+ const byId = /* @__PURE__ */ new Map();
1292
+ scene.traverse((object) => {
1293
+ const id = idOf(object);
1294
+ const list = byId.get(id);
1295
+ if (list) list.push(object);
1296
+ else byId.set(id, [object]);
1297
+ });
1298
+ return byId;
1299
+ }
1300
+ var BAKE_VIEW_ANGLE_STEP = 30;
1301
+ var OCTAVE_MIN = -3;
1302
+ var OCTAVE_MAX = 8;
1303
+ var scratchBox = new Box3();
1304
+ var scratchCenter = new Vector3();
1305
+ var scratchSize = new Vector3();
1306
+ var scratchCamera = new Vector3();
1307
+ var scratchDirection = new Vector3();
1308
+ function bakeBounds(objects) {
1309
+ const bounds = new Box3();
1310
+ for (const object of objects) {
1311
+ scratchBox.setFromObject(object);
1312
+ if (!scratchBox.isEmpty()) bounds.union(scratchBox);
1313
+ }
1314
+ return bounds;
1315
+ }
1316
+ function quantizeBakeView(camera, bounds) {
1317
+ const center = bounds.isEmpty() ? scratchCenter.set(0, 0, 0) : bounds.getCenter(scratchCenter);
1318
+ const radius = bounds.isEmpty() ? 1 : Math.max(1e-3, bounds.getSize(scratchSize).length() / 2);
1319
+ camera.updateWorldMatrix(true, false);
1320
+ const position = camera.getWorldPosition(scratchCamera);
1321
+ const offset = scratchDirection.subVectors(position, center);
1322
+ let distance = offset.length();
1323
+ if (distance < 1e-6) {
1324
+ camera.getWorldDirection(offset).negate();
1325
+ distance = radius;
1326
+ } else {
1327
+ offset.divideScalar(distance);
1328
+ }
1329
+ const elevation = toDegrees(Math.asin(clamp(offset.y, -1, 1)));
1330
+ const azimuth = toDegrees(Math.atan2(offset.x, offset.z));
1331
+ const step = BAKE_VIEW_ANGLE_STEP;
1332
+ const elevationBin = clamp(
1333
+ binCenter(elevation, step),
1334
+ -90 + step / 2,
1335
+ 90 - step / 2
1336
+ );
1337
+ const azimuthBin = binCenter(azimuth, step);
1338
+ const octave = clamp(
1339
+ Math.round(Math.log2(distance / radius)),
1340
+ OCTAVE_MIN,
1341
+ OCTAVE_MAX
1342
+ );
1343
+ const snappedDistance = radius * 2 ** octave;
1344
+ const el = toRadians(elevationBin);
1345
+ const az = toRadians(azimuthBin);
1346
+ const direction = scratchDirection.set(
1347
+ Math.cos(el) * Math.sin(az),
1348
+ Math.sin(el),
1349
+ Math.cos(el) * Math.cos(az)
1350
+ );
1351
+ const snapped = direction.multiplyScalar(snappedDistance).add(center);
1352
+ return {
1353
+ position: [round(snapped.x), round(snapped.y), round(snapped.z)]
1354
+ };
1355
+ }
1356
+ function binCenter(value, step) {
1357
+ return (Math.floor(value / step) + 0.5) * step;
1358
+ }
1359
+ function round(value) {
1360
+ const rounded = Math.round(value * 1e4) / 1e4;
1361
+ return Object.is(rounded, -0) ? 0 : rounded;
1362
+ }
1363
+ var clamp = (value, low, high) => Math.min(high, Math.max(low, value));
1364
+ var toDegrees = (radians) => radians * 180 / Math.PI;
1365
+ var toRadians = (degrees) => degrees * Math.PI / 180;
1366
+
1367
+ // src/bake/probe.ts
1368
+ var PROBE_SCENERY_FACTOR = 4;
1369
+ var SCENERY_REASONS = /* @__PURE__ */ new Set([
1370
+ "BakeTransmissiveMaterial",
1371
+ "BakeObjectHidden"
1372
+ ]);
1373
+ var scratchBox2 = new Box3();
1374
+ var scratchSize2 = new Vector3();
1375
+ var scratchCenter2 = new Vector3();
1376
+ function resolveProbe(candidates, include) {
1377
+ const included = new Set(include);
1378
+ const keptLive = candidates.filter(
1379
+ (candidate) => candidate.bakeable && !included.has(candidate.objectId) && !SCENERY_REASONS.has(candidate.reason?.code ?? "")
1380
+ );
1381
+ const subject = withoutScenery(keptLive);
1382
+ const anchor = subject.length ? subject : candidates.filter((candidate) => included.has(candidate.objectId));
1383
+ const bounds = bakeBounds(anchor.map((candidate) => candidate.object));
1384
+ const center = bounds.isEmpty() ? scratchCenter2.set(0, 0, 0) : bounds.getCenter(scratchCenter2);
1385
+ return {
1386
+ position: [round2(center.x), round2(center.y), round2(center.z)],
1387
+ hide: subject.map((candidate) => candidate.objectId)
1388
+ };
1389
+ }
1390
+ function probeTargets(candidates, bakedIds) {
1391
+ return candidates.filter((candidate) => !bakedIds.has(candidate.objectId)).map((candidate) => candidate.object);
1392
+ }
1393
+ function withoutScenery(keptLive) {
1394
+ if (keptLive.length < 2) return keptLive;
1395
+ const diagonals = keptLive.map((candidate) => {
1396
+ scratchBox2.setFromObject(candidate.object);
1397
+ return scratchBox2.isEmpty() ? 0 : scratchBox2.getSize(scratchSize2).length();
1398
+ });
1399
+ const sorted = [...diagonals].sort((a, b) => a - b);
1400
+ const median = sorted[Math.floor(sorted.length / 2)];
1401
+ if (median <= 0) return keptLive;
1402
+ return keptLive.filter(
1403
+ (_candidate, i) => diagonals[i] <= median * PROBE_SCENERY_FACTOR
1404
+ );
1405
+ }
1406
+ function round2(value) {
1407
+ return Math.round(value * 1e4) / 1e4;
1408
+ }
1409
+
1410
+ export { bakeBounds, bakeCandidates, buildPatch, captureEnvironmentSettings, captureFog, captureLights, captureRuntime, configureShadow, defaultIncludeIds, excludedItself, idOf, isBakeWrittenUv, probeTargets, quantizeBakeView, readUserData, resolveProbe, scenePath, shouldExclude, stampIds };
1411
+ //# sourceMappingURL=chunk-HQTUFVMM.js.map
1412
+ //# sourceMappingURL=chunk-HQTUFVMM.js.map