minerender 2.1.0-beta.7 → 2.1.0-beta.8

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 (73) hide show
  1. package/README.md +10 -1
  2. package/dist/browser/esm/armor/Armor.mjs +84 -0
  3. package/dist/browser/esm/armor/Armor.mjs.map +1 -0
  4. package/dist/browser/esm/armor/index.mjs +3 -0
  5. package/dist/browser/esm/armor/index.mjs.map +1 -0
  6. package/dist/browser/esm/assets/BlockEntities.mjs +2 -0
  7. package/dist/browser/esm/assets/BlockEntities.mjs.map +1 -1
  8. package/dist/browser/esm/assets/Entities.mjs +39 -11
  9. package/dist/browser/esm/assets/Entities.mjs.map +1 -1
  10. package/dist/browser/esm/assets/MineRenderData.mjs +8 -17
  11. package/dist/browser/esm/assets/MineRenderData.mjs.map +1 -1
  12. package/dist/browser/esm/assets/ModelTextures.mjs +13 -7
  13. package/dist/browser/esm/assets/ModelTextures.mjs.map +1 -1
  14. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs +22 -0
  15. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs.map +1 -0
  16. package/dist/browser/esm/cache/Caching.mjs +8 -0
  17. package/dist/browser/esm/cache/Caching.mjs.map +1 -1
  18. package/dist/browser/esm/entity/scene/EntityObject.mjs +31 -5
  19. package/dist/browser/esm/entity/scene/EntityObject.mjs.map +1 -1
  20. package/dist/browser/esm/env/browser/BrowserEnv.mjs +11 -4
  21. package/dist/browser/esm/env/browser/BrowserEnv.mjs.map +1 -1
  22. package/dist/browser/esm/index.browser.d.mts +129 -13
  23. package/dist/browser/esm/index.browser.mjs.map +1 -1
  24. package/dist/browser/esm/index.mjs +1 -0
  25. package/dist/browser/esm/index.mjs.map +1 -1
  26. package/dist/browser/esm/model/ItemGlint.mjs +7 -6
  27. package/dist/browser/esm/model/ItemGlint.mjs.map +1 -1
  28. package/dist/browser/esm/model/block/scene/BlockObject.mjs +16 -5
  29. package/dist/browser/esm/model/block/scene/BlockObject.mjs.map +1 -1
  30. package/dist/browser/esm/scene/SceneDocument.mjs +35 -5
  31. package/dist/browser/esm/scene/SceneDocument.mjs.map +1 -1
  32. package/dist/browser/esm/scene/SceneDocumentLoader.mjs +14 -3
  33. package/dist/browser/esm/scene/SceneDocumentLoader.mjs.map +1 -1
  34. package/dist/browser/esm/skin/SkinGeometries.mjs +4 -1
  35. package/dist/browser/esm/skin/SkinGeometries.mjs.map +1 -1
  36. package/dist/browser/esm/skin/SkinPart.mjs +2 -0
  37. package/dist/browser/esm/skin/SkinPart.mjs.map +1 -1
  38. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs +11 -1
  39. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs.map +1 -1
  40. package/dist/browser/esm/skin/SkinTextures.mjs +1 -1
  41. package/dist/browser/esm/skin/SkinTextures.mjs.map +1 -1
  42. package/dist/browser/esm/skin/scene/SkinObject.mjs +119 -7
  43. package/dist/browser/esm/skin/scene/SkinObject.mjs.map +1 -1
  44. package/dist/browser/esm/texture/PalettedTextures.mjs +46 -0
  45. package/dist/browser/esm/texture/PalettedTextures.mjs.map +1 -0
  46. package/dist/browser/esm/texture/index.mjs +1 -0
  47. package/dist/browser/esm/texture/index.mjs.map +1 -1
  48. package/dist/browser/esm/util/util.mjs +0 -2
  49. package/dist/browser/esm/util/util.mjs.map +1 -1
  50. package/dist/browser/esm/world/Chunk.mjs +62 -33
  51. package/dist/browser/esm/world/Chunk.mjs.map +1 -1
  52. package/dist/browser/esm/world/MineRenderWorld.mjs +98 -13
  53. package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
  54. package/dist/browser/esm/world/SectionMesh.mjs +65 -9
  55. package/dist/browser/esm/world/SectionMesh.mjs.map +1 -1
  56. package/dist/browser/esm/world/SectionModels.mjs +21 -15
  57. package/dist/browser/esm/world/SectionModels.mjs.map +1 -1
  58. package/dist/browser/esm/world/SectionWorker.mjs +8 -2
  59. package/dist/browser/esm/world/SectionWorker.mjs.map +1 -1
  60. package/dist/browser/esm/world/WorldStreamer.mjs +109 -15
  61. package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
  62. package/dist/browser/index.d.ts +129 -13
  63. package/dist/browser/index.js +1592 -894
  64. package/dist/browser/index.js.map +1 -1
  65. package/dist/bundle.js +1330 -619
  66. package/dist/bundle.js.map +1 -1
  67. package/dist/node/index.d.mts +117 -12
  68. package/dist/node/index.d.ts +117 -12
  69. package/dist/node/index.js +1505 -793
  70. package/dist/node/index.js.map +1 -1
  71. package/dist/node/index.mjs +1303 -593
  72. package/dist/node/index.mjs.map +1 -1
  73. package/package.json +2 -1
@@ -1,9 +1,12 @@
1
1
  // src/world/SectionMesh.ts
2
- import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, FrontSide, Group, Mesh, Uint32BufferAttribute } from "three";
2
+ import { BufferGeometry, Color, DoubleSide, DynamicDrawUsage, Float32BufferAttribute, FrontSide, Group, Matrix4, Mesh, Uint32BufferAttribute, Vector3 } from "three";
3
3
  import { createCanvas } from "../canvas/CanvasCompat.mjs";
4
4
  import { Materials } from "../Materials.mjs";
5
5
  import { buildSectionGeometry } from "./SectionGeometry.mjs";
6
6
  import { SectionWorker } from "./SectionWorker.mjs";
7
+ var BLOCK_SIZE = 16;
8
+ var QUAD_VERTICES = 4;
9
+ var QUAD_INDICES = 6;
7
10
  var templateCache = /* @__PURE__ */ new WeakMap();
8
11
  var WATER_TINT = new Color(4159204).toArray();
9
12
  function toInput(entries, maxAtlasSize, fluids, lighting) {
@@ -40,8 +43,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
40
43
  if (!data) {
41
44
  const indices = template.geometry.getIndex();
42
45
  const position = template.geometry.getAttribute("position");
43
- const quads = indices.count / 6;
44
- if (indices.count % 6 !== 0 || position.count !== quads * 4) {
46
+ const quads = indices.count / QUAD_INDICES;
47
+ if (indices.count % QUAD_INDICES !== 0 || position.count !== quads * QUAD_VERTICES) {
45
48
  throw new RangeError("Section template geometry must hold four vertices and six indices per quad");
46
49
  }
47
50
  data = {
@@ -51,8 +54,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
51
54
  uvs: new Float32Array(quads * 8),
52
55
  uvBounds: new Float32Array(quads * 16),
53
56
  colors: new Float32Array(quads * 12),
54
- shade: new Float32Array(quads * 4),
55
- indices: new Uint16Array(quads * 6),
57
+ shade: new Float32Array(quads * QUAD_VERTICES),
58
+ indices: new Uint16Array(quads * QUAD_INDICES),
56
59
  cullFaces: new Uint8Array(template.cullFaces),
57
60
  biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
58
61
  atlas: 0,
@@ -64,7 +67,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
64
67
  const bounds = template.geometry.getAttribute("uvBounds");
65
68
  const color = template.geometry.getAttribute("color");
66
69
  const shade = template.geometry.getAttribute("shade");
67
- for (let vertex = 0; vertex < quads * 4; vertex++) {
70
+ for (let vertex = 0; vertex < quads * QUAD_VERTICES; vertex++) {
68
71
  data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
69
72
  data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
70
73
  data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
@@ -77,7 +80,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
77
80
  data.colors.set([color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1], vertex * 3);
78
81
  data.shade[vertex] = shade?.getX(vertex) ?? 1;
79
82
  }
80
- for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2) - 4 * Math.floor(i2 / 6);
83
+ for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2) - QUAD_VERTICES * Math.floor(i2 / QUAD_INDICES);
81
84
  templateCache.set(template, data);
82
85
  }
83
86
  id = input.templateData.length;
@@ -110,6 +113,54 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
110
113
  }
111
114
  return { input, atlases };
112
115
  }
