mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-06-26 03:14:47 +00:00
提交
This commit is contained in:
parent
b6461675a8
commit
aa4d6c3ce2
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:
|
||||
|
8
JNFrame/Assets/Plugins/JNGame/BepuPhysics/Core.meta
Normal file
8
JNFrame/Assets/Plugins/JNGame/BepuPhysics/Core.meta
Normal file
@ -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
Loading…
x
Reference in New Issue
Block a user