minerender 2.1.0-beta.6 → 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 (128) hide show
  1. package/README.md +10 -1
  2. package/dist/browser/esm/Materials.mjs +3 -1
  3. package/dist/browser/esm/Materials.mjs.map +1 -1
  4. package/dist/browser/esm/Ticker.mjs +3 -1
  5. package/dist/browser/esm/Ticker.mjs.map +1 -1
  6. package/dist/browser/esm/UVMapper.mjs +9 -9
  7. package/dist/browser/esm/UVMapper.mjs.map +1 -1
  8. package/dist/browser/esm/armor/Armor.mjs +84 -0
  9. package/dist/browser/esm/armor/Armor.mjs.map +1 -0
  10. package/dist/browser/esm/armor/index.mjs +3 -0
  11. package/dist/browser/esm/armor/index.mjs.map +1 -0
  12. package/dist/browser/esm/assets/AssetDefaults.mjs +8 -0
  13. package/dist/browser/esm/assets/AssetDefaults.mjs.map +1 -0
  14. package/dist/browser/esm/assets/AssetKey.mjs +1 -1
  15. package/dist/browser/esm/assets/AssetKey.mjs.map +1 -1
  16. package/dist/browser/esm/assets/AssetLoader.mjs +5 -5
  17. package/dist/browser/esm/assets/AssetLoader.mjs.map +1 -1
  18. package/dist/browser/esm/assets/Assets.mjs +1 -5
  19. package/dist/browser/esm/assets/Assets.mjs.map +1 -1
  20. package/dist/browser/esm/assets/BlockEntities.mjs +5 -1
  21. package/dist/browser/esm/assets/BlockEntities.mjs.map +1 -1
  22. package/dist/browser/esm/assets/BlockStates.mjs +1 -1
  23. package/dist/browser/esm/assets/BlockStates.mjs.map +1 -1
  24. package/dist/browser/esm/assets/Entities.mjs +40 -12
  25. package/dist/browser/esm/assets/Entities.mjs.map +1 -1
  26. package/dist/browser/esm/assets/MineRenderData.mjs +8 -17
  27. package/dist/browser/esm/assets/MineRenderData.mjs.map +1 -1
  28. package/dist/browser/esm/assets/ModelTextures.mjs +13 -7
  29. package/dist/browser/esm/assets/ModelTextures.mjs.map +1 -1
  30. package/dist/browser/esm/assets/Models.mjs +1 -1
  31. package/dist/browser/esm/assets/Models.mjs.map +1 -1
  32. package/dist/browser/esm/assets/_fonts/Unihex.mjs +5 -3
  33. package/dist/browser/esm/assets/_fonts/Unihex.mjs.map +1 -1
  34. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs +22 -0
  35. package/dist/browser/esm/assets/_versions/DatasetVersions.mjs.map +1 -0
  36. package/dist/browser/esm/assets/index.mjs +1 -0
  37. package/dist/browser/esm/assets/index.mjs.map +1 -1
  38. package/dist/browser/esm/assets/source/HostedAssetSource.mjs +7 -8
  39. package/dist/browser/esm/assets/source/HostedAssetSource.mjs.map +1 -1
  40. package/dist/browser/esm/cache/Caching.mjs +8 -0
  41. package/dist/browser/esm/cache/Caching.mjs.map +1 -1
  42. package/dist/browser/esm/entity/scene/EntityObject.mjs +31 -5
  43. package/dist/browser/esm/entity/scene/EntityObject.mjs.map +1 -1
  44. package/dist/browser/esm/env/browser/BrowserCache.mjs +3 -3
  45. package/dist/browser/esm/env/browser/BrowserCache.mjs.map +1 -1
  46. package/dist/browser/esm/env/browser/BrowserEnv.mjs +11 -4
  47. package/dist/browser/esm/env/browser/BrowserEnv.mjs.map +1 -1
  48. package/dist/browser/esm/image/ImageLoader.mjs +3 -3
  49. package/dist/browser/esm/image/ImageLoader.mjs.map +1 -1
  50. package/dist/browser/esm/index.browser.d.mts +191 -22
  51. package/dist/browser/esm/index.browser.mjs.map +1 -1
  52. package/dist/browser/esm/index.mjs +1 -0
  53. package/dist/browser/esm/index.mjs.map +1 -1
  54. package/dist/browser/esm/inspector/SceneInspector.mjs +3 -3
  55. package/dist/browser/esm/inspector/SceneInspector.mjs.map +1 -1
  56. package/dist/browser/esm/instance/InstanceManager.mjs +4 -4
  57. package/dist/browser/esm/instance/InstanceManager.mjs.map +1 -1
  58. package/dist/browser/esm/model/ItemGlint.mjs +7 -6
  59. package/dist/browser/esm/model/ItemGlint.mjs.map +1 -1
  60. package/dist/browser/esm/model/block/scene/BlockObject.mjs +19 -6
  61. package/dist/browser/esm/model/block/scene/BlockObject.mjs.map +1 -1
  62. package/dist/browser/esm/model/multiblock/StructureParser.mjs +7 -4
  63. package/dist/browser/esm/model/multiblock/StructureParser.mjs.map +1 -1
  64. package/dist/browser/esm/model/scene/ModelObject.mjs +4 -4
  65. package/dist/browser/esm/model/scene/ModelObject.mjs.map +1 -1
  66. package/dist/browser/esm/nbt/NBTHelper.mjs +5 -2
  67. package/dist/browser/esm/nbt/NBTHelper.mjs.map +1 -1
  68. package/dist/browser/esm/renderer/Renderer.mjs +4 -2
  69. package/dist/browser/esm/renderer/Renderer.mjs.map +1 -1
  70. package/dist/browser/esm/renderer/SceneObject.mjs +0 -6
  71. package/dist/browser/esm/renderer/SceneObject.mjs.map +1 -1
  72. package/dist/browser/esm/request/Requests.mjs +3 -3
  73. package/dist/browser/esm/request/Requests.mjs.map +1 -1
  74. package/dist/browser/esm/scene/SceneDocument.mjs +35 -5
  75. package/dist/browser/esm/scene/SceneDocument.mjs.map +1 -1
  76. package/dist/browser/esm/scene/SceneDocumentLoader.mjs +14 -3
  77. package/dist/browser/esm/scene/SceneDocumentLoader.mjs.map +1 -1
  78. package/dist/browser/esm/skin/SkinGeometries.mjs +4 -1
  79. package/dist/browser/esm/skin/SkinGeometries.mjs.map +1 -1
  80. package/dist/browser/esm/skin/SkinPart.mjs +2 -0
  81. package/dist/browser/esm/skin/SkinPart.mjs.map +1 -1
  82. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs +11 -1
  83. package/dist/browser/esm/skin/SkinTextureCoordinates.mjs.map +1 -1
  84. package/dist/browser/esm/skin/SkinTextures.mjs +1 -1
  85. package/dist/browser/esm/skin/SkinTextures.mjs.map +1 -1
  86. package/dist/browser/esm/skin/Skins.mjs +6 -4
  87. package/dist/browser/esm/skin/Skins.mjs.map +1 -1
  88. package/dist/browser/esm/skin/scene/SkinObject.mjs +119 -7
  89. package/dist/browser/esm/skin/scene/SkinObject.mjs.map +1 -1
  90. package/dist/browser/esm/texture/PalettedTextures.mjs +46 -0
  91. package/dist/browser/esm/texture/PalettedTextures.mjs.map +1 -0
  92. package/dist/browser/esm/texture/TextureLoader.mjs +4 -2
  93. package/dist/browser/esm/texture/TextureLoader.mjs.map +1 -1
  94. package/dist/browser/esm/texture/index.mjs +1 -0
  95. package/dist/browser/esm/texture/index.mjs.map +1 -1
  96. package/dist/browser/esm/util/log.mjs +75 -0
  97. package/dist/browser/esm/util/log.mjs.map +1 -1
  98. package/dist/browser/esm/util/performance.mjs.map +1 -1
  99. package/dist/browser/esm/util/util.mjs +0 -2
  100. package/dist/browser/esm/util/util.mjs.map +1 -1
  101. package/dist/browser/esm/world/Chunk.mjs +70 -38
  102. package/dist/browser/esm/world/Chunk.mjs.map +1 -1
  103. package/dist/browser/esm/world/MineRenderWorld.mjs +187 -60
  104. package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
  105. package/dist/browser/esm/world/SectionMesh.mjs +65 -9
  106. package/dist/browser/esm/world/SectionMesh.mjs.map +1 -1
  107. package/dist/browser/esm/world/SectionModels.mjs +21 -15
  108. package/dist/browser/esm/world/SectionModels.mjs.map +1 -1
  109. package/dist/browser/esm/world/SectionWorker.mjs +8 -2
  110. package/dist/browser/esm/world/SectionWorker.mjs.map +1 -1
  111. package/dist/browser/esm/world/WorldStreamer.mjs +129 -34
  112. package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
  113. package/dist/browser/esm/world/_culling/BlockCulling.mjs +1 -1
  114. package/dist/browser/esm/world/_culling/BlockCulling.mjs.map +1 -1
  115. package/dist/browser/esm/world/_entities/WorldEntities.mjs +3 -1
  116. package/dist/browser/esm/world/_entities/WorldEntities.mjs.map +1 -1
  117. package/dist/browser/index.d.ts +191 -22
  118. package/dist/browser/index.js +4461 -3648
  119. package/dist/browser/index.js.map +1 -1
  120. package/dist/bundle.js +13482 -12651
  121. package/dist/bundle.js.map +1 -1
  122. package/dist/node/index.d.mts +179 -21
  123. package/dist/node/index.d.ts +179 -21
  124. package/dist/node/index.js +4495 -3666
  125. package/dist/node/index.js.map +1 -1
  126. package/dist/node/index.mjs +4412 -3586
  127. package/dist/node/index.mjs.map +1 -1
  128. package/package.json +2 -1