116
+ function sortTransparentQuads(mesh) {
117
+ const index = mesh.geometry.getIndex();
118
+ if (index.count <= QUAD_INDICES) return;
119
+ index.setUsage(DynamicDrawUsage);
120
+ const count = index.count / QUAD_INDICES;
121
+ let source, centers, depths, order;
122
+ const view = new Matrix4(), localCamera = new Vector3(), key = new Vector3(), previous = new Vector3();
123
+ let previousOrthographic;
124
+ mesh.onBeforeRender = (_renderer, _scene, camera) => {
125
+ localCamera.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(view.copy(mesh.matrixWorld).invert());
126
+ const orthographic = !!camera.isOrthographicCamera;
127
+ if (orthographic) {
128
+ view.multiplyMatrices(camera.matrixWorldInverse, mesh.matrixWorld);
129
+ key.set(view.elements[2], view.elements[6], view.elements[10]);
130
+ } else {
131
+ key.copy(localCamera).divideScalar(BLOCK_SIZE).floor();
132
+ }
133
+ if (orthographic === previousOrthographic && key.equals(previous)) return;
134
+ previous.copy(key);
135
+ previousOrthographic = orthographic;
136
+ if (!centers) {
137
+ source = new Uint32Array(index.array);
138
+ centers = new Float32Array(count * 3);
139
+ depths = new Float32Array(count);
140
+ order = Uint32Array.from({ length: count }, (_, quad) => quad);
141
+ const positions = mesh.geometry.getAttribute("position");
142
+ for (let quad = 0; quad < count; quad++) {
143
+ for (let axis = 0; axis < 3; axis++) {
144
+ let center = 0;
145
+ for (let corner = 0; corner < QUAD_VERTICES; corner++) center += positions.getComponent(quad * QUAD_VERTICES + corner, axis);
146
+ centers[quad * 3 + axis] = center / QUAD_VERTICES;
147
+ }
148
+ }
149
+ }
150
+ for (let quad = 0; quad < count; quad++) {
151
+ const x = centers[quad * 3], y = centers[quad * 3 + 1], z = centers[quad * 3 + 2];
152
+ depths[quad] = orthographic ? key.x * x + key.y * y + key.z * z : -((x - localCamera.x) ** 2 + (y - localCamera.y) ** 2 + (z - localCamera.z) ** 2);
153
+ }
154
+ order.sort((a, b) => depths[a] - depths[b] || a - b);
155
+ if (!order.some((quad, offset) => index.array[offset * QUAD_INDICES] !== source[quad * QUAD_INDICES])) return;
156
+ for (let offset = 0; offset < count; offset++) {
157
+ for (let indexOffset = 0; indexOffset < QUAD_INDICES; indexOffset++) {
158
+ index.array[offset * QUAD_INDICES + indexOffset] = source[order[offset] * QUAD_INDICES + indexOffset];
159
+ }
160
+ }
161
+ index.needsUpdate = true;
162
+ };
163
+ }
113
164
  var SectionMesh = class _SectionMesh extends Group {
114
165
  constructor() {
115
166
  super(...arguments);
@@ -119,7 +170,11 @@ var SectionMesh = class _SectionMesh extends Group {
119
170
  /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */
120
171
  getBlockIndex(hit) {
121
172
  if (hit.faceIndex === void 0 || hit.faceIndex === null) return void 0;
122
- return this.blockIndices.get(hit.object)?.[Math.floor(hit.faceIndex / 2)];
173
+ const mesh = hit.object;
174
+ const owners = this.blockIndices.get(mesh);
175
+ if (!owners) return void 0;
176
+ const vertex = mesh.geometry.getIndex().getX(hit.faceIndex * 3);
177
+ return owners[Math.floor(vertex / QUAD_VERTICES)];
123
178
  }
124
179
  /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
125
180
  static build(entries, maxAtlasSize = 2048, fluids, lighting) {
@@ -155,13 +210,14 @@ var SectionMesh = class _SectionMesh extends Group {
155
210
  const material = Materials.createShadedCanvasMaterial(canvas, transparent, false, true);
156
211
  material.side = FrontSide;
157
212
  if (page.layer === 2) {
213
+ material.depthWrite = !material.transparent;
158
214
  material.side = DoubleSide;
159
215
  material.forceSinglePass = true;
160
- material.depthWrite = !material.transparent;
161
216
  }
162
217
  material.vertexColors = true;
163
218
  const texture = material.uniforms?.map?.value ?? material.map;
164
219
  const mesh = new Mesh(geometry, material);
220
+ if (transparent) sortTransparentQuads(mesh);
165
221
  section.add(mesh);
166
222
  section.blockIndices.set(mesh, page.blockIndices);
167
223
  const unsubscribe = [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/world/SectionMesh.ts"],"sourcesContent":["import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, FrontSide, Group, Material, Mesh, MeshBasicMaterial, ShaderMaterial, Texture, Uint32BufferAttribute } from \"three\";\nimport { createCanvas } from \"../canvas/CanvasCompat\";\nimport { Materials } from \"../Materials\";\nimport type { MineRenderScene } from \"../renderer/MineRenderScene\";\nimport { TextureAtlas } from \"../texture/TextureAtlas\";\nimport { buildSectionGeometry, SectionGeometryInput, SectionGeometryPage, SectionTemplateData } from \"./SectionGeometry\";\nimport { SectionWorker } from \"./SectionWorker\";\nimport type { Intersection } from \"three\";\n\n/** Shared quad geometry and atlas data prepared for section merging. */\nexport interface SectionMeshTemplate {\n geometry: BufferGeometry;\n atlas: TextureAtlas;\n cullFaces: Uint8Array;\n /** Quads that accept a placement's biome tint, stored as zero or one. */\n biomeTint?: Uint8Array;\n occludes: boolean;\n /** Whether the model permits corner shading from neighboring full cubes. */\n ambientOcclusion?: boolean;\n layer: 0 | 1;\n}\n\n/** One block placement in a section, with its storage index and hidden-face mask. */\nexport interface SectionMeshEntry {\n index: number;\n template: SectionMeshTemplate;\n cullMask: number;\n /** Linear RGB replacing automatic biome colors on eligible quads. */\n biomeTint?: readonly [number, number, number];\n}\n\n/** Fluid cells for one section and the shared atlases of the kinds present. */\nexport interface SectionFluids {\n cells: Uint8Array;\n /** Linear RGB per section cell, indexed by (x + 16z + 256y) * 3. */\n waterTints?: Float32Array;\n water?: TextureAtlas;\n lava?: TextureAtlas;\n}\n\nconst templateCache = new WeakMap<SectionMeshTemplate, SectionTemplateData>();\nconst WATER_TINT = new Color(0x3f76e4).toArray() as [number, number, number];\n\nfunction toInput(entries: readonly SectionMeshEntry[], maxAtlasSize: number, fluids?: SectionFluids,\n lighting?: Pick<SectionGeometryInput, \"light\" | \"ambientLight\" | \"ambientOcclusion\">): { input: SectionGeometryInput; atlases: TextureAtlas[] } {\n if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError(\"Section atlas size must be a positive integer\");\n const atlases: TextureAtlas[] = [];\n const atlasIds = new Map<TextureAtlas, number>();\n const templateIds = new Map<SectionMeshTemplate, number>();\n const input: SectionGeometryInput = {\n count: entries.length, indices: new Uint16Array(entries.length), templates: new Uint16Array(entries.length),\n cullMasks: new Uint8Array(entries.length), templateData: [], atlases: [], maxAtlasSize, ...lighting\n };\n for (let i = 0; i < entries.length; i++) {\n const { index, template, cullMask, biomeTint } = entries[i];\n if (biomeTint) {\n input.biomeTints ??= new Float32Array(entries.length * 3).fill(NaN);\n input.biomeTints.set(biomeTint, i * 3);\n }\n let id = templateIds.get(template);\n if (id === undefined) {\n let atlas = atlasIds.get(template.atlas);\n if (atlas === undefined) {\n atlas = atlases.length;\n atlasIds.set(template.atlas, atlas);\n atlases.push(template.atlas);\n input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height, animated: template.atlas.hasAnimation });\n }\n let data = templateCache.get(template);\n if (!data) {\n const indices = template.geometry.getIndex()!;\n const position = template.geometry.getAttribute(\"position\");\n const quads = indices.count / 6;\n if (indices.count % 6 !== 0 || position.count !== quads * 4) {\n throw new RangeError(\"Section template geometry must hold four vertices and six indices per quad\");\n }\n data = {\n quads, positions: new Float32Array(quads * 12), normals: new Float32Array(quads * 12), uvs: new Float32Array(quads * 8),\n uvBounds: new Float32Array(quads * 16), colors: new Float32Array(quads * 12), shade: new Float32Array(quads * 4), indices: new Uint16Array(quads * 6),\n cullFaces: new Uint8Array(template.cullFaces), biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : undefined, atlas: 0, layer: template.layer, ambientOcclusion: template.ambientOcclusion\n };\n const normal = template.geometry.getAttribute(\"normal\");\n const uv = template.geometry.getAttribute(\"uv\");\n const bounds = template.geometry.getAttribute(\"uvBounds\");\n const color = template.geometry.getAttribute(\"color\");\n const shade = template.geometry.getAttribute(\"shade\");\n for (let vertex = 0; vertex < quads * 4; vertex++) {\n data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);\n data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);\n data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);\n data.uvBounds.set([bounds?.getX(vertex) ?? 0, bounds?.getY(vertex) ?? 0,\n bounds?.getZ(vertex) ?? 1, bounds?.getW(vertex) ?? 1], vertex * 4);\n data.colors.set([color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1], vertex * 3);\n data.shade![vertex] = shade?.getX(vertex) ?? 1;\n }\n for (let i = 0; i < data.indices.length; i++) data.indices[i] = indices.getX(i) - 4 * Math.floor(i / 6);\n templateCache.set(template, data);\n }\n id = input.templateData.length;\n templateIds.set(template, id);\n // Atlas IDs belong to each build; cached buffers can be shared across concurrent builds.\n input.templateData.push({ ...data, atlas });\n }\n input.indices[i] = index;\n input.templates[i] = id;\n input.cullMasks[i] = cullMask;\n }\n if (fluids) {\n input.fluids = { cells: fluids.cells, waterTints: fluids.waterTints };\n for (const kind of [\"water\", \"lava\"] as const) {\n const atlas = fluids[kind];\n if (!atlas) continue;\n let id = atlasIds.get(atlas);\n if (id === undefined) {\n id = atlases.length;\n atlasIds.set(atlas, id);\n atlases.push(atlas);\n input.atlases.push({ width: atlas.image.width, height: atlas.image.height, animated: atlas.hasAnimation });\n }\n input.fluids[kind] = {\n atlas: id, still: [...atlas.positions.still, ...atlas.sizes.still],\n flow: [...atlas.positions.flow, ...atlas.sizes.flow], tint: kind === \"water\" ? WATER_TINT : [1, 1, 1]\n };\n }\n }\n return { input, atlases };\n}\n\n/** Merged terrain geometry and atlas pages for one 16×16×16 section. Owns its generated render resources. */\nexport class SectionMesh extends Group {\n\n private readonly ownedMeshes: { mesh: Mesh<BufferGeometry, Material>; texture?: Texture; unsubscribe: (() => void)[] }[] = [];\n private readonly blockIndices = new Map<Mesh, Uint16Array>();\n\n /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */\n public getBlockIndex(hit: Intersection): number | undefined {\n if (hit.faceIndex === undefined || hit.faceIndex === null) return undefined;\n return this.blockIndices.get(hit.object as Mesh)?.[Math.floor(hit.faceIndex / 2)];\n }\n\n /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */\n public static build(entries: readonly SectionMeshEntry[], maxAtlasSize = 2048, fluids?: SectionFluids,\n lighting?: Pick<SectionGeometryInput, \"light\" | \"ambientLight\" | \"ambientOcclusion\">): SectionMesh {\n const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);\n return this.fromPages(buildSectionGeometry(input), atlases);\n }\n\n /** Builds section-local meshes in a browser worker when available, with synchronous fallback. */\n public static async buildAsync(entries: readonly SectionMeshEntry[], maxAtlasSize = 2048, fluids?: SectionFluids,\n lighting?: Pick<SectionGeometryInput, \"light\" | \"ambientLight\" | \"ambientOcclusion\">): Promise<SectionMesh> {\n const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);\n const worker = SectionWorker.shared();\n const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);\n return this.fromPages(pages, atlases);\n }\n\n private static fromPages(pages: SectionGeometryPage[], atlases: TextureAtlas[]): SectionMesh {\n const section = new SectionMesh();\n for (const page of pages) {\n const canvas = createCanvas(page.width, page.height);\n const context = canvas.getContext(\"2d\") as CanvasRenderingContext2D;\n for (const { atlas, x, y } of page.placements) {\n context.drawImage(atlases[atlas].image.canvas as CanvasImageSource, x, y);\n }\n const geometry = new BufferGeometry();\n // Passing buffers keeps Three's typed attributes from copying the builder's arrays.\n geometry.setAttribute(\"position\", new Float32BufferAttribute(page.positions.buffer, 3));\n geometry.setAttribute(\"normal\", new Float32BufferAttribute(page.normals.buffer, 3));\n geometry.setAttribute(\"uv\", new Float32BufferAttribute(page.uvs.buffer, 2));\n geometry.setAttribute(\"uvBounds\", new Float32BufferAttribute(page.uvBounds.buffer, 4));\n geometry.setAttribute(\"color\", new Float32BufferAttribute(page.colors.buffer, 3));\n geometry.setAttribute(\"shade\", new Float32BufferAttribute(page.shade.buffer, 1));\n geometry.setIndex(new Uint32BufferAttribute(page.indices.buffer, 1));\n geometry.computeBoundingBox();\n geometry.computeBoundingSphere();\n const transparent = page.layer === 2 ? atlases[page.placements[0].atlas].hasTranslucency : page.layer === 1;\n const material = Materials.createShadedCanvasMaterial(canvas as HTMLCanvasElement, transparent, false, true);\n material.side = FrontSide;\n if (page.layer === 2) {\n material.side = DoubleSide;\n material.forceSinglePass = true;\n material.depthWrite = !material.transparent;\n }\n material.vertexColors = true;\n const texture = (material as ShaderMaterial).uniforms?.map?.value ?? (material as MeshBasicMaterial).map;\n const mesh = new Mesh(geometry, material);\n section.add(mesh);\n section.blockIndices.set(mesh, page.blockIndices);\n const unsubscribe: (() => void)[] = [];\n for (const { atlas: id, x, y } of page.placements) {\n const atlas = atlases[id];\n if (!atlas.hasAnimation) continue;\n unsubscribe.push(atlas.subscribe(() => {\n context.clearRect(x, y, atlas.image.width, atlas.image.height);\n context.drawImage(atlas.image.canvas as CanvasImageSource, x, y);\n texture.needsUpdate = true;\n section.traverseAncestors(parent => {\n if ((parent as MineRenderScene).isMineRenderScene) (parent as MineRenderScene).dirty = true;\n });\n }));\n }\n section.ownedMeshes.push({ mesh, texture, unsubscribe });\n }\n return section;\n }\n\n public dispose(): void {\n for (const { mesh, texture, unsubscribe } of this.ownedMeshes.splice(0)) {\n for (const stop of unsubscribe) stop();\n mesh.removeFromParent();\n mesh.geometry.dispose();\n mesh.material.dispose();\n texture?.dispose();\n }\n this.blockIndices.clear();\n this.removeFromParent();\n super.dispose();\n }\n\n}\n"],"mappings":"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","names":["i"]}
1
+ {"version":3,"sources":["../../../../src/world/SectionMesh.ts"],"sourcesContent":["import { BufferGeometry, Color, DoubleSide, DynamicDrawUsage, Float32BufferAttribute, FrontSide, Group, Material, Matrix4, Mesh, MeshBasicMaterial, ShaderMaterial, Texture, Uint32BufferAttribute, Vector3 } from \"three\";\nimport { createCanvas } from \"../canvas/CanvasCompat\";\nimport { Materials } from \"../Materials\";\nimport type { MineRenderScene } from \"../renderer/MineRenderScene\";\nimport { TextureAtlas } from \"../texture/TextureAtlas\";\nimport { buildSectionGeometry, SectionGeometryInput, SectionGeometryPage, SectionTemplateData } from \"./SectionGeometry\";\nimport { SectionWorker } from \"./SectionWorker\";\nimport type { Camera, Intersection } from \"three\";\n\n/** Shared quad geometry and atlas data prepared for section merging. */\nexport interface SectionMeshTemplate {\n geometry: BufferGeometry;\n atlas: TextureAtlas;\n cullFaces: Uint8Array;\n /** Quads that accept a placement's biome tint, stored as zero or one. */\n biomeTint?: Uint8Array;\n occludes: boolean;\n /** Whether the model permits corner shading from neighboring full cubes. */\n ambientOcclusion?: boolean;\n layer: 0 | 1;\n}\n\n/** One block placement in a section, with its storage index and hidden-face mask. */\nexport interface SectionMeshEntry {\n index: number;\n template: SectionMeshTemplate;\n cullMask: number;\n /** Linear RGB replacing automatic biome colors on eligible quads. */\n biomeTint?: readonly [number, number, number];\n}\n\n/** Fluid cells for one section and the shared atlases of the kinds present. */\nexport interface SectionFluids {\n cells: Uint8Array;\n /** Linear RGB per section cell, indexed by (x + 16z + 256y) * 3. */\n waterTints?: Float32Array;\n water?: TextureAtlas;\n lava?: TextureAtlas;\n}\n\nconst BLOCK_SIZE = 16;\nconst QUAD_VERTICES = 4;\nconst QUAD_INDICES = 6;\n\nconst templateCache = new WeakMap<SectionMeshTemplate, SectionTemplateData>();\nconst WATER_TINT = new Color(0x3f76e4).toArray() as [number, number, number];\n\nfunction toInput(entries: readonly SectionMeshEntry[], maxAtlasSize: number, fluids?: SectionFluids,\n lighting?: Pick<SectionGeometryInput, \"light\" | \"ambientLight\" | \"ambientOcclusion\">): { input: SectionGeometryInput; atlases: TextureAtlas[] } {\n if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError(\"Section atlas size must be a positive integer\");\n const atlases: TextureAtlas[] = [];\n const atlasIds = new Map<TextureAtlas, number>();\n const templateIds = new Map<SectionMeshTemplate, number>();\n const input: SectionGeometryInput = {\n count: entries.length, indices: new Uint16Array(entries.length), templates: new Uint16Array(entries.length),\n cullMasks: new Uint8Array(entries.length), templateData: [], atlases: [], maxAtlasSize, ...lighting\n };\n for (let i = 0; i < entries.length; i++) {\n const { index, template, cullMask, biomeTint } = entries[i];\n if (biomeTint) {\n input.biomeTints ??= new Float32Array(entries.length * 3).fill(NaN);\n input.biomeTints.set(biomeTint, i * 3);\n }\n let id = templateIds.get(template);\n if (id === undefined) {\n let atlas = atlasIds.get(template.atlas);\n if (atlas === undefined) {\n atlas = atlases.length;\n atlasIds.set(template.atlas, atlas);\n atlases.push(template.atlas);\n input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height, animated: template.atlas.hasAnimation });\n }\n let data = templateCache.get(template);\n if (!data) {\n const indices = template.geometry.getIndex()!;\n const position = template.geometry.getAttribute(\"position\");\n const quads = indices.count / QUAD_INDICES;\n if (indices.count % QUAD_INDICES !== 0 || position.count !== quads * QUAD_VERTICES) {\n throw new RangeError(\"Section template geometry must hold four vertices and six indices per quad\");\n }\n data = {\n quads, positions: new Float32Array(quads * 12), normals: new Float32Array(quads * 12), uvs: new Float32Array(quads * 8),\n uvBounds: new Float32Array(quads * 16), colors: new Float32Array(quads * 12), shade: new Float32Array(quads * QUAD_VERTICES), indices: new Uint16Array(quads * QUAD_INDICES),\n cullFaces: new Uint8Array(template.cullFaces), biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : undefined, atlas: 0, layer: template.layer, ambientOcclusion: template.ambientOcclusion\n };\n const normal = template.geometry.getAttribute(\"normal\");\n const uv = template.geometry.getAttribute(\"uv\");\n const bounds = template.geometry.getAttribute(\"uvBounds\");\n const color = template.geometry.getAttribute(\"color\");\n const shade = template.geometry.getAttribute(\"shade\");\n for (let vertex = 0; vertex < quads * QUAD_VERTICES; vertex++) {\n data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);\n data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);\n data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);\n data.uvBounds.set([bounds?.getX(vertex) ?? 0, bounds?.getY(vertex) ?? 0,\n bounds?.getZ(vertex) ?? 1, bounds?.getW(vertex) ?? 1], vertex * 4);\n data.colors.set([color?.getX(vertex) ?? 1, color?.getY(vertex) ?? 1, color?.getZ(vertex) ?? 1], vertex * 3);\n data.shade![vertex] = shade?.getX(vertex) ?? 1;\n }\n for (let i = 0; i < data.indices.length; i++) data.indices[i] = indices.getX(i) - QUAD_VERTICES * Math.floor(i / QUAD_INDICES);\n templateCache.set(template, data);\n }\n id = input.templateData.length;\n templateIds.set(template, id);\n // Atlas IDs belong to each build; cached buffers can be shared across concurrent builds.\n input.templateData.push({ ...data, atlas });\n }\n input.indices[i] = index;\n input.templates[i] = id;\n input.cullMasks[i] = cullMask;\n }\n if (fluids) {\n input.fluids = { cells: fluids.cells, waterTints: fluids.waterTints };\n for (const kind of [\"water\", \"lava\"] as const) {\n const atlas = fluids[kind];\n if (!atlas) continue;\n let id = atlasIds.get(atlas);\n if (id === undefined) {\n id = atlases.length;\n atlasIds.set(atlas, id);\n atlases.push(atlas);\n input.atlases.push({ width: atlas.image.width, height: atlas.image.height, animated: atlas.hasAnimation });\n }\n input.fluids[kind] = {\n atlas: id, still: [...atlas.positions.still, ...atlas.sizes.still],\n flow: [...atlas.positions.flow, ...atlas.sizes.flow], tint: kind === \"water\" ? WATER_TINT : [1, 1, 1]\n };\n }\n }\n return { input, atlases };\n}\n\nfunction sortTransparentQuads(mesh: Mesh<BufferGeometry, Material>): void {\n const index = mesh.geometry.getIndex()!;\n if (index.count <= QUAD_INDICES) return;\n index.setUsage(DynamicDrawUsage);\n const count = index.count / QUAD_INDICES;\n let source: Uint32Array, centers: Float32Array, depths: Float32Array, order: Uint32Array;\n const view = new Matrix4(), localCamera = new Vector3(), key = new Vector3(), previous = new Vector3();\n let previousOrthographic: boolean | undefined;\n mesh.onBeforeRender = (_renderer, _scene, camera) => {\n localCamera.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(view.copy(mesh.matrixWorld).invert());\n const orthographic = !!(camera as Camera & { isOrthographicCamera?: boolean }).isOrthographicCamera;\n if (orthographic) {\n // Three updates mesh.modelViewMatrix after this callback.\n view.multiplyMatrices(camera.matrixWorldInverse, mesh.matrixWorld);\n key.set(view.elements[2], view.elements[6], view.elements[10]);\n } else {\n key.copy(localCamera).divideScalar(BLOCK_SIZE).floor();\n }\n if (orthographic === previousOrthographic && key.equals(previous)) return;\n previous.copy(key);\n previousOrthographic = orthographic;\n if (!centers) {\n source = new Uint32Array(index.array);\n centers = new Float32Array(count * 3);\n depths = new Float32Array(count);\n order = Uint32Array.from({ length: count }, (_, quad) => quad);\n const positions = mesh.geometry.getAttribute(\"position\");\n for (let quad = 0; quad < count; quad++) {\n for (let axis = 0; axis < 3; axis++) {\n let center = 0;\n for (let corner = 0; corner < QUAD_VERTICES; corner++) center += positions.getComponent(quad * QUAD_VERTICES + corner, axis);\n centers[quad * 3 + axis] = center / QUAD_VERTICES;\n }\n }\n }\n for (let quad = 0; quad < count; quad++) {\n const x = centers[quad * 3], y = centers[quad * 3 + 1], z = centers[quad * 3 + 2];\n depths[quad] = orthographic ? key.x * x + key.y * y + key.z * z\n : -((x - localCamera.x) ** 2 + (y - localCamera.y) ** 2 + (z - localCamera.z) ** 2);\n }\n order.sort((a, b) => depths[a] - depths[b] || a - b);\n if (!order.some((quad, offset) => index.array[offset * QUAD_INDICES] !== source[quad * QUAD_INDICES])) return;\n for (let offset = 0; offset < count; offset++) {\n for (let indexOffset = 0; indexOffset < QUAD_INDICES; indexOffset++) {\n index.array[offset * QUAD_INDICES + indexOffset] = source[order[offset] * QUAD_INDICES + indexOffset];\n }\n }\n index.needsUpdate = true;\n };\n}\n\n/** Merged terrain geometry and atlas pages for one 16×16×16 section. Owns its generated render resources. */\nexport class SectionMesh extends Group {\n\n private readonly ownedMeshes: { mesh: Mesh<BufferGeometry, Material>; texture?: Texture; unsubscribe: (() => void)[] }[] = [];\n private readonly blockIndices = new Map<Mesh, Uint16Array>();\n\n /** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */\n public getBlockIndex(hit: Intersection): number | undefined {\n if (hit.faceIndex === undefined || hit.faceIndex === null) return undefined;\n const mesh = hit.object as Mesh;\n const owners = this.blockIndices.get(mesh);\n if (!owners) return undefined;\n // Sorted triangles still refer to each quad's original four consecutive vertices.\n const vertex = mesh.geometry.getIndex()!.getX(hit.faceIndex * 3);\n return owners[Math.floor(vertex / QUAD_VERTICES)];\n }\n\n /** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */\n public static build(entries: readonly SectionMeshEntry[], maxAtlasSize = 2048, fluids?: SectionFluids,\n lighting?: Pick<SectionGeometryInput, \"light\" | \"ambientLight\" | \"ambientOcclusion\">): SectionMesh {\n const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);\n return this.fromPages(buildSectionGeometry(input), atlases);\n }\n\n /** Builds section-local meshes in a browser worker when available, with synchronous fallback. */\n public static async buildAsync(entries: readonly SectionMeshEntry[], maxAtlasSize = 2048, fluids?: SectionFluids,\n lighting?: Pick<SectionGeometryInput, \"light\" | \"ambientLight\" | \"ambientOcclusion\">): Promise<SectionMesh> {\n const { input, atlases } = toInput(entries, maxAtlasSize, fluids, lighting);\n const worker = SectionWorker.shared();\n const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);\n return this.fromPages(pages, atlases);\n }\n\n private static fromPages(pages: SectionGeometryPage[], atlases: TextureAtlas[]): SectionMesh {\n const section = new SectionMesh();\n for (const page of pages) {\n const canvas = createCanvas(page.width, page.height);\n const context = canvas.getContext(\"2d\") as CanvasRenderingContext2D;\n for (const { atlas, x, y } of page.placements) {\n context.drawImage(atlases[atlas].image.canvas as CanvasImageSource, x, y);\n }\n const geometry = new BufferGeometry();\n // Passing buffers keeps Three's typed attributes from copying the builder's arrays.\n geometry.setAttribute(\"position\", new Float32BufferAttribute(page.positions.buffer, 3));\n geometry.setAttribute(\"normal\", new Float32BufferAttribute(page.normals.buffer, 3));\n geometry.setAttribute(\"uv\", new Float32BufferAttribute(page.uvs.buffer, 2));\n geometry.setAttribute(\"uvBounds\", new Float32BufferAttribute(page.uvBounds.buffer, 4));\n geometry.setAttribute(\"color\", new Float32BufferAttribute(page.colors.buffer, 3));\n geometry.setAttribute(\"shade\", new Float32BufferAttribute(page.shade.buffer, 1));\n geometry.setIndex(new Uint32BufferAttribute(page.indices.buffer, 1));\n geometry.computeBoundingBox();\n geometry.computeBoundingSphere();\n const transparent = page.layer === 2 ? atlases[page.placements[0].atlas].hasTranslucency : page.layer === 1;\n const material = Materials.createShadedCanvasMaterial(canvas as HTMLCanvasElement, transparent, false, true);\n material.side = FrontSide;\n if (page.layer === 2) {\n material.depthWrite = !material.transparent;\n material.side = DoubleSide;\n material.forceSinglePass = true;\n }\n material.vertexColors = true;\n const texture = (material as ShaderMaterial).uniforms?.map?.value ?? (material as MeshBasicMaterial).map;\n const mesh = new Mesh(geometry, material);\n if (transparent) sortTransparentQuads(mesh);\n section.add(mesh);\n section.blockIndices.set(mesh, page.blockIndices);\n const unsubscribe: (() => void)[] = [];\n for (const { atlas: id, x, y } of page.placements) {\n const atlas = atlases[id];\n if (!atlas.hasAnimation) continue;\n unsubscribe.push(atlas.subscribe(() => {\n context.clearRect(x, y, atlas.image.width, atlas.image.height);\n context.drawImage(atlas.image.canvas as CanvasImageSource, x, y);\n texture.needsUpdate = true;\n section.traverseAncestors(parent => {\n if ((parent as MineRenderScene).isMineRenderScene) (parent as MineRenderScene).dirty = true;\n });\n }));\n }\n section.ownedMeshes.push({ mesh, texture, unsubscribe });\n }\n return section;\n }\n\n public dispose(): void {\n for (const { mesh, texture, unsubscribe } of this.ownedMeshes.splice(0)) {\n for (const stop of unsubscribe) stop();\n mesh.removeFromParent();\n mesh.geometry.dispose();\n mesh.material.dispose();\n texture?.dispose();\n }\n this.blockIndices.clear();\n this.removeFromParent();\n super.dispose();\n }\n\n}\n"],"mappings":"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","names":["i"]}
@@ -93,6 +93,10 @@ var SectionModels = class {
93
93
  biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
94
94
  };
95
95
  }
96
+ /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
97
+ async preload(blockState, properties = {}) {
98
+ await this.state(blockState, properties);
99
+ }
96
100
  async state(blockState, properties) {
97
101
  let states = this.states.get(blockState);
98
102
  if (!states) this.states.set(blockState, states = /* @__PURE__ */ new Map());
@@ -115,22 +119,24 @@ var SectionModels = class {
115
119
  async prepare(blockState, properties) {
116
120
  const state = { ...await BlockStateResolver.defaults(blockState), ...properties };
117
121
  const groups = BlockStateResolver.matching(blockState, state);
118
- if (!groups.length) return void 0;
122
+ if (!groups.length || groups.some((group) => Array.isArray(group) && !group.length)) return void 0;
123
+ const variants = groups.flatMap((group) => Array.isArray(group) ? group : [group]);
124
+ if (variants.some((variant) => !variant.model)) return void 0;
125
+ const settled = await Promise.allSettled(variants.map(async (variant) => {
126
+ const modelKey = AssetKey.parse("models", variant.model);
127
+ modelKey.root = blockState.key?.root;
128
+ const model = await Models.getMerged(modelKey);
129
+ if (!model?.elements?.length) return void 0;
130
+ const atlas = await UVMapper.getAtlas(model);
131
+ const rotation = BlockStateResolver.rotation(variant);
132
+ if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
133
+ return { model, atlas, rotation };
134
+ }));
119
135
  const prepared = /* @__PURE__ */ new Map();
120
- for (const group of groups) {
121
- const choices = Array.isArray(group) ? group : [group];
122
- if (!choices.length) return void 0;
123
- for (const variant of choices) {
124
- if (!variant.model) return void 0;
125
- const modelKey = AssetKey.parse("models", variant.model);
126
- modelKey.root = blockState.key?.root;
127
- const model = await Models.getMerged(modelKey);
128
- if (!model?.elements?.length) return void 0;
129
- const atlas = await UVMapper.getAtlas(model);
130
- const rotation = BlockStateResolver.rotation(variant);
131
- if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
132
- prepared.set(variant, { model, atlas, rotation });
133
- }
136
+ for (const [i, result] of settled.entries()) {
137
+ if (result.status === "rejected") throw result.reason;
138
+ if (!result.value) return void 0;
139
+ prepared.set(variants[i], result.value);
134
140
  }
135
141
  return { state, groups, choices: prepared };
136
142
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/world/SectionModels.ts"],"sourcesContent":["import { BufferAttribute, BufferGeometry, Euler, Matrix4, Mesh, Vector3 } from \"three\";\nimport { AssetKey } from \"../assets/AssetKey\";\nimport { Models } from \"../assets/Models\";\nimport { CUBE_FACES, CUBE_FACE_OFFSETS } from \"../CubeFace\";\nimport { BlockState, BlockStateVariant } from \"../model/block/BlockState\";\nimport { BlockStateProperties } from \"../model/block/BlockStateProperties\";\nimport { BlockStateResolver } from \"../model/block/BlockStateResolver\";\nimport { BlockTints } from \"../model/block/BlockTints\";\nimport { FluidKind } from \"../model/fluid/FluidGeometry\";\nimport { BlockModel, Model, TripleArray } from \"../model/Model\";\nimport { ModelCulling } from \"../model/ModelCulling\";\nimport { ModelObject } from \"../model/scene/ModelObject\";\nimport { TextureAtlas } from \"../texture/TextureAtlas\";\nimport { UVMapper } from \"../UVMapper\";\nimport { SectionMeshTemplate } from \"./SectionMesh\";\n\ninterface PreparedVariant {\n model: Model;\n atlas: TextureAtlas;\n rotation: Euler;\n}\n\ninterface PreparedState {\n state: BlockStateProperties;\n groups: (BlockStateVariant | BlockStateVariant[])[];\n choices: Map<BlockStateVariant, PreparedVariant>;\n}\n\n// Section meshes supply the material; templates only need the existing model geometry pipeline.\nclass SectionModel extends ModelObject {\n protected applyTextures() {}\n}\n\nfunction cached<T>(map: Map<string, Promise<T>>, key: string, load: () => Promise<T>): Promise<T> {\n let value = map.get(key);\n if (!value) {\n value = load().catch(error => { map.delete(key); throw error; });\n map.set(key, value);\n }\n return value;\n}\n\nfunction compactQuads(geometry: BufferGeometry, keep: boolean[]): BufferGeometry {\n const quads = keep.flatMap((kept, index) => kept ? [index] : []);\n const compact = new BufferGeometry();\n for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3, shade: 1 })) {\n const source = geometry.getAttribute(name);\n const defaults = name === \"uvBounds\" ? [0, 0, 1, 1] : name === \"color\" ? [1, 1, 1] : name === \"shade\" ? [1] : undefined;\n const values = new Float32Array(quads.length * 4 * size);\n for (let q = 0; q < quads.length; q++) {\n for (let vertex = 0; vertex < 4; vertex++) {\n for (let component = 0; component < size; component++) {\n values[(q * 4 + vertex) * size + component] = source\n ? source.getComponent(quads[q] * 4 + vertex, component) : defaults![component];\n }\n }\n }\n compact.setAttribute(name, new BufferAttribute(values, size));\n }\n const source = geometry.getIndex()!;\n const indices: number[] = [];\n for (let q = 0; q < quads.length; q++) {\n for (let k = 0; k < 6; k++) indices.push(source.getX(quads[q] * 6 + k) - quads[q] * 4 + q * 4);\n }\n compact.setIndex(indices);\n geometry.dispose();\n return compact;\n}\n\n/** Prepares and caches model geometry and fluid atlases for merged world sections. */\nexport class SectionModels {\n private states = new WeakMap<BlockState, Map<string, Promise<PreparedState | undefined>>>();\n private models = new WeakMap<Model, Map<string, Promise<SectionMeshTemplate>>>();\n private readonly templates = new Set<SectionMeshTemplate>();\n private readonly fluidAtlases = new Map<string, Promise<TextureAtlas>>();\n\n constructor(readonly maxAtlasSize = 2048) {\n if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError(\"maxAtlasSize must be a positive integer\");\n }\n\n /** Returns templates for the matched parts, or `undefined` when the block needs an individual render object. */\n public async get(blockState: BlockState, properties: BlockStateProperties = {},\n position?: Readonly<TripleArray>, biome?: string): Promise<SectionMeshTemplate[] | undefined> {\n const biomePosition: TripleArray | undefined = position ? [...position] : undefined;\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n return Promise.all(prepared.groups.map(async group => {\n const variant = BlockStateResolver.choose(group, biomePosition);\n const choice = prepared.choices.get(variant)!;\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined,\n () => BlockTints.getBiomeColor(blockState.key, biome, biomePosition));\n return this.template(variant, choice, tints);\n }));\n }\n\n /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */\n public async prepareState(blockState: BlockState, properties: BlockStateProperties = {},\n biomeColor?: number | (() => Promise<number | undefined>), dynamicBiomeTint = false):\n Promise<{ pick(position?: Readonly<TripleArray>): SectionMeshTemplate[]; biomeTint?: boolean } | undefined> {\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n let color: Promise<number | undefined> | undefined;\n const resolveColor = typeof biomeColor === \"function\" ? () => color ??= biomeColor() : biomeColor;\n const templates = new Map<BlockStateVariant, SectionMeshTemplate>();\n const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState.key);\n await Promise.all([...prepared.choices].map(async ([variant, choice]) => {\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, resolveColor);\n const biomeTints = source ? await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, 0xffffff) : undefined;\n templates.set(variant, await this.template(variant, choice, tints, biomeTints));\n }));\n return {\n pick: (position?: Readonly<TripleArray>) => prepared.groups.map(group => templates.get(BlockStateResolver.choose(group, position))!),\n biomeTint: source ? [...templates.values()].some(template => template.biomeTint?.some(value => value === 1)) || undefined : undefined\n };\n }\n\n private async state(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n let states = this.states.get(blockState);\n if (!states) this.states.set(blockState, states = new Map());\n const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));\n return cached(states, key, () => this.prepare(blockState, properties));\n }\n\n /** Returns the shared still/flow atlas used by per-block fluid objects. */\n public fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas> {\n const key = new AssetKey(\"minecraft\", kind, \"models\", \"fluid\", \"assets\", \".json\", root);\n return cached(this.fluidAtlases, key.serialize(), async () => {\n const atlas = await UVMapper.getAtlas({\n key,\n textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },\n elements: []\n });\n if (!atlas) throw new Error(`Missing ${kind} fluid atlas`);\n return atlas;\n });\n }\n\n private async prepare(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n const state = { ...await BlockStateResolver.defaults(blockState), ...properties };\n const groups = BlockStateResolver.matching(blockState, state);\n if (!groups.length) return undefined;\n const prepared = new Map<BlockStateVariant, PreparedVariant>();\n for (const group of groups) {\n const choices = Array.isArray(group) ? group : [group];\n if (!choices.length) return undefined;\n for (const variant of choices) {\n if (!variant.model) return undefined;\n const modelKey = AssetKey.parse(\"models\", variant.model);\n modelKey.root = blockState.key?.root;\n const model = await Models.getMerged(modelKey);\n if (!model?.elements?.length) return undefined;\n const atlas = await UVMapper.getAtlas(model);\n const rotation = BlockStateResolver.rotation(variant);\n if (!atlas\n || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return undefined;\n prepared.set(variant, { model, atlas, rotation });\n }\n }\n return { state, groups, choices: prepared };\n }\n\n private async template(variant: BlockStateVariant, { model, atlas, rotation }: PreparedVariant,\n tints?: Record<number, number>, biomeTints?: Record<number, number>): Promise<SectionMeshTemplate> {\n let models = this.models.get(model);\n if (!models) this.models.set(model, models = new Map());\n const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);\n return cached(models, key, async () => {\n const object = new SectionModel(model, {\n instanceMeshes: false, mergeMeshes: true, tints,\n uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0)\n ? [rotation.x, rotation.y, rotation.z] : undefined\n });\n await object.init();\n let geometry = (object.children[0] as Mesh).geometry;\n geometry.applyMatrix4(new Matrix4().makeRotationFromEuler(rotation));\n object.clear();\n const keep: boolean[] = [];\n const cullFaces: number[] = [];\n const biomeTint: number[] | undefined = biomeTints ? [] : undefined;\n for (const element of atlas.model.elements!) {\n for (const face of CUBE_FACES) {\n const data = element.faces[face];\n keep.push(data !== undefined);\n if (!data) continue;\n biomeTint?.push(Number(data.tintindex !== undefined && biomeTints?.[data.tintindex] !== undefined));\n const local = CUBE_FACES.findIndex(name => name === data.cullface);\n if (local < 0) {\n cullFaces.push(0);\n continue;\n }\n const normal = new Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);\n const world = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) =>\n Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);\n cullFaces.push(world < 0 ? 0 : 1 << world);\n }\n }\n geometry = compactQuads(geometry, keep);\n const template: SectionMeshTemplate = {\n geometry, atlas, cullFaces: new Uint8Array(cullFaces),\n biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : undefined,\n occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,\n layer: atlas.hasTranslucency ? 1 : 0, ambientOcclusion: (model as BlockModel).ambientocclusion !== false\n };\n this.templates.add(template);\n return template;\n });\n }\n\n /** Disposes prepared geometry and clears this world's template caches. */\n public clear(): void {\n for (const template of this.templates) template.geometry.dispose();\n this.templates.clear();\n this.states = new WeakMap();\n this.models = new WeakMap();\n this.fluidAtlases.clear();\n }\n}\n"],"mappings":";AAAA,SAAS,iBAAiB,gBAAuB,SAAe,eAAe;AAC/E,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,YAAY,yBAAyB;AAG9C,SAAS,0BAA0B;AACnC,SAAS,kBAAkB;AAG3B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAgBzB,IAAM,eAAN,cAA2B,YAAY;AAAA,EACzB,gBAAgB;AAAA,EAAC;AAC/B;AAEA,SAAS,OAAU,KAA8B,KAAa,MAAoC;AAC9F,MAAI,QAAQ,IAAI,IAAI,GAAG;AACvB,MAAI,CAAC,OAAO;AACR,YAAQ,KAAK,EAAE,MAAM,WAAS;AAAE,UAAI,OAAO,GAAG;AAAG,YAAM;AAAA,IAAO,CAAC;AAC/D,QAAI,IAAI,KAAK,KAAK;AAAA,EACtB;AACA,SAAO;AACX;AAEA,SAAS,aAAa,UAA0B,MAAiC;AAC7E,QAAM,QAAQ,KAAK,QAAQ,CAAC,MAAM,UAAU,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;AAC/D,QAAM,UAAU,IAAI,eAAe;AACnC,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,EAAE,UAAU,GAAG,QAAQ,GAAG,IAAI,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,EAAE,CAAC,GAAG;AAC3G,UAAMA,UAAS,SAAS,aAAa,IAAI;AACzC,UAAM,WAAW,SAAS,aAAa,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,SAAS,UAAU,CAAC,GAAG,GAAG,CAAC,IAAI,SAAS,UAAU,CAAC,CAAC,IAAI;AAC9G,UAAM,SAAS,IAAI,aAAa,MAAM,SAAS,IAAI,IAAI;AACvD,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACnC,eAAS,SAAS,GAAG,SAAS,GAAG,UAAU;AACvC,iBAAS,YAAY,GAAG,YAAY,MAAM,aAAa;AACnD,kBAAQ,IAAI,IAAI,UAAU,OAAO,SAAS,IAAIA,UACxCA,QAAO,aAAa,MAAM,CAAC,IAAI,IAAI,QAAQ,SAAS,IAAI,SAAU,SAAS;AAAA,QACrF;AAAA,MACJ;AAAA,IACJ;AACA,YAAQ,aAAa,MAAM,IAAI,gBAAgB,QAAQ,IAAI,CAAC;AAAA,EAChE;AACA,QAAM,SAAS,SAAS,SAAS;AACjC,QAAM,UAAoB,CAAC;AAC3B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACnC,aAAS,IAAI,GAAG,IAAI,GAAG,IAAK,SAAQ,KAAK,OAAO,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC;AAAA,EACjG;AACA,UAAQ,SAAS,OAAO;AACxB,WAAS,QAAQ;AACjB,SAAO;AACX;AAGO,IAAM,gBAAN,MAAoB;AAAA,EAMvB,YAAqB,eAAe,MAAM;AAArB;AALrB,SAAQ,SAAS,oBAAI,QAAqE;AAC1F,SAAQ,SAAS,oBAAI,QAA0D;AAC/E,SAAiB,YAAY,oBAAI,IAAyB;AAC1D,SAAiB,eAAe,oBAAI,IAAmC;AAGnE,QAAI,CAAC,OAAO,UAAU,YAAY,KAAK,eAAe,EAAG,OAAM,IAAI,WAAW,yCAAyC;AAAA,EAC3H;AAAA;AAAA,EAGA,MAAa,IAAI,YAAwB,aAAmC,CAAC,GAC5D,UAAkC,OAA4D;AAC3G,UAAM,gBAAyC,WAAW,CAAC,GAAG,QAAQ,IAAI;AAC1E,UAAM,WAAW,MAAM,KAAK,MAAM,YAAY,UAAU;AACxD,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,QAAQ,IAAI,SAAS,OAAO,IAAI,OAAM,UAAS;AAClD,YAAM,UAAU,mBAAmB,OAAO,OAAO,aAAa;AAC9D,YAAM,SAAS,SAAS,QAAQ,IAAI,OAAO;AAC3C,YAAM,QAAQ,MAAM,WAAW;AAAA,QAAI,WAAW;AAAA,QAAK,SAAS;AAAA,QAAO,OAAO;AAAA,QAAO;AAAA,QAC7E,MAAM,WAAW,cAAc,WAAW,KAAK,OAAO,aAAa;AAAA,MAAC;AACxE,aAAO,KAAK,SAAS,SAAS,QAAQ,KAAK;AAAA,IAC/C,CAAC,CAAC;AAAA,EACN;AAAA;AAAA,EAGA,MAAa,aAAa,YAAwB,aAAmC,CAAC,GAC5D,YAA2D,mBAAmB,OACQ;AAC5G,UAAM,WAAW,MAAM,KAAK,MAAM,YAAY,UAAU;AACxD,QAAI,CAAC,SAAU,QAAO;AACtB,QAAI;AACJ,UAAM,eAAe,OAAO,eAAe,aAAa,MAAM,kBAAU,WAAW,KAAI;AACvF,UAAM,YAAY,oBAAI,IAA4C;AAClE,UAAM,SAAS,oBAAoB,MAAM,WAAW,YAAY,WAAW,GAAG;AAC9E,UAAM,QAAQ,IAAI,CAAC,GAAG,SAAS,OAAO,EAAE,IAAI,OAAO,CAAC,SAAS,MAAM,MAAM;AACrE,YAAM,QAAQ,MAAM,WAAW,IAAI,WAAW,KAAK,SAAS,OAAO,OAAO,OAAO,QAAW,YAAY;AACxG,YAAM,aAAa,SAAS,MAAM,WAAW,IAAI,WAAW,KAAK,SAAS,OAAO,OAAO,OAAO,QAAW,QAAQ,IAAI;AACtH,gBAAU,IAAI,SAAS,MAAM,KAAK,SAAS,SAAS,QAAQ,OAAO,UAAU,CAAC;AAAA,IAClF,CAAC,CAAC;AACF,WAAO;AAAA,MACH,MAAM,CAAC,aAAqC,SAAS,OAAO,IAAI,WAAS,UAAU,IAAI,mBAAmB,OAAO,OAAO,QAAQ,CAAC,CAAE;AAAA,MACnI,WAAW,SAAS,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,KAAK,cAAY,SAAS,WAAW,KAAK,WAAS,UAAU,CAAC,CAAC,KAAK,SAAY;AAAA,IAChI;AAAA,EACJ;AAAA,EAEA,MAAc,MAAM,YAAwB,YAAsE;AAC9G,QAAI,SAAS,KAAK,OAAO,IAAI,UAAU;AACvC,QAAI,CAAC,OAAQ,MAAK,OAAO,IAAI,YAAY,SAAS,oBAAI,IAAI,CAAC;AAC3D,UAAM,MAAM,KAAK,UAAU,OAAO,QAAQ,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;AAC5F,WAAO,OAAO,QAAQ,KAAK,MAAM,KAAK,QAAQ,YAAY,UAAU,CAAC;AAAA,EACzE;AAAA;AAAA,EAGO,WAAW,MAAiB,MAAsC;AACrE,UAAM,MAAM,IAAI,SAAS,aAAa,MAAM,UAAU,SAAS,UAAU,SAAS,IAAI;AACtF,WAAO,OAAO,KAAK,cAAc,IAAI,UAAU,GAAG,YAAY;AAC1D,YAAM,QAAQ,MAAM,SAAS,SAAS;AAAA,QAClC;AAAA,QACA,UAAU,EAAE,OAAO,mBAAmB,IAAI,UAAU,MAAM,mBAAmB,IAAI,QAAQ;AAAA,QACzF,UAAU,CAAC;AAAA,MACf,CAAC;AACD,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,WAAW,IAAI,cAAc;AACzD,aAAO;AAAA,IACX,CAAC;AAAA,EACL;AAAA,EAEA,MAAc,QAAQ,YAAwB,YAAsE;AAChH,UAAM,QAAQ,EAAE,GAAG,MAAM,mBAAmB,SAAS,UAAU,GAAG,GAAG,WAAW;AAChF,UAAM,SAAS,mBAAmB,SAAS,YAAY,KAAK;AAC5D,QAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,UAAM,WAAW,oBAAI,IAAwC;AAC7D,eAAW,SAAS,QAAQ;AACxB,YAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACrD,UAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,iBAAW,WAAW,SAAS;AAC3B,YAAI,CAAC,QAAQ,MAAO,QAAO;AAC3B,cAAM,WAAW,SAAS,MAAM,UAAU,QAAQ,KAAK;AACvD,iBAAS,OAAO,WAAW,KAAK;AAChC,cAAM,QAAQ,MAAM,OAAO,UAAU,QAAQ;AAC7C,YAAI,CAAC,OAAO,UAAU,OAAQ,QAAO;AACrC,cAAM,QAAQ,MAAM,SAAS,SAAS,KAAK;AAC3C,cAAM,WAAW,mBAAmB,SAAS,OAAO;AACpD,YAAI,CAAC,SACE,MAAM,MAAM,QAAQ,KAAK,gBAAgB,MAAM,MAAM,SAAS,KAAK,aAAc,QAAO;AAC/F,iBAAS,IAAI,SAAS,EAAE,OAAO,OAAO,SAAS,CAAC;AAAA,MACpD;AAAA,IACJ;AACA,WAAO,EAAE,OAAO,QAAQ,SAAS,SAAS;AAAA,EAC9C;AAAA,EAEA,MAAc,SAAS,SAA4B,EAAE,OAAO,OAAO,SAAS,GACrD,OAAgC,YAAmE;AACtH,QAAI,SAAS,KAAK,OAAO,IAAI,KAAK;AAClC,QAAI,CAAC,OAAQ,MAAK,OAAO,IAAI,OAAO,SAAS,oBAAI,IAAI,CAAC;AACtD,UAAM,MAAM,KAAK,UAAU,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,QAAQ,OAAO,UAAU,CAAC;AAClG,WAAO,OAAO,QAAQ,KAAK,YAAY;AACnC,YAAM,SAAS,IAAI,aAAa,OAAO;AAAA,QACnC,gBAAgB;AAAA,QAAO,aAAa;AAAA,QAAM;AAAA,QAC1C,gBAAgB,QAAQ,WAAW,SAAS,MAAM,KAAK,SAAS,MAAM,KAChE,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,IAAI;AAAA,MACjD,CAAC;AACD,YAAM,OAAO,KAAK;AAClB,UAAI,WAAY,OAAO,SAAS,CAAC,EAAW;AAC5C,eAAS,aAAa,IAAI,QAAQ,EAAE,sBAAsB,QAAQ,CAAC;AACnE,aAAO,MAAM;AACb,YAAM,OAAkB,CAAC;AACzB,YAAM,YAAsB,CAAC;AAC7B,YAAM,YAAkC,aAAa,CAAC,IAAI;AAC1D,iBAAW,WAAW,MAAM,MAAM,UAAW;AACzC,mBAAW,QAAQ,YAAY;AAC3B,gBAAM,OAAO,QAAQ,MAAM,IAAI;AAC/B,eAAK,KAAK,SAAS,MAAS;AAC5B,cAAI,CAAC,KAAM;AACX,qBAAW,KAAK,OAAO,KAAK,cAAc,UAAa,aAAa,KAAK,SAAS,MAAM,MAAS,CAAC;AAClG,gBAAM,QAAQ,WAAW,UAAU,UAAQ,SAAS,KAAK,QAAQ;AACjE,cAAI,QAAQ,GAAG;AACX,sBAAU,KAAK,CAAC;AAChB;AAAA,UACJ;AACA,gBAAM,SAAS,IAAI,QAAQ,GAAG,kBAAkB,KAAK,CAAC,EAAE,WAAW,QAAQ;AAC3E,gBAAM,QAAQ,kBAAkB,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC,MAC/C,KAAK,IAAI,OAAO,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,OAAO,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,OAAO,IAAI,CAAC,IAAI,IAAI;AACnG,oBAAU,KAAK,QAAQ,IAAI,IAAI,KAAK,KAAK;AAAA,QAC7C;AAAA,MACJ;AACA,iBAAW,aAAa,UAAU,IAAI;AACtC,YAAM,WAAgC;AAAA,QAClC;AAAA,QAAU;AAAA,QAAO,WAAW,IAAI,WAAW,SAAS;AAAA,QACpD,WAAW,WAAW,KAAK,OAAO,IAAI,IAAI,WAAW,SAAS,IAAI;AAAA,QAClE,UAAU,aAAa,iBAAiB,KAAK,KAAK,aAAa,YAAY,IAAI,QAAQ,MAAM;AAAA,QAC7F,OAAO,MAAM,kBAAkB,IAAI;AAAA,QAAG,kBAAmB,MAAqB,qBAAqB;AAAA,MACvG;AACA,WAAK,UAAU,IAAI,QAAQ;AAC3B,aAAO;AAAA,IACX,CAAC;AAAA,EACL;AAAA;AAAA,EAGO,QAAc;AACjB,eAAW,YAAY,KAAK,UAAW,UAAS,SAAS,QAAQ;AACjE,SAAK,UAAU,MAAM;AACrB,SAAK,SAAS,oBAAI,QAAQ;AAC1B,SAAK,SAAS,oBAAI,QAAQ;AAC1B,SAAK,aAAa,MAAM;AAAA,EAC5B;AACJ;","names":["source"]}
1
+ {"version":3,"sources":["../../../../src/world/SectionModels.ts"],"sourcesContent":["import { BufferAttribute, BufferGeometry, Euler, Matrix4, Mesh, Vector3 } from \"three\";\nimport { AssetKey } from \"../assets/AssetKey\";\nimport { Models } from \"../assets/Models\";\nimport { CUBE_FACES, CUBE_FACE_OFFSETS } from \"../CubeFace\";\nimport { BlockState, BlockStateVariant } from \"../model/block/BlockState\";\nimport { BlockStateProperties } from \"../model/block/BlockStateProperties\";\nimport { BlockStateResolver } from \"../model/block/BlockStateResolver\";\nimport { BlockTints } from \"../model/block/BlockTints\";\nimport { FluidKind } from \"../model/fluid/FluidGeometry\";\nimport { BlockModel, Model, TripleArray } from \"../model/Model\";\nimport { ModelCulling } from \"../model/ModelCulling\";\nimport { ModelObject } from \"../model/scene/ModelObject\";\nimport { TextureAtlas } from \"../texture/TextureAtlas\";\nimport { UVMapper } from \"../UVMapper\";\nimport { SectionMeshTemplate } from \"./SectionMesh\";\n\ninterface PreparedVariant {\n model: Model;\n atlas: TextureAtlas;\n rotation: Euler;\n}\n\ninterface PreparedState {\n state: BlockStateProperties;\n groups: (BlockStateVariant | BlockStateVariant[])[];\n choices: Map<BlockStateVariant, PreparedVariant>;\n}\n\n// Section meshes supply the material; templates only need the existing model geometry pipeline.\nclass SectionModel extends ModelObject {\n protected applyTextures() {}\n}\n\nfunction cached<T>(map: Map<string, Promise<T>>, key: string, load: () => Promise<T>): Promise<T> {\n let value = map.get(key);\n if (!value) {\n value = load().catch(error => { map.delete(key); throw error; });\n map.set(key, value);\n }\n return value;\n}\n\nfunction compactQuads(geometry: BufferGeometry, keep: boolean[]): BufferGeometry {\n const quads = keep.flatMap((kept, index) => kept ? [index] : []);\n const compact = new BufferGeometry();\n for (const [name, size] of Object.entries({ position: 3, normal: 3, uv: 2, uvBounds: 4, color: 3, shade: 1 })) {\n const source = geometry.getAttribute(name);\n const defaults = name === \"uvBounds\" ? [0, 0, 1, 1] : name === \"color\" ? [1, 1, 1] : name === \"shade\" ? [1] : undefined;\n const values = new Float32Array(quads.length * 4 * size);\n for (let q = 0; q < quads.length; q++) {\n for (let vertex = 0; vertex < 4; vertex++) {\n for (let component = 0; component < size; component++) {\n values[(q * 4 + vertex) * size + component] = source\n ? source.getComponent(quads[q] * 4 + vertex, component) : defaults![component];\n }\n }\n }\n compact.setAttribute(name, new BufferAttribute(values, size));\n }\n const source = geometry.getIndex()!;\n const indices: number[] = [];\n for (let q = 0; q < quads.length; q++) {\n for (let k = 0; k < 6; k++) indices.push(source.getX(quads[q] * 6 + k) - quads[q] * 4 + q * 4);\n }\n compact.setIndex(indices);\n geometry.dispose();\n return compact;\n}\n\n/** Prepares and caches model geometry and fluid atlases for merged world sections. */\nexport class SectionModels {\n private states = new WeakMap<BlockState, Map<string, Promise<PreparedState | undefined>>>();\n private models = new WeakMap<Model, Map<string, Promise<SectionMeshTemplate>>>();\n private readonly templates = new Set<SectionMeshTemplate>();\n private readonly fluidAtlases = new Map<string, Promise<TextureAtlas>>();\n\n constructor(readonly maxAtlasSize = 2048) {\n if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError(\"maxAtlasSize must be a positive integer\");\n }\n\n /** Returns templates for the matched parts, or `undefined` when the block needs an individual render object. */\n public async get(blockState: BlockState, properties: BlockStateProperties = {},\n position?: Readonly<TripleArray>, biome?: string): Promise<SectionMeshTemplate[] | undefined> {\n const biomePosition: TripleArray | undefined = position ? [...position] : undefined;\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n return Promise.all(prepared.groups.map(async group => {\n const variant = BlockStateResolver.choose(group, biomePosition);\n const choice = prepared.choices.get(variant)!;\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined,\n () => BlockTints.getBiomeColor(blockState.key, biome, biomePosition));\n return this.template(variant, choice, tints);\n }));\n }\n\n /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */\n public async prepareState(blockState: BlockState, properties: BlockStateProperties = {},\n biomeColor?: number | (() => Promise<number | undefined>), dynamicBiomeTint = false):\n Promise<{ pick(position?: Readonly<TripleArray>): SectionMeshTemplate[]; biomeTint?: boolean } | undefined> {\n const prepared = await this.state(blockState, properties);\n if (!prepared) return undefined;\n let color: Promise<number | undefined> | undefined;\n const resolveColor = typeof biomeColor === \"function\" ? () => color ??= biomeColor() : biomeColor;\n const templates = new Map<BlockStateVariant, SectionMeshTemplate>();\n const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState.key);\n await Promise.all([...prepared.choices].map(async ([variant, choice]) => {\n const tints = await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, resolveColor);\n const biomeTints = source ? await BlockTints.get(blockState.key, prepared.state, choice.model, undefined, 0xffffff) : undefined;\n templates.set(variant, await this.template(variant, choice, tints, biomeTints));\n }));\n return {\n pick: (position?: Readonly<TripleArray>) => prepared.groups.map(group => templates.get(BlockStateResolver.choose(group, position))!),\n biomeTint: source ? [...templates.values()].some(template => template.biomeTint?.some(value => value === 1)) || undefined : undefined\n };\n }\n\n /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */\n public async preload(blockState: BlockState, properties: BlockStateProperties = {}): Promise<void> {\n await this.state(blockState, properties);\n }\n\n private async state(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n let states = this.states.get(blockState);\n if (!states) this.states.set(blockState, states = new Map());\n const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));\n return cached(states, key, () => this.prepare(blockState, properties));\n }\n\n /** Returns the shared still/flow atlas used by per-block fluid objects. */\n public fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas> {\n const key = new AssetKey(\"minecraft\", kind, \"models\", \"fluid\", \"assets\", \".json\", root);\n return cached(this.fluidAtlases, key.serialize(), async () => {\n const atlas = await UVMapper.getAtlas({\n key,\n textures: { still: `minecraft:block/${kind}_still`, flow: `minecraft:block/${kind}_flow` },\n elements: []\n });\n if (!atlas) throw new Error(`Missing ${kind} fluid atlas`);\n return atlas;\n });\n }\n\n private async prepare(blockState: BlockState, properties: BlockStateProperties): Promise<PreparedState | undefined> {\n const state = { ...await BlockStateResolver.defaults(blockState), ...properties };\n const groups = BlockStateResolver.matching(blockState, state);\n if (!groups.length || groups.some(group => Array.isArray(group) && !group.length)) return undefined;\n const variants = groups.flatMap(group => Array.isArray(group) ? group : [group]);\n if (variants.some(variant => !variant.model)) return undefined;\n const settled = await Promise.allSettled(variants.map(async variant => {\n const modelKey = AssetKey.parse(\"models\", variant.model!);\n modelKey.root = blockState.key?.root;\n const model = await Models.getMerged(modelKey);\n if (!model?.elements?.length) return undefined;\n const atlas = await UVMapper.getAtlas(model);\n const rotation = BlockStateResolver.rotation(variant);\n if (!atlas\n || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return undefined;\n return { model, atlas, rotation };\n }));\n const prepared = new Map<BlockStateVariant, PreparedVariant>();\n for (const [i, result] of settled.entries()) {\n if (result.status === \"rejected\") throw result.reason;\n if (!result.value) return undefined;\n prepared.set(variants[i], result.value);\n }\n return { state, groups, choices: prepared };\n }\n\n private async template(variant: BlockStateVariant, { model, atlas, rotation }: PreparedVariant,\n tints?: Record<number, number>, biomeTints?: Record<number, number>): Promise<SectionMeshTemplate> {\n let models = this.models.get(model);\n if (!models) this.models.set(model, models = new Map());\n const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);\n return cached(models, key, async () => {\n const object = new SectionModel(model, {\n instanceMeshes: false, mergeMeshes: true, tints,\n uvLockRotation: variant.uvlock && (rotation.x !== 0 || rotation.y !== 0)\n ? [rotation.x, rotation.y, rotation.z] : undefined\n });\n await object.init();\n let geometry = (object.children[0] as Mesh).geometry;\n geometry.applyMatrix4(new Matrix4().makeRotationFromEuler(rotation));\n object.clear();\n const keep: boolean[] = [];\n const cullFaces: number[] = [];\n const biomeTint: number[] | undefined = biomeTints ? [] : undefined;\n for (const element of atlas.model.elements!) {\n for (const face of CUBE_FACES) {\n const data = element.faces[face];\n keep.push(data !== undefined);\n if (!data) continue;\n biomeTint?.push(Number(data.tintindex !== undefined && biomeTints?.[data.tintindex] !== undefined));\n const local = CUBE_FACES.findIndex(name => name === data.cullface);\n if (local < 0) {\n cullFaces.push(0);\n continue;\n }\n const normal = new Vector3(...CUBE_FACE_OFFSETS[local]).applyEuler(rotation);\n const world = CUBE_FACE_OFFSETS.findIndex(([x, y, z]) =>\n Math.abs(normal.x - x) < 1e-6 && Math.abs(normal.y - y) < 1e-6 && Math.abs(normal.z - z) < 1e-6);\n cullFaces.push(world < 0 ? 0 : 1 << world);\n }\n }\n geometry = compactQuads(geometry, keep);\n const template: SectionMeshTemplate = {\n geometry, atlas, cullFaces: new Uint8Array(cullFaces),\n biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : undefined,\n occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,\n layer: atlas.hasTranslucency ? 1 : 0, ambientOcclusion: (model as BlockModel).ambientocclusion !== false\n };\n this.templates.add(template);\n return template;\n });\n }\n\n /** Disposes prepared geometry and clears this world's template caches. */\n public clear(): void {\n for (const template of this.templates) template.geometry.dispose();\n this.templates.clear();\n this.states = new WeakMap();\n this.models = new WeakMap();\n this.fluidAtlases.clear();\n }\n}\n"],"mappings":"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","names":["source"]}
@@ -1,5 +1,7 @@
1
1
  // src/world/SectionWorker.ts
2
2
  import { Env } from "../Env.mjs";
3
+ import { logger } from "../util/log.mjs";
4
+ var log = logger("SectionWorker");
3
5
  var _SectionWorker = class _SectionWorker {
4
6
  constructor(worker) {
5
7
  this.worker = worker;
@@ -13,8 +15,12 @@ var _SectionWorker = class _SectionWorker {
13
15
  if (reply.type === "pages") pending.resolve(reply.pages);
14
16
  else pending.reject(new Error(reply.message));
15
17
  });
16
- worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
17
- worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
18
+ const fallBack = (message) => {
19
+ log.warn(`${message}, so sections build on the main thread`);
20
+ this.fail(message);
21
+ };
22
+ worker.addEventListener("error", (event) => fallBack(event.message || "Section worker failed"));
23
+ worker.addEventListener("messageerror", () => fallBack("Section worker message could not be deserialized"));
18
24
  }
19
25
  /** The shared worker, or undefined when the environment has none or a previous worker failed. */
20
26
  static shared() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/world/SectionWorker.ts"],"sourcesContent":["import { Env } from \"../Env\";\nimport type { SectionGeometryInput, SectionGeometryPage } from \"./SectionGeometry\";\n\n/** Runs section geometry builds in the environment's shared worker. */\nexport class SectionWorker {\n private static initialized = false;\n private static instance: SectionWorker | undefined;\n private nextId = 0;\n private readonly pending = new Map<number, {\n resolve: (pages: SectionGeometryPage[]) => void;\n reject: (error: Error) => void;\n }>();\n\n private constructor(private worker: Worker | undefined) {\n worker!.addEventListener(\"message\", (event: MessageEvent<\n { id: number; type: \"pages\"; pages: SectionGeometryPage[] } |\n { id: number; type: \"error\"; message: string }\n >) => {\n const reply = event.data;\n const pending = this.pending.get(reply.id);\n if (!pending) return;\n this.pending.delete(reply.id);\n if (reply.type === \"pages\") pending.resolve(reply.pages);\n else pending.reject(new Error(reply.message));\n });\n // A failed worker or an undeliverable message leaves the pending builds unanswerable, so stop using it.\n worker!.addEventListener(\"error\", event => this.fail(event.message || \"Section worker failed\"));\n worker!.addEventListener(\"messageerror\", () => this.fail(\"Section worker message could not be deserialized\"));\n }\n\n /** The shared worker, or undefined when the environment has none or a previous worker failed. */\n public static shared(): SectionWorker | undefined {\n if (!this.initialized) {\n this.initialized = true;\n const worker = Env.provider.createWorker?.(\"section\");\n if (worker) this.instance = new SectionWorker(worker);\n }\n return this.instance;\n }\n\n /** Builds geometry without transferring the input buffers away from the caller. */\n public build(input: SectionGeometryInput): Promise<SectionGeometryPage[]> {\n if (!this.worker) return Promise.reject(new Error(\"Section worker terminated\"));\n return new Promise((resolve, reject) => {\n const id = this.nextId++;\n this.pending.set(id, { resolve, reject });\n this.worker!.postMessage({ id, type: \"build\", input });\n });\n }\n\n /** Rejects pending builds, terminates the worker, and makes shared() return undefined. */\n public terminate(): void {\n this.fail(\"Section worker terminated\");\n }\n\n private fail(message: string): void {\n const error = new Error(message);\n for (const pending of this.pending.values()) pending.reject(error);\n this.pending.clear();\n this.worker?.terminate();\n this.worker = undefined;\n SectionWorker.instance = undefined;\n }\n}\n"],"mappings":";AAAA,SAAS,WAAW;AAIb,IAAM,iBAAN,MAAM,eAAc;AAAA,EASf,YAAoB,QAA4B;AAA5B;AAN5B,SAAQ,SAAS;AACjB,SAAiB,UAAU,oBAAI,IAG5B;AAGC,WAAQ,iBAAiB,WAAW,CAAC,UAG/B;AACF,YAAM,QAAQ,MAAM;AACpB,YAAM,UAAU,KAAK,QAAQ,IAAI,MAAM,EAAE;AACzC,UAAI,CAAC,QAAS;AACd,WAAK,QAAQ,OAAO,MAAM,EAAE;AAC5B,UAAI,MAAM,SAAS,QAAS,SAAQ,QAAQ,MAAM,KAAK;AAAA,UAClD,SAAQ,OAAO,IAAI,MAAM,MAAM,OAAO,CAAC;AAAA,IAChD,CAAC;AAED,WAAQ,iBAAiB,SAAS,WAAS,KAAK,KAAK,MAAM,WAAW,uBAAuB,CAAC;AAC9F,WAAQ,iBAAiB,gBAAgB,MAAM,KAAK,KAAK,kDAAkD,CAAC;AAAA,EAChH;AAAA;AAAA,EAGA,OAAc,SAAoC;AAC9C,QAAI,CAAC,KAAK,aAAa;AACnB,WAAK,cAAc;AACnB,YAAM,SAAS,IAAI,SAAS,eAAe,SAAS;AACpD,UAAI,OAAQ,MAAK,WAAW,IAAI,eAAc,MAAM;AAAA,IACxD;AACA,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA,EAGO,MAAM,OAA6D;AACtE,QAAI,CAAC,KAAK,OAAQ,QAAO,QAAQ,OAAO,IAAI,MAAM,2BAA2B,CAAC;AAC9E,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACpC,YAAM,KAAK,KAAK;AAChB,WAAK,QAAQ,IAAI,IAAI,EAAE,SAAS,OAAO,CAAC;AACxC,WAAK,OAAQ,YAAY,EAAE,IAAI,MAAM,SAAS,MAAM,CAAC;AAAA,IACzD,CAAC;AAAA,EACL;AAAA;AAAA,EAGO,YAAkB;AACrB,SAAK,KAAK,2BAA2B;AAAA,EACzC;AAAA,EAEQ,KAAK,SAAuB;AAChC,UAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,eAAW,WAAW,KAAK,QAAQ,OAAO,EAAG,SAAQ,OAAO,KAAK;AACjE,SAAK,QAAQ,MAAM;AACnB,SAAK,QAAQ,UAAU;AACvB,SAAK,SAAS;AACd,mBAAc,WAAW;AAAA,EAC7B;AACJ;AA3Da,eACM,cAAc;AAD1B,IAAM,gBAAN;","names":[]}
1
+ {"version":3,"sources":["../../../../src/world/SectionWorker.ts"],"sourcesContent":["import { Env } from \"../Env\";\nimport { logger } from \"../util/log\";\nimport type { SectionGeometryInput, SectionGeometryPage } from \"./SectionGeometry\";\n\nconst log = logger(\"SectionWorker\");\n\n/** Runs section geometry builds in the environment's shared worker. */\nexport class SectionWorker {\n private static initialized = false;\n private static instance: SectionWorker | undefined;\n private nextId = 0;\n private readonly pending = new Map<number, {\n resolve: (pages: SectionGeometryPage[]) => void;\n reject: (error: Error) => void;\n }>();\n\n private constructor(private worker: Worker | undefined) {\n worker!.addEventListener(\"message\", (event: MessageEvent<\n { id: number; type: \"pages\"; pages: SectionGeometryPage[] } |\n { id: number; type: \"error\"; message: string }\n >) => {\n const reply = event.data;\n const pending = this.pending.get(reply.id);\n if (!pending) return;\n this.pending.delete(reply.id);\n if (reply.type === \"pages\") pending.resolve(reply.pages);\n else pending.reject(new Error(reply.message));\n });\n // A failed worker or an undeliverable message leaves the pending builds unanswerable, so stop using it.\n const fallBack = (message: string) => {\n log.warn(`${message}, so sections build on the main thread`);\n this.fail(message);\n };\n worker!.addEventListener(\"error\", event => fallBack(event.message || \"Section worker failed\"));\n worker!.addEventListener(\"messageerror\", () => fallBack(\"Section worker message could not be deserialized\"));\n }\n\n /** The shared worker, or undefined when the environment has none or a previous worker failed. */\n public static shared(): SectionWorker | undefined {\n if (!this.initialized) {\n this.initialized = true;\n const worker = Env.provider.createWorker?.(\"section\");\n if (worker) this.instance = new SectionWorker(worker);\n }\n return this.instance;\n }\n\n /** Builds geometry without transferring the input buffers away from the caller. */\n public build(input: SectionGeometryInput): Promise<SectionGeometryPage[]> {\n if (!this.worker) return Promise.reject(new Error(\"Section worker terminated\"));\n return new Promise((resolve, reject) => {\n const id = this.nextId++;\n this.pending.set(id, { resolve, reject });\n this.worker!.postMessage({ id, type: \"build\", input });\n });\n }\n\n /** Rejects pending builds, terminates the worker, and makes shared() return undefined. */\n public terminate(): void {\n this.fail(\"Section worker terminated\");\n }\n\n private fail(message: string): void {\n const error = new Error(message);\n for (const pending of this.pending.values()) pending.reject(error);\n this.pending.clear();\n this.worker?.terminate();\n this.worker = undefined;\n SectionWorker.instance = undefined;\n }\n}\n"],"mappings":";AAAA,SAAS,WAAW;AACpB,SAAS,cAAc;AAGvB,IAAM,MAAM,OAAO,eAAe;AAG3B,IAAM,iBAAN,MAAM,eAAc;AAAA,EASf,YAAoB,QAA4B;AAA5B;AAN5B,SAAQ,SAAS;AACjB,SAAiB,UAAU,oBAAI,IAG5B;AAGC,WAAQ,iBAAiB,WAAW,CAAC,UAG/B;AACF,YAAM,QAAQ,MAAM;AACpB,YAAM,UAAU,KAAK,QAAQ,IAAI,MAAM,EAAE;AACzC,UAAI,CAAC,QAAS;AACd,WAAK,QAAQ,OAAO,MAAM,EAAE;AAC5B,UAAI,MAAM,SAAS,QAAS,SAAQ,QAAQ,MAAM,KAAK;AAAA,UAClD,SAAQ,OAAO,IAAI,MAAM,MAAM,OAAO,CAAC;AAAA,IAChD,CAAC;AAED,UAAM,WAAW,CAAC,YAAoB;AAClC,UAAI,KAAK,GAAG,OAAO,wCAAwC;AAC3D,WAAK,KAAK,OAAO;AAAA,IACrB;AACA,WAAQ,iBAAiB,SAAS,WAAS,SAAS,MAAM,WAAW,uBAAuB,CAAC;AAC7F,WAAQ,iBAAiB,gBAAgB,MAAM,SAAS,kDAAkD,CAAC;AAAA,EAC/G;AAAA;AAAA,EAGA,OAAc,SAAoC;AAC9C,QAAI,CAAC,KAAK,aAAa;AACnB,WAAK,cAAc;AACnB,YAAM,SAAS,IAAI,SAAS,eAAe,SAAS;AACpD,UAAI,OAAQ,MAAK,WAAW,IAAI,eAAc,MAAM;AAAA,IACxD;AACA,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA,EAGO,MAAM,OAA6D;AACtE,QAAI,CAAC,KAAK,OAAQ,QAAO,QAAQ,OAAO,IAAI,MAAM,2BAA2B,CAAC;AAC9E,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACpC,YAAM,KAAK,KAAK;AAChB,WAAK,QAAQ,IAAI,IAAI,EAAE,SAAS,OAAO,CAAC;AACxC,WAAK,OAAQ,YAAY,EAAE,IAAI,MAAM,SAAS,MAAM,CAAC;AAAA,IACzD,CAAC;AAAA,EACL;AAAA;AAAA,EAGO,YAAkB;AACrB,SAAK,KAAK,2BAA2B;AAAA,EACzC;AAAA,EAEQ,KAAK,SAAuB;AAChC,UAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,eAAW,WAAW,KAAK,QAAQ,OAAO,EAAG,SAAQ,OAAO,KAAK;AACjE,SAAK,QAAQ,MAAM;AACnB,SAAK,QAAQ,UAAU;AACvB,SAAK,SAAS;AACd,mBAAc,WAAW;AAAA,EAC7B;AACJ;AA/Da,eACM,cAAc;AAD1B,IAAM,gBAAN;","names":[]}
@@ -1,4 +1,5 @@
1
1
  // src/world/WorldStreamer.ts
2
+ var MAX_PREFETCHED_COLUMNS = 4;
2
3
  var WorldStreamer = class {
3
4
  constructor(world, source, options = {}) {
4
5
  this.world = world;
@@ -8,6 +9,7 @@ var WorldStreamer = class {
8
9
  this.missing = /* @__PURE__ */ new Map();
9
10
  this.failed = /* @__PURE__ */ new Map();
10
11
  this.desired = [];
12
+ this.reads = /* @__PURE__ */ new Map();
11
13
  this.disposed = false;
12
14
  this.loadRadius = options.loadRadius ?? 2;
13
15
  this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
@@ -52,6 +54,12 @@ var WorldStreamer = class {
52
54
  if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
53
55
  this.loading.controller.abort();
54
56
  }
57
+ for (const [key, read] of this.reads) {
58
+ if (!this.within(read.position, this.loadRadius)) {
59
+ read.controller.abort();
60
+ if (read.ready) this.reads.delete(key);
61
+ }
62
+ }
55
63
  }
56
64
  return this.running ?? (this.running = Promise.resolve().then(() => this.drain()));
57
65
  }
@@ -72,8 +80,11 @@ var WorldStreamer = class {
72
80
  this.desired = [];
73
81
  this.center = void 0;
74
82
  this.loading?.controller.abort();
83
+ for (const read of this.reads.values()) read.controller.abort();
75
84
  return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
76
85
  await this.running?.catch(() => void 0);
86
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
87
+ this.reads.clear();
77
88
  try {
78
89
  await this.unloadMany([...this.owned.values()]);
79
90
  } finally {
@@ -99,6 +110,10 @@ var WorldStreamer = class {
99
110
  await this.unloadMany(obsolete);
100
111
  continue;
101
112
  }
113
+ if (this.world.placeChunks) {
114
+ if (await this.placeReadyChunks()) continue;
115
+ return;
116
+ }
102
117
  const position = this.desired.find((position2) => this.needsLoad(position2));
103
118
  if (!position) return;
104
119
  let chunk;
@@ -121,26 +136,105 @@ var WorldStreamer = class {
121
136
  continue;
122
137
  }
123
138
  this.owned.set(this.key(position), position);
139
+ await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk));
140
+ }
141
+ } finally {
142
+ for (const [key, read] of this.reads) {
143
+ read.controller.abort();
144
+ if (read.ready) this.reads.delete(key);
145
+ }
146
+ this.running = void 0;
147
+ }
148
+ }
149
+ fillReadSlots() {
150
+ while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {
151
+ const position = this.desired.find((position2) => this.needsLoad(position2) && !this.reads.has(this.key(position2)));
152
+ if (!position) return;
153
+ const key = this.key(position);
154
+ const read = {
155
+ position,
156
+ controller: new AbortController(),
157
+ promise: Promise.resolve(),
158
+ ready: false,
159
+ failed: false
160
+ };
161
+ this.reads.set(key, read);
162
+ read.promise = (async () => {
124
163
  try {
125
- await this.world.placeChunk(chunk);
126
- this.loaded.set(this.key(position), position);
164
+ read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);
165
+ if (read.chunk) this.validateChunk(read.chunk, position);
127
166
  } catch (error) {
128
- try {
129
- await this.unloadMany([position]);
130
- } catch (cleanupError) {
131
- const failure = error instanceof Error ? error : new Error(String(error));
132
- try {
133
- Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
134
- } catch {
135
- throw Object.assign(new Error(failure.message), { cause: cleanupError });
136
- }
137
- throw failure;
167
+ read.failed = true;
168
+ read.error = error;
169
+ } finally {
170
+ read.ready = true;
171
+ if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {
172
+ this.reads.delete(key);
138
173
  }
139
- throw error;
174
+ }
175
+ })();
176
+ }
177
+ }
178
+ /** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
179
+ async placeReadyChunks() {
180
+ this.fillReadSlots();
181
+ const position = this.desired.find((position2) => this.needsLoad(position2));
182
+ if (!position) {
183
+ if (!this.reads.size) return false;
184
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
185
+ return true;
186
+ }
187
+ const head = this.reads.get(this.key(position));
188
+ if (!head) {
189
+ for (let index = this.desired.length - 1; index >= 0; index--) {
190
+ const key = this.key(this.desired[index]);
191
+ if (this.reads.get(key)?.ready) {
192
+ this.reads.delete(key);
193
+ return true;
140
194
  }
141
195
  }
142
- } finally {
143
- this.running = void 0;
196
+ await Promise.race([...this.reads.values()].map((read) => read.promise));
197
+ return true;
198
+ }
199
+ if (!head.ready) {
200
+ await head.promise;
201
+ return true;
202
+ }
203
+ const chunks = [], positions = [];
204
+ for (const desired of this.desired) {
205
+ if (!this.needsLoad(desired)) continue;
206
+ const key = this.key(desired), read = this.reads.get(key);
207
+ if (!read?.ready) break;
208
+ this.reads.delete(key);
209
+ if (read.failed) this.failed.set(key, { ...desired, error: read.error });
210
+ else if (!read.chunk) this.missing.set(key, desired);
211
+ else {
212
+ chunks.push(read.chunk);
213
+ positions.push(desired);
214
+ }
215
+ }
216
+ if (!chunks.length) return true;
217
+ for (const position2 of positions) this.owned.set(this.key(position2), position2);
218
+ await this.placeOwnedColumns(positions, () => this.world.placeChunks(chunks));
219
+ return true;
220
+ }
221
+ async placeOwnedColumns(positions, place) {
222
+ try {
223
+ await place();
224
+ for (const position of positions) this.loaded.set(this.key(position), position);
225
+ } catch (error) {
226
+ try {
227
+ await this.unloadMany(positions);
228
+ } catch (cleanupError) {
229
+ const failure = error instanceof Error ? error : new Error(String(error));
230
+ try {
231
+ Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
232
+ } catch {
233
+ throw Object.assign(new Error(failure.message), { cause: cleanupError });
234
+ }
235
+ throw failure;
236
+ }
237
+ throw error;
144
238
  }
145
239
  }
146
240
  needsLoad(position) {