@woosh/meep-engine 3.29.0 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +14 -2
- package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/BVH.js +14 -1
- package/src/core/cache/FrequencySketch.d.ts +3 -1
- package/src/core/cache/FrequencySketch.d.ts.map +1 -1
- package/src/core/cache/FrequencySketch.js +10 -4
- package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
- package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +48 -18
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +262 -70
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
- package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
- package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
- package/src/core/math/lookup/ParameterLookupTable.js +10 -5
- package/src/core/math/noise/sdnoise.d.ts.map +1 -1
- package/src/core/math/noise/sdnoise.js +16 -10
- package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
- package/src/core/model/object/ImmutableObjectPool.js +17 -7
- package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
- package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
- package/src/core/process/executor/ConcurrentExecutor.js +95 -1
- package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
- package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
- package/src/core/process/task/task_find_dependency_cycle.js +81 -0
- package/src/core/wfc/WFCCellChoice.d.ts +17 -0
- package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
- package/src/core/wfc/WFCCellChoice.js +36 -0
- package/src/core/wfc/WFCLattice.d.ts +105 -0
- package/src/core/wfc/WFCLattice.d.ts.map +1 -0
- package/src/core/wfc/WFCLattice.js +235 -0
- package/src/core/wfc/WFCRuleTable.d.ts +172 -0
- package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
- package/src/core/wfc/WFCRuleTable.js +334 -0
- package/src/core/wfc/WFCState.d.ts +273 -0
- package/src/core/wfc/WFCState.d.ts.map +1 -0
- package/src/core/wfc/WFCState.js +552 -0
- package/src/core/wfc/WFCStatus.d.ts +11 -0
- package/src/core/wfc/WFCStatus.d.ts.map +1 -0
- package/src/core/wfc/WFCStatus.js +28 -0
- package/src/core/wfc/WFCTileWeights.d.ts +48 -0
- package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
- package/src/core/wfc/WFCTileWeights.js +86 -0
- package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
- package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
- package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
- package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
- package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
- package/src/core/wfc/wfc_estimate_bytes.js +47 -0
- package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
- package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
- package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
- package/src/core/wfc/wfc_solve.d.ts +21 -0
- package/src/core/wfc/wfc_solve.d.ts.map +1 -0
- package/src/core/wfc/wfc_solve.js +154 -0
- package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
- package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
- package/src/core/wfc/wfc_state_assign.d.ts +11 -0
- package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_assign.js +36 -0
- package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
- package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_ban_bits.js +81 -0
- package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
- package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_collapse.js +73 -0
- package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
- package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
- package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
- package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_propagate.js +155 -0
- package/src/core/wfc/wfc_state_read.d.ts +11 -0
- package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_read.js +38 -0
- package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
- package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_restrict.js +39 -0
- package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
- package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_set_eligible.js +49 -0
- package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
- package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_undo_to.js +84 -0
- package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
- package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
- package/src/core/wfc/wfc_words_per_cell.js +16 -0
- package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
- package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
- package/src/engine/ecs/EntityComponentDataset.js +37 -14
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
- package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
- package/src/engine/ecs/guid/UUID.js +45 -17
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
- package/src/engine/interpolation/Interpoland.d.ts +7 -1
- package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
- package/src/engine/interpolation/Interpoland.js +8 -1
- package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
- package/src/engine/interpolation/InterpolationSystem.js +3 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
- package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
- package/src/engine/interpolation/pose_interpoland.js +2 -0
- package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
- package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
- package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
- package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
- package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
- package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
- package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
- package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
- package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
- package/src/format/image/avif/av1/util/round2.d.ts +19 -2
- package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
- package/src/format/image/avif/av1/util/round2.js +51 -27
- package/src/format/image/avif/heif/parse_avif_file.js +24 -0
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +46 -3
- package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +91 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +0 -13
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +0 -15
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +5 -5
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +11 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +11 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +18 -9
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +20 -26
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +1 -3
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +2 -5
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
- package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
- package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
- package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
- package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
- package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
- package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
- package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
- package/src/shade/renderer/particles/DESIGN.md +14 -5
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
- package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
- package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
- package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles.js +8 -2
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +19 -2
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
- package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
- package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
- package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
- package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
- package/src/view/tooltip/DomTooltipManager.js +22 -0
- package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
- package/src/view/tooltip/DomTooltipObserver.js +13 -3
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.js +0 -45
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bits that name real tiles in the final word of a `words_per_cell`-word mask.
|
|
3
|
+
*
|
|
4
|
+
* A named function because the natural spelling `(1 << (tile_count & 31)) - 1` yields `0` — every
|
|
5
|
+
* tile dead — at exactly 32, 64, 96 … tiles, JavaScript taking the shift count modulo 32.
|
|
6
|
+
*
|
|
7
|
+
* @param {number} tile_count
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export function wfc_tail_mask(tile_count: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* Bytes a {@link WFCRuleTable} of this shape would hold, without building one.
|
|
13
|
+
*
|
|
14
|
+
* @param {number} tile_count
|
|
15
|
+
* @param {number} direction_count
|
|
16
|
+
* @returns {number}
|
|
17
|
+
*/
|
|
18
|
+
export function wfc_rule_table_byte_size(tile_count: number, direction_count: number): number;
|
|
19
|
+
/**
|
|
20
|
+
* Is the table symmetric under `d ^ 1` — does `t` allow `u` across `d` exactly when `u` allows `t`
|
|
21
|
+
* across `d ^ 1`?
|
|
22
|
+
*
|
|
23
|
+
* Propagation assumes this. A table built only from {@link WFCRuleTable#allow_symmetric} and
|
|
24
|
+
* {@link WFCRuleTable#deny_symmetric} has it by construction; check one that did not.
|
|
25
|
+
*
|
|
26
|
+
* `O(tile_count² × direction_count)` — a build-time check, not a per-solve one.
|
|
27
|
+
*
|
|
28
|
+
* @param {WFCRuleTable} rules
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
export function wfc_rule_table_is_symmetric(rules: WFCRuleTable): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Which adjacencies are legal, as one flat bit table.
|
|
34
|
+
*
|
|
35
|
+
* `data[((d * tile_count) + t) * words_per_cell + w]` — bit `b` set means "tile `w*32 + b` may sit
|
|
36
|
+
* at the neighbour reached by stepping in direction `d` away from a tile `t`".
|
|
37
|
+
*
|
|
38
|
+
* **Directions.** `direction_count` must be even. Direction `d` is `axis = d >> 1`,
|
|
39
|
+
* `side = d & 1`, and `d ^ 1` is the opposite direction. Propagation is sound only if the table is
|
|
40
|
+
* symmetric under that involution, so build one with {@link allow_symmetric} and
|
|
41
|
+
* {@link deny_symmetric}; {@link allow_directed} is for deliberately asymmetric tables and is the
|
|
42
|
+
* documented way to get it wrong. {@link wfc_rule_table_is_symmetric} checks.
|
|
43
|
+
*
|
|
44
|
+
* **Size is quadratic in the tile count** — `direction_count × tile_count × ceil(tile_count/32) × 4`
|
|
45
|
+
* bytes: 2 KiB at 64 tiles in 2-D, 32 KiB at 256 (about one L1), 12 MiB at 4096 in 3-D. The design
|
|
46
|
+
* assumes this table is small enough to stay hot, so check {@link wfc_rule_table_byte_size} before
|
|
47
|
+
* committing to a large tile count.
|
|
48
|
+
*
|
|
49
|
+
* Call {@link finalize} after the last mutation and before building a {@link WFCState}.
|
|
50
|
+
*
|
|
51
|
+
* @author Alex Goldring
|
|
52
|
+
* @copyright Company Named Limited (c) 2026
|
|
53
|
+
*/
|
|
54
|
+
export class WFCRuleTable {
|
|
55
|
+
/**
|
|
56
|
+
* @param {number} tile_count number of distinct tiles, at least 1
|
|
57
|
+
* @param {number} direction_count `2 × dimension_count`, at least 2 and even
|
|
58
|
+
*/
|
|
59
|
+
constructor(tile_count: number, direction_count: number);
|
|
60
|
+
/** @type {number} */
|
|
61
|
+
tile_count: number;
|
|
62
|
+
/** @type {number} */
|
|
63
|
+
direction_count: number;
|
|
64
|
+
/** @type {number} */
|
|
65
|
+
words_per_cell: number;
|
|
66
|
+
/**
|
|
67
|
+
* Bits at or above `tile_count` in the final word of a row. Every write masks with this,
|
|
68
|
+
* so no rule row can name a tile that does not exist and the wave's tail invariant is
|
|
69
|
+
* never threatened from this side.
|
|
70
|
+
* @type {number}
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
private __tail_mask;
|
|
74
|
+
/**
|
|
75
|
+
* `direction_count × tile_count` rows of `words_per_cell` words.
|
|
76
|
+
* @type {Uint32Array}
|
|
77
|
+
*/
|
|
78
|
+
data: Uint32Array;
|
|
79
|
+
/**
|
|
80
|
+
* `full_support[d * words_per_cell + w]` — the union of every tile's row in direction `d`.
|
|
81
|
+
*
|
|
82
|
+
* Read by the propagator, which uses it to relax across a cell that has lost nothing in
|
|
83
|
+
* `O(words_per_cell)` rather than scanning that cell's whole domain.
|
|
84
|
+
*
|
|
85
|
+
* Built by {@link finalize}; stale until then.
|
|
86
|
+
* @type {Uint32Array}
|
|
87
|
+
*/
|
|
88
|
+
full_support: Uint32Array;
|
|
89
|
+
/**
|
|
90
|
+
* @type {boolean}
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
private __finalized;
|
|
94
|
+
/**
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
97
|
+
get finalized(): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Bytes held by {@link data} and {@link full_support}.
|
|
100
|
+
* @returns {number}
|
|
101
|
+
*/
|
|
102
|
+
get byte_size(): number;
|
|
103
|
+
/**
|
|
104
|
+
* Every adjacency legal. The starting point for a table you build by denying.
|
|
105
|
+
*/
|
|
106
|
+
fill_allow(): void;
|
|
107
|
+
/**
|
|
108
|
+
* No adjacency legal. The starting point for a table you build by allowing.
|
|
109
|
+
*/
|
|
110
|
+
fill_deny(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Allow `tile_b` to sit on the `direction` side of `tile_a`, **and** `tile_a` to sit on the
|
|
113
|
+
* opposite side of `tile_b`.
|
|
114
|
+
*
|
|
115
|
+
* This is the mutator to use. The one-sided {@link allow_directed} exists for callers building
|
|
116
|
+
* a deliberately asymmetric table, and is the documented way to make propagation unsound.
|
|
117
|
+
*
|
|
118
|
+
* @param {number} direction
|
|
119
|
+
* @param {number} tile_a
|
|
120
|
+
* @param {number} tile_b
|
|
121
|
+
*/
|
|
122
|
+
allow_symmetric(direction: number, tile_a: number, tile_b: number): void;
|
|
123
|
+
/**
|
|
124
|
+
* @param {number} direction
|
|
125
|
+
* @param {number} tile_a
|
|
126
|
+
* @param {number} tile_b
|
|
127
|
+
*/
|
|
128
|
+
deny_symmetric(direction: number, tile_a: number, tile_b: number): void;
|
|
129
|
+
/**
|
|
130
|
+
* Allow `tile_b` to sit on the `direction` side of `tile_a`, and say nothing about the reverse.
|
|
131
|
+
* @param {number} direction
|
|
132
|
+
* @param {number} tile_a
|
|
133
|
+
* @param {number} tile_b
|
|
134
|
+
*/
|
|
135
|
+
allow_directed(direction: number, tile_a: number, tile_b: number): void;
|
|
136
|
+
/**
|
|
137
|
+
* @param {number} direction
|
|
138
|
+
* @param {number} tile_a
|
|
139
|
+
* @param {number} tile_b
|
|
140
|
+
*/
|
|
141
|
+
deny_directed(direction: number, tile_a: number, tile_b: number): void;
|
|
142
|
+
/**
|
|
143
|
+
* @param {number} direction
|
|
144
|
+
* @param {number} tile_a
|
|
145
|
+
* @param {number} tile_b
|
|
146
|
+
* @returns {boolean} may `tile_b` sit on the `direction` side of `tile_a`?
|
|
147
|
+
*/
|
|
148
|
+
allows(direction: number, tile_a: number, tile_b: number): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Build {@link full_support}. Must be called after the last mutation and before a
|
|
151
|
+
* {@link WFCState} is built against this table; every mutator clears the flag.
|
|
152
|
+
*
|
|
153
|
+
* Builds nothing else. In particular a tile whose rule row is empty in some direction is left
|
|
154
|
+
* alone rather than removed, because such a tile can still sit legally on the matching border,
|
|
155
|
+
* where there is no neighbour that way. {@link rows_without_support} reports them.
|
|
156
|
+
*/
|
|
157
|
+
finalize(): void;
|
|
158
|
+
/**
|
|
159
|
+
* Diagnostic: `(direction, tile)` pairs whose rule row is empty.
|
|
160
|
+
*
|
|
161
|
+
* Such a tile can only be placed where the lattice has no neighbour in that direction — on the
|
|
162
|
+
* matching border, or nowhere at all if the axis wraps. Usually it means a rule was forgotten,
|
|
163
|
+
* but a border-only tile is legitimate, so this is reported rather than treated as an error.
|
|
164
|
+
*
|
|
165
|
+
* @returns {{direction:number, tile:number}[]}
|
|
166
|
+
*/
|
|
167
|
+
rows_without_support(): {
|
|
168
|
+
direction: number;
|
|
169
|
+
tile: number;
|
|
170
|
+
}[];
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=WFCRuleTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WFCRuleTable.d.ts","sourceRoot":"","sources":["../../../../src/core/wfc/WFCRuleTable.js"],"names":[],"mappings":"AAqRA;;;;;;;;GAQG;AACH,0CAHW,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;GAMG;AACH,qDAJW,MAAM,mBACN,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;;;;;;GAWG;AACH,mDAHW,YAAY,GACV,OAAO,CAkBnB;AAzUD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IACI;;;OAGG;IACH,wBAHW,MAAM,mBACN,MAAM,EAuDhB;IAtCG,qBAAqB;IACrB,YADW,MAAM,CACW;IAC5B,qBAAqB;IACrB,iBADW,MAAM,CACqB;IACtC,qBAAqB;IACrB,gBADW,MAAM,CACmB;IAEpC;;;;;;OAMG;IACH,oBAA4C;IAE5C;;;OAGG;IACH,MAFU,WAAW,CAEqD;IAE1E;;;;;;;;OAQG;IACH,cAFU,WAAW,CAEgD;IAErE;;;OAGG;IACH,oBAAwB;IAG5B;;OAEG;IACH,yBAEC;IAED;;;OAGG;IACH,wBAEC;IAED;;OAEG;IACH,mBAaC;IAED;;OAEG;IACH,kBAGC;IAED;;;;;;;;;;OAUG;IACH,2BAJW,MAAM,UACN,MAAM,UACN,MAAM,QAKhB;IAED;;;;OAIG;IACH,0BAJW,MAAM,UACN,MAAM,UACN,MAAM,QAKhB;IAED;;;;;OAKG;IACH,0BAJW,MAAM,UACN,MAAM,UACN,MAAM,QAehB;IAED;;;;OAIG;IACH,yBAJW,MAAM,UACN,MAAM,UACN,MAAM,QAehB;IAED;;;;;OAKG;IACH,kBALW,MAAM,UACN,MAAM,UACN,MAAM,GACJ,OAAO,CAanB;IAED;;;;;;;OAOG;IACH,iBAmBC;IAED;;;;;;;;OAQG;IACH,wBAFa;QAAC,SAAS,EAAC,MAAM,CAAC;QAAC,IAAI,EAAC,MAAM,CAAA;KAAC,EAAE,CAyB7C;CACJ"}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
import { WFC_UNRESOLVED } from "./WFC_UNRESOLVED.js";
|
|
3
|
+
import { wfc_words_per_cell } from "./wfc_words_per_cell.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Which adjacencies are legal, as one flat bit table.
|
|
7
|
+
*
|
|
8
|
+
* `data[((d * tile_count) + t) * words_per_cell + w]` — bit `b` set means "tile `w*32 + b` may sit
|
|
9
|
+
* at the neighbour reached by stepping in direction `d` away from a tile `t`".
|
|
10
|
+
*
|
|
11
|
+
* **Directions.** `direction_count` must be even. Direction `d` is `axis = d >> 1`,
|
|
12
|
+
* `side = d & 1`, and `d ^ 1` is the opposite direction. Propagation is sound only if the table is
|
|
13
|
+
* symmetric under that involution, so build one with {@link allow_symmetric} and
|
|
14
|
+
* {@link deny_symmetric}; {@link allow_directed} is for deliberately asymmetric tables and is the
|
|
15
|
+
* documented way to get it wrong. {@link wfc_rule_table_is_symmetric} checks.
|
|
16
|
+
*
|
|
17
|
+
* **Size is quadratic in the tile count** — `direction_count × tile_count × ceil(tile_count/32) × 4`
|
|
18
|
+
* bytes: 2 KiB at 64 tiles in 2-D, 32 KiB at 256 (about one L1), 12 MiB at 4096 in 3-D. The design
|
|
19
|
+
* assumes this table is small enough to stay hot, so check {@link wfc_rule_table_byte_size} before
|
|
20
|
+
* committing to a large tile count.
|
|
21
|
+
*
|
|
22
|
+
* Call {@link finalize} after the last mutation and before building a {@link WFCState}.
|
|
23
|
+
*
|
|
24
|
+
* @author Alex Goldring
|
|
25
|
+
* @copyright Company Named Limited (c) 2026
|
|
26
|
+
*/
|
|
27
|
+
export class WFCRuleTable {
|
|
28
|
+
/**
|
|
29
|
+
* @param {number} tile_count number of distinct tiles, at least 1
|
|
30
|
+
* @param {number} direction_count `2 × dimension_count`, at least 2 and even
|
|
31
|
+
*/
|
|
32
|
+
constructor(tile_count, direction_count) {
|
|
33
|
+
assert.isPositiveInteger(tile_count, 'tile_count');
|
|
34
|
+
assert.isPositiveInteger(direction_count, 'direction_count');
|
|
35
|
+
|
|
36
|
+
if (tile_count >= WFC_UNRESOLVED) {
|
|
37
|
+
// the read-out sentinel would collide with a real tile id
|
|
38
|
+
throw new Error(`tile_count(=${tile_count}) must be below ${WFC_UNRESOLVED}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if ((direction_count & 1) !== 0) {
|
|
42
|
+
throw new Error(`direction_count(=${direction_count}) must be even; d ^ 1 is the opposite direction`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const words_per_cell = wfc_words_per_cell(tile_count);
|
|
46
|
+
|
|
47
|
+
/** @type {number} */
|
|
48
|
+
this.tile_count = tile_count;
|
|
49
|
+
/** @type {number} */
|
|
50
|
+
this.direction_count = direction_count;
|
|
51
|
+
/** @type {number} */
|
|
52
|
+
this.words_per_cell = words_per_cell;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Bits at or above `tile_count` in the final word of a row. Every write masks with this,
|
|
56
|
+
* so no rule row can name a tile that does not exist and the wave's tail invariant is
|
|
57
|
+
* never threatened from this side.
|
|
58
|
+
* @type {number}
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
this.__tail_mask = wfc_tail_mask(tile_count);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `direction_count × tile_count` rows of `words_per_cell` words.
|
|
65
|
+
* @type {Uint32Array}
|
|
66
|
+
*/
|
|
67
|
+
this.data = new Uint32Array(direction_count * tile_count * words_per_cell);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `full_support[d * words_per_cell + w]` — the union of every tile's row in direction `d`.
|
|
71
|
+
*
|
|
72
|
+
* Read by the propagator, which uses it to relax across a cell that has lost nothing in
|
|
73
|
+
* `O(words_per_cell)` rather than scanning that cell's whole domain.
|
|
74
|
+
*
|
|
75
|
+
* Built by {@link finalize}; stale until then.
|
|
76
|
+
* @type {Uint32Array}
|
|
77
|
+
*/
|
|
78
|
+
this.full_support = new Uint32Array(direction_count * words_per_cell);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @type {boolean}
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
this.__finalized = false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
get finalized() {
|
|
91
|
+
return this.__finalized;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Bytes held by {@link data} and {@link full_support}.
|
|
96
|
+
* @returns {number}
|
|
97
|
+
*/
|
|
98
|
+
get byte_size() {
|
|
99
|
+
return this.data.byteLength + this.full_support.byteLength;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Every adjacency legal. The starting point for a table you build by denying.
|
|
104
|
+
*/
|
|
105
|
+
fill_allow() {
|
|
106
|
+
const { data, words_per_cell } = this;
|
|
107
|
+
const last = words_per_cell - 1;
|
|
108
|
+
const tail = this.__tail_mask;
|
|
109
|
+
|
|
110
|
+
data.fill(0xFFFFFFFF);
|
|
111
|
+
|
|
112
|
+
// the final word of every row carries only the bits that name real tiles
|
|
113
|
+
for (let address = last; address < data.length; address += words_per_cell) {
|
|
114
|
+
data[address] = tail;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.__finalized = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* No adjacency legal. The starting point for a table you build by allowing.
|
|
122
|
+
*/
|
|
123
|
+
fill_deny() {
|
|
124
|
+
this.data.fill(0);
|
|
125
|
+
this.__finalized = false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Allow `tile_b` to sit on the `direction` side of `tile_a`, **and** `tile_a` to sit on the
|
|
130
|
+
* opposite side of `tile_b`.
|
|
131
|
+
*
|
|
132
|
+
* This is the mutator to use. The one-sided {@link allow_directed} exists for callers building
|
|
133
|
+
* a deliberately asymmetric table, and is the documented way to make propagation unsound.
|
|
134
|
+
*
|
|
135
|
+
* @param {number} direction
|
|
136
|
+
* @param {number} tile_a
|
|
137
|
+
* @param {number} tile_b
|
|
138
|
+
*/
|
|
139
|
+
allow_symmetric(direction, tile_a, tile_b) {
|
|
140
|
+
this.allow_directed(direction, tile_a, tile_b);
|
|
141
|
+
this.allow_directed(direction ^ 1, tile_b, tile_a);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {number} direction
|
|
146
|
+
* @param {number} tile_a
|
|
147
|
+
* @param {number} tile_b
|
|
148
|
+
*/
|
|
149
|
+
deny_symmetric(direction, tile_a, tile_b) {
|
|
150
|
+
this.deny_directed(direction, tile_a, tile_b);
|
|
151
|
+
this.deny_directed(direction ^ 1, tile_b, tile_a);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Allow `tile_b` to sit on the `direction` side of `tile_a`, and say nothing about the reverse.
|
|
156
|
+
* @param {number} direction
|
|
157
|
+
* @param {number} tile_a
|
|
158
|
+
* @param {number} tile_b
|
|
159
|
+
*/
|
|
160
|
+
allow_directed(direction, tile_a, tile_b) {
|
|
161
|
+
assert.isNonNegativeInteger(direction, 'direction');
|
|
162
|
+
assert.isNonNegativeInteger(tile_a, 'tile_a');
|
|
163
|
+
assert.isNonNegativeInteger(tile_b, 'tile_b');
|
|
164
|
+
assert.lessThan(direction, this.direction_count, 'direction < direction_count');
|
|
165
|
+
assert.lessThan(tile_a, this.tile_count, 'tile_a < tile_count');
|
|
166
|
+
assert.lessThan(tile_b, this.tile_count, 'tile_b < tile_count');
|
|
167
|
+
|
|
168
|
+
const row = ((direction * this.tile_count) + tile_a) * this.words_per_cell;
|
|
169
|
+
|
|
170
|
+
this.data[row + (tile_b >> 5)] |= (1 << (tile_b & 31));
|
|
171
|
+
|
|
172
|
+
this.__finalized = false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {number} direction
|
|
177
|
+
* @param {number} tile_a
|
|
178
|
+
* @param {number} tile_b
|
|
179
|
+
*/
|
|
180
|
+
deny_directed(direction, tile_a, tile_b) {
|
|
181
|
+
assert.isNonNegativeInteger(direction, 'direction');
|
|
182
|
+
assert.isNonNegativeInteger(tile_a, 'tile_a');
|
|
183
|
+
assert.isNonNegativeInteger(tile_b, 'tile_b');
|
|
184
|
+
assert.lessThan(direction, this.direction_count, 'direction < direction_count');
|
|
185
|
+
assert.lessThan(tile_a, this.tile_count, 'tile_a < tile_count');
|
|
186
|
+
assert.lessThan(tile_b, this.tile_count, 'tile_b < tile_count');
|
|
187
|
+
|
|
188
|
+
const row = ((direction * this.tile_count) + tile_a) * this.words_per_cell;
|
|
189
|
+
|
|
190
|
+
this.data[row + (tile_b >> 5)] &= ~(1 << (tile_b & 31));
|
|
191
|
+
|
|
192
|
+
this.__finalized = false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {number} direction
|
|
197
|
+
* @param {number} tile_a
|
|
198
|
+
* @param {number} tile_b
|
|
199
|
+
* @returns {boolean} may `tile_b` sit on the `direction` side of `tile_a`?
|
|
200
|
+
*/
|
|
201
|
+
allows(direction, tile_a, tile_b) {
|
|
202
|
+
assert.isNonNegativeInteger(direction, 'direction');
|
|
203
|
+
assert.isNonNegativeInteger(tile_a, 'tile_a');
|
|
204
|
+
assert.isNonNegativeInteger(tile_b, 'tile_b');
|
|
205
|
+
assert.lessThan(direction, this.direction_count, 'direction < direction_count');
|
|
206
|
+
assert.lessThan(tile_a, this.tile_count, 'tile_a < tile_count');
|
|
207
|
+
assert.lessThan(tile_b, this.tile_count, 'tile_b < tile_count');
|
|
208
|
+
|
|
209
|
+
const row = ((direction * this.tile_count) + tile_a) * this.words_per_cell;
|
|
210
|
+
|
|
211
|
+
return (this.data[row + (tile_b >> 5)] & (1 << (tile_b & 31))) !== 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Build {@link full_support}. Must be called after the last mutation and before a
|
|
216
|
+
* {@link WFCState} is built against this table; every mutator clears the flag.
|
|
217
|
+
*
|
|
218
|
+
* Builds nothing else. In particular a tile whose rule row is empty in some direction is left
|
|
219
|
+
* alone rather than removed, because such a tile can still sit legally on the matching border,
|
|
220
|
+
* where there is no neighbour that way. {@link rows_without_support} reports them.
|
|
221
|
+
*/
|
|
222
|
+
finalize() {
|
|
223
|
+
const { data, full_support, tile_count, direction_count, words_per_cell } = this;
|
|
224
|
+
|
|
225
|
+
full_support.fill(0);
|
|
226
|
+
|
|
227
|
+
for (let d = 0; d < direction_count; d++) {
|
|
228
|
+
const support_base = d * words_per_cell;
|
|
229
|
+
const direction_base = d * tile_count * words_per_cell;
|
|
230
|
+
|
|
231
|
+
for (let t = 0; t < tile_count; t++) {
|
|
232
|
+
const row = direction_base + t * words_per_cell;
|
|
233
|
+
|
|
234
|
+
for (let w = 0; w < words_per_cell; w++) {
|
|
235
|
+
full_support[support_base + w] |= data[row + w];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.__finalized = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Diagnostic: `(direction, tile)` pairs whose rule row is empty.
|
|
245
|
+
*
|
|
246
|
+
* Such a tile can only be placed where the lattice has no neighbour in that direction — on the
|
|
247
|
+
* matching border, or nowhere at all if the axis wraps. Usually it means a rule was forgotten,
|
|
248
|
+
* but a border-only tile is legitimate, so this is reported rather than treated as an error.
|
|
249
|
+
*
|
|
250
|
+
* @returns {{direction:number, tile:number}[]}
|
|
251
|
+
*/
|
|
252
|
+
rows_without_support() {
|
|
253
|
+
const { data, tile_count, direction_count, words_per_cell } = this;
|
|
254
|
+
const result = [];
|
|
255
|
+
|
|
256
|
+
for (let d = 0; d < direction_count; d++) {
|
|
257
|
+
const direction_base = d * tile_count * words_per_cell;
|
|
258
|
+
|
|
259
|
+
for (let t = 0; t < tile_count; t++) {
|
|
260
|
+
const row = direction_base + t * words_per_cell;
|
|
261
|
+
|
|
262
|
+
let any = 0;
|
|
263
|
+
|
|
264
|
+
for (let w = 0; w < words_per_cell; w++) {
|
|
265
|
+
any |= data[row + w];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (any === 0) {
|
|
269
|
+
result.push({ direction: d, tile: t });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Bits that name real tiles in the final word of a `words_per_cell`-word mask.
|
|
280
|
+
*
|
|
281
|
+
* A named function because the natural spelling `(1 << (tile_count & 31)) - 1` yields `0` — every
|
|
282
|
+
* tile dead — at exactly 32, 64, 96 … tiles, JavaScript taking the shift count modulo 32.
|
|
283
|
+
*
|
|
284
|
+
* @param {number} tile_count
|
|
285
|
+
* @returns {number}
|
|
286
|
+
*/
|
|
287
|
+
export function wfc_tail_mask(tile_count) {
|
|
288
|
+
const remainder = tile_count & 31;
|
|
289
|
+
|
|
290
|
+
return remainder === 0 ? 0xFFFFFFFF : (((1 << remainder) >>> 0) - 1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Bytes a {@link WFCRuleTable} of this shape would hold, without building one.
|
|
295
|
+
*
|
|
296
|
+
* @param {number} tile_count
|
|
297
|
+
* @param {number} direction_count
|
|
298
|
+
* @returns {number}
|
|
299
|
+
*/
|
|
300
|
+
export function wfc_rule_table_byte_size(tile_count, direction_count) {
|
|
301
|
+
const words_per_cell = wfc_words_per_cell(tile_count);
|
|
302
|
+
|
|
303
|
+
return (direction_count * tile_count * words_per_cell * 4) + (direction_count * words_per_cell * 4);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Is the table symmetric under `d ^ 1` — does `t` allow `u` across `d` exactly when `u` allows `t`
|
|
308
|
+
* across `d ^ 1`?
|
|
309
|
+
*
|
|
310
|
+
* Propagation assumes this. A table built only from {@link WFCRuleTable#allow_symmetric} and
|
|
311
|
+
* {@link WFCRuleTable#deny_symmetric} has it by construction; check one that did not.
|
|
312
|
+
*
|
|
313
|
+
* `O(tile_count² × direction_count)` — a build-time check, not a per-solve one.
|
|
314
|
+
*
|
|
315
|
+
* @param {WFCRuleTable} rules
|
|
316
|
+
* @returns {boolean}
|
|
317
|
+
*/
|
|
318
|
+
export function wfc_rule_table_is_symmetric(rules) {
|
|
319
|
+
const { tile_count, direction_count } = rules;
|
|
320
|
+
|
|
321
|
+
for (let d = 0; d < direction_count; d++) {
|
|
322
|
+
const opposite = d ^ 1;
|
|
323
|
+
|
|
324
|
+
for (let a = 0; a < tile_count; a++) {
|
|
325
|
+
for (let b = 0; b < tile_count; b++) {
|
|
326
|
+
if (rules.allows(d, a, b) !== rules.allows(opposite, b, a)) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return true;
|
|
334
|
+
}
|