@@ -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,21 +80,17 @@ 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);
77
- let failure;
78
- let failed = false;
79
- for (const position of this.owned.values()) {
80
- try {
81
- await this.unload(position);
82
- } catch (error) {
83
- failed = true;
84
- failure ?? (failure = error);
85
- }
86
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
87
+ this.reads.clear();
88
+ try {
89
+ await this.unloadMany([...this.owned.values()]);
90
+ } finally {
91
+ this.missing.clear();
92
+ this.failed.clear();
86
93
  }
87
- this.missing.clear();
88
- this.failed.clear();
89
- if (failed) throw failure;
90
94
  }).catch((error) => {
91
95
  this.disposal = void 0;
92
96
  throw error;
@@ -101,11 +105,15 @@ var WorldStreamer = class {
101
105
  for (const [key, position2] of this.failed) {
102
106
  if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
103
107
  }
104
- const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
105
- if (obsolete) {
106
- await this.unload(obsolete);
108
+ const obsolete = [...this.owned.values()].filter((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
109
+ if (obsolete.length) {
110
+ await this.unloadMany(obsolete);
107
111
  continue;
108
112
  }
113
+ if (this.world.placeChunks) {
114
+ if (await this.placeReadyChunks()) continue;
115
+ return;
116
+ }
109
117
  const position = this.desired.find((position2) => this.needsLoad(position2));
110
118
  if (!position) return;
111
119
  let chunk;
@@ -128,36 +136,123 @@ var WorldStreamer = class {
128
136
  continue;
129
137
  }
130
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 () => {
131
163
  try {
132
- await this.world.placeChunk(chunk);
133
- 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);
134
166
  } catch (error) {
135
- try {
136
- await this.unload(position);
137
- } catch (cleanupError) {
138
- const failure = error instanceof Error ? error : new Error(String(error));
139
- try {
140
- Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
141
- } catch {
142
- throw Object.assign(new Error(failure.message), { cause: cleanupError });
143
- }
144
- 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);
145
173
  }
146
- 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;
147
194
  }
148
195
  }
149
- } finally {
150
- 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;
151
238
  }
152
239
  }
153
240
  needsLoad(position) {
154
241
  const key = this.key(position);
155
242
  return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
156
243
  }
157
- async unload(position) {
158
- await this.world.unloadChunkColumn(position.x, position.z);
159
- this.owned.delete(this.key(position));
160
- this.loaded.delete(this.key(position));
244
+ async unloadMany(positions) {
245
+ if (!positions.length) return;
246
+ try {
247
+ await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));
248
+ } catch (error) {
249
+ for (const position of positions) this.loaded.delete(this.key(position));
250
+ throw error;
251
+ }
252
+ for (const position of positions) {
253
+ this.owned.delete(this.key(position));
254
+ this.loaded.delete(this.key(position));
255
+ }
161
256
  }
162
257
  validateChunk(chunk, position) {
163
258
  if (chunk.x !== position.x || chunk.z !== position.z) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/world/WorldStreamer.ts"],"sourcesContent":["import type { AnvilChunk } from \"./AnvilParser\";\nimport type { MineRenderWorld } from \"./MineRenderWorld\";\nimport type { WorldChunkSource } from \"./WorldChunkSource\";\n\nexport interface WorldStreamerOptions {\n /** Loads a square of chunk columns around the center. Integer from 0 to 32; defaults to 2. */\n loadRadius?: number;\n /** Retains loaded columns within this radius. Defaults to loadRadius + 1, capped at 32. */\n unloadRadius?: number;\n}\n\ninterface ChunkPosition {\n x: number;\n z: number;\n}\n\n/**\n * Loads nearby chunk columns in distance order and unloads distant columns.\n * Updates coalesce to the latest center and serialize world mutations. Use a dedicated world:\n * the streamer replaces columns it loads, and callers must await disposal before editing that world.\n * The source remains caller-owned. Lighting, biome tint, and LOD are not added by streaming.\n */\nexport class WorldStreamer {\n private readonly loadRadius: number;\n private readonly unloadRadius: number;\n private readonly loaded = new Map<string, ChunkPosition>();\n private readonly owned = new Map<string, ChunkPosition>();\n private readonly missing = new Map<string, ChunkPosition>();\n private readonly failed = new Map<string, ChunkPosition & { error: unknown }>();\n private desired: ChunkPosition[] = [];\n private center?: ChunkPosition;\n private loading?: { position: ChunkPosition; controller: AbortController };\n private running?: Promise<void>;\n private disposal?: Promise<void>;\n private disposed = false;\n\n constructor(\n private readonly world: Pick<MineRenderWorld<boolean>, \"placeChunk\" | \"unloadChunkColumn\">,\n private readonly source: WorldChunkSource,\n options: WorldStreamerOptions = {}\n ) {\n this.loadRadius = options.loadRadius ?? 2;\n this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);\n if (![this.loadRadius, this.unloadRadius].every(value => Number.isInteger(value) && value >= 0 && value <= 32)\n || this.unloadRadius < this.loadRadius) {\n throw new RangeError(\"Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius\");\n }\n }\n\n /** Returns absolute coordinates of successfully placed columns, including retained columns. */\n public get loadedChunks(): readonly { x: number; z: number }[] {\n return [...this.loaded.values()].map(position => ({ ...position }));\n }\n\n /** Returns source failures retained within the unload radius. Failed columns do not block other loads. */\n public get failedChunks(): readonly { x: number; z: number; error: unknown }[] {\n return [...this.failed.values()].map(failure => ({ ...failure }));\n }\n\n /** Counts desired columns still waiting for a source read or placement, including the active column. */\n public get pendingChunks(): number {\n return this.desired.filter(position => this.needsLoad(position)).length;\n }\n\n /**\n * Updates the center in absolute chunk coordinates. Repeated calls share the active drain.\n * Source failures are recorded in failedChunks; other columns continue loading. Missing and failed\n * columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.\n * Placement and unload failures reject the drain; call update again to retry those operations.\n * Reads outside the latest load radius receive an abort signal; placement finishes before unloading.\n * Sources should represent an unchanged world during streaming.\n */\n public async update(x: number, z: number): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n if (![x, z].every(value => Number.isSafeInteger(value)\n && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {\n throw new RangeError(\"Chunk coordinates must be safe integers within the streaming radius\");\n }\n if (!this.center || this.center.x !== x || this.center.z !== z) {\n this.center = { x, z };\n this.desired = [];\n for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {\n for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {\n this.desired.push({ x: x + dx, z: z + dz });\n }\n }\n this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);\n if (this.loading && !this.within(this.loading.position, this.loadRadius)) {\n this.loading.controller.abort();\n }\n }\n return this.running ??= Promise.resolve().then(() => this.drain());\n }\n\n /** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */\n public async updatePosition(position: { x: number; z: number }): Promise<void> {\n if (![position.x, position.z].every(Number.isFinite)) throw new RangeError(\"Scene coordinates must be finite\");\n return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));\n }\n\n /** Forgets recorded source failures and retries desired columns at the latest center. */\n public async retryFailedChunks(): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n this.failed.clear();\n if (this.center) await this.update(this.center.x, this.center.z);\n }\n\n /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */\n public dispose(): Promise<void> {\n this.disposed = true;\n this.desired = [];\n this.center = undefined;\n this.loading?.controller.abort();\n return this.disposal ??= Promise.resolve().then(async () => {\n await this.running?.catch(() => undefined);\n let failure: unknown;\n let failed = false;\n for (const position of this.owned.values()) {\n try {\n await this.unload(position);\n } catch (error) {\n failed = true;\n failure ??= error;\n }\n }\n this.missing.clear();\n this.failed.clear();\n if (failed) throw failure;\n }).catch(error => {\n this.disposal = undefined;\n throw error;\n });\n }\n\n private async drain(): Promise<void> {\n try {\n while (!this.disposed) {\n for (const [key, position] of this.missing) {\n if (!this.within(position, this.unloadRadius)) this.missing.delete(key);\n }\n for (const [key, position] of this.failed) {\n if (!this.within(position, this.unloadRadius)) this.failed.delete(key);\n }\n // Failed placements remain owned until cleanup succeeds, even inside the retained area.\n const obsolete = [...this.owned.values()].find(position =>\n !this.loaded.has(this.key(position)) || !this.within(position, this.unloadRadius));\n if (obsolete) {\n await this.unload(obsolete);\n continue;\n }\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) return;\n let chunk: AnvilChunk | undefined;\n const controller = new AbortController();\n this.loading = { position, controller };\n try {\n chunk = await this.source.getChunk(position.x, position.z, controller.signal);\n if (chunk) this.validateChunk(chunk, position);\n } catch (error) {\n if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {\n this.failed.set(this.key(position), { ...position, error });\n }\n continue;\n } finally {\n this.loading = undefined;\n }\n if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;\n if (!chunk) {\n this.missing.set(this.key(position), position);\n continue;\n }\n this.owned.set(this.key(position), position);\n try {\n await this.world.placeChunk(chunk);\n this.loaded.set(this.key(position), position);\n } catch (error) {\n try {\n await this.unload(position);\n } catch (cleanupError) {\n const failure = error instanceof Error ? error : new Error(String(error));\n try {\n Object.defineProperty(failure, \"cause\", { value: cleanupError, configurable: true });\n } catch {\n throw Object.assign(new Error(failure.message), { cause: cleanupError });\n }\n throw failure;\n }\n throw error;\n }\n }\n } finally {\n this.running = undefined;\n }\n }\n\n private needsLoad(position: ChunkPosition): boolean {\n const key = this.key(position);\n return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);\n }\n\n private async unload(position: ChunkPosition): Promise<void> {\n await this.world.unloadChunkColumn(position.x, position.z);\n this.owned.delete(this.key(position));\n this.loaded.delete(this.key(position));\n }\n\n private validateChunk(chunk: AnvilChunk, position: ChunkPosition): void {\n if (chunk.x !== position.x || chunk.z !== position.z) {\n throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);\n }\n }\n\n private within(position: ChunkPosition, radius: number): boolean {\n return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;\n }\n\n private key(position: ChunkPosition): string {\n return `${position.x},${position.z}`;\n }\n}\n"],"mappings":"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","names":["position"]}
