/*
* Copyright 2026 The Ray Optics Simulation authors and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Primitive data contracts shared by scene objects, preprocessing, and
* simulation engines.
* @file
*/
/**
* @typedef {Object} LineSegmentCurveParams
* @property {Point} start - The start endpoint.
* @property {Point} end - The end endpoint.
*/
/**
* @typedef {Object} SmoothLineSegmentCurveParams
* @property {Point} start - The start endpoint.
* @property {Point} end - The end endpoint.
* @property {Point} startNormal - The optical front normal at `start`.
* @property {Point} endNormal - The optical front normal at `end`.
*/
/**
* @typedef {Object} CircularArcCurveParams
* @property {Point} start - The start endpoint.
* @property {Point} end - The end endpoint.
* @property {number} bulge - The tangent of one quarter of the signed sweep angle from `start` to `end`. A positive sweep is counter-clockwise. A zero bulge degenerates to a line segment; absolute values below, equal to, and above 1 describe minor arcs, semicircles, and major arcs respectively.
*/
/**
* @typedef {Object} CubicBezierCurveParams
* @property {Point} start - The start endpoint.
* @property {Point} control1 - The first control point.
* @property {Point} control2 - The second control point.
* @property {Point} end - The end endpoint.
*/
/**
* @typedef {Object} CircleCurveParams
* @property {Point} center - The center of the circle.
* @property {number} radius - The nonzero signed radius. Its absolute value defines the geometry; a positive value gives the circle an outward front normal and a negative value gives it an inward front normal.
*/
/**
* @typedef {Object} LineSegmentPrimitiveCurve
* @property {'lineSegment'} kind
* @property {LineSegmentCurveParams} params
*/
/**
* A line segment whose optical front normal is the normalized linear
* interpolation of its endpoint normals. Its intersection geometry and native
* parameter are identical to those of a line segment. Boundary-crossing
* orientation remains geometric and is derived from `start` to `end`, rather
* than from the interpolated normal.
* @typedef {Object} SmoothLineSegmentPrimitiveCurve
* @property {'smoothLineSegment'} kind
* @property {SmoothLineSegmentCurveParams} params
*/
/**
* @typedef {Object} CircularArcPrimitiveCurve
* @property {'circularArc'} kind
* @property {CircularArcCurveParams} params
*/
/**
* @typedef {Object} CubicBezierPrimitiveCurve
* @property {'cubicBezier'} kind
* @property {CubicBezierCurveParams} params
*/
/**
* A complete oriented circle. The sign of its radius determines whether its
* front normal points away from or toward its center.
* @typedef {Object} CirclePrimitiveCurve
* @property {'circle'} kind
* @property {CircleCurveParams} params
*/
/**
* A curve which will be extracted as an individual intersection primitive and
* inserted into the BVH. Open curves are directed from `start` to `end`; this
* direction is significant for surfaces and for resolving coincident region
* boundary pieces.
*
* The engine-independent curve parameters are converted during preprocessing
* into whatever intersection representation an engine requires.
*
* Intersection testing assigns every open curve a native parameter `u` from
* 0 at `start` to 1 at `end`. A line segment uses its affine parameter, a
* circular arc uses its center-free rational-quadratic parameter, and a cubic
* Bezier uses its usual Bezier parameter. A circle has no endpoints or useful
* native parameter and returns the neutral placeholder `u = 0.5`.
*
* Reversing a line segment means swapping `start` and `end`. Reversing a
* smooth line segment additionally means swapping and negating its endpoint
* normals. Reversing a circular arc means swapping its endpoints and negating
* `bulge`. Reversing a cubic Bezier means swapping its endpoints and also
* swapping `control1` and `control2`. Reversing a circle means negating its
* radius.
* @typedef {LineSegmentPrimitiveCurve|SmoothLineSegmentPrimitiveCurve|CircularArcPrimitiveCurve|CubicBezierPrimitiveCurve|CirclePrimitiveCurve} PrimitiveCurve
*/
/**
* Controls whether a surface participates in intersection testing for a ray.
* When `invert` is false, the surface is intersectable for wavelengths in the
* inclusive interval `wavelength ± bandwidth`. When `invert` is true, the
* surface is intersectable for wavelengths outside that interval. If the
* filter rejects a ray, the surface produces no hit and does not participate
* in surface merging or ray-depth accounting.
* @typedef {Object} WavelengthFilter
* @property {number} wavelength - The center wavelength in nm.
* @property {number} bandwidth - The half-width of the wavelength interval in nm.
* @property {boolean} invert - Whether wavelengths outside, rather than inside, the interval are accepted.
*/
/**
* Defines the optical behavior shared by a set of surface primitives. Type
* definitions must be treated as immutable. Preprocessing uses object identity
* as a fast path, then structurally deduplicates equivalent plain-data
* definitions from separately expanded objects. `name` is diagnostic rather
* than a registry ID, but it remains part of that structural definition so
* differently named types are kept distinct. The DAG uses the format
* implemented by the formula utilities in `src/core/formula`.
*
* Before evaluating the DAG, the engine converts the hit into a local
* orthonormal frame. The adjusted, incident-side normal is mapped to `(0, 1)`,
* and the local x-axis is obtained by rotating that normal clockwise by 90
* degrees. The normal is adjusted for the side from which the ray arrived, so
* the incoming unit direction satisfies `d_0y < 0`; a ray exactly tangent to
* the surface is not considered a hit. For f32 range analysis, `-d_0y` can
* therefore use the closed range from the smallest positive f32 through 1.
*
* The following formula symbols are reserved inputs:
*
* - `d_0x`, `d_0y`: components of the incoming unit direction in the local
* surface frame.
* - `P_0s`, `P_0p`: incoming s- and p-polarized powers.
* - `lambda`: incoming wavelength in nm.
* - `x`, `y`: world-space coordinates of the hit.
* - `u`: the native curve parameter at the hit. It is in `[0, 1]` for line
* segments, circular arcs, and cubic Bezier curves. A circle supplies the
* neutral placeholder `0.5`; code must use the curve kind, rather than this
* placeholder, when deciding whether a hit is at an endpoint.
* - `sigma`: the geometric side of the hit. It is 1 when the ray approaches
* against the oriented curve's front normal and -1 when it approaches from
* behind that normal. Its range is the discrete union `{-1, 1}` for a
* two-sided primitive and the singleton `{1}` for a one-sided primitive.
* - `n_0`, `n_1`: effective refractive indices on the incident and opposite
* sides of the surface respectively.
*
* `u` and `sigma` are derived hit inputs, not instance parameters, and
* therefore are not entries in `paramNames` or the scene parameter buffer.
* Detector types use the same reserved hit-input symbols and `sigma`
* convention, although their outputs describe accumulated detector data
* rather than outgoing rays.
*
* For every one-based output index `j` from 1 through `outRayCount`, the DAG
* must contain the four labeled outputs `d_jx`, `d_jy`, `P_js`, and `P_jp`.
* `d_jx` and `d_jy` are the outgoing unit direction in the same local frame;
* `P_js` and `P_jp` are its s- and p-polarized powers. A slot whose two powers
* are both zero is ignored. The output count and layout never vary at runtime.
* Outgoing rays inherit the incoming wavelength and non-optical bookkeeping.
*
* Existing angle-based Custom Surface formulas can be translated with
* `theta_0 = atan2(-d_0x, -d_0y)`, then with
* `d_jx = -sin(theta_j)` and `d_jy = -cos(theta_j)`. Polarization is evaluated
* directly through `P_0s` and `P_0p`; there is no polarization-selector input.
* If the two polarizations leave in different directions, they occupy separate
* output slots and the unused power component of each slot is zero.
*
* WGSL range specialization uses the actual packed f32 parameter values of all
* primitives sharing this type, rather than a largest declared parameter
* range. Changing instance parameters requires recompilation only when the
* range-dependent WGSL safety decisions change; otherwise the engine only
* updates its parameter buffer.
*
* @typedef {Object} SurfaceType
* @property {string} name - A human-readable diagnostic name, not a registry ID.
* @property {string[]} paramNames - The formula symbols and keys accepted in a surface primitive's `params` object. Their order defines the packed parameter layout and is therefore significant, particularly for WebGPU buffers. Names must not collide with reserved surface-DAG symbols.
* @property {Object} dag - The formula DAG containing the required labeled outputs.
* @property {number} outRayCount - The constant positive number of outgoing-ray slots.
* @property {boolean} mergesWithBoundary - Whether the surface interaction supports coincident region boundaries.
*/
/**
* Defines how one invocation emits one ray for a set of source primitives.
* Like surface types, source type definitions are immutable plain data and are
* structurally deduplicated during preprocessing, with object identity as a
* fast path. The DAG uses the format implemented by the formula utilities in
* `src/core/formula`.
*
* The reserved DAG inputs are `i`, the zero-based invocation index, and `N`,
* the source primitive's total `rayCount`. Both are integer-valued formula
* scalars and satisfy `0 <= i < N`. An engine only needs to materialize either
* input when the DAG references it.
*
* The DAG must contain the seven labeled outputs `x`, `y`, `d_x`, `d_y`,
* `P_s`, `P_p`, and `lambda`. `x` and `y` are the emitted ray's world-space
* starting position; `d_x` and `d_y` are its unit world-space direction;
* `P_s` and `P_p` are its s- and p-polarized powers; and `lambda` is its
* wavelength in nm. An invocation whose two powers are both zero is ignored.
* A source invocation has no incoming ray and emits only one ray, so these
* output labels do not use ray-index subscripts.
*
* Sampling density, brightness limits, color mode, and source-specific
* rounding are resolved by the scene object when it creates the primitive.
* They affect `rayCount` and may produce derived entries in `params`, but raw
* requested or effective ray density is not a reserved DAG input. The source
* format also has no `gap`, `isNew`, or random-number output.
*
* @typedef {Object} SourceType
* @property {string} name - A human-readable diagnostic name, not a registry ID.
* @property {string[]} paramNames - The formula symbols and keys accepted in a source primitive's `params` object. Their order defines the packed parameter layout and is therefore significant, particularly for WebGPU buffers. Names must not collide with the reserved inputs `i` and `N`.
* @property {Object} dag - The formula DAG containing the seven required labeled ray outputs.
*/
/**
* A ray source. It has no geometry field: its ray distribution is defined
* entirely by `sourceType` and `params`. A source never contains a
* {@link PrimitiveCurve} and is not inserted into the BVH. The scene object
* calculates `rayCount` and any derived sampling parameters each time
* `getPrimitives()` is called, so changing global sampling settings does not
* require a separate sizing DAG.
* @typedef {Object} LightSourcePrimitive
* @property {'source'} kind
* @property {SourceType} sourceType - The shared per-invocation ray definition.
* @property {Object<string, number>} params - Numeric instance and derived sampling parameters matching `sourceType.paramNames`.
* @property {number} rayCount - The nonnegative integer number of source-formula invocations.
*/
/**
* An optical surface represented by one oriented curve. For a one-sided
* surface, an intersection from behind the curve's front normal is ignored
* completely: it is not a surface hit and does not participate in surface
* merging or ray-depth accounting. Sidedness is interaction policy rather
* than curve geometry and is therefore stored at the primitive's top level.
* @typedef {Object} SurfacePrimitive
* @property {'surface'} kind
* @property {PrimitiveCurve} curve - The surface geometry.
* @property {boolean} twoSided - Whether rays approaching from either side can interact. If false, only rays approaching against the curve's front normal can interact.
* @property {WavelengthFilter} [filter] - An optional pre-intersection wavelength filter. Omit this property when filtering is disabled.
* @property {SurfaceType} surfaceType - The shared surface behavior definition.
* @property {Object<string, number>} params - Numeric instance parameters matching `surfaceType.paramNames`.
*/
/**
* Defines the relative refractive-index field shared by a set of region
* primitives. Like surface types, bulk type definitions are immutable plain
* data and are structurally deduplicated during preprocessing, with object
* identity as a fast path. The DAG uses the format implemented by the formula
* utilities in `src/core/formula` and must contain the labeled scalar outputs
* `n` and `alpha`.
*
* The reserved DAG inputs are `x` and `y`, the world-space position at which
* the field is evaluated, and `lambda`, the ray wavelength in nm. Direction,
* polarization, and power are not inputs because bulk media are isotropic.
* Coordinate origins or other instance-specific transformations are expressed
* through `params`.
*
* `alpha` is the local power-absorption coefficient in inverse scene-length
* units. It is applied only when the engine performs a GRIN step: a step of
* length `L` multiplies both polarized powers by `exp(-alpha * L)`. A segment
* ending at a boundary, authored surface, or detector does not apply a
* partial-step absorption update. Thus zero represents no bulk absorption,
* positive values absorb light, and negative values represent gain.
*
* The formula compiler symbolically derives `n_x` and `n_y`, the partial
* derivatives of `n` with respect to world-space `x` and `y`. They are part of
* the compiled evaluator used for GRIN propagation, not independently authored
* DAG outputs. A wavelength-dependent field with no spatial dependence is
* still homogeneous and has zero spatial derivatives.
*
* @typedef {Object} BulkType
* @property {string} name - A human-readable diagnostic name, not a registry ID.
* @property {string[]} paramNames - The formula symbols and keys accepted in a region primitive's `params` object. Their order defines the packed parameter layout and is therefore significant, particularly for WebGPU buffers. Names must not collide with the reserved bulk-DAG symbols.
* @property {Object} dag - The formula DAG containing the required labeled outputs `n` and `alpha`.
*/
/**
* A bulk optical region. The curves must collectively form a valid closed
* boundary whose interior follows the even-odd rule. Curve order is
* irrelevant, and reversing the direction of every curve does not change the
* region. Pieces forming one smooth boundary must nevertheless have
* consistent directions. A geometrically self-overlapping boundary section
* must be traversed in opposite directions by its coincident pieces.
*
* At a merged incident point, the engine records whether each region has at
* least one positive crossing and at least one negative crossing. Repeated
* crossings with the same sign are idempotent; the region changes parity only
* when exactly one sign is present. Under the direction constraints above,
* this is equivalent to the even-odd rule while remaining insensitive to
* duplicate hits at endpoints of consecutive curve pieces.
*
* Each curve belongs only to this region, even when its geometry coincides
* with another primitive's curve.
*
* Region-boundary refraction and reflection are built into the engine rather
* than represented by a formula-defined surface type. When `partialReflect`
* is true, a transmissible incident ray produces Fresnel-reflected and
* transmitted rays. When false, it produces only the transmitted ray. Total
* internal reflection still produces a reflected ray in either case. More
* advanced boundary behavior, such as coatings, is represented by overlapping
* surface primitives instead of additional engine-internal boundary modes.
*
* @typedef {Object} RegionPrimitive
* @property {'region'} kind
* @property {PrimitiveCurve[]} curves - The region boundary curves.
* @property {BulkType} bulkType - The shared refractive-index field definition.
* @property {Object<string, number>} params - Numeric instance parameters matching `bulkType.paramNames`.
* @property {number} stepSize - The nonnegative interior propagation step size in scene units. Zero denotes a spatially homogeneous region; a positive value enables GRIN propagation.
* @property {boolean} partialReflect - Whether transmissible boundary interactions generate the Fresnel-reflected ray in addition to the transmitted ray.
*/
/**
* A mutable holder for the completed result of one logical detector output.
* Object identity defines the output: detector primitives which reference the
* same holder accumulate into the same result array, while different holders
* remain separate even when they belong to the same scene object. This lets a
* scene object expose several detector outputs without assigning IDs, and lets
* wrappers such as ModuleObj forward child primitives unchanged.
*
* The holder is preprocessing-only metadata and is never sent to a simulation
* engine or worker. PrimitiveBasedSimulator assigns it a numeric result ID,
* retains the reverse binding, and updates `values` only after readback from a
* successfully completed current run. Cancelled, failed, and superseded runs
* leave it unchanged.
*
* @typedef {Object} DetectorResult
* @property {ArrayLike<number>|null} values - The most recently completed logical result values, or null before a result is available. The simulator may replace this value or update a compatible preallocated typed array.
*/
/**
* Defines how one detector hit contributes to a logical result array.
* Detector types use the same immutable, structurally deduplicated plain-data
* convention as surface types. Their DAG accepts the reserved hit inputs
* documented by {@link SurfaceType}, except for `n_0` and `n_1`, together
* with the instance parameters named by `paramNames`. Detectors observe ray
* power rather than participating in refraction, so detector types must not
* declare or reference `n_0` or `n_1`.
*
* `writeCount` is the fixed positive number of result writes produced by one
* hit. For every one-based write index `j` from 1 through `writeCount`, the DAG
* must contain the two labeled scalar outputs `k_j` and `v_j`. The engine
* accumulates each pair as:
*
* ```
* result[k_j] += v_j
* ```
*
* `k_j` must evaluate to an integer index within the detector primitive's
* logical `resultSize`. `v_j` is a real-valued contribution. This contract
* does not prescribe an engine's storage representation or accumulation
* method; a CPU engine may accumulate floating-point values directly, while
* another engine may use a different internal representation and convert it
* during readback.
*
* @typedef {Object} DetectorType
* @property {string} name - A human-readable diagnostic name, not a registry ID.
* @property {string[]} paramNames - The formula symbols and keys accepted in a detector primitive's `params` object. Their order defines the packed parameter layout and is therefore significant.
* @property {Object} dag - The formula DAG containing the required `k_j` and `v_j` labeled outputs.
* @property {number} writeCount - The constant positive number of result writes produced by each hit.
*/
/**
* A detector represented by one oriented curve. Its type defines how ray
* incidents are accumulated and exposed as detector results. For a one-sided
* detector, an intersection from behind the curve's front normal is ignored
* and produces no detector reading. Detector formulas use the reserved
* hit-input symbols documented by {@link SurfaceType}, except `n_0` and
* `n_1`, and include `u` and `sigma` for
* distinguishing the two geometric sides; they define detector outputs rather
* than outgoing-ray slots.
* @typedef {Object} DetectorPrimitive
* @property {'detector'} kind
* @property {PrimitiveCurve} curve - The detector geometry.
* @property {boolean} twoSided - Whether rays approaching from either side can be detected. If false, only rays approaching against the curve's front normal are detected.
* @property {DetectorType} detectorType - The shared detector behavior definition.
* @property {Object<string, number>} params - Numeric instance parameters matching `detectorType.paramNames`.
* @property {number} resultSize - The positive integer length of the logical result array. Primitives sharing a `result` holder must specify the same size.
* @property {DetectorResult} result - The result holder. Its object identity associates this primitive with other detector surfaces and with the scene-object state used by `draw()` and result collection.
*/
/**
* A primitive returned by {@link BaseSceneObj#getPrimitives}.
* @typedef {LightSourcePrimitive|SurfacePrimitive|RegionPrimitive|DetectorPrimitive} Primitive
*/