component
Cliff
A rocky promontory: a low-poly, seed-jittered truncated cone with a flattish plateau and a convex-hull collider.
⌖ Explore the library in 3D → · View as Markdown
Add it
npx @runek/cli add cliff
Pulls @react-three/rapier@^2.2.0, @runek/core@^0.15.0, three@^0.184.0.
Use it
import { Cliff } from './runek/Cliff'
<Cliff position={[0, 0, 0]} seed={1} />
Or as a node in a world file (worlds as data):
{ "type": "Cliff", "props": { "position": [0, 0, 0], "seed": 1 } }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
radius |
number |
10 |
Base radius at the waterline, in units. |
topRadius |
number |
6 |
Plateau (top) radius, in units. |
height |
number |
12 |
Height from the base to the plateau, in units. |
segments |
number |
8 |
Radial facets — fewer reads as blockier rock. |
rough |
number |
0.35 |
Surface jitter, as a fraction of a facet. |
color |
color |
Rock color; defaults to the palette’s stone. |
|
position |
[x, y, z] |
[0, 0, 0] |
Position [x, y, z] in units (1 unit = 1 m, Y-up). |
rotation |
[x, y, z] |
[0, 0, 0] |
Euler rotation [x, y, z] in radians. |
seed |
number |
1 |
Seed for the deterministic variation: same seed, same result. |
Registry manifest
registry manifest /r/components/cliff.json Self-contained JSON: inlined source plus resolved dependencies, exactly whatrunek add cliff fetches.
Browse the whole catalog in the gallery →.
Source (Cliff.tsx)
import { RigidBody } from '@react-three/rapier'
import { useWorld, type WorldComponentProps } from '@runek/core'
import { useEffect, useMemo } from 'react'
import { CylinderGeometry } from 'three'
export interface CliffProps extends WorldComponentProps {
/** Base radius at the waterline, in units. */
radius?: number
/** Plateau (top) radius, in units. */
topRadius?: number
/** Height from the base to the plateau, in units. */
height?: number
/** Radial facets — fewer reads as blockier rock. */
segments?: number
/** Surface jitter, as a fraction of a facet. */
rough?: number
/** Rock color; defaults to the palette's `stone`. */
color?: string
}
/**
* A rocky promontory: a low-poly truncated cone, vertex-jittered by `seed`, rising from the
* waterline to a flattish plateau you can stand on. A convex-hull collider blocks the steep sides
* and carries the top. Sink the base below the water in placement so it reads as rising from the
* sea. (Convex — true overhangs aren't modelled.)
*/
export function Cliff({
position = [0, 0, 0],
rotation = [0, 0, 0],
seed = 1,
radius = 10,
topRadius = 6,
height = 12,
segments = 8,
rough = 0.35,
color,
}: CliffProps) {
const { unit, palette } = useWorld()
const rock = color ?? palette.stone
const R = radius * unit
const Rt = topRadius * unit
const Hgt = height * unit
const geometry = useMemo(() => {
const g = new CylinderGeometry(Rt, R, Hgt, segments, 3, false)
g.translate(0, Hgt / 2, 0) // base at local y=0
const amp = rough * (R / segments)
// Jitter keyed on vertex position (not draw order): the cylinder's cap and wall duplicate
// vertices at the same location, and independent offsets would tear cracks between them.
const jitter = (x: number, y: number, z: number, k: number) => {
let h =
(seed ^
Math.imul(Math.round(x * 64), 374761393) ^
Math.imul(Math.round(y * 64), 668265263) ^
Math.imul(Math.round(z * 64), 1274126177) ^
Math.imul(k + 1, 2246822519)) >>>
0
h = Math.imul(h ^ (h >>> 13), 1274126177)
return (((h ^ (h >>> 16)) >>> 0) / 4294967296) * 2 - 1
}
const pos = g.attributes.position
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i)
const y = pos.getY(i)
const z = pos.getZ(i)
const top = y > Hgt - 0.01 // keep the plateau roughly flat for standing
pos.setX(i, x + jitter(x, y, z, 0) * amp)
pos.setZ(i, z + jitter(x, y, z, 1) * amp)
pos.setY(i, y + jitter(x, y, z, 2) * amp * (top ? 0.1 : 0.5))
}
pos.needsUpdate = true
g.computeVertexNormals()
return g
}, [R, Rt, Hgt, segments, rough, seed])
useEffect(() => () => geometry.dispose(), [geometry])
return (
<RigidBody type="fixed" colliders="hull" position={position} rotation={rotation}>
<mesh geometry={geometry} castShadow receiveShadow>
<meshStandardMaterial color={rock} roughness={1} flatShading />
</mesh>
</RigidBody>
)
}