1 package jme3tools.optimize; 2 3 import com.jme3.material.Material; 4 import com.jme3.math.Matrix4f; 5 import com.jme3.math.Transform; 6 import com.jme3.math.Vector3f; 7 import com.jme3.scene.Mesh.Mode; 8 import com.jme3.scene.*; 9 import com.jme3.scene.VertexBuffer.Format; 10 import com.jme3.scene.VertexBuffer.Type; 11 import com.jme3.scene.VertexBuffer.Usage; 12 import com.jme3.scene.mesh.IndexBuffer; 13 import com.jme3.util.BufferUtils; 14 import com.jme3.util.IntMap.Entry; 15 import java.nio.Buffer; 16 import java.nio.FloatBuffer; 17 import java.nio.ShortBuffer; 18 import java.util.*; 19 import java.util.logging.Logger; 20 21 public class GeometryBatchFactory { 22 23 private static final Logger logger = Logger.getLogger(GeometryBatchFactory.class.getName()); 24 doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform)25 private static void doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) { 26 Vector3f pos = new Vector3f(); 27 28 // offset is given in element units 29 // convert to be in component units 30 offset *= 3; 31 32 for (int i = 0; i < inBuf.capacity() / 3; i++) { 33 pos.x = inBuf.get(i * 3 + 0); 34 pos.y = inBuf.get(i * 3 + 1); 35 pos.z = inBuf.get(i * 3 + 2); 36 37 transform.mult(pos, pos); 38 39 outBuf.put(offset + i * 3 + 0, pos.x); 40 outBuf.put(offset + i * 3 + 1, pos.y); 41 outBuf.put(offset + i * 3 + 2, pos.z); 42 } 43 } 44 doTransformNorms(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform)45 private static void doTransformNorms(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) { 46 Vector3f norm = new Vector3f(); 47 48 // offset is given in element units 49 // convert to be in component units 50 offset *= 3; 51 52 for (int i = 0; i < inBuf.capacity() / 3; i++) { 53 norm.x = inBuf.get(i * 3 + 0); 54 norm.y = inBuf.get(i * 3 + 1); 55 norm.z = inBuf.get(i * 3 + 2); 56 57 transform.multNormal(norm, norm); 58 59 outBuf.put(offset + i * 3 + 0, norm.x); 60 outBuf.put(offset + i * 3 + 1, norm.y); 61 outBuf.put(offset + i * 3 + 2, norm.z); 62 } 63 } 64 doTransformTangents(FloatBuffer inBuf, int offset, int components, FloatBuffer outBuf, Matrix4f transform)65 private static void doTransformTangents(FloatBuffer inBuf, int offset, int components, FloatBuffer outBuf, Matrix4f transform) { 66 Vector3f tan = new Vector3f(); 67 68 // offset is given in element units 69 // convert to be in component units 70 offset *= components; 71 72 for (int i = 0; i < inBuf.capacity() / components; i++) { 73 tan.x = inBuf.get(i * components + 0); 74 tan.y = inBuf.get(i * components + 1); 75 tan.z = inBuf.get(i * components + 2); 76 77 transform.multNormal(tan, tan); 78 79 outBuf.put(offset + i * components + 0, tan.x); 80 outBuf.put(offset + i * components + 1, tan.y); 81 outBuf.put(offset + i * components + 2, tan.z); 82 83 if (components == 4){ 84 outBuf.put(offset + i * components + 3, inBuf.get(i * components + 3)); 85 } 86 } 87 } 88 89 /** 90 * Merges all geometries in the collection into 91 * the output mesh. Creates a new material using the TextureAtlas. 92 * 93 * @param geometries 94 * @param outMesh 95 */ mergeGeometries(Collection<Geometry> geometries, Mesh outMesh)96 public static void mergeGeometries(Collection<Geometry> geometries, Mesh outMesh) { 97 int[] compsForBuf = new int[VertexBuffer.Type.values().length]; 98 Format[] formatForBuf = new Format[compsForBuf.length]; 99 100 int totalVerts = 0; 101 int totalTris = 0; 102 int totalLodLevels = 0; 103 104 Mode mode = null; 105 for (Geometry geom : geometries) { 106 totalVerts += geom.getVertexCount(); 107 totalTris += geom.getTriangleCount(); 108 totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); 109 110 Mode listMode; 111 int components; 112 switch (geom.getMesh().getMode()) { 113 case Points: 114 listMode = Mode.Points; 115 components = 1; 116 break; 117 case LineLoop: 118 case LineStrip: 119 case Lines: 120 listMode = Mode.Lines; 121 components = 2; 122 break; 123 case TriangleFan: 124 case TriangleStrip: 125 case Triangles: 126 listMode = Mode.Triangles; 127 components = 3; 128 break; 129 default: 130 throw new UnsupportedOperationException(); 131 } 132 133 for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()){ 134 compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); 135 formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); 136 } 137 138 if (mode != null && mode != listMode) { 139 throw new UnsupportedOperationException("Cannot combine different" 140 + " primitive types: " + mode + " != " + listMode); 141 } 142 mode = listMode; 143 compsForBuf[Type.Index.ordinal()] = components; 144 } 145 146 outMesh.setMode(mode); 147 if (totalVerts >= 65536) { 148 // make sure we create an UnsignedInt buffer so 149 // we can fit all of the meshes 150 formatForBuf[Type.Index.ordinal()] = Format.UnsignedInt; 151 } else { 152 formatForBuf[Type.Index.ordinal()] = Format.UnsignedShort; 153 } 154 155 // generate output buffers based on retrieved info 156 for (int i = 0; i < compsForBuf.length; i++) { 157 if (compsForBuf[i] == 0) { 158 continue; 159 } 160 161 Buffer data; 162 if (i == Type.Index.ordinal()) { 163 data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris); 164 } else { 165 data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts); 166 } 167 168 VertexBuffer vb = new VertexBuffer(Type.values()[i]); 169 vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data); 170 outMesh.setBuffer(vb); 171 } 172 173 int globalVertIndex = 0; 174 int globalTriIndex = 0; 175 176 for (Geometry geom : geometries) { 177 Mesh inMesh = geom.getMesh(); 178 geom.computeWorldMatrix(); 179 Matrix4f worldMatrix = geom.getWorldMatrix(); 180 181 int geomVertCount = inMesh.getVertexCount(); 182 int geomTriCount = inMesh.getTriangleCount(); 183 184 for (int bufType = 0; bufType < compsForBuf.length; bufType++) { 185 VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]); 186 VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]); 187 188 if (inBuf == null || outBuf == null) { 189 continue; 190 } 191 192 if (Type.Index.ordinal() == bufType) { 193 int components = compsForBuf[bufType]; 194 195 IndexBuffer inIdx = inMesh.getIndicesAsList(); 196 IndexBuffer outIdx = outMesh.getIndexBuffer(); 197 198 for (int tri = 0; tri < geomTriCount; tri++) { 199 for (int comp = 0; comp < components; comp++) { 200 int idx = inIdx.get(tri * components + comp) + globalVertIndex; 201 outIdx.put((globalTriIndex + tri) * components + comp, idx); 202 } 203 } 204 } else if (Type.Position.ordinal() == bufType) { 205 FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); 206 FloatBuffer outPos = (FloatBuffer) outBuf.getData(); 207 doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix); 208 } else if (Type.Normal.ordinal() == bufType) { 209 FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); 210 FloatBuffer outPos = (FloatBuffer) outBuf.getData(); 211 doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix); 212 }else if(Type.Tangent.ordinal() == bufType){ 213 FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); 214 FloatBuffer outPos = (FloatBuffer) outBuf.getData(); 215 int components = inBuf.getNumComponents(); 216 doTransformTangents(inPos, globalVertIndex, components, outPos, worldMatrix); 217 } else { 218 inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); 219 } 220 } 221 222 globalVertIndex += geomVertCount; 223 globalTriIndex += geomTriCount; 224 } 225 } 226 makeLods(Collection<Geometry> geometries, Mesh outMesh)227 public static void makeLods(Collection<Geometry> geometries, Mesh outMesh) { 228 int lodLevels = 0; 229 int[] lodSize = null; 230 int index = 0; 231 for (Geometry g : geometries) { 232 if (lodLevels == 0) { 233 lodLevels = g.getMesh().getNumLodLevels(); 234 } 235 if (lodSize == null) { 236 lodSize = new int[lodLevels]; 237 } 238 for (int i = 0; i < lodLevels; i++) { 239 lodSize[i] += g.getMesh().getLodLevel(i).getData().capacity(); 240 //if( i == 0) System.out.println(index + " " +lodSize[i]); 241 } 242 index++; 243 } 244 int[][] lodData = new int[lodLevels][]; 245 for (int i = 0; i < lodLevels; i++) { 246 lodData[i] = new int[lodSize[i]]; 247 } 248 VertexBuffer[] lods = new VertexBuffer[lodLevels]; 249 int bufferPos[] = new int[lodLevels]; 250 //int index = 0; 251 int numOfVertices = 0; 252 int curGeom = 0; 253 for (Geometry g : geometries) { 254 if (numOfVertices == 0) { 255 numOfVertices = g.getVertexCount(); 256 } 257 for (int i = 0; i < lodLevels; i++) { 258 ShortBuffer buffer = (ShortBuffer) g.getMesh().getLodLevel(i).getDataReadOnly(); 259 //System.out.println("buffer: " + buffer.capacity() + " limit: " + lodSize[i] + " " + index); 260 for (int j = 0; j < buffer.capacity(); j++) { 261 lodData[i][bufferPos[i] + j] = buffer.get() + numOfVertices * curGeom; 262 //bufferPos[i]++; 263 } 264 bufferPos[i] += buffer.capacity(); 265 } 266 curGeom++; 267 } 268 for (int i = 0; i < lodLevels; i++) { 269 lods[i] = new VertexBuffer(Type.Index); 270 lods[i].setupData(Usage.Dynamic, 1, Format.UnsignedInt, BufferUtils.createIntBuffer(lodData[i])); 271 } 272 System.out.println(lods.length); 273 outMesh.setLodLevels(lods); 274 } 275 makeBatches(Collection<Geometry> geometries)276 public static List<Geometry> makeBatches(Collection<Geometry> geometries) { 277 return makeBatches(geometries, false); 278 } 279 280 /** 281 * Batches a collection of Geometries so that all with the same material get combined. 282 * @param geometries The Geometries to combine 283 * @return A List of newly created Geometries, each with a distinct material 284 */ makeBatches(Collection<Geometry> geometries, boolean useLods)285 public static List<Geometry> makeBatches(Collection<Geometry> geometries, boolean useLods) { 286 ArrayList<Geometry> retVal = new ArrayList<Geometry>(); 287 HashMap<Material, List<Geometry>> matToGeom = new HashMap<Material, List<Geometry>>(); 288 289 for (Geometry geom : geometries) { 290 List<Geometry> outList = matToGeom.get(geom.getMaterial()); 291 if (outList == null) { 292 outList = new ArrayList<Geometry>(); 293 matToGeom.put(geom.getMaterial(), outList); 294 } 295 outList.add(geom); 296 } 297 298 int batchNum = 0; 299 for (Map.Entry<Material, List<Geometry>> entry : matToGeom.entrySet()) { 300 Material mat = entry.getKey(); 301 List<Geometry> geomsForMat = entry.getValue(); 302 Mesh mesh = new Mesh(); 303 mergeGeometries(geomsForMat, mesh); 304 // lods 305 if (useLods) { 306 makeLods(geomsForMat, mesh); 307 } 308 mesh.updateCounts(); 309 mesh.updateBound(); 310 311 Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh); 312 out.setMaterial(mat); 313 retVal.add(out); 314 } 315 316 return retVal; 317 } 318 gatherGeoms(Spatial scene, List<Geometry> geoms)319 public static void gatherGeoms(Spatial scene, List<Geometry> geoms) { 320 if (scene instanceof Node) { 321 Node node = (Node) scene; 322 for (Spatial child : node.getChildren()) { 323 gatherGeoms(child, geoms); 324 } 325 } else if (scene instanceof Geometry) { 326 geoms.add((Geometry) scene); 327 } 328 } 329 330 /** 331 * Optimizes a scene by combining Geometry with the same material. 332 * All Geometries found in the scene are detached from their parent and 333 * a new Node containing the optimized Geometries is attached. 334 * @param scene The scene to optimize 335 * @return The newly created optimized geometries attached to a node 336 */ optimize(Node scene)337 public static Spatial optimize(Node scene) { 338 return optimize(scene, false); 339 } 340 341 /** 342 * Optimizes a scene by combining Geometry with the same material. 343 * All Geometries found in the scene are detached from their parent and 344 * a new Node containing the optimized Geometries is attached. 345 * @param scene The scene to optimize 346 * @param useLods true if you want the resulting geometry to keep lod information 347 * @return The newly created optimized geometries attached to a node 348 */ optimize(Node scene, boolean useLods)349 public static Node optimize(Node scene, boolean useLods) { 350 ArrayList<Geometry> geoms = new ArrayList<Geometry>(); 351 352 gatherGeoms(scene, geoms); 353 354 List<Geometry> batchedGeoms = makeBatches(geoms, useLods); 355 for (Geometry geom : batchedGeoms) { 356 scene.attachChild(geom); 357 } 358 359 for (Iterator<Geometry> it = geoms.iterator(); it.hasNext();) { 360 Geometry geometry = it.next(); 361 geometry.removeFromParent(); 362 } 363 364 // Since the scene is returned unaltered the transform must be reset 365 scene.setLocalTransform(Transform.IDENTITY); 366 367 return scene; 368 } 369 printMesh(Mesh mesh)370 public static void printMesh(Mesh mesh) { 371 for (int bufType = 0; bufType < Type.values().length; bufType++) { 372 VertexBuffer outBuf = mesh.getBuffer(Type.values()[bufType]); 373 if (outBuf == null) { 374 continue; 375 } 376 377 System.out.println(outBuf.getBufferType() + ": "); 378 for (int vert = 0; vert < outBuf.getNumElements(); vert++) { 379 String str = "["; 380 for (int comp = 0; comp < outBuf.getNumComponents(); comp++) { 381 Object val = outBuf.getElementComponent(vert, comp); 382 outBuf.setElementComponent(vert, comp, val); 383 val = outBuf.getElementComponent(vert, comp); 384 str += val; 385 if (comp != outBuf.getNumComponents() - 1) { 386 str += ", "; 387 } 388 } 389 str += "]"; 390 System.out.println(str); 391 } 392 System.out.println("------"); 393 } 394 } 395 main(String[] args)396 public static void main(String[] args) { 397 Mesh mesh = new Mesh(); 398 mesh.setBuffer(Type.Position, 3, new float[]{ 399 0, 0, 0, 400 1, 0, 0, 401 1, 1, 0, 402 0, 1, 0 403 }); 404 mesh.setBuffer(Type.Index, 2, new short[]{ 405 0, 1, 406 1, 2, 407 2, 3, 408 3, 0 409 }); 410 411 Geometry g1 = new Geometry("g1", mesh); 412 413 ArrayList<Geometry> geoms = new ArrayList<Geometry>(); 414 geoms.add(g1); 415 416 Mesh outMesh = new Mesh(); 417 mergeGeometries(geoms, outMesh); 418 printMesh(outMesh); 419 } 420 } 421