Animating SketchUp Models in Java
Brad J. Cox, Ph.D.
Binary Consulting; Bethesda MD.
bcox@virtualschool.edu
June 2006
 
This is a follow-on to Animating SketchUp models in Ruby which describes the use of SketchUp as drawing environment and animation engine for visual models that are animated in Sketchup’s embedded Ruby programming language. This document describes a subsequent approach that retains Sketchup as the drawing environment. These are exported into the JME (Java Monkey) game engine where they are animated with Java code.
Sketchup as animation engine
Sketchup and Ruby leave little to be desired when they’re used separately. Even in combination, the interface between them is well documented, if sparsely. The problem is that the documentation covers only the interface and reveals almost nothing about how the binary code on the Sketchup side of the interface works. In particular:
  1. Animation speeds rarely exceeded one frame per section. It was never clear whether the limit is intrinsic to Sketchup, bottlenecks in the Sketchup-Ruby interface, or defects in my own code.
  2. There is what appears to be a bug in Ruby exception handling such that Sketchup would crash if certain exceptions were thrown that weren’t explicitly handled by user code.
  3. Details of how ruby code should request translation and rotation transforms are idiosyncratic to Sketchup and not documented sufficiently to be used reliably.
  4. When Ruby is embedded within Sketchup, external debugging tools (such as eclipse) no longer work, so debugging is excruciating.
Java Animation Engines
While some the above problems could probably be surmounted, they consumed enough time that I began to look around for more productive alternatives. I eventually settled on JME (Java Monkey Engine).
To get the obvious “Java versus Ruby” questions out of the way first:
  1. Java code is far more verbose than Ruby code. This is to support Java’s compile-time type-checking. Ruby’s terseness derives from its reliance on run-time type-checking. The savings in source code size is the price of having to build comprehensive test libraries to detect errors that Java detects at compile time.
  2. Eclipse supports both languages (and many others). However it provides full-featured support only for Java. In particular refactoring support is not supported for Ruby. Refactoring is doubly painful in Ruby because the changes must be manually replicated not just for the code, but also for the test scripts.
  3. Although Eclipse’s debugger works for ordinary Ruby applications, it does not work at all for Sketchup’s embedded Ruby.
  4. JME and LWJGL (the java OpenGL interface upon which JME is based) are open source whereas Sketchup is proprietary closed source.  Although the ability to examine or even change all parts of the environment was what drew me to JME in the first place, the open source community brings its own kinds of challenges. On the whole, JME’s developers shares the widespread misconception that source code, examples (test cases), JavaDocs and wikis are adequate compensation for well-written documentation.
