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]; } }