Custom RenderSource integration

Draw your own simulation with GameView through the instance-buffer contract.

Instance buffer contract

A RenderSource packs instances grouped by mesh: matrices holds a column-major mat4 each, colors an RGBA each, and optional regions a texture rectangle each, in the same order. Only the first count instances draw; capacity sizes the GPU buffers.

ranges holds [first, count] per mesh id: opaque instances at ranges[2 * mesh], transparent ones at ranges[2 * (MAX_MESHES + mesh)]. Meshes must already be registered (built-ins after warmUpEngine, createShape or a loader).

An opaque and a see-through cube

React and React Native · shared engine API

An opaque and a see-through cube
import { MAX_MESHES, type RenderSource } from "@nayan-ui/engine";
const CUBE = 0; // built-in cube mesh id
export function twoCubes(): RenderSource {
const matrices = new Float32Array(32);
matrices.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 1], 0);
matrices.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1], 16);
const ranges = new Uint32Array(MAX_MESHES * 4);
ranges.set([0, 1], 2 * CUBE); // opaque: instance 0
ranges.set([1, 1], 2 * (MAX_MESHES + CUBE)); // transparent: instance 1
const colors = new Float32Array([1, 0.4, 0.2, 1, 0.2, 0.7, 1, 0.4]);
return { matrices, colors, ranges, count: 2, capacity: 2 };
}

Stepping and the view

GameView calls source.update(dt) after onUpdate each frame, unless its updateCount changed during onUpdate or isDisposed is true. setView(viewProj, width, height) receives the camera of each drawn frame for your own picking.

A custom source gets no physics, timers, animation or picking from World, and nativeWorld belongs only to a real World. Prefer World unless you already own a simulation.

A spinning cube source

React and React Native · shared engine API

A spinning cube source
import { MAX_MESHES, type RenderSource } from "@nayan-ui/engine";
export class Spinner implements RenderSource {
readonly matrices = new Float32Array(16);
readonly colors = new Float32Array([0.2, 0.7, 1, 1]);
readonly ranges = new Uint32Array(MAX_MESHES * 4);
readonly count = 1;
readonly capacity = 1;
updateCount = 0;
private angle = 0;
constructor() {
this.ranges.set([0, 1], 0); // built-in cube, instance 0
}
update(dt: number) {
this.updateCount++;
this.angle += dt;
const c = Math.cos(this.angle), s = Math.sin(this.angle);
this.matrices.set([c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 1, 0, 1]); // Y rotation
}
}

Draw it with GameView

Await warmUpEngine so the built-in meshes exist, then pass the source to GameView with a Camera and Light, like a world. A custom source can also be GameView's overlay.

CustomView.tsx

React and React Native · shared engine API

CustomView.tsx
import { useEffect, useState } from "react";
import { GameView, warmUpEngine, type Camera, type Light, type RenderSource } from "@nayan-ui/engine";
const camera: Camera = { eye: [0, 3, 6], target: [0, 1, 0], fov: 1 };
const light: Light = { direction: [0.4, 0.8, 0.5], ambient: 0.35, color: "white" };
export function CustomView({ source }: { source: RenderSource }) {
const [ready, setReady] = useState(false);
useEffect(() => {
void warmUpEngine().then(() => setReady(true));
}, []);
return ready ? <GameView source={source} camera={camera} light={light} style={{ flex: 1 }} /> : null;
}