feat: add benchmark settings panel
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m59s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m59s
This commit is contained in:
parent
8bf2958e1d
commit
d9afec5bf6
@ -18,6 +18,7 @@
|
|||||||
"@types/three": "^0.164.0",
|
"@types/three": "^0.164.0",
|
||||||
"@unocss/reset": "^0.59.4",
|
"@unocss/reset": "^0.59.4",
|
||||||
"comlink": "^4.4.1",
|
"comlink": "^4.4.1",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
"three": "^0.164.1"
|
"three": "^0.164.1"
|
||||||
},
|
},
|
||||||
@ -27,6 +28,7 @@
|
|||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "next",
|
"@sveltejs/vite-plugin-svelte": "next",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
"@unocss/preset-icons": "^0.59.4",
|
"@unocss/preset-icons": "^0.59.4",
|
||||||
"svelte": "5.0.0-next.118",
|
"svelte": "5.0.0-next.118",
|
||||||
"svelte-check": "^3.7.0",
|
"svelte-check": "^3.7.0",
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
import Camera from "../Camera.svelte";
|
import Camera from "../Camera.svelte";
|
||||||
import GraphView from "./GraphView.svelte";
|
import GraphView from "./GraphView.svelte";
|
||||||
import type { Node, NodeId, Node as NodeType, Socket } from "@nodes/types";
|
import type { Node, NodeId, Node as NodeType, Socket } from "@nodes/types";
|
||||||
import { NodeDefinitionSchema } from "@nodes/types";
|
import { GraphSchema, NodeDefinitionSchema } from "@nodes/types";
|
||||||
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
||||||
import {
|
import {
|
||||||
activeNodeId,
|
activeNodeId,
|
||||||
@ -28,6 +28,7 @@
|
|||||||
import { createWasmWrapper } from "@nodes/utils";
|
import { createWasmWrapper } from "@nodes/utils";
|
||||||
|
|
||||||
import HelpView from "../HelpView.svelte";
|
import HelpView from "../HelpView.svelte";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
export let manager: GraphManager;
|
export let manager: GraphManager;
|
||||||
|
|
||||||
@ -612,6 +613,7 @@
|
|||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: "a",
|
key: "a",
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
|
preventDefault: true,
|
||||||
description: "Select all nodes",
|
description: "Select all nodes",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!isBodyFocused()) return;
|
if (!isBodyFocused()) return;
|
||||||
@ -637,7 +639,6 @@
|
|||||||
ctrl: true,
|
ctrl: true,
|
||||||
description: "Redo",
|
description: "Redo",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (!isBodyFocused()) return;
|
|
||||||
manager.redo();
|
manager.redo();
|
||||||
for (const node of $nodes.values()) {
|
for (const node of $nodes.values()) {
|
||||||
updateNodePosition(node);
|
updateNodePosition(node);
|
||||||
@ -645,6 +646,20 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
keymap.addShortcut({
|
||||||
|
key: "s",
|
||||||
|
ctrl: true,
|
||||||
|
description: "Save",
|
||||||
|
preventDefault: true,
|
||||||
|
callback: () => {
|
||||||
|
const state = manager.serialize();
|
||||||
|
const blob = new Blob([JSON.stringify(state)], {
|
||||||
|
type: "application/json;charset=utf-8",
|
||||||
|
});
|
||||||
|
FileSaver.saveAs(blob, "nodarium-graph.json");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
keymap.addShortcut({
|
keymap.addShortcut({
|
||||||
key: ["Delete", "Backspace", "x"],
|
key: ["Delete", "Backspace", "x"],
|
||||||
description: "Delete selected nodes",
|
description: "Delete selected nodes",
|
||||||
@ -833,7 +848,9 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (event.dataTransfer.files.length) {
|
} else if (event.dataTransfer.files.length) {
|
||||||
const files = event.dataTransfer.files;
|
const file = event.dataTransfer.files[0];
|
||||||
|
|
||||||
|
if (file.type === "application/wasm") {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const buffer = e.target?.result as Buffer;
|
const buffer = e.target?.result as Buffer;
|
||||||
@ -841,9 +858,21 @@
|
|||||||
const wrapper = createWasmWrapper(buffer);
|
const wrapper = createWasmWrapper(buffer);
|
||||||
const definition = wrapper.get_definition();
|
const definition = wrapper.get_definition();
|
||||||
const res = NodeDefinitionSchema.parse(definition);
|
const res = NodeDefinitionSchema.parse(definition);
|
||||||
|
console.log(res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(files[0]);
|
reader.readAsArrayBuffer(file);
|
||||||
|
} else if (file.type === "application/json") {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const buffer = e.target?.result as Buffer;
|
||||||
|
if (buffer) {
|
||||||
|
const state = GraphSchema.parse(JSON.parse(buffer.toString()));
|
||||||
|
manager.load(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -893,13 +922,13 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="application/wasm"
|
accept="application/wasm,application/json"
|
||||||
id="drop-zone"
|
id="drop-zone"
|
||||||
disabled={!isDragging}
|
disabled={!isDragging}
|
||||||
on:dragend={handleDragEnd}
|
on:dragend={handleDragEnd}
|
||||||
on:dragleave={handleDragEnd}
|
on:dragleave={handleDragEnd}
|
||||||
/>
|
/>
|
||||||
<label for="drop-zone" />
|
<label for="drop-zone"></label>
|
||||||
|
|
||||||
{#if showHelp}
|
{#if showHelp}
|
||||||
<HelpView registry={manager.registry} />
|
<HelpView registry={manager.registry} />
|
||||||
|
@ -20,7 +20,7 @@ export function grid(width: number, height: number) {
|
|||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
position: [x * 30, y * 40],
|
position: [x * 30, y * 40],
|
||||||
props: i == 0 ? { value: 0 } : { op_type: 2, a: 2, b: 2 },
|
props: i == 0 ? { value: 0 } : { op_type: 0, a: 1, b: 0.05 },
|
||||||
type: i == 0 ? "max/plantarium/float" : "max/plantarium/math",
|
type: i == 0 ? "max/plantarium/float" : "max/plantarium/math",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
export { grid } from "./grid";
|
export { grid } from "./grid";
|
||||||
export { tree } from "./tree";
|
export { tree } from "./tree";
|
||||||
export { plant } from "./plant";
|
export { plant } from "./plant";
|
||||||
|
export { default as lottaFaces } from "./lotta-faces.json";
|
||||||
|
export { default as lottaNodes } from "./lotta-nodes.json";
|
||||||
|
export { default as lottaNodesAndFaces } from "./lotta-nodes-and-faces.json";
|
||||||
|
|
||||||
|
1
app/src/lib/graph-templates/lotta-faces.json
Normal file
1
app/src/lib/graph-templates/lotta-faces.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"settings":{"resolution.circle":64,"resolution.curve":64,"randomSeed":false},"nodes":[{"id":9,"position":[260,0],"type":"max/plantarium/output","props":{}},{"id":18,"position":[185,0],"type":"max/plantarium/stem","props":{"amount":64,"length":12,"thickness":0.15}},{"id":19,"position":[210,0],"type":"max/plantarium/noise","props":{"scale":1.3,"strength":5.4}},{"id":20,"position":[235,0],"type":"max/plantarium/branch","props":{"length":0.8,"thickness":0.8,"amount":3}},{"id":21,"position":[160,0],"type":"max/plantarium/vec3","props":{"0":0.39,"1":0,"2":0.41}},{"id":22,"position":[130,0],"type":"max/plantarium/random","props":{"min":-2,"max":2}}],"edges":[[18,0,19,"plant"],[19,0,20,"plant"],[20,0,9,"input"],[21,0,18,"origin"],[22,0,21,"0"],[22,0,21,"2"]]}
|
1
app/src/lib/graph-templates/lotta-nodes-and-faces.json
Normal file
1
app/src/lib/graph-templates/lotta-nodes-and-faces.json
Normal file
File diff suppressed because one or more lines are too long
1
app/src/lib/graph-templates/lotta-nodes.json
Normal file
1
app/src/lib/graph-templates/lotta-nodes.json
Normal file
File diff suppressed because one or more lines are too long
@ -5,6 +5,7 @@ type Shortcut = {
|
|||||||
shift?: boolean,
|
shift?: boolean,
|
||||||
ctrl?: boolean,
|
ctrl?: boolean,
|
||||||
alt?: boolean,
|
alt?: boolean,
|
||||||
|
preventDefault?: boolean,
|
||||||
description?: string,
|
description?: string,
|
||||||
callback: (event: KeyboardEvent) => void
|
callback: (event: KeyboardEvent) => void
|
||||||
}
|
}
|
||||||
@ -17,8 +18,11 @@ export function createKeyMap(keys: Shortcut[]) {
|
|||||||
|
|
||||||
const store = writable(new Map(keys.map(k => [getShortcutId(k), k])));
|
const store = writable(new Map(keys.map(k => [getShortcutId(k), k])));
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleKeyboardEvent: (event: KeyboardEvent) => {
|
handleKeyboardEvent: (event: KeyboardEvent) => {
|
||||||
|
const activeElement = document.activeElement as HTMLElement;
|
||||||
|
if (activeElement?.tagName === "INPUT" || activeElement?.tagName === "TEXTAREA") return;
|
||||||
const key = [...get(store).values()].find(k => {
|
const key = [...get(store).values()].find(k => {
|
||||||
if (Array.isArray(k.key) ? !k.key.includes(event.key) : k.key !== event.key) return false;
|
if (Array.isArray(k.key) ? !k.key.includes(event.key) : k.key !== event.key) return false;
|
||||||
if ("shift" in k && k.shift !== event.shiftKey) return false;
|
if ("shift" in k && k.shift !== event.shiftKey) return false;
|
||||||
@ -26,6 +30,7 @@ export function createKeyMap(keys: Shortcut[]) {
|
|||||||
if ("alt" in k && k.alt !== event.altKey) return false;
|
if ("alt" in k && k.alt !== event.altKey) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
if (key && key.preventDefault) event.preventDefault();
|
||||||
key?.callback(event);
|
key?.callback(event);
|
||||||
},
|
},
|
||||||
addShortcut: (shortcut: Shortcut) => {
|
addShortcut: (shortcut: Shortcut) => {
|
||||||
|
48
app/src/lib/performance/BarSplit.svelte
Normal file
48
app/src/lib/performance/BarSplit.svelte
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let labels: string[] = [];
|
||||||
|
export let values: number[] = [];
|
||||||
|
|
||||||
|
$: total = values.reduce((acc, v) => acc + v, 0);
|
||||||
|
|
||||||
|
let colors = ["red", "green", "blue"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="bars">
|
||||||
|
{#each values as value, i}
|
||||||
|
<div class="bar bg-{colors[i]}" style="width: {(value / total) * 100}%;">
|
||||||
|
{Math.round(value)}ms
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="labels mt-2">
|
||||||
|
{#each values as _label, i}
|
||||||
|
<div class="text-{colors[i]}">{labels[i]}</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="bg-red bg-green bg-yellow bg-blue text-red text-green text-yellow text-blue"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
margin-block: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bars {
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
height: 100%;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding-left: 0.4em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -4,6 +4,7 @@
|
|||||||
import { Checkbox } from "@nodes/ui";
|
import { Checkbox } from "@nodes/ui";
|
||||||
import localStore from "$lib/helpers/localStore";
|
import localStore from "$lib/helpers/localStore";
|
||||||
import { type PerformanceData } from "./store";
|
import { type PerformanceData } from "./store";
|
||||||
|
import BarSplit from "./BarSplit.svelte";
|
||||||
|
|
||||||
export let data: PerformanceData;
|
export let data: PerformanceData;
|
||||||
|
|
||||||
@ -42,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLast(key: string) {
|
function getLast(key: string) {
|
||||||
return data.at(-1)?.[key][0] || 0;
|
return data.at(-1)?.[key]?.[0] || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLasts() {
|
function getLasts() {
|
||||||
@ -53,13 +54,13 @@
|
|||||||
if (onlyLast) {
|
if (onlyLast) {
|
||||||
return (
|
return (
|
||||||
getLast("runtime") +
|
getLast("runtime") +
|
||||||
getLast("create-geometries") +
|
getLast("update-geometries") +
|
||||||
getLast("worker-transfer")
|
getLast("worker-transfer")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
getAverage("runtime") +
|
getAverage("runtime") +
|
||||||
getAverage("create-geometries") +
|
getAverage("update-geometries") +
|
||||||
getAverage("worker-transfer")
|
getAverage("worker-transfer")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -73,7 +74,7 @@
|
|||||||
const viewerKeys = [
|
const viewerKeys = [
|
||||||
"total-vertices",
|
"total-vertices",
|
||||||
"total-faces",
|
"total-faces",
|
||||||
"create-geometries",
|
"update-geometries",
|
||||||
"split-result",
|
"split-result",
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -116,8 +117,8 @@
|
|||||||
return data.map((run) => {
|
return data.map((run) => {
|
||||||
return (
|
return (
|
||||||
run["runtime"].reduce((acc, v) => acc + v, 0) +
|
run["runtime"].reduce((acc, v) => acc + v, 0) +
|
||||||
run["create-geometries"].reduce((acc, v) => acc + v, 0) +
|
run["update-geometries"].reduce((acc, v) => acc + v, 0) +
|
||||||
run["worker-transfer"].reduce((acc, v) => acc + v, 0)
|
(run["worker-transfer"]?.reduce((acc, v) => acc + v, 0) || 0)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -125,8 +126,8 @@
|
|||||||
return data.map((run) => {
|
return data.map((run) => {
|
||||||
return (
|
return (
|
||||||
run["runtime"][0] +
|
run["runtime"][0] +
|
||||||
run["create-geometries"][0] +
|
run["update-geometries"][0] +
|
||||||
run["worker-transfer"][0]
|
(run["worker-transfer"]?.[0] || 0)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -147,6 +148,22 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSplitValues(): number[] {
|
||||||
|
if (showAverage) {
|
||||||
|
return [
|
||||||
|
getAverage("worker-transfer"),
|
||||||
|
getAverage("runtime"),
|
||||||
|
getAverage("update-geometries"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
getLast("worker-transfer"),
|
||||||
|
getLast("runtime"),
|
||||||
|
getLast("update-geometries"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function getTitle(t: string) {
|
function getTitle(t: string) {
|
||||||
if (t.includes("/")) {
|
if (t.includes("/")) {
|
||||||
return `Node ${t.split("/").slice(-1).join("/")}`;
|
return `Node ${t.split("/").slice(-1).join("/")}`;
|
||||||
@ -159,7 +176,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key $activeType && data}
|
{#key $activeType}
|
||||||
{#if $activeType === "cache-hit"}
|
{#if $activeType === "cache-hit"}
|
||||||
<Monitor
|
<Monitor
|
||||||
title="Cache Hits"
|
title="Cache Hits"
|
||||||
@ -174,14 +191,22 @@
|
|||||||
points={constructPoints($activeType)}
|
points={constructPoints($activeType)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/key}
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4 performance-tabler">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Checkbox id="show-total" bind:value={showAverage} />
|
<Checkbox id="show-total" bind:value={showAverage} />
|
||||||
<label for="show-total">Show Average</label>
|
<label for="show-total">Show Average</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if data.length !== 0}
|
{#if data.length !== 0}
|
||||||
|
<BarSplit
|
||||||
|
labels={["worker-transfer", "runtime", "update-geometries"]}
|
||||||
|
values={getSplitValues()}
|
||||||
|
/>
|
||||||
|
|
||||||
<h3>General</h3>
|
<h3>General</h3>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -193,9 +218,7 @@
|
|||||||
on:click={() => ($activeType = "total")}
|
on:click={() => ($activeType = "total")}
|
||||||
>
|
>
|
||||||
total<span
|
total<span
|
||||||
>({Math.floor(
|
>({Math.floor(1000 / getTotalPerformance(showAverage))}fps)</span
|
||||||
1000 / getTotalPerformance(showAverage),
|
|
||||||
)}fps)</span
|
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -283,7 +306,6 @@
|
|||||||
<p>No runs available</p>
|
<p>No runs available</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
h3 {
|
h3 {
|
||||||
@ -296,6 +318,9 @@
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
table {
|
||||||
|
margin-bottom: 70px;
|
||||||
|
}
|
||||||
td {
|
td {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
padding-block: 5px;
|
padding-block: 5px;
|
||||||
|
@ -6,15 +6,18 @@ export interface PerformanceStore extends Readable<PerformanceData> {
|
|||||||
startRun(): void;
|
startRun(): void;
|
||||||
stopRun(): void;
|
stopRun(): void;
|
||||||
addPoint(name: string, value?: number): void;
|
addPoint(name: string, value?: number): void;
|
||||||
|
endPoint(name?: string): void;
|
||||||
mergeData(data: PerformanceData[number]): void;
|
mergeData(data: PerformanceData[number]): void;
|
||||||
get: () => PerformanceData;
|
get: () => PerformanceData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPerformanceStore(): PerformanceStore {
|
export function createPerformanceStore(id?: string): PerformanceStore {
|
||||||
|
|
||||||
let data: PerformanceData = [];
|
let data: PerformanceData = [];
|
||||||
|
|
||||||
let currentRun: Record<string, number[]> | undefined;
|
let currentRun: Record<string, number[]> | undefined;
|
||||||
|
let temp: Record<string, number> | undefined;
|
||||||
|
let lastPoint: string | undefined;
|
||||||
|
|
||||||
let set: (v: PerformanceData) => void;
|
let set: (v: PerformanceData) => void;
|
||||||
|
|
||||||
@ -23,23 +26,37 @@ export function createPerformanceStore(): PerformanceStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function startRun() {
|
function startRun() {
|
||||||
|
if (currentRun) return;
|
||||||
currentRun = {};
|
currentRun = {};
|
||||||
|
lastPoint = undefined;
|
||||||
|
temp = {
|
||||||
|
start: performance.now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopRun() {
|
function stopRun() {
|
||||||
if (currentRun) {
|
if (currentRun && temp) {
|
||||||
|
currentRun["total"] = [performance.now() - temp.start];
|
||||||
data.push(currentRun);
|
data.push(currentRun);
|
||||||
data = data.slice(-100);
|
data = data.slice(-100);
|
||||||
currentRun = undefined;
|
currentRun = undefined;
|
||||||
|
temp = undefined;
|
||||||
if (set) set(data);
|
if (set) set(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPoint(name: string, value: number) {
|
function addPoint(name: string, value?: number) {
|
||||||
if (!currentRun) return;
|
if (!currentRun) return;
|
||||||
|
if (value === undefined) {
|
||||||
|
if (temp) {
|
||||||
|
lastPoint = name;
|
||||||
|
temp[name] = performance.now();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
currentRun[name] = currentRun[name] || [];
|
currentRun[name] = currentRun[name] || [];
|
||||||
currentRun[name].push(value);
|
currentRun[name].push(value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function get() {
|
function get() {
|
||||||
return data;
|
return data;
|
||||||
@ -59,11 +76,21 @@ export function createPerformanceStore(): PerformanceStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function endPoint(name = lastPoint) {
|
||||||
|
if (name === lastPoint) lastPoint = undefined;
|
||||||
|
if (name && currentRun && temp && name in temp) {
|
||||||
|
currentRun[name] = currentRun[name] || [];
|
||||||
|
currentRun[name].push(performance.now() - temp[name]);
|
||||||
|
delete temp[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
startRun,
|
startRun,
|
||||||
stopRun,
|
stopRun,
|
||||||
addPoint,
|
addPoint,
|
||||||
|
endPoint,
|
||||||
mergeData,
|
mergeData,
|
||||||
get
|
get
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { T } from "@threlte/core";
|
import { T, useThrelte } from "@threlte/core";
|
||||||
import {
|
import { MeshLineGeometry, MeshLineMaterial, Text } from "@threlte/extras";
|
||||||
MeshLineGeometry,
|
|
||||||
MeshLineMaterial,
|
|
||||||
Text,
|
|
||||||
useTexture,
|
|
||||||
} from "@threlte/extras";
|
|
||||||
import {
|
import {
|
||||||
type Group,
|
type Group,
|
||||||
type BufferGeometry,
|
type BufferGeometry,
|
||||||
@ -16,15 +11,20 @@
|
|||||||
import { AppSettings } from "../settings/app-settings";
|
import { AppSettings } from "../settings/app-settings";
|
||||||
import Camera from "./Camera.svelte";
|
import Camera from "./Camera.svelte";
|
||||||
|
|
||||||
|
const d = useThrelte();
|
||||||
|
|
||||||
|
export const invalidate = d.invalidate;
|
||||||
|
|
||||||
export let geometries: BufferGeometry[];
|
export let geometries: BufferGeometry[];
|
||||||
export let lines: Vector3[][];
|
export let lines: Vector3[][];
|
||||||
|
export let scene;
|
||||||
let geos: Group;
|
let geos: Group;
|
||||||
|
$: scene = geos;
|
||||||
|
export let geoGroup: Group;
|
||||||
|
|
||||||
export let centerCamera: boolean = true;
|
export let centerCamera: boolean = true;
|
||||||
let center = new Vector3(0, 4, 0);
|
let center = new Vector3(0, 4, 0);
|
||||||
|
|
||||||
const matcap = useTexture("/matcap_green.jpg");
|
|
||||||
|
|
||||||
function getPosition(geo: BufferGeometry, i: number) {
|
function getPosition(geo: BufferGeometry, i: number) {
|
||||||
return [
|
return [
|
||||||
geo.attributes.position.array[i],
|
geo.attributes.position.array[i],
|
||||||
@ -48,9 +48,6 @@
|
|||||||
<T.GridHelper args={[20, 20]} />
|
<T.GridHelper args={[20, 20]} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<T.DirectionalLight position={[0, 10, 10]} />
|
|
||||||
<T.AmbientLight intensity={2} />
|
|
||||||
|
|
||||||
<T.Group bind:ref={geos}>
|
<T.Group bind:ref={geos}>
|
||||||
{#each geometries as geo}
|
{#each geometries as geo}
|
||||||
{#if $AppSettings.showIndices}
|
{#if $AppSettings.showIndices}
|
||||||
@ -67,15 +64,9 @@
|
|||||||
<T.PointsMaterial size={0.25} />
|
<T.PointsMaterial size={0.25} />
|
||||||
</T.Points>
|
</T.Points>
|
||||||
{/if}
|
{/if}
|
||||||
{#await matcap then value}
|
|
||||||
<T.Mesh geometry={geo}>
|
|
||||||
<T.MeshMatcapMaterial
|
|
||||||
matcap={value}
|
|
||||||
wireframe={$AppSettings.wireframe}
|
|
||||||
/>
|
|
||||||
</T.Mesh>
|
|
||||||
{/await}
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<T.Group bind:ref={geoGroup}></T.Group>
|
||||||
</T.Group>
|
</T.Group>
|
||||||
|
|
||||||
{#if $AppSettings.showStemLines && lines}
|
{#if $AppSettings.showStemLines && lines}
|
||||||
|
@ -1,155 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Canvas } from "@threlte/core";
|
import { Canvas } from "@threlte/core";
|
||||||
import Scene from "./Scene.svelte";
|
import Scene from "./Scene.svelte";
|
||||||
import {
|
import { BufferGeometry, Group, Vector3 } from "three";
|
||||||
BufferAttribute,
|
|
||||||
BufferGeometry,
|
import { updateGeometries } from "./updateGeometries";
|
||||||
Float32BufferAttribute,
|
import { decodeFloat, splitNestedArray } from "@nodes/utils";
|
||||||
Vector3,
|
|
||||||
} from "three";
|
|
||||||
import { decodeFloat, fastHashArrayBuffer } from "@nodes/utils";
|
|
||||||
import type { PerformanceStore } from "$lib/performance";
|
import type { PerformanceStore } from "$lib/performance";
|
||||||
import { AppSettings } from "$lib/settings/app-settings";
|
import { AppSettings } from "$lib/settings/app-settings";
|
||||||
|
|
||||||
export let centerCamera: boolean = true;
|
export let centerCamera: boolean = true;
|
||||||
export let perf: PerformanceStore;
|
export let perf: PerformanceStore;
|
||||||
|
export let scene: Group;
|
||||||
|
|
||||||
|
let geoGroup: Group;
|
||||||
|
|
||||||
let geometries: BufferGeometry[] = [];
|
let geometries: BufferGeometry[] = [];
|
||||||
let lines: Vector3[][] = [];
|
let lines: Vector3[][] = [];
|
||||||
|
|
||||||
function fastArrayHash(arr: ArrayBuffer) {
|
let invalidate: () => void;
|
||||||
let ints = new Uint8Array(arr);
|
|
||||||
|
|
||||||
const sampleDistance = Math.max(Math.floor(ints.length / 100), 1);
|
|
||||||
const sampleCount = Math.floor(ints.length / sampleDistance);
|
|
||||||
|
|
||||||
let hash = new Uint8Array(sampleCount);
|
|
||||||
|
|
||||||
for (let i = 0; i < sampleCount; i++) {
|
|
||||||
const index = i * sampleDistance;
|
|
||||||
hash[i] = ints[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return fastHashArrayBuffer(hash.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGeometryFromEncodedData(
|
|
||||||
encodedData: Int32Array,
|
|
||||||
geometry = new BufferGeometry(),
|
|
||||||
): BufferGeometry {
|
|
||||||
// Extract data from the encoded array
|
|
||||||
let index = 1;
|
|
||||||
// const geometryType = encodedData[index++];
|
|
||||||
const vertexCount = encodedData[index++];
|
|
||||||
const faceCount = encodedData[index++];
|
|
||||||
|
|
||||||
// Indices
|
|
||||||
const indicesEnd = index + faceCount * 3;
|
|
||||||
const indices = encodedData.subarray(index, indicesEnd);
|
|
||||||
index = indicesEnd;
|
|
||||||
|
|
||||||
// Vertices
|
|
||||||
const vertices = new Float32Array(
|
|
||||||
encodedData.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
|
||||||
let hash = fastArrayHash(vertices);
|
|
||||||
let posAttribute = geometry.getAttribute(
|
|
||||||
"position",
|
|
||||||
) as BufferAttribute | null;
|
|
||||||
|
|
||||||
if (geometry.userData?.hash === hash) {
|
|
||||||
return geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (posAttribute && posAttribute.count === vertexCount) {
|
|
||||||
posAttribute.set(vertices, 0);
|
|
||||||
posAttribute.needsUpdate = true;
|
|
||||||
} else {
|
|
||||||
geometry.setAttribute(
|
|
||||||
"position",
|
|
||||||
new Float32BufferAttribute(vertices, 3),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const normals = new Float32Array(
|
|
||||||
encodedData.buffer,
|
|
||||||
index * 4,
|
|
||||||
vertexCount * 3,
|
|
||||||
);
|
|
||||||
index = index + vertexCount * 3;
|
|
||||||
|
|
||||||
if (
|
|
||||||
geometry.userData?.faceCount !== faceCount ||
|
|
||||||
geometry.userData?.vertexCount !== vertexCount
|
|
||||||
) {
|
|
||||||
// Add data to geometry
|
|
||||||
geometry.setIndex([...indices]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalsAttribute = geometry.getAttribute(
|
|
||||||
"normal",
|
|
||||||
) as BufferAttribute | null;
|
|
||||||
if (normalsAttribute && normalsAttribute.count === vertexCount) {
|
|
||||||
normalsAttribute.set(normals, 0);
|
|
||||||
normalsAttribute.needsUpdate = true;
|
|
||||||
} else {
|
|
||||||
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
geometry.userData = {
|
|
||||||
vertexCount,
|
|
||||||
faceCount,
|
|
||||||
hash,
|
|
||||||
};
|
|
||||||
|
|
||||||
return geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_args(input: Int32Array) {
|
|
||||||
let index = 0;
|
|
||||||
const length = input.length;
|
|
||||||
let res: Int32Array[] = [];
|
|
||||||
|
|
||||||
let nextBracketIndex = 0;
|
|
||||||
let argStartIndex = 0;
|
|
||||||
let depth = -1;
|
|
||||||
|
|
||||||
while (index < length) {
|
|
||||||
const value = input[index];
|
|
||||||
|
|
||||||
if (index === nextBracketIndex) {
|
|
||||||
nextBracketIndex = index + input[index + 1] + 1;
|
|
||||||
if (value === 0) {
|
|
||||||
depth++;
|
|
||||||
} else {
|
|
||||||
depth--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (depth === 1 && value === 0) {
|
|
||||||
// if opening bracket
|
|
||||||
argStartIndex = index + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (depth === 0 && value === 1) {
|
|
||||||
// if closing bracket
|
|
||||||
res.push(input.slice(argStartIndex, index));
|
|
||||||
argStartIndex = index + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = nextBracketIndex;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we should not be here
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLineGeometryFromEncodedData(encodedData: Int32Array) {
|
function createLineGeometryFromEncodedData(encodedData: Int32Array) {
|
||||||
const positions: Vector3[] = [];
|
const positions: Vector3[] = [];
|
||||||
@ -166,16 +34,13 @@
|
|||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let result: Int32Array;
|
export const update = function update(result: Int32Array) {
|
||||||
$: result && updateGeometries();
|
perf?.addPoint("split-result");
|
||||||
function updateGeometries() {
|
const inputs = splitNestedArray(result);
|
||||||
let a = performance.now();
|
perf?.endPoint();
|
||||||
const inputs = parse_args(result);
|
|
||||||
let b = performance.now();
|
|
||||||
perf?.addPoint("split-result", b - a);
|
|
||||||
|
|
||||||
if ($AppSettings.showStemLines) {
|
if ($AppSettings.showStemLines) {
|
||||||
a = performance.now();
|
perf?.addPoint("create-lines");
|
||||||
lines = inputs
|
lines = inputs
|
||||||
.map((input) => {
|
.map((input) => {
|
||||||
if (input[0] === 0) {
|
if (input[0] === 0) {
|
||||||
@ -183,31 +48,27 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean) as Vector3[][];
|
.filter(Boolean) as Vector3[][];
|
||||||
b = performance.now();
|
perf.endPoint();
|
||||||
perf?.addPoint("create-lines", b - a);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalVertices = 0;
|
perf?.addPoint("update-geometries");
|
||||||
let totalFaces = 0;
|
|
||||||
|
const { totalVertices, totalFaces } = updateGeometries(inputs, geoGroup);
|
||||||
|
perf?.endPoint();
|
||||||
|
|
||||||
a = performance.now();
|
|
||||||
geometries = inputs
|
|
||||||
.map((input, i) => {
|
|
||||||
if (input[0] === 1) {
|
|
||||||
let geo = createGeometryFromEncodedData(input);
|
|
||||||
totalVertices += geo.userData.vertexCount;
|
|
||||||
totalFaces += geo.userData.faceCount;
|
|
||||||
return geo;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Boolean) as BufferGeometry[];
|
|
||||||
b = performance.now();
|
|
||||||
perf?.addPoint("create-geometries", b - a);
|
|
||||||
perf?.addPoint("total-vertices", totalVertices);
|
perf?.addPoint("total-vertices", totalVertices);
|
||||||
perf?.addPoint("total-faces", totalFaces);
|
perf?.addPoint("total-faces", totalFaces);
|
||||||
}
|
invalidate();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Scene {geometries} {lines} {centerCamera} />
|
<Scene
|
||||||
|
bind:scene
|
||||||
|
bind:geoGroup
|
||||||
|
bind:invalidate
|
||||||
|
{geometries}
|
||||||
|
{lines}
|
||||||
|
{centerCamera}
|
||||||
|
/>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
146
app/src/lib/result-viewer/updateGeometries.ts
Normal file
146
app/src/lib/result-viewer/updateGeometries.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { fastHashArrayBuffer } from "@nodes/utils";
|
||||||
|
import { BufferAttribute, BufferGeometry, Float32BufferAttribute, Mesh, MeshMatcapMaterial, TextureLoader, type Group } from "three";
|
||||||
|
|
||||||
|
function fastArrayHash(arr: ArrayBuffer) {
|
||||||
|
let ints = new Uint8Array(arr);
|
||||||
|
|
||||||
|
const sampleDistance = Math.max(Math.floor(ints.length / 100), 1);
|
||||||
|
const sampleCount = Math.floor(ints.length / sampleDistance);
|
||||||
|
|
||||||
|
let hash = new Uint8Array(sampleCount);
|
||||||
|
|
||||||
|
for (let i = 0; i < sampleCount; i++) {
|
||||||
|
const index = i * sampleDistance;
|
||||||
|
hash[i] = ints[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fastHashArrayBuffer(hash.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = new TextureLoader();
|
||||||
|
const matcap = loader.load('/matcap_green.jpg');
|
||||||
|
matcap.colorSpace = "srgb";
|
||||||
|
const material = new MeshMatcapMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
matcap
|
||||||
|
});
|
||||||
|
|
||||||
|
function createGeometryFromEncodedData(
|
||||||
|
encodedData: Int32Array,
|
||||||
|
geometry = new BufferGeometry(),
|
||||||
|
): BufferGeometry {
|
||||||
|
// Extract data from the encoded array
|
||||||
|
let index = 1;
|
||||||
|
// const geometryType = encodedData[index++];
|
||||||
|
const vertexCount = encodedData[index++];
|
||||||
|
const faceCount = encodedData[index++];
|
||||||
|
|
||||||
|
let hash = fastArrayHash(encodedData);
|
||||||
|
|
||||||
|
if (geometry.userData?.hash === hash) {
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indices
|
||||||
|
const indicesEnd = index + faceCount * 3;
|
||||||
|
const indices = encodedData.subarray(index, indicesEnd);
|
||||||
|
index = indicesEnd;
|
||||||
|
|
||||||
|
// Vertices
|
||||||
|
const vertices = new Float32Array(
|
||||||
|
encodedData.buffer,
|
||||||
|
index * 4,
|
||||||
|
vertexCount * 3,
|
||||||
|
);
|
||||||
|
index = index + vertexCount * 3;
|
||||||
|
|
||||||
|
let posAttribute = geometry.getAttribute(
|
||||||
|
"position",
|
||||||
|
) as BufferAttribute | null;
|
||||||
|
|
||||||
|
if (posAttribute && posAttribute.count === vertexCount) {
|
||||||
|
posAttribute.set(vertices, 0);
|
||||||
|
posAttribute.needsUpdate = true;
|
||||||
|
} else {
|
||||||
|
geometry.setAttribute(
|
||||||
|
"position",
|
||||||
|
new Float32BufferAttribute(vertices, 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normals = new Float32Array(
|
||||||
|
encodedData.buffer,
|
||||||
|
index * 4,
|
||||||
|
vertexCount * 3,
|
||||||
|
);
|
||||||
|
index = index + vertexCount * 3;
|
||||||
|
|
||||||
|
if (
|
||||||
|
geometry.userData?.faceCount !== faceCount ||
|
||||||
|
geometry.userData?.vertexCount !== vertexCount
|
||||||
|
) {
|
||||||
|
// Add data to geometry
|
||||||
|
geometry.setIndex([...indices]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalsAttribute = geometry.getAttribute(
|
||||||
|
"normal",
|
||||||
|
) as BufferAttribute | null;
|
||||||
|
if (normalsAttribute && normalsAttribute.count === vertexCount) {
|
||||||
|
normalsAttribute.set(normals, 0);
|
||||||
|
normalsAttribute.needsUpdate = true;
|
||||||
|
} else {
|
||||||
|
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry.userData = {
|
||||||
|
vertexCount,
|
||||||
|
faceCount,
|
||||||
|
hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
let meshes: Mesh[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
export function updateGeometries(inputs: Int32Array[], group: Group) {
|
||||||
|
let totalVertices = 0;
|
||||||
|
let totalFaces = 0;
|
||||||
|
let newGeometries = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(meshes.length, inputs.length); i++) {
|
||||||
|
let existingMesh = meshes[i];
|
||||||
|
let input = inputs[i];
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
if (input[0] !== 1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalVertices += input[1];
|
||||||
|
totalFaces += input[2];
|
||||||
|
} else {
|
||||||
|
if (existingMesh) {
|
||||||
|
existingMesh.visible = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingMesh) {
|
||||||
|
createGeometryFromEncodedData(input, existingMesh.geometry);
|
||||||
|
} else {
|
||||||
|
let geo = createGeometryFromEncodedData(input);
|
||||||
|
const mesh = new Mesh(geo, material);
|
||||||
|
meshes[i] = mesh;
|
||||||
|
newGeometries.push(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < newGeometries.length; i++) {
|
||||||
|
group.add(newGeometries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { totalFaces, totalVertices };
|
||||||
|
}
|
@ -128,9 +128,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
|
|
||||||
async execute(graph: Graph, settings: Record<string, unknown>) {
|
async execute(graph: Graph, settings: Record<string, unknown>) {
|
||||||
|
|
||||||
this.perf?.startRun();
|
this.perf?.addPoint("runtime");
|
||||||
|
|
||||||
let a0 = performance.now();
|
|
||||||
|
|
||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
|
|
||||||
@ -250,18 +248,20 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
// return the result of the parent of the output node
|
// return the result of the parent of the output node
|
||||||
const res = results[outputNode.id];
|
const res = results[outputNode.id];
|
||||||
|
|
||||||
this.perf?.addPoint("runtime", performance.now() - a0);
|
|
||||||
|
|
||||||
this.perf?.stopRun();
|
|
||||||
|
|
||||||
if (this.cache) {
|
if (this.cache) {
|
||||||
this.cache.size = sortedNodes.length * 2;
|
this.cache.size = sortedNodes.length * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.perf?.endPoint("runtime");
|
||||||
|
|
||||||
return res as unknown as Int32Array;
|
return res as unknown as Int32Array;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPerformanceData() {
|
||||||
|
return this.perf?.get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MemoryRuntimeCache implements RuntimeCache {
|
export class MemoryRuntimeCache implements RuntimeCache {
|
||||||
|
@ -93,6 +93,8 @@
|
|||||||
.content {
|
.content {
|
||||||
background: var(--layer-1);
|
background: var(--layer-1);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
|
@ -10,8 +10,10 @@ export const AppSettings = localStore("node-settings", {
|
|||||||
showIndices: false,
|
showIndices: false,
|
||||||
showVertices: false,
|
showVertices: false,
|
||||||
showPerformancePanel: false,
|
showPerformancePanel: false,
|
||||||
|
showBenchmarkPanel: false,
|
||||||
centerCamera: true,
|
centerCamera: true,
|
||||||
showStemLines: false,
|
showStemLines: false,
|
||||||
|
useWorker: true,
|
||||||
amount: 5
|
amount: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,6 +71,11 @@ export const AppSettingTypes = {
|
|||||||
label: "Wireframe",
|
label: "Wireframe",
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
useWorker: {
|
||||||
|
type: "boolean",
|
||||||
|
label: "Execute runtime in worker",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
showIndices: {
|
showIndices: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Show Indices",
|
label: "Show Indices",
|
||||||
@ -79,6 +86,11 @@ export const AppSettingTypes = {
|
|||||||
label: "Show Performance Panel",
|
label: "Show Performance Panel",
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
showBenchmarkPanel: {
|
||||||
|
type: "boolean",
|
||||||
|
label: "Show Benchmark Panel",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
showVertices: {
|
showVertices: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
label: "Show Vertices",
|
label: "Show Vertices",
|
||||||
@ -104,6 +116,18 @@ export const AppSettingTypes = {
|
|||||||
type: "button",
|
type: "button",
|
||||||
label: "Load Tree"
|
label: "Load Tree"
|
||||||
},
|
},
|
||||||
|
lottaFaces: {
|
||||||
|
type: "button",
|
||||||
|
label: "Load 'lots of faces'"
|
||||||
|
},
|
||||||
|
lottaNodes: {
|
||||||
|
type: "button",
|
||||||
|
label: "Load 'lots of nodes'"
|
||||||
|
},
|
||||||
|
lottaNodesAndFaces: {
|
||||||
|
type: "button",
|
||||||
|
label: "Load 'lots of nodes and faces'"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
? filterInputs(node.tmp.type.inputs)
|
? filterInputs(node.tmp.type.inputs)
|
||||||
: undefined;
|
: undefined;
|
||||||
$: store = node ? createStore(node.props, nodeDefinition) : undefined;
|
$: store = node ? createStore(node.props, nodeDefinition) : undefined;
|
||||||
$: console.log(nodeDefinition, store);
|
|
||||||
|
|
||||||
let lastPropsHash = "";
|
let lastPropsHash = "";
|
||||||
function updateNode() {
|
function updateNode() {
|
||||||
|
88
app/src/lib/settings/panels/BenchmarkPanel.svelte
Normal file
88
app/src/lib/settings/panels/BenchmarkPanel.svelte
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import localStore from "$lib/helpers/localStore";
|
||||||
|
import { Integer } from "@nodes/ui";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export let run: () => Promise<any>;
|
||||||
|
|
||||||
|
let isRunning = false;
|
||||||
|
let amount = localStore<number>("nodes.benchmark.samples", 500);
|
||||||
|
let samples = 0;
|
||||||
|
let warmUp = writable(0);
|
||||||
|
let warmUpAmount = 10;
|
||||||
|
let state = "";
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
const copyContent = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to copy: ", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function benchmark() {
|
||||||
|
if (isRunning) return;
|
||||||
|
isRunning = true;
|
||||||
|
|
||||||
|
samples = 0;
|
||||||
|
$warmUp = 0;
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
|
|
||||||
|
// warm up
|
||||||
|
for (let i = 0; i < warmUpAmount; i++) {
|
||||||
|
await run();
|
||||||
|
$warmUp = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = [];
|
||||||
|
|
||||||
|
// perform run
|
||||||
|
for (let i = 0; i < $amount; i++) {
|
||||||
|
const a = performance.now();
|
||||||
|
await run();
|
||||||
|
samples = i;
|
||||||
|
const b = performance.now();
|
||||||
|
results.push(b - a);
|
||||||
|
console.log(b - a);
|
||||||
|
}
|
||||||
|
result = results.join(" ");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{state}
|
||||||
|
|
||||||
|
<div class="wrapper" class:running={isRunning}>
|
||||||
|
{#if isRunning}
|
||||||
|
<p>WarmUp ({$warmUp}/{warmUpAmount})</p>
|
||||||
|
<progress value={$warmUp} max={warmUpAmount}
|
||||||
|
>{Math.floor(($warmUp / warmUpAmount) * 100)}%</progress
|
||||||
|
>
|
||||||
|
<p>Progress ({samples}/{$amount})</p>
|
||||||
|
<progress value={samples} max={$amount}
|
||||||
|
>{Math.floor((samples / $amount) * 100)}%</progress
|
||||||
|
>
|
||||||
|
|
||||||
|
{#if result}
|
||||||
|
<textarea readonly>{result}</textarea>
|
||||||
|
<div>
|
||||||
|
<button on:click={() => copyContent(result)}>Copy</button>
|
||||||
|
<button on:click={() => (isRunning = false)}>reset</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<label for="bench-samples">Samples</label>
|
||||||
|
<Integer id="bench-sample" bind:value={$amount} max={1000} />
|
||||||
|
<button on:click={benchmark} disabled={isRunning}> start </button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
53
app/src/lib/settings/panels/ExportSettings.svelte
Normal file
53
app/src/lib/settings/panels/ExportSettings.svelte
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Scene } from "three";
|
||||||
|
import { OBJExporter } from "three/addons/exporters/OBJExporter.js";
|
||||||
|
import { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
|
// Download
|
||||||
|
const download = (
|
||||||
|
data: string,
|
||||||
|
name: string,
|
||||||
|
mimetype: string,
|
||||||
|
extension: string,
|
||||||
|
) => {
|
||||||
|
if (typeof data !== "string") data = JSON.stringify(data);
|
||||||
|
const blob = new Blob([data], { type: mimetype + ";charset=utf-8" });
|
||||||
|
FileSaver.saveAs(blob, name + "." + extension);
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const json = (data, name = 'default') => {
|
||||||
|
// download(JSON.stringify(data), name, 'application/json', 'json');
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// export const obj = (data, name = 'default') => {
|
||||||
|
// };
|
||||||
|
|
||||||
|
export let scene: Scene;
|
||||||
|
|
||||||
|
function exportGltf() {
|
||||||
|
const exporter = new GLTFExporter();
|
||||||
|
exporter.parse(
|
||||||
|
scene,
|
||||||
|
(gltf) => {
|
||||||
|
// download .gltf file
|
||||||
|
download(gltf, "plant", "text/plain", "gltf");
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.log(err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportObj() {
|
||||||
|
const exporter = new OBJExporter();
|
||||||
|
const result = exporter.parse(scene);
|
||||||
|
// download .obj file
|
||||||
|
download(result, "plant", "text/plain", "obj");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-2">
|
||||||
|
<button on:click={exportObj}> export obj </button>
|
||||||
|
<button on:click={exportGltf}> export gltf </button>
|
||||||
|
</div>
|
@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
export let keymap: ReturnType<typeof createKeyMap>;
|
export let keymap: ReturnType<typeof createKeyMap>;
|
||||||
const keys = keymap?.keys;
|
const keys = keymap?.keys;
|
||||||
|
export let title = "Keymap";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<h3>Editor</h3>
|
<h3>{title}</h3>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
{#each $keys as key}
|
{#each $keys as key}
|
||||||
|
@ -7,12 +7,15 @@ const cache = new MemoryRuntimeCache();
|
|||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("");
|
||||||
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
|
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
|
||||||
|
|
||||||
const performanceStore = createPerformanceStore();
|
const performanceStore = createPerformanceStore("worker");
|
||||||
executor.perf = performanceStore;
|
executor.perf = performanceStore;
|
||||||
|
|
||||||
export async function executeGraph(graph: Graph, settings: Record<string, unknown>): Promise<Int32Array> {
|
export async function executeGraph(graph: Graph, settings: Record<string, unknown>): Promise<Int32Array> {
|
||||||
await nodeRegistry.load(graph.nodes.map((n) => n.type));
|
await nodeRegistry.load(graph.nodes.map((n) => n.type));
|
||||||
return executor.execute(graph, settings);
|
performanceStore.startRun();
|
||||||
|
let res = await executor.execute(graph, settings);
|
||||||
|
performanceStore.stopRun();
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPerformanceData() {
|
export function getPerformanceData() {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import { AppSettingTypes, AppSettings } from "$lib/settings/app-settings";
|
import { AppSettingTypes, AppSettings } from "$lib/settings/app-settings";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
import Keymap from "$lib/settings/panels/Keymap.svelte";
|
import Keymap from "$lib/settings/panels/Keymap.svelte";
|
||||||
import type { createKeyMap } from "$lib/helpers/createKeyMap";
|
import { createKeyMap } from "$lib/helpers/createKeyMap";
|
||||||
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
import NodeStore from "$lib/node-store/NodeStore.svelte";
|
||||||
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
import type { GraphManager } from "$lib/graph-interface/graph-manager";
|
||||||
import { setContext } from "svelte";
|
import { setContext } from "svelte";
|
||||||
@ -20,15 +20,28 @@
|
|||||||
import GraphSettings from "$lib/settings/panels/GraphSettings.svelte";
|
import GraphSettings from "$lib/settings/panels/GraphSettings.svelte";
|
||||||
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
|
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
|
||||||
import { createPerformanceStore } from "$lib/performance";
|
import { createPerformanceStore } from "$lib/performance";
|
||||||
|
import type { Scene } from "three";
|
||||||
|
import ExportSettings from "$lib/settings/panels/ExportSettings.svelte";
|
||||||
|
import {
|
||||||
|
MemoryRuntimeCache,
|
||||||
|
MemoryRuntimeExecutor,
|
||||||
|
} from "$lib/runtime-executor";
|
||||||
|
import { fastHashString } from "@nodes/utils";
|
||||||
|
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
|
||||||
|
|
||||||
|
let performanceStore = createPerformanceStore("page");
|
||||||
|
|
||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("");
|
||||||
const workerRuntime = new WorkerRuntimeExecutor();
|
const workerRuntime = new WorkerRuntimeExecutor();
|
||||||
|
const runtimeCache = new MemoryRuntimeCache();
|
||||||
|
const memoryRuntime = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
||||||
|
memoryRuntime.perf = performanceStore;
|
||||||
|
|
||||||
let performanceStore = createPerformanceStore();
|
$: runtime = $AppSettings.useWorker ? workerRuntime : memoryRuntime;
|
||||||
|
|
||||||
let activeNode: Node | undefined;
|
let activeNode: Node | undefined;
|
||||||
|
let scene: Scene;
|
||||||
let graphResult: Int32Array;
|
let updateViewerResult: (result: Int32Array) => void;
|
||||||
|
|
||||||
let graph = localStorage.getItem("graph")
|
let graph = localStorage.getItem("graph")
|
||||||
? JSON.parse(localStorage.getItem("graph")!)
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
@ -36,11 +49,22 @@
|
|||||||
|
|
||||||
let manager: GraphManager;
|
let manager: GraphManager;
|
||||||
let managerStatus: Writable<"loading" | "error" | "idle">;
|
let managerStatus: Writable<"loading" | "error" | "idle">;
|
||||||
$: if (manager) {
|
|
||||||
setContext("graphManager", manager);
|
async function randomGenerate() {
|
||||||
|
const g = manager.serialize();
|
||||||
|
const s = { ...$graphSettings, randomSeed: true };
|
||||||
|
const res = await handleResult(g, s);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
let keymap: ReturnType<typeof createKeyMap>;
|
let keymap: ReturnType<typeof createKeyMap>;
|
||||||
|
let applicationKeymap = createKeyMap([
|
||||||
|
{
|
||||||
|
key: "r",
|
||||||
|
description: "Regenerate the plant model",
|
||||||
|
callback: randomGenerate,
|
||||||
|
},
|
||||||
|
]);
|
||||||
let graphSettings = writable<Record<string, any>>({});
|
let graphSettings = writable<Record<string, any>>({});
|
||||||
let graphSettingTypes = {};
|
let graphSettingTypes = {};
|
||||||
|
|
||||||
@ -50,42 +74,59 @@
|
|||||||
| {
|
| {
|
||||||
graph: Graph;
|
graph: Graph;
|
||||||
settings: Record<string, any>;
|
settings: Record<string, any>;
|
||||||
|
hash: number;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
async function handleResult(_graph: Graph, _settings: Record<string, any>) {
|
async function handleResult(_graph: Graph, _settings: Record<string, any>) {
|
||||||
if (!_settings) return;
|
if (!_settings) return;
|
||||||
|
const inputHash = fastHashString(
|
||||||
|
JSON.stringify(_graph) + JSON.stringify(_settings),
|
||||||
|
);
|
||||||
if (isWorking) {
|
if (isWorking) {
|
||||||
unfinished = {
|
unfinished = {
|
||||||
graph: _graph,
|
graph: _graph,
|
||||||
settings: _settings,
|
settings: _settings,
|
||||||
|
hash: inputHash,
|
||||||
};
|
};
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
isWorking = true;
|
isWorking = true;
|
||||||
|
performanceStore.startRun();
|
||||||
try {
|
try {
|
||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
graphResult = await workerRuntime.execute(_graph, _settings);
|
const graphResult = await runtime.execute(_graph, _settings);
|
||||||
let b = performance.now();
|
let b = performance.now();
|
||||||
let perfData = await workerRuntime.getPerformanceData();
|
|
||||||
let lastRun = perfData.at(-1);
|
if ($AppSettings.useWorker) {
|
||||||
if (lastRun) {
|
let perfData = await runtime.getPerformanceData();
|
||||||
lastRun["worker-transfer"] = [b - a - lastRun.runtime[0]];
|
let lastRun = perfData?.at(-1);
|
||||||
|
if (lastRun?.total) {
|
||||||
|
lastRun.runtime = lastRun.total;
|
||||||
|
delete lastRun.total;
|
||||||
performanceStore.mergeData(lastRun);
|
performanceStore.mergeData(lastRun);
|
||||||
|
performanceStore.addPoint(
|
||||||
|
"worker-transfer",
|
||||||
|
b - a - lastRun.runtime[0],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
isWorking = false;
|
}
|
||||||
|
|
||||||
|
updateViewerResult(graphResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("errors", error);
|
console.log("errors", error);
|
||||||
|
} finally {
|
||||||
|
performanceStore.stopRun();
|
||||||
|
isWorking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
performanceStore.stopRun();
|
if (unfinished && unfinished.hash === inputHash) {
|
||||||
performanceStore.startRun();
|
|
||||||
|
|
||||||
if (unfinished) {
|
|
||||||
let d = unfinished;
|
let d = unfinished;
|
||||||
unfinished = undefined;
|
unfinished = undefined;
|
||||||
handleResult(d.graph, d.settings);
|
await handleResult(d.graph, d.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (AppSettings) {
|
$: if (AppSettings) {
|
||||||
@ -97,6 +138,18 @@
|
|||||||
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
AppSettingTypes.debug.stressTest.loadTree.callback = () => {
|
||||||
graph = templates.tree($AppSettings.amount);
|
graph = templates.tree($AppSettings.amount);
|
||||||
};
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.lottaFaces.callback = () => {
|
||||||
|
graph = templates.lottaFaces;
|
||||||
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.lottaNodes.callback = () => {
|
||||||
|
graph = templates.lottaNodes;
|
||||||
|
};
|
||||||
|
//@ts-ignore
|
||||||
|
AppSettingTypes.debug.stressTest.lottaNodesAndFaces.callback = () => {
|
||||||
|
graph = templates.lottaNodesAndFaces;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSave(event: CustomEvent<Graph>) {
|
function handleSave(event: CustomEvent<Graph>) {
|
||||||
@ -104,13 +157,15 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:document on:keydown={applicationKeymap.handleKeyboardEvent} />
|
||||||
<div class="wrapper manager-{$managerStatus}">
|
<div class="wrapper manager-{$managerStatus}">
|
||||||
<header></header>
|
<header></header>
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
<Viewer
|
<Viewer
|
||||||
result={graphResult}
|
|
||||||
perf={performanceStore}
|
perf={performanceStore}
|
||||||
|
bind:scene
|
||||||
|
bind:update={updateViewerResult}
|
||||||
centerCamera={$AppSettings.centerCamera}
|
centerCamera={$AppSettings.centerCamera}
|
||||||
/>
|
/>
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
@ -143,10 +198,14 @@
|
|||||||
title="Keyboard Shortcuts"
|
title="Keyboard Shortcuts"
|
||||||
icon="i-tabler-keyboard"
|
icon="i-tabler-keyboard"
|
||||||
>
|
>
|
||||||
|
<Keymap title="Application" keymap={applicationKeymap} />
|
||||||
{#if keymap}
|
{#if keymap}
|
||||||
<Keymap {keymap} />
|
<Keymap title="Node-Editor" {keymap} />
|
||||||
{/if}
|
{/if}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
<Panel id="exports" title="Exporter" icon="i-tabler-package-export">
|
||||||
|
<ExportSettings {scene} />
|
||||||
|
</Panel>
|
||||||
<Panel
|
<Panel
|
||||||
id="node-store"
|
id="node-store"
|
||||||
classes="text-green-400"
|
classes="text-green-400"
|
||||||
@ -166,6 +225,15 @@
|
|||||||
<PerformanceViewer data={$performanceStore} />
|
<PerformanceViewer data={$performanceStore} />
|
||||||
{/if}
|
{/if}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
<Panel
|
||||||
|
id="benchmark"
|
||||||
|
title="Benchmark"
|
||||||
|
classes="text-red-400"
|
||||||
|
hidden={!$AppSettings.showBenchmarkPanel}
|
||||||
|
icon="i-tabler-graph"
|
||||||
|
>
|
||||||
|
<BenchmarkPanel run={randomGenerate} />
|
||||||
|
</Panel>
|
||||||
<Panel
|
<Panel
|
||||||
id="graph-settings"
|
id="graph-settings"
|
||||||
title="Graph Settings"
|
title="Graph Settings"
|
||||||
@ -190,8 +258,6 @@
|
|||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="font-red" />
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
/* border-bottom: solid thin var(--outline); */
|
/* border-bottom: solid thin var(--outline); */
|
||||||
|
@ -9,13 +9,13 @@
|
|||||||
},
|
},
|
||||||
"strength": {
|
"strength": {
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"min": 0.1,
|
"min": 0,
|
||||||
"max": 1
|
"max": 1
|
||||||
},
|
},
|
||||||
"curviness": {
|
"curviness": {
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"min": 0.1,
|
"min": 0,
|
||||||
"max": 1
|
"max": 1
|
||||||
},
|
},
|
||||||
"depth": {
|
"depth": {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
use macros::include_definition_file;
|
use macros::include_definition_file;
|
||||||
use utils::{
|
use utils::{
|
||||||
concat_args, evaluate_float, evaluate_int, geometry::wrap_path_mut, log, reset_call_count,
|
concat_args, evaluate_float, evaluate_int,
|
||||||
set_panic_hook, split_args,
|
geometry::{wrap_path, wrap_path_mut},
|
||||||
|
log, reset_call_count, set_panic_hook, split_args,
|
||||||
};
|
};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
@ -34,58 +35,64 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
let output: Vec<Vec<i32>> = plants
|
let output: Vec<Vec<i32>> = plants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|_path_data| {
|
.map(|_path_data| {
|
||||||
let mut path_data = _path_data.to_vec();
|
let path_data = _path_data.to_vec();
|
||||||
if path_data[2] != 0 || path_data[3] < (max_depth - depth + 1) {
|
if path_data[2] != 0 || path_data[3] < (max_depth - depth + 1) {
|
||||||
return path_data;
|
return path_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = wrap_path_mut(&mut path_data);
|
let path = wrap_path(&path_data);
|
||||||
|
let mut output_data = path_data.clone();
|
||||||
|
let output = wrap_path_mut(&mut output_data);
|
||||||
|
|
||||||
let mut offset_vec = Vec3::ZERO;
|
let mut offset_vec = Vec3::ZERO;
|
||||||
|
|
||||||
for i in 1..path.length {
|
let original_length = path.get_length();
|
||||||
// let alpha = i as f32 / (path.length - 1) as f32;
|
|
||||||
let start_index = (i - 1) * 4;
|
for i in 0..path.length - 1 {
|
||||||
let end_index = start_index + 4;
|
let alpha = i as f32 / (path.length - 1) as f32;
|
||||||
|
let start_index = i * 4;
|
||||||
|
|
||||||
let start_point = Vec3::from_slice(&path.points[start_index..start_index + 3]);
|
let start_point = Vec3::from_slice(&path.points[start_index..start_index + 3]);
|
||||||
let end_point = Vec3::from_slice(&path.points[end_index..end_index + 3]);
|
let end_point = Vec3::from_slice(&path.points[start_index + 4..start_index + 7]);
|
||||||
|
|
||||||
let length = (end_point - start_point).length();
|
let direction = end_point - start_point;
|
||||||
|
|
||||||
let normalised = (end_point - start_point).normalize();
|
let length = direction.length();
|
||||||
|
|
||||||
let strength = evaluate_float(args[1]);
|
|
||||||
let down_point = Vec3::new(0.0, -length * strength, 0.0);
|
|
||||||
|
|
||||||
let curviness = evaluate_float(args[2]);
|
let curviness = evaluate_float(args[2]);
|
||||||
|
let strength =
|
||||||
|
evaluate_float(args[1]) / curviness.max(0.0001) * evaluate_float(args[1]);
|
||||||
|
|
||||||
let mut mid_point = lerp_vec3(
|
log!(
|
||||||
normalised,
|
"length: {}, curviness: {}, strength: {}",
|
||||||
down_point,
|
length,
|
||||||
curviness * (i as f32 / path.length as f32).sqrt(),
|
curviness,
|
||||||
|
strength
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let down_point = Vec3::new(0.0, -length * strength, 0.0);
|
||||||
|
|
||||||
|
let mut mid_point = lerp_vec3(direction, down_point, curviness * alpha.sqrt());
|
||||||
|
|
||||||
if mid_point[0] == 0.0 && mid_point[2] == 0.0 {
|
if mid_point[0] == 0.0 && mid_point[2] == 0.0 {
|
||||||
mid_point[0] += 0.0001;
|
mid_point[0] += 0.0001;
|
||||||
mid_point[2] += 0.0001;
|
mid_point[2] += 0.0001;
|
||||||
}
|
}
|
||||||
|
|
||||||
mid_point = mid_point.normalize();
|
// Correct midpoint length
|
||||||
|
mid_point *= mid_point.length() / length;
|
||||||
mid_point *= length;
|
|
||||||
|
|
||||||
let final_end_point = start_point + mid_point;
|
let final_end_point = start_point + mid_point;
|
||||||
let offset_end_point = end_point + offset_vec;
|
let offset_end_point = end_point + offset_vec;
|
||||||
|
|
||||||
path.points[end_index] = offset_end_point[0];
|
output.points[start_index + 4] = offset_end_point[0];
|
||||||
path.points[end_index + 1] = offset_end_point[1];
|
output.points[start_index + 5] = offset_end_point[1];
|
||||||
path.points[end_index + 2] = offset_end_point[2];
|
output.points[start_index + 6] = offset_end_point[2];
|
||||||
|
|
||||||
let offset = final_end_point - end_point;
|
offset_vec += final_end_point - end_point;
|
||||||
offset_vec += offset;
|
|
||||||
}
|
}
|
||||||
path_data
|
log!("length: {} final: {}", original_length, output.get_length());
|
||||||
|
output_data
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use glam::Vec3;
|
|
||||||
use macros::include_definition_file;
|
use macros::include_definition_file;
|
||||||
use noise::{HybridMulti, MultiFractal, NoiseFn, OpenSimplex};
|
use noise::{HybridMulti, MultiFractal, NoiseFn, OpenSimplex};
|
||||||
use utils::{
|
use utils::{
|
||||||
@ -66,15 +65,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
|
|
||||||
let path = wrap_path_mut(&mut path_data);
|
let path = wrap_path_mut(&mut path_data);
|
||||||
|
|
||||||
let p0 = Vec3::new(path.points[0], path.points[1], path.points[2]);
|
let length = path.get_length() as f64;
|
||||||
|
|
||||||
let p2 = Vec3::new(
|
|
||||||
path.points[path.length * 4 - 3],
|
|
||||||
path.points[path.length * 4 - 2],
|
|
||||||
path.points[path.length * 4 - 1],
|
|
||||||
);
|
|
||||||
|
|
||||||
let length = (p2 - p0).length() as f64;
|
|
||||||
|
|
||||||
for i in 0..path.length {
|
for i in 0..path.length {
|
||||||
let a = i as f64 / (path.length - 1) as f64;
|
let a = i as f64 / (path.length - 1) as f64;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export type { NodeInput } from "./inputs";
|
export type { NodeInput } from "./inputs";
|
||||||
export type { NodeRegistry, RuntimeExecutor, RuntimeCache } from "./components";
|
export type { NodeRegistry, RuntimeExecutor, RuntimeCache } from "./components";
|
||||||
export type { Node, NodeDefinition, Socket, NodeId, Edge, Graph } from "./types";
|
export type { Node, NodeDefinition, Socket, NodeId, Edge, Graph } from "./types";
|
||||||
|
export { NodeSchema, GraphSchema } from "./types";
|
||||||
export { NodeDefinitionSchema } from "./types";
|
export { NodeDefinitionSchema } from "./types";
|
||||||
|
|
||||||
|
@ -3,10 +3,18 @@ import { NodeInputSchema } from "./inputs";
|
|||||||
|
|
||||||
export type NodeId = `${string}/${string}/${string}`;
|
export type NodeId = `${string}/${string}/${string}`;
|
||||||
|
|
||||||
|
export const NodeSchema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
type: z.string(),
|
||||||
|
props: z.record(z.union([z.number(), z.array(z.number())])).optional(),
|
||||||
|
meta: z.object({
|
||||||
|
title: z.string().optional(),
|
||||||
|
lastModified: z.string().optional(),
|
||||||
|
}).optional(),
|
||||||
|
position: z.tuple([z.number(), z.number()])
|
||||||
|
});
|
||||||
|
|
||||||
export type Node = {
|
export type Node = {
|
||||||
id: number;
|
|
||||||
type: NodeId;
|
|
||||||
props?: Record<string, number | number[]>,
|
|
||||||
tmp?: {
|
tmp?: {
|
||||||
depth?: number;
|
depth?: number;
|
||||||
mesh?: any;
|
mesh?: any;
|
||||||
@ -22,13 +30,8 @@ export type Node = {
|
|||||||
ref?: HTMLElement;
|
ref?: HTMLElement;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
isMoving?: boolean;
|
isMoving?: boolean;
|
||||||
},
|
|
||||||
meta?: {
|
|
||||||
title?: string;
|
|
||||||
lastModified?: string;
|
|
||||||
},
|
|
||||||
position: [x: number, y: number]
|
|
||||||
}
|
}
|
||||||
|
} & z.infer<typeof NodeSchema>;
|
||||||
|
|
||||||
export const NodeDefinitionSchema = z.object({
|
export const NodeDefinitionSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
@ -50,16 +53,17 @@ export type Socket = {
|
|||||||
position: [number, number];
|
position: [number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type Edge = [Node, number, Node, string];
|
export type Edge = [Node, number, Node, string];
|
||||||
|
|
||||||
export type Graph = {
|
export const GraphSchema = z.object({
|
||||||
id: number;
|
id: z.number().optional(),
|
||||||
meta?: {
|
meta: z.object({
|
||||||
title?: string;
|
title: z.string().optional(),
|
||||||
lastModified?: string;
|
lastModified: z.string().optional(),
|
||||||
},
|
}).optional(),
|
||||||
settings?: Record<string, any>,
|
settings: z.record(z.any()).optional(),
|
||||||
nodes: Node[];
|
nodes: z.array(NodeSchema),
|
||||||
edges: [number, number, number, string][];
|
edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])),
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export type Graph = z.infer<typeof GraphSchema> & { nodes: Node[] };
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Checkbox from "./elements/Checkbox.svelte";
|
import Checkbox from './elements/Checkbox.svelte';
|
||||||
import Float from "./elements/Float.svelte";
|
import Float from './elements/Float.svelte';
|
||||||
import Integer from "./elements/Integer.svelte";
|
import Integer from './elements/Integer.svelte';
|
||||||
import Select from "./elements/Select.svelte";
|
import Select from './elements/Select.svelte';
|
||||||
|
|
||||||
import type { NodeInput } from "@nodes/types";
|
import type { NodeInput } from '@nodes/types';
|
||||||
import Vec3 from "./elements/Vec3.svelte";
|
import Vec3 from './elements/Vec3.svelte';
|
||||||
|
|
||||||
export let input: NodeInput;
|
export let input: NodeInput;
|
||||||
export let value: any;
|
export let value: any;
|
||||||
export let id: string;
|
export let id: string;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if input.type === "float"}
|
{#if input.type === 'float'}
|
||||||
<Float {id} bind:value min={input?.min} max={input?.max} />
|
<Float {id} bind:value min={input?.min} max={input?.max} />
|
||||||
{:else if input.type === "integer"}
|
{:else if input.type === 'integer'}
|
||||||
<Integer {id} bind:value min={input?.min} max={input?.max} />
|
<Integer {id} bind:value min={input?.min} max={input?.max} />
|
||||||
{:else if input.type === "boolean"}
|
{:else if input.type === 'boolean'}
|
||||||
<Checkbox {id} bind:value />
|
<Checkbox {id} bind:value />
|
||||||
{:else if input.type === "select"}
|
{:else if input.type === 'select'}
|
||||||
<Select {id} bind:value options={input.options} />
|
<Select {id} bind:value options={input?.options || []} />
|
||||||
{:else if input.type === "vec3"}
|
{:else if input.type === 'vec3'}
|
||||||
<Vec3 {id} bind:value />
|
<Vec3 {id} bind:value />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let value: boolean;
|
export let value: boolean;
|
||||||
|
|
||||||
$: if (typeof value === "string") {
|
$: if (typeof value === 'string') {
|
||||||
value = value === "true";
|
value = value === 'true';
|
||||||
} else if (typeof value === "number") {
|
} else if (typeof value === 'number') {
|
||||||
value = value === 1;
|
value = value === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let id = "";
|
export let id = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input {id} type="checkbox" bind:checked={value} />
|
<input {id} type="checkbox" bind:checked={value} />
|
||||||
@ -24,7 +24,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
input[type="checkbox"] {
|
input[type='checkbox'] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
@ -49,7 +49,7 @@
|
|||||||
color: rgb(0, 0, 0);
|
color: rgb(0, 0, 0);
|
||||||
}
|
}
|
||||||
input + label::before {
|
input + label::before {
|
||||||
content: " ";
|
content: ' ';
|
||||||
display: inline;
|
display: inline;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
@ -61,7 +61,7 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
input:checked + label::after {
|
input:checked + label::after {
|
||||||
content: " ";
|
content: ' ';
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 12px 12px;
|
background-size: 12px 12px;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
let {
|
||||||
|
onchange,
|
||||||
export let value = 0.5;
|
value = $bindable(),
|
||||||
export let step = 0.01;
|
id,
|
||||||
export let min = 0;
|
step = 0.01,
|
||||||
export let max = 1;
|
min = 0,
|
||||||
export let id = '';
|
max = 1
|
||||||
|
}: {
|
||||||
|
onchange?: (num: number) => void;
|
||||||
|
value?: number;
|
||||||
|
id?: string;
|
||||||
|
step?: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
if (min > max) {
|
if (min > max) {
|
||||||
[min, max] = [max, min];
|
[min, max] = [max, min];
|
||||||
@ -18,31 +26,32 @@
|
|||||||
return +parseFloat(input + '').toPrecision(2);
|
return +parseFloat(input + '').toPrecision(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl: HTMLInputElement;
|
||||||
|
|
||||||
$: if ((value || 0).toString().length > 5) {
|
$effect(() => {
|
||||||
|
if ((value || 0).toString().length > 5) {
|
||||||
value = strip(value || 0);
|
value = strip(value || 0);
|
||||||
}
|
}
|
||||||
$: value !== undefined && handleChange();
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (value !== undefined) handleChange();
|
||||||
|
});
|
||||||
|
|
||||||
let oldValue: number;
|
let oldValue: number;
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
if (value === oldValue) return;
|
if (value === oldValue) return;
|
||||||
oldValue = value;
|
oldValue = value;
|
||||||
dispatch('change', parseFloat(value + ''));
|
onchange?.(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: width = Number.isFinite(value)
|
let width = $derived(
|
||||||
? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px'
|
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px' : '20px'
|
||||||
: '20px';
|
);
|
||||||
|
|
||||||
let isMouseDown = false;
|
let isMouseDown = $state(false);
|
||||||
/* let downX = 0; */
|
|
||||||
/* let downY = 0; */
|
|
||||||
let downV = 0;
|
let downV = 0;
|
||||||
let vx = 0;
|
let vx = 0;
|
||||||
/* let vy = 0; */
|
|
||||||
let rect: DOMRect;
|
let rect: DOMRect;
|
||||||
|
|
||||||
function handleMouseDown(ev: MouseEvent) {
|
function handleMouseDown(ev: MouseEvent) {
|
||||||
@ -53,8 +62,6 @@
|
|||||||
isMouseDown = true;
|
isMouseDown = true;
|
||||||
|
|
||||||
downV = value;
|
downV = value;
|
||||||
/* downX = ev.clientX; */
|
|
||||||
/* downY = ev.clientY; */
|
|
||||||
rect = inputEl.getBoundingClientRect();
|
rect = inputEl.getBoundingClientRect();
|
||||||
|
|
||||||
window.removeEventListener('mousemove', handleMouseMove);
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
@ -78,16 +85,6 @@
|
|||||||
min = value;
|
min = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setTimeout(() => {
|
|
||||||
// if (value >= 0) {
|
|
||||||
// max = getBoundingValue(value);
|
|
||||||
// min = 0;
|
|
||||||
// } else {
|
|
||||||
// min = getBoundingValue(value);
|
|
||||||
// max = 0;
|
|
||||||
// }
|
|
||||||
// }, 500);
|
|
||||||
|
|
||||||
document.body.style.cursor = 'unset';
|
document.body.style.cursor = 'unset';
|
||||||
window.removeEventListener('mouseup', handleMouseUp);
|
window.removeEventListener('mouseup', handleMouseUp);
|
||||||
window.removeEventListener('mousemove', handleMouseMove);
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
@ -122,9 +119,9 @@
|
|||||||
{step}
|
{step}
|
||||||
{max}
|
{max}
|
||||||
{min}
|
{min}
|
||||||
on:keydown={handleKeyDown}
|
onkeydown={handleKeyDown}
|
||||||
on:mousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
on:mouseup={handleMouseUp}
|
onmouseup={handleMouseUp}
|
||||||
type="number"
|
type="number"
|
||||||
style={`width:${width};`}
|
style={`width:${width};`}
|
||||||
/>
|
/>
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
<svelte:options accessors />
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from "svelte";
|
let {
|
||||||
const dispatch = createEventDispatcher();
|
min = 0,
|
||||||
|
max = 10,
|
||||||
// Styling
|
step = 1,
|
||||||
export let min: number | undefined = undefined;
|
value = $bindable(),
|
||||||
export let max: number | undefined = undefined;
|
id,
|
||||||
export let step = 1;
|
onchange
|
||||||
export let value = 0;
|
}: {
|
||||||
export let id = "";
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
value?: number;
|
||||||
|
id?: string;
|
||||||
|
onchange?: (num: number) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = 0;
|
value = 0;
|
||||||
@ -17,24 +21,25 @@
|
|||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl: HTMLInputElement;
|
||||||
let wrapper: HTMLDivElement;
|
let wrapper: HTMLDivElement;
|
||||||
$: value !== undefined && update();
|
$effect(() => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let prev = -1;
|
let prev = -1;
|
||||||
function update() {
|
function update() {
|
||||||
if (prev === value) return;
|
if (prev === value) return;
|
||||||
prev = value;
|
prev = value;
|
||||||
dispatch("change", parseFloat(value + ""));
|
onchange?.(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: width = Number.isFinite(value)
|
let width = $derived(
|
||||||
? Math.max((value?.toString().length ?? 1) * 8, 30) + "px"
|
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px' : '20px'
|
||||||
: "20px";
|
);
|
||||||
|
|
||||||
function handleChange(change: number) {
|
function handleChange(change: number) {
|
||||||
value = Math.max(
|
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
|
||||||
min ?? -Infinity,
|
|
||||||
Math.min(+value + change, max ?? Infinity),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let downX = 0;
|
let downX = 0;
|
||||||
@ -48,10 +53,10 @@
|
|||||||
downX = ev.clientX;
|
downX = ev.clientX;
|
||||||
rect = wrapper.getBoundingClientRect();
|
rect = wrapper.getBoundingClientRect();
|
||||||
|
|
||||||
window.removeEventListener("mousemove", handleMouseMove);
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
window.addEventListener("mousemove", handleMouseMove);
|
window.addEventListener('mousemove', handleMouseMove);
|
||||||
window.addEventListener("mouseup", handleMouseUp);
|
window.addEventListener('mouseup', handleMouseUp);
|
||||||
document.body.style.cursor = "ew-resize";
|
document.body.style.cursor = 'ew-resize';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseUp() {
|
function handleMouseUp() {
|
||||||
@ -61,13 +66,13 @@
|
|||||||
inputEl.blur();
|
inputEl.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.style.cursor = "unset";
|
document.body.style.cursor = 'unset';
|
||||||
window.removeEventListener("mouseup", handleMouseUp);
|
window.removeEventListener('mouseup', handleMouseUp);
|
||||||
window.removeEventListener("mousemove", handleMouseMove);
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseMove(ev: MouseEvent) {
|
function handleMouseMove(ev: MouseEvent) {
|
||||||
if (!ev.ctrlKey && typeof min === "number" && typeof max === "number") {
|
if (!ev.ctrlKey && typeof min === 'number' && typeof max === 'number') {
|
||||||
const vx = (ev.clientX - rect.left) / rect.width;
|
const vx = (ev.clientX - rect.left) / rect.width;
|
||||||
value = Math.max(Math.min(Math.round(min + (max - min) * vx), max), min);
|
value = Math.max(Math.min(Math.round(min + (max - min) * vx), max), min);
|
||||||
} else {
|
} else {
|
||||||
@ -83,16 +88,14 @@
|
|||||||
role="slider"
|
role="slider"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-valuenow={value}
|
aria-valuenow={value}
|
||||||
on:mousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
on:mouseup={handleMouseUp}
|
onmouseup={handleMouseUp}
|
||||||
>
|
>
|
||||||
{#if typeof min !== "undefined" && typeof max !== "undefined"}
|
{#if typeof min !== 'undefined' && typeof max !== 'undefined'}
|
||||||
<span
|
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
|
||||||
class="overlay"
|
></span>
|
||||||
style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
<button on:click={() => handleChange(-step)}>-</button>
|
<button onclick={() => handleChange(-step)}>-</button>
|
||||||
<input
|
<input
|
||||||
bind:value
|
bind:value
|
||||||
bind:this={inputEl}
|
bind:this={inputEl}
|
||||||
@ -104,7 +107,7 @@
|
|||||||
style={`width:${width};`}
|
style={`width:${width};`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button on:click={() => handleChange(+step)}>+</button>
|
<button onclick={() => handleChange(+step)}>+</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -120,7 +123,7 @@
|
|||||||
border-radius: var(--border-radius, 2px);
|
border-radius: var(--border-radius, 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"] {
|
input[type='number'] {
|
||||||
-webkit-appearance: textfield;
|
-webkit-appearance: textfield;
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
appearance: textfield;
|
appearance: textfield;
|
||||||
@ -132,8 +135,8 @@
|
|||||||
width: 72%;
|
width: 72%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
input[type='number']::-webkit-inner-spin-button,
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
input[type='number']::-webkit-outer-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +160,7 @@
|
|||||||
margin-inline: 6px;
|
margin-inline: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div input[type="number"] {
|
div input[type='number'] {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
padding: var(--padding, 6px);
|
padding: var(--padding, 6px);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let options: string[] = [];
|
let {
|
||||||
export let value: number = 0;
|
id,
|
||||||
export let id = "";
|
value = $bindable(),
|
||||||
|
options
|
||||||
|
}: { id: string; value: number; options: string[] } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<select {id} bind:value>
|
<select {id} bind:value>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Float from "./Float.svelte";
|
import Float from './Float.svelte';
|
||||||
|
|
||||||
export let value = [0, 0, 0];
|
let { value = $bindable(), id }: { value: number[]; id: string } = $props();
|
||||||
export let id = "";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -21,8 +20,7 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
border: solid thin var(--outline);
|
border: solid thin var(--outline);
|
||||||
border-top: solid thin color-mix(in srgb, var(--outline) 50%, transparent);
|
border-top: solid thin color-mix(in srgb, var(--outline) 50%, transparent);
|
||||||
border-bottom: solid thin
|
border-bottom: solid thin color-mix(in srgb, var(--outline) 50%, transparent);
|
||||||
color-mix(in srgb, var(--outline) 50%, transparent);
|
|
||||||
}
|
}
|
||||||
div > :global(.component-wrapper:nth-child(3)) {
|
div > :global(.component-wrapper:nth-child(3)) {
|
||||||
border-top: none !important;
|
border-top: none !important;
|
||||||
|
@ -1,7 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "$lib/app.css";
|
import '$lib/app.css';
|
||||||
import Slider from "$lib/elements/Float.svelte";
|
import Float from '$lib/elements/Float.svelte';
|
||||||
|
import Integer from '$lib/elements/Integer.svelte';
|
||||||
|
import Vec3 from '$lib/elements/Vec3.svelte';
|
||||||
|
|
||||||
|
let intValue = $state(0);
|
||||||
|
let floatValue = $state(0.2);
|
||||||
|
let vecValue = $state([0.2, 0.3, 0.4]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Slider id="asd" />
|
<main>
|
||||||
|
<section>
|
||||||
|
<h3>Integer {intValue}</h3>
|
||||||
|
<Integer bind:value={intValue} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Float {floatValue}</h3>
|
||||||
|
<Float bind:value={floatValue} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Vec3 {JSON.stringify(vecValue)}</h3>
|
||||||
|
<Vec3 bind:value={vecValue} />
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
main {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -102,3 +102,47 @@ function decode_recursive(dense: number[] | Int32Array, index = 0) {
|
|||||||
export function decodeNestedArray(dense: number[] | Int32Array) {
|
export function decodeNestedArray(dense: number[] | Int32Array) {
|
||||||
return decode_recursive(dense, 0)[0];
|
return decode_recursive(dense, 0)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function splitNestedArray(input: Int32Array) {
|
||||||
|
let index = 0;
|
||||||
|
const length = input.length;
|
||||||
|
let res: Int32Array[] = [];
|
||||||
|
|
||||||
|
let nextBracketIndex = 0;
|
||||||
|
let argStartIndex = 0;
|
||||||
|
let depth = -1;
|
||||||
|
|
||||||
|
while (index < length) {
|
||||||
|
const value = input[index];
|
||||||
|
|
||||||
|
if (index === nextBracketIndex) {
|
||||||
|
nextBracketIndex = index + input[index + 1] + 1;
|
||||||
|
if (value === 0) {
|
||||||
|
depth++;
|
||||||
|
} else {
|
||||||
|
depth--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth === 1 && value === 0) {
|
||||||
|
// if opening bracket
|
||||||
|
argStartIndex = index + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth === 0 && value === 1) {
|
||||||
|
// if closing bracket
|
||||||
|
res.push(input.slice(argStartIndex, index));
|
||||||
|
argStartIndex = index + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = nextBracketIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should not be here
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
@ -11,12 +11,52 @@ pub struct PathDataMut<'a> {
|
|||||||
pub points: &'a mut [f32],
|
pub points: &'a mut [f32],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PathDataMut<'_> {
|
||||||
|
pub fn get_length(&self) -> f32 {
|
||||||
|
let mut l = 0.0;
|
||||||
|
for i in 0..(self.length - 1) {
|
||||||
|
let a = vec3(
|
||||||
|
self.points[i * 4],
|
||||||
|
self.points[i * 4 + 1],
|
||||||
|
self.points[i * 4 + 2],
|
||||||
|
);
|
||||||
|
let b = vec3(
|
||||||
|
self.points[(i + 1) * 4],
|
||||||
|
self.points[(i + 1) * 4 + 1],
|
||||||
|
self.points[(i + 1) * 4 + 2],
|
||||||
|
);
|
||||||
|
l += (b - a).length();
|
||||||
|
}
|
||||||
|
l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PathData<'a> {
|
pub struct PathData<'a> {
|
||||||
pub depth: i32,
|
pub depth: i32,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
pub points: &'a [f32],
|
pub points: &'a [f32],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PathData<'_> {
|
||||||
|
pub fn get_length(&self) -> f32 {
|
||||||
|
let mut l = 0.0;
|
||||||
|
for i in 0..(self.length - 1) {
|
||||||
|
let a = vec3(
|
||||||
|
self.points[i * 4],
|
||||||
|
self.points[i * 4 + 1],
|
||||||
|
self.points[i * 4 + 2],
|
||||||
|
);
|
||||||
|
let b = vec3(
|
||||||
|
self.points[(i + 1) * 4],
|
||||||
|
self.points[(i + 1) * 4 + 1],
|
||||||
|
self.points[(i + 1) * 4 + 2],
|
||||||
|
);
|
||||||
|
l += (b - a).length();
|
||||||
|
}
|
||||||
|
l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_multiple_paths(amount: usize, point_amount: usize, depth: i32) -> Vec<i32> {
|
pub fn create_multiple_paths(amount: usize, point_amount: usize, depth: i32) -> Vec<i32> {
|
||||||
let output_size = amount * (point_amount * 4 + PATH_HEADER_SIZE + 4) + 4;
|
let output_size = amount * (point_amount * 4 + PATH_HEADER_SIZE + 4) + 4;
|
||||||
|
|
||||||
|
@ -162,7 +162,12 @@ pub fn evaluate_vec3(input_args: &[i32]) -> Vec<f32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate_float(arg: &[i32]) -> f32 {
|
pub fn evaluate_float(arg: &[i32]) -> f32 {
|
||||||
decode_float(evaluate_int(arg))
|
let res = decode_float(evaluate_int(arg));
|
||||||
|
if res.is_nan() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate_int(input_args: &[i32]) -> i32 {
|
pub fn evaluate_int(input_args: &[i32]) -> i32 {
|
||||||
|
@ -34,6 +34,9 @@ importers:
|
|||||||
comlink:
|
comlink:
|
||||||
specifier: ^4.4.1
|
specifier: ^4.4.1
|
||||||
version: 4.4.1
|
version: 4.4.1
|
||||||
|
file-saver:
|
||||||
|
specifier: ^2.0.5
|
||||||
|
version: 2.0.5
|
||||||
jsondiffpatch:
|
jsondiffpatch:
|
||||||
specifier: ^0.6.0
|
specifier: ^0.6.0
|
||||||
version: 0.6.0
|
version: 0.6.0
|
||||||
@ -56,6 +59,9 @@ importers:
|
|||||||
'@tsconfig/svelte':
|
'@tsconfig/svelte':
|
||||||
specifier: ^5.0.4
|
specifier: ^5.0.4
|
||||||
version: 5.0.4
|
version: 5.0.4
|
||||||
|
'@types/file-saver':
|
||||||
|
specifier: ^2.0.7
|
||||||
|
version: 2.0.7
|
||||||
'@unocss/preset-icons':
|
'@unocss/preset-icons':
|
||||||
specifier: ^0.59.4
|
specifier: ^0.59.4
|
||||||
version: 0.59.4
|
version: 0.59.4
|
||||||
@ -747,6 +753,9 @@ packages:
|
|||||||
'@types/estree@1.0.5':
|
'@types/estree@1.0.5':
|
||||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||||
|
|
||||||
|
'@types/file-saver@2.0.7':
|
||||||
|
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@ -1314,6 +1323,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
|
|
||||||
|
file-saver@2.0.5:
|
||||||
|
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
|
||||||
|
|
||||||
fill-range@7.0.1:
|
fill-range@7.0.1:
|
||||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -3006,6 +3018,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.5': {}
|
'@types/estree@1.0.5': {}
|
||||||
|
|
||||||
|
'@types/file-saver@2.0.7': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/node@20.12.7':
|
'@types/node@20.12.7':
|
||||||
@ -3735,6 +3749,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 3.2.0
|
flat-cache: 3.2.0
|
||||||
|
|
||||||
|
file-saver@2.0.5: {}
|
||||||
|
|
||||||
fill-range@7.0.1:
|
fill-range@7.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
|
Loading…
Reference in New Issue
Block a user