#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using Microsoft.Xna.Framework;
namespace Nuclex.Geometry.Volumes {
/// Three-dimensional cylinder with arbitrary orientation
///
/// With an identity matrix, the cylinder extents along the Y axis in both directions,
/// the translational part of the matrix will be equivalent to the cylinder's center.
///
#if !NO_SERIALIZATION
[Serializable]
#endif
public class Cylinder3 : IVolume3 {
/// Initializes a new instance of the cylinder
/// Orientation and position of cylinder
/// The radius of the cylinder
/// The length of the cylinder
[System.Diagnostics.DebuggerStepThrough]
public Cylinder3(Matrix transform, float radius, float length) {
this.Transform = transform;
this.Radius = radius;
this.Height = length;
}
/// Initializes a new cylinder as copy of an existing cylinder
/// Existing cylinder that will be copied
[System.Diagnostics.DebuggerStepThrough]
public Cylinder3(Cylinder3 other)
: this(other.Transform, other.Radius, other.Height) { }
/// Accepts a visitor to access the concrete volume implementation
/// Visitor to be accepted
public void Accept(VolumeVisitor visitor) {
visitor.Visit(this);
}
/// Smallest box that encloses the volume in its entirety
///
/// This always produces an optimal box which means a tight-fitting box is generated
/// that will touch the volume on each of its six sides. As a side effect, it is very
/// likely that this box needs to be recalculated whenever the volume changes its
/// orientation.
///
public AxisAlignedBox3 BoundingBox {
get { throw new NotImplementedException("Not implemented yet"); }
}
/// Smallest sphere that encloses the volume in its entirety
///
/// Bounding spheres have the advantage to not change even when the volume is
/// rotated. That makes them ideal for dynamic objects that are not keeping their
/// original orientation.
///
public Sphere3 BoundingSphere {
get {
// We can obtain the minimum diameter by taking the distance from the cylinder's
// center of mass to a random point on the perimeter of one of its caps
return new Sphere3(
this.Center, new Vector2(this.Height / 2.0f, this.Radius).Length()
);
}
}
/// Amount of mass that the volume contains
public float Mass {
get {
// Formula for cylinder volume: pi * (r ^ 2) * h
return MathHelper.Pi * (this.Radius * this.Radius) * this.Height;
}
}
/// The volume's total surface area
public float SurfaceArea {
get {
// Formula for cylinder surface area: 2 * pi * (r ^ 2) + 2 * pi * r * h
return
(MathHelper.TwoPi * this.Radius * this.Height) + // cylinder side
(MathHelper.TwoPi * this.Radius * this.Radius); // upper and lower cap
}
}
/// Center of the volume's mass
public Vector3 CenterOfMass {
get { return this.Center; }
}
/// The inertia tensor matrix of the volume
public Matrix InertiaTensor {
get {
// TODO: Check that this is correct.
// http://eta.physics.uoguelph.ca/tutorials/torque/Q.torque.inertia.html
// http://www.gamedev.net/community/forums/topic.asp?topic_id=57001
// http://www.physicsforums.com/showthread.php?t=175182
// (Careful, there are different methods depending on whether your cylinder
// is centered on its cap or on its center of mass)
float length = (this.Radius * this.Radius) / 4.0f + (this.Height * this.Height) / 3.0f;
float width = (this.Radius * this.Radius) / 2.0f;
return new Matrix(
width, 0.0f, 0.0f, 0.0f,
0.0f, length, 0.0f, 0.0f,
0.0f, 0.0f, width, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f
);
}
}
/// Locates the nearest point in the volume to some arbitrary location
/// Location to which the closest point is determined
/// The closest point in the volume to the specified location
public Vector3 ClosestPointTo(Vector3 location) {
throw new NotImplementedException("Not implemented yet");
}
/// Determines if the volume clips the circle
/// Circle that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Sphere3 sphere) {
throw new NotImplementedException("Not implemented yet");
}
/// Determines if the volume clips the axis aligned box
/// Box that will be checked for intersection
/// True if the objects overlap
public bool Intersects(AxisAlignedBox3 box) {
throw new NotImplementedException("Not implemented yet");
}
/// Determines if the volume clips the box
/// Box that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Box3 box) {
throw new NotImplementedException("Not implemented yet");
}
/// Returns a random point on the volume's surface
/// Random number generator that will be used
/// A random point on the volume's surface
public Vector3 RandomPointOnSurface(IRandom randomNumberGenerator) {
return PointGenerators.CylinderPointGenerator.GenerateRandomPointOnSurface(
randomNumberGenerator, this.Transform, this.Radius, this.Height
) + this.Center;
}
/// Returns a random point within the volume
/// Random number generator that will be used
/// A random point within the volume
public Vector3 RandomPointWithin(IRandom randomNumberGenerator) {
return PointGenerators.CylinderPointGenerator.GenerateRandomPointWithin(
randomNumberGenerator, this.Transform, this.Radius, this.Height
) + this.Center;
}
/// Determines if two oriented boxes are equal
/// First oriented box to be compared
/// Second oriented box to be compared
/// True if both axis oriented are equal
[System.Diagnostics.DebuggerStepThrough]
public static bool operator ==(Cylinder3 first, Cylinder3 second) {
return
(first.Transform == second.Transform) &&
(first.Radius == second.Radius) &&
(first.Height == second.Height);
}
/// Determines if two oriented boxes are unequal
/// First oriented box to be compared
/// Second oriented box to be compared
/// True if both oriented boxes are unequal
[System.Diagnostics.DebuggerStepThrough]
public static bool operator !=(Cylinder3 first, Cylinder3 second) {
return
(first.Transform != second.Transform) ||
(first.Radius != second.Radius) ||
(first.Height != second.Height);
}
/// Determines if an object is identical to the oriented box
/// Object to compare to
/// True if the object is identical to the oriented box
public override bool Equals(object obj) {
if(obj is Cylinder3)
return this == (obj as Cylinder3);
else
return false;
}
/// Builds a hashing code for the instance
/// The instance's hashing code
public override int GetHashCode() {
return
Transform.GetHashCode() ^ Center.GetHashCode() ^
Radius.GetHashCode() ^ Height.GetHashCode();
}
/// Converts the cylinder to a readable string representation
/// The cylinder as a string
public override string ToString() {
return
"{ " +
Transform.ToString() + " R:" + Radius.ToString() + " H:" + Height.ToString() +
" }";
}
/// Determines the closest point in the cylinder to another point
/// The cylinder's radius
/// The cylinder's length
/// Location to which to determine the closest point
/// The closest point to the given location
///
///
/// This method works entirely in the cylinder's coordinate frame. To use
/// this function on a cylinder that is not axis-aligned, translate the
/// reference location into the cylinder's coordinate frame before and
/// apply the cylinder's transformation matrix to the result.
///
///
/// This design decision allows algorithms which are not interested in
/// rotating the resulting closest point back into the global coodinate frame
/// to save some time (think of intersection tests as an example). For a
/// convenient closest point determination see the appropriate instance
/// method of this class.
///
///
public static Vector3 GetClosestPoint(
float cylinderRadius, float cylinderLength, Vector3 location
) {
float cylinderExtents = cylinderLength / 2.0f;
float cylinderRadius2 = cylinderRadius * cylinderRadius;
// We'll take the point on the cylinder's Z axis closest to the sphere...
Vector3 axisPoint = new Vector3(0.0f, 0.0f, location.Z);
// ...walk towards the sphere as far as the cylinder's radius permits...
Vector3 radiusPoint = location - axisPoint;
if(radiusPoint.LengthSquared() > cylinderRadius2)
radiusPoint = axisPoint + Vector3.Normalize(radiusPoint) * cylinderRadius;
else
radiusPoint = axisPoint + radiusPoint;
// ...and finally clamp this to the cylinder length to obtain the closest point
return new Vector3(
radiusPoint.X,
radiusPoint.Y,
Math.Min(Math.Max(radiusPoint.Z, -cylinderExtents), cylinderExtents)
);
}
/// Location of the cylinder's center
public Vector3 Center {
get { return this.Transform.Translation; }
set { this.Transform.Translation = value; }
}
/// Orientation of the cylinder in 3D space
public Matrix Transform;
/// Radius of the cylinder
public float Radius;
/// Length of the cylinder
public float Height;
}
} // namespace Nuclex.Geometry.Volumes