CloneCreator addition to jME

Change title: Clone of Spatials

Created classes: CloneCreator

Affected classes: All classes extending Spatial and Controller that want to be cloned (it is optional). LWJGLRenderer and RenderQueue

Summary of changes: Clones are created from a CloneCreator and rendered in the RenderQueue. VBO aren’t currently supported (mostly because it doens’t seem to work on my machine), but would be by changing the getVBOVertexID and setVBOVertexID to check a static HashMap inside CloneCreator against its ZOrder (only if the render state is CLONE of course).


CloneCreator is straitforward. Mojomonkey has the exact code. Here are a few snippits to give you an idea of how it works.

        Box c=new Box("my box",new Vector3f(0,0,0),new Vector3f(1,1,1));
        c.setModelBound(new BoundingBox());
        cc2=new CloneCreator(c);
        Spatial theCopy=cc2.createCopy();

Spatial.putClone(Spatial Store, CloneCreator properties)

     * This function stores into the "store" spatial the attributes of this Spatial.
     * This function should be overridden in any Spatial extending classes that wish
     * to be cloned.  The properties tag signals how cloning should take place.
     * @param store The spatial to store this Spatial's information into.
     * @param properties This signals how to clone this spatial.
     * @return The store spatial, after Clone is complete.
    public Spatial putClone(Spatial store,CloneCreator properties){
        if (store==null)
            return null;
        store.setLocalTranslation(new Vector3f(getLocalTranslation()));
        store.setLocalRotation(new Quaternion(getLocalRotation()));
        store.setLocalScale(new Vector3f(getLocalScale()));
        for (int i=0;i<renderStateList.length;i++){
        Iterator I=geometricalControllers.iterator();
        while (I.hasNext()){
            Controller c=(Controller);
            Controller toAdd=c.putClone(null,properties);
            if (toAdd!=null)
        return store;


    public Spatial putClone(Spatial store,CloneCreator properties){
        if (store==null)
            return null;
        Geometry toStore=(Geometry) store;  // This should never throw a class cast exception if
                                            // the clone is written correctly.

        // Create a CloneID if none exist for this mesh.
        if (!properties.CloneIDExist(this))


        if (properties.isSet("vertices")){
            toStore.vertBuf      = this.vertBuf;
            toStore.vertex       = this.vertex;
            toStore.vertQuantity = this.vertQuantity;
        } else{
            Vector3f[] temp=new Vector3f[this.vertex.length];
            for (int i=0;i<temp.length;i++){
                temp[i]=new Vector3f(this.vertex[i]);

        if (properties.isSet("colors")){    // if I should shallow copy colors
            toStore.colorBuf = this.colorBuf;
            toStore.color    = this.color;
        } else if (color!=null){                                             // If I should deep copy colors

            ColorRGBA[] temp=new ColorRGBA[this.color.length];
            for (int i=0;i<temp.length;i++){
                temp[i]=new ColorRGBA(this.color[i]);

        if (properties.isSet("normals")){
            toStore.normBuf = this.normBuf;
            toStore.normal  = this.normal;
        } else if (normal!=null){
            Vector3f[] temp = new Vector3f[this.normal.length];
            for (int i=0;i<temp.length;i++){
                temp[i] = new Vector3f(this.normal[i]);

        if (properties.isSet("texcoords")){
            toStore.texBuf  = this.texBuf;
            toStore.texture = this.texture;
        } else {
            Vector2f[][] temp=new Vector2f[this.texture.length][];
            for (int i=0;i<temp.length;i++){
                if (this.texBuf[i]==null) continue;
                temp[i] = new Vector2f[this.texture[i].length];
                for (int j=0;j<temp[i].length;j++){
                    temp[i][j] = new Vector2f(this.texture[i][j]);

        if (bound!=null)
            toStore.setModelBound((BoundingVolume) bound.clone(null));

        return toStore;


    public Spatial putClone(Spatial store,CloneCreator properties){
        TriMesh toStore;
        if (store==null){
            toStore=new TriMesh(this.getName()+"copy");
        } else {
            toStore=(TriMesh) store;

        if (properties.isSet("indices")){
            toStore.indices          = this.indices;
            toStore.indexBuffer      = this.indexBuffer;
            toStore.triangleQuantity = this.triangleQuantity;
        } else{
            int[] temp=new int[this.indices.length];
            for (int i=0;i<temp.length;i++){

        if (properties.isSet("obbtree")){
            toStore.collisionTree = this.collisionTree;

        return toStore;


    public Spatial putClone(Spatial store,CloneCreator properties){
        Node toStore;
        if (store==null)
            toStore=new Node(getName()+"copy");
            toStore=(Node) store;
        for (int i=0,size=children.size();i<size;i++){
            Spatial child=(Spatial) children.get(i);
        return toStore;


    public Controller putClone(Controller store,CloneCreator properties){
        if (store==null)
            return null; = active;
        store.repeatType = repeatType;
        store.minTime = minTime;
        store.maxTime = maxTime;
        store.speed = speed;       
        return store;


    public Controller putClone(Controller store,CloneCreator properties){
        if (!properties.isSet("spatialcontroller"))
            return null;
        SpatialTransformer toReturn=new SpatialTransformer(this.numObjects);

        toReturn.parentIndexes = this.parentIndexes;
        toReturn.keyframes = this.keyframes;
        toReturn.curTime = this.curTime;
        toReturn.haveChanged = this.haveChanged;


        return toReturn;

Part of LWJGLRenderer.draw(TriMesh)

        FloatBuffer verticies=t.getVerticeAsFloatBuffer();
        if (prevVerts!=verticies){
            if (t.isVBOVertexEnabled() && GLContext.OpenGL15) {
                usingVBO = true;
                GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, t.getVBOVertexID());
                GL11.glVertexPointer(3, GL11.GL_FLOAT, 0, 0);
            } else {
                if (usingVBO) GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
                GL11.glVertexPointer(3, 0, t.getVerticeAsFloatBuffer());
        prevVerts = verticies;

Files for review are at:

Is there code to order the opaque render queue grouping clones?

Why does Spatial define a putClone if everything that extends it overrides it?

If clone creator creates the copies, why bother having the putClone methods at all? (You don’t use it in your example)

verticies is not a word. :wink:

Files for review are at:

"Is there code to order the opaque render queue grouping clones?"

Opaque Clones are simply Clones. CLONE is a new type in the renderqueue. They are sorted by Z order by the RenderQueue. Their Z order is automatically set to their "Clone ID" which is unique for each cloned Geometry.

"Why does Spatial define a putClone if everything that extends it overrides it?"

Because of the super.putClone() chaining.

TriMesh writes index/OBBTree values, then calls super.putClone(). Geometry then writes color/normal/vertex/texture/Bounding values, then calls super.putClone(). Spatial writes local translation/rotation/scale.

This allows for the least amount of repeated code. Both Node and Geometry call super.putClone() to write local translation/rotation/scale values.

"If clone creator creates the copies, why bother having the putClone methods at all? (You don't use it in your example)"

Users don't call putClone at all. I should put that in the javadoc. Instead, users call CloneCreator.createClone() to get their clone. The putClone method is for the super.() chaining. It also lets people create their own spatial extending class called "MyUberModel" and as long as they define putClone() for "MyUberModel" they can use the CloneCreator class to copy it. The method createClone() is also there to provide some filtering to clone creation to handle special cases.

"verticies is not a word"

I think we all need to enbiggen our vocabularly.

Oops, missed the super call. Teach me to read something so late.

I suppose I am alright with a new CLONE renderqueue flag. However, make sure it’s explicitly clear that you only want to set this if the clones are opaque. Otherwise, use TRANSPARENT. I was hoping for a little behind the scenes magic that if it’s OPAQUE group clones.

This is the only way to signal that it is a clone and not another regular TriMesh. Well, either this or create a brand new "Clone" boolean.

I realize that, that’s why I just want it to be clear that you should still use the TRANSPARENT flag on transparent clones. Javadoc’d and in your starters guide.

Hey Cep, the cloning code looks pretty nifty. Looking at your tests (haven’t run them yet as my jME is undergoing a bit of operation) it appears that controllers (while cloned) are still able to be independently controlled. Is that the case? I hope so as obviously that is important.

The one change I would recommend revolves around the “hijacking” of the mode flag. I do not think it is a great idea to add a CLONE flag as that is misleading and also dangerous (since the user can think they are setting something that they really aren’t.)

I would argue that instead we should have a boolean isClone in Geometry or perhaps Spatial that your clone creator would flip on. The queue mode would be left alone to be set by the user as either opaque or transparent. When adding to the queue, if mode = opaque then check isClone… if true, add to clone bucket, if false add to opaque bucket. transparents would still go to the transp bucket whether clones or not (otherwise they won’t render correctly.) It is still a danger that the user would think setting this field would enable cloning, but at least it is a segregated field, not overlapping other functionality.

Also, I am keen on the shared vbo ids working. :slight_smile: Anything I can do to help with that, let me know.

The clone creator keeps a HashMap of Originals to Clones whenever a Spatial is cloned, so that you can reasign cloned Controllers to point to the correct “Cloned” spatial after the clone is complete. I’m fine with adding an “isClone” boolean. I would actually rather have that. I was just trying to keep the size of a Spatial as small as posible, so I didn’t want to add more information to them.

The isClone would go on Geometry because only leaf nodes get rendered.

For VBO support, if there was a unified “VBO” flag that would help. If I can get VBO working on my home computer, putting them in should be easy. A question about how VBO work though: If I change the float values in the FloatBuffer, do I have to update the VBO? If a user creates a brand new FloatBuffer by assigning a larger Vector3f[] in set*(), does the VBO id need to update then?

Cool, the flag would alleviate the concerns I was having with users having to remember when to use CLONE and when not to. They can just use OPAQUE and TRANSPARENT like normal.

"Cep21" wrote:
For VBO support, if there was a unified "VBO" flag that would help. If I can get VBO working on my home computer, putting them in should be easy. A question about how VBO work though: If I change the float values in the FloatBuffer, do I have to update the VBO? If a user creates a brand new FloatBuffer by assigning a larger Vector3f[] in set*(), does the VBO id need to update then?

VBOs come in different flavors... static and dynamic being the basic distinction. So the id doesn't need to be updated if our VBO code supports dynamic mode. Right now we only support static. As I mentioned in another thread, I'd like to take on cleaning up and enlarging VBO support. If Mojo is ok with that, I'll formally post something later in the week.
If Mojo is ok with that, I'll formally post something later in the week.

Fine by me.

You get the VBO stuff in, I’ll add the support.

I added a

int cloneID;

to Spatial. It’s default to -1, which means it’s not a Clone. All clones have a cloneID, which when VBO support is in can be used to refrence which VBO array the clone should access. Is this ready for commit?

Shouldn’t it already work with the existing VBO code (albeit only static trimeshes… so only static terrain, shapes or animations that rotate, scale or transpose would work.)

If so, support for the other types should probably be automatic as well once, as you say, that is in.

How would I match up the clone to the VBO buffer? If users create the clone before the VBO buffer is made (IE before prepVBO in LWJGLRenderer), then I will need a way to identify which VBO buffer this cloned Geometry should use. I can’t copy VBO index IDs during clone because they aren’t created yet. If each clone had a clone ID, then getVBOID could refrence a HashMap against a cloneID to get the correct VBO.

Right, I was off thinking for the moment that you were using that commonly linked Trimesh methodology we’d originally talked about.

So a vbo prebuilder function that the clone process could use to establish the vbo ids (there may be more than one per trimesh), should do the trick right?

Can we take prepVBO out of the main LWJGLRenderer.draw(trimesh) and move it to a prestep that users need to do to "prep" the VBO for their mesh? That would solve this.

Yep, we could, as long as it is still part of the renderer class. (or another implementation - eg LWJGL - specific class)

Hmm, any consequences though? For example, right now the card isn’t cluttered with any VBOs until they are actually drawn the first time.

It would be the user’s responsibility to only prev VBO they know they going to use. Do created VBO need to be “deleted” or is that automatic when the game ends? Should there be a way to delete created VBO buffers during the game if a user knows an object is not going to be used anymore?

As far as “where” to put them, this kind of brings up the whole “renderer abstraction” issue. Things inside non-LWJGL classes should be non-LWJGL specific.

Well sure, but they can still access the current renderer and call Renderer interface defined methods. So it’s a non-issue really.

VBOs may need to be better cleaned up, I’ll look at that whenever I get to the VBO stuff (which is at #3 on my things to do in Life list.)