THREE.js+Physijs实现实体边缘Torus:球体穿内环遇边缘反弹
Got it, let's break down why your current setup isn't working, and how to fix it to get the exact behavior you want—spheres bouncing off the torus edges but passing through the inner ring.
The Root Problem
When you use Physijs.Mesh with a THREE.TorusGeometry, Physijs automatically generates a convex hull collision shape for it. Convex hulls wrap the entire geometry in the smallest possible convex shape, which for a torus means it creates a sort of "donut-shaped blob" that blocks the inner ring entirely. That's why your spheres can pass straight through the torus—they're interacting with this convex hull, not the actual torus edges. Using BoxMesh isn't an option either, since it can't replicate the torus's curved, hollow shape.
The Solution: Compound Collision Shapes
To get the right collision behavior, we need to build a compound collision shape for the torus. This means creating multiple small convex collision bodies (like capsules or cylinders) that follow the torus's outer edge, leaving the inner ring empty. Physijs will treat these combined shapes as a single collision object, so spheres will bounce off the edges but pass through the middle.
Modified Working Code
Here's the full, fixed code with proper scene setup and compound collision toruses:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Physijs Torus Collision Demo</title> <style> #display { position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; z-index: -1 } </style> </head> <body> <canvas id="display"></canvas> <script src="three.min.js"></script> <script src="physi.js"></script> <script src="http://cdn.jsdelivr.net/gh/mrdoob/three.js@r73/examples/js/controls/OrbitControls.js"></script> <script> let scene, renderer, camera, controls; function init() { // Initialize Physijs dependencies Physijs.scripts.worker = 'physijs_worker.js'; // Ensure this file exists in your project Physijs.scripts.ammo = 'ammo.js'; // Scene setup scene = new Physijs.Scene(); scene.setGravity(new THREE.Vector3(0, -50, 0)); // Camera configuration camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.set(0, 30, 80); camera.lookAt(new THREE.Vector3(0, 20, 0)); // Renderer setup renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('display'), antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; // Orbit controls for camera manipulation controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; // Lighting setup const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(50, 100, 50); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; scene.add(directionalLight); // Ground plane with working collision const plane = new Physijs.BoxMesh( new THREE.CubeGeometry(100, 100, 2, 10, 10), Physijs.createMaterial( new THREE.MeshLambertMaterial({ color: 0xeeeeee }), 0.4, 0.99 ), 0 ); plane.rotation.x = -Math.PI / 2; plane.receiveShadow = true; scene.add(plane); // Create toruses with compound collision shapes for (let i = 0; i < 50; i++) { createCollidableTorus(i); } // Spawn bouncing balls at intervals setInterval(() => { const ball = new Physijs.SphereMesh( new THREE.SphereGeometry(Math.random() * (4 - 1) + 1, 16, 16), Physijs.createMaterial( new THREE.MeshLambertMaterial({ color: 0xff0000, reflectivity: 0.8 }), 0.4, 0.99 ), 1 ); ball.position.y = 40; ball.position.x = (Math.random() - 0.5) * 40; ball.position.z = (Math.random() - 0.5) * 40; ball.castShadow = true; ball.receiveShadow = true; scene.add(ball); }, 600); // Start physics simulation scene.simulate(); render(); } function createCollidableTorus(yPosition) { // Visual torus mesh const torusGeometry = new THREE.TorusGeometry(18, 0.2, 16, 100); const torusMaterial = Physijs.createMaterial( new THREE.MeshNormalMaterial({ color: 0x00FFFF }), 0.4, 0.99 ); const torus = new Physijs.Mesh(torusGeometry, torusMaterial, 0); // Build compound collision shape for torus edge const collisionShape = new Physijs.CompoundCollisionShape(); const segments = 60; // Balance between collision precision and performance const torusRadius = 18; const tubeRadius = 0.2; for (let i = 0; i < segments; i++) { const angle = (i / segments) * Math.PI * 2; // Create small capsule for each segment of the torus edge const capsule = new Physijs.CapsuleMesh( new THREE.CylinderGeometry(tubeRadius, tubeRadius, 0.1, 8), torusMaterial, 0 ); // Position capsule along torus circumference capsule.position.x = Math.cos(angle) * torusRadius; capsule.position.z = Math.sin(angle) * torusRadius; // Rotate capsule to align with torus curve capsule.rotation.y = angle; // Add capsule to compound collision shape collisionShape.addChild(capsule, capsule.position, capsule.rotation); } // Assign compound shape to the torus torus.setCollisionShape(collisionShape); // Position and rotate the torus torus.position.y = yPosition; torus.rotation.y = Math.PI / 24; torus.rotation.x = -Math.PI / 2; torus.collisions = 0; scene.add(torus); } function render() { requestAnimationFrame(render); controls.update(); renderer.render(scene, camera); scene.simulate(undefined, 1); } window.onload = init; // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html>
Key Changes Explained
- Compound Collision Shape: We use 60 small capsule collision bodies arranged along the torus's outer edge. This lets spheres bounce off the edges while leaving the inner ring open for passage.
- Full Scene Initialization: Added missing camera, renderer, lighting, and orbit controls to make the demo fully functional.
- Physijs Dependencies: Ensure you have
physijs_worker.jsandammo.jsin your project directory—these are required for Physijs to run physics simulations. - Adjustable Precision: Tweak the
segmentsvariable increateCollidableTorusto balance collision accuracy and performance (more segments = better accuracy, slightly higher resource usage).
Important Notes
- Use compatible versions of Three.js (r73, matching your original code) and Physijs to avoid breaking changes.
- Capsule shapes are ideal for curved edges, but you can replace them with small cylinders if you prefer similar behavior.
内容的提问来源于stack exchange,提问作者λjk.jk

