diff options
Diffstat (limited to 'engine/src/core/com/jme3/scene')
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 |