aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/scene
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/scene')
-rw-r--r--engine/src/core/com/jme3/scene/AssetLinkNode.java185
-rw-r--r--engine/src/core/com/jme3/scene/BatchNode.java653
-rw-r--r--engine/src/core/com/jme3/scene/CameraNode.java93
-rw-r--r--engine/src/core/com/jme3/scene/CollisionData.java52
-rw-r--r--engine/src/core/com/jme3/scene/Geometry.java565
-rw-r--r--engine/src/core/com/jme3/scene/LightNode.java93
-rw-r--r--engine/src/core/com/jme3/scene/Mesh.java1315
-rw-r--r--engine/src/core/com/jme3/scene/Node.java643
-rw-r--r--engine/src/core/com/jme3/scene/SceneGraphVisitor.java16
-rw-r--r--engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java35
-rw-r--r--engine/src/core/com/jme3/scene/SimpleBatchNode.java56
-rw-r--r--engine/src/core/com/jme3/scene/Spatial.java1478
-rw-r--r--engine/src/core/com/jme3/scene/UserData.java156
-rw-r--r--engine/src/core/com/jme3/scene/VertexBuffer.java1025
-rw-r--r--engine/src/core/com/jme3/scene/control/AbstractControl.java112
-rw-r--r--engine/src/core/com/jme3/scene/control/AreaUtils.java85
-rw-r--r--engine/src/core/com/jme3/scene/control/BillboardControl.java307
-rw-r--r--engine/src/core/com/jme3/scene/control/CameraControl.java159
-rw-r--r--engine/src/core/com/jme3/scene/control/Control.java90
-rw-r--r--engine/src/core/com/jme3/scene/control/LightControl.java189
-rw-r--r--engine/src/core/com/jme3/scene/control/LodControl.java204
-rw-r--r--engine/src/core/com/jme3/scene/control/UpdateControl.java96
-rw-r--r--engine/src/core/com/jme3/scene/control/package.html17
-rw-r--r--engine/src/core/com/jme3/scene/debug/Arrow.java142
-rw-r--r--engine/src/core/com/jme3/scene/debug/Grid.java105
-rw-r--r--engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java80
-rw-r--r--engine/src/core/com/jme3/scene/debug/SkeletonPoints.java80
-rw-r--r--engine/src/core/com/jme3/scene/debug/SkeletonWire.java109
-rw-r--r--engine/src/core/com/jme3/scene/debug/WireBox.java108
-rw-r--r--engine/src/core/com/jme3/scene/debug/WireFrustum.java88
-rw-r--r--engine/src/core/com/jme3/scene/debug/WireSphere.java159
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexBuffer.java96
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java71
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java70
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java70
-rw-r--r--engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java106
-rw-r--r--engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java86
-rw-r--r--engine/src/core/com/jme3/scene/mesh/package.html25
-rw-r--r--engine/src/core/com/jme3/scene/package.html26
-rw-r--r--engine/src/core/com/jme3/scene/shape/AbstractBox.java211
-rw-r--r--engine/src/core/com/jme3/scene/shape/Box.java176
-rw-r--r--engine/src/core/com/jme3/scene/shape/Curve.java271
-rw-r--r--engine/src/core/com/jme3/scene/shape/Cylinder.java420
-rw-r--r--engine/src/core/com/jme3/scene/shape/Dome.java339
-rw-r--r--engine/src/core/com/jme3/scene/shape/Line.java123
-rw-r--r--engine/src/core/com/jme3/scene/shape/PQTorus.java239
-rw-r--r--engine/src/core/com/jme3/scene/shape/Quad.java130
-rw-r--r--engine/src/core/com/jme3/scene/shape/Sphere.java420
-rw-r--r--engine/src/core/com/jme3/scene/shape/StripBox.java191
-rw-r--r--engine/src/core/com/jme3/scene/shape/Surface.java283
-rw-r--r--engine/src/core/com/jme3/scene/shape/Torus.java255
51 files changed, 12103 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/scene/AssetLinkNode.java b/engine/src/core/com/jme3/scene/AssetLinkNode.java
new file mode 100644
index 0000000..e50680c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/AssetLinkNode.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.ModelKey;
+import com.jme3.export.InputCapsule;
+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.SafeArrayList;
+import java.io.IOException;
+import java.util.Map.Entry;
+import java.util.*;
+import java.util.logging.Level;
+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 {
+
+ protected ArrayList<ModelKey> assetLoaderKeys = new ArrayList<ModelKey>();
+ protected Map<ModelKey, Spatial> assetChildren = new HashMap<ModelKey, Spatial>();
+
+ public AssetLinkNode() {
+ }
+
+ public AssetLinkNode(ModelKey key) {
+ this(key.getName(), key);
+ }
+
+ public AssetLinkNode(String name, ModelKey key) {
+ super(name);
+ assetLoaderKeys.add(key);
+ }
+
+ /**
+ * Add a "linked" child. These are loaded from the assetManager when the
+ * AssetLinkNode is loaded from a binary file.
+ * @param key
+ */
+ public void addLinkedChild(ModelKey key) {
+ if (assetLoaderKeys.contains(key)) {
+ return;
+ }
+ assetLoaderKeys.add(key);
+ }
+
+ public void removeLinkedChild(ModelKey key) {
+ assetLoaderKeys.remove(key);
+ }
+
+ public ArrayList<ModelKey> getAssetLoaderKeys() {
+ return assetLoaderKeys;
+ }
+
+ public void attachLinkedChild(AssetManager manager, ModelKey key) {
+ addLinkedChild(key);
+ Spatial child = manager.loadAsset(key);
+ assetChildren.put(key, child);
+ attachChild(child);
+ }
+
+ public void attachLinkedChild(Spatial spat, ModelKey key) {
+ addLinkedChild(key);
+ assetChildren.put(key, spat);
+ attachChild(spat);
+ }
+
+ public void detachLinkedChild(ModelKey key) {
+ Spatial spatial = assetChildren.get(key);
+ if (spatial != null) {
+ detachChild(spatial);
+ }
+ removeLinkedChild(key);
+ assetChildren.remove(key);
+ }
+
+ public void detachLinkedChild(Spatial child, ModelKey key) {
+ removeLinkedChild(key);
+ assetChildren.remove(key);
+ detachChild(child);
+ }
+
+ /**
+ * Loads the linked children AssetKeys from the AssetManager and attaches them to the Node<br>
+ * If they are already attached, they will be reloaded.
+ * @param manager
+ */
+ public void attachLinkedChildren(AssetManager manager) {
+ detachLinkedChildren();
+ for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
+ ModelKey assetKey = it.next();
+ Spatial curChild = assetChildren.get(assetKey);
+ if (curChild != null) {
+ curChild.removeFromParent();
+ }
+ Spatial child = manager.loadAsset(assetKey);
+ attachChild(child);
+ assetChildren.put(assetKey, child);
+ }
+ }
+
+ public void detachLinkedChildren() {
+ Set<Entry<ModelKey, Spatial>> set = assetChildren.entrySet();
+ for (Iterator<Entry<ModelKey, Spatial>> it = set.iterator(); it.hasNext();) {
+ Entry<ModelKey, Spatial> entry = it.next();
+ entry.getValue().removeFromParent();
+ it.remove();
+ }
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ BinaryImporter importer = BinaryImporter.getInstance();
+ AssetManager loaderManager = e.getAssetManager();
+
+ assetLoaderKeys = (ArrayList<ModelKey>) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<ModelKey>());
+ for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
+ ModelKey modelKey = it.next();
+ AssetInfo info = loaderManager.locateAsset(modelKey);
+ Spatial child = null;
+ if (info != null) {
+ child = (Spatial) importer.load(info);
+ }
+ if (child != null) {
+ child.parent = this;
+ children.add(child);
+ assetChildren.put(modelKey, child);
+ } else {
+ Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
+ new Object[]{ modelKey, key });
+ }
+ }
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ SafeArrayList<Spatial> childs = children;
+ children = new SafeArrayList<Spatial>(Spatial.class);
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null);
+ children = childs;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/BatchNode.java b/engine/src/core/com/jme3/scene/BatchNode.java
new file mode 100644
index 0000000..bc1b2cb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/BatchNode.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph.
+ * There is one geometry per different material in the sub tree.
+ * this geometries are directly attached to the node in the scene graph.
+ * usage is like any other node except you have to call the {@link #batch()} method once all geoms have been attached to the sub scene graph and theire material set
+ * (see todo more automagic for further enhancements)
+ * all the geometry that have been batched are set to {@link CullHint#Always} to not render them.
+ * the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch.
+ * 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 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 updateLigicalState)
+ * @author Nehon
+ */
+public class BatchNode extends Node implements Savable {
+
+ private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
+ /**
+ * the map of geometry holding the batched meshes
+ */
+ protected Map<Material, Batch> batches = new HashMap<Material, Batch>();
+ /**
+ * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
+ */
+ private float[] tmpFloat;
+ private float[] tmpFloatN;
+ private float[] tmpFloatT;
+ int maxVertCount = 0;
+ boolean useTangents = false;
+ boolean needsFullRebatch = true;
+
+ /**
+ * Construct a batchNode
+ */
+ public BatchNode() {
+ super();
+ }
+
+ public BatchNode(String name) {
+ super(name);
+ }
+
+ @Override
+ public void updateGeometricState() {
+ if ((refreshFlags & RF_LIGHTLIST) != 0) {
+ updateWorldLightList();
+ }
+
+ if ((refreshFlags & RF_TRANSFORM) != 0) {
+ // combine with parent transforms- same for all spatial
+ // subclasses.
+ updateWorldTransforms();
+ }
+
+ if (!children.isEmpty()) {
+ // the important part- make sure child geometric state is refreshed
+ // first before updating own world bound. This saves
+ // a round-trip later on.
+ // NOTE 9/19/09
+ // Although it does save a round trip,
+
+ for (Spatial child : children.getArray()) {
+ child.updateGeometricState();
+ }
+
+ for (Batch batch : batches.values()) {
+ if (batch.needMeshUpdate) {
+ batch.geometry.getMesh().updateBound();
+ batch.geometry.updateWorldBound();
+ batch.needMeshUpdate = false;
+
+ }
+ }
+
+
+ }
+
+ if ((refreshFlags & RF_BOUND) != 0) {
+ updateWorldBound();
+ }
+
+ assert refreshFlags == 0;
+ }
+
+ protected Transform getTransforms(Geometry geom) {
+ return geom.getWorldTransform();
+ }
+
+ protected void updateSubBatch(Geometry bg) {
+ Batch batch = batches.get(bg.getMaterial());
+ if (batch != null) {
+ Mesh mesh = batch.geometry.getMesh();
+
+ VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
+ FloatBuffer posBuf = (FloatBuffer) pvb.getData();
+ VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
+ FloatBuffer normBuf = (FloatBuffer) nvb.getData();
+
+ if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
+
+ VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
+ FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
+ doTransformsTangents(posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
+ tvb.updateData(tanBuf);
+ } else {
+ doTransforms(posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
+ }
+ pvb.updateData(posBuf);
+ nvb.updateData(normBuf);
+
+
+ batch.needMeshUpdate = true;
+ }
+ }
+
+ /**
+ * Batch this batchNode
+ * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
+ */
+ public void batch() {
+ doBatch();
+ //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice
+ for (Batch batch : batches.values()) {
+ batch.geometry.setIgnoreTransform(true);
+ }
+ }
+
+ protected void doBatch() {
+ Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
+ maxVertCount = 0;
+ int nbGeoms = 0;
+
+ gatherGeomerties(matMap, this, needsFullRebatch);
+ if (needsFullRebatch) {
+ for (Batch batch : batches.values()) {
+ batch.geometry.removeFromParent();
+ }
+ batches.clear();
+ }
+
+ for (Material material : matMap.keySet()) {
+ Mesh m = new Mesh();
+ List<Geometry> list = matMap.get(material);
+ nbGeoms += list.size();
+ if (!needsFullRebatch) {
+ list.add(batches.get(material).geometry);
+ }
+ mergeGeometries(m, list);
+ m.setDynamic();
+ Batch batch = new Batch();
+
+ batch.geometry = new Geometry(name + "-batch" + batches.size());
+ batch.geometry.setMaterial(material);
+ this.attachChild(batch.geometry);
+
+
+ batch.geometry.setMesh(m);
+ batch.geometry.getMesh().updateCounts();
+ batch.geometry.getMesh().updateBound();
+ batches.put(material, batch);
+ }
+
+ logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
+
+
+ //init temp float arrays
+ tmpFloat = new float[maxVertCount * 3];
+ tmpFloatN = new float[maxVertCount * 3];
+ if (useTangents) {
+ tmpFloatT = new float[maxVertCount * 4];
+ }
+ }
+
+ private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
+
+ if (n.getClass() == Geometry.class) {
+
+ if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) {
+ Geometry g = (Geometry) n;
+ if (!g.isBatched() || rebatch) {
+ if (g.getMaterial() == null) {
+ throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching");
+ }
+ List<Geometry> list = map.get(g.getMaterial());
+ if (list == null) {
+ list = new ArrayList<Geometry>();
+ map.put(g.getMaterial(), list);
+ }
+ g.setTransformRefresh();
+ list.add(g);
+ }
+ }
+
+ } else if (n instanceof Node) {
+ for (Spatial child : ((Node) n).getChildren()) {
+ if (child instanceof BatchNode) {
+ continue;
+ }
+ gatherGeomerties(map, child, rebatch);
+ }
+ }
+
+ }
+
+ private boolean isBatch(Spatial s) {
+ for (Batch batch : batches.values()) {
+ if (batch.geometry == s) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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
+ public void setMaterial(Material material) {
+// for (Batch batch : batches.values()) {
+// batch.geometry.setMaterial(material);
+// }
+ throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
+ }
+
+ /**
+ * 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)
+ */
+ public Material getMaterial() {
+ if (!batches.isEmpty()) {
+ Batch b = batches.get(batches.keySet().iterator().next());
+ return b.geometry.getMaterial();
+ }
+ return null;//material;
+ }
+
+// /**
+// * Sets the material to the a specific batch of this BatchNode
+// *
+// *
+// * @param material the material to use for this geometry
+// */
+// public void setMaterial(Material material,int batchIndex) {
+// if (!batches.isEmpty()) {
+//
+// }
+//
+// }
+//
+// /**
+// * 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)
+// */
+// public Material getMaterial(int batchIndex) {
+// if (!batches.isEmpty()) {
+// Batch b = batches.get(batches.keySet().iterator().next());
+// return b.geometry.getMaterial();
+// }
+// return null;//material;
+// }
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+//
+// if (material != null) {
+// oc.write(material.getAssetName(), "materialName", null);
+// }
+// oc.write(material, "material", null);
+
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+
+
+// material = null;
+// String matName = ic.readString("materialName", null);
+// if (matName != null) {
+// // Material name is set,
+// // Attempt to load material via J3M
+// try {
+// material = im.getAssetManager().loadMaterial(matName);
+// } catch (AssetNotFoundException ex) {
+// // Cannot find J3M file.
+// logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
+// matName);
+// }
+// }
+// // If material is NULL, try to load it from the geometry
+// if (material == null) {
+// material = (Material) ic.readSavable("material", null);
+// }
+
+ }
+
+ /**
+ * Merges all geometries in the collection into
+ * the output mesh. Does not take into account materials.
+ *
+ * @param geometries
+ * @param outMesh
+ */
+ private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
+ int[] compsForBuf = new int[VertexBuffer.Type.values().length];
+ VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];
+
+ int totalVerts = 0;
+ int totalTris = 0;
+ int totalLodLevels = 0;
+
+ Mesh.Mode mode = null;
+ for (Geometry geom : geometries) {
+ totalVerts += geom.getVertexCount();
+ totalTris += geom.getTriangleCount();
+ totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
+ if (maxVertCount < geom.getVertexCount()) {
+ maxVertCount = geom.getVertexCount();
+ }
+ Mesh.Mode listMode;
+ int components;
+ switch (geom.getMesh().getMode()) {
+ case Points:
+ listMode = Mesh.Mode.Points;
+ components = 1;
+ break;
+ case LineLoop:
+ case LineStrip:
+ case Lines:
+ listMode = Mesh.Mode.Lines;
+ components = 2;
+ break;
+ case TriangleFan:
+ case TriangleStrip:
+ case Triangles:
+ listMode = Mesh.Mode.Triangles;
+ components = 3;
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+
+ for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
+ compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
+ formatForBuf[entry.getKey()] = entry.getValue().getFormat();
+ }
+
+ if (mode != null && mode != listMode) {
+ throw new UnsupportedOperationException("Cannot combine different"
+ + " primitive types: " + mode + " != " + listMode);
+ }
+ mode = listMode;
+ compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
+ }
+
+ outMesh.setMode(mode);
+ if (totalVerts >= 65536) {
+ // make sure we create an UnsignedInt buffer so
+ // we can fit all of the meshes
+ formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
+ } else {
+ formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
+ }
+
+ // generate output buffers based on retrieved info
+ for (int i = 0; i < compsForBuf.length; i++) {
+ if (compsForBuf[i] == 0) {
+ continue;
+ }
+
+ Buffer data;
+ if (i == VertexBuffer.Type.Index.ordinal()) {
+ data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
+ } else {
+ data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
+ }
+
+ VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]);
+ vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data);
+ outMesh.setBuffer(vb);
+ }
+
+ int globalVertIndex = 0;
+ int globalTriIndex = 0;
+
+ for (Geometry geom : geometries) {
+ Mesh inMesh = geom.getMesh();
+ geom.batch(this, globalVertIndex);
+
+ int geomVertCount = inMesh.getVertexCount();
+ int geomTriCount = inMesh.getTriangleCount();
+
+ for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
+ VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+
+ VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+
+ if (outBuf == null) {
+ continue;
+ }
+
+ if (VertexBuffer.Type.Index.ordinal() == bufType) {
+ int components = compsForBuf[bufType];
+
+ IndexBuffer inIdx = inMesh.getIndicesAsList();
+ IndexBuffer outIdx = outMesh.getIndexBuffer();
+
+ for (int tri = 0; tri < geomTriCount; tri++) {
+ for (int comp = 0; comp < components; comp++) {
+ int idx = inIdx.get(tri * components + comp) + globalVertIndex;
+ outIdx.put((globalTriIndex + tri) * components + comp, idx);
+ }
+ }
+ } else if (VertexBuffer.Type.Position.ordinal() == bufType) {
+ FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+ FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+ doCopyBuffer(inPos, globalVertIndex, outPos, 3);
+ } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) {
+ FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+ FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+ doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]);
+ if (VertexBuffer.Type.Tangent.ordinal() == bufType) {
+ useTangents = true;
+ }
+ } else {
+ inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount);
+// for (int vert = 0; vert < geomVertCount; vert++) {
+// int curGlobalVertIndex = globalVertIndex + vert;
+// inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
+// }
+ }
+ }
+
+ globalVertIndex += geomVertCount;
+ globalTriIndex += geomTriCount;
+ }
+ }
+
+ private void doTransforms(FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) {
+ TempVars vars = TempVars.get();
+ Vector3f pos = vars.vect1;
+ Vector3f norm = vars.vect2;
+
+ int length = (end - start) * 3;
+
+ // offset is given in element units
+ // convert to be in component units
+ int offset = start * 3;
+ bufPos.position(offset);
+ bufNorm.position(offset);
+ bufPos.get(tmpFloat, 0, length);
+ bufNorm.get(tmpFloatN, 0, length);
+ int index = 0;
+ while (index < length) {
+ pos.x = tmpFloat[index];
+ norm.x = tmpFloatN[index++];
+ pos.y = tmpFloat[index];
+ norm.y = tmpFloatN[index++];
+ pos.z = tmpFloat[index];
+ norm.z = tmpFloatN[index];
+
+ transform.mult(pos, pos);
+ transform.multNormal(norm, norm);
+
+ index -= 2;
+ tmpFloat[index] = pos.x;
+ tmpFloatN[index++] = norm.x;
+ tmpFloat[index] = pos.y;
+ tmpFloatN[index++] = norm.y;
+ tmpFloat[index] = pos.z;
+ tmpFloatN[index++] = norm.z;
+
+ }
+ vars.release();
+ bufPos.position(offset);
+ //using bulk put as it's faster
+ bufPos.put(tmpFloat, 0, length);
+ bufNorm.position(offset);
+ //using bulk put as it's faster
+ bufNorm.put(tmpFloatN, 0, length);
+ }
+
+ private void doTransformsTangents(FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
+ TempVars vars = TempVars.get();
+ Vector3f pos = vars.vect1;
+ Vector3f norm = vars.vect2;
+ Vector3f tan = vars.vect3;
+
+ int length = (end - start) * 3;
+ int tanLength = (end - start) * 4;
+
+ // offset is given in element units
+ // convert to be in component units
+ int offset = start * 3;
+ int tanOffset = start * 4;
+
+ bufPos.position(offset);
+ bufNorm.position(offset);
+ bufTangents.position(tanOffset);
+ bufPos.get(tmpFloat, 0, length);
+ bufNorm.get(tmpFloatN, 0, length);
+ bufTangents.get(tmpFloatT, 0, tanLength);
+
+ int index = 0;
+ int tanIndex = 0;
+ while (index < length) {
+ pos.x = tmpFloat[index];
+ norm.x = tmpFloatN[index++];
+ pos.y = tmpFloat[index];
+ norm.y = tmpFloatN[index++];
+ pos.z = tmpFloat[index];
+ norm.z = tmpFloatN[index];
+
+ tan.x = tmpFloatT[tanIndex++];
+ tan.y = tmpFloatT[tanIndex++];
+ tan.z = tmpFloatT[tanIndex++];
+
+ transform.mult(pos, pos);
+ transform.multNormal(norm, norm);
+ transform.multNormal(tan, tan);
+
+ index -= 2;
+ tanIndex -= 3;
+
+ tmpFloat[index] = pos.x;
+ tmpFloatN[index++] = norm.x;
+ tmpFloat[index] = pos.y;
+ tmpFloatN[index++] = norm.y;
+ tmpFloat[index] = pos.z;
+ tmpFloatN[index++] = norm.z;
+
+ tmpFloatT[tanIndex++] = tan.x;
+ tmpFloatT[tanIndex++] = tan.y;
+ tmpFloatT[tanIndex++] = tan.z;
+
+ //Skipping 4th element of tangent buffer (handedness)
+ tanIndex++;
+
+ }
+ vars.release();
+ bufPos.position(offset);
+ //using bulk put as it's faster
+ bufPos.put(tmpFloat, 0, length);
+ bufNorm.position(offset);
+ //using bulk put as it's faster
+ bufNorm.put(tmpFloatN, 0, length);
+ bufTangents.position(tanOffset);
+ //using bulk put as it's faster
+ bufTangents.put(tmpFloatT, 0, tanLength);
+ }
+
+ private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
+ TempVars vars = TempVars.get();
+ Vector3f pos = vars.vect1;
+
+ // offset is given in element units
+ // convert to be in component units
+ offset *= componentSize;
+
+ for (int i = 0; i < inBuf.capacity() / componentSize; i++) {
+ pos.x = inBuf.get(i * componentSize + 0);
+ pos.y = inBuf.get(i * componentSize + 1);
+ pos.z = inBuf.get(i * componentSize + 2);
+
+ outBuf.put(offset + i * componentSize + 0, pos.x);
+ outBuf.put(offset + i * componentSize + 1, pos.y);
+ outBuf.put(offset + i * componentSize + 2, pos.z);
+ }
+ vars.release();
+ }
+
+ protected class Batch {
+
+ Geometry geometry;
+ boolean needMeshUpdate = false;
+ }
+
+ protected void setNeedsFullRebatch(boolean needsFullRebatch) {
+ this.needsFullRebatch = needsFullRebatch;
+ }
+
+ public int getOffsetIndex(Geometry batchedGeometry){
+ return batchedGeometry.startIndex;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/CameraNode.java b/engine/src/core/com/jme3/scene/CameraNode.java
new file mode 100644
index 0000000..c6bb48b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/CameraNode.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.control.CameraControl;
+import com.jme3.scene.control.CameraControl.ControlDirection;
+
+/**
+ * <code>CameraNode</code> simply uses {@link CameraControl} to implement
+ * linking of camera and node data.
+ *
+ * @author Tim8Dev
+ */
+public class CameraNode extends Node {
+
+ private CameraControl camControl;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public CameraNode() {
+ }
+
+ public CameraNode(String name, Camera camera) {
+ this(name, new CameraControl(camera));
+ }
+
+ public CameraNode(String name, CameraControl control) {
+ super(name);
+ addControl(control);
+ camControl = control;
+ }
+
+ public void setEnabled(boolean enabled) {
+ camControl.setEnabled(enabled);
+ }
+
+ public boolean isEnabled() {
+ return camControl.isEnabled();
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ camControl.setControlDir(controlDir);
+ }
+
+ public void setCamera(Camera camera) {
+ camControl.setCamera(camera);
+ }
+
+ public ControlDirection getControlDir() {
+ return camControl.getControlDir();
+ }
+
+ public Camera getCamera() {
+ return camControl.getCamera();
+ }
+
+// @Override
+// public void lookAt(Vector3f position, Vector3f upVector) {
+// this.lookAt(position, upVector);
+// camControl.getCamera().lookAt(position, upVector);
+// }
+}
diff --git a/engine/src/core/com/jme3/scene/CollisionData.java b/engine/src/core/com/jme3/scene/CollisionData.java
new file mode 100644
index 0000000..914bf24
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/CollisionData.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.Savable;
+import com.jme3.math.Matrix4f;
+
+/**
+ * <code>CollisionData</code> is an interface that can be used to
+ * do triangle-accurate collision with bounding volumes and rays.
+ *
+ * @author Kirill Vainer
+ */
+public interface CollisionData extends Savable, Cloneable {
+ public int collideWith(Collidable other,
+ Matrix4f worldMatrix,
+ BoundingVolume worldBound,
+ CollisionResults results);
+}
diff --git a/engine/src/core/com/jme3/scene/Geometry.java b/engine/src/core/com/jme3/scene/Geometry.java
new file mode 100644
index 0000000..b02196d
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Geometry.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Geometry</code> defines a leaf node of the scene graph. The leaf node
+ * 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.
+ // 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());
+ protected Mesh mesh;
+ protected transient int lodLevel = 0;
+ protected Material material;
+ /**
+ * When true, the geometry's transform will not be applied.
+ */
+ protected boolean ignoreTransform = false;
+ protected transient Matrix4f cachedWorldMat = new Matrix4f();
+ /**
+ * used when geometry is batched
+ */
+ protected BatchNode batchNode = null;
+ /**
+ * the start index of this geom's mesh in the batchNode mesh
+ */
+ protected int startIndex;
+ /**
+ * the previous transforms of the geometry used to compute world transforms
+ */
+ protected Transform prevBatchTransforms = null;
+ /**
+ * the cached offset matrix used when the geometry is batched
+ */
+ protected Matrix4f cachedOffsetMat = null;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Geometry() {
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 NullPointerException();
+ }
+
+ this.mesh = mesh;
+ }
+
+ /**
+ * @return If ignoreTransform mode is set.
+ *
+ * @see Geometry#setIgnoreTransform(boolean)
+ */
+ public boolean isIgnoreTransform() {
+ return ignoreTransform;
+ }
+
+ /**
+ * @param ignoreTransform If true, the geometry's transform will not be applied.
+ */
+ public void setIgnoreTransform(boolean ignoreTransform) {
+ this.ignoreTransform = ignoreTransform;
+ }
+
+ /**
+ * Sets the LOD level to use when rendering the mesh of this geometry.
+ * 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
+ public void setLodLevel(int lod) {
+ if (mesh.getNumLodLevels() == 0) {
+ throw new IllegalStateException("LOD levels are not set on this mesh");
+ }
+
+ if (lod < 0 || lod >= mesh.getNumLodLevels()) {
+ throw new IllegalArgumentException("LOD level is out of range: " + lod);
+ }
+
+ lodLevel = lod;
+ }
+
+ /**
+ * Returns the LOD level set with {@link #setLodLevel(int) }.
+ *
+ * @return the LOD level set
+ */
+ public int getLodLevel() {
+ return lodLevel;
+ }
+
+ /**
+ * Returns this geometry's mesh vertex count.
+ *
+ * @return this geometry's mesh vertex count.
+ *
+ * @see Mesh#getVertexCount()
+ */
+ public int getVertexCount() {
+ return mesh.getVertexCount();
+ }
+
+ /**
+ * Returns this geometry's mesh triangle count.
+ *
+ * @return this geometry's mesh triangle count.
+ *
+ * @see Mesh#getTriangleCount()
+ */
+ public int getTriangleCount() {
+ return mesh.getTriangleCount();
+ }
+
+ /**
+ * 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) {
+ if (mesh == null) {
+ throw new IllegalArgumentException();
+ }
+ if (isBatched()) {
+ throw new UnsupportedOperationException("Cannot set the mesh of a batched geometry");
+ }
+
+ this.mesh = mesh;
+ setBoundRefresh();
+ }
+
+ /**
+ * Returns the mseh to use for this geometry
+ *
+ * @return the mseh to use for this geometry
+ *
+ * @see #setMesh(com.jme3.scene.Mesh)
+ */
+ public Mesh getMesh() {
+ return mesh;
+ }
+
+ /**
+ * Sets the material to use for this geometry.
+ *
+ * @param material the material to use for this geometry
+ */
+ @Override
+ public void setMaterial(Material material) {
+ if (isBatched()) {
+ throw new UnsupportedOperationException("Cannot set the material of a batched geometry, change the material of the parent BatchNode.");
+ }
+ this.material = material;
+ }
+
+ /**
+ * Returns the material that is used for this geometry.
+ *
+ * @return the material that is used for this geometry
+ *
+ * @see #setMaterial(com.jme3.material.Material)
+ */
+ public Material getMaterial() {
+ return material;
+ }
+
+ /**
+ * @return The bounding volume of the mesh, in model space.
+ */
+ public BoundingVolume getModelBound() {
+ return mesh.getBound();
+ }
+
+ /**
+ * Updates the bounding volume of the mesh. Should be called when the
+ * mesh has been modified.
+ */
+ public void updateModelBound() {
+ mesh.updateBound();
+ setBoundRefresh();
+ }
+
+ /**
+ * <code>updateWorldBound</code> updates the bounding volume that contains
+ * this geometry. The location of the geometry is based on the location of
+ * all this node's parents.
+ *
+ * @see Spatial#updateWorldBound()
+ */
+ @Override
+ protected void updateWorldBound() {
+ super.updateWorldBound();
+ if (mesh == null) {
+ throw new NullPointerException("Geometry: " + getName() + " has null mesh");
+ }
+
+ if (mesh.getBound() != null) {
+ if (ignoreTransform) {
+ // we do not transform the model bound by the world transform,
+ // just use the model bound as-is
+ worldBound = mesh.getBound().clone(worldBound);
+ } else {
+ worldBound = mesh.getBound().transform(worldTransform, worldBound);
+ }
+ }
+ }
+
+ @Override
+ protected void updateWorldTransforms() {
+
+ super.updateWorldTransforms();
+ computeWorldMatrix();
+
+ if (isBatched()) {
+ computeOffsetTransform();
+ batchNode.updateSubBatch(this);
+ prevBatchTransforms.set(batchNode.getTransforms(this));
+
+ }
+ // geometry requires lights to be sorted
+ worldLights.sort(true);
+ }
+
+ /**
+ * Batch this geometry, should only be called by the BatchNode.
+ * @param node the batchNode
+ * @param startIndex the starting index of this geometry in the batched mesh
+ */
+ protected void batch(BatchNode node, int startIndex) {
+ this.batchNode = node;
+ this.startIndex = startIndex;
+ prevBatchTransforms = new Transform();
+ cachedOffsetMat = new Matrix4f();
+ setCullHint(CullHint.Always);
+ }
+
+ /**
+ * unBatch this geometry.
+ */
+ protected void unBatch() {
+ this.startIndex = 0;
+ prevBatchTransforms = null;
+ cachedOffsetMat = null;
+ //once the geometry is removed from the screnegraph the batchNode needs to be rebatched.
+ this.batchNode.setNeedsFullRebatch(true);
+ this.batchNode = null;
+ setCullHint(CullHint.Dynamic);
+ }
+
+ @Override
+ public boolean removeFromParent() {
+ boolean removed = super.removeFromParent();
+ //if the geometry is batched we also have to unbatch it
+ if (isBatched()) {
+ unBatch();
+ }
+ return removed;
+ }
+
+ /**
+ * Recomputes the cached offset matrix used when the geometry is batched *
+ */
+ public void computeOffsetTransform() {
+ TempVars vars = TempVars.get();
+ Matrix4f tmpMat = vars.tempMat42;
+
+ // Compute the cached world matrix
+ cachedOffsetMat.loadIdentity();
+ cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation());
+ cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation());
+
+
+ Matrix4f scaleMat = vars.tempMat4;
+ scaleMat.loadIdentity();
+ scaleMat.scale(prevBatchTransforms.getScale());
+ cachedOffsetMat.multLocal(scaleMat);
+ cachedOffsetMat.invertLocal();
+
+ tmpMat.loadIdentity();
+ tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation());
+ tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation());
+ scaleMat.loadIdentity();
+ scaleMat.scale(batchNode.getTransforms(this).getScale());
+ tmpMat.multLocal(scaleMat);
+
+ tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
+
+ vars.release();
+ }
+
+ /**
+ * Indicate that the transform of this spatial has changed and that
+ * a refresh is required.
+ */
+ @Override
+ protected void setTransformRefresh() {
+ refreshFlags |= RF_TRANSFORM;
+ setBoundRefresh();
+ }
+
+ /**
+ * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }.
+ * This will require a localized transform update for this geometry.
+ */
+ public void computeWorldMatrix() {
+ // Force a local update of the geometry's transform
+ checkDoTransformUpdate();
+
+ // Compute the cached world matrix
+ cachedWorldMat.loadIdentity();
+ cachedWorldMat.setRotationQuaternion(worldTransform.getRotation());
+ cachedWorldMat.setTranslation(worldTransform.getTranslation());
+
+ TempVars vars = TempVars.get();
+ Matrix4f scaleMat = vars.tempMat4;
+ scaleMat.loadIdentity();
+ scaleMat.scale(worldTransform.getScale());
+ cachedWorldMat.multLocal(scaleMat);
+ vars.release();
+ }
+
+ /**
+ * A {@link Matrix4f matrix} that transforms the {@link Geometry#getMesh() mesh}
+ * from model space to world space. This matrix is computed based on the
+ * {@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() {
+ return cachedWorldMat;
+ }
+
+ /**
+ * Sets the model bound to use for this geometry.
+ * 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
+ public void setModelBound(BoundingVolume modelBound) {
+ this.worldBound = null;
+ mesh.setBound(modelBound);
+ setBoundRefresh();
+
+ // NOTE: Calling updateModelBound() would cause the mesh
+ // to recompute the bound based on the geometry thus making
+ // this call useless!
+ //updateModelBound();
+ }
+
+ public int collideWith(Collidable other, CollisionResults results) {
+ // Force bound to update
+ checkDoBoundUpdate();
+ // Update transform, and compute cached world matrix
+ computeWorldMatrix();
+
+ assert (refreshFlags & (RF_BOUND | RF_TRANSFORM)) == 0;
+
+ if (mesh != null) {
+ // NOTE: BIHTree in mesh already checks collision with the
+ // mesh's bound
+ int prevSize = results.size();
+ int added = mesh.collideWith(other, cachedWorldMat, worldBound, results);
+ int newSize = results.size();
+ for (int i = prevSize; i < newSize; i++) {
+ results.getCollisionDirect(i).setGeometry(this);
+ }
+ return added;
+ }
+ return 0;
+ }
+
+ @Override
+ public void depthFirstTraversal(SceneGraphVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+ }
+
+ public boolean isBatched() {
+ return batchNode != null;
+ }
+
+ /**
+ * This version of clone is a shallow clone, in other words, the
+ * same mesh is referenced as the original geometry.
+ * Exception: if the mesh is marked as being a software
+ * animated mesh, (bind pose is set) then the positions
+ * and normals are deep copied.
+ */
+ @Override
+ public Geometry clone(boolean cloneMaterial) {
+ Geometry geomClone = (Geometry) super.clone(cloneMaterial);
+ geomClone.cachedWorldMat = cachedWorldMat.clone();
+ if (material != null) {
+ if (cloneMaterial) {
+ geomClone.material = material.clone();
+ } else {
+ geomClone.material = material;
+ }
+ }
+
+ if (mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) {
+ geomClone.mesh = mesh.cloneForAnim();
+ }
+
+ return geomClone;
+ }
+
+ /**
+ * This version of clone is a shallow clone, in other words, the
+ * same mesh is referenced as the original geometry.
+ * Exception: if the mesh is marked as being a software
+ * animated mesh, (bind pose is set) then the positions
+ * and normals are deep copied.
+ */
+ @Override
+ public Geometry clone() {
+ return clone(true);
+ }
+
+ /**
+ * Creates a deep clone of the geometry,
+ * this creates an identical copy of the mesh
+ * with the vertexbuffer data duplicated.
+ */
+ @Override
+ public Spatial deepClone() {
+ Geometry geomClone = clone(true);
+ geomClone.mesh = mesh.deepClone();
+ return geomClone;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(mesh, "mesh", null);
+ if (material != null) {
+ oc.write(material.getAssetName(), "materialName", null);
+ }
+ oc.write(material, "material", null);
+ oc.write(ignoreTransform, "ignoreTransform", false);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ mesh = (Mesh) ic.readSavable("mesh", null);
+
+ material = null;
+ String matName = ic.readString("materialName", null);
+ if (matName != null) {
+ // Material name is set,
+ // Attempt to load material via J3M
+ try {
+ material = im.getAssetManager().loadMaterial(matName);
+ } catch (AssetNotFoundException ex) {
+ // Cannot find J3M file.
+ logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key});
+ }
+ }
+ // If material is NULL, try to load it from the geometry
+ if (material == null) {
+ material = (Material) ic.readSavable("material", null);
+ }
+ ignoreTransform = ic.readBoolean("ignoreTransform", false);
+
+ if (ic.getSavableVersion(Geometry.class) == 0){
+ // Fix shared mesh (if set)
+ Mesh sharedMesh = getUserData(UserData.JME_SHAREDMESH);
+ if (sharedMesh != null){
+ getMesh().extractVertexData(sharedMesh);
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/LightNode.java b/engine/src/core/com/jme3/scene/LightNode.java
new file mode 100644
index 0000000..5e6dc76
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/LightNode.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.light.Light;
+import com.jme3.scene.control.LightControl;
+import com.jme3.scene.control.LightControl.ControlDirection;
+
+/**
+ * <code>LightNode</code> is used to link together a {@link Light} object
+ * with a {@link Node} object.
+ *
+ * @author Tim8Dev
+ */
+public class LightNode extends Node {
+
+ private LightControl lightControl;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public LightNode() {
+ }
+
+ public LightNode(String name, Light light) {
+ this(name, new LightControl(light));
+ }
+
+ public LightNode(String name, LightControl control) {
+ super(name);
+ addControl(control);
+ lightControl = control;
+ }
+
+ /**
+ * Enable or disable the <code>LightNode</code> functionality.
+ *
+ * @param enabled If false, the functionality of LightNode will
+ * be disabled.
+ */
+ public void setEnabled(boolean enabled) {
+ lightControl.setEnabled(enabled);
+ }
+
+ public boolean isEnabled() {
+ return lightControl.isEnabled();
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ lightControl.setControlDir(controlDir);
+ }
+
+ public void setLight(Light light) {
+ lightControl.setLight(light);
+ }
+
+ public ControlDirection getControlDir() {
+ return lightControl.getControlDir();
+ }
+
+ public Light getLight() {
+ return lightControl.getLight();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/Mesh.java b/engine/src/core/com/jme3/scene/Mesh.java
new file mode 100644
index 0000000..6c587f2
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Mesh.java
@@ -0,0 +1,1315 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.bih.BIHTree;
+import com.jme3.export.*;
+import com.jme3.material.RenderState;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <code>Mesh</code> is used to store rendering data.
+ * <p>
+ * All visible elements in a scene are represented by meshes.
+ * Meshes may contain three types of geometric primitives:
+ * <ul>
+ * <li>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.</li>
+ * <li>Lines - 2 vertices represent a line segment, with the width specified
+ * via {@link Mesh#setLineWidth(float) }.</li>
+ * <li>Triangles - 3 vertices represent a solid triangle primitive. </li>
+ * </ul>
+ *
+ * @author Kirill Vainer
+ */
+public class Mesh implements Savable, Cloneable {
+
+ /**
+ * The mode of the Mesh specifies both the type of primitive represented
+ * by the mesh and how the data should be interpreted.
+ */
+ public enum Mode {
+ /**
+ * 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 Mesh#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
+ * previous vertex to make a line. {@link Mesh#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.
+ * {@link Mesh#setLineWidth(float) } can be used
+ * 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
+ * specify a triangle, while subsequent vertices are combined with
+ * the previous two to form a triangle.
+ */
+ TriangleStrip(false),
+
+ /**
+ * 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
+ * be specified for this mode.
+ */
+ Hybrid(false);
+
+ 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
+ * 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(){
+ return listMode;
+ }
+ }
+
+ /**
+ * The bounding volume that contains the mesh entirely.
+ * By default a BoundingBox (AABB).
+ */
+ private BoundingVolume meshBound = new BoundingBox();
+
+ private CollisionData collisionTree = null;
+
+ private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+ private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
+ private VertexBuffer[] lodLevels;
+ private float pointSize = 1;
+ private float lineWidth = 1;
+
+ private transient int vertexArrayID = -1;
+
+ private int vertCount = -1;
+ private int elementCount = -1;
+ private int maxNumWeights = -1; // only if using skeletal animation
+
+ private int[] elementLengths;
+ private int[] modeStart;
+
+ private Mode mode = Mode.Triangles;
+
+ /**
+ * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
+ */
+ public Mesh(){
+ }
+
+ /**
+ * 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
+ public Mesh clone() {
+ try {
+ Mesh clone = (Mesh) super.clone();
+ clone.meshBound = meshBound.clone();
+ clone.collisionTree = collisionTree != null ? collisionTree : null;
+ clone.buffers = buffers.clone();
+ clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
+ clone.vertexArrayID = -1;
+ if (elementLengths != null) {
+ clone.elementLengths = elementLengths.clone();
+ }
+ if (modeStart != null) {
+ clone.modeStart = modeStart.clone();
+ }
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * 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(){
+ try{
+ Mesh clone = (Mesh) super.clone();
+ clone.meshBound = meshBound != null ? meshBound.clone() : null;
+
+ // TODO: Collision tree cloning
+ //clone.collisionTree = collisionTree != null ? collisionTree : null;
+ clone.collisionTree = null; // it will get re-generated in any case
+
+ clone.buffers = new IntMap<VertexBuffer>();
+ clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+ for (Entry<VertexBuffer> ent : buffers){
+ VertexBuffer bufClone = ent.getValue().clone();
+ clone.buffers.put(ent.getKey(), bufClone);
+ clone.buffersList.add(bufClone);
+ }
+
+ clone.vertexArrayID = -1;
+ clone.vertCount = -1;
+ clone.elementCount = -1;
+
+ // although this could change
+ // if the bone weight/index buffers are modified
+ clone.maxNumWeights = maxNumWeights;
+
+ clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
+ clone.modeStart = modeStart != null ? modeStart.clone() : null;
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Clone the mesh for animation use.
+ * This creates a shallow clone of the mesh, sharing most
+ * 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);
+ clone.setBuffer(newPos);
+
+ if (getBuffer(Type.BindPoseNormal) != null){
+ VertexBuffer oldNorm = getBuffer(Type.Normal);
+ 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();
+ clone.clearBuffer(Type.Tangent);
+ clone.setBuffer(newTang);
+ }
+ }
+ }
+ return clone;
+ }
+
+ /**
+ * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal},
+ * 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){
+ if (forSoftwareAnim){
+ VertexBuffer pos = getBuffer(Type.Position);
+ if (pos == null || getBuffer(Type.BoneIndex) == null) {
+ // ignore, this mesh doesn't have positional data
+ // or it doesn't have bone-vertex assignments, so its not animated
+ return;
+ }
+
+ VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
+ bindPos.setupData(Usage.CpuOnly,
+ 3,
+ Format.Float,
+ BufferUtils.clone(pos.getData()));
+ setBuffer(bindPos);
+
+ // XXX: note that this method also sets stream mode
+ // so that animation is faster. this is not needed for hardware skinning
+ pos.setUsage(Usage.Stream);
+
+ VertexBuffer norm = getBuffer(Type.Normal);
+ if (norm != null) {
+ VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
+ bindNorm.setupData(Usage.CpuOnly,
+ 3,
+ Format.Float,
+ BufferUtils.clone(norm.getData()));
+ setBuffer(bindNorm);
+ norm.setUsage(Usage.Stream);
+ }
+
+ VertexBuffer tangents = getBuffer(Type.Tangent);
+ if (tangents != null) {
+ VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
+ bindTangents.setupData(Usage.CpuOnly,
+ 4,
+ Format.Float,
+ BufferUtils.clone(tangents.getData()));
+ setBuffer(bindTangents);
+ tangents.setUsage(Usage.Stream);
+ }
+ }
+ }
+
+ /**
+ * Prepares the mesh for software skinning by converting the bone index
+ * and weight buffers to heap buffers.
+ *
+ * @param forSoftwareAnim Should be true to enable the conversion.
+ */
+ public void prepareForAnim(boolean forSoftwareAnim){
+ if (forSoftwareAnim){
+ // convert indices
+ VertexBuffer indices = getBuffer(Type.BoneIndex);
+ ByteBuffer originalIndex = (ByteBuffer) indices.getData();
+ ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
+ originalIndex.clear();
+ arrayIndex.put(originalIndex);
+ indices.updateData(arrayIndex);
+
+ // convert weights
+ VertexBuffer weights = getBuffer(Type.BoneWeight);
+ FloatBuffer originalWeight = (FloatBuffer) weights.getData();
+ FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
+ originalWeight.clear();
+ arrayWeight.put(originalWeight);
+ weights.updateData(arrayWeight);
+ }
+ }
+
+ /**
+ * Set the LOD (level of detail) index buffers on this mesh.
+ *
+ * @param lodLevels The LOD levels to set
+ */
+ public void setLodLevels(VertexBuffer[] lodLevels){
+ this.lodLevels = lodLevels;
+ }
+
+ /**
+ * @return The number of LOD levels set on this mesh, including the main
+ * index buffer, returns zero if there are no lod levels.
+ */
+ public int getNumLodLevels(){
+ return lodLevels != null ? lodLevels.length : 0;
+ }
+
+ /**
+ * 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
+ * range [0, {@link #getNumLodLevels()}].
+ *
+ * @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() {
+ return elementLengths;
+ }
+
+ /**
+ * Set the element lengths for {@link Mode#Hybrid} mesh mode.
+ *
+ * @param elementLengths The element lengths to set
+ */
+ public void setElementLengths(int[] elementLengths) {
+ this.elementLengths = elementLengths;
+ }
+
+ /**
+ * Set the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
+ public int[] getModeStart() {
+ return modeStart;
+ }
+
+ /**
+ * Get the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
+ public void setModeStart(int[] modeStart) {
+ this.modeStart = modeStart;
+ }
+
+ /**
+ * Returns the mesh mode
+ *
+ * @return the mesh mode
+ *
+ * @see #setMode(com.jme3.scene.Mesh.Mode)
+ */
+ public Mode getMode() {
+ return mode;
+ }
+
+ /**
+ * 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) {
+ this.mode = mode;
+ updateCounts();
+ }
+
+ /**
+ * Returns the maximum number of weights per vertex on this mesh.
+ *
+ * @return maximum number of weights per vertex
+ *
+ * @see #setMaxNumWeights(int)
+ */
+ public int getMaxNumWeights() {
+ return maxNumWeights;
+ }
+
+ /**
+ * 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
+ */
+ public void setMaxNumWeights(int maxNumWeights) {
+ this.maxNumWeights = maxNumWeights;
+ }
+
+ /**
+ * Returns the size of points for point meshes
+ *
+ * @return the size of points
+ *
+ * @see #setPointSize(float)
+ */
+ public float getPointSize() {
+ return pointSize;
+ }
+
+ /**
+ * 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
+ * point size by writing to <code>gl_PointSize</code>.
+ *
+ * @param pointSize The size of points
+ */
+ public void setPointSize(float pointSize) {
+ this.pointSize = pointSize;
+ }
+
+ /**
+ * Returns the line width for line meshes.
+ *
+ * @return the line width
+ */
+ public float getLineWidth() {
+ return lineWidth;
+ }
+
+ /**
+ * Specify the line width for meshes of the line modes, such
+ * as {@link Mode#Lines}. The line width is specified as on-screen pixels,
+ * the default value is 1.0.
+ *
+ * @param lineWidth The line width
+ */
+ public void setLineWidth(float lineWidth) {
+ this.lineWidth = lineWidth;
+ }
+
+ /**
+ * 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.
+ */
+ public void setStatic() {
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Static);
+ }
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will be modified occasionally (a hint).
+ * Sets the usage mode to {@link Usage#Dynamic}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setDynamic() {
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Dynamic);
+ }
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will be modified every frame (a hint).
+ * Sets the usage mode to {@link Usage#Stream}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setStreamed(){
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Stream);
+ }
+ }
+
+ /**
+ * Interleaves the data in this mesh. This operation cannot be reversed.
+ * Some GPUs may prefer the data in this format, however it is a good idea
+ * to <em>avoid</em> using this method as it disables some engine features.
+ */
+ public void setInterleaved(){
+ ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
+ for (Entry<VertexBuffer> entry : buffers){
+ vbs.add(entry.getValue());
+ }
+// ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
+ // index buffer not included when interleaving
+ vbs.remove(getBuffer(Type.Index));
+
+ int stride = 0; // aka bytes per vertex
+ for (int i = 0; i < vbs.size(); i++){
+ VertexBuffer vb = vbs.get(i);
+// if (vb.getFormat() != Format.Float){
+// throw new UnsupportedOperationException("Cannot interleave vertex buffer.\n" +
+// "Contains not-float data.");
+// }
+ stride += vb.componentsLength;
+ vb.getData().clear(); // reset position & limit (used later)
+ }
+
+ 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);
+
+ for (int vert = 0; vert < getVertexCount(); vert++){
+ for (int i = 0; i < vbs.size(); i++){
+ VertexBuffer vb = vbs.get(i);
+ switch (vb.getFormat()){
+ case Float:
+ FloatBuffer fb = (FloatBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putFloat(fb.get());
+ }
+ break;
+ case Byte:
+ case UnsignedByte:
+ ByteBuffer bb = (ByteBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.put(bb.get());
+ }
+ break;
+ case Half:
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sb = (ShortBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putShort(sb.get());
+ }
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer ib = (IntBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putInt(ib.get());
+ }
+ break;
+ case Double:
+ DoubleBuffer db = (DoubleBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putDouble(db.get());
+ }
+ break;
+ }
+ }
+ }
+
+ int offset = 0;
+ 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;
+ }
+ }
+
+ private int computeNumElements(int bufSize){
+ switch (mode){
+ case Triangles:
+ return bufSize / 3;
+ case TriangleFan:
+ case TriangleStrip:
+ return bufSize - 2;
+ case Points:
+ return bufSize;
+ case Lines:
+ return bufSize / 2;
+ case LineLoop:
+ return bufSize;
+ case LineStrip:
+ return bufSize - 1;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * 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
+ * {@link #setInterleaved() interleaved} format.
+ */
+ public void updateCounts(){
+ if (getBuffer(Type.InterleavedData) != null)
+ throw new IllegalStateException("Should update counts before interleave");
+
+ VertexBuffer pb = getBuffer(Type.Position);
+ VertexBuffer ib = getBuffer(Type.Index);
+ if (pb != null){
+ vertCount = pb.getData().capacity() / pb.getNumComponents();
+ }
+ if (ib != null){
+ elementCount = computeNumElements(ib.getData().capacity());
+ }else{
+ elementCount = computeNumElements(vertCount);
+ }
+ }
+
+ /**
+ * 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
+ */
+ public int getTriangleCount(int lod){
+ if (lodLevels != null){
+ if (lod < 0)
+ throw new IllegalArgumentException("LOD level cannot be < 0");
+
+ if (lod >= lodLevels.length)
+ throw new IllegalArgumentException("LOD level "+lod+" does not exist!");
+
+ return computeNumElements(lodLevels[lod].getData().capacity());
+ }else if (lod == 0){
+ return elementCount;
+ }else{
+ throw new IllegalArgumentException("There are no LOD levels on the mesh!");
+ }
+ }
+
+ /**
+ * 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
+ * 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(){
+ return elementCount;
+ }
+
+ /**
+ * Returns the number of vertices on this mesh.
+ * 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;
+ }
+
+ /**
+ * 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.
+ * 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
+ */
+ public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){
+ VertexBuffer pb = getBuffer(Type.Position);
+ IndexBuffer ib = getIndicesAsList();
+ if (pb != null && pb.getFormat() == Format.Float && pb.getNumComponents() == 3){
+ FloatBuffer fpb = (FloatBuffer) pb.getData();
+
+ // aquire triangle's vertex indices
+ int vertIndex = index * 3;
+ int vert1 = ib.get(vertIndex);
+ int vert2 = ib.get(vertIndex+1);
+ int vert3 = ib.get(vertIndex+2);
+
+ BufferUtils.populateFromBuffer(v1, fpb, vert1);
+ BufferUtils.populateFromBuffer(v2, fpb, vert2);
+ BufferUtils.populateFromBuffer(v3, fpb, vert3);
+ }else{
+ throw new UnsupportedOperationException("Position buffer not set or "
+ + " has incompatible format");
+ }
+ }
+
+ /**
+ * 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 <code>index</code> argument.
+ *
+ * @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){
+ getTriangle(index, tri.get1(), tri.get2(), tri.get3());
+ tri.setIndex(index);
+ tri.setNormal(null);
+ }
+
+ /**
+ * 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.
+ * Should be between 0 and {@link #getTriangleCount()}.
+ *
+ * @param indices Indices of the triangle's vertices
+ */
+ public void getTriangle(int index, int[] indices){
+ IndexBuffer ib = getIndicesAsList();
+
+ // acquire triangle's vertex indices
+ int vertIndex = index * 3;
+ indices[0] = ib.get(vertIndex);
+ indices[1] = ib.get(vertIndex+1);
+ indices[2] = ib.get(vertIndex+2);
+ }
+
+ /**
+ * Returns the mesh's VAO ID. Internal use only.
+ */
+ public int getId(){
+ return vertexArrayID;
+ }
+
+ /**
+ * Sets the mesh's VAO ID. Internal use only.
+ */
+ 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,
+ * com.jme3.collision.CollisionResults) }.
+ */
+ public void createCollisionData(){
+ BIHTree tree = new BIHTree(this);
+ tree.construct();
+ collisionTree = tree;
+ }
+
+ /**
+ * Handles collision detection, internal use only.
+ * User code should only use collideWith() on scene
+ * graph elements such as {@link Spatial}s.
+ */
+ public int collideWith(Collidable other,
+ Matrix4f worldMatrix,
+ BoundingVolume worldBound,
+ CollisionResults results){
+
+ if (collisionTree == null){
+ createCollisionData();
+ }
+
+ return collisionTree.collideWith(other, worldMatrix, worldBound, results);
+ }
+
+ /**
+ * 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) {
+// VertexBuffer vb = buffers.get(type);
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ if (buf == null)
+ return;
+
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+// buffers.put(type, vb);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ }else{
+ vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+ }
+ updateCounts();
+ }
+
+ public void setBuffer(Type type, int components, float[] buf){
+ setBuffer(type, components, BufferUtils.createFloatBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, IntBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedInt, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, int[] buf){
+ setBuffer(type, components, BufferUtils.createIntBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, ShortBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedShort, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, byte[] buf){
+ setBuffer(type, components, BufferUtils.createByteBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, ByteBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedByte, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(VertexBuffer vb){
+ if (buffers.containsKey(vb.getBufferType().ordinal()))
+ throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType());
+
+ buffers.put(vb.getBufferType().ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+
+ /**
+ * Clears or unsets the {@link VertexBuffer} set on this mesh
+ * with the given type.
+ * Does nothing if the vertex buffer type is not set initially
+ *
+ * @param type The type to remove
+ */
+ public void clearBuffer(VertexBuffer.Type type){
+ VertexBuffer vb = buffers.remove(type.ordinal());
+ if (vb != null){
+ buffersList.remove(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, short[] buf){
+ setBuffer(type, components, BufferUtils.createShortBuffer(buf));
+ }
+
+ /**
+ * 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
+ */
+ public VertexBuffer getBuffer(Type type){
+ return buffers.get(type.ordinal());
+ }
+
+ /**
+ * 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
+ */
+ public FloatBuffer getFloatBuffer(Type type) {
+ VertexBuffer vb = getBuffer(type);
+ if (vb == null)
+ return null;
+
+ 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
+ */
+ public ShortBuffer getShortBuffer(Type type) {
+ VertexBuffer vb = getBuffer(type);
+ if (vb == null)
+ return null;
+
+ return (ShortBuffer) vb.getData();
+ }
+
+ /**
+ * 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;
+ }else{
+ // not in list mode but it does have an index buffer
+ // wrap it so the data is converted to list format
+ return new WrappedIndexBuffer(this);
+ }
+ }else{
+ // return a virtual index buffer that will supply
+ // "fake" indices in list format
+ return new VirtualIndexBuffer(vertCount, mode);
+ }
+ }
+
+ /**
+ * Get the index buffer for this mesh.
+ * Will return <code>null</code> 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;
+
+ Buffer buf = vb.getData();
+ if (buf instanceof ByteBuffer) {
+ return new IndexByteBuffer((ByteBuffer) buf);
+ } else if (buf instanceof ShortBuffer) {
+ return new IndexShortBuffer((ShortBuffer) buf);
+ } else if (buf instanceof IntBuffer) {
+ return new IndexIntBuffer((IntBuffer) buf);
+ } else {
+ throw new UnsupportedOperationException("Index buffer type unsupported: "+ buf.getClass());
+ }
+ }
+
+ /**
+ * Extracts the vertex attributes from the given mesh into
+ * this mesh, by using this mesh's {@link #getIndexBuffer() index buffer}
+ * 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) {
+ // Determine the number of unique vertices need to
+ // be created. Also determine the mappings
+ // between old indices to new indices (since we avoid duplicating
+ // vertices, this is a map and not an array).
+ VertexBuffer oldIdxBuf = getBuffer(Type.Index);
+ IndexBuffer indexBuf = getIndexBuffer();
+ int numIndices = indexBuf.size();
+
+ IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices);
+ ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>();
+ int newIndex = 0;
+
+ for (int i = 0; i < numIndices; i++) {
+ int oldIndex = indexBuf.get(i);
+
+ if (!oldIndicesToNewIndices.containsKey(oldIndex)) {
+ // 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);
+
+ // increment to have the next index
+ newIndex++;
+ }
+ }
+
+ // Number of unique verts to be created now available
+ int newNumVerts = newIndicesToOldIndices.size();
+
+ if (newIndex != newNumVerts) {
+ throw new AssertionError();
+ }
+
+ // 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) {
+ newIndexBuf = new IndexIntBuffer(BufferUtils.createIntBuffer(numIndices));
+ } else {
+ newIndexBuf = new IndexShortBuffer(BufferUtils.createShortBuffer(numIndices));
+ }
+
+ for (int i = 0; i < numIndices; i++) {
+ // Map the old indices to the new indices
+ int oldIndex = indexBuf.get(i);
+ newIndex = oldIndicesToNewIndices.get(oldIndex);
+
+ newIndexBuf.put(i, newIndex);
+ }
+
+ VertexBuffer newIdxBuf = new VertexBuffer(Type.Index);
+ newIdxBuf.setupData(oldIdxBuf.getUsage(),
+ oldIdxBuf.getNumComponents(),
+ newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort,
+ newIndexBuf.getBuffer());
+ clearBuffer(Type.Index);
+ setBuffer(newIdxBuf);
+
+ // Now, create the vertex buffers
+ SafeArrayList<VertexBuffer> oldVertexData = other.getBufferList();
+ for (VertexBuffer oldVb : oldVertexData) {
+ if (oldVb.getBufferType() == VertexBuffer.Type.Index) {
+ // ignore the index buffer
+ continue;
+ }
+
+ // Create a new vertex buffer with similar configuration, but
+ // with the capacity of number of unique vertices
+ Buffer buffer = VertexBuffer.createBuffer(oldVb.getFormat(), oldVb.getNumComponents(), newNumVerts);
+
+ VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType());
+ newVb.setNormalized(oldVb.isNormalized());
+ newVb.setupData(oldVb.getUsage(), oldVb.getNumComponents(), oldVb.getFormat(), buffer);
+
+ // Copy the vertex data from the old buffer into the new buffer
+ for (int i = 0; i < newNumVerts; i++) {
+ int oldIndex = newIndicesToOldIndices.get(i);
+
+ // Copy the vertex attribute from the old index
+ // to the new index
+ 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.
+ * <p>
+ * Note that values above 1 will cause the
+ * texture to tile, while values below 1 will cause the texture
+ * to stretch.
+ * </p>
+ *
+ * @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
+ * buffer is not in 2D float format.
+ */
+ public void scaleTextureCoordinates(Vector2f scaleFactor){
+ VertexBuffer tc = getBuffer(Type.TexCoord);
+ if (tc == null)
+ throw new IllegalStateException("The mesh has no texture coordinates");
+
+ if (tc.getFormat() != VertexBuffer.Format.Float)
+ throw new UnsupportedOperationException("Only float texture coord format is supported");
+
+ if (tc.getNumComponents() != 2)
+ throw new UnsupportedOperationException("Only 2D texture coords are supported");
+
+ FloatBuffer fb = (FloatBuffer) tc.getData();
+ fb.clear();
+ for (int i = 0; i < fb.capacity() / 2; i++){
+ float x = fb.get();
+ float y = fb.get();
+ fb.position(fb.position()-2);
+ x *= scaleFactor.getX();
+ y *= scaleFactor.getY();
+ fb.put(x).put(y);
+ }
+ fb.clear();
+ tc.updateData(fb);
+ }
+
+ /**
+ * 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.
+ */
+ public void updateBound(){
+ VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position);
+ if (meshBound != null && posBuf != null){
+ meshBound.computeFromPoints((FloatBuffer)posBuf.getData());
+ }
+ }
+
+ /**
+ * 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() {
+ return meshBound;
+ }
+
+ /**
+ * 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) {
+ meshBound = modelBound;
+ }
+
+ /**
+ * 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,
+ * modifying it will cause undefined results.
+ *
+ * @return map of vertex buffers on this mesh.
+ */
+ public IntMap<VertexBuffer> 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<VertexBuffer> getBufferList(){
+ return buffersList;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule out = ex.getCapsule(this);
+
+// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
+// for (Entry<VertexBuffer> buf : buffers){
+// if (buf.getValue() != null)
+// map.put(buf.getKey()+"a", buf.getValue());
+// }
+// out.writeStringSavableMap(map, "buffers", null);
+
+ out.write(meshBound, "modelBound", null);
+ out.write(vertCount, "vertCount", -1);
+ out.write(elementCount, "elementCount", -1);
+ out.write(maxNumWeights, "max_num_weights", -1);
+ out.write(mode, "mode", Mode.Triangles);
+ out.write(collisionTree, "collisionTree", null);
+ out.write(elementLengths, "elementLengths", null);
+ out.write(modeStart, "modeStart", null);
+ out.write(pointSize, "pointSize", 1f);
+
+ out.writeIntSavableMap(buffers, "buffers", null);
+ out.write(lodLevels, "lodLevels", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ meshBound = (BoundingVolume) in.readSavable("modelBound", null);
+ vertCount = in.readInt("vertCount", -1);
+ elementCount = in.readInt("elementCount", -1);
+ maxNumWeights = in.readInt("max_num_weights", -1);
+ mode = in.readEnum("mode", Mode.class, Mode.Triangles);
+ elementLengths = in.readIntArray("elementLengths", null);
+ modeStart = in.readIntArray("modeStart", null);
+ collisionTree = (BIHTree) in.readSavable("collisionTree", null);
+ elementLengths = in.readIntArray("elementLengths", null);
+ modeStart = in.readIntArray("modeStart", null);
+ pointSize = in.readFloat("pointSize", 1f);
+
+// in.readStringSavableMap("buffers", null);
+ buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
+ for (Entry<VertexBuffer> entry : buffers){
+ buffersList.add(entry.getValue());
+ }
+
+ Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null);
+ if (lodLevelsSavable != null) {
+ lodLevels = new VertexBuffer[lodLevelsSavable.length];
+ System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/Node.java b/engine/src/core/com/jme3/scene/Node.java
new file mode 100644
index 0000000..bb7534a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Node.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import com.jme3.material.Material;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * <code>Node</code> defines an internal node of a scene graph. The internal
+ * 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
+ */
+public class Node extends Spatial implements Savable {
+
+ private static final Logger logger = Logger.getLogger(Node.class.getName());
+
+
+ /**
+ * This node's children.
+ */
+ protected SafeArrayList<Spatial> children = new SafeArrayList<Spatial>(Spatial.class);
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Node() {
+ }
+
+ /**
+ * Constructor instantiates a new <code>Node</code> with a default empty
+ * list for containing children.
+ *
+ * @param name
+ * the name of the scene element. This is required for
+ * identification and comparision purposes.
+ */
+ public Node(String name) {
+ super(name);
+ }
+
+ /**
+ *
+ * <code>getQuantity</code> returns the number of children this node
+ * maintains.
+ *
+ * @return the number of children this node maintains.
+ */
+ public int getQuantity() {
+ return children.size();
+ }
+
+ @Override
+ protected void setTransformRefresh(){
+ super.setTransformRefresh();
+ for (Spatial child : children.getArray()){
+ if ((child.refreshFlags & RF_TRANSFORM) != 0)
+ continue;
+
+ child.setTransformRefresh();
+ }
+ }
+
+ @Override
+ protected void setLightListRefresh(){
+ super.setLightListRefresh();
+ for (Spatial child : children.getArray()){
+ if ((child.refreshFlags & RF_LIGHTLIST) != 0)
+ continue;
+
+ child.setLightListRefresh();
+ }
+ }
+
+ @Override
+ protected void updateWorldBound(){
+ super.updateWorldBound();
+
+ // for a node, the world bound is a combination of all it's children
+ // bounds
+ BoundingVolume resultBound = null;
+ for (Spatial child : children.getArray()) {
+ // child bound is assumed to be updated
+ assert (child.refreshFlags & RF_BOUND) == 0;
+ if (resultBound != null) {
+ // merge current world bound with child world bound
+ resultBound.mergeLocal(child.getWorldBound());
+ } else {
+ // set world bound to first non-null child world bound
+ if (child.getWorldBound() != null) {
+ resultBound = child.getWorldBound().clone(this.worldBound);
+ }
+ }
+ }
+ this.worldBound = resultBound;
+ }
+
+ @Override
+ public void updateLogicalState(float tpf){
+ super.updateLogicalState(tpf);
+
+ if (children.isEmpty()) {
+ return;
+ }
+
+ for (Spatial child : children.getArray()) {
+ child.updateLogicalState(tpf);
+ }
+ }
+
+ @Override
+ public void updateGeometricState(){
+ if ((refreshFlags & RF_LIGHTLIST) != 0){
+ updateWorldLightList();
+ }
+
+ if ((refreshFlags & RF_TRANSFORM) != 0){
+ // combine with parent transforms- same for all spatial
+ // subclasses.
+ updateWorldTransforms();
+ }
+
+ if (!children.isEmpty()) {
+ // the important part- make sure child geometric state is refreshed
+ // first before updating own world bound. This saves
+ // a round-trip later on.
+ // NOTE 9/19/09
+ // Although it does save a round trip,
+ for (Spatial child : children.getArray()) {
+ child.updateGeometricState();
+ }
+ }
+
+ if ((refreshFlags & RF_BOUND) != 0){
+ updateWorldBound();
+ }
+
+ assert refreshFlags == 0;
+ }
+
+ /**
+ * <code>getTriangleCount</code> returns the number of triangles contained
+ * in all sub-branches of this node that contain geometry.
+ *
+ * @return the triangle count of this branch.
+ */
+ @Override
+ public int getTriangleCount() {
+ int count = 0;
+ if(children != null) {
+ for(int i = 0; i < children.size(); i++) {
+ count += children.get(i).getTriangleCount();
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * <code>getVertexCount</code> returns the number of vertices contained
+ * in all sub-branches of this node that contain geometry.
+ *
+ * @return the vertex count of this branch.
+ */
+ @Override
+ public int getVertexCount() {
+ int count = 0;
+ if(children != null) {
+ for(int i = 0; i < children.size(); i++) {
+ count += children.get(i).getVertexCount();
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * <code>attachChild</code> attaches a child to this node. This node
+ * becomes the child's parent. The current number of children maintained is
+ * returned.
+ * <br>
+ * 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.
+ * @throws NullPointerException If child is null.
+ */
+ public int attachChild(Spatial child) {
+ if (child == null)
+ throw new NullPointerException();
+
+ if (child.getParent() != this && child != this) {
+ if (child.getParent() != null) {
+ child.getParent().detachChild(child);
+ }
+ child.setParent(this);
+ children.add(child);
+
+ // XXX: Not entirely correct? Forces bound update up the
+ // tree stemming from the attached child. Also forces
+ // transform update down the tree-
+ child.setTransformRefresh();
+ child.setLightListRefresh();
+ if (logger.isLoggable(Level.INFO)) {
+ logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
+ new Object[]{child.getName(), getName()});
+ }
+ }
+
+ return children.size();
+ }
+
+ /**
+ *
+ * <code>attachChildAt</code> attaches a child to this node at an index. This node
+ * becomes the child's parent. The current number of children maintained is
+ * returned.
+ * <br>
+ * 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.
+ * @throws NullPointerException if child is null.
+ */
+ public int attachChildAt(Spatial child, int index) {
+ if (child == null)
+ throw new NullPointerException();
+
+ if (child.getParent() != this && child != this) {
+ if (child.getParent() != null) {
+ child.getParent().detachChild(child);
+ }
+ child.setParent(this);
+ children.add(index, child);
+ child.setTransformRefresh();
+ child.setLightListRefresh();
+ if (logger.isLoggable(Level.INFO)) {
+ logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
+ new Object[]{child.getName(), getName()});
+ }
+ }
+
+ return children.size();
+ }
+
+ /**
+ * <code>detachChild</code> 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.
+ */
+ public int detachChild(Spatial child) {
+ if (child == null)
+ throw new NullPointerException();
+
+ if (child.getParent() == this) {
+ int index = children.indexOf(child);
+ if (index != -1) {
+ detachChildAt(index);
+ }
+ return index;
+ }
+
+ return -1;
+ }
+
+ /**
+ * <code>detachChild</code> 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.
+ */
+ public int detachChildNamed(String childName) {
+ if (childName == null)
+ throw new NullPointerException();
+
+ for (int x = 0, max = children.size(); x < max; x++) {
+ Spatial child = children.get(x);
+ if (childName.equals(child.getName())) {
+ detachChildAt( x );
+ return x;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ *
+ * <code>detachChildAt</code> 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.
+ */
+ public Spatial detachChildAt(int index) {
+ Spatial child = children.remove(index);
+ if ( child != null ) {
+ child.setParent( null );
+ logger.log(Level.INFO, "{0}: Child removed.", this.toString());
+
+ // since a child with a bound was detached;
+ // our own bound will probably change.
+ setBoundRefresh();
+
+ // our world transform no longer influences the child.
+ // XXX: Not neccessary? Since child will have transform updated
+ // when attached anyway.
+ child.setTransformRefresh();
+ // lights are also inherited from parent
+ child.setLightListRefresh();
+ }
+ return child;
+ }
+
+ /**
+ *
+ * <code>detachAllChildren</code> removes all children attached to this
+ * node.
+ */
+ public void detachAllChildren() {
+ for ( int i = children.size() - 1; i >= 0; i-- ) {
+ detachChildAt(i);
+ }
+ logger.log(Level.INFO, "{0}: All children removed.", this.toString());
+ }
+
+ /**
+ * <code>getChildIndex</code> returns the index of the given spatial
+ * in this node's list of children.
+ * @param sp
+ * The spatial to look up
+ * @return
+ * The index of the spatial in the node's children, or -1
+ * if the spatial is not attached to this node
+ */
+ public int getChildIndex(Spatial sp) {
+ return children.indexOf(sp);
+ }
+
+ /**
+ * 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
+ */
+ public void swapChildren(int index1, int index2) {
+ Spatial c2 = children.get(index2);
+ Spatial c1 = children.remove(index1);
+ children.add(index1, c2);
+ children.remove(index2);
+ children.add(index2, c1);
+ }
+
+ /**
+ *
+ * <code>getChild</code> returns a child at a given index.
+ *
+ * @param i
+ * the index to retrieve the child from.
+ * @return the child at a specified index.
+ */
+ public Spatial getChild(int i) {
+ return children.get(i);
+ }
+
+ /**
+ * <code>getChild</code> returns the first child found with exactly the
+ * given name (case sensitive.)
+ *
+ * @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)
+ return null;
+
+ for (Spatial child : children.getArray()) {
+ if (name.equals(child.getName())) {
+ return child;
+ } else if(child instanceof Node) {
+ Spatial out = ((Node)child).getChild(name);
+ if(out != null) {
+ return out;
+ }
+ }
+ }
+ 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.
+ */
+ public boolean hasChild(Spatial spat) {
+ if (children.contains(spat))
+ return true;
+
+ for (Spatial child : children.getArray()) {
+ if (child instanceof Node && ((Node) child).hasChild(spat))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns all children to this node. Note that modifying that given
+ * list is not allowed.
+ *
+ * @return a list containing all children to this node
+ */
+ public List<Spatial> getChildren() {
+ return children;
+ }
+
+ @Override
+ public void setMaterial(Material mat){
+ for (int i = 0; i < children.size(); i++){
+ children.get(i).setMaterial(mat);
+ }
+ }
+
+ @Override
+ public void setLodLevel(int lod){
+ super.setLodLevel(lod);
+ for (Spatial child : children.getArray()) {
+ child.setLodLevel(lod);
+ }
+ }
+
+ public int collideWith(Collidable other, CollisionResults results){
+ int total = 0;
+ for (Spatial child : children.getArray()){
+ total += child.collideWith(other, results);
+ }
+ return total;
+ }
+
+
+ /**
+ * Returns flat list of Spatials implementing the specified class AND
+ * with name matching the specified pattern.
+ * </P> <P>
+ * Note that we are <i>matching</i> the pattern, therefore the pattern
+ * must match the entire pattern (i.e. it behaves as if it is sandwiched
+ * between "^" and "$").
+ * You can set regex modes, like case insensitivity, by using the (?X)
+ * or (?X:Y) constructs.
+ * </P> <P>
+ * By design, it is always safe to code loops like:<CODE><PRE>
+ * for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
+ * </PRE></CODE>
+ * </P> <P>
+ * "Descendants" does not include self, per the definition of the word.
+ * To test for descendants AND self, you must do a
+ * <code>node.matches(aClass, aRegex)</code> +
+ * <code>node.descendantMatches(aClass, aRegex)</code>.
+ * <P>
+ *
+ * @param spatialSubclass Subclass which matching Spatials must implement.
+ * Null causes all Spatials to qualify.
+ * @param nameRegex Regular expression to match Spatial name against.
+ * Null causes all Names to qualify.
+ * @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)
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends Spatial>List<T> descendantMatches(
+ Class<T> spatialSubclass, String nameRegex) {
+ List<T> newList = new ArrayList<T>();
+ if (getQuantity() < 1) return newList;
+ for (Spatial child : getChildren()) {
+ if (child.matches(spatialSubclass, nameRegex))
+ newList.add((T)child);
+ if (child instanceof Node)
+ newList.addAll(((Node) child).descendantMatches(
+ spatialSubclass, nameRegex));
+ }
+ return newList;
+ }
+
+ /**
+ * Convenience wrapper.
+ *
+ * @see #descendantMatches(java.lang.Class, java.lang.String)
+ */
+ public <T extends Spatial>List<T> descendantMatches(
+ Class<T> spatialSubclass) {
+ return descendantMatches(spatialSubclass, null);
+ }
+
+ /**
+ * Convenience wrapper.
+ *
+ * @see #descendantMatches(java.lang.Class, java.lang.String)
+ */
+ public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
+ return descendantMatches(null, nameRegex);
+ }
+
+ @Override
+ public Node clone(boolean cloneMaterials){
+ Node nodeClone = (Node) super.clone(cloneMaterials);
+// nodeClone.children = new ArrayList<Spatial>();
+// for (Spatial child : children){
+// Spatial childClone = child.clone();
+// childClone.parent = nodeClone;
+// nodeClone.children.add(childClone);
+// }
+ return nodeClone;
+ }
+
+ @Override
+ public Spatial deepClone(){
+ Node nodeClone = (Node) super.clone();
+ nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
+ for (Spatial child : children){
+ Spatial childClone = child.deepClone();
+ childClone.parent = nodeClone;
+ nodeClone.children.add(childClone);
+ }
+ return nodeClone;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ e.getCapsule(this).writeSavableArrayList(new ArrayList(children), "children", null);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ // XXX: Load children before loading itself!!
+ // This prevents empty children list if controls query
+ // it in Control.setSpatial().
+
+ children = new SafeArrayList( Spatial.class,
+ e.getCapsule(this).readSavableArrayList("children", null) );
+
+ // go through children and set parent to this node
+ if (children != null) {
+ for (Spatial child : children.getArray()) {
+ child.parent = this;
+ }
+ }
+
+ super.read(e);
+ }
+
+ @Override
+ public void setModelBound(BoundingVolume modelBound) {
+ if(children != null) {
+ for (Spatial child : children.getArray()) {
+ child.setModelBound(modelBound != null ? modelBound.clone(null) : null);
+ }
+ }
+ }
+
+ @Override
+ public void updateModelBound() {
+ if(children != null) {
+ for (Spatial child : children.getArray()) {
+ child.updateModelBound();
+ }
+ }
+ }
+
+ @Override
+ public void depthFirstTraversal(SceneGraphVisitor visitor) {
+ for (Spatial child : children.getArray()) {
+ child.depthFirstTraversal(visitor);
+ }
+ visitor.visit(this);
+ }
+
+ @Override
+ protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+ queue.addAll(children);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/SceneGraphVisitor.java b/engine/src/core/com/jme3/scene/SceneGraphVisitor.java
new file mode 100644
index 0000000..35cc115
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SceneGraphVisitor.java
@@ -0,0 +1,16 @@
+package com.jme3.scene;
+
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree.
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+public interface SceneGraphVisitor {
+ /**
+ * Called when a spatial is visited in the scene graph.
+ *
+ * @param spatial The visited spatial
+ */
+ public void visit(Spatial spatial);
+}
diff --git a/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java b/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java
new file mode 100644
index 0000000..b91a8fb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java
@@ -0,0 +1,35 @@
+package com.jme3.scene;
+
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree. The adapter version of the interface simply separates
+ * between the {@link Geometry geometries} and the {@link Node nodes} by
+ * supplying visit methods that take them.
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+public class SceneGraphVisitorAdapter implements SceneGraphVisitor {
+
+ /**
+ * Called when a {@link Geometry} is visited.
+ *
+ * @param geom The visited geometry
+ */
+ public void visit(Geometry geom) {}
+
+ /**
+ * Called when a {@link visit} is visited.
+ *
+ * @param geom The visited node
+ */
+ public void visit(Node geom) {}
+
+ @Override
+ public final void visit(Spatial spatial) {
+ if (spatial instanceof Geometry) {
+ visit((Geometry)spatial);
+ } else {
+ visit((Node)spatial);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/SimpleBatchNode.java b/engine/src/core/com/jme3/scene/SimpleBatchNode.java
new file mode 100644
index 0000000..0f1ed29
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SimpleBatchNode.java
@@ -0,0 +1,56 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.scene;
+
+import com.jme3.math.Transform;
+
+/**
+ *
+ * SimpleBatchNode comes with some restrictions, but can yield better performances.
+ * Geometries to be batched has to be attached directly to the BatchNode
+ * You can't attach a Node to a SimpleBatchNode
+ * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure.
+ * @see BatchNode
+ * @author Nehon
+ */
+public class SimpleBatchNode extends BatchNode {
+
+ public SimpleBatchNode() {
+ super();
+ }
+
+ public SimpleBatchNode(String name) {
+ super(name);
+ }
+
+ @Override
+ public int attachChild(Spatial child) {
+
+ if (!(child instanceof Geometry)) {
+ throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure");
+ }
+
+ return super.attachChild(child);
+ }
+
+ @Override
+ protected void setTransformRefresh() {
+
+ refreshFlags |= RF_TRANSFORM;
+ setBoundRefresh();
+ for (Batch batch : batches.values()) {
+ batch.geometry.setTransformRefresh();
+ }
+ }
+
+ protected Transform getTransforms(Geometry geom){
+ return geom.getLocalTransform();
+ }
+
+ @Override
+ public void batch() {
+ doBatch();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/Spatial.java b/engine/src/core/com/jme3/scene/Spatial.java
new file mode 100644
index 0000000..3785d6e
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Spatial.java
@@ -0,0 +1,1478 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.export.*;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+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.SafeArrayList;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * <code>Spatial</code> defines the base class for scene graph nodes. It
+ * maintains a link to a parent, it's local transforms and the world's
+ * transforms. All other scene graph elements, such as {@link Node} and
+ * {@link Geometry} are subclasses of <code>Spatial</code>.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @version $Revision: 4075 $, $Data$
+ */
+public abstract class Spatial implements Savable, Cloneable, Collidable, Asset {
+
+ private static final Logger logger = Logger.getLogger(Spatial.class.getName());
+
+ /**
+ * 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,
+ /**
+ * Do not draw if we are not at least partially within the view frustum
+ * of the camera. This is determined via the defined
+ * 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.
+ * Note that we will still get culled if our parent is culled.
+ */
+ Never;
+ }
+
+ /**
+ * Specifies if this spatial should be batched
+ */
+ 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;
+ }
+ /**
+ * Refresh flag types
+ */
+ protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
+ RF_BOUND = 0x02,
+ RF_LIGHTLIST = 0x04; // changes in light lists
+ protected CullHint cullHint = CullHint.Inherit;
+ protected BatchHint batchHint = BatchHint.Inherit;
+ /**
+ * Spatial's bounding volume relative to the world.
+ */
+ protected BoundingVolume worldBound;
+ /**
+ * LightList
+ */
+ protected LightList localLights;
+ protected transient LightList worldLights;
+ /**
+ * This spatial's name.
+ */
+ protected String name;
+ // scale values
+ protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects;
+ protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit;
+ protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit;
+ public transient float queueDistance = Float.NEGATIVE_INFINITY;
+ protected Transform localTransform;
+ protected Transform worldTransform;
+ protected SafeArrayList<Control> controls = new SafeArrayList<Control>(Control.class);
+ protected HashMap<String, Savable> userData = null;
+ /**
+ * Used for smart asset caching
+ *
+ * @see AssetKey#useSmartCache()
+ */
+ protected AssetKey key;
+ /**
+ * Spatial's parent, or null if it has none.
+ */
+ protected transient Node parent;
+ /**
+ * Refresh flags. Indicate what data of the spatial need to be
+ * updated to reflect the correct state.
+ */
+ protected transient int refreshFlags = 0;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Spatial() {
+ localTransform = new Transform();
+ worldTransform = new Transform();
+
+ localLights = new LightList(this);
+ worldLights = new LightList(this);
+
+ refreshFlags |= RF_BOUND;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Spatial</code> object setting the
+ * rotation, translation and scale value to defaults.
+ *
+ * @param name
+ * the name of the scene element. This is required for
+ * identification and comparison purposes.
+ */
+ public Spatial(String name) {
+ this();
+ this.name = name;
+ }
+
+ public void setKey(AssetKey key) {
+ this.key = key;
+ }
+
+ public AssetKey getKey() {
+ return key;
+ }
+
+ /**
+ * Indicate that the transform of this spatial has changed and that
+ * a refresh is required.
+ */
+ protected void setTransformRefresh() {
+ refreshFlags |= RF_TRANSFORM;
+ setBoundRefresh();
+ }
+
+ protected void setLightListRefresh() {
+ refreshFlags |= RF_LIGHTLIST;
+ }
+
+ /**
+ * Indicate that the bounding of this spatial has changed and that
+ * a refresh is required.
+ */
+ protected void setBoundRefresh() {
+ refreshFlags |= RF_BOUND;
+
+ // XXX: Replace with a recursive call?
+ Spatial p = parent;
+ while (p != null) {
+ if ((p.refreshFlags & RF_BOUND) != 0) {
+ return;
+ }
+
+ p.refreshFlags |= RF_BOUND;
+ p = p.parent;
+ }
+ }
+
+ /**
+ * <code>checkCulling</code> checks the spatial with the camera to see if it
+ * should be culled.
+ * <p>
+ * This method is called by the renderer. Usually it should not be called
+ * directly.
+ *
+ * @param cam The camera to check against.
+ * @return true if inside or intersecting camera frustum
+ * (should be rendered), false if outside.
+ */
+ public boolean checkCulling(Camera cam) {
+ if (refreshFlags != 0) {
+ throw new IllegalStateException("Scene graph is not properly updated for rendering.\n"
+ + "State was changed after rootNode.updateGeometricState() call. \n"
+ + "Make sure you do not modify the scene from another thread!\n"
+ + "Problem spatial name: " + getName());
+ }
+
+ CullHint cm = getCullHint();
+ assert cm != CullHint.Inherit;
+ if (cm == Spatial.CullHint.Always) {
+ setLastFrustumIntersection(Camera.FrustumIntersect.Outside);
+ return false;
+ } else if (cm == Spatial.CullHint.Never) {
+ setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
+ return true;
+ }
+
+ // check to see if we can cull this node
+ frustrumIntersects = (parent != null ? parent.frustrumIntersects
+ : Camera.FrustumIntersect.Intersects);
+
+ if (frustrumIntersects == Camera.FrustumIntersect.Intersects) {
+ if (getQueueBucket() == Bucket.Gui) {
+ return cam.containsGui(getWorldBound());
+ } else {
+ frustrumIntersects = cam.contains(getWorldBound());
+ }
+ }
+
+ return frustrumIntersects != Camera.FrustumIntersect.Outside;
+ }
+
+ /**
+ * Sets the name of this spatial.
+ *
+ * @param name
+ * The spatial's new name.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the name of this spatial.
+ *
+ * @return This spatial's name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the local {@link LightList}, which are the lights
+ * that were directly attached to this <code>Spatial</code> through the
+ * {@link #addLight(com.jme3.light.Light) } and
+ * {@link #removeLight(com.jme3.light.Light) } methods.
+ *
+ * @return The local light list
+ */
+ public LightList getLocalLightList() {
+ return localLights;
+ }
+
+ /**
+ * Returns the world {@link LightList}, containing the lights
+ * combined from all this <code>Spatial's</code> parents up to and including
+ * this <code>Spatial</code>'s lights.
+ *
+ * @return The combined world light list
+ */
+ public LightList getWorldLightList() {
+ return worldLights;
+ }
+
+ /**
+ * <code>getWorldRotation</code> retrieves the absolute rotation of the
+ * Spatial.
+ *
+ * @return the Spatial's world rotation quaternion.
+ */
+ public Quaternion getWorldRotation() {
+ checkDoTransformUpdate();
+ return worldTransform.getRotation();
+ }
+
+ /**
+ * <code>getWorldTranslation</code> retrieves the absolute translation of
+ * the spatial.
+ *
+ * @return the Spatial's world tranlsation vector.
+ */
+ public Vector3f getWorldTranslation() {
+ checkDoTransformUpdate();
+ return worldTransform.getTranslation();
+ }
+
+ /**
+ * <code>getWorldScale</code> retrieves the absolute scale factor of the
+ * spatial.
+ *
+ * @return the Spatial's world scale factor.
+ */
+ public Vector3f getWorldScale() {
+ checkDoTransformUpdate();
+ return worldTransform.getScale();
+ }
+
+ /**
+ * <code>getWorldTransform</code> retrieves the world transformation
+ * of the spatial.
+ *
+ * @return the world transform.
+ */
+ public Transform getWorldTransform() {
+ checkDoTransformUpdate();
+ return worldTransform;
+ }
+
+ /**
+ * <code>rotateUpTo</code> is a utility function that alters the
+ * local rotation to point the Y axis in the direction given by newUp.
+ *
+ * @param newUp
+ * the up vector to use - assumed to be a unit vector.
+ */
+ public void rotateUpTo(Vector3f newUp) {
+ TempVars vars = TempVars.get();
+
+ Vector3f compVecA = vars.vect1;
+ Quaternion q = vars.quat1;
+
+ // First figure out the current up vector.
+ Vector3f upY = compVecA.set(Vector3f.UNIT_Y);
+ Quaternion rot = localTransform.getRotation();
+ rot.multLocal(upY);
+
+ // get angle between vectors
+ float angle = upY.angleBetween(newUp);
+
+ // figure out rotation axis by taking cross product
+ Vector3f rotAxis = upY.crossLocal(newUp).normalizeLocal();
+
+ // Build a rotation quat and apply current local rotation.
+ q.fromAngleNormalAxis(angle, rotAxis);
+ q.mult(rot, rot);
+
+ vars.release();
+
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>lookAt</code> is a convenience method for auto-setting the local
+ * rotation based on a position 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) }
+ * this method takes a world position to look at and not a relative direction.
+ *
+ * @param position
+ * where to look at in terms of world coordinates
+ * @param upVector
+ * a vector indicating the (local) up direction. (typically {0,
+ * 1, 0} in jME.)
+ */
+ public void lookAt(Vector3f position, Vector3f upVector) {
+ Vector3f worldTranslation = getWorldTranslation();
+
+ TempVars vars = TempVars.get();
+
+ Vector3f compVecA = vars.vect4;
+ vars.release();
+
+ compVecA.set(position).subtractLocal(worldTranslation);
+ getLocalRotation().lookAt(compVecA, upVector);
+
+ setTransformRefresh();
+ }
+
+ /**
+ * Should be overridden by Node and Geometry.
+ */
+ protected void updateWorldBound() {
+ // the world bound of a leaf is the same as it's model bound
+ // for a node, the world bound is a combination of all it's children
+ // bounds
+ // -> handled by subclass
+ refreshFlags &= ~RF_BOUND;
+ }
+
+ protected void updateWorldLightList() {
+ if (parent == null) {
+ worldLights.update(localLights, null);
+ refreshFlags &= ~RF_LIGHTLIST;
+ } else {
+ if ((parent.refreshFlags & RF_LIGHTLIST) == 0) {
+ worldLights.update(localLights, parent.worldLights);
+ refreshFlags &= ~RF_LIGHTLIST;
+ } else {
+ assert false;
+ }
+ }
+ }
+
+ /**
+ * Should only be called from updateGeometricState().
+ * In most cases should not be subclassed.
+ */
+ protected void updateWorldTransforms() {
+ if (parent == null) {
+ worldTransform.set(localTransform);
+ refreshFlags &= ~RF_TRANSFORM;
+ } else {
+ // check if transform for parent is updated
+ assert ((parent.refreshFlags & RF_TRANSFORM) == 0);
+ worldTransform.set(localTransform);
+ worldTransform.combineWithParent(parent.worldTransform);
+ refreshFlags &= ~RF_TRANSFORM;
+ }
+ }
+
+ /**
+ * Computes the world transform of this Spatial in the most
+ * efficient manner possible.
+ */
+ void checkDoTransformUpdate() {
+ if ((refreshFlags & RF_TRANSFORM) == 0) {
+ return;
+ }
+
+ if (parent == null) {
+ worldTransform.set(localTransform);
+ refreshFlags &= ~RF_TRANSFORM;
+ } else {
+ TempVars vars = TempVars.get();
+
+ Spatial[] stack = vars.spatialStack;
+ Spatial rootNode = this;
+ int i = 0;
+ while (true) {
+ Spatial hisParent = rootNode.parent;
+ if (hisParent == null) {
+ rootNode.worldTransform.set(rootNode.localTransform);
+ rootNode.refreshFlags &= ~RF_TRANSFORM;
+ i--;
+ break;
+ }
+
+ stack[i] = rootNode;
+
+ if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) {
+ break;
+ }
+
+ rootNode = hisParent;
+ i++;
+ }
+
+ vars.release();
+
+ for (int j = i; j >= 0; j--) {
+ rootNode = stack[j];
+ //rootNode.worldTransform.set(rootNode.localTransform);
+ //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform);
+ //rootNode.refreshFlags &= ~RF_TRANSFORM;
+ rootNode.updateWorldTransforms();
+ }
+ }
+ }
+
+ /**
+ * Computes this Spatial's world bounding volume in the most efficient
+ * manner possible.
+ */
+ void checkDoBoundUpdate() {
+ if ((refreshFlags & RF_BOUND) == 0) {
+ return;
+ }
+
+ checkDoTransformUpdate();
+
+ // Go to children recursively and update their bound
+ if (this instanceof Node) {
+ Node node = (Node) this;
+ int len = node.getQuantity();
+ for (int i = 0; i < len; i++) {
+ Spatial child = node.getChild(i);
+ child.checkDoBoundUpdate();
+ }
+ }
+
+ // All children's bounds have been updated. Update my own now.
+ updateWorldBound();
+ }
+
+ private void runControlUpdate(float tpf) {
+ if (controls.isEmpty()) {
+ return;
+ }
+
+ for (Control c : controls.getArray()) {
+ c.update(tpf);
+ }
+ }
+
+ /**
+ * Called when the Spatial is about to be rendered, to notify
+ * controls attached to this Spatial using the Control.render() method.
+ *
+ * @param rm The RenderManager rendering the Spatial.
+ * @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)
+ */
+ public void runControlRender(RenderManager rm, ViewPort vp) {
+ if (controls.isEmpty()) {
+ return;
+ }
+
+ for (Control c : controls.getArray()) {
+ c.render(rm, vp);
+ }
+ }
+
+ /**
+ * Add a control to the list of controls.
+ * @param control The control to add.
+ *
+ * @see Spatial#removeControl(java.lang.Class)
+ */
+ public void addControl(Control control) {
+ controls.add(control);
+ control.setSpatial(this);
+ }
+
+ /**
+ * Removes the first control that is an instance of the given class.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public void removeControl(Class<? extends Control> controlType) {
+ for (int i = 0; i < controls.size(); i++) {
+ if (controlType.isAssignableFrom(controls.get(i).getClass())) {
+ Control control = controls.remove(i);
+ control.setSpatial(null);
+ }
+ }
+ }
+
+ /**
+ * Removes the given control from this spatial's controls.
+ *
+ * @param control The control to remove
+ * @return True if the control was successfuly removed. False if
+ * the control is not assigned to this spatial.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public boolean removeControl(Control control) {
+ boolean result = controls.remove(control);
+ if (result) {
+ control.setSpatial(null);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the first control that is an instance of the given class,
+ * or null if no such control exists.
+ *
+ * @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)
+ */
+ public <T extends Control> T getControl(Class<T> controlType) {
+ for (Control c : controls.getArray()) {
+ if (controlType.isAssignableFrom(c.getClass())) {
+ return (T) c;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the control at the given index in the list.
+ *
+ * @param index The index of the control in the list to find.
+ * @return The control at the given index.
+ *
+ * @throws IndexOutOfBoundsException
+ * If the index is outside the range [0, getNumControls()-1]
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public Control getControl(int index) {
+ return controls.get(index);
+ }
+
+ /**
+ * @return The number of controls attached to this Spatial.
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ * @see Spatial#removeControl(java.lang.Class)
+ */
+ public int getNumControls() {
+ return controls.size();
+ }
+
+ /**
+ * <code>updateLogicalState</code> calls the <code>update()</code> method
+ * for all controls attached to this Spatial.
+ *
+ * @param tpf Time per frame.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public void updateLogicalState(float tpf) {
+ runControlUpdate(tpf);
+ }
+
+ /**
+ * <code>updateGeometricState</code> updates the lightlist,
+ * computes the world transforms, and computes the world bounds
+ * for this Spatial.
+ * 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()
+ */
+ public void updateGeometricState() {
+ // assume that this Spatial is a leaf, a proper implementation
+ // for this method should be provided by Node.
+
+ // NOTE: Update world transforms first because
+ // bound transform depends on them.
+ if ((refreshFlags & RF_LIGHTLIST) != 0) {
+ updateWorldLightList();
+ }
+ if ((refreshFlags & RF_TRANSFORM) != 0) {
+ updateWorldTransforms();
+ }
+ if ((refreshFlags & RF_BOUND) != 0) {
+ updateWorldBound();
+ }
+
+ assert refreshFlags == 0;
+ }
+
+ /**
+ * Convert a vector (in) from this spatials' local coordinate space to world
+ * coordinate space.
+ *
+ * @param in
+ * vector to read from
+ * @param store
+ * where to write the result (null to create a new vector, may be
+ * same as in)
+ * @return the result (store)
+ */
+ public Vector3f localToWorld(final Vector3f in, Vector3f store) {
+ checkDoTransformUpdate();
+ return worldTransform.transformVector(in, store);
+ }
+
+ /**
+ * Convert a vector (in) from world coordinate space to this spatials' local
+ * coordinate space.
+ *
+ * @param in
+ * vector to read from
+ * @param store
+ * where to write the result
+ * @return the result (store)
+ */
+ public Vector3f worldToLocal(final Vector3f in, final Vector3f store) {
+ checkDoTransformUpdate();
+ return worldTransform.transformInverseVector(in, store);
+ }
+
+ /**
+ * <code>getParent</code> retrieves this node's parent. If the parent is
+ * null this is the root node.
+ *
+ * @return the parent of this node.
+ */
+ public Node getParent() {
+ return parent;
+ }
+
+ /**
+ * Called by {@link Node#attachChild(Spatial)} and
+ * {@link Node#detachChild(Spatial)} - don't call directly.
+ * <code>setParent</code> sets the parent of this node.
+ *
+ * @param parent
+ * the parent of this node.
+ */
+ protected void setParent(Node parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * <code>removeFromParent</code> removes this Spatial from it's parent.
+ *
+ * @return true if it has a parent and performed the remove.
+ */
+ public boolean removeFromParent() {
+ if (parent != null) {
+ parent.detachChild(this);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * determines if the provided Node is the parent, or parent's parent, etc. of this Spatial.
+ *
+ * @param ancestor
+ * the ancestor object to look for.
+ * @return true if the ancestor is found, false otherwise.
+ */
+ public boolean hasAncestor(Node ancestor) {
+ if (parent == null) {
+ return false;
+ } else if (parent.equals(ancestor)) {
+ return true;
+ } else {
+ return parent.hasAncestor(ancestor);
+ }
+ }
+
+ /**
+ * <code>getLocalRotation</code> retrieves the local rotation of this
+ * node.
+ *
+ * @return the local rotation of this node.
+ */
+ public Quaternion getLocalRotation() {
+ return localTransform.getRotation();
+ }
+
+ /**
+ * <code>setLocalRotation</code> sets the local rotation of this node
+ * by using a {@link Matrix3f}.
+ *
+ * @param rotation
+ * the new local rotation.
+ */
+ public void setLocalRotation(Matrix3f rotation) {
+ localTransform.getRotation().fromRotationMatrix(rotation);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalRotation</code> sets the local rotation of this node.
+ *
+ * @param quaternion
+ * the new local rotation.
+ */
+ public void setLocalRotation(Quaternion quaternion) {
+ localTransform.setRotation(quaternion);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>getLocalScale</code> retrieves the local scale of this node.
+ *
+ * @return the local scale of this node.
+ */
+ public Vector3f getLocalScale() {
+ return localTransform.getScale();
+ }
+
+ /**
+ * <code>setLocalScale</code> sets the local scale of this node.
+ *
+ * @param localScale
+ * the new local scale, applied to x, y and z
+ */
+ public void setLocalScale(float localScale) {
+ localTransform.setScale(localScale);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalScale</code> sets the local scale of this node.
+ */
+ public void setLocalScale(float x, float y, float z) {
+ localTransform.setScale(x, y, z);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalScale</code> sets the local scale of this node.
+ *
+ * @param localScale
+ * the new local scale.
+ */
+ public void setLocalScale(Vector3f localScale) {
+ localTransform.setScale(localScale);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>getLocalTranslation</code> retrieves the local translation of
+ * this node.
+ *
+ * @return the local translation of this node.
+ */
+ public Vector3f getLocalTranslation() {
+ return localTransform.getTranslation();
+ }
+
+ /**
+ * <code>setLocalTranslation</code> sets the local translation of this
+ * spatial.
+ *
+ * @param localTranslation
+ * the local translation of this spatial.
+ */
+ public void setLocalTranslation(Vector3f localTranslation) {
+ this.localTransform.setTranslation(localTranslation);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalTranslation</code> sets the local translation of this
+ * spatial.
+ */
+ public void setLocalTranslation(float x, float y, float z) {
+ this.localTransform.setTranslation(x, y, z);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalTransform</code> sets the local transform of this
+ * spatial.
+ */
+ public void setLocalTransform(Transform t) {
+ this.localTransform.set(t);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>getLocalTransform</code> retrieves the local transform of
+ * this spatial.
+ *
+ * @return the local transform of this spatial.
+ */
+ public Transform getLocalTransform() {
+ return localTransform;
+ }
+
+ /**
+ * Applies the given material to the Spatial, this will propagate the
+ * material down to the geometries in the scene graph.
+ *
+ * @param material The material to set.
+ */
+ public void setMaterial(Material material) {
+ }
+
+ /**
+ * <code>addLight</code> adds the given light to the Spatial; causing
+ * all child Spatials to be effected by it.
+ *
+ * @param light The light to add.
+ */
+ public void addLight(Light light) {
+ localLights.add(light);
+ setLightListRefresh();
+ }
+
+ /**
+ * <code>removeLight</code> removes the given light from the Spatial.
+ *
+ * @param light The light to remove.
+ * @see Spatial#addLight(com.jme3.light.Light)
+ */
+ public void removeLight(Light light) {
+ localLights.remove(light);
+ setLightListRefresh();
+ }
+
+ /**
+ * Translates the spatial by the given translation vector.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial move(float x, float y, float z) {
+ this.localTransform.getTranslation().addLocal(x, y, z);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Translates the spatial by the given translation vector.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial move(Vector3f offset) {
+ this.localTransform.getTranslation().addLocal(offset);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Scales the spatial by the given value
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial scale(float s) {
+ return scale(s, s, s);
+ }
+
+ /**
+ * Scales the spatial by the given scale vector.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial scale(float x, float y, float z) {
+ this.localTransform.getScale().multLocal(x, y, z);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Rotates the spatial by the given rotation.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial rotate(Quaternion rot) {
+ this.localTransform.getRotation().multLocal(rot);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Rotates the spatial by the yaw, roll and pitch angles (in radians),
+ * in the local coordinate space.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial rotate(float yaw, float roll, float pitch) {
+ TempVars vars = TempVars.get();
+ Quaternion q = vars.quat1;
+ q.fromAngles(yaw, roll, pitch);
+ rotate(q);
+ vars.release();
+
+ return this;
+ }
+
+ /**
+ * Centers the spatial in the origin of the world bound.
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial center() {
+ Vector3f worldTrans = getWorldTranslation();
+ Vector3f worldCenter = getWorldBound().getCenter();
+
+ Vector3f absTrans = worldTrans.subtract(worldCenter);
+ setLocalTranslation(absTrans);
+
+ return this;
+ }
+
+ /**
+ * @see #setCullHint(CullHint)
+ * @return the cull mode of this spatial, or if set to CullHint.Inherit,
+ * the cullmode of it's parent.
+ */
+ public CullHint getCullHint() {
+ if (cullHint != CullHint.Inherit) {
+ return cullHint;
+ } else if (parent != null) {
+ return parent.getCullHint();
+ } else {
+ return CullHint.Dynamic;
+ }
+ }
+
+ public BatchHint getBatchHint() {
+ if (batchHint != BatchHint.Inherit) {
+ return batchHint;
+ } else if (parent != null) {
+ return parent.getBatchHint();
+ } else {
+ return BatchHint.Always;
+ }
+ }
+
+ /**
+ * Returns this spatial's renderqueue bucket. If the mode is set to inherit,
+ * then the spatial gets its renderqueue bucket from its parent.
+ *
+ * @return The spatial's current renderqueue mode.
+ */
+ public RenderQueue.Bucket getQueueBucket() {
+ if (queueBucket != RenderQueue.Bucket.Inherit) {
+ return queueBucket;
+ } else if (parent != null) {
+ return parent.getQueueBucket();
+ } else {
+ return RenderQueue.Bucket.Opaque;
+ }
+ }
+
+ /**
+ * @return The shadow mode of this spatial, if the local shadow
+ * mode is set to inherit, then the parent's shadow mode is returned.
+ *
+ * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode)
+ * @see ShadowMode
+ */
+ public RenderQueue.ShadowMode getShadowMode() {
+ if (shadowMode != RenderQueue.ShadowMode.Inherit) {
+ return shadowMode;
+ } else if (parent != null) {
+ return parent.getShadowMode();
+ } else {
+ return ShadowMode.Off;
+ }
+ }
+
+ /**
+ * Sets the level of detail to use when rendering this Spatial,
+ * this call propagates to all geometries under this Spatial.
+ *
+ * @param lod The lod level to set.
+ */
+ public void setLodLevel(int lod) {
+ }
+
+ /**
+ * <code>updateModelBound</code> recalculates the bounding object for this
+ * Spatial.
+ */
+ public abstract void updateModelBound();
+
+ /**
+ * <code>setModelBound</code> sets the bounding object for this Spatial.
+ *
+ * @param modelBound
+ * the bounding object for this spatial.
+ */
+ public abstract void setModelBound(BoundingVolume modelBound);
+
+ /**
+ * @return The sum of all verticies under this Spatial.
+ */
+ public abstract int getVertexCount();
+
+ /**
+ * @return The sum of all triangles under this Spatial.
+ */
+ public abstract int getTriangleCount();
+
+ /**
+ * @return A clone of this Spatial, the scene graph in its entirety
+ * is cloned and can be altered independently of the original scene graph.
+ *
+ * Note that meshes of geometries are not cloned explicitly, they
+ * are shared if static, or specially cloned if animated.
+ *
+ * All controls will be cloned using the Control.cloneForSpatial method
+ * on the clone.
+ *
+ * @see Mesh#cloneForAnim()
+ */
+ public Spatial clone(boolean cloneMaterial) {
+ try {
+ Spatial clone = (Spatial) super.clone();
+ if (worldBound != null) {
+ clone.worldBound = worldBound.clone();
+ }
+ clone.worldLights = worldLights.clone();
+ clone.localLights = localLights.clone();
+
+ // Set the new owner of the light lists
+ clone.localLights.setOwner(clone);
+ clone.worldLights.setOwner(clone);
+
+ // No need to force cloned to update.
+ // This node already has the refresh flags
+ // set below so it will have to update anyway.
+ clone.worldTransform = worldTransform.clone();
+ clone.localTransform = localTransform.clone();
+
+ if (clone instanceof Node) {
+ Node node = (Node) this;
+ Node nodeClone = (Node) clone;
+ nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
+ for (Spatial child : node.children) {
+ Spatial childClone = child.clone(cloneMaterial);
+ childClone.parent = nodeClone;
+ nodeClone.children.add(childClone);
+ }
+ }
+
+ clone.parent = null;
+ clone.setBoundRefresh();
+ clone.setTransformRefresh();
+ clone.setLightListRefresh();
+
+ clone.controls = new SafeArrayList<Control>(Control.class);
+ for (int i = 0; i < controls.size(); i++) {
+ clone.controls.add(controls.get(i).cloneForSpatial(clone));
+ }
+
+ if (userData != null) {
+ clone.userData = (HashMap<String, Savable>) userData.clone();
+ }
+
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * @return A clone of this Spatial, the scene graph in its entirety
+ * is cloned and can be altered independently of the original scene graph.
+ *
+ * Note that meshes of geometries are not cloned explicitly, they
+ * are shared if static, or specially cloned if animated.
+ *
+ * All controls will be cloned using the Control.cloneForSpatial method
+ * on the clone.
+ *
+ * @see Mesh#cloneForAnim()
+ */
+ @Override
+ public Spatial clone() {
+ return clone(true);
+ }
+
+ /**
+ * @return Similar to Spatial.clone() except will create a deep clone
+ * of all geometry's meshes, normally this method shouldn't be used
+ * instead use Spatial.clone()
+ *
+ * @see Spatial#clone()
+ */
+ public abstract Spatial deepClone();
+
+ public void setUserData(String key, Object data) {
+ if (userData == null) {
+ userData = new HashMap<String, Savable>();
+ }
+
+ if (data instanceof Savable) {
+ userData.put(key, (Savable) data);
+ } else {
+ userData.put(key, new UserData(UserData.getObjectType(data), data));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T getUserData(String key) {
+ if (userData == null) {
+ return null;
+ }
+
+ Savable s = userData.get(key);
+ if (s instanceof UserData) {
+ return (T) ((UserData) s).getValue();
+ } else {
+ return (T) s;
+ }
+ }
+
+ public Collection<String> getUserDataKeys() {
+ if (userData != null) {
+ return userData.keySet();
+ }
+
+ return Collections.EMPTY_SET;
+ }
+
+ /**
+ * Note that we are <i>matching</i> the pattern, therefore the pattern
+ * must match the entire pattern (i.e. it behaves as if it is sandwiched
+ * between "^" and "$").
+ * You can set regex modes, like case insensitivity, by using the (?X)
+ * or (?X:Y) constructs.
+ *
+ * @param spatialSubclass Subclass which this must implement.
+ * Null causes all Spatials to qualify.
+ * @param nameRegex Regular expression to match this name against.
+ * Null causes all Names to qualify.
+ * @return true if this implements the specified class and this's name
+ * matches the specified pattern.
+ *
+ * @see java.util.regex.Pattern
+ */
+ public boolean matches(Class<? extends Spatial> spatialSubclass,
+ String nameRegex) {
+ if (spatialSubclass != null && !spatialSubclass.isInstance(this)) {
+ return false;
+ }
+
+ if (nameRegex != null && (name == null || !name.matches(nameRegex))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule capsule = ex.getCapsule(this);
+ capsule.write(name, "name", null);
+ capsule.write(worldBound, "world_bound", null);
+ capsule.write(cullHint, "cull_mode", CullHint.Inherit);
+ capsule.write(batchHint, "batch_hint", BatchHint.Inherit);
+ capsule.write(queueBucket, "queue", RenderQueue.Bucket.Inherit);
+ capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit);
+ capsule.write(localTransform, "transform", Transform.IDENTITY);
+ capsule.write(localLights, "lights", null);
+
+ // Shallow clone the controls array to convert its type.
+ capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null);
+ capsule.writeStringSavableMap(userData, "user_data", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+
+ name = ic.readString("name", null);
+ worldBound = (BoundingVolume) ic.readSavable("world_bound", null);
+ cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit);
+ batchHint = ic.readEnum("batch_hint", BatchHint.class, BatchHint.Inherit);
+ queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class,
+ RenderQueue.Bucket.Inherit);
+ shadowMode = ic.readEnum("shadow_mode", ShadowMode.class,
+ ShadowMode.Inherit);
+
+ localTransform = (Transform) ic.readSavable("transform", Transform.IDENTITY);
+
+ localLights = (LightList) ic.readSavable("lights", null);
+ localLights.setOwner(this);
+
+ //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 :
+ //controls = ic.readSavableArrayList("controlsList", null));
+ controls.addAll(0, ic.readSavableArrayList("controlsList", null));
+
+ userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null);
+ }
+
+ /**
+ * <code>getWorldBound</code> retrieves the world bound at this node
+ * level.
+ *
+ * @return the world bound at this level.
+ */
+ public BoundingVolume getWorldBound() {
+ checkDoBoundUpdate();
+ return worldBound;
+ }
+
+ /**
+ * <code>setCullHint</code> sets how scene culling should work on this
+ * spatial during drawing. NOTE: You must set this AFTER attaching to a
+ * parent or it will be reset with the parent's cullMode value.
+ *
+ * @param hint
+ * one of CullHint.Dynamic, CullHint.Always, CullHint.Inherit or
+ * CullHint.Never
+ */
+ public void setCullHint(CullHint hint) {
+ cullHint = hint;
+ }
+
+ /**
+ * <code>setBatchHint</code> sets how batching should work on this
+ * spatial. NOTE: You must set this AFTER attaching to a
+ * parent or it will be reset with the parent's cullMode value.
+ *
+ * @param hint
+ * one of BatchHint.Never, BatchHint.Always, BatchHint.Inherit
+ */
+ public void setBatchHint(BatchHint hint) {
+ batchHint = hint;
+ }
+
+ /**
+ * @return the cullmode set on this Spatial
+ */
+ public CullHint getLocalCullHint() {
+ return cullHint;
+ }
+
+ /**
+ * @return the batchHint set on this Spatial
+ */
+ public BatchHint getLocalBatchHint() {
+ return batchHint;
+ }
+
+ /**
+ * <code>setQueueBucket</code> determines at what phase of the
+ * rendering process this Spatial will rendered. See the
+ * {@link Bucket} enum for an explanation of the various
+ * render queue buckets.
+ *
+ * @param queueBucket
+ * The bucket to use for this Spatial.
+ */
+ public void setQueueBucket(RenderQueue.Bucket queueBucket) {
+ this.queueBucket = queueBucket;
+ }
+
+ /**
+ * Sets the shadow mode of the spatial
+ * The shadow mode determines how the spatial should be shadowed,
+ * when a shadowing technique is used. See the
+ * documentation for the class {@link ShadowMode} for more information.
+ *
+ * @see ShadowMode
+ *
+ * @param shadowMode The local shadow mode to set.
+ */
+ public void setShadowMode(RenderQueue.ShadowMode shadowMode) {
+ this.shadowMode = shadowMode;
+ }
+
+ /**
+ * @return The locally set queue bucket mode
+ *
+ * @see Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket)
+ */
+ public RenderQueue.Bucket getLocalQueueBucket() {
+ return queueBucket;
+ }
+
+ /**
+ * @return The locally set shadow mode
+ *
+ * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode)
+ */
+ public RenderQueue.ShadowMode getLocalShadowMode() {
+ return shadowMode;
+ }
+
+ /**
+ * Returns this spatial's last frustum intersection result. This int is set
+ * when a check is made to determine if the bounds of the object fall inside
+ * a camera's frustum. If a parent is found to fall outside the frustum, the
+ * value for this spatial will not be updated.
+ *
+ * @return The spatial's last frustum intersection result.
+ */
+ public Camera.FrustumIntersect getLastFrustumIntersection() {
+ return frustrumIntersects;
+ }
+
+ /**
+ * Overrides the last intersection result. This is useful for operations
+ * that want to start rendering at the middle of a scene tree and don't want
+ * the parent of that node to influence culling.
+ *
+ * @param intersects
+ * the new value
+ */
+ public void setLastFrustumIntersection(Camera.FrustumIntersect intersects) {
+ frustrumIntersects = intersects;
+ }
+
+ /**
+ * Returns the Spatial's name followed by the class of the spatial <br>
+ * Example: "MyNode (com.jme3.scene.Spatial)
+ *
+ * @return Spatial's name followed by the class of the Spatial
+ */
+ @Override
+ public String toString() {
+ return name + " (" + this.getClass().getSimpleName() + ')';
+ }
+
+ /**
+ * Creates a transform matrix that will convert from this spatials'
+ * local coordinate space to the world coordinate space
+ * based on the world transform.
+ *
+ * @param store Matrix where to store the result, if null, a new one
+ * will be created and returned.
+ *
+ * @return store if not null, otherwise, a new matrix containing the result.
+ *
+ * @see Spatial#getWorldTransform()
+ */
+ public Matrix4f getLocalToWorldMatrix(Matrix4f store) {
+ if (store == null) {
+ store = new Matrix4f();
+ } else {
+ store.loadIdentity();
+ }
+ // multiply with scale first, then rotate, finally translate (cf.
+ // Eberly)
+ store.scale(getWorldScale());
+ store.multLocal(getWorldRotation());
+ store.setTranslation(getWorldTranslation());
+ return store;
+ }
+
+ /**
+ * Visit each scene graph element ordered by DFS
+ * @param visitor
+ */
+ public abstract void depthFirstTraversal(SceneGraphVisitor visitor);
+
+ /**
+ * Visit each scene graph element ordered by BFS
+ * @param visitor
+ */
+ public void breadthFirstTraversal(SceneGraphVisitor visitor) {
+ Queue<Spatial> queue = new LinkedList<Spatial>();
+ queue.add(this);
+
+ while (!queue.isEmpty()) {
+ Spatial s = queue.poll();
+ visitor.visit(s);
+ s.breadthFirstTraversal(visitor, queue);
+ }
+ }
+
+ protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue);
+}
diff --git a/engine/src/core/com/jme3/scene/UserData.java b/engine/src/core/com/jme3/scene/UserData.java
new file mode 100644
index 0000000..855a31c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/UserData.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+/**
+ * <code>UserData</code> is used to contain user data objects
+ * set on spatials (primarily primitives) that do not implement
+ * the {@link Savable} interface. Note that attempting
+ * to export any models which have non-savable objects
+ * attached to them will fail.
+ */
+public final class UserData implements Savable {
+
+ /**
+ * Boolean type on Geometries to indicate that physics collision
+ * shape generation should ignore them.
+ */
+ public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore";
+
+ /**
+ * For geometries using shared mesh, this will specify the shared
+ * mesh reference.
+ */
+ public static final String JME_SHAREDMESH = "JmeSharedMesh";
+
+ protected byte type;
+ protected Object value;
+
+ public UserData() {
+ }
+
+ /**
+ * Creates a new <code>UserData</code> with the given
+ * type and value.
+ *
+ * @param type Type of data, should be between 0 and 4.
+ * @param value Value of the data
+ */
+ public UserData(byte type, Object value) {
+ assert type >= 0 && type <= 4;
+ this.type = type;
+ this.value = value;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+
+ public static byte getObjectType(Object type) {
+ if (type instanceof Integer) {
+ return 0;
+ } else if (type instanceof Float) {
+ return 1;
+ } else if (type instanceof Boolean) {
+ return 2;
+ } else if (type instanceof String) {
+ return 3;
+ } else if (type instanceof Long) {
+ return 4;
+ } else {
+ throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName());
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(type, "type", (byte)0);
+
+ switch (type) {
+ case 0:
+ int i = (Integer) value;
+ oc.write(i, "intVal", 0);
+ break;
+ case 1:
+ float f = (Float) value;
+ oc.write(f, "floatVal", 0f);
+ break;
+ case 2:
+ boolean b = (Boolean) value;
+ oc.write(b, "boolVal", false);
+ break;
+ case 3:
+ String s = (String) value;
+ oc.write(s, "strVal", null);
+ break;
+ case 4:
+ Long l = (Long) value;
+ oc.write(l, "longVal", 0l);
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ type = ic.readByte("type", (byte) 0);
+
+ switch (type) {
+ case 0:
+ value = ic.readInt("intVal", 0);
+ break;
+ case 1:
+ value = ic.readFloat("floatVal", 0f);
+ break;
+ case 2:
+ value = ic.readBoolean("boolVal", false);
+ break;
+ case 3:
+ value = ic.readString("strVal", null);
+ break;
+ case 4:
+ value = ic.readLong("longVal", 0l);
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/VertexBuffer.java b/engine/src/core/com/jme3/scene/VertexBuffer.java
new file mode 100644
index 0000000..1a844bb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/VertexBuffer.java
@@ -0,0 +1,1025 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.renderer.Renderer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+import java.nio.*;
+
+/**
+ * A <code>VertexBuffer</code> contains a particular type of geometry
+ * data used by {@link Mesh}es. Every VertexBuffer set on a <code>Mesh</code>
+ * is sent as an attribute to the vertex shader to be processed.
+ * <p>
+ * Several terms are used throughout the javadoc for this class, explanation:
+ * <ul>
+ * <li>Element - A single element is the largest individual object
+ * inside a VertexBuffer. E.g. if the VertexBuffer is used to store 3D position
+ * data, then an element will be a single 3D vector.</li>
+ * <li>Component - A component represents the parts inside an element.
+ * For a 3D vector, a single component is one of the dimensions, X, Y or Z.</li>
+ * </ul>
+ */
+public class VertexBuffer extends NativeObject implements Savable, Cloneable {
+
+ /**
+ * Type of buffer. Specifies the actual attribute it defines.
+ */
+ public static enum Type {
+ /**
+ * Position of the vertex (3 floats)
+ */
+ Position,
+
+ /**
+ * The size of the point when using point buffers (float).
+ */
+ Size,
+
+ /**
+ * Normal vector, normalized (3 floats).
+ */
+ Normal,
+
+ /**
+ * Texture coordinate (2 float)
+ */
+ TexCoord,
+
+ /**
+ * Color and Alpha (4 floats)
+ */
+ Color,
+
+ /**
+ * Tangent vector, normalized (4 floats) (x,y,z,w)
+ * the w component is called the binormal parity, is not normalized and is either 1f or -1f
+ * It's used to compuste the direction on the binormal verctor on the GPU at render time.
+ */
+ Tangent,
+
+ /**
+ * Binormal vector, normalized (3 floats, optional)
+ */
+ Binormal,
+
+ /**
+ * Specifies the source data for various vertex buffers
+ * when interleaving is used. By default the format is
+ * byte.
+ */
+ InterleavedData,
+
+ /**
+ * Do not use.
+ */
+ @Deprecated
+ MiscAttrib,
+
+ /**
+ * Specifies the index buffer, must contain integer data
+ * (ubyte, ushort, or uint).
+ */
+ Index,
+
+ /**
+ * Initial vertex position, used with animation.
+ * Should have the same format and size as {@link Type#Position}.
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BindPosePosition,
+
+ /**
+ * Initial vertex normals, used with animation.
+ * Should have the same format and size as {@link Type#Normal}.
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BindPoseNormal,
+
+ /**
+ * Bone weights, used with animation (4 floats).
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BoneWeight,
+
+ /**
+ * Bone indices, used with animation (4 ubytes).
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BoneIndex,
+
+ /**
+ * Texture coordinate #2
+ */
+ TexCoord2,
+
+ /**
+ * Texture coordinate #3
+ */
+ TexCoord3,
+
+ /**
+ * Texture coordinate #4
+ */
+ TexCoord4,
+
+ /**
+ * Texture coordinate #5
+ */
+ TexCoord5,
+
+ /**
+ * Texture coordinate #6
+ */
+ TexCoord6,
+
+ /**
+ * Texture coordinate #7
+ */
+ TexCoord7,
+
+ /**
+ * Texture coordinate #8
+ */
+ TexCoord8,
+
+ /**
+ * Initial vertex tangents, used with animation.
+ * Should have the same format and size as {@link Type#Tangent}.
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BindPoseTangent,
+ }
+
+ /**
+ * The usage of the VertexBuffer, specifies how often the buffer
+ * is used. This can determine if a vertex buffer is placed in VRAM
+ * or held in video memory, but no guarantees are made- it's only a hint.
+ */
+ public static enum Usage {
+
+ /**
+ * Mesh data is sent once and very rarely updated.
+ */
+ Static,
+
+ /**
+ * Mesh data is updated occasionally (once per frame or less).
+ */
+ Dynamic,
+
+ /**
+ * Mesh data is updated every frame.
+ */
+ Stream,
+
+ /**
+ * Mesh data is <em>not</em> sent to GPU at all. It is only
+ * used by the CPU.
+ */
+ CpuOnly;
+ }
+
+ /**
+ * Specifies format of the data stored in the buffer.
+ * This should directly correspond to the buffer's class, for example,
+ * an {@link Format#UnsignedShort} formatted buffer should use the
+ * class {@link ShortBuffer} (e.g. the closest resembling type).
+ * For the {@link Format#Half} type, {@link ByteBuffer}s should
+ * be used.
+ */
+ public static enum Format {
+ /**
+ * Half precision floating point.
+ * 2 bytes, signed.
+ */
+ Half(2),
+
+ /**
+ * Single precision floating point.
+ * 4 bytes, signed
+ */
+ Float(4),
+
+ /**
+ * Double precision floating point.
+ * 8 bytes, signed. May not
+ * be supported by all GPUs.
+ */
+ Double(8),
+
+ /**
+ * 1 byte integer, signed.
+ */
+ Byte(1),
+
+ /**
+ * 1 byte integer, unsigned.
+ */
+ UnsignedByte(1),
+
+ /**
+ * 2 byte integer, signed.
+ */
+ Short(2),
+
+ /**
+ * 2 byte integer, unsigned.
+ */
+ UnsignedShort(2),
+
+ /**
+ * 4 byte integer, signed.
+ */
+ Int(4),
+
+ /**
+ * 4 byte integer, unsigned.
+ */
+ UnsignedInt(4);
+
+ private int componentSize = 0;
+
+ Format(int componentSize){
+ this.componentSize = componentSize;
+ }
+
+ /**
+ * Returns the size in bytes of this data type.
+ *
+ * @return Size in bytes of this data type.
+ */
+ public int getComponentSize(){
+ return componentSize;
+ }
+ }
+
+ protected int offset = 0;
+ protected int lastLimit = 0;
+ protected int stride = 0;
+ protected int components = 0;
+
+ /**
+ * derived from components * format.getComponentSize()
+ */
+ protected transient int componentsLength = 0;
+ protected Buffer data = null;
+ protected Usage usage;
+ protected Type bufType;
+ protected Format format;
+ protected boolean normalized = false;
+ protected transient boolean dataSizeChanged = false;
+
+ /**
+ * Creates an empty, uninitialized buffer.
+ * Must call setupData() to initialize.
+ */
+ public VertexBuffer(Type type){
+ super(VertexBuffer.class);
+ this.bufType = type;
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public VertexBuffer(){
+ super(VertexBuffer.class);
+ }
+
+ protected VertexBuffer(int id){
+ super(VertexBuffer.class, id);
+ }
+
+ /**
+ * @return The offset after which the data is sent to the GPU.
+ *
+ * @see #setOffset(int)
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ /**
+ * @param offset Specify the offset (in bytes) from the start of the buffer
+ * after which the data is sent to the GPU.
+ */
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * @return The stride (in bytes) for the data.
+ *
+ * @see #setStride(int)
+ */
+ public int getStride() {
+ return stride;
+ }
+
+ /**
+ * Set the stride (in bytes) for the data.
+ * <p>
+ * If the data is packed in the buffer, then stride is 0, if there's other
+ * data that is between the current component and the next component in the
+ * buffer, then this specifies the size in bytes of that additional data.
+ *
+ * @param stride the stride (in bytes) for the data
+ */
+ public void setStride(int stride) {
+ this.stride = stride;
+ }
+
+ /**
+ * Returns the raw internal data buffer used by this VertexBuffer.
+ * This buffer is not safe to call from multiple threads since buffers
+ * have their own internal position state that cannot be shared.
+ * Call getData().duplicate(), getData().asReadOnlyBuffer(), or
+ * the more convenient getDataReadOnly() if the buffer may be accessed
+ * from multiple threads.
+ *
+ * @return A native buffer, in the specified {@link Format format}.
+ */
+ public Buffer getData(){
+ return data;
+ }
+
+ /**
+ * Returns a safe read-only version of this VertexBuffer's data. The
+ * contents of the buffer will reflect whatever changes are made on
+ * other threads (eventually) but these should not be used in that way.
+ * This method provides a read-only buffer that is safe to _read_ from
+ * a separate thread since it has its own book-keeping state (position, limit, etc.)
+ *
+ * @return A rewound native buffer in the specified {@link Format format}
+ * that is safe to read from a separate thread from other readers.
+ */
+ public Buffer getDataReadOnly() {
+
+ if (data == null) {
+ return null;
+ }
+
+ // Create a read-only duplicate(). Note: this does not copy
+ // the underlying memory, it just creates a new read-only wrapper
+ // with its own buffer position state.
+
+ // Unfortunately, this is not 100% straight forward since Buffer
+ // does not have an asReadOnlyBuffer() method.
+ Buffer result;
+ if( data instanceof ByteBuffer ) {
+ result = ((ByteBuffer)data).asReadOnlyBuffer();
+ } else if( data instanceof FloatBuffer ) {
+ result = ((FloatBuffer)data).asReadOnlyBuffer();
+ } else if( data instanceof ShortBuffer ) {
+ result = ((ShortBuffer)data).asReadOnlyBuffer();
+ } else if( data instanceof IntBuffer ) {
+ result = ((IntBuffer)data).asReadOnlyBuffer();
+ } else {
+ throw new UnsupportedOperationException( "Cannot get read-only view of buffer type:" + data );
+ }
+
+ // Make sure the caller gets a consistent view since we may
+ // have grabbed this buffer while another thread was reading
+ // the raw data.
+ result.rewind();
+
+ return result;
+ }
+
+ /**
+ * @return The usage of this buffer. See {@link Usage} for more
+ * information.
+ */
+ public Usage getUsage(){
+ return usage;
+ }
+
+ /**
+ * @param usage The usage of this buffer. See {@link Usage} for more
+ * information.
+ */
+ public void setUsage(Usage usage){
+// if (id != -1)
+// throw new UnsupportedOperationException("Data has already been sent. Cannot set usage.");
+
+ this.usage = usage;
+ }
+
+ /**
+ * @param normalized Set to true if integer components should be converted
+ * from their maximal range into the range 0.0 - 1.0 when converted to
+ * a floating-point value for the shader.
+ * E.g. if the {@link Format} is {@link Format#UnsignedInt}, then
+ * the components will be converted to the range 0.0 - 1.0 by dividing
+ * every integer by 2^32.
+ */
+ public void setNormalized(boolean normalized){
+ this.normalized = normalized;
+ }
+
+ /**
+ * @return True if integer components should be converted to the range 0-1.
+ * @see VertexBuffer#setNormalized(boolean)
+ */
+ public boolean isNormalized(){
+ return normalized;
+ }
+
+ /**
+ * @return The type of information that this buffer has.
+ */
+ public Type getBufferType(){
+ return bufType;
+ }
+
+ /**
+ * @return The {@link Format format}, or data type of the data.
+ */
+ public Format getFormat(){
+ return format;
+ }
+
+ /**
+ * @return The number of components of the given {@link Format format} per
+ * element.
+ */
+ public int getNumComponents(){
+ return components;
+ }
+
+ /**
+ * @return The total number of data elements in the data buffer.
+ */
+ public int getNumElements(){
+ int elements = data.capacity() / components;
+ if (format == Format.Half)
+ elements /= 2;
+ return elements;
+ }
+
+ /**
+ * Called to initialize the data in the <code>VertexBuffer</code>. Must only
+ * be called once.
+ *
+ * @param usage The usage for the data, or how often will the data
+ * be updated per frame. See the {@link Usage} enum.
+ * @param components The number of components per element.
+ * @param format The {@link Format format}, or data-type of a single
+ * component.
+ * @param data A native buffer, the format of which matches the {@link Format}
+ * argument.
+ */
+ public void setupData(Usage usage, int components, Format format, Buffer data){
+ if (id != -1)
+ throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
+
+ if (usage == null || format == null || data == null)
+ throw new IllegalArgumentException("None of the arguments can be null");
+
+ if (data.isReadOnly())
+ throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
+
+ if (components < 1 || components > 4)
+ throw new IllegalArgumentException("components must be between 1 and 4");
+
+ this.data = data;
+ this.components = components;
+ this.usage = usage;
+ this.format = format;
+ this.componentsLength = components * format.getComponentSize();
+ this.lastLimit = data.limit();
+ setUpdateNeeded();
+ }
+
+ /**
+ * Called to update the data in the buffer with new data. Can only
+ * be called after {@link VertexBuffer#setupData(com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) }
+ * has been called. Note that it is fine to call this method on the
+ * data already set, e.g. vb.updateData(vb.getData()), this will just
+ * set the proper update flag indicating the data should be sent to the GPU
+ * again.
+ * It is allowed to specify a buffer with different capacity than the
+ * originally set buffer.
+ *
+ * @param data The data buffer to set
+ */
+ public void updateData(Buffer data){
+ if (id != -1){
+ // request to update data is okay
+ }
+
+ // Check if the data buffer is read-only which is a sign
+ // of a bug on the part of the caller
+ if (data != null && data.isReadOnly()) {
+ throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
+ }
+
+ // will force renderer to call glBufferData again
+ if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)){
+ dataSizeChanged = true;
+ lastLimit = data.limit();
+ }
+
+ this.data = data;
+ setUpdateNeeded();
+ }
+
+ /**
+ * Returns true if the data size of the VertexBuffer has changed.
+ * Internal use only.
+ * @return true if the data size has changed
+ */
+ public boolean hasDataSizeChanged() {
+ return dataSizeChanged;
+ }
+
+ @Override
+ public void clearUpdateNeeded(){
+ super.clearUpdateNeeded();
+ dataSizeChanged = false;
+ }
+
+ /**
+ * Converts single floating-point data to {@link Format#Half half} floating-point data.
+ */
+ public void convertToHalf(){
+ if (id != -1)
+ throw new UnsupportedOperationException("Data has already been sent.");
+
+ if (format != Format.Float)
+ throw new IllegalStateException("Format must be float!");
+
+ int numElements = data.capacity() / components;
+ format = Format.Half;
+ this.componentsLength = components * format.getComponentSize();
+
+ ByteBuffer halfData = BufferUtils.createByteBuffer(componentsLength * numElements);
+ halfData.rewind();
+
+ FloatBuffer floatData = (FloatBuffer) data;
+ floatData.rewind();
+
+ for (int i = 0; i < floatData.capacity(); i++){
+ float f = floatData.get(i);
+ short half = FastMath.convertFloatToHalf(f);
+ halfData.putShort(half);
+ }
+ this.data = halfData;
+ setUpdateNeeded();
+ dataSizeChanged = true;
+ }
+
+ /**
+ * Reduces the capacity of the buffer to the given amount
+ * of elements, any elements at the end of the buffer are truncated
+ * as necessary.
+ *
+ * @param numElements The number of elements to reduce to.
+ */
+ public void compact(int numElements){
+ int total = components * numElements;
+ data.clear();
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bbuf = (ByteBuffer) data;
+ bbuf.limit(total);
+ ByteBuffer bnewBuf = BufferUtils.createByteBuffer(total);
+ bnewBuf.put(bbuf);
+ data = bnewBuf;
+ break;
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sbuf = (ShortBuffer) data;
+ sbuf.limit(total);
+ ShortBuffer snewBuf = BufferUtils.createShortBuffer(total);
+ snewBuf.put(sbuf);
+ data = snewBuf;
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer ibuf = (IntBuffer) data;
+ ibuf.limit(total);
+ IntBuffer inewBuf = BufferUtils.createIntBuffer(total);
+ inewBuf.put(ibuf);
+ data = inewBuf;
+ break;
+ case Float:
+ FloatBuffer fbuf = (FloatBuffer) data;
+ fbuf.limit(total);
+ FloatBuffer fnewBuf = BufferUtils.createFloatBuffer(total);
+ fnewBuf.put(fbuf);
+ data = fnewBuf;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+ data.clear();
+ setUpdateNeeded();
+ dataSizeChanged = true;
+ }
+
+ /**
+ * Modify a component inside an element.
+ * The <code>val</code> parameter must be in the buffer's format:
+ * {@link Format}.
+ *
+ * @param elementIndex The element index to modify
+ * @param componentIndex The component index to modify
+ * @param val The value to set, either byte, short, int or float depending
+ * on the {@link Format}.
+ */
+ public void setElementComponent(int elementIndex, int componentIndex, Object val){
+ int inPos = elementIndex * components;
+ int elementPos = componentIndex;
+
+ if (format == Format.Half){
+ inPos *= 2;
+ elementPos *= 2;
+ }
+
+ data.clear();
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bin = (ByteBuffer) data;
+ bin.put(inPos + elementPos, (Byte)val);
+ break;
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sin = (ShortBuffer) data;
+ sin.put(inPos + elementPos, (Short)val);
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer iin = (IntBuffer) data;
+ iin.put(inPos + elementPos, (Integer)val);
+ break;
+ case Float:
+ FloatBuffer fin = (FloatBuffer) data;
+ fin.put(inPos + elementPos, (Float)val);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+ }
+
+ /**
+ * Get the component inside an element.
+ *
+ * @param elementIndex The element index
+ * @param componentIndex The component index
+ * @return The component, as one of the primitive types, byte, short,
+ * int or float.
+ */
+ public Object getElementComponent(int elementIndex, int componentIndex){
+ int inPos = elementIndex * components;
+ int elementPos = componentIndex;
+
+ if (format == Format.Half){
+ inPos *= 2;
+ elementPos *= 2;
+ }
+
+ Buffer srcData = getDataReadOnly();
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bin = (ByteBuffer) srcData;
+ return bin.get(inPos + elementPos);
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sin = (ShortBuffer) srcData;
+ return sin.get(inPos + elementPos);
+ case Int:
+ case UnsignedInt:
+ IntBuffer iin = (IntBuffer) srcData;
+ return iin.get(inPos + elementPos);
+ case Float:
+ FloatBuffer fin = (FloatBuffer) srcData;
+ return fin.get(inPos + elementPos);
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+ }
+
+ /**
+ * Copies a single element of data from this <code>VertexBuffer</code>
+ * to the given output VertexBuffer.
+ *
+ * @param inIndex The input element index
+ * @param outVb The buffer to copy to
+ * @param outIndex The output element index
+ *
+ * @throws IllegalArgumentException If the formats of the buffers do not
+ * match.
+ */
+ public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){
+ copyElements(inIndex, outVb, outIndex, 1);
+ }
+
+ /**
+ * Copies a sequence of elements of data from this <code>VertexBuffer</code>
+ * to the given output VertexBuffer.
+ *
+ * @param inIndex The input element index
+ * @param outVb The buffer to copy to
+ * @param outIndex The output element index
+ * @param len The number of elements to copy
+ *
+ * @throws IllegalArgumentException If the formats of the buffers do not
+ * match.
+ */
+ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){
+ if (outVb.format != format || outVb.components != components)
+ throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
+
+ int inPos = inIndex * components;
+ int outPos = outIndex * components;
+ int elementSz = components;
+ if (format == Format.Half){
+ // because half is stored as bytebuf but its 2 bytes long
+ inPos *= 2;
+ outPos *= 2;
+ elementSz *= 2;
+ }
+
+ // Make sure to grab a read-only copy in case some other
+ // thread is also accessing the buffer and messing with its
+ // position()
+ Buffer srcData = getDataReadOnly();
+ outVb.data.clear();
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bin = (ByteBuffer) srcData;
+ ByteBuffer bout = (ByteBuffer) outVb.data;
+ bin.position(inPos).limit(inPos + elementSz * len);
+ bout.position(outPos).limit(outPos + elementSz * len);
+ bout.put(bin);
+ break;
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sin = (ShortBuffer) srcData;
+ ShortBuffer sout = (ShortBuffer) outVb.data;
+ sin.position(inPos).limit(inPos + elementSz * len);
+ sout.position(outPos).limit(outPos + elementSz * len);
+ sout.put(sin);
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer iin = (IntBuffer) srcData;
+ IntBuffer iout = (IntBuffer) outVb.data;
+ iin.position(inPos).limit(inPos + elementSz * len);
+ iout.position(outPos).limit(outPos + elementSz * len);
+ iout.put(iin);
+ break;
+ case Float:
+ FloatBuffer fin = (FloatBuffer) srcData;
+ FloatBuffer fout = (FloatBuffer) outVb.data;
+ fin.position(inPos).limit(inPos + elementSz * len);
+ fout.position(outPos).limit(outPos + elementSz * len);
+ fout.put(fin);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+
+ // Clear the output buffer to rewind it and reset its
+ // limit from where we shortened it above.
+ outVb.data.clear();
+ }
+
+ /**
+ * Creates a {@link Buffer} that satisfies the given type and size requirements
+ * of the parameters. The buffer will be of the type specified by
+ * {@link Format format} and would be able to contain the given number
+ * of elements with the given number of components in each element.
+ */
+ public static Buffer createBuffer(Format format, int components, int numElements){
+ if (components < 1 || components > 4)
+ throw new IllegalArgumentException("Num components must be between 1 and 4");
+
+ int total = numElements * components;
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ return BufferUtils.createByteBuffer(total);
+ case Half:
+ return BufferUtils.createByteBuffer(total * 2);
+ case Short:
+ case UnsignedShort:
+ return BufferUtils.createShortBuffer(total);
+ case Int:
+ case UnsignedInt:
+ return BufferUtils.createIntBuffer(total);
+ case Float:
+ return BufferUtils.createFloatBuffer(total);
+ case Double:
+ return BufferUtils.createDoubleBuffer(total);
+ default:
+ throw new UnsupportedOperationException("Unrecoginized buffer format: "+format);
+ }
+ }
+
+ /**
+ * Creates a deep clone of the {@link VertexBuffer}.
+ *
+ * @return Deep clone of this buffer
+ */
+ @Override
+ public VertexBuffer clone(){
+ // NOTE: Superclass GLObject automatically creates shallow clone
+ // e.g re-use ID.
+ VertexBuffer vb = (VertexBuffer) super.clone();
+ vb.handleRef = new Object();
+ vb.id = -1;
+ if (data != null) {
+ // Make sure to pass a read-only buffer to clone so that
+ // the position information doesn't get clobbered by another
+ // reading thread during cloning (and vice versa) since this is
+ // a purely read-only operation.
+ vb.updateData(BufferUtils.clone(getDataReadOnly()));
+ }
+
+ return vb;
+ }
+
+ /**
+ * Creates a deep clone of this VertexBuffer but overrides the
+ * {@link Type}.
+ *
+ * @param overrideType The type of the cloned VertexBuffer
+ * @return A deep clone of the buffer
+ */
+ public VertexBuffer clone(Type overrideType){
+ VertexBuffer vb = new VertexBuffer(overrideType);
+ vb.components = components;
+ vb.componentsLength = componentsLength;
+
+ // Make sure to pass a read-only buffer to clone so that
+ // the position information doesn't get clobbered by another
+ // reading thread during cloning (and vice versa) since this is
+ // a purely read-only operation.
+ vb.data = BufferUtils.clone(getDataReadOnly());
+ vb.format = format;
+ vb.handleRef = new Object();
+ vb.id = -1;
+ vb.normalized = normalized;
+ vb.offset = offset;
+ vb.stride = stride;
+ vb.updateNeeded = true;
+ vb.usage = usage;
+ return vb;
+ }
+
+ @Override
+ public String toString(){
+ String dataTxt = null;
+ if (data != null){
+ dataTxt = ", elements="+data.capacity();
+ }
+ return getClass().getSimpleName() + "[fmt="+format.name()
+ +", type="+bufType.name()
+ +", usage="+usage.name()
+ +dataTxt+"]";
+ }
+
+ @Override
+ public void resetObject() {
+// assert this.id != -1;
+ this.id = -1;
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((Renderer)rendererObject).deleteBuffer(this);
+ }
+
+ @Override
+ public NativeObject createDestructableClone(){
+ return new VertexBuffer(id);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(components, "components", 0);
+ oc.write(usage, "usage", Usage.Dynamic);
+ oc.write(bufType, "buffer_type", null);
+ oc.write(format, "format", Format.Float);
+ oc.write(normalized, "normalized", false);
+ oc.write(offset, "offset", 0);
+ oc.write(stride, "stride", 0);
+
+ String dataName = "data" + format.name();
+ Buffer roData = getDataReadOnly();
+ switch (format){
+ case Float:
+ oc.write((FloatBuffer) roData, dataName, null);
+ break;
+ case Short:
+ case UnsignedShort:
+ oc.write((ShortBuffer) roData, dataName, null);
+ break;
+ case UnsignedByte:
+ case Byte:
+ case Half:
+ oc.write((ByteBuffer) roData, dataName, null);
+ break;
+ case Int:
+ case UnsignedInt:
+ oc.write((IntBuffer) roData, dataName, null);
+ break;
+ default:
+ throw new IOException("Unsupported export buffer format: "+format);
+ }
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ components = ic.readInt("components", 0);
+ usage = ic.readEnum("usage", Usage.class, Usage.Dynamic);
+ bufType = ic.readEnum("buffer_type", Type.class, null);
+ format = ic.readEnum("format", Format.class, Format.Float);
+ normalized = ic.readBoolean("normalized", false);
+ offset = ic.readInt("offset", 0);
+ stride = ic.readInt("stride", 0);
+ componentsLength = components * format.getComponentSize();
+
+ String dataName = "data" + format.name();
+ switch (format){
+ case Float:
+ data = ic.readFloatBuffer(dataName, null);
+ break;
+ case Short:
+ case UnsignedShort:
+ data = ic.readShortBuffer(dataName, null);
+ break;
+ case UnsignedByte:
+ case Byte:
+ case Half:
+ data = ic.readByteBuffer(dataName, null);
+ break;
+ case Int:
+ case UnsignedInt:
+ data = ic.readIntBuffer(dataName, null);
+ break;
+ default:
+ throw new IOException("Unsupported import buffer format: "+format);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/AbstractControl.java b/engine/src/core/com/jme3/scene/control/AbstractControl.java
new file mode 100644
index 0000000..2887ec9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/AbstractControl.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * An abstract implementation of the Control interface.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class AbstractControl implements Control {
+
+ protected boolean enabled = true;
+ protected Spatial spatial;
+
+ public AbstractControl(){
+ }
+
+ public void setSpatial(Spatial spatial) {
+ if (this.spatial != null && spatial != null) {
+ throw new IllegalStateException("This control has already been added to a Spatial");
+ }
+ this.spatial = spatial;
+ }
+
+ public Spatial getSpatial(){
+ return spatial;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * To be implemented in subclass.
+ */
+ protected abstract void controlUpdate(float tpf);
+
+ /**
+ * To be implemented in subclass.
+ */
+ protected abstract void controlRender(RenderManager rm, ViewPort vp);
+
+ public void update(float tpf) {
+ if (!enabled)
+ return;
+
+ controlUpdate(tpf);
+ }
+
+ public void render(RenderManager rm, ViewPort vp) {
+ if (!enabled)
+ return;
+
+ controlRender(rm, vp);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(enabled, "enabled", true);
+ oc.write(spatial, "spatial", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ enabled = ic.readBoolean("enabled", true);
+ spatial = (Spatial) ic.readSavable("spatial", null);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/AreaUtils.java b/engine/src/core/com/jme3/scene/control/AreaUtils.java
new file mode 100644
index 0000000..47e7c5f
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/AreaUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.FastMath;
+
+/**
+ * <code>AreaUtils</code> is used to calculate the area of various objects, such as bounding volumes. These
+ * functions are very loose approximations.
+ * @author Joshua Slack
+ * @version $Id: AreaUtils.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+
+public class AreaUtils {
+
+ /**
+ * calcScreenArea -- in Pixels
+ * Aproximates the screen area of a bounding volume. If the volume isn't a
+ * BoundingSphere, BoundingBox, or OrientedBoundingBox 0 is returned.
+ *
+ * @param bound The bounds to calculate the volume from.
+ * @param distance The distance from camera to object.
+ * @param screenWidth The width of the screen.
+ * @return The area in pixels on the screen of the bounding volume.
+ */
+ public static float calcScreenArea(BoundingVolume bound, float distance, float screenWidth) {
+ if (bound.getType() == BoundingVolume.Type.Sphere){
+ return calcScreenArea((BoundingSphere) bound, distance, screenWidth);
+ }else if (bound.getType() == BoundingVolume.Type.AABB){
+ return calcScreenArea((BoundingBox) bound, distance, screenWidth);
+ }
+ return 0.0f;
+ }
+
+ private static float calcScreenArea(BoundingSphere bound, float distance, float screenWidth) {
+ // Where is the center point and a radius point that lies in a plan parallel to the view plane?
+// // Calc radius based on these two points and plug into circle area formula.
+// Vector2f centerSP = null;
+// Vector2f outerSP = null;
+// float radiusSq = centerSP.subtract(outerSP).lengthSquared();
+ float radius = (bound.getRadius() * screenWidth) / (distance * 2);
+ return radius * radius * FastMath.PI;
+ }
+
+ private static float calcScreenArea(BoundingBox bound, float distance, float screenWidth) {
+ // Calc as if we are a BoundingSphere for now...
+ float radiusSquare = bound.getXExtent() * bound.getXExtent()
+ + bound.getYExtent() * bound.getYExtent()
+ + bound.getZExtent() * bound.getZExtent();
+ return ((radiusSquare * screenWidth * screenWidth) / (distance * distance * 4)) * FastMath.PI;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/BillboardControl.java b/engine/src/core/com/jme3/scene/control/BillboardControl.java
new file mode 100644
index 0000000..40c2fea
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/BillboardControl.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+public class BillboardControl extends AbstractControl {
+
+ private Matrix3f orient;
+ private Vector3f look;
+ private Vector3f left;
+ private Alignment alignment;
+
+ /**
+ * Determines how the billboard is aligned to the screen/camera.
+ */
+ public enum Alignment {
+ /**
+ * Aligns this Billboard to the screen.
+ */
+ Screen,
+
+ /**
+ * Aligns this Billboard to the camera position.
+ */
+ Camera,
+
+ /**
+ * Aligns this Billboard to the screen, but keeps the Y axis fixed.
+ */
+ AxialY,
+
+ /**
+ * Aligns this Billboard to the screen, but keeps the Z axis fixed.
+ */
+ AxialZ;
+ }
+
+ public BillboardControl() {
+ super();
+ orient = new Matrix3f();
+ look = new Vector3f();
+ left = new Vector3f();
+ alignment = Alignment.Screen;
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ BillboardControl control = new BillboardControl();
+ control.alignment = this.alignment;
+ control.setSpatial(spatial);
+ return control;
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ Camera cam = vp.getCamera();
+ rotateBillboard(cam);
+ }
+
+ private void fixRefreshFlags(){
+ // force transforms to update below this node
+ spatial.updateGeometricState();
+
+ // force world bound to update
+ Spatial rootNode = spatial;
+ while (rootNode.getParent() != null){
+ rootNode = rootNode.getParent();
+ }
+ rootNode.getWorldBound();
+ }
+
+ /**
+ * rotate the billboard based on the type set
+ *
+ * @param cam
+ * Camera
+ */
+ private void rotateBillboard(Camera cam) {
+ switch (alignment) {
+ case AxialY:
+ rotateAxial(cam, Vector3f.UNIT_Y);
+ break;
+ case AxialZ:
+ rotateAxial(cam, Vector3f.UNIT_Z);
+ break;
+ case Screen:
+ rotateScreenAligned(cam);
+ break;
+ case Camera:
+ rotateCameraAligned(cam);
+ break;
+ }
+ }
+
+ /**
+ * Aligns this Billboard so that it points to the camera position.
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateCameraAligned(Camera camera) {
+ look.set(camera.getLocation()).subtractLocal(
+ spatial.getWorldTranslation());
+ // coopt left for our own purposes.
+ Vector3f xzp = left;
+ // The xzp vector is the projection of the look vector on the xz plane
+ xzp.set(look.x, 0, look.z);
+
+ // check for undefined rotation...
+ if (xzp.equals(Vector3f.ZERO)) {
+ return;
+ }
+
+ look.normalizeLocal();
+ xzp.normalizeLocal();
+ float cosp = look.dot(xzp);
+
+ // compute the local orientation matrix for the billboard
+ orient.set(0, 0, xzp.z);
+ orient.set(0, 1, xzp.x * -look.y);
+ orient.set(0, 2, xzp.x * cosp);
+ orient.set(1, 0, 0);
+ orient.set(1, 1, cosp);
+ orient.set(1, 2, look.y);
+ orient.set(2, 0, -xzp.x);
+ orient.set(2, 1, xzp.z * -look.y);
+ orient.set(2, 2, xzp.z * cosp);
+
+ // The billboard must be oriented to face the camera before it is
+ // transformed into the world.
+ spatial.setLocalRotation(orient);
+ fixRefreshFlags();
+ }
+
+ /**
+ * Rotate the billboard so it points directly opposite the direction the
+ * camera's facing
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateScreenAligned(Camera camera) {
+ // coopt diff for our in direction:
+ look.set(camera.getDirection()).negateLocal();
+ // coopt loc for our left direction:
+ left.set(camera.getLeft()).negateLocal();
+ orient.fromAxes(left, camera.getUp(), look);
+ Node parent = spatial.getParent();
+ Quaternion rot=new Quaternion().fromRotationMatrix(orient);
+ if ( parent != null ) {
+ rot = parent.getWorldRotation().inverse().multLocal(rot);
+ rot.normalizeLocal();
+ }
+ spatial.setLocalRotation(rot);
+ fixRefreshFlags();
+ }
+
+ /**
+ * Rotate the billboard towards the camera, but keeping a given axis fixed.
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateAxial(Camera camera, Vector3f axis) {
+ // Compute the additional rotation required for the billboard to face
+ // the camera. To do this, the camera must be inverse-transformed into
+ // the model space of the billboard.
+ look.set(camera.getLocation()).subtractLocal(
+ spatial.getWorldTranslation());
+ spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own
+ // purposes.
+ left.x *= 1.0f / spatial.getWorldScale().x;
+ left.y *= 1.0f / spatial.getWorldScale().y;
+ left.z *= 1.0f / spatial.getWorldScale().z;
+
+ // squared length of the camera projection in the xz-plane
+ float lengthSquared = left.x * left.x + left.z * left.z;
+ if (lengthSquared < FastMath.FLT_EPSILON) {
+ // camera on the billboard axis, rotation not defined
+ return;
+ }
+
+ // unitize the projection
+ float invLength = FastMath.invSqrt(lengthSquared);
+ if (axis.y == 1) {
+ left.x *= invLength;
+ left.y = 0.0f;
+ left.z *= invLength;
+
+ // compute the local orientation matrix for the billboard
+ orient.set(0, 0, left.z);
+ orient.set(0, 1, 0);
+ orient.set(0, 2, left.x);
+ orient.set(1, 0, 0);
+ orient.set(1, 1, 1);
+ orient.set(1, 2, 0);
+ orient.set(2, 0, -left.x);
+ orient.set(2, 1, 0);
+ orient.set(2, 2, left.z);
+ } else if (axis.z == 1) {
+ left.x *= invLength;
+ left.y *= invLength;
+ left.z = 0.0f;
+
+ // compute the local orientation matrix for the billboard
+ orient.set(0, 0, left.y);
+ orient.set(0, 1, left.x);
+ orient.set(0, 2, 0);
+ orient.set(1, 0, -left.y);
+ orient.set(1, 1, left.x);
+ orient.set(1, 2, 0);
+ orient.set(2, 0, 0);
+ orient.set(2, 1, 0);
+ orient.set(2, 2, 1);
+ }
+
+ // The billboard must be oriented to face the camera before it is
+ // transformed into the world.
+ spatial.setLocalRotation(orient);
+ fixRefreshFlags();
+ }
+
+ /**
+ * Returns the alignment this Billboard is set too.
+ *
+ * @return The alignment of rotation, AxialY, AxialZ, Camera or Screen.
+ */
+ public Alignment getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Sets the type of rotation this Billboard will have. The alignment can
+ * be Camera, Screen, AxialY, or AxialZ. Invalid alignments will
+ * assume no billboard rotation.
+ */
+ public void setAlignment(Alignment alignment) {
+ this.alignment = alignment;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(orient, "orient", null);
+ capsule.write(look, "look", null);
+ capsule.write(left, "left", null);
+ capsule.write(alignment, "alignment", Alignment.Screen);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ orient = (Matrix3f) capsule.readSavable("orient", null);
+ look = (Vector3f) capsule.readSavable("look", null);
+ left = (Vector3f) capsule.readSavable("left", null);
+ alignment = capsule.readEnum("alignment", Alignment.class, Alignment.Screen);
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/control/CameraControl.java b/engine/src/core/com/jme3/scene/control/CameraControl.java
new file mode 100644
index 0000000..5dd559c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/CameraControl.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * This Control maintains a reference to a Camera,
+ * which will be synched with the position (worldTranslation)
+ * of the current spatial.
+ * @author tim
+ */
+public class CameraControl extends AbstractControl {
+
+ public static enum ControlDirection {
+
+ /**
+ * Means, that the Camera's transform is "copied"
+ * to the Transform of the Spatial.
+ */
+ CameraToSpatial,
+ /**
+ * Means, that the Spatial's transform is "copied"
+ * to the Transform of the Camera.
+ */
+ SpatialToCamera;
+ }
+ private Camera camera;
+ private ControlDirection controlDir = ControlDirection.CameraToSpatial;
+
+ /**
+ * Constructor used for Serialization.
+ */
+ public CameraControl() {
+ }
+
+ /**
+ * @param camera The Camera to be synced.
+ */
+ public CameraControl(Camera camera) {
+ this.camera = camera;
+ }
+
+ /**
+ * @param camera The Camera to be synced.
+ */
+ public CameraControl(Camera camera, ControlDirection controlDir) {
+ this.camera = camera;
+ this.controlDir = controlDir;
+ }
+
+ public Camera getCamera() {
+ return camera;
+ }
+
+ public void setCamera(Camera camera) {
+ this.camera = camera;
+ }
+
+ public ControlDirection getControlDir() {
+ return controlDir;
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ this.controlDir = controlDir;
+ }
+
+ // fields used, when inversing ControlDirection:
+ @Override
+ protected void controlUpdate(float tpf) {
+ if (spatial != null && camera != null) {
+ switch (controlDir) {
+ case SpatialToCamera:
+ camera.setLocation(spatial.getWorldTranslation());
+ camera.setRotation(spatial.getWorldRotation());
+ break;
+ case CameraToSpatial:
+ // set the localtransform, so that the worldtransform would be equal to the camera's transform.
+ // Location:
+ TempVars vars = TempVars.get();
+
+ Vector3f vecDiff = vars.vect1.set(camera.getLocation()).subtractLocal(spatial.getWorldTranslation());
+ spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+
+ // Rotation:
+ Quaternion worldDiff = vars.quat1.set(camera.getRotation()).subtractLocal(spatial.getWorldRotation());
+ spatial.setLocalRotation(worldDiff.addLocal(spatial.getLocalRotation()));
+ vars.release();
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ // nothing to do
+ }
+
+ @Override
+ public Control cloneForSpatial(Spatial newSpatial) {
+ CameraControl control = new CameraControl(camera, controlDir);
+ control.setSpatial(newSpatial);
+ control.setEnabled(isEnabled());
+ return control;
+ }
+ private static final String CONTROL_DIR_NAME = "controlDir";
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ im.getCapsule(this).readEnum(CONTROL_DIR_NAME,
+ ControlDirection.class, ControlDirection.SpatialToCamera);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ ex.getCapsule(this).write(controlDir, CONTROL_DIR_NAME,
+ ControlDirection.SpatialToCamera);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/Control.java b/engine/src/core/com/jme3/scene/control/Control.java
new file mode 100644
index 0000000..b5b0057
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/Control.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.Savable;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+
+/**
+ * An interface for scene-graph controls.
+ * <p>
+ * <code>Control</code>s are used to specify certain update and render logic
+ * for a {@link Spatial}.
+ *
+ * @author Kirill Vainer
+ */
+public interface Control extends Savable {
+
+ /**
+ * Creates a clone of the Control, the given Spatial is the cloned
+ * version of the spatial to which this control is attached to.
+ * @param spatial
+ * @return A clone of this control for the spatial
+ */
+ public Control cloneForSpatial(Spatial spatial);
+
+ /**
+ * @param spatial the spatial to be controlled. This should not be called
+ * from user code.
+ */
+ public void setSpatial(Spatial spatial);
+
+ /**
+ * @param enabled Enable or disable the control. If disabled, update()
+ * should do nothing.
+ */
+ public void setEnabled(boolean enabled);
+
+ /**
+ * @return True if enabled, false otherwise.
+ * @see Control#setEnabled(boolean)
+ */
+ public boolean isEnabled();
+
+ /**
+ * Updates the control. This should not be called from user code.
+ * @param tpf Time per frame.
+ */
+ public void update(float tpf);
+
+ /**
+ * Should be called prior to queuing the spatial by the RenderManager. This
+ * should not be called from user code.
+ *
+ * @param rm
+ * @param vp
+ */
+ public void render(RenderManager rm, ViewPort vp);
+}
diff --git a/engine/src/core/com/jme3/scene/control/LightControl.java b/engine/src/core/com/jme3/scene/control/LightControl.java
new file mode 100644
index 0000000..2859c9b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/LightControl.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * This Control maintains a reference to a Camera,
+ * which will be synched with the position (worldTranslation)
+ * of the current spatial.
+ * @author tim
+ */
+public class LightControl extends AbstractControl {
+
+ public static enum ControlDirection {
+
+ /**
+ * Means, that the Light's transform is "copied"
+ * to the Transform of the Spatial.
+ */
+ LightToSpatial,
+ /**
+ * Means, that the Spatial's transform is "copied"
+ * to the Transform of the light.
+ */
+ SpatialToLight;
+ }
+ private Light light;
+ private ControlDirection controlDir = ControlDirection.SpatialToLight;
+
+ /**
+ * Constructor used for Serialization.
+ */
+ public LightControl() {
+ }
+
+ /**
+ * @param light The light to be synced.
+ */
+ public LightControl(Light light) {
+ this.light = light;
+ }
+
+ /**
+ * @param light The light to be synced.
+ */
+ public LightControl(Light light, ControlDirection controlDir) {
+ this.light = light;
+ this.controlDir = controlDir;
+ }
+
+ public Light getLight() {
+ return light;
+ }
+
+ public void setLight(Light light) {
+ this.light = light;
+ }
+
+ public ControlDirection getControlDir() {
+ return controlDir;
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ this.controlDir = controlDir;
+ }
+
+ // fields used, when inversing ControlDirection:
+ @Override
+ protected void controlUpdate(float tpf) {
+ if (spatial != null && light != null) {
+ switch (controlDir) {
+ case SpatialToLight:
+ spatialTolight(light);
+ break;
+ case LightToSpatial:
+ lightToSpatial(light);
+ break;
+ }
+ }
+ }
+
+ private void spatialTolight(Light light) {
+ if (light instanceof PointLight) {
+ ((PointLight) light).setPosition(spatial.getWorldTranslation());
+ }
+ TempVars vars = TempVars.get();
+
+ if (light instanceof DirectionalLight) {
+ ((DirectionalLight) light).setDirection(vars.vect1.set(spatial.getWorldTranslation()).multLocal(-1.0f));
+ }
+ vars.release();
+ //TODO add code for Spot light here when it's done
+// if( light instanceof SpotLight){
+// ((SpotLight)light).setPosition(spatial.getWorldTranslation());
+// ((SpotLight)light).setRotation(spatial.getWorldRotation());
+// }
+
+ }
+
+ private void lightToSpatial(Light light) {
+ TempVars vars = TempVars.get();
+ if (light instanceof PointLight) {
+
+ PointLight pLight = (PointLight) light;
+
+ Vector3f vecDiff = vars.vect1.set(pLight.getPosition()).subtractLocal(spatial.getWorldTranslation());
+ spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+ }
+
+ if (light instanceof DirectionalLight) {
+ DirectionalLight dLight = (DirectionalLight) light;
+ vars.vect1.set(dLight.getDirection()).multLocal(-1.0f);
+ Vector3f vecDiff = vars.vect1.subtractLocal(spatial.getWorldTranslation());
+ spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+ }
+ vars.release();
+ //TODO add code for Spot light here when it's done
+
+
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ // nothing to do
+ }
+
+ @Override
+ public Control cloneForSpatial(Spatial newSpatial) {
+ LightControl control = new LightControl(light, controlDir);
+ control.setSpatial(newSpatial);
+ control.setEnabled(isEnabled());
+ return control;
+ }
+ private static final String CONTROL_DIR_NAME = "controlDir";
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ im.getCapsule(this).readEnum(CONTROL_DIR_NAME,
+ ControlDirection.class, ControlDirection.SpatialToLight);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ ex.getCapsule(this).write(controlDir, CONTROL_DIR_NAME,
+ ControlDirection.SpatialToLight);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/LodControl.java b/engine/src/core/com/jme3/scene/control/LodControl.java
new file mode 100644
index 0000000..6cbfefb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/LodControl.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Determines what Level of Detail a spatial should be, based on how many pixels
+ * on the screen the spatial is taking up. The more pixels covered, the more detailed
+ * the spatial should be.
+ * It calculates the area of the screen that the spatial covers by using its bounding box.
+ * When initializing, it will ask the spatial for how many triangles it has for each LOD.
+ * It then uses that, along with the trisPerPixel value to determine what LOD it should be at.
+ * It requires the camera to do this.
+ * The controlRender method is called each frame and will update the spatial's LOD
+ * if the camera has moved by a specified amount.
+ */
+public class LodControl extends AbstractControl implements Cloneable {
+
+ private float trisPerPixel = 1f;
+ private float distTolerance = 1f;
+ private float lastDistance = 0f;
+ private int lastLevel = 0;
+ private int numLevels;
+ private int[] numTris;
+
+ /**
+ * Creates a new <code>LodControl</code>.
+ */
+ public LodControl(){
+ }
+
+ /**
+ * Returns the distance tolerance for changing LOD.
+ *
+ * @return the distance tolerance for changing LOD.
+ *
+ * @see #setDistTolerance(float)
+ */
+ public float getDistTolerance() {
+ return distTolerance;
+ }
+
+ /**
+ * Specifies the distance tolerance for changing the LOD level on the geometry.
+ * The LOD level will only get changed if the geometry has moved this
+ * distance beyond the current LOD level.
+ *
+ * @param distTolerance distance tolerance for changing LOD
+ */
+ public void setDistTolerance(float distTolerance) {
+ this.distTolerance = distTolerance;
+ }
+
+ /**
+ * Returns the triangles per pixel value.
+ *
+ * @return the triangles per pixel value.
+ *
+ * @see #setTrisPerPixel(float)
+ */
+ public float getTrisPerPixel() {
+ return trisPerPixel;
+ }
+
+ /**
+ * Sets the triangles per pixel value.
+ * The <code>LodControl</code> will use this value as an error metric
+ * to determine which LOD level to use based on the geometry's
+ * area on the screen.
+ *
+ * @param trisPerPixel triangles per pixel
+ */
+ public void setTrisPerPixel(float trisPerPixel) {
+ this.trisPerPixel = trisPerPixel;
+ }
+
+ @Override
+ public void setSpatial(Spatial spatial){
+ if (!(spatial instanceof Geometry))
+ throw new IllegalArgumentException("LodControl can only be attached to Geometry!");
+
+ super.setSpatial(spatial);
+ Geometry geom = (Geometry) spatial;
+ Mesh mesh = geom.getMesh();
+ numLevels = mesh.getNumLodLevels();
+ numTris = new int[numLevels];
+ for (int i = numLevels - 1; i >= 0; i--)
+ numTris[i] = mesh.getTriangleCount(i);
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ try {
+ LodControl clone = (LodControl) super.clone();
+ clone.lastDistance = 0;
+ clone.lastLevel = 0;
+ clone.numTris = numTris != null ? numTris.clone() : null;
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ }
+
+ protected void controlRender(RenderManager rm, ViewPort vp){
+ BoundingVolume bv = spatial.getWorldBound();
+
+ Camera cam = vp.getCamera();
+ float atanNH = FastMath.atan(cam.getFrustumNear() * cam.getFrustumTop());
+ float ratio = (FastMath.PI / (8f * atanNH));
+ float newDistance = bv.distanceTo(vp.getCamera().getLocation()) / ratio;
+ int level;
+
+ if (Math.abs(newDistance - lastDistance) <= distTolerance)
+ level = lastLevel; // we haven't moved relative to the model, send the old measurement back.
+ else if (lastDistance > newDistance && lastLevel == 0)
+ level = lastLevel; // we're already at the lowest setting and we just got closer to the model, no need to keep trying.
+ else if (lastDistance < newDistance && lastLevel == numLevels - 1)
+ level = lastLevel; // we're already at the highest setting and we just got further from the model, no need to keep trying.
+ else{
+ lastDistance = newDistance;
+
+ // estimate area of polygon via bounding volume
+ float area = AreaUtils.calcScreenArea(bv, lastDistance, cam.getWidth());
+ float trisToDraw = area * trisPerPixel;
+ level = numLevels - 1;
+ for (int i = numLevels; --i >= 0;){
+ if (trisToDraw - numTris[i] < 0){
+ break;
+ }
+ level = i;
+ }
+ lastLevel = level;
+ }
+
+ spatial.setLodLevel(level);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException{
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(trisPerPixel, "trisPerPixel", 1f);
+ oc.write(distTolerance, "distTolerance", 1f);
+ oc.write(numLevels, "numLevels", 0);
+ oc.write(numTris, "numTris", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException{
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ trisPerPixel = ic.readFloat("trisPerPixel", 1f);
+ distTolerance = ic.readFloat("distTolerance", 1f);
+ numLevels = ic.readInt("numLevels", 0);
+ numTris = ic.readIntArray("numTris", null);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/UpdateControl.java b/engine/src/core/com/jme3/scene/control/UpdateControl.java
new file mode 100644
index 0000000..7fbca01
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/UpdateControl.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.app.AppTask;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+
+/**
+ * Allows for enqueueing tasks onto the update loop / rendering thread.
+ *
+ * Usage:
+ * mySpatial.addControl(new UpdateControl()); // add it once
+ * mySpatial.getControl(UpdateControl.class).enqueue(new Callable() {
+ * public Object call() throws Exception {
+ * // do stuff here
+ * return null;
+ * }
+ * });
+ *
+ * @author Brent Owens
+ */
+public class UpdateControl extends AbstractControl {
+
+ private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
+
+ /**
+ * Enqueues a task/callable object to execute in the jME3
+ * rendering thread.
+ */
+ public <V> Future<V> enqueue(Callable<V> callable) {
+ AppTask<V> task = new AppTask<V>(callable);
+ taskQueue.add(task);
+ return task;
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ AppTask<?> task = taskQueue.poll();
+ toploop: do {
+ if (task == null) break;
+ while (task.isCancelled()) {
+ task = taskQueue.poll();
+ if (task == null) break toploop;
+ }
+ task.invoke();
+ } while (((task = taskQueue.poll()) != null));
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+
+ }
+
+ public Control cloneForSpatial(Spatial newSpatial) {
+ UpdateControl control = new UpdateControl();
+ control.setSpatial(newSpatial);
+ control.setEnabled(isEnabled());
+ control.taskQueue.addAll(taskQueue);
+ return control;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/package.html b/engine/src/core/com/jme3/scene/control/package.html
new file mode 100644
index 0000000..a387840
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.control</code> package provides
+{@link com.jme3.scene.control.Control controls}.
+Controls represent the "logical" programming of scene graph elements, containing
+callbacks for when a {@link com.jme3.scene.Spatial} is rendered or updated
+by the engine.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/debug/Arrow.java b/engine/src/core/com/jme3/scene/debug/Arrow.java
new file mode 100644
index 0000000..1c3dd94
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/Arrow.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.debug;
+
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import java.nio.FloatBuffer;
+
+/**
+ * The <code>Arrow</code> debug shape represents an arrow.
+ * An arrow is simply a line going from the original toward an extent
+ * and at the tip there will be triangle-like shape.
+ *
+ * @author Kirill Vainer
+ */
+public class Arrow extends Mesh {
+
+ private Quaternion tempQuat = new Quaternion();
+ private Vector3f tempVec = new Vector3f();
+
+ private static final float[] positions = new float[]{
+ 0, 0, 0,
+ 0, 0, 1, // tip
+ 0.05f, 0, 0.9f, // tip right
+ -0.05f, 0, 0.9f, // tip left
+ 0, 0.05f, 0.9f, // tip top
+ 0, -0.05f, 0.9f, // tip buttom
+ };
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Arrow() {
+ }
+
+ /**
+ * Creates an arrow mesh with the given extent.
+ * The arrow will start at the origin (0,0,0) and finish
+ * at the given extent.
+ *
+ * @param extent Extent of the arrow from origin
+ */
+ public Arrow(Vector3f extent) {
+ float len = extent.length();
+ Vector3f dir = extent.normalize();
+
+ tempQuat.lookAt(dir, Vector3f.UNIT_Y);
+ tempQuat.normalizeLocal();
+
+ float[] newPositions = new float[positions.length];
+ for (int i = 0; i < positions.length; i += 3) {
+ Vector3f vec = tempVec.set(positions[i],
+ positions[i + 1],
+ positions[i + 2]);
+ vec.multLocal(len);
+ tempQuat.mult(vec, vec);
+
+ newPositions[i] = vec.getX();
+ newPositions[i + 1] = vec.getY();
+ newPositions[i + 2] = vec.getZ();
+ }
+
+ setBuffer(Type.Position, 3, newPositions);
+ setBuffer(Type.Index, 2,
+ new short[]{
+ 0, 1,
+ 1, 2,
+ 1, 3,
+ 1, 4,
+ 1, 5,});
+ setMode(Mode.Lines);
+
+ updateBound();
+ updateCounts();
+ }
+
+ /**
+ * Sets the arrow's extent.
+ * This will modify the buffers on the mesh.
+ *
+ * @param extent the arrow's extent.
+ */
+ public void setArrowExtent(Vector3f extent) {
+ float len = extent.length();
+// Vector3f dir = extent.normalize();
+
+ tempQuat.lookAt(extent, Vector3f.UNIT_Y);
+ tempQuat.normalizeLocal();
+
+ VertexBuffer pvb = getBuffer(Type.Position);
+ FloatBuffer buffer = (FloatBuffer)pvb.getData();
+ buffer.rewind();
+ for (int i = 0; i < positions.length; i += 3) {
+ Vector3f vec = tempVec.set(positions[i],
+ positions[i + 1],
+ positions[i + 2]);
+ vec.multLocal(len);
+ tempQuat.mult(vec, vec);
+
+ buffer.put(vec.x);
+ buffer.put(vec.y);
+ buffer.put(vec.z);
+ }
+
+ pvb.updateData(buffer);
+
+ updateBound();
+ updateCounts();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/Grid.java b/engine/src/core/com/jme3/scene/debug/Grid.java
new file mode 100644
index 0000000..ea6225c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/Grid.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Simple grid shape.
+ *
+ * @author Kirill Vainer
+ */
+public class Grid extends Mesh {
+
+ /**
+ * Creates a grid debug shape.
+ * @param xLines
+ * @param yLines
+ * @param lineDist
+ */
+ public Grid(int xLines, int yLines, float lineDist){
+ xLines -= 2;
+ yLines -= 2;
+ int lineCount = xLines + yLines + 4;
+
+ FloatBuffer fpb = BufferUtils.createFloatBuffer(6 * lineCount);
+ ShortBuffer sib = BufferUtils.createShortBuffer(2 * lineCount);
+
+ float xLineLen = (yLines + 1) * lineDist;
+ float yLineLen = (xLines + 1) * lineDist;
+ int curIndex = 0;
+
+ // add lines along X
+ for (int i = 0; i < xLines + 2; i++){
+ float y = (i) * lineDist;
+
+ // positions
+ fpb.put(0) .put(0).put(y);
+ fpb.put(xLineLen).put(0).put(y);
+
+ // indices
+ sib.put( (short) (curIndex++) );
+ sib.put( (short) (curIndex++) );
+ }
+
+ // add lines along Y
+ for (int i = 0; i < yLines + 2; i++){
+ float x = (i) * lineDist;
+
+ // positions
+ fpb.put(x).put(0).put(0);
+ fpb.put(x).put(0).put(yLineLen);
+
+ // indices
+ sib.put( (short) (curIndex++) );
+ sib.put( (short) (curIndex++) );
+ }
+
+ fpb.flip();
+ sib.flip();
+
+ setBuffer(Type.Position, 3, fpb);
+ setBuffer(Type.Index, 2, sib);
+
+ setMode(Mode.Lines);
+
+ updateBound();
+ updateCounts();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java
new file mode 100644
index 0000000..07efdc9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Skeleton;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+
+public class SkeletonDebugger extends Node {
+
+ private SkeletonWire wires;
+ private SkeletonPoints points;
+ private Skeleton skeleton;
+
+ public SkeletonDebugger(String name, Skeleton skeleton){
+ super(name);
+
+ this.skeleton = skeleton;
+ wires = new SkeletonWire(skeleton);
+ points = new SkeletonPoints(skeleton);
+
+ attachChild(new Geometry(name+"_wires", wires));
+ attachChild(new Geometry(name+"_points", points));
+
+ setQueueBucket(Bucket.Transparent);
+ }
+
+ public SkeletonDebugger(){
+ }
+
+ @Override
+ public void updateLogicalState(float tpf){
+ super.updateLogicalState(tpf);
+
+// skeleton.resetAndUpdate();
+ wires.updateGeometry();
+ points.updateGeometry();
+ }
+
+ public SkeletonPoints getPoints() {
+ return points;
+ }
+
+ public SkeletonWire getWires() {
+ return wires;
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java
new file mode 100644
index 0000000..2e49ce5
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class SkeletonPoints extends Mesh {
+
+ private Skeleton skeleton;
+
+ public SkeletonPoints(Skeleton skeleton){
+ this.skeleton = skeleton;
+
+ setMode(Mode.Points);
+
+ VertexBuffer pb = new VertexBuffer(Type.Position);
+ FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3);
+ pb.setupData(Usage.Stream, 3, Format.Float, fpb);
+ setBuffer(pb);
+
+ setPointSize(7);
+
+ updateCounts();
+ }
+
+ public void updateGeometry(){
+ VertexBuffer vb = getBuffer(Type.Position);
+ FloatBuffer posBuf = getFloatBuffer(Type.Position);
+ posBuf.clear();
+ for (int i = 0; i < skeleton.getBoneCount(); i++){
+ Bone bone = skeleton.getBone(i);
+ Vector3f bonePos = bone.getModelSpacePosition();
+
+ posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ());
+ }
+ posBuf.flip();
+ vb.updateData(posBuf);
+
+ updateBound();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonWire.java b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java
new file mode 100644
index 0000000..0796334
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class SkeletonWire extends Mesh {
+
+ private int numConnections = 0;
+ private Skeleton skeleton;
+
+ private void countConnections(Bone bone){
+ for (Bone child : bone.getChildren()){
+ numConnections ++;
+ countConnections(child);
+ }
+ }
+
+ private void writeConnections(ShortBuffer indexBuf, Bone bone){
+ for (Bone child : bone.getChildren()){
+ // write myself
+ indexBuf.put( (short) skeleton.getBoneIndex(bone) );
+ // write the child
+ indexBuf.put( (short) skeleton.getBoneIndex(child) );
+
+ writeConnections(indexBuf, child);
+ }
+ }
+
+ public SkeletonWire(Skeleton skeleton){
+ this.skeleton = skeleton;
+ for (Bone bone : skeleton.getRoots())
+ countConnections(bone);
+
+ setMode(Mode.Lines);
+
+ VertexBuffer pb = new VertexBuffer(Type.Position);
+ FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3);
+ pb.setupData(Usage.Stream, 3, Format.Float, fpb);
+ setBuffer(pb);
+
+ VertexBuffer ib = new VertexBuffer(Type.Index);
+ ShortBuffer sib = BufferUtils.createShortBuffer(numConnections * 2);
+ ib.setupData(Usage.Static, 2, Format.UnsignedShort, sib);
+ setBuffer(ib);
+
+ for (Bone bone : skeleton.getRoots())
+ writeConnections(sib, bone);
+ sib.flip();
+
+ updateCounts();
+ }
+
+ public void updateGeometry(){
+ VertexBuffer vb = getBuffer(Type.Position);
+ FloatBuffer posBuf = getFloatBuffer(Type.Position);
+ posBuf.clear();
+ for (int i = 0; i < skeleton.getBoneCount(); i++){
+ Bone bone = skeleton.getBone(i);
+ Vector3f bonePos = bone.getModelSpacePosition();
+
+ posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ());
+ }
+ posBuf.flip();
+ vb.updateData(posBuf);
+
+ updateBound();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireBox.java b/engine/src/core/com/jme3/scene/debug/WireBox.java
new file mode 100644
index 0000000..50af28a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireBox.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class WireBox extends Mesh {
+
+ public WireBox(){
+ this(1,1,1);
+ }
+
+ public WireBox(float xExt, float yExt, float zExt){
+ updatePositions(xExt,yExt,zExt);
+ setBuffer(Type.Index, 2,
+ new short[]{
+ 0, 1,
+ 1, 2,
+ 2, 3,
+ 3, 0,
+
+ 4, 5,
+ 5, 6,
+ 6, 7,
+ 7, 4,
+
+ 0, 4,
+ 1, 5,
+ 2, 6,
+ 3, 7,
+ }
+ );
+ setMode(Mode.Lines);
+
+ updateCounts();
+ }
+
+ public void updatePositions(float xExt, float yExt, float zExt){
+ VertexBuffer pvb = getBuffer(Type.Position);
+ FloatBuffer pb;
+ if (pvb == null){
+ pvb = new VertexBuffer(Type.Position);
+ pb = BufferUtils.createVector3Buffer(8);
+ pvb.setupData(Usage.Dynamic, 3, Format.Float, pb);
+ setBuffer(pvb);
+ }else{
+ pb = (FloatBuffer) pvb.getData();
+ pvb.updateData(pb);
+ }
+ pb.rewind();
+ pb.put(
+ new float[]{
+ -xExt, -yExt, zExt,
+ xExt, -yExt, zExt,
+ xExt, yExt, zExt,
+ -xExt, yExt, zExt,
+
+ -xExt, -yExt, -zExt,
+ xExt, -yExt, -zExt,
+ xExt, yExt, -zExt,
+ -xExt, yExt, -zExt,
+ }
+ );
+ updateBound();
+ }
+
+ public void fromBoundingBox(BoundingBox bbox){
+ updatePositions(bbox.getXExtent(), bbox.getYExtent(), bbox.getZExtent());
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireFrustum.java b/engine/src/core/com/jme3/scene/debug/WireFrustum.java
new file mode 100644
index 0000000..7a86d90
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireFrustum.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class WireFrustum extends Mesh {
+
+ public WireFrustum(Vector3f[] points){
+ if (points != null)
+ setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
+
+ setBuffer(Type.Index, 2,
+ new short[]{
+ 0, 1,
+ 1, 2,
+ 2, 3,
+ 3, 0,
+
+ 4, 5,
+ 5, 6,
+ 6, 7,
+ 7, 4,
+
+ 0, 4,
+ 1, 5,
+ 2, 6,
+ 3, 7,
+ }
+ );
+ setMode(Mode.Lines);
+ }
+
+ public void update(Vector3f[] points){
+ VertexBuffer vb = getBuffer(Type.Position);
+ if (vb == null){
+ setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
+ return;
+ }
+
+ FloatBuffer b = BufferUtils.createFloatBuffer(points);
+ FloatBuffer a = (FloatBuffer) vb.getData();
+ b.rewind();
+ a.rewind();
+ a.put(b);
+ a.rewind();
+
+ vb.updateData(a);
+
+ updateBound();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireSphere.java b/engine/src/core/com/jme3/scene/debug/WireSphere.java
new file mode 100644
index 0000000..df863e2
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireSphere.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.debug;
+
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.math.FastMath;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class WireSphere extends Mesh {
+
+ private static final int samples = 30;
+ private static final int zSamples = 10;
+
+ public WireSphere() {
+ this(1);
+ }
+
+ public WireSphere(float radius) {
+ updatePositions(radius);
+ ShortBuffer ib = BufferUtils.createShortBuffer(samples * 2 * 2 + zSamples * samples * 2 /*+ 3 * 2*/);
+ setBuffer(Type.Index, 2, ib);
+
+// ib.put(new byte[]{
+// (byte) 0, (byte) 1,
+// (byte) 2, (byte) 3,
+// (byte) 4, (byte) 5,
+// });
+
+// int curNum = 3 * 2;
+ int curNum = 0;
+ for (int j = 0; j < 2 + zSamples; j++) {
+ for (int i = curNum; i < curNum + samples - 1; i++) {
+ ib.put((short) i).put((short) (i + 1));
+ }
+ ib.put((short) (curNum + samples - 1)).put((short) curNum);
+ curNum += samples;
+ }
+
+ setMode(Mode.Lines);
+
+ updateBound();
+ updateCounts();
+ }
+
+ public void updatePositions(float radius) {
+ VertexBuffer pvb = getBuffer(Type.Position);
+ FloatBuffer pb;
+
+ if (pvb == null) {
+ pvb = new VertexBuffer(Type.Position);
+ pb = BufferUtils.createVector3Buffer(samples * 2 + samples * zSamples /*+ 6 * 3*/);
+ pvb.setupData(Usage.Dynamic, 3, Format.Float, pb);
+ setBuffer(pvb);
+ } else {
+ pb = (FloatBuffer) pvb.getData();
+ }
+
+ pb.rewind();
+
+ // X axis
+// pb.put(radius).put(0).put(0);
+// pb.put(-radius).put(0).put(0);
+//
+// // Y axis
+// pb.put(0).put(radius).put(0);
+// pb.put(0).put(-radius).put(0);
+//
+// // Z axis
+// pb.put(0).put(0).put(radius);
+// pb.put(0).put(0).put(-radius);
+
+ float rate = FastMath.TWO_PI / (float) samples;
+ float angle = 0;
+ for (int i = 0; i < samples; i++) {
+ float x = radius * FastMath.cos(angle);
+ float y = radius * FastMath.sin(angle);
+ pb.put(x).put(y).put(0);
+ angle += rate;
+ }
+
+ angle = 0;
+ for (int i = 0; i < samples; i++) {
+ float x = radius * FastMath.cos(angle);
+ float y = radius * FastMath.sin(angle);
+ pb.put(0).put(x).put(y);
+ angle += rate;
+ }
+
+ float zRate = (radius * 2) / (float) (zSamples);
+ float zHeight = -radius + (zRate / 2f);
+
+
+ float rb = 1f / zSamples;
+ float b = rb / 2f;
+
+ for (int k = 0; k < zSamples; k++) {
+ angle = 0;
+ float scale = FastMath.sin(b * FastMath.PI);
+ for (int i = 0; i < samples; i++) {
+ float x = radius * FastMath.cos(angle);
+ float y = radius * FastMath.sin(angle);
+
+ pb.put(x * scale).put(zHeight).put(y * scale);
+
+ angle += rate;
+ }
+ zHeight += zRate;
+ b += rb;
+ }
+ }
+
+ /**
+ * Create a WireSphere from a BoundingSphere
+ *
+ * @param bsph
+ * BoundingSphere used to create the WireSphere
+ *
+ */
+ public void fromBoundingSphere(BoundingSphere bsph) {
+ updatePositions(bsph.getRadius());
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java
new file mode 100644
index 0000000..5c41c04
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import com.jme3.util.BufferUtils;
+import java.nio.Buffer;
+
+/**
+ * <code>IndexBuffer</code> is an abstraction for integer index buffers,
+ * it is used to retrieve indices without knowing in which format they
+ * are stored (ushort or uint).
+ *
+ * @author lex
+ */
+public abstract class IndexBuffer {
+
+ /**
+ * Creates an index buffer that can contain the given amount
+ * of vertices.
+ * Returns {@link IndexShortBuffer}
+ *
+ * @param vertexCount The amount of vertices to contain
+ * @param indexCount The amount of indices
+ * to contain.
+ * @return A new index buffer
+ */
+ public static IndexBuffer createIndexBuffer(int vertexCount, int indexCount){
+ if (vertexCount > 65535){
+ return new IndexIntBuffer(BufferUtils.createIntBuffer(indexCount));
+ }else{
+ return new IndexShortBuffer(BufferUtils.createShortBuffer(indexCount));
+ }
+ }
+
+ /**
+ * Returns the vertex index for the given index in the index buffer.
+ *
+ * @param i The index inside the index buffer
+ * @return
+ */
+ public abstract int get(int i);
+
+ /**
+ * Puts the vertex index at the index buffer's index.
+ * Implementations may throw an {@link UnsupportedOperationException}
+ * if modifying the IndexBuffer is not supported (e.g. virtual index
+ * buffers).
+ */
+ public abstract void put(int i, int value);
+
+ /**
+ * Returns the size of the index buffer.
+ *
+ * @return the size of the index buffer.
+ */
+ public abstract int size();
+
+ /**
+ * Returns the underlying data-type specific {@link Buffer}.
+ * Implementations may return null if there's no underlying
+ * buffer.
+ *
+ * @return the underlying {@link Buffer}.
+ */
+ public abstract Buffer getBuffer();
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java
new file mode 100644
index 0000000..fd3bec4
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+
+/**
+ * IndexBuffer implementation for {@link ByteBuffer}s.
+ *
+ * @author lex
+ */
+public class IndexByteBuffer extends IndexBuffer {
+
+ private ByteBuffer buf;
+
+ public IndexByteBuffer(ByteBuffer buffer) {
+ this.buf = buffer;
+ }
+
+ @Override
+ public int get(int i) {
+ return buf.get(i) & 0x000000FF;
+ }
+
+ @Override
+ public void put(int i, int value) {
+ buf.put(i, (byte) value);
+ }
+
+ @Override
+ public int size() {
+ return buf.limit();
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return buf;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java
new file mode 100644
index 0000000..3369301
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+
+/**
+ * IndexBuffer implementation for {@link IntBuffer}s.
+ *
+ * @author lex
+ */
+public class IndexIntBuffer extends IndexBuffer {
+
+ private IntBuffer buf;
+
+ public IndexIntBuffer(IntBuffer buffer) {
+ this.buf = buffer;
+ }
+
+ @Override
+ public int get(int i) {
+ return buf.get(i);
+ }
+
+ @Override
+ public void put(int i, int value) {
+ buf.put(i, value);
+ }
+
+ @Override
+ public int size() {
+ return buf.limit();
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return buf;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java
new file mode 100644
index 0000000..5017e6f
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+
+/**
+ * IndexBuffer implementation for {@link ShortBuffer}s.
+ *
+ * @author lex
+ */
+public class IndexShortBuffer extends IndexBuffer {
+
+ private ShortBuffer buf;
+
+ public IndexShortBuffer(ShortBuffer buffer) {
+ this.buf = buffer;
+ }
+
+ @Override
+ public int get(int i) {
+ return buf.get(i) & 0x0000FFFF;
+ }
+
+ @Override
+ public void put(int i, int value) {
+ buf.put(i, (short) value);
+ }
+
+ @Override
+ public int size() {
+ return buf.limit();
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return buf;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java
new file mode 100644
index 0000000..c1499cc
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java
@@ -0,0 +1,106 @@
+package com.jme3.scene.mesh;
+
+import com.jme3.scene.Mesh.Mode;
+import java.nio.Buffer;
+
+/**
+ * IndexBuffer implementation that generates vertex indices sequentially
+ * based on a specific Mesh {@link Mode}.
+ * The generated indices are as if the mesh is in the given mode
+ * but contains no index buffer, thus this implementation will
+ * return the indices if the index buffer was there and contained sequential
+ * triangles.
+ * Example:
+ * <ul>
+ * <li>{@link Mode#Triangles}: 0, 1, 2 | 3, 4, 5 | 6, 7, 8 | ...</li>
+ * <li>{@link Mode#TriangleStrip}: 0, 1, 2 | 2, 1, 3 | 2, 3, 4 | ...</li>
+ * <li>{@link Mode#TriangleFan}: 0, 1, 2 | 0, 2, 3 | 0, 3, 4 | ...</li>
+ * </ul>
+ *
+ * @author Kirill Vainer
+ */
+public class VirtualIndexBuffer extends IndexBuffer {
+
+ protected int numVerts = 0;
+ protected int numIndices = 0;
+ protected Mode meshMode;
+
+ public VirtualIndexBuffer(int numVerts, Mode meshMode){
+ this.numVerts = numVerts;
+ this.meshMode = meshMode;
+ switch (meshMode) {
+ case Points:
+ numIndices = numVerts;
+ return;
+ case LineLoop:
+ numIndices = (numVerts - 1) * 2 + 1;
+ return;
+ case LineStrip:
+ numIndices = (numVerts - 1) * 2;
+ return;
+ case Lines:
+ numIndices = numVerts;
+ return;
+ case TriangleFan:
+ numIndices = (numVerts - 2) * 3;
+ return;
+ case TriangleStrip:
+ numIndices = (numVerts - 2) * 3;
+ return;
+ case Triangles:
+ numIndices = numVerts;
+ return;
+ case Hybrid:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public int get(int i) {
+ if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){
+ return i;
+ }else if (meshMode == Mode.LineStrip){
+ return (i + 1) / 2;
+ }else if (meshMode == Mode.LineLoop){
+ return (i == (numVerts-1)) ? 0 : ((i + 1) / 2);
+ }else if (meshMode == Mode.TriangleStrip){
+ int triIndex = i/3;
+ int vertIndex = i%3;
+ boolean isBack = (i/3)%2==1;
+ if (!isBack){
+ return triIndex + vertIndex;
+ }else{
+ switch (vertIndex){
+ case 0: return triIndex + 1;
+ case 1: return triIndex;
+ case 2: return triIndex + 2;
+ default: throw new AssertionError();
+ }
+ }
+ }else if (meshMode == Mode.TriangleFan){
+ int vertIndex = i%3;
+ if (vertIndex == 0)
+ return 0;
+ else
+ return (i / 3) + vertIndex;
+ }else{
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public void put(int i, int value) {
+ throw new UnsupportedOperationException("Does not represent index buffer");
+ }
+
+ @Override
+ public int size() {
+ return numIndices;
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return null;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java
new file mode 100644
index 0000000..056a1cd
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java
@@ -0,0 +1,86 @@
+package com.jme3.scene.mesh;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * <code>WrappedIndexBuffer</code> converts vertex indices from a non list based
+ * mesh mode such as {@link Mode#TriangleStrip} or {@link Mode#LineLoop}
+ * into a list based mode such as {@link Mode#Triangles} or {@link Mode#Lines}.
+ * As it is often more convenient to read vertex data in list format
+ * than in a non-list format, using this class is recommended to avoid
+ * convoluting classes used to process mesh data from an external source.
+ *
+ * @author Kirill Vainer
+ */
+public class WrappedIndexBuffer extends VirtualIndexBuffer {
+
+ private final IndexBuffer ib;
+
+ public WrappedIndexBuffer(Mesh mesh){
+ super(mesh.getVertexCount(), mesh.getMode());
+ this.ib = mesh.getIndexBuffer();
+ switch (meshMode){
+ case Points:
+ numIndices = mesh.getTriangleCount();
+ break;
+ case Lines:
+ case LineLoop:
+ case LineStrip:
+ numIndices = mesh.getTriangleCount() * 2;
+ break;
+ case Triangles:
+ case TriangleStrip:
+ case TriangleFan:
+ numIndices = mesh.getTriangleCount() * 3;
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public int get(int i) {
+ int superIdx = super.get(i);
+ return ib.get(superIdx);
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return ib.getBuffer();
+ }
+
+ public static void convertToList(Mesh mesh){
+ IndexBuffer inBuf = mesh.getIndicesAsList();
+ IndexBuffer outBuf = IndexBuffer.createIndexBuffer(mesh.getVertexCount(),
+ inBuf.size());
+
+ for (int i = 0; i < inBuf.size(); i++){
+ outBuf.put(i, inBuf.get(i));
+ }
+
+ mesh.clearBuffer(Type.Index);
+ switch (mesh.getMode()){
+ case LineLoop:
+ case LineStrip:
+ mesh.setMode(Mode.Lines);
+ break;
+ case TriangleStrip:
+ case TriangleFan:
+ mesh.setMode(Mode.Triangles);
+ break;
+ default:
+ break;
+ }
+ if (outBuf instanceof IndexIntBuffer){
+ mesh.setBuffer(Type.Index, 3, (IntBuffer)outBuf.getBuffer());
+ }else{
+ mesh.setBuffer(Type.Index, 3, (ShortBuffer)outBuf.getBuffer());
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/package.html b/engine/src/core/com/jme3/scene/mesh/package.html
new file mode 100644
index 0000000..5362c52
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.scene.mesh</code> package contains utilities
+for reading from {@link com.jme3.scene.mesh.IndexBuffer index buffers}.
+Several implementations are provided of the {@link com.jme3.scene.mesh.IndexBuffer}
+class:
+<ul>
+ <li>{@link com.jme3.scene.mesh.IndexByteBuffer} - For reading 8-bit index buffers</li>
+ <li>{@link com.jme3.scene.mesh.IndexShortBuffer} - For reading 16-bit index buffers</li>
+ <li>{@link com.jme3.scene.mesh.IndexIntBuffer} - For reading 32-bit index buffers</li>
+ <li>{@link com.jme3.scene.mesh.VirtualIndexBuffer} - For reading "virtual indices", for
+ those meshes that do not have an index buffer</li>
+ <li>{@link com.jme3.scene.mesh.WrappedIndexBuffer} - For converting from
+ non-list based mode indices to list based</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/package.html b/engine/src/core/com/jme3/scene/package.html
new file mode 100644
index 0000000..53f8105
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/package.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input</code> package contains the scene graph implementation
+in jMonkeyEngine.
+
+<p>
+ The scene graph is the most important package in jME, as it is the API
+ used to manage scene elements so that they can be rendered.
+ The {@link com.jme3.scene.Spatial} class provides a common base class
+ for all scene graph elements. The {@link com.jme3.scene.Node} class provides
+ the "branches" in the graph, used to organize elements in a tree
+ hierarchy. The {@link com.jme3.scene.Geometry} is the leaf class that
+ will contain a {@link com.jme3.scene.Mesh} object (geometry data
+ such as vertex positions, normals, etc) and a {@link com.jme3.scene.Material}
+ object containing information on how the geometry should be shaded.
+</p>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/shape/AbstractBox.java b/engine/src/core/com/jme3/scene/shape/AbstractBox.java
new file mode 100644
index 0000000..6c68dc5
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/AbstractBox.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.io.IOException;
+
+/**
+ * An eight sided box.
+ * <p>
+ * A {@code Box} is defined by a minimal point and a maximal point. The eight
+ * vertices that make the box are then computed, they are computed in such
+ * a way as to generate an axis-aligned box.
+ * <p>
+ * This class does not control how the geometry data is generated, see {@link Box}
+ * for that.
+ *
+ * @author <a href="mailto:ianp@ianp.org">Ian Phillips</a>
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public abstract class AbstractBox extends Mesh {
+
+ public final Vector3f center = new Vector3f(0f, 0f, 0f);
+
+ public float xExtent, yExtent, zExtent;
+
+ public AbstractBox() {
+ super();
+ }
+
+ /**
+ * Gets the array or vectors representing the 8 vertices of the box.
+ *
+ * @return a newly created array of vertex vectors.
+ */
+ protected final Vector3f[] computeVertices() {
+ Vector3f[] axes = {
+ Vector3f.UNIT_X.mult(xExtent),
+ Vector3f.UNIT_Y.mult(yExtent),
+ Vector3f.UNIT_Z.mult(zExtent)
+ };
+ return new Vector3f[] {
+ center.subtract(axes[0]).subtractLocal(axes[1]).subtractLocal(axes[2]),
+ center.add(axes[0]).subtractLocal(axes[1]).subtractLocal(axes[2]),
+ center.add(axes[0]).addLocal(axes[1]).subtractLocal(axes[2]),
+ center.subtract(axes[0]).addLocal(axes[1]).subtractLocal(axes[2]),
+ center.add(axes[0]).subtractLocal(axes[1]).addLocal(axes[2]),
+ center.subtract(axes[0]).subtractLocal(axes[1]).addLocal(axes[2]),
+ center.add(axes[0]).addLocal(axes[1]).addLocal(axes[2]),
+ center.subtract(axes[0]).addLocal(axes[1]).addLocal(axes[2])
+ };
+ }
+
+ /**
+ * Convert the indices into the list of vertices that define the box's geometry.
+ */
+ protected abstract void duUpdateGeometryIndices();
+
+ /**
+ * Update the normals of each of the box's planes.
+ */
+ protected abstract void duUpdateGeometryNormals();
+
+ /**
+ * Update the points that define the texture of the box.
+ * <p>
+ * It's a one-to-one ratio, where each plane of the box has it's own copy
+ * of the texture. That is, the texture is repeated one time for each face.
+ */
+ protected abstract void duUpdateGeometryTextures();
+
+ /**
+ * Update the position of the vertices that define the box.
+ * <p>
+ * These eight points are determined from the minimum and maximum point.
+ */
+ protected abstract void duUpdateGeometryVertices();
+
+ /**
+ * Get the center point of this box.
+ */
+ public final Vector3f getCenter() {
+ return center;
+ }
+
+ /**
+ * Get the x-axis size (extent) of this box.
+ */
+ public final float getXExtent() {
+ return xExtent;
+ }
+
+ /**
+ * Get the y-axis size (extent) of this box.
+ */
+ public final float getYExtent() {
+ return yExtent;
+ }
+
+ /**
+ * Get the z-axis size (extent) of this box.
+ */
+ public final float getZExtent() {
+ return zExtent;
+ }
+
+ /**
+ * Rebuilds the box after a property has been directly altered.
+ * <p>
+ * For example, if you call {@code getXExtent().x = 5.0f} then you will
+ * need to call this method afterwards in order to update the box.
+ */
+ public final void updateGeometry() {
+ duUpdateGeometryVertices();
+ duUpdateGeometryNormals();
+ duUpdateGeometryTextures();
+ duUpdateGeometryIndices();
+ }
+
+ /**
+ * Rebuilds this box based on a new set of parameters.
+ * <p>
+ * Note that the actual sides will be twice the given extent values because
+ * the box extends in both directions from the center for each extent.
+ *
+ * @param center the center of the box.
+ * @param x the x extent of the box, in each directions.
+ * @param y the y extent of the box, in each directions.
+ * @param z the z extent of the box, in each directions.
+ */
+ public final void updateGeometry(Vector3f center, float x, float y, float z) {
+ if (center != null) {this.center.set(center); }
+ this.xExtent = x;
+ this.yExtent = y;
+ this.zExtent = z;
+ updateGeometry();
+ }
+
+ /**
+ * Rebuilds this box based on a new set of parameters.
+ * <p>
+ * The box is updated so that the two opposite corners are {@code minPoint}
+ * and {@code maxPoint}, the other corners are created from those two positions.
+ *
+ * @param minPoint the new minimum point of the box.
+ * @param maxPoint the new maximum point of the box.
+ */
+ public final void updateGeometry(Vector3f minPoint, Vector3f maxPoint) {
+ center.set(maxPoint).addLocal(minPoint).multLocal(0.5f);
+ float x = maxPoint.x - center.x;
+ float y = maxPoint.y - center.y;
+ float z = maxPoint.z - center.z;
+ updateGeometry(center, x, y, z);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ xExtent = capsule.readFloat("xExtent", 0);
+ yExtent = capsule.readFloat("yExtent", 0);
+ zExtent = capsule.readFloat("zExtent", 0);
+ center.set((Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone()));
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(xExtent, "xExtent", 0);
+ capsule.write(yExtent, "yExtent", 0);
+ capsule.write(zExtent, "zExtent", 0);
+ capsule.write(center, "center", Vector3f.ZERO);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Box.java b/engine/src/core/com/jme3/scene/shape/Box.java
new file mode 100644
index 0000000..307547e
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Box.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ * A box with solid (filled) faces.
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Box extends AbstractBox {
+
+ private static final short[] GEOMETRY_INDICES_DATA = {
+ 2, 1, 0, 3, 2, 0, // back
+ 6, 5, 4, 7, 6, 4, // right
+ 10, 9, 8, 11, 10, 8, // front
+ 14, 13, 12, 15, 14, 12, // left
+ 18, 17, 16, 19, 18, 16, // top
+ 22, 21, 20, 23, 22, 20 // bottom
+ };
+
+ private static final float[] GEOMETRY_NORMALS_DATA = {
+ 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, // back
+ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // right
+ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // front
+ -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // left
+ 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // top
+ 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0 // bottom
+ };
+
+ private static final float[] GEOMETRY_TEXTURE_DATA = {
+ 1, 0, 0, 0, 0, 1, 1, 1, // back
+ 1, 0, 0, 0, 0, 1, 1, 1, // right
+ 1, 0, 0, 0, 0, 1, 1, 1, // front
+ 1, 0, 0, 0, 0, 1, 1, 1, // left
+ 1, 0, 0, 0, 0, 1, 1, 1, // top
+ 1, 0, 0, 0, 0, 1, 1, 1 // bottom
+ };
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has a center of 0,0,0 and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public Box(float x, float y, float z) {
+ super();
+ updateGeometry(Vector3f.ZERO, x, y, z);
+ }
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has the given center and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param center the center of the box.
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public Box(Vector3f center, float x, float y, float z) {
+ super();
+ updateGeometry(center, x, y, z);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Box</code> object.
+ * <p>
+ * The minimum and maximum point are provided, these two points define the
+ * shape and size of the box but not it's orientation or position. You should
+ * use the {@link #setLocalTranslation()} and {@link #setLocalRotation()}
+ * methods to define those properties.
+ *
+ * @param min the minimum point that defines the box.
+ * @param max the maximum point that defines the box.
+ */
+ public Box(Vector3f min, Vector3f max) {
+ super();
+ updateGeometry(min, max);
+ }
+
+ /**
+ * Empty constructor for serialization only. Do not use.
+ */
+ public Box(){
+ super();
+ }
+
+ /**
+ * Creates a clone of this box.
+ * <p>
+ * The cloned box will have '_clone' appended to it's name, but all other
+ * properties will be the same as this box.
+ */
+ @Override
+ public Box clone() {
+ return new Box(center.clone(), xExtent, yExtent, zExtent);
+ }
+
+ protected void duUpdateGeometryIndices() {
+ if (getBuffer(Type.Index) == null){
+ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryNormals() {
+ if (getBuffer(Type.Normal) == null){
+ setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryTextures() {
+ if (getBuffer(Type.TexCoord) == null){
+ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryVertices() {
+ FloatBuffer fpb = BufferUtils.createVector3Buffer(24);
+ Vector3f[] v = computeVertices();
+ fpb.put(new float[] {
+ v[0].x, v[0].y, v[0].z, v[1].x, v[1].y, v[1].z, v[2].x, v[2].y, v[2].z, v[3].x, v[3].y, v[3].z, // back
+ v[1].x, v[1].y, v[1].z, v[4].x, v[4].y, v[4].z, v[6].x, v[6].y, v[6].z, v[2].x, v[2].y, v[2].z, // right
+ v[4].x, v[4].y, v[4].z, v[5].x, v[5].y, v[5].z, v[7].x, v[7].y, v[7].z, v[6].x, v[6].y, v[6].z, // front
+ v[5].x, v[5].y, v[5].z, v[0].x, v[0].y, v[0].z, v[3].x, v[3].y, v[3].z, v[7].x, v[7].y, v[7].z, // left
+ v[2].x, v[2].y, v[2].z, v[6].x, v[6].y, v[6].z, v[7].x, v[7].y, v[7].z, v[3].x, v[3].y, v[3].z, // top
+ v[0].x, v[0].y, v[0].z, v[5].x, v[5].y, v[5].z, v[4].x, v[4].y, v[4].z, v[1].x, v[1].y, v[1].z // bottom
+ });
+ setBuffer(Type.Position, 3, fpb);
+ updateBound();
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Curve.java b/engine/src/core/com/jme3/scene/shape/Curve.java
new file mode 100644
index 0000000..fbeda59
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Curve.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.shape;
+
+import com.jme3.math.Spline;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A <code>Curve</code> is a visual, line-based representation of a {@link Spline}.
+ * The underlying Spline will be sampled N times where N is the number of
+ * segments as specified in the constructor. Each segment will represent
+ * one line in the generated mesh.
+ *
+ * @author Nehon
+ */
+public class Curve extends Mesh {
+
+ private Spline spline;
+ private Vector3f temp = new Vector3f();
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Curve(){
+ }
+
+ /**
+ * Create a curve mesh.
+ * Use a CatmullRom spline model that does not cycle.
+ *
+ * @param controlPoints the control points to use to create this curve
+ * @param nbSubSegments the number of subsegments between the control points
+ */
+ public Curve(Vector3f[] controlPoints, int nbSubSegments) {
+ this(new Spline(Spline.SplineType.CatmullRom, controlPoints, 10, false), nbSubSegments);
+ }
+
+ /**
+ * Create a curve mesh from a Spline
+ *
+ * @param spline the spline to use
+ * @param nbSubSegments the number of subsegments between the control points
+ */
+ public Curve(Spline spline, int nbSubSegments) {
+ super();
+ this.spline = spline;
+ switch (spline.getType()) {
+ case CatmullRom:
+ this.createCatmullRomMesh(nbSubSegments);
+ break;
+ case Bezier:
+ this.createBezierMesh(nbSubSegments);
+ break;
+ case Nurb:
+ this.createNurbMesh(nbSubSegments);
+ break;
+ case Linear:
+ default:
+ this.createLinearMesh();
+ break;
+ }
+ }
+
+ private void createCatmullRomMesh(int nbSubSegments) {
+ float[] array = new float[((spline.getControlPoints().size() - 1) * nbSubSegments + 1) * 3];
+ short[] indices = new short[(spline.getControlPoints().size() - 1) * nbSubSegments * 2];
+ int i = 0;
+ int cptCP = 0;
+ for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+ Vector3f vector3f = it.next();
+ array[i] = vector3f.x;
+ i++;
+ array[i] = vector3f.y;
+ i++;
+ array[i] = vector3f.z;
+ i++;
+ if (it.hasNext()) {
+ for (int j = 1; j < nbSubSegments; j++) {
+ spline.interpolate((float) j / nbSubSegments, cptCP, temp);
+ array[i] = temp.getX();
+ i++;
+ array[i] = temp.getY();
+ i++;
+ array[i] = temp.getZ();
+ i++;
+ }
+ }
+ cptCP++;
+ }
+
+ i = 0;
+ int k = 0;
+ for (int j = 0; j < (spline.getControlPoints().size() - 1) * nbSubSegments; j++) {
+ k = j;
+ indices[i] = (short) k;
+ i++;
+ k++;
+ indices[i] = (short) k;
+ i++;
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);//(spline.getControlPoints().size() - 1) * nbSubSegments * 2
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ /**
+ * This method creates the Bezier path for this curve.
+ *
+ * @param nbSubSegments
+ * amount of subsegments between position control points
+ */
+ private void createBezierMesh(int nbSubSegments) {
+ if(nbSubSegments==0) {
+ nbSubSegments = 1;
+ }
+ int centerPointsAmount = (spline.getControlPoints().size() + 2) / 3;
+
+ //calculating vertices
+ float[] array = new float[((centerPointsAmount - 1) * nbSubSegments + 1) * 3];
+ int currentControlPoint = 0;
+ List<Vector3f> controlPoints = spline.getControlPoints();
+ int lineIndex = 0;
+ for (int i = 0; i < centerPointsAmount - 1; ++i) {
+ Vector3f vector3f = controlPoints.get(currentControlPoint);
+ array[lineIndex++] = vector3f.x;
+ array[lineIndex++] = vector3f.y;
+ array[lineIndex++] = vector3f.z;
+ for (int j = 1; j < nbSubSegments; ++j) {
+ spline.interpolate((float) j / nbSubSegments, currentControlPoint, temp);
+ array[lineIndex++] = temp.getX();
+ array[lineIndex++] = temp.getY();
+ array[lineIndex++] = temp.getZ();
+ }
+ currentControlPoint += 3;
+ }
+ Vector3f vector3f = controlPoints.get(currentControlPoint);
+ array[lineIndex++] = vector3f.x;
+ array[lineIndex++] = vector3f.y;
+ array[lineIndex++] = vector3f.z;
+
+ //calculating indexes
+ int i = 0, k = 0;
+ short[] indices = new short[(centerPointsAmount - 1) * nbSubSegments << 1];
+ for (int j = 0; j < (centerPointsAmount - 1) * nbSubSegments; ++j) {
+ k = j;
+ indices[i++] = (short) k;
+ ++k;
+ indices[i++] = (short) k;
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ /**
+ * This method creates the Nurb path for this curve.
+ * @param nbSubSegments
+ * amount of subsegments between position control points
+ */
+ private void createNurbMesh(int nbSubSegments) {
+ float minKnot = spline.getMinNurbKnot();
+ float maxKnot = spline.getMaxNurbKnot();
+ float deltaU = (maxKnot - minKnot)/nbSubSegments;
+
+ float[] array = new float[(nbSubSegments + 1) * 3];
+
+ float u = minKnot;
+ Vector3f interpolationResult = new Vector3f();
+ for(int i=0;i<array.length;i+=3) {
+ spline.interpolate(u, 0, interpolationResult);
+ array[i] = interpolationResult.x;
+ array[i + 1] = interpolationResult.y;
+ array[i + 2] = interpolationResult.z;
+ u += deltaU;
+ }
+
+ //calculating indexes
+ int i = 0;
+ short[] indices = new short[nbSubSegments << 1];
+ for (int j = 0; j < nbSubSegments; ++j) {
+ indices[i++] = (short) j;
+ indices[i++] = (short) (j + 1);
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ private void createLinearMesh() {
+ float[] array = new float[spline.getControlPoints().size() * 3];
+ short[] indices = new short[(spline.getControlPoints().size() - 1) * 2];
+ int i = 0;
+ int cpt = 0;
+ int k = 0;
+ int j = 0;
+ for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+ Vector3f vector3f = it.next();
+ array[i] = vector3f.getX();
+ i++;
+ array[i] = vector3f.getY();
+ i++;
+ array[i] = vector3f.getZ();
+ i++;
+ if (it.hasNext()) {
+ k = j;
+ indices[cpt] = (short) k;
+ cpt++;
+ k++;
+ indices[cpt] = (short) k;
+ cpt++;
+ j++;
+ }
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ /**
+ * This method returns the length of the curve.
+ * @return the length of the curve
+ */
+ public float getLength() {
+ return spline.getTotalLength();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Cylinder.java b/engine/src/core/com/jme3/scene/shape/Cylinder.java
new file mode 100644
index 0000000..85bb970
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Cylinder.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+import static com.jme3.util.BufferUtils.*;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A simple cylinder, defined by it's height and radius.
+ * (Ported to jME3)
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Cylinder extends Mesh {
+
+ private int axisSamples;
+
+ private int radialSamples;
+
+ private float radius;
+ private float radius2;
+
+ private float height;
+ private boolean closed;
+ private boolean inverted;
+
+ /**
+ * Default constructor for serialization only. Do not use.
+ */
+ public Cylinder() {
+ }
+
+ /**
+ * Creates a new Cylinder. By default its center is the origin. Usually, a
+ * higher sample number creates a better looking cylinder, but at the cost
+ * of more vertex information.
+ *
+ * @param axisSamples
+ * Number of triangle samples along the axis.
+ * @param radialSamples
+ * Number of triangle samples along the radial.
+ * @param radius
+ * The radius of the cylinder.
+ * @param height
+ * The cylinder's height.
+ */
+ public Cylinder(int axisSamples, int radialSamples,
+ float radius, float height) {
+ this(axisSamples, radialSamples, radius, height, false);
+ }
+
+ /**
+ * Creates a new Cylinder. By default its center is the origin. Usually, a
+ * higher sample number creates a better looking cylinder, but at the cost
+ * of more vertex information. <br>
+ * If the cylinder is closed the texture is split into axisSamples parts:
+ * top most and bottom most part is used for top and bottom of the cylinder,
+ * rest of the texture for the cylinder wall. The middle of the top is
+ * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
+ * a suited distorted texture.
+ *
+ * @param axisSamples
+ * Number of triangle samples along the axis.
+ * @param radialSamples
+ * Number of triangle samples along the radial.
+ * @param radius
+ * The radius of the cylinder.
+ * @param height
+ * The cylinder's height.
+ * @param closed
+ * true to create a cylinder with top and bottom surface
+ */
+ public Cylinder(int axisSamples, int radialSamples,
+ float radius, float height, boolean closed) {
+ this(axisSamples, radialSamples, radius, height, closed, false);
+ }
+
+ /**
+ * Creates a new Cylinder. By default its center is the origin. Usually, a
+ * higher sample number creates a better looking cylinder, but at the cost
+ * of more vertex information. <br>
+ * If the cylinder is closed the texture is split into axisSamples parts:
+ * top most and bottom most part is used for top and bottom of the cylinder,
+ * rest of the texture for the cylinder wall. The middle of the top is
+ * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
+ * a suited distorted texture.
+ *
+ * @param axisSamples
+ * Number of triangle samples along the axis.
+ * @param radialSamples
+ * Number of triangle samples along the radial.
+ * @param radius
+ * The radius of the cylinder.
+ * @param height
+ * The cylinder's height.
+ * @param closed
+ * true to create a cylinder with top and bottom surface
+ * @param inverted
+ * true to create a cylinder that is meant to be viewed from the
+ * interior.
+ */
+ public Cylinder(int axisSamples, int radialSamples,
+ float radius, float height, boolean closed, boolean inverted) {
+ this(axisSamples, radialSamples, radius, radius, height, closed, inverted);
+ }
+
+ public Cylinder(int axisSamples, int radialSamples,
+ float radius, float radius2, float height, boolean closed, boolean inverted) {
+ super();
+ updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted);
+ }
+
+ /**
+ * @return the number of samples along the cylinder axis
+ */
+ public int getAxisSamples() {
+ return axisSamples;
+ }
+
+ /**
+ * @return Returns the height.
+ */
+ public float getHeight() {
+ return height;
+ }
+
+ /**
+ * @return number of samples around cylinder
+ */
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ /**
+ * @return Returns the radius.
+ */
+ public float getRadius() {
+ return radius;
+ }
+
+ public float getRadius2() {
+ return radius2;
+ }
+
+ /**
+ * @return true if end caps are used.
+ */
+ public boolean isClosed() {
+ return closed;
+ }
+
+ /**
+ * @return true if normals and uvs are created for interior use
+ */
+ public boolean isInverted() {
+ return inverted;
+ }
+
+ /**
+ * Rebuilds the cylinder based on a new set of parameters.
+ *
+ * @param axisSamples the number of samples along the axis.
+ * @param radialSamples the number of samples around the radial.
+ * @param radius the radius of the bottom of the cylinder.
+ * @param radius2 the radius of the top of the cylinder.
+ * @param height the cylinder's height.
+ * @param closed should the cylinder have top and bottom surfaces.
+ * @param inverted is the cylinder is meant to be viewed from the inside.
+ */
+ public void updateGeometry(int axisSamples, int radialSamples,
+ float radius, float radius2, float height, boolean closed, boolean inverted) {
+ this.axisSamples = axisSamples + (closed ? 2 : 0);
+ this.radialSamples = radialSamples;
+ this.radius = radius;
+ this.radius2 = radius2;
+ this.height = height;
+ this.closed = closed;
+ this.inverted = inverted;
+
+// VertexBuffer pvb = getBuffer(Type.Position);
+// VertexBuffer nvb = getBuffer(Type.Normal);
+// VertexBuffer tvb = getBuffer(Type.TexCoord);
+
+ // Vertices
+ int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);
+
+ setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));
+
+ // Normals
+ setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));
+
+ // Texture co-ordinates
+ setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));
+
+ int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;
+
+ setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));
+
+ // generate geometry
+ float inverseRadial = 1.0f / radialSamples;
+ float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
+ float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
+ float halfHeight = 0.5f * height;
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a cylinder slice.
+ float[] sin = new float[radialSamples + 1];
+ float[] cos = new float[radialSamples + 1];
+
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+ float angle = FastMath.TWO_PI * inverseRadial * radialCount;
+ cos[radialCount] = FastMath.cos(angle);
+ sin[radialCount] = FastMath.sin(angle);
+ }
+ sin[radialSamples] = sin[0];
+ cos[radialSamples] = cos[0];
+
+ // calculate normals
+ Vector3f[] vNormals = null;
+ Vector3f vNormal = Vector3f.UNIT_Z;
+
+ if ((height != 0.0f) && (radius != radius2)) {
+ vNormals = new Vector3f[radialSamples];
+ Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
+ Vector3f vRadial = new Vector3f();
+
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+ vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
+ Vector3f vRadius = vRadial.mult(radius);
+ Vector3f vRadius2 = vRadial.mult(radius2);
+ Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
+ Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
+ vNormals[radialCount] = vMantle.cross(vTangent).normalize();
+ }
+ }
+
+ FloatBuffer nb = getFloatBuffer(Type.Normal);
+ FloatBuffer pb = getFloatBuffer(Type.Position);
+ FloatBuffer tb = getFloatBuffer(Type.TexCoord);
+
+ // generate the cylinder itself
+ Vector3f tempNormal = new Vector3f();
+ for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
+ float axisFraction;
+ float axisFractionTexture;
+ int topBottom = 0;
+ if (!closed) {
+ axisFraction = axisCount * inverseAxisLess; // in [0,1]
+ axisFractionTexture = axisFraction;
+ } else {
+ if (axisCount == 0) {
+ topBottom = -1; // bottom
+ axisFraction = 0;
+ axisFractionTexture = inverseAxisLessTexture;
+ } else if (axisCount == axisSamples - 1) {
+ topBottom = 1; // top
+ axisFraction = 1;
+ axisFractionTexture = 1 - inverseAxisLessTexture;
+ } else {
+ axisFraction = (axisCount - 1) * inverseAxisLess;
+ axisFractionTexture = axisCount * inverseAxisLessTexture;
+ }
+ }
+
+ // compute center of slice
+ float z = -halfHeight + height * axisFraction;
+ Vector3f sliceCenter = new Vector3f(0, 0, z);
+
+ // compute slice vertices with duplication at end point
+ int save = i;
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
+ float radialFraction = radialCount * inverseRadial; // in [0,1)
+ tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);
+
+ if (vNormals != null) {
+ vNormal = vNormals[radialCount];
+ } else if (radius == radius2) {
+ vNormal = tempNormal;
+ }
+
+ if (topBottom == 0) {
+ if (!inverted)
+ nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
+ else
+ nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
+ } else {
+ nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
+ }
+
+ tempNormal.multLocal((radius - radius2) * axisFraction + radius2)
+ .addLocal(sliceCenter);
+ pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);
+
+ tb.put((inverted ? 1 - radialFraction : radialFraction))
+ .put(axisFractionTexture);
+ }
+
+ BufferUtils.copyInternalVector3(pb, save, i);
+ BufferUtils.copyInternalVector3(nb, save, i);
+
+ tb.put((inverted ? 0.0f : 1.0f))
+ .put(axisFractionTexture);
+ }
+
+ if (closed) {
+ pb.put(0).put(0).put(-halfHeight); // bottom center
+ nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
+ tb.put(0.5f).put(0);
+ pb.put(0).put(0).put(halfHeight); // top center
+ nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
+ tb.put(0.5f).put(1);
+ }
+
+ IndexBuffer ib = getIndexBuffer();
+ int index = 0;
+ // Connectivity
+ for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {
+ int i0 = axisStart;
+ int i1 = i0 + 1;
+ axisStart += radialSamples + 1;
+ int i2 = axisStart;
+ int i3 = i2 + 1;
+ for (int i = 0; i < radialSamples; i++) {
+ if (closed && axisCount == 0) {
+ if (!inverted) {
+ ib.put(index++, i0++);
+ ib.put(index++, vertCount - 2);
+ ib.put(index++, i1++);
+ } else {
+ ib.put(index++, i0++);
+ ib.put(index++, i1++);
+ ib.put(index++, vertCount - 2);
+ }
+ } else if (closed && axisCount == axisSamples - 2) {
+ ib.put(index++, i2++);
+ ib.put(index++, inverted ? vertCount - 1 : i3++);
+ ib.put(index++, inverted ? i3++ : vertCount - 1);
+ } else {
+ ib.put(index++, i0++);
+ ib.put(index++, inverted ? i2 : i1);
+ ib.put(index++, inverted ? i1 : i2);
+ ib.put(index++, i1++);
+ ib.put(index++, inverted ? i2++ : i3++);
+ ib.put(index++, inverted ? i3++ : i2++);
+ }
+ }
+ }
+
+ updateBound();
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ axisSamples = capsule.readInt("axisSamples", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ radius = capsule.readFloat("radius", 0);
+ radius2 = capsule.readFloat("radius2", 0);
+ height = capsule.readFloat("height", 0);
+ closed = capsule.readBoolean("closed", false);
+ inverted = capsule.readBoolean("inverted", false);
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(axisSamples, "axisSamples", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(radius, "radius", 0);
+ capsule.write(radius2, "radius2", 0);
+ capsule.write(height, "height", 0);
+ capsule.write(closed, "closed", false);
+ capsule.write(inverted, "inverted", false);
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Dome.java b/engine/src/core/com/jme3/scene/shape/Dome.java
new file mode 100644
index 0000000..6f429fc
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Dome.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+// $Id: Dome.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A hemisphere.
+ *
+ * @author Peter Andersson
+ * @author Joshua Slack (Original sphere code that was adapted)
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Dome extends Mesh {
+
+ private int planes;
+ private int radialSamples;
+ /** The radius of the dome */
+ private float radius;
+ /** The center of the dome */
+ private Vector3f center;
+ private boolean insideView = true;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Dome() {
+ }
+
+ /**
+ * Constructs a dome for use as a SkyDome. The SkyDome is centered at the origin
+ * and only visible from the inside.
+ * @param planes
+ * The number of planes along the Z-axis. Must be >= 2.
+ * Influences how round the arch of the dome is.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * Influences how round the base of the dome is.
+ * @param radius
+ * Radius of the dome.
+ * @see #Dome(java.lang.String, com.jme.math.Vector3f, int, int, float)
+ */
+ public Dome(int planes, int radialSamples, float radius) {
+ this(new Vector3f(0, 0, 0), planes, radialSamples, radius);
+ }
+
+ /**
+ * Constructs a dome visible from the inside, e.g. for use as a SkyDome.
+ * All geometry data buffers are updated automatically. <br>
+ * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2.
+ * Increasing planes and radialSamples increase the quality of the dome.
+ *
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis. Must be >= 2.
+ * Influences how round the arch of the dome is.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * Influences how round the base of the dome is.
+ * @param radius
+ * The radius of the dome.
+ */
+ public Dome(Vector3f center, int planes, int radialSamples,
+ float radius) {
+ super();
+ updateGeometry(center, planes, radialSamples, radius, true);
+ }
+
+ /**
+ * Constructs a dome. Use this constructor for half-sphere, pyramids, or cones.
+ * All geometry data buffers are updated automatically. <br>
+ * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2.
+ * Setting higher values for planes and radialSamples increases
+ * the quality of the half-sphere.
+ *
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis. Must be >= 2.
+ * Influences how round the arch of the dome is.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * Influences how round the base of the dome is.
+ * @param radius
+ * The radius of the dome.
+ * @param insideView
+ * If true, the dome is only visible from the inside, like a SkyDome.
+ * If false, the dome is only visible from the outside.
+ */
+ public Dome(Vector3f center, int planes, int radialSamples,
+ float radius, boolean insideView) {
+ super();
+ updateGeometry(center, planes, radialSamples, radius, insideView);
+ }
+
+ public Vector3f getCenter() {
+ return center;
+ }
+
+ /**
+ * Get the number of planar segments along the z-axis of the dome.
+ */
+ public int getPlanes() {
+ return planes;
+ }
+
+ /**
+ * Get the number of samples radially around the main axis of the dome.
+ */
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ /**
+ * Get the radius of the dome.
+ */
+ public float getRadius() {
+ return radius;
+ }
+
+ /**
+ * Are the triangles connected in such a way as to present a view out from the dome or not.
+ */
+ public boolean isInsideView() {
+ return insideView;
+ }
+
+ /**
+ * Rebuilds the dome with a new set of parameters.
+ *
+ * @param center the new center of the dome.
+ * @param planes the number of planes along the Z-axis.
+ * @param radialSamples the new number of radial samples of the dome.
+ * @param radius the new radius of the dome.
+ * @param insideView should the dome be set up to be viewed from the inside looking out.
+ */
+ public void updateGeometry(Vector3f center, int planes,
+ int radialSamples, float radius, boolean insideView) {
+ this.insideView = insideView;
+ this.center = center != null ? center : new Vector3f(0, 0, 0);
+ this.planes = planes;
+ this.radialSamples = radialSamples;
+ this.radius = radius;
+
+ int vertCount = ((planes - 1) * (radialSamples + 1)) + 1;
+
+ // Allocate vertices, allocating one extra in each radial to get the
+ // correct texture coordinates
+// setVertexCount();
+// setVertexBuffer(createVector3Buffer(getVertexCount()));
+
+ // allocate normals
+// setNormalBuffer(createVector3Buffer(getVertexCount()));
+
+ // allocate texture coordinates
+// getTextureCoords().set(0, new TexCoords(createVector2Buffer(getVertexCount())));
+
+ FloatBuffer vb = BufferUtils.createVector3Buffer(vertCount);
+ FloatBuffer nb = BufferUtils.createVector3Buffer(vertCount);
+ FloatBuffer tb = BufferUtils.createVector2Buffer(vertCount);
+ setBuffer(Type.Position, 3, vb);
+ setBuffer(Type.Normal, 3, nb);
+ setBuffer(Type.TexCoord, 2, tb);
+
+ // generate geometry
+ float fInvRS = 1.0f / radialSamples;
+ float fYFactor = 1.0f / (planes - 1);
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a dome slice.
+ float[] afSin = new float[(radialSamples)];
+ float[] afCos = new float[(radialSamples)];
+ for (int iR = 0; iR < radialSamples; iR++) {
+ float fAngle = FastMath.TWO_PI * fInvRS * iR;
+ afCos[iR] = FastMath.cos(fAngle);
+ afSin[iR] = FastMath.sin(fAngle);
+ }
+
+ TempVars vars = TempVars.get();
+ Vector3f tempVc = vars.vect3;
+ Vector3f tempVb = vars.vect2;
+ Vector3f tempVa = vars.vect1;
+
+ // generate the dome itself
+ int i = 0;
+ for (int iY = 0; iY < (planes - 1); iY++, i++) {
+ float fYFraction = fYFactor * iY; // in (0,1)
+ float fY = radius * fYFraction;
+ // compute center of slice
+ Vector3f kSliceCenter = tempVb.set(center);
+ kSliceCenter.y += fY;
+
+ // compute radius of slice
+ float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius - fY * fY));
+
+ // compute slice vertices
+ Vector3f kNormal;
+ int iSave = i;
+ for (int iR = 0; iR < radialSamples; iR++, i++) {
+ float fRadialFraction = iR * fInvRS; // in [0,1)
+ Vector3f kRadial = tempVc.set(afCos[iR], 0, afSin[iR]);
+ kRadial.mult(fSliceRadius, tempVa);
+ vb.put(kSliceCenter.x + tempVa.x).put(
+ kSliceCenter.y + tempVa.y).put(
+ kSliceCenter.z + tempVa.z);
+
+ BufferUtils.populateFromBuffer(tempVa, vb, i);
+ kNormal = tempVa.subtractLocal(center);
+ kNormal.normalizeLocal();
+ if (insideView) {
+ nb.put(kNormal.x).put(kNormal.y).put(kNormal.z);
+ } else {
+ nb.put(-kNormal.x).put(-kNormal.y).put(-kNormal.z);
+ }
+
+ tb.put(fRadialFraction).put(fYFraction);
+ }
+ BufferUtils.copyInternalVector3(vb, iSave, i);
+ BufferUtils.copyInternalVector3(nb, iSave, i);
+ tb.put(1.0f).put(fYFraction);
+ }
+
+ vars.release();
+
+ // pole
+ vb.put(center.x).put(center.y + radius).put(center.z);
+ nb.put(0).put(insideView ? 1 : -1).put(0);
+ tb.put(0.5f).put(1.0f);
+
+ // allocate connectivity
+ int triCount = (planes - 2) * radialSamples * 2 + radialSamples;
+ ShortBuffer ib = BufferUtils.createShortBuffer(3 * triCount);
+ setBuffer(Type.Index, 3, ib);
+
+ // generate connectivity
+ int index = 0;
+ // Generate only for middle planes
+ for (int plane = 1; plane < (planes - 1); plane++) {
+ int bottomPlaneStart = ((plane - 1) * (radialSamples + 1));
+ int topPlaneStart = (plane * (radialSamples + 1));
+ for (int sample = 0; sample < radialSamples; sample++, index += 6) {
+ if (insideView){
+ ib.put((short) (bottomPlaneStart + sample));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample));
+ }else{
+ ib.put((short) (bottomPlaneStart + sample));
+ ib.put((short) (topPlaneStart + sample));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample));
+ ib.put((short) (topPlaneStart + sample + 1));
+ }
+ }
+ }
+
+ // pole triangles
+ int bottomPlaneStart = (planes - 2) * (radialSamples + 1);
+ for (int samples = 0; samples < radialSamples; samples++, index += 3) {
+ if (insideView){
+ ib.put((short) (bottomPlaneStart + samples));
+ ib.put((short) (bottomPlaneStart + samples + 1));
+ ib.put((short) (vertCount - 1));
+ }else{
+ ib.put((short) (bottomPlaneStart + samples));
+ ib.put((short) (vertCount - 1));
+ ib.put((short) (bottomPlaneStart + samples + 1));
+ }
+ }
+
+ updateBound();
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ planes = capsule.readInt("planes", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ radius = capsule.readFloat("radius", 0);
+ center = (Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone());
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(planes, "planes", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(radius, "radius", 0);
+ capsule.write(center, "center", Vector3f.ZERO);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Line.java b/engine/src/core/com/jme3/scene/shape/Line.java
new file mode 100644
index 0000000..1edbba7
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Line.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A simple line implementation with a start and an end.
+ *
+ * @author Brent Owens
+ */
+public class Line extends Mesh {
+
+ private Vector3f start;
+ private Vector3f end;
+
+ public Line() {
+ }
+
+ public Line(Vector3f start, Vector3f end) {
+ setMode(Mode.Lines);
+ updateGeometry(start, end);
+ }
+
+ protected void updateGeometry(Vector3f start, Vector3f end) {
+ this.start = start;
+ this.end = end;
+ setBuffer(Type.Position, 3, new float[]{start.x, start.y, start.z,
+ end.x, end.y, end.z,});
+
+
+ setBuffer(Type.TexCoord, 2, new float[]{0, 0,
+ 1, 1});
+
+ setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
+ 0, 0, 1});
+
+ setBuffer(Type.Index, 3, new short[]{0, 1});
+
+ updateBound();
+ }
+
+ /**
+ * Update the start and end points of the line.
+ */
+ public void updatePoints(Vector3f start, Vector3f end) {
+ VertexBuffer posBuf = getBuffer(Type.Position);
+
+ FloatBuffer fb = (FloatBuffer) posBuf.getData();
+
+ fb.put(start.x).put(start.y).put(start.z);
+ fb.put(end.x).put(end.y).put(end.z);
+
+ posBuf.updateData(fb);
+
+ updateBound();
+ }
+
+ public Vector3f getEnd() {
+ return end;
+ }
+
+ public Vector3f getStart() {
+ return start;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule out = ex.getCapsule(this);
+
+ out.write(start, "startVertex", null);
+ out.write(end, "endVertex", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule in = im.getCapsule(this);
+
+ start = (Vector3f) in.readSavable("startVertex", null);
+ end = (Vector3f) in.readSavable("endVertex", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/PQTorus.java b/engine/src/core/com/jme3/scene/shape/PQTorus.java
new file mode 100644
index 0000000..26baed7
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/PQTorus.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: PQTorus.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import static com.jme3.util.BufferUtils.*;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A parameterized torus, also known as a <em>pq</em> torus.
+ *
+ * @author Joshua Slack, Eric Woroshow
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class PQTorus extends Mesh {
+
+ private float p, q;
+
+ private float radius, width;
+
+ private int steps, radialSamples;
+
+ public PQTorus() {
+ }
+
+ /**
+ * Creates a parameterized torus.
+ * <p>
+ * Steps and radialSamples are both degree of accuracy values.
+ *
+ * @param p the x/z oscillation.
+ * @param q the y oscillation.
+ * @param radius the radius of the PQTorus.
+ * @param width the width of the torus.
+ * @param steps the steps along the torus.
+ * @param radialSamples radial samples for the torus.
+ */
+ public PQTorus(float p, float q, float radius, float width,
+ int steps, int radialSamples) {
+ super();
+ updateGeometry(p, q, radius, width, steps, radialSamples);
+ }
+
+ public float getP() {
+ return p;
+ }
+
+ public float getQ() {
+ return q;
+ }
+
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ public float getRadius() {
+ return radius;
+ }
+
+ public int getSteps() {
+ return steps;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ p = capsule.readFloat("p", 0);
+ q = capsule.readFloat("q", 0);
+ radius = capsule.readFloat("radius", 0);
+ width = capsule.readFloat("width", 0);
+ steps = capsule.readInt("steps", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ }
+
+ /**
+ * Rebuilds this torus based on a new set of parameters.
+ *
+ * @param p the x/z oscillation.
+ * @param q the y oscillation.
+ * @param radius the radius of the PQTorus.
+ * @param width the width of the torus.
+ * @param steps the steps along the torus.
+ * @param radialSamples radial samples for the torus.
+ */
+ public void updateGeometry(float p, float q, float radius, float width, int steps, int radialSamples) {
+ this.p = p;
+ this.q = q;
+ this.radius = radius;
+ this.width = width;
+ this.steps = steps;
+ this.radialSamples = radialSamples;
+
+ final float thetaStep = (FastMath.TWO_PI / steps);
+ final float betaStep = (FastMath.TWO_PI / radialSamples);
+ Vector3f[] torusPoints = new Vector3f[steps];
+
+ // Allocate all of the required buffers
+ int vertCount = radialSamples * steps;
+
+ FloatBuffer fpb = createVector3Buffer(vertCount);
+ FloatBuffer fnb = createVector3Buffer(vertCount);
+ FloatBuffer ftb = createVector2Buffer(vertCount);
+
+ Vector3f pointB = new Vector3f(), T = new Vector3f(), N = new Vector3f(), B = new Vector3f();
+ Vector3f tempNorm = new Vector3f();
+ float r, x, y, z, theta = 0.0f, beta = 0.0f;
+ int nvertex = 0;
+
+ // Move along the length of the pq torus
+ for (int i = 0; i < steps; i++) {
+ theta += thetaStep;
+ float circleFraction = ((float) i) / (float) steps;
+
+ // Find the point on the torus
+ r = (0.5f * (2.0f + FastMath.sin(q * theta)) * radius);
+ x = (r * FastMath.cos(p * theta) * radius);
+ y = (r * FastMath.sin(p * theta) * radius);
+ z = (r * FastMath.cos(q * theta) * radius);
+ torusPoints[i] = new Vector3f(x, y, z);
+
+ // Now find a point slightly farther along the torus
+ r = (0.5f * (2.0f + FastMath.sin(q * (theta + 0.01f))) * radius);
+ x = (r * FastMath.cos(p * (theta + 0.01f)) * radius);
+ y = (r * FastMath.sin(p * (theta + 0.01f)) * radius);
+ z = (r * FastMath.cos(q * (theta + 0.01f)) * radius);
+ pointB = new Vector3f(x, y, z);
+
+ // Approximate the Frenet Frame
+ T = pointB.subtract(torusPoints[i]);
+ N = torusPoints[i].add(pointB);
+ B = T.cross(N);
+ N = B.cross(T);
+
+ // Normalise the two vectors and then use them to create an oriented circle
+ N = N.normalize();
+ B = B.normalize();
+ beta = 0.0f;
+ for (int j = 0; j < radialSamples; j++, nvertex++) {
+ beta += betaStep;
+ float cx = FastMath.cos(beta) * width;
+ float cy = FastMath.sin(beta) * width;
+ float radialFraction = ((float) j) / radialSamples;
+ tempNorm.x = (cx * N.x + cy * B.x);
+ tempNorm.y = (cx * N.y + cy * B.y);
+ tempNorm.z = (cx * N.z + cy * B.z);
+ fnb.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
+ tempNorm.addLocal(torusPoints[i]);
+ fpb.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
+ ftb.put(radialFraction).put(circleFraction);
+ }
+ }
+
+ // Update the indices data
+ ShortBuffer sib = createShortBuffer(6 * vertCount);
+ for (int i = 0; i < vertCount; i++) {
+ sib.put(new short[] {
+ (short)(i),
+ (short)(i - radialSamples),
+ (short)(i + 1),
+ (short)(i + 1),
+ (short)(i - radialSamples),
+ (short)(i - radialSamples + 1)
+ });
+ }
+ for (int i = 0, len = sib.capacity(); i < len; i++) {
+ int ind = sib.get(i);
+ if (ind < 0) {
+ ind += vertCount;
+ sib.put(i, (short) ind);
+ } else if (ind >= vertCount) {
+ ind -= vertCount;
+ sib.put(i, (short) ind);
+ }
+ }
+ sib.rewind();
+
+ setBuffer(Type.Position, 3, fpb);
+ setBuffer(Type.Normal, 3, fnb);
+ setBuffer(Type.TexCoord, 2, ftb);
+ setBuffer(Type.Index, 3, sib);
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(p, "p", 0);
+ capsule.write(q, "q", 0);
+ capsule.write(radius, "radius", 0);
+ capsule.write(width, "width", 0);
+ capsule.write(steps, "steps", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Quad.java b/engine/src/core/com/jme3/scene/shape/Quad.java
new file mode 100644
index 0000000..1945358
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Quad.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+
+/**
+ * <code>Quad</code> represents a rectangular plane in space
+ * defined by 4 vertices. The quad's lower-left side is contained
+ * at the local space origin (0, 0, 0), while the upper-right
+ * side is located at the width/height coordinates (width, height, 0).
+ *
+ * @author Kirill Vainer
+ */
+public class Quad extends Mesh {
+
+ private float width;
+ private float height;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Quad(){
+ }
+
+ /**
+ * Create a quad with the given width and height. The quad
+ * is always created in the XY plane.
+ *
+ * @param width The X extent or width
+ * @param height The Y extent or width
+ */
+ public Quad(float width, float height){
+ updateGeometry(width, height);
+ }
+
+ /**
+ * Create a quad with the given width and height. The quad
+ * is always created in the XY plane.
+ *
+ * @param width The X extent or width
+ * @param height The Y extent or width
+ * @param flipCoords If true, the texture coordinates will be flipped
+ * along the Y axis.
+ */
+ public Quad(float width, float height, boolean flipCoords){
+ updateGeometry(width, height, flipCoords);
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ public void updateGeometry(float width, float height){
+ updateGeometry(width, height, false);
+ }
+
+ public void updateGeometry(float width, float height, boolean flipCoords) {
+ this.width = width;
+ this.height = height;
+ setBuffer(Type.Position, 3, new float[]{0, 0, 0,
+ width, 0, 0,
+ width, height, 0,
+ 0, height, 0
+ });
+
+
+ if (flipCoords){
+ setBuffer(Type.TexCoord, 2, new float[]{0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0});
+ }else{
+ setBuffer(Type.TexCoord, 2, new float[]{0, 0,
+ 1, 0,
+ 1, 1,
+ 0, 1});
+ }
+ setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
+ 0, 0, 1,
+ 0, 0, 1,
+ 0, 0, 1});
+ if (height < 0){
+ setBuffer(Type.Index, 3, new short[]{0, 2, 1,
+ 0, 3, 2});
+ }else{
+ setBuffer(Type.Index, 3, new short[]{0, 1, 2,
+ 0, 2, 3});
+ }
+
+ updateBound();
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Sphere.java b/engine/src/core/com/jme3/scene/shape/Sphere.java
new file mode 100644
index 0000000..f8a5281
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Sphere.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+// $Id: Sphere.java 4163 2009-03-25 01:14:55Z matt.yellen $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * <code>Sphere</code> represents a 3D object with all points equidistance
+ * from a center point.
+ *
+ * @author Joshua Slack
+ * @version $Revision: 4163 $, $Date: 2009-03-24 21:14:55 -0400 (Tue, 24 Mar 2009) $
+ */
+public class Sphere extends Mesh {
+
+ public enum TextureMode {
+
+ /**
+ * Wrap texture radially and along z-axis
+ */
+ Original,
+ /**
+ * Wrap texure radially, but spherically project along z-axis
+ */
+ Projected,
+ /**
+ * Apply texture to each pole. Eliminates polar distortion,
+ * but mirrors the texture across the equator
+ */
+ Polar
+ }
+ protected int vertCount;
+ protected int triCount;
+ protected int zSamples;
+ protected int radialSamples;
+ protected boolean useEvenSlices;
+ protected boolean interior;
+ /** the distance from the center point each point falls on */
+ public float radius;
+ protected TextureMode textureMode = TextureMode.Original;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Sphere() {
+ }
+
+ /**
+ * Constructs a sphere. All geometry data buffers are updated automatically.
+ * Both zSamples and radialSamples increase the quality of the generated
+ * sphere.
+ *
+ * @param zSamples
+ * The number of samples along the Z.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the sphere.
+ */
+ public Sphere(int zSamples, int radialSamples, float radius) {
+ this(zSamples, radialSamples, radius, false, false);
+ }
+
+ /**
+ * Constructs a sphere. Additional arg to evenly space latitudinal slices
+ *
+ * @param zSamples
+ * The number of samples along the Z.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the sphere.
+ * @param useEvenSlices
+ * Slice sphere evenly along the Z axis
+ * @param interior
+ * Not yet documented
+ */
+ public Sphere(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) {
+ updateGeometry(zSamples, radialSamples, radius, useEvenSlices, interior);
+ }
+
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ public float getRadius() {
+ return radius;
+ }
+
+ /**
+ * @return Returns the textureMode.
+ */
+ public TextureMode getTextureMode() {
+ return textureMode;
+ }
+
+ public int getZSamples() {
+ return zSamples;
+ }
+
+ /**
+ * builds the vertices based on the radius, radial and zSamples.
+ */
+ private void setGeometryData() {
+ // allocate vertices
+ vertCount = (zSamples - 2) * (radialSamples + 1) + 2;
+
+ FloatBuffer posBuf = BufferUtils.createVector3Buffer(vertCount);
+
+ // allocate normals if requested
+ FloatBuffer normBuf = BufferUtils.createVector3Buffer(vertCount);
+
+ // allocate texture coordinates
+ FloatBuffer texBuf = BufferUtils.createVector2Buffer(vertCount);
+
+ setBuffer(Type.Position, 3, posBuf);
+ setBuffer(Type.Normal, 3, normBuf);
+ setBuffer(Type.TexCoord, 2, texBuf);
+
+ // generate geometry
+ float fInvRS = 1.0f / radialSamples;
+ float fZFactor = 2.0f / (zSamples - 1);
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a sphere slice.
+ float[] afSin = new float[(radialSamples + 1)];
+ float[] afCos = new float[(radialSamples + 1)];
+ for (int iR = 0; iR < radialSamples; iR++) {
+ float fAngle = FastMath.TWO_PI * fInvRS * iR;
+ afCos[iR] = FastMath.cos(fAngle);
+ afSin[iR] = FastMath.sin(fAngle);
+ }
+ afSin[radialSamples] = afSin[0];
+ afCos[radialSamples] = afCos[0];
+
+ TempVars vars = TempVars.get();
+ Vector3f tempVa = vars.vect1;
+ Vector3f tempVb = vars.vect2;
+ Vector3f tempVc = vars.vect3;
+
+ // generate the sphere itself
+ int i = 0;
+ for (int iZ = 1; iZ < (zSamples - 1); iZ++) {
+ float fAFraction = FastMath.HALF_PI * (-1.0f + fZFactor * iZ); // in (-pi/2, pi/2)
+ float fZFraction;
+ if (useEvenSlices) {
+ fZFraction = -1.0f + fZFactor * iZ; // in (-1, 1)
+ } else {
+ fZFraction = FastMath.sin(fAFraction); // in (-1,1)
+ }
+ float fZ = radius * fZFraction;
+
+ // compute center of slice
+ Vector3f kSliceCenter = tempVb.set(Vector3f.ZERO);
+ kSliceCenter.z += fZ;
+
+ // compute radius of slice
+ float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius
+ - fZ * fZ));
+
+ // compute slice vertices with duplication at end point
+ Vector3f kNormal;
+ int iSave = i;
+ for (int iR = 0; iR < radialSamples; iR++) {
+ float fRadialFraction = iR * fInvRS; // in [0,1)
+ Vector3f kRadial = tempVc.set(afCos[iR], afSin[iR], 0);
+ kRadial.mult(fSliceRadius, tempVa);
+ posBuf.put(kSliceCenter.x + tempVa.x).put(
+ kSliceCenter.y + tempVa.y).put(
+ kSliceCenter.z + tempVa.z);
+
+ BufferUtils.populateFromBuffer(tempVa, posBuf, i);
+ kNormal = tempVa;
+ kNormal.normalizeLocal();
+ if (!interior) // allow interior texture vs. exterior
+ {
+ normBuf.put(kNormal.x).put(kNormal.y).put(
+ kNormal.z);
+ } else {
+ normBuf.put(-kNormal.x).put(-kNormal.y).put(
+ -kNormal.z);
+ }
+
+ if (textureMode == TextureMode.Original) {
+ texBuf.put(fRadialFraction).put(
+ 0.5f * (fZFraction + 1.0f));
+ } else if (textureMode == TextureMode.Projected) {
+ texBuf.put(fRadialFraction).put(
+ FastMath.INV_PI
+ * (FastMath.HALF_PI + FastMath.asin(fZFraction)));
+ } else if (textureMode == TextureMode.Polar) {
+ float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI;
+ float u = r * afCos[iR] + 0.5f;
+ float v = r * afSin[iR] + 0.5f;
+ texBuf.put(u).put(v);
+ }
+
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(posBuf, iSave, i);
+ BufferUtils.copyInternalVector3(normBuf, iSave, i);
+
+ if (textureMode == TextureMode.Original) {
+ texBuf.put(1.0f).put(
+ 0.5f * (fZFraction + 1.0f));
+ } else if (textureMode == TextureMode.Projected) {
+ texBuf.put(1.0f).put(
+ FastMath.INV_PI
+ * (FastMath.HALF_PI + FastMath.asin(fZFraction)));
+ } else if (textureMode == TextureMode.Polar) {
+ float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI;
+ texBuf.put(r + 0.5f).put(0.5f);
+ }
+
+ i++;
+ }
+
+ vars.release();
+
+ // south pole
+ posBuf.position(i * 3);
+ posBuf.put(0f).put(0f).put(-radius);
+
+ normBuf.position(i * 3);
+ if (!interior) {
+ normBuf.put(0).put(0).put(-1); // allow for inner
+ } // texture orientation
+ // later.
+ else {
+ normBuf.put(0).put(0).put(1);
+ }
+
+ texBuf.position(i * 2);
+
+ if (textureMode == TextureMode.Polar) {
+ texBuf.put(0.5f).put(0.5f);
+ } else {
+ texBuf.put(0.5f).put(0.0f);
+ }
+
+ i++;
+
+ // north pole
+ posBuf.put(0).put(0).put(radius);
+
+ if (!interior) {
+ normBuf.put(0).put(0).put(1);
+ } else {
+ normBuf.put(0).put(0).put(-1);
+ }
+
+ if (textureMode == TextureMode.Polar) {
+ texBuf.put(0.5f).put(0.5f);
+ } else {
+ texBuf.put(0.5f).put(1.0f);
+ }
+
+ updateBound();
+ setStatic();
+ }
+
+ /**
+ * sets the indices for rendering the sphere.
+ */
+ private void setIndexData() {
+ // allocate connectivity
+ triCount = 2 * (zSamples - 2) * radialSamples;
+ ShortBuffer idxBuf = BufferUtils.createShortBuffer(3 * triCount);
+ setBuffer(Type.Index, 3, idxBuf);
+
+ // generate connectivity
+ int index = 0;
+ for (int iZ = 0, iZStart = 0; iZ < (zSamples - 3); iZ++) {
+ int i0 = iZStart;
+ int i1 = i0 + 1;
+ iZStart += (radialSamples + 1);
+ int i2 = iZStart;
+ int i3 = i2 + 1;
+ for (int i = 0; i < radialSamples; i++, index += 6) {
+ if (!interior) {
+ idxBuf.put((short) i0++);
+ idxBuf.put((short) i1);
+ idxBuf.put((short) i2);
+ idxBuf.put((short) i1++);
+ idxBuf.put((short) i3++);
+ idxBuf.put((short) i2++);
+ } else { // inside view
+ idxBuf.put((short) i0++);
+ idxBuf.put((short) i2);
+ idxBuf.put((short) i1);
+ idxBuf.put((short) i1++);
+ idxBuf.put((short) i2++);
+ idxBuf.put((short) i3++);
+ }
+ }
+ }
+
+ // south pole triangles
+ for (int i = 0; i < radialSamples; i++, index += 3) {
+ if (!interior) {
+ idxBuf.put((short) i);
+ idxBuf.put((short) (vertCount - 2));
+ idxBuf.put((short) (i + 1));
+ } else { // inside view
+ idxBuf.put((short) i);
+ idxBuf.put((short) (i + 1));
+ idxBuf.put((short) (vertCount - 2));
+ }
+ }
+
+ // north pole triangles
+ int iOffset = (zSamples - 3) * (radialSamples + 1);
+ for (int i = 0; i < radialSamples; i++, index += 3) {
+ if (!interior) {
+ idxBuf.put((short) (i + iOffset));
+ idxBuf.put((short) (i + 1 + iOffset));
+ idxBuf.put((short) (vertCount - 1));
+ } else { // inside view
+ idxBuf.put((short) (i + iOffset));
+ idxBuf.put((short) (vertCount - 1));
+ idxBuf.put((short) (i + 1 + iOffset));
+ }
+ }
+ }
+
+ /**
+ * @param textureMode
+ * The textureMode to set.
+ */
+ public void setTextureMode(TextureMode textureMode) {
+ this.textureMode = textureMode;
+ setGeometryData();
+ }
+
+ /**
+ * Changes the information of the sphere into the given values.
+ *
+ * @param zSamples the number of zSamples of the sphere.
+ * @param radialSamples the number of radial samples of the sphere.
+ * @param radius the radius of the sphere.
+ */
+ public void updateGeometry(int zSamples, int radialSamples, float radius) {
+ updateGeometry(zSamples, radialSamples, radius, false, false);
+ }
+
+ public void updateGeometry(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) {
+ this.zSamples = zSamples;
+ this.radialSamples = radialSamples;
+ this.radius = radius;
+ this.useEvenSlices = useEvenSlices;
+ this.interior = interior;
+ setGeometryData();
+ setIndexData();
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ zSamples = capsule.readInt("zSamples", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ radius = capsule.readFloat("radius", 0);
+ useEvenSlices = capsule.readBoolean("useEvenSlices", false);
+ textureMode = capsule.readEnum("textureMode", TextureMode.class, TextureMode.Original);
+ interior = capsule.readBoolean("interior", false);
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(zSamples, "zSamples", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(radius, "radius", 0);
+ capsule.write(useEvenSlices, "useEvenSlices", false);
+ capsule.write(textureMode, "textureMode", TextureMode.Original);
+ capsule.write(interior, "interior", false);
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/StripBox.java b/engine/src/core/com/jme3/scene/shape/StripBox.java
new file mode 100644
index 0000000..81abff9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/StripBox.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ * A box with solid (filled) faces.
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class StripBox extends AbstractBox {
+
+ private static final short[] GEOMETRY_INDICES_DATA =
+ { 1, 0, 4,
+ 5,
+ 7,
+ 0,
+ 3,
+ 1,
+ 2,
+ 4,
+ 6,
+ 7,
+ 2,
+ 3 };
+
+ private static final float[] GEOMETRY_TEXTURE_DATA = {
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 1, 1,
+
+ 1, 0,
+ 0, 0,
+ 1, 1,
+ 0, 1
+ };
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has a center of 0,0,0 and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public StripBox(float x, float y, float z) {
+ super();
+ updateGeometry(Vector3f.ZERO, x, y, z);
+ }
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has the given center and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param center the center of the box.
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public StripBox(Vector3f center, float x, float y, float z) {
+ super();
+ updateGeometry(center, x, y, z);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Box</code> object.
+ * <p>
+ * The minimum and maximum point are provided, these two points define the
+ * shape and size of the box but not it’s orientation or position. You should
+ * use the {@link #setLocalTranslation()} and {@link #setLocalRotation()}
+ * methods to define those properties.
+ *
+ * @param min the minimum point that defines the box.
+ * @param max the maximum point that defines the box.
+ */
+ public StripBox(Vector3f min, Vector3f max) {
+ super();
+ updateGeometry(min, max);
+ }
+
+ /**
+ * Empty constructor for serialization only. Do not use.
+ */
+ public StripBox(){
+ super();
+ }
+
+ /**
+ * Creates a clone of this box.
+ * <p>
+ * The cloned box will have ‘_clone’ appended to it’s name, but all other
+ * properties will be the same as this box.
+ */
+ @Override
+ public StripBox clone() {
+ return new StripBox(center.clone(), xExtent, yExtent, zExtent);
+ }
+
+ protected void duUpdateGeometryIndices() {
+ if (getBuffer(Type.Index) == null){
+ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryNormals() {
+ if (getBuffer(Type.Normal) == null){
+ float[] normals = new float[8 * 3];
+
+ Vector3f[] vert = computeVertices();
+ Vector3f norm = new Vector3f();
+
+ for (int i = 0; i < 8; i++) {
+ norm.set(vert[i]).normalizeLocal();
+
+ normals[i * 3 + 0] = norm.x;
+ normals[i * 3 + 1] = norm.x;
+ normals[i * 3 + 2] = norm.x;
+ }
+
+ setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
+ }
+ }
+
+ protected void duUpdateGeometryTextures() {
+ if (getBuffer(Type.TexCoord) == null){
+ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryVertices() {
+ FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3);
+ Vector3f[] v = computeVertices();
+ fpb.put(new float[] {
+ v[0].x, v[0].y, v[0].z,
+ v[1].x, v[1].y, v[1].z,
+ v[2].x, v[2].y, v[2].z,
+ v[3].x, v[3].y, v[3].z,
+ v[4].x, v[4].y, v[4].z,
+ v[5].x, v[5].y, v[5].z,
+ v[6].x, v[6].y, v[6].z,
+ v[7].x, v[7].y, v[7].z,
+ });
+ setBuffer(Type.Position, 3, fpb);
+ setMode(Mode.TriangleStrip);
+ updateBound();
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Surface.java b/engine/src/core/com/jme3/scene/shape/Surface.java
new file mode 100644
index 0000000..77d259b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Surface.java
@@ -0,0 +1,283 @@
+package com.jme3.scene.shape;
+
+import com.jme3.math.CurveAndSurfaceMath;
+import com.jme3.math.FastMath;
+import com.jme3.math.Spline.SplineType;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.util.BufferUtils;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class represents a surface described by knots, weights and control points.
+ * Currently the following types are supported:
+ * a) NURBS
+ * @author Marcin Roguski (Kealthas)
+ */
+public class Surface extends Mesh {
+
+ private SplineType type; //the type of the surface
+ private List<List<Vector4f>> controlPoints; //space control points and their weights
+ private List<Float>[] knots; //knots of the surface
+ private int basisUFunctionDegree; //the degree of basis U function
+ private int basisVFunctionDegree; //the degree of basis V function
+ private int uSegments; //the amount of U segments
+ private int vSegments; //the amount of V segments
+
+ /**
+ * Constructor. Constructs required surface.
+ * @param controlPoints space control points
+ * @param nurbKnots knots of the surface
+ * @param uSegments the amount of U segments
+ * @param vSegments the amount of V segments
+ * @param basisUFunctionDegree the degree of basis U function
+ * @param basisVFunctionDegree the degree of basis V function
+ */
+ private Surface(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,
+ int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) {
+ this.validateInputData(controlPoints, nurbKnots, uSegments, vSegments);
+ this.type = SplineType.Nurb;
+ this.uSegments = uSegments;
+ this.vSegments = vSegments;
+ this.controlPoints = controlPoints;
+ this.knots = nurbKnots;
+ this.basisUFunctionDegree = basisUFunctionDegree;
+ CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[0], basisUFunctionDegree);
+ if (nurbKnots[1] != null) {
+ this.basisVFunctionDegree = basisVFunctionDegree;
+ CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[1], basisVFunctionDegree);
+ }
+
+ this.buildSurface();
+ }
+
+ /**
+ * This method creates a NURBS surface.
+ * @param controlPoints space control points
+ * @param nurbKnots knots of the surface
+ * @param uSegments the amount of U segments
+ * @param vSegments the amount of V segments
+ * @param basisUFunctionDegree the degree of basis U function
+ * @param basisVFunctionDegree the degree of basis V function
+ * @return an instance of NURBS surface
+ */
+ public static final Surface createNurbsSurface(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,
+ int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) {
+ Surface result = new Surface(controlPoints, nurbKnots, uSegments, vSegments, basisUFunctionDegree, basisVFunctionDegree);
+ result.type = SplineType.Nurb;
+ return result;
+ }
+
+ /**
+ * This method creates the surface.
+ */
+ private void buildSurface() {
+ boolean smooth = true;//TODO: take smoothing into consideration
+ float minUKnot = this.getMinUNurbKnot();
+ float maxUKnot = this.getMaxUNurbKnot();
+ float deltaU = (maxUKnot - minUKnot) / uSegments;
+
+ float minVKnot = this.getMinVNurbKnot();
+ float maxVKnot = this.getMaxVNurbKnot();
+ float deltaV = (maxVKnot - minVKnot) / vSegments;
+
+ Vector3f[] vertices = new Vector3f[(uSegments + 1) * (vSegments + 1)];
+
+ float u = minUKnot, v = minVKnot;
+ int arrayIndex = 0;
+
+ for (int i = 0; i <= vSegments; ++i) {
+ for (int j = 0; j <= uSegments; ++j) {
+ Vector3f interpolationResult = new Vector3f();
+ CurveAndSurfaceMath.interpolate(u, v, controlPoints, knots, basisUFunctionDegree, basisVFunctionDegree, interpolationResult);
+ vertices[arrayIndex++] = interpolationResult;
+ u += deltaU;
+ }
+ u = minUKnot;
+ v += deltaV;
+ }
+
+ //adding indexes
+ int uVerticesAmount = uSegments + 1;
+ int[] indices = new int[uSegments * vSegments * 6];
+ arrayIndex = 0;
+ for (int i = 0; i < vSegments; ++i) {
+ for (int j = 0; j < uSegments; ++j) {
+ indices[arrayIndex++] = j + i * uVerticesAmount;
+ indices[arrayIndex++] = j + i * uVerticesAmount + 1;
+ indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount;
+ indices[arrayIndex++] = j + i * uVerticesAmount + 1;
+ indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount + 1;
+ indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount;
+ }
+ }
+
+ //normalMap merges normals of faces that will be rendered smooth
+ Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(vertices.length);
+ for (int i = 0; i < indices.length; i += 3) {
+ Vector3f n = FastMath.computeNormal(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);
+ this.addNormal(n, normalMap, smooth, vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);
+ }
+ //preparing normal list (the order of normals must match the order of vertices)
+ float[] normals = new float[vertices.length * 3];
+ arrayIndex = 0;
+ for (int i = 0; i < vertices.length; ++i) {
+ Vector3f n = normalMap.get(vertices[i]);
+ normals[arrayIndex++] = n.x;
+ normals[arrayIndex++] = n.y;
+ normals[arrayIndex++] = n.z;
+ }
+
+ this.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
+ this.setBuffer(VertexBuffer.Type.Index, 3, indices);
+ this.setBuffer(VertexBuffer.Type.Normal, 3, normals);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ public List<List<Vector4f>> getControlPoints() {
+ return controlPoints;
+ }
+
+ /**
+ * This method returns the amount of U control points.
+ * @return the amount of U control points
+ */
+ public int getUControlPointsAmount() {
+ return controlPoints.size();
+ }
+
+ /**
+ * This method returns the amount of V control points.
+ * @return the amount of V control points
+ */
+ public int getVControlPointsAmount() {
+ return controlPoints.get(0) == null ? 0 : controlPoints.get(0).size();
+ }
+
+ /**
+ * This method returns the degree of basis U function.
+ * @return the degree of basis U function
+ */
+ public int getBasisUFunctionDegree() {
+ return basisUFunctionDegree;
+ }
+
+ /**
+ * This method returns the degree of basis V function.
+ * @return the degree of basis V function
+ */
+ public int getBasisVFunctionDegree() {
+ return basisVFunctionDegree;
+ }
+
+ /**
+ * This method returns the knots for specified dimension (U knots - value: '0',
+ * V knots - value: '1').
+ * @param dim an integer specifying if the U or V knots are required
+ * @return an array of knots
+ */
+ public List<Float> getKnots(int dim) {
+ return knots[dim];
+ }
+
+ /**
+ * This method returns the type of the surface.
+ * @return the type of the surface
+ */
+ public SplineType getType() {
+ return type;
+ }
+
+ /**
+ * This method returns the minimum nurb curve U knot value.
+ * @return the minimum nurb curve knot value
+ */
+ private float getMinUNurbKnot() {
+ return knots[0].get(basisUFunctionDegree - 1);
+ }
+
+ /**
+ * This method returns the maximum nurb curve U knot value.
+ * @return the maximum nurb curve knot value
+ */
+ private float getMaxUNurbKnot() {
+ return knots[0].get(knots[0].size() - basisUFunctionDegree);
+ }
+
+ /**
+ * This method returns the minimum nurb curve U knot value.
+ * @return the minimum nurb curve knot value
+ */
+ private float getMinVNurbKnot() {
+ return knots[1].get(basisVFunctionDegree - 1);
+ }
+
+ /**
+ * This method returns the maximum nurb curve U knot value.
+ * @return the maximum nurb curve knot value
+ */
+ private float getMaxVNurbKnot() {
+ return knots[1].get(knots[1].size() - basisVFunctionDegree);
+ }
+
+ /**
+ * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.
+ * @param normalToAdd
+ * a normal to be added
+ * @param normalMap
+ * merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector
+ * @param smooth
+ * the variable that indicates wheather to merge normals (creating the smooth mesh) or not
+ * @param vertices
+ * a list of vertices read from the blender file
+ */
+ private void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {
+ for (Vector3f v : vertices) {
+ Vector3f n = normalMap.get(v);
+ if (!smooth || n == null) {
+ normalMap.put(v, normalToAdd.clone());
+ } else {
+ n.addLocal(normalToAdd).normalizeLocal();
+ }
+ }
+ }
+
+ /**
+ * This method validates the input data. It throws {@link IllegalArgumentException} if
+ * the data is invalid.
+ * @param controlPoints space control points
+ * @param nurbKnots knots of the surface
+ * @param uSegments the amount of U segments
+ * @param vSegments the amount of V segments
+ */
+ private void validateInputData(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,
+ int uSegments, int vSegments) {
+ int uPointsAmount = controlPoints.get(0).size();
+ for (int i = 1; i < controlPoints.size(); ++i) {
+ if (controlPoints.get(i).size() != uPointsAmount) {
+ throw new IllegalArgumentException("The amount of 'U' control points is invalid!");
+ }
+ }
+ if (uSegments <= 0) {
+ throw new IllegalArgumentException("U segments amount should be positive!");
+ }
+ if (vSegments < 0) {
+ throw new IllegalArgumentException("V segments amount cannot be negative!");
+ }
+ if (nurbKnots.length != 2) {
+ throw new IllegalArgumentException("Nurb surface should have two rows of knots!");
+ }
+ for (int i = 0; i < nurbKnots.length; ++i) {
+ for (int j = 0; j < nurbKnots[i].size() - 1; ++j) {
+ if (nurbKnots[i].get(j) > nurbKnots[i].get(j + 1)) {
+ throw new IllegalArgumentException("The knots' values cannot decrease!");
+ }
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Torus.java b/engine/src/core/com/jme3/scene/shape/Torus.java
new file mode 100644
index 0000000..8b9285a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Torus.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Torus.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * An ordinary (single holed) torus.
+ * <p>
+ * The center is by default the origin.
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Torus extends Mesh {
+
+ private int circleSamples;
+
+ private int radialSamples;
+
+ private float innerRadius;
+
+ private float outerRadius;
+
+ public Torus() {
+ }
+
+ /**
+ * Constructs a new Torus. Center is the origin, but the Torus may be
+ * transformed.
+ *
+ * @param circleSamples
+ * The number of samples along the circles.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param innerRadius
+ * The radius of the inner begining of the Torus.
+ * @param outerRadius
+ * The radius of the outter end of the Torus.
+ */
+ public Torus(int circleSamples, int radialSamples,
+ float innerRadius, float outerRadius) {
+ super();
+ updateGeometry(circleSamples, radialSamples, innerRadius, outerRadius);
+ }
+
+ public int getCircleSamples() {
+ return circleSamples;
+ }
+
+ public float getInnerRadius() {
+ return innerRadius;
+ }
+
+ public float getOuterRadius() {
+ return outerRadius;
+ }
+
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ circleSamples = capsule.readInt("circleSamples", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ innerRadius = capsule.readFloat("innerRadius", 0);
+ outerRadius = capsule.readFloat("outerRaidus", 0);
+ }
+
+ private void setGeometryData() {
+ // allocate vertices
+ int vertCount = (circleSamples + 1) * (radialSamples + 1);
+ FloatBuffer fpb = BufferUtils.createVector3Buffer(vertCount);
+ setBuffer(Type.Position, 3, fpb);
+
+ // allocate normals if requested
+ FloatBuffer fnb = BufferUtils.createVector3Buffer(vertCount);
+ setBuffer(Type.Normal, 3, fnb);
+
+ // allocate texture coordinates
+ FloatBuffer ftb = BufferUtils.createVector2Buffer(vertCount);
+ setBuffer(Type.TexCoord, 2, ftb);
+
+ // generate geometry
+ float inverseCircleSamples = 1.0f / circleSamples;
+ float inverseRadialSamples = 1.0f / radialSamples;
+ int i = 0;
+ // generate the cylinder itself
+ Vector3f radialAxis = new Vector3f(), torusMiddle = new Vector3f(), tempNormal = new Vector3f();
+ for (int circleCount = 0; circleCount < circleSamples; circleCount++) {
+ // compute center point on torus circle at specified angle
+ float circleFraction = circleCount * inverseCircleSamples;
+ float theta = FastMath.TWO_PI * circleFraction;
+ float cosTheta = FastMath.cos(theta);
+ float sinTheta = FastMath.sin(theta);
+ radialAxis.set(cosTheta, sinTheta, 0);
+ radialAxis.mult(outerRadius, torusMiddle);
+
+ // compute slice vertices with duplication at end point
+ int iSave = i;
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+ float radialFraction = radialCount * inverseRadialSamples;
+ // in [0,1)
+ float phi = FastMath.TWO_PI * radialFraction;
+ float cosPhi = FastMath.cos(phi);
+ float sinPhi = FastMath.sin(phi);
+ tempNormal.set(radialAxis).multLocal(cosPhi);
+ tempNormal.z += sinPhi;
+ fnb.put(tempNormal.x).put(tempNormal.y).put(
+ tempNormal.z);
+
+ tempNormal.multLocal(innerRadius).addLocal(torusMiddle);
+ fpb.put(tempNormal.x).put(tempNormal.y).put(
+ tempNormal.z);
+
+ ftb.put(radialFraction).put(circleFraction);
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(fpb, iSave, i);
+ BufferUtils.copyInternalVector3(fnb, iSave, i);
+
+ ftb.put(1.0f).put(circleFraction);
+
+ i++;
+ }
+
+ // duplicate the cylinder ends to form a torus
+ for (int iR = 0; iR <= radialSamples; iR++, i++) {
+ BufferUtils.copyInternalVector3(fpb, iR, i);
+ BufferUtils.copyInternalVector3(fnb, iR, i);
+ BufferUtils.copyInternalVector2(ftb, iR, i);
+ ftb.put(i * 2 + 1, 1.0f);
+ }
+ }
+
+ private void setIndexData() {
+ // allocate connectivity
+ int triCount = 2 * circleSamples * radialSamples;
+
+ ShortBuffer sib = BufferUtils.createShortBuffer(3 * triCount);
+ setBuffer(Type.Index, 3, sib);
+
+ int i;
+ // generate connectivity
+ int connectionStart = 0;
+ int index = 0;
+ for (int circleCount = 0; circleCount < circleSamples; circleCount++) {
+ int i0 = connectionStart;
+ int i1 = i0 + 1;
+ connectionStart += radialSamples + 1;
+ int i2 = connectionStart;
+ int i3 = i2 + 1;
+ for (i = 0; i < radialSamples; i++, index += 6) {
+// if (true) {
+ sib.put((short)i0++);
+ sib.put((short)i2);
+ sib.put((short)i1);
+ sib.put((short)i1++);
+ sib.put((short)i2++);
+ sib.put((short)i3++);
+
+// getIndexBuffer().put(i0++);
+// getIndexBuffer().put(i2);
+// getIndexBuffer().put(i1);
+// getIndexBuffer().put(i1++);
+// getIndexBuffer().put(i2++);
+// getIndexBuffer().put(i3++);
+// } else {
+// getIndexBuffer().put(i0++);
+// getIndexBuffer().put(i1);
+// getIndexBuffer().put(i2);
+// getIndexBuffer().put(i1++);
+// getIndexBuffer().put(i3++);
+// getIndexBuffer().put(i2++);
+// }
+ }
+ }
+ }
+
+ /**
+ * Rebuilds this torus based on a new set of parameters.
+ *
+ * @param circleSamples the number of samples along the circles.
+ * @param radialSamples the number of samples along the radial.
+ * @param innerRadius the radius of the inner begining of the Torus.
+ * @param outerRadius the radius of the outter end of the Torus.
+ */
+ public void updateGeometry(int circleSamples, int radialSamples, float innerRadius, float outerRadius) {
+ this.circleSamples = circleSamples;
+ this.radialSamples = radialSamples;
+ this.innerRadius = innerRadius;
+ this.outerRadius = outerRadius;
+ setGeometryData();
+ setIndexData();
+ updateBound();
+ updateCounts();
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(circleSamples, "circleSamples", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(innerRadius, "innerRadius", 0);
+ capsule.write(outerRadius, "outerRadius", 0);
+ }
+
+} \ No newline at end of file