Compare commits
14 Commits
46202451ba
...
feat/shape
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bb301153a
|
||
|
|
02eee5f9bf
|
||
|
|
4f48a519a9
|
||
|
|
97199ac20f
|
||
|
|
f36f0cb230
|
||
|
|
ed3d48e07f
|
||
|
|
c610d6c991
|
||
|
8865b9b032
|
|||
|
235ee5d979
|
|||
|
23a48572f3
|
|||
|
e89a46e146
|
|||
|
cefda41fcf
|
|||
|
21d0f0da5a
|
|||
|
|
9271d3a7e4
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -62,6 +62,14 @@ version = "1.0.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"nodarium_macros",
|
||||||
|
"nodarium_utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "math"
|
name = "math"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ uniform vec3 camPos;
|
|||||||
uniform vec2 zoomLimits;
|
uniform vec2 zoomLimits;
|
||||||
uniform vec3 backgroundColor;
|
uniform vec3 backgroundColor;
|
||||||
uniform vec3 lineColor;
|
uniform vec3 lineColor;
|
||||||
|
uniform int gridType; // 0 = grid lines, 1 = dots
|
||||||
|
|
||||||
// Anti-aliased step: threshold in the same units as `value`
|
// Anti-aliased step: threshold in the same units as `value`
|
||||||
float aaStep(float threshold, float value, float deriv) {
|
float aaStep(float threshold, float value, float deriv) {
|
||||||
@@ -78,6 +79,7 @@ void main(void) {
|
|||||||
float ux = (vUv.x - 0.5) * width + cx * cz;
|
float ux = (vUv.x - 0.5) * width + cx * cz;
|
||||||
float uy = (vUv.y - 0.5) * height - cy * cz;
|
float uy = (vUv.y - 0.5) * height - cy * cz;
|
||||||
|
|
||||||
|
if(gridType == 0) {
|
||||||
// extra small grid
|
// extra small grid
|
||||||
float m1 = grid(ux, uy, divisions * 4.0, thickness * 4.0) * 0.9;
|
float m1 = grid(ux, uy, divisions * 4.0, thickness * 4.0) * 0.9;
|
||||||
float m2 = grid(ux, uy, divisions * 16.0, thickness * 16.0) * 0.5;
|
float m2 = grid(ux, uy, divisions * 16.0, thickness * 16.0) * 0.5;
|
||||||
@@ -108,5 +110,20 @@ void main(void) {
|
|||||||
vec3 color = mix(backgroundColor, lineColor, c);
|
vec3 color = mix(backgroundColor, lineColor, c);
|
||||||
|
|
||||||
gl_FragColor = vec4(color, 1.0);
|
gl_FragColor = vec4(color, 1.0);
|
||||||
|
} else {
|
||||||
|
float large = circle_grid(ux, uy, cz * 20.0, 1.0) * 0.4;
|
||||||
|
|
||||||
|
float medium = circle_grid(ux, uy, cz * 10.0, 1.0) * 0.6;
|
||||||
|
|
||||||
|
float small = circle_grid(ux, uy, cz * 2.5, 1.0) * 0.8;
|
||||||
|
|
||||||
|
float c = mix(large, medium, min(nz * 2.0 + 0.05, 1.0));
|
||||||
|
c = mix(c, small, clamp((nz - 0.3) / 0.7, 0.0, 1.0));
|
||||||
|
|
||||||
|
vec3 color = mix(backgroundColor, lineColor, c);
|
||||||
|
|
||||||
|
gl_FragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,12 @@
|
|||||||
import BackgroundVert from './Background.vert';
|
import BackgroundVert from './Background.vert';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
minZoom: number;
|
minZoom?: number;
|
||||||
maxZoom: number;
|
maxZoom?: number;
|
||||||
cameraPosition: [number, number, number];
|
cameraPosition?: [number, number, number];
|
||||||
width: number;
|
width?: number;
|
||||||
height: number;
|
height?: number;
|
||||||
|
type?: 'grid' | 'dots' | 'none';
|
||||||
};
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -18,9 +19,18 @@
|
|||||||
maxZoom = 150,
|
maxZoom = 150,
|
||||||
cameraPosition = [0, 1, 0],
|
cameraPosition = [0, 1, 0],
|
||||||
width = globalThis?.innerWidth || 100,
|
width = globalThis?.innerWidth || 100,
|
||||||
height = globalThis?.innerHeight || 100
|
height = globalThis?.innerHeight || 100,
|
||||||
|
type = 'grid'
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
const typeMap = new Map([
|
||||||
|
['grid', 0],
|
||||||
|
['dots', 1],
|
||||||
|
['none', 2]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const gridType = $derived(typeMap.get(type) || 0);
|
||||||
|
|
||||||
let bw = $derived(width / cameraPosition[2]);
|
let bw = $derived(width / cameraPosition[2]);
|
||||||
let bh = $derived(height / cameraPosition[2]);
|
let bh = $derived(height / cameraPosition[2]);
|
||||||
</script>
|
</script>
|
||||||
@@ -51,6 +61,9 @@
|
|||||||
},
|
},
|
||||||
dimensions: {
|
dimensions: {
|
||||||
value: [100, 100]
|
value: [100, 100]
|
||||||
|
},
|
||||||
|
gridType: {
|
||||||
|
value: 0
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
uniforms.camPos.value={cameraPosition}
|
uniforms.camPos.value={cameraPosition}
|
||||||
@@ -59,6 +72,7 @@
|
|||||||
uniforms.lineColor.value={appSettings.value.theme && colors['outline']}
|
uniforms.lineColor.value={appSettings.value.theme && colors['outline']}
|
||||||
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
uniforms.zoomLimits.value={[minZoom, maxZoom]}
|
||||||
uniforms.dimensions.value={[width, height]}
|
uniforms.dimensions.value={[width, height]}
|
||||||
|
uniforms.gridType.value={gridType}
|
||||||
/>
|
/>
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
</T.Group>
|
</T.Group>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class GraphState {
|
|||||||
addMenuPosition = $state<[number, number] | null>(null);
|
addMenuPosition = $state<[number, number] | null>(null);
|
||||||
|
|
||||||
snapToGrid = $state(false);
|
snapToGrid = $state(false);
|
||||||
showGrid = $state(true);
|
backgroundType = $state<'grid' | 'dots' | 'none'>('grid');
|
||||||
showHelp = $state(false);
|
showHelp = $state(false);
|
||||||
|
|
||||||
cameraDown = [0, 0];
|
cameraDown = [0, 0];
|
||||||
@@ -194,7 +194,11 @@ export class GraphState {
|
|||||||
if (node?.inputs?.[key] === undefined) continue;
|
if (node?.inputs?.[key] === undefined) continue;
|
||||||
if ('setting' in node.inputs[key]) continue;
|
if ('setting' in node.inputs[key]) continue;
|
||||||
if (node.inputs[key].hidden) continue;
|
if (node.inputs[key].hidden) continue;
|
||||||
if (node.inputs[key].type === 'shape') {
|
if (
|
||||||
|
node.inputs[key].type === 'shape'
|
||||||
|
&& node.inputs[key].external !== true
|
||||||
|
&& node.inputs[key].internal !== false
|
||||||
|
) {
|
||||||
height += 20;
|
height += 20;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,8 +132,9 @@
|
|||||||
position={graphState.cameraPosition}
|
position={graphState.cameraPosition}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if graphState.showGrid !== false}
|
{#if graphState.backgroundType !== 'none'}
|
||||||
<Background
|
<Background
|
||||||
|
type={graphState.backgroundType}
|
||||||
cameraPosition={graphState.cameraPosition}
|
cameraPosition={graphState.cameraPosition}
|
||||||
{maxZoom}
|
{maxZoom}
|
||||||
{minZoom}
|
{minZoom}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
settings?: Record<string, unknown>;
|
settings?: Record<string, unknown>;
|
||||||
|
|
||||||
activeNode?: NodeInstance;
|
activeNode?: NodeInstance;
|
||||||
showGrid?: boolean;
|
backgroundType?: 'grid' | 'dots' | 'none';
|
||||||
snapToGrid?: boolean;
|
snapToGrid?: boolean;
|
||||||
showHelp?: boolean;
|
showHelp?: boolean;
|
||||||
settingTypes?: Record<string, unknown>;
|
settingTypes?: Record<string, unknown>;
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
registry,
|
registry,
|
||||||
settings = $bindable(),
|
settings = $bindable(),
|
||||||
activeNode = $bindable(),
|
activeNode = $bindable(),
|
||||||
showGrid = $bindable(true),
|
backgroundType = $bindable('grid'),
|
||||||
snapToGrid = $bindable(true),
|
snapToGrid = $bindable(true),
|
||||||
showHelp = $bindable(false),
|
showHelp = $bindable(false),
|
||||||
settingTypes = $bindable(),
|
settingTypes = $bindable(),
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
const graphState = new GraphState(manager);
|
const graphState = new GraphState(manager);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
graphState.showGrid = showGrid;
|
graphState.backgroundType = backgroundType;
|
||||||
graphState.snapToGrid = snapToGrid;
|
graphState.snapToGrid = snapToGrid;
|
||||||
graphState.showHelp = showHelp;
|
graphState.showHelp = showHelp;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
transition:
|
transition:
|
||||||
d 0.3s ease,
|
d 0.3s ease,
|
||||||
fill 0.3s ease;
|
fill 0.3s ease;
|
||||||
fill: var(--color-outline);
|
fill: var(--color-layer-2);
|
||||||
stroke: var(--stroke);
|
stroke: var(--stroke);
|
||||||
stroke-width: var(--stroke-width);
|
stroke-width: var(--stroke-width);
|
||||||
d: var(--path);
|
d: var(--path);
|
||||||
|
|||||||
@@ -31,11 +31,24 @@
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = $state(getDefaultValue());
|
let value = $state(structuredClone($state.snapshot(getDefaultValue())));
|
||||||
|
|
||||||
|
function diffArray(a: number[], b?: number[] | number) {
|
||||||
|
if (!Array.isArray(b)) return true;
|
||||||
|
if (Array.isArray(a) !== Array.isArray(b)) return true;
|
||||||
|
if (a.length !== b.length) return true;
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (value !== undefined && node?.props?.[id] !== value) {
|
const a = $state.snapshot(value);
|
||||||
node.props = { ...node.props, [id]: value };
|
const b = $state.snapshot(node?.props?.[id]);
|
||||||
|
const isDiff = Array.isArray(a) ? diffArray(a, b) : a !== b;
|
||||||
|
if (value !== undefined && isDiff) {
|
||||||
|
node.props = { ...node.props, [id]: a };
|
||||||
if (graph) {
|
if (graph) {
|
||||||
graph.save();
|
graph.save();
|
||||||
graph.execute();
|
graph.execute();
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
const inputType = $derived(node?.state?.type?.inputs?.[id]);
|
const inputType = $derived(node?.state?.type?.inputs?.[id]);
|
||||||
|
|
||||||
const socketId = $derived(`${node.id}-${id}`);
|
const socketId = $derived(`${node.id}-${id}`);
|
||||||
const height = $derived(input.type === 'shape' ? 200 : 100);
|
const isShape = $derived(input.type === 'shape' && input.external !== true);
|
||||||
|
const height = $derived(isShape ? 200 : 100);
|
||||||
|
|
||||||
const graphState = getGraphState();
|
const graphState = getGraphState();
|
||||||
const graphId = graph?.id;
|
const graphId = graph?.id;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { decodeFloat, splitNestedArray } from '@nodarium/utils';
|
import { decodeFloat, splitNestedArray } from '@nodarium/utils';
|
||||||
import type { PerformanceStore } from '@nodarium/utils';
|
import type { PerformanceStore } from '@nodarium/utils';
|
||||||
import { Canvas } from '@threlte/core';
|
import { Canvas } from '@threlte/core';
|
||||||
import { Vector3 } from 'three';
|
import { DoubleSide, Vector3 } from 'three';
|
||||||
import { type Group, MeshMatcapMaterial, TextureLoader } from 'three';
|
import { type Group, MeshMatcapMaterial, TextureLoader } from 'three';
|
||||||
import { createGeometryPool, createInstancedGeometryPool } from './geometryPool';
|
import { createGeometryPool, createInstancedGeometryPool } from './geometryPool';
|
||||||
import Scene from './Scene.svelte';
|
import Scene from './Scene.svelte';
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
matcap.colorSpace = 'srgb';
|
matcap.colorSpace = 'srgb';
|
||||||
const material = new MeshMatcapMaterial({
|
const material = new MeshMatcapMaterial({
|
||||||
color: 0xffffff,
|
color: 0xffffff,
|
||||||
matcap
|
matcap,
|
||||||
|
side: DoubleSide
|
||||||
});
|
});
|
||||||
|
|
||||||
let sceneComponent = $state<ReturnType<typeof Scene>>();
|
let sceneComponent = $state<ReturnType<typeof Scene>>();
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function getValue(input: NodeInput, value?: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (input.type === 'vec3') {
|
if (input.type === 'vec3' || input.type === 'shape') {
|
||||||
return [
|
return [
|
||||||
0,
|
0,
|
||||||
value.length + 1,
|
value.length + 1,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const themes = [
|
|||||||
'catppuccin',
|
'catppuccin',
|
||||||
'solarized',
|
'solarized',
|
||||||
'high-contrast',
|
'high-contrast',
|
||||||
|
'high-contrast-light',
|
||||||
'nord',
|
'nord',
|
||||||
'dracula'
|
'dracula'
|
||||||
] as const;
|
] as const;
|
||||||
@@ -29,10 +30,11 @@ export const AppSettingTypes = {
|
|||||||
},
|
},
|
||||||
nodeInterface: {
|
nodeInterface: {
|
||||||
title: 'Node Interface',
|
title: 'Node Interface',
|
||||||
showNodeGrid: {
|
backgroundType: {
|
||||||
type: 'boolean',
|
type: 'select',
|
||||||
label: 'Show Grid',
|
label: 'Background',
|
||||||
value: true
|
options: ['grid', 'dots', 'none'],
|
||||||
|
value: 'grid'
|
||||||
},
|
},
|
||||||
snapToGrid: {
|
snapToGrid: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
const match = line.match(regex);
|
const match = line.match(regex);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [, sha, link, description] = match;
|
const [, sha, link, description] = match;
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang="postcss">
|
||||||
@reference "tailwindcss";
|
@reference "tailwindcss";
|
||||||
|
|
||||||
#changelog :global(.commits) {
|
#changelog :global(.commits) {
|
||||||
|
|||||||
20
app/src/routes/+error.svelte
Normal file
20
app/src/routes/+error.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="w-screen h-screen flex flex-col items-center justify-center">
|
||||||
|
<div class="outline-1 outline-outline bg-layer-2">
|
||||||
|
<h1 class="p-8 text-3xl">@nodarium/error</h1>
|
||||||
|
<hr>
|
||||||
|
<pre class="p-8">{JSON.stringify(page.error, null, 2)}</pre>
|
||||||
|
<hr>
|
||||||
|
<div class="flex p-4">
|
||||||
|
<button
|
||||||
|
class="bg-layer-2 outline-1 outline-outline p-3 px-6 rounded-sm cursor-pointer"
|
||||||
|
on:click={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
reload
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
graph={pm.graph}
|
graph={pm.graph}
|
||||||
bind:this={graphInterface}
|
bind:this={graphInterface}
|
||||||
registry={nodeRegistry}
|
registry={nodeRegistry}
|
||||||
showGrid={appSettings.value.nodeInterface.showNodeGrid}
|
backgroundType={appSettings.value.nodeInterface.backgroundType}
|
||||||
snapToGrid={appSettings.value.nodeInterface.snapToGrid}
|
snapToGrid={appSettings.value.nodeInterface.snapToGrid}
|
||||||
bind:activeNode
|
bind:activeNode
|
||||||
bind:showHelp={appSettings.value.nodeInterface.showHelp}
|
bind:showHelp={appSettings.value.nodeInterface.showHelp}
|
||||||
|
|||||||
@@ -28,6 +28,13 @@
|
|||||||
"value": 1,
|
"value": 1,
|
||||||
"hidden": true
|
"hidden": true
|
||||||
},
|
},
|
||||||
|
"rotation": {
|
||||||
|
"type": "float",
|
||||||
|
"min": 0,
|
||||||
|
"max": 1,
|
||||||
|
"value": 0.5,
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
"depth": {
|
"depth": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
use glam::{Mat4, Quat, Vec3};
|
use glam::{Mat4, Quat, Vec3};
|
||||||
use nodarium_macros::nodarium_execute;
|
use nodarium_macros::{nodarium_execute, nodarium_definition_file};
|
||||||
use nodarium_macros::nodarium_definition_file;
|
|
||||||
use nodarium_utils::{
|
use nodarium_utils::{
|
||||||
concat_args, evaluate_float, evaluate_int,
|
concat_args, evaluate_float, evaluate_int,
|
||||||
geometry::{
|
geometry::{create_instance_data, wrap_geometry_data, wrap_instance_data, wrap_path},
|
||||||
create_instance_data, wrap_geometry_data, wrap_instance_data, wrap_path,
|
split_args,
|
||||||
},
|
|
||||||
log, split_args,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
nodarium_definition_file!("src/input.json");
|
nodarium_definition_file!("src/input.json");
|
||||||
@@ -15,13 +12,13 @@ nodarium_definition_file!("src/input.json");
|
|||||||
pub fn execute(input: &[i32]) -> Vec<i32> {
|
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||||
let args = split_args(input);
|
let args = split_args(input);
|
||||||
let mut inputs = split_args(args[0]);
|
let mut inputs = split_args(args[0]);
|
||||||
log!("WASM(instance): inputs: {:?}", inputs);
|
|
||||||
|
|
||||||
let mut geo_data = args[1].to_vec();
|
let mut geo_data = args[1].to_vec();
|
||||||
let geo = wrap_geometry_data(&mut geo_data);
|
let geo = wrap_geometry_data(&mut geo_data);
|
||||||
|
|
||||||
let mut transforms: Vec<Mat4> = Vec::new();
|
let mut transforms: Vec<Mat4> = Vec::new();
|
||||||
|
|
||||||
|
// Find max depth
|
||||||
let mut max_depth = 0;
|
let mut max_depth = 0;
|
||||||
for path_data in inputs.iter() {
|
for path_data in inputs.iter() {
|
||||||
if path_data[2] != 0 {
|
if path_data[2] != 0 {
|
||||||
@@ -30,7 +27,8 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
max_depth = max_depth.max(path_data[3]);
|
max_depth = max_depth.max(path_data[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let depth = evaluate_int(args[5]);
|
let rotation = evaluate_float(args[5]);
|
||||||
|
let depth = evaluate_int(args[6]);
|
||||||
|
|
||||||
for path_data in inputs.iter() {
|
for path_data in inputs.iter() {
|
||||||
if path_data[3] < (max_depth - depth + 1) {
|
if path_data[3] < (max_depth - depth + 1) {
|
||||||
@@ -38,24 +36,34 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let amount = evaluate_int(args[2]);
|
let amount = evaluate_int(args[2]);
|
||||||
|
|
||||||
let lowest_instance = evaluate_float(args[3]);
|
let lowest_instance = evaluate_float(args[3]);
|
||||||
let highest_instance = evaluate_float(args[4]);
|
let highest_instance = evaluate_float(args[4]);
|
||||||
|
|
||||||
let path = wrap_path(path_data);
|
let path = wrap_path(path_data);
|
||||||
|
|
||||||
for i in 0..amount {
|
for i in 0..amount {
|
||||||
let alpha =
|
let alpha = lowest_instance
|
||||||
lowest_instance + (i as f32 / amount as f32) * (highest_instance - lowest_instance);
|
+ (i as f32 / (amount - 1) as f32) * (highest_instance - lowest_instance);
|
||||||
|
|
||||||
let point = path.get_point_at(alpha);
|
let point = path.get_point_at(alpha);
|
||||||
let direction = path.get_direction_at(alpha);
|
let tangent = path.get_direction_at(alpha);
|
||||||
|
let size = point[3] + 0.01;
|
||||||
|
|
||||||
|
let axis_rotation = Quat::from_axis_angle(
|
||||||
|
Vec3::from_slice(&tangent).normalize(),
|
||||||
|
i as f32 * rotation,
|
||||||
|
);
|
||||||
|
|
||||||
|
let path_rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::from_slice(&tangent).normalize());
|
||||||
|
|
||||||
|
let rotation = path_rotation * axis_rotation;
|
||||||
|
|
||||||
let transform = Mat4::from_scale_rotation_translation(
|
let transform = Mat4::from_scale_rotation_translation(
|
||||||
Vec3::new(point[3], point[3], point[3]),
|
Vec3::new(size, size, size),
|
||||||
Quat::from_xyzw(direction[0], direction[1], direction[2], 1.0).normalize(),
|
rotation,
|
||||||
Vec3::from_slice(&point),
|
Vec3::from_slice(&point),
|
||||||
);
|
);
|
||||||
|
|
||||||
transforms.push(transform);
|
transforms.push(transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,11 +75,11 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
);
|
);
|
||||||
let mut instances = wrap_instance_data(&mut instance_data);
|
let mut instances = wrap_instance_data(&mut instance_data);
|
||||||
instances.set_geometry(geo);
|
instances.set_geometry(geo);
|
||||||
(0..transforms.len()).for_each(|i| {
|
|
||||||
instances.set_transformation_matrix(i, &transforms[i].to_cols_array());
|
|
||||||
});
|
|
||||||
|
|
||||||
log!("WASM(instance): geo: {:?}", instance_data);
|
for (i, transform) in transforms.iter().enumerate() {
|
||||||
|
instances.set_transformation_matrix(i, &transform.to_cols_array());
|
||||||
|
}
|
||||||
|
|
||||||
inputs.push(&instance_data);
|
inputs.push(&instance_data);
|
||||||
|
|
||||||
concat_args(inputs)
|
concat_args(inputs)
|
||||||
|
|||||||
6
nodes/max/plantarium/leaf/.gitignore
vendored
Normal file
6
nodes/max/plantarium/leaf/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
pkg/
|
||||||
|
wasm-pack.log
|
||||||
12
nodes/max/plantarium/leaf/Cargo.toml
Normal file
12
nodes/max/plantarium/leaf/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Max Richter <jim-x@web.de>"]
|
||||||
|
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" }
|
||||||
24
nodes/max/plantarium/leaf/src/input.json
Normal file
24
nodes/max/plantarium/leaf/src/input.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"id": "max/plantarium/leaf",
|
||||||
|
"outputs": [
|
||||||
|
"geometry"
|
||||||
|
],
|
||||||
|
"inputs": {
|
||||||
|
"shape": {
|
||||||
|
"type": "shape",
|
||||||
|
"external": true
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "float",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
"xResolution": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The amount of stems to produce",
|
||||||
|
"min": 1,
|
||||||
|
"max": 64,
|
||||||
|
"value": 1,
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
166
nodes/max/plantarium/leaf/src/lib.rs
Normal file
166
nodes/max/plantarium/leaf/src/lib.rs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
use std::convert::TryInto;
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use nodarium_macros::nodarium_definition_file;
|
||||||
|
use nodarium_macros::nodarium_execute;
|
||||||
|
use nodarium_utils::encode_float;
|
||||||
|
use nodarium_utils::evaluate_float;
|
||||||
|
use nodarium_utils::evaluate_int;
|
||||||
|
use nodarium_utils::log;
|
||||||
|
use nodarium_utils::wrap_arg;
|
||||||
|
use nodarium_utils::{split_args, decode_float};
|
||||||
|
|
||||||
|
nodarium_definition_file!("src/input.json");
|
||||||
|
|
||||||
|
fn calculate_y(x: f32) -> f32 {
|
||||||
|
let term1 = (x * PI * 2.0).sin().abs();
|
||||||
|
let term2 = (x * 2.0 * PI + (PI / 2.0)).sin() / 2.0;
|
||||||
|
term1 + term2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper vector math functions
|
||||||
|
fn vec_sub(a: &[f32; 3], b: &[f32; 3]) -> [f32; 3] {
|
||||||
|
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vec_cross(a: &[f32; 3], b: &[f32; 3]) -> [f32; 3] {
|
||||||
|
[
|
||||||
|
a[1] * b[2] - a[2] * b[1],
|
||||||
|
a[2] * b[0] - a[0] * b[2],
|
||||||
|
a[0] * b[1] - a[1] * b[0],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vec_normalize(v: &[f32; 3]) -> [f32; 3] {
|
||||||
|
let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
|
||||||
|
if len == 0.0 { [0.0, 0.0, 0.0] } else { [v[0]/len, v[1]/len, v[2]/len] }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[nodarium_execute]
|
||||||
|
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||||
|
let args = split_args(input);
|
||||||
|
let input_path = split_args(args[0])[0];
|
||||||
|
let size = evaluate_float(args[1]);
|
||||||
|
let width_resolution = evaluate_int(args[2]).max(3) as usize;
|
||||||
|
let path_length = (input_path.len() - 4) / 2;
|
||||||
|
|
||||||
|
let slice_count = path_length;
|
||||||
|
let face_amount = (slice_count - 1) * (width_resolution - 1) * 2;
|
||||||
|
let position_amount = slice_count * width_resolution;
|
||||||
|
|
||||||
|
let out_length =
|
||||||
|
3 // metadata
|
||||||
|
+ face_amount * 3 // indices
|
||||||
|
+ position_amount * 3 // positions
|
||||||
|
+ position_amount * 3; // normals
|
||||||
|
|
||||||
|
let mut out = vec![0 as i32; out_length];
|
||||||
|
|
||||||
|
log!("face_amount={:?} position_amount={:?}", face_amount, position_amount);
|
||||||
|
|
||||||
|
out[0] = 1;
|
||||||
|
out[1] = position_amount.try_into().unwrap();
|
||||||
|
out[2] = face_amount.try_into().unwrap();
|
||||||
|
let mut offset = 3;
|
||||||
|
|
||||||
|
// Writing Indices
|
||||||
|
let mut idx = 0;
|
||||||
|
for i in 0..(slice_count - 1) {
|
||||||
|
let base0 = (i * width_resolution) as i32;
|
||||||
|
let base1 = ((i + 1) * width_resolution) as i32;
|
||||||
|
|
||||||
|
for j in 0..(width_resolution - 1) {
|
||||||
|
let a = base0 + j as i32;
|
||||||
|
let b = base0 + j as i32 + 1;
|
||||||
|
let c = base1 + j as i32;
|
||||||
|
let d = base1 + j as i32 + 1;
|
||||||
|
|
||||||
|
// triangle 1
|
||||||
|
out[offset + idx + 0] = a;
|
||||||
|
out[offset + idx + 1] = b;
|
||||||
|
out[offset + idx + 2] = c;
|
||||||
|
|
||||||
|
// triangle 2
|
||||||
|
out[offset + idx + 3] = b;
|
||||||
|
out[offset + idx + 4] = d;
|
||||||
|
out[offset + idx + 5] = c;
|
||||||
|
|
||||||
|
idx += 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += face_amount * 3;
|
||||||
|
|
||||||
|
// Writing Positions
|
||||||
|
let width = 50.0;
|
||||||
|
let mut positions = vec![[0.0f32; 3]; position_amount];
|
||||||
|
for i in 0..slice_count {
|
||||||
|
let ax = i as f32 / (slice_count -1) as f32;
|
||||||
|
|
||||||
|
let px = decode_float(input_path[2 + i * 2 + 0]);
|
||||||
|
let pz = decode_float(input_path[2 + i * 2 + 1]);
|
||||||
|
|
||||||
|
|
||||||
|
for j in 0..width_resolution {
|
||||||
|
let alpha = j as f32 / (width_resolution - 1) as f32;
|
||||||
|
let x = 2.0 * (-px * (alpha - 0.5) + alpha * width);
|
||||||
|
let py = calculate_y(alpha-0.5)*5.0*(ax*PI).sin();
|
||||||
|
let pz_val = pz - 100.0;
|
||||||
|
|
||||||
|
let pos_idx = i * width_resolution + j;
|
||||||
|
positions[pos_idx] = [x - width, py, pz_val];
|
||||||
|
|
||||||
|
let flat_idx = offset + pos_idx * 3;
|
||||||
|
out[flat_idx + 0] = encode_float((x - width) * size);
|
||||||
|
out[flat_idx + 1] = encode_float(py * size);
|
||||||
|
out[flat_idx + 2] = encode_float(pz_val * size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing Normals
|
||||||
|
offset += position_amount * 3;
|
||||||
|
let mut normals = vec![[0.0f32; 3]; position_amount];
|
||||||
|
|
||||||
|
for i in 0..(slice_count - 1) {
|
||||||
|
for j in 0..(width_resolution - 1) {
|
||||||
|
let a = i * width_resolution + j;
|
||||||
|
let b = i * width_resolution + j + 1;
|
||||||
|
let c = (i + 1) * width_resolution + j;
|
||||||
|
let d = (i + 1) * width_resolution + j + 1;
|
||||||
|
|
||||||
|
// triangle 1: a,b,c
|
||||||
|
let u = vec_sub(&positions[b], &positions[a]);
|
||||||
|
let v = vec_sub(&positions[c], &positions[a]);
|
||||||
|
let n1 = vec_cross(&u, &v);
|
||||||
|
|
||||||
|
// triangle 2: b,d,c
|
||||||
|
let u2 = vec_sub(&positions[d], &positions[b]);
|
||||||
|
let v2 = vec_sub(&positions[c], &positions[b]);
|
||||||
|
let n2 = vec_cross(&u2, &v2);
|
||||||
|
|
||||||
|
for &idx in &[a, b, c] {
|
||||||
|
normals[idx][0] += n1[0];
|
||||||
|
normals[idx][1] += n1[1];
|
||||||
|
normals[idx][2] += n1[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
for &idx in &[b, d, c] {
|
||||||
|
normals[idx][0] += n2[0];
|
||||||
|
normals[idx][1] += n2[1];
|
||||||
|
normals[idx][2] += n2[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize and write to output
|
||||||
|
for i in 0..position_amount {
|
||||||
|
let n = vec_normalize(&normals[i]);
|
||||||
|
let flat_idx = offset + i * 3;
|
||||||
|
out[flat_idx + 0] = encode_float(n[0]);
|
||||||
|
out[flat_idx + 1] = encode_float(n[1]);
|
||||||
|
out[flat_idx + 2] = encode_float(n[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap_arg(&out)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
use nodarium_macros::nodarium_definition_file;
|
use nodarium_macros::nodarium_definition_file;
|
||||||
use nodarium_macros::nodarium_execute;
|
use nodarium_macros::nodarium_execute;
|
||||||
use nodarium_utils::{concat_args, log, split_args};
|
use nodarium_utils::{concat_args, split_args};
|
||||||
|
|
||||||
nodarium_definition_file!("src/input.json");
|
nodarium_definition_file!("src/input.json");
|
||||||
|
|
||||||
#[nodarium_execute]
|
#[nodarium_execute]
|
||||||
pub fn execute(input: &[i32]) -> Vec<i32> {
|
pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||||
let args = split_args(input);
|
concat_args(split_args(input))
|
||||||
log!("vec3 input: {:?}", input);
|
|
||||||
log!("vec3 args: {:?}", args);
|
|
||||||
concat_args(args)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,11 +144,25 @@ html.theme-high-contrast {
|
|||||||
--color-outline: white;
|
--color-outline: white;
|
||||||
--color-layer-0: black;
|
--color-layer-0: black;
|
||||||
--color-layer-1: black;
|
--color-layer-1: black;
|
||||||
--color-layer-2: #ababab;
|
--color-layer-2: black;
|
||||||
--color-layer-3: white;
|
--color-layer-3: white;
|
||||||
|
--color-active: #00ff00;
|
||||||
|
--color-selected: #ff0000;
|
||||||
--color-connection: #fff;
|
--color-connection: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.theme-high-contrast-light {
|
||||||
|
--color-text: black;
|
||||||
|
--color-outline: black;
|
||||||
|
--color-layer-0: white;
|
||||||
|
--color-layer-1: white;
|
||||||
|
--color-layer-2: white;
|
||||||
|
--color-layer-3: black;
|
||||||
|
--color-active: #00ffff;
|
||||||
|
--color-selected: #ff0000;
|
||||||
|
--color-connection: black;
|
||||||
|
}
|
||||||
|
|
||||||
html.theme-nord {
|
html.theme-nord {
|
||||||
--color-text: #d8dee9;
|
--color-text: #d8dee9;
|
||||||
--color-outline: #4c566a;
|
--color-outline: #4c566a;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
class="relative inline-flex h-5.5 w-5.5 cursor-pointer items-center justify-center bg-layer-2 rounded-[5px]"
|
class="relative inline-flex h-5.5 w-5.5 cursor-pointer items-center justify-center bg-layer-2 outline-1 outline-outline rounded-[5px]"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -274,6 +274,11 @@
|
|||||||
stroke-width: 1px;
|
stroke-width: 1px;
|
||||||
stroke: var(--color-layer-3);
|
stroke: var(--color-layer-3);
|
||||||
fill: var(--color-layer-2);
|
fill: var(--color-layer-2);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
svg:hover circle {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
circle.active,
|
circle.active,
|
||||||
circle:hover {
|
circle:hover {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
'solarized',
|
'solarized',
|
||||||
'catppuccin',
|
'catppuccin',
|
||||||
'high-contrast',
|
'high-contrast',
|
||||||
|
'high-contrast-light',
|
||||||
'nord',
|
'nord',
|
||||||
'dracula',
|
'dracula',
|
||||||
'custom'
|
'custom'
|
||||||
|
|||||||
Reference in New Issue
Block a user