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, Surfaces and ParametricSurfaces, the latter being a subset of the former. Surfaces are standalone surfaces which cannot be used in CSG operations, e.g. an aperture or rectangle. ParametricSurfaces 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.SurfaceType
Surface{T<:Real}

T is the number type used to represent the surface, e.g., Float64. Basic Surfaces 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
source

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.EllipseType
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 OpticalSystems.

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

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

source
OpticSim.CircleFunction
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].

source
OpticSim.RectangleType
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 OpticalSystems.

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

source
OpticSim.HexagonType
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].

source
OpticSim.TriangleType
Triangle{T} <: Surface{T}

Triangular surface, not a valid CSG object. Primarily used as a component part of TriangleMesh or to enable intersection of AcceleratedParametricSurfaces. 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}])
source
OpticSim.TriangleMeshType
TriangleMesh{T} <: Surface{T}

An array of Triangles forming a mesh. Used for visualization purposes only.

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

Stops

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

OpticSim.InfiniteStopType
InfiniteStop{T,P<:StopShape} <: Surface{T}

Stop surface with infinite extent (outside of the aperture). P refers to the shape of the aperture.

source
OpticSim.FiniteStopType
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.

source
OpticSim.RectangularApertureFunction
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.

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

source
OpticSim.CircularApertureFunction
CircularAperture(radius::T, surfacenormal::SVector{3,T}, centrepoint::SVector{3,T})

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

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

source
OpticSim.AnnulusFunction
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}.

source

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.ParametricSurfaceType
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. ParametricSurfaces 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}
source
OpticSim.AcceleratedParametricSurfaceType
AcceleratedParametricSurface{T,N,S} <: ParametricSurface{T,N}

Wrapper class for ParametricSurfaces 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))
source

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 ParametricSurfaces can be added with relative ease providing they follow the interface defined above.

OpticSim.CylinderType
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))
source
OpticSim.PlaneType
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])
source
OpticSim.SphereType
Sphere{T,N} <: ParametricSurface{T,N}

Spherical surface centered at the origin.

Sphere(radius::T = 1.0; interface::NullOrFresnel{T} = nullinterface(T))
source
OpticSim.SphericalCapType
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 OpticalSystems.

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

source
OpticSim.ZernikeSurfaceType
ZernikeSurface{T,N,P,Q} <: 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 and Q is the number of aspheric terms. Only even aspheric terms are supported.

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 (must be even) and v is the corresponding coefficient $A_i$ or $\alpha_i$ respectively..

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.

source
OpticSim.BezierSurfaceType
BezierSurface{P,S,N,M} <: SplineSurface{P,S,N,M}

Bezier surface defined by grid of control points.

Danger

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})
source
OpticSim.BSplineSurfaceType
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.

Danger

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})
source
OpticSim.QTypeSurfaceType
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$.

source
OpticSim.ChebyshevSurfaceType
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.

source
OpticSim.GridSagSurfaceType
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)
source

Functions

These are some useful functions related to Surface objects.

OpticSim.pointMethod
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.

source
OpticSim.normalFunction
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.

source
OpticSim.partialsMethod
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.

source
OpticSim.uvrangeFunction
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 Surfaces which are not ParametricSurfaces (e.g. Rectangle).

source
OpticSim.uvFunction
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.

source
OpticSim.uvtopixFunction
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 OpticalSystem.

source
OpticSim.insideMethod
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.

source
OpticSim.onsurfaceMethod
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.

source
OpticSim.samplesurfaceFunction
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

source
OpticSim.triangulateFunction
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.

source

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.BoundingBoxType
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::RigidBodyTransform{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})
source
OpticSim.surfaceintersectionMethod
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.

source