# Primitives

All geometry is built up from a small(ish) number of primitives and a number of constructive solid geometry (CSG) operations (see CSG). Primitives are split into two types, `Surface`

s and `ParametricSurface`

s, the latter being a subset of the former. `Surface`

s are standalone surfaces which cannot be used in CSG operations, e.g. an aperture or rectangle. `ParametricSurface`

s are valid csg objects and can be composed into very complex structures.

## Surfaces

A surface can be any surface in 3D space, it can be bounded and not create a half-space (i.e. not partition space into *inside* and *outside*).

`OpticSim.Surface`

— Type`Surface{T<:Real}`

`T`

is the number type used to represent the surface, e.g., `Float64`

. Basic `Surface`

s are *not* valid CSG objects, they function only in a stand-alone capacity.

**Must** implement the following:

```
surfaceintersection(surface::Surface{T}, ray::AbstractRay{T,3}) -> Union{EmptyInterval{T},Interval{T}}
normal(surface::Surface{T}) -> SVector{3,T}
interface(surface::Surface{T}) -> OpticalInterface{T}
makemesh(surface::Surface{T}) -> TriangleMesh{T}
```

In a conventional ray tracer the surface intersection function would only return the first surface the ray intersects. Because our ray tracer does CSG operations the surface intersection function intersects the ray with all leaf surfaces which are part of the CSG tree.

Each leaf surface returns one or more 1D intervals along the ray. These intervals contain the part of the ray which is inside the surface. The intervals computed at the leaves are propagated upward through the CSG tree and the CSG operations of union, intersection, and difference are applied to generate new intervals which are themselves propagated upward.

The result is a union of 1D intervals, which may be disjoint, a single interval, or empty. The union of intervals represents the parts of the ray which are inside the CSG object.

Inside is well defined for halfspaces such as cylinders and spheres which divide space into two parts, but not for Bezier or NURBS patches which generally do not enclose a volume. For surfaces which are not halfspaces the notion of inside is defined locally by computing the angle between the incoming ray and the normal of the surface at the point of intersection. All surfaces must be defined so that the normal points to the outside of the surface.

A negative dot product between the incoming ray and the normal indicates the ray is coming from the outside of the surface and heading toward the inside. A positive dot product indicates the ray is coming from the inside of the surface and heading toward the outside.

Intervals are defined along the ray which is being intersected with the surface, so they are one dimensional. For example, assume we have a ray with origin o on the outside of a plane and an intersection with the plane at point int = o + td where t is a scalar and d is the unit direction of the ray. The inside interval will be (Intersection(t),Infinity). This interval begins at the intersection point on the plane and continues to positive infinity. The Intersection struct stores both the parametric value t and the 3D point of intersection to make various operations more efficient. But the interval operations only depend on the parametric value t.

If the origin o is on the inside of the plane then the inside interval will be (RayOrigin,Intersection(t)). Only the part of the ray from the ray origin to the intersection point is inside the plane.

It is the programmer's responsibility to return Interval results from surfaceintersection that maintain these properties.

The following must be impemented only if the surface is being used as a detector

```
uv(surface::Surface{T}, p::SVector{3,T}) -> SVector{2,T}
uvtopix(surface::Surface{T}, uv::SVector{2,T}, imsize::Tuple{Int,Int}) -> Tuple{Int,Int}
onsurface(surface::Surface{T}, p::SVector{3,T}) -> Bool
```

### Basic Shapes

These are the simple shapes with are provided already, they act only as standalone objects and cannot be used in CSG objects. Adding a new `Surface`

is easy, the new structure must simply follow the interface defined above.

`OpticSim.Ellipse`

— Type`Ellipse{T} <: Surface{T}`

Elliptical surface, not a valid CSG object. The rotation of the rectangle around its normal is defined by `rotationvec`

. `rotationvec×surfacenormal`

is taken as the vector along the u axis.

**Can be used as a detector in AbstractOpticalSystems.**

`Ellipse(halfsizeu::T, halfsizev::T, [surfacenormal::SVector{3,T}, centrepoint::SVector{3,T}]; interface::NullOrFresnel{T} = nullinterface(T))`

The minimal case returns an ellipse centered at the origin with `surfacenormal = [0, 0, 1]`

.

`OpticSim.Circle`

