Home | History | Annotate | Download | only in scene
      1 /*
      2  * Copyright (c) 2009-2012 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.scene;
     33 
     34 import com.jme3.export.*;
     35 import com.jme3.material.Material;
     36 import com.jme3.math.Matrix4f;
     37 import com.jme3.math.Transform;
     38 import com.jme3.math.Vector3f;
     39 import com.jme3.scene.mesh.IndexBuffer;
     40 import com.jme3.util.IntMap.Entry;
     41 import com.jme3.util.TempVars;
     42 import java.io.IOException;
     43 import java.nio.Buffer;
     44 import java.nio.FloatBuffer;
     45 import java.util.ArrayList;
     46 import java.util.HashMap;
     47 import java.util.List;
     48 import java.util.Map;
     49 import java.util.Set;
     50 import java.util.logging.Level;
     51 import java.util.logging.Logger;
     52 
     53 /**
     54  * BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph.
     55  * There is one geometry per different material in the sub tree.
     56  * this geometries are directly attached to the node in the scene graph.
     57  * 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
     58  * (see todo more automagic for further enhancements)
     59  * all the geometry that have been batched are set to {@link CullHint#Always} to not render them.
     60  * the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch.
     61  * sub geoms can be removed but it may be slower than the normal spatial removing
     62  * Sub geoms can be added after the batch() method has been called but won't be batched and will be rendered as normal geometries.
     63  * To integrate them in the batch you have to call the batch() method again on the batchNode.
     64  *
     65  * TODO normal or tangents or both looks a bit weird
     66  * TODO more automagic (batch when needed in the updateLigicalState)
     67  * @author Nehon
     68  */
     69 public class BatchNode extends Node implements Savable {
     70 
     71     private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
     72     /**
     73      * the map of geometry holding the batched meshes
     74      */
     75     protected Map<Material, Batch> batches = new HashMap<Material, Batch>();
     76     /**
     77      * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
     78      */
     79     private float[] tmpFloat;
     80     private float[] tmpFloatN;
     81     private float[] tmpFloatT;
     82     int maxVertCount = 0;
     83     boolean useTangents = false;
     84     boolean needsFullRebatch = true;
     85 
     86     /**
     87      * Construct a batchNode
     88      */
     89     public BatchNode() {
     90         super();
     91     }
     92 
     93     public BatchNode(String name) {
     94         super(name);
     95     }
     96 
     97     @Override
     98     public void updateGeometricState() {
     99         if ((refreshFlags & RF_LIGHTLIST) != 0) {
    100             updateWorldLightList();
    101         }
    102 
    103         if ((refreshFlags & RF_TRANSFORM) != 0) {
    104             // combine with parent transforms- same for all spatial
    105             // subclasses.
    106             updateWorldTransforms();
    107         }
    108 
    109         if (!children.isEmpty()) {
    110             // the important part- make sure child geometric state is refreshed
    111             // first before updating own world bound. This saves
    112             // a round-trip later on.
    113             // NOTE 9/19/09
    114             // Although it does save a round trip,
    115 
    116             for (Spatial child : children.getArray()) {
    117                 child.updateGeometricState();
    118             }
    119 
    120             for (Batch batch : batches.values()) {
    121                 if (batch.needMeshUpdate) {
    122                     batch.geometry.getMesh().updateBound();
    123                     batch.geometry.updateWorldBound();
    124                     batch.needMeshUpdate = false;
    125 
    126                 }
    127             }
    128 
    129 
    130         }
    131 
    132         if ((refreshFlags & RF_BOUND) != 0) {
    133             updateWorldBound();
    134         }
    135 
    136         assert refreshFlags == 0;
    137     }
    138 
    139     protected Transform getTransforms(Geometry geom) {
    140         return geom.getWorldTransform();
    141     }
    142 
    143     protected void updateSubBatch(Geometry bg) {
    144         Batch batch = batches.get(bg.getMaterial());
    145         if (batch != null) {
    146             Mesh mesh = batch.geometry.getMesh();
    147 
    148             VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
    149             FloatBuffer posBuf = (FloatBuffer) pvb.getData();
    150             VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
    151             FloatBuffer normBuf = (FloatBuffer) nvb.getData();
    152 
    153             if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
    154 
    155                 VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
    156                 FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
    157                 doTransformsTangents(posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
    158                 tvb.updateData(tanBuf);
    159             } else {
    160                 doTransforms(posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
    161             }
    162             pvb.updateData(posBuf);
    163             nvb.updateData(normBuf);
    164 
    165 
    166             batch.needMeshUpdate = true;
    167         }
    168     }
    169 
    170     /**
    171      * Batch this batchNode
    172      * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
    173      */
    174     public void batch() {
    175         doBatch();
    176         //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice
    177         for (Batch batch : batches.values()) {
    178             batch.geometry.setIgnoreTransform(true);
    179         }
    180     }
    181 
    182     protected void doBatch() {
    183         Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
    184         maxVertCount = 0;
    185         int nbGeoms = 0;
    186 
    187         gatherGeomerties(matMap, this, needsFullRebatch);
    188         if (needsFullRebatch) {
    189             for (Batch batch : batches.values()) {
    190                 batch.geometry.removeFromParent();
    191             }
    192             batches.clear();
    193         }
    194         for (Map.Entry<Material, List<Geometry>> entry : matMap.entrySet()) {
    195             Mesh m = new Mesh();
    196             Material material = entry.getKey();
    197             List<Geometry> list = entry.getValue();
    198             nbGeoms += list.size();
    199             if (!needsFullRebatch) {
    200                 list.add(batches.get(material).geometry);
    201             }
    202             mergeGeometries(m, list);
    203             m.setDynamic();
    204             Batch batch = new Batch();
    205 
    206             batch.geometry = new Geometry(name + "-batch" + batches.size());
    207             batch.geometry.setMaterial(material);
    208             this.attachChild(batch.geometry);
    209 
    210 
    211             batch.geometry.setMesh(m);
    212             batch.geometry.getMesh().updateCounts();
    213             batch.geometry.getMesh().updateBound();
    214             batches.put(material, batch);
    215         }
    216 
    217         logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
    218 
    219 
    220         //init temp float arrays
    221         tmpFloat = new float[maxVertCount * 3];
    222         tmpFloatN = new float[maxVertCount * 3];
    223         if (useTangents) {
    224             tmpFloatT = new float[maxVertCount * 4];
    225         }
    226     }
    227 
    228     private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
    229 
    230         if (n.getClass() == Geometry.class) {
    231 
    232             if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) {
    233                 Geometry g = (Geometry) n;
    234                 if (!g.isBatched() || rebatch) {
    235                     if (g.getMaterial() == null) {
    236                         throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching");
    237                     }
    238                     List<Geometry> list = map.get(g.getMaterial());
    239                     if (list == null) {
    240                         list = new ArrayList<Geometry>();
    241                         map.put(g.getMaterial(), list);
    242                     }
    243                     g.setTransformRefresh();
    244                     list.add(g);
    245                 }
    246             }
    247 
    248         } else if (n instanceof Node) {
    249             for (Spatial child : ((Node) n).getChildren()) {
    250                 if (child instanceof BatchNode) {
    251                     continue;
    252                 }
    253                 gatherGeomerties(map, child, rebatch);
    254             }
    255         }
    256 
    257     }
    258 
    259     private boolean isBatch(Spatial s) {
    260         for (Batch batch : batches.values()) {
    261             if (batch.geometry == s) {
    262                 return true;
    263             }
    264         }
    265         return false;
    266     }
    267 
    268     /**
    269      * Sets the material to the all the batches of this BatchNode
    270      * use setMaterial(Material material,int batchIndex) to set a material to a specific batch
    271      *
    272      * @param material the material to use for this geometry
    273      */
    274     @Override
    275     public void setMaterial(Material material) {
    276 //        for (Batch batch : batches.values()) {
    277 //            batch.geometry.setMaterial(material);
    278 //        }
    279         throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
    280     }
    281 
    282     /**
    283      * Returns the material that is used for the first batch of this BatchNode
    284      *
    285      * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
    286      *
    287      * @return the material that is used for the first batch of this BatchNode
    288      *
    289      * @see #setMaterial(com.jme3.material.Material)
    290      */
    291     public Material getMaterial() {
    292         if (!batches.isEmpty()) {
    293             Batch b = batches.get(batches.keySet().iterator().next());
    294             return b.geometry.getMaterial();
    295         }
    296         return null;//material;
    297     }
    298 
    299 //    /**
    300 //     * Sets the material to the a specific batch of this BatchNode
    301 //     *
    302 //     *
    303 //     * @param material the material to use for this geometry
    304 //     */
    305 //    public void setMaterial(Material material,int batchIndex) {
    306 //        if (!batches.isEmpty()) {
    307 //
    308 //        }
    309 //
    310 //    }
    311 //
    312 //    /**
    313 //     * Returns the material that is used for the first batch of this BatchNode
    314 //     *
    315 //     * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
    316 //     *
    317 //     * @return the material that is used for the first batch of this BatchNode
    318 //     *
    319 //     * @see #setMaterial(com.jme3.material.Material)
    320 //     */
    321 //    public Material getMaterial(int batchIndex) {
    322 //        if (!batches.isEmpty()) {
    323 //            Batch b = batches.get(batches.keySet().iterator().next());
    324 //            return b.geometry.getMaterial();
    325 //        }
    326 //        return null;//material;
    327 //    }
    328     @Override
    329     public void write(JmeExporter ex) throws IOException {
    330         super.write(ex);
    331         OutputCapsule oc = ex.getCapsule(this);
    332 //
    333 //        if (material != null) {
    334 //            oc.write(material.getAssetName(), "materialName", null);
    335 //        }
    336 //        oc.write(material, "material", null);
    337 
    338     }
    339 
    340     @Override
    341     public void read(JmeImporter im) throws IOException {
    342         super.read(im);
    343         InputCapsule ic = im.getCapsule(this);
    344 
    345 
    346 //        material = null;
    347 //        String matName = ic.readString("materialName", null);
    348 //        if (matName != null) {
    349 //            // Material name is set,
    350 //            // Attempt to load material via J3M
    351 //            try {
    352 //                material = im.getAssetManager().loadMaterial(matName);
    353 //            } catch (AssetNotFoundException ex) {
    354 //                // Cannot find J3M file.
    355 //                logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
    356 //                        matName);
    357 //            }
    358 //        }
    359 //        // If material is NULL, try to load it from the geometry
    360 //        if (material == null) {
    361 //            material = (Material) ic.readSavable("material", null);
    362 //        }
    363 
    364     }
    365 
    366     /**
    367      * Merges all geometries in the collection into
    368      * the output mesh. Does not take into account materials.
    369      *
    370      * @param geometries
    371      * @param outMesh
    372      */
    373     private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
    374         int[] compsForBuf = new int[VertexBuffer.Type.values().length];
    375         VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];
    376 
    377         int totalVerts = 0;
    378         int totalTris = 0;
    379         int totalLodLevels = 0;
    380 
    381         Mesh.Mode mode = null;
    382         for (Geometry geom : geometries) {
    383             totalVerts += geom.getVertexCount();
    384             totalTris += geom.getTriangleCount();
    385             totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
    386             if (maxVertCount < geom.getVertexCount()) {
    387                 maxVertCount = geom.getVertexCount();
    388             }
    389             Mesh.Mode listMode;
    390             int components;
    391             switch (geom.getMesh().getMode()) {
    392                 case Points:
    393                     listMode = Mesh.Mode.Points;
    394                     components = 1;
    395                     break;
    396                 case LineLoop:
    397                 case LineStrip:
    398                 case Lines:
    399                     listMode = Mesh.Mode.Lines;
    400                     components = 2;
    401                     break;
    402                 case TriangleFan:
    403                 case TriangleStrip:
    404                 case Triangles:
    405                     listMode = Mesh.Mode.Triangles;
    406                     components = 3;
    407                     break;
    408                 default:
    409                     throw new UnsupportedOperationException();
    410             }
    411 
    412             for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) {
    413                 compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents();
    414                 formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat();
    415             }
    416 
    417             if (mode != null && mode != listMode) {
    418                 throw new UnsupportedOperationException("Cannot combine different"
    419                         + " primitive types: " + mode + " != " + listMode);
    420             }
    421             mode = listMode;
    422             compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
    423         }
    424 
    425         outMesh.setMode(mode);
    426         if (totalVerts >= 65536) {
    427             // make sure we create an UnsignedInt buffer so
    428             // we can fit all of the meshes
    429             formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
    430         } else {
    431             formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
    432         }
    433 
    434         // generate output buffers based on retrieved info
    435         for (int i = 0; i < compsForBuf.length; i++) {
    436             if (compsForBuf[i] == 0) {
    437                 continue;
    438             }
    439 
    440             Buffer data;
    441             if (i == VertexBuffer.Type.Index.ordinal()) {
    442                 data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
    443             } else {
    444                 data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
    445             }
    446 
    447             VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]);
    448             vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data);
    449             outMesh.setBuffer(vb);
    450         }
    451 
    452         int globalVertIndex = 0;
    453         int globalTriIndex = 0;
    454 
    455         for (Geometry geom : geometries) {
    456             Mesh inMesh = geom.getMesh();
    457             geom.batch(this, globalVertIndex);
    458 
    459             int geomVertCount = inMesh.getVertexCount();
    460             int geomTriCount = inMesh.getTriangleCount();
    461 
    462             for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
    463                 VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
    464 
    465                 VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
    466 
    467                 if (outBuf == null) {
    468                     continue;
    469                 }
    470 
    471                 if (VertexBuffer.Type.Index.ordinal() == bufType) {
    472                     int components = compsForBuf[bufType];
    473 
    474                     IndexBuffer inIdx = inMesh.getIndicesAsList();
    475                     IndexBuffer outIdx = outMesh.getIndexBuffer();
    476 
    477                     for (int tri = 0; tri < geomTriCount; tri++) {
    478                         for (int comp = 0; comp < components; comp++) {
    479                             int idx = inIdx.get(tri * components + comp) + globalVertIndex;
    480                             outIdx.put((globalTriIndex + tri) * components + comp, idx);
    481                         }
    482                     }
    483                 } else if (VertexBuffer.Type.Position.ordinal() == bufType) {
    484                     FloatBuffer inPos = (FloatBuffer) inBuf.getData();
    485                     FloatBuffer outPos = (FloatBuffer) outBuf.getData();
    486                     doCopyBuffer(inPos, globalVertIndex, outPos, 3);
    487                 } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) {
    488                     FloatBuffer inPos = (FloatBuffer) inBuf.getData();
    489                     FloatBuffer outPos = (FloatBuffer) outBuf.getData();
    490                     doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]);
    491                     if (VertexBuffer.Type.Tangent.ordinal() == bufType) {
    492                         useTangents = true;
    493                     }
    494                 } else {
    495                     inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount);
    496 //                    for (int vert = 0; vert < geomVertCount; vert++) {
    497 //                        int curGlobalVertIndex = globalVertIndex + vert;
    498 //                        inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
    499 //                    }
    500                 }
    501             }
    502 
    503             globalVertIndex += geomVertCount;
    504             globalTriIndex += geomTriCount;
    505         }
    506     }
    507 
    508     private void doTransforms(FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) {
    509         TempVars vars = TempVars.get();
    510         Vector3f pos = vars.vect1;
    511         Vector3f norm = vars.vect2;
    512 
    513         int length = (end - start) * 3;
    514 
    515         // offset is given in element units
    516         // convert to be in component units
    517         int offset = start * 3;
    518         bufPos.position(offset);
    519         bufNorm.position(offset);
    520         bufPos.get(tmpFloat, 0, length);
    521         bufNorm.get(tmpFloatN, 0, length);
    522         int index = 0;
    523         while (index < length) {
    524             pos.x = tmpFloat[index];
    525             norm.x = tmpFloatN[index++];
    526             pos.y = tmpFloat[index];
    527             norm.y = tmpFloatN[index++];
    528             pos.z = tmpFloat[index];
    529             norm.z = tmpFloatN[index];
    530 
    531             transform.mult(pos, pos);
    532             transform.multNormal(norm, norm);
    533 
    534             index -= 2;
    535             tmpFloat[index] = pos.x;
    536             tmpFloatN[index++] = norm.x;
    537             tmpFloat[index] = pos.y;
    538             tmpFloatN[index++] = norm.y;
    539             tmpFloat[index] = pos.z;
    540             tmpFloatN[index++] = norm.z;
    541 
    542         }
    543         vars.release();
    544         bufPos.position(offset);
    545         //using bulk put as it's faster
    546         bufPos.put(tmpFloat, 0, length);
    547         bufNorm.position(offset);
    548         //using bulk put as it's faster
    549         bufNorm.put(tmpFloatN, 0, length);
    550     }
    551 
    552     private void doTransformsTangents(FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
    553         TempVars vars = TempVars.get();
    554         Vector3f pos = vars.vect1;
    555         Vector3f norm = vars.vect2;
    556         Vector3f tan = vars.vect3;
    557 
    558         int length = (end - start) * 3;
    559         int tanLength = (end - start) * 4;
    560 
    561         // offset is given in element units
    562         // convert to be in component units
    563         int offset = start * 3;
    564         int tanOffset = start * 4;
    565 
    566         bufPos.position(offset);
    567         bufNorm.position(offset);
    568         bufTangents.position(tanOffset);
    569         bufPos.get(tmpFloat, 0, length);
    570         bufNorm.get(tmpFloatN, 0, length);
    571         bufTangents.get(tmpFloatT, 0, tanLength);
    572 
    573         int index = 0;
    574         int tanIndex = 0;
    575         while (index < length) {
    576             pos.x = tmpFloat[index];
    577             norm.x = tmpFloatN[index++];
    578             pos.y = tmpFloat[index];
    579             norm.y = tmpFloatN[index++];
    580             pos.z = tmpFloat[index];
    581             norm.z = tmpFloatN[index];
    582 
    583             tan.x = tmpFloatT[tanIndex++];
    584             tan.y = tmpFloatT[tanIndex++];
    585             tan.z = tmpFloatT[tanIndex++];
    586 
    587             transform.mult(pos, pos);
    588             transform.multNormal(norm, norm);
    589             transform.multNormal(tan, tan);
    590 
    591             index -= 2;
    592             tanIndex -= 3;
    593 
    594             tmpFloat[index] = pos.x;
    595             tmpFloatN[index++] = norm.x;
    596             tmpFloat[index] = pos.y;
    597             tmpFloatN[index++] = norm.y;
    598             tmpFloat[index] = pos.z;
    599             tmpFloatN[index++] = norm.z;
    600 
    601             tmpFloatT[tanIndex++] = tan.x;
    602             tmpFloatT[tanIndex++] = tan.y;
    603             tmpFloatT[tanIndex++] = tan.z;
    604 
    605             //Skipping 4th element of tangent buffer (handedness)
    606             tanIndex++;
    607 
    608         }
    609         vars.release();
    610         bufPos.position(offset);
    611         //using bulk put as it's faster
    612         bufPos.put(tmpFloat, 0, length);
    613         bufNorm.position(offset);
    614         //using bulk put as it's faster
    615         bufNorm.put(tmpFloatN, 0, length);
    616         bufTangents.position(tanOffset);
    617         //using bulk put as it's faster
    618         bufTangents.put(tmpFloatT, 0, tanLength);
    619     }
    620 
    621     private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
    622         TempVars vars = TempVars.get();
    623         Vector3f pos = vars.vect1;
    624 
    625         // offset is given in element units
    626         // convert to be in component units
    627         offset *= componentSize;
    628 
    629         for (int i = 0; i < inBuf.capacity() / componentSize; i++) {
    630             pos.x = inBuf.get(i * componentSize + 0);
    631             pos.y = inBuf.get(i * componentSize + 1);
    632             pos.z = inBuf.get(i * componentSize + 2);
    633 
    634             outBuf.put(offset + i * componentSize + 0, pos.x);
    635             outBuf.put(offset + i * componentSize + 1, pos.y);
    636             outBuf.put(offset + i * componentSize + 2, pos.z);
    637         }
    638         vars.release();
    639     }
    640 
    641     protected class Batch {
    642 
    643         Geometry geometry;
    644         boolean needMeshUpdate = false;
    645     }
    646 
    647     protected void setNeedsFullRebatch(boolean needsFullRebatch) {
    648         this.needsFullRebatch = needsFullRebatch;
    649     }
    650 
    651     public int getOffsetIndex(Geometry batchedGeometry){
    652         return batchedGeometry.startIndex;
    653     }
    654 }
    655