Files
2024-12-11 13:26:13 +02:00

613 lines
21 KiB
C#

// Copyright (c) 2017 Kastellanos Nikolaos
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
/*
* Farseer Physics Engine:
* Copyright (c) 2012 Ian Qvist
*
* Original source Box2D:
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
using System.Collections.Generic;
using FarseerPhysics.Collision;
using FarseerPhysics.Dynamics.Contacts;
namespace FarseerPhysics.Dynamics
{
public class ContactManager
{
#region Settings
/// <summary>
/// A threshold for activating multiple cores to solve VelocityConstraints.
/// An Island with a contact count above this threshold will use multiple threads to solve VelocityConstraints.
/// A value of 0 will always use multithreading. A value of (int.MaxValue) will never use multithreading.
/// Typical values are {128 or 256}.
/// </summary>
public int VelocityConstraintsMultithreadThreshold = 128;
/// <summary>
/// A threshold for activating multiple cores to solve PositionConstraints.
/// An Island with a contact count above this threshold will use multiple threads to solve PositionConstraints.
/// A value of 0 will always use multithreading. A value of (int.MaxValue) will never use multithreading.
/// Typical values are {128 or 256}.
/// </summary>
public int PositionConstraintsMultithreadThreshold = 128;
/// <summary>
/// A threshold for activating multiple cores to solve Collide.
/// An World with a contact count above this threshold will use multiple threads to solve Collide.
/// A value of 0 will always use multithreading. A value of (int.MaxValue) will never use multithreading.
/// Typical values are {128 or 256}.
/// </summary>
public int CollideMultithreadThreshold = 64;
#endregion
/// <summary>
/// Fires when a contact is created
/// </summary>
public BeginContactDelegate BeginContact;
public IBroadPhase BroadPhase;
public readonly ContactListHead ContactList;
public int ContactCount { get; private set; }
internal readonly ContactListHead _contactPoolList;
/// <summary>
/// The filter used by the contact manager.
/// </summary>
public CollisionFilterDelegate ContactFilter;
#if USE_ACTIVE_CONTACT_SET
/// <summary>
/// The set of active contacts.
/// </summary>
public HashSet<Contact> ActiveContacts = new HashSet<Contact>();
/// <summary>
/// A temporary copy of active contacts that is used during updates so
/// the hash set can have members added/removed during the update.
/// This list is cleared after every update.
/// </summary>
List<Contact> ActiveList = new List<Contact>();
#endif
/// <summary>
/// Fires when a contact is deleted
/// </summary>
public EndContactDelegate EndContact;
/// <summary>
/// Fires when the broadphase detects that two Fixtures are close to each other.
/// </summary>
public BroadphaseDelegate OnBroadphaseCollision;
/// <summary>
/// Fires after the solver has run
/// </summary>
public PostSolveDelegate PostSolve;
/// <summary>
/// Fires before the solver runs
/// </summary>
public PreSolveDelegate PreSolve;
internal ContactManager(IBroadPhase broadPhase)
{
ContactList = new ContactListHead();
ContactCount = 0;
_contactPoolList = new ContactListHead();
BroadPhase = broadPhase;
OnBroadphaseCollision = AddPair;
}
// Broad-phase callback.
private void AddPair(int proxyIdA, int proxyIdB)
{
FixtureProxy proxyA = BroadPhase.GetProxy(proxyIdA);
FixtureProxy proxyB = BroadPhase.GetProxy(proxyIdB);
Body bodyA = proxyA.Body;
Body bodyB = proxyB.Body;
// Are the fixtures on the same body?
if (bodyA == bodyB)
{
return;
}
Fixture fixtureA = proxyA.Fixture;
Fixture fixtureB = proxyB.Fixture;
int indexA = proxyA.ChildIndex;
int indexB = proxyB.ChildIndex;
// Does a contact already exist?
for (ContactEdge ceB = bodyB.ContactList; ceB != null; ceB = ceB.Next)
{
if (ceB.Other == bodyA)
{
Fixture fA = ceB.Contact.FixtureA;
Fixture fB = ceB.Contact.FixtureB;
int iA = ceB.Contact.ChildIndexA;
int iB = ceB.Contact.ChildIndexB;
if (fA == fixtureA && fB == fixtureB && iA == indexA && iB == indexB)
{
// A contact already exists.
return;
}
if (fA == fixtureB && fB == fixtureA && iA == indexB && iB == indexA)
{
// A contact already exists.
return;
}
}
}
// Does a joint override collision? Is at least one body dynamic?
if (bodyB.ShouldCollide(bodyA) == false)
return;
if (ShouldCollide(fixtureA, fixtureB) == false)
return;
// Check user filtering.
if (ContactFilter != null && ContactFilter(fixtureA, fixtureB) == false)
return;
//FPE feature: BeforeCollision delegate
if (fixtureA.BeforeCollision != null && fixtureA.BeforeCollision(fixtureA, fixtureB) == false)
return;
if (fixtureB.BeforeCollision != null && fixtureB.BeforeCollision(fixtureB, fixtureA) == false)
return;
// Call the factory.
Contact c = Contact.Create(this, fixtureA, indexA, fixtureB, indexB);
if (c == null)
return;
// Contact creation may swap fixtures.
fixtureA = c.FixtureA;
fixtureB = c.FixtureB;
bodyA = fixtureA.Body;
bodyB = fixtureB.Body;
// Insert into the world.
c.Prev = ContactList;
c.Next = c.Prev.Next;
c.Prev.Next = c;
c.Next.Prev = c;
ContactCount++;
#if USE_ACTIVE_CONTACT_SET
ActiveContacts.Add(c);
#endif
// Connect to island graph.
// Connect to body A
c._nodeA.Contact = c;
c._nodeA.Other = bodyB;
c._nodeA.Prev = null;
c._nodeA.Next = bodyA.ContactList;
if (bodyA.ContactList != null)
{
bodyA.ContactList.Prev = c._nodeA;
}
bodyA.ContactList = c._nodeA;
// Connect to body B
c._nodeB.Contact = c;
c._nodeB.Other = bodyA;
c._nodeB.Prev = null;
c._nodeB.Next = bodyB.ContactList;
if (bodyB.ContactList != null)
{
bodyB.ContactList.Prev = c._nodeB;
}
bodyB.ContactList = c._nodeB;
// Wake up the bodies
if (fixtureA.IsSensor == false && fixtureB.IsSensor == false)
{
bodyA.Awake = true;
bodyB.Awake = true;
}
}
internal void FindNewContacts()
{
BroadPhase.UpdatePairs(OnBroadphaseCollision);
}
internal void Destroy(Contact contact)
{
Fixture fixtureA = contact.FixtureA;
Fixture fixtureB = contact.FixtureB;
Body bodyA = fixtureA.Body;
Body bodyB = fixtureB.Body;
if (contact.IsTouching)
{
//Report the separation to both participants:
if (fixtureA != null && fixtureA.OnSeparation != null)
fixtureA.OnSeparation(fixtureA, fixtureB, contact);
//Reverse the order of the reported fixtures. The first fixture is always the one that the
//user subscribed to.
if (fixtureB != null && fixtureB.OnSeparation != null)
fixtureB.OnSeparation(fixtureB, fixtureA, contact);
//Report the separation to both bodies:
if (fixtureA != null && fixtureA.Body != null && fixtureA.Body.onSeparationEventHandler != null)
fixtureA.Body.onSeparationEventHandler(fixtureA, fixtureB, contact);
//Reverse the order of the reported fixtures. The first fixture is always the one that the
//user subscribed to.
if (fixtureB != null && fixtureB.Body != null && fixtureB.Body.onSeparationEventHandler != null)
fixtureB.Body.onSeparationEventHandler(fixtureB, fixtureA, contact);
if (EndContact != null)
EndContact(contact);
}
// Remove from the world.
contact.Prev.Next = contact.Next;
contact.Next.Prev = contact.Prev;
contact.Next = null;
contact.Prev = null;
ContactCount--;
// Remove from body 1
if (contact._nodeA == bodyA.ContactList)
bodyA.ContactList = contact._nodeA.Next;
if (contact._nodeA.Prev != null)
contact._nodeA.Prev.Next = contact._nodeA.Next;
if (contact._nodeA.Next != null)
contact._nodeA.Next.Prev = contact._nodeA.Prev;
// Remove from body 2
if (contact._nodeB == bodyB.ContactList)
bodyB.ContactList = contact._nodeB.Next;
if (contact._nodeB.Prev != null)
contact._nodeB.Prev.Next = contact._nodeB.Next;
if (contact._nodeB.Next != null)
contact._nodeB.Next.Prev = contact._nodeB.Prev;
#if USE_ACTIVE_CONTACT_SET
if (ActiveContacts.Contains(contact))
ActiveContacts.Remove(contact);
#endif
contact.Destroy();
// Insert into the pool.
contact.Next = _contactPoolList.Next;
_contactPoolList.Next = contact;
}
internal void Collide()
{
#if NET40 || NET45 || PORTABLE40 || PORTABLE45 || W10 || W8_1 || WP8_1
if (this.ContactCount > CollideMultithreadThreshold && System.Environment.ProcessorCount > 1)
{
CollideMultiCore();
return;
}
#endif
// Update awake contacts.
#if USE_ACTIVE_CONTACT_SET
ActiveList.AddRange(ActiveContacts);
foreach (var tmpc in ActiveList)
{
Contact c = tmpc;
#else
for (Contact c = ContactList.Next; c != ContactList;)
{
#endif
Fixture fixtureA = c.FixtureA;
Fixture fixtureB = c.FixtureB;
int indexA = c.ChildIndexA;
int indexB = c.ChildIndexB;
Body bodyA = fixtureA.Body;
Body bodyB = fixtureB.Body;
//Do no try to collide disabled bodies
if (!bodyA.Enabled || !bodyB.Enabled)
{
c = c.Next;
continue;
}
// Is this contact flagged for filtering?
if (c.FilterFlag)
{
// Should these bodies collide?
if (bodyB.ShouldCollide(bodyA) == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// Check default filtering
if (ShouldCollide(fixtureA, fixtureB) == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// Check user filtering.
if (ContactFilter != null && ContactFilter(fixtureA, fixtureB) == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// Clear the filtering flag.
c.FilterFlag = false;
}
bool activeA = bodyA.Awake && bodyA.BodyType != BodyType.Static;
bool activeB = bodyB.Awake && bodyB.BodyType != BodyType.Static;
// At least one body must be awake and it must be dynamic or kinematic.
if (activeA == false && activeB == false)
{
#if USE_ACTIVE_CONTACT_SET
ActiveContacts.Remove(c);
#endif
c = c.Next;
continue;
}
int proxyIdA = fixtureA.Proxies[indexA].ProxyId;
int proxyIdB = fixtureB.Proxies[indexB].ProxyId;
bool overlap = BroadPhase.TestOverlap(proxyIdA, proxyIdB);
// Here we destroy contacts that cease to overlap in the broad-phase.
if (overlap == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// The contact persists.
c.Update(this);
c = c.Next;
}
#if USE_ACTIVE_CONTACT_SET
ActiveList.Clear();
#endif
}
/// <summary>
/// A temporary list of contacts to be updated during Collide().
/// </summary>
List<Contact> updateList = new List<Contact>();
#if NET40 || NET45 || PORTABLE40 || PORTABLE45 || W10 || W8_1 || WP8_1
internal void CollideMultiCore()
{
int lockOrder = 0;
// Update awake contacts.
#if USE_ACTIVE_CONTACT_SET
ActiveList.AddRange(ActiveContacts);
foreach (var tmpc in ActiveList)
{
Contact c = tmpc;
#else
for (Contact c = ContactList.Next; c != ContactList; )
{
#endif
Fixture fixtureA = c.FixtureA;
Fixture fixtureB = c.FixtureB;
int indexA = c.ChildIndexA;
int indexB = c.ChildIndexB;
Body bodyA = fixtureA.Body;
Body bodyB = fixtureB.Body;
//Do no try to collide disabled bodies
if (!bodyA.Enabled || !bodyB.Enabled)
{
c = c.Next;
continue;
}
// Is this contact flagged for filtering?
if (c.FilterFlag)
{
// Should these bodies collide?
if (bodyB.ShouldCollide(bodyA) == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// Check default filtering
if (ShouldCollide(fixtureA, fixtureB) == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// Check user filtering.
if (ContactFilter != null && ContactFilter(fixtureA, fixtureB) == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// Clear the filtering flag.
c.FilterFlag = false;
}
bool activeA = bodyA.Awake && bodyA.BodyType != BodyType.Static;
bool activeB = bodyB.Awake && bodyB.BodyType != BodyType.Static;
// At least one body must be awake and it must be dynamic or kinematic.
if (activeA == false && activeB == false)
{
#if USE_ACTIVE_CONTACT_SET
ActiveContacts.Remove(c);
#endif
c = c.Next;
continue;
}
int proxyIdA = fixtureA.Proxies[indexA].ProxyId;
int proxyIdB = fixtureB.Proxies[indexB].ProxyId;
bool overlap = BroadPhase.TestOverlap(proxyIdA, proxyIdB);
// Here we destroy contacts that cease to overlap in the broad-phase.
if (overlap == false)
{
Contact cNuke = c;
c = c.Next;
Destroy(cNuke);
continue;
}
// The contact persists.
updateList.Add(c);
// Assign a unique id for lock order
bodyA._lockOrder = lockOrder++;
bodyB._lockOrder = lockOrder++;
c = c.Next;
}
#if USE_ACTIVE_CONTACT_SET
ActiveList.Clear();
#endif
// update contacts
System.Threading.Tasks.Parallel.ForEach<Contact>(updateList, (c) =>
{
// find lower order item
Fixture fixtureA = c.FixtureA;
Fixture fixtureB = c.FixtureB;
// find lower order item
Body orderedBodyA = fixtureA.Body;
Body orderedBodyB = fixtureB.Body;
int idA = orderedBodyA._lockOrder;
int idB = orderedBodyB._lockOrder;
if (idA == idB)
throw new System.Exception();
if (idA > idB)
{
orderedBodyA = fixtureB.Body;
orderedBodyB = fixtureA.Body;
}
// obtain lock
for (; ; )
{
if (System.Threading.Interlocked.CompareExchange(ref orderedBodyA._lock, 1, 0) == 0)
{
if (System.Threading.Interlocked.CompareExchange(ref orderedBodyB._lock, 1, 0) == 0)
break;
System.Threading.Interlocked.Exchange(ref orderedBodyA._lock, 0);
}
#if NET40 || NET45
System.Threading.Thread.Sleep(0);
#endif
}
c.Update(this);
System.Threading.Interlocked.Exchange(ref orderedBodyB._lock, 0);
System.Threading.Interlocked.Exchange(ref orderedBodyA._lock, 0);
});
updateList.Clear();
}
#endif
private static bool ShouldCollide(Fixture fixtureA, Fixture fixtureB)
{
if (fixtureA.CollisionGroup != 0 && fixtureA.CollisionGroup == fixtureB.CollisionGroup)
{
return (fixtureA.CollisionGroup > 0);
}
bool collide = ((fixtureA.CollidesWith & fixtureB.CollisionCategories) != 0) &&
((fixtureB.CollidesWith & fixtureA.CollisionCategories) != 0);
return collide;
}
#if USE_ACTIVE_CONTACT_SET
internal void UpdateActiveContacts(ContactEdge ContactList, bool value)
{
if(value)
{
for (var contactEdge = ContactList; contactEdge != null; contactEdge = contactEdge.Next)
{
if (!ActiveContacts.Contains(contactEdge.Contact))
ActiveContacts.Add(contactEdge.Contact);
}
}
else
{
for (var contactEdge = ContactList; contactEdge != null; contactEdge = contactEdge.Next)
{
if (!contactEdge.Other.Awake)
{
if (ActiveContacts.Contains(contactEdge.Contact))
ActiveContacts.Remove(contactEdge.Contact);
}
}
}
}
#endif
}
}