It is often useful to add custom rendering to an application. For example, one may wish to render forces acting on bodies, or marker positions that are not otherwise assigned to a component. Custom rendering is relatively easy to do, as described in this section.
All renderable ArtiSynth components implement the IsRenderable interface, which contains the methods prerender() and render(). As its name implies, prerender() is called prior to rendering and is discussed in Section 4.4.4. The render() method, discussed here, performs the actual rendering. It has the signature
where the Renderer supplies a large set of methods for performing the rendering, and flags is described in the API documentation. The methods supplied by the renderer include ones for drawing simple primitives such as points, lines, and triangles; simple 3D shapes such as cubes, cones, cylinders and spheres; and text components.
A full description of the Renderer can be obtained by checking its API documentation and also by consulting the “Rendering” section of the Maspack Reference Manual.
A small sampling of the methods supplied by a Renderer include:
For example, the render() implementation below renders three spheres connected by pixel-based lines, as show in Figure 4.3 (left):
A Renderer also contains draw mode methods to implement the drawing of points, lines and triangles in a manner similar to the immediate mode of legacy OpenGL,
where drawMode is an instance of Renderer.DrawMode and includes points, lines, line strips and loops, triangles, and triangle strips and fans. The draw mode methods include:
void beginDraw(DrawMode mode) |
Begin a draw mode sequence. |
void addVertex(Vector3d vtx) |
Add a vertex to the object being drawn. |
void setNormal(Vector3d nrm) |
Sets the current vertex normal for the object being drawn. |
void endDraw() |
Finish a draw mode sequence. |
The render() implementation below uses draw mode to render the image shown in Figure 4.3, right:
Finally, a Renderer contains methods for the rendering of render objects, which are collections of vertex, normal and color information that can be rendered quickly; it may be more efficient to use render objects for complex rendering involving large numbers of primitives. Full details on this, and other features of the rendering interface, are given in the “Rendering” section of the Maspack Reference Manual.
There are two easy ways to add custom rendering to a model:
Override the root model’s render() method;
Define a custom rendering component, with its own render() method, and add it to the model. This approach is more modular and makes it easier to reuse the rendering between models.
To override the root model render method, one simply adds the following declaration to the model’s definition:
A call to super.render() is recommended to ensure that any rendering done by the model’s base class will still occur. Subsequent statements should then use the renderer object to perform whatever rendering is required, using methods such as described in Section 4.4.1 or in the “Rendering” section of the Maspack Reference Manual.
To create a custom rendering component, one can simply declare a subclass of RenderableComponentBase with a custom render() method:
There is no need to call super.render() since RenderableComponentBase does not provide an implementation of render(). It does, however, provide default implementations of everything else required by the Renderable interface. This includes exporting render properties via the composite property renderProps, which the render() method may use to control the rendering appearance in a manner consistent with Section 4.3.2. It also includes an implementation of prerender() which by default does nothing. As discussed more in Section 4.4.4, this is often acceptable unless:
rendering will be adversely affected by quantities being updated asynchronously within the simulation;
the component contains subcomponents that also need to be rendered.
Once a custom rendering component has been defined, it can be created and added to the model within the build() method:
In this example, the component is added to the MechModel’s subcomponent list renderables, which ensures that it will be found by the rendering code. Methods for managing this list include:
void addRenderable(Renderable r) |
Adds a renderable to the model. |
boolean removeRenderable(Renderable r) |
Removes a renderable from the model. |
void clearRenderables() |
Removes all renderables from the model. |
ComponentListView<RenderableComponent> renderables() |
Returns the list of all renderables. |
The application model
artisynth.demos.tutorial.BodyForceRendering
gives an example of custom rendering to draw the force and moment vectors acting on a rigid body as cyan and green arrows, respectively. The model extends RigidBodySpring (Section 3.2.2), defines a custom renderable class named ForceRenderer, and then uses this to render the forces on the box in the original model. The code, with the include files omitted, is listed below:
The force renderer is defined at lines (4-34). For attributes, it contains a reference to the rigid body, plus scale factors for rendering the force and moment. Within the render() method (lines 16-33), the force and moment vectors are draw as cyan and green arrows, starting at the current body position, and scaled by their respective factors. This scaling is needed to ensure the arrows have a size appropriate to the viewer, and separate scale factors are needed because the force and moment vectors have different units. The arrow radii are given by the renderer’s lineRadius render property (lines 22 and 30).
The build() method starts by calling the super class build() method to create the original model (line 36), and then uses findComponent() to retrieve its MechModel, within which the “box” rigid body is located (lines 40-41). A ForceRenderer is then created for this body, with force and moment scale factors of 0.1 and 0.5, and added to the MechModel (lines 44-45). The renderer’s lineRadius render property is then set to 0.01 (line 48).
To run this example in ArtiSynth, select All demos > tutorial > BodyForceRenderer from the Models menu. When run, the model should appear as in Figure 4.4, showing the force and moment vectors acting on the box.
Component render() methods are called within ArtiSynth’s graphics thread, and so are called asynchronously with respect to the simulation thread(s). This means that the simulation may be updating component attributes, such as positions or forces, at the same time they are being accessed within render(), leading to inconsistent results in the viewer. While these inconsistencies may not be significant, particularly if the attributes are changing slowly between time steps, some applications may wish to avoid them. For this, renderable components also implement a prerender() method, with the signature
that is called in synchronization with the simulation prior to rendering. Its purpose is to:
Make copies of simulation-varying attributes so that they can be used without conflict in the render() method;
Identify to the system any additional subcomponents that also need to be rendered.
The first task is usually accomplished by copying simulation-varying attributes into cache variables stored within the component itself. For example, if a component is responsible for rendering the forces of a rigid body (as per Section 4.4.3), it may wish to make a local copy of these forces within prerender:
The second task, identifying renderable subcomponents, is accomplished using the addIfVisible() and addIfVisibleAll() methods of the RenderList argument, as in the following examples:
It should be noted that some ArtiSynth components already support cached copies of attributes that can be used for rendering. In particular, Point (whose subclasses include Particle and FemNode3d) uses prerender() to cache its position as an array of float[] which can be obtained using getRenderCoords(), and Frame (whose subclasses include RigidBody) caches its pose as a RigidTransform3d that can be obtained with getRenderFrame().