@woosh/meep-engine 3.24.0 → 3.25.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/engine/graphics3/PickingSystem.d.ts +3 -3
- package/src/engine/graphics3/PickingSystem.d.ts.map +1 -1
- package/src/engine/graphics3/PickingSystem.js +52 -55
- package/src/engine/graphics3/shader_picking_gather.d.ts +40 -0
- package/src/engine/graphics3/shader_picking_gather.d.ts.map +1 -0
- package/src/engine/graphics3/shader_picking_gather.js +120 -0
- package/src/engine/physics/cloth/MEASUREMENTS.md +1050 -0
- package/src/engine/physics/cloth/PLAN.md +411 -49
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts +52 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.js +70 -0
- package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts +12 -3
- package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothDynamics.js +340 -331
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +36 -22
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +38 -23
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts +84 -0
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.js +141 -0
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +51 -82
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +81 -869
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts +48 -0
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.js +107 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +286 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.js +1104 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +182 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +562 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.d.ts +2 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.js +12 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_build_rows.js +2 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +58 -0
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +341 -271
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +20 -2
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +39 -15
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts +29 -0
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.js +78 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts +17 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.js +18 -0
- package/src/engine/physics/cloth/playground/README.md +73 -2
- package/src/engine/physics/cloth/playground/wind.html +166 -0
- package/src/engine/physics/cloth/playground/wind_build.d.ts +81 -0
- package/src/engine/physics/cloth/playground/wind_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/wind_build.js +141 -0
- package/src/engine/physics/cloth/playground/wind_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/wind_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/wind_main.js +681 -0
- package/src/engine/physics/cloth/solver/ClothState.d.ts +134 -79
- package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/ClothState.js +273 -12
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +33 -0
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.js +34 -0
- package/src/engine/physics/cloth/solver/cloth_step.d.ts +21 -2
- package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_step.js +173 -4
- package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +40 -0
- package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_step_instance.js +284 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts +107 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.js +263 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +88 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.js +100 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +85 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.js +182 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +79 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.js +210 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +101 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.js +290 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/device/mock/texture_copy.d.ts.map +1 -1
- package/src/shade/device/mock/texture_copy.js +28 -0
- package/src/shade/playground/vgeo_runtime/README.md +94 -0
- package/src/shade/playground/vgeo_runtime/index.html +62 -0
- package/src/shade/playground/vgeo_runtime/main.d.ts +2 -0
- package/src/shade/playground/vgeo_runtime/main.d.ts.map +1 -0
- package/src/shade/playground/vgeo_runtime/main.js +399 -0
- package/src/shade/playground/vgeo_viewer/sample_asset.js +1 -1
- package/src/shade/renderer/camera/Camera.d.ts +14 -0
- package/src/shade/renderer/camera/Camera.d.ts.map +1 -1
- package/src/shade/renderer/camera/Camera.js +7 -1
- package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.js +16 -1
- package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GPUGeometryManager.js +29 -9
- package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts +10 -0
- package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GPUGeometryMetadata.js +13 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +46 -18
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +70 -22
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +97 -19
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +12 -2
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +14 -3
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +2 -2
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +124 -6
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.js +53 -0
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +8 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.js +15 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts +54 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.js +53 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +13 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +14 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +100 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +113 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +5 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +108 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +23 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +40 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +228 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +932 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +176 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +425 -0
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +241 -0
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +574 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +120 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +674 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +287 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +976 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +42 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +96 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.js +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +83 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +160 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +255 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +80 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +144 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +530 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +39 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +54 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts +41 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.js +56 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts +49 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +328 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +51 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +99 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +27 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.js +35 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.js +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +36 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.js +54 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.js +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.js +30 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +61 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.js +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts +23 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +70 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +25 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +35 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +41 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +32 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +138 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +9 -0
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +12 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +12 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +10 -0
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +18 -0
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +647 -594
- package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.js +22 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts +35 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.js +91 -0
- package/src/engine/graphics3/instantiate_scene_bundle.d.ts +0 -54
- package/src/engine/graphics3/instantiate_scene_bundle.d.ts.map +0 -1
- package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +0 -39
- package/src/engine/graphics3/shade_node_to_entity_composition.d.ts.map +0 -1
- package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts +0 -35
- package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts.map +0 -1
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts +0 -96
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts.map +0 -1
- package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts +0 -20
- package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_bind_transform.d.ts +0 -25
- package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_frame_defect.d.ts +0 -34
- package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts +0 -24
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +0 -70
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +0 -1
- package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts +0 -96
- package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts.map +0 -1
- package/src/shade/renderer/scene/InstanceBatch.d.ts +0 -92
- package/src/shade/renderer/scene/InstanceBatch.d.ts.map +0 -1
- package/src/shade/renderer/scene/Mesh.d.ts +0 -55
- package/src/shade/renderer/scene/Mesh.d.ts.map +0 -1
- package/src/shade/renderer/scene/Node3D.d.ts +0 -159
- package/src/shade/renderer/scene/Node3D.d.ts.map +0 -1
- package/src/shade/renderer/scene/Node3DDirtyList.d.ts +0 -59
- package/src/shade/renderer/scene/Node3DDirtyList.d.ts.map +0 -1
- package/src/shade/renderer/scene/SceneNodeBinding.d.ts +0 -52
- package/src/shade/renderer/scene/SceneNodeBinding.d.ts.map +0 -1
- package/src/shade/renderer/scene/SkinnedMesh.d.ts +0 -27
- package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +0 -1
- package/src/shade/renderer/scene/TransformAuthority.d.ts +0 -19
- package/src/shade/renderer/scene/TransformAuthority.d.ts.map +0 -1
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { quat3_multiply } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
|
|
3
|
+
import { quat3_nlerp } from "../../../../core/geom/3d/quaternion/quat3_nlerp.js";
|
|
4
|
+
import { v3_quaternion_apply } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
|
|
5
|
+
import { v3_quaternion_apply_inverse } from "../../../../core/geom/vec3/v3_quaternion_apply_inverse.js";
|
|
6
|
+
import {
|
|
7
|
+
TransformAttachment
|
|
8
|
+
} from "../../../ecs/transform-attachment/TransformAttachment.js";
|
|
9
|
+
import {
|
|
10
|
+
TransformAttachmentSystem
|
|
11
|
+
} from "../../../ecs/transform-attachment/TransformAttachmentSystem.js";
|
|
12
|
+
import {
|
|
13
|
+
TRANSFORM64_ROTATION_OFFSET,
|
|
14
|
+
TRANSFORM64_TRANSLATION_OFFSET
|
|
15
|
+
} from "../../../ecs/transform/Transform64.js";
|
|
16
|
+
import { t64_evaluate_world } from "../../../ecs/transform/t64_evaluate_world.js";
|
|
17
|
+
import {
|
|
18
|
+
collect_entity_playbacks
|
|
19
|
+
} from "../../../graphics3/pose/collect_entity_playbacks.js";
|
|
20
|
+
import { MeshSystem } from "../../../graphics3/MeshSystem.js";
|
|
21
|
+
import { CLOTH_SELF_RADIUS_MAX_RATIO, CLOTH_SELF_RADIUS_MIN_RATIO } from "../solver/cloth_solver_constants.js";
|
|
22
|
+
import { cloth_air_changed, cloth_sample_air } from "../wind/cloth_sample_air.js";
|
|
23
|
+
import { ClothStepParam } from "../solver/cloth_step_instance.js";
|
|
24
|
+
import { ClothStepResult } from "../solver/ClothState.js";
|
|
25
|
+
import { ClothColliderSystem } from "./ClothColliderSystem.js";
|
|
26
|
+
import { ClothDynamicsFlags } from "./ClothDynamicsFlags.js";
|
|
27
|
+
import { ClothFlags } from "./ClothFlags.js";
|
|
28
|
+
import { ClothInstance } from "./ClothInstance.js";
|
|
29
|
+
import { ClothRig } from "./ClothRig.js";
|
|
30
|
+
import {
|
|
31
|
+
CLOTH_ANCHOR_ROTATION_LIMIT,
|
|
32
|
+
CLOTH_ANCHOR_TRAVEL_REACH,
|
|
33
|
+
CLOTH_SLEEP_SPEED_RATIO,
|
|
34
|
+
CLOTH_SLEEP_STEPS,
|
|
35
|
+
CLOTH_STABILISE_STEPS,
|
|
36
|
+
CLOTH_TELEPORT_FLOOR,
|
|
37
|
+
CLOTH_TELEPORT_REACH,
|
|
38
|
+
cloth_drag_response,
|
|
39
|
+
cloth_velocity_limit,
|
|
40
|
+
cloth_velocity_retention
|
|
41
|
+
} from "./cloth_dynamics_map.js";
|
|
42
|
+
import {
|
|
43
|
+
cloth_colliders_moved,
|
|
44
|
+
cloth_gather_colliders
|
|
45
|
+
} from "./cloth_gather_colliders.js";
|
|
46
|
+
import {
|
|
47
|
+
cloth_dynamics_change_needs_reseed,
|
|
48
|
+
cloth_reparameterise
|
|
49
|
+
} from "./cloth_build_rows.js";
|
|
50
|
+
import { cloth_seed_subtree } from "./cloth_seed_subtree.js";
|
|
51
|
+
import { cloth_seed_rig } from "./cloth_seed_rig.js";
|
|
52
|
+
import { cloth_write_back } from "./cloth_write_back.js";
|
|
53
|
+
import { cloth_write_back_rig } from "./cloth_write_back_rig.js";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* # Every cloth in a scene, and everything a step needs that is not arithmetic
|
|
57
|
+
*
|
|
58
|
+
* One {@link ClothInstance} per {@link Cloth} component, stepped at the fixed
|
|
59
|
+
* rate. Per step, per cloth: move the local frame with the anchor, point the
|
|
60
|
+
* leash rows at where they measure from, gather the colliders that could touch
|
|
61
|
+
* it, step, and put the result back on the subtree. The animated pose the
|
|
62
|
+
* leashes are measured against is not recomposed per step — in the entity path
|
|
63
|
+
* it is constant in the anchor's own frame, and `cloth_seed_subtree.js` bakes
|
|
64
|
+
* it.
|
|
65
|
+
*
|
|
66
|
+
* ## Why this is not a `System`
|
|
67
|
+
*
|
|
68
|
+
* Because two of them drive it. {@link ClothSystem} steps every cloth inline;
|
|
69
|
+
* {@link WorkerClothSystem} hands the arithmetic to a worker and joins it on the
|
|
70
|
+
* next tick. §12 asks those two to produce **bit-identical** results, and the
|
|
71
|
+
* cheapest way to be sure of that is for there to be one copy of everything
|
|
72
|
+
* except the dispatch — so the registry, the seeding, the frame handling, the
|
|
73
|
+
* broadphase and the write-back live here, and the two systems differ only in
|
|
74
|
+
* where {@link ClothWorld#prepare} and {@link ClothWorld#finish} are separated
|
|
75
|
+
* by a thread.
|
|
76
|
+
*
|
|
77
|
+
* That split is also exactly §12's: "arbitrary user-facing work — component
|
|
78
|
+
* reads, `Transform64` sync, index queries — stays on the main thread; the pure
|
|
79
|
+
* numeric step crosses". {@link ensure_seeded} and {@link advance} are the first
|
|
80
|
+
* half, `cloth_step_instance` is the second, {@link finish} is what the result
|
|
81
|
+
* is worth.
|
|
82
|
+
*
|
|
83
|
+
* Seeding is its own call rather than the top of {@link advance} because the
|
|
84
|
+
* worker needs to get between them: a cloth's `ClothState` is what it registers
|
|
85
|
+
* with the worker, so it has to exist — and be acknowledged — before the cloth
|
|
86
|
+
* can be nominated for a step, and a re-seed replaces it and starts that again.
|
|
87
|
+
*
|
|
88
|
+
* ## The parameter block
|
|
89
|
+
*
|
|
90
|
+
* {@link prepare} does not call the step and does not return its arguments; it
|
|
91
|
+
* **writes them into a `Float64` block** the caller supplies. For
|
|
92
|
+
* {@link ClothSystem} that block is a private scratch array and the step follows
|
|
93
|
+
* immediately; for {@link WorkerClothSystem} it is a slice of the shared command
|
|
94
|
+
* region and the step happens on another thread. Neither system ever computes a
|
|
95
|
+
* step parameter of its own, so neither can compute one differently.
|
|
96
|
+
*
|
|
97
|
+
* ## The local frame and the inertia model — §7 P7
|
|
98
|
+
*
|
|
99
|
+
* Particles live in the anchor's own frame. When the anchor moves, the cloth is
|
|
100
|
+
* asked how much of that motion it would like to feel:
|
|
101
|
+
* `ClothDynamics#inertia = 1` keeps every particle's **world** position and
|
|
102
|
+
* velocity, so a cape trails a sprinting character; `0` keeps their **local**
|
|
103
|
+
* ones, so the cloth rides the anchor as though it were standing still. The
|
|
104
|
+
* blend between the two is a lerp in the local frame, which is all the frame
|
|
105
|
+
* change is.
|
|
106
|
+
*
|
|
107
|
+
* The felt motion is limited before the blend, which is what makes "character
|
|
108
|
+
* sprints, cape behaves" a default rather than a tuning exercise, and the limits
|
|
109
|
+
* are derived from the cloth's own reach rather than authored.
|
|
110
|
+
*
|
|
111
|
+
* ## The wind — §10
|
|
112
|
+
*
|
|
113
|
+
* {@link wind} is a source of air and {@link advance} samples it into the
|
|
114
|
+
* cloth's own buffer, once per particle per step. That is the one piece of M6
|
|
115
|
+
* that stays on this thread: a fluid field is an ECS component holding a
|
|
116
|
+
* hundred thousand floats that its own solver rewrites every step, and the
|
|
117
|
+
* *answer* is three floats a particle. `cloth_sample_air.js` carries the
|
|
118
|
+
* decision; the aerodynamic arithmetic built on top of it lives in
|
|
119
|
+
* `cloth_step_instance` with everything else the budget is spent on.
|
|
120
|
+
*
|
|
121
|
+
* A source is optional and additive, like the collider index. With none, the
|
|
122
|
+
* step's drag response is zero, `cloth_step` takes the uniform-gravity path,
|
|
123
|
+
* and a scene produces the bytes M5 measured.
|
|
124
|
+
*
|
|
125
|
+
* ## Teleport — §7 P6
|
|
126
|
+
*
|
|
127
|
+
* Anchor motion past a threshold is not locomotion and is not treated as any
|
|
128
|
+
* amount of it. `ClothFlags.TeleportKeep` carries the shape across, which in a
|
|
129
|
+
* local frame is literally doing nothing; the default re-seeds at the animated
|
|
130
|
+
* pose with zero velocity and opens a stabilisation window in which the
|
|
131
|
+
* velocity limit ramps back from nothing, so the first steps after a reset
|
|
132
|
+
* cannot produce a whip.
|
|
133
|
+
*
|
|
134
|
+
* @author Alex Goldring
|
|
135
|
+
* @copyright Company Named Limited (c) 2026
|
|
136
|
+
*/
|
|
137
|
+
export class ClothWorld {
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* World gravity, m/s². Rotated into each cloth's local frame per step.
|
|
141
|
+
* @type {Float64Array}
|
|
142
|
+
*/
|
|
143
|
+
gravity = new Float64Array([0, -9.81, 0]);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Live cloths, ordered so that an instance whose anchor sits inside another
|
|
147
|
+
* instance's subtree steps after it.
|
|
148
|
+
* @type {ClothInstance[]}
|
|
149
|
+
*/
|
|
150
|
+
instances = [];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Where a skinned model instance comes from. Anything that answers
|
|
154
|
+
* `instance_of(entity)` with a `PrefabInstance` — skins whose joints
|
|
155
|
+
* are entities — will do, and {@link MeshSystem} is what does in a
|
|
156
|
+
* running engine.
|
|
157
|
+
*
|
|
158
|
+
* Public and assignable because a garment's joints are a *rendering*
|
|
159
|
+
* structure and `MeshSystem` needs a graphics engine, a scene and a loader
|
|
160
|
+
* to exist — so a spec that wants joints without a renderer sets this
|
|
161
|
+
* instead, and gets exactly the interface the system uses.
|
|
162
|
+
*
|
|
163
|
+
* `null` means no rig can seed, which is the same additive shape the
|
|
164
|
+
* collider index has: a scene without one gets M1's cloth rather than an
|
|
165
|
+
* error.
|
|
166
|
+
* @type {{instance_of: function(number): (PrefabInstance|null)}|null}
|
|
167
|
+
*/
|
|
168
|
+
models = null;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @type {EntityManager|null}
|
|
172
|
+
*/
|
|
173
|
+
entityManager = null;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @type {TransformAttachmentSystem|null}
|
|
177
|
+
*/
|
|
178
|
+
attachments = null;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The collider index's owner, or `null` when the manager has none — in
|
|
182
|
+
* which case cloth simply does not collide, which is exactly M1's
|
|
183
|
+
* behaviour and is what makes the collision layer additive rather than a
|
|
184
|
+
* new requirement on every scene.
|
|
185
|
+
* @type {ClothColliderSystem|null}
|
|
186
|
+
*/
|
|
187
|
+
colliders = null;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Where the air comes from — §10. `null` means no air, which is the
|
|
191
|
+
* default and is **additive** in exactly the way the collider index is: a
|
|
192
|
+
* scene without one gets M5's cloth rather than an error, and adding one is
|
|
193
|
+
* the whole of turning wind on.
|
|
194
|
+
*
|
|
195
|
+
* Null rather than a still-air ambient, and the distinction is real. A
|
|
196
|
+
* source that answers zero everywhere is not "no wind": the drag row is a
|
|
197
|
+
* relaxation toward the air, so still air still resists a *moving* cloth,
|
|
198
|
+
* with the normal-facing factor making that resistance anisotropic. That is
|
|
199
|
+
* better physics than `ClothDynamics#damping`'s isotropic decay and it is
|
|
200
|
+
* not free — so it is a thing a scene asks for. What a cloth does in air
|
|
201
|
+
* that is not moving is `damping`'s job (§7 P5, where it is load-bearing);
|
|
202
|
+
* what it does in air that *is* is this.
|
|
203
|
+
*
|
|
204
|
+
* Assign {@link ClothAmbientWind} for weather, {@link ClothFluidWind} to
|
|
205
|
+
* read the fluid simulation and fall back to weather outside it.
|
|
206
|
+
* @type {AbstractClothWind|null}
|
|
207
|
+
*/
|
|
208
|
+
wind = null;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Seconds of fixed step this world has **stepped**, which is the clock a
|
|
212
|
+
* wind source's gust is a function of.
|
|
213
|
+
*
|
|
214
|
+
* Accumulated from `dt` rather than read from a wall clock, because
|
|
215
|
+
* {@link ClothSystem} and {@link WorkerClothSystem} share this object and
|
|
216
|
+
* §12's parity gate is over every byte of every step: two runs of the same
|
|
217
|
+
* scene have the same `dt` sequence and therefore the same weather.
|
|
218
|
+
*
|
|
219
|
+
* **Stepped rather than ticked**, and that distinction is load-bearing
|
|
220
|
+
* rather than pedantic. A cloth under the worker is not advanced until its
|
|
221
|
+
* registration is acknowledged (§39), so the two systems reach a cloth's
|
|
222
|
+
* *first* step at different tick counts — and a clock that counted ticks
|
|
223
|
+
* would hand that cloth a different gust on the same step under the two
|
|
224
|
+
* systems, which is a divergence the parity gate would report and the design
|
|
225
|
+
* would deserve. So the clock moves when {@link advance} first runs in a
|
|
226
|
+
* tick: the world's weather is a function of how much cloth time has
|
|
227
|
+
* passed, not of how long something has been asking.
|
|
228
|
+
* @type {number}
|
|
229
|
+
*/
|
|
230
|
+
time = 0;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Fixed-step seconds this tick owes the clock, consumed by the first
|
|
234
|
+
* {@link advance} — see {@link time}. Zero once spent.
|
|
235
|
+
* @type {number}
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
__wind_pending = 0;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Whether a cloth's {@link ClothState} must be backed by a
|
|
242
|
+
* `SharedArrayBuffer`. Stamped onto every instance as it is linked;
|
|
243
|
+
* {@link WorkerClothSystem} sets it, {@link ClothSystem} leaves it alone.
|
|
244
|
+
* @type {boolean}
|
|
245
|
+
*/
|
|
246
|
+
shared_state = false;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @type {Map<number, ClothInstance>}
|
|
250
|
+
* @private
|
|
251
|
+
*/
|
|
252
|
+
__by_entity = new Map();
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @type {boolean}
|
|
256
|
+
* @private
|
|
257
|
+
*/
|
|
258
|
+
__order_dirty = false;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Resolve the systems a cloth world reads. Called from the owning system's
|
|
262
|
+
* `startup`.
|
|
263
|
+
*
|
|
264
|
+
* @param {EntityManager} entityManager
|
|
265
|
+
*/
|
|
266
|
+
attach(entityManager) {
|
|
267
|
+
this.entityManager = entityManager;
|
|
268
|
+
|
|
269
|
+
this.attachments = entityManager.getSystem(TransformAttachmentSystem);
|
|
270
|
+
|
|
271
|
+
assert.notNull(
|
|
272
|
+
this.attachments,
|
|
273
|
+
'ClothSystem needs a TransformAttachmentSystem: the subtree walk is its parent-to-children index'
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Optional by design. A scene with no ClothColliderSystem gets cloth
|
|
277
|
+
// that does not collide, rather than cloth that throws.
|
|
278
|
+
this.colliders = entityManager.getSystem(ClothColliderSystem) ?? null;
|
|
279
|
+
|
|
280
|
+
if (this.models === null) {
|
|
281
|
+
this.models = entityManager.getSystem(MeshSystem) ?? null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @param {Cloth} cloth
|
|
287
|
+
* @param {Transform64} transform
|
|
288
|
+
* @param {number} entity
|
|
289
|
+
* @returns {ClothInstance}
|
|
290
|
+
*/
|
|
291
|
+
link(cloth, transform, entity) {
|
|
292
|
+
const instance = new ClothInstance();
|
|
293
|
+
|
|
294
|
+
instance.entity = entity;
|
|
295
|
+
instance.cloth = cloth;
|
|
296
|
+
instance.transform = transform;
|
|
297
|
+
instance.shared_state = this.shared_state;
|
|
298
|
+
|
|
299
|
+
this.instances.push(instance);
|
|
300
|
+
this.__by_entity.set(entity, instance);
|
|
301
|
+
|
|
302
|
+
this.__order_dirty = true;
|
|
303
|
+
|
|
304
|
+
return instance;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @param {number} entity
|
|
309
|
+
* @returns {ClothInstance|undefined} the instance that was removed
|
|
310
|
+
*/
|
|
311
|
+
unlink(entity) {
|
|
312
|
+
const instance = this.__by_entity.get(entity);
|
|
313
|
+
|
|
314
|
+
if (instance === undefined) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this.__by_entity.delete(entity);
|
|
319
|
+
|
|
320
|
+
const index = this.instances.indexOf(instance);
|
|
321
|
+
|
|
322
|
+
if (index >= 0) {
|
|
323
|
+
this.instances.splice(index, 1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return instance;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Throw away a cloth's binding so the next step walks its subtree again.
|
|
331
|
+
*
|
|
332
|
+
* The walk is done once, because a rope's links do not come and go. When
|
|
333
|
+
* they do — a chain gains a link, a rig is rebuilt — this is how you say so,
|
|
334
|
+
* and it is the same contract `TransformAttachment#parent` already carries:
|
|
335
|
+
* the structure is immutable while it is attached, and changing it is an
|
|
336
|
+
* explicit act rather than something the engine polls for.
|
|
337
|
+
*
|
|
338
|
+
* @param {number} entity
|
|
339
|
+
* @returns {ClothInstance|undefined} the instance that will re-seed
|
|
340
|
+
*/
|
|
341
|
+
reseed(entity) {
|
|
342
|
+
const instance = this.__by_entity.get(entity);
|
|
343
|
+
|
|
344
|
+
if (instance === undefined) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
instance.seeded = false;
|
|
349
|
+
instance.anchor_valid = false;
|
|
350
|
+
|
|
351
|
+
this.__order_dirty = true;
|
|
352
|
+
|
|
353
|
+
return instance;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Wake a sleeping cloth. Anchor motion and a `ClothDynamics` change do this
|
|
358
|
+
* on their own; a caller that has changed the world in some way the cloth
|
|
359
|
+
* cannot see — M6's wind, for one — says so here.
|
|
360
|
+
*
|
|
361
|
+
* @param {number} entity
|
|
362
|
+
* @returns {void}
|
|
363
|
+
*/
|
|
364
|
+
wake(entity) {
|
|
365
|
+
const instance = this.__by_entity.get(entity);
|
|
366
|
+
|
|
367
|
+
if (instance !== undefined) {
|
|
368
|
+
instance.asleep = false;
|
|
369
|
+
instance.still_steps = 0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @param {number} entity
|
|
375
|
+
* @returns {ClothInstance|undefined}
|
|
376
|
+
*/
|
|
377
|
+
instanceOf(entity) {
|
|
378
|
+
return this.__by_entity.get(entity);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Everything that happens once per tick rather than once per cloth.
|
|
383
|
+
*
|
|
384
|
+
* @param {EntityComponentDataset} ecd
|
|
385
|
+
* @param {number} dt seconds
|
|
386
|
+
*/
|
|
387
|
+
begin(ecd, dt) {
|
|
388
|
+
if (this.__order_dirty) {
|
|
389
|
+
this.#reorder(ecd);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Owed to the clock, and spent by the first `advance` — see {@link time}
|
|
393
|
+
// for why the two are not the same moment. Assigned rather than set, so a
|
|
394
|
+
// world whose source is taken away does not carry a debt.
|
|
395
|
+
this.__wind_pending = this.wind === null ? 0 : dt;
|
|
396
|
+
|
|
397
|
+
// The index is refreshed here rather than in its own system's fixed
|
|
398
|
+
// update, because the engine's ordering cannot put a reader after a
|
|
399
|
+
// writer of the same component and `ClothColliderSystem` reads strictly
|
|
400
|
+
// less than this system does. §4.1's conclusion, applied a second time:
|
|
401
|
+
// the answer is not a third mechanism, it is one call site.
|
|
402
|
+
if (this.colliders !== null) {
|
|
403
|
+
this.colliders.refresh();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Make sure this cloth has a `ClothState` that matches its current
|
|
409
|
+
* `ClothDynamics`, seeding or re-seeding it if not.
|
|
410
|
+
*
|
|
411
|
+
* @param {ClothInstance} instance
|
|
412
|
+
* @param {EntityComponentDataset} ecd
|
|
413
|
+
* @param {number} dt seconds
|
|
414
|
+
* @returns {boolean} whether the cloth is ready to be stepped — `false` for
|
|
415
|
+
* one that has been refused, or a rig whose model has not loaded yet
|
|
416
|
+
*/
|
|
417
|
+
ensure_seeded(instance, ecd, dt) {
|
|
418
|
+
if (instance.refused) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!instance.seeded) {
|
|
423
|
+
this.#seed(instance, ecd, dt);
|
|
424
|
+
|
|
425
|
+
if (!instance.seeded) {
|
|
426
|
+
// A rig whose model has not finished loading. Nothing is
|
|
427
|
+
// wrong and nothing is retried-with-backoff: the next step
|
|
428
|
+
// asks again, and a model that never loads costs one map
|
|
429
|
+
// lookup a step.
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
} else if (!instance.cloth.dynamics.equals(instance.dynamics_seen)) {
|
|
433
|
+
if (cloth_dynamics_change_needs_reseed(instance.dynamics_seen, instance.cloth.dynamics)) {
|
|
434
|
+
this.#seed(instance, ecd, dt);
|
|
435
|
+
} else {
|
|
436
|
+
cloth_reparameterise(instance, dt);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
instance.asleep = false;
|
|
440
|
+
instance.still_steps = 0;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Take what the step produced: the sleep test, and the pose onto whichever
|
|
448
|
+
* hierarchy this cloth was built on.
|
|
449
|
+
*
|
|
450
|
+
* @param {ClothInstance} instance
|
|
451
|
+
* @param {EntityComponentDataset} ecd
|
|
452
|
+
* @returns {void}
|
|
453
|
+
*/
|
|
454
|
+
finish(instance, ecd) {
|
|
455
|
+
const result = instance.state.result;
|
|
456
|
+
|
|
457
|
+
instance.contact_count = result[ClothStepResult.CONTACT_COUNT];
|
|
458
|
+
instance.self_pairs = result[ClothStepResult.SELF_PAIRS];
|
|
459
|
+
|
|
460
|
+
const speed = result[ClothStepResult.SPEED];
|
|
461
|
+
|
|
462
|
+
if ((instance.cloth.flags & ClothFlags.NeverSleep) === 0) {
|
|
463
|
+
if (speed < CLOTH_SLEEP_SPEED_RATIO * instance.reach) {
|
|
464
|
+
instance.still_steps++;
|
|
465
|
+
|
|
466
|
+
if (instance.still_steps >= CLOTH_SLEEP_STEPS) {
|
|
467
|
+
instance.asleep = true;
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
instance.still_steps = 0;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
instance.blend_seen = instance.cloth.blend;
|
|
475
|
+
|
|
476
|
+
if (instance.rig === null) {
|
|
477
|
+
cloth_write_back(instance, ecd);
|
|
478
|
+
} else {
|
|
479
|
+
cloth_write_back_rig(instance, ecd);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Walk whichever hierarchy this cloth is built on.
|
|
485
|
+
*
|
|
486
|
+
* **The discriminator is a component's presence** (§3.1): `Cloth` alone
|
|
487
|
+
* simulates the entity's own transform subtree, `Cloth` + {@link ClothRig}
|
|
488
|
+
* simulates the joints of the skinned model the entity is showing. There is
|
|
489
|
+
* no mode field anywhere, and the solver does not know which ran.
|
|
490
|
+
*
|
|
491
|
+
* A rig can fail to seed for reasons that are not errors — the model has
|
|
492
|
+
* not loaded, or nothing in the manager hands out model instances — and one
|
|
493
|
+
* that does is left unseeded for the next step to try again. A rig that
|
|
494
|
+
* fails for a reason that *is* an error says so once and then stops, because
|
|
495
|
+
* a proxy that does not fit its skin will not start fitting it.
|
|
496
|
+
*
|
|
497
|
+
* @param {ClothInstance} instance
|
|
498
|
+
* @param {EntityComponentDataset} ecd
|
|
499
|
+
* @param {number} dt
|
|
500
|
+
* @private
|
|
501
|
+
*/
|
|
502
|
+
#seed(instance, ecd, dt) {
|
|
503
|
+
const rig = ecd.getComponent(instance.entity, ClothRig);
|
|
504
|
+
|
|
505
|
+
if (rig === undefined || rig === null || rig.proxy === null) {
|
|
506
|
+
instance.rig = null;
|
|
507
|
+
|
|
508
|
+
cloth_seed_subtree(instance, ecd, this.attachments, dt);
|
|
509
|
+
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (this.models === null) {
|
|
514
|
+
this.#refuse(instance, `entity ${instance.entity} has a ClothRig and nothing hands out model instances`);
|
|
515
|
+
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const model = this.models.instance_of(instance.entity);
|
|
520
|
+
|
|
521
|
+
if (model === null || model === undefined) {
|
|
522
|
+
// Still loading. Not an error, and not worth a message.
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const skin = model.skins[rig.skin];
|
|
527
|
+
|
|
528
|
+
const error = rig.proxy.checkAgainst(skin);
|
|
529
|
+
|
|
530
|
+
if (error !== "") {
|
|
531
|
+
this.#refuse(instance, `entity ${instance.entity}: ${error}`);
|
|
532
|
+
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
instance.rig = rig;
|
|
537
|
+
|
|
538
|
+
cloth_seed_rig(instance, rig.proxy, skin.joints, ecd, dt);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Say once why a rig will not seed, and stop asking.
|
|
543
|
+
*
|
|
544
|
+
* @param {ClothInstance} instance
|
|
545
|
+
* @param {string} message
|
|
546
|
+
* @private
|
|
547
|
+
*/
|
|
548
|
+
#refuse(instance, message) {
|
|
549
|
+
if (instance.refused) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
instance.refused = true;
|
|
554
|
+
|
|
555
|
+
console.warn(`ClothSystem: ${message}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Depth-first order by hierarchy depth, so a nested cloth reads an anchor
|
|
560
|
+
* the cloth above it has already written.
|
|
561
|
+
*
|
|
562
|
+
* @param {EntityComponentDataset} ecd
|
|
563
|
+
* @private
|
|
564
|
+
*/
|
|
565
|
+
#reorder(ecd) {
|
|
566
|
+
const instances = this.instances;
|
|
567
|
+
|
|
568
|
+
for (let i = 0; i < instances.length; i++) {
|
|
569
|
+
const instance = instances[i];
|
|
570
|
+
|
|
571
|
+
let depth = 0;
|
|
572
|
+
let entity = instance.entity;
|
|
573
|
+
|
|
574
|
+
// Bounded by the number of entities; a cycle in the attachment
|
|
575
|
+
// graph is not this system's to diagnose, but it is this loop's to
|
|
576
|
+
// survive.
|
|
577
|
+
for (let guard = 0; guard < 4096; guard++) {
|
|
578
|
+
const attachment = ecd.getComponent(entity, TransformAttachment);
|
|
579
|
+
|
|
580
|
+
if (attachment === undefined) {
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
entity = attachment.parent;
|
|
585
|
+
depth++;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
instance.depth = depth;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
instances.sort((a, b) => a.depth - b.depth);
|
|
592
|
+
|
|
593
|
+
this.__order_dirty = false;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Carry one seeded cloth to the edge of its step and write the parameters
|
|
598
|
+
* the step will run on: whatever the anchor did to the local frame, where
|
|
599
|
+
* the leash rows measure from, and the colliders within reach.
|
|
600
|
+
*
|
|
601
|
+
* @param {ClothInstance} instance
|
|
602
|
+
* @param {number} dt seconds
|
|
603
|
+
* @param {Float64Array} params
|
|
604
|
+
* @param {number} offset first word of this cloth's block in `params`
|
|
605
|
+
* @returns {boolean} whether the cloth should be stepped — `false` for one
|
|
606
|
+
* that is asleep, and then nothing has been written to `params`
|
|
607
|
+
*/
|
|
608
|
+
advance(instance, dt, params, offset) {
|
|
609
|
+
if (this.__wind_pending !== 0) {
|
|
610
|
+
this.time += this.__wind_pending;
|
|
611
|
+
|
|
612
|
+
this.__wind_pending = 0;
|
|
613
|
+
|
|
614
|
+
// Once a tick rather than once a cloth: a source that reads the
|
|
615
|
+
// dataset — every live fluid field and its world box — walks it here
|
|
616
|
+
// and every cloth then reads the cache. `FluidSystem` makes the same
|
|
617
|
+
// trade for its own effector broadphase.
|
|
618
|
+
//
|
|
619
|
+
// Here rather than in `begin` because of the clock, and {@link time}
|
|
620
|
+
// is where that is argued. Above the sleep tests deliberately: a
|
|
621
|
+
// world whose every cloth is asleep still has weather, and §10's
|
|
622
|
+
// "sleep wakes on a wind change" is the whole reason it needs to.
|
|
623
|
+
if (this.wind !== null) {
|
|
624
|
+
this.wind.begin(
|
|
625
|
+
this.time,
|
|
626
|
+
this.entityManager === null ? null : this.entityManager.dataset
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const transform = instance.rig === null
|
|
632
|
+
? instance.transform
|
|
633
|
+
: this.#anchor_pose(instance);
|
|
634
|
+
|
|
635
|
+
const tx = transform[TRANSFORM64_TRANSLATION_OFFSET];
|
|
636
|
+
const ty = transform[TRANSFORM64_TRANSLATION_OFFSET + 1];
|
|
637
|
+
const tz = transform[TRANSFORM64_TRANSLATION_OFFSET + 2];
|
|
638
|
+
|
|
639
|
+
const qx = transform[TRANSFORM64_ROTATION_OFFSET];
|
|
640
|
+
const qy = transform[TRANSFORM64_ROTATION_OFFSET + 1];
|
|
641
|
+
const qz = transform[TRANSFORM64_ROTATION_OFFSET + 2];
|
|
642
|
+
const qw = transform[TRANSFORM64_ROTATION_OFFSET + 3];
|
|
643
|
+
|
|
644
|
+
const previous_translation = instance.anchor_translation;
|
|
645
|
+
const previous_rotation = instance.anchor_rotation;
|
|
646
|
+
|
|
647
|
+
if (instance.cloth.blend !== instance.blend_seen) {
|
|
648
|
+
instance.asleep = false;
|
|
649
|
+
instance.still_steps = 0;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const moved = instance.anchor_valid && (
|
|
653
|
+
previous_translation[0] !== tx
|
|
654
|
+
|| previous_translation[1] !== ty
|
|
655
|
+
|| previous_translation[2] !== tz
|
|
656
|
+
|| previous_rotation[0] !== qx
|
|
657
|
+
|| previous_rotation[1] !== qy
|
|
658
|
+
|| previous_rotation[2] !== qz
|
|
659
|
+
|| previous_rotation[3] !== qw
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
if (moved) {
|
|
663
|
+
instance.asleep = false;
|
|
664
|
+
instance.still_steps = 0;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (instance.asleep && this.colliders !== null && instance.state !== null) {
|
|
668
|
+
// §7 P5's "wake on collider motion". A sleeping cloth is neither
|
|
669
|
+
// stepped nor written back, so something has to notice the world it
|
|
670
|
+
// was resting against moving out from under it, and a broadphase
|
|
671
|
+
// query plus a pose comparison is three orders of magnitude cheaper
|
|
672
|
+
// than the step it decides whether to skip.
|
|
673
|
+
if (cloth_colliders_moved(instance, this.colliders.index)) {
|
|
674
|
+
instance.asleep = false;
|
|
675
|
+
instance.still_steps = 0;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (instance.asleep && this.wind !== null && instance.state !== null
|
|
680
|
+
&& instance.cloth.dynamics.drag > 0) {
|
|
681
|
+
// §10's "sleep wakes on a wind change, so a settled banner does not
|
|
682
|
+
// sleep through a gust". One sample at the cloth's centre against
|
|
683
|
+
// the air the last applied step saw — the same shape as the collider
|
|
684
|
+
// test above, and the same argument: asking is orders of magnitude
|
|
685
|
+
// cheaper than the step it decides whether to skip.
|
|
686
|
+
if (cloth_air_changed(
|
|
687
|
+
instance.state, this.wind,
|
|
688
|
+
instance.anchor_translation, instance.anchor_rotation,
|
|
689
|
+
instance.air_seen, CLOTH_SLEEP_SPEED_RATIO * instance.reach
|
|
690
|
+
)) {
|
|
691
|
+
instance.asleep = false;
|
|
692
|
+
instance.still_steps = 0;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (instance.asleep) {
|
|
697
|
+
// Nothing is stepped and nothing is written: the pose on the
|
|
698
|
+
// subtree is already the pose, and rewriting it is what would make
|
|
699
|
+
// §7 P5's "bit-identical across the following 300 steps" an
|
|
700
|
+
// aspiration rather than a fact.
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (instance.anchor_valid) {
|
|
705
|
+
const displacement = Math.hypot(tx - previous_translation[0], ty - previous_translation[1], tz - previous_translation[2]);
|
|
706
|
+
|
|
707
|
+
const teleport = displacement > Math.max(
|
|
708
|
+
CLOTH_TELEPORT_REACH * instance.reach,
|
|
709
|
+
CLOTH_TELEPORT_FLOOR
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
if (teleport) {
|
|
713
|
+
this.#teleport(instance);
|
|
714
|
+
} else {
|
|
715
|
+
move_local_frame(
|
|
716
|
+
instance,
|
|
717
|
+
tx, ty, tz, qx, qy, qz, qw,
|
|
718
|
+
previous_translation, previous_rotation
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
previous_translation[0] = tx;
|
|
724
|
+
previous_translation[1] = ty;
|
|
725
|
+
previous_translation[2] = tz;
|
|
726
|
+
|
|
727
|
+
previous_rotation[0] = qx;
|
|
728
|
+
previous_rotation[1] = qy;
|
|
729
|
+
previous_rotation[2] = qz;
|
|
730
|
+
previous_rotation[3] = qw;
|
|
731
|
+
|
|
732
|
+
instance.anchor_valid = true;
|
|
733
|
+
|
|
734
|
+
refresh_leash_targets(instance);
|
|
735
|
+
|
|
736
|
+
const dynamics = instance.cloth.dynamics;
|
|
737
|
+
|
|
738
|
+
// Gravity is a world vector and the solve is in the anchor's frame.
|
|
739
|
+
const gravity = this.gravity;
|
|
740
|
+
|
|
741
|
+
v3_quaternion_apply_inverse(LOCAL_GRAVITY, 0, gravity[0], gravity[1], gravity[2], qx, qy, qz, qw);
|
|
742
|
+
|
|
743
|
+
const state = instance.state;
|
|
744
|
+
|
|
745
|
+
const limit = cloth_velocity_limit(
|
|
746
|
+
instance.shortest_row, dynamics.substeps, dynamics.iterations, dt
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
state.contact_radius = dynamics.thickness;
|
|
750
|
+
|
|
751
|
+
if (this.colliders !== null) {
|
|
752
|
+
// The gather's margin is how far a particle may travel this step,
|
|
753
|
+
// which is the velocity clamp read as a distance — the quantity it
|
|
754
|
+
// was built out of (`cloth_dynamics_map.js`).
|
|
755
|
+
cloth_gather_colliders(instance, this.colliders.index, limit * dt);
|
|
756
|
+
} else {
|
|
757
|
+
state.collider_count = 0;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// §10. The one piece of a cloth's step that has to read the world and
|
|
761
|
+
// cannot cross to a worker, so it runs here in both systems and what
|
|
762
|
+
// crosses is the answer — three floats a particle, written into a region
|
|
763
|
+
// of the cloth's own shared buffer. `cloth_sample_air.js` is the
|
|
764
|
+
// argument.
|
|
765
|
+
let drag_response = 0;
|
|
766
|
+
|
|
767
|
+
if (this.wind !== null && dynamics.drag > 0) {
|
|
768
|
+
drag_response = cloth_drag_response(dynamics.drag, dt);
|
|
769
|
+
|
|
770
|
+
instance.air_varies = cloth_sample_air(
|
|
771
|
+
state, this.wind,
|
|
772
|
+
instance.anchor_translation, instance.anchor_rotation,
|
|
773
|
+
instance.air_seen
|
|
774
|
+
);
|
|
775
|
+
} else {
|
|
776
|
+
instance.air_varies = false;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
params[offset + ClothStepParam.DRAG_RESPONSE] = drag_response;
|
|
780
|
+
|
|
781
|
+
const self_collision = (dynamics.flags & ClothDynamicsFlags.SelfCollision) !== 0;
|
|
782
|
+
|
|
783
|
+
params[offset + ClothStepParam.DT] = dt;
|
|
784
|
+
params[offset + ClothStepParam.GRAVITY_X] = LOCAL_GRAVITY[0];
|
|
785
|
+
params[offset + ClothStepParam.GRAVITY_Y] = LOCAL_GRAVITY[1];
|
|
786
|
+
params[offset + ClothStepParam.GRAVITY_Z] = LOCAL_GRAVITY[2];
|
|
787
|
+
params[offset + ClothStepParam.SUBSTEPS] = dynamics.substeps;
|
|
788
|
+
params[offset + ClothStepParam.ITERATIONS] = dynamics.iterations;
|
|
789
|
+
params[offset + ClothStepParam.RETENTION] = cloth_velocity_retention(dynamics.damping, dt);
|
|
790
|
+
params[offset + ClothStepParam.CONTACT_RADIUS] = dynamics.thickness;
|
|
791
|
+
params[offset + ClothStepParam.COLLIDER_COUNT] = state.collider_count;
|
|
792
|
+
params[offset + ClothStepParam.SELF_COLLISION] = self_collision ? 1 : 0;
|
|
793
|
+
|
|
794
|
+
// Half the shortest row, so the row's diameter is one particle
|
|
795
|
+
// spacing. `ClothState#self_radius` carries why the fabric's own
|
|
796
|
+
// thickness is the wrong length here and `MEASUREMENTS.md` §31 is the
|
|
797
|
+
// sweep that settled it. A fabric thicker than its proxy is coarse
|
|
798
|
+
// keeps its thickness — and is then clamped under the closest pair
|
|
799
|
+
// the proxy has with no row between them, because a diameter that
|
|
800
|
+
// reaches that pair puts a cloth which is not folded at all in
|
|
801
|
+
// permanent contact with itself (§36, §37).
|
|
802
|
+
params[offset + ClothStepParam.SELF_RADIUS] = self_collision
|
|
803
|
+
? Math.min(
|
|
804
|
+
Math.max(dynamics.thickness, instance.shortest_row * CLOTH_SELF_RADIUS_MIN_RATIO),
|
|
805
|
+
instance.closest_free_pair * CLOTH_SELF_RADIUS_MAX_RATIO
|
|
806
|
+
)
|
|
807
|
+
: 0;
|
|
808
|
+
|
|
809
|
+
// Ramp back from nothing over the window. The first step after a
|
|
810
|
+
// reset is allowed no velocity at all, which is what stops a
|
|
811
|
+
// re-seeded cloth from snapping taut and whipping.
|
|
812
|
+
//
|
|
813
|
+
// After the gather, which wants the cloth's *unramped* reach: the
|
|
814
|
+
// broadphase margin is how far a particle could travel, and a cloth
|
|
815
|
+
// that is being held still for three steps is still a cloth whose
|
|
816
|
+
// colliders have to be found before it is let go.
|
|
817
|
+
let clamp_limit = limit;
|
|
818
|
+
|
|
819
|
+
if (instance.stabilise_remaining > 0) {
|
|
820
|
+
clamp_limit *= (CLOTH_STABILISE_STEPS - instance.stabilise_remaining) / CLOTH_STABILISE_STEPS;
|
|
821
|
+
|
|
822
|
+
instance.stabilise_remaining--;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
params[offset + ClothStepParam.VELOCITY_LIMIT] = clamp_limit;
|
|
826
|
+
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* The anchor joint's world pose, computed on the CPU from the clips the
|
|
832
|
+
* entity is playing.
|
|
833
|
+
*
|
|
834
|
+
* **Computed, never read back**, for two reasons that are both the engine's
|
|
835
|
+
* own. A joint of an animated skeleton is the GPU's (`GPUStateAuthority`),
|
|
836
|
+
* so nothing maintains its CPU-side world and reading it gives its rest
|
|
837
|
+
* pose under whatever the CPU last composed. And GPU readback is a frame
|
|
838
|
+
* late and asynchronous, so anything depending on it would depend on frame
|
|
839
|
+
* pacing — which is why `query_entity_node_world_pose` exists and is
|
|
840
|
+
* documented as the only sanctioned way to ask this question. A cape and a
|
|
841
|
+
* scabbard on the same shoulder get their answer from the same evaluator,
|
|
842
|
+
* off the same clock, which is what keeps them on the same shoulder.
|
|
843
|
+
*
|
|
844
|
+
* @param {ClothInstance} instance
|
|
845
|
+
* @returns {Transform64}
|
|
846
|
+
* @private
|
|
847
|
+
*/
|
|
848
|
+
#anchor_pose(instance) {
|
|
849
|
+
const playbacks = PLAYBACKS;
|
|
850
|
+
|
|
851
|
+
playbacks.length = 0;
|
|
852
|
+
|
|
853
|
+
collect_entity_playbacks(playbacks, this.entityManager, instance.entity);
|
|
854
|
+
|
|
855
|
+
t64_evaluate_world(instance.anchor_pose, this.entityManager.dataset, instance.node_joint[0], playbacks);
|
|
856
|
+
|
|
857
|
+
playbacks.length = 0;
|
|
858
|
+
|
|
859
|
+
return instance.anchor_pose;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* @param {ClothInstance} instance
|
|
864
|
+
* @private
|
|
865
|
+
*/
|
|
866
|
+
#teleport(instance) {
|
|
867
|
+
instance.teleport_count++;
|
|
868
|
+
|
|
869
|
+
if ((instance.cloth.flags & ClothFlags.TeleportKeep) !== 0) {
|
|
870
|
+
// `Keep` carries the cloth's shape across the jump. Particles are
|
|
871
|
+
// stored relative to the anchor, so that is exactly what happens if
|
|
872
|
+
// this function does nothing to them — the frame moved and they did
|
|
873
|
+
// not, which is MagicaCloth2's rigid delta expressed in the frame
|
|
874
|
+
// that makes it free.
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const state = instance.state;
|
|
879
|
+
|
|
880
|
+
const node_count = instance.node_count;
|
|
881
|
+
const animated = instance.animated_position;
|
|
882
|
+
const particle_of_node = instance.particle_of_node;
|
|
883
|
+
|
|
884
|
+
for (let node = 0; node < node_count; node++) {
|
|
885
|
+
const p = particle_of_node[node] * 3;
|
|
886
|
+
const n = node * 3;
|
|
887
|
+
|
|
888
|
+
state.position[p] = animated[n];
|
|
889
|
+
state.position[p + 1] = animated[n + 1];
|
|
890
|
+
state.position[p + 2] = animated[n + 2];
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
state.position_previous.set(state.position);
|
|
894
|
+
state.velocity.fill(0);
|
|
895
|
+
state.inertia_blend.fill(0);
|
|
896
|
+
|
|
897
|
+
// The multipliers describe forces in a configuration that no longer
|
|
898
|
+
// exists. Carrying them across would apply the old pose's tension to
|
|
899
|
+
// the new one on the first iteration, which is a whip with extra steps.
|
|
900
|
+
state.stretch_lambda.fill(0);
|
|
901
|
+
state.bend_lambda.fill(0);
|
|
902
|
+
state.leash_lambda.fill(0);
|
|
903
|
+
|
|
904
|
+
instance.stabilise_remaining = CLOTH_STABILISE_STEPS;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* The clip set handed to the pose evaluator, rebuilt per call from the
|
|
910
|
+
* animation systems' own entries. Nothing in here outlives the call.
|
|
911
|
+
* @type {PosePlayback[]}
|
|
912
|
+
* @private
|
|
913
|
+
*/
|
|
914
|
+
const PLAYBACKS = [];
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* @type {Float64Array}
|
|
918
|
+
* @private
|
|
919
|
+
*/
|
|
920
|
+
const LOCAL_GRAVITY = new Float64Array(3);
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* @type {Float64Array}
|
|
924
|
+
* @private
|
|
925
|
+
*/
|
|
926
|
+
const FRAME = new Float64Array(8);
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Carry the particles across a change of anchor pose, by however much of it the
|
|
930
|
+
* fabric is willing to feel. §7 P7.
|
|
931
|
+
*
|
|
932
|
+
* A particle at local `p` was at world `t_prev + q_prev·p`. Holding that world
|
|
933
|
+
* position fixed, its local coordinates become `D + Q·p` with
|
|
934
|
+
* `D = q_now⁻¹·(t_prev − t_now)` and `Q = q_now⁻¹ ⊗ q_prev`. Feeling *all* of
|
|
935
|
+
* the anchor's motion is taking that; feeling *none* is leaving `p` alone; and
|
|
936
|
+
* `inertia` is the lerp between them. Velocity is carried the same way, minus
|
|
937
|
+
* the translation, since a translation of the frame does not rotate a vector.
|
|
938
|
+
*
|
|
939
|
+
* `D` and `Q` are limited before the lerp. The limits are the cloth's own reach
|
|
940
|
+
* per step and a fixed angle per step, which is what makes an anchor that
|
|
941
|
+
* teleports a metre — or spins — something a cape *lags behind* rather than
|
|
942
|
+
* something it is fired out of.
|
|
943
|
+
*
|
|
944
|
+
* @param {ClothInstance} instance
|
|
945
|
+
* @param {number} tx
|
|
946
|
+
* @param {number} ty
|
|
947
|
+
* @param {number} tz
|
|
948
|
+
* @param {number} qx
|
|
949
|
+
* @param {number} qy
|
|
950
|
+
* @param {number} qz
|
|
951
|
+
* @param {number} qw
|
|
952
|
+
* @param {Float64Array} previous_translation
|
|
953
|
+
* @param {Float64Array} previous_rotation
|
|
954
|
+
* @private
|
|
955
|
+
*/
|
|
956
|
+
function move_local_frame(
|
|
957
|
+
instance,
|
|
958
|
+
tx, ty, tz, qx, qy, qz, qw,
|
|
959
|
+
previous_translation, previous_rotation
|
|
960
|
+
) {
|
|
961
|
+
let inertia = instance.cloth.dynamics.inertia;
|
|
962
|
+
|
|
963
|
+
if (inertia <= 0) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (inertia > 1) {
|
|
968
|
+
inertia = 1;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
v3_quaternion_apply_inverse(
|
|
972
|
+
FRAME, 0,
|
|
973
|
+
previous_translation[0] - tx,
|
|
974
|
+
previous_translation[1] - ty,
|
|
975
|
+
previous_translation[2] - tz,
|
|
976
|
+
qx, qy, qz, qw
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
const travel_limit = CLOTH_ANCHOR_TRAVEL_REACH * instance.reach;
|
|
980
|
+
|
|
981
|
+
const travel = Math.hypot(FRAME[0], FRAME[1], FRAME[2]);
|
|
982
|
+
|
|
983
|
+
if (travel > travel_limit && travel > 0) {
|
|
984
|
+
const scale = travel_limit / travel;
|
|
985
|
+
|
|
986
|
+
FRAME[0] *= scale;
|
|
987
|
+
FRAME[1] *= scale;
|
|
988
|
+
FRAME[2] *= scale;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
quat3_multiply(
|
|
992
|
+
FRAME, 4,
|
|
993
|
+
-qx, -qy, -qz, qw,
|
|
994
|
+
previous_rotation[0], previous_rotation[1], previous_rotation[2], previous_rotation[3]
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
// `|w|` is `cos(θ/2)`, so this is the angle test without an `acos`.
|
|
998
|
+
const half_angle_cos = FRAME[7] < 0 ? -FRAME[7] : FRAME[7];
|
|
999
|
+
|
|
1000
|
+
if (half_angle_cos < Math.cos(CLOTH_ANCHOR_ROTATION_LIMIT * 0.5)) {
|
|
1001
|
+
const angle = 2 * Math.acos(half_angle_cos > 1 ? 1 : half_angle_cos);
|
|
1002
|
+
|
|
1003
|
+
quat3_nlerp(
|
|
1004
|
+
FRAME, 4,
|
|
1005
|
+
0, 0, 0, 1,
|
|
1006
|
+
FRAME[4], FRAME[5], FRAME[6], FRAME[7],
|
|
1007
|
+
CLOTH_ANCHOR_ROTATION_LIMIT / angle
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const state = instance.state;
|
|
1012
|
+
|
|
1013
|
+
const particle_count = state.particle_count;
|
|
1014
|
+
|
|
1015
|
+
const position = state.position;
|
|
1016
|
+
const velocity = state.velocity;
|
|
1017
|
+
|
|
1018
|
+
const dx = FRAME[0];
|
|
1019
|
+
const dy = FRAME[1];
|
|
1020
|
+
const dz = FRAME[2];
|
|
1021
|
+
|
|
1022
|
+
const rx = FRAME[4];
|
|
1023
|
+
const ry = FRAME[5];
|
|
1024
|
+
const rz = FRAME[6];
|
|
1025
|
+
const rw = FRAME[7];
|
|
1026
|
+
|
|
1027
|
+
for (let i = 0; i < particle_count; i++) {
|
|
1028
|
+
const p = i * 3;
|
|
1029
|
+
|
|
1030
|
+
v3_quaternion_apply(CARRIED, 0, position[p], position[p + 1], position[p + 2], rx, ry, rz, rw);
|
|
1031
|
+
|
|
1032
|
+
position[p] += inertia * (CARRIED[0] + dx - position[p]);
|
|
1033
|
+
position[p + 1] += inertia * (CARRIED[1] + dy - position[p + 1]);
|
|
1034
|
+
position[p + 2] += inertia * (CARRIED[2] + dz - position[p + 2]);
|
|
1035
|
+
|
|
1036
|
+
v3_quaternion_apply(CARRIED, 0, velocity[p], velocity[p + 1], velocity[p + 2], rx, ry, rz, rw);
|
|
1037
|
+
|
|
1038
|
+
velocity[p] += inertia * (CARRIED[0] - velocity[p]);
|
|
1039
|
+
velocity[p + 1] += inertia * (CARRIED[1] - velocity[p + 1]);
|
|
1040
|
+
velocity[p + 2] += inertia * (CARRIED[2] - velocity[p + 2]);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// The anchor is pinned and is the origin of the frame it anchors, so it is
|
|
1044
|
+
// the one particle that must not be carried anywhere.
|
|
1045
|
+
const anchor = instance.particle_of_node[0] * 3;
|
|
1046
|
+
|
|
1047
|
+
position[anchor] = 0;
|
|
1048
|
+
position[anchor + 1] = 0;
|
|
1049
|
+
position[anchor + 2] = 0;
|
|
1050
|
+
|
|
1051
|
+
velocity[anchor] = 0;
|
|
1052
|
+
velocity[anchor + 1] = 0;
|
|
1053
|
+
velocity[anchor + 2] = 0;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* @type {Float64Array}
|
|
1058
|
+
* @private
|
|
1059
|
+
*/
|
|
1060
|
+
const CARRIED = new Float64Array(3);
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Point every leash row at where it measures from: a slack row at the
|
|
1064
|
+
* particle's animated position, a tether at the anchor.
|
|
1065
|
+
*
|
|
1066
|
+
* @param {ClothInstance} instance
|
|
1067
|
+
* @private
|
|
1068
|
+
*/
|
|
1069
|
+
function refresh_leash_targets(instance) {
|
|
1070
|
+
const state = instance.state;
|
|
1071
|
+
|
|
1072
|
+
const target = state.leash_target;
|
|
1073
|
+
|
|
1074
|
+
const animated = instance.animated_position;
|
|
1075
|
+
|
|
1076
|
+
const anchor = instance.particle_of_node[0] * 3;
|
|
1077
|
+
|
|
1078
|
+
const anchor_x = state.position[anchor];
|
|
1079
|
+
const anchor_y = state.position[anchor + 1];
|
|
1080
|
+
const anchor_z = state.position[anchor + 2];
|
|
1081
|
+
|
|
1082
|
+
const node_count = instance.node_count;
|
|
1083
|
+
|
|
1084
|
+
const slack_row = instance.slack_row;
|
|
1085
|
+
const tether_row = instance.tether_row;
|
|
1086
|
+
|
|
1087
|
+
for (let node = 1; node < node_count; node++) {
|
|
1088
|
+
const tether = tether_row[node];
|
|
1089
|
+
|
|
1090
|
+
if (tether >= 0) {
|
|
1091
|
+
target[tether * 3] = anchor_x;
|
|
1092
|
+
target[tether * 3 + 1] = anchor_y;
|
|
1093
|
+
target[tether * 3 + 2] = anchor_z;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const slack = slack_row[node];
|
|
1097
|
+
|
|
1098
|
+
if (slack >= 0) {
|
|
1099
|
+
target[slack * 3] = animated[node * 3];
|
|
1100
|
+
target[slack * 3 + 1] = animated[node * 3 + 1];
|
|
1101
|
+
target[slack * 3 + 2] = animated[node * 3 + 2];
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|