import { Color } from "../engine/render/Color"
import type { DrawGroup, Material } from "../engine/render/Material"
import type { CloudLayer, SkyConfig } from "../engine/render/Sky"
import { STRIDE, type Mesh } from "../engine/scene/Mesh"
import { Boulder } from "./actors/Boulder"
import { Bush } from "./actors/Bush"
import { Flower, type FlowerColor } from "./actors/Flower"
import type { Mob, MobKind } from "./actors/Mob"
import { Terrain } from "./Terrain"
import { Tree } from "./actors/Tree"
import type { Textures } from "./textures"

type Corner = [number, number, number]

/** Axis-aligned solid. Blocks the player horizontally while their feet are
 *  below `top`; if `standable`, its `top` also counts as ground to land on. */
export type Aabb = {
  minX: number
  maxX: number
  minZ: number
  maxZ: number
  top: number
  standable: boolean
}

/** One spatial cell of the outdoor world: its terrain patch + the trees/boulders
 *  standing in it, baked into `DrawGroup`s (mesh + material), plus an axis-aligned
 *  bounding box (tight to the actual geometry, so overhanging canopies aren't
 *  clipped). The renderer frustum-tests the box and skips the whole cell when it
 *  is off-screen -- this is what keeps a big, dense world affordable. Empty cells
 *  are never created; empty groups are pruned at bake time. */
export type Chunk = {
  minX: number
  minY: number
  minZ: number
  maxX: number
  maxY: number
  maxZ: number
  /** Full-detail draw groups (grass + full trees/boulders), used up close. */
  near: DrawGroup[]
  /** LOD draw groups (grass + cheap tree/boulder impostors, no bushes/flowers),
   *  used once the chunk is past `config.lodDistance` (see `chunkFar`). */
  far: DrawGroup[]
}

/** The materials the chunk baker binds its meshes to -- one per ground/prop
 *  texture. Built once from the loaded `Textures`, shared across every chunk. */
type ChunkMaterials = {
  grass: Material
  bark: Material
  birch: Material
  leaf: Material
  needle: Material
  rock: Material
  flower: Material
}

/** A chunk-material key (also the tag props reference, e.g. a tree's `trunk`). */
type MatKey = keyof ChunkMaterials

/** The fixed order draw groups are emitted in (grass first, flowers -- double-sided
 *  -- last), so the per-chunk draw sequence is deterministic and matches the pre-
 *  registry order. Every material key must appear here. */
const MAT_ORDER: MatKey[] = ["grass", "rock", "bark", "birch", "leaf", "needle", "flower"]

/** The playground: a flat-floored room dropped into the center of a big open
 *  landscape. The room (floor/walls/crate) is small and always drawn; the
 *  outdoor world is split into `chunks` that are frustum-culled per frame. */
export type Level = {
  floor: Mesh
  walls: Mesh
  crate: Mesh
  chunks: Chunk[]
  colliders: Aabb[]
  npcPosition: { x: number; y: number; z: number }
  /** Roaming mobs -- simulated on the main thread each frame (see main.ts), not
   *  baked into the static culled chunks. */
  mobs: Mob[]
  terrain: Terrain
  sky: SkyConfig
}

const ARENA = 12
const WALL_HEIGHT = 4
/** How deep the perimeter walls are. Thick enough to read as solid walls (and to
 *  give the doorway real jambs); their outer faces sit flush with the room edge,
 *  so they eat into the interior, not the terrain. */
const WALL_THICKNESS = 1.5
const CRATE = { x: -2, z: -2, half: 1, height: 1 }
/** Z-bias lifting the stone floor above the terrain skirt that laps under the
 *  room edge (see `buildLevel`). Big enough to beat depth precision, too small
 *  to see. */
const FLOOR_LIFT = 0.02

/** The world around the room: a flat clearing the size of the room (`inner`),
 *  rolling hills beyond, ramping into very high peaks at the `outer` rim ~20x
 *  the room across. Tune freely -- crank `peakHeight` for taller mountains,
 *  `outer` for a bigger world. */
const TERRAIN: Terrain = {
  inner: ARENA,
  outer: ARENA * 10,
  blend: 12,
  amplitude: 5,
  frequency: 0.14,
  peakHeight: 0,
  peakFrequency: 0.05,
  peakStart: 0.45,
}

