Use Ammo js debug drawer interface

I’m using the interface code provided here:

Here’s the error being thrown in the console:

ammo.js:46 Uncaught TypeError: S[r[((r[(a >> 2)] + 24) >> 2)]] is not a function
    at qd (ammo.js:46:25190)
    at Z.debugDrawWorld (ammo.js:900:424)
    at AmmoDebugDrawer.update (<anonymous>:95:16)
    at <anonymous>:219:23

I’m not really sure how to proceed from here. Oddly enough I can run the code using my own local build of Ammo.js but I can’t see anything being rendered.

EDIT: If I use the underlying vertex data Ammo.js provides to the debugVertices variable to draw a point cloud it sort of works (with my local Ammo.js build):

My guess here is ammojs interface/build changed a bit between when this interface was done(3 years ago) and now.

Is there anything missing in PhysicsViewer PhysicsViewer | Babylon.js Documentation that forces you to use ammo-debug-drawer ?

2 Likes

Is there anything missing in PhysicsViewer PhysicsViewer | Babylon.js Documentation that forces you to use ammo-debug-drawer ?

Nothing, other than my own ignorance :laughing: I can’t get it to work with these mesh imposters:

I don’t see it working in this example either:

I need this bad. I will take a look tomorrow. Had no clue this was a thing.

1 Like

A simple example PG here:

You can also enable debug viewer for whole scene with the inspector:

image

I tried that though in the playground I linked

An error gets thrown -

Uncaught TypeError: Cannot read properties of undefined (reading 'meshMap')
    at new t (babylon.js?t=1658390685880:sourcemap:1:825510)
    at e._getDebugMeshMesh (babylon.js?t=1658390685880:sourcemap:1:1852533)
    at e._getDebugMesh (babylon.js?t=1658390685880:sourcemap:1:1853590)
    at e.showImpostor (babylon.js?t=1658390685880:sourcemap:1:1849835)
    at Cs.switchPhysicsViewers (babylon.inspector.bundle.js?t=1658390685880:sourcemap:2:548722)
    at Object.onSelect (babylon.inspector.bundle.js?t=1658390685880:sourcemap:2:549267)
    at Hr.onChange (babylon.inspector.bundle.js?t=1658390685880:sourcemap:2:515472)
    at onChange (babylon.inspector.bundle.js?t=1658390685880:sourcemap:2:517000)
    at Object.Ye (babylon.inspector.bundle.js?t=1658390685880:sourcemap:2:269795)
    at Qe (babylon.inspector.bundle.js?t=1658390685880:sourcemap:2:269949)

But it doesn’t do that for this example

In that example ^ I can get the mesh imposter to be visible (I think one of them is the mesh imposter anyways) by using the toggle feature, but when I use the physics Viewer directly it does not work.

You have to set the 2nd argument as the mesh:

1 Like

Good catch! I didn’t see that before, I think that particular pg got pulled up from either the playground search or directly from the docs. I’ll go back and check later.

So, I did notice I was using PhysicsViewer directly export in the BABYLON namespace, and not the method exported from the BABYLON.Debug name space. Not sure if that has an effect.

I still can’t get my example to work though, using instanced meshes with the rocks:

This is the relevant code:

                const instancedMesh = rootMesh.createInstance(
                    `${rootMesh.id}-inst-${i}`
                )
                const { x, y, z} = scalingInfo[meshName]
                instancedMesh.scaling = new Vector3(x, y, z)
                    
                // instancedMesh.scaling = transformations.scaling.clone()
                instancedMesh.position = transformations.translation.clone()
                instancedMesh.rotationQuaternion = transformations.rotation.clone()

                window.instancedMeshes.push(instancedMesh)
                instancedMesh.parent = null

                const meshImpostor = new PhysicsImpostor(
                    instancedMesh,
                    // PhysicsImpostor['ConvexHullImpostor'],
                    PhysicsImpostor['MeshImpostor'],
                    { mass: 0, friction: 0.2 },
                    scene,
                )
                instancedMesh.physicsImpostor = meshImpostor

                window.imps.push(meshImpostor)
                
                window.vImps.push(physV.showImpostor(
                    meshImpostor, 
                    instancedMesh,
                ))

I can’t enable the debug viewer from the inspector. this is weird.
can you try to reduce the PG until you get a debug viewer working?

1 Like

Sure thing! But I think that’s the error I was mentioning earlier with the undefined mesh maps.

