overte/libraries/shared/src/CapsuleShape.cpp
2014-09-10 17:01:51 -07:00

220 lines
7.6 KiB
C++

//
// CapsuleShape.cpp
// libraries/shared/src
//
// Created by Andrew Meadows on 02/20/2014.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <iostream>
#include <glm/gtx/vector_angle.hpp>
#include "CapsuleShape.h"
#include "GeometryUtil.h"
#include "SharedUtil.h"
CapsuleShape::CapsuleShape() : Shape(CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {}
CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(CAPSULE_SHAPE),
_radius(radius), _halfHeight(halfHeight) {
updateBoundingRadius();
}
CapsuleShape::CapsuleShape(float radius, float halfHeight, const glm::vec3& position, const glm::quat& rotation) :
Shape(CAPSULE_SHAPE, position, rotation), _radius(radius), _halfHeight(halfHeight) {
updateBoundingRadius();
}
CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint) :
Shape(CAPSULE_SHAPE), _radius(radius), _halfHeight(0.0f) {
setEndPoints(startPoint, endPoint);
}
/// \param[out] startPoint is the center of start cap
void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
startPoint = _translation - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
}
/// \param[out] endPoint is the center of the end cap
void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
endPoint = _translation + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
}
void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
// default axis of a capsule is along the yAxis
axis = _rotation * DEFAULT_CAPSULE_AXIS;
}
void CapsuleShape::setRadius(float radius) {
_radius = radius;
updateBoundingRadius();
}
void CapsuleShape::setHalfHeight(float halfHeight) {
_halfHeight = halfHeight;
updateBoundingRadius();
}
void CapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
_radius = radius;
_halfHeight = halfHeight;
updateBoundingRadius();
}
void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) {
glm::vec3 axis = endPoint - startPoint;
_translation = 0.5f * (endPoint + startPoint);
float height = glm::length(axis);
if (height > EPSILON) {
_halfHeight = 0.5f * height;
axis /= height;
_rotation = computeNewRotation(axis);
}
updateBoundingRadius();
}
// helper
bool findRayIntersectionWithCap(const glm::vec3& sphereCenter, float sphereRadius,
const glm::vec3& capsuleCenter, RayIntersectionInfo& intersection) {
float r2 = sphereRadius * sphereRadius;
// compute closest approach (CA)
float a = glm::dot(sphereCenter - intersection._rayStart, intersection._rayDirection); // a = distance from ray-start to CA
float b2 = glm::distance2(sphereCenter, intersection._rayStart + a * intersection._rayDirection); // b2 = squared distance from sphere-center to CA
if (b2 > r2) {
// ray does not hit sphere
return false;
}
float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along intersection._rayDirection
float d2 = glm::distance2(intersection._rayStart, sphereCenter); // d2 = squared distance from sphere-center to ray-start
float distance = FLT_MAX;
if (a < 0.0f) {
// ray points away from sphere-center
if (d2 > r2) {
// ray starts outside sphere
return false;
}
// ray starts inside sphere
distance = c + a;
} else if (d2 > r2) {
// ray starts outside sphere
distance = a - c;
} else {
// ray starts inside sphere
distance = a + c;
}
if (distance > 0.0f && distance < intersection._rayLength && distance < intersection._hitDistance) {
glm::vec3 sphereCenterToHitPoint = intersection._rayStart + distance * intersection._rayDirection - sphereCenter;
if (glm::dot(sphereCenterToHitPoint, sphereCenter - capsuleCenter) >= 0.0f) {
intersection._hitDistance = distance;
intersection._hitNormal = glm::normalize(sphereCenterToHitPoint);
return true;
}
}
return false;
}
bool CapsuleShape::findRayIntersectionWithCaps(const glm::vec3& capsuleCenter, RayIntersectionInfo& intersection) const {
glm::vec3 capCenter;
getStartPoint(capCenter);
bool hit = findRayIntersectionWithCap(capCenter, _radius, capsuleCenter, intersection);
getEndPoint(capCenter);
hit = findRayIntersectionWithCap(capCenter, _radius, capsuleCenter, intersection) || hit;
if (hit) {
intersection._hitShape = const_cast<CapsuleShape*>(this);
}
return hit;
}
bool CapsuleShape::findRayIntersection(RayIntersectionInfo& intersection) const {
// ray is U, capsule is V
glm::vec3 axisV;
computeNormalizedAxis(axisV);
glm::vec3 centerV = getTranslation();
// first handle parallel case
float uDotV = glm::dot(axisV, intersection._rayDirection);
glm::vec3 UV = intersection._rayStart - centerV;
if (glm::abs(1.0f - glm::abs(uDotV)) < EPSILON) {
// line and cylinder are parallel
float distanceV = glm::dot(UV, intersection._rayDirection);
if (glm::length2(UV - distanceV * intersection._rayDirection) <= _radius * _radius) {
// ray is inside cylinder's radius and might intersect caps
return findRayIntersectionWithCaps(centerV, intersection);
}
return false;
}
// Given a line with point 'U' and normalized direction 'u' and
// a cylinder with axial point 'V', radius 'r', and normalized direction 'v'
// the intersection of the two is on the line at distance 't' from 'U'.
//
// Determining the values of t reduces to solving a quadratic equation: At^2 + Bt + C = 0
//
// where:
//
// UV = U-V
// w = u-(u.v)v
// Q = UV-(UV.v)v
//
// A = w^2
// B = 2(w.Q)
// C = Q^2 - r^2
glm::vec3 w = intersection._rayDirection - uDotV * axisV;
glm::vec3 Q = UV - glm::dot(UV, axisV) * axisV;
// we save a few multiplies by storing 2*A rather than just A
float A2 = 2.0f * glm::dot(w, w);
float B = 2.0f * glm::dot(w, Q);
// since C is only ever used once (in the determinant) we compute it inline
float determinant = B * B - 2.0f * A2 * (glm::dot(Q, Q) - _radius * _radius);
if (determinant < 0.0f) {
return false;
}
float hitLow = (-B - sqrtf(determinant)) / A2;
float hitHigh = -(hitLow + 2.0f * B / A2);
if (hitLow > hitHigh) {
// re-arrange so hitLow is always the smaller value
float temp = hitHigh;
hitHigh = hitLow;
hitLow = temp;
}
if (hitLow < 0.0f) {
if (hitHigh < 0.0f) {
// capsule is completely behind rayStart
return false;
}
hitLow = hitHigh;
}
glm::vec3 p = intersection._rayStart + hitLow * intersection._rayDirection;
float d = glm::dot(p - centerV, axisV);
if (glm::abs(d) <= getHalfHeight()) {
// we definitely hit the cylinder wall
intersection._hitDistance = hitLow;
intersection._hitNormal = glm::normalize(p - centerV - d * axisV);
intersection._hitShape = const_cast<CapsuleShape*>(this);
return true;
}
// ray still might hit the caps
return findRayIntersectionWithCaps(centerV, intersection);
}
// static
glm::quat CapsuleShape::computeNewRotation(const glm::vec3& newAxis) {
float angle = glm::angle(newAxis, DEFAULT_CAPSULE_AXIS);
if (angle > EPSILON) {
glm::vec3 rotationAxis = glm::normalize(glm::cross(DEFAULT_CAPSULE_AXIS, newAxis));
return glm::angleAxis(angle, rotationAxis);
}
return glm::quat();
}