← gallery

interiors

Rug

Procedural striped rug (seeded stripes, no textures).

Install

npx runek add rug

Pulls: core

Props

export interface RugProps {
  position?: Vec3
  rotation?: Vec3
  /** `[width, depth]` in units. */
  size?: [number, number]
  /** Defaults to the world palette's `fabric` slot. */
  baseColor?: string
  /** Defaults to the world palette's `accent` slot. */
  borderColor?: string
  accentColor?: string
  seed?: number
}

Source

Rug.tsx
import { rng, useWorld, type Vec3 } from '@runek/core'
import { useMemo } from 'react'

export interface RugProps {
  position?: Vec3
  rotation?: Vec3
  /** `[width, depth]` in units. */
  size?: [number, number]
  /** Defaults to the world palette's `fabric` slot. */
  baseColor?: string
  /** Defaults to the world palette's `accent` slot. */
  borderColor?: string
  accentColor?: string
  seed?: number
}

interface Stripe {
  x: number
  width: number
}

/** Decorative — flat, no collider; walk over it. */
export function Rug({
  position = [0, 0, 0],
  rotation = [0, 0, 0],
  size = [3, 2],
  baseColor,
  borderColor,
  accentColor = '#9c5252',
  seed = 1,
}: RugProps) {
  const { unit, palette } = useWorld()
  const baseCol = baseColor ?? palette.fabric
  const borderCol = borderColor ?? palette.accent
  const w = size[0] * unit
  const d = size[1] * unit
  const t = 0.02 * unit
  const border = 0.12 * unit

  const stripes = useMemo<Stripe[]>(() => {
    const next = rng(seed)
    const span = w - border * 2
    const count = 3 + Math.floor(next() * 4)
    return Array.from({ length: count }, () => ({
      x: (next() - 0.5) * span,
      width: (0.04 + next() * 0.08) * unit,
    }))
  }, [w, border, seed, unit])

  return (
    <group position={position} rotation={rotation}>
      <mesh receiveShadow position={[0, t / 2, 0]}>
        <boxGeometry args={[w, t, d]} />
        <meshStandardMaterial color={borderCol} />
      </mesh>
      <mesh position={[0, t + 0.001 * unit, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[w - border * 2, d - border * 2]} />
        <meshStandardMaterial color={baseCol} />
      </mesh>
      {stripes.map((s) => (
        <mesh
          key={`stripe-${s.x.toFixed(3)}`}
          position={[s.x, t + 0.002 * unit, 0]}
          rotation={[-Math.PI / 2, 0, 0]}
        >
          <planeGeometry args={[s.width, d - border * 2]} />
          <meshStandardMaterial color={accentColor} />
        </mesh>
      ))}
    </group>
  )
}