Here’s the same pg but with a lot less code:

It’s 4am here, so I’m gonna have to hit the sack soon, but I wanted to thank you for looking at this. I’m assuming I’ve done something wrong here, but I just don’t know what

1 Like

I think I get it, at least for a part of the problem.
The Object_6 (terrain) is too big for ammojs, it can’t create an impostor for that and so it’s impossible to get a debug display.
I get an OOM error in the console. half a million faces is a bit to big :slight_smile:
Can you try with a less detailed terrain ?

I agree the map is large :D, but I’m only creating imposters for 50 rocks in the pg example. I can even remove the terrain itself:

The error:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'meshMap')
    at new t (mesh.ts:510:51)
    at e._getDebugMeshMesh (physicsViewer.ts:228:31)
    at e._getDebugMesh (physicsViewer.ts:277:33)
    at e.showImpostor (physicsViewer.ts:107:32)
    at <anonymous>:183:37
    at Array.forEach (<anonymous>)
    at <anonymous>:151:38

Still occurs.

Got it!
It doesn’t work with createInstance but does with clone.
I’ll do more tests Monday.

1 Like

While I’m waiting for a resolution on the instanced mesh physics overlay I wrote a utility class to handle rendering large physics scenes that the Debug.PhysicsViewer class would not perform very well on (when you need to render 1,000,000 + vertices).

You can increase the line segment interpolation density as well as set alternating colors with an adjustable point size. There’s some other stuff too like like setting an update period for when you want to capture still frames of movement.

As you can see here in this example I have some trees that have some slight rotation offset from their parent model:


I’ve already started to get quite a bit of use out of this. Though I admit the color alternation wasn’t necessary, just pretty :stuck_out_tongue: .

I made this gist that has everything you need, including the Ammo.js build I made locally to work with the DebugDrawerInterface. In an npm/webpack project just unpack the builds.zip file and drop the contents in your projects node_modules/ammo.js/builds/ directory. If you find this useful I’d love to hear some feedback on the tool.

2 Likes

I made this just to kind of check the feasibility of making a debug system using our lineSystem. Performance wise Im not sure how many dynamic meshes it can show really, but if most of your stuff is static this might work.

Would need to add the rest of the collider shapes, and I would not want to update mesh colliders with a greater then 0 mass. But this might work.

1 Like

I too tried that. When there are many vertices like I mentioned it takes a while to load. A line system and the Debug.PhysicsViewer probably get similar performance in the range of 10k - 50k objects and it’ll run fine, but when you’re using a large heightmap terrain (<1 million vertices) it just completely freezes for a good minute. Using the point cloud system is viable at any scale.

I’ve found that the Debug.PhysicsViewer is good for debugging more localized objects (mesh objects that are unique or only have a few instances in a given scene) vs environmental objects that appear thousands of times in a given scene - trees, rocks, grass etc.

1 Like

Yo @arcman7 … Are you using btHeightfieldTerrainShape for this large terrain you are talking about…I am trying to add support for large terrain exports from unity. I am having POSITION and CENTERING issues because of how Ammo re-centers the heightfeild based on the center extents and the man and max allowed heights… How are you dealing with this stuff for your terrain implementation of heightfield shapes

1 Like

@MackeyK24 Here’s the code:

export const getDims = (meshes: Mesh[] | AbstractMesh[]) => {
  let [minX, minY, minZ] = [Infinity, Infinity, Infinity]
  let [maxX, maxY, maxZ] = [-Infinity, -Infinity, -Infinity]
  
  meshes.forEach((m) => {
    const box = m.getBoundingInfo().boundingBox

    // update maxs
    if (box.maximumWorld.x > maxX) {
      maxX = box.maximumWorld.x
    }
    if (box.maximumWorld.y > maxY) {
      maxY = box.maximumWorld.y
    } 
    if (box.maximumWorld.z > maxZ) {
      maxZ = box.maximumWorld.z
    }
    
    // update mins
    if (box.minimumWorld.x < minX) {
      minX = box.minimumWorld.x
    }
    if (box.minimumWorld.y < minY) {
      minY = box.minimumWorld.y
    } 
    if (box.minimumWorld.z < minZ) {
      minZ = box.minimumWorld.z
    }  
  })

  const terrainWidthX = maxX - minX
  const terrainHeightY = maxY - minY
  const terrainWidthZ = maxZ - minZ

  return { terrainWidthZ, terrainHeightY, terrainWidthX, maxX, minX, maxY, minY, maxZ, minZ, terrainMinHeight: minY, terrainMaxHeight: maxY }
}

