Source code for simvx.core.reflection_probe

"""ReflectionProbe3D — Cubemap reflection capture probe for 3D scenes."""


from __future__ import annotations

import logging

from .descriptors import Property, Signal
from .math.types import Vec3
from .nodes_3d.node3d import Node3D

log = logging.getLogger(__name__)


[docs] class ReflectionProbe3D(Node3D): """Cubemap reflection capture probe. Captures a cubemap from a point in the scene and applies it as an environment map to meshes within the probe's influence box. Supports parallax-corrected box projection for accurate indoor reflections. The probe influence volume is an axis-aligned box centred on ``world_position + origin_offset`` with half-extents given by ``size``. Example:: probe = ReflectionProbe3D( size=(5, 3, 5), box_projection=True, interior=True, ) room.add_child(probe) """ # --- Influence volume --- size = Property((10.0, 10.0, 10.0), hint="Half-extents of the probe influence box (Vec3)", group="Volume") origin_offset = Property((0.0, 0.0, 0.0), hint="Capture origin offset from node position (Vec3)", group="Volume") # --- Projection --- box_projection = Property(False, hint="Enable parallax-corrected box projection", group="Volume") # --- Intensity --- intensity = Property(1.0, range=(0.0, 5.0), hint="Reflection intensity multiplier", group="Volume") # --- Capture --- max_distance = Property(0.0, range=(0.0, 16384.0), hint="Max capture distance (0 = infinite)", group="Capture") update_mode = Property("once", enum=["once", "always"], hint="Cubemap update mode", group="Capture") # --- Interior --- interior = Property(False, hint="Indoor probe — disables skybox blending") ambient_mode = Property( "disabled", enum=["disabled", "environment", "constant"], hint="Ambient light contribution mode" ) ambient_colour = Property((0.0, 0.0, 0.0, 1.0), hint="Ambient colour (RGBA, used when ambient_mode='constant')") # --- Culling --- cull_mask = Property(0xFFFFFFFF, range=(0, 0xFFFFFFFF), hint="Cull mask — which render layers are captured") # --- Shadows --- enable_shadows = Property(False, hint="Include shadow-casting in cubemap capture") # --- Signals --- cubemap_updated = Signal() # --- Internal state --- def __init__(self, size=None, origin_offset=None, **kwargs): super().__init__(**kwargs) if size is not None: self.size = tuple(Vec3(size)) if origin_offset is not None: self.origin_offset = tuple(Vec3(origin_offset)) self._update_requested: bool = False self._cubemap_version: int = 0 # --- Public API --- @property def capture_position(self) -> Vec3: """World-space position where the cubemap is captured from.""" offset = Vec3(self.origin_offset) return self.world_position + offset
[docs] def request_update(self) -> None: """Mark this probe for cubemap recapture on the next frame. The rendering backend checks ``_update_requested`` each frame and, after capturing, increments ``_cubemap_version`` and emits ``cubemap_updated``. """ self._update_requested = True
# --- Cull mask helpers ---
[docs] def set_cull_mask_layer(self, index: int, enabled: bool = True) -> None: """Enable or disable a specific cull mask layer (0-31).""" if not 0 <= index < 32: raise ValueError(f"Cull mask layer index must be 0-31, got {index}") if enabled: self.cull_mask = self.cull_mask | (1 << index) else: self.cull_mask = self.cull_mask & ~(1 << index)
[docs] def is_cull_mask_layer_enabled(self, index: int) -> bool: """Check if a specific cull mask layer is enabled (0-31).""" if not 0 <= index < 32: raise ValueError(f"Cull mask layer index must be 0-31, got {index}") return bool(self.cull_mask & (1 << index))
# --- Editor gizmo ---
[docs] def get_gizmo_lines(self) -> list[tuple[Vec3, Vec3]]: """Return wireframe box lines for the probe influence volume. Draws 12 edges of an axis-aligned box centred on ``capture_position`` with half-extents ``size``. """ c = self.capture_position sx, sy, sz = (float(v) for v in Vec3(self.size)) # 8 corners of the AABB corners = [ Vec3(c.x + dx * sx, c.y + dy * sy, c.z + dz * sz) for dx in (-1, 1) for dy in (-1, 1) for dz in (-1, 1) ] # 12 edges — connect corners that differ in exactly one axis lines: list[tuple[Vec3, Vec3]] = [] for i, a in enumerate(corners): for b in corners[i + 1:]: diff = ( abs(float(a.x) - float(b.x)) > 1e-6, abs(float(a.y) - float(b.y)) > 1e-6, abs(float(a.z) - float(b.z)) > 1e-6, ) if sum(diff) == 1: lines.append((a, b)) return lines