Created
December 11, 2021 16:46
-
-
Save kenjinp/1b1eb135747682f9b7a2b5c37bfbd7d5 to your computer and use it in GitHub Desktop.
Create a Rapier Heightfield Collider from an Image (Heightmap)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import getPixels from "get-pixels"; | |
import { max, min } from "lodash"; | |
import { NdArray } from "ndarray"; | |
import { Vector3 } from "three"; | |
// Example, use like: | |
const makeHeightfieldColliderFromImage = async () => { | |
const heightMapTexture = "myimage.png"; | |
const xSubdivisions = 200; | |
const zSubdivisions = 200; | |
const yScale = 10; | |
const data = await imageToHeightmap( | |
heightMapTexture, | |
xSubdivisions, | |
zSubdivisions, | |
yScale | |
); | |
const scale = 1; | |
const nrows = data.numYRows; | |
const ncols = data.numXCols; | |
const heights = data.tileHeights; | |
return new Rapier.Heightfield(nrows, ncols, heights, scale); | |
}; | |
export const getImageValues = ( | |
imageURL: string | |
): Promise<NdArray<Uint8Array>> => { | |
return new Promise((resolve, reject) => { | |
getPixels(imageURL, (error, pixels) => { | |
if (error) { | |
return reject(error); | |
} | |
return resolve(pixels); | |
}); | |
}); | |
}; | |
// example taken from | |
// https://www.npmjs.com/package/image-to-heightmap | |
export const imageToHeightmap = async ( | |
imageURL: string, | |
numXCols: number, | |
numYRows: number, | |
scale: number | |
) => { | |
const pixels = await getImageValues(imageURL); | |
const tileHeights = new Array(); | |
const imageWidth = pixels.shape[0]; | |
const imageHeight = pixels.shape[1]; | |
const sizeX = numXCols + 1; | |
const sizeY = numYRows + 1; | |
// Loop through all of our tile corners, and generate a height for each corner | |
// tileHeights[x] = []; | |
for (let x = 0; x < sizeX; x++) { | |
for (let y = 0; y < sizeY; y++) { | |
// Get the corresponding pixel for the current tile corner | |
const pixelXPos = Math.ceil((x * (imageWidth - 1)) / numXCols); | |
const pixelYPos = Math.ceil((y * (imageHeight - 1)) / numYRows); | |
// Get the colors for the given pixel | |
const red = pixels.get(pixelXPos, pixelYPos, 0); | |
const green = pixels.get(pixelXPos, pixelYPos, 1); | |
const blue = pixels.get(pixelXPos, pixelYPos, 2); | |
// TODO: Should we calculate alpha for each pixel if we're expecting | |
// the same value every time? Should we even expect the same value every time? | |
const alpha = pixels.get(pixelXPos, pixelYPos, 3); | |
// Note that we divide by `alpha` to get the height | |
// If we always use fully opaque images, alpha will be the maximum possible value | |
// This allows us to handle 8bit, 16bit, or Nbit images | |
// Values are based on ratios. We don't care about the images bit depth | |
const heightAtThisPosition = (scale * (red + green + blue)) / 3 / alpha; | |
tileHeights.push(heightAtThisPosition); | |
} | |
} | |
return { | |
imageWidth, | |
imageHeight, | |
numXCols, | |
numYRows, | |
tileHeights, | |
max: max(tileHeights), | |
min: min(tileHeights), | |
}; | |
}; | |
export const genHeightfieldGeometry = ( | |
nrows: number, | |
ncols: number, | |
heights: number[], | |
scale: Vector3 | |
) => { | |
let vertices = new Array(); | |
let indices = new Array(); | |
let eltWX = 1.0 / nrows; | |
let eltWY = 1.0 / ncols; | |
let i, j; | |
for (j = 0; j <= ncols; ++j) { | |
for (i = 0; i <= nrows; ++i) { | |
let x = (j * eltWX - 0.5) * scale.x; | |
let y = heights[j * (nrows + 1) + i] * scale.y; | |
let z = (i * eltWY - 0.5) * scale.z; | |
vertices.push(x, y, z); | |
} | |
} | |
for (j = 0; j < ncols; ++j) { | |
for (i = 0; i < nrows; ++i) { | |
let i1 = (i + 0) * (ncols + 1) + (j + 0); | |
let i2 = (i + 0) * (ncols + 1) + (j + 1); | |
let i3 = (i + 1) * (ncols + 1) + (j + 0); | |
let i4 = (i + 1) * (ncols + 1) + (j + 1); | |
indices.push(i1, i3, i2); | |
indices.push(i3, i4, i2); | |
} | |
} | |
return { | |
vertices: new Float32Array(vertices), | |
indices: new Uint32Array(indices), | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment