threejs-heightfield: Know where the ground is.
Bake any mesh or group into a heightfield once, then look up the height, measure the slope and ray-march the ground anywhere, with no per-object raycasts. A height function works the same way, so a modelled island and an endless procedural world look alike to everything built on top.
Ask the ground anything.
Bake once, then every lookup is a few multiplies. threejs-grass and threejs-scatter do this for you when you hand them a terrain; use it directly to put your own things on the same ground.
import { Raycaster } from 'three/webgpu'
import { bakeHeightfield, createHeightSampler, raycastHeight,
slopeAt } from 'threejs-heightfield'
const height = bakeHeightfield(island) // any mesh, baked once
const y = height(12, -40) // NaN off the terrain
// keep trees off cliffs
if (slopeAt(height, 12, -40) < 0.6) tree.position.set(12, y, -40)
// brushes and picking without a triangle raycast
const ray = new Raycaster()
ray.setFromCamera(pointer, camera)
const { origin, direction } = ray.ray
const hit = raycastHeight(height, origin, direction)
// procedural worlds: functions pass straight through
const hills = createHeightSampler((x, z) => 6 * Math.sin(x / 50))
import { useMemo } from 'react'
import { useGLTF } from '@react-three/drei'
import { bakeHeightfield, slopeAt } from 'threejs-heightfield'
import { Grass } from 'threejs-grass/react'
function Island() {
const { scene } = useGLTF('/island.glb')
const height = useMemo(() => bakeHeightfield(scene), [scene])
const flat = spots.filter(([x, z]) =>
slopeAt(height, x, z) < 0.4)
return (
<>
<primitive object={scene} />
<Grass terrain={height} />
{flat.map(([x, z], i) => (
<mesh key={i} geometry={rock}
position={[x, height(x, z), z]} />
))}
</>
)
}
Small, and underneath everything.
Four functions and two types. The sandbox puts all four on a height function and on baked meshes side by side; threejs-grass and threejs-scatter sample their terrain through it, so these shots come from their demos.
Bake any mesh
Triangles in, heights out.
bakeHeightfield() rasterises every triangle under a mesh or group on the CPU, world transforms included, into a grid of about maxResolution samples along the longer side (1024 by default). Where surfaces overlap the highest one wins, so things land on the bridge, not under it.
threejs-grass demoSmooth lookups
Bilinear inside, NaN outside.
The baked sampler blends the four nearest samples, so heights are continuous across the grid and objects don't step. Off the edge of the terrain it returns NaN instead of a made-up zero, and callers simply skip those points.
threejs-grass demoFunctions pass straight through
Endless worlds, same API.
createHeightSampler() takes a mesh, an (x, z) => y function or nothing at all (flat ground at y = 0) and always hands back a HeightFn. Procedural terrain, such as the height of a threejs-biomes world, is never baked, so it can go on forever.
threejs-biomes demoSlope
Keep trees off cliffs.
slopeAt() measures rise over run by central differences, 25 cm either side by default. Grass uses it to skip ground steeper than maxSlope; use it the same way to keep trees off cliffs and buildings off hillsides.
threejs-grass demoRay-march the ground
Brushes, picking, camera collision.
raycastHeight() marches a ray in half-metre steps, then bisects twelve times to the surface. It works on baked meshes and procedural terrain alike, so nothing needs a triangle raycast. The threejs-grass terrain painter runs on it.
Made for React too
Nothing new to learn.
There is no component: bake in a useMemo and pass the sampler to <Grass>, <Scatter> or your own meshes. Those components also take a mesh ref as terrain and bake it for you.
React Three Fiber demoStand on solid ground.
Every function is documented with examples in the API reference. Try every parameter in the sandbox, and see it at work in the grass and scatter demos.