This commit is contained in:
DESKTOP-5RP3AKU\Jisol 2024-02-01 04:56:41 +08:00
parent b6461675a8
commit aa4d6c3ce2
932 changed files with 112600 additions and 4058 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 31356a43a7579d34d9b102dbfe1b77e4 guid: fb52241f50642ec4481571bf105f15cc
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 267f4a03f7790b745967bfbd68e0095d guid: 83bff39e9c87b454fa155d86020ca0bf
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: eedc788daf73e8b46ad39b7553f7df0b guid: 6d0d461b18d011e4e96f4e2bd53f9a9d
TextureImporter: TextureImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}
@ -93,6 +93,19 @@ TextureImporter:
ignorePlatformSupport: 0 ignorePlatformSupport: 0
androidETC2FallbackOverride: 0 androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0 forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3 - serializedVersion: 3
buildTarget: Server buildTarget: Server
maxTextureSize: 2048 maxTextureSize: 2048

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: f1d65185644671f4c9a60dbc6733303c guid: ad185d9a7ed824e4aa1843a512e11ba1
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5ce6ef99def540a43a92f3be35cca4cd guid: f094965b882503b40a02f68c61d299bc
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b405948c0480ad843b6aa640c7ffd28a guid: 67bae28016d9a154c8de54a1228de0bf
labels: labels:
- NuGetForUnity - NuGetForUnity
PluginImporter: PluginImporter:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: ab482ab5027a2d141a6b8234db8e5b1c guid: 998c2d4c4a1b1f94eb0ba1573b8f7c64
TextScriptImporter: TextScriptImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 0c1dc29160d088b449c008c586f26c83 guid: 197f2d43e882aab4f83df3217ea82ddd
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 57ad12de7bd0a424b90fa6e65daca702 guid: 745bbb026c1abb64fa4063dd14cf313e
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 994c9e52f887aba4d8bd51018a243ad4 guid: e408c137718b826488a2a60c35e88a4b
TextureImporter: TextureImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}
@ -93,6 +93,19 @@ TextureImporter:
ignorePlatformSupport: 0 ignorePlatformSupport: 0
androidETC2FallbackOverride: 0 androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0 forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3 - serializedVersion: 3
buildTarget: Server buildTarget: Server
maxTextureSize: 2048 maxTextureSize: 2048

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 13939b529f639c64c9f8ed642ae8225d guid: 8a743569fbf019e4c9a2d497d9c4e5c1
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b3758ce5f94c47c40a26d23db7225cf7 guid: 229b1ad6a9780414ca0ff9a52499207d
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: d18075e399b7444499be851fe111d283 guid: 3581c68fb30ce4641af7b64238201135
labels: labels:
- NuGetForUnity - NuGetForUnity
PluginImporter: PluginImporter:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: d4e1291355a40db41ab3daa142d4681b guid: 91b96c1f3174bf941aaf8a5b5f4f0375
TextScriptImporter: TextScriptImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 943ed975f4553d349ac6178591167aad
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0886c3a27fa906740a2f0394fc2c8255
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 77d09f292abbe6147a23b9e7e8dd63a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Platforms>AnyCPU;x64;x86</Platforms>
<PackageLicenseUrl>https://github.com/bepu/bepuphysics1/blob/master/LICENSE.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/bepu/bepuphysics1</PackageProjectUrl>
<PackageIconUrl>https://raw.githubusercontent.com/bepu/bepuphysics1/master/Documentation/images/readme/bepuphysicslogo256.png</PackageIconUrl>
<RepositoryUrl>https://github.com/bepu/bepuphysics1.git</RepositoryUrl>
<PackageTags>physics;3d;rigid body;real time;simulation</PackageTags>
<Description>Real time physics simulation library.</Description>
<Version>1.5.2</Version>
<Authors>Ross Nordby</Authors>
<Company>Bepu Entertainment LLC</Company>
<Copyright>© Bepu Entertainment LLC</Copyright>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BEPUutilities\BEPUutilities.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a17ab40a468e7a345be4af87f7e8e7e6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 65fde1803a01d6f46857bbc9149119df
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,154 @@
using System;
using BEPUphysics.BroadPhaseSystems;
using BEPUphysics.CollisionRuleManagement;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUutilities;
namespace BEPUphysics.BroadPhaseEntries
{
/// <summary>
/// Superclass of all objects which live inside the broad phase.
/// The BroadPhase will generate pairs between BroadPhaseEntries.
/// </summary>
public abstract class BroadPhaseEntry : IBoundingBoxOwner, ICollisionRulesOwner
{
internal int hashCode;
protected BroadPhaseEntry()
{
CollisionRules = new CollisionRules();
collisionRulesUpdatedDelegate = CollisionRulesUpdated;
hashCode = (int)(base.GetHashCode() * 0xd8163841);
}
/// <summary>
/// Gets the broad phase to which this broad phase entry belongs.
/// </summary>
public BroadPhase BroadPhase
{
get;
internal set;
}
/// <summary>
/// Gets the object's hash code.
/// </summary>
/// <returns>Hash code for the object.</returns>
public override int GetHashCode()
{
return hashCode;
}
private Action collisionRulesUpdatedDelegate;
protected abstract void CollisionRulesUpdated();
protected internal BoundingBox boundingBox;
/// <summary>
/// Gets or sets the bounding box of the entry.
/// </summary>
public BoundingBox BoundingBox
{
get { return boundingBox; }
set
{
boundingBox = value;
}
}
/// <summary>
/// Gets whether this collidable is associated with an active entity. True if it is, false if it's not.
/// </summary>
public abstract bool IsActive { get; }
internal CollisionRules collisionRules;
/// <summary>
/// Gets the entry's collision rules.
/// </summary>
public CollisionRules CollisionRules
{
get { return collisionRules; }
set
{
if (collisionRules != value)
{
if (collisionRules != null)
collisionRules.CollisionRulesChanged -= collisionRulesUpdatedDelegate;
collisionRules = value;
if (collisionRules != null)
collisionRules.CollisionRulesChanged += collisionRulesUpdatedDelegate;
CollisionRulesUpdated();
}
}
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public abstract bool RayCast(Ray ray, float maximumLength, out RayHit rayHit);
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public virtual bool RayCast(Ray ray, float maximumLength, Func<BroadPhaseEntry, bool> filter, out RayHit rayHit)
{
if (filter(this))
return RayCast(ray, maximumLength, out rayHit);
rayHit = new RayHit();
return false;
}
/// <summary>
/// Sweeps a convex shape against the entry.
/// </summary>
/// <param name="castShape">Swept shape.</param>
/// <param name="startingTransform">Beginning location and orientation of the cast shape.</param>
/// <param name="sweep">Sweep motion to apply to the cast shape.</param>
/// <param name="hit">Hit data of the cast on the entry, if any.</param>
/// <returns>Whether or not the cast hit the entry.</returns>
public abstract bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit);
/// <summary>
/// Sweeps a convex shape against the entry.
/// </summary>
/// <param name="castShape">Swept shape.</param>
/// <param name="startingTransform">Beginning location and orientation of the cast shape.</param>
/// <param name="sweep">Sweep motion to apply to the cast shape.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="hit">Hit data of the cast on the entry, if any.</param>
/// <returns>Whether or not the cast hit the entry.</returns>
public virtual bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, Func<BroadPhaseEntry, bool> filter, out RayHit hit)
{
if (filter(this))
return ConvexCast(castShape, ref startingTransform, ref sweep, out hit);
hit = new RayHit();
return false;
}
/// <summary>
/// Updates the bounding box to the current state of the entry.
/// </summary>
public abstract void UpdateBoundingBox();
/// <summary>
/// Gets or sets the user data associated with this entry.
/// </summary>
public object Tag { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 028b967f7893b5b49823e0228febe3cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,151 @@
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.CollisionShapes;
using BEPUphysics.NarrowPhaseSystems.Pairs;
using BEPUphysics.CollisionRuleManagement;
using System;
using BEPUutilities.DataStructures;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Superclass of objects living in the collision detection pipeline
/// that can result in contacts.
///</summary>
public abstract class Collidable : BroadPhaseEntry
{
protected Collidable()
{
shapeChangedDelegate = OnShapeChanged;
}
protected internal CollisionShape shape; //Having this non-private allows for some very special-casey stuff; see TriangleShape initialization.
///<summary>
/// Gets the shape used by the collidable.
///</summary>
public CollisionShape Shape
{
get
{
return shape;
}
protected set
{
if (shape != null && shapeChangedHooked)
shape.ShapeChanged -= shapeChangedDelegate;
shape = value;
if (shape != null && shapeChangedHooked)
shape.ShapeChanged += shapeChangedDelegate;
OnShapeChanged(shape);
}
}
bool shapeChangedHooked = true;
/// <summary>
/// Gets or sets whether the shape changed event is hooked. Setting this modifies the event delegate list on the associated shape, if any shape exists.
/// If no shape exists, getting this property returns whether the event would be hooked if a shape was present.
/// </summary>
/// <remarks>Yes, this is a hack.</remarks>
public bool ShapeChangedHooked
{
get
{
return shapeChangedHooked;
}
set
{
if (shape != null)
{
if (shapeChangedHooked && !value)
{
shape.ShapeChanged -= shapeChangedDelegate;
}
else if (!shapeChangedHooked && value)
{
shape.ShapeChanged += shapeChangedDelegate;
}
}
shapeChangedHooked = value;
}
}
protected internal abstract IContactEventTriggerer EventTriggerer { get; }
/// <summary>
/// Gets or sets whether or not to ignore shape changes. When true, changing the collision shape will not force the collidable to perform any updates.
/// Does not modify the shape changed event delegate list.
/// </summary>
public bool IgnoreShapeChanges { get; set; }
Action<CollisionShape> shapeChangedDelegate;
protected virtual void OnShapeChanged(CollisionShape collisionShape)
{
}
internal RawList<CollidablePairHandler> pairs = new RawList<CollidablePairHandler>();
///<summary>
/// Gets the list of pairs associated with the collidable.
/// These pairs are found by the broad phase and are managed by the narrow phase;
/// they can contain other collidables, entities, and contacts.
///</summary>
public ReadOnlyList<CollidablePairHandler> Pairs
{
get
{
return new ReadOnlyList<CollidablePairHandler>(pairs);
}
}
///<summary>
/// Gets a list of all other collidables that this collidable overlaps.
///</summary>
public CollidableCollection OverlappedCollidables
{
get
{
return new CollidableCollection(this);
}
}
protected override void CollisionRulesUpdated()
{
for (int i = 0; i < pairs.Count; i++)
{
pairs[i].CollisionRule = CollisionRules.CollisionRuleCalculator(pairs[i].BroadPhaseOverlap.entryA, pairs[i].BroadPhaseOverlap.entryB);
}
}
internal void AddPair(CollidablePairHandler pair, ref int index)
{
index = pairs.Count;
pairs.Add(pair);
}
internal void RemovePair(CollidablePairHandler pair, ref int index)
{
if (pairs.Count > index)
{
pairs.FastRemoveAt(index);
if (pairs.Count > index)
{
var endPair = pairs.Elements[index];
if (endPair.CollidableA == this)
endPair.listIndexA = index;
else
endPair.listIndexB = index;
}
}
index = -1;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1fe33aae8de6274e9a7be27b2dd798a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// List of collidable objects overlapping another collidable.
///</summary>
public struct CollidableCollection : IList<Collidable>
{
///<summary>
/// Enumerator for the CollidableCollection.
///</summary>
public struct Enumerator : IEnumerator<Collidable>
{
CollidableCollection collection;
int index;
///<summary>
/// Constructs an enumerator.
///</summary>
///<param name="collection">Collection to which the enumerator belongs.</param>
public Enumerator(CollidableCollection collection)
{
this.collection = collection;
index = -1;
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public Collidable Current
{
get { return collection[index]; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception>The collection was modified after the enumerator was created.
/// <cref>T:System.InvalidOperationException</cref>
/// </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
return ++index < collection.Count;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception>The collection was modified after the enumerator was created.
/// <cref>T:System.InvalidOperationException</cref>
/// </exception><filterpriority>2</filterpriority>
public void Reset()
{
index = -1;
}
}
///<summary>
/// Constructs a new CollidableCollection.
///</summary>
///<param name="owner">The collidable to which the collection belongs.</param>
public CollidableCollection(Collidable owner)
{
this.owner = owner;
}
internal Collidable owner;
///<summary>
/// Gets an enumerator which can be used to enumerate over the list.
///</summary>
///<returns>Enumerator for the collection.</returns>
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator<Collidable> IEnumerable<Collidable>.GetEnumerator()
{
return new Enumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}
/// <summary>
/// Determines the index of a specific item in the <see>
/// <cref>T:System.Collections.Generic.IList`1</cref>
/// </see> .
/// </summary>
/// <returns>
/// The index of <paramref name="item"/> if found in the list; otherwise, -1.
/// </returns>
/// <param name="item">The object to locate in the <see>
/// <cref>T:System.Collections.Generic.IList`1</cref>
/// </see> .</param>
public int IndexOf(Collidable item)
{
for (int i = 0; i < Count; i++)
{
if (item == this[i])
return i;
}
return -1;
}
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
/// <returns>
/// The element at the specified index.
/// </returns>
/// <param name="index">The zero-based index of the element to get or set.</param><exception>
/// <cref>T:System.ArgumentOutOfRangeException</cref>
/// <paramref name="index"/> is not a valid index in the <see>
/// <cref>T:System.Collections.Generic.IList`1</cref>
/// </see>
/// .</exception><exception>The property is set and the
/// <cref>T:System.NotSupportedException</cref>
/// <see>
/// <cref>T:System.Collections.Generic.IList`1</cref>
/// </see>
/// is read-only.</exception>
public Collidable this[int index]
{
get
{
//It's guaranteed to be a CollisionInformation, because it's a member of a CollidablePairHandler.
return (Collidable)(owner.pairs[index].broadPhaseOverlap.entryA == owner ? owner.pairs[index].broadPhaseOverlap.entryB : owner.pairs[index].broadPhaseOverlap.entryA);
}
set
{
throw new NotSupportedException();
}
}
/// <summary>
/// Determines whether the <see>
/// <cref>T:System.Collections.Generic.ICollection`1</cref>
/// </see> contains a specific value.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> is found in the <see>
/// <cref>T:System.Collections.Generic.ICollection`1</cref>
/// </see> ; otherwise, false.
/// </returns>
/// <param name="item">The object to locate in the <see>
/// <cref>T:System.Collections.Generic.ICollection`1</cref>
/// </see> .</param>
public bool Contains(Collidable item)
{
for (int i = 0; i < Count; i++)
{
if (item == this[i])
return true;
}
return false;
}
/// <summary>
/// Copies the elements of the <see>
/// <cref>T:System.Collections.Generic.ICollection`1</cref>
/// </see> to an <see>
/// <cref>T:System.Array</cref>
/// </see> , starting at a particular <see>
/// <cref>T:System.Array</cref>
/// </see> index.
/// </summary>
/// <param name="array">The one-dimensional <see>
/// <cref>T:System.Array</cref>
/// </see> that is the destination of the elements copied from <see>
/// <cref>T:System.Collections.Generic.ICollection`1</cref>
/// </see> . The <see>
/// <cref>T:System.Array</cref>
/// </see> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
public void CopyTo(Collidable[] array, int arrayIndex)
{
for (int i = 0; i < Count; i++)
{
array[arrayIndex + i] = this[i];
}
}
/// <summary>
/// Gets the number of elements contained in the <see>
/// <cref>T:System.Collections.Generic.ICollection`1</cref>
/// </see> .
/// </summary>
/// <returns>
/// The number of elements contained in the <see>
/// <cref>T:System.Collections.Generic.ICollection`1</cref>
/// </see> .
/// </returns>
public int Count
{
get { return owner.pairs.Count; }
}
bool ICollection<Collidable>.IsReadOnly
{
get { return true; }
}
bool ICollection<Collidable>.Remove(Collidable item)
{
throw new NotSupportedException();
}
void ICollection<Collidable>.Add(Collidable item)
{
throw new NotSupportedException();
}
void ICollection<Collidable>.Clear()
{
throw new NotSupportedException();
}
void IList<Collidable>.Insert(int index, Collidable item)
{
throw new NotSupportedException();
}
void IList<Collidable>.RemoveAt(int index)
{
throw new NotSupportedException();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cbce297ee646e0d4ea04f20ba96b8be8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,71 @@
using System;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Pair of collidables.
///</summary>
public struct CollidablePair : IEquatable<CollidablePair>
{
internal Collidable collidableA;
///<summary>
/// First collidable in the pair.
///</summary>
public Collidable CollidableA
{
get { return collidableA; }
}
internal Collidable collidableB;
/// <summary>
/// Second collidable in the pair.
/// </summary>
public Collidable CollidableB
{
get { return collidableB; }
}
///<summary>
/// Constructs a new collidable pair.
///</summary>
///<param name="collidableA">First collidable in the pair.</param>
///<param name="collidableB">Second collidable in the pair.</param>
public CollidablePair(Collidable collidableA, Collidable collidableB)
{
this.collidableA = collidableA;
this.collidableB = collidableB;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <filterpriority>2</filterpriority>
public override int GetHashCode()
{
//TODO: Use old prime-based system?
return collidableA.GetHashCode() + collidableB.GetHashCode();
}
#region IEquatable<BroadPhaseOverlap> Members
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(CollidablePair other)
{
return (other.collidableA == collidableA && other.collidableB == collidableB) || (other.collidableA == collidableB && other.collidableB == collidableA);
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b670bded2cddfa4a84c81d9de3a27c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,482 @@
using System;
using System.Collections.Generic;
using BEPUphysics.CollisionTests.CollisionAlgorithms;
using BEPUphysics.DataStructures;
using BEPUutilities.DataStructures;
using BEPUphysics.Entities;
using BEPUphysics.OtherSpaceStages;
using BEPUutilities;
using BEPUutilities.ResourceManagement;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUphysics.CollisionRuleManagement;
using BEPUphysics.NarrowPhaseSystems.Pairs;
namespace BEPUphysics.BroadPhaseEntries
{
/// <summary>
/// Stores flags regarding an object's degree of inclusion in a volume.
/// </summary>
public struct ContainmentState
{
/// <summary>
/// Whether or not the object is fully contained.
/// </summary>
public bool IsContained;
/// <summary>
/// Whether or not the object is partially or fully contained.
/// </summary>
public bool IsTouching;
/// <summary>
/// Whether or not the entity associated with this state has been refreshed during the last update.
/// </summary>
internal bool StaleState;
/// <summary>
/// Constructs a new ContainmentState.
/// </summary>
/// <param name="touching">Whether or not the object is partially or fully contained.</param>
/// <param name="contained">Whether or not the object is fully contained.</param>
public ContainmentState(bool touching, bool contained)
{
IsTouching = touching;
IsContained = contained;
StaleState = false;
}
/// <summary>
/// Constructs a new ContainmentState.
/// </summary>
/// <param name="touching">Whether or not the object is partially or fully contained.</param>
/// <param name="contained">Whether or not the object is fully contained.</param>
/// <param name="stale">Whether or not the entity associated with this state has been refreshed in the previous update.</param>
internal ContainmentState(bool touching, bool contained, bool stale)
{
IsTouching = touching;
IsContained = contained;
StaleState = stale;
}
}
/// <summary>
/// Manages the detection of entities within an arbitrary closed triangle mesh.
/// </summary>
public class DetectorVolume : BroadPhaseEntry, ISpaceObject, IDeferredEventCreator
{
internal Dictionary<Entity, DetectorVolumePairHandler> pairs = new Dictionary<Entity, DetectorVolumePairHandler>();
/// <summary>
/// Gets the list of pairs associated with the detector volume.
/// </summary>
public ReadOnlyDictionary<Entity, DetectorVolumePairHandler> Pairs
{
get
{
return new ReadOnlyDictionary<Entity, DetectorVolumePairHandler>(pairs);
}
}
TriangleMesh triangleMesh;
/// <summary>
/// Gets or sets the triangle mesh data and acceleration structure. Must be a closed mesh with consistent winding.
/// </summary>
public TriangleMesh TriangleMesh
{
get
{
return triangleMesh;
}
set
{
triangleMesh = value;
UpdateBoundingBox();
Reinitialize();
}
}
/// <summary>
/// Creates a detector volume.
/// </summary>
/// <param name="triangleMesh">Closed and consistently wound mesh defining the volume.</param>
public DetectorVolume(TriangleMesh triangleMesh)
{
TriangleMesh = triangleMesh;
UpdateBoundingBox();
}
/// <summary>
/// Fires when an entity comes into contact with the volume.
/// </summary>
public event EntityBeginsTouchingVolumeEventHandler EntityBeganTouching;
/// <summary>
/// Fires when an entity ceases to intersect the volume.
/// </summary>
public event EntityStopsTouchingVolumeEventHandler EntityStoppedTouching;
/// <summary>
/// Fires when an entity becomes fully engulfed by a volume.
/// </summary>
public event VolumeBeginsContainingEntityEventHandler VolumeBeganContainingEntity;
/// <summary>
/// Fires when an entity ceases to be fully engulfed by a volume.
/// </summary>
public event VolumeStopsContainingEntityEventHandler VolumeStoppedContainingEntity;
private Space space;
Space ISpaceObject.Space
{
get
{
return space;
}
set
{
space = value;
}
}
///<summary>
/// Space that owns the detector volume.
///</summary>
public Space Space
{
get
{
return space;
}
}
private bool innerFacingIsClockwise;
/// <summary>
/// Determines if a point is contained by the detector volume.
/// </summary>
/// <param name="point">Point to check for containment.</param>
/// <returns>Whether or not the point is contained by the detector volume.</returns>
public bool IsPointContained(Vector3 point)
{
var triangles = CommonResources.GetIntList();
bool contained = IsPointContained(ref point, triangles);
CommonResources.GiveBack(triangles);
return contained;
}
internal bool IsPointContained(ref Vector3 point, RawList<int> triangles)
{
Vector3 rayDirection;
//Point from the approximate center of the mesh outwards.
//This is a cheap way to reduce the number of unnecessary checks when objects are external to the mesh.
Vector3.Add(ref boundingBox.Max, ref boundingBox.Min, out rayDirection);
Vector3.Multiply(ref rayDirection, .5f, out rayDirection);
Vector3.Subtract(ref point, ref rayDirection, out rayDirection);
//If the point is right in the middle, we'll need a backup.
if (rayDirection.LengthSquared() < .01f)
rayDirection = Vector3.Up;
var ray = new Ray(point, rayDirection);
triangleMesh.Tree.GetOverlaps(ray, triangles);
float minimumT = float.MaxValue;
bool minimumIsClockwise = false;
for (int i = 0; i < triangles.Count; i++)
{
Vector3 a, b, c;
triangleMesh.Data.GetTriangle(triangles.Elements[i], out a, out b, out c);
RayHit hit;
bool hitClockwise;
if (Toolbox.FindRayTriangleIntersection(ref ray, float.MaxValue, ref a, ref b, ref c, out hitClockwise, out hit))
{
if (hit.T < minimumT)
{
minimumT = hit.T;
minimumIsClockwise = hitClockwise;
}
}
}
triangles.Clear();
//If the first hit is on the inner surface, then the ray started inside the mesh.
return minimumT < float.MaxValue && minimumIsClockwise == innerFacingIsClockwise;
}
protected override void CollisionRulesUpdated()
{
foreach (var pair in pairs.Values)
pair.CollisionRule = CollisionRules.CollisionRuleCalculator(pair.BroadPhaseOverlap.entryA, pair.BroadPhaseOverlap.entryB);
}
/// <summary>
/// Gets whether this collidable is associated with an active entity. True if it is, false if it's not.
/// </summary>
public override bool IsActive
{
get { return false; }
}
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
return triangleMesh.RayCast(ray, maximumLength, TriangleSidedness.DoubleSided, out rayHit);
}
public override bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit)
{
hit = new RayHit();
BoundingBox boundingBox;
castShape.GetSweptBoundingBox(ref startingTransform, ref sweep, out boundingBox);
var tri = PhysicsThreadResources.GetTriangle();
var hitElements = CommonResources.GetIntList();
if (triangleMesh.Tree.GetOverlaps(boundingBox, hitElements))
{
hit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
triangleMesh.Data.GetTriangle(hitElements[i], out tri.vA, out tri.vB, out tri.vC);
Vector3 center;
Vector3.Add(ref tri.vA, ref tri.vB, out center);
Vector3.Add(ref center, ref tri.vC, out center);
Vector3.Multiply(ref center, 1f / 3f, out center);
Vector3.Subtract(ref tri.vA, ref center, out tri.vA);
Vector3.Subtract(ref tri.vB, ref center, out tri.vB);
Vector3.Subtract(ref tri.vC, ref center, out tri.vC);
tri.MaximumRadius = tri.vA.LengthSquared();
float radius = tri.vB.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
radius = tri.vC.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
tri.MaximumRadius = (float)Math.Sqrt(tri.MaximumRadius);
tri.collisionMargin = 0;
var triangleTransform = new RigidTransform { Orientation = Quaternion.Identity, Position = center };
RayHit tempHit;
if (MPRToolbox.Sweep(castShape, tri, ref sweep, ref Toolbox.ZeroVector, ref startingTransform, ref triangleTransform, out tempHit) && tempHit.T < hit.T)
{
hit = tempHit;
}
}
tri.MaximumRadius = 0;
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return hit.T != float.MaxValue;
}
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return false;
}
/// <summary>
/// Sets the bounding box of the detector volume to the current hierarchy root bounding box. This is called automatically if the TriangleMesh property is set.
/// </summary>
public override void UpdateBoundingBox()
{
boundingBox = triangleMesh.Tree.BoundingBox;
}
/// <summary>
/// Updates the detector volume's interpretation of the mesh. This should be called when the the TriangleMesh is changed significantly. This is called automatically if the TriangleMesh property is set.
/// </summary>
public void Reinitialize()
{
//Pick a point that is known to be outside the mesh as the origin.
Vector3 origin = (triangleMesh.Tree.BoundingBox.Max - triangleMesh.Tree.BoundingBox.Min) * 1.5f + triangleMesh.Tree.BoundingBox.Min;
//Pick a direction which will definitely hit the mesh.
Vector3 a, b, c;
triangleMesh.Data.GetTriangle(0, out a, out b, out c);
var direction = (a + b + c) / 3 - origin;
var ray = new Ray(origin, direction);
var triangles = CommonResources.GetIntList();
triangleMesh.Tree.GetOverlaps(ray, triangles);
float minimumT = float.MaxValue;
for (int i = 0; i < triangles.Count; i++)
{
triangleMesh.Data.GetTriangle(triangles.Elements[i], out a, out b, out c);
RayHit hit;
bool hitClockwise;
if (Toolbox.FindRayTriangleIntersection(ref ray, float.MaxValue, ref a, ref b, ref c, out hitClockwise, out hit))
{
if (hit.T < minimumT)
{
minimumT = hit.T;
innerFacingIsClockwise = !hitClockwise;
}
}
}
CommonResources.GiveBack(triangles);
}
void ISpaceObject.OnAdditionToSpace(Space newSpace)
{
}
void ISpaceObject.OnRemovalFromSpace(Space oldSpace)
{
}
/// <summary>
/// Used to protect against containment changes coming in from multithreaded narrowphase contexts.
/// </summary>
SpinLock locker = new SpinLock();
struct ContainmentChange
{
public Entity Entity;
public ContainmentChangeType Change;
}
enum ContainmentChangeType : byte
{
BeganTouching,
StoppedTouching,
BeganContaining,
StoppedContaining
}
private Queue<ContainmentChange> containmentChanges = new Queue<ContainmentChange>();
internal void BeganTouching(DetectorVolumePairHandler pair)
{
locker.Enter();
containmentChanges.Enqueue(new ContainmentChange
{
Change = ContainmentChangeType.BeganTouching,
Entity = pair.Collidable.entity
});
locker.Exit();
}
internal void StoppedTouching(DetectorVolumePairHandler pair)
{
locker.Enter();
containmentChanges.Enqueue(new ContainmentChange
{
Change = ContainmentChangeType.StoppedTouching,
Entity = pair.Collidable.entity
});
locker.Exit();
}
internal void BeganContaining(DetectorVolumePairHandler pair)
{
locker.Enter();
containmentChanges.Enqueue(new ContainmentChange
{
Change = ContainmentChangeType.BeganContaining,
Entity = pair.Collidable.entity
});
locker.Exit();
}
internal void StoppedContaining(DetectorVolumePairHandler pair)
{
locker.Enter();
containmentChanges.Enqueue(new ContainmentChange
{
Change = ContainmentChangeType.StoppedContaining,
Entity = pair.Collidable.entity
});
locker.Exit();
}
DeferredEventDispatcher IDeferredEventCreator.DeferredEventDispatcher { get; set; }
bool IDeferredEventCreator.IsActive
{
get { return true; }
set { throw new NotSupportedException("Detector volumes are always active deferred event generators."); }
}
void IDeferredEventCreator.DispatchEvents()
{
while (containmentChanges.Count > 0)
{
var change = containmentChanges.Dequeue();
switch (change.Change)
{
case ContainmentChangeType.BeganTouching:
if (EntityBeganTouching != null)
EntityBeganTouching(this, change.Entity);
break;
case ContainmentChangeType.StoppedTouching:
if (EntityStoppedTouching != null)
EntityStoppedTouching(this, change.Entity);
break;
case ContainmentChangeType.BeganContaining:
if (VolumeBeganContainingEntity != null)
VolumeBeganContainingEntity(this, change.Entity);
break;
case ContainmentChangeType.StoppedContaining:
if (VolumeStoppedContainingEntity != null)
VolumeStoppedContainingEntity(this, change.Entity);
break;
}
}
}
int IDeferredEventCreator.ChildDeferredEventCreators
{
get { return 0; }
set
{
throw new NotSupportedException("The detector volume does not allow child deferred event creators.");
}
}
}
/// <summary>
/// Handles any special logic to perform when an entry begins touching a detector volume.
/// Runs within an update loop for updateables; modifying the updateable listing during the event is disallowed.
/// </summary>
/// <param name="volume">DetectorVolume being touched.</param>
/// <param name="toucher">Entry touching the volume.</param>
public delegate void EntityBeginsTouchingVolumeEventHandler(DetectorVolume volume, Entity toucher);
/// <summary>
/// Handles any special logic to perform when an entry stops touching a detector volume.
/// Runs within an update loop for updateables; modifying the updateable listing during the event is disallowed.
/// </summary>
/// <param name="volume">DetectorVolume no longer being touched.</param>
/// <param name="toucher">Entry no longer touching the volume.</param>
public delegate void EntityStopsTouchingVolumeEventHandler(DetectorVolume volume, Entity toucher);
/// <summary>
/// Handles any special logic to perform when an entity begins being contained by a detector volume.
/// Runs within an update loop for updateables; modifying the updateable listing during the event is disallowed.
/// </summary>
/// <param name="volume">DetectorVolume containing the entry.</param>
/// <param name="entity">Entity contained by the volume.</param>
public delegate void VolumeBeginsContainingEntityEventHandler(DetectorVolume volume, Entity entity);
/// <summary>
/// Handles any special logic to perform when an entry stops being contained by a detector volume.
/// Runs within an update loop for updateables; modifying the updateable listing during the event is disallowed.
/// </summary>
/// <param name="volume">DetectorVolume no longer containing the entry.</param>
/// <param name="entity">Entity no longer contained by the volume.</param>
public delegate void VolumeStopsContainingEntityEventHandler(DetectorVolume volume, Entity entity);
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edfecf6e1e521c8499d985718b69f0cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,124 @@
using System.Collections.Generic;
using BEPUphysics.BroadPhaseEntries.MobileCollidables;
using BEPUphysics.Entities;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Enumerable collection of entities associated with a collidable.
///</summary>
public struct EntityCollidableCollection : IEnumerable<Entity>
{
///<summary>
/// Enumerator for the EntityCollidableCollection.
///</summary>
public struct Enumerator : IEnumerator<Entity>
{
EntityCollidableCollection collection;
EntityCollidable current;
int index;
///<summary>
/// Constructs a new enumerator.
///</summary>
///<param name="collection">Owning collection.</param>
public Enumerator(EntityCollidableCollection collection)
{
this.collection = collection;
index = -1;
current = null;
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public Entity Current
{
get { return current.entity; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception>The collection was modified after the enumerator was created.
/// <cref>T:System.InvalidOperationException</cref>
/// </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
while (++index < collection.owner.pairs.Count)
{
if ((current = (collection.owner.pairs[index].broadPhaseOverlap.entryA == collection.owner ?
collection.owner.pairs[index].broadPhaseOverlap.entryB :
collection.owner.pairs[index].broadPhaseOverlap.entryA) as EntityCollidable) != null)
return true;
}
return false;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception>The collection was modified after the enumerator was created.
/// <cref>T:System.InvalidOperationException</cref>
/// </exception><filterpriority>2</filterpriority>
public void Reset()
{
index = -1;
}
}
///<summary>
/// Constructs a new EntityCollidableCollection.
///</summary>
///<param name="owner">Owner of the collection.</param>
public EntityCollidableCollection(EntityCollidable owner)
{
this.owner = owner;
}
internal EntityCollidable owner;
///<summary>
/// Gets an enumerator over the entities in the collection.
///</summary>
///<returns>Enumerator over the entities in the collection.</returns>
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator<Entity> IEnumerable<Entity>.GetEnumerator()
{
return new Enumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70cb455ddde8cd54eada69f847a7b082
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a767a63ee12b83c469bd8906469999bd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,270 @@
using BEPUphysics.BroadPhaseEntries;
using BEPUphysics.BroadPhaseSystems;
using BEPUphysics.CollisionTests;
using BEPUphysics.NarrowPhaseSystems.Pairs;
namespace BEPUphysics.BroadPhaseEntries.Events
{
//TODO: Contravariance isn't supported on all platforms...
/// <summary>
/// Handles any special logic when two objects' bounding boxes overlap as determined by the broadphase system.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
public delegate void PairCreatedEventHandler<T>(T sender, BroadPhaseEntry other, NarrowPhasePair pair);
/// <summary>
/// Handles any special logic when two objects' bounding boxes overlap as determined by the broadphase system.
/// Unlike PairCreatedEventHandler, this will be called as soon as a pair is created instead of at the end of the frame.
/// This allows the pair's data to be adjusted prior to any usage, but some actions are not supported due to the execution stage.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
public delegate void CreatingPairEventHandler<T>(T sender, BroadPhaseEntry other, NarrowPhasePair pair);
/// <summary>
/// Handles any special logic when two objects' bounding boxes cease to overlap as determined by the broadphase system.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">The entry formerly interacting with the sender via the deleted pair.</param>
public delegate void PairRemovedEventHandler<T>(T sender, BroadPhaseEntry other);
/// <summary>
/// Handles any special logic when two objects' bounding boxes cease to overlap as determined by the broadphase system.
/// Unlike PairRemovedEventHandler, this will trigger at the time of pair removal instead of at the end of the space's update.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">The entry formerly interacting with the sender via the deleted pair.</param>
public delegate void RemovingPairEventHandler<T>(T sender, BroadPhaseEntry other);
/// <summary>
/// Handles any special logic when two bodies are touching and generate a contact point.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
/// <param name="contact">Created contact data.</param>
public delegate void ContactCreatedEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair, ContactData contact);
/// <summary>
/// Handles any special logic when two bodies are touching and generate a contact point.
/// Unlike ContactCreatedEventHandler, this will trigger at the time of contact generation instead of at the end of the space's update.
/// This allows the contact's data to be adjusted prior to usage in the velocity solver,
/// but other actions such as altering the owning space's pair or entry listings are unsafe.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
/// <param name="contact">Newly generated contact point between the pair's two bodies.
/// This reference cannot be safely kept outside of the scope of the handler; contacts can quickly return to the resource pool.</param>
public delegate void CreatingContactEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair, Contact contact);
/// <summary>
/// Handles any special logic when two bodies initally collide and generate a contact point.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
public delegate void InitialCollisionDetectedEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair);
/// <summary>
/// Handles any special logic when two bodies initally collide and generate a contact point.
/// Unlike InitialCollisionDetectedEventHandler, this will trigger at the time of contact creation instead of at the end of the space's update.
/// Performing operations outside of the scope of the pair is unsafe.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
public delegate void DetectingInitialCollisionEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair);
/// <summary>
/// Handles any special logic when a contact point between two bodies is removed.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies and data about the removed contact.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
/// <param name="contact">Removed contact data.</param>
public delegate void ContactRemovedEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair, ContactData contact);
/// <summary>
/// Handles any special logic when a contact point between two bodies is removed.
/// Unlike ContactRemovedEventHandler, this will trigger at the time of contact removal instead of at the end of the space's update.
/// Performing operations outside of the scope of the controller is unsafe.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies and data about the removed contact.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
/// <param name="contact">Contact between the two entries. This reference cannot be safely kept outside of the scope of the handler;
/// it will be immediately returned to the resource pool after the event handler completes.</param>
public delegate void RemovingContactEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair, Contact contact);
/// <summary>
/// Handles any special logic when two bodies go from a touching state to a separated state.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair overseeing the collision. Note that this instance may be invalid if the entries' bounding boxes no longer overlap.</param>
public delegate void CollisionEndedEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair);
/// <summary>
/// Handles any special logic when two bodies go from a touching state to a separated state.
/// Unlike CollisionEndedEventHandler, this will trigger at the time of contact removal instead of at the end of the space's update.
/// Performing operations outside of the scope of the controller is unsafe.
/// </summary>
/// <param name="sender">Entry sending the event.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair presiding over the interaction of the two involved bodies.
/// This reference cannot be safely kept outside of the scope of the handler; pairs can quickly return to the resource pool.</param>
public delegate void CollisionEndingEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair);
/// <summary>
/// Handles any special logic to perform at the end of a pair's UpdateContactManifold method.
/// This is called every single update regardless if the process was quit early or did not complete due to interaction rules.
/// </summary>
/// <param name="sender">Entry involved in the pair monitored for events.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair that was updated.</param>
public delegate void PairUpdatedEventHandler<T>(T sender, BroadPhaseEntry other, NarrowPhasePair pair);
/// <summary>
/// Handles any special logic to perform at the end of a pair's UpdateContactManifold method.
/// This is called every single update regardless if the process was quit early or did not complete due to interaction rules.
/// Unlike PairUpdatedEventHandler, this is called at the time of the collision detection update rather than at the end of the space's update.
/// Other entries' information may not be up to date, and operations acting on data outside of the character controller may be unsafe.
/// </summary>
/// <param name="sender">Entry involved in the pair monitored for events.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair that was updated.</param>
public delegate void PairUpdatingEventHandler<T>(T sender, BroadPhaseEntry other, NarrowPhasePair pair);
/// <summary>
/// Handles any special logic to perform at the end of a pair's UpdateContactManifold method if the two objects are colliding.
/// This is called every single update regardless if the process was quit early or did not complete due to interaction rules.
/// </summary>
/// <param name="sender">Entry involved in the pair monitored for events.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair that was updated.</param>
public delegate void PairTouchedEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair);
/// <summary>
/// Handles any special logic to perform at the end of a pair's UpdateContactManifold method if the two objects are colliding.
/// This is called every single update regardless if the process was quit early or did not complete due to interaction rules.
/// Unlike PairTouchedEventHandler, this is called at the time of the collision detection update rather than at the end of the space's update.
/// Other entries' information may not be up to date, and operations acting on data outside of the character controller may be unsafe.
/// </summary>
/// <param name="sender">Entry involved in the pair monitored for events.</param>
/// <param name="other">Other entry within the pair opposing the monitored entry.</param>
/// <param name="pair">Pair that was updated.</param>
public delegate void PairTouchingEventHandler<T>(T sender, Collidable other, CollidablePairHandler pair);
//Storage for deferred event dispatching
internal struct EventStoragePairCreated
{
internal NarrowPhasePair pair;
internal BroadPhaseEntry other;
internal EventStoragePairCreated(BroadPhaseEntry other, NarrowPhasePair pair)
{
this.other = other;
this.pair = pair;
}
}
internal struct EventStoragePairRemoved
{
internal BroadPhaseEntry other;
internal EventStoragePairRemoved(BroadPhaseEntry other)
{
this.other = other;
}
}
internal struct EventStorageContactCreated
{
internal CollidablePairHandler pair;
internal ContactData contactData;
internal Collidable other;
internal EventStorageContactCreated(Collidable other, CollidablePairHandler pair, ref ContactData contactData)
{
this.other = other;
this.pair = pair;
this.contactData = contactData;
}
}
internal struct EventStorageInitialCollisionDetected
{
internal CollidablePairHandler pair;
internal Collidable other;
internal EventStorageInitialCollisionDetected(Collidable other, CollidablePairHandler pair)
{
this.pair = pair;
this.other = other;
}
}
internal struct EventStorageContactRemoved
{
internal CollidablePairHandler pair;
internal ContactData contactData;
internal Collidable other;
internal EventStorageContactRemoved(Collidable other, CollidablePairHandler pair, ref ContactData contactData)
{
this.other = other;
this.pair = pair;
this.contactData = contactData;
}
}
internal struct EventStorageCollisionEnded
{
internal CollidablePairHandler pair;
internal Collidable other;
internal EventStorageCollisionEnded(Collidable other, CollidablePairHandler pair)
{
this.other = other;
this.pair = pair;
}
}
internal struct EventStoragePairUpdated
{
internal NarrowPhasePair pair;
internal BroadPhaseEntry other;
internal EventStoragePairUpdated(BroadPhaseEntry other, NarrowPhasePair pair)
{
this.other = other;
this.pair = pair;
}
}
internal struct EventStoragePairTouched
{
internal CollidablePairHandler pair;
internal Collidable other;
internal EventStoragePairTouched(Collidable other, CollidablePairHandler pair)
{
this.other = other;
this.pair = pair;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78a3e4df21efa0c488e88d97443d9176
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics.BroadPhaseEntries.MobileCollidables;
using BEPUphysics.OtherSpaceStages;
namespace BEPUphysics.BroadPhaseEntries.Events
{
/// <summary>
/// Event manager for use with the CompoundCollidable.
/// It's possible to use the ContactEventManager directly with a compound,
/// but without using this class, any child event managers will fail to dispatch
/// deferred events.
/// </summary>
public class CompoundEventManager : ContactEventManager<EntityCollidable>
{
protected override void DispatchEvents()
{
//Go through all children and dispatch events.
//They won't be touched by the primary event manager otherwise.
var compound = this.owner as CompoundCollidable;
if (compound != null)
{
foreach (var child in compound.children)
{
var deferredEventCreator = child.CollisionInformation.events as IDeferredEventCreator;
if (deferredEventCreator.IsActive)
deferredEventCreator.DispatchEvents();
}
}
else
{
throw new InvalidOperationException("Cannot use a CompoundEventManager with anything but a CompoundCollidable.");
}
base.DispatchEvents();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1ff98793e18bf84b82ddd4b821dc06c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,292 @@
using BEPUutilities;
using BEPUphysics.CollisionTests;
using BEPUphysics.NarrowPhaseSystems.Pairs;
using BEPUutilities.DataStructures;
namespace BEPUphysics.BroadPhaseEntries.Events
{
///<summary>
/// Event manager for collidables (things which can create contact points).
///</summary>
///<typeparam name="T">Some Collidable subclass.</typeparam>
public class ContactEventManager<T> : EntryEventManager<T>, IContactEventTriggerer where T : Collidable
{
/// <summary>
/// Creates a new contact event manager with the given owner.
/// </summary>
/// <param name="owner">New owner of the contact event manager.</param>
public ContactEventManager(T owner = null)
{
Owner = owner;
}
#region Events
/// <summary>
/// Fires when the entity stops touching another entity.
/// </summary>
public event CollisionEndedEventHandler<T> CollisionEnded
{
add
{
InternalCollisionEnded += value;
AddToEventfuls();
}
remove
{
InternalCollisionEnded -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when the entity stops touching another entity.
/// Unlike the CollisionEnded event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event CollisionEndingEventHandler<T> CollisionEnding;
/// <summary>
/// Fires when a pair is updated and there are contact points in it.
/// </summary>
public event PairTouchedEventHandler<T> PairTouched
{
add
{
InternalPairTouched += value;
AddToEventfuls();
}
remove
{
InternalPairTouched -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when a pair is updated and there are contact points in it.
/// Unlike the PairTouched event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event PairTouchingEventHandler<T> PairTouching;
/// <summary>
/// Fires when this entity gains a contact point with another entity.
/// </summary>
public event ContactCreatedEventHandler<T> ContactCreated
{
add
{
InternalContactCreated += value;
AddToEventfuls();
}
remove
{
InternalContactCreated -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when this entity loses a contact point with another entity.
/// </summary>
public event ContactRemovedEventHandler<T> ContactRemoved
{
add
{
InternalContactRemoved += value;
AddToEventfuls();
}
remove
{
InternalContactRemoved -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when this entity gains a contact point with another entity.
/// Unlike the ContactCreated event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event CreatingContactEventHandler<T> CreatingContact;
/// <summary>
/// Fires when a collision first occurs.
/// Unlike the InitialCollisionDetected event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event DetectingInitialCollisionEventHandler<T> DetectingInitialCollision;
/// <summary>
/// Fires when a collision first occurs.
/// </summary>
public event InitialCollisionDetectedEventHandler<T> InitialCollisionDetected
{
add
{
InternalInitialCollisionDetected += value;
AddToEventfuls();
}
remove
{
InternalInitialCollisionDetected -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when this entity loses a contact point with another entity.
/// Unlike the ContactRemoved event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event RemovingContactEventHandler<T> RemovingContact;
private event CollisionEndedEventHandler<T> InternalCollisionEnded;
private event PairTouchedEventHandler<T> InternalPairTouched;
private event ContactCreatedEventHandler<T> InternalContactCreated;
private event ContactRemovedEventHandler<T> InternalContactRemoved;
private event InitialCollisionDetectedEventHandler<T> InternalInitialCollisionDetected;
#endregion
#region Supporting members
protected override bool EventsAreInactive()
{
return InternalCollisionEnded == null &&
InternalPairTouched == null &&
InternalContactCreated == null &&
InternalContactRemoved == null &&
InternalInitialCollisionDetected == null &&
base.EventsAreInactive();
}
readonly ConcurrentDeque<EventStorageContactCreated> eventStorageContactCreated = new ConcurrentDeque<EventStorageContactCreated>(0);
readonly ConcurrentDeque<EventStorageInitialCollisionDetected> eventStorageInitialCollisionDetected = new ConcurrentDeque<EventStorageInitialCollisionDetected>(0);
readonly ConcurrentDeque<EventStorageContactRemoved> eventStorageContactRemoved = new ConcurrentDeque<EventStorageContactRemoved>(0);
readonly ConcurrentDeque<EventStorageCollisionEnded> eventStorageCollisionEnded = new ConcurrentDeque<EventStorageCollisionEnded>(0);
readonly ConcurrentDeque<EventStoragePairTouched> eventStoragePairTouched = new ConcurrentDeque<EventStoragePairTouched>(0);
protected override void DispatchEvents()
{
//Note: Deferred event creation should be performed sequentially with dispatching.
//This means a event creation from this creator cannot occur ASYNCHRONOUSLY while DispatchEvents is running.
//Note: If the deferred event handler is removed during the execution of the engine, the handler may be null.
//In this situation, ignore the event.
//This is not a particularly clean behavior, but it's better than just crashing.
EventStorageContactCreated contactCreated;
while (eventStorageContactCreated.TryUnsafeDequeueFirst(out contactCreated))
if (InternalContactCreated != null)
InternalContactCreated(owner, contactCreated.other, contactCreated.pair, contactCreated.contactData);
EventStorageInitialCollisionDetected initialCollisionDetected;
while (eventStorageInitialCollisionDetected.TryUnsafeDequeueFirst(out initialCollisionDetected))
if (InternalInitialCollisionDetected != null)
InternalInitialCollisionDetected(owner, initialCollisionDetected.other, initialCollisionDetected.pair);
EventStorageContactRemoved contactRemoved;
while (eventStorageContactRemoved.TryUnsafeDequeueFirst(out contactRemoved))
if (InternalContactRemoved != null)
InternalContactRemoved(owner, contactRemoved.other, contactRemoved.pair, contactRemoved.contactData);
EventStorageCollisionEnded collisionEnded;
while (eventStorageCollisionEnded.TryUnsafeDequeueFirst(out collisionEnded))
if (InternalCollisionEnded != null)
InternalCollisionEnded(owner, collisionEnded.other, collisionEnded.pair);
EventStoragePairTouched collisionPairTouched;
while (eventStoragePairTouched.TryUnsafeDequeueFirst(out collisionPairTouched))
if (InternalPairTouched != null)
InternalPairTouched(owner, collisionPairTouched.other, collisionPairTouched.pair);
base.DispatchEvents();
}
public void OnCollisionEnded(Collidable other, CollidablePairHandler collisionPair)
{
if (InternalCollisionEnded != null)
eventStorageCollisionEnded.Enqueue(new EventStorageCollisionEnded(other, collisionPair));
if (CollisionEnding != null)
CollisionEnding(owner, other, collisionPair);
}
public void OnPairTouching(Collidable other, CollidablePairHandler collisionPair)
{
if (InternalPairTouched != null)
eventStoragePairTouched.Enqueue(new EventStoragePairTouched(other, collisionPair));
if (PairTouching != null)
PairTouching(owner, other, collisionPair);
}
public void OnContactCreated(Collidable other, CollidablePairHandler collisionPair, Contact contact)
{
if (InternalContactCreated != null)
{
ContactData contactData;
contactData.Position = contact.Position;
contactData.Normal = contact.Normal;
contactData.PenetrationDepth = contact.PenetrationDepth;
contactData.Id = contact.Id;
eventStorageContactCreated.Enqueue(new EventStorageContactCreated(other, collisionPair, ref contactData));
}
if (CreatingContact != null)
CreatingContact(owner, other, collisionPair, contact);
}
public void OnContactRemoved(Collidable other, CollidablePairHandler collisionPair, Contact contact)
{
if (InternalContactRemoved != null)
{
ContactData contactData;
contactData.Position = contact.Position;
contactData.Normal = contact.Normal;
contactData.PenetrationDepth = contact.PenetrationDepth;
contactData.Id = contact.Id;
eventStorageContactRemoved.Enqueue(new EventStorageContactRemoved(other, collisionPair, ref contactData));
}
if (RemovingContact != null)
RemovingContact(owner, other, collisionPair, contact);
}
public void OnInitialCollisionDetected(Collidable other, CollidablePairHandler collisionPair)
{
if (InternalInitialCollisionDetected != null)
eventStorageInitialCollisionDetected.Enqueue(new EventStorageInitialCollisionDetected(other, collisionPair));
if (DetectingInitialCollision != null)
DetectingInitialCollision(owner, other, collisionPair);
}
///<summary>
/// Removes all event hooks from the event manager.
///</summary>
public override void RemoveAllEvents()
{
InternalCollisionEnded = null;
InternalPairTouched = null;
InternalContactCreated = null;
InternalContactRemoved = null;
InternalInitialCollisionDetected = null;
CollisionEnding = null;
DetectingInitialCollision = null;
CreatingContact = null;
RemovingContact = null;
PairTouching = null;
base.RemoveAllEvents();
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38c65ea1012b4884d9ce0d2f43c98368
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,332 @@
using BEPUphysics.BroadPhaseEntries;
using BEPUphysics.BroadPhaseSystems;
using BEPUphysics.NarrowPhaseSystems.Pairs;
using BEPUphysics.OtherSpaceStages;
using BEPUutilities;
using System;
using BEPUutilities.DataStructures;
namespace BEPUphysics.BroadPhaseEntries.Events
{
///<summary>
/// Event manager for BroadPhaseEntries (all types that live in the broad phase).
///</summary>
///<typeparam name="T">Some BroadPhaseEntry subclass.</typeparam>
public class EntryEventManager<T> : IDeferredEventCreator, IEntryEventTriggerer where T : BroadPhaseEntry
{
protected internal int childDeferredEventCreators;
/// <summary>
/// Number of child deferred event creators.
/// </summary>
int IDeferredEventCreator.ChildDeferredEventCreators
{
get
{
return childDeferredEventCreators;
}
set
{
int previousValue = childDeferredEventCreators;
childDeferredEventCreators = value;
if (childDeferredEventCreators == 0 && previousValue != 0)
{
//Deactivate!
if (EventsAreInactive())
{
//The events are inactive method tests to see if this event manager
//has any events that need to be deferred.
//If we get here, that means that there's zero children active, and we aren't active...
((IDeferredEventCreator)this).IsActive = false;
}
}
else if (childDeferredEventCreators != 0 && previousValue == 0)
{
//Activate!
//It doesn't matter if there are any events active in this instance, just try to activate anyway.
//If it is already active, nothing will happen.
((IDeferredEventCreator)this).IsActive = true;
}
}
}
private CompoundEventManager parent;
/// <summary>
/// The parent of the event manager, if any.
/// </summary>
protected internal CompoundEventManager Parent
{
get
{
return parent;
}
set
{
//The child deferred event creator links must be maintained.
if (parent != null && isActive)
((IDeferredEventCreator)parent).ChildDeferredEventCreators--;
parent = value;
if (parent != null && isActive)
((IDeferredEventCreator)parent).ChildDeferredEventCreators++;
}
}
protected internal T owner;
///<summary>
/// Owner of the event manager.
///</summary>
public T Owner
{
get
{
return owner;
}
protected internal set
{
owner = value;
}
}
#region Events
/// <summary>
/// Fires when this entity's bounding box newly overlaps another entity's bounding box.
/// </summary>
public event PairCreatedEventHandler<T> PairCreated
{
add
{
InternalPairCreated += value;
AddToEventfuls();
}
remove
{
InternalPairCreated -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when this entity's bounding box no longer overlaps another entity's bounding box.
/// </summary>
public event PairRemovedEventHandler<T> PairRemoved
{
add
{
InternalPairRemoved += value;
AddToEventfuls();
}
remove
{
InternalPairRemoved -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when a pair is updated.
/// </summary>
public event PairUpdatedEventHandler<T> PairUpdated
{
add
{
InternalPairUpdated += value;
AddToEventfuls();
}
remove
{
InternalPairUpdated -= value;
VerifyEventStatus();
}
}
/// <summary>
/// Fires when a pair is updated.
/// Unlike the PairUpdated event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event PairUpdatingEventHandler<T> PairUpdating;
/// <summary>
/// Fires when this entity's bounding box newly overlaps another entity's bounding box.
/// Unlike the PairCreated event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event CreatingPairEventHandler<T> CreatingPair;
/// <summary>
/// Fires when this entity's bounding box no longer overlaps another entity's bounding box.
/// Unlike the PairRemoved event, this event will run inline instead of at the end of the space's update.
/// Some operations are unsupported while the engine is updating, and be especially careful if internal multithreading is enabled.
/// </summary>
public event RemovingPairEventHandler<T> RemovingPair;
private event PairCreatedEventHandler<T> InternalPairCreated;
private event PairRemovedEventHandler<T> InternalPairRemoved;
private event PairUpdatedEventHandler<T> InternalPairUpdated;
#endregion
#region Supporting members
/// <summary>
/// Removes the entity from the space's list of eventful entities if no events are active.
/// </summary>
protected void VerifyEventStatus()
{
if (EventsAreInactive() && childDeferredEventCreators == 0)
{
((IDeferredEventCreator)this).IsActive = false;
}
}
protected virtual bool EventsAreInactive()
{
return InternalPairCreated == null &&
InternalPairRemoved == null &&
InternalPairUpdated == null;
}
protected void AddToEventfuls()
{
((IDeferredEventCreator)this).IsActive = true;
}
private DeferredEventDispatcher deferredEventDispatcher;
DeferredEventDispatcher IDeferredEventCreator.DeferredEventDispatcher
{
get
{
return deferredEventDispatcher;
}
set
{
deferredEventDispatcher = value;
}
}
readonly ConcurrentDeque<EventStoragePairCreated> eventStoragePairCreated = new ConcurrentDeque<EventStoragePairCreated>(0);
readonly ConcurrentDeque<EventStoragePairRemoved> eventStoragePairRemoved = new ConcurrentDeque<EventStoragePairRemoved>(0);
readonly ConcurrentDeque<EventStoragePairUpdated> eventStoragePairUpdated = new ConcurrentDeque<EventStoragePairUpdated>(0);
void IDeferredEventCreator.DispatchEvents()
{
DispatchEvents();
}
protected virtual void DispatchEvents()
{
//Note: Deferred event creation should be performed sequentially with dispatching.
//This means a event creation from this creator cannot occur ASYNCHRONOUSLY while DispatchEvents is running.
//Note: If the deferred event handler is removed during the execution of the engine, the handler may be null.
//In this situation, ignore the event.
//This is not a particularly clean behavior, but it's better than just crashing.
EventStoragePairCreated collisionPairCreated;
while (eventStoragePairCreated.TryUnsafeDequeueFirst(out collisionPairCreated))
if (InternalPairCreated != null)
InternalPairCreated(owner, collisionPairCreated.other, collisionPairCreated.pair);
EventStoragePairRemoved collisionPairRemoved;
while (eventStoragePairRemoved.TryUnsafeDequeueFirst(out collisionPairRemoved))
if (InternalPairRemoved != null)
InternalPairRemoved(owner, collisionPairRemoved.other);
EventStoragePairUpdated collisionPairUpdated;
while (eventStoragePairUpdated.TryUnsafeDequeueFirst(out collisionPairUpdated))
if (InternalPairUpdated != null)
InternalPairUpdated(owner, collisionPairUpdated.other, collisionPairUpdated.pair);
}
public void OnPairCreated(BroadPhaseEntry other, NarrowPhasePair collisionPair)
{
if (InternalPairCreated != null)
eventStoragePairCreated.Enqueue(new EventStoragePairCreated(other, collisionPair));
if (CreatingPair != null)
CreatingPair(owner, other, collisionPair);
}
public void OnPairRemoved(BroadPhaseEntry other)
{
if (InternalPairRemoved != null)
{
eventStoragePairRemoved.Enqueue(new EventStoragePairRemoved(other));
}
if (RemovingPair != null)
{
RemovingPair(owner, other);
}
}
public void OnPairUpdated(BroadPhaseEntry other, NarrowPhasePair collisionPair)
{
if (InternalPairUpdated != null)
eventStoragePairUpdated.Enqueue(new EventStoragePairUpdated(other, collisionPair));
if (PairUpdating != null)
PairUpdating(owner, other, collisionPair);
}
private bool isActive;
//IsActive is enabled whenever this collision information can dispatch events.
bool IDeferredEventCreator.IsActive
{
get
{
return isActive;
}
set
{
if (!isActive && value)
{
isActive = true;
//Notify the parent that it needs to activate.
if (parent != null)
((IDeferredEventCreator)parent).ChildDeferredEventCreators++;
if (deferredEventDispatcher != null)
deferredEventDispatcher.CreatorActivityChanged(this);
}
else if (isActive && !value)
{
isActive = false;
//Notify the parent that it can deactivate.
if (parent != null)
((IDeferredEventCreator)parent).ChildDeferredEventCreators--;
if (deferredEventDispatcher != null)
deferredEventDispatcher.CreatorActivityChanged(this);
}
}
}
///<summary>
/// Removes all event hooks from the manager.
///</summary>
public virtual void RemoveAllEvents()
{
PairUpdating = null;
CreatingPair = null;
RemovingPair = null;
InternalPairCreated = null;
InternalPairRemoved = null;
InternalPairUpdated = null;
VerifyEventStatus();
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d0ddf42f55e2dc40b84dcd45f044d82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics.BroadPhaseSystems;
using BEPUphysics.NarrowPhaseSystems.Pairs;
using BEPUphysics.CollisionTests;
namespace BEPUphysics.BroadPhaseEntries.Events
{
/// <summary>
/// Manages triggers for events in an ContactEventManager.
/// </summary>
public interface IContactEventTriggerer : IEntryEventTriggerer
{
/// <summary>
/// Fires collision ending events.
/// </summary>
/// <param name="other">Other collidable involved in the pair.</param>
/// <param name="collisionPair">Collidable pair handler that manages the two objects.</param>
void OnCollisionEnded(Collidable other, CollidablePairHandler collisionPair);
/// <summary>
/// Fires pair touching events.
/// </summary>
/// <param name="other">Other collidable involved in the pair.</param>
/// <param name="collisionPair">Collidable pair handler that manages the two objects.</param>
void OnPairTouching(Collidable other, CollidablePairHandler collisionPair);
/// <summary>
/// Fires contact creating events.
/// </summary>
/// <param name="other">Other collidable involved in the pair.</param>
/// <param name="collisionPair">Collidable pair handler that manages the two objects.</param>
/// <param name="contact">Contact point of collision.</param>
void OnContactCreated(Collidable other, CollidablePairHandler collisionPair, Contact contact);
/// <summary>
/// Fires contact removal events.
/// </summary>
/// <param name="other">Other collidable involved in the pair.</param>
/// <param name="collisionPair">Collidable pair handler that manages the two objects.</param>
/// <param name="contact">Contact point of collision.</param>
void OnContactRemoved(Collidable other, CollidablePairHandler collisionPair, Contact contact);
/// <summary>
/// Fires initial collision detected events.
/// </summary>
/// <param name="other">Other collidable involved in the pair.</param>
/// <param name="collisionPair">Collidable pair handler that manages the two objects.</param>
void OnInitialCollisionDetected(Collidable other, CollidablePairHandler collisionPair);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aff232f13179ae641ae4c42a977b0403
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics.BroadPhaseEntries;
using BEPUphysics.BroadPhaseSystems;
using BEPUphysics.NarrowPhaseSystems.Pairs;
namespace BEPUphysics.BroadPhaseEntries.Events
{
/// <summary>
/// Manages triggers for events in an EntryEventManager.
/// </summary>
public interface IEntryEventTriggerer
{
/// <summary>
/// Fires the event manager's pair creation events.
/// </summary>
/// <param name="other">Other entry involved in the pair.</param>
/// <param name="collisionPair">Narrow phase pair governing the two objects.</param>
void OnPairCreated(BroadPhaseEntry other, NarrowPhasePair collisionPair);
/// <summary>
/// Fires the event manager's pair removal events.
/// </summary>
/// <param name="other">Other entry involved in the pair.</param>
void OnPairRemoved(BroadPhaseEntry other);
/// <summary>
/// Fires the event manager's pair updated events.
/// </summary>
/// <param name="other">Other entry involved in the pair.</param>
/// <param name="collisionPair">Narrow phase pair governing the two objects.</param>
void OnPairUpdated(BroadPhaseEntry other, NarrowPhasePair collisionPair);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7552237bf8f62184e8a0a4d33a36d751
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,252 @@
using System;
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.CollisionShapes;
using BEPUutilities;
using BEPUutilities.ResourceManagement;
using BEPUphysics.CollisionTests.CollisionAlgorithms;
using BEPUphysics.OtherSpaceStages;
using RigidTransform = BEPUutilities.RigidTransform;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Collidable mesh which can be created from a reusable InstancedMeshShape.
/// Very little data is needed for each individual InstancedMesh object, allowing
/// a complicated mesh to be repeated many times. Since the hierarchy used to accelerate
/// collisions is purely local, it may be marginally slower than an individual StaticMesh.
///</summary>
public class InstancedMesh : StaticCollidable
{
internal AffineTransform worldTransform;
///<summary>
/// Gets or sets the world transform of the mesh.
///</summary>
public AffineTransform WorldTransform
{
get
{
return worldTransform;
}
set
{
worldTransform = value;
Shape.ComputeBoundingBox(ref value, out boundingBox);
}
}
/// <summary>
/// Updates the bounding box to the current state of the entry.
/// </summary>
public override void UpdateBoundingBox()
{
Shape.ComputeBoundingBox(ref worldTransform, out boundingBox);
}
///<summary>
/// Constructs a new InstancedMesh.
///</summary>
///<param name="meshShape">Shape to use for the instance.</param>
public InstancedMesh(InstancedMeshShape meshShape)
: this(meshShape, AffineTransform.Identity)
{
}
///<summary>
/// Constructs a new InstancedMesh.
///</summary>
///<param name="meshShape">Shape to use for the instance.</param>
///<param name="worldTransform">Transform to use for the instance.</param>
public InstancedMesh(InstancedMeshShape meshShape, AffineTransform worldTransform)
{
this.worldTransform = worldTransform;
base.Shape = meshShape;
Events = new ContactEventManager<InstancedMesh>();
}
///<summary>
/// Gets the shape used by the instanced mesh.
///</summary>
public new InstancedMeshShape Shape
{
get
{
return (InstancedMeshShape)shape;
}
}
internal TriangleSidedness sidedness = TriangleSidedness.DoubleSided;
///<summary>
/// Gets or sets the sidedness of the mesh. This can be used to ignore collisions and rays coming from a direction relative to the winding of the triangle.
///</summary>
public TriangleSidedness Sidedness
{
get
{
return sidedness;
}
set
{
sidedness = value;
}
}
internal bool improveBoundaryBehavior = true;
/// <summary>
/// Gets or sets whether or not the collision system should attempt to improve contact behavior at the boundaries between triangles.
/// This has a slight performance cost, but prevents objects sliding across a triangle boundary from 'bumping,' and otherwise improves
/// the robustness of contacts at edges and vertices.
/// </summary>
public bool ImproveBoundaryBehavior
{
get
{
return improveBoundaryBehavior;
}
set
{
improveBoundaryBehavior = value;
}
}
protected internal ContactEventManager<InstancedMesh> events;
///<summary>
/// Gets the event manager of the mesh.
///</summary>
public ContactEventManager<InstancedMesh> Events
{
get
{
return events;
}
set
{
if (value.Owner != null && //Can't use a manager which is owned by a different entity.
value != events) //Stay quiet if for some reason the same event manager is being set.
throw new ArgumentException("Event manager is already owned by a mesh; event managers cannot be shared.");
if (events != null)
events.Owner = null;
events = value;
if (events != null)
events.Owner = this;
}
}
protected internal override IContactEventTriggerer EventTriggerer
{
get { return events; }
}
protected override IDeferredEventCreator EventCreator
{
get
{
return events;
}
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
return RayCast(ray, maximumLength, sidedness, out rayHit);
}
///<summary>
/// Tests a ray against the instance.
///</summary>
///<param name="ray">Ray to test.</param>
///<param name="maximumLength">Maximum length of the ray to test; in units of the ray's direction's length.</param>
///<param name="sidedness">Sidedness to use during the ray cast. This does not have to be the same as the mesh's sidedness.</param>
///<param name="rayHit">The hit location of the ray on the mesh, if any.</param>
///<returns>Whether or not the ray hit the mesh.</returns>
public bool RayCast(Ray ray, float maximumLength, TriangleSidedness sidedness, out RayHit rayHit)
{
//Put the ray into local space.
Ray localRay;
AffineTransform inverse;
AffineTransform.Invert(ref worldTransform, out inverse);
Matrix3x3.Transform(ref ray.Direction, ref inverse.LinearTransform, out localRay.Direction);
AffineTransform.Transform(ref ray.Position, ref inverse, out localRay.Position);
if (Shape.TriangleMesh.RayCast(localRay, maximumLength, sidedness, out rayHit))
{
//Transform the hit into world space.
Vector3.Multiply(ref ray.Direction, rayHit.T, out rayHit.Location);
Vector3.Add(ref rayHit.Location, ref ray.Position, out rayHit.Location);
Matrix3x3.TransformTranspose(ref rayHit.Normal, ref inverse.LinearTransform, out rayHit.Normal);
return true;
}
rayHit = new RayHit();
return false;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="hit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit)
{
hit = new RayHit();
BoundingBox boundingBox;
castShape.GetSweptLocalBoundingBox(ref startingTransform, ref worldTransform, ref sweep, out boundingBox);
var tri = PhysicsThreadResources.GetTriangle();
var hitElements = CommonResources.GetIntList();
if (this.Shape.TriangleMesh.Tree.GetOverlaps(boundingBox, hitElements))
{
hit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
Shape.TriangleMesh.Data.GetTriangle(hitElements[i], out tri.vA, out tri.vB, out tri.vC);
AffineTransform.Transform(ref tri.vA, ref worldTransform, out tri.vA);
AffineTransform.Transform(ref tri.vB, ref worldTransform, out tri.vB);
AffineTransform.Transform(ref tri.vC, ref worldTransform, out tri.vC);
Vector3 center;
Vector3.Add(ref tri.vA, ref tri.vB, out center);
Vector3.Add(ref center, ref tri.vC, out center);
Vector3.Multiply(ref center, 1f / 3f, out center);
Vector3.Subtract(ref tri.vA, ref center, out tri.vA);
Vector3.Subtract(ref tri.vB, ref center, out tri.vB);
Vector3.Subtract(ref tri.vC, ref center, out tri.vC);
tri.MaximumRadius = tri.vA.LengthSquared();
float radius = tri.vB.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
radius = tri.vC.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
tri.MaximumRadius = (float)Math.Sqrt(tri.MaximumRadius);
tri.collisionMargin = 0;
var triangleTransform = new RigidTransform { Orientation = Quaternion.Identity, Position = center };
RayHit tempHit;
if (MPRToolbox.Sweep(castShape, tri, ref sweep, ref Toolbox.ZeroVector, ref startingTransform, ref triangleTransform, out tempHit) && tempHit.T < hit.T)
{
hit = tempHit;
}
}
tri.MaximumRadius = 0;
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return hit.T != float.MaxValue;
}
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return false;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a31964f084a491b49ab61f2fadb8d1e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0013cc2c03be2e14db4ff3dce09dbb85
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,624 @@
using System.Collections.Generic;
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.BroadPhaseSystems;
using BEPUphysics.CollisionShapes;
using BEPUutilities;
using BEPUutilities.DataStructures;
using BEPUphysics.Materials;
using BEPUphysics.CollisionRuleManagement;
using System;
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
///<summary>
/// Collidable used by compound shapes.
///</summary>
public class CompoundCollidable : EntityCollidable
{
/// <summary>
/// Gets or sets the event manager for the collidable.
/// Compound collidables must use a special CompoundEventManager in order for the deferred events created
/// by child collidables to be dispatched.
/// If this method is bypassed and a different event manager is used, this method will return null and
/// deferred events from children will fail.
/// </summary>
public new CompoundEventManager Events
{
get
{
return events as CompoundEventManager;
}
set
{
//Tell every child to update their parent references to the new object.
foreach (var child in children)
{
child.CollisionInformation.events.Parent = value;
}
base.Events = value;
}
}
///<summary>
/// Gets the shape of the collidable.
///</summary>
public new CompoundShape Shape
{
get
{
return (CompoundShape)shape;
}
protected internal set
{
base.Shape = value;
}
}
internal RawList<CompoundChild> children = new RawList<CompoundChild>();
///<summary>
/// Gets a list of the children in the collidable.
///</summary>
public ReadOnlyList<CompoundChild> Children
{
get
{
return new ReadOnlyList<CompoundChild>(children);
}
}
protected override void OnEntityChanged()
{
for (int i = 0; i < children.Count; i++)
{
children.Elements[i].CollisionInformation.Entity = entity;
}
base.OnEntityChanged();
}
private CompoundChild GetChild(CompoundChildData data, int index)
{
var instance = data.Entry.Shape.GetCollidableInstance();
if (data.Events != null)
instance.Events = data.Events;
//Establish the link between the child event manager and our event manager.
instance.events.Parent = Events;
if (data.CollisionRules != null)
instance.CollisionRules = data.CollisionRules;
instance.Tag = data.Tag;
if (data.Material == null)
data.Material = new Material();
return new CompoundChild(Shape, instance, data.Material, index);
}
private CompoundChild GetChild(CompoundShapeEntry entry, int index)
{
var instance = entry.Shape.GetCollidableInstance();
//Establish the link between the child event manager and our event manager.
instance.events.Parent = Events;
return new CompoundChild(Shape, instance, index);
}
//Used to efficiently split compounds.
internal CompoundCollidable()
{
Events = new CompoundEventManager();
hierarchy = new CompoundHierarchy(this);
}
///<summary>
/// Constructs a compound collidable using additional information about the shapes in the compound.
///</summary>
///<param name="children">Data representing the children of the compound collidable.</param>
public CompoundCollidable(IList<CompoundChildData> children)
{
Events = new CompoundEventManager();
var shapeList = new RawList<CompoundShapeEntry>();
//Create the shape first.
for (int i = 0; i < children.Count; i++)
{
shapeList.Add(children[i].Entry);
}
base.Shape = new CompoundShape(shapeList);
//Now create the actual child objects.
for (int i = 0; i < children.Count; i++)
{
this.children.Add(GetChild(children[i], i));
}
hierarchy = new CompoundHierarchy(this);
}
///<summary>
/// Constructs a compound collidable using additional information about the shapes in the compound.
///</summary>
///<param name="children">Data representing the children of the compound collidable.</param>
///<param name="center">Location computed to be the center of the compound object.</param>
public CompoundCollidable(IList<CompoundChildData> children, out Vector3 center)
{
Events = new CompoundEventManager();
var shapeList = new RawList<CompoundShapeEntry>();
//Create the shape first.
for (int i = 0; i < children.Count; i++)
{
shapeList.Add(children[i].Entry);
}
base.Shape = new CompoundShape(shapeList, out center);
//Now create the actual child objects.
for (int i = 0; i < children.Count; i++)
{
this.children.Add(GetChild(children[i], i));
}
hierarchy = new CompoundHierarchy(this);
}
///<summary>
/// Constructs a new CompoundCollidable.
///</summary>
///<param name="compoundShape">Compound shape to use for the collidable.</param>
public CompoundCollidable(CompoundShape compoundShape)
: base(compoundShape)
{
Events = new CompoundEventManager();
for (int i = 0; i < compoundShape.shapes.Count; i++)
{
CompoundChild child = GetChild(compoundShape.shapes.Elements[i], i);
this.children.Add(child);
}
hierarchy = new CompoundHierarchy(this);
}
internal CompoundHierarchy hierarchy;
///<summary>
/// Gets the hierarchy of children used by the collidable.
///</summary>
public CompoundHierarchy Hierarchy
{
get
{
return hierarchy;
}
}
///<summary>
/// Updates the world transform of the collidable.
///</summary>
///<param name="position">Position to use for the calculation.</param>
///<param name="orientation">Orientation to use for the calculation.</param>
public override void UpdateWorldTransform(ref Vector3 position, ref Quaternion orientation)
{
base.UpdateWorldTransform(ref position, ref orientation);
var shapeList = Shape.shapes;
for (int i = 0; i < children.Count; i++)
{
RigidTransform transform;
RigidTransform.Multiply(ref shapeList.Elements[children.Elements[i].shapeIndex].LocalTransform, ref worldTransform, out transform);
children.Elements[i].CollisionInformation.UpdateWorldTransform(ref transform.Position, ref transform.Orientation);
}
}
protected internal override void UpdateBoundingBoxInternal(float dt)
{
for (int i = 0; i < children.Count; i++)
{
children.Elements[i].CollisionInformation.UpdateBoundingBoxInternal(dt);
}
hierarchy.Tree.Refit();
boundingBox = hierarchy.Tree.BoundingBox;
}
/// <summary>
/// Tests a ray against the collidable.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the collidable, if any.</param>
/// <returns>Whether or not the ray hit the collidable.</returns>
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
CompoundChild hitChild;
bool hit = RayCast(ray, maximumLength, out rayHit, out hitChild);
return hit;
}
/// <summary>
/// Tests a ray against the compound.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit data and the hit child collidable, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public bool RayCast(Ray ray, float maximumLength, out RayCastResult rayHit)
{
RayHit hitData;
CompoundChild hitChild;
bool hit = RayCast(ray, maximumLength, out hitData, out hitChild);
rayHit = new RayCastResult { HitData = hitData, HitObject = hitChild.CollisionInformation };
return hit;
}
/// <summary>
/// Tests a ray against the collidable.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit data, if any.</param>
/// <param name="hitChild">Child collidable hit by the ray, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public bool RayCast(Ray ray, float maximumLength, out RayHit rayHit, out CompoundChild hitChild)
{
rayHit = new RayHit();
hitChild = null;
var hitElements = PhysicsResources.GetCompoundChildList();
if (hierarchy.Tree.GetOverlaps(ray, maximumLength, hitElements))
{
rayHit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
EntityCollidable candidate = hitElements.Elements[i].CollisionInformation;
RayHit tempHit;
if (candidate.RayCast(ray, maximumLength, out tempHit) && tempHit.T < rayHit.T)
{
rayHit = tempHit;
hitChild = hitElements.Elements[i];
}
}
PhysicsResources.GiveBack(hitElements);
return rayHit.T != float.MaxValue;
}
PhysicsResources.GiveBack(hitElements);
return false;
}
/// <summary>
/// Tests a ray against the collidable.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="rayHit">Hit location of the ray on the collidable, if any.</param>
/// <returns>Whether or not the ray hit the collidable.</returns>
public override bool RayCast(Ray ray, float maximumLength, Func<BroadPhaseEntry, bool> filter, out RayHit rayHit)
{
CompoundChild hitChild;
bool hit = RayCast(ray, maximumLength, filter, out rayHit, out hitChild);
return hit;
}
/// <summary>
/// Tests a ray against the compound.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit data and the hit child collidable, if any.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public bool RayCast(Ray ray, float maximumLength, Func<BroadPhaseEntry, bool> filter, out RayCastResult rayHit)
{
RayHit hitData;
CompoundChild hitChild;
bool hit = RayCast(ray, maximumLength, filter, out hitData, out hitChild);
rayHit = new RayCastResult { HitData = hitData, HitObject = hitChild.CollisionInformation };
return hit;
}
/// <summary>
/// Tests a ray against the collidable.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="rayHit">Hit location of the ray on the collidable, if any.</param>
/// <param name="hitChild">Child hit by the ray.</param>
/// <returns>Whether or not the ray hit the collidable.</returns>
public bool RayCast(Ray ray, float maximumLength, Func<BroadPhaseEntry, bool> filter, out RayHit rayHit, out CompoundChild hitChild)
{
rayHit = new RayHit();
hitChild = null;
if (filter(this))
{
var hitElements = PhysicsResources.GetCompoundChildList();
if (hierarchy.Tree.GetOverlaps(ray, maximumLength, hitElements))
{
rayHit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
RayHit tempHit;
if (hitElements.Elements[i].CollisionInformation.RayCast(ray, maximumLength, filter, out tempHit) && tempHit.T < rayHit.T)
{
rayHit = tempHit;
hitChild = hitElements.Elements[i];
}
}
PhysicsResources.GiveBack(hitElements);
return rayHit.T != float.MaxValue;
}
PhysicsResources.GiveBack(hitElements);
}
return false;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="rayHit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit rayHit)
{
CompoundChild hitChild;
bool hit = ConvexCast(castShape, ref startingTransform, ref sweep, out rayHit, out hitChild);
return hit;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="result">Data and hit object from the first impact, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayCastResult result)
{
CompoundChild hitChild;
RayHit rayHit;
bool hit = ConvexCast(castShape, ref startingTransform, ref sweep, out rayHit, out hitChild);
result = new RayCastResult { HitData = rayHit, HitObject = hitChild.CollisionInformation };
return hit;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="hit">Hit data, if any.</param>
/// <param name="hitChild">Child hit by the cast.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit, out CompoundChild hitChild)
{
hit = new RayHit();
hitChild = null;
BoundingBox boundingBox;
castShape.GetSweptBoundingBox(ref startingTransform, ref sweep, out boundingBox);
var hitElements = PhysicsResources.GetCompoundChildList();
if (hierarchy.Tree.GetOverlaps(boundingBox, hitElements))
{
hit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
var candidate = hitElements.Elements[i].CollisionInformation;
RayHit tempHit;
if (candidate.ConvexCast(castShape, ref startingTransform, ref sweep, out tempHit) && tempHit.T < hit.T)
{
hit = tempHit;
hitChild = hitElements.Elements[i];
}
}
PhysicsResources.GiveBack(hitElements);
return hit.T != float.MaxValue;
}
PhysicsResources.GiveBack(hitElements);
return false;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="rayHit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, Func<BroadPhaseEntry, bool> filter, out RayHit rayHit)
{
CompoundChild hitChild;
bool hit = ConvexCast(castShape, ref startingTransform, ref sweep, filter, out rayHit, out hitChild);
return hit;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="result">Data and hit object from the first impact, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, Func<BroadPhaseEntry, bool> filter, out RayCastResult result)
{
CompoundChild hitChild;
RayHit rayHit;
bool hit = ConvexCast(castShape, ref startingTransform, ref sweep, filter, out rayHit, out hitChild);
result = new RayCastResult { HitData = rayHit, HitObject = hitChild.CollisionInformation };
return hit;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="hit">Hit data, if any.</param>
/// <param name="hitChild">Child hit by the cast.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, Func<BroadPhaseEntry, bool> filter, out RayHit hit, out CompoundChild hitChild)
{
hit = new RayHit();
hitChild = null;
if (!filter(this))
return false;
BoundingBox boundingBox;
castShape.GetSweptBoundingBox(ref startingTransform, ref sweep, out boundingBox);
var hitElements = PhysicsResources.GetCompoundChildList();
if (hierarchy.Tree.GetOverlaps(boundingBox, hitElements))
{
hit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
var candidate = hitElements.Elements[i].CollisionInformation;
RayHit tempHit;
if (candidate.ConvexCast(castShape, ref startingTransform, ref sweep, filter, out tempHit) && tempHit.T < hit.T)
{
hit = tempHit;
hitChild = hitElements.Elements[i];
}
}
PhysicsResources.GiveBack(hitElements);
return hit.T != float.MaxValue;
}
PhysicsResources.GiveBack(hitElements);
return false;
}
}
///<summary>
/// Data which can be used to create a CompoundChild.
/// This data is not itself a child yet; another system
/// will use it as input to construct the children.
///</summary>
public struct CompoundChildData
{
///<summary>
/// Shape entry of the compound child.
///</summary>
public CompoundShapeEntry Entry;
///<summary>
/// Event manager for the new child.
///</summary>
public ContactEventManager<EntityCollidable> Events;
///<summary>
/// Collision rules for the new child.
///</summary>
public CollisionRules CollisionRules;
///<summary>
/// Material for the new child.
///</summary>
public Material Material;
/// <summary>
/// Tag to assign to the collidable created for this child.
/// </summary>
public object Tag;
}
///<summary>
/// A collidable child of a compound.
///</summary>
public class CompoundChild : IBoundingBoxOwner
{
CompoundShape shape;
internal int shapeIndex;
/// <summary>
/// Gets the index of the shape used by this child in the CompoundShape's shapes list.
/// </summary>
public int ShapeIndex
{
get
{
return shapeIndex;
}
}
private EntityCollidable collisionInformation;
///<summary>
/// Gets the Collidable associated with the child.
///</summary>
public EntityCollidable CollisionInformation
{
get
{
return collisionInformation;
}
}
///<summary>
/// Gets or sets the material associated with the child.
///</summary>
public Material Material { get; set; }
/// <summary>
/// Gets the index of the shape associated with this child in the CompoundShape's shapes list.
/// </summary>
public CompoundShapeEntry Entry
{
get
{
return shape.shapes.Elements[shapeIndex];
}
}
internal CompoundChild(CompoundShape shape, EntityCollidable collisionInformation, Material material, int index)
{
this.shape = shape;
this.collisionInformation = collisionInformation;
Material = material;
this.shapeIndex = index;
}
internal CompoundChild(CompoundShape shape, EntityCollidable collisionInformation, int index)
{
this.shape = shape;
this.collisionInformation = collisionInformation;
this.shapeIndex = index;
}
/// <summary>
/// Gets the bounding box of the child.
/// </summary>
public BoundingBox BoundingBox
{
get { return collisionInformation.boundingBox; }
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5ad810cd901fd74c82da72fc141d85c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,519 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics.CollisionShapes;
using BEPUutilities;
using BEPUphysics.Entities;
using BEPUphysics.CollisionShapes.ConvexShapes;
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
/// <summary>
/// Contains methods to help with splitting compound objects into multiple pieces.
/// </summary>
public static class CompoundHelper
{
/// <summary>
/// Splits a single compound collidable into two separate compound collidables and computes information needed by the simulation.
/// </summary>
/// <param name="splitPredicate">Delegate which determines if a child in the original compound should be moved to the new compound.</param>
/// <param name="a">Original compound to be split. Children in this compound will be removed and added to the other compound.</param>
/// <param name="b">Compound to receive children removed from the original compound.</param>
/// <returns>Whether or not the predicate returned true for any element in the original compound and split the compound.</returns>
public static bool SplitCompound(Func<CompoundChild, bool> splitPredicate,
Entity<CompoundCollidable> a, out Entity<CompoundCollidable> b)
{
ShapeDistributionInformation distributionInfoA, distributionInfoB;
if (SplitCompound(splitPredicate, a, out b, out distributionInfoA, out distributionInfoB))
{
return true;
}
return false;
}
/// <summary>
/// Splits a single compound collidable into two separate compound collidables and computes information needed by the simulation.
/// </summary>
/// <param name="splitPredicate">Delegate which determines if a child in the original compound should be moved to the new compound.</param>
/// <param name="a">Original compound to be split. Children in this compound will be removed and added to the other compound.</param>
/// <param name="b">Compound to receive children removed from the original compound.</param>
/// <param name="distributionInfoA">Volume, volume distribution, and center information about the new form of the original compound collidable.</param>
/// <param name="distributionInfoB">Volume, volume distribution, and center information about the new compound collidable.</param>
/// <returns>Whether or not the predicate returned true for any element in the original compound and split the compound.</returns>
public static bool SplitCompound(Func<CompoundChild, bool> splitPredicate,
Entity<CompoundCollidable> a, out Entity<CompoundCollidable> b,
out ShapeDistributionInformation distributionInfoA, out ShapeDistributionInformation distributionInfoB)
{
var bCollidable = new CompoundCollidable { Shape = a.CollisionInformation.Shape };
b = null;
float weightA, weightB;
if (SplitCompound(splitPredicate, a.CollisionInformation, bCollidable, out distributionInfoA, out distributionInfoB, out weightA, out weightB))
{
//Reconfigure the entities using the data computed in the split.
float originalMass = a.mass;
if (a.CollisionInformation.children.Count > 0)
{
float newMassA = (weightA / (weightA + weightB)) * originalMass;
Matrix3x3.Multiply(ref distributionInfoA.VolumeDistribution, newMassA * InertiaHelper.InertiaTensorScale, out distributionInfoA.VolumeDistribution);
a.Initialize(a.CollisionInformation, newMassA, distributionInfoA.VolumeDistribution);
}
if (bCollidable.children.Count > 0)
{
float newMassB = (weightB / (weightA + weightB)) * originalMass;
Matrix3x3.Multiply(ref distributionInfoB.VolumeDistribution, newMassB * InertiaHelper.InertiaTensorScale, out distributionInfoB.VolumeDistribution);
b = new Entity<CompoundCollidable>();
b.Initialize(bCollidable, newMassB, distributionInfoB.VolumeDistribution);
}
SplitReposition(a, b, ref distributionInfoA, ref distributionInfoB, weightA, weightB);
return true;
}
return false;
}
static void SplitReposition(Entity a, Entity b, ref ShapeDistributionInformation distributionInfoA, ref ShapeDistributionInformation distributionInfoB, float weightA, float weightB)
{
//The compounds are not aligned with the original's position yet.
//In order to align them, first look at the centers the split method computed.
//They are offsets from the center of the original shape in local space.
//These can be used to reposition the objects in world space.
Vector3 weightedA, weightedB;
Vector3.Multiply(ref distributionInfoA.Center, weightA, out weightedA);
Vector3.Multiply(ref distributionInfoB.Center, weightB, out weightedB);
Vector3 newLocalCenter;
Vector3.Add(ref weightedA, ref weightedB, out newLocalCenter);
Vector3.Divide(ref newLocalCenter, weightA + weightB, out newLocalCenter);
Vector3 localOffsetA;
Vector3 localOffsetB;
Vector3.Subtract(ref distributionInfoA.Center, ref newLocalCenter, out localOffsetA);
Vector3.Subtract(ref distributionInfoB.Center, ref newLocalCenter, out localOffsetB);
Vector3 originalPosition = a.position;
b.Orientation = a.Orientation;
Vector3 offsetA = Quaternion.Transform(localOffsetA, a.Orientation);
Vector3 offsetB = Quaternion.Transform(localOffsetB, a.Orientation);
a.Position = originalPosition + offsetA;
b.Position = originalPosition + offsetB;
Vector3 originalLinearVelocity = a.linearVelocity;
Vector3 originalAngularVelocity = a.angularVelocity;
a.AngularVelocity = originalAngularVelocity;
b.AngularVelocity = originalAngularVelocity;
a.LinearVelocity = originalLinearVelocity + Vector3.Cross(originalAngularVelocity, offsetA);
b.LinearVelocity = originalLinearVelocity + Vector3.Cross(originalAngularVelocity, offsetB);
}
/// <summary>
/// Splits a single compound collidable into two separate compound collidables and computes information needed by the simulation.
/// </summary>
/// <param name="splitPredicate">Delegate which determines if a child in the original compound should be moved to the new compound.</param>
/// <param name="a">Original compound to be split. Children in this compound will be removed and added to the other compound.</param>
/// <param name="b">Compound to receive children removed from the original compound.</param>
/// <returns>Whether or not the predicate returned true for any element in the original compound and split the compound.</returns>
public static bool SplitCompound(Func<CompoundChild, bool> splitPredicate,
Entity<CompoundCollidable> a, Entity<CompoundCollidable> b)
{
ShapeDistributionInformation distributionInfoA, distributionInfoB;
if (SplitCompound(splitPredicate, a, b, out distributionInfoA, out distributionInfoB))
{
return true;
}
return false;
}
/// <summary>
/// Splits a single compound collidable into two separate compound collidables and computes information needed by the simulation.
/// </summary>
/// <param name="splitPredicate">Delegate which determines if a child in the original compound should be moved to the new compound.</param>
/// <param name="distributionInfoA">Volume, volume distribution, and center information about the new form of the original compound collidable.</param>
/// <param name="distributionInfoB">Volume, volume distribution, and center information about the new compound collidable.</param>
/// <param name="a">Original compound to be split. Children in this compound will be removed and added to the other compound.</param>
/// <param name="b">Compound to receive children removed from the original compound.</param>
/// <returns>Whether or not the predicate returned true for any element in the original compound and split the compound.</returns>
public static bool SplitCompound(Func<CompoundChild, bool> splitPredicate,
Entity<CompoundCollidable> a, Entity<CompoundCollidable> b,
out ShapeDistributionInformation distributionInfoA, out ShapeDistributionInformation distributionInfoB)
{
float weightA, weightB;
if (SplitCompound(splitPredicate, a.CollisionInformation, b.CollisionInformation, out distributionInfoA, out distributionInfoB, out weightA, out weightB))
{
//Reconfigure the entities using the data computed in the split.
float originalMass = a.mass;
if (a.CollisionInformation.children.Count > 0)
{
float newMassA = (weightA / (weightA + weightB)) * originalMass;
Matrix3x3.Multiply(ref distributionInfoA.VolumeDistribution, newMassA * InertiaHelper.InertiaTensorScale, out distributionInfoA.VolumeDistribution);
a.Initialize(a.CollisionInformation, newMassA, distributionInfoA.VolumeDistribution);
}
if (b.CollisionInformation.children.Count > 0)
{
float newMassB = (weightB / (weightA + weightB)) * originalMass;
Matrix3x3.Multiply(ref distributionInfoB.VolumeDistribution, newMassB * InertiaHelper.InertiaTensorScale, out distributionInfoB.VolumeDistribution);
b.Initialize(b.CollisionInformation, newMassB, distributionInfoB.VolumeDistribution);
}
SplitReposition(a, b, ref distributionInfoA, ref distributionInfoB, weightA, weightB);
return true;
}
return false;
}
/// <summary>
/// Splits a single compound collidable into two separate compound collidables and computes information needed by the simulation.
/// </summary>
/// <param name="splitPredicate">Delegate which determines if a child in the original compound should be moved to the new compound.</param>
/// <param name="a">Original compound to be split. Children in this compound will be removed and added to the other compound.</param>
/// <param name="b">Compound to receive children removed from the original compound.</param>
/// <param name="distributionInfoA">Volume, volume distribution, and center information about the new form of the original compound collidable.</param>
/// <param name="distributionInfoB">Volume, volume distribution, and center information about the new compound collidable.</param>
/// <param name="weightA">Total weight associated with the new form of the original compound collidable.</param>
/// <param name="weightB">Total weight associated with the new compound collidable.</param>
/// <returns>Whether or not the predicate returned true for any element in the original compound and split the compound.</returns>
public static bool SplitCompound(Func<CompoundChild, bool> splitPredicate,
CompoundCollidable a, CompoundCollidable b,
out ShapeDistributionInformation distributionInfoA, out ShapeDistributionInformation distributionInfoB,
out float weightA, out float weightB)
{
bool splitOccurred = false;
for (int i = a.children.Count - 1; i >= 0; i--)
{
//The shape doesn't change during this process. The entity could, though.
//All of the other collidable information, like the Tag, CollisionRules, Events, etc. all stay the same.
var child = a.children.Elements[i];
if (splitPredicate(child))
{
splitOccurred = true;
a.children.FastRemoveAt(i);
b.children.Add(child);
//The child event handler must be unhooked from the old compound and given to the new one.
child.CollisionInformation.events.Parent = b.Events;
}
}
if (!splitOccurred)
{
//No split occurred, so we cannot proceed.
distributionInfoA = new ShapeDistributionInformation();
distributionInfoB = new ShapeDistributionInformation();
weightA = 0;
weightB = 0;
return false;
}
//Compute the contributions from the original shape to the new form of the original collidable.
distributionInfoA = new ShapeDistributionInformation();
weightA = 0;
distributionInfoB = new ShapeDistributionInformation();
weightB = 0;
for (int i = a.children.Count - 1; i >= 0; i--)
{
var child = a.children.Elements[i];
var entry = child.Entry;
Vector3 weightedCenter;
Vector3.Multiply(ref entry.LocalTransform.Position, entry.Weight, out weightedCenter);
Vector3.Add(ref weightedCenter, ref distributionInfoA.Center, out distributionInfoA.Center);
distributionInfoA.Volume += entry.Shape.Volume;
weightA += entry.Weight;
}
for (int i = b.children.Count - 1; i >= 0; i--)
{
var child = b.children.Elements[i];
var entry = child.Entry;
Vector3 weightedCenter;
Vector3.Multiply(ref entry.LocalTransform.Position, entry.Weight, out weightedCenter);
Vector3.Add(ref weightedCenter, ref distributionInfoB.Center, out distributionInfoB.Center);
distributionInfoB.Volume += entry.Shape.Volume;
weightB += entry.Weight;
}
//Average the center out.
if (weightA > 0)
Vector3.Divide(ref distributionInfoA.Center, weightA, out distributionInfoA.Center);
if (weightB > 0)
Vector3.Divide(ref distributionInfoB.Center, weightB, out distributionInfoB.Center);
//Note that the 'entry' is from the Shape, and so the translations are local to the shape's center.
//That is not technically the center of the new collidable- distributionInfoA.Center is.
//Offset the child collidables by -distributionInfoA.Center using their local offset.
Vector3 offsetA;
Vector3.Negate(ref distributionInfoA.Center, out offsetA);
Vector3 offsetB;
Vector3.Negate(ref distributionInfoB.Center, out offsetB);
//Compute the unscaled inertia tensor.
for (int i = a.children.Count - 1; i >= 0; i--)
{
var child = a.children.Elements[i];
var entry = child.Entry;
Vector3 transformedOffset;
Quaternion conjugate;
Quaternion.Conjugate(ref entry.LocalTransform.Orientation, out conjugate);
Quaternion.Transform(ref offsetA, ref conjugate, out transformedOffset);
child.CollisionInformation.localPosition = transformedOffset;
Matrix3x3 contribution;
CompoundShape.TransformContribution(ref entry.LocalTransform, ref distributionInfoA.Center, ref entry.Shape.volumeDistribution, entry.Weight, out contribution);
Matrix3x3.Add(ref contribution, ref distributionInfoA.VolumeDistribution, out distributionInfoA.VolumeDistribution);
}
for (int i = b.children.Count - 1; i >= 0; i--)
{
var child = b.children.Elements[i];
var entry = child.Entry;
Vector3 transformedOffset;
Quaternion conjugate;
Quaternion.Conjugate(ref entry.LocalTransform.Orientation, out conjugate);
Quaternion.Transform(ref offsetB, ref conjugate, out transformedOffset);
child.CollisionInformation.localPosition = transformedOffset;
Matrix3x3 contribution;
CompoundShape.TransformContribution(ref entry.LocalTransform, ref distributionInfoB.Center, ref entry.Shape.volumeDistribution, entry.Weight, out contribution);
Matrix3x3.Add(ref contribution, ref distributionInfoB.VolumeDistribution, out distributionInfoB.VolumeDistribution);
}
//Normalize the volume distribution.
Matrix3x3.Multiply(ref distributionInfoA.VolumeDistribution, 1 / weightA, out distributionInfoA.VolumeDistribution);
Matrix3x3.Multiply(ref distributionInfoB.VolumeDistribution, 1 / weightB, out distributionInfoB.VolumeDistribution);
//Update the hierarchies of the compounds.
//TODO: Create a new method that does this quickly without garbage. Requires a new Reconstruct method which takes a pool which stores the appropriate node types.
a.hierarchy.Tree.Reconstruct(a.children);
b.hierarchy.Tree.Reconstruct(b.children);
return true;
}
static void RemoveReposition(Entity compound, ref ShapeDistributionInformation distributionInfo, float weight, float removedWeight, ref Vector3 removedCenter)
{
//The compounds are not aligned with the original's position yet.
//In order to align them, first look at the centers the split method computed.
//They are offsets from the center of the original shape in local space.
//These can be used to reposition the objects in world space.
Vector3 weightedA, weightedB;
Vector3.Multiply(ref distributionInfo.Center, weight, out weightedA);
Vector3.Multiply(ref removedCenter, removedWeight, out weightedB);
Vector3 newLocalCenter;
Vector3.Add(ref weightedA, ref weightedB, out newLocalCenter);
Vector3.Divide(ref newLocalCenter, weight + removedWeight, out newLocalCenter);
Vector3 localOffset;
Vector3.Subtract(ref distributionInfo.Center, ref newLocalCenter, out localOffset);
Vector3 originalPosition = compound.position;
Vector3 offset = Quaternion.Transform(localOffset, compound.orientation);
compound.Position = originalPosition + offset;
Vector3 originalLinearVelocity = compound.linearVelocity;
Vector3 originalAngularVelocity = compound.angularVelocity;
compound.AngularVelocity = originalAngularVelocity;
compound.LinearVelocity = originalLinearVelocity + Vector3.Cross(originalAngularVelocity, offset);
}
/// <summary>
/// Removes a child from a compound body.
/// </summary>
/// <param name="childContributions">List of distribution information associated with each child shape of the whole compound shape used by the compound being split.</param>
/// <param name="removalPredicate">Delegate which determines if a child in the original compound should be moved to the new compound.</param>
/// <param name="compound">Original compound to have a child removed.</param>
/// <returns>Whether or not the predicate returned true for any element in the original compound and split the compound.</returns>
public static bool RemoveChildFromCompound(Entity<CompoundCollidable> compound, Func<CompoundChild, bool> removalPredicate, IList<ShapeDistributionInformation> childContributions)
{
ShapeDistributionInformation distributionInfo;
if (RemoveChildFromCompound(compound, removalPredicate, childContributions, out distributionInfo))
{
return true;
}
else
return false;
}
/// <summary>
/// Removes a child from a compound body.
/// </summary>
/// <param name="childContributions">List of distribution information associated with each child shape of the whole compound shape used by the compound being split.</param>
/// <param name="removalPredicate">Delegate which determines if a child in the original compound should be moved to the new compound.</param>
/// <param name="distributionInfo">Volume, volume distribution, and center information about the new form of the original compound collidable.</param>
/// <param name="compound">Original compound to have a child removed.</param>
/// <returns>Whether or not the predicate returned true for any element in the original compound and split the compound.</returns>
public static bool RemoveChildFromCompound(Entity<CompoundCollidable> compound, Func<CompoundChild, bool> removalPredicate, IList<ShapeDistributionInformation> childContributions,
out ShapeDistributionInformation distributionInfo)
{
float weight;
float removedWeight;
Vector3 removedCenter;
if (RemoveChildFromCompound(compound.CollisionInformation, removalPredicate, childContributions, out distributionInfo, out weight, out removedWeight, out removedCenter))
{
//Reconfigure the entities using the data computed in the split.
//Only bother if there are any children left in the compound!
if (compound.CollisionInformation.Children.Count > 0)
{
float originalMass = compound.mass;
float newMass = (weight / (weight + removedWeight)) * originalMass;
Matrix3x3.Multiply(ref distributionInfo.VolumeDistribution, newMass * InertiaHelper.InertiaTensorScale, out distributionInfo.VolumeDistribution);
compound.Initialize(compound.CollisionInformation, newMass, distributionInfo.VolumeDistribution);
RemoveReposition(compound, ref distributionInfo, weight, removedWeight, ref removedCenter);
}
return true;
}
else
return false;
}
/// <summary>
/// Removes a child from a compound collidable.
/// </summary>
/// <param name="compound">Compound collidable to remove a child from.</param>
/// <param name="removalPredicate">Callback which analyzes a child and determines if it should be removed from the compound.</param>
/// <param name="childContributions">Distribution contributions from all shapes in the compound shape. This can include shapes which are not represented in the compound.</param>
/// <param name="distributionInfo">Distribution information of the new compound.</param>
/// <param name="weight">Total weight of the new compound.</param>
/// <param name="removedWeight">Weight removed from the compound.</param>
/// <param name="removedCenter">Center of the chunk removed from the compound.</param>
/// <returns>Whether or not any removal took place.</returns>
public static bool RemoveChildFromCompound(CompoundCollidable compound, Func<CompoundChild, bool> removalPredicate, IList<ShapeDistributionInformation> childContributions,
out ShapeDistributionInformation distributionInfo, out float weight, out float removedWeight, out Vector3 removedCenter)
{
bool removalOccurred = false;
removedWeight = 0;
removedCenter = new Vector3();
for (int i = compound.children.Count - 1; i >= 0; i--)
{
//The shape doesn't change during this process. The entity could, though.
//All of the other collidable information, like the Tag, CollisionRules, Events, etc. all stay the same.
var child = compound.children.Elements[i];
if (removalPredicate(child))
{
removalOccurred = true;
var entry = child.Entry;
removedWeight += entry.Weight;
Vector3 toAdd;
Vector3.Multiply(ref entry.LocalTransform.Position, entry.Weight, out toAdd);
Vector3.Add(ref removedCenter, ref toAdd, out removedCenter);
//The child event handler must be unhooked from the compound.
child.CollisionInformation.events.Parent = null;
compound.children.FastRemoveAt(i);
}
}
if (!removalOccurred)
{
//No removal occurred, so we cannot proceed.
distributionInfo = new ShapeDistributionInformation();
weight = 0;
return false;
}
if (removedWeight > 0)
{
Vector3.Divide(ref removedCenter, removedWeight, out removedCenter);
}
//Compute the contributions from the original shape to the new form of the original collidable.
distributionInfo = new ShapeDistributionInformation();
weight = 0;
for (int i = compound.children.Count - 1; i >= 0; i--)
{
var child = compound.children.Elements[i];
var entry = child.Entry;
var contribution = childContributions[child.shapeIndex];
Vector3.Add(ref contribution.Center, ref entry.LocalTransform.Position, out contribution.Center);
Vector3.Multiply(ref contribution.Center, child.Entry.Weight, out contribution.Center);
Vector3.Add(ref contribution.Center, ref distributionInfo.Center, out distributionInfo.Center);
distributionInfo.Volume += contribution.Volume;
weight += entry.Weight;
}
//Average the center out.
Vector3.Divide(ref distributionInfo.Center, weight, out distributionInfo.Center);
//Note that the 'entry' is from the Shape, and so the translations are local to the shape's center.
//That is not technically the center of the new collidable- distributionInfo.Center is.
//Offset the child collidables by -distributionInfo.Center using their local offset.
Vector3 offset;
Vector3.Negate(ref distributionInfo.Center, out offset);
//Compute the unscaled inertia tensor.
for (int i = compound.children.Count - 1; i >= 0; i--)
{
var child = compound.children.Elements[i];
var entry = child.Entry;
Vector3 transformedOffset;
Quaternion conjugate;
Quaternion.Conjugate(ref entry.LocalTransform.Orientation, out conjugate);
Quaternion.Transform(ref offset, ref conjugate, out transformedOffset);
child.CollisionInformation.localPosition = transformedOffset;
var contribution = childContributions[child.shapeIndex];
CompoundShape.TransformContribution(ref entry.LocalTransform, ref distributionInfo.Center, ref contribution.VolumeDistribution, entry.Weight, out contribution.VolumeDistribution);
//Vector3.Add(ref entry.LocalTransform.Position, ref offsetA, out entry.LocalTransform.Position);
Matrix3x3.Add(ref contribution.VolumeDistribution, ref distributionInfo.VolumeDistribution, out distributionInfo.VolumeDistribution);
}
//Normalize the volume distribution.
Matrix3x3.Multiply(ref distributionInfo.VolumeDistribution, 1 / weight, out distributionInfo.VolumeDistribution);
//Update the hierarchies of the compounds.
//TODO: Create a new method that does this quickly without garbage. Requires a new Reconstruct method which takes a pool which stores the appropriate node types.
compound.hierarchy.Tree.Reconstruct(compound.children);
return true;
}
/// <summary>
/// Constructs a compound collidable containing only the specified subset of children.
/// </summary>
/// <param name="shape">Shape to base the compound collidable on.</param>
/// <param name="childIndices">Indices of child shapes from the CompoundShape to include in the compound collidable.</param>
/// <returns>Compound collidable containing only the specified subset of children.</returns>
public static CompoundCollidable CreatePartialCompoundCollidable(CompoundShape shape, IList<int> childIndices)
{
if (childIndices.Count == 0)
throw new ArgumentException("Cannot create a compound from zero shapes.");
CompoundCollidable compound = new CompoundCollidable();
Vector3 center = new Vector3();
float totalWeight = 0;
for (int i = 0; i < childIndices.Count; i++)
{
//Create and add the child object itself.
var entry = shape.shapes[childIndices[i]];
compound.children.Add(new CompoundChild(shape, entry.Shape.GetCollidableInstance(), childIndices[i]));
//Grab its entry to compute the center of mass of this subset.
Vector3 toAdd;
Vector3.Multiply(ref entry.LocalTransform.Position, entry.Weight, out toAdd);
Vector3.Add(ref center, ref toAdd, out center);
totalWeight += entry.Weight;
}
if (totalWeight <= 0)
{
throw new ArgumentException("Compound has zero total weight; invalid configuration.");
}
Vector3.Divide(ref center, totalWeight, out center);
//Our subset of the compound is not necessarily aligned with the shape's origin.
//By default, an object will rotate around the center of the collision shape.
//We can't modify the shape data itself since it could be shared, which leaves
//modifying the local position of the collidable.
//We have the subset position in shape space, so pull the collidable back into alignment
//with the origin.
//This approach matches the rest of the CompoundHelper's treatment of subsets.
compound.LocalPosition = -center;
//Recompute the hierarchy for the compound.
compound.hierarchy.Tree.Reconstruct(compound.children);
compound.Shape = shape;
return compound;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f2754fd27a67764ea6a6de102c4312b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,57 @@
using System;
using BEPUphysics.DataStructures;
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
///<summary>
/// Hierarchy of children used to accelerate queries and tests for compound collidables.
///</summary>
public class CompoundHierarchy
{
private BoundingBoxTree<CompoundChild> tree;
///<summary>
/// Gets the bounding box tree of the hierarchy.
///</summary>
public BoundingBoxTree<CompoundChild> Tree
{
get
{
return tree;
}
}
private CompoundCollidable owner;
///<summary>
/// Gets the CompoundCollidable that owns this hierarchy.
///</summary>
public CompoundCollidable Owner
{
get
{
return owner;
}
}
///<summary>
/// Constructs a new compound hierarchy.
///</summary>
///<param name="owner">Owner of the hierarchy.</param>
public CompoundHierarchy(CompoundCollidable owner)
{
this.owner = owner;
var children = new CompoundChild[owner.children.Count];
Array.Copy(owner.children.Elements, children, owner.children.Count);
//In order to initialize a good tree, the local space bounding boxes should first be computed.
//Otherwise, the tree would try to create a hierarchy based on a bunch of zeroed out bounding boxes!
for (int i = 0; i < children.Length; i++)
{
children[i].CollisionInformation.worldTransform = owner.Shape.shapes.Elements[i].LocalTransform;
children[i].CollisionInformation.UpdateBoundingBoxInternal(0);
}
tree = new BoundingBoxTree<CompoundChild>(children);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47a435982dd3c6f4898b816b9642816f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,93 @@
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUphysics.CollisionTests.CollisionAlgorithms;
using BEPUutilities;
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
///<summary>
/// Collidable with a convex shape.
///</summary>
public abstract class ConvexCollidable : EntityCollidable
{
protected ConvexCollidable(ConvexShape shape)
: base(shape)
{
Events = new ContactEventManager<EntityCollidable>();
}
///<summary>
/// Gets the shape of the collidable.
///</summary>
public new ConvexShape Shape
{
get
{
return (ConvexShape)shape;
}
}
public override bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit)
{
return MPRToolbox.Sweep(castShape, Shape, ref sweep, ref Toolbox.ZeroVector, ref startingTransform, ref worldTransform, out hit);
}
}
///<summary>
/// Collidable with a convex shape of a particular type.
///</summary>
///<typeparam name="T">ConvexShape type.</typeparam>
public class ConvexCollidable<T> : ConvexCollidable where T : ConvexShape
{
///<summary>
/// Gets the shape of the collidable.
///</summary>
public new T Shape
{
get
{
return (T)shape;
}
}
///<summary>
/// Constructs a new convex collidable.
///</summary>
///<param name="shape">Shape to use in the collidable.</param>
public ConvexCollidable(T shape)
: base(shape)
{
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
return Shape.RayTest(ref ray, ref worldTransform, maximumLength, out rayHit);
}
protected internal override void UpdateBoundingBoxInternal(float dt)
{
Shape.GetBoundingBox(ref worldTransform, out boundingBox);
ExpandBoundingBox(ref boundingBox, dt);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7bc39a6813c8ee84bb058d0d8f2ba3dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,320 @@
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.CollisionShapes;
using BEPUphysics.Entities;
using BEPUutilities;
using BEPUphysics.Settings;
using System;
using BEPUphysics.PositionUpdating;
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
///<summary>
/// Mobile collidable acting as a collision proxy for an entity.
///</summary>
public abstract class EntityCollidable : MobileCollidable
{
protected EntityCollidable()
{
//This constructor is used when the subclass is going to set the shape after doing some extra initialization.
}
protected EntityCollidable(EntityShape shape)
{
base.Shape = shape;
}
/// <summary>
/// Gets the shape of the collidable.
/// </summary>
public new EntityShape Shape
{
get
{
return (EntityShape)shape;
}
protected set
{
base.Shape = value;
}
}
protected internal Entity entity;
///<summary>
/// Gets the entity owning the collidable.
///</summary>
public Entity Entity
{
get
{
return entity;
}
protected internal set
{
entity = value;
OnEntityChanged();
}
}
protected virtual void OnEntityChanged()
{
}
protected internal RigidTransform worldTransform;
///<summary>
/// Gets or sets the world transform of the collidable.
/// The EntityCollidable's LocalPosition is ignored for this process; the shape will end up
/// centered exactly on the world transform.
/// Setting this property also updates the bounding box.
///</summary>
public RigidTransform WorldTransform
{
get
{
return worldTransform;
}
set
{
//Remove the local position. The UpdateBoundingBoxForTransform will reintroduce it; we want the final result to put the shape (i.e. the WorldTransform) right where defined.
Quaternion conjugate;
Quaternion.Conjugate(ref value.Orientation, out conjugate);
Vector3 worldOffset;
Quaternion.Transform(ref localPosition, ref conjugate, out worldOffset);
Vector3.Subtract(ref value.Position, ref worldOffset, out value.Position);
UpdateBoundingBoxForTransform(ref value);
}
}
/// <summary>
/// Gets whether this collidable is associated with an active entity. True if it is, false if it's not.
/// </summary>
public override bool IsActive
{
get
{
return entity != null ? entity.activityInformation.IsActive : false;
}
}
protected internal Vector3 localPosition;
///<summary>
/// Gets or sets the local position of the collidable.
/// The local position can be used to offset the collision geometry
/// from an entity's center of mass.
///</summary>
public Vector3 LocalPosition
{
get
{
return localPosition;
}
set
{
localPosition = value;
localPosition.Validate();
}
}
///<summary>
/// Updates the bounding box of the mobile collidable according to the associated entity's current state.
/// Do not use this if the EntityCollidable does not have an associated entity; consider using
/// UpdateBoundingBoxForTransform instead.
///</summary>
public override void UpdateBoundingBox()
{
UpdateBoundingBox(0);
}
///<summary>
/// Updates the bounding box of the mobile collidable according to the associated entity's current state.
/// Do not use this if the EntityCollidable does not have an associated entity; consider using
/// UpdateBoundingBoxForTransform instead.
///</summary>
///<param name="dt">Timestep with which to update the bounding box.</param>
public override void UpdateBoundingBox(float dt)
{
//The world transform update isn't strictly required for uninterrupted simulation.
//The entity update method manages the world transforms.
//However, the redundancy allows a user to change the position in between frames.
//If the order of the space update changes to position-update-first, this is completely unnecessary.
UpdateWorldTransform(ref entity.position, ref entity.orientation);
UpdateBoundingBoxInternal(dt);
}
///<summary>
/// Updates the world transform of the shape using the given position and orientation.
/// The world transform of the shape is offset from the given position and orientation by the collidable's LocalPosition.
///</summary>
///<param name="position">Position to use for the calculation.</param>
///<param name="orientation">Orientation to use for the calculation.</param>
public virtual void UpdateWorldTransform(ref Vector3 position, ref Quaternion orientation)
{
Quaternion.Transform(ref localPosition, ref orientation, out worldTransform.Position);
Vector3.Add(ref worldTransform.Position, ref position, out worldTransform.Position);
worldTransform.Orientation = orientation;
worldTransform.Validate();
}
/// <summary>
/// Updates the collidable's world transform and bounding box. The transform provided
/// will be offset by the collidable's LocalPosition to get the shape transform.
/// This is a convenience method for external modification of the collidable's data.
/// </summary>
/// <param name="transform">Transform to use for the collidable.</param>
/// <param name="dt">Duration of the simulation time step. Used to expand the
/// bounding box using the owning entity's velocity. If the collidable
/// does not have an owning entity, this must be zero.</param>
public void UpdateBoundingBoxForTransform(ref RigidTransform transform, float dt)
{
UpdateWorldTransform(ref transform.Position, ref transform.Orientation);
UpdateBoundingBoxInternal(dt);
}
/// <summary>
/// Updates the collidable's world transform and bounding box.
/// This is a convenience method for external modification of the collidable's data.
/// </summary>
/// <param name="transform">Transform to use for the collidable.</param>
public void UpdateBoundingBoxForTransform(ref RigidTransform transform)
{
UpdateBoundingBoxForTransform(ref transform, 0);
}
protected internal abstract void UpdateBoundingBoxInternal(float dt);
//Helper method for mobile collidables.
internal void ExpandBoundingBox(ref BoundingBox boundingBox, float dt)
{
//Expand bounding box with velocity.
if (dt > 0)
{
bool useExtraExpansion = MotionSettings.UseExtraExpansionForContinuousBoundingBoxes && entity.PositionUpdateMode == PositionUpdateMode.Continuous;
float velocityScaling = useExtraExpansion ? 2 : 1;
if (entity.linearVelocity.X > 0)
boundingBox.Max.X += entity.linearVelocity.X * dt * velocityScaling;
else
boundingBox.Min.X += entity.linearVelocity.X * dt * velocityScaling;
if (entity.linearVelocity.Y > 0)
boundingBox.Max.Y += entity.linearVelocity.Y * dt * velocityScaling;
else
boundingBox.Min.Y += entity.linearVelocity.Y * dt * velocityScaling;
if (entity.linearVelocity.Z > 0)
boundingBox.Max.Z += entity.linearVelocity.Z * dt * velocityScaling;
else
boundingBox.Min.Z += entity.linearVelocity.Z * dt * velocityScaling;
if (useExtraExpansion)
{
float expansion = 0;
//It's possible that an object could have a small bounding box since its own
//velocity is low, but then a collision with a high velocity object sends
//it way out of its bounding box. By taking into account high velocity objects
//in danger of hitting us and expanding our own bounding box by their speed,
//we stand a much better chance of not missing secondary collisions.
foreach (var e in OverlappedEntities)
{
float velocity = e.linearVelocity.LengthSquared();
if (velocity > expansion)
expansion = velocity;
}
expansion = (float)Math.Sqrt(expansion) * dt;
boundingBox.Min.X -= expansion;
boundingBox.Min.Y -= expansion;
boundingBox.Min.Z -= expansion;
boundingBox.Max.X += expansion;
boundingBox.Max.Y += expansion;
boundingBox.Max.Z += expansion;
}
//Could use this to incorporate angular motion. Since the bounding box is an approximation to begin with,
//this isn't too important. If an updating system is used where the bounding box MUST fully contain the frame's motion
//then the commented area should be used.
//Math.Min(entity.angularVelocity.Length() * dt, Shape.maximumRadius) * velocityScaling;
//TODO: consider using minimum radius
}
boundingBox.Validate();
}
protected override void CollisionRulesUpdated()
{
//Try to activate the entity since our collision rules just changed; broadphase might need to update some stuff.
//Beware, though; if this collidable is still being constructed, then the entity won't be available.
if (entity != null)
entity.activityInformation.Activate();
}
protected internal ContactEventManager<EntityCollidable> events;
///<summary>
/// Gets or sets the event manager of the collidable.
///</summary>
public ContactEventManager<EntityCollidable> Events
{
get
{
return events;
}
set
{
if (value.Owner != null && //Can't use a manager which is owned by a different entity.
value != events) //Stay quiet if for some reason the same event manager is being set.
throw new ArgumentException("Event manager is already owned by an entity; event managers cannot be shared.");
//Must pass on the link to the parent event manager to the new event manager in case we are the child of a compound.
CompoundEventManager oldParent = null;
if (events != null)
{
events.Owner = null;
oldParent = events.Parent;
events.Parent = null;
}
events = value;
if (events != null)
{
events.Owner = this;
events.Parent = oldParent;
}
}
}
protected internal override IContactEventTriggerer EventTriggerer
{
get { return events; }
}
///<summary>
/// Gets an enumerable collection of all entities overlapping this collidable.
///</summary>
public EntityCollidableCollection OverlappedEntities
{
get
{
return new EntityCollidableCollection(this);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a9744aa46f413543868ed47efd093aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,19 @@
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
//This is implemented by anything which wants the engine to update its bounding box every frame (so long as it is 'active').
///<summary>
/// Superclass of all collidables which are capable of movement, and thus need bounding box updates every frame.
///</summary>
public abstract class MobileCollidable : Collidable
{
//TODO: Imagine needing to calculate the bounding box for a data structure that is not axis-aligned. Being able to return BB without 'setting' would be helpful.
//Possibly require second method. The parameterless one uses 'self data' to do the calculation, as a sort of convenience. The parameterful would return without setting.
///<summary>
/// Updates the bounding box of the mobile collidable.
///</summary>
///<param name="dt">Timestep with which to update the bounding box.</param>
public abstract void UpdateBoundingBox(float dt);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f41c884d10796ee47bc1c1337cf7afba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,250 @@
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.CollisionShapes;
using BEPUutilities;
using BEPUutilities.ResourceManagement;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUphysics.CollisionTests.CollisionAlgorithms;
using System;
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
///<summary>
/// Collidable used by compound shapes.
///</summary>
public class MobileMeshCollidable : EntityCollidable
{
///<summary>
/// Gets the shape of the collidable.
///</summary>
public new MobileMeshShape Shape
{
get
{
return (MobileMeshShape)shape;
}
}
/// <summary>
/// Constructs a new mobile mesh collidable.
/// </summary>
/// <param name="shape">Shape to use in the collidable.</param>
public MobileMeshCollidable(MobileMeshShape shape)
: base(shape)
{
Events = new ContactEventManager<EntityCollidable>();
}
internal bool improveBoundaryBehavior = true;
/// <summary>
/// Gets or sets whether or not the collision system should attempt to improve contact behavior at the boundaries between triangles.
/// This has a slight performance cost, but prevents objects sliding across a triangle boundary from 'bumping,' and otherwise improves
/// the robustness of contacts at edges and vertices.
/// </summary>
public bool ImproveBoundaryBehavior
{
get
{
return improveBoundaryBehavior;
}
set
{
improveBoundaryBehavior = value;
}
}
protected internal override void UpdateBoundingBoxInternal(float dt)
{
Shape.GetBoundingBox(ref worldTransform, out boundingBox);
//This DOES NOT EXPAND the local hierarchy.
//The bounding boxes of queries against the local hierarchy
//should be expanded using the relative velocity.
ExpandBoundingBox(ref boundingBox, dt);
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
//Put the ray into local space.
Ray localRay;
Matrix3x3 orientation;
Matrix3x3.CreateFromQuaternion(ref worldTransform.Orientation, out orientation);
Matrix3x3.TransformTranspose(ref ray.Direction, ref orientation, out localRay.Direction);
Vector3.Subtract(ref ray.Position, ref worldTransform.Position, out localRay.Position);
Matrix3x3.TransformTranspose(ref localRay.Position, ref orientation, out localRay.Position);
if (Shape.solidity == MobileMeshSolidity.Solid)
{
//Find all hits. Use the count to determine the ray started inside or outside.
//If it starts inside and we're in 'solid' mode, then return the ray start.
//The raycast must be of infinite length at first. This allows it to determine
//if it is inside or outside.
if (Shape.IsLocalRayOriginInMesh(ref localRay, out rayHit))
{
//It was inside!
rayHit = new RayHit() { Location = ray.Position, Normal = Vector3.Zero, T = 0 };
return true;
}
else
{
if (rayHit.T < maximumLength)
{
//Transform the hit into world space.
Vector3.Multiply(ref ray.Direction, rayHit.T, out rayHit.Location);
Vector3.Add(ref rayHit.Location, ref ray.Position, out rayHit.Location);
Matrix3x3.Transform(ref rayHit.Normal, ref orientation, out rayHit.Normal);
}
else
{
//The hit was too far away, or there was no hit (in which case T would be float.MaxValue).
return false;
}
return true;
}
}
else
{
//Just do a normal raycast since the object isn't solid.
TriangleSidedness sidedness;
switch (Shape.solidity)
{
case MobileMeshSolidity.Clockwise:
sidedness = TriangleSidedness.Clockwise;
break;
case MobileMeshSolidity.Counterclockwise:
sidedness = TriangleSidedness.Counterclockwise;
break;
default:
sidedness = TriangleSidedness.DoubleSided;
break;
}
if (Shape.TriangleMesh.RayCast(localRay, maximumLength, sidedness, out rayHit))
{
//Transform the hit into world space.
Vector3.Multiply(ref ray.Direction, rayHit.T, out rayHit.Location);
Vector3.Add(ref rayHit.Location, ref ray.Position, out rayHit.Location);
Matrix3x3.Transform(ref rayHit.Normal, ref orientation, out rayHit.Normal);
return true;
}
}
rayHit = new RayHit();
return false;
}
///<summary>
/// Tests a ray against the surface of the mesh. This does not take into account solidity.
///</summary>
///<param name="ray">Ray to test.</param>
///<param name="maximumLength">Maximum length of the ray to test; in units of the ray's direction's length.</param>
///<param name="sidedness">Sidedness to use during the ray cast. This does not have to be the same as the mesh's sidedness.</param>
///<param name="rayHit">The hit location of the ray on the mesh, if any.</param>
///<returns>Whether or not the ray hit the mesh.</returns>
public bool RayCast(Ray ray, float maximumLength, TriangleSidedness sidedness, out RayHit rayHit)
{
//Put the ray into local space.
Ray localRay;
Matrix3x3 orientation;
Matrix3x3.CreateFromQuaternion(ref worldTransform.Orientation, out orientation);
Matrix3x3.TransformTranspose(ref ray.Direction, ref orientation, out localRay.Direction);
Vector3.Subtract(ref ray.Position, ref worldTransform.Position, out localRay.Position);
Matrix3x3.TransformTranspose(ref localRay.Position, ref orientation, out localRay.Position);
if (Shape.TriangleMesh.RayCast(localRay, maximumLength, sidedness, out rayHit))
{
//Transform the hit into world space.
Vector3.Multiply(ref ray.Direction, rayHit.T, out rayHit.Location);
Vector3.Add(ref rayHit.Location, ref ray.Position, out rayHit.Location);
Matrix3x3.Transform(ref rayHit.Normal, ref orientation, out rayHit.Normal);
return true;
}
rayHit = new RayHit();
return false;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="hit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit)
{
if (Shape.solidity == MobileMeshSolidity.Solid)
{
//If the convex cast is inside the mesh and the mesh is solid, it should return t = 0.
var ray = new Ray() { Position = startingTransform.Position, Direction = Toolbox.UpVector };
if (Shape.IsLocalRayOriginInMesh(ref ray, out hit))
{
hit = new RayHit() { Location = startingTransform.Position, Normal = new Vector3(), T = 0 };
return true;
}
}
hit = new RayHit();
BoundingBox boundingBox;
var transform = new AffineTransform {Translation = worldTransform.Position};
Matrix3x3.CreateFromQuaternion(ref worldTransform.Orientation, out transform.LinearTransform);
castShape.GetSweptLocalBoundingBox(ref startingTransform, ref transform, ref sweep, out boundingBox);
var tri = PhysicsThreadResources.GetTriangle();
var hitElements = CommonResources.GetIntList();
if (this.Shape.TriangleMesh.Tree.GetOverlaps(boundingBox, hitElements))
{
hit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
Shape.TriangleMesh.Data.GetTriangle(hitElements[i], out tri.vA, out tri.vB, out tri.vC);
AffineTransform.Transform(ref tri.vA, ref transform, out tri.vA);
AffineTransform.Transform(ref tri.vB, ref transform, out tri.vB);
AffineTransform.Transform(ref tri.vC, ref transform, out tri.vC);
Vector3 center;
Vector3.Add(ref tri.vA, ref tri.vB, out center);
Vector3.Add(ref center, ref tri.vC, out center);
Vector3.Multiply(ref center, 1f / 3f, out center);
Vector3.Subtract(ref tri.vA, ref center, out tri.vA);
Vector3.Subtract(ref tri.vB, ref center, out tri.vB);
Vector3.Subtract(ref tri.vC, ref center, out tri.vC);
tri.MaximumRadius = tri.vA.LengthSquared();
float radius = tri.vB.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
radius = tri.vC.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
tri.MaximumRadius = (float)Math.Sqrt(tri.MaximumRadius);
tri.collisionMargin = 0;
var triangleTransform = new RigidTransform {Orientation = Quaternion.Identity, Position = center};
RayHit tempHit;
if (MPRToolbox.Sweep(castShape, tri, ref sweep, ref Toolbox.ZeroVector, ref startingTransform, ref triangleTransform, out tempHit) && tempHit.T < hit.T)
{
hit = tempHit;
}
}
tri.MaximumRadius = 0;
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return hit.T != float.MaxValue;
}
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return false;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6ebd537c48d1e141a3fcf97cb3e7f97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,54 @@
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUutilities;
namespace BEPUphysics.BroadPhaseEntries.MobileCollidables
{
///<summary>
/// Special case collidable for reuseable triangles.
///</summary>
public class TriangleCollidable : ConvexCollidable<TriangleShape>
{
///<summary>
/// Constructs a new shapeless collidable.
///</summary>
public TriangleCollidable()
: base(new TriangleShape())
{
}
///<summary>
/// Constructs the triangle collidable using the given shape.
///</summary>
///<param name="shape">TriangleShape to use in the collidable.</param>
public TriangleCollidable(TriangleShape shape)
: base(shape)
{
}
///<summary>
/// Initializes the collidable using the new triangle shape, but does NOT
/// fire any shape-changed events.
///</summary>
///<param name="a">First vertex in the triangle.</param>
///<param name="b">Second vertex in the triangle. </param>
///<param name="c">Third vertex in the triangle. </param>
public void Initialize(ref Vector3 a, ref Vector3 b, ref Vector3 c)
{
var shape = Shape;
shape.collisionMargin = 0;
shape.sidedness = TriangleSidedness.DoubleSided;
shape.vA = a;
shape.vB = b;
shape.vC = c;
}
///<summary>
/// Cleans up the collidable by removing all events.
///</summary>
public void CleanUp()
{
events.RemoveAllEvents();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08b61a7da05b63444b644506b7dc619d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,129 @@
using System;
using BEPUphysics.CollisionShapes;
using BEPUphysics.Materials;
using BEPUphysics.CollisionRuleManagement;
using BEPUphysics.OtherSpaceStages;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Superclass of static collidable objects which can be added directly to a space. Static objects cannot move.
///</summary>
public abstract class StaticCollidable : Collidable, ISpaceObject, IMaterialOwner, IDeferredEventCreatorOwner
{
///<summary>
/// Performs common initialization.
///</summary>
protected StaticCollidable()
{
collisionRules.group = CollisionRules.DefaultKinematicCollisionGroup;
//Note that the Events manager is not created here. That is left for subclasses to implement so that the type is more specific.
//Entities can get away with having EntityCollidable specificity since you generally care more about the entity than the collidable,
//but with static objects, the collidable is the only important object. It would be annoying to cast to the type you know it is every time
//just to get access to some type-specific properties.
material = new Material();
materialChangedDelegate = OnMaterialChanged;
material.MaterialChanged += materialChangedDelegate;
}
protected override void OnShapeChanged(CollisionShape collisionShape)
{
if (!IgnoreShapeChanges)
UpdateBoundingBox();
}
internal Material material;
//NOT thread safe due to material change pair update.
///<summary>
/// Gets or sets the material used by the collidable.
///</summary>
public Material Material
{
get
{
return material;
}
set
{
if (material != null)
material.MaterialChanged -= materialChangedDelegate;
material = value;
if (material != null)
material.MaterialChanged += materialChangedDelegate;
OnMaterialChanged(material);
}
}
Action<Material> materialChangedDelegate;
protected virtual void OnMaterialChanged(Material newMaterial)
{
for (int i = 0; i < pairs.Count; i++)
{
pairs[i].UpdateMaterialProperties();
}
}
/// <summary>
/// Gets whether this collidable is associated with an active entity. Returns false for all static collidables.
/// </summary>
public override bool IsActive
{
get { return false; }
}
Space space;
Space ISpaceObject.Space
{
get
{
return space;
}
set
{
space = value;
}
}
///<summary>
/// Gets the space that owns the mesh.
///</summary>
public Space Space
{
get
{
return space;
}
}
void ISpaceObject.OnAdditionToSpace(Space newSpace)
{
}
void ISpaceObject.OnRemovalFromSpace(Space oldSpace)
{
}
IDeferredEventCreator IDeferredEventCreatorOwner.EventCreator
{
get
{
return EventCreator;
}
}
/// <summary>
/// Gets the event creator associated with this collidable.
/// </summary>
protected abstract IDeferredEventCreator EventCreator
{
get;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c559c80cfc2fc8148a2ab722b2ceed45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,155 @@
using System;
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.CollisionShapes;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUutilities;
using BEPUphysics.OtherSpaceStages;
using System.Collections.Generic;
using RigidTransform = BEPUutilities.RigidTransform;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Collection of unmoving collidable objects.
///</summary>
///<remarks>
/// Batching multiple static objects together into a StaticGroup as opposed to adding them separately to the Space avoids BroadPhase pollution, improving performance.
/// </remarks>
public class StaticGroup : StaticCollidable
{
///<summary>
/// Constructs a new static mesh.
///</summary>
///<param name="collidables">List of collidables in the static group.</param>
public StaticGroup(IList<Collidable> collidables)
{
base.Shape = new StaticGroupShape(collidables, this);
Events = new ContactEventManager<StaticGroup>();
}
///<summary>
/// Gets the shape used by the mesh. Unlike most collidable-shape pairs, StaticGroupShapes cannot be shared between multiple StaticGroups.
///</summary>
public new StaticGroupShape Shape
{
get
{
return (StaticGroupShape)shape;
}
}
protected internal ContactEventManager<StaticGroup> events;
///<summary>
/// Gets the event manager used by the mesh.
///</summary>
public ContactEventManager<StaticGroup> Events
{
get
{
return events;
}
set
{
if (value.Owner != null && //Can't use a manager which is owned by a different entity.
value != events) //Stay quiet if for some reason the same event manager is being set.
throw new ArgumentException("Event manager is already owned by a mesh; event managers cannot be shared.");
if (events != null)
events.Owner = null;
events = value;
if (events != null)
events.Owner = this;
}
}
protected internal override IContactEventTriggerer EventTriggerer
{
get { return events; }
}
protected override IDeferredEventCreator EventCreator
{
get { return events; }
}
/// <summary>
/// Updates the bounding box to the current state of the entry.
/// </summary>
public override void UpdateBoundingBox()
{
boundingBox = Shape.CollidableTree.BoundingBox;
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
RayCastResult result;
bool toReturn = Shape.RayCast(ray, maximumLength, out result);
rayHit = result.HitData;
return toReturn;
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public override bool RayCast(Ray ray, float maximumLength, Func<BroadPhaseEntry, bool> filter, out RayHit rayHit)
{
RayCastResult result;
bool toReturn = Shape.RayCast(ray, maximumLength, filter, out result);
rayHit = result.HitData;
return toReturn;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="hit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit)
{
RayCastResult result;
bool toReturn = Shape.ConvexCast(castShape, ref startingTransform, ref sweep, out result);
hit = result.HitData;
return toReturn;
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="filter">Test to apply to the entry. If it returns true, the entry is processed, otherwise the entry is ignored. If a collidable hierarchy is present
/// in the entry, this filter will be passed into inner ray casts.</param>
/// <param name="hit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, Func<BroadPhaseEntry, bool> filter, out RayHit hit)
{
RayCastResult result;
bool toReturn = Shape.ConvexCast(castShape, ref startingTransform, ref sweep, filter, out result);
hit = result.HitData;
return toReturn;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1df7628a490384f49a79b8158b69c8aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,254 @@
using System;
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUphysics.CollisionShapes;
using BEPUphysics.DataStructures;
using BEPUutilities;
using BEPUutilities.ResourceManagement;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUphysics.CollisionTests.CollisionAlgorithms;
using BEPUphysics.OtherSpaceStages;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Unmoving, collidable triangle mesh.
///</summary>
///<remarks>
/// The acceleration structure for the mesh is created individually for each
/// StaticMesh; if you want to create many meshes of the same model, consider using the
/// InstancedMesh.
/// </remarks>
public class StaticMesh : StaticCollidable
{
TriangleMesh mesh;
///<summary>
/// Gets the TriangleMesh acceleration structure used by the StaticMesh.
///</summary>
public TriangleMesh Mesh
{
get
{
return mesh;
}
}
///<summary>
/// Gets or sets the world transform of the mesh.
///</summary>
public AffineTransform WorldTransform
{
get
{
return ((TransformableMeshData)mesh.Data).worldTransform;
}
set
{
((TransformableMeshData)mesh.Data).WorldTransform = value;
mesh.Tree.Refit();
UpdateBoundingBox();
}
}
///<summary>
/// Constructs a new static mesh.
///</summary>
///<param name="vertices">Vertex positions of the mesh.</param>
///<param name="indices">Index list of the mesh.</param>
public StaticMesh(Vector3[] vertices, int[] indices)
{
base.Shape = new StaticMeshShape(vertices, indices);
Events = new ContactEventManager<StaticMesh>();
}
///<summary>
/// Constructs a new static mesh.
///</summary>
///<param name="vertices">Vertex positions of the mesh.</param>
///<param name="indices">Index list of the mesh.</param>
/// <param name="worldTransform">Transform to use to create the mesh initially.</param>
public StaticMesh(Vector3[] vertices, int[] indices, AffineTransform worldTransform)
{
base.Shape = new StaticMeshShape(vertices, indices, worldTransform);
Events = new ContactEventManager<StaticMesh>();
}
///<summary>
/// Gets the shape used by the mesh.
///</summary>
public new StaticMeshShape Shape
{
get
{
return (StaticMeshShape)shape;
}
}
internal TriangleSidedness sidedness = TriangleSidedness.DoubleSided;
///<summary>
/// Gets or sets the sidedness of the mesh. This can be used to ignore collisions and rays coming from a direction relative to the winding of the triangle.
///</summary>
public TriangleSidedness Sidedness
{
get
{
return sidedness;
}
set
{
sidedness = value;
}
}
internal bool improveBoundaryBehavior = true;
/// <summary>
/// Gets or sets whether or not the collision system should attempt to improve contact behavior at the boundaries between triangles.
/// This has a slight performance cost, but prevents objects sliding across a triangle boundary from 'bumping,' and otherwise improves
/// the robustness of contacts at edges and vertices.
/// </summary>
public bool ImproveBoundaryBehavior
{
get
{
return improveBoundaryBehavior;
}
set
{
improveBoundaryBehavior = value;
}
}
protected internal ContactEventManager<StaticMesh> events;
///<summary>
/// Gets the event manager used by the mesh.
///</summary>
public ContactEventManager<StaticMesh> Events
{
get
{
return events;
}
set
{
if (value.Owner != null && //Can't use a manager which is owned by a different entity.
value != events) //Stay quiet if for some reason the same event manager is being set.
throw new ArgumentException("Event manager is already owned by a mesh; event managers cannot be shared.");
if (events != null)
events.Owner = null;
events = value;
if (events != null)
events.Owner = this;
}
}
protected internal override IContactEventTriggerer EventTriggerer
{
get { return events; }
}
protected override IDeferredEventCreator EventCreator
{
get { return events; }
}
protected override void OnShapeChanged(CollisionShape collisionShape)
{
if (!IgnoreShapeChanges)
{
mesh = new TriangleMesh(Shape.TriangleMeshData);
UpdateBoundingBox();
}
}
/// <summary>
/// Updates the bounding box to the current state of the entry.
/// </summary>
public override void UpdateBoundingBox()
{
boundingBox = mesh.Tree.BoundingBox;
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
return mesh.RayCast(ray, maximumLength, sidedness, out rayHit);
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="hit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit)
{
hit = new RayHit();
BoundingBox boundingBox;
castShape.GetSweptBoundingBox(ref startingTransform, ref sweep, out boundingBox);
var tri = PhysicsThreadResources.GetTriangle();
var hitElements = CommonResources.GetIntList();
if (Mesh.Tree.GetOverlaps(boundingBox, hitElements))
{
hit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
mesh.Data.GetTriangle(hitElements[i], out tri.vA, out tri.vB, out tri.vC);
Vector3 center;
Vector3.Add(ref tri.vA, ref tri.vB, out center);
Vector3.Add(ref center, ref tri.vC, out center);
Vector3.Multiply(ref center, 1f / 3f, out center);
Vector3.Subtract(ref tri.vA, ref center, out tri.vA);
Vector3.Subtract(ref tri.vB, ref center, out tri.vB);
Vector3.Subtract(ref tri.vC, ref center, out tri.vC);
tri.MaximumRadius = tri.vA.LengthSquared();
float radius = tri.vB.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
radius = tri.vC.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
tri.MaximumRadius = (float)Math.Sqrt(tri.MaximumRadius);
tri.collisionMargin = 0;
var triangleTransform = new RigidTransform {Orientation = Quaternion.Identity, Position = center};
RayHit tempHit;
if (MPRToolbox.Sweep(castShape, tri, ref sweep, ref Toolbox.ZeroVector, ref startingTransform, ref triangleTransform, out tempHit) && tempHit.T < hit.T)
{
hit = tempHit;
}
}
tri.MaximumRadius = 0;
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return hit.T != float.MaxValue;
}
PhysicsThreadResources.GiveBack(tri);
CommonResources.GiveBack(hitElements);
return false;
}
///<summary>
/// Tests a ray against the mesh.
///</summary>
///<param name="ray">Ray to test.</param>
///<param name="maximumLength">Maximum length to test in units of the ray direction's length.</param>
///<param name="sidedness">Sidedness to use when raycasting. Doesn't have to be the same as the mesh's own sidedness.</param>
///<param name="rayHit">Data about the ray's intersection with the mesh, if any.</param>
///<returns>Whether or not the ray hit the mesh.</returns>
public bool RayCast(Ray ray, float maximumLength, TriangleSidedness sidedness, out RayHit rayHit)
{
return mesh.RayCast(ray, maximumLength, sidedness, out rayHit);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee0c6498732818a4ea34116398b3e2ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,298 @@
using System;
using BEPUphysics.BroadPhaseEntries.Events;
using BEPUutilities;
using BEPUphysics.CollisionShapes;
using BEPUphysics.CollisionTests.CollisionAlgorithms;
using BEPUphysics.OtherSpaceStages;
using BEPUutilities.DataStructures;
using BEPUutilities.ResourceManagement;
namespace BEPUphysics.BroadPhaseEntries
{
///<summary>
/// Heightfield-based unmovable collidable object.
///</summary>
public class Terrain : StaticCollidable
{
///<summary>
/// Gets the shape of this collidable.
///</summary>
public new TerrainShape Shape
{
get
{
return (TerrainShape)shape;
}
set
{
base.Shape = value;
}
}
/// <summary>
/// The sidedness of triangles in the terrain. Precomputed based on the transform.
/// </summary>
internal TriangleSidedness sidedness;
internal AffineTransform worldTransform;
///<summary>
/// Gets or sets the affine transform of the terrain.
///</summary>
public AffineTransform WorldTransform
{
get
{
return worldTransform;
}
set
{
worldTransform = value;
//Sidedness must be calibrated based on the transform.
//To do this, note a few things:
//1) All triangles have the same sidedness in the terrain. Winding is consistent. Calibrating for one triangle calibrates for all.
//2) Taking a triangle from the terrain into world space and computing the normal there for comparison is unneeded. Picking a fixed valid normal in local space (like {0, 1, 0}) is sufficient.
//3) Normals can't be transformed by a direct application of a general affine transform. The adjugate transpose must be used.
Matrix3x3 normalTransform;
Matrix3x3.AdjugateTranspose(ref worldTransform.LinearTransform, out normalTransform);
//If the world 'up' doesn't match the normal 'up', some reflection occurred which requires a winding flip.
if (Vector3.Dot(normalTransform.Up, worldTransform.LinearTransform.Up) < 0)
{
sidedness = TriangleSidedness.Clockwise;
}
else
{
sidedness = TriangleSidedness.Counterclockwise;
}
}
}
internal bool improveBoundaryBehavior = true;
/// <summary>
/// Gets or sets whether or not the collision system should attempt to improve contact behavior at the boundaries between triangles.
/// This has a slight performance cost, but prevents objects sliding across a triangle boundary from 'bumping,' and otherwise improves
/// the robustness of contacts at edges and vertices.
/// </summary>
public bool ImproveBoundaryBehavior
{
get
{
return improveBoundaryBehavior;
}
set
{
improveBoundaryBehavior = value;
}
}
protected internal ContactEventManager<Terrain> events;
///<summary>
/// Gets the event manager used by the Terrain.
///</summary>
public ContactEventManager<Terrain> Events
{
get
{
return events;
}
set
{
if (value.Owner != null && //Can't use a manager which is owned by a different entity.
value != events) //Stay quiet if for some reason the same event manager is being set.
throw new ArgumentException("Event manager is already owned by a Terrain; event managers cannot be shared.");
if (events != null)
events.Owner = null;
events = value;
if (events != null)
events.Owner = this;
}
}
protected internal override IContactEventTriggerer EventTriggerer
{
get { return events; }
}
protected override IDeferredEventCreator EventCreator
{
get { return events; }
}
internal float thickness;
/// <summary>
/// Gets or sets the thickness of the terrain. This defines how far below the triangles of the terrain's surface the terrain 'body' extends.
/// Anything within the body of the terrain will be pulled back up to the surface.
/// </summary>
public float Thickness
{
get
{
return thickness;
}
set
{
if (value < 0)
throw new ArgumentException("Cannot use a negative thickness value.");
//Modify the bounding box to include the new thickness.
Vector3 down = Vector3.Normalize(worldTransform.LinearTransform.Down);
Vector3 thicknessOffset = down * (value - thickness);
//Use the down direction rather than the thicknessOffset to determine which
//component of the bounding box to subtract, since the down direction contains all
//previous extra thickness.
if (down.X < 0)
boundingBox.Min.X += thicknessOffset.X;
else
boundingBox.Max.X += thicknessOffset.X;
if (down.Y < 0)
boundingBox.Min.Y += thicknessOffset.Y;
else
boundingBox.Max.Y += thicknessOffset.Y;
if (down.Z < 0)
boundingBox.Min.Z += thicknessOffset.Z;
else
boundingBox.Max.Z += thicknessOffset.Z;
thickness = value;
}
}
///<summary>
/// Constructs a new Terrain.
///</summary>
///<param name="shape">Shape to use for the terrain.</param>
///<param name="worldTransform">Transform to use for the terrain.</param>
public Terrain(TerrainShape shape, AffineTransform worldTransform)
{
WorldTransform = worldTransform;
Shape = shape;
Events = new ContactEventManager<Terrain>();
}
///<summary>
/// Constructs a new Terrain.
///</summary>
///<param name="heights">Height data to use to create the TerrainShape.</param>
///<param name="worldTransform">Transform to use for the terrain.</param>
public Terrain(float[,] heights, AffineTransform worldTransform)
: this(new TerrainShape(heights), worldTransform)
{
}
///<summary>
/// Updates the bounding box of the terrain.
///</summary>
public override void UpdateBoundingBox()
{
Shape.GetBoundingBox(ref worldTransform, out boundingBox);
//Include the thickness of the terrain.
Vector3 thicknessOffset = Vector3.Normalize(worldTransform.LinearTransform.Down) * thickness;
if (thicknessOffset.X < 0)
boundingBox.Min.X += thicknessOffset.X;
else
boundingBox.Max.X += thicknessOffset.X;
if (thicknessOffset.Y < 0)
boundingBox.Min.Y += thicknessOffset.Y;
else
boundingBox.Max.Y += thicknessOffset.Y;
if (thicknessOffset.Z < 0)
boundingBox.Min.Z += thicknessOffset.Z;
else
boundingBox.Max.Z += thicknessOffset.Z;
}
/// <summary>
/// Tests a ray against the entry.
/// </summary>
/// <param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length, in units of the ray's direction's length, to test.</param>
/// <param name="rayHit">Hit location of the ray on the entry, if any.</param>
/// <returns>Whether or not the ray hit the entry.</returns>
public override bool RayCast(Ray ray, float maximumLength, out RayHit rayHit)
{
return Shape.RayCast(ref ray, maximumLength, ref worldTransform, out rayHit);
}
/// <summary>
/// Casts a convex shape against the collidable.
/// </summary>
/// <param name="castShape">Shape to cast.</param>
/// <param name="startingTransform">Initial transform of the shape.</param>
/// <param name="sweep">Sweep to apply to the shape.</param>
/// <param name="hit">Hit data, if any.</param>
/// <returns>Whether or not the cast hit anything.</returns>
public override bool ConvexCast(CollisionShapes.ConvexShapes.ConvexShape castShape, ref RigidTransform startingTransform, ref Vector3 sweep, out RayHit hit)
{
hit = new RayHit();
BoundingBox localSpaceBoundingBox;
castShape.GetSweptLocalBoundingBox(ref startingTransform, ref worldTransform, ref sweep, out localSpaceBoundingBox);
var tri = PhysicsThreadResources.GetTriangle();
var hitElements = new QuickList<int>(BufferPools<int>.Thread);
if (Shape.GetOverlaps(localSpaceBoundingBox, ref hitElements))
{
hit.T = float.MaxValue;
for (int i = 0; i < hitElements.Count; i++)
{
Shape.GetTriangle(hitElements.Elements[i], ref worldTransform, out tri.vA, out tri.vB, out tri.vC);
Vector3 center;
Vector3.Add(ref tri.vA, ref tri.vB, out center);
Vector3.Add(ref center, ref tri.vC, out center);
Vector3.Multiply(ref center, 1f / 3f, out center);
Vector3.Subtract(ref tri.vA, ref center, out tri.vA);
Vector3.Subtract(ref tri.vB, ref center, out tri.vB);
Vector3.Subtract(ref tri.vC, ref center, out tri.vC);
tri.MaximumRadius = tri.vA.LengthSquared();
float radius = tri.vB.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
radius = tri.vC.LengthSquared();
if (tri.MaximumRadius < radius)
tri.MaximumRadius = radius;
tri.MaximumRadius = (float)Math.Sqrt(tri.MaximumRadius);
tri.collisionMargin = 0;
var triangleTransform = new RigidTransform { Orientation = Quaternion.Identity, Position = center };
RayHit tempHit;
if (MPRToolbox.Sweep(castShape, tri, ref sweep, ref Toolbox.ZeroVector, ref startingTransform, ref triangleTransform, out tempHit) && tempHit.T < hit.T)
{
hit = tempHit;
}
}
tri.MaximumRadius = 0;
PhysicsThreadResources.GiveBack(tri);
hitElements.Dispose();
return hit.T != float.MaxValue;
}
PhysicsThreadResources.GiveBack(tri);
hitElements.Dispose();
return false;
}
///<summary>
/// Gets the position of a vertex at the given indices.
///</summary>
///<param name="i">First dimension index into the heightmap array.</param>
///<param name="j">Second dimension index into the heightmap array.</param>
///<param name="position">Position at the given indices.</param>
public void GetPosition(int i, int j, out Vector3 position)
{
Shape.GetPosition(i, j, ref worldTransform, out position);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 96222228af49d924c9738f1fb7b4b2c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 67c40ab7ce09fb7418ecf7305c902ddd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,114 @@
using BEPUphysics.BroadPhaseEntries;
using BEPUutilities;
using BEPUphysics.CollisionRuleManagement;
using BEPUutilities.DataStructures;
using System;
using BEPUutilities.Threading;
namespace BEPUphysics.BroadPhaseSystems
{
///<summary>
/// Superclass of all broad phases. Broad phases collect overlapping broad phase entry pairs.
///</summary>
public abstract class BroadPhase : MultithreadedProcessingStage
{
readonly SpinLock overlapAddLock = new SpinLock();
///<summary>
/// Gets the object which is locked by the broadphase during synchronized update processes.
///</summary>
public object Locker { get; protected set; }
protected BroadPhase()
{
Locker = new object();
Enabled = true;
}
protected BroadPhase(IParallelLooper parallelLooper)
: this()
{
ParallelLooper = parallelLooper;
AllowMultithreading = true;
}
//TODO: Initial capacity? Special collection type other than list due to structs? RawList? Clear at beginning of each frame?
readonly RawList<BroadPhaseOverlap> overlaps = new RawList<BroadPhaseOverlap>();
/// <summary>
/// Gets the list of overlaps identified in the previous broad phase update.
/// </summary>
public RawList<BroadPhaseOverlap> Overlaps
{
get { return overlaps; }
}
///<summary>
/// Gets an interface to the broad phase's support for volume-based queries.
///</summary>
public IQueryAccelerator QueryAccelerator { get; protected set; }
/// <summary>
/// Adds an entry to the broad phase.
/// </summary>
/// <param name="entry">Entry to add.</param>
public virtual void Add(BroadPhaseEntry entry)
{
if (entry.BroadPhase == null)
entry.BroadPhase = this;
else
throw new ArgumentException("Cannot add entry; it already belongs to a broad phase.");
}
/// <summary>
/// Removes an entry from the broad phase.
/// </summary>
/// <param name="entry">Entry to remove.</param>
public virtual void Remove(BroadPhaseEntry entry)
{
if (entry.BroadPhase == this)
entry.BroadPhase = null;
else
throw new ArgumentException("Cannot remove entry; it does not belong to this broad phase.");
}
protected internal void AddOverlap(BroadPhaseOverlap overlap)
{
overlapAddLock.Enter();
overlaps.Add(overlap);
overlapAddLock.Exit();
}
/// <summary>
/// Adds a broad phase overlap if the collision rules permit it.
/// </summary>
/// <param name="entryA">First entry of the overlap.</param>
/// <param name="entryB">Second entry of the overlap.</param>
protected internal void TryToAddOverlap(BroadPhaseEntry entryA, BroadPhaseEntry entryB)
{
CollisionRule rule;
if ((rule = GetCollisionRule(entryA, entryB)) < CollisionRule.NoBroadPhase)
{
overlapAddLock.Enter();
overlaps.Add(new BroadPhaseOverlap(entryA, entryB, rule));
overlapAddLock.Exit();
}
}
protected internal CollisionRule GetCollisionRule(BroadPhaseEntry entryA, BroadPhaseEntry entryB)
{
if (entryA.IsActive || entryB.IsActive)
return CollisionRules.collisionRuleCalculator(entryA, entryB);
return CollisionRule.NoBroadPhase;
}
//TODO: Consider what happens when an overlap is found twice. How should it be dealt with?
//Can the DBH spit out redundancies?
//The PUG definitely can- consider two entities that are both in two adjacent cells.
//Could say 'whatever' to it and handle it in the narrow phase- use the NeedsUpdate property.
//If NeedsUpdate is false, that means it's already been updated once. Consider multithreaded problems.
//Would require an interlocked compare exchange or something similar to protect it.
//Slightly ruins the whole 'embarassingly parallel' aspect.
//Need a something which has O(1) add, O(1) contains check, and fast iteration without requiring external nodes since everything gets regenerated each frame.
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90cf4c0f91955684bb162227b2556e17
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,95 @@
using System;
using BEPUphysics.BroadPhaseEntries;
using BEPUphysics.CollisionRuleManagement;
namespace BEPUphysics.BroadPhaseSystems
{
/// <summary>
/// A pair of overlapping BroadPhaseEntries.
/// </summary>
public struct BroadPhaseOverlap : IEquatable<BroadPhaseOverlap>
{
internal BroadPhaseEntry entryA;
/// <summary>
/// First entry in the pair.
/// </summary>
public BroadPhaseEntry EntryA
{
get { return entryA; }
}
internal BroadPhaseEntry entryB;
/// <summary>
/// Second entry in the pair.
/// </summary>
public BroadPhaseEntry EntryB
{
get { return entryB; }
}
internal CollisionRule collisionRule;
/// <summary>
/// Constructs an overlap.
/// </summary>
/// <param name="entryA">First entry in the pair.</param>
/// <param name="entryB">Second entry in the pair.</param>
public BroadPhaseOverlap(BroadPhaseEntry entryA, BroadPhaseEntry entryB)
{
this.entryA = entryA;
this.entryB = entryB;
collisionRule = CollisionRules.DefaultCollisionRule;
}
/// <summary>
/// Constructs an overlap.
/// </summary>
/// <param name="entryA">First entry in the pair.</param>
/// <param name="entryB">Second entry in the pair.</param>
/// <param name="collisionRule">Collision rule calculated for the pair.</param>
public BroadPhaseOverlap(BroadPhaseEntry entryA, BroadPhaseEntry entryB, CollisionRule collisionRule)
{
this.entryA = entryA;
this.entryB = entryB;
this.collisionRule = collisionRule;
}
/// <summary>
/// Gets the collision rule calculated for the pair.
/// </summary>
public CollisionRule CollisionRule
{
get { return collisionRule; }
}
/// <summary>
/// Gets the hash code of the object.
/// </summary>
/// <returns>Hash code of the object.</returns>
public override int GetHashCode()
{
//TODO: Use old prime-based system?
return (int)((entryA.hashCode + entryB.hashCode) * 0xd8163841);
}
#region IEquatable<BroadPhaseOverlap> Members
/// <summary>
/// Compares the overlaps for equality based on the involved entries.
/// </summary>
/// <param name="other">Overlap to compare.</param>
/// <returns>Whether or not the overlaps were equal.</returns>
public bool Equals(BroadPhaseOverlap other)
{
return (other.entryA == entryA && other.entryB == entryB) || (other.entryA == entryB && other.entryB == entryA);
}
#endregion
public override string ToString()
{
return "{" + entryA + ", " + entryB + "}";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 526fe092ef36bd443b926b7bdecca2b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics.BroadPhaseEntries;
namespace BEPUphysics.BroadPhaseSystems
{
public class BruteForce : BroadPhase
{
public List<BroadPhaseEntry> entries = new List<BroadPhaseEntry>();
public override void Add(BroadPhaseEntry entry)
{
entries.Add(entry);
}
public override void Remove(BroadPhaseEntry entry)
{
entries.Remove(entry);
}
protected override void UpdateMultithreaded()
{
UpdateSingleThreaded();
}
protected override void UpdateSingleThreaded()
{
Overlaps.Clear();
for (int i = 0; i < entries.Count; i++)
{
for (int j = i + 1; j < entries.Count; j++)
{
if (entries[i].boundingBox.Intersects(entries[j].boundingBox))
base.TryToAddOverlap(entries[i], entries[j]);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5cef2aa8f5cd7e4797ccaa063339e3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 71b6a18ad0de52c42a331889dc90f94d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,344 @@
using System;
using System.Collections.Generic;
using BEPUphysics.BroadPhaseEntries;
using BEPUutilities.DataStructures;
using BEPUutilities.ResourceManagement;
using BEPUutilities;
using BEPUutilities.Threading;
namespace BEPUphysics.BroadPhaseSystems.Hierarchies
{
/// <summary>
/// Broad phase that incrementally updates the internal tree acceleration structure.
/// </summary>
/// <remarks>
/// This is a good all-around broad phase; its performance is consistent and all queries are supported and speedy.
/// The memory usage is higher than simple one-axis sort and sweep, but a bit lower than the Grid2DSortAndSweep option.
/// </remarks>
public class DynamicHierarchy : BroadPhase
{
internal Node root;
/// <summary>
/// Constructs a new dynamic hierarchy broad phase.
/// </summary>
public DynamicHierarchy()
{
multithreadedRefit = MultithreadedRefit;
multithreadedOverlap = MultithreadedOverlap;
QueryAccelerator = new DynamicHierarchyQueryAccelerator(this);
}
/// <summary>
/// Constructs a new dynamic hierarchy broad phase.
/// </summary>
/// <param name="parallelLooper">Parallel loop provider to use in the broad phase.</param>
public DynamicHierarchy(IParallelLooper parallelLooper)
: base(parallelLooper)
{
multithreadedRefit = MultithreadedRefit;
multithreadedOverlap = MultithreadedOverlap;
QueryAccelerator = new DynamicHierarchyQueryAccelerator(this);
}
/// <summary>
/// This is a few test-based values which help threaded scaling.
/// By going deeper into the trees, a better distribution of work is achieved.
/// Going above the tested core count theoretically benefits from a '0 if power of 2, 2 otherwise' rule of thumb.
/// </summary>
private int[] threadSplitOffsets = new[]
#if WINDOWS
{ 0, 0, 4, 1, 2, 2, 2, 0, 2, 2, 2, 2 };
#else
{ 2, 2, 2, 1};
#endif
#if PROFILE
/// <summary>
/// Gets the time used in refitting the acceleration structure and making any necessary incremental improvements.
/// </summary>
public double RefitTime
{
get
{
return (endRefit - startRefit) / (double)Stopwatch.Frequency;
}
}
/// <summary>
/// Gets the time used in testing the tree against itself to find overlapping pairs.
/// </summary>
public double OverlapTime
{
get
{
return (endOverlap - endRefit) / (double)Stopwatch.Frequency;
}
}
long startRefit, endRefit;
long endOverlap;
#endif
#region Multithreading
public void MultithreadedRefitPhase(int splitDepth)
{
if (splitDepth > 0)
{
root.CollectMultithreadingNodes(splitDepth, 1, multithreadingSourceNodes);
//Go through every node and refit it.
ParallelLooper.ForLoop(0, multithreadingSourceNodes.Count, multithreadedRefit);
multithreadingSourceNodes.Clear();
//Now that the subtrees belonging to the source nodes are refit, refit the top nodes.
//Sometimes, this will go deeper than necessary because the refit process may require an extremely high level (nonmultithreaded) revalidation.
//The waste cost is a matter of nanoseconds due to the simplicity of the operations involved.
root.PostRefit(splitDepth, 1);
}
else
{
SingleThreadedRefitPhase();
}
}
public void MultithreadedOverlapPhase(int splitDepth)
{
if (splitDepth > 0)
{
//The trees are now fully refit (and revalidated, if the refit process found it to be necessary).
//The overlap traversal is conceptually similar to the multithreaded refit, but is a bit easier since there's no need to go back up the stack.
if (!root.IsLeaf) //If the root is a leaf, it's alone- nothing to collide against! This test is required by the assumptions of the leaf-leaf test.
{
root.GetMultithreadedOverlaps(root, splitDepth, 1, this, multithreadingSourceOverlaps);
ParallelLooper.ForLoop(0, multithreadingSourceOverlaps.Count, multithreadedOverlap);
multithreadingSourceOverlaps.Clear();
}
}
else
{
SingleThreadedOverlapPhase();
}
}
public int GetSplitDepth()
{
//To multithread the tree traversals, we have to do a little single threaded work.
//Dive down into the tree far enough that there are enough nodes to split amongst all the threads in the thread manager.
//The depth to which we dive is offset by some precomputed values (when available) or a guess based on whether or not the
//thread count is a power of 2. Thread counts which are a power of 2 match well to the binary tree, while other thread counts
//require going deeper for better distributions.
int offset = ParallelLooper.ThreadCount <= threadSplitOffsets.Length
? threadSplitOffsets[ParallelLooper.ThreadCount - 1]
: (ParallelLooper.ThreadCount & (ParallelLooper.ThreadCount - 1)) == 0 ? 0 : 2;
return offset + (int)Math.Ceiling(Math.Log(ParallelLooper.ThreadCount, 2));
}
protected override void UpdateMultithreaded()
{
lock (Locker)
{
Overlaps.Clear();
if (root != null)
{
var splitDepth = GetSplitDepth();
#if PROFILE
startRefit = Stopwatch.GetTimestamp();
#endif
MultithreadedRefitPhase(splitDepth);
#if PROFILE
endRefit = Stopwatch.GetTimestamp();
#endif
MultithreadedOverlapPhase(splitDepth);
#if PROFILE
endOverlap = Stopwatch.GetTimestamp();
#endif
}
}
}
internal struct NodePair
{
internal Node a;
internal Node b;
}
RawList<Node> multithreadingSourceNodes = new RawList<Node>(4);
Action<int> multithreadedRefit;
void MultithreadedRefit(int i)
{
multithreadingSourceNodes.Elements[i].Refit();
}
RawList<NodePair> multithreadingSourceOverlaps = new RawList<NodePair>(10);
Action<int> multithreadedOverlap;
void MultithreadedOverlap(int i)
{
var overlap = multithreadingSourceOverlaps.Elements[i];
//Note: It's okay not to check to see if a and b are equal and leaf nodes, because the systems which added nodes to the list already did it.
overlap.a.GetOverlaps(overlap.b, this);
}
#endregion
public void SingleThreadedRefitPhase()
{
root.Refit();
}
public void SingleThreadedOverlapPhase()
{
if (!root.IsLeaf) //If the root is a leaf, it's alone- nothing to collide against! This test is required by the assumptions of the leaf-leaf test.
root.GetOverlaps(root, this);
}
protected override void UpdateSingleThreaded()
{
lock (Locker)
{
Overlaps.Clear();
if (root != null)
{
#if PROFILE
startRefit = Stopwatch.GetTimestamp();
#endif
SingleThreadedRefitPhase();
#if PROFILE
endRefit = Stopwatch.GetTimestamp();
#endif
SingleThreadedOverlapPhase();
#if PROFILE
endOverlap = Stopwatch.GetTimestamp();
#endif
}
}
}
UnsafeResourcePool<LeafNode> leafNodes = new UnsafeResourcePool<LeafNode>();
/// <summary>
/// Adds an entry to the hierarchy.
/// </summary>
/// <param name="entry">Entry to add.</param>
public override void Add(BroadPhaseEntry entry)
{
base.Add(entry);
//Entities do not set up their own bounding box before getting stuck in here. If they're all zeroed out, the tree will be horrible.
Vector3 offset;
Vector3.Subtract(ref entry.boundingBox.Max, ref entry.boundingBox.Min, out offset);
if (offset.X * offset.Y * offset.Z == 0)
entry.UpdateBoundingBox();
//Could buffer additions to get a better construction in the tree.
var node = leafNodes.Take();
node.Initialize(entry);
if (root == null)
{
//Empty tree. This is the first and only node.
root = node;
}
else
{
if (root.IsLeaf) //Root is alone.
root.TryToInsert(node, out root);
else
{
BoundingBox.CreateMerged(ref node.BoundingBox, ref root.BoundingBox, out root.BoundingBox);
var internalNode = (InternalNode)root;
Vector3.Subtract(ref root.BoundingBox.Max, ref root.BoundingBox.Min, out offset);
internalNode.currentVolume = offset.X * offset.Y * offset.Z;
//internalNode.maximumVolume = internalNode.currentVolume * InternalNode.MaximumVolumeScale;
//The caller is responsible for the merge.
var treeNode = root;
while (!treeNode.TryToInsert(node, out treeNode)) ;//TryToInsert returns the next node, if any, and updates node bounding box.
}
}
}
/// <summary>
/// Removes an entry from the hierarchy.
/// </summary>
/// <param name="entry">Entry to remove.</param>
public override void Remove(BroadPhaseEntry entry)
{
if (root == null)
throw new InvalidOperationException("Entry not present in the hierarchy.");
//Attempt to search for the entry with a boundingbox lookup first.
if (!RemoveFast(entry))
{
//Oof, could not locate it with the fast method; it must have been force-moved or something.
//Fall back to a slow brute force approach.
if (!RemoveBrute(entry))
{
throw new InvalidOperationException("Entry not present in the hierarchy.");
}
}
}
internal bool RemoveFast(BroadPhaseEntry entry)
{
LeafNode leafNode;
//Update the root with the replacement just in case the removal triggers a root change.
if (root.RemoveFast(entry, out leafNode, out root))
{
leafNode.CleanUp();
leafNodes.GiveBack(leafNode);
base.Remove(entry);
return true;
}
return false;
}
internal bool RemoveBrute(BroadPhaseEntry entry)
{
LeafNode leafNode;
//Update the root with the replacement just in case the removal triggers a root change.
if (root.Remove(entry, out leafNode, out root))
{
leafNode.CleanUp();
leafNodes.GiveBack(leafNode);
base.Remove(entry);
return true;
}
return false;
}
#region Debug
internal void Analyze(List<int> depths, out int nodeCount)
{
nodeCount = 0;
root.Analyze(depths, 0, ref nodeCount);
}
/// <summary>
/// Forces a full rebuild of the tree. Useful to return the tree to a decent level of quality if the tree has gotten horribly messed up.
/// Watch out, this is a slow operation. Expect to drop frames.
/// </summary>
public void ForceRebuild()
{
if (root != null && !root.IsLeaf)
{
((InternalNode)root).Revalidate();
}
}
/// <summary>
/// Measures the cost of the tree, based on the volume of the tree's nodes.
/// Approximates the expected cost of volume-based queries against the tree.
/// Useful for comparing against other trees.
/// </summary>
/// <returns>Cost of the tree.</returns>
public float MeasureCostMetric()
{
if (root != null)
{
var offset = root.BoundingBox.Max - root.BoundingBox.Min;
var volume = offset.X * offset.Y * offset.Z;
if (volume < 1e-9f)
return 0;
return root.MeasureSubtreeCost() / volume;
}
return 0;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11d3b39dfd263914596b796ef47a0640
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,857 @@
using System;
using System.Collections.Generic;
using BEPUphysics.BroadPhaseEntries;
using BEPUutilities;
using BEPUutilities.DataStructures;
using BEPUutilities.ResourceManagement;
namespace BEPUphysics.BroadPhaseSystems.Hierarchies
{
internal abstract class Node
{
internal BoundingBox BoundingBox;
internal abstract void GetOverlaps(ref BoundingBox boundingBox, IList<BroadPhaseEntry> outputOverlappedElements);
internal abstract void GetOverlaps(ref BoundingSphere boundingSphere, IList<BroadPhaseEntry> outputOverlappedElements);
//internal abstract void GetOverlaps(ref BoundingFrustum boundingFrustum, IList<BroadPhaseEntry> outputOverlappedElements);
internal abstract void GetOverlaps(ref Ray ray, float maximumLength, IList<BroadPhaseEntry> outputOverlappedElements);
internal abstract void GetOverlaps(Node node, DynamicHierarchy owner);
internal abstract bool IsLeaf { get; }
internal abstract Node ChildA { get; }
internal abstract Node ChildB { get; }
internal abstract BroadPhaseEntry Element { get; }
internal abstract bool TryToInsert(LeafNode node, out Node treeNode);
internal abstract void Analyze(List<int> depths, int depth, ref int nodeCount);
internal abstract void Refit();
internal abstract void RetrieveNodes(RawList<LeafNode> leafNodes);
internal abstract void CollectMultithreadingNodes(int splitDepth, int currentDepth, RawList<Node> multithreadingSourceNodes);
internal abstract void PostRefit(int splitDepth, int currentDepth);
internal abstract void GetMultithreadedOverlaps(Node opposingNode, int splitDepth, int currentDepth, DynamicHierarchy owner, RawList<DynamicHierarchy.NodePair> multithreadingSourceOverlaps);
internal abstract bool Remove(BroadPhaseEntry entry, out LeafNode leafNode, out Node replacementNode);
internal abstract bool RemoveFast(BroadPhaseEntry entry, out LeafNode leafNode, out Node replacementNode);
internal abstract float MeasureSubtreeCost();
}
internal sealed class InternalNode : Node
{
internal Node childA;
internal Node childB;
internal float currentVolume;
internal float maximumVolume;
internal static float MaximumVolumeScale = 1.4f;
internal override Node ChildA
{
get
{
return childA;
}
}
internal override Node ChildB
{
get
{
return childB;
}
}
internal override BroadPhaseEntry Element
{
get
{
return default(BroadPhaseEntry);
}
}
internal override bool IsLeaf
{
get { return false; }
}
internal override void GetOverlaps(ref BoundingBox boundingBox, IList<BroadPhaseEntry> outputOverlappedElements)
{
//Users of the GetOverlaps method will have to check the bounding box before calling
//root.getoverlaps. This is actually desired in some cases, since the outer bounding box is used
//to determine a pair, and further overlap tests shouldn'BroadPhaseEntry bother retesting the root.
bool intersects;
childA.BoundingBox.Intersects(ref boundingBox, out intersects);
if (intersects)
childA.GetOverlaps(ref boundingBox, outputOverlappedElements);
childB.BoundingBox.Intersects(ref boundingBox, out intersects);
if (intersects)
childB.GetOverlaps(ref boundingBox, outputOverlappedElements);
}
internal override void GetOverlaps(ref BoundingSphere boundingSphere, IList<BroadPhaseEntry> outputOverlappedElements)
{
bool intersects;
childA.BoundingBox.Intersects(ref boundingSphere, out intersects);
if (intersects)
childA.GetOverlaps(ref boundingSphere, outputOverlappedElements);
childB.BoundingBox.Intersects(ref boundingSphere, out intersects);
if (intersects)
childB.GetOverlaps(ref boundingSphere, outputOverlappedElements);
}
//internal override void GetOverlaps(ref BoundingFrustum boundingFrustum, IList<BroadPhaseEntry> outputOverlappedElements)
//{
// bool intersects;
// boundingFrustum.Intersects(ref childA.BoundingBox, out intersects);
// if (intersects)
// childA.GetOverlaps(ref boundingFrustum, outputOverlappedElements);
// boundingFrustum.Intersects(ref childB.BoundingBox, out intersects);
// if (intersects)
// childB.GetOverlaps(ref boundingFrustum, outputOverlappedElements);
//}
internal override void GetOverlaps(ref Ray ray, float maximumLength, IList<BroadPhaseEntry> outputOverlappedElements)
{
float result;
if (ray.Intersects(ref childA.BoundingBox, out result) && result < maximumLength)
childA.GetOverlaps(ref ray, maximumLength, outputOverlappedElements);
if (ray.Intersects(ref childB.BoundingBox, out result) && result < maximumLength)
childB.GetOverlaps(ref ray, maximumLength, outputOverlappedElements);
}
internal override void GetOverlaps(Node opposingNode, DynamicHierarchy owner)
{
bool intersects;
if (this == opposingNode)
{
//We are being compared against ourselves!
//Obviously we're an internal node, so spawn three children:
//A versus A:
if (!childA.IsLeaf) //This is performed in the child method usually by convention, but this saves some time.
childA.GetOverlaps(childA, owner);
//B versus B:
if (!childB.IsLeaf) //This is performed in the child method usually by convention, but this saves some time.
childB.GetOverlaps(childB, owner);
//A versus B (if they intersect):
childA.BoundingBox.Intersects(ref childB.BoundingBox, out intersects);
if (intersects)
childA.GetOverlaps(childB, owner);
}
else
{
//Two different nodes. The other one may be a leaf.
if (opposingNode.IsLeaf)
{
//If it's a leaf, go deeper in our hierarchy, but not the opposition.
childA.BoundingBox.Intersects(ref opposingNode.BoundingBox, out intersects);
if (intersects)
childA.GetOverlaps(opposingNode, owner);
childB.BoundingBox.Intersects(ref opposingNode.BoundingBox, out intersects);
if (intersects)
childB.GetOverlaps(opposingNode, owner);
}
else
{
var opposingChildA = opposingNode.ChildA;
var opposingChildB = opposingNode.ChildB;
//If it's not a leaf, try to go deeper in both hierarchies.
childA.BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
childA.GetOverlaps(opposingChildA, owner);
childA.BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
childA.GetOverlaps(opposingChildB, owner);
childB.BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
childB.GetOverlaps(opposingChildA, owner);
childB.BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
childB.GetOverlaps(opposingChildB, owner);
}
}
}
internal static LockingResourcePool<InternalNode> nodePool = new LockingResourcePool<InternalNode>();
internal override bool TryToInsert(LeafNode node, out Node treeNode)
{
//Since we are an internal node, we know we have two children.
//Regardless of what kind of nodes they are, figure out which would be a better choice to merge the new node with.
//Use the path which produces the smallest 'volume.'
BoundingBox mergedA, mergedB;
BoundingBox.CreateMerged(ref childA.BoundingBox, ref node.BoundingBox, out mergedA);
BoundingBox.CreateMerged(ref childB.BoundingBox, ref node.BoundingBox, out mergedB);
Vector3 offset;
float originalAVolume, originalBVolume;
Vector3.Subtract(ref childA.BoundingBox.Max, ref childA.BoundingBox.Min, out offset);
originalAVolume = offset.X * offset.Y * offset.Z;
Vector3.Subtract(ref childB.BoundingBox.Max, ref childB.BoundingBox.Min, out offset);
originalBVolume = offset.X * offset.Y * offset.Z;
float mergedAVolume, mergedBVolume;
Vector3.Subtract(ref mergedA.Max, ref mergedA.Min, out offset);
mergedAVolume = offset.X * offset.Y * offset.Z;
Vector3.Subtract(ref mergedB.Max, ref mergedB.Min, out offset);
mergedBVolume = offset.X * offset.Y * offset.Z;
//Could use factor increase or absolute difference
if (mergedAVolume - originalAVolume < mergedBVolume - originalBVolume)
{
//merging A produces a better result.
if (childA.IsLeaf)
{
var newChildA = nodePool.Take();
newChildA.BoundingBox = mergedA;
newChildA.childA = this.childA;
newChildA.childB = node;
newChildA.currentVolume = mergedAVolume;
//newChildA.maximumVolume = newChildA.currentVolume * MaximumVolumeScale;
childA = newChildA;
treeNode = null;
return true;
}
else
{
childA.BoundingBox = mergedA;
var internalNode = (InternalNode)childA;
internalNode.currentVolume = mergedAVolume;
//internalNode.maximumVolume = internalNode.currentVolume * MaximumVolumeScale;
treeNode = childA;
return false;
}
}
else
{
//merging B produces a better result.
if (childB.IsLeaf)
{
//Target is a leaf! Return.
var newChildB = nodePool.Take();
newChildB.BoundingBox = mergedB;
newChildB.childA = node;
newChildB.childB = this.childB;
newChildB.currentVolume = mergedBVolume;
//newChildB.maximumVolume = newChildB.currentVolume * MaximumVolumeScale;
childB = newChildB;
treeNode = null;
return true;
}
else
{
childB.BoundingBox = mergedB;
treeNode = childB;
var internalNode = (InternalNode)childB;
internalNode.currentVolume = mergedBVolume;
//internalNode.maximumVolume = internalNode.currentVolume * MaximumVolumeScale;
return false;
}
}
}
public override string ToString()
{
return "{" + childA + ", " + childB + "}";
}
internal override void Analyze(List<int> depths, int depth, ref int nodeCount)
{
nodeCount++;
childA.Analyze(depths, depth + 1, ref nodeCount);
childB.Analyze(depths, depth + 1, ref nodeCount);
}
internal override void Refit()
{
if (currentVolume > maximumVolume)
{
Revalidate();
return;
}
childA.Refit();
childB.Refit();
BoundingBox.CreateMerged(ref childA.BoundingBox, ref childB.BoundingBox, out BoundingBox);
//float DEBUGlastVolume = currentVolume;
currentVolume = (BoundingBox.Max.X - BoundingBox.Min.X) * (BoundingBox.Max.Y - BoundingBox.Min.Y) * (BoundingBox.Max.Z - BoundingBox.Min.Z);
//if (Math.Abs(currentVolume - DEBUGlastVolume) > .000001 * (DEBUGlastVolume + currentVolume))
// Debug.WriteLine(":Break>:)");
}
internal static LockingResourcePool<RawList<LeafNode>> nodeListPool = new LockingResourcePool<RawList<LeafNode>>();
internal void Revalidate()
{
//The revalidation procedure 'reconstructs' a portion of the tree that has expanded beyond its old limits.
//To reconstruct the tree, the nodes (internal and leaf) currently in use need to be retrieved.
//The internal nodes can be put back into the nodePool. LeafNodes are reinserted one by one into the new tree.
//To retrieve the nodes, a depth-first search is used.
//Given that an internal node is being revalidated, it is known that there are at least two children.
var oldChildA = childA;
var oldChildB = childB;
childA = null;
childB = null;
var leafNodes = nodeListPool.Take();
oldChildA.RetrieveNodes(leafNodes);
oldChildB.RetrieveNodes(leafNodes);
for (int i = 0; i < leafNodes.Count; i++)
leafNodes.Elements[i].Refit();
Reconstruct(leafNodes, 0, leafNodes.Count);
leafNodes.Clear();
nodeListPool.GiveBack(leafNodes);
}
void Reconstruct(RawList<LeafNode> leafNodes, int begin, int end)
{
//It is known that we have 2 children; this is safe.
//This is because this is only an internal node if the parent figured out it involved more than 2 leaf nodes, OR
//this node was the initiator of the revalidation (in which case, it was an internal node with 2+ children).
BoundingBox.CreateMerged(ref leafNodes.Elements[begin].BoundingBox, ref leafNodes.Elements[begin + 1].BoundingBox, out BoundingBox);
for (int i = begin + 2; i < end; i++)
{
BoundingBox.CreateMerged(ref BoundingBox, ref leafNodes.Elements[i].BoundingBox, out BoundingBox);
}
Vector3 offset;
Vector3.Subtract(ref BoundingBox.Max, ref BoundingBox.Min, out offset);
currentVolume = offset.X * offset.Y * offset.Z;
maximumVolume = currentVolume * MaximumVolumeScale;
//Pick an axis and sort along it.
if (offset.X > offset.Y && offset.X > offset.Z)
{
//Maximum variance axis is X.
Array.Sort(leafNodes.Elements, begin, end - begin, xComparer);
}
else if (offset.Y > offset.Z)
{
//Maximum variance axis is Y.
Array.Sort(leafNodes.Elements, begin, end - begin, yComparer);
}
else
{
//Maximum variance axis is Z.
Array.Sort(leafNodes.Elements, begin, end - begin, zComparer);
}
//Find the median index.
int median = (begin + end) / 2;
if (median - begin >= 2)
{
//There are 2 or more leaf nodes remaining in the first half. The next childA will be an internal node.
var newChildA = nodePool.Take();
newChildA.Reconstruct(leafNodes, begin, median);
childA = newChildA;
}
else
{
//There is only 1 leaf node remaining in this half. It's a leaf node.
childA = leafNodes.Elements[begin];
}
if (end - median >= 2)
{
//There are 2 or more leaf nodes remaining in the second half. The next childB will be an internal node.
var newChildB = nodePool.Take();
newChildB.Reconstruct(leafNodes, median, end);
childB = newChildB;
}
else
{
//There is only 1 leaf node remaining in this half. It's a leaf node.
childB = leafNodes.Elements[median];
}
}
internal override void RetrieveNodes(RawList<LeafNode> leafNodes)
{
var oldChildA = childA;
var oldChildB = childB;
childA = null;
childB = null;
nodePool.GiveBack(this); //Give internal nodes back to the pool before going deeper to minimize the creation of additional internal instances.
oldChildA.RetrieveNodes(leafNodes);
oldChildB.RetrieveNodes(leafNodes);
}
internal override void CollectMultithreadingNodes(int splitDepth, int currentDepth, RawList<Node> multithreadingSourceNodes)
{
if (currentVolume > maximumVolume)
{
//Very rarely, one of these extremely high level nodes will need to be revalidated. This isn't great.
//We may lose a frame. This could be independently multithreaded, but the benefit is unknown.
Revalidate();
return;
}
if (currentDepth == splitDepth)
{
//We are deep enough in the tree where our children will act as the starting point for multithreaded refits.
//The split depth ensures that we have enough tasks to thread across our core count.
multithreadingSourceNodes.Add(childA);
multithreadingSourceNodes.Add(childB);
}
else
{
childA.CollectMultithreadingNodes(splitDepth, currentDepth + 1, multithreadingSourceNodes);
childB.CollectMultithreadingNodes(splitDepth, currentDepth + 1, multithreadingSourceNodes);
}
}
internal override void PostRefit(int splitDepth, int currentDepth)
{
if (splitDepth > currentDepth)
{
//We are not yet back to the nodes that triggered the multithreaded split.
//Need to go deeper into the tree.
childA.PostRefit(splitDepth, currentDepth + 1);
childB.PostRefit(splitDepth, currentDepth + 1);
}
BoundingBox.CreateMerged(ref childA.BoundingBox, ref childB.BoundingBox, out BoundingBox);
currentVolume = (BoundingBox.Max.X - BoundingBox.Min.X) * (BoundingBox.Max.Y - BoundingBox.Min.Y) * (BoundingBox.Max.Z - BoundingBox.Min.Z);
}
internal override void GetMultithreadedOverlaps(Node opposingNode, int splitDepth, int currentDepth, DynamicHierarchy owner, RawList<DynamicHierarchy.NodePair> multithreadingSourceOverlaps)
{
bool intersects;
if (currentDepth == splitDepth)
{
//We've reached the depth where our child comparisons will be multithreaded.
if (this == opposingNode)
{
//We are being compared against ourselves!
//Obviously we're an internal node, so spawn three children:
//A versus A:
if (!childA.IsLeaf) //This is performed in the child method usually by convention, but this saves some time.
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childA, b = childA });
//B versus B:
if (!childB.IsLeaf) //This is performed in the child method usually by convention, but this saves some time.
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childB, b = childB });
//A versus B (if they intersect):
childA.BoundingBox.Intersects(ref childB.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childA, b = childB });
}
else
{
//Two different nodes. The other one may be a leaf.
if (opposingNode.IsLeaf)
{
//If it's a leaf, go deeper in our hierarchy, but not the opposition.
childA.BoundingBox.Intersects(ref opposingNode.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childA, b = opposingNode });
childB.BoundingBox.Intersects(ref opposingNode.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childB, b = opposingNode });
}
else
{
var opposingChildA = opposingNode.ChildA;
var opposingChildB = opposingNode.ChildB;
//If it's not a leaf, try to go deeper in both hierarchies.
childA.BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childA, b = opposingChildA });
childA.BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childA, b = opposingChildB });
childB.BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childB, b = opposingChildA });
childB.BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = childB, b = opposingChildB });
}
}
return;
}
if (this == opposingNode)
{
//We are being compared against ourselves!
//Obviously we're an internal node, so spawn three children:
//A versus A:
if (!childA.IsLeaf) //This is performed in the child method usually by convention, but this saves some time.
childA.GetMultithreadedOverlaps(childA, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
//B versus B:
if (!childB.IsLeaf) //This is performed in the child method usually by convention, but this saves some time.
childB.GetMultithreadedOverlaps(childB, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
//A versus B (if they intersect):
childA.BoundingBox.Intersects(ref childB.BoundingBox, out intersects);
if (intersects)
childA.GetMultithreadedOverlaps(childB, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
}
else
{
//Two different nodes. The other one may be a leaf.
if (opposingNode.IsLeaf)
{
//If it's a leaf, go deeper in our hierarchy, but not the opposition.
childA.BoundingBox.Intersects(ref opposingNode.BoundingBox, out intersects);
if (intersects)
childA.GetMultithreadedOverlaps(opposingNode, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
childB.BoundingBox.Intersects(ref opposingNode.BoundingBox, out intersects);
if (intersects)
childB.GetMultithreadedOverlaps(opposingNode, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
}
else
{
var opposingChildA = opposingNode.ChildA;
var opposingChildB = opposingNode.ChildB;
//If it's not a leaf, try to go deeper in both hierarchies.
childA.BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
childA.GetMultithreadedOverlaps(opposingChildA, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
childA.BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
childA.GetMultithreadedOverlaps(opposingChildB, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
childB.BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
childB.GetMultithreadedOverlaps(opposingChildA, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
childB.BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
childB.GetMultithreadedOverlaps(opposingChildB, splitDepth, currentDepth + 1, owner, multithreadingSourceOverlaps);
}
}
}
internal override bool Remove(BroadPhaseEntry entry, out LeafNode leafNode, out Node replacementNode)
{
if (childA.Remove(entry, out leafNode, out replacementNode))
{
if (childA.IsLeaf)
replacementNode = childB;
else
{
//It was not a leaf node, but a child found the leaf.
//Change the child to the replacement node.
childA = replacementNode;
replacementNode = this; //We don't need to be replaced!
}
return true;
}
if (childB.Remove(entry, out leafNode, out replacementNode))
{
if (childB.IsLeaf)
replacementNode = childA;
else
{
//It was not a leaf node, but a child found the leaf.
//Change the child to the replacement node.
childB = replacementNode;
replacementNode = this; //We don't need to be replaced!
}
return true;
}
replacementNode = this;
return false;
}
internal override bool RemoveFast(BroadPhaseEntry entry, out LeafNode leafNode, out Node replacementNode)
{
//Only bother checking deeper in the path if the entry and child have overlapping bounding boxes.
bool intersects;
childA.BoundingBox.Intersects(ref entry.boundingBox, out intersects);
if (intersects && childA.RemoveFast(entry, out leafNode, out replacementNode))
{
if (childA.IsLeaf)
replacementNode = childB;
else
{
//It was not a leaf node, but a child found the leaf.
//Change the child to the replacement node.
childA = replacementNode;
replacementNode = this; //We don't need to be replaced!
}
return true;
}
childB.BoundingBox.Intersects(ref entry.boundingBox, out intersects);
if (intersects && childB.RemoveFast(entry, out leafNode, out replacementNode))
{
if (childB.IsLeaf)
replacementNode = childA;
else
{
//It was not a leaf node, but a child found the leaf.
//Change the child to the replacement node.
childB = replacementNode;
replacementNode = this; //We don't need to be replaced!
}
return true;
}
replacementNode = this;
leafNode = null;
return false;
}
internal override float MeasureSubtreeCost()
{
Vector3 offset;
Vector3.Subtract(ref BoundingBox.Max, ref BoundingBox.Min, out offset);
return offset.X * offset.Y * offset.Z + ChildA.MeasureSubtreeCost() + childB.MeasureSubtreeCost();
}
static XComparer xComparer = new XComparer();
static YComparer yComparer = new YComparer();
static ZComparer zComparer = new ZComparer();
//Try using Comparer instead of IComparer- is there some tricky hardcoded optimization?
class XComparer : IComparer<LeafNode>
{
public int Compare(LeafNode x, LeafNode y)
{
return x.BoundingBox.Min.X < y.BoundingBox.Min.X ? -1 : 1;
}
}
class YComparer : IComparer<LeafNode>
{
public int Compare(LeafNode x, LeafNode y)
{
return x.BoundingBox.Min.Y < y.BoundingBox.Min.Y ? -1 : 1;
}
}
class ZComparer : IComparer<LeafNode>
{
public int Compare(LeafNode x, LeafNode y)
{
return x.BoundingBox.Min.Z < y.BoundingBox.Min.Z ? -1 : 1;
}
}
}
internal sealed class LeafNode : Node
{
BroadPhaseEntry element;
internal override Node ChildA
{
get
{
return null;
}
}
internal override Node ChildB
{
get
{
return null;
}
}
internal override BroadPhaseEntry Element
{
get
{
return element;
}
}
internal override bool IsLeaf
{
get { return true; }
}
internal void Initialize(BroadPhaseEntry element)
{
this.element = element;
BoundingBox = element.BoundingBox;
}
internal void CleanUp()
{
element = null;
}
internal override void GetOverlaps(ref BoundingBox boundingBox, IList<BroadPhaseEntry> outputOverlappedElements)
{
//Our parent already tested the bounding box. All that's left is to add myself to the list.
outputOverlappedElements.Add(element);
}
internal override void GetOverlaps(ref BoundingSphere boundingSphere, IList<BroadPhaseEntry> outputOverlappedElements)
{
outputOverlappedElements.Add(element);
}
//internal override void GetOverlaps(ref BoundingFrustum boundingFrustum, IList<BroadPhaseEntry> outputOverlappedElements)
//{
// outputOverlappedElements.Add(element);
//}
internal override void GetOverlaps(ref Ray ray, float maximumLength, IList<BroadPhaseEntry> outputOverlappedElements)
{
outputOverlappedElements.Add(element);
}
internal override void GetOverlaps(Node opposingNode, DynamicHierarchy owner)
{
bool intersects;
//note: This is never executed when the opposing node is the current node.
if (opposingNode.IsLeaf)
{
//We're both leaves! Our parents have already done the testing for us, so we know we're overlapping.
owner.TryToAddOverlap(element, opposingNode.Element);
}
else
{
var opposingChildA = opposingNode.ChildA;
var opposingChildB = opposingNode.ChildB;
//If it's not a leaf, try to go deeper in the opposing hierarchy.
BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
GetOverlaps(opposingChildA, owner);
BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
GetOverlaps(opposingChildB, owner);
}
}
internal override bool TryToInsert(LeafNode node, out Node treeNode)
{
var newTreeNode = InternalNode.nodePool.Take();
BoundingBox.CreateMerged(ref BoundingBox, ref node.BoundingBox, out newTreeNode.BoundingBox);
Vector3 offset;
Vector3.Subtract(ref newTreeNode.BoundingBox.Max, ref newTreeNode.BoundingBox.Min, out offset);
newTreeNode.currentVolume = offset.X * offset.Y * offset.Z;
//newTreeNode.maximumVolume = newTreeNode.currentVolume * InternalNode.MaximumVolumeScale;
newTreeNode.childA = this;
newTreeNode.childB = node;
treeNode = newTreeNode;
return true;
}
public override string ToString()
{
return element.ToString();
}
internal override void Analyze(List<int> depths, int depth, ref int nodeCount)
{
nodeCount++;
depths.Add(depth);
}
internal override void Refit()
{
BoundingBox = element.boundingBox;
}
internal override void RetrieveNodes(RawList<LeafNode> leafNodes)
{
Refit();
leafNodes.Add(this);
}
internal override void CollectMultithreadingNodes(int splitDepth, int currentDepth, RawList<Node> multithreadingSourceNodes)
{
//This could happen if there are almost no elements in the tree. No biggie- do nothing!
}
internal override void PostRefit(int splitDepth, int currentDepth)
{
//This could happen if there are almost no elements in the tree. Just do a normal leaf refit.
BoundingBox = element.boundingBox;
}
internal override void GetMultithreadedOverlaps(Node opposingNode, int splitDepth, int currentDepth, DynamicHierarchy owner, RawList<DynamicHierarchy.NodePair> multithreadingSourceOverlaps)
{
bool intersects;
//note: This is never executed when the opposing node is the current node.
if (opposingNode.IsLeaf)
{
//We're both leaves! Our parents have already done the testing for us, so we know we're overlapping.
owner.TryToAddOverlap(element, opposingNode.Element);
}
else
{
var opposingChildA = opposingNode.ChildA;
var opposingChildB = opposingNode.ChildB;
if (splitDepth == currentDepth)
{
//Time to add the child overlaps to the multithreading set!
BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = this, b = opposingChildA });
BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
multithreadingSourceOverlaps.Add(new DynamicHierarchy.NodePair() { a = this, b = opposingChildB });
return;
}
//If it's not a leaf, try to go deeper in the opposing hierarchy.
BoundingBox.Intersects(ref opposingChildA.BoundingBox, out intersects);
if (intersects)
GetOverlaps(opposingChildA, owner);
BoundingBox.Intersects(ref opposingChildB.BoundingBox, out intersects);
if (intersects)
GetOverlaps(opposingChildB, owner);
}
}
internal override bool Remove(BroadPhaseEntry entry, out LeafNode leafNode, out Node replacementNode)
{
replacementNode = null;
if (element == entry)
{
leafNode = this;
return true;
}
leafNode = null;
return false;
}
internal override bool RemoveFast(BroadPhaseEntry entry, out LeafNode leafNode, out Node replacementNode)
{
//The fastremove leaf node procedure is identical to the brute force approach.
//We don't need to perform any bounding box test here; if they're equal, they're equal!
replacementNode = null;
if (element == entry)
{
leafNode = this;
return true;
}
leafNode = null;
return false;
}
internal override float MeasureSubtreeCost()
{
//Not much value in attempting to assign variable cost to leaves vs internal nodes for this diagnostic.
Vector3 offset;
Vector3.Subtract(ref BoundingBox.Max, ref BoundingBox.Min, out offset);
return offset.X * offset.Y * offset.Z;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5a088a8f65340dc44936f23f753feb55
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,103 @@
using System.Collections.Generic;
using BEPUphysics.BroadPhaseEntries;
using BEPUutilities;
namespace BEPUphysics.BroadPhaseSystems.Hierarchies
{
///<summary>
/// Interface to the DynamicHierarchy's volume query systems.
///</summary>
public class DynamicHierarchyQueryAccelerator : IQueryAccelerator
{
private readonly DynamicHierarchy hierarchy;
internal DynamicHierarchyQueryAccelerator(DynamicHierarchy hierarchy)
{
this.hierarchy = hierarchy;
}
/// <summary>
/// Gets the broad phase associated with this query accelerator.
/// </summary>
public BroadPhase BroadPhase
{
get
{
return hierarchy;
}
}
/// <summary>
/// Collects all entries with bounding boxes which intersect the given bounding box.
/// </summary>
/// <param name="box">Bounding box to test against the world.</param>
/// <param name="entries">Entries of the space which intersect the bounding box.</param>
public void GetEntries(BoundingBox box, IList<BroadPhaseEntry> entries)
{
if (hierarchy.root != null)
hierarchy.root.GetOverlaps(ref box, entries);
}
///// <summary>
///// Collects all entries with bounding boxes which intersect the given frustum.
///// </summary>
///// <param name="frustum">Frustum to test against the world.</param>
///// <param name="entries">Entries of the space which intersect the frustum.</param>
//public void GetEntries(BoundingFrustum frustum, IList<BroadPhaseEntry> entries)
//{
// if (hierarchy.root != null)
// hierarchy.root.GetOverlaps(ref frustum, entries);
//}
/// <summary>
/// Collects all entries with bounding boxes which intersect the given sphere.
/// </summary>
/// <param name="sphere">Sphere to test against the world.</param>
/// <param name="entries">Entries of the space which intersect the sphere.</param>
public void GetEntries(BoundingSphere sphere, IList<BroadPhaseEntry> entries)
{
if (hierarchy.root != null)
hierarchy.root.GetOverlaps(ref sphere, entries);
}
/// <summary>
/// Finds all intersections between the ray and broad phase entries.
/// </summary>
/// <param name="ray">Ray to test against the structure.</param>
/// <param name="maximumLength">Maximum length of the ray in units of the ray's direction's length.</param>
/// <param name="entries">Entries which have bounding boxes that overlap the ray.</param>
public bool RayCast(Ray ray, float maximumLength, IList<BroadPhaseEntry> entries)
{
if (hierarchy.root != null)
{
hierarchy.root.GetOverlaps(ref ray, maximumLength, entries);
return entries.Count > 0;
}
return false;
}
/// <summary>
/// Finds all intersections between the ray and broad phase entries.
/// </summary>
/// <param name="ray">Ray to test against the structure.</param>
/// <param name="entries">Entries which have bounding boxes that overlap the ray.</param>
public bool RayCast(Ray ray, IList<BroadPhaseEntry> entries)
{
if (hierarchy.root != null)
{
hierarchy.root.GetOverlaps(ref ray, float.MaxValue, entries);
return entries.Count > 0;
}
return false;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bdbf5f4f1b270554484d432d6e47d39e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,16 @@

using BEPUutilities;
namespace BEPUphysics.BroadPhaseSystems
{
///<summary>
/// Requires that a class have a BoundingBox.
///</summary>
public interface IBoundingBoxOwner
{
///<summary>
/// Gets the bounding box of the object.
///</summary>
BoundingBox BoundingBox { get; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d59011d537222824791de03690ad330c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using BEPUphysics.BroadPhaseEntries;
namespace BEPUphysics.BroadPhaseSystems
{
///<summary>
/// Requires that a class own a BroadPhaseEntry.
///</summary>
public interface IBroadPhaseEntryOwner
{
///<summary>
/// Gets the broad phase entry associated with this object.
///</summary>
BroadPhaseEntry Entry { get; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1dac9fa7ffe116746be8f51571eeb94c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,57 @@
using System.Collections.Generic;
using BEPUutilities;
using BEPUphysics.BroadPhaseEntries;
namespace BEPUphysics.BroadPhaseSystems
{
///<summary>
/// Defines a system that accelerates bounding volume and ray cast queries.
///</summary>
public interface IQueryAccelerator
{
/// <summary>
/// Gets the broad phase associated with this query accelerator, if any.
/// </summary>
BroadPhase BroadPhase { get; }
///<summary>
/// Gets the broad phase entries overlapping the ray.
///</summary>
///<param name="ray">Ray to test.</param>
///<param name="outputIntersections">Overlapped entries.</param>
///<returns>Whether or not the ray hit anything.</returns>
bool RayCast(Ray ray, IList<BroadPhaseEntry> outputIntersections);
///<summary>
/// Gets the broad phase entries overlapping the ray.
///</summary>
///<param name="ray">Ray to test.</param>
/// <param name="maximumLength">Maximum length of the ray in units of the ray's direction's length.</param>
///<param name="outputIntersections">Overlapped entries.</param>
///<returns>Whether or not the ray hit anything.</returns>
bool RayCast(Ray ray, float maximumLength, IList<BroadPhaseEntry> outputIntersections);
//There's no single-hit version because the TOI on queries isn't really meaningful.
//TODO: IQueryAccelerator + BroadPhase. Both have add methods. A user might expect to be able to add separately, but that doesn't really work.
//Consider pulling the query accelerator into the broadphase so people consider it to be a part of the broadphase- it accelerates queries against the broadphase.
//If someone wanted to raycast against something other than the broadphase, they can create an IQueryAccelerator of some kind in isolation.
/// <summary>
/// Gets the entries with bounding boxes which overlap the bounding shape.
/// </summary>
/// <param name="boundingShape">Bounding shape to test.</param>
/// <param name="overlaps">Overlapped entries.</param>
void GetEntries(BoundingBox boundingShape, IList<BroadPhaseEntry> overlaps);
/// <summary>
/// Gets the entries with bounding boxes which overlap the bounding shape.
/// </summary>
/// <param name="boundingShape">Bounding shape to test.</param>
/// <param name="overlaps">Overlapped entries.</param>
void GetEntries(BoundingSphere boundingShape, IList<BroadPhaseEntry> overlaps);
///// <summary>
///// Gets the entries with bounding boxes which overlap the bounding shape.
///// </summary>
///// <param name="boundingShape">Bounding shape to test.</param>
///// <param name="overlaps">Overlapped entries.</param>
//void GetEntries(BoundingFrustum boundingShape, IList<BroadPhaseEntry> overlaps);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f0ee45309f6bc1b40a666abde6c553df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b22ea97b9360df44eb6fa5c4c9268266
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics.BroadPhaseEntries;
namespace BEPUphysics.BroadPhaseSystems.SortAndSweep
{
class Grid2DEntry
{
internal void Initialize(BroadPhaseEntry entry)
{
this.item = entry;
Grid2DSortAndSweep.ComputeCell(ref entry.boundingBox.Min, out previousMin);
Grid2DSortAndSweep.ComputeCell(ref entry.boundingBox.Max, out previousMax);
}
internal BroadPhaseEntry item;
internal Int2 previousMin;
internal Int2 previousMax;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11de3c7dc79efc84393c5af59d248e4f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,273 @@
using System;
using BEPUphysics.BroadPhaseEntries;
using BEPUutilities;
using BEPUutilities.DataStructures;
using BEPUutilities.ResourceManagement;
using BEPUutilities.Threading;
namespace BEPUphysics.BroadPhaseSystems.SortAndSweep
{
/// <summary>
/// Broad phase implementation that partitions objects into a 2d grid, and then performs a sort and sweep on the final axis.
/// </summary>
/// <remarks>
/// This broad phase typically has very good collision performance and scales well with multithreading, but its query times can sometimes be worse than tree-based systems
/// since it must scan cells. Keeping rays as short as possible helps avoid unnecessary cell checks.
/// The performance can degrade noticeably in some situations involving significant off-axis motion.
/// </remarks>
public class Grid2DSortAndSweep : BroadPhase
{
/// <summary>
/// Gets or sets the width of cells in the 2D grid. For sparser, larger scenes, increasing this can help performance.
/// For denser scenes, decreasing this may help.
/// </summary>
public static float CellSize
{
get
{
return 1 / cellSizeInverse;
}
set
{
cellSizeInverse = 1 / value;
}
}
//TODO: Try different values for this.
internal static float cellSizeInverse = 1 / 8f;
internal static void ComputeCell(ref Vector3 v, out Int2 cell)
{
cell.Y = (int)Math.Floor(v.Y * cellSizeInverse);
cell.Z = (int)Math.Floor(v.Z * cellSizeInverse);
}
internal SortedGrid2DSet cellSet = new SortedGrid2DSet();
RawList<Grid2DEntry> entries = new RawList<Grid2DEntry>();
Action<int> updateEntry, updateCell;
/// <summary>
/// Constructs a grid-based sort and sweep broad phase.
/// </summary>
/// <param name="parallelLooper">Parallel loop provider to use for the broad phase.</param>
public Grid2DSortAndSweep(IParallelLooper parallelLooper)
:base(parallelLooper)
{
updateEntry = UpdateEntry;
updateCell = UpdateCell;
QueryAccelerator = new Grid2DSortAndSweepQueryAccelerator(this);
}
/// <summary>
/// Constructs a grid-based sort and sweep broad phase.
/// </summary>
public Grid2DSortAndSweep()
{
updateEntry = UpdateEntry;
updateCell = UpdateCell;
QueryAccelerator = new Grid2DSortAndSweepQueryAccelerator(this);
}
UnsafeResourcePool<Grid2DEntry> entryPool = new UnsafeResourcePool<Grid2DEntry>();
/// <summary>
/// Adds an entry to the broad phase.
/// </summary>
/// <param name="entry">Entry to add.</param>
public override void Add(BroadPhaseEntry entry)
{
base.Add(entry);
//Entities do not set up their own bounding box before getting stuck in here. If they're all zeroed out, the tree will be horrible.
Vector3 offset;
Vector3.Subtract(ref entry.boundingBox.Max, ref entry.boundingBox.Min, out offset);
if (offset.X * offset.Y * offset.Z == 0)
entry.UpdateBoundingBox();
var newEntry = entryPool.Take();
newEntry.Initialize(entry);
entries.Add(newEntry);
//Add the object to the grid.
for (int i = newEntry.previousMin.Y; i <= newEntry.previousMax.Y; i++)
{
for (int j = newEntry.previousMin.Z; j <= newEntry.previousMax.Z; j++)
{
var index = new Int2 {Y = i, Z = j};
cellSet.Add(ref index, newEntry);
}
}
}
/// <summary>
/// Removes an entry from the broad phase.
/// </summary>
/// <param name="entry">Entry to remove.</param>
public override void Remove(BroadPhaseEntry entry)
{
base.Remove(entry);
for (int i = 0; i < entries.Count; i++)
{
if (entries.Elements[i].item == entry)
{
var gridEntry = entries.Elements[i];
entries.RemoveAt(i);
//Remove the object from any cells that it is held by.
for (int j = gridEntry.previousMin.Y; j <= gridEntry.previousMax.Y; j++)
{
for (int k = gridEntry.previousMin.Z; k <= gridEntry.previousMax.Z; k++)
{
var index = new Int2 {Y = j, Z = k};
cellSet.Remove(ref index, gridEntry);
}
}
gridEntry.item = null;
entryPool.GiveBack(gridEntry);
return;
}
}
}
protected override void UpdateMultithreaded()
{
lock (Locker)
{
Overlaps.Clear();
//Update the entries!
ParallelLooper.ForLoop(0, entries.Count, updateEntry);
//Update the cells!
ParallelLooper.ForLoop(0, cellSet.count, updateCell);
}
}
protected override void UpdateSingleThreaded()
{
lock (Locker)
{
Overlaps.Clear();
//Update the placement of objects.
for (int i = 0; i < entries.Count; i++)
{
//Compute the current cells occupied by the entry.
var entry = entries.Elements[i];
Int2 min, max;
ComputeCell(ref entry.item.boundingBox.Min, out min);
ComputeCell(ref entry.item.boundingBox.Max, out max);
//For any cell that used to be occupied (defined by the previous min/max),
//remove the entry.
for (int j = entry.previousMin.Y; j <= entry.previousMax.Y; j++)
{
for (int k = entry.previousMin.Z; k <= entry.previousMax.Z; k++)
{
if (j >= min.Y && j <= max.Y && k >= min.Z && k <= max.Z)
continue; //This cell is currently occupied, do not remove.
var index = new Int2 {Y = j, Z = k};
cellSet.Remove(ref index, entry);
}
}
//For any cell that is newly occupied (was not previously contained),
//add the entry.
for (int j = min.Y; j <= max.Y; j++)
{
for (int k = min.Z; k <= max.Z; k++)
{
if (j >= entry.previousMin.Y && j <= entry.previousMax.Y && k >= entry.previousMin.Z && k <= entry.previousMax.Z)
continue; //This cell is already occupied, do not add.
var index = new Int2 {Y = j, Z = k};
cellSet.Add(ref index, entry);
}
}
entry.previousMin = min;
entry.previousMax = max;
}
//Update each cell to find the overlaps.
for (int i = 0; i < cellSet.count; i++)
{
cellSet.cells.Elements[i].UpdateOverlaps(this);
}
}
}
//TODO: Cell change operations take a while. Spin lock can't efficiently wait that long.
//This causes some pretty horrible scaling problems in some scenarios.
//Improving the cell set operations directly should improve that problem and the query times noticeably.
SpinLock cellSetLocker = new SpinLock();
void UpdateEntry(int i)
{
//Compute the current cells occupied by the entry.
var entry = entries.Elements[i];
Int2 min, max;
ComputeCell(ref entry.item.boundingBox.Min, out min);
ComputeCell(ref entry.item.boundingBox.Max, out max);
//For any cell that used to be occupied (defined by the previous min/max),
//remove the entry.
for (int j = entry.previousMin.Y; j <= entry.previousMax.Y; j++)
{
for (int k = entry.previousMin.Z; k <= entry.previousMax.Z; k++)
{
if (j >= min.Y && j <= max.Y && k >= min.Z && k <= max.Z)
continue; //This cell is currently occupied, do not remove.
var index = new Int2 {Y = j, Z = k};
cellSetLocker.Enter();
cellSet.Remove(ref index, entry);
cellSetLocker.Exit();
}
}
//For any cell that is newly occupied (was not previously contained),
//add the entry.
for (int j = min.Y; j <= max.Y; j++)
{
for (int k = min.Z; k <= max.Z; k++)
{
if (j >= entry.previousMin.Y && j <= entry.previousMax.Y && k >= entry.previousMin.Z && k <= entry.previousMax.Z)
continue; //This cell is already occupied, do not add.
var index = new Int2 {Y = j, Z = k};
cellSetLocker.Enter();
cellSet.Add(ref index, entry);
cellSetLocker.Exit();
}
}
entry.previousMin = min;
entry.previousMax = max;
}
void UpdateCell(int i)
{
//TODO: Consider permuting.
//In some simulations, there may be a ton of unoccupied cells.
//It would be best to distribute these over the threads.
//(int)((i * 122949823L) % cellSet.count)
//(i * 122949823L) % cellSet.count
cellSet.cells.Elements[i].UpdateOverlaps(this);
}
}
struct Int2
{
internal int Y;
internal int Z;
public override int GetHashCode()
{
return Y + Z;
}
internal int GetSortingHash()
{
return (int)(Y * 15485863L + Z * 32452843L);
}
public override string ToString()
{
return "{" + Y + ", " + Z + "}";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa97190dac07500488f8fcf0cb760559
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUutilities;
using BEPUphysics.BroadPhaseEntries;
namespace BEPUphysics.BroadPhaseSystems.SortAndSweep
{
public class Grid2DSortAndSweepQueryAccelerator : IQueryAccelerator
{
Grid2DSortAndSweep owner;
public Grid2DSortAndSweepQueryAccelerator(Grid2DSortAndSweep owner)
{
this.owner = owner;
}
/// <summary>
/// Gets the broad phase associated with this query accelerator.
/// </summary>
public BroadPhase BroadPhase
{
get
{
return owner;
}
}
public bool RayCast(Ray ray, IList<BroadPhaseEntry> outputIntersections)
{
throw new NotSupportedException("The Grid2DSortAndSweep broad phase cannot accelerate infinite ray casts. Consider using a broad phase which supports infinite tests, using a custom solution, or using a finite ray.");
}
public bool RayCast(Ray ray, float maximumLength, IList<BroadPhaseEntry> outputIntersections)
{
if (maximumLength == float.MaxValue)
throw new NotSupportedException("The Grid2DSortAndSweep broad phase cannot accelerate infinite ray casts. Consider specifying a maximum length or using a broad phase which supports infinite ray casts.");
//Use 2d line rasterization.
//Compute the exit location in the cell.
//Test against each bounding box up until the exit value is reached.
float length = 0;
Int2 cellIndex;
Vector3 currentPosition = ray.Position;
Grid2DSortAndSweep.ComputeCell(ref currentPosition, out cellIndex);
while (true)
{
float cellWidth = 1 / Grid2DSortAndSweep.cellSizeInverse;
float nextT; //Distance along ray to next boundary.
float nextTy; //Distance along ray to next boundary along y axis.
float nextTz; //Distance along ray to next boundary along z axis.
//Find the next cell.
if (ray.Direction.Y > 0)
nextTy = ((cellIndex.Y + 1) * cellWidth - currentPosition.Y) / ray.Direction.Y;
else if (ray.Direction.Y < 0)
nextTy = ((cellIndex.Y) * cellWidth - currentPosition.Y) / ray.Direction.Y;
else
nextTy = 10e10f;
if (ray.Direction.Z > 0)
nextTz = ((cellIndex.Z + 1) * cellWidth - currentPosition.Z) / ray.Direction.Z;
else if (ray.Direction.Z < 0)
nextTz = ((cellIndex.Z) * cellWidth - currentPosition.Z) / ray.Direction.Z;
else
nextTz = 10e10f;
bool yIsMinimum = nextTy < nextTz;
nextT = yIsMinimum ? nextTy : nextTz;
//Grab the cell that we are currently in.
GridCell2D cell;
if (owner.cellSet.TryGetCell(ref cellIndex, out cell))
{
float endingX;
if(ray.Direction.X < 0)
endingX = currentPosition.X;
else
endingX = currentPosition.X + ray.Direction.X * nextT;
//To fully accelerate this, the entries list would need to contain both min and max interval markers.
//Since it only contains the sorted min intervals, we can't just start at a point in the middle of the list.
//Consider some giant bounding box that spans the entire list.
for (int i = 0; i < cell.entries.Count
&& cell.entries.Elements[i].item.boundingBox.Min.X <= endingX; i++) //TODO: Try additional x axis pruning?
{
var item = cell.entries.Elements[i].item;
float t;
if (ray.Intersects(ref item.boundingBox, out t) && t < maximumLength && !outputIntersections.Contains(item))
{
outputIntersections.Add(item);
}
}
}
//Move the position forward.
length += nextT;
if (length > maximumLength) //Note that this catches the case in which the ray is pointing right down the middle of a row (resulting in a nextT of 10e10f).
break;
Vector3 offset;
Vector3.Multiply(ref ray.Direction, nextT, out offset);
Vector3.Add(ref offset, ref currentPosition, out currentPosition);
if (yIsMinimum)
if (ray.Direction.Y < 0)
cellIndex.Y -= 1;
else
cellIndex.Y += 1;
else
if (ray.Direction.Z < 0)
cellIndex.Z -= 1;
else
cellIndex.Z += 1;
}
return outputIntersections.Count > 0;
}
public void GetEntries(BoundingBox boundingShape, IList<BroadPhaseEntry> overlaps)
{
//Compute the min and max of the bounding box.
//Loop through the cells and select bounding boxes which overlap the x axis.
Int2 min, max;
Grid2DSortAndSweep.ComputeCell(ref boundingShape.Min, out min);
Grid2DSortAndSweep.ComputeCell(ref boundingShape.Max, out max);
for (int i = min.Y; i <= max.Y; i++)
{
for (int j = min.Z; j <= max.Z; j++)
{
//Grab the cell that we are currently in.
Int2 cellIndex;
cellIndex.Y = i;
cellIndex.Z = j;
GridCell2D cell;
if (owner.cellSet.TryGetCell(ref cellIndex, out cell))
{
//To fully accelerate this, the entries list would need to contain both min and max interval markers.
//Since it only contains the sorted min intervals, we can't just start at a point in the middle of the list.
//Consider some giant bounding box that spans the entire list.
for (int k = 0; k < cell.entries.Count
&& cell.entries.Elements[k].item.boundingBox.Min.X <= boundingShape.Max.X; k++) //TODO: Try additional x axis pruning? A bit of optimization potential due to overlap with AABB test.
{
bool intersects;
var item = cell.entries.Elements[k].item;
boundingShape.Intersects(ref item.boundingBox, out intersects);
if (intersects && !overlaps.Contains(item))
{
overlaps.Add(item);
}
}
}
}
}
}
public void GetEntries(BoundingSphere boundingShape, IList<BroadPhaseEntry> overlaps)
{
//Create a bounding box based on the bounding sphere.
//Compute the min and max of the bounding box.
//Loop through the cells and select bounding boxes which overlap the x axis.
#if !WINDOWS
Vector3 offset = new Vector3();
#else
Vector3 offset;
#endif
offset.X = boundingShape.Radius;
offset.Y = offset.X;
offset.Z = offset.Y;
BoundingBox box;
Vector3.Add(ref boundingShape.Center, ref offset, out box.Max);
Vector3.Subtract(ref boundingShape.Center, ref offset, out box.Min);
Int2 min, max;
Grid2DSortAndSweep.ComputeCell(ref box.Min, out min);
Grid2DSortAndSweep.ComputeCell(ref box.Max, out max);
for (int i = min.Y; i <= max.Y; i++)
{
for (int j = min.Z; j <= max.Z; j++)
{
//Grab the cell that we are currently in.
Int2 cellIndex;
cellIndex.Y = i;
cellIndex.Z = j;
GridCell2D cell;
if (owner.cellSet.TryGetCell(ref cellIndex, out cell))
{
//To fully accelerate this, the entries list would need to contain both min and max interval markers.
//Since it only contains the sorted min intervals, we can't just start at a point in the middle of the list.
//Consider some giant bounding box that spans the entire list.
for (int k = 0; k < cell.entries.Count
&& cell.entries.Elements[k].item.boundingBox.Min.X <= box.Max.X; k++) //TODO: Try additional x axis pruning? A bit of optimization potential due to overlap with AABB test.
{
bool intersects;
var item = cell.entries.Elements[k].item;
item.boundingBox.Intersects(ref boundingShape, out intersects);
if (intersects && !overlaps.Contains(item))
{
overlaps.Add(item);
}
}
}
}
}
}
//public void GetEntries(BoundingFrustum boundingShape, IList<BroadPhaseEntry> overlaps)
//{
// throw new NotSupportedException("The Grid2DSortAndSweep broad phase cannot accelerate frustum tests. Consider using a broad phase which supports frustum tests or using a custom solution.");
//}
}
}

Some files were not shown because too many files have changed in this diff Show More