export const createAmmoTerrainShape = (
  ammoInstance: Ammo,
  heightMapData: number[][],
  dimensions: { width: number, depth: number, minY: number, maxY: number }
) => {
  const { width, depth, minY, maxY } = dimensions
  const height = maxY - minY
  const numberOfRows = heightMapData.length
  const numberOfCols = heightMapData[0].length

  // This parameter is not really used, since we are using PHY_FLOAT height data type and hence it is ignored
  const heightScale = 1

  // Up axis = 0 for X, 1 for Y, 2 for Z. Normally 1 = Y is used.
  const upAxis = 1

  // hdt, height data type. "PHY_FLOAT" is used. Possible values are "PHY_FLOAT", "PHY_UCHAR", "PHY_SHORT"
  const hdt = "PHY_FLOAT"
  // const hdt = "PHY_SHORT"

  // Set this to your needs (inverts the triangles)
  const flipQuadEdges = false

  let p2 = 0
  //@ts-ignore Creates height data buffer in Ammo heap
  const ammoHeightData = ammoInstance._malloc(
    4 * numberOfRows * numberOfCols
  )
  //@ts-ignore
  window.hmap = heightMapData
  for (let i = 0; i < numberOfRows; i++) {
    for(let j = numberOfCols - 1; j >= 0; j--) {
      //@ts-ignore
      ammoInstance.HEAPF32[ammoHeightData + p2 >> 2] = heightMapData[i][j] * height
      p2 += 4
    }
  }

  const heightFieldShape = new ammoInstance.btHeightfieldTerrainShape(
    numberOfRows, // width subdivisions
    numberOfCols, // depth subdivisions
    ammoHeightData,
    heightScale,
    minY, // minHeight
    maxY, // maxHeight
    upAxis, //@ts-ignore
    hdt, 
    flipQuadEdges
  )

  const scaleX = (width) / numberOfRows
  const scaleZ = (depth) / numberOfCols
  const scaleY = 1.00

  console.log('ground using scaling: ',
    scaleX,
    scaleY,
    scaleZ,
  )

  heightFieldShape.setLocalScaling(
    new ammoInstance.btVector3(
      scaleX,
      scaleY,
      scaleZ,
    )
  )

  heightFieldShape.setMargin(0.05)

  return heightFieldShape
}

export const createAmmoHeightmapRigidBody = (
  meshes: Mesh[] | AbstractMesh[],
  ammoInstance: Ammo,
  physicsWorld: btDiscreteDynamicsWorld,
  groundShape: btHeightfieldTerrainShape,
  subdivisionsX: number,
  subdivisionsZ: number,
) => {
  const terrainDims = getDims(meshes)
  const { terrainMaxHeight, terrainMinHeight, terrainWidthX, terrainWidthZ } = terrainDims

  const groundTransform = new ammoInstance.btTransform()

  groundTransform.setIdentity()
  groundTransform.setRotation(
    new ammoInstance.btQuaternion(0, 0, 0, 1)
  )

  const dX = terrainWidthX / subdivisionsX
  const dZ = terrainWidthZ / subdivisionsZ
  const originX = (terrainWidthX / 2) + (dX * 0.5) // no idea why we need these offsets
  const originZ = (terrainWidthZ / 2) + (dZ * -0.5) // no idea why we need these offsets
  const originY =  (terrainMaxHeight + terrainMinHeight) / 2

  console.log(
    'ground origin: ',
    originX,
    originY,
    originZ,
  )

  groundTransform.setOrigin(
    new ammoInstance.btVector3(originX, originY, originZ)
  )
  
  const groundMass = 0
  const groundLocalInertia = new ammoInstance.btVector3(0, 0, 0)
  const groundMotionState = new ammoInstance.btDefaultMotionState(groundTransform)
  
  const groundBody = new ammoInstance.btRigidBody(
    new ammoInstance.btRigidBodyConstructionInfo(
      groundMass, groundMotionState, groundShape, groundLocalInertia
    )
  )
  groundBody.setFriction(0.5)
  /* why is this necessary? */
  // groundBody.setActivationState(DISABLE_DEACTIVATION)

  physicsWorld.addRigidBody(groundBody)

  return groundBody
}