principia/lib/Box2D/Particle/b2ParticleSystem.cc

2042 lines
58 KiB
C++

/*
* Copyright (c) 2013 Google, Inc.
*
* 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.
*/
#include <Box2D/Particle/b2ParticleSystem.h>
#include <Box2D/Particle/b2ParticleGroup.h>
#include <Box2D/Particle/b2VoronoiDiagram.h>
#include <Box2D/Common/b2BlockAllocator.h>
#include <Box2D/Dynamics/b2World.h>
#include <Box2D/Dynamics/b2WorldCallbacks.h>
#include <Box2D/Dynamics/b2Body.h>
#include <Box2D/Dynamics/b2Fixture.h>
#include <Box2D/Collision/Shapes/b2Shape.h>
#include <algorithm>
#include <tms/backend/print.h>
#include "creature.hh"
static const uint32 xTruncBits = 12;
static const uint32 yTruncBits = 12;
static const uint32 tagBits = 8 * sizeof(uint32);
static const uint32 yOffset = 1 << (yTruncBits - 1);
static const uint32 yShift = tagBits - yTruncBits;
static const uint32 xShift = tagBits - yTruncBits - xTruncBits;
static const uint32 xScale = 1 << xShift;
static const uint32 xOffset = xScale * (1 << (xTruncBits - 1));
static const uint32 xMask = (1 << xTruncBits) - 1;
static const uint32 yMask = (1 << yTruncBits) - 1;
static inline uint32 computeTag(float32 x, float32 y)
{
return ((uint32)(y + yOffset) << yShift) + (uint32)(xScale * x + xOffset);
}
static inline uint32 computeRelativeTag(uint32 tag, int32 x, int32 y)
{
return tag + (y << yShift) + (x << xShift);
}
b2ParticleSystem::b2ParticleSystem()
{
m_timestamp = 0;
m_allParticleFlags = 0;
m_allGroupFlags = 0;
m_density = 1;
m_inverseDensity = 1;
m_gravityScale = 1;
m_particleDiameter = 1;
m_inverseDiameter = 1;
m_squaredDiameter = 1;
m_count = 0;
m_internalAllocatedCapacity = 0;
m_maxCount = 0;
m_accumulationBuffer = NULL;
m_accumulation2Buffer = NULL;
m_depthBuffer = NULL;
m_groupBuffer = NULL;
m_proxyCount = 0;
m_proxyCapacity = 0;
m_proxyBuffer = NULL;
m_contactCount = 0;
m_contactCapacity = 0;
m_contactBuffer = NULL;
m_bodyContactCount = 0;
m_bodyContactCapacity = 0;
m_bodyContactBuffer = NULL;
m_pairCount = 0;
m_pairCapacity = 0;
m_pairBuffer = NULL;
m_triadCount = 0;
m_triadCapacity = 0;
m_triadBuffer = NULL;
m_groupCount = 0;
m_groupList = NULL;
m_pressureStrength = 0.05f;
m_dampingStrength = 1.0f;
m_elasticStrength = 0.25f;
m_springStrength = 0.25f;
m_viscousStrength = 0.08f;
m_surfaceTensionStrengthA = 0.1f/4.f;
m_surfaceTensionStrengthB = 0.2f/4.f;
m_powderStrength = 0.5f;
m_ejectionStrength = 0.5f;
m_colorMixingStrength = 0.5f;
m_world = NULL;
}
b2ParticleSystem::~b2ParticleSystem()
{
}
// Reallocate a buffer
template <typename T> T* b2ParticleSystem::ReallocateBuffer(T* oldBuffer, int32 oldCapacity, int32 newCapacity)
{
b2Assert(newCapacity > oldCapacity);
T* newBuffer = (T*) m_world->m_blockAllocator.Allocate(sizeof(T) * newCapacity);
memcpy(newBuffer, oldBuffer, sizeof(T) * oldCapacity);
m_world->m_blockAllocator.Free(oldBuffer, sizeof(T) * oldCapacity);
return newBuffer;
}
// Reallocate a buffer
template <typename T> T* b2ParticleSystem::ReallocateBuffer(T* buffer, int32 userSuppliedCapacity, int32 oldCapacity, int32 newCapacity, bool deferred)
{
b2Assert(newCapacity > oldCapacity);
// A 'deferred' buffer is reallocated only if it is not NULL.
// If 'userSuppliedCapacity' is not zero, buffer is user supplied and must be kept.
b2Assert(!userSuppliedCapacity || newCapacity <= userSuppliedCapacity);
if ((!deferred || buffer) && !userSuppliedCapacity)
{
buffer = ReallocateBuffer(buffer, oldCapacity, newCapacity);
}
return buffer;
}
// Reallocate a buffer
template <typename T> T* b2ParticleSystem::ReallocateBuffer(ParticleBuffer<T>* buffer, int32 oldCapacity, int32 newCapacity, bool deferred)
{
b2Assert(newCapacity > oldCapacity);
return ReallocateBuffer(buffer->data, buffer->userSuppliedCapacity, oldCapacity, newCapacity, deferred);
}
template <typename T> T* b2ParticleSystem::RequestParticleBuffer(T* buffer)
{
if (!buffer)
{
buffer = (T*) (m_world->m_blockAllocator.Allocate(sizeof(T) * m_internalAllocatedCapacity));
memset(buffer, 0, sizeof(T) * m_internalAllocatedCapacity);
}
return buffer;
}
static int32 LimitCapacity(int32 capacity, int32 maxCount)
{
return maxCount && capacity > maxCount ? maxCount : capacity;
}
int32 b2ParticleSystem::CreateParticle(const b2ParticleDef& def)
{
if (m_count >= b2_maxParticles) {
tms_debugf("reached maximum particles");
return b2_invalidParticleIndex;
}
if (m_count >= m_internalAllocatedCapacity)
{
int32 capacity = m_count ? 2 * m_count : b2_minParticleBufferCapacity;
capacity = LimitCapacity(capacity, m_maxCount);
capacity = LimitCapacity(capacity, m_flagsBuffer.userSuppliedCapacity);
capacity = LimitCapacity(capacity, m_positionBuffer.userSuppliedCapacity);
capacity = LimitCapacity(capacity, m_velocityBuffer.userSuppliedCapacity);
capacity = LimitCapacity(capacity, m_colorBuffer.userSuppliedCapacity);
capacity = LimitCapacity(capacity, m_userDataBuffer.userSuppliedCapacity);
if (m_internalAllocatedCapacity < capacity)
{
m_flagsBuffer.data = ReallocateBuffer(&m_flagsBuffer, m_internalAllocatedCapacity, capacity, false);
m_positionBuffer.data = ReallocateBuffer(&m_positionBuffer, m_internalAllocatedCapacity, capacity, false);
m_velocityBuffer.data = ReallocateBuffer(&m_velocityBuffer, m_internalAllocatedCapacity, capacity, false);
m_accumulationBuffer = ReallocateBuffer(m_accumulationBuffer, 0, m_internalAllocatedCapacity, capacity, false);
m_accumulation2Buffer = ReallocateBuffer(m_accumulation2Buffer, 0, m_internalAllocatedCapacity, capacity, true);
m_dataBuffer = ReallocateBuffer(m_dataBuffer, 0, m_internalAllocatedCapacity, capacity, false);
m_depthBuffer = ReallocateBuffer(m_depthBuffer, 0, m_internalAllocatedCapacity, capacity, true);
m_colorBuffer.data = ReallocateBuffer(&m_colorBuffer, m_internalAllocatedCapacity, capacity, true);
m_groupBuffer = ReallocateBuffer(m_groupBuffer, 0, m_internalAllocatedCapacity, capacity, false);
m_userDataBuffer.data = ReallocateBuffer(&m_userDataBuffer, m_internalAllocatedCapacity, capacity, true);
m_internalAllocatedCapacity = capacity;
}
}
if (m_count >= m_internalAllocatedCapacity)
{
return b2_invalidParticleIndex;
}
int32 index = m_count++;
m_flagsBuffer.data[index] = def.flags;
m_positionBuffer.data[index] = def.position;
m_velocityBuffer.data[index] = def.velocity;
m_groupBuffer[index] = NULL;
if (m_depthBuffer)
{
m_depthBuffer[index] = 0;
}
if (m_colorBuffer.data || !def.color.IsZero())
{
m_colorBuffer.data = RequestParticleBuffer(m_colorBuffer.data);
m_colorBuffer.data[index] = def.color;
}
if (m_userDataBuffer.data || def.userData)
{
m_userDataBuffer.data= RequestParticleBuffer(m_userDataBuffer.data);
m_userDataBuffer.data[index] = def.userData;
}
if (m_proxyCount >= m_proxyCapacity)
{
int32 oldCapacity = m_proxyCapacity;
int32 newCapacity = m_proxyCount ? 2 * m_proxyCount : b2_minParticleBufferCapacity;
m_proxyBuffer = ReallocateBuffer(m_proxyBuffer, oldCapacity, newCapacity);
m_proxyCapacity = newCapacity;
}
m_proxyBuffer[m_proxyCount++].index = index;
return index;
}
void b2ParticleSystem::DestroyParticle(
int32 index, bool callDestructionListener)
{
uint32 flags = b2_zombieParticle;
if (callDestructionListener)
{
flags |= b2_destructionListener;
}
m_flagsBuffer.data[index] |= flags;
}
int32 b2ParticleSystem::DestroyParticlesInShape(
const b2Shape& shape, const b2Transform& xf,
bool callDestructionListener)
{
class DestroyParticlesInShapeCallback : public b2QueryCallback
{
public:
DestroyParticlesInShapeCallback(
b2ParticleSystem* system, const b2Shape& shape,
const b2Transform& xf, bool callDestructionListener)
{
m_system = system;
m_shape = &shape;
m_xf = xf;
m_callDestructionListener = callDestructionListener;
m_destroyed = 0;
}
bool ReportFixture(b2Fixture* fixture)
{
return false;
}
bool ReportParticle(int32 index)
{
b2Assert(index >=0 && index < m_system->m_count);
if (m_shape->TestPoint(m_xf, m_system->m_positionBuffer.data[index]))
{
m_system->DestroyParticle(index, m_callDestructionListener);
m_destroyed++;
}
return true;
}
int32 Destroyed() { return m_destroyed; }
private:
b2ParticleSystem* m_system;
const b2Shape* m_shape;
b2Transform m_xf;
bool m_callDestructionListener;
int32 m_destroyed;
} callback(this, shape, xf, callDestructionListener);
b2AABB aabb;
shape.ComputeAABB(&aabb, xf, 0);
m_world->QueryAABB(&callback, aabb);
return callback.Destroyed();
}
void b2ParticleSystem::DestroyParticlesInGroup(
b2ParticleGroup* group, bool callDestructionListener)
{
for (int32 i = group->m_firstIndex; i < group->m_lastIndex; i++) {
DestroyParticle(i, callDestructionListener);
}
}
b2ParticleGroup* b2ParticleSystem::CreateParticleGroup(const b2ParticleGroupDef& groupDef)
{
float32 stride = GetParticleStride();
b2Transform identity;
identity.SetIdentity();
b2Transform transform = identity;
int32 firstIndex = m_count;
if (groupDef.shape)
{
b2ParticleDef particleDef;
particleDef.flags = groupDef.flags;
particleDef.color = groupDef.color;
particleDef.userData = groupDef.userData;
const b2Shape *shape = groupDef.shape;
transform.Set(groupDef.position, groupDef.angle);
b2AABB aabb;
int32 childCount = shape->GetChildCount();
for (int32 childIndex = 0; childIndex < childCount; childIndex++)
{
if (childIndex == 0)
{
shape->ComputeAABB(&aabb, identity, childIndex);
}
else
{
b2AABB childAABB;
shape->ComputeAABB(&childAABB, identity, childIndex);
aabb.Combine(childAABB);
}
}
for (float32 y = floorf(aabb.lowerBound.y / stride) * stride; y < aabb.upperBound.y; y += stride)
{
for (float32 x = floorf(aabb.lowerBound.x / stride) * stride; x < aabb.upperBound.x; x += stride)
{
b2Vec2 p(x, y);
if (shape->TestPoint(identity, p))
{
p = b2Mul(transform, p);
particleDef.position = p;
particleDef.velocity =
groupDef.linearVelocity +
b2Cross(groupDef.angularVelocity, p - groupDef.position);
CreateParticle(particleDef);
}
}
}
}
int32 lastIndex = m_count;
void* mem = m_world->m_blockAllocator.Allocate(sizeof(b2ParticleGroup));
b2ParticleGroup* group = new (mem) b2ParticleGroup();
group->m_system = this;
group->m_firstIndex = firstIndex;
group->m_lastIndex = lastIndex;
group->m_groupFlags = groupDef.groupFlags;
group->m_strength = groupDef.strength;
group->m_userData = groupDef.userData;
group->m_transform = transform;
group->m_destroyAutomatically = groupDef.destroyAutomatically;
group->m_prev = NULL;
group->m_next = m_groupList;
if (m_groupList)
{
m_groupList->m_prev = group;
}
m_groupList = group;
++m_groupCount;
for (int32 i = firstIndex; i < lastIndex; i++)
{
m_groupBuffer[i] = group;
}
UpdateContacts(true);
if (groupDef.flags & k_pairFlags)
{
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
if (a > b) b2Swap(a, b);
if (firstIndex <= a && b < lastIndex)
{
if (m_pairCount >= m_pairCapacity)
{
int32 oldCapacity = m_pairCapacity;
int32 newCapacity = m_pairCount ? 2 * m_pairCount : b2_minParticleBufferCapacity;
m_pairBuffer = ReallocateBuffer(m_pairBuffer, oldCapacity, newCapacity);
m_pairCapacity = newCapacity;
}
Pair& pair = m_pairBuffer[m_pairCount];
pair.indexA = a;
pair.indexB = b;
pair.flags = contact.flags;
pair.strength = groupDef.strength;
pair.distance = b2Distance(
m_positionBuffer.data[a],
m_positionBuffer.data[b]);
m_pairCount++;
}
}
}
if (groupDef.flags & k_triadFlags)
{
b2VoronoiDiagram diagram(
&m_world->m_stackAllocator, lastIndex - firstIndex);
for (int32 i = firstIndex; i < lastIndex; i++)
{
diagram.AddGenerator(m_positionBuffer.data[i], i);
}
diagram.Generate(stride / 2);
CreateParticleGroupCallback callback;
callback.system = this;
callback.def = &groupDef;
callback.firstIndex = firstIndex;
diagram.GetNodes(callback);
}
if (groupDef.groupFlags & b2_solidParticleGroup)
{
ComputeDepthForGroup(group);
}
return group;
}
void b2ParticleSystem::CreateParticleGroupCallback::operator()(int32 a, int32 b, int32 c) const
{
const b2Vec2& pa = system->m_positionBuffer.data[a];
const b2Vec2& pb = system->m_positionBuffer.data[b];
const b2Vec2& pc = system->m_positionBuffer.data[c];
b2Vec2 dab = pa - pb;
b2Vec2 dbc = pb - pc;
b2Vec2 dca = pc - pa;
float32 maxDistanceSquared = b2_maxTriadDistanceSquared * system->m_squaredDiameter;
if (b2Dot(dab, dab) < maxDistanceSquared &&
b2Dot(dbc, dbc) < maxDistanceSquared &&
b2Dot(dca, dca) < maxDistanceSquared)
{
if (system->m_triadCount >= system->m_triadCapacity)
{
int32 oldCapacity = system->m_triadCapacity;
int32 newCapacity = system->m_triadCount ? 2 * system->m_triadCount : b2_minParticleBufferCapacity;
system->m_triadBuffer = system->ReallocateBuffer(system->m_triadBuffer, oldCapacity, newCapacity);
system->m_triadCapacity = newCapacity;
}
Triad& triad = system->m_triadBuffer[system->m_triadCount];
triad.indexA = a;
triad.indexB = b;
triad.indexC = c;
triad.flags =
system->m_flagsBuffer.data[a] |
system->m_flagsBuffer.data[b] |
system->m_flagsBuffer.data[c];
triad.strength = def->strength;
b2Vec2 midPoint = (float32) 1 / 3 * (pa + pb + pc);
triad.pa = pa - midPoint;
triad.pb = pb - midPoint;
triad.pc = pc - midPoint;
triad.ka = -b2Dot(dca, dab);
triad.kb = -b2Dot(dab, dbc);
triad.kc = -b2Dot(dbc, dca);
triad.s = b2Cross(pa, pb) + b2Cross(pb, pc) + b2Cross(pc, pa);
system->m_triadCount++;
}
};
void b2ParticleSystem::JoinParticleGroups(b2ParticleGroup* groupA, b2ParticleGroup* groupB)
{
b2Assert(groupA != groupB);
RotateBuffer(groupB->m_firstIndex, groupB->m_lastIndex, m_count);
b2Assert(groupB->m_lastIndex == m_count);
RotateBuffer(groupA->m_firstIndex, groupA->m_lastIndex, groupB->m_firstIndex);
b2Assert(groupA->m_lastIndex == groupB->m_firstIndex);
uint32 particleFlags = 0;
for (int32 i = groupA->m_firstIndex; i < groupB->m_lastIndex; i++)
{
particleFlags |= m_flagsBuffer.data[i];
}
UpdateContacts(true);
if (particleFlags & k_pairFlags)
{
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
if (a > b) b2Swap(a, b);
if (groupA->m_firstIndex <= a && a < groupA->m_lastIndex &&
groupB->m_firstIndex <= b && b < groupB->m_lastIndex)
{
if (m_pairCount >= m_pairCapacity)
{
int32 oldCapacity = m_pairCapacity;
int32 newCapacity = m_pairCount ? 2 * m_pairCount : b2_minParticleBufferCapacity;
m_pairBuffer = ReallocateBuffer(m_pairBuffer, oldCapacity, newCapacity);
m_pairCapacity = newCapacity;
}
Pair& pair = m_pairBuffer[m_pairCount];
pair.indexA = a;
pair.indexB = b;
pair.flags = contact.flags;
pair.strength = b2Min(groupA->m_strength, groupB->m_strength);
pair.distance = b2Distance(m_positionBuffer.data[a], m_positionBuffer.data[b]);
m_pairCount++;
}
}
}
if (particleFlags & k_triadFlags)
{
b2VoronoiDiagram diagram(
&m_world->m_stackAllocator,
groupB->m_lastIndex - groupA->m_firstIndex);
for (int32 i = groupA->m_firstIndex; i < groupB->m_lastIndex; i++)
{
if (!(m_flagsBuffer.data[i] & b2_zombieParticle))
{
diagram.AddGenerator(m_positionBuffer.data[i], i);
}
}
diagram.Generate(GetParticleStride() / 2);
JoinParticleGroupsCallback callback;
callback.system = this;
callback.groupA = groupA;
callback.groupB = groupB;
diagram.GetNodes(callback);
}
for (int32 i = groupB->m_firstIndex; i < groupB->m_lastIndex; i++)
{
m_groupBuffer[i] = groupA;
}
uint32 groupFlags = groupA->m_groupFlags | groupB->m_groupFlags;
groupA->m_groupFlags = groupFlags;
groupA->m_lastIndex = groupB->m_lastIndex;
groupB->m_firstIndex = groupB->m_lastIndex;
DestroyParticleGroup(groupB);
if (groupFlags & b2_solidParticleGroup)
{
ComputeDepthForGroup(groupA);
}
}
void b2ParticleSystem::JoinParticleGroupsCallback::operator()(int32 a, int32 b, int32 c) const
{
// Create a triad if it will contain particles from both groups.
int32 countA =
(a < groupB->m_firstIndex) +
(b < groupB->m_firstIndex) +
(c < groupB->m_firstIndex);
if (countA > 0 && countA < 3)
{
uint32 af = system->m_flagsBuffer.data[a];
uint32 bf = system->m_flagsBuffer.data[b];
uint32 cf = system->m_flagsBuffer.data[c];
if (af & bf & cf & k_triadFlags)
{
const b2Vec2& pa = system->m_positionBuffer.data[a];
const b2Vec2& pb = system->m_positionBuffer.data[b];
const b2Vec2& pc = system->m_positionBuffer.data[c];
b2Vec2 dab = pa - pb;
b2Vec2 dbc = pb - pc;
b2Vec2 dca = pc - pa;
float32 maxDistanceSquared = b2_maxTriadDistanceSquared * system->m_squaredDiameter;
if (b2Dot(dab, dab) < maxDistanceSquared &&
b2Dot(dbc, dbc) < maxDistanceSquared &&
b2Dot(dca, dca) < maxDistanceSquared)
{
if (system->m_triadCount >= system->m_triadCapacity)
{
int32 oldCapacity = system->m_triadCapacity;
int32 newCapacity = system->m_triadCount ? 2 * system->m_triadCount : b2_minParticleBufferCapacity;
system->m_triadBuffer = system->ReallocateBuffer(system->m_triadBuffer, oldCapacity, newCapacity);
system->m_triadCapacity = newCapacity;
}
Triad& triad = system->m_triadBuffer[system->m_triadCount];
triad.indexA = a;
triad.indexB = b;
triad.indexC = c;
triad.flags = af | bf | cf;
triad.strength = b2Min(groupA->m_strength, groupB->m_strength);
b2Vec2 midPoint = (float32) 1 / 3 * (pa + pb + pc);
triad.pa = pa - midPoint;
triad.pb = pb - midPoint;
triad.pc = pc - midPoint;
triad.ka = -b2Dot(dca, dab);
triad.kb = -b2Dot(dab, dbc);
triad.kc = -b2Dot(dbc, dca);
triad.s = b2Cross(pa, pb) + b2Cross(pb, pc) + b2Cross(pc, pa);
system->m_triadCount++;
}
}
}
};
// Only called from SolveZombie() or JoinParticleGroups().
void b2ParticleSystem::DestroyParticleGroup(b2ParticleGroup* group)
{
b2Assert(m_groupCount > 0);
b2Assert(group);
if (m_world->m_destructionListener)
{
m_world->m_destructionListener->SayGoodbye(group);
}
for (int32 i = group->m_firstIndex; i < group->m_lastIndex; i++)
{
m_groupBuffer[i] = NULL;
}
if (group->m_prev)
{
group->m_prev->m_next = group->m_next;
}
if (group->m_next)
{
group->m_next->m_prev = group->m_prev;
}
if (group == m_groupList)
{
m_groupList = group->m_next;
}
--m_groupCount;
group->~b2ParticleGroup();
m_world->m_blockAllocator.Free(group, sizeof(b2ParticleGroup));
}
void b2ParticleSystem::ComputeDepthForGroup(b2ParticleGroup* group)
{
for (int32 i = group->m_firstIndex; i < group->m_lastIndex; i++)
{
m_accumulationBuffer[i] = 0;
}
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
if (a >= group->m_firstIndex && a < group->m_lastIndex &&
b >= group->m_firstIndex && b < group->m_lastIndex)
{
float32 w = contact.weight;
m_accumulationBuffer[a] += w;
m_accumulationBuffer[b] += w;
}
}
m_depthBuffer = RequestParticleBuffer(m_depthBuffer);
for (int32 i = group->m_firstIndex; i < group->m_lastIndex; i++)
{
float32 w = m_accumulationBuffer[i];
m_depthBuffer[i] = w < 0.8f ? 0 : b2_maxFloat;
}
int32 interationCount = group->GetParticleCount();
for (int32 t = 0; t < interationCount; t++)
{
bool updated = false;
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
if (a >= group->m_firstIndex && a < group->m_lastIndex &&
b >= group->m_firstIndex && b < group->m_lastIndex)
{
float32 r = 1 - contact.weight;
float32& ap0 = m_depthBuffer[a];
float32& bp0 = m_depthBuffer[b];
float32 ap1 = bp0 + r;
float32 bp1 = ap0 + r;
if (ap0 > ap1)
{
ap0 = ap1;
updated = true;
}
if (bp0 > bp1)
{
bp0 = bp1;
updated = true;
}
}
}
if (!updated)
{
break;
}
}
for (int32 i = group->m_firstIndex; i < group->m_lastIndex; i++)
{
float32& p = m_depthBuffer[i];
if (p < b2_maxFloat)
{
p *= m_particleDiameter;
}
else
{
p = 0;
}
}
}
inline void b2ParticleSystem::AddContact(int32 a, int32 b)
{
/* check principia layer filtering */
if ((m_flagsBuffer.data[a] & m_flagsBuffer.data[b]) < (1<<16))
return;
b2Vec2 d = m_positionBuffer.data[b] - m_positionBuffer.data[a];
float32 d2 = b2Dot(d, d);
if (d2 < m_squaredDiameter)
{
if (m_contactCount >= m_contactCapacity)
{
int32 oldCapacity = m_contactCapacity;
int32 newCapacity = m_contactCount ? 2 * m_contactCount : b2_minParticleBufferCapacity;
m_contactBuffer = ReallocateBuffer(m_contactBuffer, oldCapacity, newCapacity);
m_contactCapacity = newCapacity;
}
float32 invD = b2InvSqrt(d2);
b2ParticleContact& contact = m_contactBuffer[m_contactCount];
contact.indexA = a;
contact.indexB = b;
contact.flags = m_flagsBuffer.data[a] | m_flagsBuffer.data[b];
contact.weight = 1 - d2 * invD * m_inverseDiameter;
contact.normal = invD * d;
m_contactCount++;
}
}
static bool b2ParticleContactIsZombie(const b2ParticleContact& contact)
{
return (contact.flags & b2_zombieParticle) == b2_zombieParticle;
}
void b2ParticleSystem::UpdateContacts(bool exceptZombie)
{
Proxy* beginProxy = m_proxyBuffer;
Proxy* endProxy = beginProxy + m_proxyCount;
for (Proxy* proxy = beginProxy; proxy < endProxy; ++proxy)
{
int32 i = proxy->index;
b2Vec2 p = m_positionBuffer.data[i];
proxy->tag = computeTag(m_inverseDiameter * p.x, m_inverseDiameter * p.y);
}
std::sort(beginProxy, endProxy);
m_contactCount = 0;
for (Proxy *a = beginProxy, *c = beginProxy; a < endProxy; a++)
{
uint32 rightTag = computeRelativeTag(a->tag, 1, 0);
for (Proxy* b = a + 1; b < endProxy; b++)
{
if (rightTag < b->tag) break;
AddContact(a->index, b->index);
}
uint32 bottomLeftTag = computeRelativeTag(a->tag, -1, 1);
for (; c < endProxy; c++)
{
if (bottomLeftTag <= c->tag) break;
}
uint32 bottomRightTag = computeRelativeTag(a->tag, 1, 1);
for (Proxy* b = c; b < endProxy; b++)
{
if (bottomRightTag < b->tag) break;
AddContact(a->index, b->index);
}
}
if (exceptZombie)
{
b2ParticleContact* lastContact = std::remove_if(
m_contactBuffer, m_contactBuffer + m_contactCount,
b2ParticleContactIsZombie);
m_contactCount = (int32) (lastContact - m_contactBuffer);
}
}
void b2ParticleSystem::UpdateBodyContacts()
{
b2AABB aabb;
aabb.lowerBound.x = +b2_maxFloat;
aabb.lowerBound.y = +b2_maxFloat;
aabb.upperBound.x = -b2_maxFloat;
aabb.upperBound.y = -b2_maxFloat;
for (int32 i = 0; i < m_count; i++)
{
b2Vec2 p = m_positionBuffer.data[i];
aabb.lowerBound = b2Min(aabb.lowerBound, p);
aabb.upperBound = b2Max(aabb.upperBound, p);
}
aabb.lowerBound.x -= m_particleDiameter;
aabb.lowerBound.y -= m_particleDiameter;
aabb.upperBound.x += m_particleDiameter;
aabb.upperBound.y += m_particleDiameter;
m_bodyContactCount = 0;
class UpdateBodyContactsCallback : public b2QueryCallback
{
bool ReportFixture(b2Fixture* fixture)
{
if (fixture->IsSensor())
{
return true;
}
const b2Shape* shape = fixture->GetShape();
b2Body* b = fixture->GetBody();
b2Vec2 bp = b->GetWorldCenter();
float32 bm = b->GetMass();
float32 bI = b->GetInertia() - bm * b->GetLocalCenter().LengthSquared();
float32 invBm = bm > 0 ? 1 / bm : 0;
float32 invBI = bI > 0 ? 1 / bI : 0;
int32 childCount = shape->GetChildCount();
for (int32 childIndex = 0; childIndex < childCount; childIndex++)
{
b2AABB aabb = fixture->GetAABB(childIndex);
aabb.lowerBound.x -= m_system->m_particleDiameter;
aabb.lowerBound.y -= m_system->m_particleDiameter;
aabb.upperBound.x += m_system->m_particleDiameter;
aabb.upperBound.y += m_system->m_particleDiameter;
Proxy* beginProxy = m_system->m_proxyBuffer;
Proxy* endProxy = beginProxy + m_system->m_proxyCount;
Proxy* firstProxy = std::lower_bound(
beginProxy, endProxy,
computeTag(
m_system->m_inverseDiameter * aabb.lowerBound.x,
m_system->m_inverseDiameter * aabb.lowerBound.y));
Proxy* lastProxy = std::upper_bound(
firstProxy, endProxy,
computeTag(
m_system->m_inverseDiameter * aabb.upperBound.x,
m_system->m_inverseDiameter * aabb.upperBound.y));
for (Proxy* proxy = firstProxy; proxy != lastProxy; ++proxy)
{
int32 a = proxy->index;
/* check principia layer filtering */
if (!((m_system->m_flagsBuffer.data[a] >> 16) & fixture->m_filter.categoryBits))
continue;
{
/* do not collide with robot feet */
entity *e = (entity*)fixture->GetUserData();
if (e) {
if (e->flag_active(ENTITY_IS_CREATURE) && ((creature*)e)->is_foot_fixture(fixture))
continue;
}
}
b2Vec2 ap = m_system->m_positionBuffer.data[a];
if (aabb.lowerBound.x <= ap.x && ap.x <= aabb.upperBound.x &&
aabb.lowerBound.y <= ap.y && ap.y <= aabb.upperBound.y)
{
float32 d;
b2Vec2 n;
fixture->ComputeDistance(ap, &d, &n, childIndex);
if (d < m_system->m_particleDiameter)
{
float32 invAm =
m_system->m_flagsBuffer.data[a] & b2_wallParticle ?
0 : m_system->GetParticleInvMass();
b2Vec2 rp = ap - bp;
float32 rpn = b2Cross(rp, n);
if (m_system->m_bodyContactCount >= m_system->m_bodyContactCapacity)
{
int32 oldCapacity = m_system->m_bodyContactCapacity;
int32 newCapacity = m_system->m_bodyContactCount ? 2 * m_system->m_bodyContactCount : b2_minParticleBufferCapacity;
m_system->m_bodyContactBuffer = m_system->ReallocateBuffer(m_system->m_bodyContactBuffer, oldCapacity, newCapacity);
m_system->m_bodyContactCapacity = newCapacity;
}
b2ParticleBodyContact& contact = m_system->m_bodyContactBuffer[m_system->m_bodyContactCount];
contact.index = a;
contact.body = b;
contact.weight = 1 - d * m_system->m_inverseDiameter;
contact.normal = -n;
contact.mass = 1 / (invAm + invBm + invBI * rpn * rpn);
m_system->m_bodyContactCount++;
}
}
}
}
return true;
}
b2ParticleSystem* m_system;
public:
UpdateBodyContactsCallback(b2ParticleSystem* system)
{
m_system = system;
}
} callback(this);
m_world->QueryAABB(&callback, aabb);
}
void b2ParticleSystem::SolveCollision(const b2TimeStep& step)
{
b2AABB aabb;
aabb.lowerBound.x = +b2_maxFloat;
aabb.lowerBound.y = +b2_maxFloat;
aabb.upperBound.x = -b2_maxFloat;
aabb.upperBound.y = -b2_maxFloat;
for (int32 i = 0; i < m_count; i++)
{
b2Vec2 v = m_velocityBuffer.data[i];
b2Vec2 p1 = m_positionBuffer.data[i];
b2Vec2 p2 = p1 + step.dt * v;
aabb.lowerBound = b2Min(aabb.lowerBound, b2Min(p1, p2));
aabb.upperBound = b2Max(aabb.upperBound, b2Max(p1, p2));
}
class SolveCollisionCallback : public b2QueryCallback
{
bool ReportFixture(b2Fixture* fixture)
{
if (fixture->IsSensor())
{
return true;
}
const b2Shape* shape = fixture->GetShape();
b2Body* body = fixture->GetBody();
Proxy* beginProxy = m_system->m_proxyBuffer;
Proxy* endProxy = beginProxy + m_system->m_proxyCount;
int32 childCount = shape->GetChildCount();
for (int32 childIndex = 0; childIndex < childCount; childIndex++)
{
b2AABB aabb = fixture->GetAABB(childIndex);
aabb.lowerBound.x -= m_system->m_particleDiameter;
aabb.lowerBound.y -= m_system->m_particleDiameter;
aabb.upperBound.x += m_system->m_particleDiameter;
aabb.upperBound.y += m_system->m_particleDiameter;
Proxy* firstProxy = std::lower_bound(
beginProxy, endProxy,
computeTag(
m_system->m_inverseDiameter * aabb.lowerBound.x,
m_system->m_inverseDiameter * aabb.lowerBound.y));
Proxy* lastProxy = std::upper_bound(
firstProxy, endProxy,
computeTag(
m_system->m_inverseDiameter * aabb.upperBound.x,
m_system->m_inverseDiameter * aabb.upperBound.y));
for (Proxy* proxy = firstProxy; proxy != lastProxy; ++proxy)
{
int32 a = proxy->index;
/* check principia layer filtering */
if (!((m_system->m_flagsBuffer.data[a] >> 16) & fixture->m_filter.categoryBits))
continue;
{
/* do not collide with robot feet */
entity *e = (entity*)fixture->GetUserData();
if (e) {
if (e->flag_active(ENTITY_IS_CREATURE) && ((creature*)e)->is_foot_fixture(fixture))
continue;
}
}
b2Vec2 ap = m_system->m_positionBuffer.data[a];
if (aabb.lowerBound.x <= ap.x && ap.x <= aabb.upperBound.x &&
aabb.lowerBound.y <= ap.y && ap.y <= aabb.upperBound.y)
{
b2Vec2 av = m_system->m_velocityBuffer.data[a];
b2RayCastOutput output;
b2RayCastInput input;
input.p1 = b2Mul(body->m_xf, b2MulT(body->m_xf0, ap));
input.p2 = ap + m_step.dt * av;
input.maxFraction = 1;
if (fixture->RayCast(&output, input, childIndex))
{
b2Vec2 p =
(1 - output.fraction) * input.p1 +
output.fraction * input.p2 +
b2_linearSlop * output.normal;
b2Vec2 v = m_step.inv_dt * (p - ap);
m_system->m_velocityBuffer.data[a] = v;
b2Vec2 f = m_system->GetParticleMass() * (av - v);
f = b2Dot(f, output.normal) * output.normal;
body->ApplyLinearImpulse(f, p, true);
}
}
}
}
return true;
}
b2ParticleSystem* m_system;
b2TimeStep m_step;
public:
SolveCollisionCallback(b2ParticleSystem* system, const b2TimeStep& step)
{
m_system = system;
m_step = step;
}
} callback(this, step);
m_world->QueryAABB(&callback, aabb);
}
void b2ParticleSystem::Solve(const b2TimeStep& step)
{
++m_timestamp;
if (m_count == 0)
{
return;
}
m_allParticleFlags = 0;
for (int32 i = 0; i < m_count; i++)
{
m_allParticleFlags |= m_flagsBuffer.data[i];
}
if (m_allParticleFlags & b2_zombieParticle)
{
SolveZombie();
}
m_allGroupFlags = 0;
for (const b2ParticleGroup* group = m_groupList; group; group = group->GetNext())
{
m_allGroupFlags |= group->m_groupFlags;
}
b2Vec2 gravity = step.dt * m_gravityScale * m_world->GetGravity();
float32 criticalVelocytySquared = GetCriticalVelocitySquared(step);
for (int32 i = 0; i < m_count; i++)
{
b2Vec2& v = m_velocityBuffer.data[i];
v += gravity;
float32 v2 = b2Dot(v, v);
if (v2 > criticalVelocytySquared)
{
v *= b2Sqrt(criticalVelocytySquared / v2);
}
}
SolveCollision(step);
if (m_allGroupFlags & b2_rigidParticleGroup)
{
SolveRigid(step);
}
if (m_allParticleFlags & b2_wallParticle)
{
SolveWall(step);
}
for (int32 i = 0; i < m_count; i++)
{
m_positionBuffer.data[i] += step.dt * m_velocityBuffer.data[i];
m_dataBuffer[i] *= .99f;
}
UpdateBodyContacts();
UpdateContacts(false);
if (m_allParticleFlags & b2_viscousParticle)
{
SolveViscous(step);
}
if (m_allParticleFlags & b2_powderParticle)
{
SolvePowder(step);
}
if (m_allParticleFlags & b2_tensileParticle)
{
SolveTensile(step);
}
if (m_allParticleFlags & b2_elasticParticle)
{
SolveElastic(step);
}
if (m_allParticleFlags & b2_springParticle)
{
SolveSpring(step);
}
if (m_allGroupFlags & b2_solidParticleGroup)
{
SolveSolid(step);
}
if (m_allParticleFlags & b2_colorMixingParticle)
{
SolveColorMixing(step);
}
SolvePressure(step);
SolveDamping(step);
}
void b2ParticleSystem::SolvePressure(const b2TimeStep& step)
{
// calculates the sum of contact-weights for each particle
// that means dimensionless density
for (int32 i = 0; i < m_count; i++)
{
m_accumulationBuffer[i] = 0;
m_dataBuffer[i] = 0;
}
for (int32 k = 0; k < m_bodyContactCount; k++)
{
const b2ParticleBodyContact& contact = m_bodyContactBuffer[k];
int32 a = contact.index;
float32 w = contact.weight;
m_accumulationBuffer[a] += w;
}
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
float32 w = contact.weight;
m_accumulationBuffer[a] += w;
m_accumulationBuffer[b] += w;
/*
m_dataBuffer[a] += w;
m_dataBuffer[b] += w;
*/
}
// ignores powder particles
if (m_allParticleFlags & k_noPressureFlags)
{
for (int32 i = 0; i < m_count; i++)
{
if (m_flagsBuffer.data[i] & k_noPressureFlags)
{
m_accumulationBuffer[i] = 0;
}
}
}
// calculates pressure as a linear function of density
float32 pressurePerWeight = m_pressureStrength * GetCriticalPressure(step);
for (int32 i = 0; i < m_count; i++)
{
float32 w = m_accumulationBuffer[i];
float32 h = pressurePerWeight * b2Max(0.0f, b2Min(w, b2_maxParticleWeight) - b2_minParticleWeight);
m_accumulationBuffer[i] = h;
m_dataBuffer[i] = w;
}
// applies pressure between each particles in contact
float32 velocityPerPressure = step.dt / (m_density * m_particleDiameter);
for (int32 k = 0; k < m_bodyContactCount; k++)
{
const b2ParticleBodyContact& contact = m_bodyContactBuffer[k];
int32 a = contact.index;
b2Body* b = contact.body;
float32 w = contact.weight;
float32 m = contact.mass;
b2Vec2 n = contact.normal;
b2Vec2 p = m_positionBuffer.data[a];
float32 h = m_accumulationBuffer[a] + pressurePerWeight * w;
b2Vec2 f = velocityPerPressure * w * m * h * n;
m_velocityBuffer.data[a] -= GetParticleInvMass() * f;
b->ApplyLinearImpulse(f, p, true);
}
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
float32 w = contact.weight;
b2Vec2 n = contact.normal;
float32 h = m_accumulationBuffer[a] + m_accumulationBuffer[b];
b2Vec2 f = velocityPerPressure * w * h * n;
m_velocityBuffer.data[a] -= f;
m_velocityBuffer.data[b] += f;
}
}
void b2ParticleSystem::SolveDamping(const b2TimeStep& step)
{
// reduces normal velocity of each contact
float32 damping = m_dampingStrength;
for (int32 k = 0; k < m_bodyContactCount; k++)
{
const b2ParticleBodyContact& contact = m_bodyContactBuffer[k];
int32 a = contact.index;
b2Body* b = contact.body;
float32 w = contact.weight;
float32 m = contact.mass;
b2Vec2 n = contact.normal;
b2Vec2 p = m_positionBuffer.data[a];
b2Vec2 v = b->GetLinearVelocityFromWorldPoint(p) - m_velocityBuffer.data[a];
float32 vn = b2Dot(v, n);
if (vn < 0)
{
b2Vec2 f = damping * w * m * vn * n;
m_velocityBuffer.data[a] += GetParticleInvMass() * f;
b->ApplyLinearImpulse(-f, p, true);
}
}
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
float32 w = contact.weight;
b2Vec2 n = contact.normal;
b2Vec2 v = m_velocityBuffer.data[b] - m_velocityBuffer.data[a];
float32 vn = b2Dot(v, n);
if (vn < 0)
{
b2Vec2 f = damping * w * vn * n;
m_velocityBuffer.data[a] += f;
m_velocityBuffer.data[b] -= f;
}
}
}
void b2ParticleSystem::SolveWall(const b2TimeStep& step)
{
for (int32 i = 0; i < m_count; i++)
{
if (m_flagsBuffer.data[i] & b2_wallParticle)
{
m_velocityBuffer.data[i].SetZero();
}
}
}
void b2ParticleSystem::SolveRigid(const b2TimeStep& step)
{
for (b2ParticleGroup* group = m_groupList; group; group = group->GetNext())
{
if (group->m_groupFlags & b2_rigidParticleGroup)
{
group->UpdateStatistics();
b2Rot rotation(step.dt * group->m_angularVelocity);
b2Transform transform(
group->m_center + step.dt * group->m_linearVelocity -
b2Mul(rotation, group->m_center), rotation);
group->m_transform = b2Mul(transform, group->m_transform);
b2Transform velocityTransform;
velocityTransform.p.x = step.inv_dt * transform.p.x;
velocityTransform.p.y = step.inv_dt * transform.p.y;
velocityTransform.q.s = step.inv_dt * transform.q.s;
velocityTransform.q.c = step.inv_dt * (transform.q.c - 1);
for (int32 i = group->m_firstIndex; i < group->m_lastIndex; i++)
{
m_velocityBuffer.data[i] = b2Mul(velocityTransform, m_positionBuffer.data[i]);
}
}
}
}
void b2ParticleSystem::SolveElastic(const b2TimeStep& step)
{
float32 elasticStrength = step.inv_dt * m_elasticStrength;
for (int32 k = 0; k < m_triadCount; k++)
{
const Triad& triad = m_triadBuffer[k];
if (triad.flags & b2_elasticParticle)
{
int32 a = triad.indexA;
int32 b = triad.indexB;
int32 c = triad.indexC;
const b2Vec2& oa = triad.pa;
const b2Vec2& ob = triad.pb;
const b2Vec2& oc = triad.pc;
const b2Vec2& pa = m_positionBuffer.data[a];
const b2Vec2& pb = m_positionBuffer.data[b];
const b2Vec2& pc = m_positionBuffer.data[c];
b2Vec2 p = (float32) 1 / 3 * (pa + pb + pc);
b2Rot r;
r.s = b2Cross(oa, pa) + b2Cross(ob, pb) + b2Cross(oc, pc);
r.c = b2Dot(oa, pa) + b2Dot(ob, pb) + b2Dot(oc, pc);
float32 r2 = r.s * r.s + r.c * r.c;
float32 invR = b2InvSqrt(r2);
r.s *= invR;
r.c *= invR;
float32 strength = elasticStrength * triad.strength;
m_velocityBuffer.data[a] += strength * (b2Mul(r, oa) - (pa - p));
m_velocityBuffer.data[b] += strength * (b2Mul(r, ob) - (pb - p));
m_velocityBuffer.data[c] += strength * (b2Mul(r, oc) - (pc - p));
}
}
}
void b2ParticleSystem::SolveSpring(const b2TimeStep& step)
{
float32 springStrength = step.inv_dt * m_springStrength;
for (int32 k = 0; k < m_pairCount; k++)
{
const Pair& pair = m_pairBuffer[k];
if (pair.flags & b2_springParticle)
{
int32 a = pair.indexA;
int32 b = pair.indexB;
b2Vec2 d = m_positionBuffer.data[b] - m_positionBuffer.data[a];
float32 r0 = pair.distance;
float32 r1 = d.Length();
float32 strength = springStrength * pair.strength;
b2Vec2 f = strength * (r0 - r1) / r1 * d;
m_velocityBuffer.data[a] -= f;
m_velocityBuffer.data[b] += f;
}
}
}
void b2ParticleSystem::SolveTensile(const b2TimeStep& step)
{
m_accumulation2Buffer = RequestParticleBuffer(m_accumulation2Buffer);
for (int32 i = 0; i < m_count; i++)
{
m_accumulationBuffer[i] = 0;
m_accumulation2Buffer[i] = b2Vec2_zero;
}
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
if (contact.flags & b2_tensileParticle)
{
int32 a = contact.indexA;
int32 b = contact.indexB;
float32 w = contact.weight;
b2Vec2 n = contact.normal;
m_accumulationBuffer[a] += w;
m_accumulationBuffer[b] += w;
m_accumulation2Buffer[a] -= (1 - w) * w * n;
m_accumulation2Buffer[b] += (1 - w) * w * n;
}
}
float32 strengthA = m_surfaceTensionStrengthA * GetCriticalVelocity(step);
float32 strengthB = m_surfaceTensionStrengthB * GetCriticalVelocity(step);
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
if (contact.flags & b2_tensileParticle)
{
int32 a = contact.indexA;
int32 b = contact.indexB;
float32 w = contact.weight;
b2Vec2 n = contact.normal;
float32 h = m_accumulationBuffer[a] + m_accumulationBuffer[b];
b2Vec2 s = m_accumulation2Buffer[b] - m_accumulation2Buffer[a];
float32 fn = (strengthA * (h - 2) + strengthB * b2Dot(s, n)) * w;
b2Vec2 f = fn * n;
m_velocityBuffer.data[a] -= f;
m_velocityBuffer.data[b] += f;
}
}
}
void b2ParticleSystem::SolveViscous(const b2TimeStep& step)
{
float32 viscousStrength = m_viscousStrength;
for (int32 k = 0; k < m_bodyContactCount; k++)
{
const b2ParticleBodyContact& contact = m_bodyContactBuffer[k];
int32 a = contact.index;
if (m_flagsBuffer.data[a] & b2_viscousParticle)
{
b2Body* b = contact.body;
float32 w = contact.weight;
float32 m = contact.mass;
b2Vec2 p = m_positionBuffer.data[a];
b2Vec2 v = b->GetLinearVelocityFromWorldPoint(p) - m_velocityBuffer.data[a];
b2Vec2 f = viscousStrength * m * w * v;
m_velocityBuffer.data[a] += GetParticleInvMass() * f;
b->ApplyLinearImpulse(-f, p, true);
}
}
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
if (contact.flags & b2_viscousParticle)
{
int32 a = contact.indexA;
int32 b = contact.indexB;
float32 w = contact.weight;
b2Vec2 v = m_velocityBuffer.data[b] - m_velocityBuffer.data[a];
b2Vec2 f = viscousStrength * w * v;
m_velocityBuffer.data[a] += f;
m_velocityBuffer.data[b] -= f;
}
}
}
void b2ParticleSystem::SolvePowder(const b2TimeStep& step)
{
float32 powderStrength = m_powderStrength * GetCriticalVelocity(step);
float32 minWeight = 1.0f - b2_particleStride;
for (int32 k = 0; k < m_bodyContactCount; k++)
{
const b2ParticleBodyContact& contact = m_bodyContactBuffer[k];
int32 a = contact.index;
if (m_flagsBuffer.data[a] & b2_powderParticle)
{
float32 w = contact.weight;
if (w > minWeight)
{
b2Body* b = contact.body;
float32 m = contact.mass;
b2Vec2 p = m_positionBuffer.data[a];
b2Vec2 n = contact.normal;
b2Vec2 f = powderStrength * m * (w - minWeight) * n;
m_velocityBuffer.data[a] -= GetParticleInvMass() * f;
b->ApplyLinearImpulse(f, p, true);
}
}
}
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
if (contact.flags & b2_powderParticle)
{
float32 w = contact.weight;
if (w > minWeight)
{
int32 a = contact.indexA;
int32 b = contact.indexB;
b2Vec2 n = contact.normal;
b2Vec2 f = powderStrength * (w - minWeight) * n;
m_velocityBuffer.data[a] -= f;
m_velocityBuffer.data[b] += f;
}
}
}
}
void b2ParticleSystem::SolveSolid(const b2TimeStep& step)
{
// applies extra repulsive force from solid particle groups
m_depthBuffer = RequestParticleBuffer(m_depthBuffer);
float32 ejectionStrength = step.inv_dt * m_ejectionStrength;
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
if (m_groupBuffer[a] != m_groupBuffer[b])
{
float32 w = contact.weight;
b2Vec2 n = contact.normal;
float32 h = m_depthBuffer[a] + m_depthBuffer[b];
b2Vec2 f = ejectionStrength * h * w * n;
m_velocityBuffer.data[a] -= f;
m_velocityBuffer.data[b] += f;
}
}
}
void b2ParticleSystem::SolveColorMixing(const b2TimeStep& step)
{
// mixes color between contacting particles
m_colorBuffer.data = RequestParticleBuffer(m_colorBuffer.data);
int32 colorMixing256 = (int32) (256 * m_colorMixingStrength);
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
if (m_flagsBuffer.data[a] & m_flagsBuffer.data[b] & b2_colorMixingParticle)
{
b2ParticleColor& colorA = m_colorBuffer.data[a];
b2ParticleColor& colorB = m_colorBuffer.data[b];
int32 dr = (colorMixing256 * (colorB.r - colorA.r)) >> 8;
int32 dg = (colorMixing256 * (colorB.g - colorA.g)) >> 8;
int32 db = (colorMixing256 * (colorB.b - colorA.b)) >> 8;
int32 da = (colorMixing256 * (colorB.a - colorA.a)) >> 8;
colorA.r += dr;
colorA.g += dg;
colorA.b += db;
colorA.a += da;
colorB.r -= dr;
colorB.g -= dg;
colorB.b -= db;
colorB.a -= da;
}
}
}
void b2ParticleSystem::SolveZombie()
{
// removes particles with zombie flag
int32 newCount = 0;
int32* newIndices = (int32*) m_world->m_stackAllocator.Allocate(sizeof(int32) * m_count);
for (int32 i = 0; i < m_count; i++)
{
int32 flags = m_flagsBuffer.data[i];
if (flags & b2_zombieParticle)
{
b2DestructionListener * const destructionListener =
m_world->m_destructionListener;
if ((flags & b2_destructionListener) &&
destructionListener)
{
destructionListener->SayGoodbye(i);
}
newIndices[i] = b2_invalidParticleIndex;
}
else
{
newIndices[i] = newCount;
if (i != newCount)
{
m_flagsBuffer.data[newCount] = m_flagsBuffer.data[i];
m_positionBuffer.data[newCount] = m_positionBuffer.data[i];
m_velocityBuffer.data[newCount] = m_velocityBuffer.data[i];
m_groupBuffer[newCount] = m_groupBuffer[i];
if (m_depthBuffer)
{
m_depthBuffer[newCount] = m_depthBuffer[i];
}
if (m_colorBuffer.data)
{
m_colorBuffer.data[newCount] = m_colorBuffer.data[i];
}
if (m_userDataBuffer.data)
{
m_userDataBuffer.data[newCount] = m_userDataBuffer.data[i];
}
}
newCount++;
}
}
// predicate functions
struct Test
{
static bool IsProxyInvalid(const Proxy& proxy)
{
return proxy.index < 0;
}
static bool IsContactInvalid(const b2ParticleContact& contact)
{
return contact.indexA < 0 || contact.indexB < 0;
}
static bool IsBodyContactInvalid(const b2ParticleBodyContact& contact)
{
return contact.index < 0;
}
static bool IsPairInvalid(const Pair& pair)
{
return pair.indexA < 0 || pair.indexB < 0;
}
static bool IsTriadInvalid(const Triad& triad)
{
return triad.indexA < 0 || triad.indexB < 0 || triad.indexC < 0;
}
};
// update proxies
for (int32 k = 0; k < m_proxyCount; k++)
{
Proxy& proxy = m_proxyBuffer[k];
proxy.index = newIndices[proxy.index];
}
Proxy* lastProxy = std::remove_if(
m_proxyBuffer, m_proxyBuffer + m_proxyCount,
Test::IsProxyInvalid);
m_proxyCount = (int32) (lastProxy - m_proxyBuffer);
// update contacts
for (int32 k = 0; k < m_contactCount; k++)
{
b2ParticleContact& contact = m_contactBuffer[k];
contact.indexA = newIndices[contact.indexA];
contact.indexB = newIndices[contact.indexB];
}
b2ParticleContact* lastContact = std::remove_if(
m_contactBuffer, m_contactBuffer + m_contactCount,
Test::IsContactInvalid);
m_contactCount = (int32) (lastContact - m_contactBuffer);
// update particle-body contacts
for (int32 k = 0; k < m_bodyContactCount; k++)
{
b2ParticleBodyContact& contact = m_bodyContactBuffer[k];
contact.index = newIndices[contact.index];
}
b2ParticleBodyContact* lastBodyContact = std::remove_if(
m_bodyContactBuffer, m_bodyContactBuffer + m_bodyContactCount,
Test::IsBodyContactInvalid);
m_bodyContactCount = (int32) (lastBodyContact - m_bodyContactBuffer);
// update pairs
for (int32 k = 0; k < m_pairCount; k++)
{
Pair& pair = m_pairBuffer[k];
pair.indexA = newIndices[pair.indexA];
pair.indexB = newIndices[pair.indexB];
}
Pair* lastPair = std::remove_if(
m_pairBuffer, m_pairBuffer + m_pairCount, Test::IsPairInvalid);
m_pairCount = (int32) (lastPair - m_pairBuffer);
// update triads
for (int32 k = 0; k < m_triadCount; k++)
{
Triad& triad = m_triadBuffer[k];
triad.indexA = newIndices[triad.indexA];
triad.indexB = newIndices[triad.indexB];
triad.indexC = newIndices[triad.indexC];
}
Triad* lastTriad = std::remove_if(
m_triadBuffer, m_triadBuffer + m_triadCount,
Test::IsTriadInvalid);
m_triadCount = (int32) (lastTriad - m_triadBuffer);
// update groups
for (b2ParticleGroup* group = m_groupList; group; group = group->GetNext())
{
int32 firstIndex = newCount;
int32 lastIndex = 0;
bool modified = false;
for (int32 i = group->m_firstIndex; i < group->m_lastIndex; i++)
{
int32 j = newIndices[i];
if (j >= 0) {
firstIndex = b2Min(firstIndex, j);
lastIndex = b2Max(lastIndex, j + 1);
} else {
modified = true;
}
}
if (firstIndex < lastIndex)
{
group->m_firstIndex = firstIndex;
group->m_lastIndex = lastIndex;
if (modified)
{
if (group->m_groupFlags & b2_rigidParticleGroup)
{
group->m_toBeSplit = true;
}
}
}
else
{
group->m_firstIndex = 0;
group->m_lastIndex = 0;
if (group->m_destroyAutomatically)
{
group->m_toBeDestroyed = true;
}
}
}
// update particle count
m_count = newCount;
m_world->m_stackAllocator.Free(newIndices);
// destroy bodies with no particles
for (b2ParticleGroup* group = m_groupList; group;)
{
b2ParticleGroup* next = group->GetNext();
if (group->m_toBeDestroyed)
{
DestroyParticleGroup(group);
}
else if (group->m_toBeSplit)
{
// TODO: split the group
}
group = next;
}
}
void b2ParticleSystem::RotateBuffer(int32 start, int32 mid, int32 end)
{
// move the particles assigned to the given group toward the end of array
if (start == mid || mid == end)
{
return;
}
struct NewIndices
{
int32 operator[](int32 i) const
{
if (i < start)
{
return i;
}
else if (i < mid)
{
return i + end - mid;
}
else if (i < end)
{
return i + start - mid;
}
else
{
return i;
}
}
int32 start, mid, end;
} newIndices;
newIndices.start = start;
newIndices.mid = mid;
newIndices.end = end;
std::rotate(m_flagsBuffer.data + start, m_flagsBuffer.data + mid, m_flagsBuffer.data + end);
std::rotate(m_positionBuffer.data + start, m_positionBuffer.data + mid, m_positionBuffer.data + end);
std::rotate(m_velocityBuffer.data + start, m_velocityBuffer.data + mid, m_velocityBuffer.data + end);
std::rotate(m_groupBuffer + start, m_groupBuffer + mid, m_groupBuffer + end);
if (m_depthBuffer)
{
std::rotate(m_depthBuffer + start, m_depthBuffer + mid, m_depthBuffer + end);
}
if (m_colorBuffer.data)
{
std::rotate(m_colorBuffer.data + start, m_colorBuffer.data + mid, m_colorBuffer.data + end);
}
if (m_userDataBuffer.data)
{
std::rotate(m_userDataBuffer.data + start, m_userDataBuffer.data + mid, m_userDataBuffer.data + end);
}
// update proxies
for (int32 k = 0; k < m_proxyCount; k++)
{
Proxy& proxy = m_proxyBuffer[k];
proxy.index = newIndices[proxy.index];
}
// update contacts
for (int32 k = 0; k < m_contactCount; k++)
{
b2ParticleContact& contact = m_contactBuffer[k];
contact.indexA = newIndices[contact.indexA];
contact.indexB = newIndices[contact.indexB];
}
// update particle-body contacts
for (int32 k = 0; k < m_bodyContactCount; k++)
{
b2ParticleBodyContact& contact = m_bodyContactBuffer[k];
contact.index = newIndices[contact.index];
}
// update pairs
for (int32 k = 0; k < m_pairCount; k++)
{
Pair& pair = m_pairBuffer[k];
pair.indexA = newIndices[pair.indexA];
pair.indexB = newIndices[pair.indexB];
}
// update triads
for (int32 k = 0; k < m_triadCount; k++)
{
Triad& triad = m_triadBuffer[k];
triad.indexA = newIndices[triad.indexA];
triad.indexB = newIndices[triad.indexB];
triad.indexC = newIndices[triad.indexC];
}
// update groups
for (b2ParticleGroup* group = m_groupList; group; group = group->GetNext())
{
group->m_firstIndex = newIndices[group->m_firstIndex];
group->m_lastIndex = newIndices[group->m_lastIndex - 1] + 1;
}
}
void b2ParticleSystem::SetParticleRadius(float32 radius)
{
m_particleDiameter = 2 * radius;
m_squaredDiameter = m_particleDiameter * m_particleDiameter;
m_inverseDiameter = 1 / m_particleDiameter;
}
void b2ParticleSystem::SetParticleDensity(float32 density)
{
m_density = density;
m_inverseDensity = 1 / m_density;
}
float32 b2ParticleSystem::GetParticleDensity() const
{
return m_density;
}
void b2ParticleSystem::SetParticleGravityScale(float32 gravityScale)
{
m_gravityScale = gravityScale;
}
float32 b2ParticleSystem::GetParticleGravityScale() const
{
return m_gravityScale;
}
void b2ParticleSystem::SetParticleDamping(float32 damping)
{
m_dampingStrength = damping;
}
float32 b2ParticleSystem::GetParticleDamping() const
{
return m_dampingStrength;
}
float32 b2ParticleSystem::GetParticleRadius() const
{
return m_particleDiameter / 2;
}
float32 b2ParticleSystem::GetCriticalVelocity(const b2TimeStep& step) const
{
return m_particleDiameter * step.inv_dt;
}
float32 b2ParticleSystem::GetCriticalVelocitySquared(const b2TimeStep& step) const
{
float32 velocity = GetCriticalVelocity(step);
return velocity * velocity;
}
float32 b2ParticleSystem::GetCriticalPressure(const b2TimeStep& step) const
{
return m_density * GetCriticalVelocitySquared(step);
}
float32 b2ParticleSystem::GetParticleStride() const
{
return b2_particleStride * m_particleDiameter;
}
float32 b2ParticleSystem::GetParticleMass() const
{
float32 stride = GetParticleStride();
return m_density * stride * stride;
}
float32 b2ParticleSystem::GetParticleInvMass() const
{
return 1.777777f * m_inverseDensity * m_inverseDiameter * m_inverseDiameter;
}
uint32* b2ParticleSystem::GetParticleFlagsBuffer()
{
return m_flagsBuffer.data;
}
float32* b2ParticleSystem::GetParticleAccumulationBuffer()
{
return m_dataBuffer;
}
b2Vec2* b2ParticleSystem::GetParticlePositionBuffer()
{
return m_positionBuffer.data;
}
b2Vec2* b2ParticleSystem::GetParticleVelocityBuffer()
{
return m_velocityBuffer.data;
}
b2ParticleColor* b2ParticleSystem::GetParticleColorBuffer()
{
m_colorBuffer.data = RequestParticleBuffer(m_colorBuffer.data);
return m_colorBuffer.data;
}
void** b2ParticleSystem::GetParticleUserDataBuffer()
{
m_userDataBuffer.data = RequestParticleBuffer(m_userDataBuffer.data);
return m_userDataBuffer.data;
}
int32 b2ParticleSystem::GetParticleMaxCount() const
{
return m_maxCount;
}
void b2ParticleSystem::SetParticleMaxCount(int32 count)
{
b2Assert(m_count <= count);
m_maxCount = count;
}
const uint32* b2ParticleSystem::GetParticleFlagsBuffer() const
{
return m_flagsBuffer.data;
}
const b2Vec2* b2ParticleSystem::GetParticlePositionBuffer() const
{
return m_positionBuffer.data;
}
const b2Vec2* b2ParticleSystem::GetParticleVelocityBuffer() const
{
return m_velocityBuffer.data;
}
const b2ParticleColor* b2ParticleSystem::GetParticleColorBuffer() const
{
return ((b2ParticleSystem*) this)->GetParticleColorBuffer();
}
const b2ParticleGroup* const* b2ParticleSystem::GetParticleGroupBuffer() const
{
return m_groupBuffer;
}
void* const* b2ParticleSystem::GetParticleUserDataBuffer() const
{
return ((b2ParticleSystem*) this)->GetParticleUserDataBuffer();
}
template <typename T> void b2ParticleSystem::SetParticleBuffer(ParticleBuffer<T>* buffer, T* newData, int32 newCapacity)
{
b2Assert((newData && newCapacity) || (!newData && !newCapacity));
if (!buffer->userSuppliedCapacity)
{
m_world->m_blockAllocator.Free(buffer->data, sizeof(T) * m_internalAllocatedCapacity);
}
buffer->data = newData;
buffer->userSuppliedCapacity = newCapacity;}
void b2ParticleSystem::SetParticleFlagsBuffer(uint32* buffer, int32 capacity)
{
SetParticleBuffer(&m_flagsBuffer, buffer, capacity);
}
void b2ParticleSystem::SetParticlePositionBuffer(b2Vec2* buffer, int32 capacity)
{
SetParticleBuffer(&m_positionBuffer, buffer, capacity);
}
void b2ParticleSystem::SetParticleVelocityBuffer(b2Vec2* buffer, int32 capacity)
{
SetParticleBuffer(&m_velocityBuffer, buffer, capacity);
}
void b2ParticleSystem::SetParticleColorBuffer(b2ParticleColor* buffer, int32 capacity)
{
SetParticleBuffer(&m_colorBuffer, buffer, capacity);
}
b2ParticleGroup* const* b2ParticleSystem::GetParticleGroupBuffer()
{
return m_groupBuffer;
}
void b2ParticleSystem::SetParticleUserDataBuffer(void** buffer, int32 capacity)
{
SetParticleBuffer(&m_userDataBuffer, buffer, capacity);
}
void b2ParticleSystem::QueryAABB(b2QueryCallback* callback, const b2AABB& aabb) const
{
if (m_proxyCount == 0)
{
return;
}
Proxy* beginProxy = m_proxyBuffer;
Proxy* endProxy = beginProxy + m_proxyCount;
Proxy* firstProxy = std::lower_bound(
beginProxy, endProxy,
computeTag(
m_inverseDiameter * aabb.lowerBound.x,
m_inverseDiameter * aabb.lowerBound.y));
Proxy* lastProxy = std::upper_bound(
firstProxy, endProxy,
computeTag(
m_inverseDiameter * aabb.upperBound.x,
m_inverseDiameter * aabb.upperBound.y));
for (Proxy* proxy = firstProxy; proxy < lastProxy; ++proxy)
{
int32 i = proxy->index;
const b2Vec2& p = m_positionBuffer.data[i];
if (aabb.lowerBound.x < p.x && p.x < aabb.upperBound.x &&
aabb.lowerBound.y < p.y && p.y < aabb.upperBound.y)
{
if (!callback->ReportParticle(i))
{
break;
}
}
}
}
void b2ParticleSystem::RayCast(b2RayCastCallback* callback, const b2Vec2& point1, const b2Vec2& point2) const
{
if (m_proxyCount == 0)
{
return;
}
Proxy* beginProxy = m_proxyBuffer;
Proxy* endProxy = beginProxy + m_proxyCount;
Proxy* firstProxy = std::lower_bound(
beginProxy, endProxy,
computeTag(
m_inverseDiameter * b2Min(point1.x, point2.x) - 1,
m_inverseDiameter * b2Min(point1.y, point2.y) - 1));
Proxy* lastProxy = std::upper_bound(
firstProxy, endProxy,
computeTag(
m_inverseDiameter * b2Max(point1.x, point2.x) + 1,
m_inverseDiameter * b2Max(point1.y, point2.y) + 1));
float32 fraction = 1;
// solving the following equation:
// ((1-t)*point1+t*point2-position)^2=diameter^2
// where t is a potential fraction
b2Vec2 v = point2 - point1;
float32 v2 = b2Dot(v, v);
for (Proxy* proxy = firstProxy; proxy < lastProxy; ++proxy)
{
int32 i = proxy->index;
b2Vec2 p = point1 - m_positionBuffer.data[i];
float32 pv = b2Dot(p, v);
float32 p2 = b2Dot(p, p);
float32 determinant = pv * pv - v2 * (p2 - m_squaredDiameter);
if (determinant >= 0)
{
float32 sqrtDeterminant = b2Sqrt(determinant);
// find a solution between 0 and fraction
float32 t = (-pv - sqrtDeterminant) / v2;
if (t > fraction)
{
continue;
}
if (t < 0)
{
t = (-pv + sqrtDeterminant) / v2;
if (t < 0 || t > fraction)
{
continue;
}
}
b2Vec2 n = p + t * v;
n.Normalize();
float32 f = callback->ReportParticle(i, point1 + t * v, n, t);
fraction = b2Min(fraction, f);
if (fraction <= 0)
{
break;
}
}
}
}
float32 b2ParticleSystem::ComputeParticleCollisionEnergy() const
{
float32 sum_v2 = 0;
for (int32 k = 0; k < m_contactCount; k++)
{
const b2ParticleContact& contact = m_contactBuffer[k];
int32 a = contact.indexA;
int32 b = contact.indexB;
b2Vec2 n = contact.normal;
b2Vec2 v = m_velocityBuffer.data[b] - m_velocityBuffer.data[a];
float32 vn = b2Dot(v, n);
if (vn < 0)
{
sum_v2 += vn * vn;
}
}
return 0.5f * GetParticleMass() * sum_v2;
}