File: src\classes\Shapes\CylinderShape.js
/**
* @class CylinderShape
* @param radius {Number} radius of the cylinder
* @param half_height {Number} half height of the cylinder
* @constructor
*/
Goblin.CylinderShape = function( radius, half_height ) {
/**
* radius of the cylinder
*
* @property radius
* @type {Number}
*/
this.radius = radius;
/**
* half height of the cylinder
*
* @property half_height
* @type {Number}
*/
this.half_height = half_height;
this.aabb = new Goblin.AABB();
this.calculateLocalAABB( this.aabb );
};
/**
* Calculates this shape's local AABB and stores it in the passed AABB object
*
* @method calculateLocalAABB
* @param aabb {AABB}
*/
Goblin.CylinderShape.prototype.calculateLocalAABB = function( aabb ) {
aabb.min.x = aabb.min.z = -this.radius;
aabb.min.y = -this.half_height;
aabb.max.x = aabb.max.z = this.radius;
aabb.max.y = this.half_height;
};
Goblin.CylinderShape.prototype.getInertiaTensor = function( mass ) {
var element = 0.0833 * mass * ( 3 * this.radius * this.radius + ( this.half_height + this.half_height ) * ( this.half_height + this.half_height ) );
return new Goblin.Matrix3(
element, 0, 0,
0, 0.5 * mass * this.radius * this.radius, 0,
0, 0, element
);
};
/**
* Given `direction`, find the point in this body which is the most extreme in that direction.
* This support point is calculated in world coordinates and stored in the second parameter `support_point`
*
* @method findSupportPoint
* @param direction {vec3} direction to use in finding the support point
* @param support_point {vec3} vec3 variable which will contain the supporting point after calling this method
*/
Goblin.CylinderShape.prototype.findSupportPoint = function( direction, support_point ) {
// Calculate the support point in the local frame
if ( direction.y < 0 ) {
support_point.y = -this.half_height;
} else {
support_point.y = this.half_height;
}
if ( direction.x === 0 && direction.z === 0 ) {
support_point.x = support_point.z = 0;
} else {
var sigma = Math.sqrt( direction.x * direction.x + direction.z * direction.z ),
r_s = this.radius / sigma;
support_point.x = r_s * direction.x;
support_point.z = r_s * direction.z;
}
};
/**
* Checks if a ray segment intersects with the shape
*
* @method rayIntersect
* @property start {vec3} start point of the segment
* @property end {vec3{ end point of the segment
* @return {RayIntersection|null} if the segment intersects, a RayIntersection is returned, else `null`
*/
Goblin.CylinderShape.prototype.rayIntersect = (function(){
var p = new Goblin.Vector3(),
q = new Goblin.Vector3();
return function ( start, end ) {
p.y = this.half_height;
q.y = -this.half_height;
var d = new Goblin.Vector3();
d.subtractVectors( q, p );
var m = new Goblin.Vector3();
m.subtractVectors( start, p );
var n = new Goblin.Vector3();
n.subtractVectors( end, start );
var md = m.dot( d ),
nd = n.dot( d ),
dd = d.dot( d );
// Test if segment fully outside either endcap of cylinder
if ( md < 0 && md + nd < 0 ) {
return null; // Segment outside 'p' side of cylinder
}
if ( md > dd && md + nd > dd ) {
return null; // Segment outside 'q' side of cylinder
}
var nn = n.dot( n ),
mn = m.dot( n ),
a = dd * nn - nd * nd,
k = m.dot( m ) - this.radius * this.radius,
c = dd * k - md * md,
t, t0;
if ( Math.abs( a ) < Goblin.EPSILON ) {
// Segment runs parallel to cylinder axis
if ( c > 0 ) {
return null; // 'a' and thus the segment lie outside cylinder
}
// Now known that segment intersects cylinder; figure out how it intersects
if ( md < 0 ) {
t = -mn / nn; // Intersect segment against 'p' endcap
} else if ( md > dd ) {
t = (nd - mn) / nn; // Intersect segment against 'q' endcap
} else {
t = 0; // 'a' lies inside cylinder
}
} else {
var b = dd * mn - nd * md,
discr = b * b - a * c;
if ( discr < 0 ) {
return null; // No real roots; no intersection
}
t0 = t = ( -b - Math.sqrt( discr ) ) / a;
if ( md + t * nd < 0 ) {
// Intersection outside cylinder on 'p' side
if ( nd <= 0 ) {
return null; // Segment pointing away from endcap
}
t = -md / nd;
// Keep intersection if Dot(S(t) - p, S(t) - p) <= r^2
if ( k + t * ( 2 * mn + t * nn ) <= 0 ) {
t0 = t;
} else {
return null;
}
} else if ( md + t * nd > dd ) {
// Intersection outside cylinder on 'q' side
if ( nd >= 0 ) {
return null; // Segment pointing away from endcap
}
t = ( dd - md ) / nd;
// Keep intersection if Dot(S(t) - q, S(t) - q) <= r^2
if ( k + dd - 2 * md + t * ( 2 * ( mn - nd ) + t * nn ) <= 0 ) {
t0 = t;
} else {
return null;
}
}
t = t0;
// Intersection if segment intersects cylinder between the end-caps
if ( t < 0 || t > 1 ) {
return null;
}
}
// Segment intersects cylinder between the endcaps; t is correct
var intersection = Goblin.ObjectPool.getObject( 'RayIntersection' );
intersection.object = this;
intersection.t = t * n.length();
intersection.point.scaleVector( n, t );
intersection.point.add( start );
if ( Math.abs( intersection.point.y - this.half_height ) <= Goblin.EPSILON ) {
intersection.normal.x = intersection.normal.z = 0;
intersection.normal.y = intersection.point.y < 0 ? -1 : 1;
} else {
intersection.normal.y = 0;
intersection.normal.x = intersection.point.x;
intersection.normal.z = intersection.point.z;
intersection.normal.scale( 1 / this.radius );
}
return intersection;
};
})( );