Areas
  • J3D FAQ
  • Tutorials
  • Utilities
  • Books
  • News
  • Other Sites
  •  Chapters
  • TOC
  • Geometry
  • Lighting
  • Textures
  • Behaviours
  • User Feedback
  • Navigation
  • Audio
  • Input Devices
  • No Monitors
  •  Sections
  • Chapter TOC
  • A Window to the World
  • Geometry
  • Textures
  • Event Model
  • Cameras and Viewing
  •   

    Scene Graph Basics

    © Justin Couch 1999

    Once you have the basic window established, you need to put something in that window. There are objects to place, sound to listen to, and above all, putting your view into the world so that you can see it. There are many different ways of doing this in general 3D graphics techniques. On the low level end, you just put in lines, points and polygons. On the high end, you create just objects and let the system take care of it.

    Java3D uses the scene graph approach. A scene graph is a hierarchical approach to describing objects and their relationship to each other. For example, you would describe the connection of your hand relative to your arm. That way, when moving your arm, the hand moves with it. However, you can describe the angle of rotation of the hand in an angle that is relative to the arm. This description of information relative to the parent object is termed a local coordinate system, and is the heart of the scene graph approach to 3D graphics.

    As you descend each level, there is a grouping structure. Typically this grouping structure contains objects of similar characteristics and always has something useful from the parent. Our hand/arm example is typical. Usually at each group there is the ability to move the object relative to the parent.

    Describing the Universe

    Java3D operates on a number of levels of progressive refinement of the scene graph. In the conceptual model of the world, the scene graph is a standalone entity. We can create a scene graph without the need to have anything else from Java3D present - not even a canvas.

    Despite having the sexiest scene graph, what's the point if we can't see or play with it? Apart from the scene graph, we also need to have an on screen place to render it. We also need to put a camera in there to connect the scene graph with the screen. How about mouse input where a user might want to click on an object? Doing a little reading between the lines, this also suggests that it is possible to share the one scene graph between multiple windows.

    If we have all of these parts floating around, it also suggests a collection requirement to pull all of these bits together. At the top level, we use a universe descriptor. A universe describes everything that we see and do within a particular world.

    The universe can be a pretty darn big place. An object a couple of billion units away may be pretty hard to see. If we're stuck in the land of floating point numbers, it's pretty tricky to exactly specify the position. To take care of these problems, we introduce the concept of a locale. This provides a basic frame of reference to increase the amount of precision that we can specify a point in 3d space. A universe may have many locales as needed to describe it. For example, if we were modelling the real universe, then there probably would be one locale for each galaxy.

    From the Locale level, you may now start building a scene graph. You do this by creating various groups and pieces of geometry in whatever order meets your needs. In a typical application, you probably have a collection of pre-built classes that represent objects. Other times these objects are built on the fly in response to your data source(s) like a file or database. You then assemble these objects into groups as needed by the application in each locale.

    In the Java3D model of the world, cameras are also placed inside this scene graph. There is a series of transforms that locate a view in the world, and then a couple of connecting classes used to represent you with the world and finally to glue that to the canvas. Within the 3D world, the camera is called a ViewPlatform and the glue classes are a View.

    Building a Basic Universe

    Now we come to building that universe in Java3D. A number of different approaches are available to building this universe. Because this tutorial is focused around learning the lowest level implementation details of Java 3D, we will extend the VirtualUniverse class itself. Then, within your own universe, you can create methods to add items exactly how you want to classify them.

    Because our basic world is very small, there is no need to have multiple locales. Creating the universe will create a single locale and place two BranchGraphs underneath it for the following objects.

    1. Cameras: Any camera that can be used to view the scene.
    2. WorldSpace Objects: Objects that should be rendered in the world space coordinate system
    Within the BranchGraphs, any object may be placed. The universe class can then provide convenience methods for adding and removing objects to the top level. We can add objects at any level within the scene graph because that is the nature of the scene graph. These methods are only for adding top-level objects.

    After creating a universe (remember that we're extending the VirtualUniverse class) we need to add a locale and BranchGraphs to it. A default Locale is generally used in 3D applications were we don't have to worry about really large coordinate values. Looking at the Locale class you will notice that the only 3D item that we can add to it is a BranchGraph. A BranchGraph is used by Java3D to indicate a part of the scene graph that may act independently. As you'll discover later, there are some optimisation techniques that BranchGraphs give Java3D.

    Constructing a basic universe takes almost no code at all. For illustrative purposes, we'll create everything in code rather than extending various objects:

    VirtualUniverse universe = new VirtualUniverse();
    Locale locale = new Locale(universe);
    
    BranchGroup view_group = new BranchGroup();
    BranchGroup world_object_group = new BranchGroup();
    
    // now add them to the locale
    locale.addBranchGraph(view_group);
    locale.addBranchGraph(world_object_group);
    
    Once you've added the branch graph to the locale, that branch graph, and anything you add to it, is considered live (we'll discuss the difference between live and dead scene graphs shortly). A live scene graph will place restrictions on what you can add, so it is always best to leave this step to the last line of code in your setup routines.

    Locales have a lot more use than just presenting a bunch of geometry. As you will see in later chapters, it is used for many of the interactions with the scene graph by the display devices.

    You may want to use more branch groups than this. It is common that top-level branch groups may be assigned to non-3D objects like lights and sound. How you divide up the top level groups is really up to the application that you are writing and also personal style.

    Viewing the Scene

    With the basic scene layout in place, we now need to concentrate on making sure that the user can see what you want to show them. To do this, we need two mechanisms - one to represent the view position in the world and another to map a particular view to the screen.

    As explained earlier, a view exists as part of the standard scene graph using the ViewPlatform class. They represent the user in the world space. A simple view object can be made by instancing this class and placing it directly into the scene graph at the appropriate spot (placement and orientation are covered shortly).

    ViewPlatform camera1 = new ViewPlatform();
    
    For a standard application, there is nothing more to do. When dealing with more unusual displays, there are a range of options. These options are based on dealing with HMD's and stereo projections. Using the setViewAttachPolicy() method, you can place the virtual head of the user in different positions. The different positions are used to take into account rendering differences for stereo views.

    Coming from the other side, a particular canvas needs to know what to render. On inspection of the Canvas3D and the ViewPlatform classes, you will notice that neither of them has a reference to the other. There is not even a hint as to what class may be needed to form the glue between the two. What you need to know is the View class. View forms the connection between a given view platform and the canvas. Assuming that our view platform that we created has been inserted into the scene graph (an extremely important point as we'll get only a grey screen otherwise!), we can bring the two halves together with the following code:

    View view = new View();
    view.addCanvas3D(canvas);
    view.attachViewPlatform(camera1);
    
    The relationship between views and canvases allows multiple renderings of the same view. In normal programming you probably won't be needing to use more than one canvas because of the limitations of the flat screen. What you might use though, is the collection of different policy options available as part of the view class. Options are based around the viewing parameters that need to be projected to the camera. One that you are most likely to use is the setProjectionPolicy() for controlling orthographic visualisation or standard perspective. You can set it to these two options using the following code:
    view.setProjectionPolicy(View.PARALLEL_PROJECTION);
    view.setProjectionPolicy(View.PERSPECTIVE_PROJECTION);
    
    Dealing with view objects can be a trifle kludgy. Not only do they need to know about both the canvas and view platform, but typically there is a lot of other associated baggage like camera specific displays (HUD type objects or modal requirements). There are many different ways of organising your code:
    • Everything in the universe: The universe is responsible for managing all of the views, which are active and what geometry is currently displayed. The view object and canvas remain as simple Java3D classes.
    • Everything in the Canvas. The universe is minimal; containing the raw world space geometry, lighting and sound. The canvas is the manager of what it sees. It only ever contains one view and that is moved around the universe as required. The canvas also contains the scene graph for any display space or view specific geometry and handling code.
    • Everything in the view: The universe is minimal; containing the raw world space geometry, lighting and sound. A new container class contains the view object and all of the geometry associated with a particular display. When the display changes view platform, the container takes care of the changing of geometry, mode handling and any other items.
    There are many combinations and variations of the above basic themes. Which you choose depends heavily on how much external interaction you have. For example, later on you will see that we do most of our mouse handling through the AWT interface so it makes most sense to use the second arrangement. For general use, we think that you'll find the last option is probably the most used and the most portable because it closely matches the camera concept that we use in this book.

    Placing Objects

    Until this point all you've probably seen is a blank black square when running any of the code. Now it is time to quickly look at different ways of describing and organising your objects.

    Java3D organises all scene graph elements into one of two categories: Leafs and Groups. Objects derived from Leaf are the end of a line - they represent a real, renderable object (light and sounds are considered to be renderable). Group derived objects are those that can represent a branching point or collection of either further groups or Leafs.

    Leafs cover a very broad spectrum of objects from primitives to sound, lights, behaviours and more. For the moment, we'll concentrate on one specific leaf - the Shape3D class. Shape3D is the basic object that all solid (and non-solid!) visible objects in the scene graph take. The shape consists of two properties:

    • Geometry: The raw collection of 3D points and their relationships to form planes and lines in 3D space. There are no standard pre-made primitives like cubes, spheres or cylinders. These must be constructed from sets of points to represent the desired object
    • Appearance: Attributes of the object that define what it looks like in terms of colour, reflection, transparency and texturing. An object with geometry but no appearance set looks like dull grey plastic.

    These may be changed at will; changing one will not change the other.

    On the organisational side of the house are the grouping nodes. The idea of the grouping node is to collect together a bunch of the leaf nodes into common parts. With grouping nodes you can move objects about (TransformGroup), selectively show one of many objects (Switch), use one object many times (SharedGroup) and you've already seen the BranchGroup object, that acts as a manipulation point to act as a local scene graph root node.

    All groups may contain either other groups or leaf nodes, or collections of both. Because you can arrange these groups in this hierarchical order, it leads to term scene graph. In a scene graph you have many objects arranged in a (typically) tree fashion starting from some root group and branching out into sub groups and objects, until you reach the ends of each branch - the individual leaf nodes.

    We won't look much more at these topics, because they'll be covered in more depth shortly.

       Note
      Chapter 2 Geometry covers in very heavy detail how to create every primitive object in Java 3D. Chapter 3 Lighting covers more detail on how the Appearance object and its components effect the look of the geometry.

    Scene Graph Attributes

    Objects in the scene graph have a number of attributes. The scene graph itself also may have a number of attributes that cover all or large parts of objects. These attributes are used to control internal optimisation of the scene graph. A lot of this is tied in with the existance of the BranchGroup class.

    When you first create a collection of group and leaf nodes, they exist exactly in the order that you create them. Typically when you create a scene graph, you don't construct it from a top down approach. That is, start at the locale, add branch graphs, and then children nodes from there. Instead, you'll probably have a collection of pre-built parts that describe say a box or piece of terrain (elevation grid). From these, you then assemble then with the appropriate transforms to make a complete scene. Some of these objects may even come from files that you've loaded from disk, like a VRML world or DXF mesh.

    Because of these arrangements, it is possible that you have not created the most efficient structure when it comes to rendering it at the hardware level. A complex scene may contain thousands of groups and primitive objects, so it is also quite possible that most of them you won't be directly changing. That leaves us a lot of room for doing some optimisations under the hood. For example, in our virtual world, we have a car. The car doesn't change colour or size, but it can change location. Being logical thinkers, we've placed a single transform that contains all of the car parts as the root of that piece of scene graph. All of the objects below it remain in fixed positions, so that allows us to optimise away any lower level transforms that might exist.

    Capability Bits

    If you've already gone for a wander through the javadoc for Java3D, you will note that the base class for both group and leaf classes is the SceneGraphObject. In this, you will find the setCapability() method. Capability bits are the Java3D way of defining exactly what can and cannot be optimised away by the library. Although the base class does not define any itself, you will note that every derived class will.

    By default, all capabilities are set to the negative. If you want to do something to an object in the scene graph, you need to set the capability bit to allow it. Capabilities are also very fine grained. For example, in the TransformGroup class, you have separate capabilities to read and write the transform object used to actually move the object. If you set the read bit, it does not automatically allow you to write to it or vice versa.

    Capability bits do not transfer to the children of groups. If you have a scene graph that represents an arm with two transform groups, one each for elbow and wrist, setting the capability to write to the shoulder transform does not automatically allow you to write to the wrist or elbow transforms. These must be set individually.

    One potential area of confusion is the difference between object attributes and capabilities. A capability can only ever be set through the setCapability() method. If the object has any other set or get methods, then these define attributes. The capability bits determine what happens when you call one of these get/set methods - will the request be accepted or will it bounce?

    Live and Dead Scene Graphs

    At the point where you add your objects to the current locale, they now become eligible for rendering. Any renderable part of a scene graph is termed live. It is now possible that the low level rendering system will pass through the contents of that object and turn them into something visible on screen. The corollary of this is that any scene graph part that is not currently added to a locale can be classified as dead.

    In the previous section, we quickly looked at capability bits. These bits define what can and cannot be done. When the object becomes live, those capabilities are used to determine what a user may do on the active scene. The specification is very loose about what this means though when it comes to dead scene graphs. Under some implementations, the capability bits may be ignored when dealing with a dead scene graph, while for others they will have full effect. For any node in the scene graph, you can determine its status by calling the isLive() method of SceneGraphObject. A true result means that it is currently being rendered.

    If the capability is not set to allow you make a modification to an object, it will generate a CapabilityNotSetException. Get used to seeing this because it comes up very often! When you see this it means that you currently have a live scene graph and you are trying to make modifications to it.

    Having capabilities is only part of the optimisation story. You actually need to make use of them as well by telling Java3D that it is OK to start the optimisation process. To do this, you compile the scene graph. The compile() method is found in the BranchGroup class, which, if you remember, is always treated as the root object of a sub graph. When you call this, it allows Java3D to take that whole scene graph and apply whatever tricks it can to make it run as fast as possible. Obviously, the more capabilities that you request, the smaller the amount of potential optimisation that may take place. It pays to be as miserly with capabilities as possible.

       Note
      Doug Gehringer from the Sun Java 3D team has written up a great explanation of what the compile() really does and where it is useful. See the FAQ for more information (Put in correct link!)
    You can check if a node in the scene graph has been compiled by calling the isCompiled() method of SceneGraphObject. A true result means that it is compiled. Like a live scene graph, if you attempt to modify something that you have not set the capability for, it will result in an exception.

    A compiled scene graph is not necessarily the same as a live one. They are independent states and either could be the result of your CapabilityNotSetException.

    Code Implementation

    With the scene graph well described, we can create a representative class that will be used as part of the demonstration code. For this, we will create a class called UniverseManager that extends VirtualUniverse. The idea is that this will perform a basic management role for looking after the geometry and associated information.
    public class UniverseManager extends VirtualUniverse
    {
      private Locale   locale;
      private BranchGroup view_group;
      private BranchGroup world_object_group;
    
      public UniverseManager()
      {
        locale = new Locale(this);
    
        view_group = new BranchGroup();
        view_group.setCapability(Group.ALLOW_CHILDREN_READ);
        view_group.setCapability(Group.ALLOW_CHILDREN_WRITE);
        view_group.setCapability(Group.ALLOW_CHILDREN_EXTEND);
    
        world_object_group = new BranchGroup();
        world_object_group.setCapability(Group.ALLOW_CHILDREN_READ);
        world_object_group.setCapability(Group.ALLOW_CHILDREN_WRITE);
        world_object_group.setCapability(Group.ALLOW_CHILDREN_EXTEND);
      }
    
      public void addWorldObject()
      {
        world_object_group.addChild(node);
      }
    
      public void makeLive()
      {
        view_group.compile();
        world_object_group.compile();
    
        locale.addBranchGraph(view_group);
        locale.addBranchGraph(world_object_group);
      }
    }
    
    
    Convenience methods will be added as we go along. However, you should also note
    the capability bits that have been set. In order to be able to add and remove
    objects from the scene graph, we need to have all these capabilities specified.
    
    

      

    [ TOC ] [ Home ] [ FAQ ] [ Books ] [ Tutorials ] [ Utilities ] [ Contact Us ]