From 2bdb3de2f5ff26d34219664e21398064aedd2cce Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Fri, 25 Mar 2016 04:31:10 -0400 Subject: [PATCH] Started implementing the JmeCloneable stuff for Spatial and Mesh. Still need to catch some of the outer subclasses of Node and Geometry. Nothing is hooked up or tested yet. --- .../src/main/java/com/jme3/app/StatsView.java | 28 +- .../main/java/com/jme3/light/LightList.java | 56 ++- .../java/com/jme3/scene/AssetLinkNode.java | 17 +- .../main/java/com/jme3/scene/BatchNode.java | 83 ++-- .../main/java/com/jme3/scene/CameraNode.java | 14 +- .../main/java/com/jme3/scene/Geometry.java | 115 ++--- .../main/java/com/jme3/scene/LightNode.java | 18 +- .../src/main/java/com/jme3/scene/Mesh.java | 405 ++++++++++-------- .../src/main/java/com/jme3/scene/Node.java | 144 ++++--- .../src/main/java/com/jme3/scene/Spatial.java | 180 +++++--- .../scene/instancing/InstancedGeometry.java | 125 +++--- .../jme3/scene/instancing/InstancedNode.java | 123 ++++-- .../src/main/java/com/jme3/util/IntMap.java | 59 ++- .../com/jme3/system/version.properties | 3 +- 14 files changed, 826 insertions(+), 544 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index 9fb7670fe..8b88833c2 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -69,7 +69,7 @@ public class StatsView extends Node implements Control, JmeCloneable { private int[] statData; private boolean enabled = true; - + private final StringBuilder stringBuilder = new StringBuilder(); public StatsView(String name, AssetManager manager, Statistics stats){ @@ -95,22 +95,22 @@ public class StatsView extends Node implements Control, JmeCloneable { public float getHeight() { return statText.getLineHeight() * statLabels.length; } - + public void update(float tpf) { - - if (!isEnabled()) + + if (!isEnabled()) return; - + statistics.getData(statData); stringBuilder.setLength(0); - - // Need to walk through it backwards, as the first label + + // Need to walk through it backwards, as the first label // should appear at the bottom, not the top. for (int i = statLabels.length - 1; i >= 0; i--) { stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n'); } statText.setText(stringBuilder); - + // Moved to ResetStatsState to make sure it is // done even if there is no StatsView or the StatsView // is disable. @@ -122,16 +122,16 @@ public class StatsView extends Node implements Control, JmeCloneable { return (Control) spatial; } - @Override - public Object jmeClone() { + @Override + public StatsView jmeClone() { throw new UnsupportedOperationException("Not yet implemented."); - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { + @Override + public void cloneFields( Cloner cloner, Object original ) { throw new UnsupportedOperationException("Not yet implemented."); } - + public void setSpatial(Spatial spatial) { } diff --git a/jme3-core/src/main/java/com/jme3/light/LightList.java b/jme3-core/src/main/java/com/jme3/light/LightList.java index fc08df2c2..dfeb65405 100644 --- a/jme3-core/src/main/java/com/jme3/light/LightList.java +++ b/jme3-core/src/main/java/com/jme3/light/LightList.java @@ -33,6 +33,8 @@ package com.jme3.light; import com.jme3.export.*; import com.jme3.scene.Spatial; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import com.jme3.util.SortUtil; import java.io.IOException; import java.util.*; @@ -40,10 +42,10 @@ import java.util.*; /** * LightList is used internally by {@link Spatial}s to manage * lights that are attached to them. - * + * * @author Kirill Vainer */ -public final class LightList implements Iterable, Savable, Cloneable { +public final class LightList implements Iterable, Savable, Cloneable, JmeCloneable { private Light[] list, tlist; private float[] distToOwner; @@ -74,7 +76,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Creates a LightList for the given {@link Spatial}. - * + * * @param owner The spatial owner */ public LightList(Spatial owner) { @@ -87,7 +89,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Set the owner of the LightList. Only used for cloning. - * @param owner + * @param owner */ public void setOwner(Spatial owner){ this.owner = owner; @@ -118,7 +120,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Remove the light at the given index. - * + * * @param index */ public void remove(int index){ @@ -139,7 +141,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Removes the given light from the LightList. - * + * * @param l the light to remove */ public void remove(Light l){ @@ -187,12 +189,12 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Sorts the elements in the list according to their Comparator. - * There are two reasons why lights should be resorted. - * First, if the lights have moved, that means their distance to - * the spatial changed. - * Second, if the spatial itself moved, it means the distance from it to + * There are two reasons why lights should be resorted. + * First, if the lights have moved, that means their distance to + * the spatial changed. + * Second, if the spatial itself moved, it means the distance from it to * the individual lights might have changed. - * + * * * @param transformChanged Whether the spatial's transform has changed */ @@ -252,7 +254,7 @@ public final class LightList implements Iterable, Savable, Cloneable { list[p] = parent.list[i]; distToOwner[p] = Float.NEGATIVE_INFINITY; } - + listSize = local.listSize + parent.listSize; }else{ listSize = local.listSize; @@ -261,7 +263,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Returns an iterator that can be used to iterate over this LightList. - * + * * @return an iterator that can be used to iterate over this LightList. */ public Iterator iterator() { @@ -276,10 +278,10 @@ public final class LightList implements Iterable, Savable, Cloneable { public Light next() { if (!hasNext()) throw new NoSuchElementException(); - + return list[index++]; } - + public void remove() { LightList.this.remove(--index); } @@ -290,7 +292,7 @@ public final class LightList implements Iterable, Savable, Cloneable { public LightList clone(){ try{ LightList clone = (LightList) super.clone(); - + clone.owner = null; clone.list = list.clone(); clone.distToOwner = distToOwner.clone(); @@ -302,6 +304,24 @@ public final class LightList implements Iterable, Savable, Cloneable { } } + @Override + public LightList jmeClone() { + try{ + LightList clone = (LightList)super.clone(); + clone.tlist = null; // list used for sorting only + return clone; + }catch (CloneNotSupportedException ex){ + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.owner = cloner.clone(owner); + this.list = cloner.clone(list); + this.distToOwner = cloner.clone(distToOwner); + } + public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); // oc.write(owner, "owner", null); @@ -319,7 +339,7 @@ public final class LightList implements Iterable, Savable, Cloneable { List lights = ic.readSavableArrayList("lights", null); listSize = lights.size(); - + // NOTE: make sure the array has a length of at least 1 int arraySize = Math.max(DEFAULT_SIZE, listSize); list = new Light[arraySize]; @@ -328,7 +348,7 @@ public final class LightList implements Iterable, Savable, Cloneable { for (int i = 0; i < listSize; i++){ list[i] = lights.get(i); } - + Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY); } diff --git a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java index bf376006e..fd6900a97 100644 --- a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java @@ -39,6 +39,7 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.export.binary.BinaryImporter; +import com.jme3.util.clone.Cloner; import com.jme3.util.SafeArrayList; import java.io.IOException; import java.util.*; @@ -50,7 +51,7 @@ import java.util.logging.Logger; * The AssetLinkNode does not store its children when exported to file. * Instead, you can add a list of AssetKeys that will be loaded and attached * when the AssetLinkNode is restored. - * + * * @author normenhansen */ public class AssetLinkNode extends Node { @@ -70,6 +71,18 @@ public class AssetLinkNode extends Node { assetLoaderKeys.add(key); } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + // This is a change in behavior because the old version did not clone + // this list... changes to one clone would be reflected in all. + // I think that's probably undesirable. -pspeed + this.assetLoaderKeys = cloner.clone(assetLoaderKeys); + this.assetChildren = new HashMap(); + } + /** * Add a "linked" child. These are loaded from the assetManager when the * AssetLinkNode is loaded from a binary file. @@ -166,7 +179,7 @@ public class AssetLinkNode extends Node { children.add(child); assetChildren.put(modelKey, child); } else { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", new Object[]{ modelKey, key }); } } diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 69a30897d..1338b50ef 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -48,6 +48,8 @@ import com.jme3.math.Vector3f; import com.jme3.scene.mesh.IndexBuffer; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; /** * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph. @@ -60,7 +62,7 @@ import com.jme3.util.TempVars; * Sub geoms can be removed but it may be slower than the normal spatial removing * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries. * To integrate them in the batch you have to call the batch() method again on the batchNode. - * + * * TODO normal or tangents or both looks a bit weird * TODO more automagic (batch when needed in the updateLogicalState) * @author Nehon @@ -77,7 +79,7 @@ public class BatchNode extends GeometryGroupNode { */ protected Map batchesByGeom = new HashMap(); /** - * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer + * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer */ private float[] tmpFloat; private float[] tmpFloatN; @@ -96,7 +98,7 @@ public class BatchNode extends GeometryGroupNode { public BatchNode(String name) { super(name); } - + @Override public void onTransformChange(Geometry geom) { updateSubBatch(geom); @@ -123,7 +125,7 @@ public class BatchNode extends GeometryGroupNode { protected Matrix4f getTransformMatrix(Geometry g){ return g.cachedWorldMat; } - + protected void updateSubBatch(Geometry bg) { Batch batch = batchesByGeom.get(bg); if (batch != null) { @@ -134,13 +136,13 @@ public class BatchNode extends GeometryGroupNode { FloatBuffer posBuf = (FloatBuffer) pvb.getData(); VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal); FloatBuffer normBuf = (FloatBuffer) nvb.getData(); - + VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position); FloatBuffer oposBuf = (FloatBuffer) opvb.getData(); VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal); FloatBuffer onormBuf = (FloatBuffer) onvb.getData(); Matrix4f transformMat = getTransformMatrix(bg); - + if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) { VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent); @@ -184,12 +186,12 @@ public class BatchNode extends GeometryGroupNode { } batches.clear(); batchesByGeom.clear(); - } + } //only reset maxVertCount if there is something new to batch if (matMap.size() > 0) { maxVertCount = 0; } - + for (Map.Entry> entry : matMap.entrySet()) { Mesh m = new Mesh(); Material material = entry.getKey(); @@ -255,7 +257,7 @@ public class BatchNode extends GeometryGroupNode { /** * recursively visit the subgraph and unbatch geometries - * @param s + * @param s */ private void unbatchSubGraph(Spatial s) { if (s instanceof Node) { @@ -269,8 +271,8 @@ public class BatchNode extends GeometryGroupNode { } } } - - + + private void gatherGeometries(Map> map, Spatial n, boolean rebatch) { if (n instanceof Geometry) { @@ -283,7 +285,7 @@ public class BatchNode extends GeometryGroupNode { } List list = map.get(g.getMaterial()); if (list == null) { - //trying to compare materials with the isEqual method + //trying to compare materials with the isEqual method for (Map.Entry> mat : map.entrySet()) { if (g.getMaterial().contentEquals(mat.getKey())) { list = mat.getValue(); @@ -331,7 +333,7 @@ public class BatchNode extends GeometryGroupNode { /** * Sets the material to the all the batches of this BatchNode * use setMaterial(Material material,int batchIndex) to set a material to a specific batch - * + * * @param material the material to use for this geometry */ @Override @@ -341,12 +343,12 @@ public class BatchNode extends GeometryGroupNode { /** * Returns the material that is used for the first batch of this BatchNode - * + * * use getMaterial(Material material,int batchIndex) to get a material from a specific batch - * + * * @return the material that is used for the first batch of this BatchNode - * - * @see #setMaterial(com.jme3.material.Material) + * + * @see #setMaterial(com.jme3.material.Material) */ public Material getMaterial() { if (!batches.isEmpty()) { @@ -359,7 +361,7 @@ public class BatchNode extends GeometryGroupNode { /** * Merges all geometries in the collection into * the output mesh. Does not take into account materials. - * + * * @param geometries * @param outMesh */ @@ -418,7 +420,7 @@ public class BatchNode extends GeometryGroupNode { formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); normForBuf[vb.getBufferType().ordinal()] = vb.isNormalized(); } - + maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights()); if (mode != null && mode != listMode) { @@ -586,7 +588,7 @@ public class BatchNode extends GeometryGroupNode { int offset = start * 3; int tanOffset = start * 4; - + bindBufPos.rewind(); bindBufNorm.rewind(); bindBufTangents.rewind(); @@ -662,10 +664,10 @@ public class BatchNode extends GeometryGroupNode { vars.release(); } - protected class Batch { + protected class Batch implements JmeCloneable { /** * update the batchesByGeom map for this batch with the given List of geometries - * @param list + * @param list */ void updateGeomList(List list) { for (Geometry geom : list) { @@ -675,10 +677,25 @@ public class BatchNode extends GeometryGroupNode { } } Geometry geometry; - + public final Geometry getGeometry() { return geometry; } + + @Override + public Batch jmeClone() { + try { + return (Batch)super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.geometry = cloner.clone(geometry); + } + } protected void setNeedsFullRebatch(boolean needsFullRebatch) { @@ -704,7 +721,25 @@ public class BatchNode extends GeometryGroupNode { } return clone; } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.batches = cloner.clone(batches); + this.tmpFloat = cloner.clone(tmpFloat); + this.tmpFloatN = cloner.clone(tmpFloatN); + this.tmpFloatT = cloner.clone(tmpFloatT); + + + HashMap newBatchesByGeom = new HashMap(); + for( Map.Entry e : batchesByGeom.entrySet() ) { + newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); + } + this.batchesByGeom = newBatchesByGeom; + } + @Override public int collideWith(Collidable other, CollisionResults results) { int total = 0; diff --git a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java index 36cde482c..11de0c3c0 100644 --- a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java @@ -36,6 +36,7 @@ import com.jme3.export.JmeImporter; import com.jme3.renderer.Camera; import com.jme3.scene.control.CameraControl; import com.jme3.scene.control.CameraControl.ControlDirection; +import com.jme3.util.clone.Cloner; import java.io.IOException; /** @@ -93,7 +94,18 @@ public class CameraNode extends Node { // this.lookAt(position, upVector); // camControl.getCamera().lookAt(position, upVector); // } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + // A change in behavior... I think previously CameraNode was probably + // not really cloneable... or at least its camControl would be pointing + // to the wrong control. -pspeed + this.camControl = cloner.clone(camControl); + } + @Override public void read(JmeImporter im) throws IOException { super.read(im); diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index 4c394101a..6cd8f4758 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -43,6 +43,7 @@ import com.jme3.material.Material; import com.jme3.math.Matrix4f; import com.jme3.renderer.Camera; import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.clone.Cloner; import com.jme3.util.TempVars; import java.io.IOException; import java.util.Queue; @@ -54,12 +55,12 @@ import java.util.logging.Logger; * contains the geometric data for rendering objects. It manages all rendering * information such as a {@link Material} object to define how the surface * should be shaded and the {@link Mesh} data to contain the actual geometry. - * + * * @author Kirill Vainer */ public class Geometry extends Spatial { - // Version #1: removed shared meshes. + // Version #1: removed shared meshes. // models loaded with shared mesh will be automatically fixed. public static final int SAVABLE_VERSION = 1; private static final Logger logger = Logger.getLogger(Geometry.class.getName()); @@ -71,19 +72,19 @@ public class Geometry extends Spatial { */ protected boolean ignoreTransform = false; protected transient Matrix4f cachedWorldMat = new Matrix4f(); - + /** * Specifies which {@link GeometryGroupNode} this Geometry * is managed by. */ protected GeometryGroupNode groupNode; - + /** - * The start index of this Geometry's inside + * The start index of this Geometry's inside * the {@link GeometryGroupNode}. */ protected int startIndex = -1; - + /** * Serialization only. Do not use. */ @@ -95,37 +96,37 @@ public class Geometry extends Spatial { * Create a geometry node without any mesh data. * Both the mesh and the material are null, the geometry * cannot be rendered until those are set. - * + * * @param name The name of this geometry */ public Geometry(String name) { super(name); - + // For backwards compatibility, only clear the "requires // update" flag if we are not a subclass of Geometry. // This prevents subclass from silently failing to receive // updates when they upgrade. - setRequiresUpdates(Geometry.class != getClass()); + setRequiresUpdates(Geometry.class != getClass()); } /** * Create a geometry node with mesh data. * The material of the geometry is null, it cannot * be rendered until it is set. - * + * * @param name The name of this geometry * @param mesh The mesh data for this geometry */ public Geometry(String name, Mesh mesh) { this(name); - + if (mesh == null) { throw new IllegalArgumentException("mesh cannot be null"); } this.mesh = mesh; } - + @Override public boolean checkCulling(Camera cam) { if (isGrouped()) { @@ -137,8 +138,8 @@ public class Geometry extends Spatial { /** * @return If ignoreTransform mode is set. - * - * @see Geometry#setIgnoreTransform(boolean) + * + * @see Geometry#setIgnoreTransform(boolean) */ public boolean isIgnoreTransform() { return ignoreTransform; @@ -156,7 +157,7 @@ public class Geometry extends Spatial { * Level 0 indicates that the default index buffer should be used, * levels [1, LodLevels + 1] represent the levels set on the mesh * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. - * + * * @param lod The lod level to set */ @Override @@ -170,7 +171,7 @@ public class Geometry extends Spatial { } lodLevel = lod; - + if (isGrouped()) { groupNode.onMeshChange(this); } @@ -178,7 +179,7 @@ public class Geometry extends Spatial { /** * Returns the LOD level set with {@link #setLodLevel(int) }. - * + * * @return the LOD level set */ public int getLodLevel() { @@ -187,10 +188,10 @@ public class Geometry extends Spatial { /** * Returns this geometry's mesh vertex count. - * + * * @return this geometry's mesh vertex count. - * - * @see Mesh#getVertexCount() + * + * @see Mesh#getVertexCount() */ public int getVertexCount() { return mesh.getVertexCount(); @@ -198,10 +199,10 @@ public class Geometry extends Spatial { /** * Returns this geometry's mesh triangle count. - * + * * @return this geometry's mesh triangle count. - * - * @see Mesh#getTriangleCount() + * + * @see Mesh#getTriangleCount() */ public int getTriangleCount() { return mesh.getTriangleCount(); @@ -209,9 +210,9 @@ public class Geometry extends Spatial { /** * Sets the mesh to use for this geometry when rendering. - * + * * @param mesh the mesh to use for this geometry - * + * * @throws IllegalArgumentException If mesh is null */ public void setMesh(Mesh mesh) { @@ -221,7 +222,7 @@ public class Geometry extends Spatial { this.mesh = mesh; setBoundRefresh(); - + if (isGrouped()) { groupNode.onMeshChange(this); } @@ -229,10 +230,10 @@ public class Geometry extends Spatial { /** * Returns the mesh to use for this geometry - * + * * @return the mesh to use for this geometry - * - * @see #setMesh(com.jme3.scene.Mesh) + * + * @see #setMesh(com.jme3.scene.Mesh) */ public Mesh getMesh() { return mesh; @@ -240,13 +241,13 @@ public class Geometry extends Spatial { /** * Sets the material to use for this geometry. - * + * * @param material the material to use for this geometry */ @Override public void setMaterial(Material material) { this.material = material; - + if (isGrouped()) { groupNode.onMaterialChange(this); } @@ -254,10 +255,10 @@ public class Geometry extends Spatial { /** * Returns the material that is used for this geometry. - * + * * @return the material that is used for this geometry - * - * @see #setMaterial(com.jme3.material.Material) + * + * @see #setMaterial(com.jme3.material.Material) */ public Material getMaterial() { return material; @@ -310,18 +311,18 @@ public class Geometry extends Spatial { computeWorldMatrix(); if (isGrouped()) { - groupNode.onTransformChange(this); + groupNode.onTransformChange(this); } - + // geometry requires lights to be sorted worldLights.sort(true); } /** * Associate this Geometry with a {@link GeometryGroupNode}. - * + * * Should only be called by the parent {@link GeometryGroupNode}. - * + * * @param node Which {@link GeometryGroupNode} to associate with. * @param startIndex The starting index of this geometry in the group. */ @@ -329,26 +330,26 @@ public class Geometry extends Spatial { if (isGrouped()) { unassociateFromGroupNode(); } - + this.groupNode = node; this.startIndex = startIndex; } /** - * Removes the {@link GeometryGroupNode} association from this + * Removes the {@link GeometryGroupNode} association from this * Geometry. - * + * * Should only be called by the parent {@link GeometryGroupNode}. */ public void unassociateFromGroupNode() { if (groupNode != null) { - // Once the geometry is removed + // Once the geometry is removed // from the parent, the group node needs to be updated. groupNode.onGeometryUnassociated(this); groupNode = null; - + // change the default to -1 to make error detection easier - startIndex = -1; + startIndex = -1; } } @@ -360,7 +361,7 @@ public class Geometry extends Spatial { @Override protected void setParent(Node parent) { super.setParent(parent); - + // If the geometry is managed by group node we need to unassociate. if (parent == null && isGrouped()) { unassociateFromGroupNode(); @@ -406,7 +407,7 @@ public class Geometry extends Spatial { * {@link Geometry#getWorldTransform() world transform} of this geometry. * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() } * before using this method. - * + * * @return Matrix to transform from local space to world space */ public Matrix4f getWorldMatrix() { @@ -418,7 +419,7 @@ public class Geometry extends Spatial { * This alters the bound used on the mesh as well via * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and * forces the world bounding volume to be recomputed. - * + * * @param modelBound The model bound to set */ @Override @@ -465,15 +466,15 @@ public class Geometry extends Spatial { } /** - * Determine whether this Geometry is managed by a + * Determine whether this Geometry is managed by a * {@link GeometryGroupNode} or not. - * + * * @return True if managed by a {@link GeometryGroupNode}. */ public boolean isGrouped() { return groupNode != null; } - + /** * @deprecated Use {@link #isGrouped()} instead. */ @@ -492,14 +493,14 @@ public class Geometry extends Spatial { @Override public Geometry clone(boolean cloneMaterial) { Geometry geomClone = (Geometry) super.clone(cloneMaterial); - + // This geometry is managed, // but the cloned one is not attached to anything, hence not managed. if (geomClone.isGrouped()) { geomClone.groupNode = null; geomClone.startIndex = -1; } - + geomClone.cachedWorldMat = cachedWorldMat.clone(); if (material != null) { if (cloneMaterial) { @@ -539,6 +540,16 @@ public class Geometry extends Spatial { return geomClone; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.mesh = cloner.clone(mesh); + this.material = cloner.clone(material); + this.groupNode = cloner.clone(groupNode); + } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/scene/LightNode.java b/jme3-core/src/main/java/com/jme3/scene/LightNode.java index fea63bf57..1a87d11b4 100644 --- a/jme3-core/src/main/java/com/jme3/scene/LightNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/LightNode.java @@ -36,11 +36,12 @@ import com.jme3.export.JmeImporter; import com.jme3.light.Light; import com.jme3.scene.control.LightControl; import com.jme3.scene.control.LightControl.ControlDirection; +import com.jme3.util.clone.Cloner; import java.io.IOException; /** * LightNode is used to link together a {@link Light} object - * with a {@link Node} object. + * with a {@link Node} object. * * @author Tim8Dev */ @@ -66,7 +67,7 @@ public class LightNode extends Node { /** * Enable or disable the LightNode functionality. - * + * * @param enabled If false, the functionality of LightNode will * be disabled. */ @@ -93,7 +94,18 @@ public class LightNode extends Node { public Light getLight() { return lightControl.getLight(); } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + // A change in behavior... I think previously LightNode was probably + // not really cloneable... or at least its lightControl would be pointing + // to the wrong control. -pspeed + this.lightControl = cloner.clone(lightControl); + } + @Override public void read(JmeImporter im) throws IOException { super.read(im); diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index 60f7eabe2..f9b66bc45 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -51,6 +51,8 @@ import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; import com.jme3.util.SafeArrayList; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.nio.*; import java.util.ArrayList; @@ -61,7 +63,7 @@ import java.util.ArrayList; * All visible elements in a scene are represented by meshes. * Meshes may contain three types of geometric primitives: *
    - *
  • Points - Every vertex represents a single point in space, + *
  • Points - Every vertex represents a single point in space, * the size of each point is specified via {@link Mesh#setPointSize(float) }. * Points can also be used for {@link RenderState#setPointSprite(boolean) point * sprite} mode.
  • @@ -69,10 +71,10 @@ import java.util.ArrayList; * via {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)}. *
  • Triangles - 3 vertices represent a solid triangle primitive.
  • *
- * + * * @author Kirill Vainer */ -public class Mesh implements Savable, Cloneable { +public class Mesh implements Savable, Cloneable, JmeCloneable { /** * The mode of the Mesh specifies both the type of primitive represented @@ -80,26 +82,26 @@ public class Mesh implements Savable, Cloneable { */ public enum Mode { /** - * A primitive is a single point in space. The size of the points + * A primitive is a single point in space. The size of the points * can be specified with {@link Mesh#setPointSize(float) }. */ Points(true), - + /** * A primitive is a line segment. Every two vertices specify * a single line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used * to set the width of the lines. */ Lines(true), - + /** * A primitive is a line segment. The first two vertices specify - * a single line, while subsequent vertices are combined with the + * a single line, while subsequent vertices are combined with the * previous vertex to make a line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can * be used to set the width of the lines. */ LineStrip(false), - + /** * Identical to {@link #LineStrip} except that at the end * the last vertex is connected with the first to form a line. @@ -107,32 +109,32 @@ public class Mesh implements Savable, Cloneable { * to set the width of the lines. */ LineLoop(false), - + /** * A primitive is a triangle. Each 3 vertices specify a single * triangle. */ Triangles(true), - + /** - * Similar to {@link #Triangles}, the first 3 vertices + * Similar to {@link #Triangles}, the first 3 vertices * specify a triangle, while subsequent vertices are combined with - * the previous two to form a triangle. + * the previous two to form a triangle. */ TriangleStrip(false), - + /** - * Similar to {@link #Triangles}, the first 3 vertices + * Similar to {@link #Triangles}, the first 3 vertices * specify a triangle, each 2 subsequent vertices are combined * with the very first vertex to make a triangle. */ TriangleFan(false), - + /** * A combination of various triangle modes. It is best to avoid * using this mode as it may not be supported by all renderers. * The {@link Mesh#setModeStart(int[]) mode start points} and - * {@link Mesh#setElementLengths(int[]) element lengths} must + * {@link Mesh#setElementLengths(int[]) element lengths} must * be specified for this mode. */ Hybrid(false), @@ -142,18 +144,18 @@ public class Mesh implements Savable, Cloneable { */ Patch(true); private boolean listMode = false; - + private Mode(boolean listMode){ this.listMode = listMode; } - + /** * Returns true if the specified mode is a list mode (meaning - * ,it specifies the indices as a linear list and not some special + * ,it specifies the indices as a linear list and not some special * format). * Will return true for the types {@link #Points}, {@link #Lines} and * {@link #Triangles}. - * + * * @return true if the mode is a list type mode */ public boolean isListMode(){ @@ -198,7 +200,7 @@ public class Mesh implements Savable, Cloneable { * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex * buffers} are shared between this and the clone mesh, the rest * of the data is cloned. - * + * * @return A shallow clone of the mesh */ @Override @@ -223,10 +225,10 @@ public class Mesh implements Savable, Cloneable { } /** - * Creates a deep clone of this mesh. + * Creates a deep clone of this mesh. * The {@link VertexBuffer vertex buffers} and the data inside them * is cloned. - * + * * @return a deep clone of this mesh. */ public Mesh deepClone(){ @@ -245,16 +247,16 @@ public class Mesh implements Savable, Cloneable { clone.buffers.put(vb.getBufferType().ordinal(), bufClone); clone.buffersList.add(bufClone); } - + clone.vertexArrayID = -1; clone.vertCount = vertCount; clone.elementCount = elementCount; clone.instanceCount = instanceCount; - + // although this could change // if the bone weight/index buffers are modified - clone.maxNumWeights = maxNumWeights; - + clone.maxNumWeights = maxNumWeights; + clone.elementLengths = elementLengths != null ? elementLengths.clone() : null; clone.modeStart = modeStart != null ? modeStart.clone() : null; return clone; @@ -269,14 +271,14 @@ public class Mesh implements Savable, Cloneable { * of the {@link VertexBuffer vertex buffer} data, however the * {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers * are deeply cloned. - * + * * @return A clone of the mesh for animation use. */ public Mesh cloneForAnim(){ Mesh clone = clone(); if (getBuffer(Type.BindPosePosition) != null){ VertexBuffer oldPos = getBuffer(Type.Position); - + // NOTE: creates deep clone VertexBuffer newPos = oldPos.clone(); clone.clearBuffer(Type.Position); @@ -287,7 +289,7 @@ public class Mesh implements Savable, Cloneable { VertexBuffer newNorm = oldNorm.clone(); clone.clearBuffer(Type.Normal); clone.setBuffer(newNorm); - + if (getBuffer(Type.BindPoseTangent) != null){ VertexBuffer oldTang = getBuffer(Type.Tangent); VertexBuffer newTang = oldTang.clone(); @@ -299,14 +301,43 @@ public class Mesh implements Savable, Cloneable { return clone; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Mesh jmeClone() { + try { + return (Mesh)super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + + // Probably could clone this now but it will get regenerated anyway. + this.collisionTree = null; + + this.meshBound = cloner.clone(meshBound); + this.buffersList = cloner.clone(buffersList); + this.buffers = cloner.clone(buffers); + this.lodLevels = cloner.clone(lodLevels); + this.elementLengths = cloner.clone(elementLengths); + this.modeStart = cloner.clone(modeStart); + } + /** * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal}, - * and {@link Type#BindPoseTangent} + * and {@link Type#BindPoseTangent} * buffers for this mesh by duplicating them based on the position and normal * buffers already set on the mesh. * This method does nothing if the mesh has no bone weight or index * buffers. - * + * * @param forSoftwareAnim Should be true if the bind pose is to be generated. */ public void generateBindPose(boolean forSoftwareAnim){ @@ -339,7 +370,7 @@ public class Mesh implements Savable, Cloneable { setBuffer(bindNorm); norm.setUsage(Usage.Stream); } - + VertexBuffer tangents = getBuffer(Type.Tangent); if (tangents != null) { VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent); @@ -355,32 +386,32 @@ public class Mesh implements Savable, Cloneable { /** * Prepares the mesh for software skinning by converting the bone index - * and weight buffers to heap buffers. - * + * and weight buffers to heap buffers. + * * @param forSoftwareAnim Should be true to enable the conversion. */ public void prepareForAnim(boolean forSoftwareAnim){ if (forSoftwareAnim) { - // convert indices to ubytes on the heap - VertexBuffer indices = getBuffer(Type.BoneIndex); - if (!indices.getData().hasArray()) { - ByteBuffer originalIndex = (ByteBuffer) indices.getData(); - ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity()); - originalIndex.clear(); - arrayIndex.put(originalIndex); - indices.updateData(arrayIndex); - } - indices.setUsage(Usage.CpuOnly); - - // convert weights on the heap - VertexBuffer weights = getBuffer(Type.BoneWeight); - if (!weights.getData().hasArray()) { - FloatBuffer originalWeight = (FloatBuffer) weights.getData(); - FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity()); - originalWeight.clear(); - arrayWeight.put(originalWeight); - weights.updateData(arrayWeight); - } + // convert indices to ubytes on the heap + VertexBuffer indices = getBuffer(Type.BoneIndex); + if (!indices.getData().hasArray()) { + ByteBuffer originalIndex = (ByteBuffer) indices.getData(); + ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity()); + originalIndex.clear(); + arrayIndex.put(originalIndex); + indices.updateData(arrayIndex); + } + indices.setUsage(Usage.CpuOnly); + + // convert weights on the heap + VertexBuffer weights = getBuffer(Type.BoneWeight); + if (!weights.getData().hasArray()) { + FloatBuffer originalWeight = (FloatBuffer) weights.getData(); + FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity()); + originalWeight.clear(); + arrayWeight.put(originalWeight); + weights.updateData(arrayWeight); + } weights.setUsage(Usage.CpuOnly); // position, normal, and tanget buffers to be in "Stream" mode VertexBuffer positions = getBuffer(Type.Position); @@ -395,7 +426,7 @@ public class Mesh implements Savable, Cloneable { } } else { //if HWBoneIndex and HWBoneWeight are empty, we setup them as direct - //buffers with software anim buffers data + //buffers with software anim buffers data VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex); if (indicesHW.getData() == null) { VertexBuffer indices = getBuffer(Type.BoneIndex); @@ -405,7 +436,7 @@ public class Mesh implements Savable, Cloneable { directIndex.put(originalIndex); indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), directIndex); } - + VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight); if (weightsHW.getData() == null) { VertexBuffer weights = getBuffer(Type.BoneWeight); @@ -415,26 +446,26 @@ public class Mesh implements Savable, Cloneable { directWeight.put(originalWeight); weightsHW.setupData(Usage.Static, weights.getNumComponents(), weights.getFormat(), directWeight); } - + // position, normal, and tanget buffers to be in "Static" mode VertexBuffer positions = getBuffer(Type.Position); VertexBuffer normals = getBuffer(Type.Normal); VertexBuffer tangents = getBuffer(Type.Tangent); - + VertexBuffer positionsBP = getBuffer(Type.BindPosePosition); VertexBuffer normalsBP = getBuffer(Type.BindPoseNormal); VertexBuffer tangentsBP = getBuffer(Type.BindPoseTangent); - + positions.setUsage(Usage.Static); positionsBP.copyElements(0, positions, 0, positionsBP.getNumElements()); positions.setUpdateNeeded(); - + if (normals != null) { normals.setUsage(Usage.Static); normalsBP.copyElements(0, normals, 0, normalsBP.getNumElements()); normals.setUpdateNeeded(); } - + if (tangents != null) { tangents.setUsage(Usage.Static); tangentsBP.copyElements(0, tangents, 0, tangentsBP.getNumElements()); @@ -445,7 +476,7 @@ public class Mesh implements Savable, Cloneable { /** * Set the LOD (level of detail) index buffers on this mesh. - * + * * @param lodLevels The LOD levels to set */ public void setLodLevels(VertexBuffer[] lodLevels){ @@ -462,23 +493,23 @@ public class Mesh implements Savable, Cloneable { /** * Returns the lod level at the given index. - * + * * @param lod The lod level index, this does not include * the main index buffer. * @return The LOD index buffer at the index - * - * @throws IndexOutOfBoundsException If the index is outside of the + * + * @throws IndexOutOfBoundsException If the index is outside of the * range [0, {@link #getNumLodLevels()}]. - * - * @see #setLodLevels(com.jme3.scene.VertexBuffer[]) + * + * @see #setLodLevels(com.jme3.scene.VertexBuffer[]) */ public VertexBuffer getLodLevel(int lod){ return lodLevels[lod]; } - + /** * Get the element lengths for {@link Mode#Hybrid} mesh mode. - * + * * @return element lengths */ public int[] getElementLengths() { @@ -487,7 +518,7 @@ public class Mesh implements Savable, Cloneable { /** * Set the element lengths for {@link Mode#Hybrid} mesh mode. - * + * * @param elementLengths The element lengths to set */ public void setElementLengths(int[] elementLengths) { @@ -496,7 +527,7 @@ public class Mesh implements Savable, Cloneable { /** * Set the mode start indices for {@link Mode#Hybrid} mesh mode. - * + * * @return mode start indices */ public int[] getModeStart() { @@ -512,10 +543,10 @@ public class Mesh implements Savable, Cloneable { /** * Returns the mesh mode - * + * * @return the mesh mode - * - * @see #setMode(com.jme3.scene.Mesh.Mode) + * + * @see #setMode(com.jme3.scene.Mesh.Mode) */ public Mode getMode() { return mode; @@ -523,9 +554,9 @@ public class Mesh implements Savable, Cloneable { /** * Change the Mesh's mode. By default the mode is {@link Mode#Triangles}. - * + * * @param mode The new mode to set - * + * * @see Mode */ public void setMode(Mode mode) { @@ -535,10 +566,10 @@ public class Mesh implements Savable, Cloneable { /** * Returns the maximum number of weights per vertex on this mesh. - * + * * @return maximum number of weights per vertex - * - * @see #setMaxNumWeights(int) + * + * @see #setMaxNumWeights(int) */ public int getMaxNumWeights() { return maxNumWeights; @@ -548,8 +579,8 @@ public class Mesh implements Savable, Cloneable { * Set the maximum number of weights per vertex on this mesh. * Only relevant if this mesh has bone index/weight buffers. * This value should be between 0 and 4. - * - * @param maxNumWeights + * + * @param maxNumWeights */ public void setMaxNumWeights(int maxNumWeights) { this.maxNumWeights = maxNumWeights; @@ -557,23 +588,23 @@ public class Mesh implements Savable, Cloneable { /** * Returns the size of points for point meshes - * + * * @return the size of points - * - * @see #setPointSize(float) + * + * @see #setPointSize(float) */ public float getPointSize() { return pointSize; } /** - * Set the size of points for meshes of mode {@link Mode#Points}. + * Set the size of points for meshes of mode {@link Mode#Points}. * The point size is specified as on-screen pixels, the default * value is 1.0. The point size * does nothing if {@link RenderState#setPointSprite(boolean) point sprite} - * render state is enabled, in that case, the vertex shader must specify the + * render state is enabled, in that case, the vertex shader must specify the * point size by writing to gl_PointSize. - * + * * @param pointSize The size of points */ public void setPointSize(float pointSize) { @@ -582,7 +613,7 @@ public class Mesh implements Savable, Cloneable { /** * Returns the line width for line meshes. - * + * * @return the line width * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#getLineWidth()} */ @@ -593,9 +624,9 @@ public class Mesh implements Savable, Cloneable { /** * Specify the line width for meshes of the line modes, such - * as {@link Mode#Lines}. The line width is specified as on-screen pixels, + * as {@link Mode#Lines}. The line width is specified as on-screen pixels, * the default value is 1.0. - * + * * @param lineWidth The line width * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} */ @@ -605,7 +636,7 @@ public class Mesh implements Savable, Cloneable { } /** - * Indicates to the GPU that this mesh will not be modified (a hint). + * Indicates to the GPU that this mesh will not be modified (a hint). * Sets the usage mode to {@link Usage#Static} * for all {@link VertexBuffer vertex buffers} on this Mesh. */ @@ -646,7 +677,7 @@ public class Mesh implements Savable, Cloneable { public void setInterleaved(){ ArrayList vbs = new ArrayList(); vbs.addAll(buffersList); - + // ArrayList vbs = new ArrayList(buffers.values()); // index buffer not included when interleaving vbs.remove(getBuffer(Type.Index)); @@ -665,7 +696,7 @@ public class Mesh implements Savable, Cloneable { VertexBuffer allData = new VertexBuffer(Type.InterleavedData); ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount()); allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf); - + // adding buffer directly so that no update counts is forced buffers.put(Type.InterleavedData.ordinal(), allData); buffersList.add(allData); @@ -716,7 +747,7 @@ public class Mesh implements Savable, Cloneable { for (VertexBuffer vb : vbs){ vb.setOffset(offset); vb.setStride(stride); - + vb.updateData(null); //vb.setupData(vb.usage, vb.components, vb.format, null); offset += vb.componentsLength; @@ -750,20 +781,20 @@ public class Mesh implements Savable, Cloneable { int max = 0; for( VertexBuffer vb : buffersList ) { if( vb.getBaseInstanceCount() > max ) { - max = vb.getBaseInstanceCount(); - } - } + max = vb.getBaseInstanceCount(); + } + } return max; } /** - * Update the {@link #getVertexCount() vertex} and + * Update the {@link #getVertexCount() vertex} and * {@link #getTriangleCount() triangle} counts for this mesh * based on the current data. This method should be called * after the {@link Buffer#capacity() capacities} of the mesh's * {@link VertexBuffer vertex buffers} has been altered. - * - * @throws IllegalStateException If this mesh is in + * + * @throws IllegalStateException If this mesh is in * {@link #setInterleaved() interleaved} format. */ public void updateCounts(){ @@ -779,13 +810,13 @@ public class Mesh implements Savable, Cloneable { elementCount = computeNumElements(ib.getData().limit()); }else{ elementCount = computeNumElements(vertCount); - } + } instanceCount = computeInstanceCount(); } /** * Returns the triangle count for the given LOD level. - * + * * @param lod The lod level to look up * @return The triangle count for that LOD level */ @@ -808,10 +839,10 @@ public class Mesh implements Savable, Cloneable { /** * Returns how many triangles or elements are on this Mesh. * This value is only updated when {@link #updateCounts() } is called. - * If the mesh mode is not a triangle mode, then this returns the + * If the mesh mode is not a triangle mode, then this returns the * number of elements/primitives, e.g. how many lines or how many points, * instead of how many triangles. - * + * * @return how many triangles/elements are on this Mesh. */ public int getTriangleCount(){ @@ -820,30 +851,30 @@ public class Mesh implements Savable, Cloneable { /** * Returns the number of vertices on this mesh. - * The value is computed based on the position buffer, which + * The value is computed based on the position buffer, which * must be set on all meshes. - * + * * @return Number of vertices on the mesh */ public int getVertexCount(){ return vertCount; } - + /** * Returns the number of instances this mesh contains. The instance * count is based on any VertexBuffers with instancing set. */ public int getInstanceCount() { return instanceCount; - } + } /** - * Gets the triangle vertex positions at the given triangle index + * Gets the triangle vertex positions at the given triangle index * and stores them into the v1, v2, v3 arguments. - * - * @param index The index of the triangle. + * + * @param index The index of the triangle. * Should be between 0 and {@link #getTriangleCount()}. - * + * * @param v1 Vector to contain first vertex position * @param v2 Vector to contain second vertex position * @param v3 Vector to contain third vertex position @@ -868,15 +899,15 @@ public class Mesh implements Savable, Cloneable { + " has incompatible format"); } } - + /** - * Gets the triangle vertex positions at the given triangle index + * Gets the triangle vertex positions at the given triangle index * and stores them into the {@link Triangle} argument. * Also sets the triangle index to the index argument. - * - * @param index The index of the triangle. + * + * @param index The index of the triangle. * Should be between 0 and {@link #getTriangleCount()}. - * + * * @param tri The triangle to store the positions in */ public void getTriangle(int index, Triangle tri){ @@ -886,12 +917,12 @@ public class Mesh implements Savable, Cloneable { } /** - * Gets the triangle vertex indices at the given triangle index + * Gets the triangle vertex indices at the given triangle index * and stores them into the given int array. - * - * @param index The index of the triangle. + * + * @param index The index of the triangle. * Should be between 0 and {@link #getTriangleCount()}. - * + * * @param indices Indices of the triangle's vertices */ public void getTriangle(int index, int[] indices){ @@ -917,15 +948,15 @@ public class Mesh implements Savable, Cloneable { public void setId(int id){ if (vertexArrayID != -1) throw new IllegalStateException("ID has already been set."); - + vertexArrayID = id; } /** * Generates a collision tree for the mesh. - * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, - * com.jme3.math.Matrix4f, - * com.jme3.bounding.BoundingVolume, + * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, + * com.jme3.math.Matrix4f, + * com.jme3.bounding.BoundingVolume, * com.jme3.collision.CollisionResults) }. */ public void createCollisionData(){ @@ -948,7 +979,7 @@ public class Mesh implements Savable, Cloneable { * User code should only use collideWith() on scene * graph elements such as {@link Spatial}s. */ - public int collideWith(Collidable other, + public int collideWith(Collidable other, Matrix4f worldMatrix, BoundingVolume worldBound, CollisionResults results){ @@ -956,18 +987,18 @@ public class Mesh implements Savable, Cloneable { if (getVertexCount() == 0) { return 0; } - + if (collisionTree == null){ createCollisionData(); } - + return collisionTree.collideWith(other, worldMatrix, worldBound, results); } /** * Sets the {@link VertexBuffer} on the mesh. * This will update the vertex/triangle counts if needed. - * + * * @param vb The buffer to set * @throws IllegalArgumentException If the buffer type is already set */ @@ -979,12 +1010,12 @@ public class Mesh implements Savable, Cloneable { buffersList.add(vb); updateCounts(); } - + /** * Unsets the {@link VertexBuffer} set on this mesh - * with the given type. Does nothing if the vertex buffer type is not set + * with the given type. Does nothing if the vertex buffer type is not set * initially. - * + * * @param type The buffer type to remove */ public void clearBuffer(VertexBuffer.Type type){ @@ -994,17 +1025,17 @@ public class Mesh implements Savable, Cloneable { updateCounts(); } } - + /** * Creates a {@link VertexBuffer} for the mesh or modifies * the existing one per the parameters given. - * + * * @param type The type of the buffer * @param components Number of components * @param format Data format * @param buf The buffer data - * - * @throws UnsupportedOperationException If the buffer already set is + * + * @throws UnsupportedOperationException If the buffer already set is * incompatible with the parameters given. */ public void setBuffer(Type type, int components, Format format, Buffer buf){ @@ -1022,16 +1053,16 @@ public class Mesh implements Savable, Cloneable { updateCounts(); } } - + /** - * Set a floating point {@link VertexBuffer} on the mesh. - * - * @param type The type of {@link VertexBuffer}, + * Set a floating point {@link VertexBuffer} on the mesh. + * + * @param type The type of {@link VertexBuffer}, * e.g. {@link Type#Position}, {@link Type#Normal}, etc. - * + * * @param components Number of components on the vertex buffer, should * be between 1 and 4. - * + * * @param buf The floating point data to contain */ public void setBuffer(Type type, int components, FloatBuffer buf) { @@ -1069,9 +1100,9 @@ public class Mesh implements Savable, Cloneable { /** * Get the {@link VertexBuffer} stored on this mesh with the given * type. - * + * * @param type The type of VertexBuffer - * @return the VertexBuffer data, or null if not set + * @return the VertexBuffer data, or null if not set */ public VertexBuffer getBuffer(Type type){ return buffers.get(type.ordinal()); @@ -1080,7 +1111,7 @@ public class Mesh implements Savable, Cloneable { /** * Get the {@link VertexBuffer} data stored on this mesh in float * format. - * + * * @param type The type of VertexBuffer * @return the VertexBuffer data, or null if not set */ @@ -1091,11 +1122,11 @@ public class Mesh implements Savable, Cloneable { return (FloatBuffer) vb.getData(); } - + /** * Get the {@link VertexBuffer} data stored on this mesh in short * format. - * + * * @param type The type of VertexBuffer * @return the VertexBuffer data, or null if not set */ @@ -1110,18 +1141,18 @@ public class Mesh implements Savable, Cloneable { /** * Acquires an index buffer that will read the vertices on the mesh * as a list. - * + * * @return A virtual or wrapped index buffer to read the data as a list */ public IndexBuffer getIndicesAsList(){ if (mode == Mode.Hybrid) throw new UnsupportedOperationException("Hybrid mode not supported"); - + IndexBuffer ib = getIndexBuffer(); if (ib != null){ if (mode.isListMode()){ // already in list mode - return ib; + return ib; }else{ // not in list mode but it does have an index buffer // wrap it so the data is converted to list format @@ -1133,20 +1164,20 @@ public class Mesh implements Savable, Cloneable { return new VirtualIndexBuffer(vertCount, mode); } } - + /** - * Get the index buffer for this mesh. + * Get the index buffer for this mesh. * Will return null if no index buffer is set. - * + * * @return The index buffer of this mesh. - * + * * @see Type#Index */ public IndexBuffer getIndexBuffer() { VertexBuffer vb = getBuffer(Type.Index); if (vb == null) return null; - + return IndexBuffer.wrapIndexBuffer(vb.getData()); } @@ -1156,7 +1187,7 @@ public class Mesh implements Savable, Cloneable { * to index into the attributes of the other mesh. * Note that this will also change this mesh's index buffer so that * the references to the vertex data match the new indices. - * + * * @param other The mesh to extract the vertex data from */ public void extractVertexData(Mesh other) { @@ -1176,7 +1207,7 @@ public class Mesh implements Savable, Cloneable { int oldIndex = indexBuf.get(i); if (!oldIndicesToNewIndices.containsKey(oldIndex)) { - // this vertex has not been added, so allocate a + // this vertex has not been added, so allocate a // new index for it and add it to the map oldIndicesToNewIndices.put(oldIndex, newIndex); newIndicesToOldIndices.add(oldIndex); @@ -1193,8 +1224,8 @@ public class Mesh implements Savable, Cloneable { throw new AssertionError(); } - // Create the new index buffer. - // Do not overwrite the old one because we might be able to + // Create the new index buffer. + // Do not overwrite the old one because we might be able to // convert from int index buffer to short index buffer IndexBuffer newIndexBuf; if (newNumVerts >= 65536) { @@ -1210,10 +1241,10 @@ public class Mesh implements Savable, Cloneable { newIndexBuf.put(i, newIndex); } - + VertexBuffer newIdxBuf = new VertexBuffer(Type.Index); - newIdxBuf.setupData(oldIdxBuf.getUsage(), - oldIdxBuf.getNumComponents(), + newIdxBuf.setupData(oldIdxBuf.getUsage(), + oldIdxBuf.getNumComponents(), newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort, newIndexBuf.getBuffer()); clearBuffer(Type.Index); @@ -1229,7 +1260,7 @@ public class Mesh implements Savable, Cloneable { VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType()); newVb.setNormalized(oldVb.isNormalized()); - //check for data before copying, some buffers are just empty shells + //check for data before copying, some buffers are just empty shells //for caching purpose (HW skinning buffers), and will be filled when //needed if(oldVb.getData()!=null){ @@ -1247,32 +1278,32 @@ public class Mesh implements Savable, Cloneable { oldVb.copyElement(oldIndex, newVb, i); } } - + // Set the buffer on the mesh clearBuffer(newVb.getBufferType()); setBuffer(newVb); } - + // Copy max weights per vertex as well setMaxNumWeights(other.getMaxNumWeights()); - + // The data has been copied over, update informations updateCounts(); updateBound(); } - + /** * Scales the texture coordinate buffer on this mesh by the given - * scale factor. + * scale factor. *

- * Note that values above 1 will cause the - * texture to tile, while values below 1 will cause the texture + * Note that values above 1 will cause the + * texture to tile, while values below 1 will cause the texture * to stretch. *

- * + * * @param scaleFactor The scale factor to scale by. Every texture * coordinate is multiplied by this vector to get the result. - * + * * @throws IllegalStateException If there's no texture coordinate * buffer on the mesh * @throws UnsupportedOperationException If the texture coordinate @@ -1304,7 +1335,7 @@ public class Mesh implements Savable, Cloneable { } /** - * Updates the bounding volume of this mesh. + * Updates the bounding volume of this mesh. * The method does nothing if the mesh has no {@link Type#Position} buffer. * It is expected that the position buffer is a float buffer with 3 components. */ @@ -1318,7 +1349,7 @@ public class Mesh implements Savable, Cloneable { /** * Returns the {@link BoundingVolume} of this Mesh. * By default the bounding volume is a {@link BoundingBox}. - * + * * @return the bounding volume of this mesh */ public BoundingVolume getBound() { @@ -1328,7 +1359,7 @@ public class Mesh implements Savable, Cloneable { /** * Sets the {@link BoundingVolume} for this Mesh. * The bounding volume is recomputed by calling {@link #updateBound() }. - * + * * @param modelBound The model bound to set */ public void setBound(BoundingVolume modelBound) { @@ -1339,38 +1370,38 @@ public class Mesh implements Savable, Cloneable { * Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh. * The integer key for the map is the {@link Enum#ordinal() ordinal} * of the vertex buffer's {@link Type}. - * Note that the returned map is a reference to the map used internally, + * Note that the returned map is a reference to the map used internally, * modifying it will cause undefined results. - * + * * @return map of vertex buffers on this mesh. */ public IntMap getBuffers(){ return buffers; } - + /** * Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh. * Using a list instead an IntMap via the {@link #getBuffers() } method is * better for iteration as there's no need to create an iterator instance. * Note that the returned list is a reference to the list used internally, * modifying it will cause undefined results. - * + * * @return list of vertex buffers on this mesh. */ public SafeArrayList getBufferList(){ return buffersList; } - + /** * Determines if the mesh uses bone animation. - * + * * A mesh uses bone animation if it has bone index / weight buffers * such as {@link Type#BoneIndex} or {@link Type#HWBoneIndex}. - * + * * @return true if the mesh uses bone animation, false otherwise */ public boolean isAnimated() { - return getBuffer(Type.BoneIndex) != null || + return getBuffer(Type.BoneIndex) != null || getBuffer(Type.HWBoneIndex) != null; } @@ -1410,7 +1441,7 @@ public class Mesh implements Savable, Cloneable { out.write(elementLengths, "elementLengths", null); out.write(modeStart, "modeStart", null); out.write(pointSize, "pointSize", 1f); - + //Removing HW skinning buffers to not save them VertexBuffer hwBoneIndex = null; VertexBuffer hwBoneWeight = null; @@ -1456,7 +1487,7 @@ public class Mesh implements Savable, Cloneable { for (Entry entry : buffers){ buffersList.add(entry.getValue()); } - + //creating hw animation buffers empty so that they are put in the cache if(isAnimated()){ VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex); @@ -1466,7 +1497,7 @@ public class Mesh implements Savable, Cloneable { hwBoneWeight.setUsage(Usage.CpuOnly); setBuffer(hwBoneWeight); } - + Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null); if (lodLevelsSavable != null) { lodLevels = new VertexBuffer[lodLevelsSavable.length]; diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index f269446a5..59f0beb85 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -40,6 +40,7 @@ import com.jme3.export.Savable; import com.jme3.material.Material; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -53,7 +54,7 @@ import java.util.logging.Logger; * node maintains a collection of children and handles merging said children * into a single bound to allow for very fast culling of multiple nodes. Node * allows for any number of children to be attached. - * + * * @author Mark Powell * @author Gregg Patton * @author Joshua Slack @@ -62,26 +63,26 @@ public class Node extends Spatial { private static final Logger logger = Logger.getLogger(Node.class.getName()); - /** + /** * This node's children. */ protected SafeArrayList children = new SafeArrayList(Spatial.class); /** * If this node is a root, this list will contain the current - * set of children (and children of children) that require + * set of children (and children of children) that require * updateLogicalState() to be called as indicated by their * requiresUpdate() method. */ private SafeArrayList updateList = null; - + /** * False if the update list requires rebuilding. This is Node.class * specific and therefore not included as part of the Spatial update flags. * A flag is used instead of nulling the updateList to avoid reallocating * a whole list every time the scene graph changes. - */ - private boolean updateListValid = false; + */ + private boolean updateListValid = false; /** * Serialization only. Do not use. @@ -93,29 +94,29 @@ public class Node extends Spatial { /** * Constructor instantiates a new Node with a default empty * list for containing children. - * + * * @param name the name of the scene element. This is required for * identification and comparison purposes. */ public Node(String name) { super(name); - + // For backwards compatibility, only clear the "requires // update" flag if we are not a subclass of Node. // This prevents subclass from silently failing to receive // updates when they upgrade. - setRequiresUpdates(Node.class != getClass()); + setRequiresUpdates(Node.class != getClass()); } /** - * + * * getQuantity returns the number of children this node * maintains. - * + * * @return the number of children this node maintains. */ public int getQuantity() { - return children.size(); + return children.size(); } @Override @@ -143,7 +144,7 @@ public class Node extends Spatial { @Override protected void updateWorldBound(){ super.updateWorldBound(); - + // for a node, the world bound is a combination of all it's children // bounds BoundingVolume resultBound = null; @@ -167,7 +168,7 @@ public class Node extends Spatial { protected void setParent(Node parent) { if( this.parent == null && parent != null ) { // We were a root before and now we aren't... make sure if - // we had an updateList then we clear it completely to + // we had an updateList then we clear it completely to // avoid holding the dead array. updateList = null; updateListValid = false; @@ -204,15 +205,15 @@ public class Node extends Spatial { return updateList; } if( updateList == null ) { - updateList = new SafeArrayList(Spatial.class); + updateList = new SafeArrayList(Spatial.class); } else { updateList.clear(); } // Build the list addUpdateChildren(updateList); - updateListValid = true; - return updateList; + updateListValid = true; + return updateList; } @Override @@ -238,7 +239,7 @@ public class Node extends Spatial { // This branch has no geometric state that requires updates. return; } - + if ((refreshFlags & RF_LIGHTLIST) != 0){ updateWorldLightList(); } @@ -250,7 +251,7 @@ public class Node extends Spatial { } refreshFlags &= ~RF_CHILD_LIGHTLIST; - + if (!children.isEmpty()) { // the important part- make sure child geometric state is refreshed // first before updating own world bound. This saves @@ -260,7 +261,7 @@ public class Node extends Spatial { for (Spatial child : children.getArray()) { child.updateGeometricState(); } - } + } if ((refreshFlags & RF_BOUND) != 0){ updateWorldBound(); @@ -272,7 +273,7 @@ public class Node extends Spatial { /** * getTriangleCount returns the number of triangles contained * in all sub-branches of this node that contain geometry. - * + * * @return the triangle count of this branch. */ @Override @@ -286,11 +287,11 @@ public class Node extends Spatial { return count; } - + /** * getVertexCount returns the number of vertices contained * in all sub-branches of this node that contain geometry. - * + * * @return the vertex count of this branch. */ @Override @@ -311,7 +312,7 @@ public class Node extends Spatial { * returned. *
* If the child already had a parent it is detached from that former parent. - * + * * @param child * the child to attach to this node. * @return the number of children maintained by this node. @@ -320,15 +321,15 @@ public class Node extends Spatial { public int attachChild(Spatial child) { return attachChildAt(child, children.size()); } - + /** - * + * * attachChildAt attaches a child to this node at an index. This node * becomes the child's parent. The current number of children maintained is * returned. *
* If the child already had a parent it is detached from that former parent. - * + * * @param child * the child to attach to this node. * @return the number of children maintained by this node. @@ -344,7 +345,7 @@ public class Node extends Spatial { } child.setParent(this); children.add(index, child); - + // XXX: Not entirely correct? Forces bound update up the // tree stemming from the attached child. Also forces // transform update down the tree- @@ -354,17 +355,17 @@ public class Node extends Spatial { logger.log(Level.FINE,"Child ({0}) attached to this node ({1})", new Object[]{child.getName(), getName()}); } - + invalidateUpdateList(); } - + return children.size(); } /** * detachChild removes a given child from the node's list. * This child will no longer be maintained. - * + * * @param child * the child to remove. * @return the index the child was at. -1 if the child was not in the list. @@ -379,16 +380,16 @@ public class Node extends Spatial { detachChildAt(index); } return index; - } - - return -1; + } + + return -1; } /** * detachChild removes a given child from the node's list. * This child will no longe be maintained. Only the first child with a * matching name is removed. - * + * * @param childName * the child to remove. * @return the index the child was at. -1 if the child was not in the list. @@ -408,10 +409,10 @@ public class Node extends Spatial { } /** - * + * * detachChildAt removes a child at a given index. That child * is returned for saving purposes. - * + * * @param index * the index of the child to be removed. * @return the child at the supplied index. @@ -432,14 +433,14 @@ public class Node extends Spatial { child.setTransformRefresh(); // lights are also inherited from parent child.setLightListRefresh(); - + invalidateUpdateList(); } return child; } /** - * + * * detachAllChildren removes all children attached to this * node. */ @@ -458,7 +459,7 @@ public class Node extends Spatial { * in this node's list of children. * @param sp * The spatial to look up - * @return + * @return * The index of the spatial in the node's children, or -1 * if the spatial is not attached to this node */ @@ -468,7 +469,7 @@ public class Node extends Spatial { /** * More efficient than e.g detaching and attaching as no updates are needed. - * + * * @param index1 The index of the first child to swap * @param index2 The index of the second child to swap */ @@ -481,9 +482,9 @@ public class Node extends Spatial { } /** - * + * * getChild returns a child at a given index. - * + * * @param i * the index to retrieve the child from. * @return the child at a specified index. @@ -497,13 +498,13 @@ public class Node extends Spatial { * given name (case sensitive.) This method does a depth first recursive * search of all descendants of this node, it will return the first spatial * found with a matching name. - * + * * @param name * the name of the child to retrieve. If null, we'll return null. * @return the child if found, or null. */ public Spatial getChild(String name) { - if (name == null) + if (name == null) return null; for (Spatial child : children.getArray()) { @@ -518,11 +519,11 @@ public class Node extends Spatial { } return null; } - + /** * determines if the provided Spatial is contained in the children list of * this node. - * + * * @param spat * the child object to look for. * @return true if the object is contained, false otherwise. @@ -566,39 +567,39 @@ public class Node extends Spatial { public int collideWith(Collidable other, CollisionResults results){ int total = 0; - + // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children - // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. + // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. // The idea is when there are few children, it can be too expensive to test boundingVolume first. /* I'm removing this change until some issues can be addressed and I really think it needs to be implemented a better way anyway. - + First, it causes issues for anyone doing collideWith() with BoundingVolumes and expecting it to trickle down to the children. For example, children with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing a collision check at the parent level then has to do a BoundingSphere to BoundingBox collision which isn't resolved. (Having to come up with a collision point in that case is tricky and the first sign that this is the wrong approach.) - + Second, the rippling changes this caused to 'optimize' collideWith() for this special use-case are another sign that this approach was a bit dodgy. The whole idea of calculating a full collision just to see if the two shapes collide at all is very wasteful. - + A proper implementation should support a simpler boolean check that doesn't do all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9% of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done. - + I don't have time to do it right now but I'll at least un-break a bunch of peoples' code until it can be 'optimized' properly. Hopefully it's not too late to back out - the other dodgy ripples this caused. -pspeed (hindsight-expert ;)) - + the other dodgy ripples this caused. -pspeed (hindsight-expert ;)) + Note: the code itself is relatively simple to implement but I don't have time to a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast enough to do all the time for > 1. - + if (children.size() > 4) { BoundingVolume bv = this.getWorldBound(); @@ -642,7 +643,7 @@ public class Node extends Spatial { * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials). * * @see java.util.regex.Pattern - * @see Spatial#matches(java.lang.Class, java.lang.String) + * @see Spatial#matches(java.lang.Class, java.lang.String) */ @SuppressWarnings("unchecked") public List descendantMatches( @@ -662,7 +663,7 @@ public class Node extends Spatial { /** * Convenience wrapper. * - * @see #descendantMatches(java.lang.Class, java.lang.String) + * @see #descendantMatches(java.lang.Class, java.lang.String) */ public List descendantMatches( Class spatialSubclass) { @@ -672,7 +673,7 @@ public class Node extends Spatial { /** * Convenience wrapper. * - * @see #descendantMatches(java.lang.Class, java.lang.String) + * @see #descendantMatches(java.lang.Class, java.lang.String) */ public List descendantMatches(String nameRegex) { return descendantMatches(null, nameRegex); @@ -691,7 +692,7 @@ public class Node extends Spatial { // Reset the fields of the clone that should be in a 'new' state. nodeClone.updateList = null; nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone() - + return nodeClone; } @@ -707,6 +708,19 @@ public class Node extends Spatial { return nodeClone; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.children = cloner.clone(children); + + // Only the outer cloning thing knows whether this should be nulled + // or not... after all, we might be cloning a root node in which case + // cloning this list is fine. + this.updateList = cloner.clone(updateList); + } + @Override public void write(JmeExporter e) throws IOException { super.write(e); @@ -718,8 +732,8 @@ public class Node extends Spatial { // XXX: Load children before loading itself!! // This prevents empty children list if controls query // it in Control.setSpatial(). - - children = new SafeArrayList( Spatial.class, + + children = new SafeArrayList( Spatial.class, e.getCapsule(this).readSavableArrayList("children", null) ); // go through children and set parent to this node @@ -728,7 +742,7 @@ public class Node extends Spatial { child.parent = this; } } - + super.read(e); } @@ -749,7 +763,7 @@ public class Node extends Spatial { } } } - + @Override public void depthFirstTraversal(SceneGraphVisitor visitor) { for (Spatial child : children.getArray()) { @@ -757,7 +771,7 @@ public class Node extends Spatial { } visitor.visit(this); } - + @Override protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { queue.addAll(children); diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 1fe68d730..f833b6758 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -47,6 +47,8 @@ import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; import java.io.IOException; @@ -63,17 +65,17 @@ import java.util.logging.Logger; * @author Joshua Slack * @version $Revision: 4075 $, $Data$ */ -public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset { +public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable { private static final Logger logger = Logger.getLogger(Spatial.class.getName()); /** - * Specifies how frustum culling should be handled by + * Specifies how frustum culling should be handled by * this spatial. */ public enum CullHint { - /** + /** * Do whatever our parent does. If no parent, default to {@link #Dynamic}. */ Inherit, @@ -83,13 +85,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Camera planes whether or not this Spatial should be culled. */ Dynamic, - /** + /** * Always cull this from the view, throwing away this object * and any children from rendering commands. */ Always, /** - * Never cull this from view, always draw it. + * Never cull this from view, always draw it. * Note that we will still get culled if our parent is culled. */ Never; @@ -100,15 +102,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ public enum BatchHint { - /** + /** * Do whatever our parent does. If no parent, default to {@link #Always}. */ Inherit, - /** + /** * This spatial will always be batched when attached to a BatchNode. */ Always, - /** + /** * This spatial will never be batched when attached to a BatchNode. */ Never; @@ -118,12 +120,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms RF_BOUND = 0x02, - RF_LIGHTLIST = 0x04, // changes in light lists + RF_LIGHTLIST = 0x04, // changes in light lists RF_CHILD_LIGHTLIST = 0x08; // some child need geometry update - + protected CullHint cullHint = CullHint.Inherit; protected BatchHint batchHint = BatchHint.Inherit; - /** + /** * Spatial's bounding volume relative to the world. */ protected BoundingVolume worldBound; @@ -132,7 +134,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected LightList localLights; protected transient LightList worldLights; - /** + /** * This spatial's name. */ protected String name; @@ -147,11 +149,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab protected HashMap userData = null; /** * Used for smart asset caching - * - * @see AssetKey#useSmartCache() + * + * @see AssetKey#useSmartCache() */ protected AssetKey key; - /** + /** * Spatial's parent, or null if it has none. */ protected transient Node parent; @@ -174,7 +176,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * Serialization only. Do not use. * Not really. This class is never instantiated directly but the - * subclasses like to use the no-arg constructor for their own + * subclasses like to use the no-arg constructor for their own * no-arg constructor... which is technically weaker than * forward supplying defaults. */ @@ -192,7 +194,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected Spatial(String name) { this.name = name; - + localTransform = new Transform(); worldTransform = new Transform(); @@ -219,13 +221,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab boolean requiresUpdates() { return requiresUpdates | !controls.isEmpty(); } - + /** - * Subclasses can call this with true to denote that they require + * Subclasses can call this with true to denote that they require * updateLogicalState() to be called even if they contain no controls. * Setting this to false reverts to the default behavior of only * updating if the spatial has controls. This is not meant to - * indicate dynamic state in any way and must be called while + * indicate dynamic state in any way and must be called while * unattached or an IllegalStateException is thrown. It is designed * to be called during object construction and then never changed, ie: * it's meant to be subclass specific state and not runtime state. @@ -251,12 +253,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab // override it for more optimal behavior. Node and Geometry will override // it to false if the class is Node.class or Geometry.class. // This means that all subclasses will default to the old behavior - // unless they opt in. + // unless they opt in. if( parent != null ) { - throw new IllegalStateException("setRequiresUpdates() cannot be called once attached."); + throw new IllegalStateException("setRequiresUpdates() cannot be called once attached."); } this.requiresUpdates = f; - } + } /** * Indicate that the transform of this spatial has changed and that @@ -269,13 +271,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab protected void setLightListRefresh() { refreshFlags |= RF_LIGHTLIST; - + // Make sure next updateGeometricState() visits this branch // to update lights. Spatial p = parent; while (p != null) { //if (p.refreshFlags != 0) { - // any refresh flag is sufficient, + // any refresh flag is sufficient, // as each propagates to the root Node // 2015/2/8: @@ -283,16 +285,16 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab // or getWorldTransform() activates a "partial refresh" // which does not update the lights but does clear // the refresh flags on the ancestors! - - // return; + + // return; //} - + if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) { // The parent already has this flag, // so must all ancestors. return; } - + p.refreshFlags |= RF_CHILD_LIGHTLIST; p = p.parent; } @@ -315,10 +317,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab p = p.parent; } } - + /** * (Internal use only) Forces a refresh of the given types of data. - * + * * @param transforms Refresh world transform based on parents' * @param bounds Refresh bounding volume data based on child nodes * @param lights Refresh light list based on parents' @@ -401,9 +403,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * Returns the local {@link LightList}, which are the lights * that were directly attached to this Spatial through the - * {@link #addLight(com.jme3.light.Light) } and + * {@link #addLight(com.jme3.light.Light) } and * {@link #removeLight(com.jme3.light.Light) } methods. - * + * * @return The local light list */ public LightList getLocalLightList() { @@ -414,7 +416,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Returns the world {@link LightList}, containing the lights * combined from all this Spatial's parents up to and including * this Spatial's lights. - * + * * @return The combined world light list */ public LightList getWorldLightList() { @@ -502,14 +504,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * lookAt is a convenience method for auto-setting the local * rotation based on a position in world space and an up vector. It computes the rotation * to transform the z-axis to point onto 'position' and the y-axis to 'up'. - * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } + * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } * this method takes a world position to look at and not a relative direction. * * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation. * This was resulting in improper rotation when the spatial had rotated parent nodes. - * This method is intended to work in world space, so no matter what parent graph the + * This method is intended to work in world space, so no matter what parent graph the * spatial has, it will look at the given position in world space. - * + * * @param position * where to look at in terms of world coordinates * @param upVector @@ -522,10 +524,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab TempVars vars = TempVars.get(); Vector3f compVecA = vars.vect4; - + compVecA.set(position).subtractLocal(worldTranslation); - getLocalRotation().lookAt(compVecA, upVector); - + getLocalRotation().lookAt(compVecA, upVector); + if ( getParent() != null ) { Quaternion rot=vars.quat1; rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); @@ -579,7 +581,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab } /** - * Computes the world transform of this Spatial in the most + * Computes the world transform of this Spatial in the most * efficient manner possible. */ void checkDoTransformUpdate() { @@ -670,7 +672,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * @param vp The ViewPort to which the Spatial is being rendered to. * * @see Spatial#addControl(com.jme3.scene.control.Control) - * @see Spatial#getControl(java.lang.Class) + * @see Spatial#getControl(java.lang.Class) */ public void runControlRender(RenderManager rm, ViewPort vp) { if (controls.isEmpty()) { @@ -686,26 +688,26 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Add a control to the list of controls. * @param control The control to add. * - * @see Spatial#removeControl(java.lang.Class) + * @see Spatial#removeControl(java.lang.Class) */ public void addControl(Control control) { boolean before = requiresUpdates(); controls.add(control); control.setSpatial(this); boolean after = requiresUpdates(); - + // If the requirement to be updated has changed // then we need to let the parent node know so it // can rebuild its update list. if( parent != null && before != after ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } } /** * Removes the first control that is an instance of the given class. * - * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#addControl(com.jme3.scene.control.Control) */ public void removeControl(Class controlType) { boolean before = requiresUpdates(); @@ -717,23 +719,23 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab } } boolean after = requiresUpdates(); - + // If the requirement to be updated has changed // then we need to let the parent node know so it // can rebuild its update list. if( parent != null && before != after ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } } /** * Removes the given control from this spatial's controls. - * + * * @param control The control to remove * @return True if the control was successfully removed. False if the * control is not assigned to this spatial. * - * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#addControl(com.jme3.scene.control.Control) */ public boolean removeControl(Control control) { boolean before = requiresUpdates(); @@ -743,14 +745,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab } boolean after = requiresUpdates(); - + // If the requirement to be updated has changed // then we need to let the parent node know so it // can rebuild its update list. if( parent != null && before != after ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } - + return result; } @@ -761,7 +763,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * @param controlType The superclass of the control to look for. * @return The first instance in the list of the controlType class, or null. * - * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#addControl(com.jme3.scene.control.Control) */ public T getControl(Class controlType) { for (Control c : controls.getArray()) { @@ -790,7 +792,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * @return The number of controls attached to this Spatial. * @see Spatial#addControl(com.jme3.scene.control.Control) - * @see Spatial#removeControl(java.lang.Class) + * @see Spatial#removeControl(java.lang.Class) */ public int getNumControls() { return controls.size(); @@ -815,7 +817,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Calling this when the Spatial is attached to a node * will cause undefined results. User code should only call this * method on Spatials having no parent. - * + * * @see Spatial#getWorldLightList() * @see Spatial#getWorldTransform() * @see Spatial#getWorldBound() @@ -835,7 +837,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab if ((refreshFlags & RF_BOUND) != 0) { updateWorldBound(); } - + assert refreshFlags == 0; } @@ -1067,9 +1069,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * removeLight removes the given light from the Spatial. - * + * * @param light The light to remove. - * @see Spatial#addLight(com.jme3.light.Light) + * @see Spatial#addLight(com.jme3.light.Light) */ public void removeLight(Light light) { localLights.remove(light); @@ -1264,7 +1266,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * All controls will be cloned using the Control.cloneForSpatial method * on the clone. * - * @see Mesh#cloneForAnim() + * @see Mesh#cloneForAnim() */ public Spatial clone(boolean cloneMaterial) { try { @@ -1328,7 +1330,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * All controls will be cloned using the Control.cloneForSpatial method * on the clone. * - * @see Mesh#cloneForAnim() + * @see Mesh#cloneForAnim() */ @Override public Spatial clone() { @@ -1344,13 +1346,59 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ public abstract Spatial deepClone(); + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Spatial jmeClone() { + try { + Spatial clone = (Spatial)super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + + // Clone all of the fields that need fix-ups and/or potential + // sharing. + this.parent = cloner.clone(parent); + this.worldBound = cloner.clone(worldBound); + this.worldLights = cloner.clone(worldLights); + this.localLights = cloner.clone(localLights); + this.worldTransform = cloner.clone(worldTransform); + this.localTransform = cloner.clone(localTransform); + this.controls = cloner.clone(controls); + + // Cloner doesn't handle maps on its own just yet. + // Note: this is more advanced cloning than the old clone() method + // did because it just shallow cloned the map. In this case, we want + // to avoid all of the nasty cloneForSpatial() fixup style code that + // used to inject stuff into the clone's user data. By using cloner + // to clone the user data we get this automatically. + userData = (HashMap)userData.clone(); + for( Map.Entry e : userData.entrySet() ) { + Savable value = e.getValue(); + if( value instanceof Cloneable ) { + // Note: all JmeCloneable objects are also Cloneable so this + // catches both cases. + e.setValue(cloner.clone(value)); + } + } + } + public void setUserData(String key, Object data) { if (userData == null) { userData = new HashMap(); } if(data == null){ - userData.remove(key); + userData.remove(key); }else if (data instanceof Savable) { userData.put(key, (Savable) data); } else { @@ -1445,7 +1493,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split //the AnimControl creates the SkeletonControl for old files and add it to the spatial. //The SkeletonControl must be the last in the stack so we add the list of all other control before it. - //When backward compatibility won't be needed anymore this can be replaced by : + //When backward compatibility won't be needed anymore this can be replaced by : //controls = ic.readSavableArrayList("controlsList", null)); controls.addAll(0, ic.readSavableArrayList("controlsList", null)); @@ -1508,9 +1556,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * setQueueBucket determines at what phase of the * rendering process this Spatial will rendered. See the - * {@link Bucket} enum for an explanation of the various + * {@link Bucket} enum for an explanation of the various * render queue buckets. - * + * * @param queueBucket * The bucket to use for this Spatial. */ @@ -1595,7 +1643,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * * @return store if not null, otherwise, a new matrix containing the result. * - * @see Spatial#getWorldTransform() + * @see Spatial#getWorldTransform() */ public Matrix4f getLocalToWorldMatrix(Matrix4f store) { if (store == null) { diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java index 7f0bb601b..b57345664 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java @@ -47,19 +47,20 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Arrays; public class InstancedGeometry extends Geometry { - + private static final int INSTANCE_SIZE = 16; - + private VertexBuffer[] globalInstanceData; private VertexBuffer transformInstanceData; private Geometry[] geometries = new Geometry[1]; - + private int firstUnusedIndex = 0; /** @@ -71,12 +72,12 @@ public class InstancedGeometry extends Geometry { setBatchHint(BatchHint.Never); setMaxNumInstances(1); } - + /** * Creates instanced geometry with the specified mode and name. - * - * @param name The name of the spatial. - * + * + * @param name The name of the spatial. + * * @see Spatial#Spatial(java.lang.String) */ public InstancedGeometry(String name) { @@ -85,57 +86,57 @@ public class InstancedGeometry extends Geometry { setBatchHint(BatchHint.Never); setMaxNumInstances(1); } - + /** - * Global user specified per-instance data. - * + * Global user specified per-instance data. + * * By default set to null, specify an array of VertexBuffers * via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }. - * - * @return global user specified per-instance data. - * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) + * + * @return global user specified per-instance data. + * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) */ public VertexBuffer[] getGlobalUserInstanceData() { return globalInstanceData; } - + /** * Specify global user per-instance data. - * + * * By default set to null, specify an array of VertexBuffers * that contain per-instance vertex attributes. - * + * * @param globalInstanceData global user per-instance data. - * - * @throws IllegalArgumentException If one of the VertexBuffers is not + * + * @throws IllegalArgumentException If one of the VertexBuffers is not * {@link VertexBuffer#setInstanced(boolean) instanced}. */ public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) { this.globalInstanceData = globalInstanceData; } - + /** * Specify camera specific user per-instance data. - * + * * @param transformInstanceData The transforms for each instance. */ public void setTransformUserInstanceData(VertexBuffer transformInstanceData) { this.transformInstanceData = transformInstanceData; } - + /** * Return user per-instance transform data. - * + * * @return The per-instance transform data. * - * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer) + * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer) */ public VertexBuffer getTransformUserInstanceData() { return transformInstanceData; } - - private void updateInstance(Matrix4f worldMatrix, float[] store, - int offset, Matrix3f tempMat3, + + private void updateInstance(Matrix4f worldMatrix, float[] store, + int offset, Matrix3f tempMat3, Quaternion tempQuat) { worldMatrix.toRotationMatrix(tempMat3); tempMat3.invertLocal(); @@ -164,17 +165,17 @@ public class InstancedGeometry extends Geometry { store[offset + 14] = worldMatrix.m23; store[offset + 15] = tempQuat.getW(); } - + /** * Set the maximum amount of instances that can be rendered by this * instanced geometry when mode is set to auto. - * + * * This re-allocates internal structures and therefore should be called - * only when necessary. - * + * only when necessary. + * * @param maxNumInstances The maximum number of instances that can be * rendered. - * + * * @throws IllegalStateException If mode is set to manual. * @throws IllegalArgumentException If maxNumInstances is zero or negative */ @@ -182,14 +183,14 @@ public class InstancedGeometry extends Geometry { if (maxNumInstances < 1) { throw new IllegalArgumentException("maxNumInstances must be 1 or higher"); } - + Geometry[] originalGeometries = geometries; this.geometries = new Geometry[maxNumInstances]; - + if (originalGeometries != null) { System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length); } - + // Resize instance data. if (transformInstanceData != null) { BufferUtils.destroyDirectBuffer(transformInstanceData.getData()); @@ -203,7 +204,7 @@ public class InstancedGeometry extends Geometry { BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE)); } } - + public int getMaxNumInstances() { return geometries.length; } @@ -211,12 +212,12 @@ public class InstancedGeometry extends Geometry { public int getActualNumInstances() { return firstUnusedIndex; } - + private void swap(int idx1, int idx2) { Geometry g = geometries[idx1]; geometries[idx1] = geometries[idx2]; geometries[idx2] = g; - + if (geometries[idx1] != null) { InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1); } @@ -224,7 +225,7 @@ public class InstancedGeometry extends Geometry { InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2); } } - + private void sanitize(boolean insideEntriesNonNull) { if (firstUnusedIndex >= geometries.length) { throw new AssertionError(); @@ -234,7 +235,7 @@ public class InstancedGeometry extends Geometry { if (geometries[i] == null) { if (insideEntriesNonNull) { throw new AssertionError(); - } + } } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) { throw new AssertionError(); } @@ -245,55 +246,55 @@ public class InstancedGeometry extends Geometry { } } } - + public void updateInstances() { FloatBuffer fb = (FloatBuffer) transformInstanceData.getData(); fb.limit(fb.capacity()); fb.position(0); - + TempVars vars = TempVars.get(); { float[] temp = vars.matrixWrite; - + for (int i = 0; i < firstUnusedIndex; i++) { Geometry geom = geometries[i]; if (geom == null) { geom = geometries[firstUnusedIndex - 1]; - + if (geom == null) { throw new AssertionError(); } - + swap(i, firstUnusedIndex - 1); - + while (geometries[firstUnusedIndex -1] == null) { firstUnusedIndex--; } } - + Matrix4f worldMatrix = geom.getWorldMatrix(); updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1); fb.put(temp); } } vars.release(); - + fb.flip(); - + if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) { throw new AssertionError(); } transformInstanceData.updateData(fb); } - + public void deleteInstance(Geometry geom) { int idx = InstancedNode.getGeometryStartIndex2(geom); InstancedNode.setGeometryStartIndex2(geom, -1); - + geometries[idx] = null; - + if (idx == firstUnusedIndex - 1) { // Deleting the last element. // Move index back. @@ -309,12 +310,12 @@ public class InstancedGeometry extends Geometry { // Deleting element in the middle } } - + public void addInstance(Geometry geometry) { if (geometry == null) { throw new IllegalArgumentException("geometry cannot be null"); } - + // Take an index from the end. if (firstUnusedIndex + 1 >= geometries.length) { // No more room. @@ -323,15 +324,15 @@ public class InstancedGeometry extends Geometry { int freeIndex = firstUnusedIndex; firstUnusedIndex++; - + geometries[freeIndex] = geometry; InstancedNode.setGeometryStartIndex2(geometry, freeIndex); } - + public Geometry[] getGeometries() { return geometries; } - + public VertexBuffer[] getAllInstanceData() { ArrayList allData = new ArrayList(); if (transformInstanceData != null) { @@ -343,6 +344,16 @@ public class InstancedGeometry extends Geometry { return allData.toArray(new VertexBuffer[allData.size()]); } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.globalInstanceData = cloner.clone(globalInstanceData); + this.transformInstanceData = cloner.clone(transformInstanceData); + this.geometries = cloner.clone(geometries); + } + @Override public void write(JmeExporter exporter) throws IOException { super.write(exporter); @@ -350,7 +361,7 @@ public class InstancedGeometry extends Geometry { //capsule.write(currentNumInstances, "cur_num_instances", 1); capsule.write(geometries, "geometries", null); } - + @Override public void read(JmeImporter importer) throws IOException { super.read(importer); diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java index 45dd85417..c3cdd21e3 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -48,18 +48,19 @@ import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.HashMap; +import java.util.Map; public class InstancedNode extends GeometryGroupNode { - + static int getGeometryStartIndex2(Geometry geom) { return getGeometryStartIndex(geom); } - + static void setGeometryStartIndex2(Geometry geom, int startIndex) { setGeometryStartIndex(geom, startIndex); } - - private static final class InstanceTypeKey implements Cloneable { + + private static final class InstanceTypeKey implements Cloneable, JmeCloneable { Mesh mesh; Material material; @@ -70,7 +71,7 @@ public class InstancedNode extends GeometryGroupNode { this.material = material; this.lodLevel = lodLevel; } - + public InstanceTypeKey(){ } @@ -97,7 +98,7 @@ public class InstancedNode extends GeometryGroupNode { } return true; } - + @Override public InstanceTypeKey clone() { try { @@ -106,26 +107,41 @@ public class InstancedNode extends GeometryGroupNode { throw new AssertionError(); } } + + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.mesh = cloner.clone(mesh); + this.material = cloner.clone(material); + } } - + private static class InstancedNodeControl implements Control, JmeCloneable { private InstancedNode node; - + public InstancedNodeControl() { } - + public InstancedNodeControl(InstancedNode node) { this.node = node; } - + @Override public Control cloneForSpatial(Spatial spatial) { - return this; + return this; // WARNING: Sets wrong control on spatial. Will be // fixed automatically by InstancedNode.clone() method. } - + @Override public Object jmeClone() { try { @@ -133,52 +149,52 @@ public class InstancedNode extends GeometryGroupNode { } catch( CloneNotSupportedException e ) { throw new RuntimeException("Error cloning control", e); } - } + } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields( Cloner cloner, Object original ) { this.node = cloner.clone(node); } - + public void setSpatial(Spatial spatial){ } - + public void update(float tpf){ } - + public void render(RenderManager rm, ViewPort vp) { node.renderFromControl(); } - + public void write(JmeExporter ex) throws IOException { } public void read(JmeImporter im) throws IOException { } } - + protected InstancedNodeControl control; - - protected HashMap igByGeom + + protected HashMap igByGeom = new HashMap(); - + private InstanceTypeKey lookUp = new InstanceTypeKey(); - - private HashMap instancesMap = + + private HashMap instancesMap = new HashMap(); - + public InstancedNode() { super(); // NOTE: since we are deserializing, // the control is going to be added automatically here. } - + public InstancedNode(String name) { super(name); control = new InstancedNodeControl(this); addControl(control); } - + private void renderFromControl() { for (InstancedGeometry ig : instancesMap.values()) { ig.updateInstances(); @@ -207,7 +223,7 @@ public class InstancedNode extends GeometryGroupNode { return ig; } - + private void addToInstancedGeometry(Geometry geom) { Material material = geom.getMaterial(); MatParam param = material.getParam("UseInstancing"); @@ -216,20 +232,20 @@ public class InstancedNode extends GeometryGroupNode { + "parameter to true on the material prior " + "to adding it to InstancedNode"); } - + InstancedGeometry ig = lookUpByGeometry(geom); igByGeom.put(geom, ig); geom.associateWithGroupNode(this, 0); ig.addInstance(geom); } - + private void removeFromInstancedGeometry(Geometry geom) { InstancedGeometry ig = igByGeom.remove(geom); if (ig != null) { ig.deleteInstance(geom); } } - + private void relocateInInstancedGeometry(Geometry geom) { InstancedGeometry oldIG = igByGeom.get(geom); InstancedGeometry newIG = lookUpByGeometry(geom); @@ -242,7 +258,7 @@ public class InstancedNode extends GeometryGroupNode { igByGeom.put(geom, newIG); } } - + private void ungroupSceneGraph(Spatial s) { if (s instanceof Node) { for (Spatial sp : ((Node) s).getChildren()) { @@ -253,14 +269,14 @@ public class InstancedNode extends GeometryGroupNode { if (g.isGrouped()) { // Will invoke onGeometryUnassociated automatically. g.unassociateFromGroupNode(); - + if (InstancedNode.getGeometryStartIndex(g) != -1) { throw new AssertionError(); } } } } - + @Override public Spatial detachChildAt(int index) { Spatial s = super.detachChildAt(index); @@ -269,7 +285,7 @@ public class InstancedNode extends GeometryGroupNode { } return s; } - + private void instance(Spatial n) { if (n instanceof Geometry) { Geometry g = (Geometry) n; @@ -285,20 +301,20 @@ public class InstancedNode extends GeometryGroupNode { } } } - + public void instance() { instance(this); } - + @Override public Node clone() { return clone(true); } - + @Override public Node clone(boolean cloneMaterials) { InstancedNode clone = (InstancedNode)super.clone(cloneMaterials); - + if (instancesMap.size() > 0) { // Remove all instanced geometries from the clone for (int i = 0; i < clone.children.size(); i++) { @@ -312,7 +328,7 @@ public class InstancedNode extends GeometryGroupNode { } } } - + // remove original control from the clone clone.controls.remove(this.control); @@ -323,12 +339,33 @@ public class InstancedNode extends GeometryGroupNode { clone.lookUp = new InstanceTypeKey(); clone.igByGeom = new HashMap(); clone.instancesMap = new HashMap(); - + clone.instance(); - + return clone; } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.control = cloner.clone(control); + this.lookUp = cloner.clone(lookUp); + + HashMap newIgByGeom = new HashMap(); + for( Map.Entry e : igByGeom.entrySet() ) { + newIgByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); + } + this.igByGeom = newIgByGeom; + + HashMap newInstancesMap = new HashMap(); + for( Map.Entry e : instancesMap.entrySet() ) { + newInstancesMap.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); + } + this.instancesMap = newInstancesMap; + } + @Override public void onTransformChange(Geometry geom) { // Handled automatically diff --git a/jme3-core/src/main/java/com/jme3/util/IntMap.java b/jme3-core/src/main/java/com/jme3/util/IntMap.java index 38ffb2431..b572fad22 100644 --- a/jme3-core/src/main/java/com/jme3/util/IntMap.java +++ b/jme3-core/src/main/java/com/jme3/util/IntMap.java @@ -32,19 +32,21 @@ package com.jme3.util; import com.jme3.util.IntMap.Entry; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; /** * Similar to a {@link Map} except that ints are used as keys. - * + * * Taken from http://code.google.com/p/skorpios/ - * - * @author Nate + * + * @author Nate */ -public final class IntMap implements Iterable>, Cloneable { - +public final class IntMap implements Iterable>, Cloneable, JmeCloneable { + private Entry[] table; private final float loadFactor; private int size, mask, capacity, threshold; @@ -93,6 +95,26 @@ public final class IntMap implements Iterable>, Cloneable { return null; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.table = cloner.clone(table); + } + public boolean containsValue(Object value) { Entry[] table = this.table; for (int i = table.length; i-- > 0;){ @@ -228,7 +250,7 @@ public final class IntMap implements Iterable>, Cloneable { idx = 0; el = 0; } - + public boolean hasNext() { return el < size; } @@ -255,20 +277,20 @@ public final class IntMap implements Iterable>, Cloneable { // the entry was null. find another non-null entry. cur = table[++idx]; } while (cur == null); - + Entry e = cur; cur = cur.next; el ++; - + return e; } public void remove() { } - + } - - public static final class Entry implements Cloneable { + + public static final class Entry implements Cloneable, JmeCloneable { final int key; T value; @@ -303,5 +325,20 @@ public final class IntMap implements Iterable>, Cloneable { } return null; } + + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.value = cloner.clone(value); + this.next = cloner.clone(next); + } } } diff --git a/jme3-core/src/main/resources/com/jme3/system/version.properties b/jme3-core/src/main/resources/com/jme3/system/version.properties index 98168a14e..ae20006c0 100644 --- a/jme3-core/src/main/resources/com/jme3/system/version.properties +++ b/jme3-core/src/main/resources/com/jme3/system/version.properties @@ -1,11 +1,12 @@ # THIS IS AN AUTO-GENERATED FILE.. # DO NOT MODIFY! -build.date=1900-01-01 +build.date=2016-03-25 git.revision=0 git.branch=unknown git.hash= git.hash.short= git.tag= name.full=jMonkeyEngine 3.1.0-UNKNOWN +version.full=3.1.0-UNKNOWN version.number=3.1.0 version.tag=SNAPSHOT \ No newline at end of file