-
Notifications
You must be signed in to change notification settings - Fork 44
Dynamic scenes
For a better understanding of what a scene is for HELIOS++, please refer to the Scene page of the wiki.
A dynamic scene is a scene that experiences some type of update during the simulation. In other words, any scene for which at least one of its components mutates its state at least one time during simulation. For example, a scene whose geometry represents the road, the sidewalks and the buildings of a city would be a typical static scene. However, if a car moving on the road is added to the scene, then it is a dynamic scene. More specifically, it is a scene with dynamic moving objects. Dynamic moving objects are scene parts which experience at least one type of rigid motion during the simulation.
It is planned that HELIOS++ will support different types of dynamic behaviors in the future. For instance, scenes where there are background components which are the same during the simulation and foreground objects that require repeating the simulation with a different foreground geometry while keeping the background. However, at the moment, dynamic scenes based on dynamic moving objects are the only supported ones.
A dynamic moving object can be specified through the scene's XML file. This requires defining the sequence of rigid motions that the object is going to perform. Currently, HELIOS++ supports all possible rigid motions in 3D, which are illustrated in the following table.
Rigid motion | XML type | XML attributes | Description |
---|---|---|---|
Translation | translation | vec, autoCRS | Add a translation vector |
Reflection | reflection | ortho | Reflection with respect to a plane defined by its orthonormal vector |
Glide plane | glideplane | ortho, shift | Combination of reflection and translation |
Rotation | rotation | axis, angle, center, autoCRS, selfMode | Rotation around given rotation axis (in degrees) around a rotation center (if specified) or around the origin O(0, 0, 0) |
Helical motion | helical | axis, angle, glide | Combination of rotation and translation (in degrees) |
Rotational symmetry | rotsym | axis, angle, center | Combination of rotation and reflection (in degrees) |
Those rigid motions that are defined with respect to an associated linear variety (for instance rotations, because they are associated to a rotation axis which is a 1D linear variety), also support a selfMode
XML attribute which specifies that the rigid motion must be applied centered with respect to the object. For example, a rotation which is specified with selfMode="true"
implies that the object will be rotated around itself (i.e., centered at the origin).
Any scene part that is meant to be a dynamic moving object must be defined using the following structure (where parameter values may be different and additional <motion>
and <dmotion>
elements may be present).
<part>
...
<dmotion id="my_motion" loop="0">
<motion type="rotation" axis="0;0;1" angle="1" selfMode="true"/>
</dmotion>
</part>
The <dmotion>
element defines a dynamic motion sequence that consists of the computation of all <motion>
elements contained inside in the exact order that they are specified. The first <dmotion>
element also defines the first dynamic motion in the sequence, in case multiple dynamic motions are defined for the same scene part. However, the order of the sequence after the first element must be explicitly specified by defining a next="another_motion"
attribute so the dynamic motion <dmotion id="another_motion" ...> ... </dmotion>
will be executed after the current one has finished.
Note that the above case also defines "my_motion"
as the identifier for the dynamic motion sequence. Furthermore, it will be executed during the entire simulation because the loop="0"
attribute means that the motion must be applied as an infinite loop. Specifying loop="n"
for any n
greater than 0 will lead to the dynamic motion being executed n
times before finishing.
The different rigid motions that can be specified are detailed in this section, in the same order as they appear in the table above.
A translation can be defined as
<motion type="translation" vec="x;y;z"/>
The vector components specify how many meters for axis x
, y
and z
will be added to the object position at each iteration. It is, at each loop iteration the new position of a point p
can be defined as p' = p + (x, y, z)
.
For the sake of simplicity, translations can use the autoCRS
attribute to automatically align the translation with respect to the internal coordinate reference system of the simulator, e.g.:
<motion type="translation" vec="x;y;z" autoCRS="1" />
A reflection can be defined as
<motion type="reflection" ortho="x;y;z"/>
The vector ortho
is understood as the orthonormal vector defining the reflection plane. For example, if the orthonormal vector is (0, 0, 1)
, then it is the e3
vector of the canonical basis for the 3D space (z
axis). Thus, the reflection plane would have e1
and e2
(x
and y
axis) as its basis.
A glide plane can be defined as
<motion type="glideplane" ortho="u;v;w" shift="x;y;z"/>
The ortho
vector is understood as the orthonormal vector defining the reflection plane. The shift
vector specifies how many meters for axis x
, y
and z
will be added to the object position at each iteration.
A rotation can be defined as:
<motion type="rotation" axis="x;y;z" angle="a" />
The axis
vector specifies the rotation axis. The angle
scalar specifies how many degrees of rotation must be applied at each iteration.
Optionally, a rotation center
can be specified to rotate around a known point:
<motion type="rotation" axis="x;y;z" angle="a" center="x;y;z"/>
Moreover, the autoCRS
attribute can be used to automatically translate the rotation center to the internal reference system of the simulation:
<motion type="rotation" axis="x;y;z" angle="a" center="x;y;z" autoCRS="1" />
To position the rotation center to the object center, use the selfMode
argument:
<motion type="rotation" axis="x;y;z" angle="a" selfMode="true" />
A helical motion can be defined as:
<motion type="helical" axis="x;y;z" angle="a" glide="k"/>
The axis
vector specifies the rotation axis which is the translation direction too. The angle
scalar specifies how many degrees of rotation must be applied at each iteration. The glide
scalar defines the magnitude of the translation so that after the rotation the p' = p + k (x,y,z)
translation is applied to complete the helical motion.
A rotational symmetry can be defined as:
<motion type="rotsym" axis="x;y;z" angle="a" center="p;q;r" />
The axis
vector specifies the rotation axis which is also the orthogonal vector of the reflection plane. The angle
scalar specifies how many degrees of rotation must be applied at each iteration. The center
point specifies the center of the rotational symmetry, it is the point where the rotation axis and the reflection plane intersect.
It was stated before that each rigid motion can be defined as a set of <motion ... />
elements contained inside a <dmotion ...> ... </dmotion>
. Let us now look at how to define two different rigid movements.
The first rigid motion is a sequence of 4 motions describing a planetary like motion.
<dmotion id ="planetary_motion" loop="0">
<motion type="rotation" axis="0;0;1" angle="2" selfMode="true" />
<motion type="translation" vec="-120;-100;0" />
<motion type="rotation" axis="0;0;1" angle="1" />
<motion type="translation" vec="120;100;0" />
</dmotion>
The first rotation is configured with selfMode="true"
to represent the rotation of a planet around the z
axis centered in the object. It is followed by a translation, then a rotation and finally another translation that reverses the first. This last set of three motions is used to represent the translation of a planet which rotates around a given point. Thus, this dynamic motion defines a behavior similar to the one described by planet Earth, which rotates around itself at the same time that it moves around the sun. The entire sequence is computed inside an infinite loop, so it will never end.
The second rigid motion consists of two chained sequences of rigid motions. It represents a particle describing an ascendant helical motion followed by its corresponding descendant helical motion.
<dmotion id="sphere_helical_up" loop="300" next="sphere_helical_down">
<motion type="translation" vec="-129;-120;0" />
<motion type="helical" axis="0;0;1" angle="4" glide="0.3"/>
<motion type="translation" vec="129;120;0" />
</dmotion>
<dmotion id="sphere_helical_down" loop="300" next="sphere_helical_up">
<motion type="translation" vec="-129;-120;0" />
<motion type="helical" axis="0;0;1" angle="-4" glide="-0.3"/>
<motion type="translation" vec="129;120;0" />
</dmotion>
The "sphere_helical_down"
sequence is repeated 300 times before proceeding to the "sphere_helical_up"
motion. Once the second has been applied 300 times, the first one is executed again. The aforementioned process is repeated until the simulation ends. Each motion starts with a translation and ends with another translation which reverts the first one. Thus, the translations can be seen as a way to centering the helical motion around an arbitrary point. The first helical motion is the ascending one, while the second helical motion is identical to the first but in the opposite direction.
By default, all rigid motions operate at the same frequency than the simulation. The operating frequency F
for the simulation is defined by the pulse frequency of the scanner. This leads to rigid motions being computed F
times per virtual second (seconds in simulated time, not real time seconds). However, it might be interesting because of performance reasons to control the frequency at which dynamic motions are computed.
It is up to the user to understand how much precision a simulation needs to be realistic enough. But it is reasonable to assume that computing a rigid motion 100000
times per virtual second just because this is the pulse frequency is an overkill. To better understand this, typical FPS values for cameras easily range from 25
FPS for typical videos to 960
FPS for super slow motion videos. This means that using a pulse frequency of 100000
implies simulating at a rate that is 4000 and 100 times that of typical and super slow motion videos, respectively.
For those cases where the simulation frequency is considerable greater than needed for an accurate output, it is possible to specify two different relative frequencies to improve the performance of the simulation. Both of them can be specified as an attribute of a <part ...> ... </part>
element in the scenes's XML file.
The first attribute is the dynStep="x"
which means that the dynamic motion will be applied each x
simulation steps. Thus, if F=100000
and dynStep="1000"
it means that the dynamic motion will be applied F/dynStep = 100
times per second. Note that this might require to update values such as the magnitude of a translation because adding 100
times a translation vector (0,0,1)
leads to an accumulated translation vector (0,0,100)
while adding 100000
times the same translation vector leads to an accumulated translation vector (0,0,100000)
.
The second attribute is the kdtDynStep="y"
which means that the KDGrove (a datastructure which handles multiple KDTrees for efficient ray casting on dynamic scenes) will update the KDTree for the dynamic object after y
consecutive updates of the object itself. For instance, having kdtDynStep="10"
means that the KDTree will be updated after computing 10
iterations of the dynamic motion sequence.
Both dynStep
and kdtDynStep
can be combined in the most convenient way. To clearly understand the difference between them, the dynStep
defines how many simulation steps are needed before updating the dynamic object while the kdtDynStep
defines how many updates of the dynamic object are necessary before updating the KDTree. Thus, in cases where a very realistic motion is required but it is not necessary to sample it at the same rate than scanning pulses, it is recommended to keep a lower dynStep
and have a higher kdtDynStep
.
Moreover, the dynStep="z"
attribute can also be specified at scene level instead of as an attribute for a scene part. When specified at scene level, it changes the default frequency of the dynamic scene from 1
to z
. Having dynStep="z"
at the scene element and dynStep="x"
at the scene part element means that the dynamic object will be updated each xz
iterations. Also, having dynStep="z"
at the scene element with default dynStep="1"
for all dynamic objects works similar than having dynStep="z"
defined for each dynamic object while having default dynStep="1"
for the scene.
An example of how to configure the different frequencies for scene is presented below:
<scene id="dyn_cube_scene" name="DynCubeScene" dynStep="20">
<!-- Ground plane as a static object -->
<part>
<filter type="objloader">
<param type="string" key="filepath" value="data/sceneparts/basic/groundplane/groundplane.obj" />
</filter>
<filter type="scale">
<param type="double" key="scale" value="120" />
</filter>
<filter type="translate">
<param type="vec3" key="offset" value="50.0;0;0" />
</filter>
</part>
<!-- Cube as a dynamic object -->
<part dynStep="5" kdtDynStep="10">
<filter type="objloader">
<param type="string" key="filepath" value="data/sceneparts/toyblocks/cube.obj" />
</filter>
<filter type="rotate">
<param key="rotation" type="rotation">
<rot angle_deg="45" axis="z"/>
</param>
</filter>
<filter type="scale">
<param type="double" key="scale" value="0.75" />
</filter>
<filter type="translate">
<param type="vec3" key="offset" value="-40.0;-5.0;0" />
</filter>
<!-- The dynamic motion sequence for the cube -->
<dmotion id="cube_translation" loop="0">
<motion type="translation" vec="0.001;-0.003;0"/>
</dmotion>
</part>
</scene>
In the above example, the frequency for the dynamic scene is set to 20
while the frequency for the dynamic object is setted to 5
, which means that the dynamic object is updated each 20 x 5 = 100
simulation steps. The frequency for KDTree updates is 10
, which means the KDTree is updated after 10
dynamic object updates. In this case, the dynamic object has an infinite sequence that repeats the same translation. Thus, it is clear that the KDTree will be updated each 1000
simulation steps. Assuming a pulse frequency of 100000
, the dynamic moving object will apply the translation vector 1000
times per virtual second while the KDTree is updated 100
times per virtual second.
The dynamic time step exploits the inverse relationship between time and frequency to provide an alternative specification. More concretely, the time step (inside dynTimeStep
and kdtDynTimeStep
attributes must be used instead of their dynStep
and kdtDynStep
counterparts.
Note also that the dynTimeStep
and kdtDynTimeStep
are given as direct time measurements. On the contrary, for the discrete steps, the dynStep
of the object is relative to the dynStep
of the scene, and the kdtDynStep
of the KDT is relative to the dynStep
of the object. Thus, when using the time-based specification, the dynTimeStep
of the scene must be greater than or equal to the dynTimeStep
of each scene part (object), and the kdtDynTimeStep
should be greater than or equal to the dynTimeStep
of the associated object. The reason is that they represent nested components. Intuitively, updating a KDT representing an object 100 times per second does not make sense when the object is only updated five times per second. Furthermore, values greater than one are not expected. The reason is that the dynamic time steps are the inverse of a frequency that represents the number of iterations per second in the main simulation loop.
The dynamic time steps can be straightforward to interpret. A dynamic scene with dynTimeStep="0.0002"
will compute its dynamic simulation logic with a time step of 0.2 ms, a dynamic object with dynTimeStep="0.001"
will run its logic with a time step of 1 ms, and a dynamic KDT with dynTimeStep="0.01"
will be updated each 10 ms. When not given, the dynamic time step of the KDT is automatically equal to the dynamic time step of its associated object. Similarly, when the dynamic time step of an object is not given, it is automatically equal to the dynamic time step of the scene. Dynamic steps (discrete) and dynamic time steps (continuous) cannot be mixed in the same specification.
The XML below yields the same simulation as the previous example but uses a time-based specification instead.
<scene id="dyn_cube_scene" name="DynCubeScene" dynTimeStep="0.0002">
<!-- Ground plane as a static object -->
<part>
<filter type="objloader">
<param type="string" key="filepath" value="data/sceneparts/basic/groundplane/groundplane.obj" />
</filter>
<filter type="scale">
<param type="double" key="scale" value="120" />
</filter>
<filter type="translate">
<param type="vec3" key="offset" value="50.0;0;0" />
</filter>
</part>
<!-- Cube as a dynamic object -->
<part dynTimeStep="0.001" kdtDynTimeStep="0.01">
<filter type="objloader">
<param type="string" key="filepath" value="data/sceneparts/toyblocks/cube.obj" />
</filter>
<filter type="rotate">
<param key="rotation" type="rotation">
<rot angle_deg="45" axis="z"/>
</param>
</filter>
<filter type="scale">
<param type="double" key="scale" value="0.75" />
</filter>
<filter type="translate">
<param type="vec3" key="offset" value="-40.0;-5.0;0" />
</filter>
<!-- The dynamic motion sequence for the cube -->
<dmotion id="cube_translation" loop="0">
<motion type="translation" vec="0.001;-0.003;0"/>
</dmotion>
</part>
</scene>
Multiple examples for different rigid motions can be found in the example scene for moving toyblocks