Surfaces Module
Surfaces Module
Unified surface interface for ray tracing. All surfaces follow the same protocol regardless of GPU capability.
Surface Types
GPU-Capable (fast signed distance computation): - PlaneSurface: Infinite flat plane (geometry_id=1) - SphereSurface: Spherical surface (geometry_id=2) - GPUGerstnerWaveSurface: Flat-earth Gerstner wave, single wave (geometry_id=3) - GPUCurvedWaveSurface: Curved-earth wave, single wave (geometry_id=4) - GPUMultiCurvedWaveSurface: Curved-earth wave, multi-wave up to 8 (geometry_id=5) - BoundedPlaneSurface: Bounded rectangular plane (geometry_id=6) - AnnularPlaneSurface: Annular (ring-shaped) plane (geometry_id=7)
CPU-Only (complex geometry, ray marching, multiple waves): - GerstnerWaveSurface: Flat-earth ocean waves (multi-wave) - CurvedWaveSurface: Curved-earth ocean waves (multi-wave)
Protocol
All surfaces provide: - role: SurfaceRole (DETECTOR, OPTICAL, ABSORBER) - name: Human-readable identifier - gpu_capable: Whether GPU acceleration is available - geometry_id: Integer ID for GPU kernels (0 for CPU-only) - intersect(): CPU ray intersection (always available) - normal_at(): CPU surface normals (always available) - get_materials(): Return materials for Fresnel calculation
GPU-capable surfaces additionally provide: - signed_distance(): For GPU kernels - get_gpu_parameters(): Parameters for GPU kernels
Examples
>>> from lsurf.surfaces import PlaneSurface, GerstnerWaveSurface, SurfaceRole
>>> from lsurf.materials import AIR_STP, WATER
>>>
>>> # GPU-capable plane
>>> detector = PlaneSurface(
... point=(0, 0, 1000),
... normal=(0, 0, 1),
... role=SurfaceRole.DETECTOR,
... name="detector",
... )
>>> print(detector.gpu_capable) # True
>>>
>>> # CPU-only wave surface
>>> from lsurf.surfaces import GerstnerWaveParams
>>> wave = GerstnerWaveParams(amplitude=1.0, wavelength=50.0)
>>> ocean = GerstnerWaveSurface(
... wave_params=[wave],
... role=SurfaceRole.OPTICAL,
... name="ocean",
... material_front=AIR_STP,
... material_back=WATER,
... )
>>> print(ocean.gpu_capable) # False
- class lsurf.surfaces.Surface(role, name, material_front=None, material_back=None, kernel=None)[source]
Bases:
ABCAbstract base class for all surfaces.
All surfaces provide CPU-based ray intersection and normal computation. GPU-capable surfaces additionally provide signed_distance() and get_gpu_parameters() for accelerated GPU computation.
- Parameters:
role (SurfaceRole) – What happens when a ray hits (DETECTOR, OPTICAL, ABSORBER).
name (str) – Human-readable identifier for the surface.
material_front (MaterialField, optional) – Material on the front side (direction of normal). Required for OPTICAL.
material_back (MaterialField, optional) – Material on the back side. Required for OPTICAL.
- role
Surface role.
- Type:
- material_front
Front-side material.
- Type:
MaterialField or None
- material_back
Back-side material.
- Type:
MaterialField or None
Examples
>>> # Check if surface can use GPU >>> if surface.gpu_capable: ... params = surface.get_gpu_parameters() ... sd = surface.signed_distance(positions) >>> else: ... distances, hits = surface.intersect(origins, directions)
- property geometry_id: int
GPU geometry type ID.
Returns 0 for CPU-only surfaces. GPU-capable surfaces return a positive integer registered in the geometry registry.
- classmethod supported_kernels()[source]
Return list of intersection kernels supported by this surface type.
- Returns:
List of kernel IDs this surface supports.
- Return type:
Examples
>>> from lsurf.surfaces import PlaneSurface >>> PlaneSurface.supported_kernels() [<IntersectionKernelID.PLANE_ANALYTICAL: ...>]
- classmethod default_kernel()[source]
Return the default intersection kernel for this surface type.
- Returns:
The default kernel ID, or None if no GPU kernels are supported.
- Return type:
IntersectionKernelID or None
- property kernel_id: IntersectionKernelID | None
Return the kernel ID configured for this surface instance.
- Returns:
The kernel ID selected for this instance.
- Return type:
IntersectionKernelID or None
- abstractmethod intersect(origins, directions, min_distance=1e-06)[source]
Compute ray-surface intersections (CPU).
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origin positions.
directions (ndarray, shape (N, 3)) – Ray direction unit vectors.
min_distance (float, optional) – Minimum valid intersection distance. Default is 1e-6.
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit).
hit_mask (ndarray, shape (N,), dtype=bool) – True for rays that hit the surface.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- abstractmethod normal_at(positions, incoming_directions=None)[source]
Compute surface normal at given positions.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface.
incoming_directions (ndarray, shape (N, 3), optional) – Incoming ray directions (for orienting normals).
- Returns:
normals – Unit normal vectors.
- Return type:
ndarray, shape (N, 3)
- get_materials()[source]
Return (material_front, material_back) for Fresnel calculation.
- Returns:
(material_front, material_back) for OPTICAL surfaces, None otherwise.
- Return type:
tuple or None
- signed_distance(positions)[source]
Compute signed distance from positions to surface.
Only available for GPU-capable surfaces. Raises NotImplementedError for CPU-only surfaces.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for.
- Returns:
Signed distance (positive on front side, negative on back side).
- Return type:
ndarray, shape (N,)
- Raises:
NotImplementedError – If surface is not GPU-capable.
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
Only available for GPU-capable surfaces. Raises NotImplementedError for CPU-only surfaces.
- Returns:
Geometry-dependent parameters for GPU signed distance calculation.
- Return type:
- Raises:
NotImplementedError – If surface is not GPU-capable.
- class lsurf.surfaces.SurfaceRole(*values)[source]
Bases:
IntEnumWhat happens when a ray hits this surface.
- DETECTOR = 1
- OPTICAL = 2
- ABSORBER = 3
- class lsurf.surfaces.SurfaceTypeInfo(name, capability, id, cls=None)[source]
Bases:
objectInformation about a registered surface type.
- __init__(name, capability, id, cls=None)
- lsurf.surfaces.register_surface_type(name, capability, surface_id=None, cls=None)[source]
Register a new surface type.
Can be used as a function or decorator.
- Parameters:
name (str) – Unique name for the surface type (e.g., “plane”, “sphere”).
capability ("gpu" or "cpu") – Whether this surface supports GPU acceleration.
surface_id (int, optional) – GPU kernel ID. Required for GPU surfaces (must be > 0). CPU surfaces always use id=0.
cls (type, optional) – The surface class. If None, returns a decorator.
- Returns:
If cls is provided, returns SurfaceTypeInfo. If cls is None, returns a decorator that registers the class.
- Return type:
SurfaceTypeInfo or decorator
Examples
>>> # Direct registration >>> register_surface_type("plane", "gpu", 1, PlaneSurface) SurfaceTypeInfo('plane', 'gpu', id=1, cls=PlaneSurface)
>>> # As decorator >>> @register_surface_type("cylinder", "gpu", 3) ... class CylinderSurface(Surface): ... pass
- lsurf.surfaces.list_surface_types(capability=None)[source]
List registered surface types.
- Parameters:
capability ("gpu", "cpu", or None) – Filter by capability. None returns all.
- lsurf.surfaces.GeometryInfo
alias of
SurfaceTypeInfo
- lsurf.surfaces.register_geometry(name, capability, surface_id=None, cls=None)
Register a new surface type.
Can be used as a function or decorator.
- Parameters:
name (str) – Unique name for the surface type (e.g., “plane”, “sphere”).
capability ("gpu" or "cpu") – Whether this surface supports GPU acceleration.
surface_id (int, optional) – GPU kernel ID. Required for GPU surfaces (must be > 0). CPU surfaces always use id=0.
cls (type, optional) – The surface class. If None, returns a decorator.
- Returns:
If cls is provided, returns SurfaceTypeInfo. If cls is None, returns a decorator that registers the class.
- Return type:
SurfaceTypeInfo or decorator
Examples
>>> # Direct registration >>> register_surface_type("plane", "gpu", 1, PlaneSurface) SurfaceTypeInfo('plane', 'gpu', id=1, cls=PlaneSurface)
>>> # As decorator >>> @register_surface_type("cylinder", "gpu", 3) ... class CylinderSurface(Surface): ... pass
- lsurf.surfaces.get_geometry_info(name)
Get full surface type info by name.
- lsurf.surfaces.get_geometry_id(name)
Get the GPU kernel ID for a surface type.
- lsurf.surfaces.list_geometries(capability=None)
List registered surface types.
- Parameters:
capability ("gpu", "cpu", or None) – Filter by capability. None returns all.
- lsurf.surfaces.geometry_exists(name)
Check if a surface type is registered.
- class lsurf.surfaces.PlaneSurface(point, normal, role, name='plane', material_front=None, material_back=None)[source]
Bases:
SurfaceInfinite plane surface with GPU acceleration.
The plane is defined by a point and a normal vector. Signed distance from point p to plane: dot(p - point, normal)
- Parameters:
normal (tuple of float) – Unit normal vector (nx, ny, nz) pointing “outward” (front side).
role (SurfaceRole) – What happens when a ray hits (DETECTOR, OPTICAL, or ABSORBER).
name (str) – Human-readable name.
material_front (MaterialField, optional) – Material on front side (where normal points). Required for OPTICAL.
material_back (MaterialField, optional) – Material on back side. Required for OPTICAL.
Examples
>>> # Detection plane at 35 km altitude >>> detector = PlaneSurface( ... point=(0, 0, 35000), ... normal=(0, 0, 1), ... role=SurfaceRole.DETECTOR, ... name="altitude_detector", ... ) >>> >>> # Glass-air interface >>> interface = PlaneSurface( ... point=(0, 0, 0), ... normal=(0, 0, -1), ... role=SurfaceRole.OPTICAL, ... material_front=glass, ... material_back=air, ... name="glass_exit", ... )
- role: SurfaceRole
- material_front: Any = None
- material_back: Any = None
- classmethod supported_kernels()[source]
Return list of intersection kernels supported by this surface type.
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
- Returns:
(normal_x, normal_y, normal_z, point_x, point_y, point_z)
- Return type:
- get_materials()[source]
Return (material_front, material_back) for Fresnel calculation.
- Returns:
(material_front, material_back) or None if not OPTICAL
- Return type:
tuple or None
- signed_distance(positions)[source]
Compute signed distance from positions to plane.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive on normal side, negative on back side)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06)[source]
Compute ray-plane intersection.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origins
directions (ndarray, shape (N, 3)) – Ray directions (normalized)
min_distance (float) – Minimum valid intersection distance
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit)
hit_mask (ndarray, shape (N,)) – Boolean mask of valid intersections
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at positions.
For a plane, normal is constant everywhere.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface
incoming_directions (ndarray, shape (N, 3), optional) – Ray directions (used to flip normal if needed)
- Returns:
Normal vectors at each position
- Return type:
ndarray, shape (N, 3)
- __init__(point, normal, role, name='plane', material_front=None, material_back=None)
- class lsurf.surfaces.SphereSurface(center, radius, role, name='sphere', material_front=None, material_back=None)[source]
Bases:
SurfaceSphere surface with GPU acceleration.
The sphere is defined by center and radius. Signed distance from point p: |p - center| - radius (positive outside, negative inside)
- Parameters:
radius (float) – Sphere radius (positive for convex, negative for concave).
role (SurfaceRole) – What happens when a ray hits (DETECTOR, OPTICAL, or ABSORBER).
name (str) – Human-readable name.
material_front (MaterialField, optional) – Material outside the sphere. Required for OPTICAL.
material_back (MaterialField, optional) – Material inside the sphere. Required for OPTICAL.
Examples
>>> # Earth's surface as ocean >>> EARTH_RADIUS = 6.371e6 >>> ocean = SphereSurface( ... center=(0, 0, -EARTH_RADIUS), ... radius=EARTH_RADIUS, ... role=SurfaceRole.OPTICAL, ... material_front=atmosphere, ... material_back=seawater, ... name="ocean_surface", ... ) >>> >>> # Spherical detector >>> detector = SphereSurface( ... center=(0, 0, 0), ... radius=1000.0, ... role=SurfaceRole.DETECTOR, ... name="spherical_detector", ... )
- role: SurfaceRole
- material_front: Any = None
- material_back: Any = None
- classmethod supported_kernels()[source]
Return list of intersection kernels supported by this surface type.
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
- Returns:
(center_x, center_y, center_z, radius)
- Return type:
- get_materials()[source]
Return (material_front, material_back) for Fresnel calculation.
- Returns:
(material_front, material_back) or None if not OPTICAL
- Return type:
tuple or None
- signed_distance(positions)[source]
Compute signed distance from positions to sphere surface.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive outside, negative inside)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06)[source]
Compute ray-sphere intersection.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origins
directions (ndarray, shape (N, 3)) – Ray directions (normalized)
min_distance (float) – Minimum valid intersection distance
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit)
hit_mask (ndarray, shape (N,)) – Boolean mask of valid intersections
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at positions.
For a sphere, normal points radially outward from center.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface
incoming_directions (ndarray, shape (N, 3), optional) – Ray directions (used to flip normal if needed)
- Returns:
Normal vectors at each position
- Return type:
ndarray, shape (N, 3)
- __init__(center, radius, role, name='sphere', material_front=None, material_back=None)
- class lsurf.surfaces.RecordingSphereSurface(altitude=33000.0, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, name='recording_sphere')[source]
Bases:
SurfaceRecording sphere surface at a specified altitude above Earth.
A specialized detector surface designed for Earth-scale simulations. The sphere is centered at Earth’s center with radius = earth_radius + altitude.
This is the recommended detector setup for atmospheric ray tracing.
- Parameters:
altitude (float) – Altitude above Earth’s surface in meters (default 33 km).
earth_center (tuple of float) – Center of Earth in simulation coordinates. Default (0, 0, -EARTH_RADIUS) places Earth surface at z=0.
earth_radius (float) – Earth radius in meters. Default is 6.371e6 m.
name (str) – Human-readable name for the detector.
Examples
>>> # Create a recording sphere at 33 km altitude >>> detector = RecordingSphereSurface( ... altitude=33000.0, ... name="satellite_detector", ... ) >>> >>> # Use in geometry builder >>> geometry = ( ... GeometryBuilder() ... .register_medium("atmosphere", atmosphere) ... .set_background("atmosphere") ... .add_detector(detector) ... .build() ... ) >>> >>> # Check GPU capability >>> print(detector.gpu_capable) # True >>> print(detector.geometry_id) # 2 (sphere)
Notes
This surface is GPU-capable and uses the same GPU kernels as SphereSurface. The geometry_id is 2 (sphere), which uses analytical ray-sphere intersection.
The coordinate system places: - Earth’s center at (0, 0, -EARTH_RADIUS) by default - Earth’s surface at z=0 - Recording sphere at z = altitude at the pole
- role: SurfaceRole = 1
- material_front: Any = None
- material_back: Any = None
- classmethod supported_kernels()[source]
Return list of intersection kernels supported by this surface type.
- property center_array: ndarray[tuple[Any, ...], dtype[float64]]
Center of the sphere as numpy array.
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
- Returns:
(center_x, center_y, center_z, radius)
- Return type:
- get_materials()[source]
Return materials for Fresnel calculation.
Returns None since DETECTOR surfaces don’t use Fresnel equations.
- signed_distance(positions)[source]
Compute signed distance from positions to recording sphere surface.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive outside, negative inside)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06)[source]
Compute ray-sphere intersection.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origins
directions (ndarray, shape (N, 3)) – Ray directions (normalized)
min_distance (float) – Minimum valid intersection distance
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit)
hit_mask (ndarray, shape (N,)) – Boolean mask of valid intersections
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at positions.
For a sphere, normal points radially outward from center.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface
incoming_directions (ndarray, shape (N, 3), optional) – Ray directions (used to flip normal if needed)
- Returns:
Normal vectors at each position
- Return type:
ndarray, shape (N, 3)
- compute_angular_coordinates(positions)[source]
Compute angular coordinates for intersection points on the sphere.
Computes spherical coordinates (latitude/longitude) of points on the detection sphere relative to Earth’s center.
- Parameters:
positions (ndarray, shape (N, 3)) – Intersection positions on the recording sphere
- Returns:
Dictionary with: - ‘latitude’: Latitude angle (-pi/2 to pi/2) - ‘longitude’: Longitude angle (-pi to pi) - ‘colatitude’: Colatitude angle (0 to pi)
- Return type:
- __init__(altitude=33000.0, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, name='recording_sphere')
- class lsurf.surfaces.LocalRecordingSphereSurface(radius=33000.0, center=(0, 0, 0), name='local_recording_sphere')[source]
Bases:
SurfaceLocal recording sphere surface centered at an arbitrary position.
A simplified recording sphere for local-scale simulations without Earth curvature considerations.
- Parameters:
Examples
>>> # Create a local recording sphere at origin >>> detector = LocalRecordingSphereSurface( ... radius=1000.0, ... center=(0, 0, 0), ... name="local_detector", ... )
- role: SurfaceRole = 1
- material_front: Any = None
- material_back: Any = None
- classmethod supported_kernels()[source]
Return list of intersection kernels supported by this surface type.
- __init__(radius=33000.0, center=(0, 0, 0), name='local_recording_sphere')
- class lsurf.surfaces.GPUGerstnerWaveSurface(amplitude, wavelength, direction, reference_z, role, phase=0.0, time=0.0, name='gpu_gerstner_wave', material_front=None, material_back=None)[source]
Bases:
SurfaceFlat-earth Gerstner wave surface with GPU acceleration.
This is a simplified single-wave surface optimized for GPU computation. For multiple superimposed waves, use the CPU-only GerstnerWaveSurface.
The surface implements a height field: z = reference_z + A*cos(k*dot(d,xy) - omega*t + phi)
- Parameters:
amplitude (float) – Wave amplitude in meters.
wavelength (float) – Wave wavelength (crest-to-crest) in meters.
direction (tuple of float) – Wave propagation direction (dx, dy), will be normalized.
reference_z (float) – Mean sea level z-coordinate in meters.
phase (float, optional) – Initial phase offset in radians. Default is 0.0.
time (float, optional) – Animation time in seconds. Default is 0.0.
role (SurfaceRole) – What happens when a ray hits (typically OPTICAL).
name (str) – Human-readable name.
material_front (MaterialField, optional) – Material above surface (air/atmosphere).
material_back (MaterialField, optional) – Material below surface (water).
Examples
>>> from lsurf.surfaces import GPUGerstnerWaveSurface, SurfaceRole >>> from lsurf.materials import AIR_STP, WATER >>> >>> ocean = GPUGerstnerWaveSurface( ... amplitude=1.5, ... wavelength=50.0, ... direction=(1.0, 0.0), ... reference_z=0.0, ... role=SurfaceRole.OPTICAL, ... name="ocean", ... material_front=AIR_STP, ... material_back=WATER, ... )
- role: SurfaceRole
- property angular_frequency: float
omega = sqrt(g*k).
- Type:
Angular frequency from deep water dispersion
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
Parameter layout (geometry_id = 3): - p0: amplitude - p1: wave_number - p2: dir_x - p3: dir_y - p4: reference_z - p5: phase - p6: time - p7-p11: unused (0.0)
- Return type:
tuple of 12 floats
- signed_distance(positions)[source]
Compute signed distance from positions to wave surface.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive above surface, negative below)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06)[source]
Find ray-surface intersections using ray marching.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origin positions.
directions (ndarray, shape (N, 3)) – Ray direction unit vectors.
min_distance (float) – Minimum valid intersection distance.
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit).
hit_mask (ndarray, shape (N,), dtype=bool) – True for rays that hit the surface.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at given positions.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface.
incoming_directions (ndarray, shape (N, 3), optional) – Incoming ray directions.
- Returns:
normals – Unit normal vectors.
- Return type:
ndarray, shape (N, 3)
- __init__(amplitude, wavelength, direction, reference_z, role, phase=0.0, time=0.0, name='gpu_gerstner_wave', material_front=None, material_back=None)
- class lsurf.surfaces.GPUCurvedWaveSurface(amplitude, wavelength, direction, role, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, time=0.0, name='gpu_curved_wave', material_front=None, material_back=None)[source]
Bases:
SurfaceCurved-earth ocean wave surface with GPU acceleration.
This is a simplified single-wave surface on a spherical Earth, optimized for GPU computation. The wave is treated as a perturbation on top of Earth’s spherical surface.
For multiple superimposed waves, use the CPU-only CurvedWaveSurface.
- Parameters:
amplitude (float) – Wave amplitude in meters.
wavelength (float) – Wave wavelength (crest-to-crest) in meters.
direction (tuple of float) – Wave propagation direction (dx, dy) in local tangent coordinates.
earth_center (tuple of float, optional) – Center of Earth sphere. Default is (0, 0, -EARTH_RADIUS).
earth_radius (float, optional) – Earth radius in meters. Default is EARTH_RADIUS.
time (float, optional) – Animation time in seconds. Default is 0.0.
role (SurfaceRole) – What happens when a ray hits (typically OPTICAL).
name (str) – Human-readable name.
material_front (MaterialField, optional) – Material above surface (atmosphere).
material_back (MaterialField, optional) – Material below surface (ocean water).
Examples
>>> from lsurf.surfaces import GPUCurvedWaveSurface, SurfaceRole >>> from lsurf.materials import ExponentialAtmosphere, WATER >>> >>> ocean = GPUCurvedWaveSurface( ... amplitude=1.5, ... wavelength=50.0, ... direction=(1.0, 0.0), ... role=SurfaceRole.OPTICAL, ... name="ocean", ... material_front=ExponentialAtmosphere(), ... material_back=WATER, ... )
- role: SurfaceRole
- property angular_frequency: float
omega = sqrt(g*k).
- Type:
Angular frequency from deep water dispersion
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
Parameter layout (geometry_id = 4): - p0: earth_center_x - p1: earth_center_y - p2: earth_center_z - p3: earth_radius - p4: amplitude - p5: wave_number - p6: dir_x - p7: dir_y - p8: time - p9-p11: unused (0.0)
- Return type:
tuple of 12 floats
- signed_distance(positions)[source]
Compute signed distance from positions to curved wave surface.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive outside, negative inside Earth+wave)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06, max_iterations=200, tolerance=0.001, max_distance=None)[source]
Find ray-surface intersections using ray marching.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origin positions.
directions (ndarray, shape (N, 3)) – Ray direction unit vectors.
min_distance (float) – Minimum valid intersection distance.
max_iterations (int) – Maximum ray marching iterations.
tolerance (float) – Convergence tolerance in meters.
max_distance (float, optional) – Maximum search distance.
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit).
hit_mask (ndarray, shape (N,), dtype=bool) – True for rays that hit the surface.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at given positions.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface.
incoming_directions (ndarray, shape (N, 3), optional) – Incoming ray directions.
- Returns:
normals – Unit normal vectors.
- Return type:
ndarray, shape (N, 3)
- __init__(amplitude, wavelength, direction, role, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, time=0.0, name='gpu_curved_wave', material_front=None, material_back=None)
- class lsurf.surfaces.GPUMultiCurvedWaveSurface(wave_params, role, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, time=0.0, name='multi_curved_wave', material_front=None, material_back=None)[source]
Bases:
SurfaceGPU-accelerated curved-earth ocean wave surface with multiple wave components.
This surface supports up to 8 superimposed wave components on a spherical Earth. Each wave is treated as a perturbation on top of Earth’s spherical surface.
Uses GPU-accelerated signed distance computation with geometry_id=5. Parameters are packed into a 64-element tuple for the GPU kernel.
- Parameters:
wave_params (list of GerstnerWaveParams) – List of wave components (max 8). Each wave has amplitude, wavelength, direction, phase, and steepness.
role (SurfaceRole) – What happens when a ray hits (typically OPTICAL).
earth_center (tuple of float, optional) – Center of Earth sphere. Default is (0, 0, -EARTH_RADIUS).
earth_radius (float, optional) – Earth radius in meters. Default is EARTH_RADIUS.
time (float, optional) – Animation time in seconds. Default is 0.0.
name (str) – Human-readable name.
material_front (MaterialField, optional) – Material above surface (atmosphere).
material_back (MaterialField, optional) – Material below surface (ocean water).
Examples
>>> from lsurf.surfaces import GPUMultiCurvedWaveSurface, SurfaceRole, GerstnerWaveParams >>> from lsurf.materials import ExponentialAtmosphere, WATER >>> >>> waves = [ ... GerstnerWaveParams(amplitude=1.5, wavelength=50.0, direction=(1.0, 0.0)), ... GerstnerWaveParams(amplitude=0.8, wavelength=30.0, direction=(0.7, 0.7)), ... GerstnerWaveParams(amplitude=0.3, wavelength=15.0, direction=(-0.5, 0.8)), ... ] >>> >>> ocean = GPUMultiCurvedWaveSurface( ... wave_params=waves, ... role=SurfaceRole.OPTICAL, ... name="multi_wave_ocean", ... material_front=ExponentialAtmosphere(), ... material_back=WATER, ... )
- wave_params: list[GerstnerWaveParams]
- role: SurfaceRole
- get_gpu_parameters()[source]
Return 64-element parameter tuple for GPU kernel.
Parameter layout (geometry_id = 5): - p0-2: earth_center_x/y/z - p3: earth_radius - p4: time - p5: num_waves - p6-7: reserved
For each wave i (i=0..7), starting at offset 8 + i*8: - p[offset+0]: amplitude - p[offset+1]: wave_number (k) - p[offset+2]: dir_x (normalized) - p[offset+3]: dir_y (normalized) - p[offset+4]: phase - p[offset+5]: steepness - p[offset+6-7]: reserved
- Return type:
tuple of 64 floats
- signed_distance(positions)[source]
Compute signed distance from positions to multi-wave curved surface.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive outside, negative inside Earth+wave)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06, max_iterations=200, tolerance=0.001, max_distance=None)[source]
Find ray-surface intersections using ray marching.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origin positions.
directions (ndarray, shape (N, 3)) – Ray direction unit vectors.
min_distance (float) – Minimum valid intersection distance.
max_iterations (int) – Maximum ray marching iterations.
tolerance (float) – Convergence tolerance in meters.
max_distance (float, optional) – Maximum search distance.
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit).
hit_mask (ndarray, shape (N,), dtype=bool) – True for rays that hit the surface.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at given positions.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface.
incoming_directions (ndarray, shape (N, 3), optional) – Incoming ray directions.
- Returns:
normals – Unit normal vectors.
- Return type:
ndarray, shape (N, 3)
- __init__(wave_params, role, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, time=0.0, name='multi_curved_wave', material_front=None, material_back=None)
- class lsurf.surfaces.BoundedPlaneSurface(point, normal, width, height, role, name='bounded_plane', material_front=None, material_back=None)[source]
Bases:
SurfaceBounded rectangular plane surface with GPU acceleration.
The plane is defined by a center point, normal vector, and dimensions. The rectangle is centered at the point with the given width and height.
- Parameters:
point (tuple of float) – Center point (px, py, pz) of the rectangle.
normal (tuple of float) – Unit normal vector (nx, ny, nz) pointing “outward” (front side).
width (float) – Width of the rectangle (size in local U direction).
height (float) – Height of the rectangle (size in local V direction).
role (SurfaceRole) – What happens when a ray hits (DETECTOR, OPTICAL, or ABSORBER).
name (str) – Human-readable name.
material_front (MaterialField, optional) – Material on front side (where normal points). Required for OPTICAL.
material_back (MaterialField, optional) – Material on back side. Required for OPTICAL.
Examples
>>> # Square detector at 33 km altitude >>> detector = BoundedPlaneSurface( ... point=(0, 0, 33000), ... normal=(0, 0, -1), # Facing down toward origin ... width=100.0, ... height=100.0, ... role=SurfaceRole.DETECTOR, ... name="detector_100m", ... ) >>> >>> # Rectangular glass window >>> window = BoundedPlaneSurface( ... point=(0, 0, 0), ... normal=(0, 0, 1), ... width=1.0, ... height=0.5, ... role=SurfaceRole.OPTICAL, ... material_front=air, ... material_back=glass, ... name="window", ... )
- role: SurfaceRole
- material_front: Any = None
- material_back: Any = None
- classmethod supported_kernels()[source]
Return list of intersection kernels supported by this surface type.
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
- Returns:
- (normal_x, normal_y, normal_z, point_x, point_y, point_z,
u_axis_x, u_axis_y, u_axis_z, v_axis_x, v_axis_y, v_axis_z, half_width, half_height)
- Return type:
- get_materials()[source]
Return (material_front, material_back) for Fresnel calculation.
- Returns:
(material_front, material_back) or None if not OPTICAL
- Return type:
tuple or None
- signed_distance(positions)[source]
Compute signed distance from positions to plane.
Note: This returns signed distance to the infinite plane. Bounds checking is performed in the intersection kernel.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive on normal side, negative on back side)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06)[source]
Compute ray-plane intersection with bounds checking.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origins
directions (ndarray, shape (N, 3)) – Ray directions (normalized)
min_distance (float) – Minimum valid intersection distance
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit)
hit_mask (ndarray, shape (N,)) – Boolean mask of valid intersections
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at positions.
For a plane, normal is constant everywhere.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface
incoming_directions (ndarray, shape (N, 3), optional) – Ray directions (used to flip normal if needed)
- Returns:
Normal vectors at each position
- Return type:
ndarray, shape (N, 3)
- __init__(point, normal, width, height, role, name='bounded_plane', material_front=None, material_back=None)
- class lsurf.surfaces.AnnularPlaneSurface(center, normal, inner_radius, outer_radius, role, name='annular_plane', material_front=None, material_back=None)[source]
Bases:
SurfaceAnnular (ring-shaped) planar detector surface with GPU acceleration.
The annular plane is defined by a center point, normal vector, and inner/outer radii. Useful for creating concentric ring detector arrays that all face the same target point.
- Parameters:
center (tuple of float) – Center point (cx, cy, cz) of the annulus.
normal (tuple of float) – Unit normal vector (nx, ny, nz) pointing “outward” (front side).
inner_radius (float) – Inner radius of the annulus (meters). Use 0 for a disk.
outer_radius (float) – Outer radius of the annulus (meters).
role (SurfaceRole) – What happens when a ray hits (DETECTOR, OPTICAL, or ABSORBER).
name (str) – Human-readable name.
material_front (MaterialField, optional) – Material on front side (where normal points). Required for OPTICAL.
material_back (MaterialField, optional) – Material on back side. Required for OPTICAL.
Examples
>>> # Annular detector ring at 33 km altitude >>> ring = AnnularPlaneSurface( ... center=(0, 0, 33000), ... normal=(0, 0, -1), # Facing down toward origin ... inner_radius=5000.0, ... outer_radius=10000.0, ... role=SurfaceRole.DETECTOR, ... name="ring_5_10km", ... ) >>> >>> # Central disk (inner_radius=0) >>> disk = AnnularPlaneSurface( ... center=(0, 0, 33000), ... normal=(0, 0, -1), ... inner_radius=0.0, ... outer_radius=2500.0, ... role=SurfaceRole.DETECTOR, ... name="disk_2.5km", ... )
- role: SurfaceRole
- material_front: Any = None
- material_back: Any = None
- classmethod supported_kernels()[source]
Return list of intersection kernels supported by this surface type.
- get_gpu_parameters()[source]
Return parameters for GPU kernel.
- Returns:
- (normal_x, normal_y, normal_z, center_x, center_y, center_z,
u_axis_x, u_axis_y, u_axis_z, v_axis_x, v_axis_y, v_axis_z, inner_radius_sq, outer_radius_sq)
- Return type:
- get_materials()[source]
Return (material_front, material_back) for Fresnel calculation.
- Returns:
(material_front, material_back) or None if not OPTICAL
- Return type:
tuple or None
- signed_distance(positions)[source]
Compute signed distance from positions to plane.
Note: This returns signed distance to the infinite plane. Bounds checking is performed in the intersection kernel.
- Parameters:
positions (ndarray, shape (N, 3)) – Points to compute distance for
- Returns:
Signed distance (positive on normal side, negative on back side)
- Return type:
ndarray, shape (N,)
- intersect(origins, directions, min_distance=1e-06)[source]
Compute ray-plane intersection with annular bounds checking.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origins
directions (ndarray, shape (N, 3)) – Ray directions (normalized)
min_distance (float) – Minimum valid intersection distance
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit)
hit_mask (ndarray, shape (N,)) – Boolean mask of valid intersections
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at positions.
For a plane, normal is constant everywhere.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface
incoming_directions (ndarray, shape (N, 3), optional) – Ray directions (used to flip normal if needed)
- Returns:
Normal vectors at each position
- Return type:
ndarray, shape (N, 3)
- get_local_coordinates(positions)[source]
Get local (u, v) coordinates for positions on the annulus.
Useful for post-processing azimuthal binning.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on or near the annular plane
- Returns:
u_coords (ndarray, shape (N,)) – U coordinate in local frame
v_coords (ndarray, shape (N,)) – V coordinate in local frame
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]
- get_polar_coordinates(positions)[source]
Get polar (r, theta) coordinates for positions on the annulus.
Useful for post-processing azimuthal binning.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on or near the annular plane
- Returns:
radii (ndarray, shape (N,)) – Radial distance from center
azimuths (ndarray, shape (N,)) – Azimuthal angle in radians (-pi to pi)
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]
- __init__(center, normal, inner_radius, outer_radius, role, name='annular_plane', material_front=None, material_back=None)
- class lsurf.surfaces.GerstnerWaveSurface(wave_params, role, name='gerstner_wave', time=0.0, reference_z=0.0, material_front=None, material_back=None, max_distance=10000.0)[source]
Bases:
SurfaceFlat ocean surface with Gerstner wave physics (CPU-only).
Implements the Gerstner (trochoidal) wave model where water particles move in circular orbits, producing realistic ocean wave shapes with sharp crests and flat troughs.
- Parameters:
wave_params (list of GerstnerWaveParams) – List of wave components to superimpose.
role (SurfaceRole) – What happens when a ray hits (typically OPTICAL).
name (str) – Human-readable name.
time (float, optional) – Time for wave animation in seconds. Default is 0.0.
reference_z (float, optional) – Mean sea level z-coordinate in meters. Default is 0.0.
material_front (MaterialField, optional) – Material above surface (air).
material_back (MaterialField, optional) – Material below surface (water).
max_distance (float, optional) – Maximum ray marching distance in meters. Default is 10000.0.
Examples
>>> from lsurf.surfaces import GerstnerWaveSurface, GerstnerWaveParams, SurfaceRole >>> from lsurf.materials import AIR_STP, WATER >>> >>> wave = GerstnerWaveParams(amplitude=1.0, wavelength=50.0) >>> surface = GerstnerWaveSurface( ... wave_params=[wave], ... role=SurfaceRole.OPTICAL, ... name="ocean", ... material_front=AIR_STP, ... material_back=WATER, ... )
- wave_params: list[GerstnerWaveParams]
- role: SurfaceRole
- intersect(origins, directions, min_distance=1e-06)[source]
Find ray-surface intersections using ray marching.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origin positions.
directions (ndarray, shape (N, 3)) – Ray direction unit vectors.
min_distance (float) – Minimum valid intersection distance.
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit).
hit_mask (ndarray, shape (N,), dtype=bool) – True for rays that hit the surface.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at given positions.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface.
incoming_directions (ndarray, shape (N, 3), optional) – Incoming ray directions.
- Returns:
normals – Unit normal vectors.
- Return type:
ndarray, shape (N, 3)
- __init__(wave_params, role, name='gerstner_wave', time=0.0, reference_z=0.0, material_front=None, material_back=None, max_distance=10000.0)
- class lsurf.surfaces.CurvedWaveSurface(wave_params, role, name='curved_wave', earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, time=0.0, material_front=None, material_back=None)[source]
Bases:
SurfaceOcean wave surface on a curved (spherical) Earth (CPU-only).
Combines Earth’s spherical curvature with Gerstner wave perturbations applied in local tangent space.
- Parameters:
wave_params (list of GerstnerWaveParams) – List of wave components.
role (SurfaceRole) – What happens when a ray hits (typically OPTICAL).
name (str) – Human-readable name.
earth_center (tuple of float, optional) – Center of Earth sphere. Default is (0, 0, -EARTH_RADIUS).
earth_radius (float, optional) – Earth radius in meters. Default is EARTH_RADIUS.
time (float, optional) – Time for wave animation in seconds. Default is 0.0.
material_front (MaterialField, optional) – Material above surface (atmosphere).
material_back (MaterialField, optional) – Material below surface (ocean water).
Examples
>>> from lsurf.surfaces import CurvedWaveSurface, GerstnerWaveParams, SurfaceRole >>> from lsurf.materials import ExponentialAtmosphere, WATER >>> >>> wave = GerstnerWaveParams(amplitude=1.0, wavelength=50.0) >>> ocean = CurvedWaveSurface( ... wave_params=[wave], ... role=SurfaceRole.OPTICAL, ... name="ocean", ... material_front=ExponentialAtmosphere(), ... material_back=WATER, ... )
- wave_params: list[GerstnerWaveParams]
- role: SurfaceRole
- intersect(origins, directions, min_distance=1e-06, max_iterations=200, tolerance=0.001, max_distance=None)[source]
Find ray-surface intersections using ray marching.
- Parameters:
origins (ndarray, shape (N, 3)) – Ray origin positions.
directions (ndarray, shape (N, 3)) – Ray direction unit vectors.
min_distance (float) – Minimum valid intersection distance.
max_iterations (int) – Maximum ray marching iterations.
tolerance (float) – Convergence tolerance in meters.
max_distance (float, optional) – Maximum search distance.
- Returns:
distances (ndarray, shape (N,)) – Distance to intersection (inf if no hit).
hit_mask (ndarray, shape (N,), dtype=bool) – True for rays that hit the surface.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float32]], ndarray[tuple[Any, …], dtype[bool]]]
- normal_at(positions, incoming_directions=None)[source]
Compute surface normal at given positions.
- Parameters:
positions (ndarray, shape (N, 3)) – Points on the surface.
incoming_directions (ndarray, shape (N, 3), optional) – Incoming ray directions.
- Returns:
normals – Unit normal vectors.
- Return type:
ndarray, shape (N, 3)
- __init__(wave_params, role, name='curved_wave', earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, time=0.0, material_front=None, material_back=None)
- class lsurf.surfaces.GerstnerWaveParams(amplitude, wavelength, direction=(1.0, 0.0), phase=0.0, steepness=0.5)[source]
Bases:
objectParameters for a single Gerstner wave component.
Gerstner waves describe the motion of water particles in circular orbits, producing realistic wave shapes with sharp crests and flat troughs.
- Parameters:
amplitude (float) – Wave amplitude (vertical displacement) in meters.
wavelength (float) – Wave wavelength (crest-to-crest distance) in meters.
direction (tuple of float, optional) – Wave propagation direction as (dx, dy), will be normalized. Default is (1.0, 0.0) for propagation in +x direction.
phase (float, optional) – Initial phase offset in radians. Default is 0.0.
steepness (float, optional) – Wave steepness Q (0 to 1). Controls horizontal displacement. Q=0 gives pure vertical sinusoidal motion. Q=1 gives maximum sharpening (breaking wave limit). Default is 0.5.
Examples
>>> # Simple wave propagating in +x direction >>> wave = GerstnerWaveParams(amplitude=1.0, wavelength=50.0)
>>> # Steep wave at 45 degrees >>> wave = GerstnerWaveParams( ... amplitude=2.0, ... wavelength=30.0, ... direction=(1.0, 1.0), ... steepness=0.8 ... )
- __init__(amplitude, wavelength, direction=(1.0, 0.0), phase=0.0, steepness=0.5)
Surface Classes
|
Flat ocean surface with Gerstner wave physics (CPU-only). |
|
Ocean wave surface on a curved (spherical) Earth (CPU-only). |