← gallery

interiors

Lamp

Lamp that emits a point light.

Install

npx runek add lamp

Pulls: core@react-three/fiber@^9.6.1@react-three/rapier@^2.2.0three@^0.184.0

Props

export interface LampProps {
  position?: Vec3
  rotation?: Vec3
  height?: number
  /** Base + pole color. Defaults to the world palette's `metal` slot. */
  color?: string
  shadeColor?: string
  lightColor?: string
  intensity?: number
  /** Candle-like intensity flicker, 0–1; 0 holds the light steady. */
  flicker?: number
}

Source

Lamp.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 { PointLight } from 'three'

export interface LampProps {
  position?: Vec3
  rotation?: Vec3
  height?: number
  /** Base + pole color. Defaults to the world palette's `metal` slot. */
  color?: string
  shadeColor?: string
  lightColor?: string
  intensity?: number
  /** Candle-like intensity flicker, 0–1; 0 holds the light steady. */
  flicker?: number
}

export function Lamp({
  position = [0, 0, 0],
  rotation = [0, 0, 0],
  height = 1.6,
  color,
  shadeColor = '#e9d8a6',
  lightColor = '#ffe8c2',
  intensity = 18,
  flicker = 0.08,
}: LampProps) {
  const { unit, palette } = useWorld()
  const metalColor = color ?? palette.metal
  const h = height * unit
  const baseR = 0.16 * unit
  const poleR = 0.02 * unit
  const shadeR = 0.19 * unit
  const shadeH = 0.26 * unit
  const poleH = h - shadeH
  const lightRef = useRef<PointLight>(null)

  useFrame(({ clock }) => {
    if (!lightRef.current || flicker <= 0) return
    const t = clock.elapsedTime
    // layered sines read as a candle without any per-frame randomness
    const n = Math.sin(t * 9.3) * 0.5 + Math.sin(t * 23.7) * 0.3 + Math.sin(t * 41.1) * 0.2
    lightRef.current.intensity = intensity * (1 + n * flicker)
  })

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

      <mesh castShadow position={[0, 0.025 * unit, 0]}>
        <cylinderGeometry args={[baseR, baseR, 0.05 * unit, 24]} />
        <meshStandardMaterial color={metalColor} metalness={0.4} roughness={0.5} />
      </mesh>
      <mesh castShadow position={[0, poleH / 2, 0]}>
        <cylinderGeometry args={[poleR, poleR, poleH, 12]} />
        <meshStandardMaterial color={metalColor} metalness={0.4} roughness={0.5} />
      </mesh>
      <mesh castShadow position={[0, h - shadeH / 2, 0]}>
        <coneGeometry args={[shadeR, shadeH, 24, 1, true]} />
        <meshStandardMaterial
          color={shadeColor}
          emissive={shadeColor}
          emissiveIntensity={0.4}
          side={2}
        />
      </mesh>

      <pointLight
        ref={lightRef}
        position={[0, h - shadeH, 0]}
        color={lightColor}
        intensity={intensity}
        distance={12 * unit}
        decay={2}
      />
    </RigidBody>
  )
}