@series-inc/rundot-syncplay 5.28.0-beta.2 → 5.28.0-beta.4
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/core/physics.d.ts +1 -0
- package/core/src/physics/index.mjs +1 -0
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +1 -1
- package/dist/creator.d.ts +3 -3
- package/dist/creator.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/physics3d-joints.d.ts +9 -0
- package/dist/physics3d-joints.js +170 -5
- package/dist/physics3d-shared.js +26 -7
- package/dist/physics3d-solver.d.ts +21 -0
- package/dist/physics3d-solver.js +309 -0
- package/dist/physics3d-voxel.d.ts +13 -0
- package/dist/physics3d-voxel.js +124 -0
- package/dist/physics3d.d.ts +5 -0
- package/dist/physics3d.js +49 -2
- package/dist/runner.d.ts +10 -0
- package/dist/runner.js +20 -1
- package/package.json +2 -2
package/core/physics.d.ts
CHANGED
|
@@ -276,6 +276,7 @@ export interface KinetixPhysicsStepResult {
|
|
|
276
276
|
readonly world: KinetixPhysicsWorld;
|
|
277
277
|
readonly contacts: readonly KinetixPhysicsContactEvent[];
|
|
278
278
|
readonly brokenJointIds: readonly string[];
|
|
279
|
+
readonly brokenJoints: readonly string[];
|
|
279
280
|
readonly scheduledQueryResults: readonly { readonly id: string; readonly hits: readonly KinetixPhysicsHit[] }[];
|
|
280
281
|
readonly prePhysicsScheduledQueryResults: readonly { readonly id: string; readonly hits: readonly KinetixPhysicsHit[] }[];
|
|
281
282
|
readonly postPhysicsScheduledQueryResults: readonly { readonly id: string; readonly hits: readonly KinetixPhysicsHit[] }[];
|
|
@@ -2953,6 +2953,7 @@ export function stepPhysicsWorld(world, options) {
|
|
|
2953
2953
|
world: nextWorld,
|
|
2954
2954
|
contacts: visibleEvents,
|
|
2955
2955
|
brokenJointIds,
|
|
2956
|
+
brokenJoints: brokenJointIds,
|
|
2956
2957
|
scheduledQueryResults,
|
|
2957
2958
|
prePhysicsScheduledQueryResults,
|
|
2958
2959
|
postPhysicsScheduledQueryResults,
|
package/dist/browser.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export { createSyncplayRunner } from './runner.js';
|
|
|
25
25
|
export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
|
|
26
26
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
27
27
|
export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
28
|
-
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
|
|
28
|
+
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
|
|
29
29
|
export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
|
|
30
30
|
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
31
31
|
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
@@ -66,8 +66,8 @@ export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, Determinist
|
|
|
66
66
|
export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, NetworkedSyncplaySlotPresence, PreparedNetworkedSyncplayTransport, SyncplayPredictionMode, SyncplayRemoteInputPrediction } from './networked-client.js';
|
|
67
67
|
export type { SyncplayRunner, SyncplayRunnerConfig, SyncplayRunnerConnectionEvent, SyncplayRunnerOccupancy, SyncplayRunnerOfflineOptions, SyncplayRunnerPresentation, SyncplayRunnerSnapshot, SyncplayRunnerStatus, SyncplayRunnerTransport } from './runner.js';
|
|
68
68
|
export type { DeterministicBodyKind, DeterministicContactEvent2D, DeterministicContactEventType, DeterministicPhysicsAabbQuery2D, DeterministicPhysicsActivePair2D, DeterministicPhysicsBody2D, DeterministicPhysicsCircleCastQuery2D, DeterministicPhysicsConstraint2D, DeterministicPhysicsHit2D, DeterministicPhysicsQueryOptions2D, DeterministicPhysicsRaycastQuery2D, DeterministicPhysicsShape2D, DeterministicPhysicsShapeOverlapQuery2D, DeterministicPhysicsStepOptions2D, DeterministicPhysicsStepResult2D, DeterministicPhysicsWorld2D } from './physics2d.js';
|
|
69
|
-
export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
|
|
70
|
-
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
|
|
69
|
+
export type { DeterministicContactCacheEntry3D, DeterministicFluidVolume3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsAabb3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
|
|
70
|
+
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelCompoundSplitResult3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
|
|
71
71
|
export type { PhysicsWorldDebugMesh, PhysicsWorldDebugMeshOptions } from './debug-wireframe.js';
|
|
72
72
|
export type { DeterministicAuthoritativeBodyProxy3D, DeterministicCosmeticPhysicsBody3D, DeterministicCosmeticPhysicsStepOptions3D, DeterministicCosmeticPhysicsStepResult3D, DeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
73
73
|
export type { DeterministicPhysicsJoint3D } from './physics3d-joints.js';
|
package/dist/browser.js
CHANGED
|
@@ -26,7 +26,7 @@ export { createSyncplayRunner } from './runner.js';
|
|
|
26
26
|
export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
|
|
27
27
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
28
28
|
export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
29
|
-
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
|
|
29
|
+
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
|
|
30
30
|
export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
|
|
31
31
|
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
32
32
|
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
package/dist/creator.d.ts
CHANGED
|
@@ -13,13 +13,13 @@ export type { ReplayRuntimeFactory, ReplayVerificationConfig, ReplayVerification
|
|
|
13
13
|
export type { SyncplaySession, SyncplaySessionPolicy } from './sdk-session.js';
|
|
14
14
|
export type { InstantReplayInputFrame, InstantReplayResult, InstantReplayRunner, InstantReplayRunnerOptions, InstantReplayRunOptions, RunInstantReplayOptions, } from './instant-replay.js';
|
|
15
15
|
export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D, } from './physics3d.js';
|
|
16
|
-
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, } from './physics3d-voxel.js';
|
|
16
|
+
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D, } from './physics3d-voxel.js';
|
|
17
17
|
export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
|
|
18
18
|
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
19
19
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
|
20
20
|
export { createDeterministicBinarySerializer, deterministicDslMaxPlayers, parseDeterministicSchemaDsl, stableDeterministicDslSchemaJson, } from './schema-dsl-binary.js';
|
|
21
|
-
export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D, } from './physics3d.js';
|
|
22
|
-
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D, } from './physics3d-voxel.js';
|
|
21
|
+
export type { DeterministicContactCacheEntry3D, DeterministicFluidVolume3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsAabb3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D, } from './physics3d.js';
|
|
22
|
+
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelCompoundSplitResult3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D, } from './physics3d-voxel.js';
|
|
23
23
|
export type { PhysicsWorldDebugMesh, PhysicsWorldDebugMeshOptions } from './debug-wireframe.js';
|
|
24
24
|
export type { DeterministicPhysicsJoint3D } from './physics3d-joints.js';
|
|
25
25
|
export type { DeterministicVehicle3D, DeterministicVehicleImpulse3D, DeterministicVehicleInput3D, DeterministicVehicleStepOptions3D, DeterministicVehicleStepResult3D, DeterministicVehicleWheel3D, DeterministicVehicleWheelTelemetry3D, } from './physics3d-vehicle.js';
|
package/dist/creator.js
CHANGED
|
@@ -10,7 +10,7 @@ export { runSyncplaySynctest } from './synctest.js';
|
|
|
10
10
|
// cooking. Cosmetic physics, KCC (movement3d), and the destruction-cert runner
|
|
11
11
|
// stay off it — the first two are not requested, the third reads wall clock.
|
|
12
12
|
export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D, } from './physics3d.js';
|
|
13
|
-
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, } from './physics3d-voxel.js';
|
|
13
|
+
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D, } from './physics3d-voxel.js';
|
|
14
14
|
export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
|
|
15
15
|
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
16
16
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export { createSyncplayRunner } from './runner.js';
|
|
|
28
28
|
export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
|
|
29
29
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
30
30
|
export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
31
|
-
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
|
|
31
|
+
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
|
|
32
32
|
export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
|
|
33
33
|
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
34
34
|
export { runDeterministicPhysics3DDestructionCert } from './physics3d-destruction-cert.js';
|
|
@@ -78,8 +78,8 @@ export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, Determinist
|
|
|
78
78
|
export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, PreparedNetworkedSyncplayTransport, SyncplayPredictionMode, SyncplayRemoteInputPrediction } from './networked-client.js';
|
|
79
79
|
export type { SyncplayRunner, SyncplayRunnerConfig, SyncplayRunnerConnectionEvent, SyncplayRunnerOccupancy, SyncplayRunnerOfflineOptions, SyncplayRunnerPresentation, SyncplayRunnerSnapshot, SyncplayRunnerStatus, SyncplayRunnerTransport } from './runner.js';
|
|
80
80
|
export type { DeterministicBodyKind, DeterministicContactEvent2D, DeterministicContactEventType, DeterministicPhysicsAabbQuery2D, DeterministicPhysicsBody2D, DeterministicPhysicsCircleCastQuery2D, DeterministicPhysicsConstraint2D, DeterministicPhysicsHit2D, DeterministicPhysicsQueryOptions2D, DeterministicPhysicsRaycastQuery2D, DeterministicPhysicsShape2D, DeterministicPhysicsShapeOverlapQuery2D, DeterministicPhysicsStepOptions2D, DeterministicPhysicsStepResult2D, DeterministicPhysicsWorld2D } from './physics2d.js';
|
|
81
|
-
export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
|
|
82
|
-
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
|
|
81
|
+
export type { DeterministicContactCacheEntry3D, DeterministicFluidVolume3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsAabb3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
|
|
82
|
+
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelCompoundSplitResult3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
|
|
83
83
|
export type { PhysicsWorldDebugMesh, PhysicsWorldDebugMeshOptions } from './debug-wireframe.js';
|
|
84
84
|
export type { DeterministicDestructionCertOptions, DeterministicDestructionCertResult } from './physics3d-destruction-cert.js';
|
|
85
85
|
export type { DeterministicAuthoritativeBodyProxy3D, DeterministicCosmeticPhysicsBody3D, DeterministicCosmeticPhysicsStepOptions3D, DeterministicCosmeticPhysicsStepResult3D, DeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { createSyncplayRunner } from './runner.js';
|
|
|
29
29
|
export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
|
|
30
30
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
31
31
|
export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
32
|
-
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
|
|
32
|
+
export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
|
|
33
33
|
export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
|
|
34
34
|
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
35
35
|
export { runDeterministicPhysics3DDestructionCert } from './physics3d-destruction-cert.js';
|
|
@@ -101,6 +101,15 @@ export type DeterministicPhysicsJoint3D = {
|
|
|
101
101
|
readonly targetZ: number;
|
|
102
102
|
readonly maxForce: number;
|
|
103
103
|
readonly damping?: number;
|
|
104
|
+
readonly linearHertz?: number;
|
|
105
|
+
readonly linearDampingRatio?: number;
|
|
106
|
+
readonly targetRotation?: Quat3D;
|
|
107
|
+
readonly maxTorque?: number;
|
|
108
|
+
readonly angularDamping?: number;
|
|
109
|
+
readonly angularHertz?: number;
|
|
110
|
+
readonly angularDampingRatio?: number;
|
|
111
|
+
readonly breakDistance?: number;
|
|
112
|
+
readonly breakImpulse?: number;
|
|
104
113
|
};
|
|
105
114
|
/** Build solver joint objects from joint definitions, seeded from the joint cache. */
|
|
106
115
|
export declare function createSolverJoints(joints: readonly DeterministicPhysicsJoint3D[], indexById: Map<string, number>, jointCache: readonly SolverJointCacheEntry[]): SolverJointSolve[];
|
package/dist/physics3d-joints.js
CHANGED
|
@@ -626,6 +626,29 @@ function springJoint(joint, aIndex, bIndex, warm) {
|
|
|
626
626
|
},
|
|
627
627
|
};
|
|
628
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* Box3D / Box2D v3 b3MakeSoft coefficient scheme:
|
|
631
|
+
* Translates (frequencyHz, dampingRatio, dt) into mass-independent constraint softness coefficients:
|
|
632
|
+
* biasRate = omega / a1, massScale = a2 / (1 + a2), impulseScale = 1 / (1 + a2).
|
|
633
|
+
* Unconditionally stable: biasRate * dt = (dt * omega) / (2 * damping + dt * omega) <= 1.0 always.
|
|
634
|
+
*/
|
|
635
|
+
function makeSoft(hertz, dampingRatio, dt) {
|
|
636
|
+
if (hertz <= 0) {
|
|
637
|
+
return { biasRate: 0, massScale: 0, impulseScale: 0 };
|
|
638
|
+
}
|
|
639
|
+
const omega = TWO_PI * hertz;
|
|
640
|
+
const a1 = 2 * Math.max(0, dampingRatio) + dt * omega;
|
|
641
|
+
if (a1 <= 1e-18) {
|
|
642
|
+
return { biasRate: 0, massScale: 0, impulseScale: 0 };
|
|
643
|
+
}
|
|
644
|
+
const a2 = dt * omega * a1;
|
|
645
|
+
const a3 = 1 / (1 + a2);
|
|
646
|
+
return {
|
|
647
|
+
biasRate: omega / a1,
|
|
648
|
+
massScale: a2 * a3,
|
|
649
|
+
impulseScale: a3,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
629
652
|
function grabJoint(joint, bodyIndex, warm) {
|
|
630
653
|
const state = {
|
|
631
654
|
impulse: warm ? { x: warm.impulseX, y: warm.impulseY, z: warm.impulseZ } : { x: 0, y: 0, z: 0 },
|
|
@@ -636,36 +659,139 @@ function grabJoint(joint, bodyIndex, warm) {
|
|
|
636
659
|
};
|
|
637
660
|
let r = { x: 0, y: 0, z: 0 };
|
|
638
661
|
let kInv = [0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
639
|
-
let desiredVel = { x: 0, y: 0, z: 0 };
|
|
640
662
|
const target = { x: joint.targetX, y: joint.targetY, z: joint.targetZ };
|
|
641
663
|
const maxImpulse = joint.maxForce * 1;
|
|
664
|
+
const targetRotation = joint.targetRotation !== undefined ? normalizeQuat(joint.targetRotation) : undefined;
|
|
665
|
+
const maxTorque = joint.maxTorque ?? 0;
|
|
666
|
+
const linearHertz = joint.linearHertz ?? 5.0;
|
|
667
|
+
const linearDampingRatio = joint.linearDampingRatio ?? joint.damping ?? 1.0;
|
|
668
|
+
const angularHertz = joint.angularHertz ?? 5.0;
|
|
669
|
+
const angularDampingRatio = joint.angularDampingRatio ?? joint.angularDamping ?? joint.damping ?? 1.0;
|
|
670
|
+
let linBiasRate = 0;
|
|
671
|
+
let linMassScale = 1;
|
|
672
|
+
let linImpulseScale = 0;
|
|
673
|
+
let angBiasVel = { x: 0, y: 0, z: 0 };
|
|
674
|
+
let angMassScale = 1;
|
|
675
|
+
let angImpulseScale = 0;
|
|
676
|
+
let angMassMat;
|
|
677
|
+
let angMassDiag;
|
|
678
|
+
let broken = false;
|
|
679
|
+
let torqueImpulse = { x: 0, y: 0, z: 0 };
|
|
642
680
|
return {
|
|
643
681
|
bodyIndices: [bodyIndex],
|
|
644
682
|
prepare(ctx) {
|
|
683
|
+
if (broken) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
torqueImpulse = { x: 0, y: 0, z: 0 };
|
|
645
687
|
const body = ctx.bodies[bodyIndex];
|
|
646
688
|
r = jointLever(body, joint.anchor);
|
|
647
689
|
const worldAnchor = addVec3(body.c, r);
|
|
690
|
+
if (joint.breakDistance !== undefined) {
|
|
691
|
+
const delta = subVec3(target, worldAnchor);
|
|
692
|
+
const d = solverSqrt(dotVec3(delta, delta));
|
|
693
|
+
if (d > joint.breakDistance) {
|
|
694
|
+
broken = true;
|
|
695
|
+
state.impulse = { x: 0, y: 0, z: 0 };
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
648
699
|
const imB = ctx.invMass[bodyIndex];
|
|
649
700
|
const linear = mat3FromDiag(imB);
|
|
650
701
|
const skewR = skewMat3(r);
|
|
651
702
|
const ang = mat3Multiply(mat3Multiply(skewR, ctx.invInertia[bodyIndex]), skewR);
|
|
652
703
|
kInv = invertMat3(mat3Sub(linear, ang));
|
|
653
|
-
const
|
|
654
|
-
|
|
704
|
+
const linSoft = (joint.linearHertz !== undefined || joint.linearDampingRatio !== undefined)
|
|
705
|
+
? makeSoft(linearHertz, linearDampingRatio, ctx.dt)
|
|
706
|
+
: undefined;
|
|
707
|
+
linBiasRate = linSoft ? linSoft.biasRate : (joint.damping ?? 1) / ctx.dt;
|
|
708
|
+
linMassScale = linSoft ? linSoft.massScale : 1.0;
|
|
709
|
+
linImpulseScale = linSoft ? linSoft.impulseScale : 0;
|
|
655
710
|
ctx.applyToBody(bodyIndex, r, state.impulse);
|
|
711
|
+
if (targetRotation !== undefined) {
|
|
712
|
+
const angSoft = makeSoft(angularHertz, angularDampingRatio, ctx.dt);
|
|
713
|
+
angMassScale = angSoft.massScale;
|
|
714
|
+
angImpulseScale = angSoft.impulseScale;
|
|
715
|
+
const errorRotation = quatMultiply(quatConjugate(targetRotation), body.q);
|
|
716
|
+
const sign = errorRotation.w < 0 ? -1 : 1;
|
|
717
|
+
const error = {
|
|
718
|
+
x: 2 * sign * errorRotation.x,
|
|
719
|
+
y: 2 * sign * errorRotation.y,
|
|
720
|
+
z: 2 * sign * errorRotation.z,
|
|
721
|
+
};
|
|
722
|
+
// Box3D soft constraint bias rate: biasRate * dt <= 1.0 always, preventing single-step overshoot
|
|
723
|
+
angBiasVel = clampMagnitude(scaleVec3(error, -angSoft.biasRate), 10.0);
|
|
724
|
+
const invI = ctx.invInertia[bodyIndex];
|
|
725
|
+
const fullInv = invertMat3(invI);
|
|
726
|
+
const isSingular = fullInv[0] === 0 && fullInv[4] === 0 && fullInv[8] === 0;
|
|
727
|
+
if (!isSingular) {
|
|
728
|
+
angMassMat = fullInv;
|
|
729
|
+
angMassDiag = undefined;
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
angMassMat = undefined;
|
|
733
|
+
angMassDiag = {
|
|
734
|
+
x: invI[0] > 1e-9 ? 1 / invI[0] : 0,
|
|
735
|
+
y: invI[4] > 1e-9 ? 1 / invI[4] : 0,
|
|
736
|
+
z: invI[8] > 1e-9 ? 1 / invI[8] : 0,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
656
740
|
},
|
|
657
741
|
iterate(ctx) {
|
|
742
|
+
if (broken) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
658
745
|
const body = ctx.bodies[bodyIndex];
|
|
746
|
+
const worldAnchor = addVec3(body.c, r);
|
|
747
|
+
const posError = subVec3(target, worldAnchor);
|
|
748
|
+
const desiredVel = scaleVec3(posError, linBiasRate);
|
|
659
749
|
const currentVel = addVec3(body.v, crossVec3(body.w, r));
|
|
660
|
-
const
|
|
750
|
+
const deltaV = subVec3(desiredVel, currentVel);
|
|
751
|
+
const rawImpulse = mulMat3Vec(kInv, deltaV);
|
|
752
|
+
const dImpulse = subVec3(scaleVec3(rawImpulse, linMassScale), scaleVec3(state.impulse, linImpulseScale));
|
|
661
753
|
const target2 = addVec3(state.impulse, dImpulse);
|
|
662
754
|
const clamped = clampMagnitude(target2, maxImpulse * ctx.dt);
|
|
663
755
|
const applied = subVec3(clamped, state.impulse);
|
|
664
756
|
state.impulse = clamped;
|
|
665
757
|
ctx.applyToBody(bodyIndex, r, applied);
|
|
758
|
+
if (targetRotation !== undefined && maxTorque > 0) {
|
|
759
|
+
const deltaW = subVec3(angBiasVel, body.w);
|
|
760
|
+
const rawTorque = angMassMat !== undefined
|
|
761
|
+
? mulMat3Vec(angMassMat, deltaW)
|
|
762
|
+
: { x: deltaW.x * angMassDiag.x, y: deltaW.y * angMassDiag.y, z: deltaW.z * angMassDiag.z };
|
|
763
|
+
const dTorque = subVec3(scaleVec3(rawTorque, angMassScale), scaleVec3(torqueImpulse, angImpulseScale));
|
|
764
|
+
const targetTorque = addVec3(torqueImpulse, dTorque);
|
|
765
|
+
const clampedTorque = clampMagnitude(targetTorque, maxTorque * ctx.dt);
|
|
766
|
+
let appliedTorque = subVec3(clampedTorque, torqueImpulse);
|
|
767
|
+
// Safeguard angular acceleration: clamp appliedTorque so Delta omega <= 0.5 rad/substep
|
|
768
|
+
const dw = mulMat3Vec(ctx.invInertia[bodyIndex], appliedTorque);
|
|
769
|
+
const dwLen = solverSqrt(dotVec3(dw, dw));
|
|
770
|
+
if (dwLen > 0.5) {
|
|
771
|
+
appliedTorque = scaleVec3(appliedTorque, 0.5 / dwLen);
|
|
772
|
+
}
|
|
773
|
+
torqueImpulse = addVec3(torqueImpulse, appliedTorque);
|
|
774
|
+
ctx.applyAngularToBody(bodyIndex, appliedTorque);
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
postSubstep(_ctx) {
|
|
778
|
+
if (!broken) {
|
|
779
|
+
state.stepImpulse = addVec3(state.stepImpulse, state.impulse);
|
|
780
|
+
}
|
|
781
|
+
torqueImpulse = { x: 0, y: 0, z: 0 };
|
|
666
782
|
},
|
|
667
783
|
emitCache() {
|
|
668
|
-
|
|
784
|
+
const reaction2 = dotVec3(state.stepImpulse, state.stepImpulse);
|
|
785
|
+
const isBroken = broken || (joint.breakImpulse !== undefined && reaction2 > joint.breakImpulse * joint.breakImpulse);
|
|
786
|
+
return {
|
|
787
|
+
jointId: joint.id,
|
|
788
|
+
impulseX: isBroken ? 0 : state.impulse.x,
|
|
789
|
+
impulseY: isBroken ? 0 : state.impulse.y,
|
|
790
|
+
impulseZ: isBroken ? 0 : state.impulse.z,
|
|
791
|
+
axialImpulse: 0,
|
|
792
|
+
hingeAngle: 0,
|
|
793
|
+
...(isBroken ? { broken: true } : {}),
|
|
794
|
+
};
|
|
669
795
|
},
|
|
670
796
|
};
|
|
671
797
|
}
|
|
@@ -687,6 +813,45 @@ function validateJoint(joint) {
|
|
|
687
813
|
if (!Number.isFinite(joint.targetX) || !Number.isFinite(joint.targetY) || !Number.isFinite(joint.targetZ)) {
|
|
688
814
|
throw new Error(`physics3d joint ${joint.id}: target must be finite`);
|
|
689
815
|
}
|
|
816
|
+
if (joint.damping !== undefined && (!Number.isFinite(joint.damping) || joint.damping < 0)) {
|
|
817
|
+
throw new Error(`physics3d joint ${joint.id}: damping must be non-negative`);
|
|
818
|
+
}
|
|
819
|
+
if (joint.linearHertz !== undefined && (!Number.isFinite(joint.linearHertz) || joint.linearHertz <= 0)) {
|
|
820
|
+
throw new Error(`physics3d joint ${joint.id}: linearHertz must be positive`);
|
|
821
|
+
}
|
|
822
|
+
if (joint.linearDampingRatio !== undefined && (!Number.isFinite(joint.linearDampingRatio) || joint.linearDampingRatio < 0)) {
|
|
823
|
+
throw new Error(`physics3d joint ${joint.id}: linearDampingRatio must be non-negative`);
|
|
824
|
+
}
|
|
825
|
+
if (joint.targetRotation !== undefined) {
|
|
826
|
+
const target = joint.targetRotation;
|
|
827
|
+
if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z) || !Number.isFinite(target.w)
|
|
828
|
+
|| target.x * target.x + target.y * target.y + target.z * target.z + target.w * target.w === 0) {
|
|
829
|
+
throw new Error(`physics3d joint ${joint.id}: targetRotation must be a finite non-zero quaternion`);
|
|
830
|
+
}
|
|
831
|
+
if (joint.maxTorque === undefined || !Number.isFinite(joint.maxTorque) || joint.maxTorque <= 0) {
|
|
832
|
+
throw new Error(`physics3d joint ${joint.id}: maxTorque must be a positive finite number`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
else if (joint.maxTorque !== undefined) {
|
|
836
|
+
if (!Number.isFinite(joint.maxTorque) || joint.maxTorque <= 0) {
|
|
837
|
+
throw new Error(`physics3d joint ${joint.id}: maxTorque must be a positive finite number`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (joint.angularDamping !== undefined && (!Number.isFinite(joint.angularDamping) || joint.angularDamping < 0)) {
|
|
841
|
+
throw new Error(`physics3d joint ${joint.id}: angularDamping must be non-negative`);
|
|
842
|
+
}
|
|
843
|
+
if (joint.angularHertz !== undefined && (!Number.isFinite(joint.angularHertz) || joint.angularHertz <= 0)) {
|
|
844
|
+
throw new Error(`physics3d joint ${joint.id}: angularHertz must be positive`);
|
|
845
|
+
}
|
|
846
|
+
if (joint.angularDampingRatio !== undefined && (!Number.isFinite(joint.angularDampingRatio) || joint.angularDampingRatio < 0)) {
|
|
847
|
+
throw new Error(`physics3d joint ${joint.id}: angularDampingRatio must be non-negative`);
|
|
848
|
+
}
|
|
849
|
+
if (joint.breakDistance !== undefined && (!Number.isFinite(joint.breakDistance) || joint.breakDistance <= 0)) {
|
|
850
|
+
throw new Error(`physics3d joint ${joint.id}: breakDistance must be positive`);
|
|
851
|
+
}
|
|
852
|
+
if (joint.breakImpulse !== undefined && (!Number.isFinite(joint.breakImpulse) || joint.breakImpulse <= 0)) {
|
|
853
|
+
throw new Error(`physics3d joint ${joint.id}: breakImpulse must be positive`);
|
|
854
|
+
}
|
|
690
855
|
return;
|
|
691
856
|
}
|
|
692
857
|
assertFiniteVec(joint.anchorA, 'anchorA', joint.id);
|
package/dist/physics3d-shared.js
CHANGED
|
@@ -141,6 +141,9 @@ export function worldInvInertia(rot, invI) {
|
|
|
141
141
|
* identity so no NaN reaches state.
|
|
142
142
|
*/
|
|
143
143
|
export function integrateQuat(q, w, dt) {
|
|
144
|
+
if (!Number.isFinite(dt) || dt <= 0 || !Number.isFinite(w.x) || !Number.isFinite(w.y) || !Number.isFinite(w.z)) {
|
|
145
|
+
return normalizeQuat(q);
|
|
146
|
+
}
|
|
144
147
|
const hx = w.x * 0.5 * dt;
|
|
145
148
|
const hy = w.y * 0.5 * dt;
|
|
146
149
|
const hz = w.z * 0.5 * dt;
|
|
@@ -148,18 +151,34 @@ export function integrateQuat(q, w, dt) {
|
|
|
148
151
|
const ny = q.y + (hy * q.w + hz * q.x - hx * q.z);
|
|
149
152
|
const nz = q.z + (hz * q.w + hx * q.y - hy * q.x);
|
|
150
153
|
const nw = q.w + (-hx * q.x - hy * q.y - hz * q.z);
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
return
|
|
154
|
+
const maxComp = Math.max(Math.abs(nx), Math.abs(ny), Math.abs(nz), Math.abs(nw));
|
|
155
|
+
if (!Number.isFinite(maxComp) || maxComp === 0) {
|
|
156
|
+
return normalizeQuat(q);
|
|
157
|
+
}
|
|
158
|
+
const sx = nx / maxComp;
|
|
159
|
+
const sy = ny / maxComp;
|
|
160
|
+
const sz = nz / maxComp;
|
|
161
|
+
const sw = nw / maxComp;
|
|
162
|
+
const length = solverSqrt(sx * sx + sy * sy + sz * sz + sw * sw);
|
|
163
|
+
if (!Number.isFinite(length) || length === 0) {
|
|
164
|
+
return normalizeQuat(q);
|
|
154
165
|
}
|
|
155
|
-
return { x:
|
|
166
|
+
return { x: sx / length, y: sy / length, z: sz / length, w: sw / length };
|
|
156
167
|
}
|
|
157
168
|
export function normalizeQuat(q) {
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
169
|
+
const maxComp = Math.max(Math.abs(q.x), Math.abs(q.y), Math.abs(q.z), Math.abs(q.w));
|
|
170
|
+
if (!Number.isFinite(maxComp) || maxComp === 0) {
|
|
171
|
+
return { x: 0, y: 0, z: 0, w: 1 };
|
|
172
|
+
}
|
|
173
|
+
const sx = q.x / maxComp;
|
|
174
|
+
const sy = q.y / maxComp;
|
|
175
|
+
const sz = q.z / maxComp;
|
|
176
|
+
const sw = q.w / maxComp;
|
|
177
|
+
const length = solverSqrt(sx * sx + sy * sy + sz * sz + sw * sw);
|
|
178
|
+
if (!Number.isFinite(length) || length === 0) {
|
|
160
179
|
return { x: 0, y: 0, z: 0, w: 1 };
|
|
161
180
|
}
|
|
162
|
-
return { x:
|
|
181
|
+
return { x: sx / length, y: sy / length, z: sz / length, w: sw / length };
|
|
163
182
|
}
|
|
164
183
|
export function quatConjugate(q) {
|
|
165
184
|
return { x: -q.x, y: -q.y, z: -q.z, w: q.w };
|
|
@@ -222,6 +222,8 @@ export interface SolverJointContext {
|
|
|
222
222
|
apply(a: number, b: number, rA: Point3D, rB: Point3D, impulse: Point3D): void;
|
|
223
223
|
/** Angular-only impulse (no linear component), e.g. hinge motor/limit rows. */
|
|
224
224
|
applyAngular(a: number, b: number, angularImpulse: Point3D): void;
|
|
225
|
+
/** Apply an angular impulse to a single body (grab joint orientation drive). */
|
|
226
|
+
applyAngularToBody(index: number, angularImpulse: Point3D): void;
|
|
225
227
|
/** Apply an impulse to a single body at anchor `r` (grab / world-target joints). */
|
|
226
228
|
applyToBody(index: number, r: Point3D, impulse: Point3D): void;
|
|
227
229
|
/** Split-impulse position correction on pseudo-velocities (no real momentum). */
|
|
@@ -247,6 +249,24 @@ export interface SolverWarmPoint {
|
|
|
247
249
|
readonly tangentImpulse1: number;
|
|
248
250
|
readonly tangentImpulse2: number;
|
|
249
251
|
}
|
|
252
|
+
export interface DeterministicPhysicsAabb3D {
|
|
253
|
+
readonly minX: number;
|
|
254
|
+
readonly minY: number;
|
|
255
|
+
readonly minZ: number;
|
|
256
|
+
readonly maxX: number;
|
|
257
|
+
readonly maxY: number;
|
|
258
|
+
readonly maxZ: number;
|
|
259
|
+
}
|
|
260
|
+
export interface DeterministicFluidVolume3D {
|
|
261
|
+
readonly id: string;
|
|
262
|
+
readonly bounds: DeterministicPhysicsAabb3D;
|
|
263
|
+
readonly density?: number;
|
|
264
|
+
readonly linearDrag?: number;
|
|
265
|
+
readonly angularDrag?: number;
|
|
266
|
+
readonly flowVelocity?: Point3D;
|
|
267
|
+
readonly layer?: number;
|
|
268
|
+
readonly mask?: number;
|
|
269
|
+
}
|
|
250
270
|
export interface SolverStepOptions {
|
|
251
271
|
readonly dt: number;
|
|
252
272
|
readonly gravityY: number;
|
|
@@ -260,6 +280,7 @@ export interface SolverStepOptions {
|
|
|
260
280
|
readonly joints?: readonly SolverJointSolve[];
|
|
261
281
|
/** Packed index pairs (min * WARM_KEY_BASE + max) whose contacts are suppressed (jointed pairs). */
|
|
262
282
|
readonly noCollidePairs?: ReadonlySet<number>;
|
|
283
|
+
readonly fluids?: readonly DeterministicFluidVolume3D[];
|
|
263
284
|
}
|
|
264
285
|
export declare function combinedContactFriction(frictionA10: number, frictionB10: number): number;
|
|
265
286
|
export interface SolverContactImpulse {
|
package/dist/physics3d-solver.js
CHANGED
|
@@ -1435,6 +1435,286 @@ function takeConstraint() {
|
|
|
1435
1435
|
constraintPool.high += 1;
|
|
1436
1436
|
return slot;
|
|
1437
1437
|
}
|
|
1438
|
+
function computeSubmergence(b, bounds) {
|
|
1439
|
+
let points = [];
|
|
1440
|
+
let volume = 0;
|
|
1441
|
+
let yMin = b.p.y;
|
|
1442
|
+
let yMax = b.p.y;
|
|
1443
|
+
let xMin = b.p.x;
|
|
1444
|
+
let xMax = b.p.x;
|
|
1445
|
+
let zMin = b.p.z;
|
|
1446
|
+
let zMax = b.p.z;
|
|
1447
|
+
const shape = b.shape;
|
|
1448
|
+
if (shape.kind === 'box') {
|
|
1449
|
+
const h = shape.half;
|
|
1450
|
+
volume = 8 * h.x * h.y * h.z;
|
|
1451
|
+
const corners = [
|
|
1452
|
+
{ x: -h.x, y: -h.y, z: -h.z },
|
|
1453
|
+
{ x: h.x, y: -h.y, z: -h.z },
|
|
1454
|
+
{ x: -h.x, y: h.y, z: -h.z },
|
|
1455
|
+
{ x: h.x, y: h.y, z: -h.z },
|
|
1456
|
+
{ x: -h.x, y: -h.y, z: h.z },
|
|
1457
|
+
{ x: h.x, y: -h.y, z: h.z },
|
|
1458
|
+
{ x: -h.x, y: h.y, z: h.z },
|
|
1459
|
+
{ x: h.x, y: h.y, z: h.z },
|
|
1460
|
+
];
|
|
1461
|
+
points = corners.map((c) => addVec3(b.p, rotatePointByQuat(b.q, c)));
|
|
1462
|
+
xMin = points[0].x;
|
|
1463
|
+
xMax = points[0].x;
|
|
1464
|
+
yMin = points[0].y;
|
|
1465
|
+
yMax = points[0].y;
|
|
1466
|
+
zMin = points[0].z;
|
|
1467
|
+
zMax = points[0].z;
|
|
1468
|
+
for (let i = 1; i < points.length; i += 1) {
|
|
1469
|
+
const p = points[i];
|
|
1470
|
+
if (p.x < xMin)
|
|
1471
|
+
xMin = p.x;
|
|
1472
|
+
if (p.x > xMax)
|
|
1473
|
+
xMax = p.x;
|
|
1474
|
+
if (p.y < yMin)
|
|
1475
|
+
yMin = p.y;
|
|
1476
|
+
if (p.y > yMax)
|
|
1477
|
+
yMax = p.y;
|
|
1478
|
+
if (p.z < zMin)
|
|
1479
|
+
zMin = p.z;
|
|
1480
|
+
if (p.z > zMax)
|
|
1481
|
+
zMax = p.z;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
else if (shape.kind === 'sphere') {
|
|
1485
|
+
const r = shape.radius;
|
|
1486
|
+
volume = (4 / 3) * Math.PI * r * r * r;
|
|
1487
|
+
xMin = b.p.x - r;
|
|
1488
|
+
xMax = b.p.x + r;
|
|
1489
|
+
yMin = b.p.y - r;
|
|
1490
|
+
yMax = b.p.y + r;
|
|
1491
|
+
zMin = b.p.z - r;
|
|
1492
|
+
zMax = b.p.z + r;
|
|
1493
|
+
points = [b.p];
|
|
1494
|
+
}
|
|
1495
|
+
else if (shape.kind === 'capsule') {
|
|
1496
|
+
const r = shape.radius;
|
|
1497
|
+
const hh = shape.halfHeight;
|
|
1498
|
+
volume = (4 / 3) * Math.PI * r * r * r + 2 * Math.PI * r * r * hh;
|
|
1499
|
+
const pTop = addVec3(b.p, rotatePointByQuat(b.q, { x: 0, y: hh, z: 0 }));
|
|
1500
|
+
const pBot = addVec3(b.p, rotatePointByQuat(b.q, { x: 0, y: -hh, z: 0 }));
|
|
1501
|
+
xMin = Math.min(pTop.x, pBot.x) - r;
|
|
1502
|
+
xMax = Math.max(pTop.x, pBot.x) + r;
|
|
1503
|
+
yMin = Math.min(pTop.y, pBot.y) - r;
|
|
1504
|
+
yMax = Math.max(pTop.y, pBot.y) + r;
|
|
1505
|
+
zMin = Math.min(pTop.z, pBot.z) - r;
|
|
1506
|
+
zMax = Math.max(pTop.z, pBot.z) + r;
|
|
1507
|
+
points = [pBot, pTop];
|
|
1508
|
+
}
|
|
1509
|
+
else if (shape.kind === 'hull') {
|
|
1510
|
+
const verts = shape.data.vertices;
|
|
1511
|
+
volume = 1.0;
|
|
1512
|
+
points = verts.map((v) => addVec3(b.p, rotatePointByQuat(b.q, v)));
|
|
1513
|
+
if (points.length > 0) {
|
|
1514
|
+
xMin = points[0].x;
|
|
1515
|
+
xMax = points[0].x;
|
|
1516
|
+
yMin = points[0].y;
|
|
1517
|
+
yMax = points[0].y;
|
|
1518
|
+
zMin = points[0].z;
|
|
1519
|
+
zMax = points[0].z;
|
|
1520
|
+
for (let i = 1; i < points.length; i += 1) {
|
|
1521
|
+
const p = points[i];
|
|
1522
|
+
if (p.x < xMin)
|
|
1523
|
+
xMin = p.x;
|
|
1524
|
+
if (p.x > xMax)
|
|
1525
|
+
xMax = p.x;
|
|
1526
|
+
if (p.y < yMin)
|
|
1527
|
+
yMin = p.y;
|
|
1528
|
+
if (p.y > yMax)
|
|
1529
|
+
yMax = p.y;
|
|
1530
|
+
if (p.z < zMin)
|
|
1531
|
+
zMin = p.z;
|
|
1532
|
+
if (p.z > zMax)
|
|
1533
|
+
zMax = p.z;
|
|
1534
|
+
}
|
|
1535
|
+
volume = Math.max(0.001, (xMax - xMin) * (yMax - yMin) * (zMax - zMin) * 0.5);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
else if (shape.kind === 'compound') {
|
|
1539
|
+
volume = 0;
|
|
1540
|
+
for (const child of shape.children) {
|
|
1541
|
+
const childCenter = addVec3(b.p, rotatePointByQuat(b.q, child.offset));
|
|
1542
|
+
const childQ = quatMultiply(b.q, child.q);
|
|
1543
|
+
if (child.shape.kind === 'box') {
|
|
1544
|
+
const h = child.shape.half;
|
|
1545
|
+
volume += 8 * h.x * h.y * h.z;
|
|
1546
|
+
const corners = [
|
|
1547
|
+
{ x: -h.x, y: -h.y, z: -h.z },
|
|
1548
|
+
{ x: h.x, y: -h.y, z: -h.z },
|
|
1549
|
+
{ x: -h.x, y: h.y, z: -h.z },
|
|
1550
|
+
{ x: h.x, y: h.y, z: -h.z },
|
|
1551
|
+
{ x: -h.x, y: -h.y, z: h.z },
|
|
1552
|
+
{ x: h.x, y: -h.y, z: h.z },
|
|
1553
|
+
{ x: -h.x, y: h.y, z: h.z },
|
|
1554
|
+
{ x: h.x, y: h.y, z: h.z },
|
|
1555
|
+
];
|
|
1556
|
+
points.push(...corners.map((c) => addVec3(childCenter, rotatePointByQuat(childQ, c))));
|
|
1557
|
+
}
|
|
1558
|
+
else if (child.shape.kind === 'sphere') {
|
|
1559
|
+
const r = child.shape.radius;
|
|
1560
|
+
volume += (4 / 3) * Math.PI * r * r * r;
|
|
1561
|
+
points.push(childCenter, { x: childCenter.x - r, y: childCenter.y, z: childCenter.z }, { x: childCenter.x + r, y: childCenter.y, z: childCenter.z }, { x: childCenter.x, y: childCenter.y - r, z: childCenter.z }, { x: childCenter.x, y: childCenter.y + r, z: childCenter.z }, { x: childCenter.x, y: childCenter.y, z: childCenter.z - r }, { x: childCenter.x, y: childCenter.y, z: childCenter.z + r });
|
|
1562
|
+
}
|
|
1563
|
+
else if (child.shape.kind === 'capsule') {
|
|
1564
|
+
const r = child.shape.radius;
|
|
1565
|
+
const hh = child.shape.halfHeight;
|
|
1566
|
+
volume += (4 / 3) * Math.PI * r * r * r + 2 * Math.PI * r * r * hh;
|
|
1567
|
+
const capTop = addVec3(childCenter, rotatePointByQuat(childQ, { x: 0, y: hh, z: 0 }));
|
|
1568
|
+
const capBot = addVec3(childCenter, rotatePointByQuat(childQ, { x: 0, y: -hh, z: 0 }));
|
|
1569
|
+
points.push(capTop, capBot, { x: capTop.x - r, y: capTop.y, z: capTop.z }, { x: capTop.x + r, y: capTop.y, z: capTop.z }, { x: capTop.x, y: capTop.y - r, z: capTop.z }, { x: capTop.x, y: capTop.y + r, z: capTop.z }, { x: capTop.x, y: capTop.y, z: capTop.z - r }, { x: capTop.x, y: capTop.y, z: capTop.z + r }, { x: capBot.x - r, y: capBot.y, z: capBot.z }, { x: capBot.x + r, y: capBot.y, z: capBot.z }, { x: capBot.x, y: capBot.y - r, z: capBot.z }, { x: capBot.x, y: capBot.y + r, z: capBot.z }, { x: capBot.x, y: capBot.y, z: capBot.z - r }, { x: capBot.x, y: capBot.y, z: capBot.z + r });
|
|
1570
|
+
}
|
|
1571
|
+
else if (child.shape.kind === 'hull') {
|
|
1572
|
+
const verts = child.shape.data.vertices;
|
|
1573
|
+
const hullPoints = verts.map((v) => addVec3(childCenter, rotatePointByQuat(childQ, v)));
|
|
1574
|
+
points.push(...hullPoints);
|
|
1575
|
+
if (hullPoints.length > 0) {
|
|
1576
|
+
let hxMin = hullPoints[0].x;
|
|
1577
|
+
let hxMax = hullPoints[0].x;
|
|
1578
|
+
let hyMin = hullPoints[0].y;
|
|
1579
|
+
let hyMax = hullPoints[0].y;
|
|
1580
|
+
let hzMin = hullPoints[0].z;
|
|
1581
|
+
let hzMax = hullPoints[0].z;
|
|
1582
|
+
for (let i = 1; i < hullPoints.length; i += 1) {
|
|
1583
|
+
const p = hullPoints[i];
|
|
1584
|
+
if (p.x < hxMin)
|
|
1585
|
+
hxMin = p.x;
|
|
1586
|
+
if (p.x > hxMax)
|
|
1587
|
+
hxMax = p.x;
|
|
1588
|
+
if (p.y < hyMin)
|
|
1589
|
+
hyMin = p.y;
|
|
1590
|
+
if (p.y > hyMax)
|
|
1591
|
+
hyMax = p.y;
|
|
1592
|
+
if (p.z < hzMin)
|
|
1593
|
+
hzMin = p.z;
|
|
1594
|
+
if (p.z > hzMax)
|
|
1595
|
+
hzMax = p.z;
|
|
1596
|
+
}
|
|
1597
|
+
volume += Math.max(0.001, (hxMax - hxMin) * (hyMax - hyMin) * (hzMax - hzMin) * 0.5);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
if (points.length > 0) {
|
|
1602
|
+
xMin = points[0].x;
|
|
1603
|
+
xMax = points[0].x;
|
|
1604
|
+
yMin = points[0].y;
|
|
1605
|
+
yMax = points[0].y;
|
|
1606
|
+
zMin = points[0].z;
|
|
1607
|
+
zMax = points[0].z;
|
|
1608
|
+
for (let i = 1; i < points.length; i += 1) {
|
|
1609
|
+
const p = points[i];
|
|
1610
|
+
if (p.x < xMin)
|
|
1611
|
+
xMin = p.x;
|
|
1612
|
+
if (p.x > xMax)
|
|
1613
|
+
xMax = p.x;
|
|
1614
|
+
if (p.y < yMin)
|
|
1615
|
+
yMin = p.y;
|
|
1616
|
+
if (p.y > yMax)
|
|
1617
|
+
yMax = p.y;
|
|
1618
|
+
if (p.z < zMin)
|
|
1619
|
+
zMin = p.z;
|
|
1620
|
+
if (p.z > zMax)
|
|
1621
|
+
zMax = p.z;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (xMax < bounds.minX || xMin > bounds.maxX || zMax < bounds.minZ || zMin > bounds.maxZ) {
|
|
1626
|
+
return undefined;
|
|
1627
|
+
}
|
|
1628
|
+
if (yMax <= bounds.minY || yMin >= bounds.maxY) {
|
|
1629
|
+
return undefined;
|
|
1630
|
+
}
|
|
1631
|
+
const yBottom = Math.max(bounds.minY, yMin);
|
|
1632
|
+
const yTop = Math.min(bounds.maxY, yMax);
|
|
1633
|
+
const span = Math.max(1e-6, yMax - yMin);
|
|
1634
|
+
const fraction = Math.min(1.0, Math.max(0.0, (yTop - yBottom) / span));
|
|
1635
|
+
let cbX = b.c.x;
|
|
1636
|
+
let cbY = (yBottom + yTop) / 2;
|
|
1637
|
+
let cbZ = b.c.z;
|
|
1638
|
+
if (fraction >= 1.0) {
|
|
1639
|
+
// Fully submerged: center of buoyancy coincides with body centroid (no phantom righting torque)
|
|
1640
|
+
cbX = b.c.x;
|
|
1641
|
+
cbY = b.c.y;
|
|
1642
|
+
cbZ = b.c.z;
|
|
1643
|
+
}
|
|
1644
|
+
else {
|
|
1645
|
+
// Partially submerged: depth-weighted average of submerged sample points relative to fluid surface
|
|
1646
|
+
let sumWeight = 0;
|
|
1647
|
+
let sumX = 0;
|
|
1648
|
+
let sumZ = 0;
|
|
1649
|
+
const surfaceY = bounds.maxY;
|
|
1650
|
+
for (const p of points) {
|
|
1651
|
+
if (p.y < surfaceY) {
|
|
1652
|
+
const depth = surfaceY - p.y;
|
|
1653
|
+
sumWeight += depth;
|
|
1654
|
+
sumX += p.x * depth;
|
|
1655
|
+
sumZ += p.z * depth;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (sumWeight > 1e-9) {
|
|
1659
|
+
cbX = sumX / sumWeight;
|
|
1660
|
+
cbZ = sumZ / sumWeight;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return {
|
|
1664
|
+
fraction,
|
|
1665
|
+
centerOfBuoyancy: { x: cbX, y: cbY, z: cbZ },
|
|
1666
|
+
displacedVolume: volume,
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
function applyFluidForces(bodies, invInertia, fluids, gravityY, dt) {
|
|
1670
|
+
for (let index = 0; index < bodies.length; index += 1) {
|
|
1671
|
+
const b = bodies[index];
|
|
1672
|
+
if (!solid(b) || !b.isDynamic) {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
for (const fluid of fluids) {
|
|
1676
|
+
if (fluid.layer !== undefined && fluid.mask !== undefined) {
|
|
1677
|
+
if ((fluid.mask & b.layer) === 0 || (b.mask & fluid.layer) === 0) {
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const submergence = computeSubmergence(b, fluid.bounds);
|
|
1682
|
+
if (submergence === undefined || submergence.fraction <= 0) {
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
const { fraction, centerOfBuoyancy, displacedVolume } = submergence;
|
|
1686
|
+
const fluidDensity = fluid.density ?? 1.0;
|
|
1687
|
+
const linearDragCoeff = fluid.linearDrag ?? 1.0;
|
|
1688
|
+
const angularDragCoeff = fluid.angularDrag ?? 0.5;
|
|
1689
|
+
const flowVel = fluid.flowVelocity ?? ZERO;
|
|
1690
|
+
const effG = gravityY * b.gravityScale;
|
|
1691
|
+
if (Math.abs(effG) > 0 && b.invMassVec.y > 0) {
|
|
1692
|
+
// Buoyant force opposes effective gravity
|
|
1693
|
+
const buoyantForceY = -effG * fluidDensity * displacedVolume * fraction;
|
|
1694
|
+
const buoyantAccelY = buoyantForceY * b.invMassVec.y;
|
|
1695
|
+
b.v.y += buoyantAccelY * dt;
|
|
1696
|
+
const rB = subVec3(centerOfBuoyancy, b.c);
|
|
1697
|
+
const torque = {
|
|
1698
|
+
x: -rB.z * buoyantForceY,
|
|
1699
|
+
y: 0,
|
|
1700
|
+
z: rB.x * buoyantForceY,
|
|
1701
|
+
};
|
|
1702
|
+
const angImpulse = scaleVec3(torque, dt);
|
|
1703
|
+
let dw = mulMat3Vec(invInertia[index], angImpulse);
|
|
1704
|
+
const dwLen = solverSqrt(dotVec3(dw, dw));
|
|
1705
|
+
if (dwLen > 0.5) {
|
|
1706
|
+
dw = scaleVec3(dw, 0.5 / dwLen);
|
|
1707
|
+
}
|
|
1708
|
+
b.w = addVec3(b.w, dw);
|
|
1709
|
+
}
|
|
1710
|
+
const vRel = subVec3(b.v, flowVel);
|
|
1711
|
+
const linDragFactor = 1 / (1 + linearDragCoeff * fraction * dt);
|
|
1712
|
+
b.v = addVec3(scaleVec3(vRel, linDragFactor), flowVel);
|
|
1713
|
+
const angDragFactor = 1 / (1 + angularDragCoeff * fraction * dt);
|
|
1714
|
+
b.w = scaleVec3(b.w, angDragFactor);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1438
1718
|
/** Runs one deterministic rigid-body step in place over `bodies`. */
|
|
1439
1719
|
export function solveRigidStep(bodies, options) {
|
|
1440
1720
|
/* istanbul ignore next -- worlds are validated far below this bound; guards the packed warm-cache key from silent index collisions. */
|
|
@@ -1589,6 +1869,25 @@ export function solveRigidStep(bodies, options) {
|
|
|
1589
1869
|
b.lowVelFrames = 0;
|
|
1590
1870
|
}
|
|
1591
1871
|
}
|
|
1872
|
+
if (options.fluids && options.fluids.length > 0) {
|
|
1873
|
+
for (const b of bodies) {
|
|
1874
|
+
if (b.isDynamic && b.sleeping) {
|
|
1875
|
+
for (const f of options.fluids) {
|
|
1876
|
+
if (f.layer !== undefined && f.mask !== undefined) {
|
|
1877
|
+
if ((f.mask & b.layer) === 0 || (b.mask & f.layer) === 0) {
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
const sub = computeSubmergence(b, f.bounds);
|
|
1882
|
+
if (sub && sub.fraction > 0) {
|
|
1883
|
+
b.sleeping = false;
|
|
1884
|
+
b.lowVelFrames = 0;
|
|
1885
|
+
break;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1592
1891
|
for (const m of manifolds) {
|
|
1593
1892
|
wakeIfContacted(bodies[m.a], bodies[m.b]);
|
|
1594
1893
|
wakeIfContacted(bodies[m.b], bodies[m.a]);
|
|
@@ -1685,6 +1984,13 @@ export function solveRigidStep(bodies, options) {
|
|
|
1685
1984
|
B.w = addVec3(B.w, mulMat3Vec(invInertia[b], angularImpulse));
|
|
1686
1985
|
}
|
|
1687
1986
|
},
|
|
1987
|
+
applyAngularToBody: (index, angularImpulse) => {
|
|
1988
|
+
const body = bodies[index];
|
|
1989
|
+
if (!solid(body)) {
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
body.w = addVec3(body.w, mulMat3Vec(invInertia[index], angularImpulse));
|
|
1993
|
+
},
|
|
1688
1994
|
applyToBody: (index, r, impulse) => {
|
|
1689
1995
|
const body = bodies[index];
|
|
1690
1996
|
if (!solid(body)) {
|
|
@@ -1706,6 +2012,9 @@ export function solveRigidStep(bodies, options) {
|
|
|
1706
2012
|
b.v.y = b.v.y + options.gravityY * b.gravityScale * h;
|
|
1707
2013
|
}
|
|
1708
2014
|
}
|
|
2015
|
+
if (options.fluids && options.fluids.length > 0) {
|
|
2016
|
+
applyFluidForces(bodies, invInertia, options.fluids, options.gravityY, h);
|
|
2017
|
+
}
|
|
1709
2018
|
for (const joint of joints) {
|
|
1710
2019
|
joint.prepare(jointCtx);
|
|
1711
2020
|
}
|
|
@@ -116,3 +116,16 @@ export interface SplitDeterministicVoxelChunkOptions3D {
|
|
|
116
116
|
* occupied count, and whether any of its voxels touch the specified anchor faces.
|
|
117
117
|
*/
|
|
118
118
|
export declare function splitDeterministicVoxelChunk3D(chunk: DeterministicVoxelChunk3D, options?: SplitDeterministicVoxelChunkOptions3D): readonly VoxelSubChunk3D[];
|
|
119
|
+
export interface DeterministicVoxelCompoundSplitResult3D {
|
|
120
|
+
readonly compounds: readonly (DeterministicVoxelCompoundShape3D | undefined)[];
|
|
121
|
+
readonly preservedBoxesCount: number;
|
|
122
|
+
readonly recookedBoxesCount: number;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Incrementally slices a parent compound collider across partitioned sub-chunks
|
|
126
|
+
* from {@link splitDeterministicVoxelChunk3D}.
|
|
127
|
+
* Preserves intact child box colliders that fall entirely within a sub-chunk's volume
|
|
128
|
+
* without re-cooking, rebasing their offset coordinates to sub-chunk local space.
|
|
129
|
+
* Only re-cooks severed/split boundary voxels that were not covered by preserved boxes.
|
|
130
|
+
*/
|
|
131
|
+
export declare function splitDeterministicVoxelCompoundCollider3D(parentCompound: DeterministicVoxelCompoundShape3D, subChunks: readonly VoxelSubChunk3D[]): DeterministicVoxelCompoundSplitResult3D;
|
package/dist/physics3d-voxel.js
CHANGED
|
@@ -693,3 +693,127 @@ export function splitDeterministicVoxelChunk3D(chunk, options) {
|
|
|
693
693
|
});
|
|
694
694
|
return subChunks;
|
|
695
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* Incrementally slices a parent compound collider across partitioned sub-chunks
|
|
698
|
+
* from {@link splitDeterministicVoxelChunk3D}.
|
|
699
|
+
* Preserves intact child box colliders that fall entirely within a sub-chunk's volume
|
|
700
|
+
* without re-cooking, rebasing their offset coordinates to sub-chunk local space.
|
|
701
|
+
* Only re-cooks severed/split boundary voxels that were not covered by preserved boxes.
|
|
702
|
+
*/
|
|
703
|
+
export function splitDeterministicVoxelCompoundCollider3D(parentCompound, subChunks) {
|
|
704
|
+
if (subChunks.length === 0) {
|
|
705
|
+
return {
|
|
706
|
+
compounds: [],
|
|
707
|
+
preservedBoxesCount: 0,
|
|
708
|
+
recookedBoxesCount: 0,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
let totalPreservedBoxes = 0;
|
|
712
|
+
let totalRecookedBoxes = 0;
|
|
713
|
+
const compounds = [];
|
|
714
|
+
for (const subChunk of subChunks) {
|
|
715
|
+
const chunk = subChunk.chunk;
|
|
716
|
+
const cellSize = chunk.cellSize;
|
|
717
|
+
const scX = subChunk.offsetCells.x;
|
|
718
|
+
const scY = subChunk.offsetCells.y;
|
|
719
|
+
const scZ = subChunk.offsetCells.z;
|
|
720
|
+
const maxBoundX = scX + chunk.dimX - 1;
|
|
721
|
+
const maxBoundY = scY + chunk.dimY - 1;
|
|
722
|
+
const maxBoundZ = scZ + chunk.dimZ - 1;
|
|
723
|
+
const totalCells = chunk.dimX * chunk.dimY * chunk.dimZ;
|
|
724
|
+
const coveredInSubChunk = new Uint8Array(totalCells);
|
|
725
|
+
const preservedChildren = [];
|
|
726
|
+
for (const child of parentCompound.children) {
|
|
727
|
+
if (child.shape.type !== 'box') {
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
const boxMinX = Math.round((child.offset.x - child.shape.halfX) / cellSize + 0.5);
|
|
731
|
+
const boxMaxX = Math.round((child.offset.x + child.shape.halfX) / cellSize - 0.5);
|
|
732
|
+
const boxMinY = Math.round((child.offset.y - child.shape.halfY) / cellSize + 0.5);
|
|
733
|
+
const boxMaxY = Math.round((child.offset.y + child.shape.halfY) / cellSize - 0.5);
|
|
734
|
+
const boxMinZ = Math.round((child.offset.z - child.shape.halfZ) / cellSize + 0.5);
|
|
735
|
+
const boxMaxZ = Math.round((child.offset.z + child.shape.halfZ) / cellSize - 0.5);
|
|
736
|
+
if (boxMinX >= scX && boxMaxX <= maxBoundX &&
|
|
737
|
+
boxMinY >= scY && boxMaxY <= maxBoundY &&
|
|
738
|
+
boxMinZ >= scZ && boxMaxZ <= maxBoundZ) {
|
|
739
|
+
let isIntact = true;
|
|
740
|
+
for (let bz = boxMinZ; bz <= boxMaxZ && isIntact; bz++) {
|
|
741
|
+
for (let by = boxMinY; by <= boxMaxY && isIntact; by++) {
|
|
742
|
+
for (let bx = boxMinX; bx <= boxMaxX; bx++) {
|
|
743
|
+
const lx = bx - scX;
|
|
744
|
+
const ly = by - scY;
|
|
745
|
+
const lz = bz - scZ;
|
|
746
|
+
const idx = lx + chunk.dimX * (ly + chunk.dimY * lz);
|
|
747
|
+
if (chunk.occupancy[idx] === 0) {
|
|
748
|
+
isIntact = false;
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (isIntact) {
|
|
755
|
+
for (let bz = boxMinZ; bz <= boxMaxZ; bz++) {
|
|
756
|
+
for (let by = boxMinY; by <= boxMaxY; by++) {
|
|
757
|
+
for (let bx = boxMinX; bx <= boxMaxX; bx++) {
|
|
758
|
+
const lx = bx - scX;
|
|
759
|
+
const ly = by - scY;
|
|
760
|
+
const lz = bz - scZ;
|
|
761
|
+
const idx = lx + chunk.dimX * (ly + chunk.dimY * lz);
|
|
762
|
+
coveredInSubChunk[idx] = 1;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
preservedChildren.push({
|
|
767
|
+
shape: child.shape,
|
|
768
|
+
offset: {
|
|
769
|
+
x: child.offset.x - scX * cellSize,
|
|
770
|
+
y: child.offset.y - scY * cellSize,
|
|
771
|
+
z: child.offset.z - scZ * cellSize,
|
|
772
|
+
},
|
|
773
|
+
...(child.quarterTurns !== undefined ? { quarterTurns: child.quarterTurns } : {}),
|
|
774
|
+
});
|
|
775
|
+
totalPreservedBoxes += 1;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
let uncoveredCount = 0;
|
|
780
|
+
for (let i = 0; i < totalCells; i++) {
|
|
781
|
+
if (chunk.occupancy[i] !== 0 && coveredInSubChunk[i] === 0) {
|
|
782
|
+
uncoveredCount += 1;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
let recookedChildren = [];
|
|
786
|
+
if (uncoveredCount > 0) {
|
|
787
|
+
const uncoveredOccupancy = new Uint8Array(totalCells);
|
|
788
|
+
for (let i = 0; i < totalCells; i++) {
|
|
789
|
+
if (chunk.occupancy[i] !== 0 && coveredInSubChunk[i] === 0) {
|
|
790
|
+
uncoveredOccupancy[i] = 1;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const uncoveredChunk = {
|
|
794
|
+
dimX: chunk.dimX,
|
|
795
|
+
dimY: chunk.dimY,
|
|
796
|
+
dimZ: chunk.dimZ,
|
|
797
|
+
cellSize: chunk.cellSize,
|
|
798
|
+
occupancy: uncoveredOccupancy,
|
|
799
|
+
};
|
|
800
|
+
recookedChildren = cookSubregionBoxes(uncoveredChunk, 0, 0, 0, chunk.dimX - 1, chunk.dimY - 1, chunk.dimZ - 1);
|
|
801
|
+
totalRecookedBoxes += recookedChildren.length;
|
|
802
|
+
}
|
|
803
|
+
const finalChildren = [...preservedChildren, ...recookedChildren];
|
|
804
|
+
if (finalChildren.length === 0) {
|
|
805
|
+
compounds.push(undefined);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
compounds.push({
|
|
809
|
+
type: 'compound',
|
|
810
|
+
children: finalChildren,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
compounds,
|
|
816
|
+
preservedBoxesCount: totalPreservedBoxes,
|
|
817
|
+
recookedBoxesCount: totalRecookedBoxes,
|
|
818
|
+
};
|
|
819
|
+
}
|
package/dist/physics3d.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type DeterministicPhysicsMaterial } from './deterministic-primitives.js';
|
|
2
2
|
import { type Point3D } from './physics3d-shared.js';
|
|
3
|
+
import { type DeterministicPhysicsAabb3D, type DeterministicFluidVolume3D } from './physics3d-solver.js';
|
|
4
|
+
export type { DeterministicPhysicsAabb3D, DeterministicFluidVolume3D };
|
|
3
5
|
import { type DeterministicPhysicsJoint3D } from './physics3d-joints.js';
|
|
4
6
|
export type DeterministicPhysicsPrimitiveShape3D = {
|
|
5
7
|
readonly type: 'box';
|
|
@@ -364,6 +366,8 @@ export interface DeterministicPhysicsStepResult3D {
|
|
|
364
366
|
readonly contacts: readonly DeterministicPhysicsContactEvent3D[];
|
|
365
367
|
/** Joint ids that broke this step (newly, sorted); sticky once broken. */
|
|
366
368
|
readonly brokenJointIds: readonly string[];
|
|
369
|
+
/** Alias for brokenJointIds requested for ergonomics parity. */
|
|
370
|
+
readonly brokenJoints: readonly string[];
|
|
367
371
|
readonly scheduledQueryResults: readonly {
|
|
368
372
|
readonly id: string;
|
|
369
373
|
readonly hits: readonly DeterministicPhysicsHit3D[];
|
|
@@ -480,6 +484,7 @@ export interface DeterministicPhysicsStepOptions3D {
|
|
|
480
484
|
readonly computeChecksum?: boolean;
|
|
481
485
|
readonly computeBroadphasePairs?: boolean;
|
|
482
486
|
readonly pairs?: readonly (readonly [number, number])[];
|
|
487
|
+
readonly fluids?: readonly DeterministicFluidVolume3D[];
|
|
483
488
|
}
|
|
484
489
|
export declare function stepDeterministicPhysicsWorld3D(world: DeterministicPhysicsWorld3D, options?: DeterministicPhysicsStepOptions3D): DeterministicPhysicsStepResult3D;
|
|
485
490
|
/** O(1) body lookup backed by the per-world query-index cache. */
|
package/dist/physics3d.js
CHANGED
|
@@ -693,7 +693,52 @@ export function setDeterministicBodyVelocity3D(world, bodyId, velocity, angularV
|
|
|
693
693
|
});
|
|
694
694
|
return { ...world, bodies: normalizeBodies(bodies, 'trusted') };
|
|
695
695
|
}
|
|
696
|
+
function validateFluidVolume(fluid) {
|
|
697
|
+
if (typeof fluid.id !== 'string' || fluid.id.length === 0) {
|
|
698
|
+
throw new Error('physics3d fluid: id must be a non-empty string');
|
|
699
|
+
}
|
|
700
|
+
const bounds = fluid.bounds;
|
|
701
|
+
if (!bounds || typeof bounds !== 'object') {
|
|
702
|
+
throw new Error(`physics3d fluid ${fluid.id}: bounds must be an object`);
|
|
703
|
+
}
|
|
704
|
+
const { minX, minY, minZ, maxX, maxY, maxZ } = bounds;
|
|
705
|
+
if (!Number.isFinite(minX) ||
|
|
706
|
+
!Number.isFinite(minY) ||
|
|
707
|
+
!Number.isFinite(minZ) ||
|
|
708
|
+
!Number.isFinite(maxX) ||
|
|
709
|
+
!Number.isFinite(maxY) ||
|
|
710
|
+
!Number.isFinite(maxZ)) {
|
|
711
|
+
throw new Error(`physics3d fluid ${fluid.id}: bounds must contain finite numbers`);
|
|
712
|
+
}
|
|
713
|
+
if (minX > maxX) {
|
|
714
|
+
throw new Error(`physics3d fluid ${fluid.id}: minX must not exceed maxX`);
|
|
715
|
+
}
|
|
716
|
+
if (minY > maxY) {
|
|
717
|
+
throw new Error(`physics3d fluid ${fluid.id}: minY must not exceed maxY`);
|
|
718
|
+
}
|
|
719
|
+
if (minZ > maxZ) {
|
|
720
|
+
throw new Error(`physics3d fluid ${fluid.id}: minZ must not exceed maxZ`);
|
|
721
|
+
}
|
|
722
|
+
if (fluid.density !== undefined && (!Number.isFinite(fluid.density) || fluid.density <= 0)) {
|
|
723
|
+
throw new Error(`physics3d fluid ${fluid.id}: density must be positive`);
|
|
724
|
+
}
|
|
725
|
+
if (fluid.linearDrag !== undefined && (!Number.isFinite(fluid.linearDrag) || fluid.linearDrag < 0)) {
|
|
726
|
+
throw new Error(`physics3d fluid ${fluid.id}: linearDrag must be non-negative`);
|
|
727
|
+
}
|
|
728
|
+
if (fluid.angularDrag !== undefined && (!Number.isFinite(fluid.angularDrag) || fluid.angularDrag < 0)) {
|
|
729
|
+
throw new Error(`physics3d fluid ${fluid.id}: angularDrag must be non-negative`);
|
|
730
|
+
}
|
|
731
|
+
if (fluid.flowVelocity !== undefined) {
|
|
732
|
+
const { x, y, z } = fluid.flowVelocity;
|
|
733
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
734
|
+
throw new Error(`physics3d fluid ${fluid.id}: flowVelocity must be finite`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
696
738
|
export function stepDeterministicPhysicsWorld3D(world, options = {}) {
|
|
739
|
+
for (const fluid of options.fluids ?? []) {
|
|
740
|
+
validateFluidVolume(fluid);
|
|
741
|
+
}
|
|
697
742
|
const scheduledQueries = sortScheduledQueries(options.scheduledQueries ?? []);
|
|
698
743
|
const prePhysicsScheduledQueryResults = scheduledQueries
|
|
699
744
|
.filter((query) => query.phase === 'pre-physics')
|
|
@@ -711,7 +756,7 @@ export function stepDeterministicPhysicsWorld3D(world, options = {}) {
|
|
|
711
756
|
}
|
|
712
757
|
const dt = options.dtTicks ?? 1;
|
|
713
758
|
const gravityY = options.gravityY ?? 0;
|
|
714
|
-
const rigid = runRigidSolver(world, dt, gravityY, velocityIterations, substeps, options.joints ?? [], options.pairs);
|
|
759
|
+
const rigid = runRigidSolver(world, dt, gravityY, velocityIterations, substeps, options.joints ?? [], options.pairs, options.fluids);
|
|
715
760
|
const constrained = solveConstraints3D(rigid.bodies, options.constraints ?? []);
|
|
716
761
|
const solvedBodies = constrained.bodies;
|
|
717
762
|
// Dynamic-involved solid-collision events come from the solver's manifolds (so
|
|
@@ -783,6 +828,7 @@ export function stepDeterministicPhysicsWorld3D(world, options = {}) {
|
|
|
783
828
|
world: nextWorld,
|
|
784
829
|
contacts: events,
|
|
785
830
|
brokenJointIds: rigid.brokenJointIds,
|
|
831
|
+
brokenJoints: rigid.brokenJointIds,
|
|
786
832
|
scheduledQueryResults,
|
|
787
833
|
prePhysicsScheduledQueryResults,
|
|
788
834
|
postPhysicsScheduledQueryResults,
|
|
@@ -2236,7 +2282,7 @@ function sleepingRebuildIsNoop(body, sb) {
|
|
|
2236
2282
|
&& eq(q(sb.w.x), body.angularVel.x) && eq(q(sb.w.y), body.angularVel.y) && eq(q(sb.w.z), body.angularVel.z)
|
|
2237
2283
|
&& eq(sb.lowVelFrames, body.lowVelFrames);
|
|
2238
2284
|
}
|
|
2239
|
-
function runRigidSolver(world, dt, gravityY, velocityIterations, substeps, joints, pairs) {
|
|
2285
|
+
function runRigidSolver(world, dt, gravityY, velocityIterations, substeps, joints, pairs, fluids) {
|
|
2240
2286
|
const source = world.bodies;
|
|
2241
2287
|
const indexById = new Map(source.map((body, index) => [body.id, index]));
|
|
2242
2288
|
const solverBodies = source.map((body, index) => buildSolverBody(body, index));
|
|
@@ -2281,6 +2327,7 @@ function runRigidSolver(world, dt, gravityY, velocityIterations, substeps, joint
|
|
|
2281
2327
|
warm,
|
|
2282
2328
|
pairs,
|
|
2283
2329
|
joints: solverJoints,
|
|
2330
|
+
fluids,
|
|
2284
2331
|
});
|
|
2285
2332
|
// Post-solve safety clamp: no fast body may end past its TOI along its motion.
|
|
2286
2333
|
for (const [index, clamp] of fastClamps) {
|
package/dist/runner.d.ts
CHANGED
|
@@ -85,7 +85,17 @@ export interface SyncplayRunnerOfflineOptions<Input> {
|
|
|
85
85
|
}
|
|
86
86
|
export interface SyncplayRunnerSnapshot<RenderState> {
|
|
87
87
|
readonly status: SyncplayRunnerStatus;
|
|
88
|
+
/**
|
|
89
|
+
* Discrete tick render state. Note: to prevent 30Hz display judder on
|
|
90
|
+
* high-refresh displays, prefer interpolatedRenderState or runner.getRenderState().
|
|
91
|
+
*/
|
|
88
92
|
readonly renderState?: Readonly<RenderState>;
|
|
93
|
+
/**
|
|
94
|
+
* Smooth presentation state interpolated between previous and current
|
|
95
|
+
* render states at the current `renderAlpha`. If no interpolator is
|
|
96
|
+
* configured or interpolation throws, falls back to `renderState`.
|
|
97
|
+
*/
|
|
98
|
+
readonly interpolatedRenderState?: Readonly<RenderState>;
|
|
89
99
|
readonly renderAlpha: number;
|
|
90
100
|
readonly localSlot: number;
|
|
91
101
|
readonly rollbackCount: number;
|
package/dist/runner.js
CHANGED
|
@@ -68,9 +68,28 @@ export function createSyncplayRunner(config) {
|
|
|
68
68
|
return localSession !== undefined ? localSession.currentFrame : 0;
|
|
69
69
|
}
|
|
70
70
|
function snapshot() {
|
|
71
|
+
let interpolatedRender;
|
|
72
|
+
if (currentRender !== undefined) {
|
|
73
|
+
if (previousRender !== undefined && config.presentation.interpolate !== undefined) {
|
|
74
|
+
try {
|
|
75
|
+
interpolatedRender = config.presentation.interpolate(previousRender, currentRender, renderAlpha);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
interpolatedRender = currentRender;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
interpolatedRender = currentRender;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
71
85
|
return Object.freeze({
|
|
72
86
|
status,
|
|
73
|
-
...(currentRender === undefined
|
|
87
|
+
...(currentRender === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: {
|
|
90
|
+
renderState: currentRender,
|
|
91
|
+
interpolatedRenderState: interpolatedRender,
|
|
92
|
+
}),
|
|
74
93
|
renderAlpha,
|
|
75
94
|
localSlot,
|
|
76
95
|
rollbackCount: networkClient?.rollbacks ?? 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/rundot-syncplay",
|
|
3
|
-
"version": "5.28.0-beta.
|
|
3
|
+
"version": "5.28.0-beta.4",
|
|
4
4
|
"description": "Kinetix orchestration, rollback, replay, late-join, and deterministic networking for RUN.game",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"test:core": "node --expose-gc --import tsx --test --test-concurrency=1 tests/canonical.test.ts tests/canonical-portability.test.ts tests/runtime-session.test.ts tests/kinetix-runtime-session.test.ts tests/runtime-support.test.ts tests/runtime-guards.test.ts tests/r3f-render-adapter.test.ts tests/snapshot-cost.test.ts tests/sdkSession.test.ts tests/input-authority.test.ts tests/session-wire.test.ts tests/runtime-room-metadata.test.ts tests/session-snapshot.test.ts tests/authority-room.test.ts tests/networked-client-snapshot.test.ts tests/runner.test.ts tests/testing.test.ts tests/browser-session.test.ts tests/secret-config.test.ts tests/secret-protocol.test.ts tests/secret-authority.test.ts tests/secret-client.test.ts tests/late-join-checksum.test.ts tests/late-join-input-backfill.test.ts tests/replay-scoring.test.ts tests/bot-backfill.test.ts tests/latency-simulation.test.ts tests/production-input-budget.test.ts tests/time-sync.test.ts tests/match-log.test.ts tests/spectator.test.ts tests/synctest.test.ts tests/instant-replay.test.ts tests/session-promotion.test.ts tests/physics3d-broadphase-stats.test.ts tests/entrypoints.test.ts tests/export-surface.test.ts tests/module-seam.test.ts tests/module-contract-portability.test.ts tests/module-identity.test.ts tests/module-corpus.test.ts tests/component-model.test.ts tests/component-model-rollback.test.ts tests/component-model-integration.test.ts tests/actors-ai.test.ts tests/character-3d.test.ts tests/character-3d-certification.test.ts tests/combat-modules.test.ts tests/combat-modules-integration.test.ts tests/firearm.test.ts tests/gameplay-effects.test.ts tests/spatial-interaction.test.ts tests/stats-abilities.test.ts tests/inventory-equipment.test.ts tests/inventory-equipment-certification.test.ts tests/platformer-2d.test.ts tests/platformer-2d-certification.test.ts tests/module-promotion.test.ts tests/module-docs.test.ts tests/module-bundle-budget.test.ts tests/published-side-effects.test.ts tests/host-timing.test.ts tests/test-manifest.test.ts tests/coarse-region.test.ts tests/input-codec.test.ts tests/command-ingress.test.ts tests/session-reconfigure.test.ts tests/prediction-mode.test.ts tests/ephemeral-channel.test.ts tests/runner-surface.test.ts tests/runner-harness.test.ts tests/effects-adapter-bounds.test.ts tests/simulated-match-secrets.test.ts tests/run-glue-surface.test.mjs core/tests/core-boundary.test.mjs tests/math-core-parity.test.ts tests/math-profile.test.ts tests/movement3d-kcc-unit-contract.test.ts tests/movement3d-static-ground.test.ts",
|
|
169
169
|
"test:sdk-contract": "node --import tsx --test tests/sdk-runtime-contract.test.ts",
|
|
170
170
|
"test:soak": "tsx --test tests/runtime-soak.test.ts",
|
|
171
|
-
"test:extended": "tsx --test tests/kcc-vehicle-characterization.test.ts tests/animation.test.ts tests/asset-cooking.test.ts tests/backend-proof-checksum-vectors.test.ts tests/bot-documents.test.ts tests/bot-primitives.test.ts tests/collider-cooking.test.ts tests/collision.test.ts tests/command-timeline.test.ts tests/config-bundle.test.ts tests/cosmetic-physics3d.test.ts tests/deterministic-primitives.test.ts tests/ecs-lite.test.ts tests/effects-adapter.test.ts tests/engine-runtime.test.ts tests/errors.test.ts tests/events.test.ts tests/input-button.test.ts tests/load-128-players.test.ts tests/math.test.ts tests/movement.test.ts tests/movement3d.test.ts tests/multiclient-authority.test.ts tests/navigation.test.ts tests/noise.test.ts tests/physics-cert.test.ts tests/physics2d.test.ts tests/physics3d-ccd.test.ts tests/physics3d-coverage.test.ts tests/physics3d-debug-wireframe.test.ts tests/physics3d-destruction-cert.test.ts tests/physics3d-forces-and-contacts.test.ts tests/physics3d-geometry-limits.test.ts tests/physics3d-heightfield.test.ts tests/physics3d-hull.test.ts tests/physics3d-joints.test.ts tests/physics3d-kinematic-mesh.test.ts tests/physics3d-kinematic-poses.test.ts tests/physics3d-kinematic-transforms.test.ts tests/physics3d-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-queries-capsule-childindex.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.test.ts tests/physics3d-shape-builders.test.ts tests/physics3d-shared.test.ts tests/physics3d-sleeping-manifold-reuse.test.ts tests/physics3d-solver.test.ts tests/physics3d-static-memoization.test.ts tests/physics3d-static-overlap-memo.test.ts tests/physics3d-cylinder-hull.test.ts tests/physics3d-stacking.test.ts tests/physics3d-toi.test.ts tests/physics3d-vehicle.test.ts tests/physics3d-voxel-carving.test.ts tests/physics3d-voxel-dda.test.ts tests/physics3d-voxel-split.test.ts tests/physics3d-voxel.test.ts tests/physics3d-world-edit.test.ts tests/physics3d.test.ts tests/physics-vector-generator.test.ts tests/random.test.ts tests/rollback-history.test.ts tests/schema-artifacts.test.ts tests/schema-codegen.test.ts tests/schema-dsl-binary.test.ts tests/schema.test.ts tests/signals.test.ts tests/sparse-set.test.ts tests/static-checker.test.ts tests/system-lifecycle.test.ts tests/wire-canonical-sentinels.test.ts tests/ws-frame.test.ts",
|
|
171
|
+
"test:extended": "tsx --test tests/kcc-vehicle-characterization.test.ts tests/animation.test.ts tests/asset-cooking.test.ts tests/backend-proof-checksum-vectors.test.ts tests/bot-documents.test.ts tests/bot-primitives.test.ts tests/collider-cooking.test.ts tests/collision.test.ts tests/command-timeline.test.ts tests/config-bundle.test.ts tests/cosmetic-physics3d.test.ts tests/deterministic-primitives.test.ts tests/ecs-lite.test.ts tests/effects-adapter.test.ts tests/engine-runtime.test.ts tests/errors.test.ts tests/events.test.ts tests/input-button.test.ts tests/load-128-players.test.ts tests/math.test.ts tests/movement.test.ts tests/movement3d.test.ts tests/multiclient-authority.test.ts tests/navigation.test.ts tests/noise.test.ts tests/physics-cert.test.ts tests/physics2d.test.ts tests/physics3d-buoyancy.test.ts tests/physics3d-ccd.test.ts tests/physics3d-coverage.test.ts tests/physics3d-debug-wireframe.test.ts tests/physics3d-destruction-cert.test.ts tests/physics3d-forces-and-contacts.test.ts tests/physics3d-geometry-limits.test.ts tests/physics3d-heightfield.test.ts tests/physics3d-hull.test.ts tests/physics3d-joints.test.ts tests/physics3d-kinematic-mesh.test.ts tests/physics3d-kinematic-poses.test.ts tests/physics3d-kinematic-transforms.test.ts tests/physics3d-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-queries-capsule-childindex.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.test.ts tests/physics3d-shape-builders.test.ts tests/physics3d-shared.test.ts tests/physics3d-sleeping-manifold-reuse.test.ts tests/physics3d-solver.test.ts tests/physics3d-static-memoization.test.ts tests/physics3d-static-overlap-memo.test.ts tests/physics3d-cylinder-hull.test.ts tests/physics3d-stacking.test.ts tests/physics3d-toi.test.ts tests/physics3d-vehicle.test.ts tests/physics3d-voxel-carving.test.ts tests/physics3d-voxel-dda.test.ts tests/physics3d-voxel-split.test.ts tests/physics3d-voxel.test.ts tests/physics3d-world-edit.test.ts tests/physics3d.test.ts tests/physics-vector-generator.test.ts tests/random.test.ts tests/rollback-history.test.ts tests/schema-artifacts.test.ts tests/schema-codegen.test.ts tests/schema-dsl-binary.test.ts tests/schema.test.ts tests/signals.test.ts tests/sparse-set.test.ts tests/static-checker.test.ts tests/system-lifecycle.test.ts tests/wire-canonical-sentinels.test.ts tests/ws-frame.test.ts",
|
|
172
172
|
"test:ci": "npm run build && npm run test",
|
|
173
173
|
"build:native": "node core/scripts/build-native.mjs",
|
|
174
174
|
"build:mobile-native": "node core/scripts/build-mobile-native.mjs",
|