import * as THREE from 'three' import * as CANNON from 'cannon-es' export class PhysicsManager { static setupPhysicsWorld(): CANNON.World { const world = new CANNON.World() // Setup physics world world.gravity.set(0, 0, 0) // Use SAPBroadphase for better collision detection with many objects world.broadphase = new CANNON.SAPBroadphase(world) // Enable collision detection and response world.allowSleep = false // Prevent objects from sleeping // Configure contact material for better collisions const defaultMaterial = new CANNON.Material('default') const defaultContactMaterial = new CANNON.ContactMaterial(defaultMaterial, defaultMaterial, { friction: 0.4, restitution: 0.3, contactEquationStiffness: 1e8, contactEquationRelaxation: 3, frictionEquationStiffness: 1e8, frictionEquationRelaxation: 3 }) world.addContactMaterial(defaultContactMaterial) world.defaultMaterial = defaultMaterial return world } static updatePhysics( world: CANNON.World, physicsObjects: Array<{ mesh: THREE.Object3D; body: CANNON.Body }>, attractionPoint: THREE.Vector3, mouseWorldPosition: THREE.Vector3, deltaTime: number ): void { // Use a smaller, more stable timestep for better collision detection const fixedTimeStep = 1/60 // 60 Hz - more stable than 120 Hz const maxSubSteps = 5 // Increased substeps for better collision accuracy world.step(fixedTimeStep, deltaTime, maxSubSteps) // Apply forces to objects physicsObjects.forEach((obj) => { // Attraction to center point const attractionForce = new CANNON.Vec3() attractionForce.x = attractionPoint.x - obj.body.position.x attractionForce.y = attractionPoint.y - obj.body.position.y attractionForce.z = attractionPoint.z - obj.body.position.z const distance = Math.sqrt( attractionForce.x * attractionForce.x + attractionForce.y * attractionForce.y + attractionForce.z * attractionForce.z ) if (distance > 0) { const strength = 16.0 / (distance * distance + 1) // Reduced from 8.0 to prevent objects moving too fast attractionForce.scale(strength, attractionForce) obj.body.force.set( obj.body.force.x + attractionForce.x, obj.body.force.y + attractionForce.y, obj.body.force.z + attractionForce.z ) } // Mouse repulsion const repulsionForce = new CANNON.Vec3() repulsionForce.x = obj.body.position.x - mouseWorldPosition.x repulsionForce.y = obj.body.position.y - mouseWorldPosition.y repulsionForce.z = obj.body.position.z - mouseWorldPosition.z const mouseDistance = Math.sqrt( repulsionForce.x * repulsionForce.x + repulsionForce.y * repulsionForce.y + repulsionForce.z * repulsionForce.z ) if (mouseDistance < 2.5 && mouseDistance > 0) { // Decreased range from 5 to 2.5 const repulsionStrength = 25.0 / (mouseDistance * mouseDistance + 0.1) // Increased strength from 15.0 to 25.0 repulsionForce.scale(repulsionStrength, repulsionForce) obj.body.force.set( obj.body.force.x + repulsionForce.x, obj.body.force.y + repulsionForce.y, obj.body.force.z + repulsionForce.z ) // Add rotational torque from mouse interaction const torque = new CANNON.Vec3( (Math.random() - 0.5) * repulsionStrength * 0.1, (Math.random() - 0.5) * repulsionStrength * 0.1, (Math.random() - 0.5) * repulsionStrength * 0.1 ) obj.body.torque.set( obj.body.torque.x + torque.x, obj.body.torque.y + torque.y, obj.body.torque.z + torque.z ) } // Apply damping obj.body.velocity.scale(0.99, obj.body.velocity) // Update mesh position to match physics body obj.mesh.position.copy(obj.body.position as any) obj.mesh.quaternion.copy(obj.body.quaternion as any) }) } }