1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.bullet.util; 33 34 import com.jme3.bounding.BoundingBox; 35 import com.jme3.bullet.collision.shapes.*; 36 import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; 37 import com.jme3.math.Matrix3f; 38 import com.jme3.math.Transform; 39 import com.jme3.math.Vector3f; 40 import com.jme3.scene.*; 41 import com.jme3.terrain.geomipmap.TerrainPatch; 42 import com.jme3.terrain.geomipmap.TerrainQuad; 43 import java.util.Iterator; 44 import java.util.LinkedList; 45 46 /** 47 * 48 * @author normenhansen, tim8dev 49 */ 50 public class CollisionShapeFactory { 51 52 /** 53 * returns the correct transform for a collisionshape in relation 54 * to the ancestor for which the collisionshape is generated 55 * @param spat 56 * @param parent 57 * @return 58 */ 59 private static Transform getTransform(Spatial spat, Spatial parent) { 60 Transform shapeTransform = new Transform(); 61 Spatial parentNode = spat.getParent() != null ? spat.getParent() : spat; 62 Spatial currentSpatial = spat; 63 //if we have parents combine their transforms 64 while (parentNode != null) { 65 if (parent == currentSpatial) { 66 //real parent -> only apply scale, not transform 67 Transform trans = new Transform(); 68 trans.setScale(currentSpatial.getLocalScale()); 69 shapeTransform.combineWithParent(trans); 70 parentNode = null; 71 } else { 72 shapeTransform.combineWithParent(currentSpatial.getLocalTransform()); 73 parentNode = currentSpatial.getParent(); 74 currentSpatial = parentNode; 75 } 76 } 77 return shapeTransform; 78 } 79 80 private static CompoundCollisionShape createCompoundShape(Node realRootNode, 81 Node rootNode, CompoundCollisionShape shape, boolean meshAccurate, boolean dynamic) { 82 for (Spatial spatial : rootNode.getChildren()) { 83 if (spatial instanceof TerrainQuad) { 84 Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE); 85 if (bool != null && bool.booleanValue()) { 86 continue; // go to the next child in the loop 87 } 88 TerrainQuad terrain = (TerrainQuad) spatial; 89 Transform trans = getTransform(spatial, realRootNode); 90 shape.addChildShape(new HeightfieldCollisionShape(terrain.getHeightMap(), trans.getScale()), 91 trans.getTranslation(), 92 trans.getRotation().toRotationMatrix()); 93 } else if (spatial instanceof Node) { 94 createCompoundShape(realRootNode, (Node) spatial, shape, meshAccurate, dynamic); 95 } else if (spatial instanceof TerrainPatch) { 96 Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE); 97 if (bool != null && bool.booleanValue()) { 98 continue; // go to the next child in the loop 99 } 100 TerrainPatch terrain = (TerrainPatch) spatial; 101 Transform trans = getTransform(spatial, realRootNode); 102 shape.addChildShape(new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()), 103 trans.getTranslation(), 104 trans.getRotation().toRotationMatrix()); 105 } else if (spatial instanceof Geometry) { 106 Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE); 107 if (bool != null && bool.booleanValue()) { 108 continue; // go to the next child in the loop 109 } 110 111 if (meshAccurate) { 112 CollisionShape childShape = dynamic 113 ? createSingleDynamicMeshShape((Geometry) spatial, realRootNode) 114 : createSingleMeshShape((Geometry) spatial, realRootNode); 115 if (childShape != null) { 116 Transform trans = getTransform(spatial, realRootNode); 117 shape.addChildShape(childShape, 118 trans.getTranslation(), 119 trans.getRotation().toRotationMatrix()); 120 } 121 } else { 122 Transform trans = getTransform(spatial, realRootNode); 123 shape.addChildShape(createSingleBoxShape(spatial, realRootNode), 124 trans.getTranslation(), 125 trans.getRotation().toRotationMatrix()); 126 } 127 } 128 } 129 return shape; 130 } 131 132 private static CompoundCollisionShape createCompoundShape( 133 Node rootNode, CompoundCollisionShape shape, boolean meshAccurate) { 134 return createCompoundShape(rootNode, rootNode, shape, meshAccurate, false); 135 } 136 137 /** 138 * This type of collision shape is mesh-accurate and meant for immovable "world objects". 139 * Examples include terrain, houses or whole shooter levels.<br> 140 * Objects with "mesh" type collision shape will not collide with each other. 141 */ 142 private static CompoundCollisionShape createMeshCompoundShape(Node rootNode) { 143 return createCompoundShape(rootNode, new CompoundCollisionShape(), true); 144 } 145 146 /** 147 * This type of collision shape creates a CompoundShape made out of boxes that 148 * are based on the bounds of the Geometries in the tree. 149 * @param rootNode 150 * @return 151 */ 152 private static CompoundCollisionShape createBoxCompoundShape(Node rootNode) { 153 return createCompoundShape(rootNode, new CompoundCollisionShape(), false); 154 } 155 156 /** 157 * This type of collision shape is mesh-accurate and meant for immovable "world objects". 158 * Examples include terrain, houses or whole shooter levels.<br/> 159 * Objects with "mesh" type collision shape will not collide with each other.<br/> 160 * Creates a HeightfieldCollisionShape if the supplied spatial is a TerrainQuad. 161 * @return A MeshCollisionShape or a CompoundCollisionShape with MeshCollisionShapes as children if the supplied spatial is a Node. A HeightieldCollisionShape if a TerrainQuad was supplied. 162 */ 163 public static CollisionShape createMeshShape(Spatial spatial) { 164 if (spatial instanceof TerrainQuad) { 165 TerrainQuad terrain = (TerrainQuad) spatial; 166 return new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()); 167 } else if (spatial instanceof TerrainPatch) { 168 TerrainPatch terrain = (TerrainPatch) spatial; 169 return new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()); 170 } else if (spatial instanceof Geometry) { 171 return createSingleMeshShape((Geometry) spatial, spatial); 172 } else if (spatial instanceof Node) { 173 return createMeshCompoundShape((Node) spatial); 174 } else { 175 throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!"); 176 } 177 } 178 179 /** 180 * This method creates a hull shape for the given Spatial.<br> 181 * If you want to have mesh-accurate dynamic shapes (CPU intense!!!) use GImpact shapes, its probably best to do so with a low-poly version of your model. 182 * @return A HullCollisionShape or a CompoundCollisionShape with HullCollisionShapes as children if the supplied spatial is a Node. 183 */ 184 public static CollisionShape createDynamicMeshShape(Spatial spatial) { 185 if (spatial instanceof Geometry) { 186 return createSingleDynamicMeshShape((Geometry) spatial, spatial); 187 } else if (spatial instanceof Node) { 188 return createCompoundShape((Node) spatial, (Node) spatial, new CompoundCollisionShape(), true, true); 189 } else { 190 throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!"); 191 } 192 193 } 194 195 public static CollisionShape createBoxShape(Spatial spatial) { 196 if (spatial instanceof Geometry) { 197 return createSingleBoxShape((Geometry) spatial, spatial); 198 } else if (spatial instanceof Node) { 199 return createBoxCompoundShape((Node) spatial); 200 } else { 201 throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!"); 202 } 203 } 204 205 /** 206 * This type of collision shape is mesh-accurate and meant for immovable "world objects". 207 * Examples include terrain, houses or whole shooter levels.<br> 208 * Objects with "mesh" type collision shape will not collide with each other. 209 */ 210 private static MeshCollisionShape createSingleMeshShape(Geometry geom, Spatial parent) { 211 Mesh mesh = geom.getMesh(); 212 Transform trans = getTransform(geom, parent); 213 if (mesh != null) { 214 MeshCollisionShape mColl = new MeshCollisionShape(mesh); 215 mColl.setScale(trans.getScale()); 216 return mColl; 217 } else { 218 return null; 219 } 220 } 221 222 /** 223 * Uses the bounding box of the supplied spatial to create a BoxCollisionShape 224 * @param spatial 225 * @return BoxCollisionShape with the size of the spatials BoundingBox 226 */ 227 private static BoxCollisionShape createSingleBoxShape(Spatial spatial, Spatial parent) { 228 spatial.setModelBound(new BoundingBox()); 229 //TODO: using world bound here instead of "local world" bound... 230 BoxCollisionShape shape = new BoxCollisionShape( 231 ((BoundingBox) spatial.getWorldBound()).getExtent(new Vector3f())); 232 return shape; 233 } 234 235 /** 236 * This method creates a hull collision shape for the given mesh.<br> 237 */ 238 private static HullCollisionShape createSingleDynamicMeshShape(Geometry geom, Spatial parent) { 239 Mesh mesh = geom.getMesh(); 240 Transform trans = getTransform(geom, parent); 241 if (mesh != null) { 242 HullCollisionShape dynamicShape = new HullCollisionShape(mesh); 243 dynamicShape.setScale(trans.getScale()); 244 return dynamicShape; 245 } else { 246 return null; 247 } 248 } 249 250 /** 251 * This method moves each child shape of a compound shape by the given vector 252 * @param vector 253 */ 254 public static void shiftCompoundShapeContents(CompoundCollisionShape compoundShape, Vector3f vector) { 255 for (Iterator<ChildCollisionShape> it = new LinkedList(compoundShape.getChildren()).iterator(); it.hasNext();) { 256 ChildCollisionShape childCollisionShape = it.next(); 257 CollisionShape child = childCollisionShape.shape; 258 Vector3f location = childCollisionShape.location; 259 Matrix3f rotation = childCollisionShape.rotation; 260 compoundShape.removeChildShape(child); 261 compoundShape.addChildShape(child, location.add(vector), rotation); 262 } 263 } 264 } 265