CSG

CSG Operations

There are three binary csg operations which can construct extremely complex objects from very simple primitives: union ($\cup$), intersection ($\cap$) and subtraction (i.e. difference).

This diagram shows the basic idea: CSG Tree visualization

The code for this in our system would look this this:

cyl = Cylinder(0.7)
cyl_cross = cyl ∪ leaf(cyl, Geometry.rotationd(90, 0, 0)) ∪ leaf(cyl, Geometry.rotationd(0, 90, 0))

cube = Cuboid(1.0, 1.0, 1.0)
sph = Sphere(1.3)
rounded_cube = cube ∩ sph

result = rounded_cube - cyl_cross
Vis.draw(result, numdivisions=100)

CSG code example image

OpticSim.leafFunction
leaf(surf::ParametricSurface{T}, transform::Transform{T} = identitytransform(T)) -> CSGGenerator{T}

Create a leaf node from a parametric surface with a given transform.

source
Base.:∪Function
∪(a::Union{CSGGenerator{T},ParametricSurface{T}}, b::Union{CSGGenerator{T},ParametricSurface{T}}) where {T<:Real}

Create a binary node in the CSG tree representing a union between a and b.

Union Image

source
Base.:∩Function
∩(a::Union{CSGGenerator{T},ParametricSurface{T}}, b::Union{CSGGenerator{T},ParametricSurface{T}}) where {T<:Real}

Create a binary node in the CSG tree representing an intersection between a and b.

Intersect Image

source
Base.:-Function
-(a::Union{CSGGenerator{T},ParametricSurface{T}}, b::Union{CSGGenerator{T},ParametricSurface{T}}) where {T<:Real}

Create a binary node in the CSG tree representing the difference of a and b, essentially a - b.

Difference Image

source

Pre-made CSG Shapes

There are also some shortcut methods available to create common CSG objects more easily:

OpticSim.BoundedCylinderFunction
BoundedCylinder(radius::T, height::T; interface::NullOrFresnel{T} = nullinterface(T)) -> CSGGenerator{T}

Create a cylinder with planar caps on both ends centred at (0, 0, 0) with axis (0, 0, 1).

source
OpticSim.CuboidFunction
Cuboid(halfsizex::T, halfsizey::T, halfsizez::T; interface::NullOrFresnel{T} = nullinterface(T)) -> CSGGenerator{T}

Create a cuboid centred at (0, 0, 0).

source
OpticSim.HexagonalPrismFunction
HexagonalPrism(side_length::T, visheight::T = 2.0; interface::NullOrFresnel{T} = nullinterface(T)) -> CSGGenerator{T}

Create an infinitely tall hexagonal prism with axis (0, 0, 1), the longer hexagon diameter is along the x axis. For visualization visheight is used, note that this does not fully represent the surface.

source
OpticSim.RectangularPrismFunction
RectangularPrism(halfsizex::T, halfsizey::T, visheight::T=2.0; interface::NullOrFresnel{T} = nullinterface(T)) -> CSGGenerator{T}

Create an infinitely tall rectangular prism with axis (0, 0, 1). For visualization visheight is used, note that this does not fully represent the surface.

source
OpticSim.TriangularPrismFunction
TriangularPrism(side_length::T, visheight::T = 2.0; interface::NullOrFresnel{T} = nullinterface(T)) -> CSGGenerator{T}

Create an infinitely tall triangular prism with axis (0, 0, 1). For visualization visheight is used, note that this does not fully represent the surface.

source
OpticSim.SpiderFunction
Spider(narms::Int, armwidth::T, radius::T, origin::SVector{3,T} = SVector{3,T}(0.0, 0.0, 0.0), normal::SVector{3,T} = SVector{3,T}(0.0, 0.0, 1.0)) -> Vector{Rectangle{T}}

Creates a 'spider' obscuration with narms rectangular arms evenly spaced around a circle defined by origin and normal. Each arm is a rectangle armwidth×radius.

e.g. for 3 and 4 arms we get:

   |         _|_
  / \         |
source

CSG Types

These are the types of the primary CSG elements, i.e. the nodes in the CSG tree.

OpticSim.CSGGeneratorType
CSGGenerator{T<:Real}

This is the type you should use when making CSG objects. This type allows for the construction of CSGTree objects with different transforms. When the generator is evaluated, all transforms are propagated down to the LeafNodes and stored there.

Example

a = Cylinder(1.0,1.0)
b = Plane([0.0,0.0,1.0], [0.0,0.0,0.0])
generator = a ∩ b
# now make a csg object that can be ray traced
csgobj = generator(Transform(1.0,1.0,2.0))
source
OpticSim.UnionNodeType
UnionNode{T,L<:CSGTree{T},R<:CSGTree{T}} <: CSGTree{T}

An evaluated union node within the CSG tree.

