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