component

Level

A stackable wall ring plus optional slab: per-side openings, sides that can be omitted, and a stairwell hole — the unit a building is composed from.

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

Tune every prop in the workshop →

Add it

npx @runek/cli add level

Pulls floor, wall, @runek/core@^0.15.0.

Use it

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

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

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

{ "type": "Level", "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.
size [number, number] [8, 8] Interior footprint [width, depth], in units.
height number 3 Height, in units.
thickness number 0.2 Thickness, in units.
walls LevelWalls Per-side wall configs; an omitted side renders a solid wall.
floor boolean | LevelFloorConfig true The slab underfoot; false for none, or a config with a stairwell opening.
color color palette wall Defaults to the world palette’s wall slot.
seed number Reserved for procedural variation.

LevelWalls

Prop Type Default Description
front LevelWallConfig
back LevelWallConfig
left LevelWallConfig
right LevelWallConfig

LevelWallConfig

Prop Type Default Description
present boolean Set false to leave this side open (porch, lean-to).
openings WallOpening[]
color color palette wall Defaults to the level color, then the world palette’s wall slot.

WallOpening

Prop Type Default Description
offset number Horizontal center offset from the wall center, in units.
width number required
height number required
sill number Height of the opening’s base above the wall base, in units.

LevelFloorConfig

Prop Type Default Description
opening FloorOpening
thickness number
color color palette floor Defaults to the world palette’s floor slot.

FloorOpening

Prop Type Default Description
x number Hole center offset from the slab center along X, in units.
z number Hole center offset from the slab center along Z, in units.
width number required
depth number required

Registry manifest

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

Browse the whole catalog in the gallery →.

Source (Level.tsx)
import { useWorld, type Vec3 } from '@runek/core'
import { Floor, type FloorOpening } from './Floor'
import { Wall, type WallOpening } from './Wall'

/** One side of a level's wall ring. */
export interface LevelWallConfig {
  /** Set `false` to leave this side open (porch, lean-to). */
  present?: boolean
  openings?: WallOpening[]
  /** Defaults to the level `color`, then the world palette's `wall` slot. */
  color?: string
}

/** Per-side wall configs: front is +z, back is -z, left is -x, right is +x. */
export interface LevelWalls {
  front?: LevelWallConfig
  back?: LevelWallConfig
  left?: LevelWallConfig
  right?: LevelWallConfig
}

export interface LevelFloorConfig {
  opening?: FloorOpening
  thickness?: number
  /** Defaults to the world palette's `floor` slot. */
  color?: string
}

export interface LevelProps {
  position?: Vec3
  rotation?: Vec3
  /** Interior footprint `[width, depth]`, in units. */
  size?: [number, number]
  height?: number
  thickness?: number
  /** Per-side wall configs; an omitted side renders a solid wall. */
  walls?: LevelWalls
  /** The slab underfoot; `false` for none, or a config with a stairwell `opening`. */
  floor?: boolean | LevelFloorConfig
  /** Defaults to the world palette's `wall` slot. */
  color?: string
  /** Reserved for procedural variation. */
  seed?: number
}

const SIDE_TURN: Vec3 = [0, Math.PI / 2, 0]

/**
 * One ring of walls plus an optional slab — the stackable unit of a building.
 * Its origin is the base of its walls, so level N sits at the summed height of
 * the levels below it. Composes `Wall` and `Floor`.
 */
export function Level({
  position = [0, 0, 0],
  rotation = [0, 0, 0],
  size = [8, 8],
  height = 3,
  thickness = 0.2,
  walls,
  floor = true,
  color,
}: LevelProps) {
  const { unit } = useWorld()
  const [w, d] = size
  const halfW = (w / 2) * unit
  const halfD = (d / 2) * unit
  const floorConfig = floor === true ? {} : floor

  const side = (config: LevelWallConfig | undefined, pos: Vec3, turned: boolean, width: number) =>
    config?.present === false ? null : (
      <Wall
        position={pos}
        rotation={turned ? SIDE_TURN : undefined}
        width={width}
        height={height}
        thickness={thickness}
        color={config?.color ?? color}
        openings={config?.openings}
      />
    )

  return (
    <group position={position} rotation={rotation}>
      {floorConfig && (
        <Floor
          position={[0, 0, 0]}
          size={size}
          thickness={floorConfig.thickness ?? thickness}
          opening={floorConfig.opening}
          color={floorConfig.color}
        />
      )}
      {side(walls?.front, [0, 0, halfD], false, w)}
      {side(walls?.back, [0, 0, -halfD], false, w)}
      {side(walls?.left, [-halfW, 0, 0], true, d)}
      {side(walls?.right, [halfW, 0, 0], true, d)}
    </group>
  )
}