— Function`Circle(radius, [surfacenormal, centrepoint]; interface = nullinterface(T))`

Shortcut method to create a circle. The minimal case returns a circle centred at the origin with `normal = [0, 0, 1]`

.

`OpticSim.Rectangle`

— Type`Rectangle{T} <: Surface{T}`

Rectangular surface, not a valid CSG object. The rotation of the rectangle around its normal is defined by `rotationvec`

. `rotationvec×surfacenormal`

is taken as the vector along the u axis.

**Can be used as a detector in AbstractOpticalSystems.**

`Rectangle(halfsizeu::T, halfsizev::T, [surfacenormal::SVector{3,T}, centrepoint::SVector{3,T}]; rotationvec::SVector{3,T} = [0.0, 1.0, 0.0], interface::NullOrFresnel{T} = nullinterface(T))`

The minimal case returns a rectangle centered at the origin with `surfacenormal = [0, 0, 1]`

.

`OpticSim.Hexagon`

— Type`Hexagon{T} <: Surface{T}`

Hexagonal surface, not a valid CSG object. The rotation of the hexagon around its normal is defined by `rotationvec`

. `rotationvec×surfacenormal`

is taken as the vector along the u axis.

`Hexagon(side_length::T, [surfacenormal::SVector{3,T}, centrepoint::SVector{3,T}]; rotationvec::SVector{3,T} = [0.0, 1.0, 0.0], interface::NullOrFresnel{T} = nullinterface(T))`

The minimal case returns a rectangle centered at the origin with `surfacenormal = [0, 0, 1]`

.

`OpticSim.Triangle`

— Type`Triangle{T} <: Surface{T}`

Triangular surface, not a valid CSG object. Primarily used as a component part of `TriangleMesh`

or to enable intersection of `AcceleratedParametricSurface`

s. Can never be used directly as an optical surface as it doesn't have an `OpticalInterface`

.

`Triangle(v1::SVector{3,T}, v2::SVector{3,T}, v3::SVector{3,T}, [uv1::SVector{2,T}, uv2::SVector{2,T}, uv3::SVector{2,T}])`

`OpticSim.TriangleMesh`

— Type`TriangleMesh{T} <: Surface{T}`

An array of `Triangle`

s forming a mesh. Used for visualization purposes only.

`TriangleMesh(tris::Vector{Triangle{T}})`

### Stops

A number of simple occlusive apertures are provided as constructing such objects using CSG can be inefficient and error-prone.

`OpticSim.InfiniteStop`

— Type`InfiniteStop{T,P<:StopShape} <: Surface{T}`

Stop surface with infinite extent (outside of the aperture). `P`

refers to the shape of the aperture.

`OpticSim.FiniteStop`

— Type`FiniteStop{T,P<:StopShape,Q<:StopShape} <: Surface{T}`

Stop surface with finite extent. `P`

refers to the shape of the aperture and `Q`

represents the shape of the bounds of the stop surface.

`OpticSim.RectangularAperture`

— Function`RectangularAperture(aphalfsizeu::T, aphalfsizev::T, surfacenormal::SVector{3,T}, centrepoint::SVector{3,T}; rotationvec::SVector{3,T} = [0.0, 1.0, 0.0])`

Creates a rectangular aperture in a plane i.e. `InfiniteStop{T,RectangularStopShape}`

. The rotation of the rectangle around its normal is defined by `rotationvec`

. `rotationvec×surfacenormal`

is taken as the vector along the u axis.

`RectangularAperture(innerhalfsizeu::T, innerhalfsizev::T, outerhalfsizeu::T, outerhalfsizev::T, surfacenormal::SVector{3,T}, centrepoint::SVector{3,T}; rotationvec::SVector{3,T} = [0.0, 1.0, 0.0])`

Creates a rectangular aperture in a rectangle i.e. `FiniteStop{T,RectangularStopShape,RectangularStopShape}`

. The rotation of the rectangle around its normal is defined by `rotationvec`

. `rotationvec×surfacenormal`

is taken as the vector along the u axis.

`OpticSim.CircularAperture`

— Function`CircularAperture(radius::T, surfacenormal::SVector{3,T}, centrepoint::SVector{3,T})`

Creates a circular aperture in a plane i.e. `InfiniteStop{T,CircularStopShape}`

.

