Kinematic system
This page explains how the kinematic API described in the Kinematics section is implemented, and what a developer has to provide so that a new optical element, ray or beam type can be moved with it.
Kinematic traits
Analogous to the BeamletOptics.AbstractShapeTrait of the Geometry representation, the kinematic API is built on a trait that is dispatched on via multiple dispatch. Every public kinematic function, e.g. translate3d!(x, offset), forwards to a trait-specific method translate3d!(kinematic_trait_of(x), x, offset). As on the Geometry representation page, solid arrows are labeled with the field that leads from one type to the next, dotted arrows point to subtypes. The primitives translate3d! and rotate3d! are implemented by the concrete types, see below.
BeamletOptics.AbstractKinematicTrait Type
The kinematic trait defines whether an entity can be moved by the kinematic API (translate3d!, rotate3d!, ...). It is returned by kinematic_trait_of and follows the same dispatch pattern as AbstractShapeTrait: every public function e.g. translate3d!(x, args...) forwards to translate3d!(kinematic_trait_of(x), x, args...).
Two traits are defined:
Static:xcannot be moved, every kin. function throws anArgumentErrorMovable:xcan be moved; its frame (OrientedorDirected) selects orientation- or direction-based behaviour
The trait is opt-in: shapes, objects, rays, beams, beam groups and object groups declare it on their abstract type, any other type with its own kinematic_trait_of method. For a type without a method, kinematic_trait_of and every kinematic function throw an ErrorException.
A subtype of a movable abstract type can opt out of the kinematic API by declaring its own kinematic_trait_of method, e.g.
struct FixedMirror{T, S <: AbstractShape{T}} <: AbstractObject{T}
shape::S
end
kinematic_trait_of(::FixedMirror) = Static()Every kinematic function on a FixedMirror now throws an ArgumentError; its getters (position, orientation, ...) stay available.
Containers
All members of a container (ObjectGroup, UnionSDF/DifferenceSDF, AbstractBeamGroup) must be either all Static or all Movable. Frames can still be mixed. This must be checked in the respective constructors; nesting is then covered because a nested container already has a uniform class from its own constructor. The container then takes that class: Static if all members are Static, else Movable(Oriented()). All shapes returned by shape(object) of a MultiShape object must be movable, since MultiShape objects (e.g. a CubeBeamsplitter) have no shared constructor to enforce this.
A container moves each of its members once per motion, compared by identity (===). Two members that reach the same shape, or a static value at any level, throw an ArgumentError before anything moves, see shape.
BeamletOptics.kinematic_trait_of Method
kinematic_trait_of(x) -> AbstractKinematicTraitReturns the AbstractKinematicTrait of x. A type takes part in the kinematic API only if it opts in with a typed method: movable types declare Movable(Oriented()) or Movable(Directed()), static types Static(). Throws an ErrorException for any other type.
Static and movable entities
Whether an entity can be moved at all is defined by its trait. There is no default trait for BeamletOptics.kinematic_trait_of: the fallback throws an ErrorException for types that do not opt in. A System is BeamletOptics.Static. The abstract types BeamletOptics.AbstractShape, BeamletOptics.AbstractObject, BeamletOptics.AbstractRay and BeamletOptics.AbstractBeam declare themselves BeamletOptics.Movable, so a new subtype of them only has to implement what the list below gives for its abstract type. Containers declare no trait, they take the class of their members: BeamletOptics.AbstractObjectGroup, BeamletOptics.AbstractBeamGroup and the composite SDFs are Static if all members are static, else Movable.
BeamletOptics.Static Type
Static <: AbstractKinematicTraitRepresents a value that cannot be moved. Every kinematic function throws an ArgumentError; the getters (e.g. position, orientation) stay available.
BeamletOptics.Movable Type
Movable{F <: AbstractKinematicFrame} <: AbstractKinematicTraitRepresents a value that can be moved by the kinematic API. The frame field (Oriented or Directed) selects orientation- or direction-based behaviour of the derived functions.
Rotations are applied about the own position of the value unless a pivot is passed explicitly. A rotation axis can have any non-zero length (it is normalized internally).
Kinematic functions
translate3d!: movesxby anoffsetvector (primitive, implemented per type)rotate3d!: rotatesxby a rotation matrixRaboutposition(x), or about a givenpivotif one is passed (theR-only form is the primitive, implemented per type; the axis/angle and pivot forms are derived from it)translate_to3d!: movesxso thatposition(x)coincides with a target pointxrotate3d!/yrotate3d!/zrotate3d!: rotatexby an angleθ[rad] about the global x-, y- or z-axis throughposition(x)align3d!: rotatesxaboutposition(x)so thatdirection(x)is aligned with a target vectorreset_translation3d!: movesxso thatposition(x)is the global origin; available for everyMovable, including rays, beams and beam groupsreset_rotation3d!: only available forOrientedvalues, where it rotatesxback to identityorientation; forDirectedvalues (rays, beams) it throws anArgumentError, usealign3d!insteaddirection: forOrientedvalues this is the local y-axis,orientation(x)[:, 2];Directedvalues provide their own getter
Sources (rays, beams and beam groups) are additionally reset to their untraced start state by every one of the functions above that moves them, i.e. all but the getter direction: a moved but already-traced source would otherwise keep rays or child beams belonging to the old, now geometrically wrong, light path. Moving a beam that is not the root of its beam tree (e.g. one created by a beamsplitter interaction) throws an ArgumentError; move its root beam instead.
Implementation reqs.
If kinematic_trait_of(::Foo) = Movable(...) is declared, Foo must implement the following:
positiontranslate3d!(::Movable, x::Foo, offset): movesxbyoffsetrotate3d!(::Movable, x::Foo, R::AbstractMatrix): rotatesxbyRaboutposition(x)
All other kin. functions listed above are derived from these two primitives.
sourceOriented and directed frames
The frame of a BeamletOptics.Movable entity selects whether the derived functions work on a full local coordinate system or only on a direction vector. Shapes, objects, object groups and beam groups are BeamletOptics.Oriented, whereas rays and beams are BeamletOptics.Directed.
BeamletOptics.AbstractKinematicFrame Type
Reference frame of a Movable value, either Oriented or Directed.
BeamletOptics.Oriented Type
Oriented <: AbstractKinematicFrameReference frame of a movable value with a full local coordinate system, e.g. shapes, objects and beam groups.
Implementation reqs.
position/position!orientation/orientation!: a right-handed orthonormal 3x3 matrix
direction returns the local y-axis, i.e. orientation(x)[:, 2]. reset_rotation3d! rotates x by transpose(orientation(x)) about its position and then sets the orientation to the identity.
BeamletOptics.Directed Type
Directed <: AbstractKinematicFrameReference frame of a movable value that only has a direction but no orientation, e.g. rays and beams.
Implementation reqs.
positiondirection: the direction getter of the type
reset_rotation3d! throws an ArgumentError, use align3d! instead.
Type-specific implementation requirements
The primitives translate3d!(::Movable, x, offset) and rotate3d!(::Movable, x, R) are implemented differently for the individual abstract types. The requirements for subtypes are listed in the Kinematic section of the respective docstrings:
BeamletOptics.AbstractShape: the default primitives act onpositionandorientation; subtypes with additional geometry data (e.g. mesh vertices) implement their ownBeamletOptics.AbstractObject: the primitives forward to theBeamletOptics.AbstractShapeTraitof the object, seeBeamletOptics.SingleShapeandBeamletOptics.MultiShapeBeamletOptics.AbstractRay: only subtypes carrying direction-dependent data (e.g. the field vector of aPolarizedRay) need their ownrotate3d!BeamletOptics.AbstractBeam: a subtype needs no move methods, only_component_beams(a ray-storing beam returns(beam,)and also implementsraysandempty!); every move resets the beam viaempty!, and moving a child beam throws anArgumentErrorBeamletOptics.AbstractObjectGroup: moves all members ofobjects(group)rigidly about the groupcenter, see belowBeamletOptics.AbstractBeamGroup: moves itsbeamsrigidly about the groupcenter
Shared parts
A part can be reachable along several paths, e.g. an object in two subgroups or a shape listed twice by a BeamletOptics.MultiShape object. Every container moves each of its parts once per motion, compared by identity (===). An object group first unwinds its tree, so that the primitives of an object type also apply within nested groups:
The shapes are collected recursively via shape(object), down to the operands of composite SDFs. A group nested in another group moves as a frame via position!/orientation!; its own primitives are not called then.