/** Forest: how many trees to scatter on the grass, and the seed for their
 *  placement/kind/growth. Trees ring the room out to `TREE_REACH` of the world;
 *  each rolls oak-or-spruce and a growth 0..1 (sapling .. full grown). */
const TREE_COUNT = 50
const TREE_SEED = 0x5EED
const TREE_REACH = 1

/** Boulders: how many to scatter, their seed, and how far out they reach
 *  (fraction of the world). Sizes range small pebble .. big boulder. */
const BOULDER_COUNT = 50
const BOULDER_SEED = 0xB0142
const BOULDER_REACH = 1

/** Bushes + flowers: ground detail, kept to the nearer band since they're small
 *  and fog/size hides them far out. Flowers roll white/red/yellow. */
const BUSH_COUNT = 50
const BUSH_SEED = 0xB554
const BUSH_REACH = 1
const FLOWER_COUNT = 50
const FLOWER_SEED = 0xF10E
const FLOWER_REACH = 0.3
const FLOWER_COLORS: FlowerColor[] = ["white", "red", "yellow"]

/** Roaming mobs: how many frogs/bees to scatter, their seed, and how far out they
 *  reach (fraction of the world). Kept modest -- roaming meshes are drawn every
 *  frame (frustum-culled), not baked into the static chunks. */
const FROG_COUNT = 20
const BEE_COUNT = 20
const ROBIN_COUNT = 20
const MOB_SEED = 0x30B
const MOB_REACH = 1

/** Spatial partition of the world for frustum culling: `CHUNK_GRID` x
 *  `CHUNK_GRID` square cells over [-outer, outer]. Smaller cells cull tighter
 *  (less drawn off-screen) but cost more per-cell tests + bounds; this is the
 *  granularity knob. `TERRAIN_SUBDIV` is the terrain quads per cell edge, so the
 *  world's terrain resolution is `CHUNK_GRID * TERRAIN_SUBDIV`. `GROUND_UV` sets
 *  texture tiles/unit. */
const CHUNK_GRID = 12
const TERRAIN_SUBDIV = 5
const GROUND_UV = 0.25

/** The two cloud styles; swap which one the sky uses in `buildLevel`.
 *  `basicCumulus` is cheap flat puffs; `fancyCumulus` is the pricier
 *  heightfield-shaded, domain-warped version with faked volume. */
export const basicCumulus: CloudLayer = {
  kind: "basic",
  color: Color.rgb(248, 250, 255),
  coverage: 0.5,
  scale: 0.9,
  speed: 0.5,
  edge: 0.005,
}

export const fancyCumulus: CloudLayer = {
  kind: "fancy",
  color: Color.rgb(250, 251, 255),
  coverage: 0.5,
  scale: 0.6,
  speed: 0.5,
  edge: 0.02,
  warp: 0.4,
  relief: 7,
}

