using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Chernobyl.Mathematics.Movement;
using Chernobyl.Mathematics.Vectors;
using Chernobyl.Utility;
using Chernobyl.Values;
namespace Chernobyl.Mathematics.Geometry
{
///
/// An implementation of . This type represents a
/// part of a line that is bounded by two distinct end points and contains
/// every point on the line between its end points. This type is an
/// but can only contain two points (
/// and ) in its . In
/// addition, this type is an whose
/// is the center point between
/// and , whose X axis is parallel
/// to the line segment between and ,
/// and whose X axis scale is the distance from to
/// .
///
public class LineSegment : MatrixTransform, ILineSegment
{
///
/// Initializes a new instance of the class.
///
/// The first end point of this instance. This
/// point is also contained within this .
/// The second end point of this instance. This
/// point is also contained within this .
/// Thrown if either
/// or are null.
public LineSegment(ITransform point1, ITransform point2)
{
point1.ThrowIfNull("point1");
point2.ThrowIfNull("point2");
_points[0] = point1;
_points[0].TransformDirtied += PointTransformDirtied;
_points[1] = point2;
_points[1].TransformDirtied += PointTransformDirtied;
IsUpdateNeeded = true;
}
///
/// The first end point of this instance. This point is also contained
/// within this .
///
public ITransform Point1
{
get { return _points[0]; }
}
///
/// The second end point of this instance. This point is also contained
/// within this .
///
public ITransform Point2
{
get { return _points[1]; }
}
///
/// Returns an enumerator that iterates through the collection.
///
/// A that can be used to iterate
/// through the collection.
public IEnumerator GetEnumerator()
{
return _points.AsEnumerable().GetEnumerator();
}
///
/// Returns an enumerator that iterates through a collection.
///
/// An object that can be used to
/// iterate through the collection.
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
///
/// This method is invoked during the update of the
/// right before the
/// is actually updated. Typically
/// you would override this method if you would like to modify the
/// prior to the final calculation
/// of the .
///
/// The value of the .
/// It is stored in a so that the calculation can be
/// avoided if necessary. Don't call unless
/// required.
protected override void PreUpdate(Values.LazyValue world)
{
base.PreUpdate(world);
// Calculate a vector from one end point to the other.
Vector3 point1ToPoint2 = Point2.Position - Point1.Position;
// Now calculate the position of the local matrix which should be
// the center point between the two end points.
Vector4 newPosition = new Vector4(Point1.Position + (point1ToPoint2 * 0.5f), 1);
//-----------------------------------------------------------------
// Calculate the rotation of the transform.
//-----------------------------------------------------------------
Vector3 xAxis = Vector3.Normalize(Point2.Position - newPosition.Xyz);
// If the xAxis is equal to the up/down vector than we will get a vector
// full of NaN when we attempt to cross them. To prevent that we just
// set the Z axis to its default value in that case.
Vector3 zAxis;
if (xAxis != Vector3.UnitY && xAxis != -Vector3.UnitY)
{
zAxis = Vector3.Normalize(Vector3.Cross(Vector3.UnitY, xAxis));
// If the line is on a 2D plane than we always want the Z axis to
// point in the -Z. This is because other parts of Chernobyl
// assume that the -Z faces away from the viewer. Due to
// precision issues on most computers, the Z axis may not be
// exactly equal to the unit Z so we'll have to check if its
// close.
const float maxDifference = 0.000001f;
if ((Vector3.UnitZ - zAxis).LengthFast < maxDifference)
zAxis = -Vector3.UnitZ;
}
else
zAxis = -Vector3.UnitZ;
Vector3 yAxis = Vector3.Normalize(Vector3.Cross(xAxis, zAxis));
// Scale the X axis to match the width between the points.
xAxis *= point1ToPoint2.Length;
Matrix4 local = new Matrix4(new Vector4(xAxis.X, xAxis.Y, xAxis.Z, 0.0f),
new Vector4(yAxis.X, yAxis.Y, yAxis.Z, 0.0f),
new Vector4(zAxis.X, zAxis.Y, zAxis.Z, 0.0f),
newPosition);
SetTransformation(ref local);
}
///
/// An event handler that is invoked when either or
/// are moved. This method just dirties this
/// so that it has to update when its
/// transformation properties are accessed.
///
/// The instance that generated the event.
/// The instance containing the
/// event data.
void PointTransformDirtied(object sender, EventArgs e)
{
IsUpdateNeeded = true;
}
///
/// The end points of the line.
///
ITransform[] _points = new ITransform[2];
}
}