1
+ {"version":3,"sources":["../../../../src/world/WorldStreamer.ts"],"sourcesContent":["import type { AnvilChunk } from \"./AnvilParser\";\nimport type { MineRenderWorld } from \"./MineRenderWorld\";\nimport type { WorldChunkSource } from \"./WorldChunkSource\";\n\nexport interface WorldStreamerOptions {\n /** Loads a square of chunk columns around the center. Integer from 0 to 32; defaults to 2. */\n loadRadius?: number;\n /** Retains loaded columns within this radius. Defaults to loadRadius + 1, capped at 32. */\n unloadRadius?: number;\n}\n\ninterface ChunkPosition {\n x: number;\n z: number;\n}\n\ninterface ChunkRead {\n position: ChunkPosition;\n controller: AbortController;\n promise: Promise<void>;\n ready: boolean;\n chunk?: AnvilChunk;\n failed: boolean;\n error?: unknown;\n}\n\nconst MAX_PREFETCHED_COLUMNS = 4;\n\n/**\n * Loads nearby chunk columns in distance order and unloads distant columns.\n * Updates coalesce to the latest center and serialize world mutations. Use a dedicated world:\n * the streamer replaces columns it loads, and callers must await disposal before editing that world.\n * The source remains caller-owned. Lighting, biome tint, and LOD are not added by streaming.\n */\nexport class WorldStreamer {\n private readonly loadRadius: number;\n private readonly unloadRadius: number;\n private readonly loaded = new Map<string, ChunkPosition>();\n private readonly owned = new Map<string, ChunkPosition>();\n private readonly missing = new Map<string, ChunkPosition>();\n private readonly failed = new Map<string, ChunkPosition & { error: unknown }>();\n private desired: ChunkPosition[] = [];\n private center?: ChunkPosition;\n private loading?: { position: ChunkPosition; controller: AbortController };\n private readonly reads = new Map<string, ChunkRead>();\n private running?: Promise<void>;\n private disposal?: Promise<void>;\n private disposed = false;\n\n constructor(\n private readonly world: Pick<MineRenderWorld<boolean>, \"placeChunk\" | \"unloadChunkColumns\">\n & Partial<Pick<MineRenderWorld<boolean>, \"placeChunks\">>,\n private readonly source: WorldChunkSource,\n options: WorldStreamerOptions = {}\n ) {\n this.loadRadius = options.loadRadius ?? 2;\n this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);\n if (![this.loadRadius, this.unloadRadius].every(value => Number.isInteger(value) && value >= 0 && value <= 32)\n || this.unloadRadius < this.loadRadius) {\n throw new RangeError(\"Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius\");\n }\n }\n\n /** Returns absolute coordinates of successfully placed columns, including retained columns. */\n public get loadedChunks(): readonly { x: number; z: number }[] {\n return [...this.loaded.values()].map(position => ({ ...position }));\n }\n\n /** Returns source failures retained within the unload radius. Failed columns do not block other loads. */\n public get failedChunks(): readonly { x: number; z: number; error: unknown }[] {\n return [...this.failed.values()].map(failure => ({ ...failure }));\n }\n\n /** Counts desired columns still waiting for a source read or placement, including the active column. */\n public get pendingChunks(): number {\n return this.desired.filter(position => this.needsLoad(position)).length;\n }\n\n /**\n * Updates the center in absolute chunk coordinates. Repeated calls share the active drain.\n * Source failures are recorded in failedChunks; other columns continue loading. Missing and failed\n * columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.\n * Placement and unload failures reject the drain; call update again to retry those operations.\n * Reads outside the latest load radius receive an abort signal; placement finishes before unloading.\n * Sources should represent an unchanged world during streaming.\n */\n public async update(x: number, z: number): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n if (![x, z].every(value => Number.isSafeInteger(value)\n && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {\n throw new RangeError(\"Chunk coordinates must be safe integers within the streaming radius\");\n }\n if (!this.center || this.center.x !== x || this.center.z !== z) {\n this.center = { x, z };\n this.desired = [];\n for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {\n for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {\n this.desired.push({ x: x + dx, z: z + dz });\n }\n }\n this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);\n if (this.loading && !this.within(this.loading.position, this.loadRadius)) {\n this.loading.controller.abort();\n }\n for (const [key, read] of this.reads) {\n if (!this.within(read.position, this.loadRadius)) {\n read.controller.abort();\n if (read.ready) this.reads.delete(key);\n }\n }\n }\n return this.running ??= Promise.resolve().then(() => this.drain());\n }\n\n /** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */\n public async updatePosition(position: { x: number; z: number }): Promise<void> {\n if (![position.x, position.z].every(Number.isFinite)) throw new RangeError(\"Scene coordinates must be finite\");\n return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));\n }\n\n /** Forgets recorded source failures and retries desired columns at the latest center. */\n public async retryFailedChunks(): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n this.failed.clear();\n if (this.center) await this.update(this.center.x, this.center.z);\n }\n\n /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */\n public dispose(): Promise<void> {\n this.disposed = true;\n this.desired = [];\n this.center = undefined;\n this.loading?.controller.abort();\n for (const read of this.reads.values()) read.controller.abort();\n return this.disposal ??= Promise.resolve().then(async () => {\n await this.running?.catch(() => undefined);\n await Promise.all([...this.reads.values()].map(read => read.promise));\n this.reads.clear();\n try {\n await this.unloadMany([...this.owned.values()]);\n } finally {\n this.missing.clear();\n this.failed.clear();\n }\n }).catch(error => {\n this.disposal = undefined;\n throw error;\n });\n }\n\n private async drain(): Promise<void> {\n try {\n while (!this.disposed) {\n for (const [key, position] of this.missing) {\n if (!this.within(position, this.unloadRadius)) this.missing.delete(key);\n }\n for (const [key, position] of this.failed) {\n if (!this.within(position, this.unloadRadius)) this.failed.delete(key);\n }\n // Failed placements remain owned until cleanup succeeds, even inside the retained area.\n const obsolete = [...this.owned.values()].filter(position =>\n !this.loaded.has(this.key(position)) || !this.within(position, this.unloadRadius));\n if (obsolete.length) {\n await this.unloadMany(obsolete);\n continue;\n }\n if (this.world.placeChunks) {\n if (await this.placeReadyChunks()) continue;\n return;\n }\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) return;\n let chunk: AnvilChunk | undefined;\n const controller = new AbortController();\n this.loading = { position, controller };\n try {\n chunk = await this.source.getChunk(position.x, position.z, controller.signal);\n if (chunk) this.validateChunk(chunk, position);\n } catch (error) {\n if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {\n this.failed.set(this.key(position), { ...position, error });\n }\n continue;\n } finally {\n this.loading = undefined;\n }\n if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;\n if (!chunk) {\n this.missing.set(this.key(position), position);\n continue;\n }\n this.owned.set(this.key(position), position);\n await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk!));\n }\n } finally {\n for (const [key, read] of this.reads) {\n read.controller.abort();\n if (read.ready) this.reads.delete(key);\n }\n this.running = undefined;\n }\n }\n\n private fillReadSlots(): void {\n while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {\n const position = this.desired.find(position => this.needsLoad(position) && !this.reads.has(this.key(position)));\n if (!position) return;\n const key = this.key(position);\n const read: ChunkRead = {\n position, controller: new AbortController(), promise: Promise.resolve(), ready: false, failed: false\n };\n this.reads.set(key, read);\n read.promise = (async () => {\n try {\n read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);\n if (read.chunk) this.validateChunk(read.chunk, position);\n } catch (error) {\n read.failed = true;\n read.error = error;\n } finally {\n read.ready = true;\n if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {\n this.reads.delete(key);\n }\n }\n })();\n }\n }\n\n /** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */\n private async placeReadyChunks(): Promise<boolean> {\n this.fillReadSlots();\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) {\n if (!this.reads.size) return false;\n await Promise.all([...this.reads.values()].map(read => read.promise));\n return true;\n }\n const head = this.reads.get(this.key(position));\n if (!head) {\n for (let index = this.desired.length - 1; index >= 0; index--) {\n const key = this.key(this.desired[index]);\n if (this.reads.get(key)?.ready) {\n this.reads.delete(key);\n return true;\n }\n }\n // Aborted reads still occupy slots until their source work settles.\n await Promise.race([...this.reads.values()].map(read => read.promise));\n return true;\n }\n if (!head.ready) {\n await head.promise;\n return true;\n }\n const chunks: AnvilChunk[] = [], positions: ChunkPosition[] = [];\n for (const desired of this.desired) {\n if (!this.needsLoad(desired)) continue;\n const key = this.key(desired), read = this.reads.get(key);\n if (!read?.ready) break;\n this.reads.delete(key);\n if (read.failed) this.failed.set(key, { ...desired, error: read.error });\n else if (!read.chunk) this.missing.set(key, desired);\n else {\n chunks.push(read.chunk);\n positions.push(desired);\n }\n }\n if (!chunks.length) return true;\n for (const position of positions) this.owned.set(this.key(position), position);\n await this.placeOwnedColumns(positions, () => this.world.placeChunks!(chunks));\n return true;\n }\n\n private async placeOwnedColumns(positions: readonly ChunkPosition[], place: () => Promise<void>): Promise<void> {\n try {\n await place();\n for (const position of positions) this.loaded.set(this.key(position), position);\n } catch (error) {\n try {\n await this.unloadMany(positions);\n } catch (cleanupError) {\n const failure = error instanceof Error ? error : new Error(String(error));\n try {\n Object.defineProperty(failure, \"cause\", { value: cleanupError, configurable: true });\n } catch {\n throw Object.assign(new Error(failure.message), { cause: cleanupError });\n }\n throw failure;\n }\n throw error;\n }\n }\n\n private needsLoad(position: ChunkPosition): boolean {\n const key = this.key(position);\n return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);\n }\n\n private async unloadMany(positions: readonly ChunkPosition[]): Promise<void> {\n if (!positions.length) return;\n try {\n await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));\n } catch (error) {\n // A rejected batch may have removed any subset; retry every owner before reloading it.\n for (const position of positions) this.loaded.delete(this.key(position));\n throw error;\n }\n for (const position of positions) {\n this.owned.delete(this.key(position));\n this.loaded.delete(this.key(position));\n }\n }\n\n private validateChunk(chunk: AnvilChunk, position: ChunkPosition): void {\n if (chunk.x !== position.x || chunk.z !== position.z) {\n throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);\n }\n }\n\n private within(position: ChunkPosition, radius: number): boolean {\n return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;\n }\n\n private key(position: ChunkPosition): string {\n return `${position.x},${position.z}`;\n }\n}\n"],"mappings":"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","names":["position"]}
@@ -1,5 +1,5 @@
1
1
  // src/world/_culling/BlockCulling.ts
