← gallery

structures

Roof

Flat or gable roof.

Install

npx runek add roof

Pulls: core@react-three/rapier@^2.2.0

Props

export interface RoofProps {
  position?: Vec3
  rotation?: Vec3
  /** `[width, depth]` in units. The roof rests with its base at the component origin. */
  size?: [number, number]
  style?: RoofStyle
  /** Ridge height for a gable roof, in units. */
  peak?: number
  thickness?: number
  overhang?: number
  /** Defaults to the world palette's `roof` slot. */
  color?: string
}

Source

Roof.tsx
import { RigidBody } from '@react-three/rapier'
import { useWorld, type Vec3 } from '@runek/core'

export type RoofStyle = 'flat' | 'gable'

export interface RoofProps {
  position?: Vec3
  rotation?: Vec3
  /** `[width, depth]` in units. The roof rests with its base at the component origin. */
  size?: [number, number]
  style?: RoofStyle
  /** Ridge height for a gable roof, in units. */
  peak?: number
  thickness?: number
  overhang?: number
  /** Defaults to the world palette's `roof` slot. */
  color?: string
}

export function Roof({
  position = [0, 0, 0],
  rotation = [0, 0, 0],
  size = [8, 8],
  style = 'gable',
  peak = 1.6,
  thickness = 0.18,
  overhang = 0.3,
  color,
}: RoofProps) {
  const { unit, palette } = useWorld()
  const roofColor = color ?? palette.roof
  const w = size[0] * unit + overhang * 2 * unit
  const d = size[1] * unit + overhang * 2 * unit
  const t = thickness * unit

  if (style === 'flat') {
    return (
      <RigidBody type="fixed" colliders="cuboid" position={position} rotation={rotation}>
        <mesh castShadow receiveShadow position={[0, t / 2, 0]}>
          <boxGeometry args={[w, t, d]} />
          <meshStandardMaterial color={roofColor} />
        </mesh>
      </RigidBody>
    )
  }

  const ridge = peak * unit
  const slope = Math.hypot(d / 2, ridge)
  const angle = Math.atan2(ridge, d / 2)

  return (
    <RigidBody type="fixed" colliders="cuboid" position={position} rotation={rotation}>
      <mesh castShadow receiveShadow position={[0, ridge / 2, d / 4]} rotation={[angle, 0, 0]}>
        <boxGeometry args={[w, t, slope]} />
        <meshStandardMaterial color={roofColor} />
      </mesh>
      <mesh castShadow receiveShadow position={[0, ridge / 2, -d / 4]} rotation={[-angle, 0, 0]}>
        <boxGeometry args={[w, t, slope]} />
        <meshStandardMaterial color={roofColor} />
      </mesh>
    </RigidBody>
  )
}