feat: update some nodes a bit
📊 Benchmark the Runtime / benchmark (push) Failing after 1m12s
🚀 Lint & Test & Deploy / quality (push) Failing after 58s
🚀 Lint & Test & Deploy / test-unit (push) Successful in 33s
🚀 Lint & Test & Deploy / test-e2e (push) Successful in 1m37s
🚀 Lint & Test & Deploy / deploy (push) Has been skipped
📊 Benchmark the Runtime / benchmark (push) Failing after 1m12s
🚀 Lint & Test & Deploy / quality (push) Failing after 58s
🚀 Lint & Test & Deploy / test-unit (push) Successful in 33s
🚀 Lint & Test & Deploy / test-e2e (push) Successful in 1m37s
🚀 Lint & Test & Deploy / deploy (push) Has been skipped
This commit is contained in:
Generated
+1
@@ -66,6 +66,7 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
name = "leaf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"nodarium_macros",
|
||||
"nodarium_utils",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { toast } from '@nodarium/ui';
|
||||
import { GraphSchema, type NodeId } from '@nodarium/types';
|
||||
import { toast } from '@nodarium/ui';
|
||||
import type { GraphManager } from '../graph-manager.svelte';
|
||||
import type { GraphState } from '../graph-state.svelte';
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ function writePath(scene: Group, data: Int32Array): Vector3[] {
|
||||
|
||||
// Instanced spheres at points
|
||||
if (positions.length > 0) {
|
||||
const sphereGeometry = new SphereGeometry(0.05, 8, 8); // keep low-poly
|
||||
const sphereGeometry = new SphereGeometry(0.02, 8, 8); // keep low-poly
|
||||
const sphereMaterial = new MeshBasicMaterial({
|
||||
color: 0xff0000,
|
||||
depthTest: false
|
||||
|
||||
@@ -134,6 +134,14 @@ function getValue(input: NodeInput, value?: unknown) {
|
||||
return encodeFloat(value as number);
|
||||
}
|
||||
|
||||
if (input.type === 'select' && typeof value !== 'number') {
|
||||
const index = input.options?.indexOf(value as string);
|
||||
if (index === undefined || index < 0) {
|
||||
throw new Error(`Unknown value ${value} for select input ${input.label}`);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (input.type === 'vec3' || input.type === 'shape') {
|
||||
return [
|
||||
@@ -159,6 +167,8 @@ function getValue(input: NodeInput, value?: unknown) {
|
||||
return value;
|
||||
}
|
||||
|
||||
console.log({ input, value });
|
||||
|
||||
throw new Error(`Unknown input type ${input.type}`);
|
||||
}
|
||||
|
||||
@@ -312,7 +322,6 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
a = performance.now();
|
||||
|
||||
// Collect the inputs for the node
|
||||
|
||||
@@ -12,8 +12,8 @@ export class WorkerRuntimeExecutor implements RuntimeExecutor {
|
||||
getPerformanceData() {
|
||||
return this.worker.getPerformanceData();
|
||||
}
|
||||
getDebugData() {
|
||||
return this.worker.getDebugData();
|
||||
async getDebugData() {
|
||||
return await this.worker.getDebugData();
|
||||
}
|
||||
set useRuntimeCache(useCache: boolean) {
|
||||
this.worker.setUseRuntimeCache(useCache);
|
||||
|
||||
@@ -299,7 +299,11 @@
|
||||
bind:showHelp={appSettings.value.nodeInterface.showHelp}
|
||||
bind:settings={graphSettings}
|
||||
bind:settingTypes={graphSettingTypes}
|
||||
onsave={async (g) => { pendingSave = true; await pm.saveGraph(g); pendingSave = false; }}
|
||||
onsave={async (g) => {
|
||||
pendingSave = true;
|
||||
await pm.saveGraph(g);
|
||||
pendingSave = false;
|
||||
}}
|
||||
onresult={(result) => handleUpdate(result as Graph)}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
@@ -13,19 +13,28 @@
|
||||
"max": 1,
|
||||
"value": 1
|
||||
},
|
||||
"curviness": {
|
||||
"type": "float",
|
||||
"hidden": true,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"value": 0.5
|
||||
},
|
||||
"depth": {
|
||||
"type": "integer",
|
||||
"min": 1,
|
||||
"max": 10,
|
||||
"hidden": true,
|
||||
"value": 1
|
||||
},
|
||||
"elasticity": {
|
||||
"type": "float",
|
||||
"description": "How rigid the stem is. 0 = rope (uniform droop), 1 = stiff rod (only the tip bends).",
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.05,
|
||||
"value": 0.3
|
||||
},
|
||||
"mode": {
|
||||
"type": "select",
|
||||
"internal": true,
|
||||
"label": "Mode",
|
||||
"options": ["closed-form", "chain"],
|
||||
"hidden": true,
|
||||
"description": "closed-form lerps each segment toward gravity; chain is a forward-kinematic cantilever where each segment rotates by an angle that grows along the stem."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
let args = split_args(input);
|
||||
|
||||
let plants = split_args(args[0]);
|
||||
let depth = evaluate_int(args[3]);
|
||||
let depth = evaluate_int(args[2]);
|
||||
let elasticity = evaluate_float(args[3]).clamp(0.0, 1.0);
|
||||
let mode = evaluate_int(args[4]); // 0 = closed-form, 1 = verlet
|
||||
// 0 → sqrt (rope), 1 → ~4.5 (only the tip droops)
|
||||
let bend_exponent = 0.5 + elasticity * 4.0;
|
||||
|
||||
let mut max_depth = 0;
|
||||
for path_data in plants.iter() {
|
||||
@@ -42,6 +46,77 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
let mut output_data = path_data.clone();
|
||||
let output = wrap_path_mut(&mut output_data);
|
||||
|
||||
if mode == 1 {
|
||||
// Forward-kinematic cantilever chain. Each segment rotates around
|
||||
// an axis perpendicular to (rest_dir, gravity) by an angle that
|
||||
// grows with alpha along the stem. Positions are built from the
|
||||
// anchored base outward, so segment lengths are preserved by
|
||||
// construction (no iteration, no rescaling, no oscillation).
|
||||
|
||||
let raw_strength = evaluate_float(args[1]);
|
||||
let gravity_dir = Vec3::new(0.0, -1.0, 0.0);
|
||||
|
||||
// Tip bend angle in radians. PI/2 = horizontal tip at strength=1.
|
||||
let max_angle = raw_strength * std::f32::consts::FRAC_PI_2;
|
||||
|
||||
let original: Vec<Vec3> = (0..path.length)
|
||||
.map(|i| {
|
||||
let s = i * 4;
|
||||
Vec3::from_slice(&path.points[s..s + 3])
|
||||
})
|
||||
.collect();
|
||||
|
||||
let seg_lens: Vec<f32> = (0..path.length - 1)
|
||||
.map(|i| (original[i + 1] - original[i]).length())
|
||||
.collect();
|
||||
let rest_dirs: Vec<Vec3> = (0..path.length - 1)
|
||||
.map(|i| {
|
||||
let d = original[i + 1] - original[i];
|
||||
let l = d.length();
|
||||
if l > 0.0001 { d / l } else { Vec3::Y }
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut cur = vec![Vec3::ZERO; path.length];
|
||||
cur[0] = original[0];
|
||||
|
||||
for i in 1..path.length {
|
||||
let seg_idx = i - 1;
|
||||
let alpha = if path.length > 2 {
|
||||
seg_idx as f32 / (path.length - 2) as f32
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let bend_angle = max_angle * alpha.powf(bend_exponent);
|
||||
|
||||
let rest_dir = rest_dirs[seg_idx];
|
||||
let mut bend_axis = rest_dir.cross(gravity_dir);
|
||||
let axis_len = bend_axis.length();
|
||||
bend_axis = if axis_len > 0.0001 {
|
||||
bend_axis / axis_len
|
||||
} else {
|
||||
// rest_dir parallel to gravity — pick an arbitrary
|
||||
// perpendicular axis to break symmetry.
|
||||
Vec3::X
|
||||
};
|
||||
|
||||
// Rodrigues' rotation formula
|
||||
let (sin_a, cos_a) = bend_angle.sin_cos();
|
||||
let bent_dir = rest_dir * cos_a
|
||||
+ bend_axis.cross(rest_dir) * sin_a
|
||||
+ bend_axis * bend_axis.dot(rest_dir) * (1.0 - cos_a);
|
||||
|
||||
cur[i] = cur[i - 1] + bent_dir * seg_lens[seg_idx];
|
||||
}
|
||||
|
||||
for i in 0..path.length {
|
||||
let s = i * 4;
|
||||
output.points[s] = cur[i].x;
|
||||
output.points[s + 1] = cur[i].y;
|
||||
output.points[s + 2] = cur[i].z;
|
||||
}
|
||||
} else {
|
||||
// Closed-form: per-segment lerp toward a downward vector
|
||||
let mut offset_vec = Vec3::ZERO;
|
||||
|
||||
for i in 0..path.length - 1 {
|
||||
@@ -49,15 +124,16 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
let start_index = i * 4;
|
||||
|
||||
let start_point = Vec3::from_slice(&path.points[start_index..start_index + 3]);
|
||||
let end_point = Vec3::from_slice(&path.points[start_index + 4..start_index + 7]);
|
||||
let end_point =
|
||||
Vec3::from_slice(&path.points[start_index + 4..start_index + 7]);
|
||||
|
||||
let direction = end_point - start_point;
|
||||
|
||||
let length = direction.length();
|
||||
|
||||
let curviness = evaluate_float(args[2]);
|
||||
let strength =
|
||||
evaluate_float(args[1]) / curviness.max(0.0001) * evaluate_float(args[1]);
|
||||
let curviness = elasticity.max(0.0001);
|
||||
let strength_arg = evaluate_float(args[1]) * 10.0;
|
||||
let strength = strength_arg / curviness * strength_arg;
|
||||
|
||||
log!(
|
||||
"length: {}, curviness: {}, strength: {}",
|
||||
@@ -68,7 +144,8 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
let down_point = Vec3::new(0.0, -length * strength, 0.0);
|
||||
|
||||
let mut mid_point = lerp_vec3(direction, down_point, curviness * alpha.sqrt());
|
||||
let mut mid_point =
|
||||
lerp_vec3(direction, down_point, curviness * alpha.powf(bend_exponent));
|
||||
|
||||
if mid_point[0] == 0.0 && mid_point[2] == 0.0 {
|
||||
mid_point[0] += 0.0001;
|
||||
@@ -87,6 +164,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
offset_vec += final_end_point - end_point;
|
||||
}
|
||||
}
|
||||
output_data
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -8,5 +8,6 @@ edition = "2018"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
glam = "0.30.10"
|
||||
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
|
||||
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
|
||||
|
||||
@@ -19,6 +19,33 @@
|
||||
"max": 64,
|
||||
"value": 1,
|
||||
"hidden": true
|
||||
},
|
||||
"yCurve": {
|
||||
"type": "float",
|
||||
"description": "Curl the leaf upward along its length (radians). 0 = flat, ~1.57 = 90° tip curl.",
|
||||
"min": -3.14,
|
||||
"max": 3.14,
|
||||
"step": 0.05,
|
||||
"value": 0,
|
||||
"hidden": true
|
||||
},
|
||||
"yTwist": {
|
||||
"type": "float",
|
||||
"description": "Twist around the leaf's spine. Combined with yCurve, produces a 3D spiral.",
|
||||
"min": -6.28,
|
||||
"max": 6.28,
|
||||
"step": 0.05,
|
||||
"value": 0,
|
||||
"hidden": true
|
||||
},
|
||||
"xCurve": {
|
||||
"type": "float",
|
||||
"description": "Curl each cross-section into an arc, mirrored around the midrib. 0 = flat, ~1.57 = U-shape.",
|
||||
"min": -3.14,
|
||||
"max": 3.14,
|
||||
"step": 0.05,
|
||||
"value": 0,
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::convert::TryInto;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use glam::Vec3;
|
||||
use nodarium_macros::nodarium_definition_file;
|
||||
use nodarium_macros::nodarium_execute;
|
||||
use nodarium_utils::encode_float;
|
||||
@@ -42,6 +43,9 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
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 y_curve = evaluate_float(args[3]);
|
||||
let y_twist = evaluate_float(args[4]);
|
||||
let x_curve = evaluate_float(args[5]);
|
||||
let path_length = (input_path.len() - 4) / 2;
|
||||
|
||||
let slice_count = path_length;
|
||||
@@ -93,27 +97,97 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
|
||||
// Writing Positions
|
||||
let width = 50.0;
|
||||
let leaf_length: f32 = 100.0;
|
||||
let mut positions = vec![[0.0f32; 3]; position_amount];
|
||||
|
||||
// Pre-compute a local frame (center, normal=local-Y, binormal=local-X) for
|
||||
// each slice by walking the FK chain. At each step we bend around the
|
||||
// current binormal (curls the leaf) and twist around the current tangent
|
||||
// (rotates the bend plane → spiral).
|
||||
let segs = (slice_count - 1).max(1) as f32;
|
||||
let bend_per_step = y_curve / segs;
|
||||
let twist_per_step = y_twist / segs;
|
||||
|
||||
let mut centers: Vec<Vec3> = Vec::with_capacity(slice_count);
|
||||
let mut frame_n: Vec<Vec3> = Vec::with_capacity(slice_count);
|
||||
let mut frame_b: Vec<Vec3> = Vec::with_capacity(slice_count);
|
||||
|
||||
let mut tangent = Vec3::new(0.0, 0.0, 1.0);
|
||||
let mut normal = Vec3::new(0.0, 1.0, 0.0);
|
||||
let mut binormal = Vec3::new(1.0, 0.0, 0.0);
|
||||
|
||||
let pz_first = decode_float(input_path[2 + 1]);
|
||||
let mut center = Vec3::new(0.0, 0.0, pz_first - leaf_length);
|
||||
|
||||
for i in 0..slice_count {
|
||||
let ax = i as f32 / (slice_count -1) as f32;
|
||||
centers.push(center);
|
||||
frame_n.push(normal);
|
||||
frame_b.push(binormal);
|
||||
|
||||
if i + 1 < slice_count {
|
||||
let pz_curr = decode_float(input_path[2 + i * 2 + 1]);
|
||||
let pz_next = decode_float(input_path[2 + (i + 1) * 2 + 1]);
|
||||
let seg_len = pz_next - pz_curr;
|
||||
|
||||
center = center + tangent * seg_len;
|
||||
|
||||
// Bend around binormal — tilts tangent toward normal
|
||||
let (sin_b, cos_b) = bend_per_step.sin_cos();
|
||||
let new_t = tangent * cos_b + normal * sin_b;
|
||||
let new_n = -tangent * sin_b + normal * cos_b;
|
||||
tangent = new_t;
|
||||
normal = new_n;
|
||||
|
||||
// Twist around tangent — rotates normal/binormal so the next bend
|
||||
// happens in a rotated plane
|
||||
let (sin_tw, cos_tw) = twist_per_step.sin_cos();
|
||||
let new_n2 = normal * cos_tw + binormal * sin_tw;
|
||||
let new_b = -normal * sin_tw + binormal * cos_tw;
|
||||
normal = new_n2;
|
||||
binormal = new_b;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..slice_count {
|
||||
let ax = i as f32 / segs;
|
||||
let px = decode_float(input_path[2 + i * 2 + 0]);
|
||||
let pz = decode_float(input_path[2 + i * 2 + 1]);
|
||||
let hw = width - px; // half-width at this slice
|
||||
|
||||
let c = centers[i];
|
||||
let n = frame_n[i];
|
||||
let b = frame_b[i];
|
||||
|
||||
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;
|
||||
// Signed cross-section parameter, -1 (left edge) → +1 (right edge)
|
||||
let t = 2.0 * alpha - 1.0;
|
||||
let py_local = calculate_y(alpha - 0.5) * 5.0 * (ax * PI).sin();
|
||||
|
||||
// X-curl: each cross-section traces a circular arc with curvature
|
||||
// x_curve / hw. Because theta = x_curve * t is signed around the
|
||||
// midrib, sin/cos give a mirrored arc (left and right edges curl
|
||||
// the same direction).
|
||||
let theta = x_curve * t;
|
||||
let (sin_t, cos_t) = theta.sin_cos();
|
||||
let (b_arc, n_arc) = if x_curve.abs() < 0.0001 {
|
||||
(t * hw, 0.0)
|
||||
} else {
|
||||
let r = hw / x_curve;
|
||||
(r * sin_t, r * (1.0 - cos_t))
|
||||
};
|
||||
// Cross-section bulge follows the rotated local frame
|
||||
let b_total = b_arc - py_local * sin_t;
|
||||
let n_total = n_arc + py_local * cos_t;
|
||||
|
||||
let world = c + b * b_total + n * n_total;
|
||||
|
||||
let pos_idx = i * width_resolution + j;
|
||||
positions[pos_idx] = [x - width, py, pz_val];
|
||||
positions[pos_idx] = [world.x, world.y, world.z];
|
||||
|
||||
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);
|
||||
out[flat_idx + 0] = encode_float(world.x * size);
|
||||
out[flat_idx + 1] = encode_float(world.y * size);
|
||||
out[flat_idx + 2] = encode_float(world.z * size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
},
|
||||
"strength": {
|
||||
"type": "float",
|
||||
"min": 0.1,
|
||||
"max": 10,
|
||||
"value": 2
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"value": 0.5
|
||||
},
|
||||
"fixBottom": {
|
||||
"type": "float",
|
||||
@@ -56,7 +56,8 @@
|
||||
"preserveLength": {
|
||||
"type": "boolean",
|
||||
"label": "Preserve length",
|
||||
"value": true
|
||||
"value": true,
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"type": "boolean",
|
||||
"internal": true,
|
||||
"hidden": true,
|
||||
"value": true,
|
||||
"value": false,
|
||||
"description": "If multiple objects are connected, should we rotate them as one or spread them?"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user