| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- /*
- * Copyright (c) 2009-2011 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;
- /**
- * 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() {
- ///List<Geometry> tmpList = new ArrayList<Geometry>();
- Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
- maxVertCount = 0;
- gatherGeomerties(matMap, this);
- batches.clear();
- int nbGeoms = 0;
- for (Material material : matMap.keySet()) {
- Mesh m = new Mesh();
- List<Geometry> list = matMap.get(material);
- nbGeoms += list.size();
- 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);
- }
- //init temp float arrays
- tmpFloat = new float[maxVertCount * 3];
- tmpFloatN = new float[maxVertCount * 3];
- if (useTangents) {
- tmpFloatT = new float[maxVertCount * 4];
- }
- logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
- }
- private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n) {
- if (n.getClass() == Geometry.class) {
- if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) {
- Geometry g = (Geometry) n;
- 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);
- }
- list.add(g);
- }
- } else if (n instanceof Node) {
- for (Spatial child : ((Node) n).getChildren()) {
- if (child instanceof BatchNode) {
- continue;
- }
- gatherGeomerties(map, child);
- }
- }
- }
- 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);
- } 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);
- if (VertexBuffer.Type.Tangent.ordinal() == bufType) {
- useTangents = true;
- }
- } else {
- 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;
- 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) {
- TempVars vars = TempVars.get();
- Vector3f pos = vars.vect1;
- // offset is given in element units
- // convert to be in component units
- offset *= 3;
- for (int i = 0; i < inBuf.capacity() / 3; i++) {
- pos.x = inBuf.get(i * 3 + 0);
- pos.y = inBuf.get(i * 3 + 1);
- pos.z = inBuf.get(i * 3 + 2);
- outBuf.put(offset + i * 3 + 0, pos.x);
- outBuf.put(offset + i * 3 + 1, pos.y);
- outBuf.put(offset + i * 3 + 2, pos.z);
- }
- vars.release();
- }
- protected class Batch {
- Geometry geometry;
- boolean needMeshUpdate = false;
- }
- }
|