component
Fountain
Two-tier stone fountain with gently rippling water (a per-frame bob, no textures).
⌖ Explore the library in 3D → · View as Markdown
Add it
npx @runek/cli add fountain
Pulls @react-three/fiber@^9.6.1, @react-three/rapier@^2.2.0, @runek/core@^0.15.0, three@^0.184.0.
Use it
import { Fountain } from './runek/Fountain'
<Fountain position={[0, 0, 0]} />
Or as a node in a world file (worlds as data):
{ "type": "Fountain", "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 |
1.6 |
Radius, in units. |
color |
color |
palette stone |
Stone color; defaults to the world palette’s stone slot. |
waterColor |
color |
palette waterShallow |
Water color; defaults to the world palette’s waterShallow slot. |
Registry manifest
registry manifest /r/components/fountain.json Self-contained JSON: inlined source plus resolved dependencies, exactly whatrunek add fountain fetches.
Browse the whole catalog in the gallery →.
Source (Fountain.tsx)
import { useFrame } from '@react-three/fiber'
import { CuboidCollider, RigidBody } from '@react-three/rapier'
import { useWorld, type Vec3 } from '@runek/core'
import { useRef } from 'react'
import type { Mesh } from 'three'
export interface FountainProps {
position?: Vec3
rotation?: Vec3
/** Accepted for the component contract; this component has no seeded variation yet. */
seed?: number
radius?: number
/** Stone color; defaults to the world palette's `stone` slot. */
color?: string
/** Water color; defaults to the world palette's `waterShallow` slot. */
waterColor?: string
}
/** A two-tier stone fountain with gently rippling water (a light per-frame bob,
* no textures). A plaza centerpiece. The basin is solid, so it owns a collider. */
export function Fountain({
position = [0, 0, 0],
rotation = [0, 0, 0],
radius = 1.6,
color,
waterColor,
}: FountainProps) {
const { unit, palette } = useWorld()
const stone = color ?? palette.stone
const water = waterColor ?? palette.waterShallow
const R = radius * unit
const basinH = 0.5 * unit
const topY = basinH + 1.0 * unit
const lowWater = useRef<Mesh>(null)
const topWater = useRef<Mesh>(null)
useFrame(({ clock }) => {
const t = clock.elapsedTime
if (lowWater.current)
lowWater.current.position.y = basinH * 0.7 + Math.sin(t * 1.6) * 0.01 * unit
if (topWater.current) topWater.current.position.y = topY + Math.sin(t * 2.1 + 1) * 0.008 * unit
})
return (
<RigidBody type="fixed" colliders={false} position={position} rotation={rotation}>
<CuboidCollider args={[R, basinH / 2, R]} position={[0, basinH / 2, 0]} />
<mesh castShadow receiveShadow position={[0, basinH / 2, 0]}>
<cylinderGeometry args={[R, R * 1.05, basinH, 28, 1, true]} />
<meshStandardMaterial color={stone} roughness={0.92} side={2} />
</mesh>
<mesh receiveShadow position={[0, 0.05 * unit, 0]}>
<cylinderGeometry args={[R, R, 0.1 * unit, 28]} />
<meshStandardMaterial color={stone} roughness={0.92} />
</mesh>
<mesh ref={lowWater} position={[0, basinH * 0.7, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<circleGeometry args={[R * 0.92, 28]} />
<meshStandardMaterial
color={water}
transparent
opacity={0.8}
roughness={0.2}
metalness={0.1}
/>
</mesh>
<mesh castShadow position={[0, basinH + 0.5 * unit, 0]}>
<cylinderGeometry args={[0.16 * unit, 0.22 * unit, 1.0 * unit, 16]} />
<meshStandardMaterial color={stone} roughness={0.92} />
</mesh>
<mesh castShadow position={[0, topY, 0]}>
<cylinderGeometry args={[R * 0.5, R * 0.35, 0.18 * unit, 24, 1, true]} />
<meshStandardMaterial color={stone} roughness={0.92} side={2} />
</mesh>
<mesh ref={topWater} position={[0, topY, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<circleGeometry args={[R * 0.46, 24]} />
<meshStandardMaterial color={water} transparent opacity={0.85} roughness={0.2} />
</mesh>
<mesh castShadow position={[0, topY + 0.2 * unit, 0]}>
<sphereGeometry args={[0.1 * unit, 12, 12]} />
<meshStandardMaterial color={stone} roughness={0.9} />
</mesh>
</RigidBody>
)
}
Fountain.groundSitting = true