|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace System.Windows.Media.Media3D
{
/// <summary>
/// The primitive which represents a 3D rectangle, i.e. a box. Rect3D is stored as
/// location (Point3D) and rectangle's size (Size3D). As a result, Rect3D cannot have
/// negative sizes.
/// </summary>
public partial struct Rect3D: IFormattable
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// Constructor which sets the initial values to the values of the parameters.
/// </summary>
/// <param name="location">Location of the new rectangle.</param>
/// <param name="size">Size of the new rectangle.</param>
public Rect3D(Point3D location, Size3D size)
{
if (size.IsEmpty)
{
this = s_empty;
}
else
{
_x = location._x;
_y = location._y;
_z = location._z;
_sizeX = size._x;
_sizeY = size._y;
_sizeZ = size._z;
}
Debug.Assert(size.IsEmpty == IsEmpty);
}
/// <summary>
/// Constructor which sets the initial values to the values of the parameters.
/// SizeX, sizeY, sizeZ must be non-negative.
/// </summary>
/// <param name="x">Value of the X location coordinate of the new rectangle.</param>
/// <param name="y">Value of the X location coordinate of the new rectangle.</param>
/// <param name="z">Value of the X location coordinate of the new rectangle.</param>
/// <param name="sizeX">Size of the new rectangle in X dimension.</param>
/// <param name="sizeY">Size of the new rectangle in Y dimension.</param>
/// <param name="sizeZ">Size of the new rectangle in Z dimension.</param>
public Rect3D(double x, double y, double z, double sizeX, double sizeY, double sizeZ)
{
if (sizeX < 0 || sizeY < 0 || sizeZ < 0)
{
throw new System.ArgumentException(SR.Size3D_DimensionCannotBeNegative);
}
_x = x;
_y = y;
_z = z;
_sizeX = sizeX;
_sizeY = sizeY;
_sizeZ = sizeZ;
}
/// <summary>
/// Constructor which sets the initial values to bound the two points provided.
/// </summary>
/// <param name="point1">First point.</param>
/// <param name="point2">Second point.</param>
internal Rect3D(Point3D point1, Point3D point2)
{
_x = Math.Min(point1._x, point2._x);
_y = Math.Min(point1._y, point2._y);
_z = Math.Min(point1._z, point2._z);
_sizeX = Math.Max(point1._x, point2._x) - _x;
_sizeY = Math.Max(point1._y, point2._y) - _y;
_sizeZ = Math.Max(point1._z, point2._z) - _z;
}
/// <summary>
/// Constructor which sets the initial values to bound the point provided and the point
/// which results from point + vector.
/// </summary>
/// <param name="point">Location of the rectangle.</param>
/// <param name="vector">Vector extending the rectangle from the location.</param>
internal Rect3D(Point3D point, Vector3D vector): this(point, point+vector)
{
}
#endregion Constructors
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// Empty - a static property which provides an Empty rectangle. X, Y, and Z are
/// positive-infinity and sizes are negative infinity. This is the only situation
/// where size can be negative.
/// </summary>
public static Rect3D Empty
{
get
{
return s_empty;
}
}
/// <summary>
/// IsEmpty - this returns true if this rect is the Empty rectangle.
/// Note: If size is 0 this Rectangle still contains a 0 or 1 dimensional set
/// of points, so this method should not be used to check for 0 area.
/// </summary>
public bool IsEmpty
{
get
{
return _sizeX < 0;
}
}
/// <summary>
/// The point representing the origin of the rectangle.
/// </summary>
public Point3D Location
{
get
{
return new Point3D(_x, _y, _z);
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
_x = value._x;
_y = value._y;
_z = value._z;
}
}
/// <summary>
/// The size representing the area of the rectangle.
/// </summary>
public Size3D Size
{
get
{
if( IsEmpty )
return Size3D.Empty;
else
return new Size3D(_sizeX, _sizeY, _sizeZ);
}
set
{
if (value.IsEmpty)
{
this = s_empty;
}
else
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
_sizeX = value._x;
_sizeY = value._y;
_sizeZ = value._z;
}
}
}
/// <summary>
/// Size of the rectangle in the X dimension.
/// </summary>
public double SizeX
{
get
{
return _sizeX;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
if (value < 0)
{
throw new System.ArgumentException(SR.Size3D_DimensionCannotBeNegative);
}
_sizeX = value;
}
}
/// <summary>
/// Size of the rectangle in the Y dimension.
/// </summary>
public double SizeY
{
get
{
return _sizeY;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
if (value < 0)
{
throw new System.ArgumentException(SR.Size3D_DimensionCannotBeNegative);
}
_sizeY = value;
}
}
/// <summary>
/// Size of the rectangle in the Z dimension.
/// </summary>
public double SizeZ
{
get
{
return _sizeZ;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
if (value < 0)
{
throw new System.ArgumentException(SR.Size3D_DimensionCannotBeNegative);
}
_sizeZ = value;
}
}
/// <summary>
/// Value of the X coordinate of the rectangle.
/// </summary>
public double X
{
get
{
return _x;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
_x = value;
}
}
/// <summary>
/// Value of the Y coordinate of the rectangle.
/// </summary>
public double Y
{
get
{
return _y;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
_y = value;
}
}
/// <summary>
/// Value of the Z coordinate of the rectangle.
/// </summary>
public double Z
{
get
{
return _z;
}
set
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotModifyEmptyRect);
}
_z = value;
}
}
#endregion Public Properties
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
/// <summary>
/// Returns true if the point is within the rectangle, inclusive of the edges.
/// Returns false otherwise.
/// </summary>
/// <param name="point">The point which is being tested.</param>
/// <returns>True if the point is within the rectangle. False otherwise</returns>
public bool Contains(Point3D point)
{
return Contains(point._x, point._y, point._z);
}
/// <summary>
/// Contains - Returns true if the Point represented by x,y,z is within the rectangle
/// inclusive of the edges. Returns false otherwise.
/// </summary>
/// <param name="x">X coordinate of the point which is being tested.</param>
/// <param name="y">Y coordinate of the point which is being tested.</param>
/// <param name="z">Y coordinate of the point which is being tested.</param>
/// <returns> True if the Point represented by x,y is within the rectangle.
/// False otherwise. </returns>
public bool Contains(double x, double y, double z)
{
if (IsEmpty)
{
return false;
}
return ContainsInternal(x, y, z);
}
/// <summary>
/// Returns true if the rectangle is non-Empty and is entirely contained within the
/// rectangle, inclusive of the edges. Returns false otherwise.
/// </summary>
/// <param name="rect">Rectangle being tested.</param>
/// <returns>Returns true if the rectangle is non-Empty and is entirely contained within the
/// rectangle, inclusive of the edges. Returns false otherwise.</returns>
public bool Contains(Rect3D rect)
{
if (IsEmpty || rect.IsEmpty)
{
return false;
}
return (_x <= rect._x &&
_y <= rect._y &&
_z <= rect._z &&
_x+_sizeX >= rect._x+rect._sizeX &&
_y+_sizeY >= rect._y+rect._sizeY &&
_z+_sizeZ >= rect._z+rect._sizeZ);
}
/// <summary>
/// Returns true if the rectangle intersects with this rectangle.
/// Returns false otherwise. Note that if one edge is coincident, this is considered
/// an intersection.
/// </summary>
/// <param name="rect">Rectangle being tested.</param>
/// <returns>True if the rectangle intersects with this rectangle.
/// False otherwise.</returns>
public bool IntersectsWith(Rect3D rect)
{
if (IsEmpty || rect.IsEmpty)
{
return false;
}
return (rect._x <= (_x + _sizeX)) &&
((rect._x + rect._sizeX) >= _x) &&
(rect._y <= (_y + _sizeY)) &&
((rect._y + rect._sizeY) >= _y) &&
(rect._z <= (_z + _sizeZ)) &&
((rect._z + rect._sizeZ) >= _z);
}
/// <summary>
/// Intersect - Update this rectangle to be the intersection of this and rect
/// If either this or rect are Empty, the result is Empty as well.
/// </summary>
/// <param name="rect"> The rect to intersect with this </param>
public void Intersect(Rect3D rect)
{
if (IsEmpty || rect.IsEmpty || !this.IntersectsWith(rect))
{
this = Empty;
}
else
{
double x = Math.Max(_x, rect._x);
double y = Math.Max(_y, rect._y);
double z = Math.Max(_z, rect._z);
_sizeX = Math.Min(_x + _sizeX, rect._x + rect._sizeX) - x;
_sizeY = Math.Min(_y + _sizeY, rect._y + rect._sizeY) - y;
_sizeZ = Math.Min(_z + _sizeZ, rect._z + rect._sizeZ) - z;
_x = x;
_y = y;
_z = z;
}
}
/// <summary>
/// Return the result of the intersection of rect1 and rect2.
/// If either this or rect are Empty, the result is Empty as well.
/// </summary>
/// <param name="rect1">First rectangle.</param>
/// <param name="rect2">Second rectangle.</param>
/// <returns>The result of the intersection of rect1 and rect2.</returns>
public static Rect3D Intersect(Rect3D rect1, Rect3D rect2)
{
rect1.Intersect(rect2);
return rect1;
}
/// <summary>
/// Update this rectangle to be the union of this and rect.
/// </summary>
/// <param name="rect">Rectangle.</param>
public void Union(Rect3D rect)
{
if (IsEmpty)
{
this = rect;
}
else if (!rect.IsEmpty)
{
double x = Math.Min(_x, rect._x);
double y = Math.Min(_y, rect._y);
double z = Math.Min(_z, rect._z);
_sizeX = Math.Max(_x + _sizeX, rect._x + rect._sizeX) - x;
_sizeY = Math.Max(_y + _sizeY, rect._y + rect._sizeY) - y;
_sizeZ = Math.Max(_z + _sizeZ, rect._z + rect._sizeZ) - z;
_x = x;
_y = y;
_z = z;
}
}
/// <summary>
/// Return the result of the union of rect1 and rect2.
/// </summary>
/// <param name="rect1">First rectangle.</param>
/// <param name="rect2">Second rectangle.</param>
/// <returns>The result of the union of the two rectangles.</returns>
public static Rect3D Union(Rect3D rect1, Rect3D rect2)
{
rect1.Union(rect2);
return rect1;
}
/// <summary>
/// Update this rectangle to be the union of this and point.
/// </summary>
/// <param name="point">Point.</param>
public void Union(Point3D point)
{
Union(new Rect3D(point, point));
}
/// <summary>
/// Return the result of the union of rect and point.
/// </summary>
/// <param name="rect">Rectangle.</param>
/// <param name="point">Point.</param>
/// <returns>The result of the union of rect and point.</returns>
public static Rect3D Union(Rect3D rect, Point3D point)
{
rect.Union(new Rect3D(point, point));
return rect;
}
/// <summary>
/// Translate the Location by the offset provided.
/// If this is Empty, this method is illegal.
/// </summary>
/// <param name="offsetVector"></param>
public void Offset(Vector3D offsetVector)
{
Offset(offsetVector._x, offsetVector._y, offsetVector._z);
}
/// <summary>
/// Offset - translate the Location by the offset provided
/// If this is Empty, this method is illegal.
/// </summary>
/// <param name="offsetX"></param>
/// <param name="offsetY"></param>
/// <param name="offsetZ"></param>
public void Offset(double offsetX, double offsetY, double offsetZ)
{
if (IsEmpty)
{
throw new System.InvalidOperationException(SR.Rect3D_CannotCallMethod);
}
_x += offsetX;
_y += offsetY;
_z += offsetZ;
}
/// <summary>
/// Offset - return the result of offsetting rect by the offset provided
/// If this is Empty, this method is illegal.
/// </summary>
/// <param name="rect"></param>
/// <param name="offsetVector"></param>
/// <returns></returns>
public static Rect3D Offset(Rect3D rect, Vector3D offsetVector)
{
rect.Offset(offsetVector._x, offsetVector._y, offsetVector._z);
return rect;
}
/// <summary>
/// Offset - return the result of offsetting rect by the offset provided
/// If this is Empty, this method is illegal.
/// </summary>
/// <param name="rect"></param>
/// <param name="offsetX"></param>
/// <param name="offsetY"></param>
/// <param name="offsetZ"></param>
/// <returns></returns>
public static Rect3D Offset(Rect3D rect, double offsetX, double offsetY, double offsetZ)
{
rect.Offset(offsetX, offsetY, offsetZ);
return rect;
}
#endregion Public Methods
//------------------------------------------------------
//
// Internal Fields
//
//------------------------------------------------------
#region Internal Fields
internal readonly static Rect3D Infinite = CreateInfiniteRect3D();
#endregion Internal Fields
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
/// <summary>
/// ContainsInternal - Performs just the "point inside" logic.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <returns>bool - true if the point is inside the rect</returns>
private bool ContainsInternal(double x, double y, double z)
{
// We include points on the edge as "contained"
return ((x >= _x) && (x <= _x + _sizeX) &&
(y >= _y) && (y <= _y + _sizeY) &&
(z >= _z) && (z <= _z + _sizeZ));
}
private static Rect3D CreateEmptyRect3D()
{
Rect3D empty = new Rect3D();
empty._x = Double.PositiveInfinity;
empty._y = Double.PositiveInfinity;
empty._z = Double.PositiveInfinity;
// Can't use setters because they throw on negative values
empty._sizeX = Double.NegativeInfinity;
empty._sizeY = Double.NegativeInfinity;
empty._sizeZ = Double.NegativeInfinity;
return empty;
}
private static Rect3D CreateInfiniteRect3D()
{
// Robustness with infinities
//
// Once the issue with Rect robustness with infinities is addressed we
// should change the values below to make this rectangle truely extend
// from -Infinite to +Infinity.
//
// Until then we use a Rect from -float.MaxValue to +float.MaxValue.
// Because this rect is used only as a conservative bounding box for
// ScreenSpaceLines3D this span should be sufficient for the following
// reasons:
//
// 1. Our meshes and transforms are reprensented in single precision
// at render time. If it's not in this range it will not be
// rendered.
//
// 2. SSLines3Ds are constructed as simple quads at render time.
// We will hit the guard band on the GPU at a limit far less than
// +/- float.MaxValue.
//
// 3. We do our managed math in double precision so this still
// leaves us ample space to account for transforms, etc.
//
Rect3D infinite = new Rect3D();
infinite._x = -float.MaxValue;
infinite._y = -float.MaxValue;
infinite._z = -float.MaxValue;
infinite._sizeX = float.MaxValue*2.0;
infinite._sizeY = float.MaxValue*2.0;
infinite._sizeZ = float.MaxValue*2.0;
return infinite;
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private readonly static Rect3D s_empty = CreateEmptyRect3D();
#endregion Private Fields
}
}
|