Export Formats
The largest challenge of the early stage of this quest was developing a work flow to support drawing a model in SketchUp and animating that model in JME (or equivalent). Fortunately SketchUp supports many (8 or so) export formats. Unfortunately, they were all developed for special purposes and have shortcomings that complicate their use in this project. I ultimately converged on the .obj format because, being ASCII instead of binary, it supports the manual interventions that proved necessary to get anything working at all.
As I mentioned before, JME provides no application capable of importing a model and inspecting it to ensure that it imported properly. It only only example code (test cases) that demonstrate how to do various things in isolation. And no matter what I tried, .obj files (and/or the related .mtl files which separately describe the “materials” used to color the model in Sketchup) were not rendering properly. When they could be seen at all (i.e. not offscreen because of an error in camera, lighting or model positioning), they were rendered as solid, almost unrecognizable white or black blobs.
After weeks of trying to diagnose the problem by inspecting run-time structures in the debugger, I tried the “community forum”. JME’s developers turned out to be remarkably friendly, knowledgeable and helpful. After several false starts, the problem turned out to be outright bugs in the ObjToJme class, the JME component that handles importing of the .obj format. In just a few days, the developers corrected the bugs while improving import performance many-fold in the process.
Another component of the solution was Blender, an open source graphical editing environment with functionality comparable to Sketchup. Although Blender suffers from the most bone-headed and counter-intuitive user interface I’ve ever encountered, I did manage to learn it well enough to prove that Sketchup was exporting .obj and .mtl files well enough that Blender could display them correctly, thus ruling out several false possibilities.
Linking Visual Objects to Procedural Code
To animate visual objects with procedural (Java) code, the code must be able to identify objects in some manner, ideally by name. When building the objects in Sketchup, I’d been careful to compose each object as a Sketchup “group” and to give each instance a unique name.
This soon ran into limitations of the .obj format itself. Sketchup’s manual puts it this way: The OBJ output supports a flat set membership hierarchy meaning that the format understands which objects belong to any set. It does not support a tree hierarchy meaning it doesn't know if one particular set is actually a component part of another set. This is a limitation of the OBJ format itself.”  Furthermore, although Sketchup faithfully recorded the group names into the exported .obj files, JME’s developers explained that the importer effectively ignored the g (group) information, expecting it instead as “o” (object) lines (I’ll provide an example in a moment). Furthermore, “flat membership hierarchy” turned out to mean that Sketchup’s recommended practice, “group early and often”, cannot be used to make subcomponents easier to manage. Groups must be used only for the top-level components that are to be managed as named entities by the Java code. The solution was as follows:
  1. Open each top-level group in Sketchup and repeatedly “explode” any groups within it. Be sure each top-level group is assigned a unique name.
  2. Export the scene as a whole in .obj format. Don’t export each model within the scene as a separate file; export the whole scene as a single .obj file. Sketchup will automatically generate .mtl files into the same directory.
  3. Use a text editor to copy each line in the .obj file that begins with the “g” character to the next line and change the “g” to an “o”. Edit the added line, omitting names that you don’t recognize, leaving only the group name. Here’s an example, with the added line underlined:
mtllib NagumoWorldNoGroups.mtl
 
g Deck Model
o Deck
 
usemtl Material7_
v 270.729 50.7336 0
vt -3248.75 0
vn -6.29408e-16 1 0
v 300.833 50.7336 0
vt -3610 0
v 300.833 50.7336 -1.8125
vt -3610 -21.75
v 270.729 50.7336 -1.8125
vt -3248.75 -21.75
f 1/1/1 2/2/1 3/3/1 4/4/1
  1. Develop an ad-hoc XML file to associate each visual instance with the Java class for that instance. For example:
<World name="World" group="TieDown1.Model">
 
    <Truck name="BombTruck" group="Truck72694.BombTruck.Model">
        <Queue name="loadQ">
            <Bomb name="Bomb1" group="Group_1272682.Bomb1.Model"/>
            <Bomb name="Bomb2" group="Group_1272682_1.Bomb2.Model"/>
            <Bomb name="Bomb3" group="Group_1272682_2.Bomb3.Model"/>
            <Bomb name="Bomb4" group="Group_1272682_3.Bomb4.Model"/>
            <Bomb name="Bomb5" group="Group_1272682_4.Bomb5.Model"/>
            <Bomb name="Bomb6" group="Group_1272682_5.Bomb6.Model"/>
            <Bomb name="Bomb7" group="Group_1272682_6.Bomb7.Model"/>
            <Bomb name="Bomb8" group="Group_1272682_7.Bomb8.Model"/>
        </Queue>
    </Truck>
    
    <Truck name="TorpedoTruck" group="Truck72694_1.TorpedoTruck.Model">
        <Queue name="loadQ">
            <Torpedo name="Torpedo1" group="Group_1472706_7.Torpedo1.Model"/>
            <Torpedo name="Torpedo2" group="Group_1472706_6.Torpedo2.Model"/>
            <Torpedo name="Torpedo3" group="Group_1472706_5.Torpedo3.Model"/>
            <Torpedo name="Torpedo4" group="Group_1472706_4.Torpedo4.Model"/>
            <Torpedo name="Torpedo5" group="Group_1472706_3.Torpedo5.Model"/>
            <Torpedo name="Torpedo6" group="Group_1472706_2.Torpedo6.Model"/>
            <Torpedo name="Torpedo7" group="Group_1472706_1.Torpedo7.Model"/>
            <Torpedo name="Torpedo8" group="Group_1472706.Torpedo8.Model"/>
        </Queue>
    </Truck>
