#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 box with arbitrary orientation
#if !NO_SERIALIZATION
[Serializable]
#endif
public class Box3 : IVolume3 {
/// Initializes a new instance of the oriented box
///
/// Transformation matrix that defines the box' rotation and translation
///
/// The extents (half the dimensions) of the box
[System.Diagnostics.DebuggerStepThrough]
public Box3(Matrix transform, Vector3 extents) {
this.Transform = transform;
this.Extents = extents;
}
/// Initializes a new oriented box as copy of an existing box
/// Existing box that will be copied
[System.Diagnostics.DebuggerStepThrough]
public Box3(Box3 other)
: this(other.Transform, other.Extents) { }
/// 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.
///
///
/// This method was actually thought up by myself when all googling did not
/// reveal a clever way to avoid the expensive matrix-vector multiplications.
/// Feel free to use it in any way you see fit.
///
///
public AxisAlignedBox3 BoundingBox {
get {
// We just calculate one half of the oriented box and obtain the other
// by mirroring these points
Vector3[] corners = new Vector3[] {
new Vector3( this.Extents.X, this.Extents.Y, this.Extents.Z),
new Vector3(-this.Extents.X, this.Extents.Y, this.Extents.Z),
new Vector3( this.Extents.X, -this.Extents.Y, this.Extents.Z),
new Vector3( this.Extents.X, this.Extents.Y, -this.Extents.Z)
};
// Transform all points and calculate the maximum distance on each
// axis in positive direction by using its absolute value
Vector3 aabbExtents = new Vector3();
foreach(Vector3 corner in corners)
aabbExtents = Vector3.Max(
aabbExtents, VectorHelper.Abs(Vector3.TransformNormal(corner, this.Transform))
);
// Now we can just mirror the other direction
return new AxisAlignedBox3(
this.Center - aabbExtents,
this.Center + aabbExtents
);
}
}
/// 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 { return new Sphere3(this.Center, this.Extents.Length()); }
}
/// Amount of mass that the volume contains
public float Mass {
get { return Dimensions.X * Dimensions.Y * Dimensions.Z; }
}
/// The volume's total surface area
public float SurfaceArea {
get {
// x 4 because the extents are half as wide as the dimensions
// if you make your screen half as wide and half as high you've got one
// quarter of what it had been before, so we multiply by 4
// x 2 because we've got two faces
return 4.0f * 2.0f * (
(Extents.X * Extents.Y) +
(Extents.X * Extents.Z) +
(Extents.Y * Extents.Z)
);
}
}
/// Center of the volume's mass
public Vector3 CenterOfMass {
get { return Center; }
}
/// The inetria tensor matrix of the volume
public Matrix InertiaTensor {
get {
float width = this.Extents.X;
float height = this.Extents.Y;
float depth = this.Extents.Z;
return new Matrix(
(height * height + depth * depth) / 3.0f, 0.0f, 0.0f, 0.0f,
0.0f, (width * width + depth * depth) / 3.0f, 0.0f, 0.0f,
0.0f, 0.0f, (width * width + height * height) / 3.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.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) {
// We transform the point to the coordinate frame of the oriented box
Vector3 difference = location - this.Center;
Vector3 offset = new Vector3(
Vector3.Dot(difference, this.Transform.Right),
Vector3.Dot(difference, this.Transform.Up),
Vector3.Dot(difference, this.Transform.Forward)
);
Vector3 local = new Vector3(
Math.Min(Math.Max(offset.X, -this.Extents.X), this.Extents.X),
Math.Min(Math.Max(offset.Y, -this.Extents.Y), this.Extents.Y),
Math.Min(Math.Max(offset.Z, -this.Extents.Z), this.Extents.Z)
);
return Vector3.Transform(local, this.Transform) + this.Center;
}
/// Determines if the volume clips the circle
/// Circle that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Sphere3 sphere) {
return Collisions.ObbSphereCollider.CheckContact(
this.Transform, this.Extents, sphere.Center, sphere.Radius
);
}
/// 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) {
return Collisions.AabbObbCollider.CheckContact(
box.Extents, this.Transform, this.Extents
);
}
/// Determines if the volume clips the box
/// Box that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Box3 box) {
return Collisions.ObbObbCollider.CheckContact(
this.Transform, this.Extents, box.Transform, box.Extents
);
}
/// 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.ObbPointGenerator.GenerateRandomPointOnSurface(
randomNumberGenerator, this.Transform, this.Extents
) + 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.ObbPointGenerator.GenerateRandomPointWithin(
randomNumberGenerator, this.Transform, this.Extents
) + this.Center;
}
/// The dimensions of this box
public Vector3 Dimensions {
get { return Extents * 2.0f; }
}
/// 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 ==(Box3 first, Box3 second) {
return
(first.Transform == second.Transform) &&
(first.Center == second.Center) &&
(first.Extents == second.Extents);
}
/// 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 !=(Box3 first, Box3 second) {
return
(first.Transform != second.Transform) ||
(first.Center != second.Center) ||
(first.Extents != second.Extents);
}
/// 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 Box3)
return this == (obj as Box3);
else
return false;
}
/// Builds a hashing code for the instance
/// The instance's hashing code
public override int GetHashCode() {
return Transform.GetHashCode() ^ Center.GetHashCode() ^ Extents.GetHashCode();
}
/// Converts the oriented box to a readable string representation
/// The axis oriented as a string
public override string ToString() {
return
"{ " +
Transform.ToString() + " C:" + Center.ToString() + " E:" + Extents.ToString() +
" }";
}
/// Location of the box' center
public Vector3 Center {
get { return this.Transform.Translation; }
set { this.Transform.Translation = value; }
}
/// Orientation of the box in 3D space
public Matrix Transform;
/// Box dimensions in the box' local coordinate system
///
/// These are the extents, not the dimensions. The dimensions are the
/// total length of the box on each of its three local coordinate axes while
/// the extents refer to the distance of each side from the center of the
/// box, much like the radius and the diameter of a sphere.
///
public Vector3 Extents;
}
} // namespace Nuclex.Geometry.Volumes