You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

THREE.js+Physijs实现实体边缘Torus:球体穿内环遇边缘反弹

Fixing Torus Collision in Physijs: Blocking Spheres on Edges While Allowing Inner Ring Pass-Through

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

  1. 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.
  2. Full Scene Initialization: Added missing camera, renderer, lighting, and orbit controls to make the demo fully functional.
  3. Physijs Dependencies: Ensure you have physijs_worker.js and ammo.js in your project directory—these are required for Physijs to run physics simulations.
  4. Adjustable Precision: Tweak the segments variable in createCollidableTorus to 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:58:14