@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
}