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: ABC

Abstract 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:

SurfaceRole

name

Surface identifier.

Type:

str

material_front

Front-side material.

Type:

MaterialField or None

material_back

Back-side material.

Type:

MaterialField or None

gpu_capable

Whether this surface supports GPU acceleration.

Type:

bool

geometry_id

GPU geometry type ID (0 for CPU-only surfaces).

Type:

int

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)
__init__(role, name, material_front=None, material_back=None, kernel=None)[source]
property gpu_capable: bool

Whether this surface supports GPU acceleration.

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.

property geometry: int

use geometry_id instead.

Type:

Deprecated

classmethod supported_kernels()[source]

Return list of intersection kernels supported by this surface type.

Returns:

List of kernel IDs this surface supports.

Return type:

list[IntersectionKernelID]

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:

tuple

Raises:

NotImplementedError – If surface is not GPU-capable.

__repr__()[source]

Return string representation.

class lsurf.surfaces.SurfaceRole(*values)[source]

Bases: IntEnum

What happens when a ray hits this surface.

DETECTOR = 1
OPTICAL = 2
ABSORBER = 3
lsurf.surfaces.GPUSurface

alias of Surface

class lsurf.surfaces.SurfaceTypeInfo(name, capability, id, cls=None)[source]

Bases: object

Information about a registered surface type.

name: str
capability: Literal['gpu', 'cpu']
id: int
cls: type[Surface] | None = None
__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.get_surface_type(name)[source]

Get full surface type info by name.

lsurf.surfaces.get_surface_type_id(name)[source]

Get the GPU kernel ID for a surface type.

lsurf.surfaces.get_surface_class(name)[source]

Get the surface class for a surface type.

lsurf.surfaces.is_gpu_capable(name)[source]

Check if a surface type supports GPU acceleration.

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.surface_type_exists(name)[source]

Check if a surface type is registered.

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: Surface

Infinite 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:
  • point (tuple of float) – A point (px, py, pz) on the plane.

  • 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",
... )
point: tuple[float, float, float]
normal: tuple[float, float, float]
role: SurfaceRole
name: str = 'plane'
material_front: Any = None
material_back: Any = None
classmethod supported_kernels()[source]

Return list of intersection kernels supported by this surface type.

classmethod default_kernel()[source]

Return the default intersection kernel for this surface type.

property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (plane = 1).

get_gpu_parameters()[source]

Return parameters for GPU kernel.

Returns:

(normal_x, normal_y, normal_z, point_x, point_y, point_z)

Return type:

tuple

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: Surface

Sphere 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:
  • center (tuple of float) – Center point (cx, cy, cz).

  • 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",
... )
center: tuple[float, float, float]
radius: float
role: SurfaceRole
name: str = 'sphere'
material_front: Any = None
material_back: Any = None
classmethod supported_kernels()[source]

Return list of intersection kernels supported by this surface type.

classmethod default_kernel()[source]

Return the default intersection kernel for this surface type.

property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (sphere = 2).

get_gpu_parameters()[source]

Return parameters for GPU kernel.

Returns:

(center_x, center_y, center_z, radius)

Return type:

tuple

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: Surface

Recording 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

altitude: float = 33000.0
earth_center: tuple[float, float, float] = (0, 0, -6371000.0)
earth_radius: float = 6371000.0
name: str = 'recording_sphere'
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.

classmethod default_kernel()[source]

Return the default intersection kernel for this surface type.

__post_init__()[source]

Initialize computed properties.

property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (sphere = 2).

property sphere_radius: float

Total radius of the recording sphere (earth_radius + altitude).

property center: tuple[float, float, float]

Center of the sphere (same as earth_center).

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:

tuple

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:

dict

__repr__()[source]

Return string representation.

__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: Surface

Local recording sphere surface centered at an arbitrary position.

A simplified recording sphere for local-scale simulations without Earth curvature considerations.

Parameters:
  • radius (float) – Sphere radius in meters (default 33 km).

  • center (tuple of float) – Center position (default (0, 0, 0)).

  • name (str) – Human-readable name for the detector.

Examples

>>> # Create a local recording sphere at origin
>>> detector = LocalRecordingSphereSurface(
...     radius=1000.0,
...     center=(0, 0, 0),
...     name="local_detector",
... )
radius: float = 33000.0
center: tuple[float, float, float] = (0, 0, 0)
name: str = 'local_recording_sphere'
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.

classmethod default_kernel()[source]

Return the default intersection kernel for this surface type.

__post_init__()[source]

Initialize computed properties.

property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (sphere = 2).

property sphere_radius: float

Radius of the recording sphere.

property center_array: ndarray[tuple[Any, ...], dtype[float64]]

Center as numpy array.

get_gpu_parameters()[source]

Return parameters for GPU kernel.

get_materials()[source]

Return None (detectors don’t use Fresnel).

signed_distance(positions)[source]

Compute signed distance from positions to sphere surface.

intersect(origins, directions, min_distance=1e-06)[source]

Compute ray-sphere intersection.

normal_at(positions, incoming_directions=None)[source]

Compute surface normal at positions.

