/*
* Copyright (c) 2003-2007 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.jme.bounding;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import com.jme.intersection.IntersectionRecord;
import com.jme.math.FastMath;
import com.jme.math.Line;
import com.jme.math.LineSegment;
import com.jme.math.Plane;
import com.jme.math.Quaternion;
import com.jme.math.Ray;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.scene.batch.GeomBatch;
import com.jme.scene.batch.TriangleBatch;
import com.jme.util.geom.BufferUtils;
public class BoundingCapsule extends BoundingVolume {
private static final long serialVersionUID = 1L;
private transient LineSegment ls;
private float radius;
private static Vector3f compVec1 = new Vector3f();
private static Vector3f compVec2 = new Vector3f();
private static Vector3f compVec3 = new Vector3f();
private static Vector3f compVec4 = new Vector3f();
private static Vector3f diff = new Vector3f();
private static Line compLine1 = new Line();
public BoundingCapsule() {
ls = new LineSegment();
}
public BoundingCapsule(Vector3f center, LineSegment ls, float radius) {
this.center = center;
this.ls = ls;
this.radius = radius;
}
@Override
public BoundingVolume clone(BoundingVolume store) {
if (store != null && store.getType() == BoundingVolume.BOUNDING_CAPSULE) {
BoundingCapsule capsule = (BoundingCapsule) store;
capsule.getCenter().set(center);
capsule.setRadius(radius);
capsule.getLineSegment().set(ls);
capsule.checkPlane = checkPlane;
return capsule;
}
BoundingCapsule capsule = new BoundingCapsule(
(Vector3f) center.clone(), (LineSegment) ls.clone(), radius);
return capsule;
}
@Override
public void computeFromBatches(ArrayList batches) {
if (batches == null || batches.size() == 0) {
return;
}
BoundingCapsule temp = new BoundingCapsule();
temp.computeFromPoints(((GeomBatch) batches.get(0)).getVertexBuffer());
for (int i = 1; i < batches.size(); i++) {
BoundingCapsule bc = new BoundingCapsule();
bc
.computeFromPoints(((GeomBatch) batches.get(i))
.getVertexBuffer());
temp.mergeLocal(bc);
}
this.ls = temp.getLineSegment();
this.center = temp.getCenter();
this.radius = temp.getRadius();
}
@Override
public void computeFromPoints(FloatBuffer points) {
leastSquaresFit(points);
}
/**
* Least-Squares Fit will fit the points by a line using the least-squares
* algorithm. Let the line be A + tW, with W unit length and A the average
* of the data points. This line contains the capsule line segment. Compute
* the radius to be the maximum distance from the data points to the line.
*
* @param points
*/
private void leastSquaresFit(FloatBuffer points) {
if (points == null) {
return;
}
points.rewind();
if (points.remaining() <= 2) { // we need at least a 3 float vector
return;
}
if (ls == null) {
ls = new LineSegment();
}
int length = points.remaining() / 3;
compLine1.orthogonalLineFit(points);
float maxRadiusSquared = 0f;
for (int i = 0; i < length; i++) {
BufferUtils.populateFromBuffer(compVec1, points, i);
float radiusSquared = compLine1.distanceSquared(compVec1);
if (radiusSquared > maxRadiusSquared) {
maxRadiusSquared = radiusSquared;
}
}
Vector3f u = new Vector3f();
Vector3f v = new Vector3f();
Vector3f w = new Vector3f(compLine1.getDirection()).normalizeLocal();
Vector3f.generateComplementBasis(u, v, w);
float min = Float.POSITIVE_INFINITY;
float max = Float.NEGATIVE_INFINITY;
for (int i = 0; i < length; i++) {
BufferUtils.populateFromBuffer(compVec1, points, i);
compVec1.subtractLocal(compLine1.getOrigin());
float uDot = u.dot(compVec1);
float vDot = v.dot(compVec1);
float wDot = w.dot(compVec1);
float discrimenator = maxRadiusSquared
- (uDot * uDot + vDot * vDot);
float radical = FastMath.sqrt(FastMath.abs(discrimenator));
float testValue = wDot + radical;
if (testValue < min) {
min = testValue;
}
testValue = wDot - radical;
if (testValue > max) {
max = testValue;
}
}
radius = FastMath.sqrt(maxRadiusSquared);
ls.getOrigin().set(
(compLine1.getOrigin().addLocal((compLine1.getDirection().mult(
((min + max) * 0.5f), compVec1)))));
center = ls.getOrigin();
ls.getDirection().set(compLine1.getDirection());
if (max > min) {
// container is a capsule
ls.setExtent((0.5f) * (max - min));
} else {
// container is a sphere
ls.setExtent(0);
}
}
@Override
public void computeFromTris(int[] triIndex, TriangleBatch batch, int start,
int end) {
// TODO Auto-generated method stub
}
@Override
public void computeFromTris(Triangle[] tris, int start, int end) {
// TODO Auto-generated method stub
}
@Override
public boolean contains(Vector3f point) {
return ls.distanceSquared(point) <= radius * radius;
}
public boolean contains(BoundingSphere sphere) {
return contains(sphere.getRadius(), sphere.getCenter());
}
public boolean contains(float sphereRadius, Vector3f sphereCenter) {
float diff = radius - sphereRadius;
if (diff >= 0.0) {
return ls.distanceSquared(sphereCenter) <= diff * diff;
} else {
return false;
}
}
public boolean contains(BoundingCapsule capsule) {
return contains(capsule.getRadius(), capsule.getLineSegment()
.getNegativeEnd(compVec1), capsule.getLineSegment()
.getPositiveEnd(compVec2));
}
public boolean contains(float radius, Vector3f start, Vector3f end) {
return contains(radius, start) && contains(radius, end);
}
@Override
public float distanceToEdge(Vector3f point) {
// return lineSegment.distance(point) - radius;
return 0;
}
@Override
public int getType() {
return BOUNDING_CAPSULE;
}
@Override
public boolean intersects(BoundingVolume bv) {
if (bv == null) {
return false;
}
return bv.intersectsCapsule(this);
}
@Override
public boolean intersects(Ray ray) {
float squareDistance = ls.distanceSquared(ray);
return squareDistance <= radius * radius;
}
@Override
public boolean intersectsBoundingBox(BoundingBox bb) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean intersectsCapsule(BoundingCapsule bc) {
float distance = ls.distance(bc.getLineSegment());
float radiusSum = radius + bc.getRadius();
return distance <= radiusSum;
}
@Override
public boolean intersectsOrientedBoundingBox(OrientedBoundingBox bb) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean intersectsSphere(BoundingSphere bs) {
// float distanceSqr = lineSegment.distanceSquared(bs.getCenter());
// return distanceSqr <= radius * bs.getRadius();
return false;
}
@Override
public IntersectionRecord intersectsWhere(Ray ray) {
// TODO Auto-generated method stub
return null;
}
@Override
public BoundingVolume merge(BoundingVolume volume) {
switch (volume.getType()) {
case BoundingVolume.BOUNDING_CAPSULE:
return mergeCapsule((BoundingCapsule) volume, new BoundingCapsule());
case BoundingVolume.BOUNDING_SPHERE:
return mergeSphere((BoundingSphere)volume, new BoundingCapsule());
case BoundingVolume.BOUNDING_BOX: {
BoundingBox box = (BoundingBox) volume;
Vector3f radVect = new Vector3f(box.xExtent, box.yExtent,
box.zExtent);
Vector3f temp_center = box.center;
BoundingSphere rVal = new BoundingSphere();
rVal.setCenter(temp_center);
rVal.setRadius(radVect.length());
return mergeSphere(rVal, new BoundingCapsule());
}
default:
return null;
}
}
public BoundingCapsule mergeSphere(BoundingSphere sphere, BoundingCapsule merged) {
if (contains(sphere)) {
return this;
}
// if (capsule.contains(this)) {
// return capsule;
// }
// axis center is average of input axis centers
compLine1.getOrigin().set(
ls.getOrigin().add(sphere.getCenter(),
compVec1).mult(0.5f, compVec1));
compLine1.getDirection().set(ls.getDirection());
compLine1.getDirection().normalizeLocal();
// Cylinder with axis 'compLine1' must contain the spheres centered at
// the end points of the input capsules.
ls.getPositiveEnd(compVec1);
float maxRadius = compLine1.distance(compVec1) + radius;
ls.getNegativeEnd(compVec2);
float temp = compLine1.distance(compVec2) + radius;
if (temp > maxRadius) {
maxRadius = temp;
}
temp = compLine1.distance(sphere.getCenter()) + sphere.getRadius();
if (temp > maxRadius) {
maxRadius = temp;
}
// process sphere <PosEnd0,r0>
float radiusDiff = maxRadius - sphere.getRadius();
float radiusDiffSqr = radiusDiff * radiusDiff;
diff = compLine1.getOrigin().subtract(compVec1, diff);
float lengthMinusDiff = diff.lengthSquared() - radiusDiffSqr;
float dotDirection = diff.dot(compLine1.getDirection());
float discr = dotDirection * dotDirection - lengthMinusDiff;
float root = FastMath.sqrt(FastMath.abs(discr));
float tPositive = -dotDirection - root;
float tNegative = -dotDirection + root;
// process sphere <NegEnd0,r0>
diff = compLine1.getOrigin().subtract(compVec2, diff);
lengthMinusDiff = diff.lengthSquared() - radiusDiffSqr;
dotDirection = diff.dot(compLine1.getDirection());
discr = dotDirection * dotDirection - lengthMinusDiff;
root = FastMath.sqrt(FastMath.abs(discr));
temp = -dotDirection - root;
if (temp > tPositive) {
tPositive = temp;
}
temp = -dotDirection + root;
if (temp < tNegative) {
tNegative = temp;
}
// process sphere <PosEnd1,r1>
radiusDiff = maxRadius - sphere.getRadius();
radiusDiffSqr = radiusDiff * radiusDiff;
diff = compLine1.getOrigin().subtract(sphere.getCenter(), diff);
lengthMinusDiff = diff.lengthSquared() - radiusDiffSqr;
dotDirection = diff.dot(compLine1.getDirection());
discr = dotDirection * dotDirection - lengthMinusDiff;
root = FastMath.sqrt(FastMath.abs(discr));
temp = -dotDirection - root;
if (temp > tPositive) {
tPositive = temp;
}
temp = -dotDirection + root;
if (temp < tNegative) {
tNegative = temp;
}
merged.setRadius(maxRadius);
merged.getLineSegment().getOrigin().set(
compLine1.getOrigin().add(
compLine1.getDirection().mult(
((float) 0.5) * (tPositive + tNegative))));
merged.getLineSegment().getDirection().set(compLine1.getDirection());
if (tPositive > tNegative) {
// container is a capsule
merged.getLineSegment().setExtent((0.5f) * (tPositive - tNegative));
} else {
// container is a sphere
merged.getLineSegment().setExtent(0.0f);
}
return merged;
}
public BoundingCapsule mergeCapsule(BoundingCapsule capsule,
BoundingCapsule merged) {
if (contains(capsule)) {
return this;
}
if (capsule.contains(this)) {
return capsule;
}
// axis center is average of input axis centers
compLine1.getOrigin().set(
ls.getOrigin().add(capsule.getLineSegment().getOrigin(),
compVec1).mult(0.5f, compVec1));
// axis unit direction is average of input axis unit directions
if (ls.getDirection().dot(capsule.getLineSegment().getDirection()) >= 0.0f) {
compLine1.getDirection().set(
ls.getDirection().add(
capsule.getLineSegment().getDirection(), compVec1));
} else {
compLine1.getDirection().set(
ls.getDirection().subtract(
capsule.getLineSegment().getDirection(), compVec1));
}
compLine1.getDirection().normalizeLocal();
// Cylinder with axis 'compLine1' must contain the spheres centered at
// the end points of the input capsules.
ls.getPositiveEnd(compVec1);
float maxRadius = compLine1.distance(compVec1) + radius;
ls.getNegativeEnd(compVec2);
float temp = compLine1.distance(compVec2) + radius;
if (temp > maxRadius) {
maxRadius = temp;
}
capsule.getLineSegment().getPositiveEnd(compVec3);
temp = compLine1.distance(compVec3) + capsule.getRadius();
if (temp > maxRadius) {
maxRadius = temp;
}
capsule.getLineSegment().getNegativeEnd(compVec4);
temp = compLine1.distance(compVec4) + capsule.getRadius();
if (temp > maxRadius) {
maxRadius = temp;
}
// process sphere <PosEnd0,r0>
float radiusDiff = maxRadius - capsule.getRadius();
float radiusDiffSqr = radiusDiff * radiusDiff;
diff = compLine1.getOrigin().subtract(compVec1, diff);
float lengthMinusDiff = diff.lengthSquared() - radiusDiffSqr;
float dotDirection = diff.dot(compLine1.getDirection());
float discr = dotDirection * dotDirection - lengthMinusDiff;
float root = FastMath.sqrt(FastMath.abs(discr));
float tPositive = -dotDirection - root;
float tNegative = -dotDirection + root;
// process sphere <NegEnd0,r0>
diff = compLine1.getOrigin().subtract(compVec2, diff);
lengthMinusDiff = diff.lengthSquared() - radiusDiffSqr;
dotDirection = diff.dot(compLine1.getDirection());
discr = dotDirection * dotDirection - lengthMinusDiff;
root = FastMath.sqrt(FastMath.abs(discr));
temp = -dotDirection - root;
if (temp > tPositive) {
tPositive = temp;
}
temp = -dotDirection + root;
if (temp < tNegative) {
tNegative = temp;
}
// process sphere <PosEnd1,r1>
radiusDiff = maxRadius - capsule.getRadius();
radiusDiffSqr = radiusDiff * radiusDiff;
diff = compLine1.getOrigin().subtract(compVec3, diff);
lengthMinusDiff = diff.lengthSquared() - radiusDiffSqr;
dotDirection = diff.dot(compLine1.getDirection());
discr = dotDirection * dotDirection - lengthMinusDiff;
root = FastMath.sqrt(FastMath.abs(discr));
temp = -dotDirection - root;
if (temp > tPositive) {
tPositive = temp;
}
temp = -dotDirection + root;
if (temp < tNegative) {
tNegative = temp;
}
// process sphere <NegEnd1,r1>
diff = compLine1.getOrigin().subtract(compVec4, diff);
lengthMinusDiff = diff.lengthSquared() - radiusDiffSqr;
dotDirection = diff.dot(compLine1.getDirection());
discr = dotDirection * dotDirection - lengthMinusDiff;
root = FastMath.sqrt(FastMath.abs(discr));
temp = -dotDirection - root;
if (temp > tPositive) {
tPositive = temp;
}
temp = -dotDirection + root;
if (temp < tNegative) {
tNegative = temp;
}
merged.setRadius(maxRadius);
merged.getLineSegment().getOrigin().set(
compLine1.getOrigin().add(
compLine1.getDirection().mult(
((float) 0.5) * (tPositive + tNegative))));
merged.getLineSegment().getDirection().set(compLine1.getDirection());
if (tPositive > tNegative) {
// container is a capsule
merged.getLineSegment().setExtent((0.5f) * (tPositive - tNegative));
} else {
// container is a sphere
merged.getLineSegment().setExtent(0.0f);
}
return merged;
}
@Override
public BoundingVolume mergeLocal(BoundingVolume volume) {
if (volume == null) {
return this;
}
switch (volume.getType()) {
case BoundingVolume.BOUNDING_CAPSULE: {
return mergeCapsule((BoundingCapsule) volume, this);
}
case BoundingVolume.BOUNDING_SPHERE: {
return mergeSphere((BoundingSphere)volume, this);
}
case BoundingVolume.BOUNDING_BOX: {
BoundingBox box = (BoundingBox) volume;
Vector3f radVect = new Vector3f(box.xExtent, box.yExtent,
box.zExtent);
Vector3f temp_center = box.center;
BoundingSphere rVal = new BoundingSphere();
rVal.setCenter(temp_center);
rVal.setRadius(radVect.length());
return mergeSphere(rVal, this);
}
default:
return this;
}
}
@Override
public BoundingVolume transform(Quaternion rotate, Vector3f translate,
Vector3f scale, BoundingVolume store) {
BoundingCapsule capsule;
if (store == null || store.getType() != BoundingVolume.BOUNDING_CAPSULE) {
capsule = new BoundingCapsule();
capsule.setLineSegment(new LineSegment());
} else {
capsule = (BoundingCapsule) store;
}
center.mult(scale, capsule.getCenter());
rotate.mult(capsule.getCenter(), capsule.getCenter());
capsule.getCenter().addLocal(translate);
ls.getOrigin().mult(scale, capsule.getLineSegment().getOrigin());
rotate.mult(capsule.getLineSegment().getOrigin(), capsule
.getLineSegment().getOrigin());
capsule.getLineSegment().getOrigin().addLocal(translate);
capsule.getLineSegment().getDirection().set(ls.getDirection());
rotate.mult(capsule.getLineSegment().getDirection(), capsule
.getLineSegment().getDirection());
ls.getDirection().mult(scale, compVec1).multLocal(ls.getExtent());
capsule.getLineSegment().setExtent(compVec1.length());
capsule.setRadius(FastMath.abs(getMaxAxis(scale) * radius));
return capsule;
}
private float getMaxAxis(Vector3f scale) {
float x = FastMath.abs(scale.x);
float y = FastMath.abs(scale.y);
float z = FastMath.abs(scale.z);
if (x >= y) {
if (x >= z)
return x;
return z;
}
if (y >= z)
return y;
return z;
}
@Override
public int whichSide(Plane plane) {
float distance = plane.pseudoDistance(ls.getNegativeEnd(compVec1));
if (distance <= -radius) {
distance = plane.pseudoDistance(ls.getPositiveEnd(compVec1));
if (distance <= -radius) {
return Plane.NEGATIVE_SIDE;
} else if (distance >= radius) {
return Plane.POSITIVE_SIDE;
} else {
return Plane.NO_SIDE;
}
} else if (distance >= radius) {
return Plane.POSITIVE_SIDE;
} else {
return Plane.NO_SIDE;
}
}
public LineSegment getLineSegment() {
return ls;
}
public void setLineSegment(LineSegment lineSegment) {
this.ls = lineSegment;
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
@Override
public float getVolume() {
return (4 * FastMath.ONE_THIRD * FastMath.PI * radius * radius * radius) + (FastMath.PI * radius * radius *
// FIXME: replace with 2 * line segment extents once that is changed over.
(getLineSegment().getOrigin().distance(getLineSegment().getDirection()))
);
}
}