Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Crafting Deformable Volumes in Ammo.js: Using btSoftBodyHelpers and btTransform

Tech 1

The Ammo.btSoftBodyHelpers constructor creates an auxiliary object for generating soft body shapes from meshes. It accepts no arguments and exposes the method CreateFromTriMesh(worldInfo, vertexArray, indexArray, indexCount, randomizeConstraints) which builds a soft body from triangle data.

  • Parameters:
    • worldInfo – the world information object (btSoftBodyWorldInfo).
    • vertexArray – a flat Float32Array of vertex coordinatse.
    • indexArray – a flat Uint32Array (or Uint16Array) of triangle indices.
    • indexCount – number of indices (total array length).
    • randomizeConstraints – boolean that controls whether constraints are randomized for stability.
  • Returns: a btSoftBody instance ready for configuration.

The Ammo.btTransform class represents a rigid body transformation (position and rotation). Its most used properties are origin (a btVector3) and rotation (a btQuaternion). Useful methods include setIdentity(), setOrigin(vector), and setRotation(quaternion). In soft body workflows it often serves as an intermediate transform for reading node positions.

Creating a Soft Volume

The typical pipeline involves preparing geometry, calling CreateFromTriMesh, configuring physical properties, and adding the body to the world. Below is a refactored function that creates a pressurized soft volume:

function makeSoftVolume(geometry, mass, pressure, world, helper) {
  // Merge duplicate vertices to get a clean indexed mesh
  const simpleGeo = new THREE.BufferGeometry();
  simpleGeo.setAttribute('position', geometry.getAttribute('position'));
  simpleGeo.setIndex(geometry.getIndex());
  const indexed = BufferGeometryUtils.mergeVertices(simpleGeo);
  
  // Build mapping from indexed vertices back to original vertices
  const origPos = geometry.attributes.position.array;
  const idxPos = indexed.attributes.position.array;
  const associations = [];
  for (let i = 0; i < idxPos.length; i += 3) {
    const matches = [];
    for (let j = 0; j < origPos.length; j += 3) {
      if (Math.abs(idxPos[i] - origPos[j]) < 1e-6 &&
          Math.abs(idxPos[i+1] - origPos[j+1]) < 1e-6 &&
          Math.abs(idxPos[i+2] - origPos[j+2]) < 1e-6) {
        matches.push(j);
      }
    }
    associations.push(matches);
  }
  
  // Ammo soft body from the indexed mesh
  const softBody = helper.CreateFromTriMesh(
    world.getWorldInfo(),
    idxPos,
    indexed.index.array,
    indexed.index.array.length / 3,
    true
  );
  
  // Configuration
  const cfg = softBody.get_m_cfg();
  cfg.set_viterations(40);
  cfg.set_piterations(40);
  cfg.set_collisions(0x11);      // soft-soft & soft-rigid
  cfg.set_kDF(0.1);              // dynamic friction
  cfg.set_kDP(0.01);             // damping
  cfg.set_kPR(pressure);         // pressure constant
  
  const material = softBody.get_m_materials().at(0);
  material.set_m_kLST(0.9);     // linear stiffness
  material.set_m_kAST(0.9);     // angular stiffness
  
  softBody.setTotalMass(mass, false);
  Ammo.castObject(softBody, Ammo.btCollisionObject)
      .getCollisionShape()
      .setMargin(0.05);
  
  world.addSoftBody(softBody, 1, -1);
  softBody.setActivationState(4); // disable deactivation
  
  // Three.js mesh for rendering
  const mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({color: 0xffffff}));
  mesh.castShadow = true;
  mesh.receiveShadow = true;
  mesh.frustumCulled = false;
  mesh.userData.physicsBody = softBody;
  mesh.userData.associations = associations;
  
  return mesh;
}

After each simulation step the mesh vertices must be updated from the soft body nodes:

function updateSoftMeshes(meshes) {
  const transform = new Ammo.btTransform();
  for (const mesh of meshes) {
    const soft = mesh.userData.physicsBody;
    const posAttr = mesh.geometry.attributes.position;
    const nrmAttr = mesh.geometry.attributes.normal;
    const assoc = mesh.userData.associations;
    const nodes = soft.get_m_nodes();
    
    for (let i = 0; i < assoc.length; i++) {
      const node = nodes.at(i);
      const nx = node.get_m_x().x();
      const ny = node.get_m_x().y();
      const nz = node.get_m_x().z();
      const nnx = node.get_m_n().x();
      const nny = node.get_m_n().y();
      const nnz = node.get_m_n().z();
      
      for (const idx of assoc[i]) {
        posAttr.array[idx] = nx;
        posAttr.array[idx+1] = ny;
        posAttr.array[idx+2] = nz;
        nrmAttr.array[idx] = -nnx;
        nrmAttr.array[idx+1] = -nny;
        nrmAttr.array[idx+2] = -nnz;
      }
    }
    posAttr.needsUpdate = true;
    nrmAttr.needsUpdate = true;
  }
}

Setting up the world and the helper is straightforward:

const collisionConfig = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfig);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const softSolver = new Ammo.btDefaultSoftBodySolver();

const dynamicsWorld = new Ammo.btSoftRigidDynamicsWorld(
  dispatcher, broadphase, solver, collisionConfig, softSolver
);
dynamicsWorld.setGravity(new Ammo.btVector3(0, -9.8, 0));
dynamicsWorld.getWorldInfo().set_m_gravity(new Ammo.btVector3(0, -9.8, 0));

const btHelper = new Ammo.btSoftBodyHelpers();

You can now call makeSoftVolume with a THREE.BoxGeometry, THREE.SphereGeometry, or any triangulated geometry. Remebmer to call dynamicsWorld.stepSimulation(deltaTime) and then updateSoftMeshes to synchronize the visuals.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.