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 */
32package com.jme3.bullet.util;
33
34import com.jme3.bounding.BoundingBox;
35import com.jme3.bullet.collision.shapes.*;
36import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
37import com.jme3.math.Matrix3f;
38import com.jme3.math.Transform;
39import com.jme3.math.Vector3f;
40import com.jme3.scene.*;
41import com.jme3.terrain.geomipmap.TerrainPatch;
42import com.jme3.terrain.geomipmap.TerrainQuad;
43import java.util.Iterator;
44import java.util.LinkedList;
45
46/**
47 *
48 * @author normenhansen, tim8dev
49 */
50public 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