@woosh/meep-engine 2.138.20 → 2.139.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/package.json +1 -1
- package/src/core/collection/PairUint32Map.d.ts +100 -0
- package/src/core/collection/PairUint32Map.d.ts.map +1 -0
- package/src/core/collection/PairUint32Map.js +321 -0
- package/src/core/collection/Uint32Map.d.ts +119 -0
- package/src/core/collection/Uint32Map.d.ts.map +1 -0
- package/src/core/collection/Uint32Map.js +345 -0
- package/src/core/collection/array/array_shuffle.d.ts +10 -3
- package/src/core/collection/array/array_shuffle.d.ts.map +1 -1
- package/src/core/collection/array/array_shuffle.js +27 -22
- package/src/core/collection/heap/FibonacciHeap.d.ts +195 -0
- package/src/core/collection/heap/FibonacciHeap.d.ts.map +1 -0
- package/src/core/collection/heap/FibonacciHeap.js +586 -0
- package/src/core/collection/heap/Uint32Heap.js +1 -1
- package/src/core/collection/heap/Uint32Heap4.d.ts +169 -0
- package/src/core/collection/heap/Uint32Heap4.d.ts.map +1 -0
- package/src/core/collection/heap/Uint32Heap4.js +490 -0
- package/src/core/geom/3d/line/line3_closest_points_segment_segment.d.ts +27 -0
- package/src/core/geom/3d/line/line3_closest_points_segment_segment.d.ts.map +1 -0
- package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +88 -0
- package/src/core/geom/3d/shape/BoxShape3D.d.ts +61 -0
- package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/BoxShape3D.js +158 -0
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts +11 -0
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +12 -0
- package/src/core/geom/3d/shape/UnitCubeShape3D.d.ts +37 -9
- package/src/core/geom/3d/shape/UnitCubeShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -98
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +10 -0
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnitSphereShape3D.js +11 -0
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts +61 -0
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts.map +1 -0
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +148 -0
- package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.d.ts +39 -0
- package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +147 -0
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.d.ts +15 -0
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +22 -0
- package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.d.ts +2 -0
- package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +673 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.d.ts +26 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +222 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.d.ts +34 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.d.ts +36 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.d.ts +33 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts +68 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +365 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +31 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +112 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +22 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +55 -0
- package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.d.ts +32 -0
- package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +22 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +49 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.d.ts +134 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +276 -3
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.d.ts +17 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +135 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.d.ts +14 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +177 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +20 -4
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +5 -3
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +9 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -45
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill.js +7 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts +8 -6
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +8 -6
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.d.ts +22 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +73 -0
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_replace.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_replace.js +51 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.d.ts +10 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.d.ts +28 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +227 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.d.ts +13 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.d.ts +11 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +20 -0
- package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.d.ts +20 -0
- package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.d.ts.map +1 -0
- package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +38 -0
- package/src/core/graph/csr/CSRGraph.d.ts +168 -0
- package/src/core/graph/csr/CSRGraph.d.ts.map +1 -0
- package/src/core/graph/csr/CSRGraph.js +319 -0
- package/src/core/graph/metis/cluster_mesh_metis.d.ts +12 -0
- package/src/core/graph/metis/cluster_mesh_metis.d.ts.map +1 -1
- package/src/core/graph/metis/cluster_mesh_metis.js +12 -0
- package/src/core/graph/metis/metis.d.ts +19 -0
- package/src/core/graph/metis/metis.d.ts.map +1 -1
- package/src/core/graph/metis/metis.js +20 -0
- package/src/core/graph/metis/metis_cluster_bs.d.ts +11 -0
- package/src/core/graph/metis/metis_cluster_bs.d.ts.map +1 -1
- package/src/core/graph/metis/metis_cluster_bs.js +11 -0
- package/src/core/graph/metis/metis_options.d.ts +17 -2
- package/src/core/graph/metis/metis_options.d.ts.map +1 -1
- package/src/core/graph/metis/metis_options.js +17 -2
- package/src/core/graph/metis/native/MetisGraph.d.ts +144 -0
- package/src/core/graph/metis/native/MetisGraph.d.ts.map +1 -0
- package/src/core/graph/metis/native/MetisGraph.js +212 -0
- package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts +72 -0
- package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts.map +1 -0
- package/src/core/graph/metis/native/bisection/BisectionScratch.js +101 -0
- package/src/core/graph/metis/native/bisection/bisect_graph.d.ts +37 -0
- package/src/core/graph/metis/native/bisection/bisect_graph.d.ts.map +1 -0
- package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -0
- package/src/core/graph/metis/native/bisection/compute_2way_params.d.ts +15 -0
- package/src/core/graph/metis/native/bisection/compute_2way_params.d.ts.map +1 -0
- package/src/core/graph/metis/native/bisection/compute_2way_params.js +84 -0
- package/src/core/graph/metis/native/bisection/fm_2way.d.ts +30 -0
- package/src/core/graph/metis/native/bisection/fm_2way.d.ts.map +1 -0
- package/src/core/graph/metis/native/bisection/fm_2way.js +290 -0
- package/src/core/graph/metis/native/bisection/grow_bisection.d.ts +23 -0
- package/src/core/graph/metis/native/bisection/grow_bisection.d.ts.map +1 -0
- package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -0
- package/src/core/graph/metis/native/bisection/split_graph_two_way.d.ts +28 -0
- package/src/core/graph/metis/native/bisection/split_graph_two_way.d.ts.map +1 -0
- package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -0
- package/src/core/graph/metis/native/coarsen/coarsen_graph.d.ts +20 -0
- package/src/core/graph/metis/native/coarsen/coarsen_graph.d.ts.map +1 -0
- package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -0
- package/src/core/graph/metis/native/coarsen/create_coarse_graph.d.ts +24 -0
- package/src/core/graph/metis/native/coarsen/create_coarse_graph.d.ts.map +1 -0
- package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -0
- package/src/core/graph/metis/native/coarsen/match_shem.d.ts +41 -0
- package/src/core/graph/metis/native/coarsen/match_shem.d.ts.map +1 -0
- package/src/core/graph/metis/native/coarsen/match_shem.js +175 -0
- package/src/core/graph/metis/native/initial/initial_kway_bfs.d.ts +24 -0
- package/src/core/graph/metis/native/initial/initial_kway_bfs.d.ts.map +1 -0
- package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -0
- package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.d.ts +29 -0
- package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.d.ts.map +1 -0
- package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -0
- package/src/core/graph/metis/native/metis_partition_kway.d.ts +41 -0
- package/src/core/graph/metis/native/metis_partition_kway.d.ts.map +1 -0
- package/src/core/graph/metis/native/metis_partition_kway.js +126 -0
- package/src/core/graph/metis/native/refine/IndexedFloatMaxHeap.d.ts +62 -0
- package/src/core/graph/metis/native/refine/IndexedFloatMaxHeap.d.ts.map +1 -0
- package/src/core/graph/metis/native/refine/IndexedFloatMaxHeap.js +261 -0
- package/src/core/graph/metis/native/refine/RefinementScratch.d.ts +45 -0
- package/src/core/graph/metis/native/refine/RefinementScratch.d.ts.map +1 -0
- package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -0
- package/src/core/graph/metis/native/refine/compute_kway_params.d.ts +18 -0
- package/src/core/graph/metis/native/refine/compute_kway_params.d.ts.map +1 -0
- package/src/core/graph/metis/native/refine/compute_kway_params.js +138 -0
- package/src/core/graph/metis/native/refine/fm_kway.d.ts +63 -0
- package/src/core/graph/metis/native/refine/fm_kway.d.ts.map +1 -0
- package/src/core/graph/metis/native/refine/fm_kway.js +462 -0
- package/src/core/graph/metis/native/refine/project_kway.d.ts +22 -0
- package/src/core/graph/metis/native/refine/project_kway.d.ts.map +1 -0
- package/src/core/graph/metis/native/refine/project_kway.js +43 -0
- package/src/core/graph/metis/native/refine/refine_kway.d.ts +34 -0
- package/src/core/graph/metis/native/refine/refine_kway.d.ts.map +1 -0
- package/src/core/graph/metis/native/refine/refine_kway.js +43 -0
- package/src/core/math/linalg/eigen/matrix_householder_in_place.d.ts +2 -2
- package/src/core/math/linalg/eigen/matrix_householder_in_place.js +2 -2
- package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts +6 -4
- package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts.map +1 -1
- package/src/core/math/linalg/eigen/matrix_qr_in_place.js +69 -23
- package/src/engine/EngineHarness.d.ts +3 -1
- package/src/engine/EngineHarness.d.ts.map +1 -1
- package/src/engine/EngineHarness.js +3 -0
- package/src/engine/control/first-person/DESIGN.md +30 -6
- package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -0
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +102 -9
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerController.js +38 -3
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +533 -4
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +315 -6
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +220 -22
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +858 -241
- package/src/engine/control/first-person/TODO.md +127 -0
- package/src/engine/control/first-person/abilities/Ability.d.ts +101 -0
- package/src/engine/control/first-person/abilities/Ability.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/Ability.js +119 -0
- package/src/engine/control/first-person/abilities/AbilitySet.d.ts +86 -0
- package/src/engine/control/first-person/abilities/AbilitySet.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/AbilitySet.js +185 -0
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +62 -0
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/LedgeGrab.js +199 -0
- package/src/engine/control/first-person/abilities/Mantle.d.ts +45 -0
- package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/Mantle.js +188 -0
- package/src/engine/control/first-person/abilities/Slide.d.ts +33 -0
- package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/Slide.js +158 -0
- package/src/engine/control/first-person/abilities/WallJump.d.ts +45 -0
- package/src/engine/control/first-person/abilities/WallJump.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/WallJump.js +131 -0
- package/src/engine/control/first-person/abilities/WallRun.d.ts +44 -0
- package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/WallRun.js +180 -0
- package/src/engine/control/first-person/composer/EyeOffsetStack.d.ts +49 -0
- package/src/engine/control/first-person/composer/EyeOffsetStack.d.ts.map +1 -0
- package/src/engine/control/first-person/composer/EyeOffsetStack.js +60 -0
- package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.d.ts +100 -0
- package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.js +133 -0
- package/src/engine/control/first-person/mastery/DecisionPoint.d.ts +10 -0
- package/src/engine/control/first-person/mastery/DecisionPoint.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -0
- package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.d.ts +61 -0
- package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.js +109 -0
- package/src/engine/control/first-person/mastery/MasteryEvaluator.d.ts +40 -0
- package/src/engine/control/first-person/mastery/MasteryEvaluator.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/MasteryEvaluator.js +45 -0
- package/src/engine/control/first-person/mastery/MasteryScore.d.ts +68 -0
- package/src/engine/control/first-person/mastery/MasteryScore.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/MasteryScore.js +100 -0
- package/src/engine/control/first-person/mastery/MasterySet.d.ts +60 -0
- package/src/engine/control/first-person/mastery/MasterySet.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/MasterySet.js +86 -0
- package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.d.ts +58 -0
- package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.js +83 -0
- package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.d.ts +69 -0
- package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.d.ts.map +1 -0
- package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.js +109 -0
- package/src/engine/control/first-person/math/Spring.d.ts +56 -0
- package/src/engine/control/first-person/math/Spring.d.ts.map +1 -0
- package/src/engine/control/first-person/math/Spring.js +71 -0
- package/src/engine/control/first-person/math/computeLRCBreathRate.d.ts +26 -0
- package/src/engine/control/first-person/math/computeLRCBreathRate.d.ts.map +1 -0
- package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -0
- package/src/engine/control/first-person/math/computeMassRatios.d.ts +35 -0
- package/src/engine/control/first-person/math/computeMassRatios.d.ts.map +1 -0
- package/src/engine/control/first-person/math/computeMassRatios.js +44 -0
- package/src/engine/control/first-person/pose/FirstPersonPose.d.ts +31 -1
- package/src/engine/control/first-person/pose/FirstPersonPose.d.ts.map +1 -1
- package/src/engine/control/first-person/pose/FirstPersonPose.js +49 -3
- package/src/engine/control/first-person/pose/FirstPersonPosture.d.ts +7 -0
- package/src/engine/control/first-person/pose/FirstPersonPosture.d.ts.map +1 -0
- package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +550 -119
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +58 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensors.js +77 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +80 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +196 -0
- package/src/engine/control/first-person/test/buildTestPlayer.d.ts +20 -0
- package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -0
- package/src/engine/control/first-person/test/buildTestPlayer.js +28 -0
- package/src/engine/graphics/camera/testClippingPlaneComputation.js +0 -2
- package/src/engine/graphics/ecs/light/Light.d.ts.map +1 -1
- package/src/engine/graphics/ecs/light/Light.js +27 -0
- package/src/engine/graphics/ecs/light/LightSystem.js +1 -1
- package/src/engine/graphics/ecs/path/PathDisplaySystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/path/testPathDisplaySystem.js +0 -2
- package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +0 -2
- package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +0 -2
- package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +0 -2
- package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +0 -2
- package/src/engine/navigation/grid/find_path_on_grid_astar.d.ts.map +1 -1
- package/src/engine/navigation/grid/find_path_on_grid_astar.js +11 -2
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +11 -1
- package/src/engine/physics/PLAN.md +236 -0
- package/src/engine/physics/body/BodyStorage.d.ts +187 -0
- package/src/engine/physics/body/BodyStorage.d.ts.map +1 -0
- package/src/engine/physics/body/BodyStorage.js +427 -0
- package/src/engine/physics/broadphase/PairList.d.ts +62 -0
- package/src/engine/physics/broadphase/PairList.d.ts.map +1 -0
- package/src/engine/physics/broadphase/PairList.js +97 -0
- package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts +30 -0
- package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +1 -0
- package/src/engine/physics/broadphase/aabb_transform_oriented.js +93 -0
- package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts +16 -0
- package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts.map +1 -0
- package/src/engine/physics/broadphase/compute_fat_world_aabb.js +61 -0
- package/src/engine/physics/broadphase/generate_pairs.d.ts +38 -0
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -0
- package/src/engine/physics/broadphase/generate_pairs.js +101 -0
- package/src/engine/physics/contact/ManifoldStore.d.ts +226 -0
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -0
- package/src/engine/physics/contact/ManifoldStore.js +499 -0
- package/src/engine/physics/ecs/BodyKind.d.ts +23 -0
- package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -0
- package/src/engine/physics/ecs/BodyKind.js +24 -0
- package/src/engine/physics/ecs/Collider.d.ts +98 -0
- package/src/engine/physics/ecs/Collider.d.ts.map +1 -0
- package/src/engine/physics/ecs/Collider.js +136 -0
- package/src/engine/physics/ecs/ColliderFlags.d.ts +14 -0
- package/src/engine/physics/ecs/ColliderFlags.d.ts.map +1 -0
- package/src/engine/physics/ecs/ColliderFlags.js +15 -0
- package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +58 -0
- package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -0
- package/src/engine/physics/ecs/ColliderObserverSystem.js +103 -0
- package/src/engine/physics/ecs/ColliderSerializationAdapter.d.ts +25 -0
- package/src/engine/physics/ecs/ColliderSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -0
- package/src/engine/physics/ecs/PhysicsEvents.d.ts +15 -0
- package/src/engine/physics/ecs/PhysicsEvents.d.ts.map +1 -0
- package/src/engine/physics/ecs/PhysicsEvents.js +16 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +520 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -0
- package/src/engine/physics/ecs/PhysicsSystem.js +1159 -0
- package/src/engine/physics/ecs/RigidBody.d.ts +197 -0
- package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -0
- package/src/engine/physics/ecs/RigidBody.js +240 -0
- package/src/engine/physics/ecs/RigidBodyFlags.d.ts +21 -0
- package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -0
- package/src/engine/physics/ecs/RigidBodyFlags.js +22 -0
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts +28 -0
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +81 -0
- package/src/engine/physics/ecs/SleepState.d.ts +11 -0
- package/src/engine/physics/ecs/SleepState.d.ts.map +1 -0
- package/src/engine/physics/ecs/SleepState.js +12 -0
- package/src/engine/physics/events/ContactEventBuffer.d.ts +46 -0
- package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -0
- package/src/engine/physics/events/ContactEventBuffer.js +83 -0
- package/src/engine/physics/events/diff_manifolds.d.ts +25 -0
- package/src/engine/physics/events/diff_manifolds.d.ts.map +1 -0
- package/src/engine/physics/events/diff_manifolds.js +50 -0
- package/src/engine/physics/fluid/FluidField.d.ts +294 -16
- package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidField.js +510 -66
- package/src/engine/physics/fluid/FluidSimulator.d.ts +188 -5
- package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidSimulator.js +455 -95
- package/src/engine/physics/fluid/SliceVisualiser.d.ts +29 -6
- package/src/engine/physics/fluid/SliceVisualiser.d.ts.map +1 -1
- package/src/engine/physics/fluid/SliceVisualiser.js +190 -165
- package/src/engine/physics/fluid/ecs/FluidComponent.d.ts +154 -0
- package/src/engine/physics/fluid/ecs/FluidComponent.d.ts.map +1 -0
- package/src/engine/physics/fluid/ecs/FluidComponent.js +238 -0
- package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.d.ts +45 -0
- package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.d.ts.map +1 -0
- package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -0
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +107 -0
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts.map +1 -0
- package/src/engine/physics/fluid/ecs/FluidSystem.js +278 -0
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.d.ts +62 -1
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -6
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts +17 -4
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +105 -12
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts +43 -0
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts.map +1 -0
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +210 -0
- package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts +62 -1
- package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/WakeFluidEffector.js +302 -8
- package/src/engine/physics/fluid/prototype.js +102 -91
- package/src/engine/physics/fluid/solver/optimal_sor_omega.d.ts +33 -0
- package/src/engine/physics/fluid/solver/optimal_sor_omega.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/optimal_sor_omega.js +41 -0
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts +20 -5
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.js +60 -38
- package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts +25 -4
- package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +93 -73
- package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.d.ts +23 -0
- package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.js +60 -0
- package/src/engine/physics/fluid/solver/v3_grid_compute_divergence.d.ts +23 -0
- package/src/engine/physics/fluid/solver/v3_grid_compute_divergence.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_compute_divergence.js +68 -0
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +30 -0
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +66 -0
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.d.ts +26 -0
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -0
- package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.d.ts +30 -0
- package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +49 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +126 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +93 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +424 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +20 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +83 -0
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +26 -0
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +70 -0
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
- package/src/engine/physics/gjk/expanding_polytope_algorithm.js +8 -10
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts +29 -0
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -0
- package/src/engine/physics/inertia/world_inverse_inertia.js +79 -0
- package/src/engine/physics/integration/integrate_position.d.ts +16 -0
- package/src/engine/physics/integration/integrate_position.d.ts.map +1 -0
- package/src/engine/physics/integration/integrate_position.js +48 -0
- package/src/engine/physics/integration/integrate_velocity.d.ts +25 -0
- package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -0
- package/src/engine/physics/integration/integrate_velocity.js +79 -0
- package/src/engine/physics/integration/quat_integrate.d.ts +27 -0
- package/src/engine/physics/integration/quat_integrate.d.ts.map +1 -0
- package/src/engine/physics/integration/quat_integrate.js +62 -0
- package/src/engine/physics/island/IslandBuilder.d.ts +167 -0
- package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -0
- package/src/engine/physics/island/IslandBuilder.js +411 -0
- package/src/engine/physics/island/union_find.d.ts +51 -0
- package/src/engine/physics/island/union_find.d.ts.map +1 -0
- package/src/engine/physics/island/union_find.js +76 -0
- package/src/engine/physics/narrowphase/PosedShape.d.ts +59 -0
- package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/PosedShape.js +110 -0
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts +32 -0
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/box_box_manifold.js +543 -0
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts +122 -0
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/capsule_contacts.js +508 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +11 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/narrowphase_step.js +382 -0
- package/src/engine/physics/narrowphase/sphere_box_contact.d.ts +38 -0
- package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/sphere_box_contact.js +130 -0
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts +26 -0
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/sphere_sphere_contact.js +51 -0
- package/src/engine/physics/queries/PhysicsSurfacePoint.d.ts +83 -0
- package/src/engine/physics/queries/PhysicsSurfacePoint.d.ts.map +1 -0
- package/src/engine/physics/queries/PhysicsSurfacePoint.js +100 -0
- package/src/engine/physics/queries/raycast.d.ts +20 -0
- package/src/engine/physics/queries/raycast.d.ts.map +1 -0
- package/src/engine/physics/queries/raycast.js +249 -0
- package/src/engine/physics/solver/friction_cone.d.ts +16 -0
- package/src/engine/physics/solver/friction_cone.d.ts.map +1 -0
- package/src/engine/physics/solver/friction_cone.js +37 -0
- package/src/engine/physics/solver/solve_contacts.d.ts +36 -0
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -0
- package/src/engine/physics/solver/solve_contacts.js +598 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/OrderedEdge.d.ts +0 -34
- package/src/core/geom/3d/topology/struct/binary/io/edge/OrderedEdge.d.ts.map +0 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/OrderedEdge.js +0 -66
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_calc_edges.d.ts +0 -2
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_calc_edges.d.ts.map +0 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_calc_edges.js +0 -54
- package/src/core/geom/3d/topology/struct/binary/io/edge/get_or_create_edge_map.d.ts +0 -2
- package/src/core/geom/3d/topology/struct/binary/io/edge/get_or_create_edge_map.d.ts.map +0 -1
- package/src/core/geom/3d/topology/struct/binary/io/edge/get_or_create_edge_map.js +0 -26
- package/src/engine/ecs/components/Motion.d.ts +0 -21
- package/src/engine/ecs/components/Motion.d.ts.map +0 -1
- package/src/engine/ecs/components/Motion.js +0 -27
- package/src/engine/ecs/components/MotionSerializationAdapter.d.ts +0 -20
- package/src/engine/ecs/components/MotionSerializationAdapter.d.ts.map +0 -1
- package/src/engine/ecs/components/MotionSerializationAdapter.js +0 -26
- package/src/engine/ecs/systems/MotionSystem.d.ts +0 -9
- package/src/engine/ecs/systems/MotionSystem.d.ts.map +0 -1
- package/src/engine/ecs/systems/MotionSystem.js +0 -29
- package/src/engine/physics/fluid/Fluid.d.ts +0 -26
- package/src/engine/physics/fluid/Fluid.d.ts.map +0 -1
- package/src/engine/physics/fluid/Fluid.js +0 -221
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_reverse.d.ts +0 -7
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_reverse.d.ts.map +0 -1
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_reverse.js +0 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assert } from "../../../core/assert.js";
|
|
1
2
|
import Quaternion from "../../../core/geom/Quaternion.js";
|
|
2
3
|
import Vector3 from "../../../core/geom/Vector3.js";
|
|
3
4
|
import { clamp } from "../../../core/math/clamp.js";
|
|
@@ -10,12 +11,19 @@ import Entity from "../../ecs/Entity.js";
|
|
|
10
11
|
import { System } from "../../ecs/System.js";
|
|
11
12
|
import { Transform } from "../../ecs/transform/Transform.js";
|
|
12
13
|
import { Camera } from "../../graphics/ecs/camera/Camera.js";
|
|
14
|
+
import { EyeOffsetStack } from "./composer/EyeOffsetStack.js";
|
|
15
|
+
import { BodyKind } from "../../physics/ecs/BodyKind.js";
|
|
16
|
+
import { RigidBody } from "../../physics/ecs/RigidBody.js";
|
|
13
17
|
import { FirstPersonPlayerController } from "./FirstPersonPlayerController.js";
|
|
18
|
+
import { DecisionPoint } from "./mastery/DecisionPoint.js";
|
|
14
19
|
import { computeJumpFromApex } from "./math/computeJumpFromApex.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
20
|
+
import { computeLRCBreathRate } from "./math/computeLRCBreathRate.js";
|
|
21
|
+
import { computeMassRatios } from "./math/computeMassRatios.js";
|
|
22
|
+
import { Spring } from "./math/Spring.js";
|
|
17
23
|
import { stepTowards } from "./math/stepTowards.js";
|
|
18
|
-
import { FirstPersonActionState } from "./pose/FirstPersonPose.js";
|
|
24
|
+
import { FirstPersonActionState, FirstPersonLocomotionMode } from "./pose/FirstPersonPose.js";
|
|
25
|
+
import { FirstPersonPosture } from "./pose/FirstPersonPosture.js";
|
|
26
|
+
import { FirstPersonSensors } from "./sensors/FirstPersonSensors.js";
|
|
19
27
|
|
|
20
28
|
// ---------------------------------------------------------------------------
|
|
21
29
|
// Scratch allocations — reused per frame to avoid GC pressure
|
|
@@ -38,10 +46,22 @@ const LN2 = Math.log(2);
|
|
|
38
46
|
*/
|
|
39
47
|
class PerEntityRuntime {
|
|
40
48
|
constructor() {
|
|
49
|
+
/**
|
|
50
|
+
* Co-attached kinematic body. Set by {@link FirstPersonPlayerControllerSystem.link}
|
|
51
|
+
* after asserting it's present. The controller writes Transform.position
|
|
52
|
+
* directly (existing motion logic); physics derives the body's velocity
|
|
53
|
+
* from the per-step delta. Other physics systems (raycasts, contact
|
|
54
|
+
* events) see the player through this body.
|
|
55
|
+
* @type {RigidBody|null}
|
|
56
|
+
*/
|
|
57
|
+
this.rigidBody = null;
|
|
58
|
+
|
|
41
59
|
/** Eye pitch in radians, clamped to config.look limits. */
|
|
42
60
|
this.eyePitch = 0;
|
|
43
61
|
/** Body yaw in radians (around world up). */
|
|
44
62
|
this.bodyYaw = 0;
|
|
63
|
+
/** Yaw rate (rad/s) computed in look consumption — for evaluators. */
|
|
64
|
+
this.yawRateRadPerSec = 0;
|
|
45
65
|
|
|
46
66
|
/** Horizontal+vertical velocity. We integrate these inside the system
|
|
47
67
|
* when no external physics layer is attached. */
|
|
@@ -60,17 +80,32 @@ class PerEntityRuntime {
|
|
|
60
80
|
this.anticipationRemaining = 0;
|
|
61
81
|
/** Cached derived gravity (m/s^2) from peakHeight + timeToApex. */
|
|
62
82
|
this.gravity = 9.81;
|
|
63
|
-
/** Cached derived jump impulse (m/s upward). */
|
|
83
|
+
/** Cached derived jump impulse (m/s upward), post-mass-scaling. */
|
|
64
84
|
this.jumpInitialVy = 5.0;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
/** Spring
|
|
73
|
-
this.
|
|
85
|
+
/**
|
|
86
|
+
* Cached mass scaling factors — computed once at link. See
|
|
87
|
+
* {@link computeMassRatios}. Heavier ⇒ lower jumpV0Scale, lower
|
|
88
|
+
* groundAccelScale, higher landingDipScale + exertionRiseScale.
|
|
89
|
+
*/
|
|
90
|
+
this.massRatios = null;
|
|
91
|
+
|
|
92
|
+
/** Spring for landing dip (under-damped → rings after impact). */
|
|
93
|
+
this.landSpring = new Spring();
|
|
94
|
+
/** Spring for FOV (critically damped). */
|
|
95
|
+
this.fovSpring = new Spring(70);
|
|
96
|
+
/** Spring for eye height (crouch transition). */
|
|
97
|
+
this.eyeHeightSpring = new Spring(1.80);
|
|
98
|
+
/** Spring for lean roll (radians) — banks into lateral acceleration. */
|
|
99
|
+
this.leanSpring = new Spring();
|
|
100
|
+
/**
|
|
101
|
+
* Lean target this tick (radians). Always set; L2.f spring-steps
|
|
102
|
+
* toward this value. Whoever owned motion this tick wrote it:
|
|
103
|
+
* base writes the lat-accel + look-lean derived value at the end
|
|
104
|
+
* of {@link _runBaseLocomotion}; abilities that want to override
|
|
105
|
+
* (WallRun → tilt-into-wall, Slide/Mantle/LedgeGrab → zero) write
|
|
106
|
+
* their own value in tick. Uniform channel — no null sentinel.
|
|
107
|
+
*/
|
|
108
|
+
this.leanTargetRad = 0;
|
|
74
109
|
|
|
75
110
|
/** Previous horizontal velocity — for lateral acceleration → lean. */
|
|
76
111
|
this.prevVelocityX = 0;
|
|
@@ -93,6 +128,94 @@ class PerEntityRuntime {
|
|
|
93
128
|
this.prevBreathPhase = 0;
|
|
94
129
|
/** Which foot fires next — flipped on each footstep signal. */
|
|
95
130
|
this.nextFootSide = "R";
|
|
131
|
+
/**
|
|
132
|
+
* Which foot is currently bearing the body's weight (the foot that
|
|
133
|
+
* most recently landed). Drives the lateral-bob direction: at R
|
|
134
|
+
* midstance the COM is over the right foot, so the head shifts
|
|
135
|
+
* laterally toward screen-right; at L midstance the opposite.
|
|
136
|
+
* Coupled to the same signal the footstep emits, so anything that
|
|
137
|
+
* listens to onFootStep.side will see the bob agree.
|
|
138
|
+
* Initialized "L" so the very first footstep fires "R" and the
|
|
139
|
+
* standingFoot updates to "R" — putting the head laterally right
|
|
140
|
+
* during the first half-stride, as expected.
|
|
141
|
+
*/
|
|
142
|
+
this.standingFoot = "L";
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* [0..1] How "backward" the player is currently moving. Derived in
|
|
146
|
+
* fixedUpdate from velocity · screen-forward, normalized to sprint
|
|
147
|
+
* speed. Drives the gait wobble amplifier on the L3 camera-composition
|
|
148
|
+
* pass. Stored on runtime (rather than state) because it's a render-
|
|
149
|
+
* side input — downstream observers should look at velocity directly.
|
|
150
|
+
*/
|
|
151
|
+
this.backwardness = 0;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Smoothed bob amplitude envelope. Target = max(speedNormalized,
|
|
155
|
+
* backwardness) when grounded, 0 airborne. Spring decay prevents
|
|
156
|
+
* the whiplash where stopping motion would snap the bob to neutral.
|
|
157
|
+
*/
|
|
158
|
+
this.bobIntensitySpring = new Spring();
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Vertical impact spring — kicked downward at each footfall, decays
|
|
162
|
+
* with a slight under-damped overshoot. Produces the impact-arrest +
|
|
163
|
+
* leg-push curve. value units: meters (added directly to eyeLocal.y).
|
|
164
|
+
*/
|
|
165
|
+
this.verticalImpactSpring = new Spring();
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Sprint-posture spring — eye pitches forward as the player commits
|
|
169
|
+
* to a sprint, returns to neutral when they slow. Value is in
|
|
170
|
+
* radians; slower half-life than other springs so it feels like
|
|
171
|
+
* a posture change rather than an input twitch. See cfg.posture.
|
|
172
|
+
*/
|
|
173
|
+
this.sprintPostureSpring = new Spring();
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Head-droop spring — additional forward pitch as exertion rises.
|
|
177
|
+
* Sells fatigue subtly. Target tracks exertion-driven max droop
|
|
178
|
+
* angle; spring lag keeps the transition slow and physical.
|
|
179
|
+
*/
|
|
180
|
+
this.headDroopSpring = new Spring();
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* [0..1] sprintness — how much of the walk→sprint speed range the
|
|
184
|
+
* body is currently in. Computed in fixedUpdate, read by L3 for FOV
|
|
185
|
+
* and the sprint-posture pitch / forward-shift offset.
|
|
186
|
+
*/
|
|
187
|
+
this.sprintness = 0;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Cached sin/cos of current body yaw — written once per fixedUpdate
|
|
191
|
+
* after look intent is consumed, read by every downstream step
|
|
192
|
+
* (locomotion, backwardness, lean look-rate, pose channels). Avoids
|
|
193
|
+
* recomputing the trig 3+ times per tick.
|
|
194
|
+
*/
|
|
195
|
+
this.sinYaw = 0;
|
|
196
|
+
this.cosYaw = 1;
|
|
197
|
+
|
|
198
|
+
/** Cached horizontal speed (m/s) for this tick — written in derived-state. */
|
|
199
|
+
this.horizSpeed = 0;
|
|
200
|
+
|
|
201
|
+
/** Cached stride frequency (Hz) for this tick — written in breath block, read by stride. */
|
|
202
|
+
this.strideFreqHz = 0;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Additive accumulator for body-local eye-position offsets. The
|
|
206
|
+
* system pushes its own contributions (bob, breath, landing,
|
|
207
|
+
* sprint posture) each render frame; external systems can push
|
|
208
|
+
* recoil/shake/knockback contributions via the same interface.
|
|
209
|
+
*/
|
|
210
|
+
this.eyeOffsetStack = new EyeOffsetStack();
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Spatial-query results populated by {@link FirstPersonSensorsSystem}
|
|
214
|
+
* (when present). Abilities and the locomotion FSM read this.
|
|
215
|
+
* Lives on runtime so other systems can populate it without
|
|
216
|
+
* touching the controller component's public surface.
|
|
217
|
+
*/
|
|
218
|
+
this.sensors = new FirstPersonSensors();
|
|
96
219
|
|
|
97
220
|
/** Cached eye entity ID. -1 until link assigns it. */
|
|
98
221
|
this.eyeEntity = -1;
|
|
@@ -121,11 +244,18 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
121
244
|
constructor() {
|
|
122
245
|
super();
|
|
123
246
|
|
|
247
|
+
// Dependencies kept to (controller, transform) so we can ASSERT on
|
|
248
|
+
// RigidBody at link time and emit a clear error if missing. If
|
|
249
|
+
// RigidBody were a hard dep, entities lacking one would silently
|
|
250
|
+
// never link — the controller would appear inert with no
|
|
251
|
+
// diagnostic. The assert below catches the missing-body case
|
|
252
|
+
// explicitly.
|
|
124
253
|
this.dependencies = [FirstPersonPlayerController, Transform];
|
|
125
254
|
|
|
126
255
|
this.components_used = [
|
|
127
256
|
ResourceAccessSpecification.from(Transform, ResourceAccessKind.Write),
|
|
128
257
|
ResourceAccessSpecification.from(Camera, ResourceAccessKind.Write),
|
|
258
|
+
ResourceAccessSpecification.from(RigidBody, ResourceAccessKind.Write),
|
|
129
259
|
];
|
|
130
260
|
|
|
131
261
|
/**
|
|
@@ -147,6 +277,25 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
147
277
|
* @type {number}
|
|
148
278
|
*/
|
|
149
279
|
this.groundY = 0;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Optional callback that returns the surface Y under the player
|
|
283
|
+
* for ground resolution. Called each tick with the player's
|
|
284
|
+
* current (x, y, z); returns the world-Y of the ground below,
|
|
285
|
+
* or null if no ground is below (gap / void).
|
|
286
|
+
*
|
|
287
|
+
* Combines with `useBuiltInFlatGround`: the effective ground for
|
|
288
|
+
* the tick is `max(this.groundY when enabled, resolver(...))`.
|
|
289
|
+
* Set both off (`useBuiltInFlatGround=false`, `groundResolver=null`)
|
|
290
|
+
* to defer to external physics entirely.
|
|
291
|
+
*
|
|
292
|
+
* Designed for prototypes / gyms that need elevated platforms
|
|
293
|
+
* without a full physics layer. Production should wire a real
|
|
294
|
+
* physics system instead.
|
|
295
|
+
*
|
|
296
|
+
* @type {((x:number, y:number, z:number) => number|null) | null}
|
|
297
|
+
*/
|
|
298
|
+
this.groundResolver = null;
|
|
150
299
|
}
|
|
151
300
|
|
|
152
301
|
/**
|
|
@@ -157,14 +306,38 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
157
306
|
link(controller, bodyTransform, entity) {
|
|
158
307
|
const ecd = this.entityManager.dataset;
|
|
159
308
|
|
|
309
|
+
// The controller assumes a kinematic-position RigidBody is co-
|
|
310
|
+
// attached on this entity. The body is the spatial proxy used
|
|
311
|
+
// for sensor raycasts and physics-side observers (other entities
|
|
312
|
+
// raycasting against the player, dynamic bodies colliding with
|
|
313
|
+
// the capsule, etc.). The controller writes Transform directly,
|
|
314
|
+
// physics derives velocity from the per-step delta. If a body is
|
|
315
|
+
// missing the controller could still drive the camera, but the
|
|
316
|
+
// physics integration silently breaks — assert here so the
|
|
317
|
+
// misconfiguration is caught at link time.
|
|
318
|
+
const rigidBody = ecd.getComponent(entity, RigidBody);
|
|
319
|
+
assert.ok(rigidBody !== undefined,
|
|
320
|
+
"FirstPersonPlayerController entity must have a co-attached RigidBody "
|
|
321
|
+
+ "(kinematic capsule). See prototype_first_person_controller.js for setup.");
|
|
322
|
+
assert.equal(rigidBody.kind, BodyKind.KinematicPosition,
|
|
323
|
+
"FirstPersonPlayerController RigidBody must be BodyKind.KinematicPosition; "
|
|
324
|
+
+ "the controller owns the Transform and physics derives velocity.");
|
|
325
|
+
|
|
160
326
|
const runtime = new PerEntityRuntime();
|
|
327
|
+
runtime.rigidBody = rigidBody;
|
|
161
328
|
this.runtime.set(entity, runtime);
|
|
162
329
|
|
|
163
|
-
// Derive gravity + jump impulse from designer-friendly params
|
|
330
|
+
// Derive gravity + jump impulse from designer-friendly params, then
|
|
331
|
+
// mass-scale the initial velocity (heavier ⇒ lower jump).
|
|
332
|
+
runtime.massRatios = computeMassRatios(
|
|
333
|
+
controller.config.body.mass,
|
|
334
|
+
controller.config.body.referenceMass,
|
|
335
|
+
controller.config.body.massCouplingStrength,
|
|
336
|
+
);
|
|
164
337
|
const derived = { gravity: 0, initialVelocity: 0 };
|
|
165
338
|
computeJumpFromApex(controller.config.jump.peakHeight, controller.config.jump.timeToApex, derived);
|
|
166
339
|
runtime.gravity = derived.gravity;
|
|
167
|
-
runtime.jumpInitialVy = derived.initialVelocity;
|
|
340
|
+
runtime.jumpInitialVy = derived.initialVelocity * runtime.massRatios.jumpV0Scale;
|
|
168
341
|
|
|
169
342
|
// Seed yaw from the starting body rotation. `toEulerAnglesYXZ`
|
|
170
343
|
// returns (pitch, yaw, roll) — we only care about y.
|
|
@@ -173,8 +346,8 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
173
346
|
runtime.eyePitch = 0;
|
|
174
347
|
|
|
175
348
|
// Initialize springs to standing-eye-height baseline
|
|
176
|
-
runtime.eyeHeightSpring.
|
|
177
|
-
runtime.fovSpring.
|
|
349
|
+
runtime.eyeHeightSpring.settle(controller.config.body.height);
|
|
350
|
+
runtime.fovSpring.settle(controller.config.fov.base);
|
|
178
351
|
controller.state.eyeHeight = controller.config.body.height;
|
|
179
352
|
|
|
180
353
|
// Create eye entity if one wasn't supplied
|
|
@@ -221,6 +394,19 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
221
394
|
this.runtime.delete(entity);
|
|
222
395
|
}
|
|
223
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Look up the per-entity runtime for an entity that has this
|
|
399
|
+
* controller. Used by cross-system code (sensors system, future
|
|
400
|
+
* ability-driven systems) to reach internal state without leaking
|
|
401
|
+
* it onto the controller component itself.
|
|
402
|
+
*
|
|
403
|
+
* @param {number} entity
|
|
404
|
+
* @returns {PerEntityRuntime|undefined} undefined if entity is not linked
|
|
405
|
+
*/
|
|
406
|
+
getRuntime(entity) {
|
|
407
|
+
return this.runtime.get(entity);
|
|
408
|
+
}
|
|
409
|
+
|
|
224
410
|
/**
|
|
225
411
|
* Deterministic simulation step — L1 + L2 + L4.
|
|
226
412
|
* @param {number} dt
|
|
@@ -264,6 +450,12 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
264
450
|
const bodyTransform = ecd.getComponent(entity, Transform);
|
|
265
451
|
if (bodyTransform === undefined) return;
|
|
266
452
|
|
|
453
|
+
// Decay the mastery score's EMA. Doing this once per tick keeps the
|
|
454
|
+
// score's time-window characteristic stable regardless of how many
|
|
455
|
+
// evaluators fire (they each *record* a sample, the decay
|
|
456
|
+
// independently ages all samples).
|
|
457
|
+
controller.mastery.tick(dt);
|
|
458
|
+
|
|
267
459
|
// -- L1.a: Consume look delta -----------------------------------
|
|
268
460
|
// intent.look is zeroed after consume so accumulated input doesn't
|
|
269
461
|
// re-apply on the next fixed step.
|
|
@@ -283,6 +475,11 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
283
475
|
const pitchDelta = intent.look.y * pitchSign;
|
|
284
476
|
intent.look.set(0, 0);
|
|
285
477
|
|
|
478
|
+
// Cache yaw rate for mastery evaluators (look-lean, foot-asymmetry-
|
|
479
|
+
// turn, etc.). Rad/s, signed (negative = turning right in our
|
|
480
|
+
// convention — matches yawDelta).
|
|
481
|
+
runtime.yawRateRadPerSec = yawDelta / Math.max(dt, 1e-4);
|
|
482
|
+
|
|
286
483
|
runtime.bodyYaw += yawDelta;
|
|
287
484
|
// keep yaw bounded (purely cosmetic — sin/cos handle wraparound fine)
|
|
288
485
|
if (runtime.bodyYaw > Math.PI) runtime.bodyYaw -= TWO_PI;
|
|
@@ -297,68 +494,311 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
297
494
|
// Write body yaw back to transform (pure yaw, no pitch on body)
|
|
298
495
|
bodyTransform.rotation.fromAxisAngle(Vector3.up, runtime.bodyYaw);
|
|
299
496
|
|
|
300
|
-
// --
|
|
497
|
+
// -- Shared flags. Computed BEFORE the ability tick so abilities
|
|
498
|
+
// can read them. `isCrouchActive` is deliberately computed
|
|
499
|
+
// AFTER the ability tick because `_resolveCrouchHeld` mutates
|
|
500
|
+
// `runtime.prevCrouchHeld` — abilities like Slide need to see
|
|
501
|
+
// the previous-tick value to detect a rising edge on the
|
|
502
|
+
// crouch press.
|
|
301
503
|
const isSprintIntent = intent.sprint && intent.move.y > 0.5 && state.grounded;
|
|
504
|
+
const isBackwardIntent = intent.move.y < 0;
|
|
505
|
+
runtime.sinYaw = Math.sin(runtime.bodyYaw);
|
|
506
|
+
runtime.cosYaw = Math.cos(runtime.bodyYaw);
|
|
507
|
+
// L2 observers read sinYaw/cosYaw as locals — destructure once.
|
|
508
|
+
const { sinYaw, cosYaw } = runtime;
|
|
509
|
+
|
|
510
|
+
// -- Ability layer: at most one active ability owns motion. The
|
|
511
|
+
// set returns true when no ability owned the tick, in which
|
|
512
|
+
// case base L1.b-h runs below; false means an ability fully
|
|
513
|
+
// handled this tick (it called the system's helpers for any
|
|
514
|
+
// standard work it wanted to keep, e.g. gravity).
|
|
515
|
+
const runBaseLocomotion = controller.abilities.tick(
|
|
516
|
+
controller, runtime, bodyTransform, runtime.sensors, dt, this,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
// Now resolve crouch (updates prevCrouchHeld) — used by base and L2.
|
|
302
520
|
const isCrouchActive = this._resolveCrouchHeld(controller, runtime);
|
|
303
521
|
|
|
304
|
-
|
|
522
|
+
if (runBaseLocomotion) {
|
|
523
|
+
this._runBaseLocomotion(
|
|
524
|
+
controller, runtime, bodyTransform, dt,
|
|
525
|
+
isCrouchActive, isSprintIntent, isBackwardIntent,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// (everything below this line runs every tick — L2 observers don't
|
|
530
|
+
// care who owned motion)
|
|
531
|
+
|
|
532
|
+
// -- L2.a: speed / moveMode ------------------------------------
|
|
533
|
+
// -- L2.a: speed / moveMode ------------------------------------
|
|
534
|
+
const horizSpeed = Math.hypot(runtime.velocityX, runtime.velocityZ);
|
|
535
|
+
runtime.horizSpeed = horizSpeed;
|
|
536
|
+
state.speed = horizSpeed;
|
|
537
|
+
state.speedNormalized = clamp(horizSpeed / Math.max(cfg.motion.sprintSpeed, 1e-3), 0, 1);
|
|
538
|
+
|
|
539
|
+
// Backwardness: 0 = moving forward (or sideways), 1 = moving directly
|
|
540
|
+
// backward at the back-pedal speed ceiling. Derived from the actual
|
|
541
|
+
// velocity (not the intent) so external knockback or stuck states
|
|
542
|
+
// also register as "moving backward" and the gait wobble reflects it.
|
|
543
|
+
//
|
|
544
|
+
// Reference speed is the *achievable* backward max — walkSpeed ×
|
|
545
|
+
// backwardSpeedFactor — NOT the sprint speed. Backward can never
|
|
546
|
+
// reach sprint, so normalizing against sprint would cap backwardness
|
|
547
|
+
// at ~0.3 and the wobble multipliers below would barely apply.
|
|
548
|
+
const screenFwdVel = runtime.velocityX * sinYaw + runtime.velocityZ * cosYaw;
|
|
549
|
+
const maxBackwardSpeed = Math.max(cfg.motion.walkSpeed * cfg.motion.backwardSpeedFactor, 1e-3);
|
|
550
|
+
runtime.backwardness = clamp(-screenFwdVel / maxBackwardSpeed, 0, 1);
|
|
551
|
+
|
|
552
|
+
// Locomotion mode is the *intent-driven* horizontal mode. Airborne
|
|
553
|
+
// state is tracked separately on pose.actionState — they're
|
|
554
|
+
// orthogonal facets (you can be Sprint+Airborne after a jump).
|
|
555
|
+
const prevLocomotionMode = state.locomotionMode;
|
|
305
556
|
if (isCrouchActive) {
|
|
306
|
-
|
|
307
|
-
} else if (isSprintIntent) {
|
|
308
|
-
|
|
557
|
+
state.locomotionMode = FirstPersonLocomotionMode.Crouch;
|
|
558
|
+
} else if (isSprintIntent && horizSpeed > 0.1) {
|
|
559
|
+
state.locomotionMode = FirstPersonLocomotionMode.Sprint;
|
|
560
|
+
} else if (horizSpeed > 0.1) {
|
|
561
|
+
state.locomotionMode = FirstPersonLocomotionMode.Walk;
|
|
309
562
|
} else {
|
|
310
|
-
|
|
563
|
+
state.locomotionMode = FirstPersonLocomotionMode.Idle;
|
|
311
564
|
}
|
|
312
565
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
|
|
566
|
+
if (state.locomotionMode === FirstPersonLocomotionMode.Sprint
|
|
567
|
+
&& prevLocomotionMode !== FirstPersonLocomotionMode.Sprint) {
|
|
568
|
+
sig.onSprintStart.send0();
|
|
569
|
+
} else if (prevLocomotionMode === FirstPersonLocomotionMode.Sprint
|
|
570
|
+
&& state.locomotionMode !== FirstPersonLocomotionMode.Sprint) {
|
|
571
|
+
sig.onSprintStop.send0();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// -- L2.b: Exertion --------------------------------------------
|
|
575
|
+
// Heavier bodies tire faster — sprint rise scales with massRatios.exertionRiseScale.
|
|
576
|
+
const exertionRise = isSprintIntent
|
|
577
|
+
? cfg.exertion.sprintRiseRate * runtime.massRatios.exertionRiseScale
|
|
578
|
+
: 0;
|
|
579
|
+
const exertionFall = exertionRise > 0 ? 0 : cfg.exertion.idleDecayRate;
|
|
580
|
+
state.exertion = clamp(state.exertion + (exertionRise - exertionFall) * dt, 0, 1);
|
|
581
|
+
|
|
582
|
+
// -- L2.c: Breath ----------------------------------------------
|
|
583
|
+
// breathRate and breathAmplitude lag exertion through separate
|
|
584
|
+
// exponential decays. Rate hangs around longer than amplitude.
|
|
585
|
+
const metabolicRate = lerp(cfg.breath.rateRestHz, cfg.breath.rateMaxHz, state.exertion);
|
|
586
|
+
const targetAmp = lerp(cfg.breath.amplitudeRestM, cfg.breath.amplitudeMaxM, state.exertion);
|
|
587
|
+
|
|
588
|
+
// Locomotor-respiratory coupling — see math/computeLRCBreathRate.
|
|
589
|
+
// The pure function is unit-tested; this site just provides inputs.
|
|
324
590
|
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
591
|
+
// Gait is gated on a "feet strike the ground" posture (Stand /
|
|
592
|
+
// Crouch). Prone (slide) and Hang (ledge-grab) have no stride —
|
|
593
|
+
// the body's feet are not making contact in a walking pattern,
|
|
594
|
+
// so stride frequency drops to zero and downstream gait
|
|
595
|
+
// signals (footsteps, bob intensity) go quiet.
|
|
596
|
+
const feetStriking = state.posture === FirstPersonPosture.Stand
|
|
597
|
+
|| state.posture === FirstPersonPosture.Crouch;
|
|
598
|
+
const strideFreqHz = feetStriking && state.grounded && horizSpeed > cfg.bob.minStepSpeed
|
|
599
|
+
? cfg.bob.stepFreqAtWalk * Math.pow(
|
|
600
|
+
Math.max(horizSpeed, 1e-3) / Math.max(cfg.motion.walkSpeed, 1e-3),
|
|
601
|
+
cfg.bob.stepFreqExp,
|
|
602
|
+
)
|
|
603
|
+
: 0;
|
|
604
|
+
const targetRate = computeLRCBreathRate(
|
|
605
|
+
metabolicRate,
|
|
606
|
+
strideFreqHz,
|
|
607
|
+
state.exertion,
|
|
608
|
+
cfg.breath.locomotorCouplingMax,
|
|
609
|
+
cfg.breath.couplingMinStrideFreqHz,
|
|
610
|
+
);
|
|
611
|
+
state.breathRateHz = exponentialApproach(state.breathRateHz, targetRate, cfg.exertion.rateDecayHalfLife, dt);
|
|
612
|
+
state.breathAmplitudeM = exponentialApproach(state.breathAmplitudeM, targetAmp, cfg.exertion.ampDecayHalfLife, dt);
|
|
329
613
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const mvMag = Math.hypot(mvX, mvY);
|
|
334
|
-
const nmvX = mvMag > 1 ? mvX / mvMag : mvX;
|
|
335
|
-
const nmvY = mvMag > 1 ? mvY / mvMag : mvY;
|
|
614
|
+
runtime.prevBreathPhase = state.breathPhase;
|
|
615
|
+
state.breathPhase += state.breathRateHz * dt;
|
|
616
|
+
state.breathPhase -= Math.floor(state.breathPhase); // wrap [0,1)
|
|
336
617
|
|
|
337
|
-
|
|
338
|
-
|
|
618
|
+
// Breath edge detection — inhale at 0.25, exhale at 0.75
|
|
619
|
+
if (phaseCrossed(runtime.prevBreathPhase, state.breathPhase, 0.25)) {
|
|
620
|
+
sig.onBreathIn.send1({ amplitude: state.breathAmplitudeM, rateHz: state.breathRateHz });
|
|
621
|
+
}
|
|
622
|
+
if (phaseCrossed(runtime.prevBreathPhase, state.breathPhase, 0.75)) {
|
|
623
|
+
sig.onBreathOut.send1({ amplitude: state.breathAmplitudeM, rateHz: state.breathRateHz });
|
|
624
|
+
}
|
|
339
625
|
|
|
340
|
-
|
|
341
|
-
|
|
626
|
+
// -- L2.d: Stride ----------------------------------------------
|
|
627
|
+
// strideFreqHz computed above in the breath block; reused here.
|
|
628
|
+
runtime.prevStridePhase = state.stridePhase;
|
|
629
|
+
if (strideFreqHz > 0) {
|
|
630
|
+
// 1 full stride cycle = 2 footfalls; phase advances at freq/2 of cycle
|
|
631
|
+
state.stridePhase += (strideFreqHz * 0.5) * dt;
|
|
632
|
+
state.stridePhase -= Math.floor(state.stridePhase);
|
|
633
|
+
}
|
|
634
|
+
// Footstep on phase wraparound past 0 (R) or past 0.5 (L). Same
|
|
635
|
+
// posture gate as stride advance — feet must be striking.
|
|
636
|
+
if (feetStriking && state.grounded && horizSpeed > cfg.bob.minStepSpeed) {
|
|
637
|
+
const fireFootstep = () => {
|
|
638
|
+
state.stepCount++;
|
|
639
|
+
const side = runtime.nextFootSide;
|
|
640
|
+
runtime.nextFootSide = side === "R" ? "L" : "R";
|
|
641
|
+
// The foot that just fired is now the one bearing weight
|
|
642
|
+
// through the upcoming half-stride. Drives lateral-bob sign.
|
|
643
|
+
runtime.standingFoot = side;
|
|
644
|
+
sig.onFootStep.send1({ side, speed: horizSpeed, surfaceTag: state.surfaceTag });
|
|
645
|
+
// Kick the vertical impact spring DOWNWARD. The kick magnitude
|
|
646
|
+
// is the per-step desired peak dip × impactKickMultiplier; the
|
|
647
|
+
// multiplier is empirical (depends on impact spring params) so
|
|
648
|
+
// that "verticalAmpAtWalk" still corresponds approximately to
|
|
649
|
+
// the visible peak dip depth. Scaled by bobIntensity so a
|
|
650
|
+
// mid-deceleration footstep doesn't deliver a full-strength
|
|
651
|
+
// impulse.
|
|
652
|
+
const massBoost = (cfg.body.mass - 80) * cfg.bob.ampMassScale;
|
|
653
|
+
const ampVMult = 1 + (cfg.bob.backwardVerticalAmpFactor - 1) * runtime.backwardness;
|
|
654
|
+
const peakDip = (cfg.bob.verticalAmpAtWalk + massBoost) * runtime.bobIntensitySpring.value * ampVMult;
|
|
655
|
+
runtime.verticalImpactSpring.kick(-peakDip * cfg.bob.impactKickMultiplier);
|
|
656
|
+
};
|
|
657
|
+
if (phaseCrossed(runtime.prevStridePhase, state.stridePhase, 0)) {
|
|
658
|
+
fireFootstep();
|
|
659
|
+
}
|
|
660
|
+
if (phaseCrossed(runtime.prevStridePhase, state.stridePhase, 0.5)) {
|
|
661
|
+
fireFootstep();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
342
664
|
|
|
343
|
-
// --
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
665
|
+
// -- L2.d.bob-intensity & impact -------------------------------
|
|
666
|
+
// Smoothed bob amplitude envelope: when the player starts/stops
|
|
667
|
+
// moving the visible bob fades in/out rather than cutting on/off.
|
|
668
|
+
// Target = the "natural" amp scale (max of speed and backwardness)
|
|
669
|
+
// while grounded, zero while airborne so the bob disappears mid-jump.
|
|
670
|
+
const naturalBobIntensity = Math.max(state.speedNormalized, runtime.backwardness);
|
|
671
|
+
// Bob fades to zero whenever feet aren't striking (airborne, or
|
|
672
|
+
// Prone/Hang posture). The verticalImpactSpring (separate
|
|
673
|
+
// channel) still carries any entry/landing kicks through to the
|
|
674
|
+
// camera, but no recurring step bob.
|
|
675
|
+
const targetBobIntensity = (state.grounded && feetStriking) ? naturalBobIntensity : 0;
|
|
676
|
+
runtime.bobIntensitySpring.stepTo(targetBobIntensity, cfg.bob.intensityHalfLife, 1.0, dt);
|
|
677
|
+
|
|
678
|
+
// Vertical impact spring — damped decay toward 0, with the under-
|
|
679
|
+
// damped overshoot that produces the recovery + leg-push curve.
|
|
680
|
+
runtime.verticalImpactSpring.stepTo(0, cfg.bob.impactSpringHalfLife, cfg.bob.impactSpringZeta, dt);
|
|
681
|
+
|
|
682
|
+
// Sprint posture — head pitches forward as commitment to sprint
|
|
683
|
+
// builds. Driven by "sprintness" — how much of the gap between
|
|
684
|
+
// walk and sprint speed the player is *currently* in (0..1). The
|
|
685
|
+
// pitch target is multiplied by sprintness, then critically damped.
|
|
686
|
+
// Only applies while grounded — pitching into airborne motion looks weird.
|
|
687
|
+
const sprintness = clamp(
|
|
688
|
+
(state.speed - cfg.motion.walkSpeed)
|
|
689
|
+
/ Math.max(cfg.motion.sprintSpeed - cfg.motion.walkSpeed, 1e-3),
|
|
690
|
+
0, 1,
|
|
691
|
+
);
|
|
692
|
+
const targetSprintPitch = state.grounded
|
|
693
|
+
? cfg.posture.sprintForwardPitchDeg * DEG_TO_RAD * sprintness
|
|
694
|
+
: 0;
|
|
695
|
+
runtime.sprintPostureSpring.stepTo(
|
|
696
|
+
targetSprintPitch,
|
|
697
|
+
cfg.posture.sprintForwardPitchHalfLife,
|
|
698
|
+
1.0, dt,
|
|
699
|
+
);
|
|
700
|
+
runtime.sprintness = sprintness;
|
|
701
|
+
|
|
702
|
+
// Head droop — exertion drives a subtle additional forward pitch.
|
|
703
|
+
// Combines with sprintPostureSpring (sprint = head down to commit)
|
|
704
|
+
// so a fatigued sprinter has BOTH effects layered.
|
|
705
|
+
const targetDroopRad = cfg.exertion.headDroopAtMaxDeg * DEG_TO_RAD * state.exertion;
|
|
706
|
+
runtime.headDroopSpring.stepTo(targetDroopRad, cfg.exertion.headDroopHalfLife, 1.0, dt);
|
|
707
|
+
|
|
708
|
+
// -- L2.e: Posture → eye height --------------------------------
|
|
709
|
+
// Posture is set by whichever layer owned motion this tick: base
|
|
710
|
+
// writes Stand / Crouch from isCrouchActive (see end of
|
|
711
|
+
// _runBaseLocomotion); active abilities write Prone (Slide) or
|
|
712
|
+
// Hang (LedgeGrab) in their tick. Mapping is one switch — adding
|
|
713
|
+
// a new posture is one enum value + one case.
|
|
714
|
+
let targetEyeH;
|
|
715
|
+
switch (state.posture) {
|
|
716
|
+
case FirstPersonPosture.Prone: targetEyeH = cfg.body.proneHeight; break;
|
|
717
|
+
case FirstPersonPosture.Crouch: targetEyeH = cfg.body.crouchHeight; break;
|
|
718
|
+
case FirstPersonPosture.Hang: targetEyeH = cfg.body.height; break;
|
|
719
|
+
case FirstPersonPosture.Stand:
|
|
720
|
+
default: targetEyeH = cfg.body.height; break;
|
|
355
721
|
}
|
|
722
|
+
const crouchHalfLife = cfg.crouch.transitionTime / 4; // halfLife is ~quarter of full transition
|
|
723
|
+
runtime.eyeHeightSpring.stepTo(targetEyeH, crouchHalfLife, 1.0, dt);
|
|
724
|
+
state.eyeHeight = runtime.eyeHeightSpring.value;
|
|
356
725
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
726
|
+
if (isCrouchActive !== state.crouchActive) {
|
|
727
|
+
state.crouchActive = isCrouchActive;
|
|
728
|
+
if (isCrouchActive) {
|
|
729
|
+
sig.onCrouchEnter.send0();
|
|
730
|
+
// Impulse: dropping into a crouch grips the knees. Small
|
|
731
|
+
// bump — we don't want crouch-spamming to instantly tire.
|
|
732
|
+
state.exertion = clamp(
|
|
733
|
+
state.exertion + cfg.exertion.crouchEnterRise * runtime.massRatios.exertionRiseScale,
|
|
734
|
+
0, 1,
|
|
735
|
+
);
|
|
736
|
+
} else {
|
|
737
|
+
sig.onCrouchExit.send0();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// -- L2.f: Lean spring → camera roll ---------------------------
|
|
742
|
+
// The TARGET for this tick was written by whichever layer owned
|
|
743
|
+
// motion: base writes the lat-accel + look-lean derived value at
|
|
744
|
+
// the end of _runBaseLocomotion; abilities override (WallRun
|
|
745
|
+
// tilts toward the wall; Slide / LedgeGrab / Mantle force zero).
|
|
746
|
+
// L2.f is now a flat spring-step + commit — no branching, no
|
|
747
|
+
// null sentinel.
|
|
748
|
+
runtime.prevVelocityX = runtime.velocityX;
|
|
749
|
+
runtime.prevVelocityZ = runtime.velocityZ;
|
|
750
|
+
runtime.leanSpring.stepTo(runtime.leanTargetRad, cfg.lean.spring.halfLife, cfg.lean.spring.zeta, dt);
|
|
751
|
+
state.leanRollRad = runtime.leanSpring.value;
|
|
752
|
+
|
|
753
|
+
// -- L2.g: Land spring decay (drives the landing recovery dip) -
|
|
754
|
+
// Target is 0; under-damped (cfg zeta < 1) so it rings.
|
|
755
|
+
runtime.landSpring.stepTo(0, cfg.landing.recovery.spring.halfLife, cfg.landing.recovery.spring.zeta, dt);
|
|
756
|
+
|
|
757
|
+
// -- L2.h: Publish pose channels --------------------------------
|
|
758
|
+
this._publishPose(controller, runtime, bodyTransform);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* @private
|
|
763
|
+
* @param {FirstPersonPlayerController} controller
|
|
764
|
+
* @param {PerEntityRuntime} runtime
|
|
765
|
+
* @returns {boolean}
|
|
766
|
+
*/
|
|
767
|
+
_resolveCrouchHeld(controller, runtime) {
|
|
768
|
+
const cfg = controller.config;
|
|
769
|
+
const intent = controller.intent;
|
|
770
|
+
|
|
771
|
+
if (cfg.crouch.mode === "toggle") {
|
|
772
|
+
// Edge: rising press flips the latch
|
|
773
|
+
if (intent.crouch && !runtime.prevCrouchHeld) {
|
|
774
|
+
runtime.crouchLatched = !runtime.crouchLatched;
|
|
775
|
+
}
|
|
776
|
+
runtime.prevCrouchHeld = intent.crouch;
|
|
777
|
+
return runtime.crouchLatched;
|
|
778
|
+
}
|
|
779
|
+
// "hold" mode
|
|
780
|
+
runtime.prevCrouchHeld = intent.crouch;
|
|
781
|
+
return intent.crouch;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Jump finite-state-machine: button-edge detection, buffer + coyote
|
|
786
|
+
* grace, anticipation timer, impulse on completion. Variable-height
|
|
787
|
+
* cut is captured here as a `state.isVariableJumpCut` flag that the
|
|
788
|
+
* gravity step in `_integrateVerticalAndResolveGround` consumes.
|
|
789
|
+
*
|
|
790
|
+
* @private
|
|
791
|
+
* @param {FirstPersonPlayerController} controller
|
|
792
|
+
* @param {PerEntityRuntime} runtime
|
|
793
|
+
* @param {Transform} bodyTransform
|
|
794
|
+
* @param {number} dt
|
|
795
|
+
*/
|
|
796
|
+
_advanceJumpFsm(controller, runtime, bodyTransform, dt) {
|
|
797
|
+
const cfg = controller.config;
|
|
798
|
+
const intent = controller.intent;
|
|
799
|
+
const state = controller.state;
|
|
800
|
+
const sig = controller.signals;
|
|
360
801
|
|
|
361
|
-
// -- L1.e: Jump (edge-triggered, buffered, coyote-graced) -------
|
|
362
802
|
const jumpPressedEdge = intent.jump && !runtime.prevJumpHeld;
|
|
363
803
|
const jumpReleasedEdge = !intent.jump && runtime.prevJumpHeld;
|
|
364
804
|
runtime.prevJumpHeld = intent.jump;
|
|
@@ -381,37 +821,68 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
381
821
|
state.jumpBufferRemaining = 0; // claimed
|
|
382
822
|
}
|
|
383
823
|
|
|
384
|
-
// Variable-height cut: only valid during ascent
|
|
824
|
+
// Variable-height cut: only valid during ascent, post-launch.
|
|
385
825
|
if (jumpReleasedEdge && runtime.midJump && runtime.velocityY > 0) {
|
|
386
826
|
state.isVariableJumpCut = true;
|
|
387
827
|
}
|
|
388
828
|
|
|
389
|
-
// Anticipation timer; impulse on completion
|
|
829
|
+
// Anticipation timer; impulse on completion.
|
|
390
830
|
if (state.inJumpAnticipation) {
|
|
391
|
-
// If the entity goes airborne mid-anticipation (ground rug-pulled),
|
|
392
|
-
// abandon the queued impulse — fire onLeaveGround{fall} instead.
|
|
393
831
|
if (!state.grounded) {
|
|
832
|
+
// Ground rug-pulled mid-anticipation — abandon the queued
|
|
833
|
+
// impulse; the airborne-transition path will fire onLeaveGround.
|
|
394
834
|
state.inJumpAnticipation = false;
|
|
395
835
|
runtime.anticipationRemaining = 0;
|
|
396
836
|
} else {
|
|
397
837
|
runtime.anticipationRemaining -= dt;
|
|
398
838
|
if (runtime.anticipationRemaining <= 0) {
|
|
399
|
-
|
|
839
|
+
// Mastery: gather a multiplier from all evaluators
|
|
840
|
+
// registered for JumpImpulse. Default (no evaluators)
|
|
841
|
+
// returns 1.0 → unchanged behaviour.
|
|
842
|
+
const masteryMul = controller.mastery.evaluate(
|
|
843
|
+
DecisionPoint.JumpImpulse, controller, runtime,
|
|
844
|
+
);
|
|
845
|
+
runtime.velocityY = runtime.jumpInitialVy * masteryMul;
|
|
400
846
|
runtime.midJump = true;
|
|
401
847
|
runtime.apexFired = false;
|
|
402
848
|
runtime.peakAltitude = bodyTransform.position.y;
|
|
403
849
|
state.inJumpAnticipation = false;
|
|
404
850
|
state.isVariableJumpCut = false;
|
|
405
851
|
state.isAscending = true;
|
|
406
|
-
|
|
852
|
+
state.exertion = clamp(
|
|
853
|
+
state.exertion + cfg.exertion.jumpRise * runtime.massRatios.exertionRiseScale,
|
|
854
|
+
0, 1,
|
|
855
|
+
);
|
|
407
856
|
|
|
408
857
|
sig.onJumpStart.send1({ peakHeight: cfg.jump.peakHeight });
|
|
409
858
|
sig.onLeaveGround.send1({ reason: "jump" });
|
|
410
859
|
}
|
|
411
860
|
}
|
|
412
861
|
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Gravity (with fall and cut multipliers), vertical integration,
|
|
866
|
+
* built-in flat-floor resolution (land event + impulse), and jump-apex
|
|
867
|
+
* detection. The full vertical phase of one fixed step.
|
|
868
|
+
*
|
|
869
|
+
* The built-in flat-floor branch only runs when `useBuiltInFlatGround`
|
|
870
|
+
* is true (the prototype's standalone mode); with an external physics
|
|
871
|
+
* layer attached the system relies on the layer to set `state.grounded`
|
|
872
|
+
* and only maintains airborne/grounded timers here.
|
|
873
|
+
*
|
|
874
|
+
* @private
|
|
875
|
+
* @param {FirstPersonPlayerController} controller
|
|
876
|
+
* @param {PerEntityRuntime} runtime
|
|
877
|
+
* @param {Transform} bodyTransform
|
|
878
|
+
* @param {number} dt
|
|
879
|
+
*/
|
|
880
|
+
_integrateVerticalAndResolveGround(controller, runtime, bodyTransform, dt) {
|
|
881
|
+
const cfg = controller.config;
|
|
882
|
+
const state = controller.state;
|
|
883
|
+
const sig = controller.signals;
|
|
413
884
|
|
|
414
|
-
//
|
|
885
|
+
// Gravity with fall/cut multipliers.
|
|
415
886
|
let gMag = runtime.gravity;
|
|
416
887
|
if (runtime.velocityY <= 0) {
|
|
417
888
|
gMag *= cfg.jump.fallGravityMult;
|
|
@@ -419,37 +890,63 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
419
890
|
} else if (state.isVariableJumpCut) {
|
|
420
891
|
gMag *= cfg.jump.cutGravityMult;
|
|
421
892
|
}
|
|
422
|
-
|
|
423
893
|
runtime.velocityY -= gMag * dt;
|
|
424
894
|
|
|
425
|
-
//
|
|
895
|
+
// Integrate position.
|
|
426
896
|
bodyTransform.position._add(
|
|
427
897
|
runtime.velocityX * dt,
|
|
428
898
|
runtime.velocityY * dt,
|
|
429
899
|
runtime.velocityZ * dt,
|
|
430
900
|
);
|
|
431
901
|
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
902
|
+
// Ground resolution.
|
|
903
|
+
// Effective ground = max(built-in flat ground, optional resolver).
|
|
904
|
+
// - useBuiltInFlatGround=true gives a baseline floor at groundY.
|
|
905
|
+
// - groundResolver lets the host scene raise the floor under
|
|
906
|
+
// platforms / terrain. Returns the surface Y under the player,
|
|
907
|
+
// or null when no ground is below (gap / void).
|
|
908
|
+
// If both are off, the original "external physics" branch
|
|
909
|
+
// (else-block below) just tracks timers and leaves grounded
|
|
910
|
+
// alone — the host's physics layer is expected to set it.
|
|
911
|
+
if (this.useBuiltInFlatGround || this.groundResolver !== null) {
|
|
912
|
+
let testY = this.useBuiltInFlatGround ? this.groundY : Number.NEGATIVE_INFINITY;
|
|
913
|
+
if (this.groundResolver !== null) {
|
|
914
|
+
const resolved = this.groundResolver(
|
|
915
|
+
bodyTransform.position.x,
|
|
916
|
+
bodyTransform.position.y,
|
|
917
|
+
bodyTransform.position.z,
|
|
918
|
+
);
|
|
919
|
+
if (resolved !== null && resolved > testY) testY = resolved;
|
|
920
|
+
}
|
|
921
|
+
const haveGround = testY !== Number.NEGATIVE_INFINITY;
|
|
922
|
+
if (haveGround && bodyTransform.position.y <= testY) {
|
|
923
|
+
bodyTransform.position.setY(testY);
|
|
436
924
|
|
|
437
925
|
if (!state.grounded) {
|
|
438
|
-
// Land
|
|
439
|
-
|
|
926
|
+
// Land — apply all state changes first, then fire the
|
|
927
|
+
// signal LAST so handlers see the fully-reacted state.
|
|
928
|
+
const impactVy = -runtime.velocityY;
|
|
440
929
|
const kind = impactVy >= cfg.landing.hardThreshold ? "hard"
|
|
441
930
|
: (impactVy >= cfg.landing.softThreshold ? "soft" : "soft");
|
|
442
|
-
sig.onLand.send1({ verticalSpeed: impactVy, kind });
|
|
443
931
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
runtime.landSpring.
|
|
932
|
+
const massScaledDip = impactVy * cfg.landing.recovery.dipPerVy
|
|
933
|
+
* runtime.massRatios.landingDipScale;
|
|
934
|
+
const dip = clamp(massScaledDip, 0, cfg.landing.recovery.dipMax);
|
|
935
|
+
runtime.landSpring.settle(-dip);
|
|
936
|
+
|
|
937
|
+
const landImpulse = clamp(
|
|
938
|
+
impactVy * cfg.exertion.landImpulsePerVy * runtime.massRatios.exertionRiseScale,
|
|
939
|
+
0,
|
|
940
|
+
cfg.exertion.landImpulseMax,
|
|
941
|
+
);
|
|
942
|
+
state.exertion = clamp(state.exertion + landImpulse, 0, 1);
|
|
448
943
|
|
|
449
944
|
runtime.midJump = false;
|
|
450
945
|
state.isAscending = false;
|
|
451
946
|
state.isVariableJumpCut = false;
|
|
452
947
|
state.fallDistance = 0;
|
|
948
|
+
|
|
949
|
+
sig.onLand.send1({ verticalSpeed: impactVy, kind });
|
|
453
950
|
}
|
|
454
951
|
|
|
455
952
|
state.grounded = true;
|
|
@@ -470,8 +967,7 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
470
967
|
state.fallDistance += Math.max(0, -runtime.velocityY * dt);
|
|
471
968
|
}
|
|
472
969
|
} else {
|
|
473
|
-
// External physics
|
|
474
|
-
// state.verticalSpeed; we still track airborne timer.
|
|
970
|
+
// External physics maintains state.grounded; just track timers.
|
|
475
971
|
if (state.grounded) {
|
|
476
972
|
state.timeSinceGrounded = 0;
|
|
477
973
|
state.airborneTime = 0;
|
|
@@ -481,7 +977,7 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
481
977
|
}
|
|
482
978
|
}
|
|
483
979
|
|
|
484
|
-
//
|
|
980
|
+
// Jump apex detection.
|
|
485
981
|
if (runtime.midJump && !runtime.apexFired) {
|
|
486
982
|
if (bodyTransform.position.y > runtime.peakAltitude) {
|
|
487
983
|
runtime.peakAltitude = bodyTransform.position.y;
|
|
@@ -490,163 +986,230 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
490
986
|
runtime.apexFired = true;
|
|
491
987
|
}
|
|
492
988
|
}
|
|
989
|
+
}
|
|
493
990
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
991
|
+
/**
|
|
992
|
+
* Run the base (no-ability) L1 locomotion phases: speed selection,
|
|
993
|
+
* desired-velocity computation, accel/decel, jump FSM, gravity, body
|
|
994
|
+
* integration, ground resolution. Only invoked when no ability owns
|
|
995
|
+
* the tick (see {@link AbilitySet.tick}).
|
|
996
|
+
*
|
|
997
|
+
* @private
|
|
998
|
+
* @param {FirstPersonPlayerController} controller
|
|
999
|
+
* @param {PerEntityRuntime} runtime
|
|
1000
|
+
* @param {Transform} bodyTransform
|
|
1001
|
+
* @param {number} dt
|
|
1002
|
+
* @param {boolean} isCrouchActive
|
|
1003
|
+
* @param {boolean} isSprintIntent
|
|
1004
|
+
* @param {boolean} isBackwardIntent
|
|
1005
|
+
*/
|
|
1006
|
+
_runBaseLocomotion(controller, runtime, bodyTransform, dt,
|
|
1007
|
+
isCrouchActive, isSprintIntent, isBackwardIntent) {
|
|
1008
|
+
const cfg = controller.config;
|
|
1009
|
+
const intent = controller.intent;
|
|
1010
|
+
const state = controller.state;
|
|
498
1011
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
state.moveMode = "Sprint";
|
|
506
|
-
} else if (horizSpeed > 0.1) {
|
|
507
|
-
state.moveMode = "Walk";
|
|
1012
|
+
// -- L1.b: Speed selection ------------------------------------
|
|
1013
|
+
let targetSpeed;
|
|
1014
|
+
if (isCrouchActive) {
|
|
1015
|
+
targetSpeed = cfg.motion.crouchSpeed;
|
|
1016
|
+
} else if (isSprintIntent) {
|
|
1017
|
+
targetSpeed = cfg.motion.sprintSpeed;
|
|
508
1018
|
} else {
|
|
509
|
-
|
|
1019
|
+
targetSpeed = cfg.motion.walkSpeed;
|
|
510
1020
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
sig.onSprintStart.send0();
|
|
514
|
-
} else if (prevMoveMode === "Sprint" && state.moveMode !== "Sprint") {
|
|
515
|
-
sig.onSprintStop.send0();
|
|
1021
|
+
if (isBackwardIntent) {
|
|
1022
|
+
targetSpeed *= cfg.motion.backwardSpeedFactor;
|
|
516
1023
|
}
|
|
517
1024
|
|
|
518
|
-
// --
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
runtime.prevBreathPhase = state.breathPhase;
|
|
532
|
-
state.breathPhase += state.breathRateHz * dt;
|
|
533
|
-
state.breathPhase -= Math.floor(state.breathPhase); // wrap [0,1)
|
|
1025
|
+
// -- L1.c: Move intent → desired horizontal velocity ----------
|
|
1026
|
+
// screen_forward(θ) = ( sin θ, 0, cos θ )
|
|
1027
|
+
// screen_right (θ) = (-cos θ, 0, sin θ )
|
|
1028
|
+
const { sinYaw, cosYaw } = runtime;
|
|
1029
|
+
const mvX = intent.move.x;
|
|
1030
|
+
const mvY = intent.move.y;
|
|
1031
|
+
const mvMag = Math.hypot(mvX, mvY);
|
|
1032
|
+
const nmvX = mvMag > 1 ? mvX / mvMag : mvX;
|
|
1033
|
+
const nmvY = mvMag > 1 ? mvY / mvMag : mvY;
|
|
1034
|
+
const desiredVx = sinYaw * nmvY + -cosYaw * nmvX;
|
|
1035
|
+
const desiredVz = cosYaw * nmvY + sinYaw * nmvX;
|
|
1036
|
+
const desiredHorizontalVx = desiredVx * targetSpeed;
|
|
1037
|
+
const desiredHorizontalVz = desiredVz * targetSpeed;
|
|
534
1038
|
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1039
|
+
// -- L1.d: Accel/decel toward desired velocity ----------------
|
|
1040
|
+
const intentLen = Math.hypot(nmvX, nmvY);
|
|
1041
|
+
let horizAccel;
|
|
1042
|
+
if (!state.grounded) {
|
|
1043
|
+
horizAccel = cfg.motion.airAccel;
|
|
1044
|
+
} else if (intentLen < 1e-4) {
|
|
1045
|
+
horizAccel = cfg.motion.groundDecel;
|
|
1046
|
+
} else {
|
|
1047
|
+
horizAccel = cfg.motion.groundAccel;
|
|
541
1048
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
runtime.prevStridePhase = state.stridePhase;
|
|
545
|
-
if (state.grounded && horizSpeed > cfg.bob.minStepSpeed) {
|
|
546
|
-
const freq = cfg.bob.stepFreqAtWalk
|
|
547
|
-
* Math.pow(Math.max(horizSpeed, 1e-3) / Math.max(cfg.motion.walkSpeed, 1e-3), cfg.bob.stepFreqExp);
|
|
548
|
-
// 1 full stride cycle = 2 footfalls; phase advances at freq/2 of cycle
|
|
549
|
-
state.stridePhase += (freq * 0.5) * dt;
|
|
550
|
-
state.stridePhase -= Math.floor(state.stridePhase);
|
|
1049
|
+
if (isBackwardIntent && state.grounded) {
|
|
1050
|
+
horizAccel *= cfg.motion.backwardAccelFactor;
|
|
551
1051
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (phaseCrossed(runtime.prevStridePhase, state.stridePhase, 0.5)) {
|
|
561
|
-
state.stepCount++;
|
|
562
|
-
const side = runtime.nextFootSide === "L" ? "L" : "R";
|
|
563
|
-
runtime.nextFootSide = side === "R" ? "L" : "R";
|
|
564
|
-
sig.onFootStep.send1({ side, speed: horizSpeed, surfaceTag: state.surfaceTag });
|
|
565
|
-
}
|
|
1052
|
+
if (state.grounded) {
|
|
1053
|
+
horizAccel *= runtime.massRatios.groundAccelScale;
|
|
1054
|
+
// Mastery: GroundAccel evaluators can scale per-tick accel
|
|
1055
|
+
// (e.g. foot-asymmetry-turn bonus). Default (no evaluators)
|
|
1056
|
+
// returns 1.0 → unchanged.
|
|
1057
|
+
horizAccel *= controller.mastery.evaluate(
|
|
1058
|
+
DecisionPoint.GroundAccel, controller, runtime,
|
|
1059
|
+
);
|
|
566
1060
|
}
|
|
1061
|
+
const maxStep = horizAccel * dt;
|
|
1062
|
+
runtime.velocityX = stepTowards(runtime.velocityX, desiredHorizontalVx, maxStep);
|
|
1063
|
+
runtime.velocityZ = stepTowards(runtime.velocityZ, desiredHorizontalVz, maxStep);
|
|
567
1064
|
|
|
568
|
-
// --
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1065
|
+
// -- L1.e/f/g/h: jump FSM + vertical integration --------------
|
|
1066
|
+
this._advanceJumpFsm(controller, runtime, bodyTransform, dt);
|
|
1067
|
+
this._integrateVerticalAndResolveGround(controller, runtime, bodyTransform, dt);
|
|
1068
|
+
|
|
1069
|
+
// -- Publish posture for L2 consumers (eye height, gait gating).
|
|
1070
|
+
// Base owns posture when no ability is active: Crouch if the
|
|
1071
|
+
// crouch intent is resolved active, otherwise Stand. Abilities
|
|
1072
|
+
// that need a different posture (slide → Prone, ledge-grab →
|
|
1073
|
+
// Hang) set state.posture themselves in their tick.
|
|
1074
|
+
controller.state.posture = isCrouchActive
|
|
1075
|
+
? FirstPersonPosture.Crouch
|
|
1076
|
+
: FirstPersonPosture.Stand;
|
|
1077
|
+
|
|
1078
|
+
// -- Publish lean target for L2.f. Base writes the natural
|
|
1079
|
+
// (lat-accel + look-lean) value; abilities override in their
|
|
1080
|
+
// own tick. L2.f spring-steps toward whatever's here.
|
|
1081
|
+
runtime.leanTargetRad = this._computeNaturalLeanTarget(controller, runtime, dt);
|
|
1082
|
+
}
|
|
573
1083
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
1084
|
+
/**
|
|
1085
|
+
* Compute the natural camera lean for this tick: lat-accel-driven
|
|
1086
|
+
* roll into a turn, plus a yaw-rate look-lean contribution, both
|
|
1087
|
+
* clamped. The result is the target the lean spring chases each
|
|
1088
|
+
* tick when no ability has opinions.
|
|
1089
|
+
*
|
|
1090
|
+
* Pure-ish helper — reads `controller`, `runtime`, `dt`; returns a
|
|
1091
|
+
* number. Extracted so both base and any future ability that wants
|
|
1092
|
+
* to compose its lean on top of the natural value can call it.
|
|
1093
|
+
*
|
|
1094
|
+
* @private
|
|
1095
|
+
* @param {FirstPersonPlayerController} controller
|
|
1096
|
+
* @param {PerEntityRuntime} runtime
|
|
1097
|
+
* @param {number} dt
|
|
1098
|
+
* @returns {number} target roll in radians
|
|
1099
|
+
*/
|
|
1100
|
+
_computeNaturalLeanTarget(controller, runtime, dt) {
|
|
1101
|
+
const cfg = controller.config;
|
|
1102
|
+
const state = controller.state;
|
|
1103
|
+
if (!cfg.lean.enabled) return 0;
|
|
579
1104
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
1105
|
+
const sinYaw = runtime.sinYaw;
|
|
1106
|
+
const cosYaw = runtime.cosYaw;
|
|
1107
|
+
|
|
1108
|
+
// Lateral acceleration projected onto screen-right.
|
|
1109
|
+
// accel_world = (vel - prevVel) / dt; screen_right = (-cos θ, 0, sin θ).
|
|
1110
|
+
const accWorldX = (runtime.velocityX - runtime.prevVelocityX) / Math.max(dt, 1e-4);
|
|
1111
|
+
const accWorldZ = (runtime.velocityZ - runtime.prevVelocityZ) / Math.max(dt, 1e-4);
|
|
1112
|
+
const latAccel = accWorldX * (-cosYaw) + accWorldZ * sinYaw;
|
|
1113
|
+
const normalized = clamp(latAccel / 9.81, -2, 2);
|
|
1114
|
+
//
|
|
1115
|
+
// Sign convention for the roll (the eye composes the rotation
|
|
1116
|
+
// as qYaw * qPitch * qRoll, where qRoll is around (0,0,1)).
|
|
1117
|
+
// After the engine's camera-invert pipeline:
|
|
1118
|
+
// φ > 0 → camera-up tilts toward screen-right (−X) → HEAD TILTS RIGHT
|
|
1119
|
+
// φ < 0 → camera-up tilts toward screen-left (+X) → HEAD TILTS LEFT
|
|
1120
|
+
//
|
|
1121
|
+
// For the "bank into the turn" feel (Apex / Titanfall / Mirror's
|
|
1122
|
+
// Edge): accelerating right (latAccel > 0) should tilt the head
|
|
1123
|
+
// RIGHT, i.e. positive φ. So leanTargetRad has the SAME sign
|
|
1124
|
+
// as latAccel.
|
|
1125
|
+
let leanTargetRad = normalized * cfg.lean.maxRollDeg * DEG_TO_RAD;
|
|
1126
|
+
|
|
1127
|
+
// Look-lean: yaw-rate-driven banking. runtime.yawRateRadPerSec
|
|
1128
|
+
// was cached at L1.a — negative is the "turn right" convention.
|
|
1129
|
+
// For "bank into the turn": turning right → head tilts right →
|
|
1130
|
+
// positive engine roll. So lookLean = -yawRate * scale matches
|
|
1131
|
+
// sign.
|
|
1132
|
+
//
|
|
1133
|
+
// Crouched players are in a low, stable, low-momentum stance —
|
|
1134
|
+
// banking the head from a mouse turn reads as unmotivated. We
|
|
1135
|
+
// scale the contribution down (default to 0) while crouched.
|
|
1136
|
+
// Lat-accel lean is left alone: its magnitude naturally tracks
|
|
1137
|
+
// the (lower) crouch acceleration, so it stays motivated.
|
|
1138
|
+
if (cfg.lean.lookLeanEnabled) {
|
|
1139
|
+
const yawRate = clamp(
|
|
1140
|
+
runtime.yawRateRadPerSec,
|
|
1141
|
+
-cfg.lean.lookLeanYawRateClamp,
|
|
1142
|
+
cfg.lean.lookLeanYawRateClamp,
|
|
1143
|
+
);
|
|
1144
|
+
const crouchFactor = state.crouchActive ? cfg.lean.crouchLookLeanFactor : 1.0;
|
|
1145
|
+
leanTargetRad += -yawRate * cfg.lean.lookLeanDegPerRadPerSec * DEG_TO_RAD * crouchFactor;
|
|
591
1146
|
}
|
|
592
|
-
runtime.prevVelocityX = runtime.velocityX;
|
|
593
|
-
runtime.prevVelocityZ = runtime.velocityZ;
|
|
594
|
-
criticallyDampedSpringStep(runtime.leanSpring, leanTargetRad, cfg.lean.spring.halfLife, dt);
|
|
595
|
-
state.leanRollRad = runtime.leanSpring.value;
|
|
596
1147
|
|
|
597
|
-
//
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
);
|
|
1148
|
+
// Final clamp on the sum: cap the combined target to ±2 ×
|
|
1149
|
+
// maxRollDeg (matches the latAccel normalized clamp range) so
|
|
1150
|
+
// even simultaneous max-strafe-accel + max-yaw-flick produces a
|
|
1151
|
+
// sane upper bound.
|
|
1152
|
+
const maxTotal = cfg.lean.maxRollDeg * DEG_TO_RAD * 2;
|
|
1153
|
+
return clamp(leanTargetRad, -maxTotal, maxTotal);
|
|
1154
|
+
}
|
|
605
1155
|
|
|
606
|
-
|
|
1156
|
+
/**
|
|
1157
|
+
* Snapshot the per-tick "what is the body doing" information into the
|
|
1158
|
+
* pose channels for downstream consumption (skeleton, sound, AI).
|
|
1159
|
+
* Read-only with respect to controller state — this is purely a publish
|
|
1160
|
+
* step.
|
|
1161
|
+
*
|
|
1162
|
+
* @private
|
|
1163
|
+
* @param {FirstPersonPlayerController} controller
|
|
1164
|
+
* @param {PerEntityRuntime} runtime
|
|
1165
|
+
* @param {Transform} bodyTransform
|
|
1166
|
+
*/
|
|
1167
|
+
_publishPose(controller, runtime, bodyTransform) {
|
|
1168
|
+
const cfg = controller.config;
|
|
1169
|
+
const state = controller.state;
|
|
607
1170
|
const pose = controller.pose;
|
|
1171
|
+
|
|
608
1172
|
pose.rootPosition.copy(bodyTransform.position);
|
|
609
1173
|
pose.rootYawRad = runtime.bodyYaw;
|
|
610
1174
|
pose.headYawRad = runtime.bodyYaw;
|
|
611
1175
|
pose.headPitchRad = runtime.eyePitch;
|
|
612
1176
|
pose.headRollRad = state.leanRollRad;
|
|
613
1177
|
pose.locomotionPhase = state.stridePhase;
|
|
614
|
-
pose.locomotionSpeed = horizSpeed;
|
|
1178
|
+
pose.locomotionSpeed = runtime.horizSpeed;
|
|
615
1179
|
// Strafe component: project velocity onto screen-right (-cos θ, 0, sin θ).
|
|
616
|
-
// Positive = moving to the player's right
|
|
617
|
-
pose.locomotionStrafe = (runtime.velocityX * (-cosYaw) + runtime.velocityZ * sinYaw)
|
|
1180
|
+
// Positive = moving to the player's right.
|
|
1181
|
+
pose.locomotionStrafe = (runtime.velocityX * (-runtime.cosYaw) + runtime.velocityZ * runtime.sinYaw)
|
|
618
1182
|
/ Math.max(cfg.motion.sprintSpeed, 1e-3);
|
|
619
1183
|
pose.actionState =
|
|
620
1184
|
state.inJumpAnticipation ? FirstPersonActionState.Anticipating
|
|
621
1185
|
: !state.grounded ? FirstPersonActionState.Airborne
|
|
622
1186
|
: (Math.abs(runtime.landSpring.value) > 0.01 ? FirstPersonActionState.Landing
|
|
623
1187
|
: FirstPersonActionState.Grounded);
|
|
1188
|
+
pose.locomotionMode = state.locomotionMode;
|
|
624
1189
|
const crouchSpan = Math.max(cfg.body.height - cfg.body.crouchHeight, 1e-3);
|
|
625
1190
|
pose.crouchAmount = clamp((cfg.body.height - state.eyeHeight) / crouchSpan, 0, 1);
|
|
626
|
-
pose.aimPitch = runtime.eyePitch;
|
|
627
|
-
}
|
|
628
1191
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return runtime.crouchLatched;
|
|
1192
|
+
// Posture channel for downstream animation: which body shape +
|
|
1193
|
+
// how far the body is into it from the standing neutral.
|
|
1194
|
+
//
|
|
1195
|
+
// `posture` is the enum (Stand / Crouch / Prone / Hang) — picks
|
|
1196
|
+
// the animation track. `postureAmount` is the [0..1] blend
|
|
1197
|
+
// weight from standing toward that posture, derived from the
|
|
1198
|
+
// eye-height spring so the value transitions smoothly across
|
|
1199
|
+
// changes (matches the visible camera motion).
|
|
1200
|
+
pose.posture = state.posture;
|
|
1201
|
+
let postureTargetH;
|
|
1202
|
+
switch (state.posture) {
|
|
1203
|
+
case FirstPersonPosture.Prone: postureTargetH = cfg.body.proneHeight; break;
|
|
1204
|
+
case FirstPersonPosture.Crouch: postureTargetH = cfg.body.crouchHeight; break;
|
|
1205
|
+
case FirstPersonPosture.Hang: postureTargetH = cfg.body.height; break;
|
|
1206
|
+
case FirstPersonPosture.Stand:
|
|
1207
|
+
default: postureTargetH = cfg.body.height; break;
|
|
646
1208
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
1209
|
+
const postureSpan = Math.max(cfg.body.height - postureTargetH, 1e-3);
|
|
1210
|
+
pose.postureAmount = clamp((cfg.body.height - state.eyeHeight) / postureSpan, 0, 1);
|
|
1211
|
+
|
|
1212
|
+
pose.aimPitch = runtime.eyePitch;
|
|
650
1213
|
}
|
|
651
1214
|
|
|
652
1215
|
/**
|
|
@@ -672,40 +1235,71 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
672
1235
|
const camera = ecd.getComponent(controller.eyeEntity, Camera);
|
|
673
1236
|
if (eyeTransform === undefined || camera === undefined) return;
|
|
674
1237
|
|
|
675
|
-
// -- Body-local eye offset
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
//
|
|
679
|
-
|
|
680
|
-
|
|
1238
|
+
// -- Body-local eye offset, composed via the additive stack ----
|
|
1239
|
+
// The base (0, eyeHeight, 0) is the standing/crouched neutral; each
|
|
1240
|
+
// additional contribution (bob, breath, landing, anticipation,
|
|
1241
|
+
// sprint posture) goes through the stack so external systems can
|
|
1242
|
+
// push their own contributions on the same channel.
|
|
1243
|
+
const stack = runtime.eyeOffsetStack;
|
|
1244
|
+
stack.clear();
|
|
1245
|
+
stack.push("eyeHeight", 0, state.eyeHeight, 0);
|
|
1246
|
+
|
|
1247
|
+
// Bob — gated on grounded only (the impact spring decays naturally
|
|
1248
|
+
// even at rest, so the bob fade-out is smooth; lateral amp uses the
|
|
1249
|
+
// bob-intensity envelope which spring-decays after stopping).
|
|
1250
|
+
if (state.grounded) {
|
|
1251
|
+
const phase = state.stridePhase * TWO_PI;
|
|
681
1252
|
const massBoost = (cfg.body.mass - 80) * cfg.bob.ampMassScale;
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1253
|
+
const intensity = runtime.bobIntensitySpring.value;
|
|
1254
|
+
|
|
1255
|
+
// Back-pedal amp boost — lateral grows more than vertical because
|
|
1256
|
+
// backward gait has worse side-to-side balance than vertical compression.
|
|
1257
|
+
// Exertion adds a smaller boost on top: tired = wobbly gait.
|
|
1258
|
+
const ampLMult = 1 + (cfg.bob.backwardLateralAmpFactor - 1) * runtime.backwardness;
|
|
1259
|
+
const exertionBoost = 1 + cfg.exertion.bobLateralBoostAtMax * state.exertion;
|
|
1260
|
+
const ampL = (cfg.bob.lateralAmpAtWalk + massBoost) * intensity * ampLMult * exertionBoost;
|
|
1261
|
+
|
|
1262
|
+
// Vertical: read directly from the impact spring (footfall kicks,
|
|
1263
|
+
// under-damped recovery → trough + leg-push overshoot).
|
|
1264
|
+
stack.push("bob.impact", 0, runtime.verticalImpactSpring.value, 0);
|
|
1265
|
+
|
|
1266
|
+
// Lateral: head shifts toward the foot bearing weight. Polarity
|
|
1267
|
+
// sourced from runtime.standingFoot — the same signal the
|
|
1268
|
+
// footstep emits — so bob direction and footstep side agree.
|
|
1269
|
+
// |sin(phase)| is the non-negative "midstance envelope".
|
|
1270
|
+
const lateralPolarity = runtime.standingFoot === "R" ? -1 : 1;
|
|
1271
|
+
stack.push("bob.lateral", ampL * lateralPolarity * Math.abs(Math.sin(phase)), 0, 0);
|
|
687
1272
|
}
|
|
688
1273
|
|
|
689
|
-
// Breath — sine + tiny noise
|
|
1274
|
+
// Breath — sine + tiny noise riding the rate spring.
|
|
690
1275
|
const breathOffset = -state.breathAmplitudeM
|
|
691
1276
|
* Math.sin(state.breathPhase * TWO_PI)
|
|
692
1277
|
* (1 + cfg.breath.noiseAmount * (Math.sin(state.breathPhase * 13.7) * 0.5));
|
|
693
|
-
|
|
1278
|
+
stack.push("breath", 0, breathOffset, 0);
|
|
694
1279
|
|
|
695
|
-
// Landing spring dip
|
|
696
|
-
|
|
1280
|
+
// Landing spring dip (under-damped — overshoots once on recovery).
|
|
1281
|
+
stack.push("landing", 0, runtime.landSpring.value, 0);
|
|
697
1282
|
|
|
698
|
-
// Jump anticipation dip (
|
|
1283
|
+
// Jump anticipation dip (eased ramp during the squash window).
|
|
699
1284
|
if (state.inJumpAnticipation) {
|
|
700
1285
|
const t = 1 - clamp(runtime.anticipationRemaining / Math.max(cfg.jump.anticipation.duration, 1e-3), 0, 1);
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
eyeLocal.y -= cfg.jump.anticipation.dipAmount * eased;
|
|
1286
|
+
const eased = t * (2 - t); // ease-out quad
|
|
1287
|
+
stack.push("anticipation", 0, -cfg.jump.anticipation.dipAmount * eased, 0);
|
|
704
1288
|
}
|
|
705
1289
|
|
|
706
|
-
//
|
|
707
|
-
//
|
|
708
|
-
|
|
1290
|
+
// Sprint posture: head leans slightly forward as commitment builds.
|
|
1291
|
+
// Pitch part is in the rotation block below; the +Z position shift
|
|
1292
|
+
// sells "head leading the hips" (Mirror's Edge), tied to the same
|
|
1293
|
+
// spring envelope so they move together.
|
|
1294
|
+
const sprintPitch = runtime.sprintPostureSpring.value;
|
|
1295
|
+
const sprintShiftFraction =
|
|
1296
|
+
cfg.posture.sprintForwardPitchDeg > 0
|
|
1297
|
+
? sprintPitch / (cfg.posture.sprintForwardPitchDeg * DEG_TO_RAD)
|
|
1298
|
+
: 0;
|
|
1299
|
+
stack.push("posture.sprintShift", 0, 0, cfg.posture.sprintForwardShiftM * sprintShiftFraction);
|
|
1300
|
+
|
|
1301
|
+
// Transform body-local accumulated offset into world space.
|
|
1302
|
+
const worldOffset = SCRATCH_V3_B.copy(stack.offset);
|
|
709
1303
|
worldOffset.applyQuaternion(bodyTransform.rotation);
|
|
710
1304
|
|
|
711
1305
|
eyeTransform.position.copy(bodyTransform.position);
|
|
@@ -717,16 +1311,42 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
717
1311
|
// breath; merged into the main pitch so we don't pay an extra quat
|
|
718
1312
|
// multiply and the composition stays trivially correct.
|
|
719
1313
|
let rollTotal = state.leanRollRad;
|
|
720
|
-
if (state.grounded
|
|
1314
|
+
if (state.grounded) {
|
|
1315
|
+
// Roll: head tilts toward the standing foot, in phase with the
|
|
1316
|
+
// lateral sway. Polarity sourced from runtime.standingFoot for
|
|
1317
|
+
// consistency with the lateral bob. Positive engine roll = head
|
|
1318
|
+
// tilts RIGHT (camera-invert convention), so R-foot midstance =
|
|
1319
|
+
// positive roll, L-foot midstance = negative roll.
|
|
721
1320
|
const phase = state.stridePhase * TWO_PI;
|
|
722
|
-
const
|
|
723
|
-
|
|
1321
|
+
const rollBackMult = 1 + (cfg.bob.backwardRollFactor - 1) * runtime.backwardness;
|
|
1322
|
+
const ampRoll = cfg.bob.rollAtWalkDeg * DEG_TO_RAD * runtime.bobIntensitySpring.value * rollBackMult;
|
|
1323
|
+
const rollPolarity = runtime.standingFoot === "R" ? 1 : -1;
|
|
1324
|
+
const rollEnvelope = Math.abs(Math.sin(phase));
|
|
1325
|
+
const bobRollSigned = ampRoll * rollPolarity * rollEnvelope;
|
|
1326
|
+
|
|
1327
|
+
// Lean × bob coupling: excursions in the lean direction get
|
|
1328
|
+
// amplified, opposite excursions attenuated. Lean is normalized
|
|
1329
|
+
// against maxRollDeg so the coupling magnitude stays bounded
|
|
1330
|
+
// regardless of how aggressively lean is configured.
|
|
1331
|
+
const maxLeanRad = Math.max(cfg.lean.maxRollDeg * DEG_TO_RAD, 1e-6);
|
|
1332
|
+
const leanFraction = clamp(state.leanRollRad / maxLeanRad, -1, 1);
|
|
1333
|
+
// sign(bobRollSigned) matches lean? amplify; else attenuate.
|
|
1334
|
+
const sameSign = (bobRollSigned * leanFraction) >= 0;
|
|
1335
|
+
const couplingMag = cfg.bob.leanCouplingFactor * Math.abs(leanFraction);
|
|
1336
|
+
const couplingScale = sameSign ? (1 + couplingMag) : (1 - couplingMag);
|
|
1337
|
+
rollTotal += bobRollSigned * couplingScale;
|
|
724
1338
|
}
|
|
725
1339
|
|
|
726
1340
|
const breathPitch = lerp(cfg.breath.pitchAmpRestDeg, cfg.breath.pitchAmpMaxDeg, state.exertion)
|
|
727
1341
|
* DEG_TO_RAD
|
|
728
1342
|
* Math.cos(state.breathPhase * TWO_PI);
|
|
729
|
-
|
|
1343
|
+
// Combined pitch contributions: player input + breath nod + sprint
|
|
1344
|
+
// commitment + fatigue droop. All in the same "positive = look-down"
|
|
1345
|
+
// convention so they sum cleanly.
|
|
1346
|
+
const pitchTotal = runtime.eyePitch
|
|
1347
|
+
+ breathPitch
|
|
1348
|
+
+ runtime.sprintPostureSpring.value
|
|
1349
|
+
+ runtime.headDroopSpring.value;
|
|
730
1350
|
|
|
731
1351
|
// composition: yaw * pitch * roll
|
|
732
1352
|
// pitch around world X — yaw applied after, so effective axis is camera-local right
|
|
@@ -741,14 +1361,11 @@ export class FirstPersonPlayerControllerSystem extends System {
|
|
|
741
1361
|
// -- FOV ---------------------------------------------------------
|
|
742
1362
|
let fovTarget = cfg.fov.base;
|
|
743
1363
|
if (cfg.fov.sprintAdd !== 0) {
|
|
744
|
-
|
|
745
|
-
const sprintness = clamp((state.speed - cfg.motion.walkSpeed)
|
|
746
|
-
/ Math.max(cfg.motion.sprintSpeed - cfg.motion.walkSpeed, 1e-3), 0, 1);
|
|
747
|
-
fovTarget += cfg.fov.sprintAdd * sprintness;
|
|
1364
|
+
fovTarget += cfg.fov.sprintAdd * runtime.sprintness;
|
|
748
1365
|
}
|
|
749
1366
|
if (state.crouchActive) fovTarget += cfg.fov.crouchAdd;
|
|
750
1367
|
|
|
751
|
-
|
|
1368
|
+
runtime.fovSpring.stepTo(fovTarget, cfg.fov.smoothHalfLife, 1.0, dt);
|
|
752
1369
|
// Write directly to the underlying Three.js camera. Going through
|
|
753
1370
|
// camera.fov.set() fires onChanged which triggers a full camera
|
|
754
1371
|
// rebuild in CameraSystem — far too expensive to do per frame.
|