Strip core to render data and render graphs

Move glTF, picking, camera controls, and conventional handles into addons. Keep camera and material mutations in SIMD-aligned shared SOA rows and synchronize material updates directly into GPU buffers.

Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-19 09:41:41 +00:00
co-authored by heaust
parent 0e44917f9e
commit 6bbf8039e4
67 changed files with 3152 additions and 3669 deletions
+122
View File
@@ -0,0 +1,122 @@
const MIN_DISTANCE = 0.1;
const MAX_PITCH = Math.PI / 2 - 0.01;
// The camera is ordinary render data, not a core API. This example maps browser
// controls straight onto its packed SOA row without sending input messages.
export function installCameraRenderDataControls(core, canvas) {
const camera = core.array("camera.state");
const abort = new AbortController();
const options = { signal: abort.signal };
const write = (state) => camera.write(0, state);
canvas.addEventListener(
"pointerdown",
(event) => {
if (event.pointerType === "mouse" && (event.button === 1 || event.button === 2)) {
event.preventDefault();
canvas.setPointerCapture(event.pointerId);
}
},
options,
);
canvas.addEventListener(
"pointermove",
(event) => {
if (
event.pointerType !== "mouse" ||
!canvas.hasPointerCapture(event.pointerId) ||
(event.buttons & 6) === 0
) {
return;
}
event.preventDefault();
const state = camera.read(0);
const offset = [state[0] - state[4], state[1] - state[5], state[2] - state[6]];
const distance = Math.hypot(...offset);
if ((event.buttons & 4) !== 0) {
const yaw = Math.atan2(offset[0], offset[2]) + event.movementX * 0.005;
const pitch = Math.max(
-MAX_PITCH,
Math.min(
MAX_PITCH,
Math.asin(offset[1] / distance) + event.movementY * 0.005,
),
);
const horizontal = Math.cos(pitch) * distance;
state[0] = state[4] + Math.sin(yaw) * horizontal;
state[1] = state[5] + Math.sin(pitch) * distance;
state[2] = state[6] + Math.cos(yaw) * horizontal;
} else {
const forward = [
(state[4] - state[0]) / distance,
(state[5] - state[1]) / distance,
(state[6] - state[2]) / distance,
];
const right = [
forward[1] * state[10] - forward[2] * state[9],
forward[2] * state[8] - forward[0] * state[10],
forward[0] * state[9] - forward[1] * state[8],
];
const rightLength = Math.hypot(...right);
right.forEach((value, index) => (right[index] = value / rightLength));
const up = [
right[1] * forward[2] - right[2] * forward[1],
right[2] * forward[0] - right[0] * forward[2],
right[0] * forward[1] - right[1] * forward[0],
];
const units =
(2 * distance * Math.tan(state[12] * 0.5)) /
Math.max(1, canvas.clientHeight);
const translation = right.map(
(value, index) =>
-value * event.movementX * units + up[index] * event.movementY * units,
);
for (let index = 0; index < 3; index++) {
state[index] += translation[index];
state[index + 4] += translation[index];
}
}
write(state);
},
options,
);
for (const type of ["pointerup", "pointercancel"]) {
canvas.addEventListener(
type,
(event) => {
if (canvas.hasPointerCapture(event.pointerId)) {
canvas.releasePointerCapture(event.pointerId);
}
},
options,
);
}
canvas.addEventListener(
"wheel",
(event) => {
event.preventDefault();
const delta =
event.deltaMode === WheelEvent.DOM_DELTA_LINE
? event.deltaY * 16
: event.deltaMode === WheelEvent.DOM_DELTA_PAGE
? event.deltaY * canvas.clientHeight
: event.deltaY;
if (!Number.isFinite(delta) || delta === 0) return;
const state = camera.read(0);
const offset = [state[0] - state[4], state[1] - state[5], state[2] - state[6]];
const distance = Math.hypot(...offset);
const nextDistance = Math.max(
MIN_DISTANCE,
Math.min(state[15] * 0.95, distance * Math.exp(0.002 * delta)),
);
for (let index = 0; index < 3; index++) {
state[index] = state[index + 4] + offset[index] * (nextDistance / distance);
}
write(state);
},
{ ...options, passive: false },
);
canvas.addEventListener("contextmenu", (event) => event.preventDefault(), options);
return () => abort.abort();
}