`CircularAperture(radius::T, outerhalfsizeu::T, outerhalfsizev::T, surfacenormal::SVector{3,T}, centrepoint::SVector{3,T}; rotationvec::SVector{3,T} = [0.0, 1.0, 0.0])`

Creates a circular aperture in a rectangle i.e. `FiniteStop{T,CircularStopShape,RectangularStopShape}`

. The rotation of the rectangle around its normal is defined by `rotationvec`

. `rotationvec×surfacenormal`

is taken as the vector along the u axis.

`OpticSim.Annulus`

— Function`Annulus(innerradius::T, outerradius::T, surfacenormal::SVector{3,T}, centrepoint::SVector{3,T})`

Creates a circular aperture in a circle i.e. `FiniteStop{T,CircularStopShape,CircularStopShape}`

.

## Parametric Surfaces

A parametric surface must partition space into two valid half-spaces, i.e. *inside* and *outside*. The surface must also be parameterized by two variables, nominally `u`

and `v`

. Typically these surfaces cannot be intersected with a ray analytically and so must be triangulated and an iterative solution found.

`OpticSim.ParametricSurface`

— Type`ParametricSurface{T,N} <: Surface{T}`

`T`

is the number type used to represent the surface, e.g., `Float64`

. `N`

is the dimension of the space the surface is embedded in. `ParametricSurface`

s are valid CSG objects, in some cases (where analytic intersection isn't possible) they must be wrapped in an `AcceleratedParametricSurface`

for use.

**Must** implement the following:

```
uv(surface::ParametricSurface{T,N}, p::SVector{N,T}) -> SVector{2,T}
uvrange(surface::ParametricSurface{T,N}) -> Tuple{Tuple{T,T},Tuple{T,T}}
point(surface::ParametricSurface{T,N}, u::T, v::T) -> SVector{N,T}
partials(surface::ParametricSurface{T,N}, u::T, v::T) -> Tuple{SVector{N,T}, SVector{N,T}}
normal(surface::ParametricSurface{T,N}, u::T, v::T) -> SVector{N,T}
inside(surface::ParametricSurface{T,N}, p: :SVector{N,T}) -> Bool
onsurface(surface::ParametricSurface{T,N}, p::SVector{N,T}) -> Bool
surfaceintersection(surface::ParametricSurface{T,N}, AbstractRay::Ray{T,N}) -> Union{EmptyInterval{T},Interval{T},DisjointUnion{T}}
interface(surface::ParametricSurface{T,N}) -> OpticalInterface{T}
```

`OpticSim.AcceleratedParametricSurface`

— Type`AcceleratedParametricSurface{T,N,S} <: ParametricSurface{T,N}`

Wrapper class for `ParametricSurface`

s where analytical intersection isn't feasible (e.g. `ZernikeSurface`

, `ChebyshevSurface`

). The surface is instead triangulated and an iterative (newton raphson) process carried out to determine precise ray intersection points. `S`

is the type of the ParametricSurface being wrapped.

`AcceleratedParametricSurface(surf::ParametricSurface{T,N}, numsamples::Int = 17; interface::NullOrFresnel{T} = nullinterface(T))`

### Parametric Surface Types

These are the available types of parametric surfaces which are already implemented, all of which can be used in the creation of CSG objects. New `ParametricSurface`

s can be added with relative ease providing they follow the interface defined above.

`OpticSim.Cylinder`

— Type`Cylinder{T,N} <: ParametricSurface{T,N}`

Cylinder of infinite height centered at the origin, oriented along the z-axis. `visheight`

is used for visualization purposes only, **note that this does not fully represent the surface**.

`Cylinder(radius::T, visheight::T = 2.0; interface::NullOrFresnel{T} = nullinterface(T))`

`OpticSim.Plane`

— Type`Plane{T,N} <: ParametricSurface{T,N}`

Infinite planar surface where the positive normal side is outside the surface.

By default this will not create any geometry for visualization, the optional `vishalfsizeu`

and `vishalfsizev`

arguments can be used to draw the plane as a rectangle for visualization **note that this does not fully represent the surface**. In this case, the rotation of the rectangle around the normal to the plane is defined by `visvec`

- `surfacenormal×visvec`

is taken as the vector along the u axis.

