#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