5 Simulation Control

5.1 Control Panels

A control panel is an editing panel that allows for the interactive adjustment of component properties.

It is always possible to adjust component properties through the GUI by selecting one or more components and then choosing Edit properties ... in the right-click context menu. However, it may be tedious to repeatedly select the required components, and the resulting panels present the user with all properties common to the selection. A control panel allows an application to provide a customized editing panel for selected properties.

If an application wishes to adjust an attribute that is not exported as a property of some ArtiSynth component, it is often possible to create a custom property for the attribute in question. Custom properties are described in Section 5.2.

5.1.1 General principles

Control panels are implemented by the ControlPanel model component. They can be set up within a model’s build() method by creating an instance of ControlPanel, populating it with widgets for editing the desired properties, and then adding it to the root model using the latter’s addControlPanel() method. A typical code sequence looks like this:

 ControlPanel panel = new ControlPanel ("controls");
 ... add widgets ...
 addControlPanel (panel);

There are various addWidget() methods available for adding widgets and components to a control panel. Two of the most commonly used are:

 addWidget (HasProperties host, String propPath)
 addWidget (HasProperties host, String propPath, double min, double max)

The first method creates a widget to control the property located by propPath with respect to the property’s host (which is usually a model component or a composite property). Property paths are discussed in Section 1.4.2, and can consist of a simple property name, a composite property name, or, for properties located in descendant components, a component path followed by a colon ‘:’ and then a simple or compound property name.

The second method creates a slider widget to control a property whose value is a single number, with an initial numeric range given by max and min.

The first method will also create a slider widget for a property whose value is a number, if the property has a default range and slider widgets are not disabled in the property’s declaration. However, the second method allows the slider range to be explicitly specified.

Both methods also return the widget component itself, which is an instance of LabeledComponentBase, and assign the widget a text label that is the same as the property’s name. In some situations, it is useful to assign a different text label (such as when creating two widgets to control the same property in two different components). For those cases, the methods

  addWidget (
     String label, HasProperties host, String propPath)
  addWidget (
     String label, HasProperties host, String propPath, double min, double max)

allow the widget’s text label to be explicitly specified.

Sometimes, it is desirable to create a widget that controls that same property across two or more host components (as illustrated in Section 5.1.3 below). For that, the methods

 addWidget (String propPath, HasProperties... hosts)
 addWidget (String propPath, double min, double max, HasProperties... hosts)
 addWidget (
    String label, String propPath, HasProperties... hosts)
 addWidget (
    String label, String propPath, double min, double max, HasProperties... hosts)

allow multiple hosts to be specified using the variable length argument list hosts.

Other flavors of addWidget() also exist, as described in the API documentation for ControlPanel. In particular, any type of Swing or awt component can be added using the method

   addWidget (Component comp)

Control panels can also be created interactively using the GUI; see the section “Control Panels” in the ArtiSynth User Interface Guide.

5.1.2 Example: Creating a simple control panel

An application model showing a control panel is defined in

  artisynth.demos.tutorial.SimpleMuscleWithPanel

This model simply extends SimpleMuscle (Section 4.5.2) to provide a control panel for adjusting gravity, the mass and color of the box, and the muscle excitation. The class definition, excluding include statements, is shown below:

1 public class SimpleMuscleWithPanel extends SimpleMuscle {
2    ControlPanel panel;
3
4    public void build (String[] args) throws IOException {
5
6       super.build (args);
7
8       // add control panel for gravity, rigid body mass and color, and excitation
9       panel = new ControlPanel("controls");
10       panel.addWidget (mech, "gravity");
11       panel.addWidget (mech, "rigidBodies/box:mass");
12       panel.addWidget (mech, "rigidBodies/box:renderProps.faceColor");
13       panel.addWidget (new JSeparator());
14       panel.addWidget (muscle, "excitation");
15
16       addControlPanel (panel);
17    }
18 }

The build() method calls super.build() to create the model used by SimpleMuscle. It then proceeds to create a ControlPanel, populate it with widgets, and add it to the root model (lines 8-15). The panel is given the name "controls" in the constructor (line 8); this is its component name and is also used as the title for the panel’s window frame. A control panel does not need to be named, but if it is, then that name must be unique among the control panels.

Lines 9-11 create widgets for three properties located relative to the MechModel referenced by mech. The first is the MechModel’s gravity. The second is the mass of the box, which is a component located relative to mech by the path name (Section 1.1.3) "rigidBodies/box". The third is the box’s face color, which is the sub-property faceColor of the box’s renderProps property.

Line 12 adds a JSeparator to the panel, using the addWidget() method that accepts general components, and line 13 adds a widget to control the excitation property for muscle.

It should be noted that there are different ways to specify target properties in addWidget(). First, component paths may contain numbers instead of names, and so the box’s mass property could be specified using "rigidBodies/0:mass" instead of "rigidBodies/box:mass" since the box’s number is 0. Second, if a reference to a subcomponent is available, one can specify properties directly with respect to that, instead of indicating the subcomponent in the property path. For example, if the box was referenced by a variable body, then one could use the construction

   panel.addWidget (body, "mass");