... many lines omitted here ...
</World>
 
  1. Use Java reflection to build a general-purpose AbstractObject constructor that all subclasses will override.
    /**
     * Construct the object described by <element> and any sub-objects that
     * this <element> contains. This implicitly requires, and assumes, that
     * all subclasses override Constructor(Element e), else Fault will be
     * thrown.
     *
     * @param element
     * @throws Fault
     */
    public AbstractObject(Element element) throws Fault
    {
        Node root = ROOT_NODE; // only used to expose to debugger
        this.name = element.getAttributeValue("name");
        this.node = new Node(name);
        Spatial spatial = ROOT_NODE.getChild(name);
        if (spatial == null)
            System.err.println("In <"+element.getName()+" name=\""+name+"\">: name not found");
        else
            node.attachChild(spatial);
        node.setModelBound(new BoundingBox());
        node.updateModelBound();
 
        // Iterate over contained elements, using reflection to invoke the
        // Constructor(Element e) for the class in "com.binary.nagumo.models"
        // whose name matches the element's name.
        // The hardcoded package name will be generalized in the
        // future. The resulting sub-objects are maintained by name in childMap to
        // help each constructor find its sub-components.
        for (Iterator i = element.getChildren().iterator(); i.hasNext(); )
        {
            Element e = (Element)i.next();
            String type = e.getName();
            try
            {
                Class[] argTypes = new Class[] { Element.class };
                Object[] argValues = new Object[]    { e };
                Class c = Class.forName("com.binary.nagumo.models."+type);
                Constructor constructor = c.getConstructor(argTypes);
                AbstractObject child = (AbstractObject)constructor.newInstance(argValues);
                node.attachChild(child.node);
                childMap.put(child.name, child);
            }
            catch (Exception ex)
            {
                throw new Fault("Exception", ex);
            }
        }
        node.setLocalScale(0.5f);
        node.updateGeometricState(0, true);
        System.out.println(name+" location:"+getLocation());
    }
  1. This code assumes and requires that each subclass to override this constructor as demonstrated by the Truck class, which implements the BombTruck and TorpedoTruck  defined in the .obj file. The Queue class is a general-purpose container for limited resources. It expresses the fact that a truck only has space for a limited number of resources, triggering a ResourceConstraintFault if the space is exhausted while loading or it munitions are exhausted while unloading.
    1. public class Truck extends AbstractObject
    2. {
    3.     final Queue loadQ;
    4.     
    5. public Truck(Element element)
    6. {
    7.     super(element);
    8.     loadQ = (Queue)getChild("LoadQ");
    9. }
    10. Queue loadQ()
    11. {
    12.     return loadQ;
    13.   }
    14. }
State Machine
The state machine logic was converted from Ruby to Java like this:
public abstract class State extends Controller
{
    protected abstract void enter();
    protected abstract void execute();
    protected abstract void exit();
The main difference is that the three state methods are abstract, which requires that all three be overridden in each subclass. Another change is that the state machine logic was extracted into the following inner class, State.Machine:
    public static class Machine
    {
        State currentState;
        State previousState;
        State globalState;
        
        public Machine()
        {
            this.currentState = null;
            this.previousState = null;
            this.globalState = null;
        }
        public Machine(State initialState)
        {
            this.currentState = initialState;
            this.previousState = null;
            this.globalState = null;
        }
        public Machine(State currentState, State previousState, State globalState)
        {
            this.currentState = currentState;
            this.previousState = previousState;
            this.globalState = globalState;
        }
 
        public State getCurrentState() { return currentState; }
        public State getGlobalState() { return globalState; }
        public State getPreviousState() { return previousState; }
        public void setCurrentState(State s) { currentState = s; }
        public void setGlobalState(State s) {globalState = s; }
        public void setPreviousState(State s) { previousState = s; }
 