```
Plane(surfacenormal::SVector{N,T}, pointonplane::SVector{N,T}; interface::NullOrFresnel{T} = nullinterface(T), vishalfsizeu::T = 0.0, vishalfsizev::T = 0.0, visvec::SVector{N,T} = [0.0, 1.0, 0.0])
Plane(nx::T, ny::T, nz::T, x::T, y::T, z::T; interface::NullOrFresnel{T} = nullinterface(T), vishalfsizeu::T = 0.0, vishalfsizev::T = 0.0, visvec::SVector{N,T} = [0.0, 1.0, 0.0])
```

`OpticSim.Sphere`

— Type`Sphere{T,N} <: ParametricSurface{T,N}`

Spherical surface centered at the origin.

`Sphere(radius::T = 1.0; interface::NullOrFresnel{T} = nullinterface(T))`

`OpticSim.SphericalCap`

— Type`SphericalCap{T} <: ParametricSurface{T}`

Spherical cap surface, creates a half-space which is essentially the subtraction of a sphere from an infinite plane. Only the spherical cap itself is visualized, not the plane. The positive normal side is outside the surface.

**Can be used as a detector in AbstractOpticalSystems.**

`SphericalCap(radius::T, ϕmax::T, [surfacenormal::SVector{3,T}, centrepoint::SVector{3,T}]; interface::NullOrFresnel{T} = nullinterface(T))`

The minimal case returns a spherical cap centered at the origin with `surfacenormal = [0, 0, 1]`

.

`OpticSim.AsphericSurface`

— Type`AsphericSurface{T,N,Q,M} <: ParametricSurface{T,N}`

Surface incorporating an aspheric polynomial - radius, conic and aspherics are defined relative to absolute semi-diameter,. `T`

is the datatype, `N`

is the dimensionality, `Q`

is the number of aspheric terms, and `M`

is the type of aspheric polynomial.

`AsphericSurface(semidiameter; radius, conic, aspherics=nothing, normradius = semidiameter)`

The surface is centered at the origin and treated as being the cap of an infinite cylinder, thus creating a true half-space. Outside of `0 <= ρ <= 1`

the height of the surface is not necessarily well defined, so NaN may be returned.

`aspherics`

aspherics should be vectors containing tuples of the form (i, v) where i is the polynomial power of the aspheric term. An empty vector is not permitted. Use `nothing`

instead.

The sag is defined by the equation

\[z(r,\phi) = \frac{cr^2}{1 + \sqrt{1 - (1+k)c^2r^2}} + \sum_{i}^{Q}\alpha_ir^{i}\]

where $\rho = \frac{r}{\texttt{normradius}}$, $c = \frac{1}{\texttt{radius}}$, and $k = \texttt{conic}$ .

The function checks if the aspheric terms are missing, even, odd or both and uses an efficient polynomial evaluation strategy.

`OpticSim.ZernikeSurface`

— Type`ZernikeSurface{T,N,P,Q,M} <: ParametricSurface{T,N}`

Surface incorporating the Zernike polynomials - radius, conic and aspherics are defined relative to absolute semi-diameter, Zernike terms are normalized according to the `normradius`

parameter. `T`

is the datatype, `N`

is the dimensionality, `P`

is the number of Zernike terms, `Q`

is the number of aspheric terms and `M`

is the Aspheric Type.

The surface is centered at the origin and treated as being the cap of an infinite cylinder, thus creating a true half-space. Outside of `0 <= ρ <= 1`

the height of the surface is not necessarily well defined, so NaN may be returned.

For convenience the input `zcoeff`

can be indexed using either OSA or Noll convention, indicated using the `indexing`

argument as either `ZernikeIndexingOSA`

or `ZernikeIndexingNoll`

.

`ZernikeSurface(semidiameter, radius = Inf, conic = 0, zcoeff = nothing, aspherics = nothing, normradius = semidiameter, indexing = ZernikeIndexingOSA)`

`zcoeff`

and `aspherics`

should be vectors containing tuples of the form `(i, v)`

where `i`

is either the index of the Zernike term for the corresponding `indexing`

, or the polynomial power of the aspheric term (may be even or odd) and `v`

is the corresponding coefficient $A_i$ or $\alpha_i$ respectively.. `M`

will be determined from the terms entered to optimize the evaluation of the aspheric polynomial.

The sag is defined by the equation

