A few Q's: j3d.util.geometry, 1000's of objects &a

(1) Triagulator.java

In Java3d 1.2, which was closed source, you were encouraged to manually invoke Triangulator(), with 3 possible constant arguments, one of which was EARS_SORTED. Going through the 1.3 source, notably com.sun.j3d.utils.geometry, other than the obvious fact that manual invocation of Triangulator is now depreciated, it turns out they never implemented EARS_SORTED in the first place. You also see that their triangulation code is based on Martin Held"s robust C Triangulation package, FIST (which is a basically a well written ear clipping routine, the source for which is available on request). Comparing sources, it seems very odd the way Sun have translated his code - single length arrays in place of pointers to primitives, copied and pasted code comments, directly copied and odd and or hard to follow looping and array indexing. Did Sun just run his code through a C to Java source converter, clean it up a bit, delete the “hard” parts (eg EARS_SORTED), and bung it in Java3d?

Either way, the Triangulator right now seems a mess. In fact I"d say that about the whole of the utils.geometry package, a good portion of which is just a scrappy source conversion of Martin"s code.

Pointless rant I know, but I don"t see why EARS_SORTED wasn"t implemented. It
would amount to uncommenting some stuff and maintaining the sorted property of
the ear heap (not rocket science), of course with direct reference to Martin"s
implementation :slight_smile:

(2) How do manage 1000"s of simple objects

If I want to set up a very simple real time simulation (as in, simulating a system in time and space, where the objects are simple but numerous) in Java3d, what would be the quickest way of updating the position of a “massive” number of geometrically simple and identical or almost identical moving objects?

I could toy with various tricks and hacks for weeks, but I assume this is a wheel that has been invented many times before. Anchoring each object under its own transform and updating thousands of transforms seems silly - do I want to have a single transform, attach everything to that, and then access each object"s co-ordinates directly? I have noted the BY_REFERENCE update method whilst perusing the API"s, but this seems to limit my own engine"s ability to fiddle with it"s own data. Is there some magical piece of synchronization code someone has discovered that lets the engine meddle with its (Java3D referenced arrays), and then tells Java3D when to look at them?

Oh, one other thought I just had whilst writing this - removing, updating, and reattaching the Shape once per engine tick. Is this an absurd idea?
Have I got the wrong idea in wanting to use Java3d as a quick way of displaying what my underlying engine is calculating? If I use Java3d, should everything be “within” Java3d? By that I mean use behaviors for movement updates, have all the engine controlled objects as direct parts of the scenegraph (and so on). It just seems that this would defeat the original purpose (handling as many engine objects as possible as fast as possible, all within Java).

I would be tempted to set up a traditional game loop system, where my updataGraphics() uses direct OpenGL calls when I want (perhaps through JOGL?), if it wasn"t for some other massively convenient Java3D features I plan on using.

Any tips? I hope I have struck the right balance between UTFSE and wasting
disproportionate amounts of time re-inventing the wheel. Oh and for the sake of argument, when I say simple object, let"s assume we could go right down to “cone”, or “box”, “triangle” even, on the off chance that would induce some different replies :slight_smile:

(3) Transform the View, not the Scene?

Oh and one other question which is going to make me look very dumb. I am using the MouseTranslate convenience class at present to pan the world view. But I seem to be doing it the wrong way round - I have attached it to the root transform, and specified a huge activation radius so everything gets transformed. So I am in fact transforming the whole world each time. Don"t I want be transforming just the view? I have tried to hook MouseTranslate up to the View but with no luck. Again, have I got the wrong idea?

On (2) it begins to sound like your getting into particle systems.

Particle systems in any engiena re typically doen in world-space, not in the scene graph and yes you update their position directly. AIR In J3D you do that with geometry by reference and geometry updaters. I havent written code to do this msyelf but Shaww Kendall has. Maybe you could impsoe on him to lay it out.

On (1) your asking about internal code of J3D. I cant say anything intelligent about that. Id try the J3D interest list because I know the Sun J3D developers read that. (You might want to word the question in a way that doesnt come off like your critquing their code though-- thats generally not the ebst wya to get the most helpful answers from engineers.)

on (3) look at the examples with Java3D. The OrbitBehaviior I believe will contain all your answers.

Here’s my OrbitView class which I created some time ago while experimenting with Xith3D. It’s kind of simple but works great and the camera stays aligned vertically which it doesn’t with the OrbitBehaviour.