        public void update()
      {
              if (globalState != null)
                  globalState.execute();
              if (currentState != null)
                  currentState.execute();
        }
        public void changeState(State newState)
      {
        previousState = currentState;
        currentState.exit();
        currentState = newState;
        currentState.enter();
        }
    
        public void revertToPreviousState()
      {
        changeState(previousState);
        }
 
        public boolean isInState(State state)
        {
            return currentState == state;
        }
    }
}
The final improvement is that the ubiquitous “agent” argument in the Ruby implementation was eliminated by making each state a non-static inner class of the agent it belongs to. For example, this is the MunitionsCrew state. This state animates the crew member while moving to to the bomb truck and loading bombs onto the transport dolly:
    private class XferBombsFromStoreToDolly extends State
    {
        protected void enter()
        {
            turnTowards(World.instance.bombTruck, loadQ.resources);
        }
        protected void execute()
        {
            if (intersects(World.instance.bombTruck))
            {
                try
                {
                    Dolly agentDolly = (Dolly) loadQ.resources.get(0);
                    xferResource(Bomb.class, World.instance.bombTruck.loadQ, agentDolly.loadQ);
                    advanceAgenda();
                }
                catch (ResourceConstraintFault e)
                {
                    log.info(e+"");
                }
            }
            else
                moveTowards(World.instance.bombTruck, loadQ.resources);
        }
        protected void exit() {}
    }
Although it is tedious to write the numerous states that make up this simulation, each one is straightforward to write and not particularly error-prone. Each state is a straightforward representation of the logic a human would use in the same situation:
  1. The enter method is called when the agent enters the state. It simply calls turnTowards, which is inherited from AbstractObject, to rotate the crew member’s image to face the destination (World.instance.bombTruck) along with whatever the crew might be carrying in its loadQ at the moment (the dolly which the agent acquired during the previous state, AcquireDolly).
  2. The execute method fires during each update cycle until the agent exits this state (via advanceAgenda). The if statement determines whether the agent has arrived at its destination by calling intersects with the destination object as the argument. This metthod is inherited from AbstractObject, which implements it by determining whether the agent’s BoundingBox (a JME artifact) intersects with the destination’s BoundingBox. If not, the else condition calls moveTowards, which moves the agent (and anything in its loadQ; the dolly in this state) one step towards the bomb truck. When the agent arrives at its destination, the other leg of the loop is taken, which will be explained at the end of this list.
  3. The exit method fires as the agent is transitioning to the next state. In this case the method does nothing other than to satisfy the Java requirement that abstract methods must be overridden in all State subclasses.
The logic inside the try...catch block above is the solution to one of the major unresolved questions of the Ruby implementation... how to handle resource constraints. As written here, the crew member should be blocked from proceeding if a) the bomb truck has no more bombs or 2) if the dolly has no empty space to receive them. The xferResource method reports both conditions by simply raising a ResourceConstraintFault, thus preventing the logic that advances the state. The agent will simply remain in the current state until the blocking condition is relieved, much as a real crew member might. Once both constraints are met (bombs are available and space is available to receive them), the xferResource transfers them from truck’s Queue to the dolly’s Queue and advanceAgenda is called which advances the agent to the next state.
States versus JME’s “Controllers”
This approach has proven so workable that I plan to stick with it. JME, by contrast uses a quite different approach that accomplishes the same thing but is less grounded in state machine theory. Unlike the approach outlined so far, JME doesn’t assume the user will provide simulation-specific classes such as FlightCrew, but will work with JME objects directly. For example, JME provides a Node class that implements an element of a scene graph, which might be a simulation object, the camera, lights and so forth. Since each node can be assigned a Controller instance. Since this instance can be changed during the simulation, JME’s Controllers roughly correspond to my state classes.
More to come
This documents progress to date in converting the Ruby implementation to Java. Both implementations work, but both are incomplete. The Java implementation currently operates at about 100 frames per second; two orders of magnitude faster than Sketchup/Ruby’s 1 frame per second, which is plenty fast for serious work.
Work was temporarily suspended at this point because of other demands on my time. This will be revised when progress resumes.