Skip to content
import { glCanvas } from "@radiancejs/gl";
import fragment from "./cube.frag?raw";
import vertex from "./cube.vert?raw";
import { mat4 } from "gl-matrix";
import "./styles.css";

// One cube: 6 faces of 4 vertices, shared between all instances through
// an index buffer (24 vertices, 36 indices).
const CUBE_FACES: [normal: number[], corners: number[][]][] = [
  [[0, 0, 1], [[-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1]]],
  [[0, 0, -1], [[-1, -1, -1], [-1, 1, -1], [1, 1, -1], [1, -1, -1]]],
  [[0, 1, 0], [[-1, 1, -1], [-1, 1, 1], [1, 1, 1], [1, 1, -1]]],
  [[0, -1, 0], [[-1, -1, -1], [1, -1, -1], [1, -1, 1], [-1, -1, 1]]],
  [[1, 0, 0], [[1, -1, -1], [1, 1, -1], [1, 1, 1], [1, -1, 1]]],
  [[-1, 0, 0], [[-1, -1, -1], [-1, 1, -1], [-1, 1, 1], [-1, -1, 1]]],
];

const positions: number[] = [];
const normals: number[] = [];
const indices: number[] = [];

for (const [normal, corners] of CUBE_FACES) {
  const first = positions.length / 3;
  for (const corner of corners) {
    positions.push(...corner);
    normals.push(...normal);
  }
  indices.push(first, first + 1, first + 2, first, first + 2, first + 3);
}

/** Deterministic pseudo-random in [0, 1). */
function hash(i: number) {
  const value = Math.sin(i * 127.1) * 43758.5453;
  return value - Math.floor(value);
}

// One row per instance: where the cube sits in 3D, how big it is,
// its color, and the phase of its spin.
const instanceCount = 150;
const offsets: number[] = [];
const scales: number[] = [];
const colors: number[] = [];
const phases: number[] = [];

for (let i = 0; i < instanceCount; i++) {
  offsets.push((hash(i) - 0.5) * 10, (hash(i + 100) - 0.5) * 7, (hash(i + 200) - 0.5) * 10);
  scales.push(0.15 + 0.35 * hash(i + 300));
  colors.push(0.2 + 0.6 * hash(i + 400), 0.45 + 0.35 * hash(i + 500), 0.65 + 0.3 * hash(i + 600));
  phases.push(hash(i + 700) * Math.PI * 2);
}

const viewMatrix = mat4.lookAt(mat4.create(), [0, 5, 16], [0, 0, 0], [0, 1, 0]);
const projectionMatrix = mat4.perspective(mat4.create(), Math.PI / 4, 1, 0.1, 100);

glCanvas({
  canvas: "#glCanvas",
  depthTest: true,
  uniforms: {
    uViewMatrix: viewMatrix,
    uProjectionMatrix: projectionMatrix,
    uLightPosition: [5, 8, 5],
    uTime: ({ time }) => time / 1000,
  },
  vertex,
  fragment,
  attributes: {
    // the mesh, read once per vertex
    aPosition: { size: 3, data: positions },
    aNormal: { size: 3, data: normals },
    index: { size: 1, data: indices },
    // the copies, read once per instance
    aInstanceOffset: { size: 3, data: offsets, divisor: 1 },
    aInstanceScale: { size: 1, data: scales, divisor: 1 },
    aInstanceColor: { size: 3, data: colors, divisor: 1 },
    aInstancePhase: { size: 1, data: phases, divisor: 1 },
  },
});

Released under the MIT License.