@woosh/meep-engine 2.164.0 → 2.165.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-terrain.js +1 -1
- package/package.json +1 -1
- package/src/core/binary/BitImage2.d.ts +91 -0
- package/src/core/binary/BitImage2.d.ts.map +1 -0
- package/src/core/binary/BitImage2.js +242 -0
- package/src/core/geom/2d/oriented-box/min_area_rect_2d.d.ts +22 -0
- package/src/core/geom/2d/oriented-box/min_area_rect_2d.d.ts.map +1 -0
- package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -0
- package/src/core/geom/2d/packing/skyline.d.ts +116 -0
- package/src/core/geom/2d/packing/skyline.d.ts.map +1 -0
- package/src/core/geom/2d/packing/skyline.js +258 -0
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts +18 -0
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts.map +1 -0
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +86 -0
- package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -0
- package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -0
- package/src/core/geom/3d/atlas/Chart.d.ts +58 -0
- package/src/core/geom/3d/atlas/Chart.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/Chart.js +77 -0
- package/src/core/geom/3d/atlas/REVIEW_02.md +392 -0
- package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts +73 -0
- package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -0
- package/src/core/geom/3d/atlas/atlas_chart_type.d.ts +17 -0
- package/src/core/geom/3d/atlas/atlas_chart_type.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -0
- package/src/core/geom/3d/atlas/atlas_compute_charts.d.ts +25 -0
- package/src/core/geom/3d/atlas/atlas_compute_charts.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -0
- package/src/core/geom/3d/atlas/atlas_extract_chart.d.ts +17 -0
- package/src/core/geom/3d/atlas/atlas_extract_chart.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -0
- package/src/core/geom/3d/atlas/atlas_generate.d.ts +39 -0
- package/src/core/geom/3d/atlas/atlas_generate.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/atlas_generate.js +43 -0
- package/src/core/geom/3d/atlas/atlas_test_fixtures.d.ts +84 -0
- package/src/core/geom/3d/atlas/atlas_test_fixtures.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -0
- package/src/core/geom/3d/atlas/atlas_write_chart_uvs.d.ts +25 -0
- package/src/core/geom/3d/atlas/atlas_write_chart_uvs.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -0
- package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.d.ts +22 -0
- package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -0
- package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.d.ts +9 -0
- package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -0
- package/src/core/geom/3d/atlas/data/atlas_build_face_areas.d.ts +9 -0
- package/src/core/geom/3d/atlas/data/atlas_build_face_areas.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -0
- package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.d.ts +27 -0
- package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -0
- package/src/core/geom/3d/atlas/io/bt_loop_uv.d.ts +22 -0
- package/src/core/geom/3d/atlas/io/bt_loop_uv.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/io/bt_loop_uv.js +57 -0
- package/src/core/geom/3d/atlas/pack/ChartPlacement.d.ts +36 -0
- package/src/core/geom/3d/atlas/pack/ChartPlacement.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -0
- package/src/core/geom/3d/atlas/pack/atlas_pack_charts.d.ts +62 -0
- package/src/core/geom/3d/atlas/pack/atlas_pack_charts.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/pack/atlas_pack_charts.js +551 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_param_validity.d.ts +22 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_param_validity.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_param_validity.js +73 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.d.ts +37 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +270 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.d.ts +23 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +224 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.d.ts +14 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.d.ts +22 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -0
- package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.d.ts +20 -0
- package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -0
- package/src/core/geom/3d/atlas/param/fit_plane_basis.d.ts +27 -0
- package/src/core/geom/3d/atlas/param/fit_plane_basis.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -0
- package/src/core/geom/3d/atlas/segment/RegionGraph.d.ts +31 -0
- package/src/core/geom/3d/atlas/segment/RegionGraph.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -0
- package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.d.ts +23 -0
- package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -0
- package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.d.ts +24 -0
- package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -0
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.d.ts +27 -0
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +113 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.d.ts +44 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +275 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.d.ts +22 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +199 -0
- package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.d.ts +32 -0
- package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.d.ts.map +1 -0
- package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -0
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.d.ts +20 -0
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.d.ts.map +1 -0
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +66 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.d.ts.map +1 -1
- package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +13 -11
- package/src/core/geom/3d/sphere/sphere_compute_bounding_of_2_spheres.d.ts.map +1 -1
- package/src/core/geom/3d/sphere/sphere_compute_bounding_of_2_spheres.js +4 -3
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +4 -3
- package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.js +4 -3
- package/src/core/geom/3d/topology/simplify/quadratic/apply_border_penalty_to_vertex_quadrics.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/quadratic/apply_border_penalty_to_vertex_quadrics.js +10 -6
- package/src/core/geom/3d/topology/struct/TopoMesh.js +2 -2
- package/src/core/geom/3d/topology/struct/TopoTriangle.d.ts +0 -2
- package/src/core/geom/3d/topology/struct/TopoTriangle.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/TopoTriangle.js +2 -2
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path_polyanya.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path_polyanya.js +8 -27
- package/src/core/geom/vec3/v3_cotangent.d.ts +20 -0
- package/src/core/geom/vec3/v3_cotangent.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_cotangent.js +29 -0
- package/src/core/geom/vec3/v3_matrix3_multiply.d.ts +19 -0
- package/src/core/geom/vec3/v3_matrix3_multiply.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -0
- package/src/core/geom/vec3/v3_multiply.d.ts +16 -0
- package/src/core/geom/vec3/v3_multiply.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_multiply.js +19 -0
- package/src/core/math/linalg/cg/cg_solve.d.ts +24 -0
- package/src/core/math/linalg/cg/cg_solve.d.ts.map +1 -0
- package/src/core/math/linalg/cg/cg_solve.js +106 -0
- package/src/core/math/linalg/cg/cg_solve_normal_equations.d.ts +21 -0
- package/src/core/math/linalg/cg/cg_solve_normal_equations.d.ts.map +1 -0
- package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -0
- package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.d.ts +19 -0
- package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.d.ts.map +1 -0
- package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -0
- package/src/core/math/linalg/sparse/SparseMatrixCSR.d.ts +50 -0
- package/src/core/math/linalg/sparse/SparseMatrixCSR.d.ts.map +1 -0
- package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -0
- package/src/core/math/linalg/sparse/spm_matvec.d.ts +9 -0
- package/src/core/math/linalg/sparse/spm_matvec.d.ts.map +1 -0
- package/src/core/math/linalg/sparse/spm_matvec.js +22 -0
- package/src/core/math/linalg/sparse/spm_matvec_transpose.d.ts +12 -0
- package/src/core/math/linalg/sparse/spm_matvec_transpose.d.ts.map +1 -0
- package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD_PRECOMPUTED.d.ts +39 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD_PRECOMPUTED.js +22 -1
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.d.ts.map +1 -1
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.js +32 -9
- package/src/core/math/physics/mie/compute_mie_phase.d.ts +2 -3
- package/src/core/math/physics/mie/compute_mie_phase.d.ts.map +1 -1
- package/src/core/math/physics/mie/compute_mie_phase.js +1 -3
- package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts +0 -1
- package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts.map +1 -1
- package/src/core/math/physics/mie/lorenz_mie_coefs.js +0 -7
- package/src/engine/ecs/attachment/TransformAttachmentBinding.js +3 -3
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +2 -2
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/Terrain.js +4 -3
- package/src/engine/ecs/terrain/tiles/TerrainTile.js +2 -2
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts.map +1 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +9 -8
- package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics/GraphicsEngine.js +2 -1
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +2 -2
- package/src/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +2 -2
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +2 -2
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +0 -1
- package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.d.ts.map +1 -1
- package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +8 -7
- package/src/engine/graphics/geometry/MikkT/AvgTSpace.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/AvgTSpace.js +11 -6
- package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +2 -3
- package/src/engine/graphics/geometry/MikkT/CalcTexArea.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/CalcTexArea.js +3 -4
- package/src/engine/graphics/geometry/MikkT/DegenEpilogue.js +4 -4
- package/src/engine/graphics/geometry/MikkT/EvalTspace.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/EvalTspace.js +11 -8
- package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +7 -8
- package/src/engine/graphics/geometry/MikkT/GenerateTSpaces.js +8 -8
- package/src/engine/graphics/geometry/MikkT/InitTriInfo.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/InitTriInfo.js +30 -25
- package/src/engine/graphics/geometry/MikkT/MikkTSpace.js +7 -7
- package/src/engine/graphics/geometry/MikkT/NormalizeSafe.d.ts +0 -1
- package/src/engine/graphics/geometry/MikkT/NormalizeSafe.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/NormalizeSafe.js +6 -4
- package/src/engine/graphics/geometry/MikkT/STSpace.d.ts +0 -2
- package/src/engine/graphics/geometry/MikkT/STSpace.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/STSpace.js +3 -3
- package/src/engine/graphics/geometry/MikkT/v3_scale_dot_sub_normalize.d.ts +0 -2
- package/src/engine/graphics/geometry/MikkT/v3_scale_dot_sub_normalize.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/v3_scale_dot_sub_normalize.js +0 -1
- package/src/engine/graphics/impostors/octahedral/ImpostorBaker.d.ts +0 -2
- package/src/engine/graphics/impostors/octahedral/ImpostorBaker.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/ImpostorBaker.js +4 -3
- package/src/engine/graphics/impostors/octahedral/grid/HemiOctahedralUvEncoder.js +2 -2
- package/src/engine/graphics/impostors/octahedral/grid/UvEncoder.d.ts +17 -7
- package/src/engine/graphics/impostors/voxel/VoxelImpostorBaker.js +2 -2
- package/src/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.js +3 -3
- package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +4 -3
- package/src/engine/graphics/render/forward_plus/LightManager.js +2 -2
- package/src/engine/graphics/render/forward_plus/model/Decal.d.ts +0 -2
- package/src/engine/graphics/render/forward_plus/model/Decal.d.ts.map +1 -1
- package/src/engine/graphics/render/forward_plus/model/Decal.js +3 -3
- package/src/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +4 -3
- package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +6 -4
- package/src/engine/graphics/sh3/lightmap/ARTIFACTS_PLAN.md +146 -0
- package/src/engine/graphics/sh3/lightmap/CODE_REVIEW.md +121 -0
- package/src/engine/graphics/sh3/lightmap/LIGHTMAPPER_REVIEW.md +183 -0
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.d.ts +90 -0
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.js +678 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_build_geometry.d.ts +32 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_build_geometry.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_build_geometry.js +155 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_denoise.d.ts +49 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_denoise.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_denoise.js +136 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_dilate.d.ts +19 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_dilate.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_dilate.js +89 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_gather.d.ts +54 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_gather.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_gather.js +168 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_rasterize_triangle.d.ts +30 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_rasterize_triangle.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_rasterize_triangle.js +145 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_seam_fix.d.ts +38 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_seam_fix.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_seam_fix.js +152 -0
- package/src/engine/graphics/sh3/lightmap/prototypeLightmapBaker.d.ts +2 -0
- package/src/engine/graphics/sh3/lightmap/prototypeLightmapBaker.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lightmap/prototypeLightmapBaker.js +186 -0
- package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.d.ts.map +1 -1
- package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +2 -2
- package/src/engine/graphics/sh3/path_tracer/PathTracer.js +2 -2
- package/src/engine/graphics/sh3/path_tracer/prepare_scene_gltf.d.ts.map +1 -1
- package/src/engine/graphics/sh3/path_tracer/prepare_scene_gltf.js +6 -4
- package/src/engine/graphics/sh3/path_tracer/prototypePathTracer.js +3 -3
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.d.ts.map +1 -1
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +5 -4
- package/src/engine/graphics/sh3/sky/hosek/prototype_hosek.js +2 -2
- package/src/engine/input/devices/KeyboardDevice.d.ts +61 -14
- package/src/engine/input/devices/KeyboardDevice.d.ts.map +1 -1
- package/src/engine/input/devices/KeyboardDevice.js +41 -15
- package/src/engine/input/devices/isHTMLElementFocusable.d.ts.map +1 -1
- package/src/engine/input/devices/isHTMLElementFocusable.js +2 -9
- package/src/engine/sound/simulation/prototypeAcousticSolver.js +261 -143
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import { BitImage2 } from "../../../../binary/BitImage2.js";
|
|
2
|
+
import { ceilPowerOfTwo } from "../../../../binary/operations/ceilPowerOfTwo.js";
|
|
3
|
+
import { min_area_rect_2d } from "../../../2d/oriented-box/min_area_rect_2d.js";
|
|
4
|
+
import { Skyline } from "../../../2d/packing/skyline.js";
|
|
5
|
+
import { tri2_rasterize_conservative } from "../../../2d/triangle/tri2_rasterize_conservative.js";
|
|
6
|
+
import { ChartPlacement } from "./ChartPlacement.js";
|
|
7
|
+
|
|
8
|
+
/** Target atlas utilization used when auto-estimating density. */
|
|
9
|
+
const UTILIZATION_TARGET = 0.75;
|
|
10
|
+
|
|
11
|
+
/** Upper bound on single-page growth — a runaway backstop, not a real limit. */
|
|
12
|
+
const MAX_ATLAS_SIZE = 1 << 16;
|
|
13
|
+
|
|
14
|
+
/** Elastic density: shrink factor per attempt, and the attempt cap. */
|
|
15
|
+
const ELASTIC_SHRINK = 0.8;
|
|
16
|
+
const MAX_ELASTIC_ATTEMPTS = 40;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The exact bitmap canBlit packer (tight, non-rectangular interlocking) is used
|
|
20
|
+
* only for atlases up to this many texels — its scan is O(area·mask). Above it,
|
|
21
|
+
* and unless `bruteForce` is forced, the fast O(n) skyline rectangle packer is
|
|
22
|
+
* used so packing scales to production atlas sizes.
|
|
23
|
+
*/
|
|
24
|
+
const BRUTE_FORCE_AREA = 1 << 16; // 256 x 256
|
|
25
|
+
|
|
26
|
+
// ─── Shared ──────────────────────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Rotate a chart's UVs so its minimum-area oriented box becomes axis-aligned
|
|
29
|
+
* (xatlas `rotateChartsToAxis`). Rigid, so validity/stretch are unchanged.
|
|
30
|
+
*
|
|
31
|
+
* @param {Chart} chart chart whose UVs are rotated in place
|
|
32
|
+
*/
|
|
33
|
+
function rotate_uv_to_min_area_box(chart) {
|
|
34
|
+
const box = min_area_rect_2d(chart.uv, chart.vertex_count);
|
|
35
|
+
const c = Math.cos(box.angle);
|
|
36
|
+
const s = Math.sin(box.angle);
|
|
37
|
+
const uv = chart.uv;
|
|
38
|
+
for (let i = 0; i < chart.vertex_count; i++) {
|
|
39
|
+
const u = uv[i * 2];
|
|
40
|
+
const v = uv[i * 2 + 1];
|
|
41
|
+
uv[i * 2] = u * c + v * s;
|
|
42
|
+
uv[i * 2 + 1] = -u * s + v * c;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Per-chart placeable at a given texel density: scale + UV bbox + integer rect
|
|
48
|
+
* size (w, h, padding-inflated). With `need_mask`, also a dilated coverage mask
|
|
49
|
+
* (+ transpose) for the bitmap packer; skyline only needs w/h.
|
|
50
|
+
*
|
|
51
|
+
* @param {Chart[]} charts parameterized charts to build placeables from
|
|
52
|
+
* @param {number} density texels per unit of surface area
|
|
53
|
+
* @param {number} padding gutter texels added on each side
|
|
54
|
+
* @param {boolean} rotate_charts allow 90-degree placement (builds the transposed mask)
|
|
55
|
+
* @param {boolean} need_mask build per-chart coverage masks (bitmap packer) vs w/h only (skyline)
|
|
56
|
+
* @returns {{packs:ChartPlacement[], mask_total:number}} placements and the total occupied/area texel count
|
|
57
|
+
*/
|
|
58
|
+
function build_packs(charts, density, padding, rotate_charts, need_mask) {
|
|
59
|
+
const packs = [];
|
|
60
|
+
let mask_total = 0;
|
|
61
|
+
|
|
62
|
+
for (const chart of charts) {
|
|
63
|
+
const scale = Math.sqrt(chart.surface_area / Math.max(chart.parametric_area, 1e-12)) * density;
|
|
64
|
+
|
|
65
|
+
let min_u = Infinity, min_v = Infinity, max_u = -Infinity, max_v = -Infinity;
|
|
66
|
+
for (let i = 0; i < chart.vertex_count; i++) {
|
|
67
|
+
const u = chart.uv[i * 2];
|
|
68
|
+
const v = chart.uv[i * 2 + 1];
|
|
69
|
+
if (u < min_u) min_u = u;
|
|
70
|
+
if (u > max_u) max_u = u;
|
|
71
|
+
if (v < min_v) min_v = v;
|
|
72
|
+
if (v > max_v) max_v = v;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ext_w = Math.max(1, Math.ceil((max_u - min_u) * scale) + 1);
|
|
76
|
+
const ext_h = Math.max(1, Math.ceil((max_v - min_v) * scale) + 1);
|
|
77
|
+
const w = ext_w + 2 * padding;
|
|
78
|
+
const h = ext_h + 2 * padding;
|
|
79
|
+
|
|
80
|
+
let mask = null;
|
|
81
|
+
let mask_t = null;
|
|
82
|
+
if (need_mask) {
|
|
83
|
+
mask = new BitImage2(w, h);
|
|
84
|
+
for (let t = 0; t < chart.triangle_count; t++) {
|
|
85
|
+
const i0 = chart.tri_local[t * 3];
|
|
86
|
+
const i1 = chart.tri_local[t * 3 + 1];
|
|
87
|
+
const i2 = chart.tri_local[t * 3 + 2];
|
|
88
|
+
tri2_rasterize_conservative(
|
|
89
|
+
mask,
|
|
90
|
+
(chart.uv[i0 * 2] - min_u) * scale + padding, (chart.uv[i0 * 2 + 1] - min_v) * scale + padding,
|
|
91
|
+
(chart.uv[i1 * 2] - min_u) * scale + padding, (chart.uv[i1 * 2 + 1] - min_v) * scale + padding,
|
|
92
|
+
(chart.uv[i2 * 2] - min_u) * scale + padding, (chart.uv[i2 * 2 + 1] - min_v) * scale + padding
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (padding > 0) {
|
|
96
|
+
mask.dilate(padding);
|
|
97
|
+
}
|
|
98
|
+
mask_t = rotate_charts ? mask.transposed() : null;
|
|
99
|
+
mask_total += mask.cardinality();
|
|
100
|
+
} else {
|
|
101
|
+
mask_total += w * h;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const placement = new ChartPlacement();
|
|
105
|
+
placement.chart = chart;
|
|
106
|
+
placement.scale = scale;
|
|
107
|
+
placement.min_u = min_u;
|
|
108
|
+
placement.min_v = min_v;
|
|
109
|
+
placement.w = w;
|
|
110
|
+
placement.h = h;
|
|
111
|
+
placement.mask = mask;
|
|
112
|
+
placement.mask_t = mask_t;
|
|
113
|
+
packs.push(placement);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { packs, mask_total };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Largest-perimeter-first order (first-fit-decreasing).
|
|
121
|
+
*
|
|
122
|
+
* @param {ChartPlacement[]} packs placements to order
|
|
123
|
+
* @returns {number[]} pack indices sorted by descending (w + h)
|
|
124
|
+
*/
|
|
125
|
+
function pack_order(packs) {
|
|
126
|
+
const order = packs.map((_, i) => i);
|
|
127
|
+
order.sort((a, b) => (packs[b].w + packs[b].h) - (packs[a].w + packs[a].h));
|
|
128
|
+
return order;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Pick the bottom-left-most of two candidate placements (rot 0 / rot 1).
|
|
133
|
+
*
|
|
134
|
+
* @param {{x:number, y:number}|null} fit0 unrotated candidate position, or null
|
|
135
|
+
* @param {{x:number, y:number}|null} fit1 90-degree-rotated candidate position, or null
|
|
136
|
+
* @returns {{rot:number, pos:{x:number, y:number}}|null} chosen placement, or null if neither fits
|
|
137
|
+
*/
|
|
138
|
+
function choose_placement(fit0, fit1) {
|
|
139
|
+
if (fit0 === null && fit1 === null) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (fit1 === null) {
|
|
143
|
+
return { rot: 0, pos: fit0 };
|
|
144
|
+
}
|
|
145
|
+
if (fit0 === null) {
|
|
146
|
+
return { rot: 1, pos: fit1 };
|
|
147
|
+
}
|
|
148
|
+
if (fit1.y < fit0.y || (fit1.y === fit0.y && fit1.x < fit0.x)) {
|
|
149
|
+
return { rot: 1, pos: fit1 };
|
|
150
|
+
}
|
|
151
|
+
return { rot: 0, pos: fit0 };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Skyline rectangle packer (default, scalable) ────────────────────────────
|
|
155
|
+
/**
|
|
156
|
+
* Try to place one placement into the skyline page, choosing the bottom-left-most
|
|
157
|
+
* of the unrotated and (if allowed) 90-degree-rotated fit. On success, writes
|
|
158
|
+
* page/rot/x/y onto the placement and raises the skyline. Non-allocating —
|
|
159
|
+
* reads {@link Skyline.found_x}/{@link Skyline.found_y} after each `find`.
|
|
160
|
+
*
|
|
161
|
+
* @param {Skyline} sky page skyline
|
|
162
|
+
* @param {ChartPlacement} pack placement to position (mutated on success)
|
|
163
|
+
* @param {number} page_index
|
|
164
|
+
* @param {boolean} rotate_charts allow 90-degree placement
|
|
165
|
+
* @returns {boolean} true if placed
|
|
166
|
+
*/
|
|
167
|
+
function skyline_try_place(sky, pack, page_index, rotate_charts) {
|
|
168
|
+
const ok0 = sky.find(pack.w, pack.h);
|
|
169
|
+
const x0 = sky.found_x;
|
|
170
|
+
const y0 = sky.found_y;
|
|
171
|
+
|
|
172
|
+
let ok1 = false;
|
|
173
|
+
let x1 = 0;
|
|
174
|
+
let y1 = 0;
|
|
175
|
+
if (rotate_charts) {
|
|
176
|
+
ok1 = sky.find(pack.h, pack.w);
|
|
177
|
+
x1 = sky.found_x;
|
|
178
|
+
y1 = sky.found_y;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let rot = -1;
|
|
182
|
+
let px = 0;
|
|
183
|
+
let py = 0;
|
|
184
|
+
if (ok0) {
|
|
185
|
+
rot = 0;
|
|
186
|
+
px = x0;
|
|
187
|
+
py = y0;
|
|
188
|
+
}
|
|
189
|
+
if (ok1 && (rot === -1 || y1 < py || (y1 === py && x1 < px))) {
|
|
190
|
+
rot = 1;
|
|
191
|
+
px = x1;
|
|
192
|
+
py = y1;
|
|
193
|
+
}
|
|
194
|
+
if (rot === -1) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pack.page = page_index;
|
|
199
|
+
pack.rot = rot;
|
|
200
|
+
pack.x = px;
|
|
201
|
+
pack.y = py;
|
|
202
|
+
if (rot === 0) {
|
|
203
|
+
sky.place(px, py, pack.w, pack.h);
|
|
204
|
+
} else {
|
|
205
|
+
sky.place(px, py, pack.h, pack.w);
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Place all packs on a single skyline page that grows by powers of two until
|
|
212
|
+
* every pack fits (xatlas growing-page mode, skyline variant).
|
|
213
|
+
*
|
|
214
|
+
* @param {ChartPlacement[]} packs
|
|
215
|
+
* @param {number} start_resolution initial square page size
|
|
216
|
+
* @param {boolean} rotate_charts allow 90-degree placement
|
|
217
|
+
* @returns {Skyline[]} single-element page list
|
|
218
|
+
*/
|
|
219
|
+
function place_growing_skyline(packs, start_resolution, rotate_charts) {
|
|
220
|
+
const sky = new Skyline(start_resolution, start_resolution);
|
|
221
|
+
for (const idx of pack_order(packs)) {
|
|
222
|
+
const p = packs[idx];
|
|
223
|
+
while (!skyline_try_place(sky, p, 0, rotate_charts)) {
|
|
224
|
+
const need = Math.max(p.w, p.h);
|
|
225
|
+
const nw = ceilPowerOfTwo(Math.max(sky.width + 1, need));
|
|
226
|
+
const nh = ceilPowerOfTwo(Math.max(sky.height + 1, need));
|
|
227
|
+
if (nw > MAX_ATLAS_SIZE || nh > MAX_ATLAS_SIZE) {
|
|
228
|
+
throw new Error("atlas_pack_charts: atlas grew past the size limit");
|
|
229
|
+
}
|
|
230
|
+
sky.grow(nw, nh);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return [sky];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Place all packs into up to `max_pages` fixed-size skyline pages at the current
|
|
238
|
+
* density, spilling onto a fresh page when one fills.
|
|
239
|
+
*
|
|
240
|
+
* @param {ChartPlacement[]} packs
|
|
241
|
+
* @param {number} page_size fixed square page size
|
|
242
|
+
* @param {number} max_pages page cap
|
|
243
|
+
* @param {boolean} rotate_charts allow 90-degree placement
|
|
244
|
+
* @returns {Skyline[]|null} page list, or null if the packs do not fit
|
|
245
|
+
*/
|
|
246
|
+
function place_fixed_skyline(packs, page_size, max_pages, rotate_charts) {
|
|
247
|
+
const pages = [new Skyline(page_size, page_size)];
|
|
248
|
+
for (const idx of pack_order(packs)) {
|
|
249
|
+
const p = packs[idx];
|
|
250
|
+
|
|
251
|
+
let placed = false;
|
|
252
|
+
for (let pi = 0; pi < pages.length; pi++) {
|
|
253
|
+
if (skyline_try_place(pages[pi], p, pi, rotate_charts)) {
|
|
254
|
+
placed = true;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (placed) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (pages.length >= max_pages) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const fresh = new Skyline(page_size, page_size);
|
|
265
|
+
if (!skyline_try_place(fresh, p, pages.length, rotate_charts)) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
pages.push(fresh);
|
|
269
|
+
}
|
|
270
|
+
return pages;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── Bitmap canBlit packer (exact, small atlases / bruteForce) ───────────────
|
|
274
|
+
/**
|
|
275
|
+
* Lowest-then-leftmost (row-major scan) position where `mask` can be blitted
|
|
276
|
+
* into `page` without overlapping a set bit, or null if it does not fit.
|
|
277
|
+
*
|
|
278
|
+
* @param {BitImage2} page atlas occupancy
|
|
279
|
+
* @param {BitImage2} mask chart coverage mask
|
|
280
|
+
* @returns {{x:number, y:number}|null}
|
|
281
|
+
*/
|
|
282
|
+
function find_first_fit(page, mask) {
|
|
283
|
+
const max_y = page.height - mask.height;
|
|
284
|
+
const max_x = page.width - mask.width;
|
|
285
|
+
for (let y = 0; y <= max_y; y++) {
|
|
286
|
+
for (let x = 0; x <= max_x; x++) {
|
|
287
|
+
if (page.canBlit(mask, x, y)) {
|
|
288
|
+
return { x, y };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Try to place one pack into a bitmap page, choosing the bottom-left-most of the
|
|
297
|
+
* unrotated and (if a transpose exists) 90-degree-rotated fit. On success, writes
|
|
298
|
+
* page/rot/x/y onto the pack and ORs its mask into the page.
|
|
299
|
+
*
|
|
300
|
+
* @param {BitImage2} page atlas occupancy
|
|
301
|
+
* @param {ChartPlacement} pack placement to position (mutated on success)
|
|
302
|
+
* @param {number} page_index
|
|
303
|
+
* @returns {boolean} true if placed
|
|
304
|
+
*/
|
|
305
|
+
function bitmap_try_place(page, pack, page_index) {
|
|
306
|
+
const fit0 = find_first_fit(page, pack.mask);
|
|
307
|
+
const fit1 = pack.mask_t !== null ? find_first_fit(page, pack.mask_t) : null;
|
|
308
|
+
const choice = choose_placement(fit0, fit1);
|
|
309
|
+
if (choice === null) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
pack.page = page_index;
|
|
313
|
+
pack.rot = choice.rot;
|
|
314
|
+
pack.x = choice.pos.x;
|
|
315
|
+
pack.y = choice.pos.y;
|
|
316
|
+
page.or_blit(choice.rot === 1 ? pack.mask_t : pack.mask, pack.x, pack.y);
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Place all packs on a single bitmap page that grows by powers of two until
|
|
322
|
+
* every pack fits (xatlas growing-page mode, exact bitmap variant).
|
|
323
|
+
*
|
|
324
|
+
* @param {ChartPlacement[]} packs
|
|
325
|
+
* @param {number} start_resolution initial square page size
|
|
326
|
+
* @returns {BitImage2[]} single-element page list
|
|
327
|
+
*/
|
|
328
|
+
function place_growing_bitmap(packs, start_resolution) {
|
|
329
|
+
const page = new BitImage2(start_resolution, start_resolution);
|
|
330
|
+
for (const idx of pack_order(packs)) {
|
|
331
|
+
const p = packs[idx];
|
|
332
|
+
while (!bitmap_try_place(page, p, 0)) {
|
|
333
|
+
const need_w = Math.max(p.mask.width, p.mask_t !== null ? p.mask_t.width : 0);
|
|
334
|
+
const need_h = Math.max(p.mask.height, p.mask_t !== null ? p.mask_t.height : 0);
|
|
335
|
+
const nw = ceilPowerOfTwo(Math.max(page.width + 1, need_w));
|
|
336
|
+
const nh = ceilPowerOfTwo(Math.max(page.height + 1, need_h));
|
|
337
|
+
if (nw > MAX_ATLAS_SIZE || nh > MAX_ATLAS_SIZE) {
|
|
338
|
+
throw new Error("atlas_pack_charts: atlas grew past the size limit");
|
|
339
|
+
}
|
|
340
|
+
page.grow_preserving(nw, nh);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return [page];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Place all packs into up to `max_pages` fixed-size bitmap pages at the current
|
|
348
|
+
* density, spilling onto a fresh page when one fills.
|
|
349
|
+
*
|
|
350
|
+
* @param {ChartPlacement[]} packs
|
|
351
|
+
* @param {number} page_size fixed square page size
|
|
352
|
+
* @param {number} max_pages page cap
|
|
353
|
+
* @returns {BitImage2[]|null} page list, or null if the packs do not fit
|
|
354
|
+
*/
|
|
355
|
+
function place_fixed_bitmap(packs, page_size, max_pages) {
|
|
356
|
+
const pages = [new BitImage2(page_size, page_size)];
|
|
357
|
+
for (const idx of pack_order(packs)) {
|
|
358
|
+
const p = packs[idx];
|
|
359
|
+
let placed = false;
|
|
360
|
+
for (let pi = 0; pi < pages.length; pi++) {
|
|
361
|
+
if (bitmap_try_place(pages[pi], p, pi)) {
|
|
362
|
+
placed = true;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (placed) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (pages.length >= max_pages) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const fresh = new BitImage2(page_size, page_size);
|
|
373
|
+
if (!bitmap_try_place(fresh, p, pages.length)) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
pages.push(fresh);
|
|
377
|
+
}
|
|
378
|
+
return pages;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Pack parameterized charts into one or more atlas pages (xatlas `PackCharts`),
|
|
383
|
+
* mutating each chart in place: its `uv` becomes final atlas-space [0,1]
|
|
384
|
+
* coordinates and `atlas_page`/`atlas_x`/`atlas_y`/`atlas_rotated` record its
|
|
385
|
+
* placement. Charts are pre-rotated to their min-area box and may be placed
|
|
386
|
+
* 90-degrees rotated for density.
|
|
387
|
+
*
|
|
388
|
+
* This works purely at the {@link Chart} level — no mesh — so charts from several
|
|
389
|
+
* different meshes can be packed into a single shared atlas. To write the packed
|
|
390
|
+
* UVs back onto a mesh, call {@link atlas_write_chart_uvs} (once per source mesh).
|
|
391
|
+
*
|
|
392
|
+
* Placement strategy is chosen by atlas size: small atlases use the exact
|
|
393
|
+
* bitmap shifted-OR `canBlit` packer (tight, non-rectangular interlocking);
|
|
394
|
+
* large atlases use the fast O(n) skyline rectangle packer (so packing scales).
|
|
395
|
+
* `bruteForce: true` forces the bitmap packer regardless of size.
|
|
396
|
+
*
|
|
397
|
+
* Page modes:
|
|
398
|
+
* - `maxResolution = 0` (default): a single page that grows by powers of two.
|
|
399
|
+
* - `maxResolution > 0`, `maxPages = 1` (default): one fixed-size page with
|
|
400
|
+
* **elastic density** — density shrinks until the charts fit.
|
|
401
|
+
* - `maxResolution > 0`, `maxPages > 1`: spill into up to `maxPages` fixed-size
|
|
402
|
+
* pages at fixed density (`maxPages = Infinity` = pure fixed-density spill).
|
|
403
|
+
*
|
|
404
|
+
* @param {Chart[]} charts from {@link atlas_compute_charts} (mutated: `uv` + placement)
|
|
405
|
+
* @param {object} [options]
|
|
406
|
+
* @param {number} [options.padding] gutter texels (default 1)
|
|
407
|
+
* @param {number} [options.resolution] start/target size for the growing page; 0 = auto
|
|
408
|
+
* @param {number} [options.texelsPerUnit] texel density; 0 = auto
|
|
409
|
+
* @param {number} [options.maxResolution] fixed page size; 0 = unbounded growing page (default 0)
|
|
410
|
+
* @param {number} [options.maxPages] page cap; density is elastic to honour it (default 1)
|
|
411
|
+
* @param {boolean} [options.bruteForce] force the exact bitmap packer (default false)
|
|
412
|
+
* @param {boolean} [options.rotateChartsToAxis] align charts to their OBB (default true)
|
|
413
|
+
* @param {boolean} [options.rotateCharts] allow 90-degree placement (default true)
|
|
414
|
+
* @returns {{pages: Array<{width:number, height:number, occupied_texels:number, utilization:number}>,
|
|
415
|
+
* page_count:number, atlas_width:number, atlas_height:number, utilization:number,
|
|
416
|
+
* occupied_texels:number, mask_texels_total:number, charts:Chart[]}}
|
|
417
|
+
*/
|
|
418
|
+
export function atlas_pack_charts(charts, options = {}) {
|
|
419
|
+
const padding = options.padding ?? 1;
|
|
420
|
+
const rotate_to_axis = options.rotateChartsToAxis ?? true;
|
|
421
|
+
const rotate_charts = options.rotateCharts ?? true;
|
|
422
|
+
const max_resolution = options.maxResolution ?? 0;
|
|
423
|
+
const max_pages = options.maxPages ?? 1;
|
|
424
|
+
const brute_force = options.bruteForce ?? false;
|
|
425
|
+
|
|
426
|
+
if (rotate_to_axis) {
|
|
427
|
+
for (const chart of charts) {
|
|
428
|
+
rotate_uv_to_min_area_box(chart);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let total_surface = 0;
|
|
433
|
+
for (const c of charts) {
|
|
434
|
+
total_surface += c.surface_area;
|
|
435
|
+
}
|
|
436
|
+
if (total_surface <= 0) {
|
|
437
|
+
total_surface = 1;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let packs;
|
|
441
|
+
let mask_total;
|
|
442
|
+
let pages;
|
|
443
|
+
let use_skyline;
|
|
444
|
+
|
|
445
|
+
if (max_resolution === 0) {
|
|
446
|
+
let resolution = options.resolution ?? 0;
|
|
447
|
+
let density = options.texelsPerUnit ?? 0;
|
|
448
|
+
if (resolution === 0 && density === 0) {
|
|
449
|
+
resolution = 1024;
|
|
450
|
+
}
|
|
451
|
+
if (density === 0) {
|
|
452
|
+
density = resolution * Math.sqrt(UTILIZATION_TARGET / total_surface);
|
|
453
|
+
}
|
|
454
|
+
if (resolution === 0) {
|
|
455
|
+
resolution = Math.max(1, Math.ceil(Math.sqrt(total_surface * density * density / UTILIZATION_TARGET)));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
use_skyline = !brute_force && resolution * resolution > BRUTE_FORCE_AREA;
|
|
459
|
+
const built = build_packs(charts, density, padding, rotate_charts, !use_skyline);
|
|
460
|
+
packs = built.packs;
|
|
461
|
+
mask_total = built.mask_total;
|
|
462
|
+
pages = use_skyline ? place_growing_skyline(packs, resolution, rotate_charts) : place_growing_bitmap(packs, resolution);
|
|
463
|
+
} else {
|
|
464
|
+
use_skyline = !brute_force && max_resolution * max_resolution > BRUTE_FORCE_AREA;
|
|
465
|
+
const estimate_pages = Number.isFinite(max_pages) ? max_pages : 1;
|
|
466
|
+
let density = options.texelsPerUnit ?? 0;
|
|
467
|
+
if (density === 0) {
|
|
468
|
+
density = Math.sqrt(UTILIZATION_TARGET * estimate_pages * max_resolution * max_resolution / total_surface);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
for (let attempt = 0; attempt < MAX_ELASTIC_ATTEMPTS; attempt++) {
|
|
472
|
+
const built = build_packs(charts, density, padding, rotate_charts, !use_skyline);
|
|
473
|
+
const placed = use_skyline
|
|
474
|
+
? place_fixed_skyline(built.packs, max_resolution, max_pages, rotate_charts)
|
|
475
|
+
: place_fixed_bitmap(built.packs, max_resolution, max_pages);
|
|
476
|
+
if (placed !== null) {
|
|
477
|
+
packs = built.packs;
|
|
478
|
+
mask_total = built.mask_total;
|
|
479
|
+
pages = placed;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
density *= ELASTIC_SHRINK;
|
|
483
|
+
}
|
|
484
|
+
if (pages === undefined) {
|
|
485
|
+
throw new Error("atlas_pack_charts: could not fit charts within maxPages pages");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Update each chart's UVs to final atlas-space coordinates (page-relative)
|
|
490
|
+
// and record its placement. Actualizing onto a mesh is a separate, explicit
|
|
491
|
+
// step (atlas_write_chart_uvs) the caller controls.
|
|
492
|
+
for (const p of packs) {
|
|
493
|
+
const page = pages[p.page];
|
|
494
|
+
const pw = page.width;
|
|
495
|
+
const ph = page.height;
|
|
496
|
+
const chart = p.chart;
|
|
497
|
+
const final_uv = new Float64Array(chart.vertex_count * 2);
|
|
498
|
+
for (let i = 0; i < chart.vertex_count; i++) {
|
|
499
|
+
const tu = (chart.uv[i * 2] - p.min_u) * p.scale;
|
|
500
|
+
const tv = (chart.uv[i * 2 + 1] - p.min_v) * p.scale;
|
|
501
|
+
if (p.rot === 1) {
|
|
502
|
+
final_uv[i * 2] = (p.x + padding + tv) / pw;
|
|
503
|
+
final_uv[i * 2 + 1] = (p.y + padding + tu) / ph;
|
|
504
|
+
} else {
|
|
505
|
+
final_uv[i * 2] = (p.x + padding + tu) / pw;
|
|
506
|
+
final_uv[i * 2 + 1] = (p.y + padding + tv) / ph;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
chart.uv = final_uv;
|
|
510
|
+
chart.atlas_page = p.page;
|
|
511
|
+
chart.atlas_x = p.x;
|
|
512
|
+
chart.atlas_y = p.y;
|
|
513
|
+
chart.atlas_rotated = p.rot === 1;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Per-page occupancy: bitmap = set texels; skyline = placed rectangle areas.
|
|
517
|
+
const occupied = new Array(pages.length).fill(0);
|
|
518
|
+
if (use_skyline) {
|
|
519
|
+
for (const p of packs) {
|
|
520
|
+
occupied[p.page] += p.w * p.h;
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
for (let i = 0; i < pages.length; i++) {
|
|
524
|
+
occupied[i] = pages[i].cardinality();
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const page_infos = pages.map((pg, i) => ({
|
|
529
|
+
width: pg.width,
|
|
530
|
+
height: pg.height,
|
|
531
|
+
occupied_texels: occupied[i],
|
|
532
|
+
utilization: occupied[i] / (pg.width * pg.height),
|
|
533
|
+
}));
|
|
534
|
+
let total_occupied = 0;
|
|
535
|
+
let total_cells = 0;
|
|
536
|
+
for (const info of page_infos) {
|
|
537
|
+
total_occupied += info.occupied_texels;
|
|
538
|
+
total_cells += info.width * info.height;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
pages: page_infos,
|
|
543
|
+
page_count: pages.length,
|
|
544
|
+
atlas_width: page_infos[0].width,
|
|
545
|
+
atlas_height: page_infos[0].height,
|
|
546
|
+
utilization: total_occupied / total_cells,
|
|
547
|
+
occupied_texels: total_occupied,
|
|
548
|
+
mask_texels_total: mask_total,
|
|
549
|
+
charts,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validity of a chart's current UV layout (xatlas
|
|
3
|
+
* `isChartParameterizationValid`). A layout is bijective when every triangle
|
|
4
|
+
* has the same winding (all positive or all flipped — a uniformly mirrored
|
|
5
|
+
* chart is fine) with no zero-area triangles, and the chart outline does not
|
|
6
|
+
* self-intersect.
|
|
7
|
+
*
|
|
8
|
+
* Boundary self-intersection is a brute-force O(b^2) test over the chart's
|
|
9
|
+
* boundary edges, skipping pairs that share an endpoint.
|
|
10
|
+
*
|
|
11
|
+
* @param {Chart} chart
|
|
12
|
+
* @returns {{positive_count:number, flipped_count:number, zero_area_count:number,
|
|
13
|
+
* mixed_winding:boolean, self_intersects:boolean}}
|
|
14
|
+
*/
|
|
15
|
+
export function atlas_chart_param_validity(chart: Chart): {
|
|
16
|
+
positive_count: number;
|
|
17
|
+
flipped_count: number;
|
|
18
|
+
zero_area_count: number;
|
|
19
|
+
mixed_winding: boolean;
|
|
20
|
+
self_intersects: boolean;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=atlas_chart_param_validity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atlas_chart_param_validity.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/geom/3d/atlas/param/atlas_chart_param_validity.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;GAaG;AACH;oBAH6B,MAAM;mBAAgB,MAAM;qBAAkB,MAAM;mBAC9D,OAAO;qBAAkB,OAAO;EAuDlD"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { segment2_intersects_segment2_2d } from "../../../2d/line/segment2_intersects_segment2_2d.js";
|
|
2
|
+
import { tri2_signed_area } from "../../../2d/triangle/tri2_signed_area.js";
|
|
3
|
+
|
|
4
|
+
const AREA_EPSILON = 1e-12;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validity of a chart's current UV layout (xatlas
|
|
8
|
+
* `isChartParameterizationValid`). A layout is bijective when every triangle
|
|
9
|
+
* has the same winding (all positive or all flipped — a uniformly mirrored
|
|
10
|
+
* chart is fine) with no zero-area triangles, and the chart outline does not
|
|
11
|
+
* self-intersect.
|
|
12
|
+
*
|
|
13
|
+
* Boundary self-intersection is a brute-force O(b^2) test over the chart's
|
|
14
|
+
* boundary edges, skipping pairs that share an endpoint.
|
|
15
|
+
*
|
|
16
|
+
* @param {Chart} chart
|
|
17
|
+
* @returns {{positive_count:number, flipped_count:number, zero_area_count:number,
|
|
18
|
+
* mixed_winding:boolean, self_intersects:boolean}}
|
|
19
|
+
*/
|
|
20
|
+
export function atlas_chart_param_validity(chart) {
|
|
21
|
+
const uv = chart.uv;
|
|
22
|
+
const tri = chart.tri_local;
|
|
23
|
+
const triangle_count = chart.triangle_count;
|
|
24
|
+
|
|
25
|
+
let positive_count = 0;
|
|
26
|
+
let flipped_count = 0;
|
|
27
|
+
let zero_area_count = 0;
|
|
28
|
+
|
|
29
|
+
for (let t = 0; t < triangle_count; t++) {
|
|
30
|
+
const i0 = tri[t * 3];
|
|
31
|
+
const i1 = tri[t * 3 + 1];
|
|
32
|
+
const i2 = tri[t * 3 + 2];
|
|
33
|
+
const area = tri2_signed_area(
|
|
34
|
+
uv[i0 * 2], uv[i0 * 2 + 1],
|
|
35
|
+
uv[i1 * 2], uv[i1 * 2 + 1],
|
|
36
|
+
uv[i2 * 2], uv[i2 * 2 + 1]
|
|
37
|
+
);
|
|
38
|
+
if (area > AREA_EPSILON) positive_count++;
|
|
39
|
+
else if (area < -AREA_EPSILON) flipped_count++;
|
|
40
|
+
else zero_area_count++;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const mixed_winding = positive_count > 0 && flipped_count > 0;
|
|
44
|
+
|
|
45
|
+
const be = chart.boundary_edges;
|
|
46
|
+
const edge_count = be.length / 2;
|
|
47
|
+
let self_intersects = false;
|
|
48
|
+
|
|
49
|
+
outer:
|
|
50
|
+
for (let i = 0; i < edge_count; i++) {
|
|
51
|
+
const a0 = be[i * 2];
|
|
52
|
+
const a1 = be[i * 2 + 1];
|
|
53
|
+
const a0x = uv[a0 * 2], a0y = uv[a0 * 2 + 1];
|
|
54
|
+
const a1x = uv[a1 * 2], a1y = uv[a1 * 2 + 1];
|
|
55
|
+
|
|
56
|
+
for (let j = i + 1; j < edge_count; j++) {
|
|
57
|
+
const b0 = be[j * 2];
|
|
58
|
+
const b1 = be[j * 2 + 1];
|
|
59
|
+
// adjacent edges share an endpoint — a shared endpoint is not a crossing
|
|
60
|
+
if (a0 === b0 || a0 === b1 || a1 === b0 || a1 === b1) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const b0x = uv[b0 * 2], b0y = uv[b0 * 2 + 1];
|
|
64
|
+
const b1x = uv[b1 * 2], b1y = uv[b1 * 2 + 1];
|
|
65
|
+
if (segment2_intersects_segment2_2d(a0x, a0y, a1x, a1y, b0x, b0y, b1x, b1y)) {
|
|
66
|
+
self_intersects = true;
|
|
67
|
+
break outer;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { positive_count, flipped_count, zero_area_count, mixed_winding, self_intersects };
|
|
73
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intrinsic boundary-first flattening (a tractable BFF, Sawhney & Crane 2018).
|
|
3
|
+
*
|
|
4
|
+
* Unlike a Tutte/convex embedding (which forces a circle and so distorts
|
|
5
|
+
* thin/curled charts catastrophically — the aspect cannot be guessed without
|
|
6
|
+
* already flattening), this builds the flattened boundary from the chart's OWN
|
|
7
|
+
* geometry: keep each boundary edge's 3D length and turn at each boundary vertex
|
|
8
|
+
* by the optimal Dirichlet-to-Neumann curvature — fix the boundary scale to 0,
|
|
9
|
+
* solve the interior Poisson `L_II u = Ω_I` (flatten the interior), then
|
|
10
|
+
* `k̃_b = (π − θ_b) − (L u)_b`. By the cot-Laplacian's zero row sums ∑ k̃ = 2π so
|
|
11
|
+
* the boundary closes, with the curvature distributed by the mesh's harmonic
|
|
12
|
+
* structure (not uniformly). The interior is then a CONFORMAL extension of that
|
|
13
|
+
* boundary (least-squares Cauchy-Riemann, every boundary vertex pinned) — the u,v
|
|
14
|
+
* coupling is far less flip-prone than a harmonic interior on a non-convex
|
|
15
|
+
* boundary. The result is a natural, low-distortion, length-preserving boundary —
|
|
16
|
+
* not a forced convex shape.
|
|
17
|
+
*
|
|
18
|
+
* Measured outcome (decimated terrain): a Pareto-safe rescue but SMALL — the DtN
|
|
19
|
+
* boundary fixed the conformal-interior flips (25 → 2), yet only ~2 of 31
|
|
20
|
+
* self-intersecting charts are rescued because the flattened BOUNDARY itself then
|
|
21
|
+
* self-intersects on ~27/31. That is fundamental, not an implementation gap (true
|
|
22
|
+
* unclamped cotangents made it worse): these high-curvature decimated charts can't
|
|
23
|
+
* be developed to a SIMPLE boundary while preserving edge lengths, so cutting
|
|
24
|
+
* (piecewise) is the correct injective answer for them. Injectivity is NOT
|
|
25
|
+
* guaranteed; the caller validates and falls back to piecewise otherwise. Solves
|
|
26
|
+
* in place on `chart.uv`. See BFF_PLAN.md / REVIEW_02.md §5.0.
|
|
27
|
+
*
|
|
28
|
+
* @param {Chart} chart with `boundary_edges`, `tri_local`, `positions`, `uv`
|
|
29
|
+
* @param {{tolerance?:number, maxIterations?:number}} [options]
|
|
30
|
+
* @returns {boolean} true if a flattening was produced; false if the chart is not
|
|
31
|
+
* a single-boundary-loop disk or the interior system is degenerate
|
|
32
|
+
*/
|
|
33
|
+
export function atlas_chart_parameterize_bff(chart: Chart, options?: {
|
|
34
|
+
tolerance?: number;
|
|
35
|
+
maxIterations?: number;
|
|
36
|
+
}): boolean;
|
|
37
|
+
//# sourceMappingURL=atlas_chart_parameterize_bff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atlas_chart_parameterize_bff.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js"],"names":[],"mappings":"AAoDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH;gBAJuB,MAAM;oBAAiB,MAAM;IACvC,OAAO,CA4LnB"}
|