export function buildLevel(textures: Textures): Level {
  // Flat room floor, lifted a hair above the terrain's clearing (y 0). The
  // outdoor grid's cells straddle the room boundary and lap under the floor's
  // edges; this small z-bias keeps the flat stone floor winning the depth test
  // there instead of z-fighting the grass. The step is invisible at the doorway.
  const floor = mesh()
  const fy = FLOOR_LIFT
  quad(floor, [-ARENA, fy, -ARENA], [ARENA, fy, -ARENA], [ARENA, fy, ARENA], [-ARENA, fy, ARENA], 12, 12)

  const walls = mesh()
  const h = WALL_HEIGHT
  const t = WALL_THICKNESS
  // Three thick perimeter walls, outer faces flush with the room edge; the north
  // (-Z) side is left open onto the world. No ceiling, so the sky shows above.
  slab(walls, -ARENA, ARENA, ARENA - t, ARENA, 0, h, 0.5) // south (+Z)
  slab(walls, ARENA - t, ARENA, -ARENA, ARENA - t, 0, h, 0.5) // east (+X)
  slab(walls, -ARENA, -ARENA + t, -ARENA, ARENA - t, 0, h, 0.5) // west (-X)

  // Crate on the flat room floor.
  const crate = mesh()
  box(crate, CRATE.x, CRATE.z, CRATE.half, 0, CRATE.height)

  const colliders: Aabb[] = [
    wall(-ARENA, ARENA, ARENA - t, ARENA),
    wall(ARENA - t, ARENA, -ARENA, ARENA - t),
    wall(-ARENA, -ARENA + t, -ARENA, ARENA - t),
    {
      minX: CRATE.x - CRATE.half,
      maxX: CRATE.x + CRATE.half,
      minZ: CRATE.z - CRATE.half,
      maxZ: CRATE.z + CRATE.half,
      top: CRATE.height,
      standable: true,
    },
  ]

  const sky: SkyConfig = {
    zenith: Color.rgb(58, 108, 196),
    horizon: Color.rgb(178, 198, 226),
    sun: Color.rgb(255, 246, 214),
    sunDir: { x: 0.3, y: 0.5, z: -0.8 },
    sunSize: 0.04,
    clouds: fancyCumulus,
    skybox: { texture: textures.skybox },
  }

  const npcPosition = { x: 2, y: 0, z: -1 }

  // The ground/prop materials the chunk baker draws with (grass + trees + rocks +
  // flowers). Solid surfaces backface-cull; flowers are double-sided. Shared by
  // every chunk, so cloning to a worker dedups them.
  const materials: ChunkMaterials = {
    grass: { texture: textures.grass, cull: true },
    bark: { texture: textures.bark, cull: true },
    birch: { texture: textures.birch, cull: true },
    leaf: { texture: textures.leaf, cull: true },
    needle: { texture: textures.needle, cull: true },
    rock: { texture: textures.rock, cull: true },
    flower: { texture: textures.flower, cull: false },
  }

  // Place the props (also pushes their colliders), then bake everything into
  // frustum-cullable spatial chunks.
  const trees = placeTrees(colliders)
  const boulders = placeBoulders(colliders)
  const bushes = placeBushes()
  const flowers = placeFlowers()
  const chunks = buildChunks(materials, trees, boulders, bushes, flowers)
  const mobs = placeMobs()

  return { floor, walls, crate, chunks, colliders, npcPosition, mobs, terrain: TERRAIN, sky }
}

/** Bake the terrain + props into a `CHUNK_GRID` x `CHUNK_GRID` set of spatial
 *  chunks. Each prop lands in the cell holding its base; the cell's bounds are
 *  grown to the real geometry so overhanging canopies never get culled early.
 *  Bushes share the oak leaf mesh; flowers get their own (double-sided) mesh. */
function buildChunks(m: ChunkMaterials, trees: Tree[], boulders: Boulder[], bushes: Bush[], flowers: Flower[]): Chunk[] {
  const cell = (TERRAIN.outer * 2) / CHUNK_GRID
  const chunks: Chunk[] = []
  for (let ci = 0; ci < CHUNK_GRID; ci++) {
    const x0 = -TERRAIN.outer + ci * cell
    const x1 = x0 + cell
    for (let cj = 0; cj < CHUNK_GRID; cj++) {
      const z0 = -TERRAIN.outer + cj * cell
      const z1 = z0 + cell
      // Accumulate geometry into one mesh per material key, for the near (full) and
      // far (impostor) LOD sets. Props declare which material(s) they write, so the
      // baker never names a texture -- adding a species/material touches no code here.
      const near = new Map<string, Mesh>()
      const far = new Map<string, Mesh>()
      const grass = matMesh(near, "grass")
      far.set("grass", grass) // the ground is drawn in both LOD sets
      Terrain.patch(TERRAIN, grass, x0, z0, x1, z1, TERRAIN_SUBDIV, TERRAIN_SUBDIV, GROUND_UV)
      for (const tree of trees) {
        if (inCell(tree.position, x0, z0, x1, z1)) {
          const s = Tree.species(tree.kind)
          Tree.build(tree, matMesh(near, s.trunk), matMesh(near, s.foliage))
          Tree.build(tree, matMesh(far, s.trunk), matMesh(far, s.foliage), "impostor")
        }
      }
      for (const boulder of boulders) {
        if (inCell(boulder.position, x0, z0, x1, z1)) {
          Boulder.build(boulder, matMesh(near, "rock"))
          Boulder.build(boulder, matMesh(far, "rock"), "impostor")
        }
      }
      // Bushes fold into the near leaf mesh; they just drop out past lodDistance.
      for (const bush of bushes) {
        if (inCell(bush.position, x0, z0, x1, z1)) {
          Bush.build(bush, matMesh(near, "leaf"))
        }
      }
      for (const flower of flowers) {
        if (inCell(flower.position, x0, z0, x1, z1)) {
          Flower.build(flower, matMesh(near, "flower"))
        }
      }
      const b = bounds([...near.values()])
      if (b === null) {
        continue
      }
      chunks.push({ ...b, near: toGroups(near, m), far: toGroups(far, m) })
    }
  }
  return chunks
}