\[z(r,\phi) = \frac{cr^2}{1 + \sqrt{1 - (1+k)c^2r^2}} + \sum_{i}^{Q}\alpha_ir^{2i} + \sum_{i}^PA_iZ_i(\rho, \phi)\]

where $\rho = \frac{r}{\texttt{normradius}}$, $c = \frac{1}{\texttt{radius}}$, $k = \texttt{conic}$ and $Z_n$ is the nᵗʰ Zernike polynomial.

`OpticSim.BezierSurface`

— Type`BezierSurface{P,S,N,M} <: SplineSurface{P,S,N,M}`

Bezier surface defined by grid of control points.

This surface does not create a valid half-space, requires updates to function correctly.

`BezierSurface{P,S,N,M}(controlpoints::AbstractArray{<:AbstractArray{S,1},2})`

`OpticSim.BSplineSurface`

— Type`BSplineSurface{P,S,N,M} <: SplineSurface{P,S,N,M}`

Curve order is the same in the u and v direction and fixed over all spans. u and v knot vectors are allowed to be different - *may change this to make them both the same*.

Control points in the u direction correspond to columns, with the lowest value of u corresponding to row 1. Control points in the v direction correspond to rows, with the lowest value of v corresponding to col 1.

This surface does not create a valid half-space, requires updates to function correctly.

```
BSplineSurface{P,S,N,M}(knots::KnotVector{S}, controlpoints::AbstractArray{<:AbstractArray{S,1},2})
BSplineSurface{P,S,N,M}(uknots::KnotVector{S}, vknots::KnotVector{S}, controlpoints::AbstractArray{<:AbstractArray{S,1},2})
```

`OpticSim.QTypeSurface`

— Type`QTypeSurface{T,D,M,N} <: ParametricSurface{T,D}`

Surface incorporating the QType polynomials - radius and conic are defined relative to absolute semi-diameter, QType terms are normalized according to the `normradius`

parameter. `T`

is the datatype, `D`

is the dimensionality, `M`

and `N`

are the maximum QType terms used.

The surface is centered at the origin and treated as being the cap of an infinite cylinder, thus creating a true half-space. Outside of 0 <= ρ <= 1 the height of the surface is not necessarily well defined, so NaN may be returned.

`QTypeSurface(semidiameter; radius = Inf, conic = 0.0, αcoeffs = nothing, βcoeffs = nothing, normradius = semidiameter)`

`αcoeffs`

and `βcoeffs`

should be a vector of tuples of the form `(m, n, v)`

where `v`

is the value of the coefficient $α_n^m$ or $β_n^m$ respectively.

The sag is defined by the equation

\[\begin{aligned} z(r,\phi) = & \frac{cr^2}{1 + \sqrt{1 - (1+k)c^2r^2}} + \frac{\sqrt{1 + kc^2r^2}}{\sqrt{1-(1+k)c^2r^2}} \cdot \\ & \left\{ \rho^2(1-\rho^2)\sum_{n=0}^{N}\alpha_n^0 Q_n^0 (\rho^2) + \sum_{m=1}^{M}\rho^m\sum_{n=0}^N \left[ \alpha_n^m\cos{m\phi} +\beta_n^m\sin{m\phi}\right]Q_n^m(\rho^2) \right\} \end{aligned}\]

where $\rho = \frac{r}{\texttt{normradius}}$, $c = \frac{1}{\texttt{radius}}$, $k = \texttt{conic}$ and $Q_n^m$ is the QType polynomial index $m$, $n$.

`OpticSim.ChebyshevSurface`

— Type`ChebyshevSurface{T,N,P,Q} <: ParametricSurface{T,N}`

Rectangular surface incorporating Chebyshev polynomials as well as radius and conic terms. `T`

is the datatype, `N`

is the dimensionality, `P`

is the number of Chebyshev terms in u and `Q`

is the number of Chebyshev terms in v.

The surface is centered at the origin and treated as being the cap of an infinite rectangular prism, thus creating a true half-space. **Note that the surface is vertically offset so that the center (i.e., (u,v) == (0,0)) lies at 0 on the z-axis.**

`ChebyshevSurface(halfsizeu, halfsizev, chebycoeff; radius = Inf, conic = 0)`

`chebycoeff`

is a vector containing tuples of the form `(i, j, v)`

where `v`

is the value of the coefficient $c_{ij}$.

The sag is defined by the equation

