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




