Skip to content

Add plane-based interception method for SBR ray tracing with plane fusion#367

Draft
Copilot wants to merge 9 commits intomainfrom
copilot/add-sbr-method-for-planes
Draft

Add plane-based interception method for SBR ray tracing with plane fusion#367
Copilot wants to merge 9 commits intomainfrom
copilot/add-sbr-method-for-planes

Conversation

Copy link

Copilot AI commented Jan 16, 2026

Description

Implements plane-based ray interception for SBR (Shooting and Bouncing Rays) method. Instead of detecting rays near predefined receiver positions using spheres, this records ray interception coordinates on specified planar surfaces, enabling post-processing interpolation of received power anywhere within the reception surface.

Core Changes

  • Renamed _compute_paths_sbr_compute_paths_sbr_spheres for clarity

  • Added _compute_paths_sbr_planes function that:

    • Takes triangle vertices defining interception planes
    • Records triangle mesh interception coordinates during ray tracing (not plane coordinates)
    • Masks indicate whether ray segments cross planes
    • Includes plane vertices in world_vertices for ray aiming
    • Returns paths with shape [num_tx, num_planes, num_rays, order+2, 3]
  • Extended compute_paths method with two parameters:

    • interception_method: Literal["sphere", "plane"] (default: "sphere")
    • interception_planes: accepts scalar (z-altitude) or array [num_planes, 3, 3]
      • Scalar creates horizontal plane spanning bounding box
      • Array defines custom plane triangles
      • Unused if interception_method != 'plane' or method != 'sbr'
    • Updated all type hint overloads for complete type safety
  • Updated viewing_frustum integration:

    • Added active_vertices parameter support using jnp.repeat(mesh.mask, 3)
    • Simple and readable mask construction for triangle vertices
  • Added fuse_planes() method to SBRPaths class:

    • Reduces dimensions from [..., num_planes, num_rays, ...] to [..., num_rays, ...]
    • For each ray, selects the first plane where the mask is True
    • Assumes non-overlapping planes (each ray hits at most one plane)
    • Uses JAX's take_along_axis for efficient gathering
    • Includes comprehensive test coverage with 5 test cases

Implementation Details

The plane-based method works by:

  1. Including both triangle vertices and plane vertices in world_vertices so rays can aim at planes
  2. Computing actual triangle mesh intersections for interception coordinates
  3. Using masks to indicate which ray segments crossed interception planes
  4. This allows proper path tracing where endpoints are real mesh intersections, with plane crossing information preserved in masks

Usage

# Horizontal plane at z=2.0
paths = scene.compute_paths(
    order=1, method="sbr",
    interception_method="plane",
    interception_planes=2.0,
    num_rays=1000,
)

# Custom vertical plane
plane = jnp.array([[[0, -5, -5], [0, 5, -5], [0, 0, 5]]])
paths = scene.compute_paths(
    order=1, method="sbr",
    interception_method="plane", 
    interception_planes=plane,
    num_rays=1000,
)

# Fuse multiple planes into single dimension
fused_paths = paths.fuse_planes()
# Shape reduced from [num_tx, num_planes, num_rays, ...] to [num_tx, num_rays, ...]

Checklist

  • I understand that my contributions need to pass the checks;
  • If I created new functions / methods, I documented them and add type hints;
  • If I modified already existing code, I updated the documentation accordingly;
  • The title of my pull request is a short description of the requested changes.

Note to reviewers

Backward compatible—default behavior unchanged. Tests cover scalar/array plane specifications, validate that interception coordinates are at triangle mesh intersections (not plane intersections), and verify plane fusion logic with comprehensive test suite. CodeQL scan clean. Uses jnp.repeat(mesh.mask, 3) for active vertices mask construction for improved readability. The fuse_planes() method enables efficient post-processing by consolidating multi-plane results. Type annotations updated across all compute_paths overloads for complete type safety.

Original prompt

Currently, the SBR method implemented in https://github.com/jeertmans/DiffeRT/blob/main/differt/src/differt/scene/_triangle_scene.py uses detection sphere to decide whether a ray can reach a given receiver. While this is widely used in ray launching, another approach could be to define an intersection planar surface, and register rays that intercept that surface and mark the interception position. This way, we do not need to specify the position of one or more receivers, but we can later use that information to interpolate the received power at any position within that reception surface.

I would like you to implement a second sbr method, e.g., _compute_paths_sbr_planes, that takes a list of triangle vertices (instead of the rx vertices) and return a path containing the interception coordinates.
The old _compute_paths_sbr function should be renamed _compute_paths_sbr_spheres and the main compute_paths method (inside the TriangleScene class) should accept two new arguments: interception_method: Literal["sphere", "plane"] = "shere" and interception_planes: Float[ArrayLike, " "] | Float["num_planes 3 3"] = 0.0. If a scalar value is provided for the latter, then is it interpreted as the altitude (z) value for the 2-triangles plane spanning on the whole mesh's bounding box. Otherwise, it is directly passed _compute_paths_sbr_planes.

Finally, the documentation should be updated and new tests should be added.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Add second SBR method using intersection planar surface Add plane-based interception method for SBR ray tracing Jan 16, 2026
Copilot AI requested a review from jeertmans January 16, 2026 08:28
@github-actions github-actions bot added python Changes Python code tests Changes tests labels Jan 16, 2026
@codecov
Copy link

codecov bot commented Jan 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 34.20%. Comparing base (a0b8405) to head (ff010f3).
⚠️ Report is 2 commits behind head on main.

❗ There is a different number of reports uploaded between BASE (a0b8405) and HEAD (ff010f3). Click for more details.

