Enhance CDN packages and playground
Amp-Thread-ID: https://ampcode.com/threads/T-01a02485-5574-707c-bff4-5668d83bee8a Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+255
-75
@@ -1,60 +1,98 @@
|
||||
const defaultSource = `import {
|
||||
AmbientLight,
|
||||
ArcRotateCamera,
|
||||
ColorGrading,
|
||||
FXAA,
|
||||
Mesh,
|
||||
Icosphere,
|
||||
PBRMaterial,
|
||||
Plane,
|
||||
PointLight,
|
||||
Scene,
|
||||
SpotLight,
|
||||
} from "@yawn/handles";
|
||||
|
||||
const scene = new Scene(canvas, { hdr: true, fps: 60 });
|
||||
await scene.ready;
|
||||
await scene.reserve({ nodes: 6, materials: 2 });
|
||||
|
||||
const material = new PBRMaterial(scene, {
|
||||
baseColor: [0.12, 0.58, 1, 1],
|
||||
metallic: 0.15,
|
||||
roughness: 0.35,
|
||||
const objectMaterial = new PBRMaterial(scene, {
|
||||
baseColor: [0.82, 0.86, 0.9, 1],
|
||||
metallic: 0.12,
|
||||
roughness: 0.32,
|
||||
});
|
||||
await material.ready;
|
||||
|
||||
const mesh = new Mesh(scene, {
|
||||
material,
|
||||
vertexData: {
|
||||
positions: [-0.72, -0.62, 0, 0.72, -0.62, 0, 0, 0.76, 0],
|
||||
normals: [0, 0, 1, 0, 0, 1, 0, 0, 1],
|
||||
indices: [0, 1, 2],
|
||||
},
|
||||
const groundMaterial = new PBRMaterial(scene, {
|
||||
baseColor: [0.24, 0.28, 0.34, 1],
|
||||
metallic: 0,
|
||||
roughness: 0.82,
|
||||
});
|
||||
await mesh.ready;
|
||||
await Promise.all([objectMaterial.ready, groundMaterial.ready]);
|
||||
|
||||
log("Scene ready — drag your pointer across the preview.");
|
||||
const move = (event: PointerEvent) => {
|
||||
const bounds = canvas.getBoundingClientRect();
|
||||
mesh.position.x = ((event.clientX - bounds.left) / bounds.width - 0.5) * 1.2;
|
||||
mesh.position.y = (0.5 - (event.clientY - bounds.top) / bounds.height) * 0.8;
|
||||
};
|
||||
canvas.addEventListener("pointermove", move);
|
||||
const icosphere = new Icosphere(scene, {
|
||||
material: objectMaterial,
|
||||
subdivisions: 2,
|
||||
position: [0, 0.2, 0],
|
||||
});
|
||||
const ground = new Plane(scene, {
|
||||
material: groundMaterial,
|
||||
size: 2.6,
|
||||
position: [0, -0.55, 0],
|
||||
});
|
||||
await Promise.all([icosphere.ready, ground.ready]);
|
||||
icosphere.rotateY(Math.PI / 5);
|
||||
|
||||
const camera = new ArcRotateCamera(scene, {
|
||||
target: icosphere,
|
||||
alpha: Math.PI / 4,
|
||||
beta: Math.PI / 3,
|
||||
radius: 4,
|
||||
aspect: canvas.width / canvas.height,
|
||||
controls: { element: canvas, pointer: true },
|
||||
});
|
||||
await camera.ready;
|
||||
|
||||
const lights = [
|
||||
new AmbientLight(scene, { color: [0.02, 0.45, 0.48], intensity: 0.5 }),
|
||||
new SpotLight(scene, {
|
||||
position: [-2.8, 3, -2.8],
|
||||
rotor: [-0.628, -0.628, 0, 0.4597],
|
||||
color: [1, 0.22, 0.03],
|
||||
intensity: 220,
|
||||
range: 7,
|
||||
innerAngle: 0.32,
|
||||
outerAngle: 0.72,
|
||||
}),
|
||||
new PointLight(scene, {
|
||||
position: [2.2, 0.9, 1.8],
|
||||
color: [1, 0.02, 0.45],
|
||||
intensity: 16,
|
||||
range: 6,
|
||||
}),
|
||||
];
|
||||
await Promise.all(lights.map((light) => light.ready));
|
||||
|
||||
log("Icosphere ready — teal ambience, orange rim, and magenta accent are active.");
|
||||
|
||||
export default {
|
||||
scene,
|
||||
mesh,
|
||||
icosphere,
|
||||
ground,
|
||||
lights,
|
||||
dispose() {
|
||||
canvas.removeEventListener("pointermove", move);
|
||||
camera.detachControls();
|
||||
scene.dispose();
|
||||
},
|
||||
};`;
|
||||
|
||||
const snippets = {
|
||||
camera: `// Orbit camera — drag to orbit and use the wheel to zoom.
|
||||
const camera = new ArcRotateCamera(scene, {
|
||||
target: mesh,
|
||||
const orbitCamera = new ArcRotateCamera(scene, {
|
||||
target: icosphere,
|
||||
alpha: 0,
|
||||
beta: Math.PI / 2,
|
||||
radius: 3,
|
||||
controls: { element: canvas, pointer: true },
|
||||
});
|
||||
await camera.ready;`,
|
||||
await orbitCamera.ready;`,
|
||||
light: `// A warm point light backed by shared rows.
|
||||
const light = new PointLight(scene, {
|
||||
position: [0, 0.7, 1],
|
||||
@@ -65,7 +103,7 @@ const light = new PointLight(scene, {
|
||||
await light.ready;`,
|
||||
instances: `// Clones share geometry until one clone changes its vertex data.
|
||||
for (let x = -2; x <= 2; x++) {
|
||||
const instance = mesh.clone({ position: [x * 0.35, 0, 0] });
|
||||
const instance = icosphere.clone({ position: [x * 0.7, 0.2, 0] });
|
||||
await instance.ready;
|
||||
}`,
|
||||
rows: `// Application-owned hot data in the same shared arena.
|
||||
@@ -123,9 +161,59 @@ const elements = Object.fromEntries(
|
||||
);
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
||||
const packages = Promise.all([import("/pkg/handles.js"), import("/pkg/core.js")]).then(
|
||||
([handles, core]) => ({ ...handles, ...core }),
|
||||
);
|
||||
const nativeSetTimeout = window.setTimeout.bind(window);
|
||||
const nativeSetInterval = window.setInterval.bind(window);
|
||||
const nativeClearTimeout = window.clearTimeout.bind(window);
|
||||
const nativeClearInterval = window.clearInterval.bind(window);
|
||||
const playgroundTimerIDs = new Set();
|
||||
|
||||
function trackedSetTimeout(callback, delay, ...arguments_) {
|
||||
let id;
|
||||
const wrapped =
|
||||
typeof callback === "function"
|
||||
? (...callbackArguments) => {
|
||||
playgroundTimerIDs.delete(id);
|
||||
callback.apply(window, callbackArguments);
|
||||
}
|
||||
: callback;
|
||||
id = nativeSetTimeout(wrapped, delay, ...arguments_);
|
||||
playgroundTimerIDs.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
function trackedSetInterval(callback, delay, ...arguments_) {
|
||||
const id = nativeSetInterval(callback, delay, ...arguments_);
|
||||
playgroundTimerIDs.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
function trackedClearTimer(id) {
|
||||
playgroundTimerIDs.delete(id);
|
||||
nativeClearTimeout(id);
|
||||
nativeClearInterval(id);
|
||||
}
|
||||
|
||||
function clearPlaygroundTimers() {
|
||||
for (const id of playgroundTimerIDs) {
|
||||
nativeClearTimeout(id);
|
||||
nativeClearInterval(id);
|
||||
}
|
||||
playgroundTimerIDs.clear();
|
||||
}
|
||||
|
||||
window.__yawnPlaygroundTimers = {
|
||||
ids: playgroundTimerIDs,
|
||||
setTimeout: trackedSetTimeout,
|
||||
setInterval: trackedSetInterval,
|
||||
clearTimeout: trackedClearTimer,
|
||||
clearInterval: trackedClearTimer,
|
||||
clear: clearPlaygroundTimers,
|
||||
};
|
||||
|
||||
const packages = Promise.all([
|
||||
import("@yawn/handles"),
|
||||
import("@yawn/core"),
|
||||
]).then(([handles, core]) => ({ ...handles, ...core }));
|
||||
let monaco;
|
||||
let editor;
|
||||
let model;
|
||||
@@ -143,8 +231,14 @@ let frameRequest;
|
||||
let sampledFrame = 0;
|
||||
let sampledAt = 0;
|
||||
|
||||
addEventListener("yawn-theme-change", (event) => {
|
||||
monaco?.editor.setTheme(event.detail.resolved === "dark" ? "yawn" : "vs");
|
||||
});
|
||||
|
||||
function savedLocation() {
|
||||
const match = location.pathname.match(/^\/playground\/([a-z0-9]{6,24})\/(\d+)\/?$/);
|
||||
const match = location.pathname.match(
|
||||
/^\/playground\/([a-z0-9]{6,24})\/(\d+)\/?$/,
|
||||
);
|
||||
return match ? { id: match[1], revision: Number(match[2]) } : undefined;
|
||||
}
|
||||
|
||||
@@ -152,8 +246,11 @@ async function loadInitialPlayground() {
|
||||
const saved = savedLocation();
|
||||
if (saved) {
|
||||
try {
|
||||
const response = await fetch(`/api/playgrounds/${saved.id}/${saved.revision}`);
|
||||
if (!response.ok) throw new Error("This saved playground could not be found.");
|
||||
const response = await fetch(
|
||||
`/api/playgrounds/${saved.id}/${saved.revision}`,
|
||||
);
|
||||
if (!response.ok)
|
||||
throw new Error("This saved playground could not be found.");
|
||||
const result = await response.json();
|
||||
currentID = result.id;
|
||||
currentRevision = result.revision;
|
||||
@@ -188,13 +285,19 @@ importScripts(${JSON.stringify(`${monacoBase}vs/base/worker/workerMain.js`)});
|
||||
);
|
||||
window.MonacoEnvironment = { getWorkerUrl: () => workerURL };
|
||||
window.require.config({ paths: { vs: `${monacoBase}vs` } });
|
||||
window.require(["vs/editor/editor.main"], () => resolve(window.monaco), reject);
|
||||
window.require(
|
||||
["vs/editor/editor.main"],
|
||||
() => resolve(window.monaco),
|
||||
reject,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function createEditor(source) {
|
||||
monaco = await loadMonaco();
|
||||
const types = await fetch("/assets/yawn.d.ts").then((response) => response.text());
|
||||
const types = await fetch("/assets/yawn.d.ts").then((response) =>
|
||||
response.text(),
|
||||
);
|
||||
const defaults = monaco.languages.typescript.typescriptDefaults;
|
||||
defaults.setEagerModelSync(true);
|
||||
defaults.setCompilerOptions({
|
||||
@@ -205,7 +308,10 @@ async function createEditor(source) {
|
||||
strict: true,
|
||||
target: monaco.languages.typescript.ScriptTarget.ESNext,
|
||||
});
|
||||
defaults.setDiagnosticsOptions({ noSemanticValidation: false, noSyntaxValidation: false });
|
||||
defaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: false,
|
||||
noSyntaxValidation: false,
|
||||
});
|
||||
defaults.addExtraLib(types, "file:///types/yawn.d.ts");
|
||||
monaco.editor.defineTheme("yawn", {
|
||||
base: "vs-dark",
|
||||
@@ -232,12 +338,17 @@ async function createEditor(source) {
|
||||
},
|
||||
});
|
||||
elements.editor.textContent = "";
|
||||
model = monaco.editor.createModel(source, "typescript", monaco.Uri.parse("file:///playground/scene.ts"));
|
||||
model = monaco.editor.createModel(
|
||||
source,
|
||||
"typescript",
|
||||
monaco.Uri.parse("file:///playground/scene.ts"),
|
||||
);
|
||||
editor = monaco.editor.create(elements.editor, {
|
||||
model,
|
||||
theme: "yawn",
|
||||
theme: document.documentElement.dataset.theme === "dark" ? "yawn" : "vs",
|
||||
automaticLayout: true,
|
||||
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
||||
fontFamily:
|
||||
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
||||
fontLigatures: true,
|
||||
fontSize: 12,
|
||||
lineHeight: 20,
|
||||
@@ -256,11 +367,13 @@ async function createEditor(source) {
|
||||
run,
|
||||
});
|
||||
editor.onDidChangeCursorPosition(({ position }) => {
|
||||
elements["cursor-status"].textContent = `Ln ${position.lineNumber}, Col ${position.column}`;
|
||||
elements["cursor-status"].textContent =
|
||||
`Ln ${position.lineNumber}, Col ${position.column}`;
|
||||
});
|
||||
model.onDidChangeContent(() => {
|
||||
markDirty(true);
|
||||
if (!currentID) localStorage.setItem("yawn:playground:draft", model.getValue());
|
||||
if (!currentID)
|
||||
localStorage.setItem("yawn:playground:draft", model.getValue());
|
||||
});
|
||||
monaco.editor.onDidChangeMarkers(([resource]) => {
|
||||
if (resource.toString() === model.uri.toString()) updateDiagnostics();
|
||||
@@ -272,8 +385,12 @@ async function createEditor(source) {
|
||||
function updateDiagnostics() {
|
||||
if (!monaco || !model) return;
|
||||
const markers = monaco.editor.getModelMarkers({ resource: model.uri });
|
||||
const errors = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Error).length;
|
||||
const warnings = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Warning).length;
|
||||
const errors = markers.filter(
|
||||
(marker) => marker.severity === monaco.MarkerSeverity.Error,
|
||||
).length;
|
||||
const warnings = markers.filter(
|
||||
(marker) => marker.severity === monaco.MarkerSeverity.Warning,
|
||||
).length;
|
||||
const total = errors + warnings;
|
||||
elements["editor-diagnostics"].classList.toggle("has-errors", errors > 0);
|
||||
elements["editor-diagnostics"].lastChild.textContent = total
|
||||
@@ -295,7 +412,8 @@ async function transpile() {
|
||||
const client = await worker(model.uri);
|
||||
const emitted = await client.getEmitOutput(model.uri.toString());
|
||||
const output = emitted.outputFiles.find((file) => file.name.endsWith(".js"));
|
||||
if (emitted.emitSkipped || !output) throw new Error("TypeScript could not emit this scene.");
|
||||
if (emitted.emitSkipped || !output)
|
||||
throw new Error("TypeScript could not emit this scene.");
|
||||
return output.text
|
||||
.replace(/^\s*import\s+(?:[\s\S]*?\s+from\s+)?["'][^"']+["'];?\s*$/gm, "")
|
||||
.replace(/^\s*export\s*\{\s*\};?\s*$/gm, "")
|
||||
@@ -329,33 +447,56 @@ function replaceCanvas() {
|
||||
canvas.setAttribute("aria-label", "Yawn WebGPU preview");
|
||||
const scale = Math.min(devicePixelRatio || 1, 2);
|
||||
canvas.width = Math.max(1, Math.round(elements.preview.clientWidth * scale));
|
||||
canvas.height = Math.max(1, Math.round(elements.preview.clientHeight * scale));
|
||||
canvas.height = Math.max(
|
||||
1,
|
||||
Math.round(elements.preview.clientHeight * scale),
|
||||
);
|
||||
previous.replaceWith(canvas);
|
||||
elements.canvas = canvas;
|
||||
elements["resolution-status"].textContent = `${canvas.width} × ${canvas.height}`;
|
||||
elements["resolution-status"].textContent =
|
||||
`${canvas.width} × ${canvas.height}`;
|
||||
return canvas;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (!editor || elements["run-button"].disabled) return;
|
||||
clearPlaygroundTimers();
|
||||
const runID = ++generation;
|
||||
elements["run-button"].disabled = true;
|
||||
elements["run-button"].lastChild.textContent = " Running";
|
||||
clearConsole();
|
||||
setRunStatus("Compiling", "running");
|
||||
showPreviewMessage("Compiling TypeScript", "Checking types and preparing the render graph.");
|
||||
showPreviewMessage(
|
||||
"Compiling TypeScript",
|
||||
"Checking types and preparing the render graph.",
|
||||
);
|
||||
try {
|
||||
if (!crossOriginIsolated) throw new Error("Cross-origin isolation is disabled on this page.");
|
||||
if (!navigator.gpu) throw new Error("WebGPU is not available in this browser.");
|
||||
if (!crossOriginIsolated)
|
||||
throw new Error("Cross-origin isolation is disabled on this page.");
|
||||
if (!navigator.gpu)
|
||||
throw new Error("WebGPU is not available in this browser.");
|
||||
const [code, api] = await Promise.all([transpile(), packages]);
|
||||
await disposeCurrent();
|
||||
const canvas = replaceCanvas();
|
||||
const names = Object.keys(api);
|
||||
const started = performance.now();
|
||||
const result = await new AsyncFunction(...names, "canvas", "log", code)(
|
||||
const result = await new AsyncFunction(
|
||||
...names,
|
||||
"canvas",
|
||||
"log",
|
||||
"setTimeout",
|
||||
"setInterval",
|
||||
"clearTimeout",
|
||||
"clearInterval",
|
||||
code,
|
||||
)(
|
||||
...names.map((name) => api[name]),
|
||||
canvas,
|
||||
(...values) => appendConsole(values.map(formatValue).join(" ")),
|
||||
trackedSetTimeout,
|
||||
trackedSetInterval,
|
||||
trackedClearTimer,
|
||||
trackedClearTimer,
|
||||
);
|
||||
if (runID !== generation) {
|
||||
if (result?.dispose) await result.dispose();
|
||||
@@ -364,7 +505,10 @@ async function run() {
|
||||
current = result;
|
||||
window.__yawnPlayground = result;
|
||||
elements["preview-message"].hidden = true;
|
||||
setRunStatus(`Running · ${Math.round(performance.now() - started)} ms`, "ready");
|
||||
setRunStatus(
|
||||
`Running · ${Math.round(performance.now() - started)} ms`,
|
||||
"ready",
|
||||
);
|
||||
if (activePanel === "profile") await attachProfiler();
|
||||
} catch (error) {
|
||||
if (runID === generation) {
|
||||
@@ -377,7 +521,8 @@ async function run() {
|
||||
} finally {
|
||||
if (runID === generation) {
|
||||
elements["run-button"].disabled = false;
|
||||
elements["run-button"].innerHTML = '<span aria-hidden="true">▶</span> Run <kbd>Ctrl ↵</kbd>';
|
||||
elements["run-button"].innerHTML =
|
||||
'<span aria-hidden="true">▶</span> Run <kbd>Ctrl ↵</kbd>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,7 +541,10 @@ async function attachProfiler() {
|
||||
}
|
||||
stopProfile = profileCore.onProfile(renderProfile);
|
||||
const supported = await profileCore.setProfiler(true);
|
||||
if (!supported) setProfileMessage("Timestamp queries are not supported by this GPU adapter.");
|
||||
if (!supported)
|
||||
setProfileMessage(
|
||||
"Timestamp queries are not supported by this GPU adapter.",
|
||||
);
|
||||
}
|
||||
|
||||
function setProfileMessage(message) {
|
||||
@@ -408,8 +556,10 @@ function setProfileMessage(message) {
|
||||
function renderProfile(profile) {
|
||||
elements["profile-empty"].hidden = true;
|
||||
elements["profile-results"].hidden = false;
|
||||
elements["profile-total"].textContent = `${profile.milliseconds.toFixed(2)} ms`;
|
||||
elements["profile-latency"].textContent = `${profile.readbackMilliseconds.toFixed(2)} ms`;
|
||||
elements["profile-total"].textContent =
|
||||
`${profile.milliseconds.toFixed(2)} ms`;
|
||||
elements["profile-latency"].textContent =
|
||||
`${profile.readbackMilliseconds.toFixed(2)} ms`;
|
||||
elements["profile-adapter"].textContent = profile.adapter || "WebGPU adapter";
|
||||
elements["profile-adapter"].title = profile.adapter || "WebGPU adapter";
|
||||
elements["profile-passes"].replaceChildren(
|
||||
@@ -441,7 +591,8 @@ function appendConsole(message, error = false) {
|
||||
line.textContent = message;
|
||||
line.classList.toggle("error", error);
|
||||
elements["console-lines"].append(line);
|
||||
elements["console-count"].textContent = elements["console-lines"].children.length;
|
||||
elements["console-count"].textContent =
|
||||
elements["console-lines"].children.length;
|
||||
line.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
@@ -452,7 +603,8 @@ function clearConsole() {
|
||||
}
|
||||
|
||||
function setRunStatus(message, state) {
|
||||
elements["run-status"].className = state === "running" ? "running" : state === "failed" ? "failed" : "";
|
||||
elements["run-status"].className =
|
||||
state === "running" ? "running" : state === "failed" ? "failed" : "";
|
||||
elements["run-status"].lastChild.textContent = ` ${message}`;
|
||||
}
|
||||
|
||||
@@ -466,7 +618,9 @@ function openInspector(panel) {
|
||||
activePanel = panel;
|
||||
elements.inspector.classList.remove("collapsed");
|
||||
elements["inspector-button"].setAttribute("aria-pressed", "true");
|
||||
for (const button of document.querySelectorAll(".inspector-tabs [data-panel]")) {
|
||||
for (const button of document.querySelectorAll(
|
||||
".inspector-tabs [data-panel]",
|
||||
)) {
|
||||
const active = button.dataset.panel === panel;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
@@ -497,7 +651,8 @@ async function save() {
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.error || "The playground could not be saved.");
|
||||
if (!response.ok)
|
||||
throw new Error(result.error || "The playground could not be saved.");
|
||||
currentID = result.id;
|
||||
currentRevision = result.revision;
|
||||
initialSource = result.code;
|
||||
@@ -518,13 +673,22 @@ function showToast(message) {
|
||||
clearTimeout(toastTimer);
|
||||
elements.toast.textContent = message;
|
||||
elements.toast.classList.add("visible");
|
||||
toastTimer = setTimeout(() => elements.toast.classList.remove("visible"), 2400);
|
||||
toastTimer = setTimeout(
|
||||
() => elements.toast.classList.remove("visible"),
|
||||
2400,
|
||||
);
|
||||
}
|
||||
|
||||
function insertSnippet(name) {
|
||||
const selection = editor.getSelection();
|
||||
const prefix = model.getValueInRange(selection).endsWith("\n") ? "" : "\n\n";
|
||||
editor.executeEdits("snippet", [{ range: selection, text: `${prefix}${snippets[name]}\n`, forceMoveMarkers: true }]);
|
||||
editor.executeEdits("snippet", [
|
||||
{
|
||||
range: selection,
|
||||
text: `${prefix}${snippets[name]}\n`,
|
||||
forceMoveMarkers: true,
|
||||
},
|
||||
]);
|
||||
elements["snippets-dialog"].close();
|
||||
editor.focus();
|
||||
}
|
||||
@@ -539,7 +703,8 @@ function sampleFPS(now = performance.now()) {
|
||||
sampledFrame = frame;
|
||||
} else if (now - sampledAt >= 500) {
|
||||
const fps = ((frame - sampledFrame) * 1000) / (now - sampledAt);
|
||||
elements["fps-status"].textContent = `${fps < 10 ? fps.toFixed(1) : Math.round(fps)} FPS`;
|
||||
elements["fps-status"].textContent =
|
||||
`${fps < 10 ? fps.toFixed(1) : Math.round(fps)} FPS`;
|
||||
sampledAt = now;
|
||||
sampledFrame = frame;
|
||||
}
|
||||
@@ -562,22 +727,34 @@ elements["reset-button"].addEventListener("click", () => {
|
||||
model?.setValue(initialSource);
|
||||
void run();
|
||||
});
|
||||
elements["format-button"].addEventListener("click", () => editor?.getAction("editor.action.formatDocument").run());
|
||||
elements["snippets-button"].addEventListener("click", () => elements["snippets-dialog"].showModal());
|
||||
elements["snippets-dialog"].querySelector("[data-close-dialog]").addEventListener("click", () => elements["snippets-dialog"].close());
|
||||
elements["format-button"].addEventListener("click", () =>
|
||||
editor?.getAction("editor.action.formatDocument").run(),
|
||||
);
|
||||
elements["snippets-button"].addEventListener("click", () =>
|
||||
elements["snippets-dialog"].showModal(),
|
||||
);
|
||||
elements["snippets-dialog"]
|
||||
.querySelector("[data-close-dialog]")
|
||||
.addEventListener("click", () => elements["snippets-dialog"].close());
|
||||
elements["snippets-dialog"].addEventListener("click", (event) => {
|
||||
if (event.target === elements["snippets-dialog"]) elements["snippets-dialog"].close();
|
||||
if (event.target === elements["snippets-dialog"])
|
||||
elements["snippets-dialog"].close();
|
||||
});
|
||||
for (const button of document.querySelectorAll("[data-snippet]")) {
|
||||
button.addEventListener("click", () => insertSnippet(button.dataset.snippet));
|
||||
}
|
||||
elements["snippets-dialog"].querySelector("input").addEventListener("input", (event) => {
|
||||
const query = event.target.value.trim().toLowerCase();
|
||||
for (const button of document.querySelectorAll("[data-snippet]")) {
|
||||
button.hidden = Boolean(query) && !button.textContent.toLowerCase().includes(query);
|
||||
}
|
||||
});
|
||||
for (const button of document.querySelectorAll(".inspector-tabs [data-panel]")) {
|
||||
elements["snippets-dialog"]
|
||||
.querySelector("input")
|
||||
.addEventListener("input", (event) => {
|
||||
const query = event.target.value.trim().toLowerCase();
|
||||
for (const button of document.querySelectorAll("[data-snippet]")) {
|
||||
button.hidden =
|
||||
Boolean(query) && !button.textContent.toLowerCase().includes(query);
|
||||
}
|
||||
});
|
||||
for (const button of document.querySelectorAll(
|
||||
".inspector-tabs [data-panel]",
|
||||
)) {
|
||||
button.addEventListener("click", () => openInspector(button.dataset.panel));
|
||||
}
|
||||
elements["clear-console"].addEventListener("click", clearConsole);
|
||||
@@ -598,7 +775,10 @@ elements["resize-handle"].addEventListener("pointerdown", (event) => {
|
||||
elements["resize-handle"].addEventListener("pointermove", (event) => {
|
||||
if (!elements["resize-handle"].hasPointerCapture(event.pointerId)) return;
|
||||
const bounds = elements.workbench.getBoundingClientRect();
|
||||
const percentage = Math.min(70, Math.max(30, ((event.clientX - bounds.left) / bounds.width) * 100));
|
||||
const percentage = Math.min(
|
||||
70,
|
||||
Math.max(30, ((event.clientX - bounds.left) / bounds.width) * 100),
|
||||
);
|
||||
elements.workbench.style.setProperty("--editor-width", `${percentage}%`);
|
||||
});
|
||||
elements["resize-handle"].addEventListener("pointerup", (event) => {
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
.theme-select {
|
||||
width: 72px;
|
||||
height: 32px;
|
||||
padding: 0 7px;
|
||||
color: #53667c;
|
||||
border: 1px solid #d4e1ea;
|
||||
border-radius: 6px;
|
||||
outline: 0;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.theme-select:hover {
|
||||
color: #177fc2;
|
||||
border-color: #add5ee;
|
||||
}
|
||||
.theme-select:focus-visible {
|
||||
border-color: #62b8eb;
|
||||
box-shadow: 0 0 0 3px rgb(50 166 232 / 14%);
|
||||
}
|
||||
.tool-context {
|
||||
font-size: 10px;
|
||||
}
|
||||
.stage-meta,
|
||||
.editor-status {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] {
|
||||
--ink: #d9e9f7;
|
||||
--muted: #91a6b9;
|
||||
--line: #20384b;
|
||||
--line-strong: #315069;
|
||||
--sky-25: #0a1927;
|
||||
--sky-50: #0d2132;
|
||||
--sky-100: #12324a;
|
||||
--sky-200: #1d4c6a;
|
||||
--navy: #06111d;
|
||||
}
|
||||
html[data-theme="dark"] body {
|
||||
color: var(--ink);
|
||||
background: #081522;
|
||||
}
|
||||
html[data-theme="dark"] .theme-select {
|
||||
color: #b4c8d8;
|
||||
border-color: #29465d;
|
||||
background: #0e2232;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .site-header {
|
||||
border-color: var(--line);
|
||||
}
|
||||
html[data-theme="dark"] .wordmark {
|
||||
color: #e1eff9;
|
||||
}
|
||||
html[data-theme="dark"] .site-nav a,
|
||||
html[data-theme="dark"] .site-footer nav a {
|
||||
color: #91a7ba;
|
||||
}
|
||||
html[data-theme="dark"] .button-quiet {
|
||||
color: #b9d1e2;
|
||||
border-color: #315069;
|
||||
background: #0d2030;
|
||||
}
|
||||
html[data-theme="dark"] .button-quiet:hover {
|
||||
color: #72c8f7;
|
||||
background: #102a3e;
|
||||
}
|
||||
html[data-theme="dark"] .hero::before {
|
||||
background: radial-gradient(circle, rgb(30 102 148 / 35%), transparent 67%);
|
||||
}
|
||||
html[data-theme="dark"] .hero h1 {
|
||||
color: #edf7ff;
|
||||
}
|
||||
html[data-theme="dark"] .hero-note {
|
||||
color: #718ba0;
|
||||
}
|
||||
html[data-theme="dark"] .hero-demo {
|
||||
border-color: #2a4a61;
|
||||
background: #0a1927;
|
||||
box-shadow: 0 30px 70px rgb(0 0 0 / 28%);
|
||||
}
|
||||
html[data-theme="dark"] .demo-toolbar {
|
||||
color: #819bb0;
|
||||
border-color: #284257;
|
||||
background: #0d2131;
|
||||
}
|
||||
html[data-theme="dark"] .hero-demo pre {
|
||||
color: #b6cad9;
|
||||
background: #091827;
|
||||
}
|
||||
html[data-theme="dark"] .trust-strip {
|
||||
background: #0a1927;
|
||||
}
|
||||
html[data-theme="dark"] .trust-strip strong {
|
||||
color: #d7e8f5;
|
||||
}
|
||||
html[data-theme="dark"] .trust-strip span {
|
||||
color: #8298ab;
|
||||
}
|
||||
html[data-theme="dark"] .gear-card {
|
||||
border-color: #20394c;
|
||||
background: #0b1b29;
|
||||
box-shadow: 0 8px 30px rgb(0 0 0 / 16%);
|
||||
}
|
||||
html[data-theme="dark"] .gear-card-featured {
|
||||
border-color: #285876;
|
||||
background: linear-gradient(145deg, #0e2940, #0b1b29);
|
||||
}
|
||||
html[data-theme="dark"] .gear-number {
|
||||
color: #52728a;
|
||||
}
|
||||
html[data-theme="dark"] .gear-icon {
|
||||
border-color: #285675;
|
||||
background: #0e2a40;
|
||||
}
|
||||
html[data-theme="dark"] .gear-card h3 {
|
||||
color: #d9eaf6;
|
||||
}
|
||||
html[data-theme="dark"] .gear-card p {
|
||||
color: #91a5b6;
|
||||
}
|
||||
html[data-theme="dark"] .graph-icon::before,
|
||||
html[data-theme="dark"] .graph-icon::after {
|
||||
background: #0e2a40;
|
||||
}
|
||||
html[data-theme="dark"] .steps h3 {
|
||||
color: #d5e6f3;
|
||||
}
|
||||
html[data-theme="dark"] .steps p {
|
||||
color: #8fa4b6;
|
||||
}
|
||||
html[data-theme="dark"] .steps code {
|
||||
color: #6fc3f1;
|
||||
border-color: #29465c;
|
||||
background: #0c2030;
|
||||
}
|
||||
html[data-theme="dark"] .step-index {
|
||||
border-color: #285b7a;
|
||||
background: #0e2a40;
|
||||
}
|
||||
html[data-theme="dark"] .site-footer p {
|
||||
color: #738da1;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .docs-body {
|
||||
background: #081522;
|
||||
}
|
||||
html[data-theme="dark"] .docs-header {
|
||||
border-color: #20384b;
|
||||
background: rgb(8 21 34 / 94%);
|
||||
}
|
||||
html[data-theme="dark"] .docs-product {
|
||||
color: #8097aa;
|
||||
border-color: #294257;
|
||||
}
|
||||
html[data-theme="dark"] .docs-search {
|
||||
color: #7891a5;
|
||||
border-color: #29465b;
|
||||
background: #0d2030;
|
||||
}
|
||||
html[data-theme="dark"] .docs-search input {
|
||||
color: #d6e7f4;
|
||||
}
|
||||
html[data-theme="dark"] .docs-search kbd {
|
||||
color: #8299ab;
|
||||
border-color: #304c61;
|
||||
background: #102536;
|
||||
}
|
||||
html[data-theme="dark"] .docs-sidebar {
|
||||
border-color: #20384b;
|
||||
}
|
||||
html[data-theme="dark"] .sidebar-group > strong,
|
||||
html[data-theme="dark"] .docs-toc > strong {
|
||||
color: #708ba0;
|
||||
}
|
||||
html[data-theme="dark"] .sidebar-group a {
|
||||
color: #91a5b7;
|
||||
}
|
||||
html[data-theme="dark"] .sidebar-group a:hover {
|
||||
color: #69c4f5;
|
||||
background: #0d2436;
|
||||
}
|
||||
html[data-theme="dark"] .sidebar-group a.active {
|
||||
color: #70cbfb;
|
||||
background: #10304a;
|
||||
}
|
||||
html[data-theme="dark"] .sidebar-playground {
|
||||
color: #8bd4fa;
|
||||
border-color: #285572;
|
||||
background: #0d2940;
|
||||
}
|
||||
html[data-theme="dark"] .sidebar-playground small {
|
||||
color: #7697ad;
|
||||
}
|
||||
html[data-theme="dark"] .docs-main section + section {
|
||||
border-color: #20384b;
|
||||
}
|
||||
html[data-theme="dark"] .docs-main h1,
|
||||
html[data-theme="dark"] .docs-main h2,
|
||||
html[data-theme="dark"] .docs-main h3 {
|
||||
color: #e0edf7;
|
||||
}
|
||||
html[data-theme="dark"] .docs-main p,
|
||||
html[data-theme="dark"] .docs-main li {
|
||||
color: #9aafc0;
|
||||
}
|
||||
html[data-theme="dark"] .docs-main strong {
|
||||
color: #c4d8e7;
|
||||
}
|
||||
html[data-theme="dark"] .doc-lede {
|
||||
color: #a9bdcc !important;
|
||||
}
|
||||
html[data-theme="dark"] .docs-main :not(pre) > code {
|
||||
color: #71c6f3;
|
||||
border-color: #28506a;
|
||||
background: #0d293d;
|
||||
}
|
||||
html[data-theme="dark"] .section-kicker span {
|
||||
border-color: #285a78;
|
||||
background: #0e2b42;
|
||||
}
|
||||
html[data-theme="dark"] .callout {
|
||||
border-color: #294052;
|
||||
background: #0c1c2a;
|
||||
}
|
||||
html[data-theme="dark"] .callout-blue {
|
||||
border-color: #285672;
|
||||
background: #0d293e;
|
||||
}
|
||||
html[data-theme="dark"] .explain-grid > div {
|
||||
border-color: #263f52;
|
||||
background: #0b1c2a;
|
||||
}
|
||||
html[data-theme="dark"] .inline-play {
|
||||
color: #8bd5fb;
|
||||
border-color: #285974;
|
||||
background: #0e2c43;
|
||||
}
|
||||
html[data-theme="dark"] .inline-play strong {
|
||||
color: #a2defb;
|
||||
}
|
||||
html[data-theme="dark"] .inline-play small {
|
||||
color: #789bb1;
|
||||
}
|
||||
html[data-theme="dark"] .mental-model > div {
|
||||
border-color: #284356;
|
||||
background: #0b1d2b;
|
||||
}
|
||||
html[data-theme="dark"] .mental-model strong {
|
||||
color: #b9d0e0;
|
||||
}
|
||||
html[data-theme="dark"] .mental-model .mental-shared {
|
||||
border-color: #2d6585;
|
||||
background: #0e2c43;
|
||||
}
|
||||
html[data-theme="dark"] .deploy-list li {
|
||||
border-color: #20384b;
|
||||
}
|
||||
html[data-theme="dark"] .docs-toc a {
|
||||
color: #768ea1;
|
||||
border-color: #294257;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .playground-app {
|
||||
background: #07131f;
|
||||
}
|
||||
html[data-theme="dark"] .playground-header {
|
||||
border-color: #21384b;
|
||||
background: #0b1b29;
|
||||
}
|
||||
html[data-theme="dark"] .playground-brand {
|
||||
color: #dcebf6;
|
||||
}
|
||||
html[data-theme="dark"] .header-divider {
|
||||
background: #294154;
|
||||
}
|
||||
html[data-theme="dark"] .project-name input {
|
||||
color: #c3d6e4;
|
||||
}
|
||||
html[data-theme="dark"] .project-name input:hover {
|
||||
background: #102536;
|
||||
}
|
||||
html[data-theme="dark"] .project-name input:focus {
|
||||
border-color: #2d7198;
|
||||
background: #0e2638;
|
||||
}
|
||||
html[data-theme="dark"] #revision-label {
|
||||
color: #8298aa;
|
||||
border-color: #294356;
|
||||
background: #0e2131;
|
||||
}
|
||||
html[data-theme="dark"] .playground-nav a {
|
||||
color: #8da3b5;
|
||||
}
|
||||
html[data-theme="dark"] .tool-button {
|
||||
color: #a7bdcd;
|
||||
border-color: #29465c;
|
||||
background: #0e2232;
|
||||
}
|
||||
html[data-theme="dark"] .tool-button:hover {
|
||||
color: #67c4f4;
|
||||
border-color: #326484;
|
||||
background: #102a3d;
|
||||
}
|
||||
html[data-theme="dark"] .playground-tools {
|
||||
border-color: #233b4e;
|
||||
background: #0c1d2b;
|
||||
}
|
||||
html[data-theme="dark"] .tool-group button {
|
||||
color: #8fa6b8;
|
||||
}
|
||||
html[data-theme="dark"] .tool-group button:hover,
|
||||
html[data-theme="dark"] .tool-group button[aria-pressed="true"] {
|
||||
color: #63c3f5;
|
||||
border-color: #2b4d64;
|
||||
background: #102536;
|
||||
}
|
||||
html[data-theme="dark"] .tool-context,
|
||||
html[data-theme="dark"] .stage-meta,
|
||||
html[data-theme="dark"] .editor-status {
|
||||
color: #aec1cf;
|
||||
}
|
||||
html[data-theme="dark"] .context-divider {
|
||||
background: #294154;
|
||||
}
|
||||
html[data-theme="dark"] .snippets-dialog {
|
||||
color: #d3e3ee;
|
||||
border-color: #2a4a60;
|
||||
background: #0b1b29;
|
||||
}
|
||||
html[data-theme="dark"] .dialog-heading p {
|
||||
color: #8399aa;
|
||||
}
|
||||
html[data-theme="dark"] .snippet-search {
|
||||
color: #7890a3;
|
||||
border-color: #29465b;
|
||||
background: #0d2131;
|
||||
}
|
||||
html[data-theme="dark"] .snippet-search input {
|
||||
color: #c9dae7;
|
||||
}
|
||||
html[data-theme="dark"] .snippet-grid > button:hover {
|
||||
border-color: #2d5772;
|
||||
background: #0e293d;
|
||||
}
|
||||
html[data-theme="dark"] .snippet-icon {
|
||||
border-color: #2a5a77;
|
||||
background: #0e2c43;
|
||||
}
|
||||
html[data-theme="dark"] .snippet-grid strong {
|
||||
color: #c9dce9;
|
||||
}
|
||||
html[data-theme="dark"] .snippet-grid small {
|
||||
color: #8298aa;
|
||||
}
|
||||
html[data-theme="dark"] .snippet-grid code {
|
||||
color: #6bc4f2;
|
||||
border-color: #29475d;
|
||||
background: #0d2131;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .workbench {
|
||||
background: #d7e4ed;
|
||||
}
|
||||
html[data-theme="light"] .pane-tabs,
|
||||
html[data-theme="light"] .stage-toolbar {
|
||||
border-color: #cfdee8;
|
||||
background: #edf4f8;
|
||||
}
|
||||
html[data-theme="light"] .pane-tab {
|
||||
color: #60778a;
|
||||
border-color: #d5e2ea;
|
||||
background: #e8f0f5;
|
||||
}
|
||||
html[data-theme="light"] .pane-tab.active {
|
||||
color: #29465e;
|
||||
background: #f8fbfd;
|
||||
}
|
||||
html[data-theme="light"] .editor {
|
||||
background: #fff;
|
||||
}
|
||||
html[data-theme="light"] .monaco-editor .margin {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
html[data-theme="light"] .editor-status {
|
||||
color: #718698;
|
||||
border-color: #d2e0e9;
|
||||
background: #eff5f8;
|
||||
}
|
||||
html[data-theme="light"] .resize-handle {
|
||||
background: #c8d8e3;
|
||||
}
|
||||
html[data-theme="light"] .resize-handle::after {
|
||||
background: #8fa9bb;
|
||||
}
|
||||
html[data-theme="light"] .stage-pane {
|
||||
background: #e7f0f5;
|
||||
}
|
||||
html[data-theme="light"] .stage-tabs button {
|
||||
color: #60778a;
|
||||
}
|
||||
html[data-theme="light"] .stage-tabs button.active {
|
||||
color: #284961;
|
||||
background: #f8fbfd;
|
||||
}
|
||||
html[data-theme="light"] .stage-meta {
|
||||
color: #6a8194;
|
||||
}
|
||||
html[data-theme="light"] .stage-divider {
|
||||
background: #c5d7e2;
|
||||
}
|
||||
html[data-theme="light"] .inspector {
|
||||
border-color: #cddde7;
|
||||
background: #f7fafc;
|
||||
}
|
||||
html[data-theme="light"] .inspector-tabs {
|
||||
border-color: #cfdee7;
|
||||
background: #edf4f8;
|
||||
}
|
||||
html[data-theme="light"] .inspector-tabs button {
|
||||
color: #687f91;
|
||||
}
|
||||
html[data-theme="light"] .inspector-tabs button.active {
|
||||
color: #287aa9;
|
||||
}
|
||||
html[data-theme="light"] .inspector-tabs button span {
|
||||
background: #dce9f1;
|
||||
}
|
||||
html[data-theme="light"] .console-empty,
|
||||
html[data-theme="light"] .profile-empty {
|
||||
color: #6f8799;
|
||||
}
|
||||
html[data-theme="light"] #console-lines {
|
||||
color: #526c80;
|
||||
}
|
||||
html[data-theme="light"] #console-lines li {
|
||||
border-color: rgb(190 207 218 / 60%);
|
||||
}
|
||||
html[data-theme="light"] .profile-summary > div {
|
||||
border-color: #d3e1e9;
|
||||
background: #eef5f8;
|
||||
}
|
||||
html[data-theme="light"] .profile-summary strong {
|
||||
color: #3c6e8d;
|
||||
}
|
||||
html[data-theme="light"] .profile-results th,
|
||||
html[data-theme="light"] .profile-results td {
|
||||
border-color: #d5e2e9;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.site-header .theme-select {
|
||||
width: 64px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
(() => {
|
||||
const key = "yawn:theme";
|
||||
const preference = matchMedia("(prefers-color-scheme: dark)");
|
||||
const saved = localStorage.getItem(key);
|
||||
let mode = ["auto", "light", "dark"].includes(saved) ? saved : "auto";
|
||||
|
||||
const apply = () => {
|
||||
const resolved =
|
||||
mode === "auto" ? (preference.matches ? "dark" : "light") : mode;
|
||||
document.documentElement.dataset.theme = resolved;
|
||||
document.documentElement.dataset.themeMode = mode;
|
||||
document.documentElement.style.colorScheme = resolved;
|
||||
document
|
||||
.querySelector('meta[name="theme-color"]')
|
||||
?.setAttribute("content", resolved === "dark" ? "#081522" : "#f7fbff");
|
||||
for (const select of document.querySelectorAll("[data-theme-select]"))
|
||||
select.value = mode;
|
||||
dispatchEvent(
|
||||
new CustomEvent("yawn-theme-change", { detail: { mode, resolved } }),
|
||||
);
|
||||
};
|
||||
|
||||
const bind = () => {
|
||||
for (const select of document.querySelectorAll("[data-theme-select]")) {
|
||||
select.value = mode;
|
||||
select.addEventListener("change", () => {
|
||||
mode = select.value;
|
||||
if (mode === "auto") localStorage.removeItem(key);
|
||||
else localStorage.setItem(key, mode);
|
||||
apply();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
preference.addEventListener("change", () => {
|
||||
if (mode === "auto") apply();
|
||||
});
|
||||
apply();
|
||||
if (document.readyState === "loading")
|
||||
addEventListener("DOMContentLoaded", bind, { once: true });
|
||||
else bind();
|
||||
})();
|
||||
Vendored
+188
-25
@@ -30,10 +30,25 @@ declare module "@yawn/core" {
|
||||
passes: Array<{ name: string; milliseconds: number }>;
|
||||
}
|
||||
export class YawnCore {
|
||||
constructor(canvas: HTMLCanvasElement, options?: { arenaBytes?: number; debug?: boolean });
|
||||
constructor(
|
||||
canvas: HTMLCanvasElement,
|
||||
options?: { arenaBytes?: number; debug?: boolean },
|
||||
);
|
||||
readonly ready: Promise<void>;
|
||||
createRows(options: { name: string; rows: number; stride: number; format: RowFormat }): Promise<SharedRows>;
|
||||
createRowsBatch(rows: Array<{ name: string; rows: number; stride: number; format: RowFormat }>): Promise<SharedRows[]>;
|
||||
createRows(options: {
|
||||
name: string;
|
||||
rows: number;
|
||||
stride: number;
|
||||
format: RowFormat;
|
||||
}): Promise<SharedRows>;
|
||||
createRowsBatch(
|
||||
rows: Array<{
|
||||
name: string;
|
||||
rows: number;
|
||||
stride: number;
|
||||
format: RowFormat;
|
||||
}>,
|
||||
): Promise<SharedRows[]>;
|
||||
deleteRows(name: string): Promise<void>;
|
||||
allocateObject(name: string): Promise<number>;
|
||||
deleteObject(name: string, id: number): Promise<void>;
|
||||
@@ -52,23 +67,44 @@ declare module "@yawn/core" {
|
||||
declare module "@yawn/handles" {
|
||||
import { SharedRows, YawnCore } from "@yawn/core";
|
||||
|
||||
export interface SceneOptions { arenaBytes?: number; debug?: boolean; fps?: number; hdr?: boolean }
|
||||
export interface SceneOptions {
|
||||
arenaBytes?: number;
|
||||
debug?: boolean;
|
||||
fps?: number;
|
||||
hdr?: boolean;
|
||||
}
|
||||
export class Scene {
|
||||
constructor(canvas: HTMLCanvasElement, options?: SceneOptions);
|
||||
readonly core: YawnCore;
|
||||
readonly ready: Promise<this>;
|
||||
readonly hdr: boolean;
|
||||
array(name: string): SharedRows;
|
||||
ensureRows(name: string, rows: number, stride: number, format: "f32" | "u32" | "i32"): Promise<SharedRows>;
|
||||
ensureRows(
|
||||
name: string,
|
||||
rows: number,
|
||||
stride: number,
|
||||
format: "f32" | "u32" | "i32",
|
||||
): Promise<SharedRows>;
|
||||
reserve(additional: { nodes?: number; materials?: number }): Promise<void>;
|
||||
batchWrites<T>(operation: () => T): T;
|
||||
batchGraphUpdates<T>(operation: () => T | Promise<T>): Promise<T>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface Vector3View { x: number; y: number; z: number; set(values: ArrayLike<number>): void }
|
||||
export interface RotorView extends Vector3View { w: number }
|
||||
export interface NodeOptions { position?: ArrayLike<number>; rotor?: ArrayLike<number>; scale?: ArrayLike<number> }
|
||||
export interface Vector3View {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
set(values: ArrayLike<number>): void;
|
||||
}
|
||||
export interface RotorView extends Vector3View {
|
||||
w: number;
|
||||
}
|
||||
export interface NodeOptions {
|
||||
position?: ArrayLike<number>;
|
||||
rotor?: ArrayLike<number>;
|
||||
scale?: ArrayLike<number>;
|
||||
}
|
||||
export class Node {
|
||||
constructor(scene: Scene, options?: NodeOptions);
|
||||
readonly scene: Scene;
|
||||
@@ -103,7 +139,13 @@ declare module "@yawn/handles" {
|
||||
dispose(): Promise<void>;
|
||||
}
|
||||
|
||||
export type VertexKind = "positions" | "normals" | "tangents" | "uvs" | "colors" | "indices";
|
||||
export type VertexKind =
|
||||
| "positions"
|
||||
| "normals"
|
||||
| "tangents"
|
||||
| "uvs"
|
||||
| "colors"
|
||||
| "indices";
|
||||
export interface MeshOptions extends NodeOptions {
|
||||
geometryId?: number;
|
||||
material?: PBRMaterial;
|
||||
@@ -119,10 +161,55 @@ declare module "@yawn/handles" {
|
||||
material: PBRMaterial;
|
||||
clone(options?: Omit<MeshOptions, "geometryId" | "vertexData">): Mesh;
|
||||
setVertexData(kind: VertexKind, data: ArrayLike<number>): Promise<this>;
|
||||
setMaterialForFaces(material: PBRMaterial, faces: number | number[]): Promise<this>;
|
||||
setMaterialForFaces(
|
||||
material: PBRMaterial,
|
||||
faces: number | number[],
|
||||
): Promise<this>;
|
||||
}
|
||||
|
||||
export interface CameraOptions extends NodeOptions { fov?: number; near?: number; far?: number; aspect?: number }
|
||||
export interface CubeOptions
|
||||
extends Omit<MeshOptions, "geometryId" | "vertexData"> {
|
||||
size?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
depth?: number;
|
||||
}
|
||||
export class Cube extends Mesh {
|
||||
constructor(scene: Scene, options?: CubeOptions);
|
||||
}
|
||||
export interface IcosphereOptions
|
||||
extends Omit<MeshOptions, "geometryId" | "vertexData"> {
|
||||
radius?: number;
|
||||
subdivisions?: number;
|
||||
}
|
||||
export class Icosphere extends Mesh {
|
||||
constructor(scene: Scene, options?: IcosphereOptions);
|
||||
}
|
||||
export interface PlaneOptions
|
||||
extends Omit<MeshOptions, "geometryId" | "vertexData"> {
|
||||
size?: number;
|
||||
width?: number;
|
||||
depth?: number;
|
||||
}
|
||||
export class Plane extends Mesh {
|
||||
constructor(scene: Scene, options?: PlaneOptions);
|
||||
}
|
||||
export interface SphereOptions
|
||||
extends Omit<MeshOptions, "geometryId" | "vertexData"> {
|
||||
radius?: number;
|
||||
segments?: number;
|
||||
rings?: number;
|
||||
}
|
||||
export class Sphere extends Mesh {
|
||||
constructor(scene: Scene, options?: SphereOptions);
|
||||
}
|
||||
|
||||
export interface CameraOptions extends NodeOptions {
|
||||
fov?: number;
|
||||
near?: number;
|
||||
far?: number;
|
||||
aspect?: number;
|
||||
}
|
||||
export class Camera extends Node {
|
||||
fov: number;
|
||||
near: number;
|
||||
@@ -130,30 +217,106 @@ declare module "@yawn/handles" {
|
||||
aspect: number;
|
||||
}
|
||||
export class ArcRotateCamera extends Camera {
|
||||
constructor(scene: Scene, options?: CameraOptions & { target?: Node; alpha?: number; beta?: number; radius?: number; controls?: { element: HTMLElement; pointer?: boolean; controller?: boolean } });
|
||||
constructor(
|
||||
scene: Scene,
|
||||
options?: CameraOptions & {
|
||||
target?: Node;
|
||||
alpha?: number;
|
||||
beta?: number;
|
||||
radius?: number;
|
||||
controls?: {
|
||||
element: HTMLElement;
|
||||
pointer?: boolean;
|
||||
controller?: boolean;
|
||||
};
|
||||
},
|
||||
);
|
||||
alpha: number;
|
||||
beta: number;
|
||||
radius: number;
|
||||
detachControls(): this;
|
||||
}
|
||||
export class FreeCamera extends Camera {
|
||||
constructor(scene: Scene, options?: CameraOptions & { controls?: { element: HTMLElement; keyboard?: boolean; pointer?: boolean; controller?: boolean; speed?: number } });
|
||||
constructor(
|
||||
scene: Scene,
|
||||
options?: CameraOptions & {
|
||||
controls?: {
|
||||
element: HTMLElement;
|
||||
keyboard?: boolean;
|
||||
pointer?: boolean;
|
||||
controller?: boolean;
|
||||
speed?: number;
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
export class FollowCamera extends Camera {
|
||||
constructor(scene: Scene, options?: CameraOptions & { target?: Node; distance?: number; height?: number; smoothing?: number });
|
||||
constructor(
|
||||
scene: Scene,
|
||||
options?: CameraOptions & {
|
||||
target?: Node;
|
||||
distance?: number;
|
||||
height?: number;
|
||||
smoothing?: number;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export interface LightOptions extends NodeOptions { color?: ArrayLike<number>; intensity?: number }
|
||||
export class PointLight extends Node { constructor(scene: Scene, options?: LightOptions & { range?: number }); intensity: number; range: number }
|
||||
export class AmbientLight extends Node { constructor(scene: Scene, options?: LightOptions); intensity: number }
|
||||
export class DirectionalLight extends Node { constructor(scene: Scene, options?: LightOptions); intensity: number }
|
||||
export class SpotLight extends Node { constructor(scene: Scene, options?: LightOptions & { range?: number; innerAngle?: number; outerAngle?: number }) }
|
||||
export class RectAreaLight extends Node { constructor(scene: Scene, options?: LightOptions & { width?: number; height?: number }) }
|
||||
export interface LightOptions extends NodeOptions {
|
||||
color?: ArrayLike<number>;
|
||||
intensity?: number;
|
||||
}
|
||||
export class PointLight extends Node {
|
||||
constructor(scene: Scene, options?: LightOptions & { range?: number });
|
||||
intensity: number;
|
||||
range: number;
|
||||
}
|
||||
export class AmbientLight extends Node {
|
||||
constructor(scene: Scene, options?: LightOptions);
|
||||
intensity: number;
|
||||
}
|
||||
export class DirectionalLight extends Node {
|
||||
constructor(scene: Scene, options?: LightOptions);
|
||||
intensity: number;
|
||||
}
|
||||
export class SpotLight extends Node {
|
||||
constructor(
|
||||
scene: Scene,
|
||||
options?: LightOptions & {
|
||||
range?: number;
|
||||
innerAngle?: number;
|
||||
outerAngle?: number;
|
||||
},
|
||||
);
|
||||
}
|
||||
export class RectAreaLight extends Node {
|
||||
constructor(
|
||||
scene: Scene,
|
||||
options?: LightOptions & { width?: number; height?: number },
|
||||
);
|
||||
}
|
||||
|
||||
export class ComputePass { constructor(options: Record<string, unknown>); readonly id: string }
|
||||
export class FXAA { constructor(scene: Scene, options?: Record<string, unknown>); readonly ready: Promise<this>; dispose(): Promise<void> }
|
||||
export class ColorGrading extends FXAA { constructor(scene: Scene, options?: { toneMap?: "aces" | "reinhard" | "linear"; amount?: number }) }
|
||||
export class DynamicExposure extends FXAA { constructor(scene: Scene, options?: { exposure?: number }) }
|
||||
export class SSAO extends FXAA { constructor(scene: Scene, options?: { amount?: number }) }
|
||||
export class ComputePass {
|
||||
constructor(options: Record<string, unknown>);
|
||||
readonly id: string;
|
||||
}
|
||||
export class FXAA {
|
||||
constructor(scene: Scene, options?: Record<string, unknown>);
|
||||
readonly ready: Promise<this>;
|
||||
dispose(): Promise<void>;
|
||||
}
|
||||
export class ColorGrading extends FXAA {
|
||||
constructor(
|
||||
scene: Scene,
|
||||
options?: { toneMap?: "aces" | "reinhard" | "linear"; amount?: number },
|
||||
);
|
||||
}
|
||||
export class DynamicExposure extends FXAA {
|
||||
constructor(scene: Scene, options?: { exposure?: number });
|
||||
}
|
||||
export class SSAO extends FXAA {
|
||||
constructor(scene: Scene, options?: { amount?: number });
|
||||
}
|
||||
export function importGltf(scene: Scene, url: string | URL): Promise<Mesh[]>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user