\[z(u,v) = \frac{c(u^2 + v^2)^2}{1 + \sqrt{1 - (1+k)c^2(u^2 + v^2)}} + \sum_{i}^{P}\sum_{j}^{Q}c_{ij}T_i(u)T_j(v)\]

where $c = \frac{1}{\texttt{radius}}$, $k = \texttt{conic}$ and $T_n$ is the nᵗʰ Chebyshev polynomial of the first kind.

`OpticSim.GridSagSurface`

— Type`GridSagSurface{T,N,S<:Union{ZernikeSurface{T,N},ChebyshevSurface{T,N}},Nu,Nv} <: ParametricSurface{T,N}`

Either a Zernike (circular) or Chebyshev (rectangular) surface with grid sag height added to the base sag. The surface shape is determined by either a linear or a bicubic spline interpolation of the `Nu×Nv`

grid of sag values, set by the `interpolation`

argument taking either `GridSagLinear`

or `GridSagBicubic`

.

Each entry in the grid is a vector of the form $[z, \frac{\partial z}{\partial x}, \frac{\partial z}{\partial y}, \frac{\partial^2 z}{\partial x \partial y}]$. The first data item corresponds to the lower left corner of the surface, that is, the corner defined by the -u and -v limit. Each point that follows is read across the face of the surface from left to right moving upwards. If zero is given for the partials (and using bicubic interpolation) then the partials will be approximated using finite differences.

The sag grid can be decentered from the surface in uv space, if so the surface may become wild outside of the area over which the grid is defined. It is advised to clip the surface to the valid area using CSG operations in this case.

A surface can also be generated from a `.GRD`

file by passing in the filename as the first and only positional argument. In this case the surface will be rectangular with optional radius and conic.

See docs for `ZernikeSurface`

and `ChebyshevSurface`

for details of the base surface.

```
GridSagSurface(basesurface::Union{ZernikeSurface{T,N},ChebyshevSurface{T,N}}, sag_grid::AbstractArray{T,3}; interpolation = GridSagBicubic, decenteruv = (0, 0))
GridSagSurface{T}(filename::String; radius = Inf, conic = 0, interpolation = GridSagBicubic)
```

## Functions

These are some useful functions related to `Surface`

objects.

`OpticSim.point`

— Method```
point(surf::ParametricSurface{T}, u::T, v::T) -> SVector{3,T}
point(surf::ParametricSurface{T}, uv::SVector{2,T}) -> SVector{3,T}
```

Returns the 3D point on `surf`

at the given uv coordinate.

`OpticSim.normal`

— Function```
normal(surf::ParametricSurface{T}, u::T, v::T) -> SVector{3,T}
normal(surf::ParametricSurface{T}, uv::SVector{2,T}) -> SVector{3,T}
```

Returns the normal to `surf`

at the given uv coordinate.

`OpticSim.partials`

— Method```
partials(surf::ParametricSurface{T}, u::T, v::T) -> (SVector{3,T}, SVector{3,T})
partials(surf::ParametricSurface{T}, uv::SVector{2,T}) -> (SVector{3,T}, SVector{3,T})
```

Returns a tuple of the 3D partial derivatives of `surf`

with respect to u and v at the given uv coordinate.

`OpticSim.uvrange`

— Function```
uvrange(s::ParametricSurface)
uvrange(::Type{S}) where {S<:ParametricSurface}
```

Returns a tuple of the form: `((umin, umax), (vmin, vmax))`

specifying the limits of the parameterisation for this surface type. Also implemented for some `Surface`

s which are not `ParametricSurface`

s (e.g. `Rectangle`

).

`OpticSim.uv`

— Function```
uv(surf::ParametricSurface{T}, p::SVector{3,T}) -> SVector{2,T}
uv(surf::ParametricSurface{T}, x::T, y::T, z::T) -> SVector{2,T}
```

Returns the uv coordinate on `surf`

of a point, `p`

, in 3D space. If `onsurface(surf, p)`

is false then the behavior is undefined, it may return an inorrect uv, an invalid uv, NaN or crash.

`OpticSim.uvtopix`

— Function`uvtopix(surf::Surface{T}, uv::SVector{2,T}, imsize::Tuple{Int,Int}) -> Tuple{Int,Int}`

Converts a uvcoordinate on `surf`

to an integer index to a pixel in an image of size `imsize`

. Not implemented on all `Surface`