function inCell(p: { x: number; z: number }, x0: number, z0: number, x1: number, z1: number): boolean {
  return p.x >= x0 && p.x < x1 && p.z >= z0 && p.z < z1
}

/** Lazily get (creating on first use) the accumulation mesh for a material key in a
 *  chunk's near/far map. Props write into these by key, so the baker stays generic. */
function matMesh(map: Map<string, Mesh>, key: string): Mesh {
  let m = map.get(key)
  if (m === undefined) {
    m = mesh()
    map.set(key, m)
  }
  return m
}

/** Turn a chunk's per-material meshes into a draw-group list, in a fixed material
 *  order (so the draw sequence is deterministic across bakes) and dropping any that
 *  ended up empty (a cell rarely holds every prop kind). */
function toGroups(map: Map<string, Mesh>, materials: ChunkMaterials): DrawGroup[] {
  const out: DrawGroup[] = []
  for (const key of MAT_ORDER) {
    const m = map.get(key)
    if (m !== undefined && m.indices.length > 0) {
      out.push({ mesh: m, material: materials[key] })
    }
  }
  return out
}

/** Tight AABB over several meshes' vertices, or null if they are all empty. */
function bounds(meshes: Mesh[]): Pick<Chunk, "minX" | "minY" | "minZ" | "maxX" | "maxY" | "maxZ"> | null {
  let minX = Infinity
  let minY = Infinity
  let minZ = Infinity
  let maxX = -Infinity
  let maxY = -Infinity
  let maxZ = -Infinity
  for (const m of meshes) {
    const verts = m.verts
    for (let i = 0; i < verts.length; i += STRIDE) {
      const x = verts[i]
      const y = verts[i + 1]
      const z = verts[i + 2]
      minX = Math.min(minX, x)
      minY = Math.min(minY, y)
      minZ = Math.min(minZ, z)
      maxX = Math.max(maxX, x)
      maxY = Math.max(maxY, y)
      maxZ = Math.max(maxZ, z)
    }
  }
  return maxX < minX ? null : { minX, minY, minZ, maxX, maxY, maxZ }
}

/** Place `TREE_COUNT` trees around the room on walkable grass: each sits on the
 *  terrain, rolls oak/spruce and a growth stage, and (once past sapling size)
 *  drops a trunk collider so you can't walk through it. */
function placeTrees(colliders: Aabb[]): Tree[] {
  const rand = mulberry(TREE_SEED)
  const maxDist = TERRAIN.outer * TREE_REACH
  const trees: Tree[] = []
  for (let guard = 0; trees.length < TREE_COUNT && guard < TREE_COUNT * 20; guard++) {
    const angle = rand() * Math.PI * 2
    const dist = ARENA + 5 + rand() * (maxDist - ARENA - 5)
    const x = Math.cos(angle) * dist
    const z = Math.sin(angle) * dist
    // Stay out of the room clearing and its flat rim.
    if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 3) {
      continue
    }
    const roll = rand()
    const kind = roll < 0.4 ? "oak" : roll < 0.72 ? "spruce" : "birch"
    const growth = 0.08 + rand() * 0.92
    const position = { x, y: Terrain.height(TERRAIN, x, z), z }
    trees.push({ kind, position, growth, seed: (rand() * 0xFFFFFFFF) | 0 })
    // Saplings are passable; grown trunks block. Square footprint, non-standable.
    if (growth > 0.35) {
      const r = growth * (kind === "oak" ? 0.3 : 0.2) + 0.15
      colliders.push({ minX: x - r, maxX: x + r, minZ: z - r, maxZ: z + r, top: position.y + 3, standable: false })
    }
  }
  return trees
}

/** Scatter `BOULDER_COUNT` boulders across the terrain, sizes biased toward
 *  small. Each sits on the ground; big ones drop a blocking collider so you
 *  can't walk through them (little rocks stay passable). */
