component

Well

Stone well with a dark pool, posts, a little pyramid roof, and a bucket on a rope.

⌖ Explore the library in 3D → · View as Markdown

Tune every prop in the workshop →

Add it

npx @runek/cli add well

Pulls @react-three/rapier@^2.2.0, @runek/core@^0.15.0.

Use it

import { Well } from './runek/Well'

<Well position={[0, 0, 0]} />

Or as a node in a world file (worlds as data):

{ "type": "Well", "props": { "position": [0, 0, 0] } }

Props

Prop Type Default Description
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 Accepted for the component contract; this component has no seeded variation yet.
radius number 0.7 Radius, in units.
wallHeight number 0.8
color color palette stone Stone color; defaults to the world palette’s stone slot.
roofColor color palette wood Roof + frame color; defaults to the world palette’s wood slot.

Registry manifest

registry manifest /r/components/well.json Self-contained JSON: inlined source plus resolved dependencies, exactly what runek add well fetches.

Browse the whole catalog in the gallery →.

Source (Well.tsx)
import { CuboidCollider, RigidBody } from '@react-three/rapier'
import { useWorld, type Vec3 } from '@runek/core'

export interface WellProps {
  position?: Vec3
  rotation?: Vec3
  /** Accepted for the component contract; this component has no seeded variation yet. */
  seed?: number
  radius?: number
  wallHeight?: number
  /** Stone color; defaults to the world palette's `stone` slot. */
  color?: string
  /** Roof + frame color; defaults to the world palette's `wood` slot. */
  roofColor?: string
}

/** A stone well: ring wall, a dark pool, two posts carrying a little pyramid roof,
 *  and a bucket on a rope. A village focal point. */
export function Well({
  position = [0, 0, 0],
  rotation = [0, 0, 0],
  radius = 0.7,
  wallHeight = 0.8,
  color,
  roofColor,
}: WellProps) {
  const { unit, palette } = useWorld()
  const stone = color ?? palette.stone
  const wood = roofColor ?? palette.wood
  const R = radius * unit
  const wh = wallHeight * unit
  const postX = R * 0.92
  const postH = wh + 1.0 * unit
  const roofH = 0.5 * unit

  return (
    <RigidBody type="fixed" colliders={false} position={position} rotation={rotation}>
      <CuboidCollider args={[R, wh / 2, R]} position={[0, wh / 2, 0]} />

      <mesh castShadow receiveShadow position={[0, wh / 2, 0]}>
        <cylinderGeometry args={[R, R * 1.05, wh, 20]} />
        <meshStandardMaterial color={stone} roughness={0.95} />
      </mesh>
      <mesh castShadow position={[0, wh, 0]} rotation={[Math.PI / 2, 0, 0]}>
        <torusGeometry args={[R, 0.06 * unit, 8, 20]} />
        <meshStandardMaterial color={stone} roughness={0.9} />
      </mesh>
      <mesh position={[0, wh - 0.08 * unit, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <circleGeometry args={[R * 0.8, 20]} />
        <meshStandardMaterial color="#0c1a22" roughness={1} />
      </mesh>

      {[-1, 1].map((s) => (
        <mesh key={`post-${s}`} castShadow position={[s * postX, postH / 2, 0]}>
          <boxGeometry args={[0.1 * unit, postH, 0.1 * unit]} />
          <meshStandardMaterial color={wood} roughness={0.85} />
        </mesh>
      ))}
      <mesh castShadow position={[0, postH, 0]} rotation={[0, 0, Math.PI / 2]}>
        <cylinderGeometry args={[0.04 * unit, 0.04 * unit, postX * 2, 8]} />
        <meshStandardMaterial color={wood} roughness={0.85} />
      </mesh>

      <mesh castShadow position={[0, postH + roofH / 2, 0]} rotation={[0, Math.PI / 4, 0]}>
        <coneGeometry args={[R * 1.6, roofH, 4]} />
        <meshStandardMaterial color={wood} roughness={0.8} flatShading />
      </mesh>

      <mesh position={[R * 0.3, postH - 0.4 * unit, 0]}>
        <cylinderGeometry args={[0.008 * unit, 0.008 * unit, 0.75 * unit, 6]} />
        <meshStandardMaterial color="#5a4a32" />
      </mesh>
      <mesh castShadow position={[R * 0.3, postH - 0.85 * unit, 0]}>
        <cylinderGeometry args={[0.1 * unit, 0.08 * unit, 0.16 * unit, 12]} />
        <meshStandardMaterial color={wood} roughness={0.8} />
      </mesh>
    </RigidBody>
  )
}

Well.groundSitting = true