objects. Used to determine where in the detector image a ray has hit when in intersects the detector surface of an `AbstractOpticalSystem`

.

`OpticSim.inside`

— Method```
inside(surf::ParametricSurface{T}, p::SVector{3,T}) -> Bool
inside(surf::ParametricSurface{T}, x::T, y::T, z::T) -> Bool
```

Tests whether a 3D point in world space is *inside* `surf`

.

`OpticSim.onsurface`

— Method```
onsurface(surf::ParametricSurface{T}, p::SVector{3,T}) -> Bool
onsurface(surf::ParametricSurface{T}, x::T, y::T, z::T) -> Bool
```

Tests whether a 3D point in world space is *on* `surf`

.

`OpticSim.interface`

— Function`interface(surf::Surface{T}) -> OpticalInterface{T}`

Return the `OpticalInterface`

associated with `surf`

.

`OpticSim.surfaceintersection`

— Method`surfaceintersection(surf::Surface{T}, r::AbstractRay{T}) where {T}`

Calculates the intersection of `r`

with a surface of any type, `surf`

. Note that some surfaces cannot be intersected analytically so must be wrapped in an `AcceleratedParametricSurface`

in order to be intersected.

Returns an `EmptyInterval`

if there is no `Intersection`

, an `Interval`

if there is one or two intersections and a `DisjointUnion`

if there are more than two intersections.

`OpticSim.samplesurface`

— Function`samplesurface(surf::ParametricSurface{T,N}, samplefunction::Function, numsamples::Int = 30)`

Sample a parametric surface on an even `numsamples`

×`numsamples`

grid in UV space with provided function

`OpticSim.triangulate`

— Function`triangulate(surf::ParametricSurface{S,N}, quads_per_row::Int, extensionu::Bool = false, extensionv::Bool = false, radialu::Bool = false, radialv::Bool = false)`

Create an array of triangles representing the parametric surface where vertices are sampled on an even grid in UV space. The surface can be extended by 1% in u and v separately, and specifying either u or v as being radial - i.e. detemining the radius on the surface e.g. rho for zernike - will result in that dimension being sampled using sqwrt so that area of triangles is uniform. The extension will also only apply to the maximum in this case.

`OpticSim.makemesh`

— Function`makemesh(object, subdivisions::Int = 30) -> TriangleMesh`

Creates a `TriangleMesh`

from an object, either a `ParametricSurface`

, `CSGTree`

or certain surfaces (e.g. `Circle`

, `Rectangle`

). This is used for visualization purposes only.

`makemesh(poly::ConvexPolygon{N, T}, ::Int = 0) where {N, T<:Real} -> TriangleMesh`

Create a triangle mesh that can be rendered by iterating on the polygon's edges and for each edge use the centroid as the third vertex of the triangle.

## Bounding Boxes

Bounding boxes are mostly used internally for efficiency, but are also exposed to the user for visualization (and any other) purposes. All bounding boxes are axis aligned.

`OpticSim.BoundingBox`

— Type`BoundingBox{T<:Real}`

Axis-aligned three-dimensional bounding box.

```
BoundingBox(xmin::T, xmax::T, ymin::T, ymax::T, zmin::T, zmax::T)
BoundingBox(s::Surface{T})
BoundingBox(s::ParametricSurface{T,3}, transform::Transform{T} = identitytransform(T))
BoundingBox(c::CSGTree{T})
BoundingBox(tri::Triangle{T})
BoundingBox(triangles::AbstractVector{Triangle{T}})
BoundingBox(points::AbstractArray{SVector{3,T}})
BoundingBox(la::LensAssembly{T})
```

`OpticSim.doesintersect`

— Function`doesintersect(bbox::BoundingBox{T}, r::AbstractRay{T,3}) -> Bool`

Tests whether `r`

intersects an axis-aligned `BoundingBox`

, `bbox`

.

`OpticSim.surfaceintersection`

— Method`surfaceintersection(bbox::BoundingBox{T}, r::AbstractRay{T,3}) -> Union{EmptyInterval{T},Interval{T}}`

Calculates the intersection of `r`

with an axis-aligned `BoundingBox`

, `bbox`

.

Returns an `EmptyInterval`

if there is no intersection or an `Interval`

if there is one or two intersections. Note that the uv of the returned intersection is always **0**.