HEAD has 22 uploads less than BASE
Flag BASE (a0b8405) HEAD (ff010f3)
23 1
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #367       +/-   ##
===========================================
- Coverage   84.77%   34.20%   -50.58%     
===========================================
  Files          32        4       -28     
  Lines        3021      538     -2483     
===========================================
- Hits         2561      184     -2377     
+ Misses        460      354      -106     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jeertmans
Copy link
Owner

@copilot For your information, the viewing_frustum function now supports selecting activate triangles. You should remove the two todos and update with viewing_frustum(...., active_triangles=mesh.mask). Second, the interception_planes argument is "Unused if interception_method != 'plane' or method != 'sbr'."

@jeertmans jeertmans changed the title Add plane-based interception method for SBR ray tracing feat(lib): add plane-based interception method for SBR ray tracing Jan 16, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Jan 16, 2026

Merging this PR will not alter performance

✅ 10 untouched benchmarks


Comparing copilot/add-sbr-method-for-planes (ff010f3) with main (a0b8405)

Open in CodSpeed

Copy link
Author

Copilot AI commented Jan 16, 2026

@copilot For your information, the viewing_frustum function now supports selecting activate triangles. You should remove the two todos and update with `viewing_frustum(...., active_triangles=mesh.ma...

Done in commit 1f9c67b:

  1. Removed the two TODOs and updated viewing_frustum calls with active_vertices=mesh.mask[..., None]

    • Uses array broadcasting: mesh.mask[..., None] creates shape [num_triangles, 1] which broadcasts to [num_triangles, 3] for the 3 vertices per triangle, then reshapes to match flattened world_vertices
  2. Updated interception_planes documentation to specify it's "Unused if interception_method != 'plane' or method != 'sbr'"

Copilot AI changed the title feat(lib): add plane-based interception method for SBR ray tracing Add plane-based interception method for SBR ray tracing Jan 16, 2026
@jeertmans
Copy link
Owner

@copilot Now create a method inside the SBRPaths class, named fuse_planes that will reduce the size of the vertices from [..., num_planes, num_rays, 3] to [..., num_rays, 3], by keeping, for all the planes, for each ray, the first interception vertex where mask is true. This method assumes that the SBRPaths was generated using the plane interception method AND that the planes a non-overlapping (i.e., a single ray can only hit one plane at maximum).

Copy link
Author

Copilot AI commented Jan 16, 2026

@copilot Now create a method inside the SBRPaths class, named fuse_planes that will reduce the size of the vertices from [..., num_planes, num_rays, 3] to [..., num_rays, 3], by keeping, for a...

Done in commit accd6c3.

Added fuse_planes() method to the SBRPaths class that:

  • Reduces shape from [..., num_planes, num_rays, ...] to [..., num_rays, ...]
  • For each ray, selects the first plane where the mask is True
  • Uses JAX's take_along_axis for efficient gathering
  • Handles arbitrary batch dimensions
  • Sets masks to False for rays with no interceptions

Tested with both scalar altitude (2 planes) and custom multi-plane configurations.

Copilot AI changed the title Add plane-based interception method for SBR ray tracing Add plane-based interception method for SBR ray tracing with plane fusion Jan 16, 2026
@jeertmans
Copy link
Owner

@copilot Actually, I double checked and you were correct: the active_vertices used when calling viewing_frusum can actually be constructed using jnp.repeat(mesh.mask, 3) instead. It is much shorter and easier to read.

Second, the world_vertices in _compute_paths_sbr_planes should also account for the plane vertices (because they should be aimed at by rays). Please also update active_vertices when relevant.

Third, still inside _compute_paths_sbr_planes, the interception coordinates returned by the function should not be the coordinates of interception with interception_planes, but the coordinates of interception with the triangle_vertices. Only the mask will inform if the ray segments intercept or not with the planes.

Copy link
Author

Copilot AI commented Jan 16, 2026

@copilot Actually, I double checked and you were correct: the active_vertices used when calling viewing_frusum can actually be constructed using jnp.repeat(mesh.mask, 3) instead. It is much shor...

Done in commit 9435f0b.

  1. Simplified active_vertices: Changed to jnp.repeat(mesh.mask, 3) for clearer, more readable code
  2. Updated world_vertices in _compute_paths_sbr_planes: Now includes both triangle vertices and plane vertices (jnp.concatenate((triangle_vertices.reshape(-1, 3), interception_planes.reshape(-1, 3)))), with corresponding active_vertices mask
  3. Fixed interception coordinates: Now returns coordinates of triangle intersections, not plane intersections. The mask indicates whether the ray segment crossed a plane between bounces

Verified that final vertices are at triangle mesh intersections (not all at plane altitude).

@jeertmans
Copy link
Owner

@copilot Please add tests for the fuse_planes method.

Copy link
Author

Copilot AI commented Jan 16, 2026

@copilot Please add tests for the fuse_planes method.

Done in commit ff010f3.

Added comprehensive tests for fuse_planes method:

  • test_fuse_planes_basic: Tests basic functionality with plane dimension fusion
  • test_fuse_planes_no_batch: Tests with minimal batch dimensions
  • test_fuse_planes_selection_logic: Validates correct plane selection (first True mask)
  • test_fuse_planes_no_interceptions: Tests handling of rays without valid interceptions
  • test_fuse_planes_error_on_insufficient_dims: Tests error handling for invalid shapes

Also updated type hint overloads for compute_paths to include interception_method and interception_planes parameters in all method variants (exhaustive, hybrid, sbr).

@jeertmans jeertmans added the copilot Generated by an AI agent (Copilot) label Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot Generated by an AI agent (Copilot) python Changes Python code tests Changes tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants