diff --git a/.dprint.jsonc b/.dprint.jsonc index 7fedc9c..ab96722 100644 --- a/.dprint.jsonc +++ b/.dprint.jsonc @@ -47,13 +47,13 @@ "**/target", ], "plugins": [ - "https://plugins.dprint.dev/typescript-0.95.13.wasm", + "https://plugins.dprint.dev/typescript-0.95.15.wasm", "https://plugins.dprint.dev/json-0.21.1.wasm", - "https://plugins.dprint.dev/markdown-0.20.0.wasm", + "https://plugins.dprint.dev/markdown-0.21.1.wasm", "https://plugins.dprint.dev/toml-0.7.0.wasm", "https://plugins.dprint.dev/dockerfile-0.3.3.wasm", "https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm", - "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm", + "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.6.0.wasm", "https://plugins.dprint.dev/exec-0.6.0.json@a054130d458f124f9b5c91484833828950723a5af3f8ff2bd1523bd47b83b364", ], } diff --git a/.gitea/scripts/build.sh b/.gitea/scripts/build.sh index f26717c..01542d4 100755 --- a/.gitea/scripts/build.sh +++ b/.gitea/scripts/build.sh @@ -21,6 +21,8 @@ else COMMITS_SINCE_LAST_RELEASE="0" fi +commit_message=$(git log -1 --pretty=%B | tr -d '\n' | sed 's/"/\\"/g') + cat >app/static/git.json <app/static/git.json < { $effect(() => { if (appSettings.value.theme === undefined) { return; } - circleMaterial.color = colors.edge.clone().convertSRGBToLinear(); - lineColor = colors.edge.clone().convertSRGBToLinear(); + circleMaterial.color = colors.connection.clone().convertSRGBToLinear(); + lineColor = colors.connection.clone().convertSRGBToLinear(); }); }); diff --git a/app/src/lib/graph-interface/graph-state.svelte.ts b/app/src/lib/graph-interface/graph-state.svelte.ts index 4b8a001..a989174 100644 --- a/app/src/lib/graph-interface/graph-state.svelte.ts +++ b/app/src/lib/graph-interface/graph-state.svelte.ts @@ -186,15 +186,21 @@ export class GraphState { if (!node?.inputs) { return 5; } - const height = 5 - + 10 - * Object.keys(node.inputs).filter( - (p) => - p !== 'seed' - && node?.inputs - && !(node?.inputs?.[p] !== undefined && 'setting' in node.inputs[p]) - && node.inputs[p].hidden !== true - ).length; + let height = 5; + + for (const key of Object.keys(node.inputs)) { + if (key === 'seed') continue; + if (!node.inputs) continue; + if (node?.inputs?.[key] === undefined) continue; + if ('setting' in node.inputs[key]) continue; + if (node.inputs[key].hidden) continue; + if (node.inputs[key].type === 'shape') { + height += 20; + continue; + } + height += 10; + } + this.nodeHeightCache[nodeTypeId] = height; return height; } diff --git a/app/src/lib/graph-interface/graph/colors.svelte.ts b/app/src/lib/graph-interface/graph/colors.svelte.ts index 6f1f5a9..9f85c6b 100644 --- a/app/src/lib/graph-interface/graph/colors.svelte.ts +++ b/app/src/lib/graph-interface/graph/colors.svelte.ts @@ -9,7 +9,7 @@ const variables = [ 'outline', 'active', 'selected', - 'edge' + 'connection' ] as const; function getColor(variable: (typeof variables)[number]) { diff --git a/app/src/lib/graph-interface/graph/mouse.events.ts b/app/src/lib/graph-interface/graph/mouse.events.ts index ad553a3..e7c32c0 100644 --- a/app/src/lib/graph-interface/graph/mouse.events.ts +++ b/app/src/lib/graph-interface/graph/mouse.events.ts @@ -166,15 +166,14 @@ export class MouseEventManager { if (this.state.mouseDown) return; this.state.edgeEndPosition = null; + const target = event.target as HTMLElement; - if (event.target instanceof HTMLElement) { - if ( - event.target.nodeName !== 'CANVAS' - && !event.target.classList.contains('node') - && !event.target.classList.contains('content') - ) { - return; - } + if ( + target.nodeName !== 'CANVAS' + && !target.classList.contains('node') + && !target.classList.contains('content') + ) { + return; } const mx = event.clientX - this.state.rect.x; diff --git a/app/src/lib/graph-interface/node/NodeParameter.svelte b/app/src/lib/graph-interface/node/NodeParameter.svelte index dc48390..cf37541 100644 --- a/app/src/lib/graph-interface/node/NodeParameter.svelte +++ b/app/src/lib/graph-interface/node/NodeParameter.svelte @@ -18,6 +18,7 @@ const inputType = $derived(node?.state?.type?.inputs?.[id]); const socketId = $derived(`${node.id}-${id}`); + const height = $derived(input.type === 'shape' ? 200 : 100); const graphState = getGraphState(); const graphId = graph?.id; @@ -64,6 +65,7 @@ class="wrapper" data-node-type={node.type} data-node-input={id} + style:height="{height}px" class:possible-socket={graphState?.possibleSocketIds.has(socketId)} > {#key id && graphId} @@ -95,8 +97,6 @@ "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } +nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" } diff --git a/nodes/max/plantarium/shape/src/input.json b/nodes/max/plantarium/shape/src/input.json new file mode 100644 index 0000000..2b27295 --- /dev/null +++ b/nodes/max/plantarium/shape/src/input.json @@ -0,0 +1,27 @@ +{ + "id": "max/plantarium/shape", + "outputs": [ + "shape" + ], + "inputs": { + "shape": { + "type": "shape", + "internal": true, + "value": [ + 47.8, + 100, + 47.8, + 82.8, + 30.9, + 69.1, + 23.2, + 40.7, + 27.1, + 14.5, + 42.5, + 0 + ], + "label": "" + } + } +} diff --git a/nodes/max/plantarium/shape/src/lib.rs b/nodes/max/plantarium/shape/src/lib.rs new file mode 100644 index 0000000..118fd29 --- /dev/null +++ b/nodes/max/plantarium/shape/src/lib.rs @@ -0,0 +1,13 @@ +use nodarium_macros::nodarium_definition_file; +use nodarium_macros::nodarium_execute; +use nodarium_utils::{concat_args, log, split_args}; + +nodarium_definition_file!("src/input.json"); + +#[nodarium_execute] +pub fn execute(input: &[i32]) -> Vec { + let args = split_args(input); + log!("vec3 input: {:?}", input); + log!("vec3 args: {:?}", args); + concat_args(args) +} diff --git a/packages/types/src/inputs.ts b/packages/types/src/inputs.ts index 2dda150..9c841a8 100644 --- a/packages/types/src/inputs.ts +++ b/packages/types/src/inputs.ts @@ -26,7 +26,6 @@ const DefaultOptionsSchema = z.object({ export const NodeInputFloatSchema = z.object({ ...DefaultOptionsSchema.shape, type: z.literal('float'), - element: z.literal('slider').optional(), value: z.number().optional(), min: z.number().optional(), max: z.number().optional(), @@ -36,12 +35,17 @@ export const NodeInputFloatSchema = z.object({ export const NodeInputIntegerSchema = z.object({ ...DefaultOptionsSchema.shape, type: z.literal('integer'), - element: z.literal('slider').optional(), value: z.number().optional(), min: z.number().optional(), max: z.number().optional() }); +export const NodeInputShapeSchema = z.object({ + ...DefaultOptionsSchema.shape, + type: z.literal('shape'), + value: z.array(z.number()).optional() +}); + export const NodeInputBooleanSchema = z.object({ ...DefaultOptionsSchema.shape, type: z.literal('boolean'), @@ -84,6 +88,7 @@ export const NodeInputSchema = z.union([ NodeInputBooleanSchema, NodeInputFloatSchema, NodeInputIntegerSchema, + NodeInputShapeSchema, NodeInputSelectSchema, NodeInputSeedSchema, NodeInputVec3Schema, diff --git a/packages/types/src/lib.rs b/packages/types/src/lib.rs index 5c5262f..c8fed22 100644 --- a/packages/types/src/lib.rs +++ b/packages/types/src/lib.rs @@ -103,6 +103,15 @@ pub struct NodeInputVec3 { pub value: Option>, } +#[derive(Serialize, Deserialize)] +pub struct NodeInputShape { + #[serde(flatten)] + pub default_options: DefaultOptions, + + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option>, +} + #[derive(Serialize, Deserialize)] pub struct NodeInputGeometry { #[serde(flatten)] @@ -125,6 +134,7 @@ pub enum NodeInput { select(NodeInputSelect), seed(NodeInputSeed), vec3(NodeInputVec3), + shape(NodeInputShape), geometry(NodeInputGeometry), path(NodeInputPath), } diff --git a/packages/ui/src/lib/Input.svelte b/packages/ui/src/lib/Input.svelte index 581b585..b16490f 100644 --- a/packages/ui/src/lib/Input.svelte +++ b/packages/ui/src/lib/Input.svelte @@ -1,7 +1,7 @@ + type Props = { + value: number[]; + mirror?: boolean; + }; + + let { value: points = $bindable(), mirror = true }: Props = $props(); + + let mouseDown = $state(); + let draggingIndex = $state(); + let downCirclePosition = $state(); + let svgElement = $state(null!); + let svgRect = $state(null!); + let isMirroredEvent = $state(false); + + const pathD = $derived(calculatePath(points, mirror)); + const groupedPoints = $derived(group(points)); + + function group(arr: T[], size = 2): T[][] { + const result = []; + for (let i = 0; i < arr.length; i += size) { + result.push(arr.slice(i, i + size)); + } + return result; + } + const dist = (a: [number, number], b: [number, number]) => Math.hypot(a[0] - b[0], a[1] - b[1]); + const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), max); + const round = (v: number) => Math.floor(v * 10) / 10; + const getPt = (i: number) => [points[i * 2], points[i * 2 + 1]] as [number, number]; + + $effect(() => { + if (!points.length) { + points = [47.8, 100, 47.8, 82.8, 30.9, 69.1, 23.2, 40.7, 27.1, 14.5, 42.5, 0]; + } + }); + + $effect(() => { + if (mirror) { + const _points: [number, number, number][] = []; + for (let i = 0; i < points.length / 2; i++) { + _points.push([...getPt(i), i]); + } + + const sortedPoints = _points + .sort((a, b) => { + if (a[1] !== b[1]) return b[1] - a[1]; + return a[0] - b[0]; + }); + + const newIndices = new Map(sortedPoints.map((p, i) => [p[2], i])); + + const sorted = sortedPoints + .map(p => [p[0], p[1]]) + .flat(); + + let sortChanged = false; + + for (let i = 0; i < sorted.length; i++) { + if (sorted[i] !== points[i]) { + sortChanged = true; + break; + } + } + + if (sortChanged) { + points = sorted; + draggingIndex = newIndices.get(draggingIndex || 0) || 0; + } + } + }); + + function insertBetween(newPt: [number, number]): number { + const count = points.length / 2; + + if (count < 2) { + points = [...points, ...newPt]; + return count; + } + + let minDist = Infinity; + let insertIdx = 0; + + for (let i = 0; i < count - 1; i++) { + const a = getPt(i); + const b = getPt(i + 1); + const d = dist(newPt, a) + dist(newPt, b) - dist(a, b); + if (d < minDist) { + minDist = d; + insertIdx = i + 1; + } + } + + points.splice(insertIdx * 2, 0, newPt[0], newPt[1]); + return insertIdx; + } + + function calculatePath(pts: number[], mirror = false): string { + if (pts.length === 0) return ''; + + const arr = [...pts]; + + let d = `M ${arr[0]} ${arr[1]}`; + for (let i = 2; i < arr.length; i += 2) { + d += ` L ${arr[i]} ${arr[i + 1]}`; + } + + if (mirror) { + for (let i = arr.length - 2; i >= 0; i -= 2) { + const x = 100 - arr[i]; + d += ` L ${x} ${arr[i + 1]}`; + } + d += ' Z'; + } + + return d; + } + + function handleMouseMove(ev: MouseEvent) { + if (mouseDown === undefined || draggingIndex === undefined || !downCirclePosition) return; + + let vx = (mouseDown[0] - ev.clientX) * (100 / svgRect.width); + let vy = (mouseDown[1] - ev.clientY) * (100 / svgRect.height); + + if (ev.shiftKey) { + vx /= 10; + vy /= 10; + } + + let x = downCirclePosition[0] + ((isMirroredEvent ? 1 : -1) * vx); + let y = downCirclePosition[1] - vy; + + x = clamp(x, 0, mirror ? 50 : 100); + y = clamp(y, 0, 100); + + points[draggingIndex * 2] = round(x); + points[draggingIndex * 2 + 1] = round(y); + } + + function handleMouseDown(ev: MouseEvent) { + ev.preventDefault(); + isMirroredEvent = false; + + svgRect = svgElement.getBoundingClientRect(); + mouseDown = [ev.clientX, ev.clientY]; + + const indexText = (ev.target as SVGCircleElement).dataset.index; + + const x = ((ev.clientX - svgRect.left) / svgRect.width) * 100; + const y = ((ev.clientY - svgRect.top) / svgRect.height) * 100; + isMirroredEvent = x > 50; + + if (indexText !== undefined) { + draggingIndex = parseInt(indexText); + downCirclePosition = getPt(draggingIndex); + } else { + draggingIndex = undefined; + + const pt = [ + round(clamp(x, 0, 100)), + round(clamp(y, 0, 100)) + ] as [ + number, + number + ]; + if (isMirroredEvent) { + pt[0] = 100 - pt[0]; + } + + draggingIndex = insertBetween(pt); + downCirclePosition = pt; + } + } + + function handleMouseUp() { + mouseDown = undefined; + draggingIndex = undefined; + } + + function handleContextMenu(ev: MouseEvent) { + const indexText = (ev.target as HTMLElement).dataset?.index; + if (indexText !== undefined) { + ev.preventDefault(); + ev.stopImmediatePropagation(); + const index = parseInt(indexText); + draggingIndex = undefined; + points.splice(index * 2, 2); + } + } + + + + +
+ + + + + {#if mirror} + {#each groupedPoints as p, i (i)} + {@const x = 100 - p[0]} + {@const y = p[1]} + + + {/each} + {/if} + + {#each groupedPoints as p, i (i)} + + + {/each} + +
+ + diff --git a/packages/ui/src/routes/+page.svelte b/packages/ui/src/routes/+page.svelte index bb69ecd..3ed1faf 100644 --- a/packages/ui/src/routes/+page.svelte +++ b/packages/ui/src/routes/+page.svelte @@ -1,6 +1,14 @@ @@ -90,6 +99,19 @@ +
+ {#snippet header()} + +

{JSON.stringify(points)}

+ {/snippet} +
+ +
+
+

Here is some more information that was previously hidden.

diff --git a/packages/ui/src/routes/Section.svelte b/packages/ui/src/routes/Section.svelte index 360b120..0e039b7 100644 --- a/packages/ui/src/routes/Section.svelte +++ b/packages/ui/src/routes/Section.svelte @@ -1,8 +1,9 @@