2
- import { DEFAULT_NAMESPACE } from "../../assets/Assets.mjs";
2
+ import { DEFAULT_NAMESPACE } from "../../assets/AssetDefaults.mjs";
3
3
  import { MineRenderData } from "../../assets/MineRenderData.mjs";
4
4
  import { CUBE_FACES, CubeFace } from "../../CubeFace.mjs";
5
5
  async function loadBlockCulling(root) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/world/_culling/BlockCulling.ts"],"sourcesContent":["import { DEFAULT_NAMESPACE } from \"../../assets/Assets\";\nimport { MineRenderData, MineRenderDatasets } from \"../../assets/MineRenderData\";\nimport { CUBE_FACES, CubeFace } from \"../../CubeFace\";\nimport type { Block } from \"../../model/block/Block\";\nimport type { Maybe } from \"../../util/util\";\n\nexport type BlockCullingData = MineRenderDatasets[\"blockCulling\"];\nexport type BlockCullingRule = BlockCullingData[\"profiles\"][number];\ntype CullingBlock = Pick<Block, \"type\" | \"properties\">;\n\n/** Loads vanilla's neighbor face rules, if the selected version publishes them. */\nexport async function loadBlockCulling(root?: string): Promise<Maybe<BlockCullingData>> {\n const { manifest } = await MineRenderData.resolve(root);\n return manifest.datasets.blockCulling ? MineRenderData.get(\"blockCulling\", root) : undefined;\n}\n\n/** The rule of a block type, or `undefined` when the block hides no faces this way. */\nexport function blockCullingRule(data: BlockCullingData, type: string): Maybe<BlockCullingRule> {\n if (!type.includes(\":\")) type = `${DEFAULT_NAMESPACE}:${type}`;\n return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : undefined;\n}\n\n/** `face` indexes CUBE_FACES. Occlusion by opaque cubes is a separate check. */\nexport function skipsBlockFace(data: BlockCullingData, rule: BlockCullingRule, block: CullingBlock, neighbor: CullingBlock, face: number): boolean {\n const type = block.type.includes(\":\") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;\n const other = neighbor.type.includes(\":\") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;\n const same = type === other;\n const vertical = CUBE_FACES[face] === CubeFace.UP || CUBE_FACES[face] === CubeFace.DOWN;\n switch (rule.type) {\n case \"same_block\": return same;\n case \"same_block_vertical\": return same && vertical;\n case \"connected\": {\n if (vertical) return same;\n const group = rule.group ? data.groups?.[rule.group] : undefined;\n return (same || !!group?.includes(type) && group.includes(other))\n && String(block.properties?.[CUBE_FACES[face]]) === \"true\"\n && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === \"true\";\n }\n default: return false;\n }\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,sBAA0C;AACnD,SAAS,YAAY,gBAAgB;AASrC,eAAsB,iBAAiB,MAAiD;AACpF,QAAM,EAAE,SAAS,IAAI,MAAM,eAAe,QAAQ,IAAI;AACtD,SAAO,SAAS,SAAS,eAAe,eAAe,IAAI,gBAAgB,IAAI,IAAI;AACvF;AAGO,SAAS,iBAAiB,MAAwB,MAAuC;AAC5F,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO,GAAG,iBAAiB,IAAI,IAAI;AAC5D,SAAO,OAAO,UAAU,eAAe,KAAK,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,IAAI,CAAC,IAAI;AACxG;AAGO,SAAS,eAAe,MAAwB,MAAwB,OAAqB,UAAwB,MAAuB;AAC/I,QAAM,OAAO,MAAM,KAAK,SAAS,GAAG,IAAI,MAAM,OAAO,GAAG,iBAAiB,IAAI,MAAM,IAAI;AACvF,QAAM,QAAQ,SAAS,KAAK,SAAS,GAAG,IAAI,SAAS,OAAO,GAAG,iBAAiB,IAAI,SAAS,IAAI;AACjG,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW,WAAW,IAAI,MAAM,SAAS,MAAM,WAAW,IAAI,MAAM,SAAS;AACnF,UAAQ,KAAK,MAAM;AAAA,IACf,KAAK;AAAc,aAAO;AAAA,IAC1B,KAAK;AAAuB,aAAO,QAAQ;AAAA,IAC3C,KAAK,aAAa;AACd,UAAI,SAAU,QAAO;AACrB,YAAM,QAAQ,KAAK,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI;AACvD,cAAQ,QAAQ,CAAC,CAAC,OAAO,SAAS,IAAI,KAAK,MAAM,SAAS,KAAK,MACxD,OAAO,MAAM,aAAa,WAAW,IAAI,CAAC,CAAC,MAAM,UACjD,OAAO,SAAS,aAAa,WAAW,OAAO,CAAC,CAAC,CAAC,MAAM;AAAA,IACnE;AAAA,IACA;AAAS,aAAO;AAAA,EACpB;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/world/_culling/BlockCulling.ts"],"sourcesContent":["import { DEFAULT_NAMESPACE } from \"../../assets/AssetDefaults\";\nimport { MineRenderData, MineRenderDatasets } from \"../../assets/MineRenderData\";\nimport { CUBE_FACES, CubeFace } from \"../../CubeFace\";\nimport type { Block } from \"../../model/block/Block\";\nimport type { Maybe } from \"../../util/util\";\n\nexport type BlockCullingData = MineRenderDatasets[\"blockCulling\"];\nexport type BlockCullingRule = BlockCullingData[\"profiles\"][number];\ntype CullingBlock = Pick<Block, \"type\" | \"properties\">;\n\n/** Loads vanilla's neighbor face rules, if the selected version publishes them. */\nexport async function loadBlockCulling(root?: string): Promise<Maybe<BlockCullingData>> {\n const { manifest } = await MineRenderData.resolve(root);\n return manifest.datasets.blockCulling ? MineRenderData.get(\"blockCulling\", root) : undefined;\n}\n\n/** The rule of a block type, or `undefined` when the block hides no faces this way. */\nexport function blockCullingRule(data: BlockCullingData, type: string): Maybe<BlockCullingRule> {\n if (!type.includes(\":\")) type = `${DEFAULT_NAMESPACE}:${type}`;\n return Object.prototype.hasOwnProperty.call(data.blocks, type) ? data.profiles[data.blocks[type]] : undefined;\n}\n\n/** `face` indexes CUBE_FACES. Occlusion by opaque cubes is a separate check. */\nexport function skipsBlockFace(data: BlockCullingData, rule: BlockCullingRule, block: CullingBlock, neighbor: CullingBlock, face: number): boolean {\n const type = block.type.includes(\":\") ? block.type : `${DEFAULT_NAMESPACE}:${block.type}`;\n const other = neighbor.type.includes(\":\") ? neighbor.type : `${DEFAULT_NAMESPACE}:${neighbor.type}`;\n const same = type === other;\n const vertical = CUBE_FACES[face] === CubeFace.UP || CUBE_FACES[face] === CubeFace.DOWN;\n switch (rule.type) {\n case \"same_block\": return same;\n case \"same_block_vertical\": return same && vertical;\n case \"connected\": {\n if (vertical) return same;\n const group = rule.group ? data.groups?.[rule.group] : undefined;\n return (same || !!group?.includes(type) && group.includes(other))\n && String(block.properties?.[CUBE_FACES[face]]) === \"true\"\n && String(neighbor.properties?.[CUBE_FACES[face ^ 1]]) === \"true\";\n }\n default: return false;\n }\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,sBAA0C;AACnD,SAAS,YAAY,gBAAgB;AASrC,eAAsB,iBAAiB,MAAiD;AACpF,QAAM,EAAE,SAAS,IAAI,MAAM,eAAe,QAAQ,IAAI;AACtD,SAAO,SAAS,SAAS,eAAe,eAAe,IAAI,gBAAgB,IAAI,IAAI;AACvF;AAGO,SAAS,iBAAiB,MAAwB,MAAuC;AAC5F,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO,GAAG,iBAAiB,IAAI,IAAI;AAC5D,SAAO,OAAO,UAAU,eAAe,KAAK,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,IAAI,CAAC,IAAI;AACxG;AAGO,SAAS,eAAe,MAAwB,MAAwB,OAAqB,UAAwB,MAAuB;AAC/I,QAAM,OAAO,MAAM,KAAK,SAAS,GAAG,IAAI,MAAM,OAAO,GAAG,iBAAiB,IAAI,MAAM,IAAI;AACvF,QAAM,QAAQ,SAAS,KAAK,SAAS,GAAG,IAAI,SAAS,OAAO,GAAG,iBAAiB,IAAI,SAAS,IAAI;AACjG,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW,WAAW,IAAI,MAAM,SAAS,MAAM,WAAW,IAAI,MAAM,SAAS;AACnF,UAAQ,KAAK,MAAM;AAAA,IACf,KAAK;AAAc,aAAO;AAAA,IAC1B,KAAK;AAAuB,aAAO,QAAQ;AAAA,IAC3C,KAAK,aAAa;AACd,UAAI,SAAU,QAAO;AACrB,YAAM,QAAQ,KAAK,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI;AACvD,cAAQ,QAAQ,CAAC,CAAC,OAAO,SAAS,IAAI,KAAK,MAAM,SAAS,KAAK,MACxD,OAAO,MAAM,aAAa,WAAW,IAAI,CAAC,CAAC,MAAM,UACjD,OAAO,SAAS,aAAa,WAAW,OAAO,CAAC,CAAC,CAAC,MAAM;AAAA,IACnE;AAAA,IACA;AAAS,aAAO;AAAA,EACpB;AACJ;","names":[]}