import com.sun.j3d.utils.behaviors.vp.ViewPlatformBehavior;
import com.sun.j3d.utils.universe.ViewingPlatform;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Transform3D;
import java.util.Enumeration;
import javax.media.j3d.WakeupCondition;
import javax.media.j3d.WakeupOnElapsedFrames;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Vector3f;

/**
 * A simple Orbit view manipulator. Left mouse rotates the view, right mouse
 * translates the view horizontaly, Shift + right mouse translates it verticaly.
 * Use mouse wheel for zoom. 
 *
 * @author Otelo
 * @version 1.1
 * @usage
 *    OrbitView orbit = new OrbitView( yourCanvas3D );
 *    OrbitView orbit = new OrbitView( yourCanvas3D, centerOfInterestDistance );
 *    orbit.setSchedulingBounds(yourBounds);
 *    yourVievingPlatform.setViewPlatformBehavior(orbit);
 */
public class OrbitView extends ViewPlatformBehavior
{
    
    private TransformGroup viewTransform;
    private Canvas3D canvas;
    //some vectors
    private Vector3f originToInterest = new Vector3f();
    private Vector3f interestToPivot  = new Vector3f();
    private Vector3f originToPivot    = new Vector3f();
    private Vector3f cameraFocusOld   = new Vector3f();
    private Vector3f cameraFocusNew   = new Vector3f();
    private Vector3f translateXYZ     = new Vector3f();
    private Vector3f rotateXYZ        = new Vector3f();
    private Vector3f helperVector     = new Vector3f();
    //some transforms
    private Transform3D t3b = new Transform3D();
    private Transform3D t3d = new Transform3D();
    private Transform3D t3a = new Transform3D();
    private AxisAngle4f a4f = new AxisAngle4f();
    //some constants
    private static final float zoomStep = 0.2f;
    private static final float moveStep = 0.01f;
    private static final float minmDist = 1.0f;
    //mouse cursor locators
    private Point oldXY = new Point();
    private Point newXY = new Point();
    //valid action?
    private boolean action = false;
    private boolean hasChanged = false;
    
    WakeupCondition trigger = new WakeupOnElapsedFrames(0);
    
    /** Creates a new instance of OrbitView */
    public OrbitView(Canvas3D canvas)
    {
        this(canvas, 2f);
    }
    
    /** Creates a new instance of OrbitView */
    public OrbitView(Canvas3D canvas, float centerOfInterestDistance)
    {                                                         
        if(centerOfInterestDistance < minmDist) centerOfInterestDistance = minmDist;
        cameraFocusOld.set(0f, 0f, centerOfInterestDistance);
        cameraFocusNew.set(cameraFocusOld);
        
        canvas.addMouseListener(new MouseAdapter()
        {
            public void mousePressed(MouseEvent e)
            {
                int mask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK;
                if((e.getModifiersEx() & mask) != 0)
                {
                    action = true;
                    oldXY = e.getPoint();
                }
            }
            
            public void mouseReleased(MouseEvent e)
            {
                action = false;
            }
        });
        //
        canvas.addMouseMotionListener(new MouseMotionAdapter()
        {
            public void mouseDragged(MouseEvent e)
            {
                int mask = MouseEvent.BUTTON1_DOWN_MASK;
                if(action)
                {
                    newXY = e.getPoint();
                    if((e.getModifiersEx() & mask) == mask)
                    {                                        //rotateXY
                        rotate(
                              (float)(oldXY.y - newXY.y),
                              (float)(oldXY.x - newXY.x));
                    }
                    else
                        if(e.isShiftDown())                  //moveY
                            translateY((float)(oldXY.y - newXY.y));
                        else                                 //moveXZ
                            translateXZ(
                                  (float)(oldXY.x - newXY.x),
                                  (float)(oldXY.y - newXY.y));
                    oldXY.setLocation(newXY);
                    hasChanged = true;
                }
            }
        });
        //
        canvas.addMouseWheelListener(new MouseWheelListener()
        {
            public void mouseWheelMoved(MouseWheelEvent e)
            {
                cameraFocusNew.z -= e.getWheelRotation() * zoomStep;
                hasChanged = true;
            }
        });
    }
    
    public void initialize()
    {        
        ViewingPlatform vp = getViewingPlatform();    
        if (vp != null) viewTransform = vp.getViewPlatformTransform();
        else throw new IllegalStateException("No VievingPlatform available!");  
        wakeupOn(trigger);        
    }
    