__repr__()[source]

Return string representation.

__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: Surface

Flat-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,
... )
amplitude: float
wavelength: float
direction: tuple[float, float]
reference_z: float
role: SurfaceRole
phase: float = 0.0
time: float = 0.0
name: str = 'gpu_gerstner_wave'
material_front: Any = None
material_back: Any = None
property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (gerstner_wave = 3).

property wave_number: float

Wave number k = 2*pi/wavelength.

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

get_materials()[source]

Return materials for Fresnel calculation.

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)

set_time(time)[source]

Update the wave animation time.

__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: Surface

Curved-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,
... )
amplitude: float
wavelength: float
direction: tuple[float, float]
role: SurfaceRole
earth_center: tuple[float, float, float] = (0, 0, -6371000.0)
earth_radius: float = 6371000.0
time: float = 0.0
name: str = 'gpu_curved_wave'
material_front: Any = None
material_back: Any = None
property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (curved_wave = 4).

property wave_number: float

Wave number k = 2*pi/wavelength.

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

get_materials()[source]

Return materials for Fresnel calculation.

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)

set_time(time)[source]

Update the wave animation time.

__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: Surface

GPU-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
earth_center: tuple[float, float, float] = (0, 0, -6371000.0)
earth_radius: float = 6371000.0
time: float = 0.0
name: str = 'multi_curved_wave'
material_front: Any = None
material_back: Any = None
property gpu_capable: bool

This surface supports GPU acceleration with geometry_id=5.

property geometry_id: int

GPU geometry type ID (multi_curved_wave = 5).

property num_waves: int

Number of wave components.

property max_amplitude: float

Maximum combined wave amplitude (sum of all amplitudes).

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

get_materials()[source]

Return materials for Fresnel calculation.

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)

set_time(time)[source]

Update the wave animation time.

__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: Surface

Bounded 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",
... )
point: tuple[float, float, float]
normal: tuple[float, float, float]
width: float
height: float
role: SurfaceRole
name: str = 'bounded_plane'
material_front: Any = None
material_back: Any = None
classmethod supported_kernels()[source]

Return list of intersection kernels supported by this surface type.

classmethod default_kernel()[source]

Return the default intersection kernel for this surface type.

property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (bounded plane = 6).

property half_width: float

Half-width for bounds checking.

property half_height: float

Half-height for bounds checking.

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:

tuple

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: Surface

Annular (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",
... )
center: tuple[float, float, float]
normal: tuple[float, float, float]
inner_radius: float
outer_radius: float
role: SurfaceRole
name: str = 'annular_plane'
material_front: Any = None
material_back: Any = None
classmethod supported_kernels()[source]

Return list of intersection kernels supported by this surface type.

classmethod default_kernel()[source]

Return the default intersection kernel for this surface type.

property gpu_capable: bool

This surface supports GPU acceleration.

property geometry_id: int

GPU geometry type ID (annular plane = 7).

property area: float

Area of the annular ring in square meters.

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:

tuple

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: Surface

Flat 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
name: str = 'gerstner_wave'
time: float = 0.0
reference_z: float = 0.0
material_front: Any = None
material_back: Any = None
max_distance: float = 10000.0
property gpu_capable: bool

This surface does NOT support GPU acceleration.

property geometry_id: int

GPU geometry type ID (0 = CPU-only).

get_materials()[source]

Return materials for Fresnel calculation.

get_max_wave_height()[source]

Get maximum possible wave height above reference_z.

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)

set_time(time)[source]

Update the wave animation time.

__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: Surface

Ocean 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
name: str = 'curved_wave'
earth_center: tuple[float, float, float] = (0, 0, -6371000.0)
earth_radius: float = 6371000.0
time: float = 0.0
material_front: Any = None
material_back: Any = None
property gpu_capable: bool

This surface does NOT support GPU acceleration.

property geometry_id: int

GPU geometry type ID (0 = CPU-only).

get_materials()[source]

Return materials for Fresnel calculation.

get_max_wave_height()[source]

Get maximum possible wave displacement.

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)

set_time(time)[source]

Update the wave animation time.

__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: object

Parameters 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.

wave_number

Wave number k = 2π/λ in radians per meter.

Type:

float

angular_frequency

Angular frequency from deep water dispersion: ω = √(gk).

Type:

float

direction_normalized

Normalized direction vector.

Type:

tuple of float

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
... )
amplitude: float
wavelength: float
direction: tuple[float, float] = (1.0, 0.0)
phase: float = 0.0
steepness: float = 0.5
property wave_number: float

Wave number k = 2π/λ.

property angular_frequency: float

ω = √(gk).

Type:

Angular frequency from deep water dispersion

property direction_normalized: tuple[float, float]

Normalized direction vector.

__init__(amplitude, wavelength, direction=(1.0, 0.0), phase=0.0, steepness=0.5)

Surface Classes

GerstnerWaveSurface(wave_params, role[, ...])

Flat ocean surface with Gerstner wave physics (CPU-only).

CurvedWaveSurface(wave_params, role[, name, ...])

Ocean wave surface on a curved (spherical) Earth (CPU-only).