You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Ammo.js如何实现指向单点的汇聚式作用力?

用Ammo.js实现空间汇聚式引力(行星/恒星引力效果)

嘿,我来给你捋清楚怎么用Ammo.js实现这种像行星一样的汇聚式引力效果,刚好你提到要复刻Oimo的那个行星示例,咱们一步步来:

核心思路

Ammo.js默认的重力是固定方向的(比如Y轴向下),要实现指向单点的引力,核心就是给每个动态刚体施加自定义的、随位置变化的中心力——每一帧计算刚体到引力中心的方向,然后把这个方向的力施加给刚体,模拟“被拉向中心”的效果。

复刻Oimo.js行星示例的具体步骤

1. 初始化物理世界(先关掉默认重力)

首先得搭好Ammo.js的物理环境,记得把默认重力关掉,因为我们要自己实现引力:

// 初始化Ammo物理世界
const collisionConfig = new Ammo.btDefaultCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfig);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const physicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfig);
physicsWorld.setGravity(new Ammo.btVector3(0, 0, 0)); // 关闭默认全局重力

2. 创建引力中心(静态刚体)

引力中心可以是一个静态的大球(质量设为0就是静态,不会被其他物体推动):

function createGravityCenter(position) {
  // 创建球形碰撞形状
  const shape = new Ammo.btSphereShape(5); // 半径5,可调整
  const transform = new Ammo.btTransform();
  transform.setIdentity();
  transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));
  
  // 静态刚体质量设为0
  const mass = 0;
  const localInertia = new Ammo.btVector3(0, 0, 0);
  shape.calculateLocalInertia(mass, localInertia);
  
  const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, new Ammo.btDefaultMotionState(transform), shape, localInertia);
  const rigidBody = new Ammo.btRigidBody(rbInfo);
  
  physicsWorld.addRigidBody(rigidBody);
  return rigidBody;
}

// 把引力中心放在原点
const gravityCenter = createGravityCenter({ x: 0, y: 0, z: 0 });

3. 每一帧给动态刚体施加引力

这是最关键的一步:在动画循环里,遍历所有需要受引力影响的动态刚体,计算它们到引力中心的方向,然后施加中心力。这里可以用平方反比定律模拟真实引力(距离越近引力越强),也可以用固定强度的力:

// 引力系数,可根据效果调整
const GRAVITY_CONSTANT = 1000;

function applyCustomGravity(rigidBody, centerBody) {
  // 获取刚体当前位置
  const bodyTransform = new Ammo.btTransform();
  rigidBody.getWorldTransform(bodyTransform);
  const bodyPos = bodyTransform.getOrigin();
  
  // 获取引力中心位置
  const centerTransform = new Ammo.btTransform();
  centerBody.getWorldTransform(centerTransform);
  const centerPos = centerTransform.getOrigin();
  
  // 计算指向引力中心的方向向量
  const direction = new Ammo.btVector3(
    centerPos.x() - bodyPos.x(),
    centerPos.y() - bodyPos.y(),
    centerPos.z() - bodyPos.z()
  );
  
  const distance = direction.length();
  if (distance < 0.1) return; // 避免距离过近导致的计算问题
  
  // 归一化方向,计算引力大小(这里用平方反比:G * 质量 / 距离平方)
  direction.normalize();
  // 如果刚体有质量,记得乘以刚体质量,效果更真实
  const forceMagnitude = GRAVITY_CONSTANT * rigidBody.getMass() / (distance * distance);
  const force = direction.multiplyScalar(forceMagnitude);
  
  // 给刚体施加中心力
  rigidBody.applyCentralForce(force);
  
  // 清理Ammo对象,避免内存泄漏
  Ammo.destroy(bodyTransform);
  Ammo.destroy(centerTransform);
  Ammo.destroy(direction);
  Ammo.destroy(force);
}

// 动画循环里的处理
function animate() {
  requestAnimationFrame(animate);
  
  // 遍历所有动态刚体,施加引力
  dynamicRigidBodies.forEach(body => {
    applyCustomGravity(body, gravityCenter);
  });
  
  // 更新物理模拟
  physicsWorld.stepSimulation(1/60, 10);
  
  // (如果用Three.js的话)同步刚体位置到3D模型
  // syncRigidBodyToMesh(body, mesh);
}

扩展到Three.js + Ammo.js实现3D引力的要点

如果要做你提到的3D引力示例,还需要注意这些细节:

  • 模型与刚体同步:每个Three.js的Mesh对应一个Ammo.js的btRigidBody,在物理模拟后,把刚体的位置和旋转同步到Mesh上(注意坐标转换,Ammo和Three.js都是右手系,直接转就行)
  • 多引力中心支持:如果有多个行星,只需要在applyCustomGravity里计算每个引力中心的力,然后叠加后施加给刚体
  • 性能优化:如果有大量刚体,不要每次创建新的btVector3,可以复用全局的向量对象,减少内存分配和销毁的开销
  • 碰撞交互:给引力中心设置碰撞响应,比如刚体碰到行星后反弹或者停止,只需要在创建刚体时设置碰撞分组即可

内容的提问来源于stack exchange,提问作者Géo-IT Solutions

火山引擎 最新活动