Skip to content

151. cannon 物理引擎:弹簧效果

Published:

生活中经常可以看到各种弹簧的效果。

比如这个沙袋:

image.png

上面有三根绳子固定,可以随着击打来摆动。

当然这个绳子不是弹簧,我是说类似这种效果,或者上面绳子有一些弹力的时候,都可以用弹簧的 api 来做。

这节我们先学会用这个 api:

npx create-vite cannon-spring

image.png

进入项目,安装依赖:

pnpm install
pnpm install --save three
pnpm install --save-dev @types/three
pnpm install --save cannon-es

改下 src/main.js

import './style.css';
import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);

scene.add(mesh);

scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const dir = new THREE.DirectionalLight(0xffffff, 0.8);
dir.position.set(10, 12, 6);
scene.add(dir);

const helper = new THREE.AxesHelper(1000);
// scene.add(helper);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
camera.position.set(6, 6, 12);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer({
  antialias: true,
});
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Light、Camera、Renderer。

改下 style.css

body {
  margin: 0;
}

写一下 mesh.js

import * as THREE from 'three';

const group = new THREE.Group();

const geo = new THREE.PlaneGeometry(20, 20);
const mat = new THREE.MeshLambertMaterial({ color: 0x8b4513, side: THREE.DoubleSide });
const ground  = new THREE.Mesh(geo, mat);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;

group.add(ground);

export default group;

先画下地面。

看下效果:

npm run dev

image.png

2025-11-29 18.37.00.gif

然后先把弹簧和悬挂的物体画出来:

image.png

const radius = 0.6;
const ballMesh = new THREE.Mesh(
  new THREE.SphereGeometry(radius, 24, 24),
  new THREE.MeshPhongMaterial({ color: 0x2196f3 })
);
ballMesh.position.set(2, 4, 0);
group.add(ballMesh);

const anchorMesh = new THREE.Mesh(
    new THREE.SphereGeometry(0.2, 16, 16),
    new THREE.MeshPhongMaterial({ color: 0xff0000 })
);
anchorMesh.position.set(0, 6, 0);
group.add(anchorMesh);

image.png

中间用弹簧连起来:

image.png

那这种弹簧怎么画呢?

很明显是 TubeGeometry 管道管道几何体。

我们要把对应的曲线画出来,然后用管道几何体画出来就好了:

image.png

const springMesh = createSpringMesh();
group.add(springMesh);

function createSpringMesh() {
    const curve = new THREE.CatmullRomCurve3([
        new THREE.Vector3(0, 0, 0),
        new THREE.Vector3(0, 1, 0)
    ]);

    const tubeGeometry = new THREE.TubeGeometry(curve, 20, 0.05, 8, false);
    const tubeMaterial = new THREE.MeshPhongMaterial({
        color: 0xffff00,
        side: THREE.DoubleSide
    });
    return new THREE.Mesh(tubeGeometry, tubeMaterial);
}

function updateSpringVisualization() {
    const start = anchorMesh.position;
    const end = ballMesh.position;
    const direction = new THREE.Vector3().subVectors(end, start);
    const length = direction.length();
  
    const points = [];
    const coils = 10;
    const radius = 0.2;
  
    for (let i = 0; i <= 50; i++) {
      const t = i / 50;
      const angle = t * coils * Math.PI * 2;
      const x = Math.cos(angle) * radius;
      const z = Math.sin(angle) * radius;
      const y = t * length;
      points.push(new THREE.Vector3(x, y, z));
    }
  
    const curve = new THREE.CatmullRomCurve3(points);
    springMesh.geometry.dispose();
    springMesh.geometry = new THREE.TubeGeometry(curve, 50, 0.05, 8, false);
  
    springMesh.position.copy(start);
    springMesh.quaternion.setFromUnitVectors(
      new THREE.Vector3(0, 1, 0),
      direction.normalize()
    );
}

updateSpringVisualization();

先看效果:

2025-11-29 20.28.10.gif

首先用三维样条曲线 CatmullRomCurve3 画经过一些点的曲线。

这些点其实就是一个个相同半径的圆上的,只不过竖直方向不同,所以是这样计算位置:

image.png

用 CatmullRomCurve 穿过这些点,连接起来。

然后 TubeGeometry 基于这个曲线画管道就可以了。

此外,还要调整方向:

image.png

这里用 quaternion.setFromUnitVectors 来指定旋转角度,分别指定起点和终点就可以了。

image.png

案例代码上传了小册仓库

总结

这节我们把弹簧小球的 ui 画了一下。

难点主要是 CatmullRomCurve3 + TubeGeometry 画弹簧。

下节加上 cannon 的物理效果。

评论