@@ -6,6 +6,8 @@ import { resolveSavedEntity } from "./SavedEntities.mjs";
6
6
  import { BannerPatterns } from "../../assets/BannerPatterns.mjs";
7
7
  import { Vector3 } from "three";
8
8
  import { hitNormal, pickableMeshes, visibleHits } from "../_picking/Picking.mjs";
9
+ import { logger } from "../../util/log.mjs";
10
+ var log = logger("WorldEntities");
9
11
  var WorldEntities = class {
10
12
  constructor(scene) {
11
13
  this.scene = scene;
@@ -74,7 +76,7 @@ var WorldEntities = class {
74
76
  placement.owner.set(object, { type: key.toNamespacedString(), position: placement.position });
75
77
  this.scene.add(object);
76
78
  } catch (error) {
77
- console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
79
+ log.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
78
80
  } finally {
79
81
  if (object && !placement.owner.has(object)) object.dispose();
80
82
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/world/_entities/WorldEntities.ts"],"sourcesContent":["import { Renames } from \"../../assets/Renames\";\nimport { Entities } from \"../../assets/Entities\";\nimport { EntityObject } from \"../../entity/scene/EntityObject\";\nimport type { MultiBlockEntity } from \"../../model/multiblock/MultiBlockStructure\";\nimport type { MineRenderScene } from \"../../renderer/MineRenderScene\";\nimport { resolveSavedEntity } from \"./SavedEntities\";\nimport { BannerPatterns } from \"../../assets/BannerPatterns\";\nimport { Mesh, Raycaster, Vector3 } from \"three\";\nimport type { WorldEntityIntersection } from \"../WorldEntityIntersection\";\nimport { hitNormal, pickableMeshes, visibleHits } from \"../_picking/Picking\";\n\n/** Owns static entity placements independently of the world's block sections. */\nexport class WorldEntities {\n private readonly columns = new Map<string, Map<EntityObject, { type: string; position: MultiBlockEntity[\"position\"] }>>();\n\n constructor(private readonly scene: MineRenderScene) {}\n\n /** Finds the nearest visible triangle belonging to an entity owned by this world. */\n raycast(raycaster: Raycaster): WorldEntityIntersection | undefined {\n const owners = new Map<Mesh, { object: EntityObject; type: string; position: MultiBlockEntity[\"position\"] }>();\n for (const column of this.columns.values()) {\n for (const [object, placement] of column) {\n for (const mesh of pickableMeshes(object, this.scene, raycaster)) {\n owners.set(mesh, { object, ...placement });\n }\n }\n }\n for (const hit of visibleHits(raycaster, [...owners.keys()])) {\n const mesh = hit.object as Mesh;\n const owner = owners.get(mesh)!;\n return {\n type: owner.type,\n position: new Vector3(...owner.position),\n object: owner.object,\n point: hit.point.clone(),\n normal: hitNormal(hit),\n distance: hit.distance\n };\n }\n return undefined;\n }\n\n /** Captures column ownership before asynchronous block or asset loading starts. */\n prepare(entities: readonly MultiBlockEntity[] = [], column?: [number, number]): () => Promise<void> {\n const placements = entities.flatMap(entity => {\n const resolved = resolveSavedEntity(entity);\n if (!resolved) return [];\n const position = [...entity.position] as MultiBlockEntity[\"position\"];\n const key = column?.join(\",\") ?? `${Math.floor(position[0] / 16)},${Math.floor(position[2] / 16)}`;\n let owner = this.columns.get(key);\n if (!owner) this.columns.set(key, owner = new Map());\n return [{ ...resolved, position, column: key, owner }];\n });\n return async () => {\n await Promise.all(placements.map(async placement => {\n const current = () => this.columns.get(placement.column) === placement.owner;\n let object: EntityObject | undefined;\n try {\n if (!current()) return;\n const models = await Entities.getEntityList();\n // An ID the entity dataset does not list may be an older name for one it lists.\n const renamed = models.includes(placement.key.path) ? undefined : await Renames.key(\"entities\", placement.key);\n const key = renamed && models.includes(renamed.path) ? renamed : placement.key;\n if (!models.includes(key.path) || !current()) return;\n const model = await Entities.getEntity(key, placement.texture, { when: placement.when });\n if (!model || !current()) return;\n let tints = placement.tints;\n if (placement.woolDye) {\n const dye = (await BannerPatterns.getColors(placement.key.root))[placement.woolDye];\n // Sheep darken each dye channel; white wool has its own fixed shade.\n const tint = placement.woolDye === \"white\" ? 0xe6e6e6 : (Math.floor((dye >> 16 & 255) * 0.75) << 16)\n | (Math.floor((dye >> 8 & 255) * 0.75) << 8) | Math.floor((dye & 255) * 0.75);\n tints = { ...tints, wool_color: tint };\n }\n if (!current()) return;\n object = new EntityObject(model, { instanceMeshes: false, tints });\n object.scene = this.scene;\n // Block geometry is centered on integer coordinates; saved positions use block corners.\n object.position.fromArray(placement.position).multiplyScalar(16).addScalar(-8);\n object.rotation.y = placement.yaw;\n await object.init();\n if (!current()) return;\n placement.owner.set(object, { type: key.toNamespacedString(), position: placement.position });\n this.scene.add(object);\n } catch (error) {\n console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);\n } finally {\n if (object && !placement.owner.has(object)) object.dispose();\n }\n }));\n };\n }\n\n clearColumn(x: number, z: number): void {\n const key = `${x},${z}`;\n const objects = this.columns.get(key);\n this.columns.delete(key);\n if (!objects) return;\n for (const object of objects.keys()) {\n object.removeFromScene();\n object.dispose();\n }\n objects.clear();\n }\n\n clear(): void {\n for (const key of this.columns.keys()) {\n const [x, z] = key.split(\",\").map(Number);\n this.clearColumn(x, z);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/world/_entities/WorldEntities.ts"],"sourcesContent":["import { Renames } from \"../../assets/Renames\";\nimport { Entities } from \"../../assets/Entities\";\nimport { EntityObject } from \"../../entity/scene/EntityObject\";\nimport type { MultiBlockEntity } from \"../../model/multiblock/MultiBlockStructure\";\nimport type { MineRenderScene } from \"../../renderer/MineRenderScene\";\nimport { resolveSavedEntity } from \"./SavedEntities\";\nimport { BannerPatterns } from \"../../assets/BannerPatterns\";\nimport { Mesh, Raycaster, Vector3 } from \"three\";\nimport type { WorldEntityIntersection } from \"../WorldEntityIntersection\";\nimport { hitNormal, pickableMeshes, visibleHits } from \"../_picking/Picking\";\nimport { logger } from \"../../util/log\";\n\nconst log = logger(\"WorldEntities\");\n\n/** Owns static entity placements independently of the world's block sections. */\nexport class WorldEntities {\n private readonly columns = new Map<string, Map<EntityObject, { type: string; position: MultiBlockEntity[\"position\"] }>>();\n\n constructor(private readonly scene: MineRenderScene) {}\n\n /** Finds the nearest visible triangle belonging to an entity owned by this world. */\n raycast(raycaster: Raycaster): WorldEntityIntersection | undefined {\n const owners = new Map<Mesh, { object: EntityObject; type: string; position: MultiBlockEntity[\"position\"] }>();\n for (const column of this.columns.values()) {\n for (const [object, placement] of column) {\n for (const mesh of pickableMeshes(object, this.scene, raycaster)) {\n owners.set(mesh, { object, ...placement });\n }\n }\n }\n for (const hit of visibleHits(raycaster, [...owners.keys()])) {\n const mesh = hit.object as Mesh;\n const owner = owners.get(mesh)!;\n return {\n type: owner.type,\n position: new Vector3(...owner.position),\n object: owner.object,\n point: hit.point.clone(),\n normal: hitNormal(hit),\n distance: hit.distance\n };\n }\n return undefined;\n }\n\n /** Captures column ownership before asynchronous block or asset loading starts. */\n prepare(entities: readonly MultiBlockEntity[] = [], column?: [number, number]): () => Promise<void> {\n const placements = entities.flatMap(entity => {\n const resolved = resolveSavedEntity(entity);\n if (!resolved) return [];\n const position = [...entity.position] as MultiBlockEntity[\"position\"];\n const key = column?.join(\",\") ?? `${Math.floor(position[0] / 16)},${Math.floor(position[2] / 16)}`;\n let owner = this.columns.get(key);\n if (!owner) this.columns.set(key, owner = new Map());\n return [{ ...resolved, position, column: key, owner }];\n });\n return async () => {\n await Promise.all(placements.map(async placement => {\n const current = () => this.columns.get(placement.column) === placement.owner;\n let object: EntityObject | undefined;\n try {\n if (!current()) return;\n const models = await Entities.getEntityList();\n // An ID the entity dataset does not list may be an older name for one it lists.\n const renamed = models.includes(placement.key.path) ? undefined : await Renames.key(\"entities\", placement.key);\n const key = renamed && models.includes(renamed.path) ? renamed : placement.key;\n if (!models.includes(key.path) || !current()) return;\n const model = await Entities.getEntity(key, placement.texture, { when: placement.when });\n if (!model || !current()) return;\n let tints = placement.tints;\n if (placement.woolDye) {\n const dye = (await BannerPatterns.getColors(placement.key.root))[placement.woolDye];\n // Sheep darken each dye channel; white wool has its own fixed shade.\n const tint = placement.woolDye === \"white\" ? 0xe6e6e6 : (Math.floor((dye >> 16 & 255) * 0.75) << 16)\n | (Math.floor((dye >> 8 & 255) * 0.75) << 8) | Math.floor((dye & 255) * 0.75);\n tints = { ...tints, wool_color: tint };\n }\n if (!current()) return;\n object = new EntityObject(model, { instanceMeshes: false, tints });\n object.scene = this.scene;\n // Block geometry is centered on integer coordinates; saved positions use block corners.\n object.position.fromArray(placement.position).multiplyScalar(16).addScalar(-8);\n object.rotation.y = placement.yaw;\n await object.init();\n if (!current()) return;\n placement.owner.set(object, { type: key.toNamespacedString(), position: placement.position });\n this.scene.add(object);\n } catch (error) {\n log.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);\n } finally {\n if (object && !placement.owner.has(object)) object.dispose();\n }\n }));\n };\n }\n\n clearColumn(x: number, z: number): void {\n const key = `${x},${z}`;\n const objects = this.columns.get(key);\n this.columns.delete(key);\n if (!objects) return;\n for (const object of objects.keys()) {\n object.removeFromScene();\n object.dispose();\n }\n objects.clear();\n }\n\n clear(): void {\n for (const key of this.columns.keys()) {\n const [x, z] = key.split(\",\").map(Number);\n this.clearColumn(x, z);\n }\n }\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAG7B,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAC/B,SAA0B,eAAe;AAEzC,SAAS,WAAW,gBAAgB,mBAAmB;AACvD,SAAS,cAAc;AAEvB,IAAM,MAAM,OAAO,eAAe;AAG3B,IAAM,gBAAN,MAAoB;AAAA,EAGvB,YAA6B,OAAwB;AAAxB;AAF7B,SAAiB,UAAU,oBAAI,IAAyF;AAAA,EAElE;AAAA;AAAA,EAGtD,QAAQ,WAA2D;AAC/D,UAAM,SAAS,oBAAI,IAA0F;AAC7G,eAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AACxC,iBAAW,CAAC,QAAQ,SAAS,KAAK,QAAQ;AACtC,mBAAW,QAAQ,eAAe,QAAQ,KAAK,OAAO,SAAS,GAAG;AAC9D,iBAAO,IAAI,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;AAAA,QAC7C;AAAA,MACJ;AAAA,IACJ;AACA,eAAW,OAAO,YAAY,WAAW,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,GAAG;AAC1D,YAAM,OAAO,IAAI;AACjB,YAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,aAAO;AAAA,QACH,MAAM,MAAM;AAAA,QACZ,UAAU,IAAI,QAAQ,GAAG,MAAM,QAAQ;AAAA,QACvC,QAAQ,MAAM;AAAA,QACd,OAAO,IAAI,MAAM,MAAM;AAAA,QACvB,QAAQ,UAAU,GAAG;AAAA,QACrB,UAAU,IAAI;AAAA,MAClB;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA;AAAA,EAGA,QAAQ,WAAwC,CAAC,GAAG,QAAgD;AAChG,UAAM,aAAa,SAAS,QAAQ,YAAU;AAC1C,YAAM,WAAW,mBAAmB,MAAM;AAC1C,UAAI,CAAC,SAAU,QAAO,CAAC;AACvB,YAAM,WAAW,CAAC,GAAG,OAAO,QAAQ;AACpC,YAAM,MAAM,QAAQ,KAAK,GAAG,KAAK,GAAG,KAAK,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;AAChG,UAAI,QAAQ,KAAK,QAAQ,IAAI,GAAG;AAChC,UAAI,CAAC,MAAO,MAAK,QAAQ,IAAI,KAAK,QAAQ,oBAAI,IAAI,CAAC;AACnD,aAAO,CAAC,EAAE,GAAG,UAAU,UAAU,QAAQ,KAAK,MAAM,CAAC;AAAA,IACzD,CAAC;AACD,WAAO,YAAY;AACf,YAAM,QAAQ,IAAI,WAAW,IAAI,OAAM,cAAa;AAChD,cAAM,UAAU,MAAM,KAAK,QAAQ,IAAI,UAAU,MAAM,MAAM,UAAU;AACvE,YAAI;AACJ,YAAI;AACA,cAAI,CAAC,QAAQ,EAAG;AAChB,gBAAM,SAAS,MAAM,SAAS,cAAc;AAE5C,gBAAM,UAAU,OAAO,SAAS,UAAU,IAAI,IAAI,IAAI,SAAY,MAAM,QAAQ,IAAI,YAAY,UAAU,GAAG;AAC7G,gBAAM,MAAM,WAAW,OAAO,SAAS,QAAQ,IAAI,IAAI,UAAU,UAAU;AAC3E,cAAI,CAAC,OAAO,SAAS,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAG;AAC9C,gBAAM,QAAQ,MAAM,SAAS,UAAU,KAAK,UAAU,SAAS,EAAE,MAAM,UAAU,KAAK,CAAC;AACvF,cAAI,CAAC,SAAS,CAAC,QAAQ,EAAG;AAC1B,cAAI,QAAQ,UAAU;AACtB,cAAI,UAAU,SAAS;AACnB,kBAAM,OAAO,MAAM,eAAe,UAAU,UAAU,IAAI,IAAI,GAAG,UAAU,OAAO;AAElF,kBAAM,OAAO,UAAU,YAAY,UAAU,WAAY,KAAK,OAAO,OAAO,KAAK,OAAO,IAAI,KAAK,KAC1F,KAAK,OAAO,OAAO,IAAI,OAAO,IAAI,KAAK,IAAK,KAAK,OAAO,MAAM,OAAO,IAAI;AAChF,oBAAQ,EAAE,GAAG,OAAO,YAAY,KAAK;AAAA,UACzC;AACA,cAAI,CAAC,QAAQ,EAAG;AAChB,mBAAS,IAAI,aAAa,OAAO,EAAE,gBAAgB,OAAO,MAAM,CAAC;AACjE,iBAAO,QAAQ,KAAK;AAEpB,iBAAO,SAAS,UAAU,UAAU,QAAQ,EAAE,eAAe,EAAE,EAAE,UAAU,EAAE;AAC7E,iBAAO,SAAS,IAAI,UAAU;AAC9B,gBAAM,OAAO,KAAK;AAClB,cAAI,CAAC,QAAQ,EAAG;AAChB,oBAAU,MAAM,IAAI,QAAQ,EAAE,MAAM,IAAI,mBAAmB,GAAG,UAAU,UAAU,SAAS,CAAC;AAC5F,eAAK,MAAM,IAAI,MAAM;AAAA,QACzB,SAAS,OAAO;AACZ,cAAI,KAAK,+BAA+B,UAAU,IAAI,mBAAmB,CAAC,iBAAiB,KAAK;AAAA,QACpG,UAAE;AACE,cAAI,UAAU,CAAC,UAAU,MAAM,IAAI,MAAM,EAAG,QAAO,QAAQ;AAAA,QAC/D;AAAA,MACJ,CAAC,CAAC;AAAA,IACN;AAAA,EACJ;AAAA,EAEA,YAAY,GAAW,GAAiB;AACpC,UAAM,MAAM,GAAG,CAAC,IAAI,CAAC;AACrB,UAAM,UAAU,KAAK,QAAQ,IAAI,GAAG;AACpC,SAAK,QAAQ,OAAO,GAAG;AACvB,QAAI,CAAC,QAAS;AACd,eAAW,UAAU,QAAQ,KAAK,GAAG;AACjC,aAAO,gBAAgB;AACvB,aAAO,QAAQ;AAAA,IACnB;AACA,YAAQ,MAAM;AAAA,EAClB;AAAA,EAEA,QAAc;AACV,eAAW,OAAO,KAAK,QAAQ,KAAK,GAAG;AACnC,YAAM,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM;AACxC,WAAK,YAAY,GAAG,CAAC;AAAA,IACzB;AAAA,EACJ;AACJ;","names":[]}