/*
* 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.
*/
const GRIN_STEP_TYPE_INDEX = 0;
const REGION_BOUNDARY_TYPE_INDEX = 1;
const PARTIAL_REFLECTION_REGION_BOUNDARY_TYPE_INDEX = 2;
const FIXED_TYPE_COUNT = 3;
/**
* Build the interaction-type metadata once for a prepared scene. The layout
* remains unchanged between ping-pong iterations.
*
* @param {Object} description
* @returns {Object}
*/
export function createInteractionTypeLayout(description) {
const types = [
createType('grinStep', -1, false, 'GRIN step', 1),
createType(
'regionBoundary',
-1,
false,
'Region boundary without partial reflection',
1
),
createType(
'regionBoundary',
-1,
true,
'Region boundary with partial reflection',
2
)
];
for (let typeId = 0;
typeId < description.types.surfaces.length;
typeId++) {
const definition =
description.types.surfaces[typeId].definition;
types.push(createType(
'surface',
typeId,
false,
definition.name,
definition.outRayCount
));
}
const detectorTypeOffset = types.length;
for (let typeId = 0;
typeId < description.types.detectors.length;
typeId++) {
const definition =
description.types.detectors[typeId].definition;
// A detector records its result and lets the incident ray continue.
types.push(createType(
'detector',
typeId,
false,
definition.name,
1
));
}
return {
types,
surfaceTypeOffset: FIXED_TYPE_COUNT,
detectorTypeOffset
};
}
/**
* Create the mutable per-iteration buffers for a fixed type layout.
*
* @param {Object} layout
* @returns {Object[]}
*/
export function createInteractionIndexBuffers(layout) {
return layout.types.map(type => ({
...type,
interactionCount: 0,
sourceRayIndices: new Uint32Array(0),
destinationRayStart: 0
}));
}
/**
* Reset mutable counts and arrays without rebuilding the fixed type metadata.
*
* @param {Object[]} buffers
*/
export function resetInteractionIndexBuffers(buffers) {
for (const buffer of buffers) {
buffer.interactionCount = 0;
buffer.sourceRayIndices = new Uint32Array(0);
buffer.destinationRayStart = 0;
}
}
/**
* Return the fixed interaction-type index for one source-ray hit, or -1 when
* the ray is discarded or escapes to infinity.
*
* @param {Object} description
* @param {Object} layout
* @param {Object} hit
* @returns {number}
*/
export function getInteractionTypeIndex(description, layout, hit) {
if (hit.curveId === -1) {
return Number.isFinite(hit.s) && hit.s > 0
? GRIN_STEP_TYPE_INDEX
: -1;
}
if (hit.curveId < 0) return -1;
const curve = description.curves[hit.curveId];
switch (curve.ownerKind) {
case 'surface':
return layout.surfaceTypeOffset +
description.surfaces[curve.ownerId].surfaceTypeId;
case 'detector':
return layout.detectorTypeOffset +
description.detectors[curve.ownerId].detectorTypeId;
case 'region':
return hasPartialReflection(description, hit)
? PARTIAL_REFLECTION_REGION_BOUNDARY_TYPE_INDEX
: REGION_BOUNDARY_TYPE_INDEX;
default:
throw new TypeError(
`Unsupported curve owner kind: ${JSON.stringify(curve.ownerKind)}`
);
}
}
/**
* Allocate exact-sized source-index buffers and their slot-major destination
* ranges after the counting pass.
*
* @param {Object[]} buffers
* @returns {number} Total number of slots required in the destination buffer.
*/
export function allocateInteractionIndexBuffers(buffers) {
let destinationRayCount = 0;
for (const buffer of buffers) {
buffer.sourceRayIndices =
new Uint32Array(buffer.interactionCount);
buffer.destinationRayStart = destinationRayCount;
destinationRayCount +=
buffer.outRayCount * buffer.interactionCount;
}
return destinationRayCount;
}
function createType(
kind,
typeId,
partialReflect,
name,
outRayCount
) {
return {
kind,
typeId,
partialReflect,
name,
outRayCount
};
}
function hasPartialReflection(description, hit) {
for (let regionId = 0;
regionId < hit.regionCrossingMask.length;
regionId++) {
if (
hit.regionCrossingMask[regionId] &&
description.regions[regionId].partialReflect
) {
return true;
}
}
return false;
}