Source code for simvx.graphics.renderer.ocean_pack

"""Shared ocean-surface wire packing (design D11, RM-E7).

Dependency-free (numpy + core math only, no Vulkan) so BOTH the desktop ocean
pass (``ocean_pass.py``) and a future browser twin pack an ``OceanSurface3D``
into the identical std430 record. Mirrors the ``OceanSurface`` struct in
``ocean.vert`` / ``ocean.frag`` (and, when it lands, ``ocean3d.wgsl``).

The FFT ocean reuses the :class:`~simvx.core.water.WaterMaterial` shading knobs
(design D16): the colour / depth-fade / Fresnel / foam fields are packed exactly
like the Gerstner water record; the extra ``ocean*`` vec4s carry the cascade
geometry (patch lengths, count) and the FFT-specific displacement / foam scales.
"""

from __future__ import annotations

from typing import Any

import numpy as np

__all__ = ["OCEAN_SURFACE_DTYPE", "OCEAN_SURFACE_STRIDE", "build_ocean_records", "pack_ocean_surface"]

# Per-surface std430 record mirrored by the OceanSurface struct in the ocean
# shaders. mat4 (64) + 7 vec4 (112) = 176 bytes.
OCEAN_SURFACE_DTYPE = np.dtype(
    [
        ("model", np.float32, (16,)),
        ("shallow_colour", np.float32, (4,)),
        ("deep_colour", np.float32, (4,)),  # rgb + depth_fade_distance
        ("foam_colour", np.float32, (4,)),  # rgb + shore foam band
        ("shade", np.float32, (4,)),  # refraction_strength, fresnel_power, roughness, opacity
        ("ocean0", np.float32, (4,)),  # size_x, size_z, subdivisions, n_cascades
        ("ocean1", np.float32, (4,)),  # patch_len0, patch_len1, patch_len2, choppiness
        ("ocean2", np.float32, (4,)),  # displacement_scale, foam_intensity, normal_strength, _
    ]
)
OCEAN_SURFACE_STRIDE = OCEAN_SURFACE_DTYPE.itemsize  # 176


[docs] def pack_ocean_surface( model: np.ndarray, mat: Any, subdivisions: int, size: tuple[float, float], patch_lengths: tuple[float, ...], *, choppiness: float, displacement_scale: float, foam_intensity: float, ) -> np.ndarray: """Pack one ocean surface (row-major ``model`` + a ``WaterMaterial``) into a record. ``model`` is transposed to column-major for the shader std430 mat4. ``patch_lengths`` is the cascade patch-length tuple (metres, longest first); up to three are written and the count is stored in ``ocean0.w``. """ rec = np.zeros((), dtype=OCEAN_SURFACE_DTYPE) rec["model"] = np.ascontiguousarray(model.T, dtype=np.float32).ravel() rec["shallow_colour"] = (*mat.shallow_colour[:3], 0.0) rec["deep_colour"] = (*mat.deep_colour[:3], float(mat.depth_fade_distance)) rec["foam_colour"] = (*mat.foam_colour[:3], float(mat.foam_amount)) rec["shade"] = ( float(mat.refraction_strength), float(mat.fresnel_power), float(mat.roughness), float(mat.opacity), ) n_c = len(patch_lengths) rec["ocean0"] = (float(size[0]), float(size[1]), float(subdivisions), float(n_c)) pl = list(patch_lengths[:3]) + [0.0] * (3 - min(n_c, 3)) rec["ocean1"] = (pl[0], pl[1], pl[2], float(choppiness)) rec["ocean2"] = (float(displacement_scale), float(foam_intensity), float(mat.normal_strength), 0.0) return rec
[docs] def build_ocean_records( surfaces: list[Any], patch_lengths: tuple[float, ...], *, choppiness: float, displacement_scale: float = 1.0, foam_intensity: float = 1.0, ) -> tuple[np.ndarray, int]: """Pack a list of ``OceanSurface3D`` nodes into a contiguous record array. Used by the web exporter (the desktop pass packs per-frame from its own submission tuples via :func:`pack_ocean_surface`). Reads each node's world transform live, so a moving surface repacks correctly every frame. Returns ``(records, count)``; ``records`` is empty when ``surfaces`` is empty. """ from simvx.core.math.matrices import mat4_from_trs count = len(surfaces) packed = np.zeros(count, dtype=OCEAN_SURFACE_DTYPE) for i, node in enumerate(surfaces): model = mat4_from_trs(node.world_position, node.world_rotation, node.world_scale) packed[i] = pack_ocean_surface( model, node.material, int(node.subdivisions), tuple(node.size), patch_lengths, choppiness=choppiness, displacement_scale=displacement_scale, foam_intensity=foam_intensity, ) return packed, count