@woosh/meep-engine 2.154.0 → 2.156.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/README.md +1 -1
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/editor/view/ecs/ComponentControlView.d.ts +0 -9
- package/editor/view/ecs/ComponentControlView.js +2 -98
- package/package.json +1 -1
- package/src/core/binary/32BitEncoder.js +1 -1
- package/src/core/binary/to_half_float_uint16.js +3 -3
- package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.js +275 -253
- package/src/core/cache/Cache.d.ts.map +1 -1
- package/src/core/cache/Cache.js +7 -0
- package/src/core/cache/FrequencySketch.d.ts.map +1 -1
- package/src/core/cache/FrequencySketch.js +8 -4
- package/src/core/clipboard/obtainClipBoard.d.ts +6 -0
- package/src/core/clipboard/obtainClipBoard.d.ts.map +1 -0
- package/src/core/clipboard/obtainClipBoard.js +29 -0
- package/src/core/clipboard/safeClipboardReadText.d.ts +6 -0
- package/src/core/clipboard/safeClipboardReadText.d.ts.map +1 -0
- package/src/core/clipboard/safeClipboardReadText.js +55 -0
- package/src/core/clipboard/safeClipboardWriteText.d.ts +8 -0
- package/src/core/clipboard/safeClipboardWriteText.d.ts.map +1 -0
- package/src/core/clipboard/safeClipboardWriteText.js +23 -0
- package/src/core/collection/array/array_quick_sort_by_lookup_map.js +1 -1
- package/src/core/collection/array/array_set_diff_sorting.d.ts.map +1 -1
- package/src/core/collection/array/array_set_diff_sorting.js +4 -1
- package/src/core/collection/array/array_shuffle.d.ts.map +1 -1
- package/src/core/collection/array/array_shuffle.js +30 -27
- package/src/core/collection/array/binarySearchLowIndex.d.ts.map +1 -1
- package/src/core/collection/array/binarySearchLowIndex.js +4 -3
- package/src/core/collection/array/typed/array_buffer_hash.js +1 -1
- package/src/core/collection/array/typed/is_typed_array_equals.d.ts.map +1 -1
- package/src/core/collection/array/typed/is_typed_array_equals.js +12 -2
- package/src/core/collection/heap/BinaryHeap.d.ts.map +1 -1
- package/src/core/collection/heap/BinaryHeap.js +12 -2
- package/src/core/collection/queue/Deque.d.ts.map +1 -1
- package/src/core/collection/queue/Deque.js +10 -8
- package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
- package/src/core/collection/table/RowFirstTable.js +4 -2
- package/src/core/collection/table/RowFirstTableSpec.js +2 -2
- package/src/core/color/operations/color_lerp.d.ts.map +1 -1
- package/src/core/color/operations/color_lerp.js +10 -3
- package/src/core/color/rgb2uint32.js +1 -1
- package/src/core/color/rgbe9995_to_rgb.js +1 -1
- package/src/core/function/objectsEqual.d.ts.map +1 -1
- package/src/core/function/objectsEqual.js +2 -1
- package/src/core/geom/2d/aabb/AABB2.d.ts.map +1 -1
- package/src/core/geom/2d/aabb/AABB2.js +12 -11
- package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.d.ts.map +1 -1
- package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.js +30 -4
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.d.ts.map +1 -1
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +6 -2
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -1
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +388 -386
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.d.ts.map +1 -1
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.js +8 -3
- package/src/core/geom/2d/quad-tree/QuadTreeDatum.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/QuadTreeDatum.js +9 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts +3 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +3 -1
- package/src/core/geom/2d/quad-tree-binary/QuadTree.js +714 -714
- package/src/core/geom/2d/r-tree/StaticR2Tree.d.ts.map +1 -1
- package/src/core/geom/2d/r-tree/StaticR2Tree.js +5 -4
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +33 -29
- package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +3 -1
- package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.js +10 -7
- package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.js +30 -9
- package/src/core/geom/3d/aabb/compute_aabb_from_points.js +3 -3
- package/src/core/geom/3d/box/box3_raycast.d.ts +37 -0
- package/src/core/geom/3d/box/box3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/box/box3_raycast.js +81 -0
- package/src/core/geom/3d/capsule/capsule_raycast.d.ts +35 -0
- package/src/core/geom/3d/capsule/capsule_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/capsule/capsule_raycast.js +93 -0
- package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.d.ts.map +1 -1
- package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +4 -0
- package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +1 -1
- package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.d.ts.map +1 -1
- package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.js +3 -2
- package/src/core/geom/3d/mat4/decompose_matrix_4_array.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +12 -2
- package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +2 -2
- package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts +1 -1
- package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_multiply_alphatensor.js +19 -13
- package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.d.ts.map +1 -1
- package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.js +3 -2
- package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +3 -2
- package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/MeshShape3D.js +7 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnionShape3D.js +3 -2
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts.map +1 -1
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +153 -148
- 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 +7 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.d.ts.map +1 -1
- package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +13 -10
- package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.d.ts +1 -1
- package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +2 -2
- package/src/core/geom/3d/sphere/sphere_raycast.d.ts +33 -0
- package/src/core/geom/3d/sphere/sphere_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/sphere/sphere_raycast.js +47 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts +24 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +39 -0
- package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.js +4 -2
- package/src/core/geom/3d/topology/bounds/computeTriangleClusterNormalBoundingCone.js +3 -3
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +1 -1
- package/src/core/geom/3d/topology/tm_vertex_compute_normal.d.ts.map +1 -1
- package/src/core/geom/3d/topology/tm_vertex_compute_normal.js +4 -2
- package/src/core/geom/3d/util/make_justified_point_grid.d.ts.map +1 -1
- package/src/core/geom/3d/util/make_justified_point_grid.js +18 -10
- package/src/core/geom/ConicRay.d.ts.map +1 -1
- package/src/core/geom/ConicRay.js +11 -13
- package/src/core/geom/packing/max-rect/removeRedundantBoxes.d.ts.map +1 -1
- package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +19 -4
- package/src/core/geom/vec3/v3_array_copy.d.ts +3 -3
- package/src/core/geom/vec3/v3_array_copy.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_array_copy.js +2 -2
- package/src/core/geom/vec3/v3_cross.d.ts +17 -0
- package/src/core/geom/vec3/v3_cross.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_cross.js +20 -0
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts.map +1 -0
- package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.js +1 -1
- package/src/core/geom/vec3/v3_subtract.d.ts +16 -0
- package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_subtract.js +19 -0
- package/src/core/graph/coloring/colorizeGraph.js +2 -2
- package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
- package/src/core/graph/csr/CSRGraph.js +325 -319
- package/src/core/graph/layout/CircleLayout.d.ts.map +1 -1
- package/src/core/graph/layout/CircleLayout.js +8 -6
- package/src/core/graph/metis/native/refine/compute_kway_params.d.ts.map +1 -1
- package/src/core/graph/metis/native/refine/compute_kway_params.js +139 -138
- package/src/core/graph/mn_graph_coarsen.d.ts.map +1 -1
- package/src/core/graph/mn_graph_coarsen.js +4 -2
- package/src/core/graph/v2/NodeContainer.js +7 -7
- package/src/core/localization/LocalizationEngine.js +1 -1
- package/src/core/math/bell_membership_function.d.ts.map +1 -1
- package/src/core/math/bell_membership_function.js +3 -1
- package/src/core/math/complex/complex_add.d.ts +4 -4
- package/src/core/math/complex/complex_add.d.ts.map +1 -1
- package/src/core/math/complex/complex_add.js +3 -3
- package/src/core/math/complex/complex_div.d.ts +4 -4
- package/src/core/math/complex/complex_div.d.ts.map +1 -1
- package/src/core/math/complex/complex_div.js +3 -3
- package/src/core/math/complex/complex_mul.d.ts +4 -4
- package/src/core/math/complex/complex_mul.d.ts.map +1 -1
- package/src/core/math/complex/complex_mul.js +3 -3
- package/src/core/math/complex/complex_sub.d.ts +4 -4
- package/src/core/math/complex/complex_sub.d.ts.map +1 -1
- package/src/core/math/complex/complex_sub.js +3 -3
- package/src/core/math/idct_1d.d.ts +4 -4
- package/src/core/math/idct_1d.d.ts.map +1 -1
- package/src/core/math/idct_1d.js +3 -3
- package/src/core/math/noise/create_simplex_noise_2d.d.ts.map +1 -1
- package/src/core/math/noise/create_simplex_noise_2d.js +4 -2
- package/src/core/math/noise/sdnoise.d.ts.map +1 -1
- package/src/core/math/noise/sdnoise.js +12 -9
- package/src/core/math/physics/mie/compute_bhmie_optical_properties.d.ts.map +1 -1
- package/src/core/math/physics/mie/compute_bhmie_optical_properties.js +94 -50
- package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts +3 -6
- package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts.map +1 -1
- package/src/core/math/physics/mie/lorenz_mie_coefs.js +180 -157
- package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts +3 -4
- package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts.map +1 -1
- package/src/core/math/physics/mie/mie_ab_to_optical_properties.js +47 -21
- package/src/core/math/random/randomIntegerBetween.d.ts.map +1 -1
- package/src/core/math/random/randomIntegerBetween.js +4 -1
- package/src/core/math/solveCubic.d.ts.map +1 -1
- package/src/core/math/solveCubic.js +95 -82
- package/src/core/math/spline/computeCatmullRomSplineUniformDistance.d.ts.map +1 -1
- package/src/core/math/spline/computeCatmullRomSplineUniformDistance.js +13 -0
- package/src/core/math/statistics/softmax.js +1 -1
- package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts +1 -0
- package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts.map +1 -1
- package/src/core/model/node-graph/visual/NodeGraphVisualData.js +2 -1
- package/src/core/model/node-graph/visual/NodeVisualData.js +1 -1
- package/src/core/model/object/ImmutableObjectPool.d.ts +7 -0
- package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
- package/src/core/model/object/ImmutableObjectPool.js +20 -10
- package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts.map +1 -1
- package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +39 -2
- package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts.map +1 -1
- package/src/core/model/reactive/model/terminal/ReactiveReference.js +2 -0
- package/src/core/parser/simple/readHexToken.d.ts.map +1 -1
- package/src/core/parser/simple/readHexToken.js +6 -0
- package/src/core/primitives/numbers/number_pretty_print.d.ts.map +1 -1
- package/src/core/primitives/numbers/number_pretty_print.js +4 -1
- package/src/core/primitives/strings/string_jaro_winkler.js +1 -1
- package/src/core/process/CompositeProcess.js +1 -1
- package/src/core/process/action/AsynchronousDelayAction.d.ts.map +1 -1
- package/src/core/process/action/AsynchronousDelayAction.js +3 -0
- package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
- package/src/core/process/executor/ConcurrentExecutor.js +3 -2
- package/src/core/process/task/util/randomCountTask.d.ts.map +1 -1
- package/src/core/process/task/util/randomCountTask.js +3 -1
- package/src/core/process/undo/ActionProcessor.d.ts.map +1 -1
- package/src/core/process/undo/ActionProcessor.js +5 -3
- package/src/core/process/worker/WorkerBuilder.js +3 -3
- package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
- package/src/engine/animation/curve/AnimationCurve.js +4 -2
- package/src/engine/control/first-person/DESIGN.md +1 -1
- package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts +55 -0
- package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts.map +1 -0
- package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -0
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +23 -2
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerController.js +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +168 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +115 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +71 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +255 -55
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +82 -43
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/LedgeGrab.js +405 -213
- package/src/engine/control/first-person/abilities/Mantle.d.ts +6 -0
- package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Mantle.js +104 -45
- package/src/engine/control/first-person/abilities/ScrambleUp.d.ts +61 -0
- package/src/engine/control/first-person/abilities/ScrambleUp.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/ScrambleUp.js +182 -0
- package/src/engine/control/first-person/math/jumpDynamics.d.ts +84 -0
- package/src/engine/control/first-person/math/jumpDynamics.d.ts.map +1 -0
- package/src/engine/control/first-person/math/jumpDynamics.js +108 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +45 -1
- package/src/engine/graphics/camera/testClippingPlaneComputation.js +1 -1
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +8 -0
- package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +1 -1
- package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
- package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +1 -1
- package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +1 -1
- package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +1 -1
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +1 -1
- package/src/engine/graphics/shadows/testShadowMapRendering.js +1 -1
- package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -0
- package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -0
- package/src/engine/physics/PLAN.md +6 -5
- package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +147 -33
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +1750 -1747
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts +12 -8
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -1
- package/src/engine/physics/gjk/gjk_epa_penetration.js +447 -158
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/convex_convex_manifold.js +22 -25
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -13
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/convex_decomposition.js +61 -65
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/mesh_convex_hull.js +13 -8
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_concave.js +5 -3
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_hit.js +81 -78
- package/src/engine/sound/SoundEngine.d.ts.map +1 -1
- package/src/engine/sound/SoundEngine.js +28 -0
- package/src/engine/sound/dB2Volume.d.ts +1 -1
- package/src/engine/sound/dB2Volume.d.ts.map +1 -1
- package/src/engine/sound/dB2Volume.js +1 -1
- package/src/engine/sound/ecs/SoundController.d.ts +4 -0
- package/src/engine/sound/ecs/SoundController.d.ts.map +1 -1
- package/src/engine/sound/ecs/SoundController.js +4 -0
- package/src/engine/sound/ecs/SoundControllerSystem.d.ts +5 -0
- package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/SoundControllerSystem.js +5 -0
- package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +69 -0
- package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/AudioEmitter.js +83 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +97 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +238 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +90 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.js +324 -0
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts +59 -0
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -0
- package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts +16 -65
- package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundEmitter.js +19 -224
- package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts +26 -29
- package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.js +168 -135
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts +36 -59
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.js +154 -390
- package/src/engine/sound/ecs/emitter/SoundTrack.d.ts +20 -23
- package/src/engine/sound/ecs/emitter/SoundTrack.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundTrack.js +34 -152
- package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +993 -0
- package/src/engine/sound/sopra/README.md +643 -7
- package/src/engine/sound/sopra/SopraEngine.d.ts +229 -0
- package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -0
- package/src/engine/sound/sopra/SopraEngine.js +423 -0
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts +26 -0
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts.map +1 -0
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -0
- package/src/engine/sound/sopra/asset/BufferProvider.d.ts +24 -0
- package/src/engine/sound/sopra/asset/BufferProvider.d.ts.map +1 -0
- package/src/engine/sound/sopra/asset/BufferProvider.js +29 -0
- package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts +31 -0
- package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts.map +1 -0
- package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -0
- package/src/engine/sound/sopra/definition/BusDefinition.d.ts +83 -0
- package/src/engine/sound/sopra/definition/BusDefinition.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/BusDefinition.js +142 -0
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -0
- package/src/engine/sound/sopra/definition/DuckingRule.d.ts +71 -0
- package/src/engine/sound/sopra/definition/DuckingRule.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/DuckingRule.js +106 -0
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -0
- package/src/engine/sound/sopra/definition/EventDescription.d.ts +132 -0
- package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/EventDescription.js +259 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +71 -0
- package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts +51 -0
- package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -0
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -0
- package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts +72 -0
- package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -0
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -0
- package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts +14 -0
- package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/SopraPanningModel.js +20 -0
- package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts +10 -0
- package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +93 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +80 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +181 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +74 -0
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts +34 -0
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +100 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +101 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +230 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +103 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +40 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +91 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts +44 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +65 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +131 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -0
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +24 -0
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +70 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -0
- package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +74 -0
- package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -0
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +49 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +101 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -0
- package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts +31 -0
- package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts.map +1 -0
- package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.js +106 -0
- package/src/engine/sound/sopra/runtime/BusGraph.d.ts +79 -0
- package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/BusGraph.js +227 -0
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts +144 -0
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/EventInstance.js +579 -0
- package/src/engine/sound/sopra/runtime/ParameterStore.d.ts +42 -0
- package/src/engine/sound/sopra/runtime/ParameterStore.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -0
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts +42 -0
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -0
- package/src/engine/sound/sopra/runtime/Voice.d.ts +67 -0
- package/src/engine/sound/sopra/runtime/Voice.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/Voice.js +145 -0
- package/src/engine/sound/sopra/runtime/VoiceManager.d.ts +38 -0
- package/src/engine/sound/sopra/runtime/VoiceManager.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -0
- package/src/engine/sound/sopra/runtime/VoicePool.d.ts +12 -0
- package/src/engine/sound/sopra/runtime/VoicePool.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/VoicePool.js +17 -0
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +11 -0
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -0
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -0
- package/src/engine/sound/sopra/serialization/sopraJSON.d.ts +33 -0
- package/src/engine/sound/sopra/serialization/sopraJSON.d.ts.map +1 -0
- package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -0
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +27 -0
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -0
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -0
- package/src/engine/sound/sopra/util/MockAudioContext.d.ts +74 -0
- package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -0
- package/src/engine/sound/sopra/util/MockAudioContext.js +215 -0
- package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts +15 -0
- package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts.map +1 -0
- package/src/engine/sound/sopra/util/buildAttenuationCurve.js +40 -0
- package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +34 -0
- package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -0
- package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -0
- package/src/engine/sound/volume2dB.d.ts +1 -1
- package/src/engine/sound/volume2dB.d.ts.map +1 -1
- package/src/engine/sound/volume2dB.js +1 -1
- package/src/engine/graphics/sh3/path_tracer/sampling/v3_orthonormal_matrix_from_normal.d.ts.map +0 -1
- package/src/engine/physics/narrowphase/ray_shapes.d.ts +0 -66
- package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +0 -1
- package/src/engine/physics/narrowphase/ray_shapes.js +0 -187
- package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts +0 -23
- package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts.map +0 -1
- package/src/engine/sound/ecs/emitter/SoundEmitterChannel.js +0 -32
- package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts +0 -18
- package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts.map +0 -1
- package/src/engine/sound/ecs/emitter/SoundTrackNodes.js +0 -18
- package/src/engine/sound/sopra/AbstractAudioClip.d.ts +0 -26
- package/src/engine/sound/sopra/AbstractAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/AbstractAudioClip.js +0 -29
- package/src/engine/sound/sopra/ContainerAudioClip.d.ts +0 -12
- package/src/engine/sound/sopra/ContainerAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/ContainerAudioClip.js +0 -13
- package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts +0 -12
- package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/RandomContainerAudioClip.js +0 -15
- package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts +0 -7
- package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/SequenceContainerAudioClip.js +0 -8
- package/src/engine/sound/sopra/SilenceAudioClip.d.ts +0 -13
- package/src/engine/sound/sopra/SilenceAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/SilenceAudioClip.js +0 -15
- /package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.d.ts +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { GameAssetType } from "../../../asset/GameAssetType.js";
|
|
2
|
+
import { SoundAssetLoader } from "../../../asset/loaders/SoundAssetLoader.js";
|
|
3
|
+
import { System } from "../../../ecs/System.js";
|
|
4
|
+
import { Transform } from "../../../ecs/transform/Transform.js";
|
|
5
|
+
import { AssetManagerBufferProvider } from "../../sopra/asset/AssetManagerBufferProvider.js";
|
|
6
|
+
import { EventInstanceState } from "../../sopra/runtime/EventInstance.js";
|
|
7
|
+
import { volume2dB } from "../../volume2dB.js";
|
|
8
|
+
import SoundListener from "../SoundListener.js";
|
|
9
|
+
import { AudioEmitter } from "./AudioEmitter.js";
|
|
10
|
+
import { LiveEmitterSet } from "./LiveEmitterSet.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Drives {@link AudioEmitter} components through the shared sopra engine — the native counterpart to
|
|
14
|
+
* the legacy `SoundEmitterSystem`. Each emitter owns its {@link EventDescription} directly (no track
|
|
15
|
+
* translation); the entity's {@link Transform} feeds position; `volume` changes plumb to the instance
|
|
16
|
+
* gain. Each frame it forwards the listener pose, refreshes the spatial live set, and ticks sopra.
|
|
17
|
+
*
|
|
18
|
+
* **Two play paths**, chosen once at {@link link} (routing is fixed for the lifetime of the link —
|
|
19
|
+
* mutating `autoplay`/`is3D` afterward does nothing without an unlink/relink):
|
|
20
|
+
* - **Spatially managed** (`autoplay && is3D && looping`): registered with a {@link LiveEmitterSet} and
|
|
21
|
+
* left *dormant* until a refresh promotes the nearest in-range emitters up to a global voice budget —
|
|
22
|
+
* this is what lets the system carry far more emitters than can sound at once (the 100k-bird case). A
|
|
23
|
+
* dormant emitter costs only a BVH leaf; no instance, no nodes, no per-frame work. NOTE: for a crowd
|
|
24
|
+
* of content-equal managed events to all sound, their `maxInstances` must be ≥ the live budget — they
|
|
25
|
+
* share one sopra polyphony bucket (keyed by event content), so a lower cap gates the budget. Only
|
|
26
|
+
* *looping* events are managed: the live set models a persistent source whose phase is reconstructed
|
|
27
|
+
* on (re-)promotion — meaningless for a finite one-shot, and a dead one-shot would otherwise keep
|
|
28
|
+
* re-promoting and occupying a budget slot — so finite events take the direct path.
|
|
29
|
+
* - **Direct** (any other `autoplay` event — 2D sounds, or finite 3D one-shots): played immediately on
|
|
30
|
+
* link, stopped on unlink. 2D sounds (music, ambience beds) have nothing to cull by distance; a finite
|
|
31
|
+
* 3D one-shot still spatializes (its instance attenuates by position) and self-releases when it ends.
|
|
32
|
+
*
|
|
33
|
+
* Non-autoplay emitters are inert: neither played nor registered, awaiting a future explicit trigger.
|
|
34
|
+
*
|
|
35
|
+
* Shares the single {@link SopraEngine} owned by {@link SoundEngine} (created on demand, idempotent),
|
|
36
|
+
* so it coexists with the legacy `SoundEmitterSystem` during the strangler migration.
|
|
37
|
+
*/
|
|
38
|
+
export class AudioEmitterSystem extends System {
|
|
39
|
+
/**
|
|
40
|
+
* @param {AssetManager} assetManager
|
|
41
|
+
* @param {SoundEngine} soundEngine owns the AudioContext + master destination + sopra engine
|
|
42
|
+
* @param {{budget?:number, liveStickiness?:number, fadeOutSeconds?:number}} [liveEmitterSetOptions]
|
|
43
|
+
* forwarded to the {@link LiveEmitterSet} that manages 3D emitters
|
|
44
|
+
*/
|
|
45
|
+
constructor(assetManager, soundEngine, liveEmitterSetOptions = {}) {
|
|
46
|
+
super();
|
|
47
|
+
|
|
48
|
+
this.dependencies = [AudioEmitter, Transform];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @type {SoundEngine}
|
|
52
|
+
*/
|
|
53
|
+
this.soundEngine = soundEngine;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @type {AudioContext}
|
|
57
|
+
*/
|
|
58
|
+
this.webAudioContext = soundEngine.context;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @type {AssetManager}
|
|
62
|
+
*/
|
|
63
|
+
this.assetManager = assetManager;
|
|
64
|
+
|
|
65
|
+
// Self-sufficient: register the Sound asset loader if nothing has yet (so this system works
|
|
66
|
+
// standalone, without the legacy SoundEmitterSystem). Guarded against double-registration (D7).
|
|
67
|
+
if (!assetManager.hasLoaderForType(GameAssetType.Sound)) {
|
|
68
|
+
assetManager.registerLoader(GameAssetType.Sound, new SoundAssetLoader(soundEngine.context));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The shared sopra engine (idempotent create — reused if the legacy system already made it).
|
|
73
|
+
* @type {SopraEngine}
|
|
74
|
+
*/
|
|
75
|
+
this.sopra = soundEngine.createSopra(new AssetManagerBufferProvider(assetManager));
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Spatial lifecycle for 3D ("managed") emitters: BVH cull → promote nearest-in-range up to the
|
|
79
|
+
* budget, demote the rest. 2D emitters bypass this and play directly.
|
|
80
|
+
* @type {LiveEmitterSet}
|
|
81
|
+
*/
|
|
82
|
+
this.liveSet = new LiveEmitterSet(this.sopra, liveEmitterSetOptions);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Per-entity records: { emitter, transform, managed, instance, onVolume }. For a managed (3D)
|
|
86
|
+
* emitter the playing instance lives in {@link liveSet} (record.instance stays null); for a
|
|
87
|
+
* direct (2D) emitter it is held here.
|
|
88
|
+
* @type {object[]}
|
|
89
|
+
*/
|
|
90
|
+
this.data = [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {AudioEmitter} emitter
|
|
95
|
+
* @param {Transform} transform
|
|
96
|
+
* @param {number} entity
|
|
97
|
+
*/
|
|
98
|
+
link(emitter, transform, entity) {
|
|
99
|
+
// Spatially managed = a persistent (looping) 3D autoplay source. Finite 3D one-shots and all 2D
|
|
100
|
+
// sounds take the direct path; non-autoplay emitters are inert. Routing is fixed at link. See the
|
|
101
|
+
// class doc.
|
|
102
|
+
const managed = emitter.autoplay && emitter.event.is3D && emitter.event.rootClip.loops();
|
|
103
|
+
|
|
104
|
+
const record = { emitter, transform, managed, instance: null, onVolume: null };
|
|
105
|
+
|
|
106
|
+
if (managed) {
|
|
107
|
+
this.liveSet.add(entity, emitter.event, transform.position, volume2dB(emitter.volume.getValue()));
|
|
108
|
+
record.onVolume = () => this.liveSet.setGainDb(entity, volume2dB(emitter.volume.getValue()));
|
|
109
|
+
} else {
|
|
110
|
+
if (emitter.autoplay) {
|
|
111
|
+
record.instance = this.#play(emitter, transform);
|
|
112
|
+
}
|
|
113
|
+
record.onVolume = () => this.#applyVolume(record);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
emitter.volume.onChanged.add(record.onVolume);
|
|
117
|
+
|
|
118
|
+
this.data[entity] = record;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {AudioEmitter} emitter
|
|
123
|
+
* @param {Transform} transform
|
|
124
|
+
* @param {number} entity
|
|
125
|
+
*/
|
|
126
|
+
unlink(emitter, transform, entity) {
|
|
127
|
+
const record = this.data[entity];
|
|
128
|
+
|
|
129
|
+
if (record === undefined) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
delete this.data[entity];
|
|
134
|
+
|
|
135
|
+
emitter.volume.onChanged.remove(record.onVolume);
|
|
136
|
+
|
|
137
|
+
if (record.managed) {
|
|
138
|
+
this.liveSet.remove(entity); // demotes (hard cut) if live, then unregisters
|
|
139
|
+
} else if (record.instance !== null) {
|
|
140
|
+
record.instance.stop();
|
|
141
|
+
record.instance = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The currently-playing instance for an entity, or null. For a managed (3D) emitter this is the live
|
|
147
|
+
* instance only while it is promoted (null while dormant or out of range); for a direct (2D) emitter
|
|
148
|
+
* it is the instance held since link.
|
|
149
|
+
* @param {number} entity
|
|
150
|
+
* @returns {EventInstance|null}
|
|
151
|
+
*/
|
|
152
|
+
instanceFor(entity) {
|
|
153
|
+
const record = this.data[entity];
|
|
154
|
+
|
|
155
|
+
if (record === undefined) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (record.managed) {
|
|
160
|
+
const instance = this.liveSet.instanceOf(entity);
|
|
161
|
+
return instance === undefined ? null : instance;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return record.instance;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {AudioEmitter} emitter
|
|
169
|
+
* @param {Transform} transform
|
|
170
|
+
* @returns {EventInstance|null}
|
|
171
|
+
*/
|
|
172
|
+
#play(emitter, transform) {
|
|
173
|
+
const event = emitter.event;
|
|
174
|
+
|
|
175
|
+
// A looping event is a persistent source (plays until unlink); a finite event is a one-shot that
|
|
176
|
+
// self-releases when it finishes. oneShot ALSO arms the max-lifetime backstop, so marking a loop
|
|
177
|
+
// oneShot would wrongly kill it after maxLifetimeSeconds.
|
|
178
|
+
const oneShot = !event.rootClip.loops();
|
|
179
|
+
|
|
180
|
+
const instance = this.sopra.playEvent(event, {
|
|
181
|
+
busId: event.busId,
|
|
182
|
+
position: transform.position, // shared Vector3 — tracks the entity as it moves
|
|
183
|
+
oneShot
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (instance !== null) {
|
|
187
|
+
instance.setGainDb(volume2dB(emitter.volume.getValue()));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return instance;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {object} record
|
|
195
|
+
*/
|
|
196
|
+
#applyVolume(record) {
|
|
197
|
+
const instance = record.instance;
|
|
198
|
+
|
|
199
|
+
if (instance !== null && instance.state !== EventInstanceState.Stopped) {
|
|
200
|
+
instance.setGainDb(volume2dB(record.emitter.volume.getValue()));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
update(timeDelta) {
|
|
205
|
+
const sopra = this.sopra;
|
|
206
|
+
|
|
207
|
+
const ecd = this.entityManager.dataset;
|
|
208
|
+
|
|
209
|
+
let listenerPosition = null;
|
|
210
|
+
|
|
211
|
+
if (ecd !== null) {
|
|
212
|
+
const soundListener = ecd.getAnyComponent(SoundListener);
|
|
213
|
+
|
|
214
|
+
if (soundListener.entity !== -1) {
|
|
215
|
+
// A SoundListener entity is expected to also carry a Transform (SoundListenerSystem
|
|
216
|
+
// depends on both). Tolerate a misconfigured one rather than throwing every frame and
|
|
217
|
+
// freezing all audio: a missing Transform is treated as "no usable listener" (below).
|
|
218
|
+
const listenerTransform = ecd.getComponent(soundListener.entity, Transform);
|
|
219
|
+
|
|
220
|
+
if (listenerTransform !== undefined) {
|
|
221
|
+
listenerPosition = listenerTransform.position;
|
|
222
|
+
sopra.setListener(listenerPosition);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Promote/demote the spatial live set (throttled internally). Refresh BEFORE the tick so a
|
|
228
|
+
// freshly-promoted instance spatializes on the same frame. With no usable listener the live set
|
|
229
|
+
// is not refreshed: never-promoted managed emitters stay dormant, while any already-live ones
|
|
230
|
+
// keep playing against the last known listener pose until a listener returns. The direct (2D)
|
|
231
|
+
// instances and the sopra tick run regardless.
|
|
232
|
+
if (listenerPosition !== null) {
|
|
233
|
+
this.liveSet.refresh(listenerPosition);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
sopra.update(this.webAudioContext.currentTime);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live/dormant lifecycle for spatial audio emitters (P5.2). Wraps a {@link SpatialAudioIndex} (the
|
|
3
|
+
* BVH broadphase) and, on each {@link refresh}, promotes the nearest in-range emitters — up to a global
|
|
4
|
+
* voice budget — to live sopra {@link EventInstance}s, demoting the rest. This bounds the live-instance
|
|
5
|
+
* count (and therefore WebAudio nodes + the per-frame tick) to the budget, no matter how many emitters
|
|
6
|
+
* are registered. A registered-but-dormant emitter costs only its BVH leaf: no instance, no nodes, no
|
|
7
|
+
* per-frame work.
|
|
8
|
+
*
|
|
9
|
+
* "Voice slots" are the budget, allocated at promotion. Stealing is by distance: at budget, a closer
|
|
10
|
+
* emitter bumps a farther live one (demoted). A small stickiness keeps the live set stable so emitters
|
|
11
|
+
* near the budget cutoff don't flicker promote/demote each frame (rank hysteresis — the audible kind;
|
|
12
|
+
* range-edge churn at `distanceMax` is inaudible, gain ≈ 0 there, and is handled later with chain
|
|
13
|
+
* pooling).
|
|
14
|
+
*
|
|
15
|
+
* Demotion policy: a hard **cut** when an emitter is culled out of range (already inaudible — gain ≈ 0
|
|
16
|
+
* past `distanceMax`), a click-safe **fade** when a still-in-range live emitter loses its slot to a
|
|
17
|
+
* closer one (contention). Phase reconstruction on promote is P5.4. Intended for persistent (looping)
|
|
18
|
+
* emitters; a finite event that self-ends is dropped from the live set via its `onEnded`.
|
|
19
|
+
*
|
|
20
|
+
* Budget vs per-event polyphony — they are INDEPENDENT, composed caps. `budget` bounds the global live
|
|
21
|
+
* count; sopra's {@link VoiceManager} `EventDescription.maxInstances` (keyed by event CONTENT) bounds
|
|
22
|
+
* concurrent instances of one content-equal event. If a managed event's `maxInstances` is below the
|
|
23
|
+
* number of its content-equal emitters that can be live at once, sopra gates the budget: with
|
|
24
|
+
* `stealMode None` a promote is denied (`playEvent` → null) and the emitter stays dormant (retried each
|
|
25
|
+
* refresh — see the test); with `stealMode Oldest` sopra steals between them and the two layers fight
|
|
26
|
+
* (churn). So for spatial ambience where you want up to `budget` copies of one sound audible, give that
|
|
27
|
+
* event a `maxInstances` ≥ that count (this is a content/wiring decision finalised in P5.6).
|
|
28
|
+
*/
|
|
29
|
+
export class LiveEmitterSet {
|
|
30
|
+
/**
|
|
31
|
+
* @param {SopraEngine} sopra
|
|
32
|
+
* @param {{budget?:number, liveStickiness?:number, fadeOutSeconds?:number}} [options]
|
|
33
|
+
*/
|
|
34
|
+
constructor(sopra: SopraEngine, { budget, liveStickiness, fadeOutSeconds }?: {
|
|
35
|
+
budget?: number;
|
|
36
|
+
liveStickiness?: number;
|
|
37
|
+
fadeOutSeconds?: number;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* @returns {number} live (sounding) emitter count
|
|
41
|
+
*/
|
|
42
|
+
get liveCount(): number;
|
|
43
|
+
/**
|
|
44
|
+
* @returns {number} registered emitter count
|
|
45
|
+
*/
|
|
46
|
+
get size(): number;
|
|
47
|
+
/**
|
|
48
|
+
* @param {number} entity
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
isLive(entity: number): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* @param {number} entity
|
|
54
|
+
* @returns {EventInstance|undefined} the live instance, or undefined if dormant/unknown
|
|
55
|
+
*/
|
|
56
|
+
instanceOf(entity: number): EventInstance | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Register an emitter (dormant until a refresh promotes it).
|
|
59
|
+
* @param {number} entity
|
|
60
|
+
* @param {EventDescription} event
|
|
61
|
+
* @param {Vector3} position shared position vector (tracked reactively by the spatial index)
|
|
62
|
+
* @param {number} [gainDb] per-emitter instance gain in dB, applied on every promotion (default 0 = unity)
|
|
63
|
+
*/
|
|
64
|
+
add(entity: number, event: EventDescription, position: Vector3, gainDb?: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Set the per-emitter instance gain (dB). Updates the stored value so a future promotion replays it,
|
|
67
|
+
* and applies it immediately if the emitter is currently live. A no-op for an unknown entity, and for
|
|
68
|
+
* a fading (contention-demoted) instance — it is out of the live map, so it finishes its fade
|
|
69
|
+
* untouched and the new gain only takes effect on the next promotion.
|
|
70
|
+
* @param {number} entity
|
|
71
|
+
* @param {number} gainDb
|
|
72
|
+
*/
|
|
73
|
+
setGainDb(entity: number, gainDb: number): void;
|
|
74
|
+
/**
|
|
75
|
+
* Unregister an emitter, demoting it first if live.
|
|
76
|
+
* @param {number} entity
|
|
77
|
+
*/
|
|
78
|
+
remove(entity: number): void;
|
|
79
|
+
/**
|
|
80
|
+
* Recompute the live set for the current listener position: promote the nearest in-range emitters up
|
|
81
|
+
* to the budget, demote the rest. Cheap enough to call every tick — the BVH point-query is
|
|
82
|
+
* O(log n + candidates) and the diff is re-derived from {@link #live} each call (no maintained state
|
|
83
|
+
* to drift), so there is no reason to throttle it. Allocation-free apart from the per-candidate rank
|
|
84
|
+
* entries: the scratch containers are reused across calls.
|
|
85
|
+
* @param {Vector3} listenerPosition
|
|
86
|
+
*/
|
|
87
|
+
refresh(listenerPosition: Vector3): void;
|
|
88
|
+
#private;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=LiveEmitterSet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveEmitterSet.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/sound/ecs/audio/LiveEmitterSet.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;IA+DI;;;OAGG;IACH,6EAFW;QAAC,MAAM,CAAC,EAAC,MAAM,CAAC;QAAC,cAAc,CAAC,EAAC,MAAM,CAAC;QAAC,cAAc,CAAC,EAAC,MAAM,CAAA;KAAC,EAO1E;IAED;;OAEG;IACH,wBAEC;IAED;;OAEG;IACH,mBAEC;IAED;;;OAGG;IACH,eAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,mBAHW,MAAM,GACJ,gBAAc,SAAS,CAInC;IAED;;;;;;OAMG;IACH,YALW,MAAM,uDAGN,MAAM,QAOhB;IAED;;;;;;;OAOG;IACH,kBAHW,MAAM,UACN,MAAM,QAehB;IAED;;;OAGG;IACH,eAFW,MAAM,QAShB;IAED;;;;;;;OAOG;IACH,yCAwDC;;CAgEJ"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { EventInstanceState } from "../../sopra/runtime/EventInstance.js";
|
|
2
|
+
import { SpatialAudioIndex } from "./SpatialAudioIndex.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sort rank entries nearest-first by effective (stickiness-discounted) distance.
|
|
6
|
+
* @param {{effective:number}} a
|
|
7
|
+
* @param {{effective:number}} b
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
function compareByEffective(a, b) {
|
|
11
|
+
return a.effective - b.effective;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The live/dormant lifecycle for spatial audio emitters (P5.2). Wraps a {@link SpatialAudioIndex} (the
|
|
16
|
+
* BVH broadphase) and, on each {@link refresh}, promotes the nearest in-range emitters — up to a global
|
|
17
|
+
* voice budget — to live sopra {@link EventInstance}s, demoting the rest. This bounds the live-instance
|
|
18
|
+
* count (and therefore WebAudio nodes + the per-frame tick) to the budget, no matter how many emitters
|
|
19
|
+
* are registered. A registered-but-dormant emitter costs only its BVH leaf: no instance, no nodes, no
|
|
20
|
+
* per-frame work.
|
|
21
|
+
*
|
|
22
|
+
* "Voice slots" are the budget, allocated at promotion. Stealing is by distance: at budget, a closer
|
|
23
|
+
* emitter bumps a farther live one (demoted). A small stickiness keeps the live set stable so emitters
|
|
24
|
+
* near the budget cutoff don't flicker promote/demote each frame (rank hysteresis — the audible kind;
|
|
25
|
+
* range-edge churn at `distanceMax` is inaudible, gain ≈ 0 there, and is handled later with chain
|
|
26
|
+
* pooling).
|
|
27
|
+
*
|
|
28
|
+
* Demotion policy: a hard **cut** when an emitter is culled out of range (already inaudible — gain ≈ 0
|
|
29
|
+
* past `distanceMax`), a click-safe **fade** when a still-in-range live emitter loses its slot to a
|
|
30
|
+
* closer one (contention). Phase reconstruction on promote is P5.4. Intended for persistent (looping)
|
|
31
|
+
* emitters; a finite event that self-ends is dropped from the live set via its `onEnded`.
|
|
32
|
+
*
|
|
33
|
+
* Budget vs per-event polyphony — they are INDEPENDENT, composed caps. `budget` bounds the global live
|
|
34
|
+
* count; sopra's {@link VoiceManager} `EventDescription.maxInstances` (keyed by event CONTENT) bounds
|
|
35
|
+
* concurrent instances of one content-equal event. If a managed event's `maxInstances` is below the
|
|
36
|
+
* number of its content-equal emitters that can be live at once, sopra gates the budget: with
|
|
37
|
+
* `stealMode None` a promote is denied (`playEvent` → null) and the emitter stays dormant (retried each
|
|
38
|
+
* refresh — see the test); with `stealMode Oldest` sopra steals between them and the two layers fight
|
|
39
|
+
* (churn). So for spatial ambience where you want up to `budget` copies of one sound audible, give that
|
|
40
|
+
* event a `maxInstances` ≥ that count (this is a content/wiring decision finalised in P5.6).
|
|
41
|
+
*/
|
|
42
|
+
export class LiveEmitterSet {
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @type {SopraEngine}
|
|
46
|
+
*/
|
|
47
|
+
#sopra;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @type {SpatialAudioIndex}
|
|
51
|
+
*/
|
|
52
|
+
#index = new SpatialAudioIndex();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @type {number}
|
|
56
|
+
*/
|
|
57
|
+
#budget;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Live-emitter distance discount in [0, 1]; a live emitter at distance d competes as `d * stickiness`
|
|
61
|
+
* so a newcomer must be more than that much closer to steal its slot.
|
|
62
|
+
* @type {number}
|
|
63
|
+
*/
|
|
64
|
+
#stickiness;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fade-out duration (seconds) when a still-in-range live emitter loses its slot to a closer one
|
|
68
|
+
* (contention) — a hard cut there would click. Demotion by cull (out of range) is an immediate stop.
|
|
69
|
+
* @type {number}
|
|
70
|
+
*/
|
|
71
|
+
#fadeOutSeconds;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* entity -> { event, position, gainDb, playingSince }. `gainDb` is the per-emitter instance gain
|
|
75
|
+
* (e.g. the owning `AudioEmitter.volume` in dB) re-applied on every promotion so it survives a
|
|
76
|
+
* demote→re-promote cycle; `playingSince` carries the continuous-clock phase (see {@link add}).
|
|
77
|
+
* @type {Map<number, {event: EventDescription, position: Vector3, gainDb: number, playingSince: number}>}
|
|
78
|
+
*/
|
|
79
|
+
#emitters = new Map();
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* entity -> the live instance
|
|
83
|
+
* @type {Map<number, EventInstance>}
|
|
84
|
+
*/
|
|
85
|
+
#live = new Map();
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reused candidate buffer for {@link SpatialAudioIndex.queryAudible} (grows, never shrinks; only
|
|
89
|
+
* `[0, count)` is valid each refresh) — keeps the cull allocation-free.
|
|
90
|
+
* @type {number[]}
|
|
91
|
+
*/
|
|
92
|
+
#candidates = [];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reused refresh scratch (the cull runs every tick, so it must not allocate containers per frame):
|
|
96
|
+
* `#ranked` collects `{entity, effective}` for in-range candidates to sort by distance; `#inRange` and
|
|
97
|
+
* `#target` are the in-range and nearest-budget sets; `#demoteScratch` snapshots the live entities to
|
|
98
|
+
* demote (so `#demote` can mutate `#live` mid-loop). All cleared at the top of each {@link refresh}.
|
|
99
|
+
*/
|
|
100
|
+
#ranked = [];
|
|
101
|
+
#inRange = new Set();
|
|
102
|
+
#target = new Set();
|
|
103
|
+
#demoteScratch = [];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {SopraEngine} sopra
|
|
107
|
+
* @param {{budget?:number, liveStickiness?:number, fadeOutSeconds?:number}} [options]
|
|
108
|
+
*/
|
|
109
|
+
constructor(sopra, { budget = 64, liveStickiness = 0.8, fadeOutSeconds = 0.15 } = {}) {
|
|
110
|
+
this.#sopra = sopra;
|
|
111
|
+
this.#budget = budget;
|
|
112
|
+
this.#stickiness = liveStickiness;
|
|
113
|
+
this.#fadeOutSeconds = fadeOutSeconds;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @returns {number} live (sounding) emitter count
|
|
118
|
+
*/
|
|
119
|
+
get liveCount() {
|
|
120
|
+
return this.#live.size;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @returns {number} registered emitter count
|
|
125
|
+
*/
|
|
126
|
+
get size() {
|
|
127
|
+
return this.#emitters.size;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {number} entity
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
isLive(entity) {
|
|
135
|
+
return this.#live.has(entity);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {number} entity
|
|
140
|
+
* @returns {EventInstance|undefined} the live instance, or undefined if dormant/unknown
|
|
141
|
+
*/
|
|
142
|
+
instanceOf(entity) {
|
|
143
|
+
return this.#live.get(entity);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Register an emitter (dormant until a refresh promotes it).
|
|
148
|
+
* @param {number} entity
|
|
149
|
+
* @param {EventDescription} event
|
|
150
|
+
* @param {Vector3} position shared position vector (tracked reactively by the spatial index)
|
|
151
|
+
* @param {number} [gainDb] per-emitter instance gain in dB, applied on every promotion (default 0 = unity)
|
|
152
|
+
*/
|
|
153
|
+
add(entity, event, position, gainDb = 0) {
|
|
154
|
+
this.#index.add(entity, event, position);
|
|
155
|
+
// playingSince: continuous-clock semantics — the emitter has been "playing" since it linked, so a
|
|
156
|
+
// later promotion resumes the loop at the phase it should be at (see #promote + EventInstance startTime).
|
|
157
|
+
this.#emitters.set(entity, { event, position, gainDb, playingSince: this.#sopra.currentTime });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Set the per-emitter instance gain (dB). Updates the stored value so a future promotion replays it,
|
|
162
|
+
* and applies it immediately if the emitter is currently live. A no-op for an unknown entity, and for
|
|
163
|
+
* a fading (contention-demoted) instance — it is out of the live map, so it finishes its fade
|
|
164
|
+
* untouched and the new gain only takes effect on the next promotion.
|
|
165
|
+
* @param {number} entity
|
|
166
|
+
* @param {number} gainDb
|
|
167
|
+
*/
|
|
168
|
+
setGainDb(entity, gainDb) {
|
|
169
|
+
const record = this.#emitters.get(entity);
|
|
170
|
+
|
|
171
|
+
if (record === undefined) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
record.gainDb = gainDb;
|
|
176
|
+
|
|
177
|
+
const instance = this.#live.get(entity);
|
|
178
|
+
if (instance !== undefined) {
|
|
179
|
+
instance.setGainDb(gainDb);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Unregister an emitter, demoting it first if live.
|
|
185
|
+
* @param {number} entity
|
|
186
|
+
*/
|
|
187
|
+
remove(entity) {
|
|
188
|
+
if (this.#live.has(entity)) {
|
|
189
|
+
this.#demote(entity, false); // unlink = the entity is gone -> hard cut
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.#index.remove(entity);
|
|
193
|
+
this.#emitters.delete(entity);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Recompute the live set for the current listener position: promote the nearest in-range emitters up
|
|
198
|
+
* to the budget, demote the rest. Cheap enough to call every tick — the BVH point-query is
|
|
199
|
+
* O(log n + candidates) and the diff is re-derived from {@link #live} each call (no maintained state
|
|
200
|
+
* to drift), so there is no reason to throttle it. Allocation-free apart from the per-candidate rank
|
|
201
|
+
* entries: the scratch containers are reused across calls.
|
|
202
|
+
* @param {Vector3} listenerPosition
|
|
203
|
+
*/
|
|
204
|
+
refresh(listenerPosition) {
|
|
205
|
+
const candidates = this.#candidates;
|
|
206
|
+
const candidateCount = this.#index.queryAudible(candidates, 0, listenerPosition);
|
|
207
|
+
|
|
208
|
+
// rank truly-in-range candidates by effective distance (live emitters get a stickiness discount)
|
|
209
|
+
const ranked = this.#ranked;
|
|
210
|
+
ranked.length = 0;
|
|
211
|
+
const inRange = this.#inRange;
|
|
212
|
+
inRange.clear();
|
|
213
|
+
for (let i = 0; i < candidateCount; i++) {
|
|
214
|
+
const entity = candidates[i];
|
|
215
|
+
const record = this.#emitters.get(entity);
|
|
216
|
+
const distance = record.position.distanceTo(listenerPosition);
|
|
217
|
+
|
|
218
|
+
// queryAudible's leaf is a conservative cube; keep only the true (spherical) in-range set
|
|
219
|
+
if (distance > record.event.distanceMax) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
inRange.add(entity);
|
|
224
|
+
|
|
225
|
+
const sticky = this.#live.has(entity) ? this.#stickiness : 1;
|
|
226
|
+
ranked.push({ entity, effective: distance * sticky });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
ranked.sort(compareByEffective);
|
|
230
|
+
|
|
231
|
+
const target = this.#target;
|
|
232
|
+
target.clear();
|
|
233
|
+
const limit = Math.min(this.#budget, ranked.length);
|
|
234
|
+
for (let i = 0; i < limit; i++) {
|
|
235
|
+
target.add(ranked[i].entity);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// demote live emitters not in the target (snapshot first — #demote mutates #live). A demoted
|
|
239
|
+
// emitter still in range lost its slot to a closer one (contention) -> fade; one that left range
|
|
240
|
+
// is already inaudible -> hard cut.
|
|
241
|
+
const demote = this.#demoteScratch;
|
|
242
|
+
demote.length = 0;
|
|
243
|
+
for (const entity of this.#live.keys()) {
|
|
244
|
+
if (!target.has(entity)) {
|
|
245
|
+
demote.push(entity);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (let i = 0; i < demote.length; i++) {
|
|
249
|
+
const entity = demote[i];
|
|
250
|
+
this.#demote(entity, inRange.has(entity));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// promote target emitters not yet live (re-derived from #live, so a denied/self-ended promotion
|
|
254
|
+
// is retried next refresh automatically)
|
|
255
|
+
for (const entity of target) {
|
|
256
|
+
if (!this.#live.has(entity)) {
|
|
257
|
+
this.#promote(entity);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @param {number} entity
|
|
264
|
+
*/
|
|
265
|
+
#promote(entity) {
|
|
266
|
+
const record = this.#emitters.get(entity);
|
|
267
|
+
const event = record.event;
|
|
268
|
+
|
|
269
|
+
// a looping event is persistent; a finite one self-releases when it finishes
|
|
270
|
+
const oneShot = !event.rootClip.loops();
|
|
271
|
+
|
|
272
|
+
// startTime resumes the loop at the phase it should be at given real elapsed time (continuous clock)
|
|
273
|
+
const instance = this.#sopra.playEvent(event, {
|
|
274
|
+
busId: event.busId,
|
|
275
|
+
position: record.position,
|
|
276
|
+
oneShot,
|
|
277
|
+
startTime: record.playingSince
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (instance === null) {
|
|
281
|
+
// denied by the per-event voice limit (stealMode None) — stay dormant, retry next refresh
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (instance.state === EventInstanceState.Stopped) {
|
|
286
|
+
// a finite event whose play time already elapsed under the continuous clock self-stopped
|
|
287
|
+
// during start (playhead past its end) — nothing to track. (Loops never hit this.)
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.#live.set(entity, instance);
|
|
292
|
+
|
|
293
|
+
// replay the emitter's gain onto the fresh instance (it starts at the event's nominal gain)
|
|
294
|
+
instance.setGainDb(record.gainDb);
|
|
295
|
+
|
|
296
|
+
// if the instance ends on its own (finite content / asset-load failure), drop the stale entry
|
|
297
|
+
instance.onEnded.add(() => {
|
|
298
|
+
if (this.#live.get(entity) === instance) {
|
|
299
|
+
this.#live.delete(entity);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @param {number} entity
|
|
306
|
+
* @param {boolean} fade fade out (contention — still audible) vs hard stop (culled out of range)
|
|
307
|
+
*/
|
|
308
|
+
#demote(entity, fade) {
|
|
309
|
+
const instance = this.#live.get(entity);
|
|
310
|
+
|
|
311
|
+
// delete BEFORE stopping: stop() fires onEnded synchronously and that handler keys off whether
|
|
312
|
+
// this entity still maps to this instance, so removing it first makes the handler a no-op. (A
|
|
313
|
+
// faded instance keeps playing until its audio-clock deadline; it is already out of #live, so it
|
|
314
|
+
// no longer occupies a budget slot, and if the same entity is re-promoted meanwhile the new
|
|
315
|
+
// instance is a different object — the old fade's onEnded still no-ops.)
|
|
316
|
+
this.#live.delete(entity);
|
|
317
|
+
|
|
318
|
+
if (fade) {
|
|
319
|
+
instance.fadeOutAndStop(this.#fadeOutSeconds);
|
|
320
|
+
} else {
|
|
321
|
+
instance.stop();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|