function placeBoulders(colliders: Aabb[]): Boulder[] {
  const rand = mulberry(BOULDER_SEED)
  const maxDist = TERRAIN.outer * BOULDER_REACH
  const boulders: Boulder[] = []
  for (let guard = 0; boulders.length < BOULDER_COUNT && guard < BOULDER_COUNT * 20; guard++) {
    const angle = rand() * Math.PI * 2
    const dist = ARENA + 4 + rand() * (maxDist - ARENA - 4)
    const x = Math.cos(angle) * dist
    const z = Math.sin(angle) * dist
    if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 2) {
      continue
    }
    // Square the roll so most rocks are small, a few are big.
    const radius = 0.35 + rand() * rand() * 2.2
    const position = { x, y: Terrain.height(TERRAIN, x, z), z }
    boulders.push({ position, radius, seed: (rand() * 0xFFFFFFFF) | 0 })
    if (radius > 0.7) {
      colliders.push({ minX: x - radius, maxX: x + radius, minZ: z - radius, maxZ: z + radius, top: position.y + radius * 0.7, standable: false })
    }
  }
  return boulders
}

/** Scatter bushes on the grass near the play area (no colliders -- walk through). */
function placeBushes(): Bush[] {
  const rand = mulberry(BUSH_SEED)
  const maxDist = TERRAIN.outer * BUSH_REACH
  const bushes: Bush[] = []
  for (let guard = 0; bushes.length < BUSH_COUNT && guard < BUSH_COUNT * 20; guard++) {
    const angle = rand() * Math.PI * 2
    const dist = ARENA + 3 + rand() * (maxDist - ARENA - 3)
    const x = Math.cos(angle) * dist
    const z = Math.sin(angle) * dist
    if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 2) {
      continue
    }
    bushes.push({ position: { x, y: Terrain.height(TERRAIN, x, z), z }, size: 0.8 + rand() * 1, seed: (rand() * 0xFFFFFFFF) | 0 })
  }
  return bushes
}

/** Scatter small flowers on the grass near the play area, colors rolled. */
function placeFlowers(): Flower[] {
  const rand = mulberry(FLOWER_SEED)
  const maxDist = TERRAIN.outer * FLOWER_REACH
  const flowers: Flower[] = []
  for (let guard = 0; flowers.length < FLOWER_COUNT && guard < FLOWER_COUNT * 20; guard++) {
    const angle = rand() * Math.PI * 2
    const dist = ARENA + 2 + rand() * (maxDist - ARENA - 2)
    const x = Math.cos(angle) * dist
    const z = Math.sin(angle) * dist
    if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 1) {
      continue
    }
    const color = FLOWER_COLORS[(rand() * FLOWER_COLORS.length) | 0]
    flowers.push({ position: { x, y: Terrain.height(TERRAIN, x, z), z }, color, size: 0.28 + rand() * 0.22, seed: (rand() * 0xFFFFFFFF) | 0 })
  }
  return flowers
}

/** Scatter frogs, bees + robins across the grass (like the boulders), each at its
 *  home anchor with a random heading and size. No colliders here -- mobs move, so
 *  their block/stand-on AABBs are rebuilt per frame in `main`. */
function placeMobs(): Mob[] {
  const rand = mulberry(MOB_SEED)
  const maxDist = TERRAIN.outer * MOB_REACH
  const mobs: Mob[] = []
  const total = FROG_COUNT + BEE_COUNT + ROBIN_COUNT
  for (let guard = 0; mobs.length < total && guard < total * 20; guard++) {
    const angle = rand() * Math.PI * 2
    const dist = ARENA + 3 + rand() * (maxDist - ARENA - 3)
    const x = Math.cos(angle) * dist
    const z = Math.sin(angle) * dist
    if (Math.max(Math.abs(x), Math.abs(z)) < TERRAIN.inner + 2) {
      continue
    }
    const n = mobs.length
    const kind: MobKind = n < FROG_COUNT ? "frog" : n < FROG_COUNT + BEE_COUNT ? "bee" : "robin"
    const y = Terrain.height(TERRAIN, x, z)
    const scale = kind === "frog" ? 0.5 + rand() * 0.35 : kind === "robin" ? 0.4 + rand() * 0.25 : 0.5 + rand() * 0.3
    mobs.push({
      kind,
      home: { x, y, z },
      position: { x, y, z },
      heading: rand() * Math.PI * 2,
      scale,
      seed: (rand() * 0xFFFFFFFF) | 0,
      vx: 0,
      vz: 0,
      vy: 0,
      timer: rand() * 1.5,
      // Bees hover (never grounded) and use phase for the bob; frogs/robins start
      // resting on the ground.
      phase: kind === "bee" ? rand() * 10 : 0,
      grounded: kind !== "bee",
    })
  }
  return mobs
}