in place of

   panel.addWidget (mech, "rigidBodies/box:mass");

To run this example in ArtiSynth, select All demos > tutorial > SimpleMuscleWithPanel from the Models menu. The demo will appear with the control panel shown in Figure 5.1, allowing the displayed properties to be adjusted interactively by the user while the model is either stationary or running.

Figure 5.1: Control panel created by the model SimpleMuscleWithPanel. Each of the property widgets consists of a text label followed by a field displaying the property’s value. A and B identify the icons for the inheritable properties gravity and faceColor, indicating whether their values have has been explicitly set (A) or inherited from an ancestor component (B).

As described in Section 1.4.3, some properties are inheritable, meaning that their values can either be set explicitly within their host component, or inherited from the equivalent property in an ancestor component. Widgets for inheritable properties include an icon in the left margin indicating whether the value is explicitly set (square icon) or inherited from an ancestor (triangular icon) (Figure 5.1). These settings can be toggled by clicking on the icon. Changing an explicit setting to inherited will cause the property’s value to be changed to that of the nearest ancestor component, or to the property’s default value if no ancestor component contains an equivalent property.

5.1.3 Example: Controlling properties in multiple components

Figure 5.2: Model and control panel created by ControlPanelDemo.

It is sometimes useful to create a property widget that adjusts the same property across several different components at the same time. This can be done using the addWidget() methods that accept multiple hosts. An application model demonstrating this is defined in

  artisynth.demos.tutorial.ControlPanelDemo

and shown in Figure 5.2. The model creates a simple arrangement of three spheres, connected by point-to-point muscles and with collisions enabled (Chapter 8), whose dynamic behavior can be adjusted using the control panel. Selected rendering properties can also be changed using the panel. The class definition, excluding include statements, is shown below:

1 public class ControlPanelDemo extends RootModel {
2
3    double myStiffness = 10.0; // default spring stiffness
4    double myMaxForce = 100.0; // excitation force multiplier
5    double DTOR = Math.PI/180; // degrees to radians
6
7    // Create and attach a simple muscle with default parameters between p0 and p1
8    Muscle attachMuscle (String name, MechModel mech, Point p0, Point p1) {
9       Muscle mus = new Muscle (name);
10       mus.setMaterial (
11          new SimpleAxialMuscle (myStiffness, /*damping=*/0, myMaxForce));
12       mech.attachAxialSpring (p0, p1, mus);
13       return mus;
14    }
15
16    public void build (String[] args) {
17       // create a mech model with zero gravity
18       MechModel mech = new MechModel ("mech");
19       addModel (mech);
20       mech.setGravity (0, 0, 0);
21       mech.setInertialDamping (0.1); // add some damping
22
23       double density = 100.0;
24       double particleMass = 50.0;
25
26       // create three spheres, each with a different color, along with a marker
27       // to attach a spring to, and arrange them roughly around the origin
28       RigidBody sphere0 = RigidBody.createIcosahedralSphere (
29          "sphere0", 0.5, density, /*ndivs=*/2);
30       FrameMarker mkr0 = mech.addFrameMarker (sphere0, new Point3d(0, 0, 0.5));
31       sphere0.setPose (new RigidTransform3d (1, 0, -0.5,  0, -DTOR*60, 0));
32       RenderProps.setFaceColor (sphere0, new Color(0f, 0.4f, 0.8f));
33       mech.addRigidBody (sphere0);
34
35       RigidBody sphere1 = RigidBody.createIcosahedralSphere (
36          "sphere1", 0.5, 1.5*density, /*ndivs=*/2);
37       FrameMarker mkr1 = mech.addFrameMarker (sphere1, new Point3d(0, 0, 0.5));
38       sphere1.setPose (new RigidTransform3d (-1, 0, -0.5,  0, DTOR*60, 0));
39       RenderProps.setFaceColor (sphere1, new Color(0f, 0.8f, 0.4f));
40       mech.addRigidBody (sphere1);
41
42       RigidBody sphere2 = RigidBody.createIcosahedralSphere (
43          "sphere2", 0.5, 1.5*density, /*ndivs=*/2);
44       FrameMarker mkr2 = mech.addFrameMarker (sphere2, new Point3d(0, 0, 0.5));
45       sphere2.setPose (new RigidTransform3d (0, 0, 1.1,  0, -DTOR*180, 0));
46       RenderProps.setFaceColor (sphere2, new Color(0f, 0.8f, 0.8f));
47       mech.addRigidBody (sphere2);
48
49       // create three muscles to connect the bodies via their markers
50       Muscle muscle0 = attachMuscle ("muscle0", mech, mkr1, mkr0);
51       Muscle muscle1 = attachMuscle ("muscle1", mech, mkr0, mkr2);
52       Muscle muscle2 = attachMuscle ("muscle2", mech, mkr1, mkr2);
53
54       // enable collisions between the spheres
55       mech.setDefaultCollisionBehavior (true, /*mu=*/0);
56
57       // render muscles as red spindles
58       RenderProps.setSpindleLines (mech, 0.05, Color.RED);
59       // render markers as white spheres. Note: unlike rigid bodies, markers
60       // normally have null render properties, and so we need to explicitly set
61       // their render properties for use in the control panel
62       RenderProps.setSphericalPoints (mkr0, 0.1, Color.WHITE);
63       RenderProps.setSphericalPoints (mkr1, 0.1, Color.WHITE);
64       RenderProps.setSphericalPoints (mkr2, 0.1, Color.WHITE);
65
66       // create a control panel to collectively set muscle excitation and
67       // stiffness, inertial damping, sphere visibility and color, and marker
68       // color. Muscle excitations and sphere colors can also be set
69       // individually.
70       ControlPanel panel = new ControlPanel();
71       panel.addWidget ("excitation", muscle0, muscle1, muscle2);
72       panel.addWidget ("excitation 0", "excitation", muscle0);
73       panel.addWidget ("excitation 1", "excitation", muscle1);
74       panel.addWidget ("excitation 2", "excitation", muscle2);
75       panel.addWidget (
76          "stiffness", "material.stiffness", muscle0, muscle1, muscle2);
77       panel.addWidget ("inertialDamping", sphere0, sphere1, sphere2);
78       panel.addWidget (
79          "spheres visible", "renderProps.visible", sphere0, sphere1, sphere2);
80       panel.addWidget (
81          "spheres color", "renderProps.faceColor", sphere0, sphere1, sphere2);
82       panel.addWidget ("sphere 0 color", "renderProps.faceColor", sphere0);
83       panel.addWidget ("sphere 1 color", "renderProps.faceColor", sphere1);
84       panel.addWidget ("sphere 2 color", "renderProps.faceColor", sphere2);
85       panel.addWidget (
86          "marker color", "renderProps.pointColor", mkr0, mkr1, mkr2);
87       addControlPanel (panel);
88    }
89 }

