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
JNFrame
Assembly-CSharp-firstpass.csprojAssembly-CSharp.csproj
Assets
Packages
Plugins/JNGame/BepuPhysics
Core.meta
Core
BepuPhysics1.meta
BepuPhysics1
BEPUphysics.meta
BEPUphysics
BEPUphysics.csprojBEPUphysics.csproj.metaBroadPhaseEntries.meta
BroadPhaseEntries
BroadPhaseSystems.meta
BroadPhaseSystems

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

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

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: eedc788daf73e8b46ad39b7553f7df0b
guid: 6d0d461b18d011e4e96f4e2bd53f9a9d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
@ -93,6 +93,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 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
buildTarget: Server
maxTextureSize: 2048

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

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

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

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

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

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

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 994c9e52f887aba4d8bd51018a243ad4
guid: e408c137718b826488a2a60c35e88a4b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
@ -93,6 +93,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 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
buildTarget: Server
maxTextureSize: 2048

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

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

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

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

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

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

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

@ -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>

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

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

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

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

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

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

@ -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();
}
}
}

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

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

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

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

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

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

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

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

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

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

@ -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();
}
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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();
}
}
}

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

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

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

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

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

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

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

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

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

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

@ -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.
}
}

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

@ -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 + "}";
}
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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 + "}";
}
}
}

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

@ -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