    private void translateXZ(float dx, float dz)
    {
        helperVector.set(1, 0, 0);
        t3d.transform(helperVector);
        helperVector.normalize();
        helperVector.set(
              dx * helperVector.x * moveStep,
              0f,
              dx * helperVector.z * moveStep);
        translateXYZ.add(helperVector);
        
        t3d.get(helperVector);
        dz *= (helperVector.y < 0) ? -1f : 1f;
        
        helperVector.set(0, 0, 1);
        t3d.transform(helperVector);
        helperVector.normalize();
        helperVector.set(
              dz * helperVector.x * moveStep,
              0f,
              dz * helperVector.z * moveStep);
        translateXYZ.add(helperVector);
    }
    
    private void translateY(float dy)
    {
        translateXYZ.y -= dy * moveStep;
    }
    
    private void rotate(float dx, float dy)
    {
        rotateXYZ.x += dx;
        rotateXYZ.y += dy;
    }
    
    public void processStimulus(Enumeration criteria)
    {
        wakeupOn(trigger);
        if(hasChanged) hasChanged = false;
        else return;
        viewTransform.getTransform(t3a);
        //get camera's position which becomes the origin-to-pivot vector
        t3a.get(originToPivot);
        //find initial point of interest {from its distance...cameraFocusOld}
        interestToPivot.set(cameraFocusOld);
        t3a.transform(interestToPivot);
        //calculate origint-to-interest vector from the two vectors above
        originToInterest.sub(originToPivot, interestToPivot);
        //now we can update the interest-to-pivot vector for the new zoom distance
        if(cameraFocusNew.z != cameraFocusOld.z)
        {
            interestToPivot.set(cameraFocusNew);
            t3a.transform(interestToPivot);
            //push our center of interest forward in case of excessive zoom
            if(cameraFocusNew.z < minmDist) cameraFocusNew.z = minmDist;
            cameraFocusOld.set(cameraFocusNew);
        }
        //apply the rotations
        t3b.rotY(rotateXYZ.y * moveStep);
        helperVector.set(1, 0, 0);
        t3a.transform(helperVector);
        a4f.set(helperVector, rotateXYZ.x * moveStep);
        t3d.set(a4f);
        t3b.mul(t3d);
        //apply the translations
        originToInterest.add(translateXYZ);
        //flatten the transforms
        t3d.set(originToInterest);
        t3d.mul(t3b);
        t3a.setTranslation(interestToPivot);
        t3d.mul(t3a);
        viewTransform.setTransform(t3d);
        //clean-up
        translateXYZ.set(0f, 0f, 0f);
        rotateXYZ.set(0f, 0f, 0f);
    }
}

Sounds like a particle system (shameless promotion: www.indietechnologies.com). If the shapes are rotating or moving independent of one another, then yes a seperate transform group for each shape is a way to do it.

This is done via the GeometryUpdater interface. An example is included in this article: http://jdj.sys-con.com/read/48544.htm

Generally this would perform poorly in J3D. Updates to the scene can be triggered via a behavior each frame that ensures all of your updates are completed prior to showing the next frame. Removing and reattaching objects from a BranchGroup appears to consume a lot of memory is it relatively slow compared to using a Switch (or simply hiding the object).

Mike

Ill plug Mikes product too. ;D

Its a really ncie particle system, really well integrated with J3D, at a really reasonable price. It saves a lot of work over writing and tuning one yourself

How much is it?

Maybe I should ask for sponsoring…

I think it was $100 for my license which allows me to freely redistribute his Jar with my project (JNWN)

It’s $99 US (http://www.indietechnologies.com/store/buygenesisfx.html). The FAQ at our site covers the questions about options for other currencies, etc.

Mike

(p.s. Jeff, Thanks for the promo!)

It was some time ago now but I used the “silly” method. I simulated molecules and in “atom” mode the user could view every atom in the form of a sphere. Each Java3D Sphere had its own transform (from what I recall that’s how you move the Sphere to positions on the screen). Then when “the engine” had calculated new atom positions I just entered the new positions into the Spheres.

In this way Java3D could handle a large number (many thousands) of Sphere objects but I didn’t update the screen too often (maybe once a second at the most).

I noticed a bug also. When you tried to “load” Java3D with too many Spheres and run out of memory Java3D’s behaviour was very quirky. It took like 30 seconds before it would return and there was a memory leak. Maybe it’s fixed.