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      */
BatchNode()89     public BatchNode() {
90         super();
91     }
92 
BatchNode(String name)93     public BatchNode(String name) {
94         super(name);
95     }
96 
97     @Override
updateGeometricState()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 
getTransforms(Geometry geom)139     protected Transform getTransforms(Geometry geom) {
140         return geom.getWorldTransform();
141     }
142 
updateSubBatch(Geometry bg)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      */
batch()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 
doBatch()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 
gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch)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 
isBatch(Spatial s)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
setMaterial(Material material)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      */
getMaterial()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
write(JmeExporter ex)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
read(JmeImporter im)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      */
mergeGeometries(Mesh outMesh, List<Geometry> geometries)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 
doTransforms(FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform)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 
doTransformsTangents(FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform)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 
doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize)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 
setNeedsFullRebatch(boolean needsFullRebatch)647     protected void setNeedsFullRebatch(boolean needsFullRebatch) {
648         this.needsFullRebatch = needsFullRebatch;
649     }
650 
getOffsetIndex(Geometry batchedGeometry)651     public int getOffsetIndex(Geometry batchedGeometry){
652         return batchedGeometry.startIndex;
653     }
654 }
655