All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.bullet.collision.shapes.CollisionShape Maven / Gradle / Ivy

There is a newer version: 8.2.0+mt
Show newest version
/*
 * Copyright (c) 2009-2018 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.bullet.collision.shapes;

import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.NativePhysicsObject;
import com.jme3.bullet.util.DebugShapeFactory;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import com.simsilica.mathd.Vec3d;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import jme3utilities.Validate;
import jme3utilities.math.MyVector3f;
import jme3utilities.minie.MyShape;

/**
 * The abstract base class for collision shapes based on Bullet's
 * {@code btCollisionShape}.
 * 

* Subclasses include {@code ConvexShape} and {@code MeshCollisionShape}. As * suggested in the Bullet manual, a single collision shape can be shared among * multiple collision objects. * * @author normenhansen */ abstract public class CollisionShape extends NativePhysicsObject implements JmeCloneable, Savable { // ************************************************************************* // constants and loggers /** * message logger for this class */ final public static Logger logger = Logger.getLogger(CollisionShape.class.getName()); /** * local copy of {@link com.jme3.math.Quaternion#IDENTITY} */ final private static Quaternion rotateIdentity = new Quaternion(); /** * field names for serialization */ final private static String tagEnableContactFilter = "enableContactFilter"; final private static String tagMargin = "margin"; final private static String tagScale = "scale"; final private static String tagUserIndex = "userIndex"; final private static String tagUserIndex2 = "userIndex2"; /** * local copy of {@link com.jme3.math.Transform#IDENTITY} */ final private static Transform transformIdentity = new Transform(); /** * local copy of {@link com.jme3.math.Vector3f#ZERO} */ final private static Vector3f translateIdentity = new Vector3f(0f, 0f, 0f); // ************************************************************************* // fields /** * copy of the contact-filter enable flag */ protected boolean enableContactFilter = false; /** * default margin for new non-sphere/non-capsule shapes (in physics-space * units, >0) */ private static float defaultMargin = 0.04f; /** * copy of collision margin (in physics-space units, >0, default=0.04) */ protected float margin = defaultMargin; /** * copy of the scale factors, one for each local axis */ protected Vector3f scale = new Vector3f(1f, 1f, 1f); // ************************************************************************* // constructors /** * Instantiate a collision shape with no tracker and no assigned native * object. *

* This no-arg constructor was made explicit to avoid javadoc warnings from * JDK 18+. */ protected CollisionShape() { } // ************************************************************************* // new methods exposed /** * Return the center of the shape's axis-aligned bounding box in local * coordinates. * * @param storeResult storage for the result (modified if not null) * @return a location in the local coordinate system (either * {@code storeResult} or a new vector) */ public Vector3f aabbCenter(Vector3f storeResult) { Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; BoundingBox aabb = boundingBox(translateIdentity, rotateIdentity, null); aabb.getCenter(result); return result; } /** * Calculate a quick upper bound for the scaled volume of a shape, based on * its axis-aligned bounding box. Collision margin is included. * * @return the volume (in scaled shape units cubed, ≥0) */ public float aabbScaledVolume() { BoundingBox aabb = boundingBox(translateIdentity, rotateIdentity, null); Vector3f halfExtents = aabb.getExtent(null); float volume = 8f * halfExtents.x * halfExtents.y * halfExtents.z; assert volume >= 0f : volume; assert Float.isFinite(volume) : volume; return volume; } /** * Calculate an axis-aligned bounding box with the specified translation and * rotation applied. Rotation is applied first. Collision margin is * included. * * @param translation the translation to apply (not null, unaffected) * @param rotation the rotation to apply (not null, unaffected) * @param storeResult storage for the result (modified if not null) * @return a bounding box (either storeResult or a new instance, not null) */ public BoundingBox boundingBox( Vector3f translation, Matrix3f rotation, BoundingBox storeResult) { Validate.finite(translation, "translation"); Validate.nonNull(rotation, "rotation"); BoundingBox result = (storeResult == null) ? new BoundingBox() : storeResult; recalculateAabb(); long shapeId = nativeId(); Vector3f maxima = new Vector3f(); Vector3f minima = new Vector3f(); getAabb(shapeId, translation, rotation, minima, maxima); result.setMinMax(minima, maxima); return result; } /** * Calculate an axis-aligned bounding box with the specified translation and * rotation applied. Rotation is applied first. Collision margin is * included. * * @param translation the translation to apply (not null, unaffected) * @param rotation the rotation to apply (not null, unaffected) * @param storeResult storage for the result (modified if not null) * @return a bounding box (either storeResult or a new instance, not null) */ public BoundingBox boundingBox(Vector3f translation, Quaternion rotation, BoundingBox storeResult) { Validate.finite(translation, "translation"); Validate.nonNull(rotation, "rotation"); BoundingBox result = (storeResult == null) ? new BoundingBox() : storeResult; recalculateAabb(); long shapeId = nativeId(); Matrix3f basisMatrix = new Matrix3f().set(rotation); Vector3f maxima = new Vector3f(); Vector3f minima = new Vector3f(); getAabb(shapeId, translation, basisMatrix, minima, maxima); result.setMinMax(minima, maxima); return result; } /** * Test whether the specified scale factors can be applied to the shape. * Subclasses that restrict scaling should override this method. * * @param scale the desired scale factor for each local axis (may be null, * unaffected) * @return true if applicable, otherwise false */ public boolean canScale(Vector3f scale) { boolean result; if (scale == null) { result = false; } else { result = MyVector3f.isAllNonNegative(scale); } return result; } /** * Test whether the shape can be split by an arbitrary plane. Meant to be * overridden. * * @return true if splittable, false otherwise */ public boolean canSplit() { return false; } /** * Return the default margin for new shapes that are neither capsules nor * spheres. * * @return the margin thickness (in physics-space units, >0) */ public static float getDefaultMargin() { assert defaultMargin > 0f : defaultMargin; return defaultMargin; } /** * Return the collision margin for this shape. * * @return the margin thickness (in physics-space units, ≥0) */ public float getMargin() { assert margin >= 0f : margin; assert margin == nativeMargin() : margin + " != " + nativeMargin(); return margin; } /** * Copy the scale factors. * * @param storeResult storage for the result (modified if not null) * @return the scale factor for each local axis (either storeResult or a new * vector, not null, no negative component) */ public Vector3f getScale(Vector3f storeResult) { Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; assert checkScale(result); result.set(scale); return result; } /** * Copy the scale factors. * * @param storeResult storage for the result (modified if not null) * @return the scale factor for each local axis (either {@code storeResult} * or a new vector, not null, no negative component) */ public Vec3d getScaleDp(Vec3d storeResult) { long shapeId = nativeId(); Vec3d result = (storeResult == null) ? new Vec3d() : storeResult; getLocalScalingDp(shapeId, result); return result; } /** * Return the encoded type of the shape. * * @return the type code (from Bullet's {@code enum BroadphaseNativeTypes}) */ public int getShapeType() { long shapeId = nativeId(); int result = getShapeType(shapeId); return result; } /** * Test whether the shape has concave type. In Bullet, "concave" is a * property of types of shapes. Specific instances of * those types might actually be "convex" in the mathematical sense of the * word. *

* The only concave types are the empty, gimpact, heightfield, mesh, and * plane shapes. Note that compound shapes are neither concave nor convex. * * @return true if concave type, false otherwise */ public boolean isConcave() { long shapeId = nativeId(); boolean result = isConcave(shapeId); return result; } /** * Test whether contact filtering is enabled for this shape. Contact * filtering is implemented only for HeightfieldCollisionShape and * MeshCollisionShapes. * * @return true if enabled, otherwise false */ public boolean isContactFilterEnabled() { assert enableContactFilter == isContactFilterEnabled(nativeId()) : "copy of flag = " + enableContactFilter; return enableContactFilter; } /** * Test whether the shape has convex type. In Bullet, "convex" is a property * of types of shapes. Specific instances of non-convex * types might still be "convex" in the mathematical sense of the word. *

* Convex types include the box2d, box, capsule, cone, convex2d, cylinder, * hull, multi-sphere, simplex, and sphere shapes. Note that compound shapes * are neither concave nor convex. * * @return true if convex type, false otherwise */ public boolean isConvex() { long shapeId = nativeId(); boolean result = isConvex(shapeId); return result; } /** * Test whether the shape's type is infinite. {@code PlaneCollisionShape} is * the only type of shape that's infinite. * * @return true if infinite, false otherwise */ public boolean isInfinite() { long shapeId = nativeId(); boolean result = isInfinite(shapeId); return result; } /** * Test whether the shape can be applied to a dynamic rigid body. The only * non-moving shapes are the empty, heightfield, mesh, and plane shapes. * * @return true if non-moving, false otherwise */ public boolean isNonMoving() { long shapeId = nativeId(); boolean result = isNonMoving(shapeId); return result; } /** * Test whether this shape is convex and defined by polygons. The only * polyhedral shapes are the box, hull, and simplex shapes. * * @return true if polyhedral, false otherwise */ public boolean isPolyhedral() { long shapeId = nativeId(); boolean result = isPolyhedral(shapeId); return result; } /** * Estimate how far the scaled shape extends from its center, including * margin. * * @return a distance estimate (in physics-space units, ≥0, may be * infinite) */ public float maxRadius() { float result = DebugShapeFactory.maxDistance( this, transformIdentity, DebugShapeFactory.lowResolution); return result; } /** * Estimate the volume of this shape, including scale and margin. Meant to * be overridden. * * @return the volume (in physics-space units cubed, ≥0) */ public float scaledVolume() { throw new UnsupportedOperationException("Not implemented for: " + this); } /** * Enable/disable contact filtering for this shape. * * @param setting the desired setting (default=false) */ public void setContactFilterEnabled(boolean setting) { long shapeId = nativeId(); setContactFilterEnabled(shapeId, setting); this.enableContactFilter = setting; } /** * Alter the default margin for new shapes that are neither capsules nor * spheres. * From Bullet manual:
* It is best not to modify the default collision margin, and if you do use * a positive value: zero margin might introduce problems. * * @param margin the desired margin thickness (in physics-space units, * >0, default=0.04) */ public static void setDefaultMargin(float margin) { Validate.positive(margin, "margin"); defaultMargin = margin; } /** * Alter the collision margin of this shape. CAUTION: Margin is applied * differently, depending on the type of shape. Generally the collision * margin expands the object, creating a gap. *

* From Bullet manual:
* It is best not to modify the default collision margin, and if you do use * a positive value: zero margin might introduce problems. *

* Note that if the shape is shared (between collision objects and/or * compound shapes) changes can have unintended consequences. * * @param margin the desired margin thickness (in physics-space units, * >0, default=0.04) */ public void setMargin(float margin) { Validate.positive(margin, "margin"); long shapeId = nativeId(); setMargin(shapeId, margin); logger.log(Level.FINE, "Margining {0}.", this); this.margin = margin; } /** * Alter the scale of this shape to a uniform factor. CAUTION: Not all * shapes can be scaled. *

* Note that if the shape is shared (between collision objects and/or * compound shapes) changes can have unintended consequences. * * @param factor the desired scale factor for all axes (≥0, default=1) */ public void setScale(float factor) { Validate.nonNegative(factor, "factor"); Vector3f scaleVector = new Vector3f(factor, factor, factor); // TODO garbage setScale(scaleVector); } /** * Alter the scale of this shape. CAUTION: Not all shapes can be scaled * arbitrarily. *

* Note that if the shape is shared (between collision objects and/or * compound shapes) changes can have unintended consequences. * * @param scale the desired scale factor for each local axis (not null, no * negative component, unaffected, default=(1,1,1)) */ public void setScale(Vector3f scale) { Validate.nonNegative(scale, "scale"); if (!canScale(scale)) { String typeName = getClass().getCanonicalName(); String message = String.format("%s cannot be scaled to (%s,%s,%s)", typeName, scale.x, scale.y, scale.z); throw new IllegalArgumentException(message); } long shapeId = nativeId(); setLocalScaling(shapeId, scale); logger.log(Level.FINE, "Scaling {0}.", this); this.scale.set(scale); } /** * Alter the primary user index. Applications may use this parameter for any * purpose (native field: m_userIndex). * * @param index the desired value (default=-1) */ public void setUserIndex(int index) { long shapeId = nativeId(); setUserIndex(shapeId, index); } /** * Alter the secondary user index. Applications may use this parameter for * any purpose (native field: m_userIndex2). * * @param index the desired value (default=-1) */ public void setUserIndex2(int index) { long shapeId = nativeId(); setUserIndex2(shapeId, index); } /** * Approximate this shape with a splittable shape. Meant to be overridden. * * @return a new splittable shape */ public CollisionShape toSplittableShape() { if (canSplit()) { return this; } else { throw new IllegalArgumentException("this = " + this); } } /** * Return the primary user index (native field: m_userIndex). * * @return the value */ public int userIndex() { long shapeId = nativeId(); int result = getUserIndex(shapeId); return result; } /** * Return the secondary user index (native field: m_userIndex2). * * @return the value */ public int userIndex2() { long shapeId = nativeId(); int result = getUserIndex2(shapeId); return result; } // ************************************************************************* // new protected methods /** * Copy common properties from another {@code CollisionShape}. Used during * cloning. * * @param old the instance to copy from (not null, unaffected) */ final protected void copyShapeProperties(CollisionShape old) { setUserIndex(old.userIndex()); setUserIndex2(old.userIndex2()); } /** * Return the type of this shape. * * @param shapeId the ID of the {@code btCollisionShape} (not zero) * @return the type value (from Bullet's {@code enum BroadphaseNativeTypes}) */ final native protected static int getShapeType(long shapeId); /** * Return the native collision margin of this shape. * * @return the margin thickness (in physics-space units, ≥0) */ final protected float nativeMargin() { long shapeId = nativeId(); float result = getMargin(shapeId); assert result >= 0f : result; return margin; } /** * Read common properties from an {@code InputCapsule}. * * @param capsule the input capsule (not null, modified) * @throws IOException from the importer */ final protected void readShapeProperties(InputCapsule capsule) throws IOException { setUserIndex(capsule.readInt(tagUserIndex, -1)); setUserIndex2(capsule.readInt(tagUserIndex2, -1)); } /** * Recalculate this shape's bounding box if necessary. Meant to be * overridden. */ protected void recalculateAabb() { // do nothing } /** * Synchronize the copied scale factors with the {@code btCollisionShape}. */ protected void updateScale() { long shapeId = nativeId(); getLocalScaling(shapeId, scale); } // ************************************************************************* // JmeCloneable methods /** * Callback from {@link com.jme3.util.clone.Cloner} to convert this * shallow-cloned shape into a deep-cloned one, using the specified Cloner * and original to resolve copied fields. * * @param cloner the Cloner that's cloning this shape (not null) * @param original the instance from which this shape was shallow-cloned * (unused) */ @Override public void cloneFields(Cloner cloner, Object original) { this.scale = cloner.clone(scale); unassignNativeObject(); /* * The caller should create the btCollisionShape * and invoke setNativeId() and copyShapeProperties(). */ } /** * Create a shallow clone for the JME cloner. * * @return a new instance */ @Override public CollisionShape jmeClone() { try { CollisionShape clone = (CollisionShape) clone(); return clone; } catch (CloneNotSupportedException exception) { throw new RuntimeException(exception); } } // ************************************************************************* // Savable methods /** * De-serialize the shape from the specified importer, for example when * loading from a J3O file. * * @param importer (not null) * @throws IOException from the importer */ @Override public void read(JmeImporter importer) throws IOException { InputCapsule capsule = importer.getCapsule(this); this.enableContactFilter = capsule.readBoolean(tagEnableContactFilter, false); Savable s = capsule.readSavable(tagScale, new Vector3f(1f, 1f, 1f)); scale.set((Vector3f) s); this.margin = capsule.readFloat(tagMargin, 0.04f); /* * Subclasses must create the btCollisionShape, * apply the contact-filter enable, margin, and scale to it, * and invoke readShapeProperties(). */ } /** * Serialize this shape to the specified exporter, for example when saving * to a J3O file. * * @param exporter (not null) * @throws IOException from the exporter */ @Override public void write(JmeExporter exporter) throws IOException { OutputCapsule capsule = exporter.getCapsule(this); capsule.write(enableContactFilter, tagEnableContactFilter, false); capsule.write(scale, tagScale, null); capsule.write(margin, tagMargin, 0.04f); capsule.write(userIndex(), tagUserIndex, -1); capsule.write(userIndex2(), tagUserIndex2, -1); } // ************************************************************************* // NativePhysicsObject methods /** * Initialize the native ID. * * @param shapeId the identifier of the {@code btCollisionShape} (not zero) */ @Override protected void setNativeId(long shapeId) { super.setNativeId(shapeId); logger.log(Level.FINE, "Created {0}.", this); } /** * Represent this CollisionShape as a String. * * @return a descriptive string of text (not null, not empty) */ @Override public String toString() { String result = MyShape.describeType(this); long shapeId = nativeId(); result += "#" + Long.toHexString(shapeId); return result; } // ************************************************************************* // Java private methods /** * Compare Bullet's scale factors to the local copies. * * @param storeVector caller-allocated temporary storage (not null) * @return true if Bullet and the JVM copy match exactly, otherwise false */ private boolean checkScale(Vector3f storeVector) { assert storeVector != null; long shapeId = nativeId(); getLocalScaling(shapeId, storeVector); boolean result = scale.equals(storeVector); if (!result) { logger.log(Level.WARNING, "mismatch detected: shape={0} copy={1} native={2}", new Object[]{this, scale, storeVector}); } return result; } /** * Free the identified tracked native object. Invoked by reflection. * * @param shapeId the native identifier (not zero) */ private static void freeNativeObject(long shapeId) { assert shapeId != 0L; DebugShapeFactory.removeShapeFromCache(shapeId); finalizeNative(shapeId); } // ************************************************************************* // native private methods native private static void finalizeNative(long shapeId); native private static void getAabb(long shapeId, Vector3f location, Matrix3f basisMatrix, Vector3f storeMinima, Vector3f storeMaxima); native private static void getLocalScaling(long shapeId, Vector3f storeVector); native private static void getLocalScalingDp(long shapeId, Vec3d storeVector); native private static float getMargin(long shapeId); native private static int getUserIndex(long shapeId); native private static int getUserIndex2(long shapeId); native private static boolean isConcave(long shapeId); native private static boolean isContactFilterEnabled(long shapeId); native private static boolean isConvex(long shapeId); native private static boolean isInfinite(long shapeId); native private static boolean isNonMoving(long shapeId); native private static boolean isPolyhedral(long shapeId); native private static void setContactFilterEnabled(long shapeId, boolean setting); native private static void setLocalScaling(long shapeId, Vector3f scale); native private static void setMargin(long shapeId, float margin); native private static void setUserIndex(long shapeId, int index); native private static void setUserIndex2(long shapeId, int index); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy