using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Chernobyl.Mathematics.Movement; using Chernobyl.Mathematics.Vectors; using Chernobyl.Values; namespace Chernobyl.Mathematics.Geometry { /// /// An that can expand and contract to fit a /// collection of other instances inside of it. /// public class ShapeCollection : MatrixTransform, IShape, ICollection { /// /// Initializes a new instance of the /// class. /// public ShapeCollection() : this(0) { } /// /// Initializes a new instance of the /// class. /// /// The number of elements that the new list can /// initially store. public ShapeCollection(int capacity) : this(new List(capacity)) { } /// /// Initializes a new instance of the class. /// /// The list to store s /// in or null if the default should be used. public ShapeCollection(ICollection geometries) : this(geometries, null) { } /// /// Initializes a new instance of the class. /// /// The list to store s /// in or null if the default should be used. /// The list to use for the /// list or null if the default /// should be used.. public ShapeCollection(ICollection geometries, IList childTransformList) : base(childTransformList) { Geometries = geometries ?? new List(); WidthModifier = DefaultWidthModifier; HeightModifier = DefaultHeightModifier; DepthModifier = DefaultDepthModifier; } /// /// The largest width of the object in the object's X axis. /// public float Width { get { Update(); return _width; } } /// /// The largest height of the object in the object's Y axis. /// public float Height { get { Update(); return _height; } } /// /// The largest depth of the object in the object's Z axis. If the depth /// is zero, the object is 2D. /// public float Depth { get { Update(); return _depth; } } /// /// True if this is convex, false if it is concave /// or has 0 (such as a point).This property /// alway returns false. /// public bool IsConvex { get { return false; } } /// /// The amount of space taken up by an object on a flat plane in /// square metres (m^2). This property will return the sum of the areas /// of the s contained within this /// . /// public float Area { get { return Geometries.Sum(shape => shape.Area); } } /// /// The amount of 3D space this object consumes in cubic metres (m^3). /// This property will return the sum of the volumes of the /// s contained within this . /// public float Volume { get { return Geometries.Sum(shape => shape.Volume); } } /// /// The length of the path that surrounds a shape specified in metres (m). /// This property will return the sum of the perimeters of the /// s contained within this . /// public float Perimeter { get { return Geometries.Sum(shape => shape.Perimeter); } } /// /// The minimum number of coordinates needed to specify each point /// within this object. This property will return the largest dimension /// of the s contained in this . /// public uint Dimensions { get { return Geometries.Max(shape => shape.Dimensions); } } /// /// Adds an item to the . /// /// The object to add to the . /// The /// is read-only. public void Add(IShape item) { Geometries.Add(item); item.TransformDirtied += Geometry2DTransformDirtied; IsUpdateNeeded = true; } /// /// Adds an item to the . /// /// The objects to add to the . /// The /// is read-only. public void AddRange(IEnumerable items) { foreach (IShape volume2D in items) Geometries.Add(volume2D); } /// /// Removes all items from the . /// /// The /// is read-only. public void Clear() { // unsubscribe from these geometries, if any foreach (IShape volume2D in _geometries) volume2D.TransformDirtied -= Geometry2DTransformDirtied; Geometries.Clear(); IsUpdateNeeded = true; } /// /// Determines whether the contains a /// specific value. /// /// The object to locate in the /// . /// /// true if is found in the /// ; otherwise, false. /// public bool Contains(IShape item) { return Geometries.Contains(item); } /// /// Copies the elements of the to an /// , starting at a particular /// index. /// /// The one-dimensional that is /// the destination of the elements copied from /// . The must /// have zero-based indexing. /// The zero-based index in /// at which copying begins. /// is /// null. /// /// is less than 0. /// is /// multidimensional, is equal to or /// greater than the length of , or the number /// of elements in the source is greater /// than the available space from to the /// end of the destination . public void CopyTo(IShape[] array, int arrayIndex) { Geometries.CopyTo(array, arrayIndex); } /// /// Gets the number of s contained in the /// . /// /// The number of elements contained in the /// . public int Count { get { return Geometries.Count; } } /// /// Gets a value indicating whether the is /// read-only. /// /// true if the is read-only; /// otherwise, false. public bool IsReadOnly { get { return Geometries.IsReadOnly; } } /// /// Removes the first occurrence of a specific object from the /// . /// /// The object to remove from the /// . /// /// true if was successfully removed from the /// ; otherwise, false. This method also /// returns false if is not found in the original /// . /// /// The /// is read-only. public bool Remove(IShape item) { bool result = Geometries.Remove(item); // if the shape was properly removed then we unsubscribe from its // transform dirty event and update if (result == true) { item.TransformDirtied -= Geometry2DTransformDirtied; IsUpdateNeeded = true; } return result; } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through /// the collection. /// public IEnumerator GetEnumerator() { return Geometries.GetEnumerator(); } /// /// This method is invoked during the update of the /// right before the /// is actually updated. This method /// ensures the bounds of this instance are kept up to date. /// /// 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(LazyValue world) { base.PreUpdate(world); UpdateBounds(); } /// /// Called by the method, this /// method updates the , , and /// of this . /// protected void UpdateBounds() { // update our width, height and position based on the width, height, // and position of the geometries contained within this collection float leftMostPoint = 0; float rightMostPoint = 0; float topMostPoint = 0; float bottomMostPoint = 0; float frontMostPoint = 0; float backMostPoint = 0; using (IEnumerator volumeEnumerator = Geometries.GetEnumerator()) { // grab the first IShape and use it's left, right, top // and bottom points as our starting points. if (volumeEnumerator.MoveNext() == true) { IShape geo = volumeEnumerator.Current; float halfWidth = geo.Width * 0.5f; leftMostPoint = geo.Position.X - halfWidth; rightMostPoint = geo.Position.X + halfWidth; float halfHeight = geo.Height * 0.5f; bottomMostPoint = geo.Position.Y - halfHeight; topMostPoint = geo.Position.Y + halfHeight; float halfDepth = geo.Depth * 0.5f; backMostPoint = geo.Position.Y - halfDepth; frontMostPoint = geo.Position.Y + halfDepth; } // now locate the points on the geometries that are farthest // out. These points will define our rectangle's edges while (volumeEnumerator.MoveNext() == true) { IShape geo = volumeEnumerator.Current; float halfWidth = geo.Width * 0.5f; // check the left side float leftPoint = geo.Position.X - halfWidth; if (leftPoint < leftMostPoint) leftMostPoint = leftPoint; // check the right side float rightPoint = geo.Position.X + halfWidth; if (rightPoint > rightMostPoint) rightMostPoint = rightPoint; float halfHeight = geo.Height * 0.5f; // check the bottom float bottomPoint = geo.Position.Y - halfHeight; if (bottomPoint < bottomMostPoint) bottomMostPoint = bottomPoint; // check the top float topPoint = geo.Position.Y + halfHeight; if (topPoint > topMostPoint) topMostPoint = topPoint; float halfDepth = geo.Depth * 0.5f; // check the back float backPoint = geo.Position.Y + halfDepth; if (backPoint > backMostPoint) backMostPoint = backPoint; // check the front float frontPoint = geo.Position.Y + halfDepth; if (frontPoint > frontMostPoint) frontMostPoint = frontPoint; } } // generate our rectangle from the points that we have found _width = rightMostPoint - leftMostPoint; _height = topMostPoint - bottomMostPoint; _depth = frontMostPoint - backMostPoint; // grab the local matrix and set its row 0 (the first row, which // holds the scale in the X axis) to unit vector scaled by our // new width, set its row 1 (the second row, which holds the // scale in the Y axis) to unit vector scaled by our new height, // and set its position Matrix4 local = LocalMatrix; local.Row0 = Vector4.UnitX * (_width + WidthModifier); local.Row1 = Vector4.UnitY * (_height + HeightModifier); local.Row2 = Vector4.UnitZ * (_depth + DepthModifier); local.Row3 = new Vector4(leftMostPoint + _width * 0.5f, bottomMostPoint + _height * 0.5f, LocalMatrix.Row3.Z, LocalMatrix.Row3.W); SetTransformation(ref local); } /// /// The amount to add or remove from the width of the this /// in addition to the width calculated for /// the contained s.The default value of this /// property is given by the static field /// . /// public float WidthModifier { get { return _widthModifier; } set { _widthModifier = value; IsUpdateNeeded = true; } } /// /// The amount to add or remove from the height of the this /// in addition to the height calculated for /// the contained s. The default value of this /// property is given by the static field /// . /// public float HeightModifier { get { return _heightModifier; } set { _heightModifier = value; IsUpdateNeeded = true; } } /// /// The amount to add or remove from the depth of the this /// in addition to the depth calculated for /// the contained s. The default value of this /// property is given by the static field /// . /// public float DepthModifier { get { return _depthModifier; } set { _depthModifier = value; IsUpdateNeeded = true; } } /// /// The default value of the property . The /// value of this property is 0. /// public const float DefaultWidthModifier = 0; /// /// The default value of the property . The /// value of this property is 0. /// public const float DefaultHeightModifier = 0; /// /// The default value of the property . The /// value of this property is 0. /// public const float DefaultDepthModifier = 0; /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate /// through the collection. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// The s contained within this /// . /// protected ICollection Geometries { get { return _geometries; } set { if (_geometries != null) { // unsubscribe from our previous geometries, if any foreach (IShape volume2D in _geometries) volume2D.TransformDirtied -= Geometry2DTransformDirtied; } _geometries = value; // subscribe to our new geometries, if any foreach (IShape volume2D in _geometries) volume2D.TransformDirtied += Geometry2DTransformDirtied; if(_geometries.Count > 0) IsUpdateNeeded = true; } } /// /// An event handler that is invoked when one of the /// s in the list has /// had it's transform change. When one of those transforms change we /// need to update this /// , , and /// . /// /// The sender of the event. /// The instance containing the /// event data. protected void Geometry2DTransformDirtied(object sender, EventArgs e) { IsUpdateNeeded = true; } /// /// The backing field to . /// ICollection _geometries; /// /// The backing field to . /// float _width; /// /// The backing field to . /// float _height; /// /// The backing field to . /// float _depth; /// /// The backing field to . /// float _widthModifier; /// /// The backing field to . /// float _heightModifier; /// /// The backing field to . /// float _depthModifier; } }