source
OpticSim.LeafNodeType
LeafNode{T,S<:ParametricSurface{T}} <: CSGTree{T}

An evaluated leaf node in the CSG tree, geometry attribute which contains a ParametricSurface of type S. The leaf node also has a transform associated which is the composition of all nodes above it in the tree. As such, transforming points from the geometry using this transform puts them in world space, and transforming rays by the inverse transform puts them in object space.

source

Additional Functions and Types

These are the internal types and functions used for geometric/CSG operations.

Functions

OpticSim.surfaceintersectionMethod
surfaceintersection(obj::CSGTree{T}, r::AbstractRay{T,N})

Calculates the intersection of r with CSG object, obj.

Returns an EmptyInterval if there is no intersection, an Interval if there is one or two interesections and a DisjointUnion if there are more than two intersections.

The ray is intersected with the LeafNodes that make up the CSG object and the resulting Intervals and DisjointUnions are composed with the same boolean operations to give a final result. The ray is transformed by the inverse of the transform associated with the leaf node to put it in object space for that node before the intersection is carried out, typically this object space is centered at the origin, but may differ for each primitive.

Some intersections are culled without actually evaluating them by first checking if the ray intersects the BoundingBox of each node in the CSGTree, this can substantially improve performance in some cases.

source
OpticSim.insideMethod
inside(obj::CSGTree{T}, point::SVector{3,T}) -> Bool
inside(obj::CSGTree{T}, x::T, y::T, z::T) -> Bool

Tests whether a 3D point in world space is inside obj.

source
OpticSim.onsurfaceMethod
onsurface(obj::CSGTree{T}, point::SVector{3,T}) -> Bool
onsurface(obj::CSGTree{T}, x::T, y::T, z::T) -> Bool

Tests whether a 3D point in world space is on the surface (i.e. shell) of obj.

source

Intervals

OpticSim.IntervalType
Interval{T} <: AbstractRayInterval{T}

Datatype representing an interval between two IntervalPoints on a ray.

The lower element can either be RayOrigin or an Intersection. The upper element can either be an Intersection or Infinity.

positivehalfspace(int::Intersection) -> Interval with lower = int, upper = Infinity
rayorigininterval(int::Intersection) -> Interval with lower = RayOrigin, upper = int
Interval(low, high)

Has the following accessor methods:

lower(a::Interval{T}) -> Union{RayOrigin{T},Intersection{T,3}}
upper(a::Interval{T}) -> Union{Intersection{T,3},Infinity{T}}
source
OpticSim.DisjointUnionType

Datatype representing an ordered series of disjoint intervals on a ray. An arbitrary array of Intervals can be input to the constructor and they will automatically be processed into a valid DisjointUnion (or a single Interval if appropriate).

DisjointUnion(intervals::AbstractVector{Interval{R}})
source
OpticSim.closestintersectionFunction
closestintersection(a::Union{EmptyInterval{T},Interval{T},DisjointUnion{T}}, ignorenull::Bool = true) -> Union{Nothing,Intersection{T,3}}

Returns the closest Intersection from an Interval or DisjointUnion. Ignores intersection with null interfaces if ignorenull is true. Will return nothing if there is no valid intersection.

source
OpticSim.IntervalPoolType

To prevent allocations we have a manually managed pool of arrays of Intervals which are used to store values during execution. The memory is kept allocated and reused across runs of functions like trace.

threadedintervalpool is a global threadsafe pool which is accessed through the functions:

newinintervalpool!(::Type{T} = Float64, tid::Int = Threads.threadid()) -> Vector{Interval{T}}
indexednewinintervalpool!(::Type{T} = Float64, tid::Int = Threads.threadid()) -> Tuple{Int,Vector{Interval{T}}}
emptyintervalpool!(::Type{T} = Float64, tid::Int = Threads.threadid())
getfromintervalpool([::Type{T} = Float64], id::Int, tid::Int = Threads.threadid()) -> Vector{Interval{T}}
source

Intersections

OpticSim.RayOriginType
RayOrigin{T} <: IntervalPoint{T}

Point representing 0 within an Interval, i.e. the start of the ray.

RayOrigin(T = Float64)
RayOrigin{T}()
source
OpticSim.IntersectionType
Intersection{T,N} <: IntervalPoint{T}

Represents the point at which an Ray hits a Surface. This consists of the distance along the ray, the intersection point in world space, the normal in world space, the UV on the surface and the OpticalInterface hit.

Has the following accessor methods:

point(a::Intersection{T,N}) -> SVector{N,T}
normal(a::Intersection{T,N}) -> SVector{N,T}
uv(a::Intersection{T,N}) -> SVector{2,T}
u(a::Intersection{T,N}) -> T
v(a::Intersection{T,N}) -> T
α(a::Intersection{T,N}) -> T
interface(a::Intersection{T,N}) -> OpticalInterface{T}
flippednormal(a::Intersection{T,N}) -> Bool
source