Main repository of MikuMikuStudio
Revision | 654ba48c1411cde75816cfb78e800410438e66ac (tree) |
---|---|
Time | 2013-07-07 06:22:17 |
Author | kobayasi <kobayasi@pscn...> |
Commiter | kobayasi |
merge from jme10694
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2011 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -31,17 +31,13 @@ | ||
31 | 31 | */ |
32 | 32 | package com.jme3.scene; |
33 | 33 | |
34 | -import com.jme3.export.InputCapsule; | |
35 | -import com.jme3.export.JmeExporter; | |
36 | -import com.jme3.export.JmeImporter; | |
37 | -import com.jme3.export.OutputCapsule; | |
38 | -import com.jme3.export.Savable; | |
34 | +import com.jme3.export.*; | |
39 | 35 | import com.jme3.material.Material; |
40 | 36 | import com.jme3.math.Matrix4f; |
41 | 37 | import com.jme3.math.Transform; |
42 | 38 | import com.jme3.math.Vector3f; |
43 | 39 | import com.jme3.scene.mesh.IndexBuffer; |
44 | -import com.jme3.util.IntMap.Entry; | |
40 | +import com.jme3.util.SafeArrayList; | |
45 | 41 | import com.jme3.util.TempVars; |
46 | 42 | import java.io.IOException; |
47 | 43 | import java.nio.Buffer; |
@@ -54,28 +50,41 @@ import java.util.logging.Level; | ||
54 | 50 | import java.util.logging.Logger; |
55 | 51 | |
56 | 52 | /** |
57 | - * BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph. | |
53 | + * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph. | |
58 | 54 | * There is one geometry per different material in the sub tree. |
59 | - * this geometries are directly attached to the node in the scene graph. | |
60 | - * 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 | + * The geometries are directly attached to the node in the scene graph. | |
56 | + * Usage is like any other node except you have to call the {@link #batch()} method once all the geometries have been attached to the sub scene graph and their material set | |
61 | 57 | * (see todo more automagic for further enhancements) |
62 | - * all the geometry that have been batched are set to {@link CullHint#Always} to not render them. | |
63 | - * the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch. | |
64 | - * sub geoms can be removed but it may be slower than the normal spatial removing | |
65 | - * Sub geoms can be added after the batch() method has been called but won't be batched and will be rendered as normal geometries. | |
58 | + * All the geometries that have been batched are set to {@link CullHint#Always} to not render them. | |
59 | + * The sub geometries can be transformed as usual, their transforms are used to update the mesh of the geometryBatch. | |
60 | + * Sub geoms can be removed but it may be slower than the normal spatial removing | |
61 | + * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries. | |
66 | 62 | * To integrate them in the batch you have to call the batch() method again on the batchNode. |
67 | 63 | * |
68 | 64 | * TODO normal or tangents or both looks a bit weird |
69 | - * TODO more automagic (batch when needed in the updateLigicalState) | |
65 | + * TODO more automagic (batch when needed in the updateLogicalState) | |
70 | 66 | * @author Nehon |
71 | 67 | */ |
72 | 68 | public class BatchNode extends Node implements Savable { |
73 | 69 | |
74 | 70 | private static final Logger logger = Logger.getLogger(BatchNode.class.getName()); |
75 | 71 | /** |
76 | - * the map of geometry holding the batched meshes | |
72 | + * the list of geometry holding the batched meshes | |
77 | 73 | */ |
78 | - protected Map<Material, Batch> batches = new HashMap<Material, Batch>(); | |
74 | + protected SafeArrayList<Batch> batches = new SafeArrayList<Batch>(Batch.class); | |
75 | + /** | |
76 | + * a map storing he batches by geometry to quickly acces the batch when updating | |
77 | + */ | |
78 | + protected Map<Geometry, Batch> batchesByGeom = new HashMap<Geometry, Batch>(); | |
79 | + /** | |
80 | + * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer | |
81 | + */ | |
82 | + private float[] tmpFloat; | |
83 | + private float[] tmpFloatN; | |
84 | + private float[] tmpFloatT; | |
85 | + int maxVertCount = 0; | |
86 | + boolean useTangents = false; | |
87 | + boolean needsFullRebatch = true; | |
79 | 88 | |
80 | 89 | /** |
81 | 90 | * Construct a batchNode |
@@ -111,9 +120,9 @@ public class BatchNode extends Node implements Savable { | ||
111 | 120 | child.updateGeometricState(); |
112 | 121 | } |
113 | 122 | |
114 | - for (Batch batch : batches.values()) { | |
123 | + for (Batch batch : batches.getArray()) { | |
115 | 124 | if (batch.needMeshUpdate) { |
116 | - batch.geometry.getMesh().updateBound(); | |
125 | + batch.geometry.updateModelBound(); | |
117 | 126 | batch.geometry.updateWorldBound(); |
118 | 127 | batch.needMeshUpdate = false; |
119 | 128 |
@@ -129,31 +138,42 @@ public class BatchNode extends Node implements Savable { | ||
129 | 138 | |
130 | 139 | assert refreshFlags == 0; |
131 | 140 | } |
132 | - | |
133 | - protected Transform getTransforms(Geometry geom){ | |
134 | - return geom.getWorldTransform(); | |
135 | - } | |
136 | 141 | |
142 | + protected Matrix4f getTransformMatrix(Geometry g){ | |
143 | + return g.cachedWorldMat; | |
144 | + } | |
145 | + | |
137 | 146 | protected void updateSubBatch(Geometry bg) { |
138 | - Batch batch = batches.get(bg.getMaterial()); | |
147 | + Batch batch = batchesByGeom.get(bg); | |
139 | 148 | if (batch != null) { |
140 | 149 | Mesh mesh = batch.geometry.getMesh(); |
141 | - | |
142 | - FloatBuffer buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Position).getData(); | |
143 | - doTransformVerts(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat); | |
144 | - mesh.getBuffer(VertexBuffer.Type.Position).updateData(buf); | |
145 | - | |
146 | - buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Normal).getData(); | |
147 | - doTransformNorm(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat); | |
148 | - mesh.getBuffer(VertexBuffer.Type.Normal).updateData(buf); | |
149 | - | |
150 | - | |
150 | + Mesh origMesh = bg.getMesh(); | |
151 | + | |
152 | + VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position); | |
153 | + FloatBuffer posBuf = (FloatBuffer) pvb.getData(); | |
154 | + VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal); | |
155 | + FloatBuffer normBuf = (FloatBuffer) nvb.getData(); | |
156 | + | |
157 | + VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position); | |
158 | + FloatBuffer oposBuf = (FloatBuffer) opvb.getData(); | |
159 | + VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal); | |
160 | + FloatBuffer onormBuf = (FloatBuffer) onvb.getData(); | |
161 | + Matrix4f transformMat = getTransformMatrix(bg); | |
162 | + | |
151 | 163 | if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) { |
152 | 164 | |
153 | - buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Tangent).getData(); | |
154 | - doTransformNorm(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat); | |
155 | - mesh.getBuffer(VertexBuffer.Type.Tangent).updateData(buf); | |
165 | + VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent); | |
166 | + FloatBuffer tanBuf = (FloatBuffer) tvb.getData(); | |
167 | + VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent); | |
168 | + FloatBuffer otanBuf = (FloatBuffer) otvb.getData(); | |
169 | + doTransformsTangents(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat); | |
170 | + tvb.updateData(tanBuf); | |
171 | + } else { | |
172 | + doTransforms(oposBuf, onormBuf, posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat); | |
156 | 173 | } |
174 | + pvb.updateData(posBuf); | |
175 | + nvb.updateData(normBuf); | |
176 | + | |
157 | 177 | |
158 | 178 | batch.needMeshUpdate = true; |
159 | 179 | } |
@@ -166,26 +186,55 @@ public class BatchNode extends Node implements Savable { | ||
166 | 186 | public void batch() { |
167 | 187 | doBatch(); |
168 | 188 | //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice |
169 | - for (Batch batch : batches.values()) { | |
189 | + for (Batch batch : batches.getArray()) { | |
170 | 190 | batch.geometry.setIgnoreTransform(true); |
191 | + batch.geometry.setUserData(UserData.JME_PHYSICSIGNORE, true); | |
171 | 192 | } |
193 | + updateGeometricState(); | |
172 | 194 | } |
173 | 195 | |
174 | 196 | protected void doBatch() { |
175 | - ///List<Geometry> tmpList = new ArrayList<Geometry>(); | |
176 | - Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>(); | |
177 | - | |
178 | - gatherGeomerties(matMap, this); | |
179 | - batches.clear(); | |
197 | + Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>(); | |
180 | 198 | int nbGeoms = 0; |
181 | - for (Material material : matMap.keySet()) { | |
199 | + | |
200 | + gatherGeomerties(matMap, this, needsFullRebatch); | |
201 | + if (needsFullRebatch) { | |
202 | + for (Batch batch : batches.getArray()) { | |
203 | + batch.geometry.removeFromParent(); | |
204 | + } | |
205 | + batches.clear(); | |
206 | + batchesByGeom.clear(); | |
207 | + } | |
208 | + //only reset maxVertCount if there is something new to batch | |
209 | + if (matMap.size() > 0) { | |
210 | + maxVertCount = 0; | |
211 | + } | |
212 | + | |
213 | + for (Map.Entry<Material, List<Geometry>> entry : matMap.entrySet()) { | |
182 | 214 | Mesh m = new Mesh(); |
183 | - List<Geometry> list = matMap.get(material); | |
215 | + Material material = entry.getKey(); | |
216 | + List<Geometry> list = entry.getValue(); | |
184 | 217 | nbGeoms += list.size(); |
218 | + String batchName = name + "-batch" + batches.size(); | |
219 | + Batch batch; | |
220 | + if (!needsFullRebatch) { | |
221 | + batch = findBatchByMaterial(material); | |
222 | + if (batch != null) { | |
223 | + list.add(0, batch.geometry); | |
224 | + batchName = batch.geometry.getName(); | |
225 | + batch.geometry.removeFromParent(); | |
226 | + } else { | |
227 | + batch = new Batch(); | |
228 | + } | |
229 | + } else { | |
230 | + batch = new Batch(); | |
231 | + } | |
185 | 232 | mergeGeometries(m, list); |
186 | - Batch batch = new Batch(); | |
233 | + m.setDynamic(); | |
234 | + | |
235 | + batch.updateGeomList(list); | |
187 | 236 | |
188 | - batch.geometry = new Geometry(name + "-batch" + batches.size()); | |
237 | + batch.geometry = new Geometry(batchName); | |
189 | 238 | batch.geometry.setMaterial(material); |
190 | 239 | this.attachChild(batch.geometry); |
191 | 240 |
@@ -193,25 +242,81 @@ public class BatchNode extends Node implements Savable { | ||
193 | 242 | batch.geometry.setMesh(m); |
194 | 243 | batch.geometry.getMesh().updateCounts(); |
195 | 244 | batch.geometry.getMesh().updateBound(); |
196 | - batches.put(material, batch); | |
245 | + batches.add(batch); | |
246 | + } | |
247 | + if (batches.size() > 0) { | |
248 | + needsFullRebatch = false; | |
249 | + } | |
250 | + | |
251 | + | |
252 | + logger.log(Level.FINE, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()}); | |
253 | + | |
254 | + //init the temp arrays if something has been batched only. | |
255 | + if(matMap.size()>0){ | |
256 | + //TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed. | |
257 | + //init temp float arrays | |
258 | + tmpFloat = new float[maxVertCount * 3]; | |
259 | + tmpFloatN = new float[maxVertCount * 3]; | |
260 | + if (useTangents) { | |
261 | + tmpFloatT = new float[maxVertCount * 4]; | |
262 | + } | |
197 | 263 | } |
198 | - logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()}); | |
199 | 264 | } |
200 | 265 | |
201 | - private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n) { | |
266 | + //in case the detached spatial is a node, we unbatch all geometries in its subegraph | |
267 | + @Override | |
268 | + public Spatial detachChildAt(int index) { | |
269 | + Spatial s = super.detachChildAt(index); | |
270 | + if (s instanceof Node) { | |
271 | + unbatchSubGraph(s); | |
272 | + } | |
273 | + return s; | |
274 | + } | |
202 | 275 | |
203 | - if (n.getClass() == Geometry.class) { | |
204 | - if (!isBatch(n)) { | |
276 | + /** | |
277 | + * recursively visit the subgraph and unbatch geometries | |
278 | + * @param s | |
279 | + */ | |
280 | + private void unbatchSubGraph(Spatial s) { | |
281 | + if (s instanceof Node) { | |
282 | + for (Spatial sp : ((Node) s).getChildren()) { | |
283 | + unbatchSubGraph(sp); | |
284 | + } | |
285 | + } else if (s instanceof Geometry) { | |
286 | + Geometry g = (Geometry) s; | |
287 | + if (g.isBatched()) { | |
288 | + g.unBatch(); | |
289 | + } | |
290 | + } | |
291 | + } | |
292 | + | |
293 | + | |
294 | + private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) { | |
295 | + | |
296 | + if (n instanceof Geometry) { | |
297 | + | |
298 | + if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) { | |
205 | 299 | Geometry g = (Geometry) n; |
206 | - if (g.getMaterial() == null) { | |
207 | - throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching"); | |
208 | - } | |
209 | - List<Geometry> list = map.get(g.getMaterial()); | |
210 | - if (list == null) { | |
211 | - list = new ArrayList<Geometry>(); | |
212 | - map.put(g.getMaterial(), list); | |
300 | + if (!g.isBatched() || rebatch) { | |
301 | + if (g.getMaterial() == null) { | |
302 | + throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching"); | |
303 | + } | |
304 | + List<Geometry> list = map.get(g.getMaterial()); | |
305 | + if (list == null) { | |
306 | + //trying to compare materials with the isEqual method | |
307 | + for (Map.Entry<Material, List<Geometry>> mat : map.entrySet()) { | |
308 | + if (g.getMaterial().contentEquals(mat.getKey())) { | |
309 | + list = mat.getValue(); | |
310 | + } | |
311 | + } | |
312 | + } | |
313 | + if (list == null) { | |
314 | + list = new ArrayList<Geometry>(); | |
315 | + map.put(g.getMaterial(), list); | |
316 | + } | |
317 | + g.setTransformRefresh(); | |
318 | + list.add(g); | |
213 | 319 | } |
214 | - list.add(g); | |
215 | 320 | } |
216 | 321 | |
217 | 322 | } else if (n instanceof Node) { |
@@ -219,14 +324,23 @@ public class BatchNode extends Node implements Savable { | ||
219 | 324 | if (child instanceof BatchNode) { |
220 | 325 | continue; |
221 | 326 | } |
222 | - gatherGeomerties(map, child); | |
327 | + gatherGeomerties(map, child, rebatch); | |
223 | 328 | } |
224 | 329 | } |
225 | 330 | |
226 | 331 | } |
227 | 332 | |
333 | + private Batch findBatchByMaterial(Material m) { | |
334 | + for (Batch batch : batches.getArray()) { | |
335 | + if (batch.geometry.getMaterial().contentEquals(m)) { | |
336 | + return batch; | |
337 | + } | |
338 | + } | |
339 | + return null; | |
340 | + } | |
341 | + | |
228 | 342 | private boolean isBatch(Spatial s) { |
229 | - for (Batch batch : batches.values()) { | |
343 | + for (Batch batch : batches.getArray()) { | |
230 | 344 | if (batch.geometry == s) { |
231 | 345 | return true; |
232 | 346 | } |
@@ -259,12 +373,12 @@ public class BatchNode extends Node implements Savable { | ||
259 | 373 | */ |
260 | 374 | public Material getMaterial() { |
261 | 375 | if (!batches.isEmpty()) { |
262 | - Batch b = batches.get(batches.keySet().iterator().next()); | |
376 | + Batch b = batches.iterator().next(); | |
263 | 377 | return b.geometry.getMaterial(); |
264 | 378 | } |
265 | 379 | return null;//material; |
266 | 380 | } |
267 | - | |
381 | + | |
268 | 382 | // /** |
269 | 383 | // * Sets the material to the a specific batch of this BatchNode |
270 | 384 | // * |
@@ -294,7 +408,6 @@ public class BatchNode extends Node implements Savable { | ||
294 | 408 | // } |
295 | 409 | // return null;//material; |
296 | 410 | // } |
297 | - | |
298 | 411 | @Override |
299 | 412 | public void write(JmeExporter ex) throws IOException { |
300 | 413 | super.write(ex); |
@@ -347,13 +460,16 @@ public class BatchNode extends Node implements Savable { | ||
347 | 460 | int totalVerts = 0; |
348 | 461 | int totalTris = 0; |
349 | 462 | int totalLodLevels = 0; |
463 | + int maxWeights = -1; | |
350 | 464 | |
351 | 465 | Mesh.Mode mode = null; |
352 | 466 | for (Geometry geom : geometries) { |
353 | 467 | totalVerts += geom.getVertexCount(); |
354 | 468 | totalTris += geom.getTriangleCount(); |
355 | 469 | totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); |
356 | - | |
470 | + if (maxVertCount < geom.getVertexCount()) { | |
471 | + maxVertCount = geom.getVertexCount(); | |
472 | + } | |
357 | 473 | Mesh.Mode listMode; |
358 | 474 | int components; |
359 | 475 | switch (geom.getMesh().getMode()) { |
@@ -377,10 +493,12 @@ public class BatchNode extends Node implements Savable { | ||
377 | 493 | throw new UnsupportedOperationException(); |
378 | 494 | } |
379 | 495 | |
380 | - for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) { | |
381 | - compsForBuf[entry.getKey()] = entry.getValue().getNumComponents(); | |
382 | - formatForBuf[entry.getKey()] = entry.getValue().getFormat(); | |
496 | + for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) { | |
497 | + compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); | |
498 | + formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); | |
383 | 499 | } |
500 | + | |
501 | + maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights()); | |
384 | 502 | |
385 | 503 | if (mode != null && mode != listMode) { |
386 | 504 | throw new UnsupportedOperationException("Cannot combine different" |
@@ -390,6 +508,7 @@ public class BatchNode extends Node implements Savable { | ||
390 | 508 | compsForBuf[VertexBuffer.Type.Index.ordinal()] = components; |
391 | 509 | } |
392 | 510 | |
511 | + outMesh.setMaxNumWeights(maxWeights); | |
393 | 512 | outMesh.setMode(mode); |
394 | 513 | if (totalVerts >= 65536) { |
395 | 514 | // make sure we create an UnsignedInt buffer so |
@@ -413,7 +532,7 @@ public class BatchNode extends Node implements Savable { | ||
413 | 532 | } |
414 | 533 | |
415 | 534 | VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]); |
416 | - vb.setupData(VertexBuffer.Usage.Static, compsForBuf[i], formatForBuf[i], data); | |
535 | + vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data); | |
417 | 536 | outMesh.setBuffer(vb); |
418 | 537 | } |
419 | 538 |
@@ -422,13 +541,16 @@ public class BatchNode extends Node implements Savable { | ||
422 | 541 | |
423 | 542 | for (Geometry geom : geometries) { |
424 | 543 | Mesh inMesh = geom.getMesh(); |
425 | - geom.batch(this, globalVertIndex); | |
544 | + if (!isBatch(geom)) { | |
545 | + geom.batch(this, globalVertIndex); | |
546 | + } | |
426 | 547 | |
427 | 548 | int geomVertCount = inMesh.getVertexCount(); |
428 | 549 | int geomTriCount = inMesh.getTriangleCount(); |
429 | 550 | |
430 | 551 | for (int bufType = 0; bufType < compsForBuf.length; bufType++) { |
431 | 552 | VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]); |
553 | + | |
432 | 554 | VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]); |
433 | 555 | |
434 | 556 | if (outBuf == null) { |
@@ -450,16 +572,20 @@ public class BatchNode extends Node implements Savable { | ||
450 | 572 | } else if (VertexBuffer.Type.Position.ordinal() == bufType) { |
451 | 573 | FloatBuffer inPos = (FloatBuffer) inBuf.getData(); |
452 | 574 | FloatBuffer outPos = (FloatBuffer) outBuf.getData(); |
453 | - doCopyBuffer(inPos, globalVertIndex, outPos); | |
575 | + doCopyBuffer(inPos, globalVertIndex, outPos, 3); | |
454 | 576 | } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) { |
455 | 577 | FloatBuffer inPos = (FloatBuffer) inBuf.getData(); |
456 | 578 | FloatBuffer outPos = (FloatBuffer) outBuf.getData(); |
457 | - doCopyBuffer(inPos, globalVertIndex, outPos); | |
458 | - } else { | |
459 | - for (int vert = 0; vert < geomVertCount; vert++) { | |
460 | - int curGlobalVertIndex = globalVertIndex + vert; | |
461 | - inBuf.copyElement(vert, outBuf, curGlobalVertIndex); | |
579 | + doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]); | |
580 | + if (VertexBuffer.Type.Tangent.ordinal() == bufType) { | |
581 | + useTangents = true; | |
462 | 582 | } |
583 | + } else { | |
584 | + inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); | |
585 | +// for (int vert = 0; vert < geomVertCount; vert++) { | |
586 | +// int curGlobalVertIndex = globalVertIndex + vert; | |
587 | +// inBuf.copyElement(vert, outBuf, curGlobalVertIndex); | |
588 | +// } | |
463 | 589 | } |
464 | 590 | } |
465 | 591 |
@@ -468,75 +594,164 @@ public class BatchNode extends Node implements Savable { | ||
468 | 594 | } |
469 | 595 | } |
470 | 596 | |
471 | - private void doTransformVerts(FloatBuffer inBuf, int offset, int start, int end, FloatBuffer outBuf, Matrix4f transform) { | |
597 | + private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) { | |
472 | 598 | TempVars vars = TempVars.get(); |
473 | 599 | Vector3f pos = vars.vect1; |
600 | + Vector3f norm = vars.vect2; | |
601 | + | |
602 | + int length = (end - start) * 3; | |
474 | 603 | |
475 | 604 | // offset is given in element units |
476 | 605 | // convert to be in component units |
477 | - offset *= 3; | |
478 | - | |
479 | - for (int i = start; i < end; i++) { | |
480 | - int index = i * 3; | |
481 | - pos.x = inBuf.get(index); | |
482 | - pos.y = inBuf.get(index + 1); | |
483 | - pos.z = inBuf.get(index + 2); | |
606 | + int offset = start * 3; | |
607 | + bindBufPos.rewind(); | |
608 | + bindBufNorm.rewind(); | |
609 | + //bufPos.position(offset); | |
610 | + //bufNorm.position(offset); | |
611 | + bindBufPos.get(tmpFloat, 0, length); | |
612 | + bindBufNorm.get(tmpFloatN, 0, length); | |
613 | + int index = 0; | |
614 | + while (index < length) { | |
615 | + pos.x = tmpFloat[index]; | |
616 | + norm.x = tmpFloatN[index++]; | |
617 | + pos.y = tmpFloat[index]; | |
618 | + norm.y = tmpFloatN[index++]; | |
619 | + pos.z = tmpFloat[index]; | |
620 | + norm.z = tmpFloatN[index]; | |
484 | 621 | |
485 | 622 | transform.mult(pos, pos); |
486 | - index += offset; | |
487 | - outBuf.put(index, pos.x); | |
488 | - outBuf.put(index + 1, pos.y); | |
489 | - outBuf.put(index + 2, pos.z); | |
623 | + transform.multNormal(norm, norm); | |
624 | + | |
625 | + index -= 2; | |
626 | + tmpFloat[index] = pos.x; | |
627 | + tmpFloatN[index++] = norm.x; | |
628 | + tmpFloat[index] = pos.y; | |
629 | + tmpFloatN[index++] = norm.y; | |
630 | + tmpFloat[index] = pos.z; | |
631 | + tmpFloatN[index++] = norm.z; | |
632 | + | |
490 | 633 | } |
491 | 634 | vars.release(); |
635 | + bufPos.position(offset); | |
636 | + //using bulk put as it's faster | |
637 | + bufPos.put(tmpFloat, 0, length); | |
638 | + bufNorm.position(offset); | |
639 | + //using bulk put as it's faster | |
640 | + bufNorm.put(tmpFloatN, 0, length); | |
492 | 641 | } |
493 | 642 | |
494 | - private void doTransformNorm(FloatBuffer inBuf, int offset, int start, int end, FloatBuffer outBuf, Matrix4f transform) { | |
643 | + private void doTransformsTangents(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bindBufTangents,FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) { | |
495 | 644 | TempVars vars = TempVars.get(); |
496 | 645 | Vector3f pos = vars.vect1; |
646 | + Vector3f norm = vars.vect2; | |
647 | + Vector3f tan = vars.vect3; | |
648 | + | |
649 | + int length = (end - start) * 3; | |
650 | + int tanLength = (end - start) * 4; | |
497 | 651 | |
498 | 652 | // offset is given in element units |
499 | 653 | // convert to be in component units |
500 | - offset *= 3; | |
501 | - | |
502 | - for (int i = start; i < end; i++) { | |
503 | - int index = i * 3; | |
504 | - pos.x = inBuf.get(index); | |
505 | - pos.y = inBuf.get(index + 1); | |
506 | - pos.z = inBuf.get(index + 2); | |
507 | - | |
508 | - transform.multNormal(pos, pos); | |
509 | - index += offset; | |
510 | - outBuf.put(index, pos.x); | |
511 | - outBuf.put(index + 1, pos.y); | |
512 | - outBuf.put(index + 2, pos.z); | |
654 | + int offset = start * 3; | |
655 | + int tanOffset = start * 4; | |
656 | + | |
657 | + | |
658 | + bindBufPos.rewind(); | |
659 | + bindBufNorm.rewind(); | |
660 | + bindBufTangents.rewind(); | |
661 | + bindBufPos.get(tmpFloat, 0, length); | |
662 | + bindBufNorm.get(tmpFloatN, 0, length); | |
663 | + bindBufTangents.get(tmpFloatT, 0, tanLength); | |
664 | + | |
665 | + int index = 0; | |
666 | + int tanIndex = 0; | |
667 | + while (index < length) { | |
668 | + pos.x = tmpFloat[index]; | |
669 | + norm.x = tmpFloatN[index++]; | |
670 | + pos.y = tmpFloat[index]; | |
671 | + norm.y = tmpFloatN[index++]; | |
672 | + pos.z = tmpFloat[index]; | |
673 | + norm.z = tmpFloatN[index]; | |
674 | + | |
675 | + tan.x = tmpFloatT[tanIndex++]; | |
676 | + tan.y = tmpFloatT[tanIndex++]; | |
677 | + tan.z = tmpFloatT[tanIndex++]; | |
678 | + | |
679 | + transform.mult(pos, pos); | |
680 | + transform.multNormal(norm, norm); | |
681 | + transform.multNormal(tan, tan); | |
682 | + | |
683 | + index -= 2; | |
684 | + tanIndex -= 3; | |
685 | + | |
686 | + tmpFloat[index] = pos.x; | |
687 | + tmpFloatN[index++] = norm.x; | |
688 | + tmpFloat[index] = pos.y; | |
689 | + tmpFloatN[index++] = norm.y; | |
690 | + tmpFloat[index] = pos.z; | |
691 | + tmpFloatN[index++] = norm.z; | |
692 | + | |
693 | + tmpFloatT[tanIndex++] = tan.x; | |
694 | + tmpFloatT[tanIndex++] = tan.y; | |
695 | + tmpFloatT[tanIndex++] = tan.z; | |
696 | + | |
697 | + //Skipping 4th element of tangent buffer (handedness) | |
698 | + tanIndex++; | |
699 | + | |
513 | 700 | } |
514 | 701 | vars.release(); |
702 | + bufPos.position(offset); | |
703 | + //using bulk put as it's faster | |
704 | + bufPos.put(tmpFloat, 0, length); | |
705 | + bufNorm.position(offset); | |
706 | + //using bulk put as it's faster | |
707 | + bufNorm.put(tmpFloatN, 0, length); | |
708 | + bufTangents.position(tanOffset); | |
709 | + //using bulk put as it's faster | |
710 | + bufTangents.put(tmpFloatT, 0, tanLength); | |
515 | 711 | } |
516 | 712 | |
517 | - private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf) { | |
713 | + private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) { | |
518 | 714 | TempVars vars = TempVars.get(); |
519 | 715 | Vector3f pos = vars.vect1; |
520 | 716 | |
521 | 717 | // offset is given in element units |
522 | 718 | // convert to be in component units |
523 | - offset *= 3; | |
719 | + offset *= componentSize; | |
524 | 720 | |
525 | - for (int i = 0; i < inBuf.capacity() / 3; i++) { | |
526 | - pos.x = inBuf.get(i * 3 + 0); | |
527 | - pos.y = inBuf.get(i * 3 + 1); | |
528 | - pos.z = inBuf.get(i * 3 + 2); | |
721 | + for (int i = 0; i < inBuf.limit() / componentSize; i++) { | |
722 | + pos.x = inBuf.get(i * componentSize + 0); | |
723 | + pos.y = inBuf.get(i * componentSize + 1); | |
724 | + pos.z = inBuf.get(i * componentSize + 2); | |
529 | 725 | |
530 | - outBuf.put(offset + i * 3 + 0, pos.x); | |
531 | - outBuf.put(offset + i * 3 + 1, pos.y); | |
532 | - outBuf.put(offset + i * 3 + 2, pos.z); | |
726 | + outBuf.put(offset + i * componentSize + 0, pos.x); | |
727 | + outBuf.put(offset + i * componentSize + 1, pos.y); | |
728 | + outBuf.put(offset + i * componentSize + 2, pos.z); | |
533 | 729 | } |
534 | 730 | vars.release(); |
535 | 731 | } |
536 | 732 | |
537 | 733 | protected class Batch { |
538 | 734 | |
735 | + /** | |
736 | + * update the batchesByGeom map for this batch with the given List of geometries | |
737 | + * @param list | |
738 | + */ | |
739 | + void updateGeomList(List<Geometry> list) { | |
740 | + for (Geometry geom : list) { | |
741 | + if (!isBatch(geom)) { | |
742 | + batchesByGeom.put(geom, this); | |
743 | + } | |
744 | + } | |
745 | + } | |
539 | 746 | Geometry geometry; |
540 | 747 | boolean needMeshUpdate = false; |
541 | 748 | } |
749 | + | |
750 | + protected void setNeedsFullRebatch(boolean needsFullRebatch) { | |
751 | + this.needsFullRebatch = needsFullRebatch; | |
752 | + } | |
753 | + | |
754 | + public int getOffsetIndex(Geometry batchedGeometry) { | |
755 | + return batchedGeometry.startIndex; | |
756 | + } | |
542 | 757 | } |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -35,9 +35,9 @@ import com.jme3.asset.AssetNotFoundException; | ||
35 | 35 | import com.jme3.bounding.BoundingVolume; |
36 | 36 | import com.jme3.collision.Collidable; |
37 | 37 | import com.jme3.collision.CollisionResults; |
38 | +import com.jme3.export.InputCapsule; | |
38 | 39 | import com.jme3.export.JmeExporter; |
39 | 40 | import com.jme3.export.JmeImporter; |
40 | -import com.jme3.export.InputCapsule; | |
41 | 41 | import com.jme3.export.OutputCapsule; |
42 | 42 | import com.jme3.material.Material; |
43 | 43 | import com.jme3.math.Matrix4f; |
@@ -59,6 +59,9 @@ import java.util.logging.Logger; | ||
59 | 59 | */ |
60 | 60 | public class Geometry extends Spatial { |
61 | 61 | |
62 | + // Version #1: removed shared meshes. | |
63 | + // models loaded with shared mesh will be automatically fixed. | |
64 | + public static final int SAVABLE_VERSION = 1; | |
62 | 65 | private static final Logger logger = Logger.getLogger(Geometry.class.getName()); |
63 | 66 | protected Mesh mesh; |
64 | 67 | protected transient int lodLevel = 0; |
@@ -75,16 +78,7 @@ public class Geometry extends Spatial { | ||
75 | 78 | /** |
76 | 79 | * the start index of this geom's mesh in the batchNode mesh |
77 | 80 | */ |
78 | - protected int startIndex; | |
79 | - /** | |
80 | - * the previous transforms of the geometry used to compute world transforms | |
81 | - */ | |
82 | - protected Transform prevBatchTransforms = null; | |
83 | - /** | |
84 | - * the cached offset matrix used when the geometry is batched | |
85 | - */ | |
86 | - protected Matrix4f cachedOffsetMat = null; | |
87 | - | |
81 | + protected int startIndex; | |
88 | 82 | /** |
89 | 83 | * Serialization only. Do not use. |
90 | 84 | */ |
@@ -288,11 +282,8 @@ public class Geometry extends Spatial { | ||
288 | 282 | super.updateWorldTransforms(); |
289 | 283 | computeWorldMatrix(); |
290 | 284 | |
291 | - if (isBatched()) { | |
292 | - computeOffsetTransform(); | |
293 | - batchNode.updateSubBatch(this); | |
294 | - prevBatchTransforms.set(batchNode.getTransforms(this)); | |
295 | - | |
285 | + if (isBatched()) { | |
286 | + batchNode.updateSubBatch(this); | |
296 | 287 | } |
297 | 288 | // geometry requires lights to be sorted |
298 | 289 | worldLights.sort(true); |
@@ -305,9 +296,7 @@ public class Geometry extends Spatial { | ||
305 | 296 | */ |
306 | 297 | protected void batch(BatchNode node, int startIndex) { |
307 | 298 | this.batchNode = node; |
308 | - this.startIndex = startIndex; | |
309 | - prevBatchTransforms = new Transform(); | |
310 | - cachedOffsetMat = new Matrix4f(); | |
299 | + this.startIndex = startIndex; | |
311 | 300 | setCullHint(CullHint.Always); |
312 | 301 | } |
313 | 302 |
@@ -316,65 +305,40 @@ public class Geometry extends Spatial { | ||
316 | 305 | */ |
317 | 306 | protected void unBatch() { |
318 | 307 | this.startIndex = 0; |
319 | - prevBatchTransforms = null; | |
320 | - cachedOffsetMat = null; | |
321 | - //once the geometry is removed from the screnegraph we call batch on the batchNode before unreferencing it. | |
322 | - this.batchNode.batch(); | |
323 | - this.batchNode = null; | |
308 | + //once the geometry is removed from the screnegraph the batchNode needs to be rebatched. | |
309 | + if (batchNode != null) { | |
310 | + this.batchNode.setNeedsFullRebatch(true); | |
311 | + this.batchNode = null; | |
312 | + } | |
324 | 313 | setCullHint(CullHint.Dynamic); |
325 | 314 | } |
326 | 315 | |
327 | 316 | @Override |
328 | 317 | public boolean removeFromParent() { |
329 | - boolean removed = super.removeFromParent(); | |
318 | + return super.removeFromParent(); | |
319 | + } | |
320 | + | |
321 | + @Override | |
322 | + protected void setParent(Node parent) { | |
323 | + super.setParent(parent); | |
330 | 324 | //if the geometry is batched we also have to unbatch it |
331 | - if (isBatched()) { | |
325 | + if (parent == null && isBatched()) { | |
332 | 326 | unBatch(); |
333 | 327 | } |
334 | - return removed; | |
335 | 328 | } |
336 | 329 | |
337 | - /** | |
338 | - * Recomputes the cached offset matrix used when the geometry is batched * | |
339 | - */ | |
340 | - public void computeOffsetTransform() { | |
341 | - TempVars vars = TempVars.get(); | |
342 | - Matrix4f tmpMat = vars.tempMat42; | |
343 | - | |
344 | - // Compute the cached world matrix | |
345 | - cachedOffsetMat.loadIdentity(); | |
346 | - cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation()); | |
347 | - cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation()); | |
348 | - | |
349 | - | |
350 | - Matrix4f scaleMat = vars.tempMat4; | |
351 | - scaleMat.loadIdentity(); | |
352 | - scaleMat.scale(prevBatchTransforms.getScale()); | |
353 | - cachedOffsetMat.multLocal(scaleMat); | |
354 | - cachedOffsetMat.invertLocal(); | |
355 | - | |
356 | - tmpMat.loadIdentity(); | |
357 | - tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation()); | |
358 | - tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation()); | |
359 | - scaleMat.loadIdentity(); | |
360 | - scaleMat.scale(batchNode.getTransforms(this).getScale()); | |
361 | - tmpMat.multLocal(scaleMat); | |
362 | - | |
363 | - tmpMat.mult(cachedOffsetMat, cachedOffsetMat); | |
364 | - | |
365 | - vars.release(); | |
366 | - } | |
367 | 330 | |
368 | 331 | /** |
369 | 332 | * Indicate that the transform of this spatial has changed and that |
370 | 333 | * a refresh is required. |
371 | 334 | */ |
372 | - @Override | |
373 | - protected void setTransformRefresh() { | |
374 | - refreshFlags |= RF_TRANSFORM; | |
375 | - setBoundRefresh(); | |
376 | - } | |
377 | - | |
335 | + // NOTE: Spatial has an identical implementation of this method, | |
336 | + // thus it was commented out. | |
337 | +// @Override | |
338 | +// protected void setTransformRefresh() { | |
339 | +// refreshFlags |= RF_TRANSFORM; | |
340 | +// setBoundRefresh(); | |
341 | +// } | |
378 | 342 | /** |
379 | 343 | * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }. |
380 | 344 | * This will require a localized transform update for this geometry. |
@@ -474,6 +438,11 @@ public class Geometry extends Spatial { | ||
474 | 438 | @Override |
475 | 439 | public Geometry clone(boolean cloneMaterial) { |
476 | 440 | Geometry geomClone = (Geometry) super.clone(cloneMaterial); |
441 | + //this geometry is batched but the clonned one should not be | |
442 | + if (isBatched()) { | |
443 | + geomClone.batchNode = null; | |
444 | + geomClone.unBatch(); | |
445 | + } | |
477 | 446 | geomClone.cachedWorldMat = cachedWorldMat.clone(); |
478 | 447 | if (material != null) { |
479 | 448 | if (cloneMaterial) { |
@@ -541,8 +510,7 @@ public class Geometry extends Spatial { | ||
541 | 510 | material = im.getAssetManager().loadMaterial(matName); |
542 | 511 | } catch (AssetNotFoundException ex) { |
543 | 512 | // Cannot find J3M file. |
544 | - logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.", | |
545 | - matName); | |
513 | + logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key}); | |
546 | 514 | } |
547 | 515 | } |
548 | 516 | // If material is NULL, try to load it from the geometry |
@@ -550,5 +518,13 @@ public class Geometry extends Spatial { | ||
550 | 518 | material = (Material) ic.readSavable("material", null); |
551 | 519 | } |
552 | 520 | ignoreTransform = ic.readBoolean("ignoreTransform", false); |
521 | + | |
522 | + if (ic.getSavableVersion(Geometry.class) == 0) { | |
523 | + // Fix shared mesh (if set) | |
524 | + Mesh sharedMesh = getUserData(UserData.JME_SHAREDMESH); | |
525 | + if (sharedMesh != null) { | |
526 | + getMesh().extractVertexData(sharedMesh); | |
527 | + } | |
528 | + } | |
553 | 529 | } |
554 | 530 | } |
@@ -1,10 +1,39 @@ | ||
1 | 1 | /* |
2 | - * To change this template, choose Tools | Templates | |
3 | - * and open the template in the editor. | |
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. | |
4 | 31 | */ |
5 | 32 | package com.jme3.scene; |
6 | 33 | |
34 | +import com.jme3.math.Matrix4f; | |
7 | 35 | import com.jme3.math.Transform; |
36 | +import com.jme3.util.TempVars; | |
8 | 37 | |
9 | 38 | /** |
10 | 39 | * |
@@ -37,17 +66,30 @@ public class SimpleBatchNode extends BatchNode { | ||
37 | 66 | |
38 | 67 | @Override |
39 | 68 | protected void setTransformRefresh() { |
40 | - | |
41 | 69 | refreshFlags |= RF_TRANSFORM; |
42 | 70 | setBoundRefresh(); |
43 | - for (Batch batch : batches.values()) { | |
71 | + for (Batch batch : batches.getArray()) { | |
44 | 72 | batch.geometry.setTransformRefresh(); |
45 | 73 | } |
46 | 74 | } |
47 | - | |
48 | - protected Transform getTransforms(Geometry geom){ | |
49 | - return geom.getLocalTransform(); | |
75 | + private Matrix4f cachedLocalMat = new Matrix4f(); | |
76 | + | |
77 | + @Override | |
78 | + protected Matrix4f getTransformMatrix(Geometry g){ | |
79 | + // Compute the Local matrix for the geometry | |
80 | + cachedLocalMat.loadIdentity(); | |
81 | + cachedLocalMat.setRotationQuaternion(g.localTransform.getRotation()); | |
82 | + cachedLocalMat.setTranslation(g.localTransform.getTranslation()); | |
83 | + | |
84 | + TempVars vars = TempVars.get(); | |
85 | + Matrix4f scaleMat = vars.tempMat4; | |
86 | + scaleMat.loadIdentity(); | |
87 | + scaleMat.scale(g.localTransform.getScale()); | |
88 | + cachedLocalMat.multLocal(scaleMat); | |
89 | + vars.release(); | |
90 | + return cachedLocalMat; | |
50 | 91 | } |
92 | + | |
51 | 93 | |
52 | 94 | @Override |
53 | 95 | public void batch() { |
@@ -33,6 +33,7 @@ package com.jme3.scene; | ||
33 | 33 | |
34 | 34 | import com.jme3.asset.Asset; |
35 | 35 | import com.jme3.asset.AssetKey; |
36 | +import com.jme3.asset.CloneableSmartAsset; | |
36 | 37 | import com.jme3.bounding.BoundingVolume; |
37 | 38 | import com.jme3.collision.Collidable; |
38 | 39 | import com.jme3.export.*; |
@@ -50,12 +51,7 @@ import com.jme3.scene.control.Control; | ||
50 | 51 | import com.jme3.util.SafeArrayList; |
51 | 52 | import com.jme3.util.TempVars; |
52 | 53 | import java.io.IOException; |
53 | -import java.util.ArrayList; | |
54 | -import java.util.Collection; | |
55 | -import java.util.Collections; | |
56 | -import java.util.HashMap; | |
57 | -import java.util.LinkedList; | |
58 | -import java.util.Queue; | |
54 | +import java.util.*; | |
59 | 55 | import java.util.logging.Logger; |
60 | 56 | |
61 | 57 | /** |
@@ -68,7 +64,7 @@ import java.util.logging.Logger; | ||
68 | 64 | * @author Joshua Slack |
69 | 65 | * @version $Revision: 4075 $, $Data$ |
70 | 66 | */ |
71 | -public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | |
67 | +public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, Asset { | |
72 | 68 | |
73 | 69 | private static final Logger logger = Logger.getLogger(Spatial.class.getName()); |
74 | 70 |
@@ -99,31 +95,47 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
99 | 95 | */ |
100 | 96 | Never; |
101 | 97 | } |
98 | + | |
99 | + /** | |
100 | + * Specifies if this spatial should be batched | |
101 | + */ | |
102 | + public enum BatchHint { | |
103 | + | |
104 | + /** | |
105 | + * Do whatever our parent does. If no parent, default to {@link #Always}. | |
106 | + */ | |
107 | + Inherit, | |
108 | + /** | |
109 | + * This spatial will always be batched when attached to a BatchNode. | |
110 | + */ | |
111 | + Always, | |
112 | + /** | |
113 | + * This spatial will never be batched when attached to a BatchNode. | |
114 | + */ | |
115 | + Never; | |
116 | + } | |
102 | 117 | /** |
103 | 118 | * Refresh flag types |
104 | 119 | */ |
105 | 120 | protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms |
106 | - RF_BOUND = 0x02, | |
107 | - RF_LIGHTLIST = 0x04; // changes in light lists | |
121 | + RF_BOUND = 0x02, | |
122 | + RF_LIGHTLIST = 0x04; // changes in light lists | |
108 | 123 | |
109 | 124 | protected CullHint cullHint = CullHint.Inherit; |
110 | - | |
125 | + protected BatchHint batchHint = BatchHint.Inherit; | |
111 | 126 | /** |
112 | 127 | * Spatial's bounding volume relative to the world. |
113 | 128 | */ |
114 | 129 | protected BoundingVolume worldBound; |
115 | - | |
116 | 130 | /** |
117 | 131 | * LightList |
118 | 132 | */ |
119 | 133 | protected LightList localLights; |
120 | 134 | protected transient LightList worldLights; |
121 | - | |
122 | 135 | /** |
123 | 136 | * This spatial's name. |
124 | 137 | */ |
125 | 138 | protected String name; |
126 | - | |
127 | 139 | // scale values |
128 | 140 | protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects; |
129 | 141 | protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit; |
@@ -133,14 +145,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
133 | 145 | protected Transform worldTransform; |
134 | 146 | protected SafeArrayList<Control> controls = new SafeArrayList<Control>(Control.class); |
135 | 147 | protected HashMap<String, Savable> userData = null; |
136 | - | |
137 | 148 | /** |
138 | 149 | * Used for smart asset caching |
139 | 150 | * |
140 | 151 | * @see AssetKey#useSmartCache() |
141 | 152 | */ |
142 | - protected AssetKey key; | |
143 | - | |
153 | + protected AssetKey key; | |
144 | 154 | /** |
145 | 155 | * Spatial's parent, or null if it has none. |
146 | 156 | */ |
@@ -177,14 +187,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
177 | 187 | this.name = name; |
178 | 188 | } |
179 | 189 | |
180 | - public void setKey(AssetKey key){ | |
190 | + public void setKey(AssetKey key) { | |
181 | 191 | this.key = key; |
182 | 192 | } |
183 | - | |
184 | - public AssetKey getKey(){ | |
193 | + | |
194 | + public AssetKey getKey() { | |
185 | 195 | return key; |
186 | 196 | } |
187 | - | |
197 | + | |
188 | 198 | /** |
189 | 199 | * Indicate that the transform of this spatial has changed and that |
190 | 200 | * a refresh is required. |
@@ -205,7 +215,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
205 | 215 | protected void setBoundRefresh() { |
206 | 216 | refreshFlags |= RF_BOUND; |
207 | 217 | |
208 | - // XXX: Replace with a recursive call? | |
209 | 218 | Spatial p = parent; |
210 | 219 | while (p != null) { |
211 | 220 | if ((p.refreshFlags & RF_BOUND) != 0) { |
@@ -216,7 +225,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
216 | 225 | p = p.parent; |
217 | 226 | } |
218 | 227 | } |
219 | - | |
228 | + | |
220 | 229 | /** |
221 | 230 | * (Internal use only) Forces a refresh of the given types of data. |
222 | 231 | * |
@@ -401,11 +410,16 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
401 | 410 | |
402 | 411 | /** |
403 | 412 | * <code>lookAt</code> is a convenience method for auto-setting the local |
404 | - * rotation based on a position and an up vector. It computes the rotation | |
413 | + * rotation based on a position in world space and an up vector. It computes the rotation | |
405 | 414 | * to transform the z-axis to point onto 'position' and the y-axis to 'up'. |
406 | 415 | * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } |
407 | 416 | * this method takes a world position to look at and not a relative direction. |
408 | 417 | * |
418 | + * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation. | |
419 | + * This was resulting in improper rotation when the spatial had rotated parent nodes. | |
420 | + * This method is intended to work in world space, so no matter what parent graph the | |
421 | + * spatial has, it will look at the given position in world space. | |
422 | + * | |
409 | 423 | * @param position |
410 | 424 | * where to look at in terms of world coordinates |
411 | 425 | * @param upVector |
@@ -418,11 +432,17 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
418 | 432 | TempVars vars = TempVars.get(); |
419 | 433 | |
420 | 434 | Vector3f compVecA = vars.vect4; |
421 | - vars.release(); | |
422 | - | |
435 | + | |
423 | 436 | compVecA.set(position).subtractLocal(worldTranslation); |
424 | - getLocalRotation().lookAt(compVecA, upVector); | |
425 | - | |
437 | + getLocalRotation().lookAt(compVecA, upVector); | |
438 | + | |
439 | + if ( getParent() != null ) { | |
440 | + Quaternion rot=vars.quat1; | |
441 | + rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); | |
442 | + rot.normalizeLocal(); | |
443 | + setLocalRotation(rot); | |
444 | + } | |
445 | + vars.release(); | |
426 | 446 | setTransformRefresh(); |
427 | 447 | } |
428 | 448 |
@@ -567,7 +587,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
567 | 587 | return; |
568 | 588 | } |
569 | 589 | |
570 | - for (Control c : controls.getArray() ) { | |
590 | + for (Control c : controls.getArray()) { | |
571 | 591 | c.render(rm, vp); |
572 | 592 | } |
573 | 593 | } |
@@ -627,7 +647,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
627 | 647 | public <T extends Control> T getControl(Class<T> controlType) { |
628 | 648 | for (Control c : controls.getArray()) { |
629 | 649 | if (controlType.isAssignableFrom(c.getClass())) { |
630 | - return (T)c; | |
650 | + return (T) c; | |
631 | 651 | } |
632 | 652 | } |
633 | 653 | return null; |
@@ -994,15 +1014,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
994 | 1014 | } |
995 | 1015 | |
996 | 1016 | /** |
997 | - * Rotates the spatial by the yaw, roll and pitch angles (in radians), | |
998 | - * in the local coordinate space. | |
1017 | + * Rotates the spatial by the xAngle, yAngle and zAngle angles (in radians), | |
1018 | + * (aka pitch, yaw, roll) in the local coordinate space. | |
999 | 1019 | * |
1000 | 1020 | * @return The spatial on which this method is called, e.g <code>this</code>. |
1001 | 1021 | */ |
1002 | - public Spatial rotate(float yaw, float roll, float pitch) { | |
1022 | + public Spatial rotate(float xAngle, float yAngle, float zAngle) { | |
1003 | 1023 | TempVars vars = TempVars.get(); |
1004 | 1024 | Quaternion q = vars.quat1; |
1005 | - q.fromAngles(yaw, roll, pitch); | |
1025 | + q.fromAngles(xAngle, yAngle, zAngle); | |
1006 | 1026 | rotate(q); |
1007 | 1027 | vars.release(); |
1008 | 1028 |
@@ -1038,6 +1058,16 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
1038 | 1058 | } |
1039 | 1059 | } |
1040 | 1060 | |
1061 | + public BatchHint getBatchHint() { | |
1062 | + if (batchHint != BatchHint.Inherit) { | |
1063 | + return batchHint; | |
1064 | + } else if (parent != null) { | |
1065 | + return parent.getBatchHint(); | |
1066 | + } else { | |
1067 | + return BatchHint.Always; | |
1068 | + } | |
1069 | + } | |
1070 | + | |
1041 | 1071 | /** |
1042 | 1072 | * Returns this spatial's renderqueue bucket. If the mode is set to inherit, |
1043 | 1073 | * then the spatial gets its renderqueue bucket from its parent. |
@@ -1153,7 +1183,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
1153 | 1183 | |
1154 | 1184 | clone.controls = new SafeArrayList<Control>(Control.class); |
1155 | 1185 | for (int i = 0; i < controls.size(); i++) { |
1156 | - clone.controls.add(controls.get(i).cloneForSpatial(clone)); | |
1186 | + Control newControl = controls.get(i).cloneForSpatial(clone); | |
1187 | + newControl.setSpatial(clone); | |
1188 | + clone.controls.add(newControl); | |
1157 | 1189 | } |
1158 | 1190 | |
1159 | 1191 | if (userData != null) { |
@@ -1197,7 +1229,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
1197 | 1229 | userData = new HashMap<String, Savable>(); |
1198 | 1230 | } |
1199 | 1231 | |
1200 | - if (data instanceof Savable) { | |
1232 | + if(data == null){ | |
1233 | + userData.remove(key); | |
1234 | + }else if (data instanceof Savable) { | |
1201 | 1235 | userData.put(key, (Savable) data); |
1202 | 1236 | } else { |
1203 | 1237 | userData.put(key, new UserData(UserData.getObjectType(data), data)); |
@@ -1260,13 +1294,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
1260 | 1294 | capsule.write(name, "name", null); |
1261 | 1295 | capsule.write(worldBound, "world_bound", null); |
1262 | 1296 | capsule.write(cullHint, "cull_mode", CullHint.Inherit); |
1297 | + capsule.write(batchHint, "batch_hint", BatchHint.Inherit); | |
1263 | 1298 | capsule.write(queueBucket, "queue", RenderQueue.Bucket.Inherit); |
1264 | 1299 | capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit); |
1265 | 1300 | capsule.write(localTransform, "transform", Transform.IDENTITY); |
1266 | 1301 | capsule.write(localLights, "lights", null); |
1267 | - | |
1302 | + | |
1268 | 1303 | // Shallow clone the controls array to convert its type. |
1269 | - capsule.writeSavableArrayList( new ArrayList(controls), "controlsList", null); | |
1304 | + capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null); | |
1270 | 1305 | capsule.writeStringSavableMap(userData, "user_data", null); |
1271 | 1306 | } |
1272 | 1307 |
@@ -1276,6 +1311,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
1276 | 1311 | name = ic.readString("name", null); |
1277 | 1312 | worldBound = (BoundingVolume) ic.readSavable("world_bound", null); |
1278 | 1313 | cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit); |
1314 | + batchHint = ic.readEnum("batch_hint", BatchHint.class, BatchHint.Inherit); | |
1279 | 1315 | queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class, |
1280 | 1316 | RenderQueue.Bucket.Inherit); |
1281 | 1317 | shadowMode = ic.readEnum("shadow_mode", ShadowMode.class, |
@@ -1321,6 +1357,18 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
1321 | 1357 | } |
1322 | 1358 | |
1323 | 1359 | /** |
1360 | + * <code>setBatchHint</code> sets how batching should work on this | |
1361 | + * spatial. NOTE: You must set this AFTER attaching to a | |
1362 | + * parent or it will be reset with the parent's cullMode value. | |
1363 | + * | |
1364 | + * @param hint | |
1365 | + * one of BatchHint.Never, BatchHint.Always, BatchHint.Inherit | |
1366 | + */ | |
1367 | + public void setBatchHint(BatchHint hint) { | |
1368 | + batchHint = hint; | |
1369 | + } | |
1370 | + | |
1371 | + /** | |
1324 | 1372 | * @return the cullmode set on this Spatial |
1325 | 1373 | */ |
1326 | 1374 | public CullHint getLocalCullHint() { |
@@ -1328,6 +1376,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { | ||
1328 | 1376 | } |
1329 | 1377 | |
1330 | 1378 | /** |
1379 | + * @return the batchHint set on this Spatial | |
1380 | + */ | |
1381 | + public BatchHint getLocalBatchHint() { | |
1382 | + return batchHint; | |
1383 | + } | |
1384 | + | |
1385 | + /** | |
1331 | 1386 | * <code>setQueueBucket</code> determines at what phase of the |
1332 | 1387 | * rendering process this Spatial will rendered. See the |
1333 | 1388 | * {@link Bucket} enum for an explanation of the various |
@@ -743,6 +743,22 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { | ||
743 | 743 | * match. |
744 | 744 | */ |
745 | 745 | public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){ |
746 | + copyElements(inIndex, outVb, outIndex, 1); | |
747 | + } | |
748 | + | |
749 | + /** | |
750 | + * Copies a sequence of elements of data from this <code>VertexBuffer</code> | |
751 | + * to the given output VertexBuffer. | |
752 | + * | |
753 | + * @param inIndex The input element index | |
754 | + * @param outVb The buffer to copy to | |
755 | + * @param outIndex The output element index | |
756 | + * @param len The number of elements to copy | |
757 | + * | |
758 | + * @throws IllegalArgumentException If the formats of the buffers do not | |
759 | + * match. | |
760 | + */ | |
761 | + public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){ | |
746 | 762 | if (outVb.format != format || outVb.components != components) |
747 | 763 | throw new IllegalArgumentException("Buffer format mismatch. Cannot copy"); |
748 | 764 |