First, a MechModel is created with zero gravity and a default inertialDamping of 0.1 (lines 18-21). Next, three spheres are created, each with a different color and a frame marker attached to the top. These are positioned around the world coordinate origin, and oriented with their tops pointing toward the origin (lines 26-47), allowing them to be connected, via their markers, with three simple muscles (lines 49-52) created using the support method attachMuscle() (lines 8-14). Collisions (Chapter 8) are then enabled between all spheres (line 55).

At lines 62-64, we explicitly set the render properties for each marker; this is done because marker render properties are null by default and hence need to be explicitly set to enable widgets to be created for them, as discussed further below.

Finally, a control panel is created for various dynamic and rendering properties. These include the excitation and material stiffness for all muscles (lines 71 and 75); the inertialDamping, rendering visibility and faceColor for all spheres (lines 77, 79, and 81); and the rendering pointColor for all markers (lines 85). The panel also allows muscle excitations and sphere face colors to be set individually (lines 72-74 and 82-84). Some of the addWidget() calls explicitly set the label text for their widgets. For example, those controlling individual muscle excitations are labeled as “excitation 0”, “excitation 1”, and “excitation 2” to denote their associated muscle.

Some widgets are created for subproperties of their components (e.g., material.stiffness and renderProps.faceColor). The following caveats apply in these cases:

  1. 1.

    The parent property (e.g., renderProps for renderProps.faceColor) must be present in the component. While this will generally be true, in some instances the parent property may have a default value of null, and must be explicitly set to a non-null value before the widget is created (otherwise the subproperty will not be found and the widget creation will fail). This most commonly occurs for the renderProps property of smaller components, like particles and markers; in the example, render properties are explicitly assigned to the markers at lines 62-64.

  2. 2.

    If the parent property is changed for a particular host, then the widget will no longer be able to access the subproperty. For instance, in the example, if the material property for a muscle is changed (via either code or the GUI), then the widget controlling material.stiffness will no longer be able to access the stiffness subproperty for that muscle.

To run this example in ArtiSynth, select All demos > tutorial > ControlPanelDemo from the Models menu. When the model is run, the spheres will start to be drawn together by the muscles’ intrinsic stiffness. Setting non-zero excitation values in the control panel will increase the attraction, while setting stiffness values will likewise affect the dynamics. The panel can also be used to make all the spheres invisible, or to change their colors, either separately or collectively.

When a single widget is used to control a property across multiple host components, the property’s value may not be the same for those components. When this occurs, the widget’s value field displays either blank space (for numeric, string and enum values), a “?” (for boolean values), or a checked pattern (for color values). This is illustrated in Figure 5.2 for the “excitation” and “spheres color” widgets, since their individual values differ.