Skip to content

Support Nested Group and Custom Relationships - #18

Open
Sheerwin02 wants to merge 28 commits into
mainfrom
feature/node_graph
Open

Support Nested Group and Custom Relationships#18
Sheerwin02 wants to merge 28 commits into
mainfrom
feature/node_graph

Conversation

@Sheerwin02

@Sheerwin02 Sheerwin02 commented May 26, 2026

Copy link
Copy Markdown
Contributor

Resolved #17

Replaces the rigid ChildOf operand approach with a custom SdfOperandOf/SdfOperands relationship system, enabling fully nested SDF group hierarchies.

Changes

1. Relationship Model (src/sdf/boolean.rs)

  • SdfOperandOf(Entity): Component on each operand entity pointing to its parent group.
  • SdfOperands(Vec<Entity>): Component on the group entity listing its children (uses Bevy's #[relationship_target] with linked_spawn for automatic cleanup).
  • SdfBooleanOp(BooleanOp) / SdfOrder(usize): Per-operand metadata for evaluation order and boolean operation.
  • SdfExtractedOperand: Render-world copy of operand state for GPU buffer construction.
  • SdfEntityCommandsExt trait: Ergonomic add_to_group(), remove_from_group(), set_boolean_op(), and set_order() methods.

2. Flat Preorder Buffer Layout (src/sdf.rs)

  • Replaces non-deterministic group_id assignment.
  • emit_group(): Recursively writes a group header + its children (primitives and sub-groups) into a flat BufferVec<SdfInput> in DFS preorder.
  • Parent Index Pointer: Each child's parent_index is set to the header's buffer position, giving the GPU a stable tree to walk.
  • update_input_buffers: Handles root-level primitives (no parent), group hierarchy collection, and order sorting.
  • Component Tracking: Proper RemovedComponents tracking implemented for both primitives and operands.

3. Stack-Based Shader Composition (assets/shaders/sdf_raymarch.wgsl)

  • Walks the flat preorder buffer with an explicit stack (max depth 8).
  • Group headers push the current accumulator and open a new scope; leaf primitives evaluate and fold into the current scope.
  • Drain Loop: Pops closed groups by comparing parent_index against stack entries.
  • Fallback Strategy: Falls back to min() on stack overflow instead of silently dropping geometry.
  • Correctly handles sibling groups, nested groups, and mixed primitive/subgroup ordering.

4. Transform Propagation (src/sdf/transform.rs)

  • SdfTransform: Local, user-authored transform with #[require(SdfGlobalTransform)].
  • propagate_transform system: Walks the operand graph iteratively, recomputing world_from_local (including scale via Affine3A) only when local transforms change.
  • Skips entities with SdfOperandOf and starts from root groups (Without<SdfOperandOf>).

5. Builder API & Ergonomics

  • Chained Syntax: SdfGroup::new(entity).union(b).difference(c).intersect(d) allows fluent operand construction.
  • One-Shot Spawning: .translate(Vec3) -> SdfGroupBuilder, .build() -> impl Bundle.
  • Shorthands: SdfTransform::from_xyz(x, y, z) shorthand added.
  • Auto-Insertion: All primitive types (SdfSphere, etc.) and SdfGroup have #[require(SdfTransform)].

6. Performance Optimizations

  • Early Returns: update_primitive_buffers and update_input_buffers early-return when nothing changed.
  • Filtered Extraction: extract_sdf_operands uses Changed<T> filters to avoid redundant extraction.
  • Skipped Render Pass: SDF render pass is skipped entirely when input_count == 0.
  • GPU Data Sanity: GPU writes always include at least a sentinel element to prevent stale data.

@Sheerwin02
Sheerwin02 requested a review from nixonyh as a code owner May 26, 2026 14:19
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors boolean SDFs to use relationship-based operands, assigns deterministic parent-before-child group IDs during extraction, propagates transforms via the operand graph (including scale), emits parent metadata into GPU inputs, and replaces the shader composition with a stack-based parent-aware algorithm.

Changes

Nested Group Support with Operand Relationships

Layer / File(s) Summary
Operand Relationship Types
src/sdf/boolean.rs
Introduces SdfOperandOf, SdfOperands, SdfBooleanOp, SdfOrder, and SdfExtractedOperand; replaces flat SdfOperand and adds SdfEntityCommandsExt for membership management.
Render extraction and input-buffer builder
src/sdf.rs
Adds extract_sdf_operands and rewrites update_input_buffers to build group→children links, run deterministic DFS assigning group_ids parent-before-child, populate SdfBuffers.input_buffer with SdfInput entries (including parent_group_id/parent_op), always write a GPU buffer (with placeholder when empty), and update primitive buffer change-tracking.
Transform propagation via operand graph
src/sdf/transform.rs
Refactors propagate_transform to traverse SdfOperandOf/SdfOperands, recompute roots only on local changes, and build world_from_local including scale via Affine3A::from_scale_rotation_translation.
GPU input layout and constructor changes
src/sdf.rs
Extends SdfInput GPU struct with parent_group_id and parent_op; updates SdfInput::new signature to accept and write these fields.
GPU Shader: Stack-based Parent-aware Composition
assets/shaders/sdf_raymarch.wgsl
Adds MAX_STACK_DEPTH and eval_primitive, and replaces composition() with a stack-based algorithm that accumulates per-level distances and applies parent boolean ops bottom-up using parent_group_id/parent_op.
Nested Boolean Test Scenes and Rotation Updates
src/main.rs
Adds 2-level and 3-level nested difference test scenes and a sibling subgroup test; updates rotate_sdf to only rotate transforms associated with SdfOperands, excluding operand-of entities.

🎯 4 (Complex) | ⏱️ ~45 minutes

sequenceDiagram
  participant ExtractStage
  participant InputBuilder
  participant GPU
  ExtractStage->>InputBuilder: emit SdfExtractedOperand
  InputBuilder->>InputBuilder: deterministic DFS assign ids
  InputBuilder->>GPU: upload SdfInput array with parent metadata
  GPU->>GPU: eval_primitive then drain groups
Loading

🐰 A hierarchy of spheres and cubes,
Now grouped with boolean loops,
DFS through operand groups,
Stack-based composition—
Nested geometry blooms!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Support Nested Group and Custom Relationships' is clear and specific, directly reflecting the main changes around nested groups and relationship-based SDF operand management.
Linked Issues check ✅ Passed The PR implements nested group support with stack-based composition, parent-aware operand relationships, and custom relationship components (#17).
Out of Scope Changes check ✅ Passed All changes are focused on nested group support and custom relationships implementation; no unrelated refactoring or scope drift detected.
Description check ✅ Passed The pull request description comprehensively explains the changeset, covering all major components (relationship model, buffer layout, shader composition, transform propagation, builder API, and optimizations) with specific details about implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sheerwin02 Sheerwin02 changed the title Support Nested Group Support Nested Group and Custom Relationships May 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sdf.rs`:
- Around line 259-274: The early-exit uses q_changed and only watches
Changed<SdfGlobalTransform>, Changed<PrimitiveIndex>, and Added<PrimitiveIndex>,
which misses entity removals and operand changes; update the query `q_changed`
to also include RemovedComponents<PrimitiveIndex> and change detection for
operand data (e.g. Changed<SdfExtractedOperand>) so removals and
operand/boolean-order updates trigger the system; adjust any downstream logic
that assumed only add/change events (functions/blocks that reference q_changed,
SdfGlobalTransform, PrimitiveIndex, and SdfExtractedOperand) to handle removals
appropriately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 641e8b27-7de7-47f3-9cba-a779ef8b60fa

📥 Commits

Reviewing files that changed from the base of the PR and between 27e943f and 3b3c42c.

📒 Files selected for processing (5)
  • assets/shaders/sdf_raymarch.wgsl
  • src/main.rs
  • src/sdf.rs
  • src/sdf/boolean.rs
  • src/sdf/transform.rs

Comment thread src/sdf.rs Outdated

@nixonyh nixonyh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactor from entity-index group IDs to DFS-based IDs is a solid correctness improvement, and the SdfOperandOf/SdfOperands relationship model is cleaner than the old approach. The CodeRabbit finding about missing RemovedComponents was addressed in the latest commit. Three issues to look at before merging:


1. Shader drain loop is incorrect for sibling groups (sdf_raymarch.wgsl, drain section)

The end-of-loop drain pops each stack frame and applies it to stack_acc[stack_top] (the frame immediately below), which is only correct when groups form a linear chain. When a parent has two or more child groups, the second child is merged into the first child's accumulator instead of the parent's.

Counterexample — Group A contains [P1, Group B, Group C], B contains P2, C contains P3. DFS pre-order assigns A=1, B=2, C=3. After the main loop the stack is [A(idx=0), B(idx=1), C(idx=2)]. The drain pops C and writes into stack_acc[stack_top] which is now stack_acc[1] (B's slot), even though C's parent_group_id correctly points to A. B and C end up merged together rather than both being applied to A independently.

The existing tests only exercise linear chains (A→B→C), so this is not caught. A fix requires either storing each frame's group_id alongside it and scanning for the matching parent slot during drain, or maintaining a group_id → stack_index map updated on push.


2. Silent geometry loss when stack depth is exceeded (sdf_raymarch.wgsl, "New group" branch)

if stack_top < i32(MAX_STACK_DEPTH) - 1 {
    stack_top += 1;
    ...
    cur_group_id = input.group_id;
}
// else: the first primitive of the new group (d) is silently discarded

When the limit is hit, d is dropped and cur_group_id is not updated, so every subsequent primitive in that group also triggers this branch and is also dropped. Nesting beyond 8 levels silently loses entire sub-trees with no visual indication. At minimum, fall back to dist = min(dist, d) so geometry is at least preserved even if the boolean operation is approximated.


3. Redundant add_children in assign_sdf_group_children (src/sdf/boolean.rs)

commands.entity(group_entity).add_children(&child_entities);

This inserts Bevy's built-in ChildOf/Children hierarchy on top of the new SdfOperandOf/SdfOperands relationship — looks like a leftover from the previous design. The main unintended side effect is Bevy's hierarchy-aware recursive despawn: despawning a group entity will also despawn all its primitive children. If that is not the intended behaviour, remove the add_children call; SdfOperandOf already maintains the operand list through SdfOperands.


Generated by Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/sdf.rs (4)

330-385: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The packed input order cannot represent sibling-group transitions correctly.

Sorting by (group_id, order) forces all direct operands of a parent group ahead of every descendant group, and it also makes sibling subgroups appear back-to-back. With the current shader stack logic, a sequence like A, subgroup_1, B is evaluated as A, B, subgroup_1, and subgroup_2 gets folded into subgroup_1 instead of their common parent. This needs either explicit close/depth metadata in the packed stream or shader-side draining back to input.parent_group_id before pushing the next group.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdf.rs` around lines 330 - 385, Sorting the packed inputs by (group_id,
order) in sorted_inputs loses sibling/parent transition information causing
incorrect shader stack evaluation; modify the packing so SdfInput includes
explicit close/depth metadata (e.g., add fields like depth or is_group_close) or
emit an explicit "close-to-parent" sentinel between groups instead of relying
solely on (group_id, order). Locate the loop building sorted_inputs (iterating
q_primitives and calling SdfInput::new) and change the SdfInput constructor and
pushed tuple to carry either a depth level or an explicit close flag derived
from group_id_map and operand_of (and when absent default to 0/false), then
adjust the sorting key to preserve the original traversal order (e.g., stable
sort by traversal index) so shader-side stack draining can unambiguously pop to
input.parent_group_id.

414-416: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The unchanged fast path never triggers for empty primitive buffers.

After the first no-primitive rebuild, Lines 423-435 leave only the sentinel element in the buffer. buffer.len() > 1 is then false forever, so absent primitive types still rewrite the same one-element GPU buffer every frame.

Suggested fix
-    if q_changed.is_empty() && removed.is_empty() && buffer.len() > 1
+    if q_changed.is_empty() && removed.is_empty() && buffer.len() > 0
     {
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdf.rs` around lines 414 - 416, The fast-path check currently uses `if
q_changed.is_empty() && removed.is_empty() && buffer.len() > 1 { return; }`,
which skips returning when the GPU buffer only contains the sentinel (len == 1);
change the length check to include the sentinel case (e.g. `buffer.len() > 0` or
`buffer.len() >= 1`) so that when `q_changed` and `removed` are empty and the
buffer has only the sentinel element the function returns early and avoids
rewriting the one-element GPU buffer every frame.

391-395: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Fix stale inputs on the zero-primitive path (GPU data not rewritten)

When update_input_buffers clears buffers.input_buffer and skips write_buffer because it’s empty, the shader can still iterate using the old storage size/data: sdf_raymarch.wgsl uses len = arrayLength(&inputs) and loops over inputs[i]. Since the GPU buffer isn’t updated/resized on the empty path, the raymarch pass may keep using stale SdfInput values after the last primitive is removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdf.rs` around lines 391 - 395, The GPU can see stale inputs when
buffers.input_buffer is cleared on the CPU but write_buffer is skipped; update
the code so the GPU is explicitly informed of zero inputs (either by
writing/updating the GPU buffer to a zero-length/zero-count or by updating a
dedicated input count uniform) rather than skipping write_buffer. In practice
modify the logic around buffers.input_buffer / update_input_buffers so that when
input_buffer.is_empty() you still update the GPU (call the buffer write or
update the SdfInput count uniform used by sdf_raymarch.wgsl) so the shader’s
arrayLength(&inputs) / loop over inputs will see zero elements and not use stale
SdfInput data.

259-275: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rebuild SdfInput when SdfExtractedOperand is removed, and write a cleared/empty input buffer

  • update_input_buffers’ early-return guard only considers Changed<SdfExtractedOperand> and RemovedComponents<PrimitiveIndex>. Operand removals can change the computed group_id / parent_group_id / parent_op for existing primitives, but won’t trigger a rebuild—so stale nesting metadata can persist on the GPU.
  • When there are no primitives, update_input_buffers clears the CPU-side buffers.input_buffer but skips write_buffer if it’s empty, leaving the GPU storage buffer with the previous SdfInput contents (unlike update_primitive_buffers, which always writes a default/sentinel element).
Suggested fix
 fn update_input_buffers(
     q_primitives: Query<(
         &SdfGlobalTransform,
         &PrimitiveType,
         &PrimitiveIndex,
         Option<&SdfExtractedOperand>,
     )>,
     q_group_operands: Query<(&SdfExtractedOperand, &MainEntity)>,
     q_changed: Query<
         (),
         Or<(
             Changed<SdfGlobalTransform>,
             Changed<PrimitiveIndex>,
             Added<PrimitiveIndex>,
             Changed<SdfExtractedOperand>,
         )>,
     >,
     removed_primitives: RemovedComponents<PrimitiveIndex>,
+    removed_operands: RemovedComponents<SdfExtractedOperand>,
     mut buffers: ResMut<SdfBuffers>,
     render_device: Res<RenderDevice>,
     render_queue: Res<RenderQueue>,
 ) {
-    if q_changed.is_empty() && removed_primitives.is_empty() {
+    if q_changed.is_empty()
+        && removed_primitives.is_empty()
+        && removed_operands.is_empty()
+    {
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdf.rs` around lines 259 - 275, The early-return in update_input_buffers
misses operand removals and skips writing an empty SdfInput to the GPU; modify
the change detection to also consider removals of SdfExtractedOperand (or add a
RemovedComponents<SdfExtractedOperand> parameter and include it in the
q_changed/guard) so a rebuild is triggered when operands are removed, and ensure
that when buffers.input_buffer is empty you still call render_queue.write_buffer
/ render_device create_buffer_with_data to upload a default/cleared SdfInput
(matching the sentinel behavior in update_primitive_buffers) so GPU state cannot
remain stale; update references: update_input_buffers, SdfExtractedOperand,
RemovedComponents<PrimitiveIndex>, buffers.input_buffer, write_buffer,
update_primitive_buffers, and SdfInput.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/sdf.rs`:
- Around line 330-385: Sorting the packed inputs by (group_id, order) in
sorted_inputs loses sibling/parent transition information causing incorrect
shader stack evaluation; modify the packing so SdfInput includes explicit
close/depth metadata (e.g., add fields like depth or is_group_close) or emit an
explicit "close-to-parent" sentinel between groups instead of relying solely on
(group_id, order). Locate the loop building sorted_inputs (iterating
q_primitives and calling SdfInput::new) and change the SdfInput constructor and
pushed tuple to carry either a depth level or an explicit close flag derived
from group_id_map and operand_of (and when absent default to 0/false), then
adjust the sorting key to preserve the original traversal order (e.g., stable
sort by traversal index) so shader-side stack draining can unambiguously pop to
input.parent_group_id.
- Around line 414-416: The fast-path check currently uses `if
q_changed.is_empty() && removed.is_empty() && buffer.len() > 1 { return; }`,
which skips returning when the GPU buffer only contains the sentinel (len == 1);
change the length check to include the sentinel case (e.g. `buffer.len() > 0` or
`buffer.len() >= 1`) so that when `q_changed` and `removed` are empty and the
buffer has only the sentinel element the function returns early and avoids
rewriting the one-element GPU buffer every frame.
- Around line 391-395: The GPU can see stale inputs when buffers.input_buffer is
cleared on the CPU but write_buffer is skipped; update the code so the GPU is
explicitly informed of zero inputs (either by writing/updating the GPU buffer to
a zero-length/zero-count or by updating a dedicated input count uniform) rather
than skipping write_buffer. In practice modify the logic around
buffers.input_buffer / update_input_buffers so that when input_buffer.is_empty()
you still update the GPU (call the buffer write or update the SdfInput count
uniform used by sdf_raymarch.wgsl) so the shader’s arrayLength(&inputs) / loop
over inputs will see zero elements and not use stale SdfInput data.
- Around line 259-275: The early-return in update_input_buffers misses operand
removals and skips writing an empty SdfInput to the GPU; modify the change
detection to also consider removals of SdfExtractedOperand (or add a
RemovedComponents<SdfExtractedOperand> parameter and include it in the
q_changed/guard) so a rebuild is triggered when operands are removed, and ensure
that when buffers.input_buffer is empty you still call render_queue.write_buffer
/ render_device create_buffer_with_data to upload a default/cleared SdfInput
(matching the sentinel behavior in update_primitive_buffers) so GPU state cannot
remain stale; update references: update_input_buffers, SdfExtractedOperand,
RemovedComponents<PrimitiveIndex>, buffers.input_buffer, write_buffer,
update_primitive_buffers, and SdfInput.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 84ea9d72-8541-403a-98f6-c13904b1ae8b

📥 Commits

Reviewing files that changed from the base of the PR and between 3b3c42c and 8dd30ae.

📒 Files selected for processing (2)
  • src/sdf.rs
  • src/sdf/boolean.rs

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sdf/boolean.rs (1)

164-165: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear render-world SdfExtractedOperand when removing operand membership from a group.

remove_from_group only removes (SdfOperandOf, SdfBooleanOp, SdfOrder) in the main world. extract_sdf_operands only inserts SdfExtractedOperand in the render world for entities that still match those main-world components (with Changed<...> gating), and there’s no shown render-world removal path for SdfExtractedOperand. Since update_input_buffers relies on RemovedComponents<SdfExtractedOperand> and also builds group/operand maps from Query<(&SdfExtractedOperand, &MainEntity)>, a removed-from-group operand can leave stale parent/op/order data in the render world until despawn/reattachment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdf/boolean.rs` around lines 164 - 165, remove_from_group currently only
removes the main-world markers (SdfOperandOf, SdfBooleanOp, SdfOrder) which
leaves SdfExtractedOperand in the render world and causes stale data for
extract_sdf_operands / update_input_buffers; modify the remove_from_group
implementation to also clear SdfExtractedOperand in the render world for the
same entity (use whatever render-world command API you have to remove
SdfExtractedOperand from that entity) so that
RemovedComponents<SdfExtractedOperand> and subsequent queries reflect the
removal immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/sdf/boolean.rs`:
- Around line 164-165: remove_from_group currently only removes the main-world
markers (SdfOperandOf, SdfBooleanOp, SdfOrder) which leaves SdfExtractedOperand
in the render world and causes stale data for extract_sdf_operands /
update_input_buffers; modify the remove_from_group implementation to also clear
SdfExtractedOperand in the render world for the same entity (use whatever
render-world command API you have to remove SdfExtractedOperand from that
entity) so that RemovedComponents<SdfExtractedOperand> and subsequent queries
reflect the removal immediately.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c40c6db-1f4b-4d7b-989e-19399f7d1ef7

📥 Commits

Reviewing files that changed from the base of the PR and between 6bfe572 and b56ffbb.

📒 Files selected for processing (2)
  • src/sdf.rs
  • src/sdf/boolean.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/sdf.rs

@Sheerwin02
Sheerwin02 requested a review from nixonyh May 31, 2026 10:21
@mrclputra
mrclputra force-pushed the feature/node_graph branch from 90bf8f2 to 0d4ac3b Compare June 2, 2026 02:04
@Sheerwin02
Sheerwin02 requested a review from mrclputra June 3, 2026 13:01

@nixonyh nixonyh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial pass review! Will do a few more passes once these are addressed! Thanks :)

Comment thread assets/shaders/sdf_raymarch.wgsl
}

// Evaluate the raw SDF distance for a single input at world-space `point`.
fn eval_primitive(input: SdfInput, point: vec3f) -> f32 {

@nixonyh nixonyh Jun 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to move this and all the sd functions into a new file. (This can be a follow up though, so feel free to ignore this)

Comment thread src/sdf/boolean.rs
/// The group entity owns references to all its operand children.
#[derive(Component, Default)]
#[relationship_target(relationship = SdfOperandOf, linked_spawn)]
pub struct SdfOperands(Vec<Entity>);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub struct SdfOperands(Vec<Entity>);
pub struct SdfOperands(Entity);

Can this be a one to one relationship, from what I know, boolean operation only involves 2 things (hence the name boolean)

Comment thread src/sdf/boolean.rs
PartialOrd,
Ord,
)]
pub struct SdfOrder(pub usize);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can reduce down to 1 to 1 relationship, this shud be removed.

Comment thread src/main.rs Outdated
Comment on lines +135 to +148
let nested_inner_group = commands
.spawn((
SdfGroup::new(nested_medium_sphere)
.difference(nested_small_sphere),
SdfTransform::default(),
))
.id();

commands.spawn((
SdfGroup::new(nested_big_sphere)
.difference(nested_inner_group),
SdfTransform::default()
.with_translation(Vec3::new(0.0, 1.5, 0.0)),
));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I envision the API to be sth like:

let boolean_op = SdfBoolean::new(big_sphere).difference(medium_sphere).difference(small_sphere);

// Then maybe..
boolean_op.build(); // Or sth like that?

Cuz the current API is quite complex (not user friendly).

@nixonyh
nixonyh force-pushed the feature/node_graph branch from 0d4ac3b to 31eed6b Compare June 9, 2026 02:30

@mrclputra mrclputra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

  • commented from mobile phone

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Nested Group in SDF

3 participants