/** Deterministic 0..1 generator (mulberry32) for tree placement. */
function mulberry(seed: number): () => number {
  let a = seed >>> 0
  return () => {
    a = (a + 0x6D2B79F5) | 0
    let t = Math.imul(a ^ (a >>> 15), 1 | a)
    t ^= t + Math.imul(t ^ (t >>> 7), 61 | t)
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296
  }
}

function mesh(): Mesh {
  return { verts: [], indices: [] }
}

/** A perimeter wall collider: blocks from the sides, and `standable` so you can
 *  jump up and land on its top (given enough JUMP_SPEED to clear WALL_HEIGHT). */
function wall(minX: number, maxX: number, minZ: number, maxZ: number): Aabb {
  return { minX, maxX, minZ, maxZ, top: WALL_HEIGHT, standable: true }
}

/** One flat quad (two tris). Corners run a (uv 0,0) -> b (us,0) -> c (us,vs) ->
 *  d (0,vs); `us`/`vs` set how many texture tiles span it. No subdivision is
 *  needed -- texturing is perspective-correct, so a single quad looks right at
 *  any size. */
function quad(m: Mesh, a: Corner, b: Corner, c: Corner, d: Corner, us: number, vs: number): void {
  const base = m.verts.length / STRIDE
  m.verts.push(a[0], a[1], a[2], 0, 0, b[0], b[1], b[2], us, 0, c[0], c[1], c[2], us, vs, d[0], d[1], d[2], 0, vs)
  m.indices.push(base, base + 1, base + 2, base, base + 2, base + 3)
}

/** An axis-aligned box from (x0,z0)-(x1,z1), y0..y1: four sides + top, no bottom
 *  (never seen from below). `tpu` = texture tiles per world unit, so every face
 *  tiles at the same density whatever its size. Used for the thick walls. */
function slab(m: Mesh, x0: number, x1: number, z0: number, z1: number, y0: number, y1: number, tpu: number): void {
  const dx = (x1 - x0) * tpu
  const dz = (z1 - z0) * tpu
  const dy = (y1 - y0) * tpu
  quad(m, [x0, y1, z0], [x1, y1, z0], [x1, y1, z1], [x0, y1, z1], dx, dz)
  quad(m, [x0, y0, z0], [x1, y0, z0], [x1, y1, z0], [x0, y1, z0], dx, dy)
  quad(m, [x1, y0, z1], [x0, y0, z1], [x0, y1, z1], [x1, y1, z1], dx, dy)
  quad(m, [x0, y0, z1], [x0, y0, z0], [x0, y1, z0], [x0, y1, z1], dz, dy)
  quad(m, [x1, y0, z0], [x1, y0, z1], [x1, y1, z1], [x1, y1, z0], dz, dy)
}

/** A box centered at (cx, cz), rising `height` units from `base`: top face plus
 *  four sides, one uv tile per face. No bottom (never seen). */
function box(m: Mesh, cx: number, cz: number, half: number, base: number, height: number): void {
  const x0 = cx - half
  const x1 = cx + half
  const z0 = cz - half
  const z1 = cz + half
  const y0 = base
  const y1 = base + height
  quad(m, [x0, y1, z0], [x1, y1, z0], [x1, y1, z1], [x0, y1, z1], 1, 1)
  quad(m, [x0, y0, z0], [x1, y0, z0], [x1, y1, z0], [x0, y1, z0], 1, 1)
  quad(m, [x1, y0, z1], [x0, y0, z1], [x0, y1, z1], [x1, y1, z1], 1, 1)
  quad(m, [x1, y0, z0], [x1, y0, z1], [x1, y1, z1], [x1, y1, z0], 1, 1)
  quad(m, [x0, y0, z1], [x0, y0, z0], [x0, y1, z0], [x0, y1, z1], 1, 1)
}
