BatchNode.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*
  2. * Copyright (c) 2009-2011 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. import com.jme3.export.*;
  34. import com.jme3.material.Material;
  35. import com.jme3.math.Matrix4f;
  36. import com.jme3.math.Transform;
  37. import com.jme3.math.Vector3f;
  38. import com.jme3.scene.mesh.IndexBuffer;
  39. import com.jme3.util.IntMap.Entry;
  40. import com.jme3.util.TempVars;
  41. import java.io.IOException;
  42. import java.nio.Buffer;
  43. import java.nio.FloatBuffer;
  44. import java.util.ArrayList;
  45. import java.util.HashMap;
  46. import java.util.List;
  47. import java.util.Map;
  48. import java.util.logging.Level;
  49. import java.util.logging.Logger;
  50. /**
  51. * BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph.
  52. * There is one geometry per different material in the sub tree.
  53. * this geometries are directly attached to the node in the scene graph.
  54. * 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
  55. * (see todo more automagic for further enhancements)
  56. * all the geometry that have been batched are set to {@link CullHint#Always} to not render them.
  57. * the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch.
  58. * sub geoms can be removed but it may be slower than the normal spatial removing
  59. * Sub geoms can be added after the batch() method has been called but won't be batched and will be rendered as normal geometries.
  60. * To integrate them in the batch you have to call the batch() method again on the batchNode.
  61. *
  62. * TODO normal or tangents or both looks a bit weird
  63. * TODO more automagic (batch when needed in the updateLigicalState)
  64. * @author Nehon
  65. */
  66. public class BatchNode extends Node implements Savable {
  67. private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
  68. /**
  69. * the map of geometry holding the batched meshes
  70. */
  71. protected Map<Material, Batch> batches = new HashMap<Material, Batch>();
  72. /**
  73. * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
  74. */
  75. private float[] tmpFloat;
  76. private float[] tmpFloatN;
  77. private float[] tmpFloatT;
  78. int maxVertCount = 0;
  79. boolean useTangents = false;
  80. /**
  81. * Construct a batchNode
  82. */
  83. public BatchNode() {
  84. super();
  85. }
  86. public BatchNode(String name) {
  87. super(name);
  88. }
  89. @Override
  90. public void updateGeometricState() {
  91. if ((refreshFlags & RF_LIGHTLIST) != 0) {
  92. updateWorldLightList();
  93. }
  94. if ((refreshFlags & RF_TRANSFORM) != 0) {
  95. // combine with parent transforms- same for all spatial
  96. // subclasses.
  97. updateWorldTransforms();
  98. }
  99. if (!children.isEmpty()) {
  100. // the important part- make sure child geometric state is refreshed
  101. // first before updating own world bound. This saves
  102. // a round-trip later on.
  103. // NOTE 9/19/09
  104. // Although it does save a round trip,
  105. for (Spatial child : children.getArray()) {
  106. child.updateGeometricState();
  107. }
  108. for (Batch batch : batches.values()) {
  109. if (batch.needMeshUpdate) {
  110. batch.geometry.getMesh().updateBound();
  111. batch.geometry.updateWorldBound();
  112. batch.needMeshUpdate = false;
  113. }
  114. }
  115. }
  116. if ((refreshFlags & RF_BOUND) != 0) {
  117. updateWorldBound();
  118. }
  119. assert refreshFlags == 0;
  120. }
  121. protected Transform getTransforms(Geometry geom) {
  122. return geom.getWorldTransform();
  123. }
  124. protected void updateSubBatch(Geometry bg) {
  125. Batch batch = batches.get(bg.getMaterial());
  126. if (batch != null) {
  127. Mesh mesh = batch.geometry.getMesh();
  128. VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
  129. FloatBuffer posBuf = (FloatBuffer) pvb.getData();
  130. VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
  131. FloatBuffer normBuf = (FloatBuffer) nvb.getData();
  132. if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
  133. VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
  134. FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
  135. doTransformsTangents(posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
  136. tvb.updateData(tanBuf);
  137. } else {
  138. doTransforms(posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
  139. }
  140. pvb.updateData(posBuf);
  141. nvb.updateData(normBuf);
  142. batch.needMeshUpdate = true;
  143. }
  144. }
  145. /**
  146. * Batch this batchNode
  147. * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
  148. */
  149. public void batch() {
  150. doBatch();
  151. //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice
  152. for (Batch batch : batches.values()) {
  153. batch.geometry.setIgnoreTransform(true);
  154. }
  155. }
  156. protected void doBatch() {
  157. ///List<Geometry> tmpList = new ArrayList<Geometry>();
  158. Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
  159. maxVertCount = 0;
  160. gatherGeomerties(matMap, this);
  161. batches.clear();
  162. int nbGeoms = 0;
  163. for (Material material : matMap.keySet()) {
  164. Mesh m = new Mesh();
  165. List<Geometry> list = matMap.get(material);
  166. nbGeoms += list.size();
  167. mergeGeometries(m, list);
  168. m.setDynamic();
  169. Batch batch = new Batch();
  170. batch.geometry = new Geometry(name + "-batch" + batches.size());
  171. batch.geometry.setMaterial(material);
  172. this.attachChild(batch.geometry);
  173. batch.geometry.setMesh(m);
  174. batch.geometry.getMesh().updateCounts();
  175. batch.geometry.getMesh().updateBound();
  176. batches.put(material, batch);
  177. }
  178. //init temp float arrays
  179. tmpFloat = new float[maxVertCount * 3];
  180. tmpFloatN = new float[maxVertCount * 3];
  181. if (useTangents) {
  182. tmpFloatT = new float[maxVertCount * 4];
  183. }
  184. logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
  185. }
  186. private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n) {
  187. if (n.getClass() == Geometry.class) {
  188. if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) {
  189. Geometry g = (Geometry) n;
  190. if (g.getMaterial() == null) {
  191. throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching");
  192. }
  193. List<Geometry> list = map.get(g.getMaterial());
  194. if (list == null) {
  195. list = new ArrayList<Geometry>();
  196. map.put(g.getMaterial(), list);
  197. }
  198. list.add(g);
  199. }
  200. } else if (n instanceof Node) {
  201. for (Spatial child : ((Node) n).getChildren()) {
  202. if (child instanceof BatchNode) {
  203. continue;
  204. }
  205. gatherGeomerties(map, child);
  206. }
  207. }
  208. }
  209. private boolean isBatch(Spatial s) {
  210. for (Batch batch : batches.values()) {
  211. if (batch.geometry == s) {
  212. return true;
  213. }
  214. }
  215. return false;
  216. }
  217. /**
  218. * Sets the material to the all the batches of this BatchNode
  219. * use setMaterial(Material material,int batchIndex) to set a material to a specific batch
  220. *
  221. * @param material the material to use for this geometry
  222. */
  223. @Override
  224. public void setMaterial(Material material) {
  225. // for (Batch batch : batches.values()) {
  226. // batch.geometry.setMaterial(material);
  227. // }
  228. throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
  229. }
  230. /**
  231. * Returns the material that is used for the first batch of this BatchNode
  232. *
  233. * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
  234. *
  235. * @return the material that is used for the first batch of this BatchNode
  236. *
  237. * @see #setMaterial(com.jme3.material.Material)
  238. */
  239. public Material getMaterial() {
  240. if (!batches.isEmpty()) {
  241. Batch b = batches.get(batches.keySet().iterator().next());
  242. return b.geometry.getMaterial();
  243. }
  244. return null;//material;
  245. }
  246. // /**
  247. // * Sets the material to the a specific batch of this BatchNode
  248. // *
  249. // *
  250. // * @param material the material to use for this geometry
  251. // */
  252. // public void setMaterial(Material material,int batchIndex) {
  253. // if (!batches.isEmpty()) {
  254. //
  255. // }
  256. //
  257. // }
  258. //
  259. // /**
  260. // * Returns the material that is used for the first batch of this BatchNode
  261. // *
  262. // * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
  263. // *
  264. // * @return the material that is used for the first batch of this BatchNode
  265. // *
  266. // * @see #setMaterial(com.jme3.material.Material)
  267. // */
  268. // public Material getMaterial(int batchIndex) {
  269. // if (!batches.isEmpty()) {
  270. // Batch b = batches.get(batches.keySet().iterator().next());
  271. // return b.geometry.getMaterial();
  272. // }
  273. // return null;//material;
  274. // }
  275. @Override
  276. public void write(JmeExporter ex) throws IOException {
  277. super.write(ex);
  278. OutputCapsule oc = ex.getCapsule(this);
  279. //
  280. // if (material != null) {
  281. // oc.write(material.getAssetName(), "materialName", null);
  282. // }
  283. // oc.write(material, "material", null);
  284. }
  285. @Override
  286. public void read(JmeImporter im) throws IOException {
  287. super.read(im);
  288. InputCapsule ic = im.getCapsule(this);
  289. // material = null;
  290. // String matName = ic.readString("materialName", null);
  291. // if (matName != null) {
  292. // // Material name is set,
  293. // // Attempt to load material via J3M
  294. // try {
  295. // material = im.getAssetManager().loadMaterial(matName);
  296. // } catch (AssetNotFoundException ex) {
  297. // // Cannot find J3M file.
  298. // logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
  299. // matName);
  300. // }
  301. // }
  302. // // If material is NULL, try to load it from the geometry
  303. // if (material == null) {
  304. // material = (Material) ic.readSavable("material", null);
  305. // }
  306. }
  307. /**
  308. * Merges all geometries in the collection into
  309. * the output mesh. Does not take into account materials.
  310. *
  311. * @param geometries
  312. * @param outMesh
  313. */
  314. private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
  315. int[] compsForBuf = new int[VertexBuffer.Type.values().length];
  316. VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];
  317. int totalVerts = 0;
  318. int totalTris = 0;
  319. int totalLodLevels = 0;
  320. Mesh.Mode mode = null;
  321. for (Geometry geom : geometries) {
  322. totalVerts += geom.getVertexCount();
  323. totalTris += geom.getTriangleCount();
  324. totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
  325. if (maxVertCount < geom.getVertexCount()) {
  326. maxVertCount = geom.getVertexCount();
  327. }
  328. Mesh.Mode listMode;
  329. int components;
  330. switch (geom.getMesh().getMode()) {
  331. case Points:
  332. listMode = Mesh.Mode.Points;
  333. components = 1;
  334. break;
  335. case LineLoop:
  336. case LineStrip:
  337. case Lines:
  338. listMode = Mesh.Mode.Lines;
  339. components = 2;
  340. break;
  341. case TriangleFan:
  342. case TriangleStrip:
  343. case Triangles:
  344. listMode = Mesh.Mode.Triangles;
  345. components = 3;
  346. break;
  347. default:
  348. throw new UnsupportedOperationException();
  349. }
  350. for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
  351. compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
  352. formatForBuf[entry.getKey()] = entry.getValue().getFormat();
  353. }
  354. if (mode != null && mode != listMode) {
  355. throw new UnsupportedOperationException("Cannot combine different"
  356. + " primitive types: " + mode + " != " + listMode);
  357. }
  358. mode = listMode;
  359. compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
  360. }
  361. outMesh.setMode(mode);
  362. if (totalVerts >= 65536) {
  363. // make sure we create an UnsignedInt buffer so
  364. // we can fit all of the meshes
  365. formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
  366. } else {
  367. formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
  368. }
  369. // generate output buffers based on retrieved info
  370. for (int i = 0; i < compsForBuf.length; i++) {
  371. if (compsForBuf[i] == 0) {
  372. continue;
  373. }
  374. Buffer data;
  375. if (i == VertexBuffer.Type.Index.ordinal()) {
  376. data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
  377. } else {
  378. data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
  379. }
  380. VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]);
  381. vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data);
  382. outMesh.setBuffer(vb);
  383. }
  384. int globalVertIndex = 0;
  385. int globalTriIndex = 0;
  386. for (Geometry geom : geometries) {
  387. Mesh inMesh = geom.getMesh();
  388. geom.batch(this, globalVertIndex);
  389. int geomVertCount = inMesh.getVertexCount();
  390. int geomTriCount = inMesh.getTriangleCount();
  391. for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
  392. VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
  393. VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
  394. if (outBuf == null) {
  395. continue;
  396. }
  397. if (VertexBuffer.Type.Index.ordinal() == bufType) {
  398. int components = compsForBuf[bufType];
  399. IndexBuffer inIdx = inMesh.getIndicesAsList();
  400. IndexBuffer outIdx = outMesh.getIndexBuffer();
  401. for (int tri = 0; tri < geomTriCount; tri++) {
  402. for (int comp = 0; comp < components; comp++) {
  403. int idx = inIdx.get(tri * components + comp) + globalVertIndex;
  404. outIdx.put((globalTriIndex + tri) * components + comp, idx);
  405. }
  406. }
  407. } else if (VertexBuffer.Type.Position.ordinal() == bufType) {
  408. FloatBuffer inPos = (FloatBuffer) inBuf.getData();
  409. FloatBuffer outPos = (FloatBuffer) outBuf.getData();
  410. doCopyBuffer(inPos, globalVertIndex, outPos);
  411. } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) {
  412. FloatBuffer inPos = (FloatBuffer) inBuf.getData();
  413. FloatBuffer outPos = (FloatBuffer) outBuf.getData();
  414. doCopyBuffer(inPos, globalVertIndex, outPos);
  415. if (VertexBuffer.Type.Tangent.ordinal() == bufType) {
  416. useTangents = true;
  417. }
  418. } else {
  419. for (int vert = 0; vert < geomVertCount; vert++) {
  420. int curGlobalVertIndex = globalVertIndex + vert;
  421. inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
  422. }
  423. }
  424. }
  425. globalVertIndex += geomVertCount;
  426. globalTriIndex += geomTriCount;
  427. }
  428. }
  429. private void doTransforms(FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) {
  430. TempVars vars = TempVars.get();
  431. Vector3f pos = vars.vect1;
  432. Vector3f norm = vars.vect2;
  433. int length = (end - start) * 3;
  434. // offset is given in element units
  435. // convert to be in component units
  436. int offset = start * 3;
  437. bufPos.position(offset);
  438. bufNorm.position(offset);
  439. bufPos.get(tmpFloat, 0, length);
  440. bufNorm.get(tmpFloatN, 0, length);
  441. int index = 0;
  442. while (index < length) {
  443. pos.x = tmpFloat[index];
  444. norm.x = tmpFloatN[index++];
  445. pos.y = tmpFloat[index];
  446. norm.y = tmpFloatN[index++];
  447. pos.z = tmpFloat[index];
  448. norm.z = tmpFloatN[index];
  449. transform.mult(pos, pos);
  450. transform.multNormal(norm, norm);
  451. index -= 2;
  452. tmpFloat[index] = pos.x;
  453. tmpFloatN[index++] = norm.x;
  454. tmpFloat[index] = pos.y;
  455. tmpFloatN[index++] = norm.y;
  456. tmpFloat[index] = pos.z;
  457. tmpFloatN[index++] = norm.z;
  458. }
  459. vars.release();
  460. bufPos.position(offset);
  461. //using bulk put as it's faster
  462. bufPos.put(tmpFloat, 0, length);
  463. bufNorm.position(offset);
  464. //using bulk put as it's faster
  465. bufNorm.put(tmpFloatN, 0, length);
  466. }
  467. private void doTransformsTangents(FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
  468. TempVars vars = TempVars.get();
  469. Vector3f pos = vars.vect1;
  470. Vector3f norm = vars.vect2;
  471. Vector3f tan = vars.vect3;
  472. int length = (end - start) * 3;
  473. int tanLength = (end - start) * 4;
  474. // offset is given in element units
  475. // convert to be in component units
  476. int offset = start * 3;
  477. int tanOffset = start * 4;
  478. bufPos.position(offset);
  479. bufNorm.position(offset);
  480. bufTangents.position(tanOffset);
  481. bufPos.get(tmpFloat, 0, length);
  482. bufNorm.get(tmpFloatN, 0, length);
  483. bufTangents.get(tmpFloatT, 0, tanLength);
  484. int index = 0;
  485. int tanIndex = 0;
  486. while (index < length) {
  487. pos.x = tmpFloat[index];
  488. norm.x = tmpFloatN[index++];
  489. pos.y = tmpFloat[index];
  490. norm.y = tmpFloatN[index++];
  491. pos.z = tmpFloat[index];
  492. norm.z = tmpFloatN[index];
  493. tan.x = tmpFloatT[tanIndex++];
  494. tan.y = tmpFloatT[tanIndex++];
  495. tan.z = tmpFloatT[tanIndex++];
  496. transform.mult(pos, pos);
  497. transform.multNormal(norm, norm);
  498. transform.multNormal(tan, tan);
  499. index -= 2;
  500. tanIndex -= 3;
  501. tmpFloat[index] = pos.x;
  502. tmpFloatN[index++] = norm.x;
  503. tmpFloat[index] = pos.y;
  504. tmpFloatN[index++] = norm.y;
  505. tmpFloat[index] = pos.z;
  506. tmpFloatN[index++] = norm.z;
  507. tmpFloatT[tanIndex++] = tan.x;
  508. tmpFloatT[tanIndex++] = tan.y;
  509. tmpFloatT[tanIndex++] = tan.z;
  510. tanIndex++;
  511. }
  512. vars.release();
  513. bufPos.position(offset);
  514. //using bulk put as it's faster
  515. bufPos.put(tmpFloat, 0, length);
  516. bufNorm.position(offset);
  517. //using bulk put as it's faster
  518. bufNorm.put(tmpFloatN, 0, length);
  519. bufTangents.position(tanOffset);
  520. //using bulk put as it's faster
  521. bufTangents.put(tmpFloatT, 0, tanLength);
  522. }
  523. private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf) {
  524. TempVars vars = TempVars.get();
  525. Vector3f pos = vars.vect1;
  526. // offset is given in element units
  527. // convert to be in component units
  528. offset *= 3;
  529. for (int i = 0; i < inBuf.capacity() / 3; i++) {
  530. pos.x = inBuf.get(i * 3 + 0);
  531. pos.y = inBuf.get(i * 3 + 1);
  532. pos.z = inBuf.get(i * 3 + 2);
  533. outBuf.put(offset + i * 3 + 0, pos.x);
  534. outBuf.put(offset + i * 3 + 1, pos.y);
  535. outBuf.put(offset + i * 3 + 2, pos.z);
  536. }
  537. vars.release();
  538. }
  539. protected class Batch {
  540. Geometry geometry;
  541. boolean needMeshUpdate = false;
  542. }
  543. }