1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.graphics.g3d.utils; 18 19 import com.badlogic.gdx.graphics.Color; 20 import com.badlogic.gdx.graphics.GL20; 21 import com.badlogic.gdx.graphics.Mesh; 22 import com.badlogic.gdx.graphics.VertexAttributes; 23 import com.badlogic.gdx.graphics.g3d.Material; 24 import com.badlogic.gdx.graphics.g3d.Model; 25 import com.badlogic.gdx.graphics.g3d.model.MeshPart; 26 import com.badlogic.gdx.graphics.g3d.model.Node; 27 import com.badlogic.gdx.graphics.g3d.model.NodePart; 28 import com.badlogic.gdx.math.Matrix4; 29 import com.badlogic.gdx.math.Vector3; 30 import com.badlogic.gdx.utils.Array; 31 import com.badlogic.gdx.utils.Disposable; 32 import com.badlogic.gdx.utils.GdxRuntimeException; 33 34 /** Helper class to create {@link Model}s from code. To start building use the {@link #begin()} method, when finished building use 35 * the {@link #end()} method. The end method returns the model just build. Building cannot be nested, only one model (per 36 * ModelBuilder) can be build at the time. The same ModelBuilder can be used to build multiple models sequential. Use the 37 * {@link #node()} method to start a new node. Use one of the #part(...) methods to add a part within a node. The 38 * {@link #part(String, int, VertexAttributes, Material)} method will return a {@link MeshPartBuilder} which can be used to build 39 * the node part. 40 * @author Xoppa */ 41 public class ModelBuilder { 42 /** The model currently being build */ 43 private Model model; 44 /** The node currently being build */ 45 private Node node; 46 /** The mesh builders created between begin and end */ 47 private Array<MeshBuilder> builders = new Array<MeshBuilder>(); 48 49 private Matrix4 tmpTransform = new Matrix4(); 50 51 private MeshBuilder getBuilder (final VertexAttributes attributes) { 52 for (final MeshBuilder mb : builders) 53 if (mb.getAttributes().equals(attributes) && mb.lastIndex() < Short.MAX_VALUE / 2) return mb; 54 final MeshBuilder result = new MeshBuilder(); 55 result.begin(attributes); 56 builders.add(result); 57 return result; 58 } 59 60 /** Begin building a new model */ 61 public void begin () { 62 if (model != null) throw new GdxRuntimeException("Call end() first"); 63 node = null; 64 model = new Model(); 65 builders.clear(); 66 } 67 68 /** End building the model. 69 * @return The newly created model. Call the {@link Model#dispose()} method when no longer used. */ 70 public Model end () { 71 if (model == null) throw new GdxRuntimeException("Call begin() first"); 72 final Model result = model; 73 endnode(); 74 model = null; 75 76 for (final MeshBuilder mb : builders) 77 mb.end(); 78 builders.clear(); 79 80 rebuildReferences(result); 81 return result; 82 } 83 84 private void endnode () { 85 if (node != null) { 86 node = null; 87 } 88 } 89 90 /** Adds the {@link Node} to the model and sets it active for building. Use any of the part(...) method to add a NodePart. */ 91 protected Node node (final Node node) { 92 if (model == null) throw new GdxRuntimeException("Call begin() first"); 93 94 endnode(); 95 96 model.nodes.add(node); 97 this.node = node; 98 99 return node; 100 } 101 102 /** Add a node to the model. Use any of the part(...) method to add a NodePart. 103 * @return The node being created. */ 104 public Node node () { 105 final Node node = new Node(); 106 node(node); 107 node.id = "node" + model.nodes.size; 108 return node; 109 } 110 111 /** Adds the nodes of the specified model to a new node of the model being build. After this method the given model can no 112 * longer be used. Do not call the {@link Model#dispose()} method on that model. 113 * @return The newly created node containing the nodes of the given model. */ 114 public Node node (final String id, final Model model) { 115 final Node node = new Node(); 116 node.id = id; 117 node.addChildren(model.nodes); 118 node(node); 119 for (final Disposable disposable : model.getManagedDisposables()) 120 manage(disposable); 121 return node; 122 } 123 124 /** Add the {@link Disposable} object to the model, causing it to be disposed when the model is disposed. */ 125 public void manage (final Disposable disposable) { 126 if (model == null) throw new GdxRuntimeException("Call begin() first"); 127 model.manageDisposable(disposable); 128 } 129 130 /** Adds the specified MeshPart to the current Node. The Mesh will be managed by the model and disposed when the model is 131 * disposed. The resources the Material might contain are not managed, use {@link #manage(Disposable)} to add those to the 132 * model. */ 133 public void part (final MeshPart meshpart, final Material material) { 134 if (node == null) node(); 135 node.parts.add(new NodePart(meshpart, material)); 136 } 137 138 /** Adds the specified mesh part to the current node. The Mesh will be managed by the model and disposed when the model is 139 * disposed. The resources the Material might contain are not managed, use {@link #manage(Disposable)} to add those to the 140 * model. 141 * @return The added MeshPart. */ 142 public MeshPart part (final String id, final Mesh mesh, int primitiveType, int offset, int size, final Material material) { 143 final MeshPart meshPart = new MeshPart(); 144 meshPart.id = id; 145 meshPart.primitiveType = primitiveType; 146 meshPart.mesh = mesh; 147 meshPart.offset = offset; 148 meshPart.size = size; 149 part(meshPart, material); 150 return meshPart; 151 } 152 153 /** Adds the specified mesh part to the current node. The Mesh will be managed by the model and disposed when the model is 154 * disposed. The resources the Material might contain are not managed, use {@link #manage(Disposable)} to add those to the 155 * model. 156 * @return The added MeshPart. */ 157 public MeshPart part (final String id, final Mesh mesh, int primitiveType, final Material material) { 158 return part(id, mesh, primitiveType, 0, mesh.getNumIndices(), material); 159 } 160 161 /** Creates a new MeshPart within the current Node and returns a {@link MeshPartBuilder} which can be used to build the shape of 162 * the part. If possible a previously used {@link MeshPartBuilder} will be reused, to reduce the number of mesh binds. 163 * Therefore you can only build one part at a time. The resources the Material might contain are not managed, use 164 * {@link #manage(Disposable)} to add those to the model. 165 * @return The {@link MeshPartBuilder} you can use to build the MeshPart. */ 166 public MeshPartBuilder part (final String id, int primitiveType, final VertexAttributes attributes, final Material material) { 167 final MeshBuilder builder = getBuilder(attributes); 168 part(builder.part(id, primitiveType), material); 169 return builder; 170 } 171 172 /** Creates a new MeshPart within the current Node and returns a {@link MeshPartBuilder} which can be used to build the shape of 173 * the part. If possible a previously used {@link MeshPartBuilder} will be reused, to reduce the number of mesh binds. 174 * Therefore you can only build one part at a time. The resources the Material might contain are not managed, use 175 * {@link #manage(Disposable)} to add those to the model. 176 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 177 * and TextureCoordinates is supported. 178 * @return The {@link MeshPartBuilder} you can use to build the MeshPart. */ 179 public MeshPartBuilder part (final String id, int primitiveType, final long attributes, final Material material) { 180 return part(id, primitiveType, MeshBuilder.createAttributes(attributes), material); 181 } 182 183 /** Convenience method to create a model with a single node containing a box shape. The resources the Material might contain are 184 * not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 185 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 186 * and TextureCoordinates is supported. */ 187 public Model createBox (float width, float height, float depth, final Material material, final long attributes) { 188 return createBox(width, height, depth, GL20.GL_TRIANGLES, material, attributes); 189 } 190 191 /** Convenience method to create a model with a single node containing a box shape. The resources the Material might contain are 192 * not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 193 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 194 * and TextureCoordinates is supported. */ 195 public Model createBox (float width, float height, float depth, int primitiveType, final Material material, 196 final long attributes) { 197 begin(); 198 part("box", primitiveType, attributes, material).box(width, height, depth); 199 return end(); 200 } 201 202 /** Convenience method to create a model with a single node containing a rectangle shape. The resources the Material might 203 * contain are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 204 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 205 * and TextureCoordinates is supported. */ 206 public Model createRect (float x00, float y00, float z00, float x10, float y10, float z10, float x11, float y11, float z11, 207 float x01, float y01, float z01, float normalX, float normalY, float normalZ, final Material material, final long attributes) { 208 return createRect(x00, y00, z00, x10, y10, z10, x11, y11, z11, x01, y01, z01, normalX, normalY, normalZ, GL20.GL_TRIANGLES, 209 material, attributes); 210 } 211 212 /** Convenience method to create a model with a single node containing a rectangle shape. The resources the Material might 213 * contain are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 214 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 215 * and TextureCoordinates is supported. */ 216 public Model createRect (float x00, float y00, float z00, float x10, float y10, float z10, float x11, float y11, float z11, 217 float x01, float y01, float z01, float normalX, float normalY, float normalZ, int primitiveType, final Material material, 218 final long attributes) { 219 begin(); 220 part("rect", primitiveType, attributes, material).rect(x00, y00, z00, x10, y10, z10, x11, y11, z11, x01, y01, z01, normalX, 221 normalY, normalZ); 222 return end(); 223 } 224 225 /** Convenience method to create a model with a single node containing a cylinder shape. The resources the Material might 226 * contain are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 227 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 228 * and TextureCoordinates is supported. */ 229 public Model createCylinder (float width, float height, float depth, int divisions, final Material material, 230 final long attributes) { 231 return createCylinder(width, height, depth, divisions, GL20.GL_TRIANGLES, material, attributes); 232 } 233 234 /** Convenience method to create a model with a single node containing a cylinder shape. The resources the Material might 235 * contain are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 236 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 237 * and TextureCoordinates is supported. */ 238 public Model createCylinder (float width, float height, float depth, int divisions, int primitiveType, 239 final Material material, final long attributes) { 240 return createCylinder(width, height, depth, divisions, primitiveType, material, attributes, 0, 360); 241 } 242 243 /** Convenience method to create a model with a single node containing a cylinder shape. The resources the Material might 244 * contain are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 245 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 246 * and TextureCoordinates is supported. */ 247 public Model createCylinder (float width, float height, float depth, int divisions, final Material material, 248 final long attributes, float angleFrom, float angleTo) { 249 return createCylinder(width, height, depth, divisions, GL20.GL_TRIANGLES, material, attributes, angleFrom, angleTo); 250 } 251 252 /** Convenience method to create a model with a single node containing a cylinder shape. The resources the Material might 253 * contain are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 254 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 255 * and TextureCoordinates is supported. */ 256 public Model createCylinder (float width, float height, float depth, int divisions, int primitiveType, 257 final Material material, final long attributes, float angleFrom, float angleTo) { 258 begin(); 259 part("cylinder", primitiveType, attributes, material).cylinder(width, height, depth, divisions, angleFrom, angleTo); 260 return end(); 261 } 262 263 /** Convenience method to create a model with a single node containing a cone shape. The resources the Material might contain 264 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 265 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 266 * and TextureCoordinates is supported. */ 267 public Model createCone (float width, float height, float depth, int divisions, final Material material, final long attributes) { 268 return createCone(width, height, depth, divisions, GL20.GL_TRIANGLES, material, attributes); 269 } 270 271 /** Convenience method to create a model with a single node containing a cone shape. The resources the Material might contain 272 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 273 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 274 * and TextureCoordinates is supported. */ 275 public Model createCone (float width, float height, float depth, int divisions, int primitiveType, final Material material, 276 final long attributes) { 277 return createCone(width, height, depth, divisions, primitiveType, material, attributes, 0, 360); 278 } 279 280 /** Convenience method to create a model with a single node containing a cone shape. The resources the Material might contain 281 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 282 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 283 * and TextureCoordinates is supported. */ 284 public Model createCone (float width, float height, float depth, int divisions, final Material material, 285 final long attributes, float angleFrom, float angleTo) { 286 return createCone(width, height, depth, divisions, GL20.GL_TRIANGLES, material, attributes, angleFrom, angleTo); 287 } 288 289 /** Convenience method to create a model with a single node containing a cone shape. The resources the Material might contain 290 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 291 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 292 * and TextureCoordinates is supported. */ 293 public Model createCone (float width, float height, float depth, int divisions, int primitiveType, final Material material, 294 final long attributes, float angleFrom, float angleTo) { 295 begin(); 296 part("cone", primitiveType, attributes, material).cone(width, height, depth, divisions, angleFrom, angleTo); 297 return end(); 298 } 299 300 /** Convenience method to create a model with a single node containing a sphere shape. The resources the Material might contain 301 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 302 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 303 * and TextureCoordinates is supported. */ 304 public Model createSphere (float width, float height, float depth, int divisionsU, int divisionsV, final Material material, 305 final long attributes) { 306 return createSphere(width, height, depth, divisionsU, divisionsV, GL20.GL_TRIANGLES, material, attributes); 307 } 308 309 /** Convenience method to create a model with a single node containing a sphere shape. The resources the Material might contain 310 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 311 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 312 * and TextureCoordinates is supported. */ 313 public Model createSphere (float width, float height, float depth, int divisionsU, int divisionsV, int primitiveType, 314 final Material material, final long attributes) { 315 return createSphere(width, height, depth, divisionsU, divisionsV, primitiveType, material, attributes, 0, 360, 0, 180); 316 } 317 318 /** Convenience method to create a model with a single node containing a sphere shape. The resources the Material might contain 319 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 320 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 321 * and TextureCoordinates is supported. */ 322 public Model createSphere (float width, float height, float depth, int divisionsU, int divisionsV, final Material material, 323 final long attributes, float angleUFrom, float angleUTo, float angleVFrom, float angleVTo) { 324 return createSphere(width, height, depth, divisionsU, divisionsV, GL20.GL_TRIANGLES, material, attributes, angleUFrom, 325 angleUTo, angleVFrom, angleVTo); 326 } 327 328 /** Convenience method to create a model with a single node containing a sphere shape. The resources the Material might contain 329 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 330 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 331 * and TextureCoordinates is supported. */ 332 public Model createSphere (float width, float height, float depth, int divisionsU, int divisionsV, int primitiveType, 333 final Material material, final long attributes, float angleUFrom, float angleUTo, float angleVFrom, float angleVTo) { 334 begin(); 335 part("cylinder", primitiveType, attributes, material).sphere(width, height, depth, divisionsU, divisionsV, angleUFrom, 336 angleUTo, angleVFrom, angleVTo); 337 return end(); 338 } 339 340 /** Convenience method to create a model with a single node containing a capsule shape. The resources the Material might contain 341 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 342 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 343 * and TextureCoordinates is supported. */ 344 public Model createCapsule (float radius, float height, int divisions, final Material material, final long attributes) { 345 return createCapsule(radius, height, divisions, GL20.GL_TRIANGLES, material, attributes); 346 } 347 348 /** Convenience method to create a model with a single node containing a capsule shape. The resources the Material might contain 349 * are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 350 * @param attributes bitwise mask of the {@link com.badlogic.gdx.graphics.VertexAttributes.Usage}, only Position, Color, Normal 351 * and TextureCoordinates is supported. */ 352 public Model createCapsule (float radius, float height, int divisions, int primitiveType, final Material material, 353 final long attributes) { 354 begin(); 355 part("capsule", primitiveType, attributes, material).capsule(radius, height, divisions); 356 return end(); 357 } 358 359 /** Resets the references to {@link Material}s, {@link Mesh}es and {@link MeshPart}s within the model to the ones used within 360 * it's nodes. This will make the model responsible for disposing all referenced meshes. */ 361 public static void rebuildReferences (final Model model) { 362 model.materials.clear(); 363 model.meshes.clear(); 364 model.meshParts.clear(); 365 for (final Node node : model.nodes) 366 rebuildReferences(model, node); 367 } 368 369 private static void rebuildReferences (final Model model, final Node node) { 370 for (final NodePart mpm : node.parts) { 371 if (!model.materials.contains(mpm.material, true)) model.materials.add(mpm.material); 372 if (!model.meshParts.contains(mpm.meshPart, true)) { 373 model.meshParts.add(mpm.meshPart); 374 if (!model.meshes.contains(mpm.meshPart.mesh, true)) model.meshes.add(mpm.meshPart.mesh); 375 model.manageDisposable(mpm.meshPart.mesh); 376 } 377 } 378 for (final Node child : node.getChildren()) 379 rebuildReferences(model, child); 380 } 381 382 /** Convenience method to create a model with three orthonormal vectors shapes. The resources the Material might contain are not 383 * managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 384 * @param axisLength Length of each axis. 385 * @param capLength is the height of the cap in percentage, must be in (0,1) 386 * @param stemThickness is the percentage of stem diameter compared to cap diameter, must be in (0,1] 387 * @param divisions the amount of vertices used to generate the cap and stem ellipsoidal bases */ 388 public Model createXYZCoordinates (float axisLength, float capLength, float stemThickness, int divisions, int primitiveType, 389 Material material, long attributes) { 390 begin(); 391 MeshPartBuilder partBuilder; 392 Node node = node(); 393 394 partBuilder = part("xyz", primitiveType, attributes, material); 395 partBuilder.setColor(Color.RED); 396 partBuilder.arrow(0, 0, 0, axisLength, 0, 0, capLength, stemThickness, divisions); 397 partBuilder.setColor(Color.GREEN); 398 partBuilder.arrow(0, 0, 0, 0, axisLength, 0, capLength, stemThickness, divisions); 399 partBuilder.setColor(Color.BLUE); 400 partBuilder.arrow(0, 0, 0, 0, 0, axisLength, capLength, stemThickness, divisions); 401 402 return end(); 403 } 404 405 public Model createXYZCoordinates (float axisLength, Material material, long attributes) { 406 return createXYZCoordinates(axisLength, 0.1f, 0.1f, 5, GL20.GL_TRIANGLES, material, attributes); 407 } 408 409 /** Convenience method to create a model with an arrow. The resources the Material might contain are not managed, use 410 * {@link Model#manageDisposable(Disposable)} to add those to the model. 411 * @param material 412 * @param capLength is the height of the cap in percentage, must be in (0,1) 413 * @param stemThickness is the percentage of stem diameter compared to cap diameter, must be in (0,1] 414 * @param divisions the amount of vertices used to generate the cap and stem ellipsoidal bases */ 415 public Model createArrow (float x1, float y1, float z1, float x2, float y2, float z2, float capLength, float stemThickness, 416 int divisions, int primitiveType, Material material, long attributes) { 417 begin(); 418 part("arrow", primitiveType, attributes, material).arrow(x1, y1, z1, x2, y2, z2, capLength, stemThickness, divisions); 419 return end(); 420 } 421 422 /** Convenience method to create a model with an arrow. The resources the Material might contain are not managed, use 423 * {@link Model#manageDisposable(Disposable)} to add those to the model. */ 424 public Model createArrow (Vector3 from, Vector3 to, Material material, long attributes) { 425 return createArrow(from.x, from.y, from.z, to.x, to.y, to.z, 0.1f, 0.1f, 5, GL20.GL_TRIANGLES, material, attributes); 426 } 427 428 /** Convenience method to create a model which represents a grid of lines on the XZ plane. The resources the Material might 429 * contain are not managed, use {@link Model#manageDisposable(Disposable)} to add those to the model. 430 * @param xDivisions row count along x axis. 431 * @param zDivisions row count along z axis. 432 * @param xSize Length of a single row on x. 433 * @param zSize Length of a single row on z. */ 434 public Model createLineGrid (int xDivisions, int zDivisions, float xSize, float zSize, Material material, long attributes) { 435 begin(); 436 MeshPartBuilder partBuilder = part("lines", GL20.GL_LINES, attributes, material); 437 float xlength = xDivisions * xSize, zlength = zDivisions * zSize, hxlength = xlength / 2, hzlength = zlength / 2; 438 float x1 = -hxlength, y1 = 0, z1 = hzlength, x2 = -hxlength, y2 = 0, z2 = -hzlength; 439 for (int i = 0; i <= xDivisions; ++i) { 440 partBuilder.line(x1, y1, z1, x2, y2, z2); 441 x1 += xSize; 442 x2 += xSize; 443 } 444 445 x1 = -hxlength; 446 y1 = 0; 447 z1 = -hzlength; 448 x2 = hxlength; 449 y2 = 0; 450 z2 = -hzlength; 451 for (int j = 0; j <= zDivisions; ++j) { 452 partBuilder.line(x1, y1, z1, x2, y2, z2); 453 z1 += zSize; 454 z2 += zSize; 455 } 456 457 return end(); 458 } 459 460 } 461