Scene Graph
Like many engines, Trial includes a "scene graph" – a tree of nodes in which the objects that are drawn or are interactable are organised. Each node is a scene-node
with a container
pointer to its parent node. Nodes that can contain other child nodes are container
s. Nodes that can be named are called entity
s. If an entity
is named, it is uniquely identified by its name within the scene
it is contained in. An entity can be obtained via its name with the node
function.
Graph Operations
Operating on the graph is rather simple: enter
is used to introduce a node into a container, and leave
is used to remove it again. When a node is enter
ed, it is also register
ed on the graph's root scene
. Similarly, when it is left, the node and any children it might have are first deregister
ed. When the name of an entity
is changed via setf
, the object is also first deregister
ed and then re-register
ed after the name change.
Each container
must be a sequence
and as such can be iterated over as one, using either for:for
or sequences:dosequence
. All other standard sequence operations are naturally required to be supported as well.
You can also clear
a container fully, though note that this will not recursively clear if the container contained other containers. When a container is finalize
d, all of its children are also finalized and the container is clear
ed, though as with all finalization you should not rely on any particular state after finalization.
Finally, you can check whether a node is within a container with contained-p
, and retrieve the container it is in (if any) with the container
function. To retrieve the root scene, simply use the scene
function.
Reacting to Scene Changes
When a node in the scene graph is added or removed, you can react to that change at the immediate child level via methods on enter
and leave
. Any enter
and leave
operation will also bubble upwards (to the root) of the tree via register
and deregister
calls correspondingly, so you can react to changes lower down in the tree via methods on those functions.
Note that all of those functions are called synchronously, so performing expensive operations in them will also make scene tree changes expensive as well. However, it also allows you to perform consistency checks and error out, should an insertion or removal be considered invalid.
In order to react to changes elsewhere in the scene graph, you can add an event handler for the register
and deregister
events. Note that these will be asynchronous and will only be handled after the change has already been performed.
Entity Mixins
By themselves nodes don't actually do anything. Often you'd like the nodes to carry some properties such as a location, orientation, etc. To this end, Trial offers a number of mixin classes:
located-entity
An entity with alocation
vec3.sized-entity
An entity with absize
vec3 describing the half-size of the entity's bounding box.oriented-entity
An entity that is pointing in theorientation
direction according to theup
vector.rotated-entity
An entity that is rotated by therotation
quaternion.axis-rotated-entity
An entity that is rotated around theaxis
byangle
.pivoted-entity
An entity that is pivoted bypivot
.scaled-entity
An entity that is scaled byscaling
.transformed-entity
An entity with atransform
gizmo attached viatf
. It also supports convenience accessors forlocation
,scaling
,rotation
,axis
,angle
.
This is what all nodes have in a typical game engine. The transform gizmo is a convenient way to encapsulate separated affine transformations.
Note that if you use multiple of these mixins that the order in which you specify them in the superclass list matters, as it determines the order in which their transformations apply to the model-matrix
via apply-transforms
.
Container Types
While you can create your own container types when necessary, Trial already provides a number of containers that should fit almost every need out there.
array-container
A container backed by a simple-vector. Insertion is O(1) and simply adds to the back, and removal is O(n). Iteration should be fast and memory-efficient.bag
A container where the order of elements is not guaranteed to be consistent. However, in return, insertion and removal are both O(1), and iteration is also fast and memory-efficient. This should be the default container type unless you expect a very small number of elements.hash-table-container
A container backed by a simple hash table. Iteration order is not guaranteed, but insertion and removal are both O(1). Additionally, setting elements viaelt
does not "make sense" as there's no guarantee about the index of new elements.layered-container
A container that segregates objects into a multiplebag
s depending on theirlayer-index
. The container needs to be initialised with the correctlayer-count
. Any object with a layer-index beyond [0,layer-count] is clamped to within that range.list-container
A container backed by a list. Insertion is O(1) and simply adds to the front, and removal is O(n). Iteration is not memory efficient due to the cons chain.
Defining your own container types is simple – all you need to do is subclass container
, and implement clear
, enter
, leave
, and the necessary sequence methods (most notably sequences:make-sequence-iterator
).