mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1337 lines
43 KiB
C++
1337 lines
43 KiB
C++
/* Copyright 2003-2007 ROBLOX Corporation, All Rights Reserved */
|
|
#include "stdafx.h"
|
|
|
|
#include "V8World/ContactManager.h"
|
|
#include "V8World/ContactManagerSpatialHash.h"
|
|
#include "V8World/BallPolyContact.h"
|
|
#include "V8World/PolyPolyContact.h"
|
|
#include "V8World/PolyCellContact.h"
|
|
#include "V8World/BallCellContact.h"
|
|
#include "V8World/BulletShapeContact.h"
|
|
#include "V8World/BulletShapeCellContact.h"
|
|
#include "V8World/BulletContact.h"
|
|
#include "V8World/TriangleMesh.h"
|
|
#include "V8World/World.h"
|
|
#include "V8World/Primitive.h"
|
|
#include "V8World/Assembly.h"
|
|
#include "V8World/Contact.h"
|
|
#include "V8World/Joint.h"
|
|
#include "V8world/Mechanism.h"
|
|
#include "V8World/SpatialFilter.h"
|
|
#include "rbx/Debug.h"
|
|
#include "Util/Profiling.h"
|
|
#include "V8DataModel/MegaCluster.h"
|
|
#include "V8World/MegaClusterPoly.h"
|
|
#include "V8World/SmoothClusterGeometry.h"
|
|
#include "v8world/Buoyancy.h"
|
|
#include "Voxel/Grid.h"
|
|
#include "rbx/DenseHash.h"
|
|
#include "v8world/Tolerance.h"
|
|
|
|
#include "voxel2/Grid.h"
|
|
#include "voxel2/Conversion.h"
|
|
|
|
#include <boost/math/special_functions/fpclassify.hpp>
|
|
|
|
#include "BulletCollision/CollisionDispatch/btCollisionWorld.h"
|
|
|
|
#include "rbx/Profiler.h"
|
|
|
|
LOGGROUP(TerrainCellListener)
|
|
DYNAMIC_LOGVARIABLE(DeferredVoxelUpdates, 0)
|
|
DYNAMIC_LOGGROUP(PartStreamingRequests)
|
|
FASTINTVARIABLE(IntersectingOthersCallsAllowedOnSpawn, 5)
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(ContactManagerOptimizedQueryExtents, false)
|
|
|
|
#define LARGE_TERRAIN_CONTACT_VOLUME (4 * 4 * 4)
|
|
|
|
namespace RBX {
|
|
|
|
Vector3 ContactManager::dummySurfaceNormal;
|
|
PartMaterial ContactManager::dummySurfaceMaterial;
|
|
|
|
ContactManager::ContactManager(World* world)
|
|
: world(world),
|
|
myMegaClusterPrim(NULL)
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 4355) // 'this' : used in base member initializer list
|
|
, spatialHash(new ContactManagerSpatialHash(world, this))
|
|
, profilingBroadphase(new Profiling::CodeProfiler("Broadphase"))
|
|
#pragma warning(pop)
|
|
{}
|
|
|
|
ContactManager::~ContactManager()
|
|
{
|
|
WriteValidator writeValidator(concurrencyValidator);
|
|
delete spatialHash;
|
|
}
|
|
|
|
void ContactManager::fastClear()
|
|
{
|
|
spatialHash->fastClear();
|
|
}
|
|
|
|
void ContactManager::doStats() // spit out hash stats
|
|
{
|
|
spatialHash->doStats();
|
|
}
|
|
|
|
bool ContactManager::intersectingMySimulation(Primitive* check, RBX::SystemAddress myLocalAddress, float overlapIgnored)
|
|
{
|
|
RBXASSERT_FISHING(check->getNetworkOwner() != myLocalAddress);
|
|
|
|
if (Assembly* assembly = check->getAssembly()) {
|
|
Contact* c = check->getFirstContact();
|
|
while (c) {
|
|
Primitive* other = c->otherPrimitive(check);
|
|
|
|
if (Assembly* otherAssembly = other->getAssembly()) {
|
|
if ( (otherAssembly != assembly)
|
|
&& (otherAssembly->getAssemblyPrimitive()->getNetworkOwner() == myLocalAddress)
|
|
&& (c->computeIsCollidingUi(overlapIgnored)) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
c = check->getNextContact(c);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ContactManager::intersectingOthers(Primitive* check, const std::set<Primitive*>& checkSet, float overlapIgnored)
|
|
{
|
|
Contact* c = check->getFirstContact();
|
|
|
|
|
|
while (c) {
|
|
Primitive* other = c->otherPrimitive(check);
|
|
|
|
if ( (checkSet.find(other) == checkSet.end())
|
|
&& c->computeIsCollidingUi(overlapIgnored) )
|
|
{
|
|
return true;
|
|
}
|
|
c = check->getNextContact(c);
|
|
}
|
|
|
|
// This is to support US15509 Deferred Contact for memory saving
|
|
DenseHashSet<Primitive*> primsFound(NULL);
|
|
getSpatialHash()->getPrimitivesOverlapping(check->getFastFuzzyExtents(), primsFound);
|
|
|
|
if (primsFound.size() == 0)
|
|
return false;
|
|
|
|
for (DenseHashSet<Primitive*>::const_iterator it = primsFound.begin(); primsFound.end() != it; ++it)
|
|
{
|
|
if (checkSet.find(*it) != checkSet.end())
|
|
continue;
|
|
Contact* c = createContact(check, *it);
|
|
if (!c)
|
|
continue;
|
|
bool colliding = c->computeIsCollidingUi(overlapIgnored);
|
|
delete c;
|
|
if (colliding)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ContactManager::intersectingOthers(const G3D::Array<Primitive*>& check, float overlapIgnored)
|
|
{
|
|
std::set<Primitive*> checkSet(check.begin(), check.end());
|
|
|
|
for (int i = 0; i < check.size(); ++i) {
|
|
if (intersectingOthers(check[i], checkSet, overlapIgnored)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ContactManager::intersectingOthers(Primitive* check, float overlapIgnored)
|
|
{
|
|
std::set<Primitive*> checkSet;
|
|
checkSet.insert(check);
|
|
|
|
return intersectingOthers(check, checkSet, overlapIgnored);
|
|
}
|
|
|
|
shared_ptr<const Instances> ContactManager::getPartCollisions(Primitive* check)
|
|
{
|
|
shared_ptr<Instances> instanceList = shared_ptr<Instances>(new Instances());
|
|
|
|
Contact* c = check->getFirstContact();
|
|
while (c) {
|
|
Primitive* other = c->otherPrimitive(check);
|
|
|
|
if (check != other)
|
|
{
|
|
PartInstance* partInstance = PartInstance::fromPrimitive(other);
|
|
|
|
if (RBX::Instance::fastDynamicCast<MegaClusterInstance>(partInstance))
|
|
{
|
|
shared_ptr<Instance> instance = shared_from(static_cast<Instance*>(partInstance));
|
|
if (c->computeIsCollidingUi(2E-4))
|
|
{
|
|
instanceList->push_back(instance);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
c = check->getNextContact(c);
|
|
}
|
|
|
|
DenseHashSet<Primitive*> primsFound(NULL);
|
|
getSpatialHash()->getPrimitivesOverlapping(check->getFastFuzzyExtents(), primsFound);
|
|
|
|
for (DenseHashSet<Primitive*>::const_iterator it = primsFound.begin(); primsFound.end() != it; ++it)
|
|
{
|
|
if (*it == check)
|
|
continue;
|
|
|
|
Contact* c = createContact(check, *it);
|
|
if (!c)
|
|
continue;
|
|
bool colliding = c->computeIsCollidingUi(2E-4);
|
|
delete c;
|
|
|
|
if (colliding)
|
|
{
|
|
PartInstance* partInstance = PartInstance::fromPrimitive(*it);
|
|
|
|
if (partInstance)
|
|
{
|
|
shared_ptr<Instance> instance = shared_from(partInstance);
|
|
|
|
instanceList->push_back(instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
return instanceList;
|
|
}
|
|
|
|
static void CheckPrimitive(Primitive* primitive, const Extents* extents, G3D::Array<Primitive*>* found)
|
|
{
|
|
if (extents->overlapsOrTouches(primitive->getFastFuzzyExtents())) {
|
|
found->append(primitive);
|
|
}
|
|
}
|
|
|
|
void ContactManager::getPrimitivesTouchingExtents(const Extents& extents,
|
|
const Primitive* ignore,
|
|
int maxCount,
|
|
G3D::Array<Primitive*>& found)
|
|
{
|
|
boost::unordered_set<Primitive*> tempBuffer;
|
|
spatialHash->getPrimitivesTouchingGrids(extents, ignore, maxCount, tempBuffer);
|
|
std::for_each(tempBuffer.begin(), tempBuffer.end(), boost::bind(&CheckPrimitive, _1, &extents, &found));
|
|
}
|
|
|
|
// does the same as above, just passes an Instance* instead of a Primitive* [is there a shared base class for both Primitive and Instance? didn't see one...
|
|
void ContactManager::getPrimitivesTouchingExtents(const Extents& extents,
|
|
const boost::unordered_set<const Primitive*>& ignorePrimitives,
|
|
int maxCount,
|
|
G3D::Array<Primitive*>& found)
|
|
{
|
|
boost::unordered_set<Primitive*> tempBuffer;
|
|
spatialHash->getPrimitivesTouchingGrids(extents, ignorePrimitives, maxCount, tempBuffer);
|
|
std::for_each(tempBuffer.begin(), tempBuffer.end(), boost::bind(&CheckPrimitive, _1, &extents, &found));
|
|
}
|
|
|
|
void ContactManager::getPrimitivesOverlapping(const Extents& extents, DenseHashSet<Primitive*>& result)
|
|
{
|
|
if (DFFlag::ContactManagerOptimizedQueryExtents)
|
|
spatialHash->getPrimitivesOverlappingRec(extents, result);
|
|
else
|
|
spatialHash->getPrimitivesOverlapping(extents, result);
|
|
}
|
|
|
|
Primitive* ContactManager::getSlowHit( const G3D::Array<Primitive*>& primitives,
|
|
const RbxRay& unitRay,
|
|
const G3D::Array<const Primitive*>& ignorePrim,
|
|
const HitTestFilter* filter,
|
|
Vector3& hitPoint,
|
|
Vector3& surfaceNormal,
|
|
PartMaterial& surfaceMaterial,
|
|
float maxDistance,
|
|
bool& stopped) const
|
|
{
|
|
RBXASSERT(unitRay.direction().isUnit());
|
|
|
|
Primitive* bestPrimitive = NULL;
|
|
float bestOffset = maxDistance;
|
|
float stopOffset = maxDistance;
|
|
stopped = false;
|
|
|
|
for (int i = 0; i < primitives.size(); ++i)
|
|
{
|
|
Primitive* p = primitives[i];
|
|
if (!p->getBody()) {
|
|
RBXCRASH("getSlowHit with deleted primitive");
|
|
}
|
|
|
|
bool dontIgnore = ignorePrim.find(p) == ignorePrim.end();
|
|
HitTestFilter::Result filterResult = filter ? filter->filterResult(p) : HitTestFilter::INCLUDE_PRIM;
|
|
if (dontIgnore && (filterResult != HitTestFilter::IGNORE_PRIM)) {
|
|
Vector3 worldPos = p->getCoordinateFrame().translation;
|
|
float radius = p->getRadius();
|
|
|
|
Vector3 rayToModel = worldPos - unitRay.origin();
|
|
float rayToPerpPoint = unitRay.direction().dot(rayToModel);
|
|
|
|
// perp point could be behind the ray - example: very large plane. This is just the center of the object
|
|
Vector3 perpPointOnRay = unitRay.origin() + (rayToPerpPoint * unitRay.direction()); // on the ray
|
|
Vector3 perpPointToCenter = worldPos - perpPointOnRay;
|
|
float perpOffset = perpPointToCenter.length();
|
|
|
|
// first test - just check a circle....
|
|
if (perpOffset <= radius)
|
|
{
|
|
// second test - G3D collision detection stuff
|
|
Vector3 thisHitPoint;
|
|
Vector3 thisSurfaceNormal;
|
|
if (p->hitTest(unitRay, thisHitPoint, thisSurfaceNormal)) {
|
|
float distanceToHit = unitRay.direction().dot(thisHitPoint - unitRay.origin());
|
|
if (distanceToHit > 0.0f) // i.e. - can't hit behind the origin
|
|
{
|
|
switch (filterResult)
|
|
{
|
|
case HitTestFilter::INCLUDE_PRIM:
|
|
{
|
|
if (distanceToHit < bestOffset) {
|
|
bestOffset = distanceToHit;
|
|
bestPrimitive = p;
|
|
hitPoint = thisHitPoint;
|
|
surfaceNormal = thisSurfaceNormal;
|
|
surfaceMaterial = PartInstance::fromPrimitive(p)->getRenderMaterial();
|
|
}
|
|
break;
|
|
}
|
|
case HitTestFilter::STOP_TEST:
|
|
{
|
|
if (distanceToHit < stopOffset) {
|
|
stopOffset = distanceToHit;
|
|
stopped = true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
RBXASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stopped && bestPrimitive && (bestOffset < stopOffset)) {
|
|
stopped = false;
|
|
}
|
|
return bestPrimitive;
|
|
}
|
|
|
|
|
|
Primitive* ContactManager::getHit( const RbxRay& worldRay,
|
|
const std::vector<const Primitive*>* ignorePrim,
|
|
const HitTestFilter* filter,
|
|
Vector3& hitPoint,
|
|
bool terrainCellsAreCubes,
|
|
bool ignoreWater,
|
|
Vector3& surfaceNormal,
|
|
PartMaterial& surfaceMaterial) const
|
|
{
|
|
ReadOnlyValidator readOnlyValidator(concurrencyValidator);
|
|
|
|
G3D::Array<const Primitive*> tempIgnore;
|
|
|
|
if (ignorePrim)
|
|
tempIgnore = *ignorePrim;
|
|
|
|
bool stopped;
|
|
Primitive* answer = getFastHit( worldRay,
|
|
tempIgnore,
|
|
filter,
|
|
hitPoint,
|
|
stopped,
|
|
terrainCellsAreCubes,
|
|
ignoreWater,
|
|
surfaceNormal,
|
|
surfaceMaterial);
|
|
|
|
return stopped ? NULL : answer;
|
|
}
|
|
|
|
|
|
// TODO: Refactor to user std::vector, begin/end iterators
|
|
Primitive* ContactManager::getFastHit( const RbxRay& worldRay,
|
|
const G3D::Array<const Primitive*>& ignorePrim,
|
|
const HitTestFilter* filter,
|
|
Vector3& hitPointWorld,
|
|
bool& stopped,
|
|
bool terrainCellsAreCubes,
|
|
bool ignoreWater,
|
|
Vector3& surfaceNormal,
|
|
PartMaterial& surfaceMaterial) const
|
|
{
|
|
RBXPROFILER_SCOPE("Physics", "getFastHit");
|
|
|
|
// don't do raycast if incoming ray does not have good (i.e. finite)
|
|
// floating point values in it.
|
|
for (int index = 0; index < 3; ++index) {
|
|
if (!boost::math::isfinite(worldRay.origin()[index]) ||
|
|
!boost::math::isfinite(worldRay.direction()[index])) {
|
|
hitPointWorld = worldRay.direction() + worldRay.origin();
|
|
stopped = false; // filter didn't stop
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
RBXASSERT(worldRay.direction().magnitude() < 5000.0f);
|
|
|
|
G3D::Array<Primitive*> primitives;
|
|
Vector3int32 grid = SpatialHashStatic::realToHashGrid(0, worldRay.origin());
|
|
|
|
float maxDistance = worldRay.direction().magnitude();
|
|
RBXASSERT(maxDistance < 5000.0f);
|
|
maxDistance = std::min(5000.0f, maxDistance);
|
|
const RbxRay unitRay = worldRay.unit();
|
|
|
|
// check first against megacluster, so only have to check once
|
|
Vector3 megaHitPointWorld;
|
|
Vector3 megaSurfaceNormal;
|
|
PartMaterial megaSurfaceMaterial;
|
|
float distanceToMegaHit = -1;
|
|
if ( myMegaClusterPrim && unitRay.direction().isUnit()){ // the latter check is necessary, unfortunately
|
|
bool dontIgnore = ignorePrim.find(myMegaClusterPrim) == ignorePrim.end();
|
|
HitTestFilter::Result filterResult = filter ? filter->filterResult(myMegaClusterPrim) : HitTestFilter::INCLUDE_PRIM;
|
|
if (dontIgnore && filterResult == HitTestFilter::INCLUDE_PRIM)
|
|
{
|
|
bool collided;
|
|
if (myMegaClusterPrim->getGeometryType() == Geometry::GEOMETRY_SMOOTHCLUSTER)
|
|
{
|
|
unsigned char voxelMaterial = Voxel2::Conversion::kMaterialDefault;
|
|
collided = static_cast<SmoothClusterGeometry*>(myMegaClusterPrim->getGeometry())->castRay(unitRay, megaHitPointWorld, megaSurfaceNormal, voxelMaterial, maxDistance, ignoreWater);
|
|
megaSurfaceMaterial = Voxel2::Conversion::getMaterialFromVoxelMaterial(voxelMaterial);
|
|
|
|
}
|
|
else
|
|
{
|
|
collided = static_cast<MegaClusterPoly*>(myMegaClusterPrim->getGeometry())->hitTest(unitRay, megaHitPointWorld, megaSurfaceNormal, maxDistance, terrainCellsAreCubes, ignoreWater);
|
|
megaSurfaceMaterial = GRASS_MATERIAL;
|
|
}
|
|
|
|
distanceToMegaHit = unitRay.direction().dot(megaHitPointWorld - unitRay.origin());
|
|
if (!collided) distanceToMegaHit = maxDistance;
|
|
}
|
|
}
|
|
|
|
do {
|
|
primitives.fastClear();
|
|
spatialHash->getPrimitivesInGrid(grid, primitives);
|
|
|
|
if (Primitive* answer = getSlowHit( primitives,
|
|
unitRay,
|
|
ignorePrim,
|
|
filter,
|
|
hitPointWorld,
|
|
surfaceNormal,
|
|
surfaceMaterial,
|
|
maxDistance,
|
|
stopped))
|
|
{
|
|
Extents hashGrid = SpatialHashStatic::hashGridToRealExtents(0, grid);
|
|
if (hashGrid.fuzzyContains(hitPointWorld, 0.001f)) {
|
|
if ( myMegaClusterPrim && distanceToMegaHit > 0.0f ){
|
|
float thisDistanceToHit = unitRay.direction().dot(hitPointWorld - unitRay.origin());
|
|
if (thisDistanceToHit < distanceToMegaHit) return answer;
|
|
}
|
|
else return answer;
|
|
}
|
|
}
|
|
|
|
if ( myMegaClusterPrim && distanceToMegaHit > 0.0f && distanceToMegaHit < maxDistance){
|
|
Extents hashGrid = SpatialHashStatic::hashGridToRealExtents(0, grid);
|
|
if (hashGrid.fuzzyContains(megaHitPointWorld, .001f)){
|
|
hitPointWorld = megaHitPointWorld;
|
|
surfaceNormal = megaSurfaceNormal;
|
|
surfaceMaterial = megaSurfaceMaterial;
|
|
return myMegaClusterPrim;
|
|
}
|
|
}
|
|
}
|
|
while (spatialHash->getNextGrid(grid, unitRay, maxDistance));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool ContactManager::terrainCellsInRegion3(Region3 region) const
|
|
{
|
|
if (!myMegaClusterPrim)
|
|
return false;
|
|
|
|
Vector3 minV = region.minPos();
|
|
Vector3 maxV = region.maxPos();
|
|
|
|
if (myMegaClusterPrim->getGeometryType() == Geometry::GEOMETRY_SMOOTHCLUSTER)
|
|
return static_cast<SmoothClusterGeometry*>(myMegaClusterPrim->getGeometry())->findCellsInBoundingBox(minV, maxV);
|
|
else
|
|
return static_cast<MegaClusterPoly*>(myMegaClusterPrim->getGeometry())->cellsInBoundingBox(minV, maxV);
|
|
}
|
|
|
|
|
|
template <class Set>
|
|
static bool anyExtentsOverlapsOrTouchesPrimitives(const Extents &origin, const Set &primitives)
|
|
{
|
|
for (typename Set::const_iterator it = primitives.begin(); primitives.end() != it; ++it) {
|
|
Extents primExtents = (*it)->getExtentsWorld();
|
|
if (primExtents.overlapsOrTouches(origin)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Perform a linear scan up to find a place large enough in searchExtents to fit spaceNeeded.
|
|
Vector3 ContactManager::findUpNearestLocationWithSpaceNeeded(const float maxSearchDepth, const Vector3 &startCenter,
|
|
const Vector3 &spaceNeededToCorner)
|
|
{
|
|
bool foundFittingLocation = false;
|
|
Vector3 calcCenter = startCenter;
|
|
int intersectingOthersChecks = 0;
|
|
|
|
for (Vector3 searchCenter = startCenter; !foundFittingLocation && searchCenter.y <= maxSearchDepth;) {
|
|
// Create a search box twice the height of the spaceNeededToCorner so we catch collisions
|
|
// above us as we pop our "local box" on top of primitives found in the first half
|
|
// of our search box.
|
|
Extents searchExtents = Extents::fromCenterCorner(
|
|
searchCenter + Vector3(0.0f, spaceNeededToCorner.y / 2.0f, 0.0f),
|
|
spaceNeededToCorner * Vector3(1.0f, 2.0f, 1.0f));
|
|
|
|
DenseHashSet<Primitive*> primsFound(NULL);
|
|
getSpatialHash()->getPrimitivesOverlapping(searchExtents, primsFound);
|
|
|
|
if (primsFound.size()) {
|
|
// Scan any primitives in searchExtents and find the lowest available location above us, if there is one fitting spaceNeededToCorner.
|
|
for (DenseHashSet<Primitive*>::const_iterator it = primsFound.begin(); primsFound.end() != it; ++it) {
|
|
Extents primExtents = (*it)->getExtentsWorld();
|
|
if (primExtents.overlapsOrTouches(searchExtents) && primExtents.max().y <= searchExtents.center().y) {
|
|
// Create a local extents that has space dimensions we require for our new virtual location.
|
|
Vector3 adjSpaceNeeded = spaceNeededToCorner - Vector3(Tolerance::maxOverlapOrGap(), Tolerance::maxOverlapOrGap(), Tolerance::maxOverlapOrGap());
|
|
Vector3 localCenter(calcCenter.x, primExtents.max().y + adjSpaceNeeded.y + Tolerance::maxOverlapOrGap(), calcCenter.z);
|
|
Extents local = Extents::fromCenterCorner(localCenter, adjSpaceNeeded);
|
|
|
|
// Should not be touching any other primitives or terrain at "local".
|
|
if (intersectingOthersChecks < FInt::IntersectingOthersCallsAllowedOnSpawn) {
|
|
intersectingOthersChecks++;
|
|
boost::shared_ptr<RBX::PartInstance> tempPart;
|
|
tempPart = Creatable<Instance>::create<PartInstance>();
|
|
CoordinateFrame frame(localCenter);
|
|
tempPart->setCoordinateFrame(frame);
|
|
Vector3 size = Math::toGrid(local.size(), 0.001f);
|
|
tempPart->setPartSizeXml(size);
|
|
if (!intersectingOthers(tempPart->getPartPrimitive(), 0.001f) && !terrainCellsInRegion3(Region3(local))) {
|
|
if (foundFittingLocation && localCenter.y < calcCenter.y) {
|
|
calcCenter.y = localCenter.y;
|
|
} else if (!foundFittingLocation) {
|
|
calcCenter.y = localCenter.y;
|
|
foundFittingLocation = true;
|
|
}
|
|
}
|
|
} else {
|
|
if (!anyExtentsOverlapsOrTouchesPrimitives(local, primsFound) && !terrainCellsInRegion3(Region3(local))) {
|
|
if (foundFittingLocation && localCenter.y < calcCenter.y) {
|
|
calcCenter.y = localCenter.y;
|
|
} else if (!foundFittingLocation) {
|
|
calcCenter.y = localCenter.y;
|
|
foundFittingLocation = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
searchCenter.y += spaceNeededToCorner.y * 2;
|
|
} else {
|
|
// If we didn't find any primitives, there may still be terrain above us, find the lowest available terrain location above us and use it.
|
|
if (!terrainCellsInRegion3(Region3(searchExtents))) {
|
|
foundFittingLocation = true;
|
|
calcCenter.y = searchCenter.y;
|
|
} else {
|
|
searchCenter.y += Voxel::kCELL_SIZE;
|
|
|
|
// Align to terrain grid.
|
|
int bottomCenterY = static_cast<int>(searchCenter.y - spaceNeededToCorner.y);
|
|
bottomCenterY -= bottomCenterY % Voxel::kCELL_SIZE;
|
|
calcCenter.y = bottomCenterY + spaceNeededToCorner.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
return calcCenter;
|
|
}
|
|
|
|
Contact* ContactManager::createContact( Primitive* p0, Primitive* p1)
|
|
{
|
|
RBXASSERT(!p0->getGeometry()->isTerrain() && !p1->getGeometry()->isTerrain());
|
|
|
|
// Skip Contact creation (for narrow phase) if either primitive is set to not collide
|
|
// AND neither primitive is reporting touches
|
|
if ((p0->getPreventCollide() || p1->getPreventCollide()) &&
|
|
(!p0->getOwner()->reportTouches() && !p1->getOwner()->reportTouches()) &&
|
|
(!p0->getDragging() && !p1->getDragging()))
|
|
return NULL;
|
|
|
|
// Handle the case where one or other is a CSG part that will collide using Bullet Narrow Phase
|
|
if (p0->getCollideType() == Geometry::COLLIDE_BULLET ||
|
|
p1->getCollideType() == Geometry::COLLIDE_BULLET)
|
|
{
|
|
if (world->getUsingPGSSolver())
|
|
return setUpbulletCollisionShapes(p0, p1) ? new BulletContact(world, p0, p1) : NULL;
|
|
else
|
|
return setUpbulletCollisionShapes(p0, p1) ? new BulletShapeContact(p0, p1, world) : NULL;
|
|
}
|
|
|
|
if (p0->getCollideType() > p1->getCollideType()) {
|
|
std::swap(p0, p1);
|
|
}
|
|
Geometry::CollideType t0 = p0->getCollideType();
|
|
Geometry::CollideType t1 = p1->getCollideType();
|
|
|
|
switch (t0)
|
|
{
|
|
case (Geometry::COLLIDE_BALL):
|
|
{
|
|
switch (t1)
|
|
{
|
|
case (Geometry::COLLIDE_BALL):
|
|
return new BallBallContact(p0, p1);
|
|
|
|
case (Geometry::COLLIDE_BLOCK):
|
|
return new BallBlockContact(p0, p1);
|
|
|
|
case (Geometry::COLLIDE_POLY):
|
|
return new BallPolyContact(p0, p1);
|
|
|
|
default:
|
|
RBXASSERT(0); return NULL;
|
|
}
|
|
}
|
|
case (Geometry::COLLIDE_BLOCK): // ball, block, poly
|
|
{
|
|
switch (t1)
|
|
{
|
|
case (Geometry::COLLIDE_BLOCK):
|
|
return new BlockBlockContact(p0, p1);
|
|
|
|
case (Geometry::COLLIDE_POLY):
|
|
return new PolyPolyContact(p0, p1);
|
|
|
|
default:
|
|
RBXASSERT(0); return NULL;
|
|
}
|
|
}
|
|
case (Geometry::COLLIDE_POLY):
|
|
{
|
|
return new PolyPolyContact(p0, p1);
|
|
}
|
|
default:
|
|
RBXASSERT(0);
|
|
}
|
|
RBXCRASH("ContactManager::createContact");
|
|
return NULL;
|
|
}
|
|
|
|
void ContactManager::onNewPair(Primitive* p0, Primitive* p1)
|
|
{
|
|
RBXASSERT_VERY_FAST(Primitive::getContact(p0, p1) == NULL);
|
|
const Assembly* a0 = Assembly::getConstPrimitiveAssembly(p0);
|
|
const Assembly* a1 = Assembly::getConstPrimitiveAssembly(p1);
|
|
if (a0 == a1)
|
|
return;
|
|
if (a0->computeIsGrounded() && a1->computeIsGrounded())
|
|
return;
|
|
const Assembly* root0 = Mechanism::getConstMovingAssemblyRoot(a0);
|
|
const Assembly* root1 = Mechanism::getConstMovingAssemblyRoot(a1);
|
|
if ((world->getSpatialFilter()->getSimSendFilter().mode == SimSendFilter::dPhysClient) &&
|
|
!SpatialFilter::simulatingPhase(root0->getFilterPhase()) &&
|
|
!SpatialFilter::simulatingPhase(root1->getFilterPhase()))
|
|
return;
|
|
Contact* c = createContact(p0, p1);
|
|
|
|
if (c)
|
|
{
|
|
world->insertContact(c);
|
|
RBXASSERT_VERY_FAST(Primitive::getContact(p0, p1) != NULL);
|
|
}
|
|
}
|
|
|
|
void ContactManager::releasePair(Primitive* p0, Primitive* p1)
|
|
{
|
|
if( p0 && p1 )
|
|
{
|
|
// for mega cluster contact only
|
|
if (p0->getGeometry()->isTerrain() || p1->getGeometry()->isTerrain())
|
|
{
|
|
Contact* c = Primitive::getContact(p0, p1);
|
|
while (c)
|
|
{
|
|
world->destroyContact(c);
|
|
c = Primitive::getContact(p0, p1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Contact* c = Primitive::getContact(p0, p1);
|
|
|
|
RBXASSERT_VERY_FAST(c);
|
|
|
|
if (c)
|
|
{
|
|
world->destroyContact(c);
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(0); // this should never happen - something amiss in adding/removing contacts from Spatial Hash stage
|
|
}
|
|
}
|
|
RBXASSERT_VERY_FAST(Primitive::getContact(p0, p1) == NULL); // should be clear now
|
|
}
|
|
}
|
|
|
|
void ContactManager::onPrimitiveAdded(Primitive* p)
|
|
{
|
|
if (p->getGeometry()->isTerrain())
|
|
{
|
|
RBXASSERT(!myMegaClusterPrim);
|
|
myMegaClusterPrim = p;
|
|
|
|
if (p->getGeometryType() == Geometry::GEOMETRY_SMOOTHCLUSTER)
|
|
{
|
|
static_cast<SmoothClusterGeometry*>(myMegaClusterPrim->getGeometry())->updateAllChunks();
|
|
|
|
getSmoothGrid()->connectListener(this);
|
|
}
|
|
else
|
|
{
|
|
FASTLOG2(FLog::TerrainCellListener,
|
|
"ContactManager: connecting %p to vg %p",
|
|
this, getVoxelGrid());
|
|
getVoxelGrid()->connectListener(this);
|
|
}
|
|
}
|
|
WriteValidator writeValidator(concurrencyValidator);
|
|
spatialHash->onPrimitiveAdded(p, false);
|
|
}
|
|
|
|
void ContactManager::onPrimitiveRemoved(Primitive* p)
|
|
{
|
|
if (p->getGeometry()->isTerrain())
|
|
{
|
|
if (p->getGeometryType() == Geometry::GEOMETRY_SMOOTHCLUSTER)
|
|
{
|
|
getSmoothGrid()->disconnectListener(this);
|
|
}
|
|
else
|
|
{
|
|
FASTLOG2(FLog::TerrainCellListener,
|
|
"ContactManager: disconnecting %p to vg %p",
|
|
this, getVoxelGrid());
|
|
getVoxelGrid()->disconnectListener(this);
|
|
}
|
|
|
|
RBXASSERT(myMegaClusterPrim);
|
|
myMegaClusterPrim = NULL;
|
|
}
|
|
|
|
WriteValidator writeValidator(concurrencyValidator);
|
|
spatialHash->onPrimitiveRemoved(p);
|
|
}
|
|
|
|
void ContactManager::onPrimitiveExtentsChanged(Primitive* p)
|
|
{
|
|
WriteValidator writeValidator(concurrencyValidator);
|
|
spatialHash->onPrimitiveExtentsChanged(p);
|
|
}
|
|
|
|
void ContactManager::onPrimitiveGeometryChanged(Primitive* p)
|
|
{
|
|
WriteValidator writeValidator(concurrencyValidator);
|
|
G3D::Array<Primitive*> oldContactingPrimitives;
|
|
|
|
// 1. Nuke all existing contacts for this primitive
|
|
while (Contact* c = p->getFirstContact()) {
|
|
oldContactingPrimitives.append(c->otherPrimitive(p));
|
|
world->destroyContact(c);
|
|
}
|
|
|
|
// 2. Rebuild new ones
|
|
for (int i = 0; i < oldContactingPrimitives.size(); ++i) {
|
|
Primitive* other = oldContactingPrimitives[i];
|
|
if (other->getGeometry()->isTerrain())
|
|
{
|
|
checkTerrainContact(p);
|
|
}
|
|
else
|
|
{
|
|
Contact* newContact = createContact(p, other);
|
|
if( newContact )
|
|
world->insertContact(newContact);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ContactManager::onPrimitiveAssembled(Primitive* p)
|
|
{
|
|
|
|
WriteValidator writeValidator(concurrencyValidator);
|
|
spatialHash->onPrimitiveAssembled(p);
|
|
|
|
}
|
|
|
|
struct AppendPrimitivePredicate
|
|
{
|
|
std::vector<Primitive*>* storage;
|
|
|
|
void operator()(Primitive* p)
|
|
{
|
|
storage->push_back(p);
|
|
}
|
|
};
|
|
|
|
void ContactManager::onAssemblyMovedFromStep(Assembly& a)
|
|
{
|
|
WriteValidator writeValidator(concurrencyValidator);
|
|
|
|
// Gather
|
|
tempPrimitives.clear();
|
|
|
|
AppendPrimitivePredicate pred = { &tempPrimitives };
|
|
a.visitPrimitives(pred);
|
|
|
|
// Notify
|
|
for (Primitive* p: tempPrimitives)
|
|
spatialHash->onPrimitiveExtentsChanged(p);
|
|
}
|
|
|
|
Primitive* ContactManager::getHitLegacy(const RbxRay& originDirection,
|
|
const Primitive* ignorePrim, // set to NULL to not use
|
|
const HitTestFilter* filter, // set to NULL to not use
|
|
Vector3& hitPointWorld,
|
|
float& distanceToHit,
|
|
const float& maxSearchDepth,
|
|
bool ignoreWater) const
|
|
{
|
|
RBXASSERT(originDirection.direction().isUnit());
|
|
RbxRay worldRay = RbxRay::fromOriginAndDirection(originDirection.origin(), originDirection.direction());
|
|
worldRay.direction() *= maxSearchDepth;
|
|
|
|
std::vector<const Primitive*> ignorePrims;
|
|
if (ignorePrim) {
|
|
ignorePrims.push_back(ignorePrim);
|
|
}
|
|
|
|
Primitive* answer = getHit(worldRay,
|
|
&ignorePrims,
|
|
filter,
|
|
hitPointWorld,
|
|
false,
|
|
ignoreWater);
|
|
|
|
distanceToHit = answer ? (hitPointWorld - worldRay.origin()).magnitude() : 0.0f;
|
|
return answer;
|
|
}
|
|
|
|
|
|
bool ContactManager::primitiveIsExcludedFromSpatialHash(Primitive* p)
|
|
{
|
|
return p->getGeometry()->isTerrain();
|
|
}
|
|
|
|
bool ContactManager::checkMegaClusterWaterContact(Primitive* otherPrim, const Vector3int16& extentStart,
|
|
const Vector3int16& extentEnd, const Vector3int16& extentSize)
|
|
{
|
|
bool touchedWater = false;
|
|
bool touchingWater = false;
|
|
|
|
Vector3int16 gridCell;
|
|
for (gridCell.y = extentStart.y; gridCell.y <= extentEnd.y; gridCell.y += ( extentSize.y > 1 ? extentSize.y - 1 : 1 ))
|
|
for (gridCell.z = extentStart.z; gridCell.z <= extentEnd.z; gridCell.z += ( extentSize.z > 1 ? extentSize.z - 1 : 1 ))
|
|
for (gridCell.x = extentStart.x; gridCell.x <= extentEnd.x; gridCell.x += ( extentSize.x > 1 ? extentSize.x - 1 : 1 ))
|
|
if ( !getVoxelGrid()->getWaterCell(gridCell).isEmpty() )
|
|
{
|
|
touchingWater = true;
|
|
break;
|
|
}
|
|
|
|
// Check center of extent
|
|
if ( !touchingWater )
|
|
{
|
|
Vector3int16 middleCell = ( extentStart + extentEnd ) / 2;
|
|
if ( ( ( middleCell.x > extentStart.x && middleCell.x < extentEnd.x ) ||
|
|
( middleCell.y > extentStart.y && middleCell.y < extentEnd.y ) ||
|
|
( middleCell.z > extentStart.z && middleCell.z < extentEnd.z ) ) &&
|
|
!getVoxelGrid()->getWaterCell( middleCell ).isEmpty() )
|
|
{
|
|
touchingWater = true;
|
|
}
|
|
}
|
|
|
|
// remove buoyancy contact if the primitive no longer touches water
|
|
for (int i = 0; i < otherPrim->getNumContacts(); ++i)
|
|
{
|
|
if (otherPrim->getContactOther(i) != myMegaClusterPrim)
|
|
continue;
|
|
|
|
Contact* contact = otherPrim->getContact(i);
|
|
if (contact->getContactType() != Contact::Contact_Buoyancy)
|
|
continue;
|
|
|
|
touchedWater = true;
|
|
if (!touchingWater)
|
|
world->destroyContact(contact);
|
|
}
|
|
if ( !touchedWater && touchingWater )
|
|
world->insertContact(BuoyancyContact::create(myMegaClusterPrim, otherPrim));
|
|
return touchingWater;
|
|
}
|
|
|
|
// TBD: Optimize this later
|
|
bool ContactManager::checkMegaClusterBigTerrainContact(Primitive* otherPrim)
|
|
{
|
|
std::vector<Vector3int16> cellsUnfiltered;
|
|
MegaClusterPoly* mCPoly = static_cast<MegaClusterPoly*>(myMegaClusterPrim->getGeometry());
|
|
mCPoly->findCellsTouchingGeometry(myMegaClusterPrim->getCoordinateFrame(), *(otherPrim->getConstGeometry()), otherPrim->getCoordinateFrame(), &cellsUnfiltered);
|
|
if (cellsUnfiltered.size() == 0)
|
|
return false;
|
|
|
|
typedef boost::unordered_set<Vector3int16, Vector3int16::boost_compatible_hash_value> CellSet;
|
|
CellSet cells;
|
|
cells.insert(cellsUnfiltered.begin(), cellsUnfiltered.end());
|
|
|
|
// remove cells from "cells" map if we already have contact with this primitive and that particular cell
|
|
// so we aren't deleting Contact objects and recreating duplicates (for coherence)
|
|
// The other pre-existing contacts that are *not* in the new cells list should be removed,
|
|
// since they are no longer relevant
|
|
for (int i = 0; i < otherPrim->getNumContacts(); ++i)
|
|
{
|
|
if (otherPrim->getContactOther(i) != myMegaClusterPrim)
|
|
continue;
|
|
|
|
Contact* contact = otherPrim->getContact(i);
|
|
if (contact->getContactType() != Contact::Contact_Cell)
|
|
continue;
|
|
|
|
CellSet::iterator it = cells.find(static_cast<CellContact*>(contact)->getGridFeature().toVector3int16());
|
|
if( it != cells.end() )
|
|
cells.erase(it);
|
|
else
|
|
world->destroyContact(contact);
|
|
}
|
|
|
|
// the cells map will only have non-preexisting contacts, so create them
|
|
for(CellSet::iterator i = cells.begin(); i != cells.end(); i++ )
|
|
{
|
|
if( otherPrim->getCollideType() == Geometry::COLLIDE_BALL )
|
|
world->insertContact(new BallCellContact(myMegaClusterPrim, otherPrim, *i));
|
|
else if (otherPrim->getCollideType() == Geometry::COLLIDE_BULLET && otherPrim->getGeometry()->setUpBulletCollisionData())
|
|
world->insertContact(new BulletShapeCellContact(myMegaClusterPrim, otherPrim, *i, world));
|
|
else
|
|
world->insertContact(new PolyCellContact(myMegaClusterPrim, otherPrim, *i));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ContactManager::checkMegaClusterSmallTerrainContact(Primitive* otherPrim, const Vector3int16& extentStart, const Vector3int16& extentEnd,
|
|
const Vector3int16& extentSize, bool cellChanged)
|
|
{
|
|
bool touchingTerrain = false;
|
|
|
|
RBXASSERT(extentSize.x * extentSize.y * extentSize.z <= LARGE_TERRAIN_CONTACT_VOLUME);
|
|
|
|
bool cellMap[LARGE_TERRAIN_CONTACT_VOLUME];
|
|
memset(cellMap, 0, LARGE_TERRAIN_CONTACT_VOLUME);
|
|
|
|
// Copy data from terrain to cell map
|
|
Vector3int16 gridCell(0, 0, 0);
|
|
|
|
if (SpatialRegion::regionContainingVoxel(extentStart) == SpatialRegion::regionContainingVoxel(extentEnd))
|
|
{
|
|
Voxel::Grid::Region region = getVoxelGrid()->getRegion(extentStart, extentEnd);
|
|
|
|
int i = 0;
|
|
|
|
for (gridCell.y = extentStart.y; gridCell.y <= extentEnd.y; ++gridCell.y)
|
|
{
|
|
for (gridCell.z = extentStart.z; gridCell.z <= extentEnd.z; ++gridCell.z)
|
|
{
|
|
for (gridCell.x = extentStart.x; gridCell.x <= extentEnd.x; ++gridCell.x)
|
|
{
|
|
cellMap[i] = region.voxelAt(gridCell).solid.getBlock() != Voxel::CELL_BLOCK_Empty;
|
|
touchingTerrain = touchingTerrain || cellMap[i];
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i = 0;
|
|
|
|
for (gridCell.y = extentStart.y; gridCell.y <= extentEnd.y; ++gridCell.y)
|
|
{
|
|
for (gridCell.z = extentStart.z; gridCell.z <= extentEnd.z; ++gridCell.z)
|
|
{
|
|
for (gridCell.x = extentStart.x; gridCell.x <= extentEnd.x; ++gridCell.x)
|
|
{
|
|
cellMap[i] = getVoxelGrid()->getCell(gridCell).solid.getBlock() != Voxel::CELL_BLOCK_Empty;
|
|
touchingTerrain = touchingTerrain || cellMap[i];
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector3int16 extentCell(0, 0, 0); // Cell within the AA extent of the primitive
|
|
|
|
// Remove cell contacts if they no longer touch the primitive
|
|
for (int i = 0; i < otherPrim->getNumContacts(); ++i)
|
|
{
|
|
if (otherPrim->getContactOther(i) != myMegaClusterPrim)
|
|
continue;
|
|
|
|
Contact* contact = otherPrim->getContact(i);
|
|
if (contact->getContactType() != Contact::Contact_Cell)
|
|
continue;
|
|
|
|
const Vector3int32& gridCell = static_cast<CellContact*>(contact)->getGridFeature();
|
|
|
|
int offsetX = gridCell.x - extentStart.x;
|
|
int offsetY = gridCell.y - extentStart.y;
|
|
int offsetZ = gridCell.z - extentStart.z;
|
|
|
|
if ((unsigned)offsetX >= (unsigned)extentSize.x || (unsigned)offsetY >= (unsigned)extentSize.y || (unsigned)offsetZ >= (unsigned)extentSize.z)
|
|
{
|
|
world->destroyContact(contact);
|
|
continue;
|
|
}
|
|
|
|
bool& cellSolid = cellMap[offsetX + extentSize.x * (offsetZ + extentSize.z * offsetY)];
|
|
|
|
if (cellChanged && !cellSolid)
|
|
{
|
|
world->destroyContact(contact);
|
|
continue;
|
|
}
|
|
|
|
// A contact already exists for this cell so don't create a new one
|
|
cellSolid = false;
|
|
}
|
|
|
|
int i = 0;
|
|
|
|
for (gridCell.y = extentStart.y; gridCell.y <= extentEnd.y; ++gridCell.y)
|
|
{
|
|
for (gridCell.z = extentStart.z; gridCell.z <= extentEnd.z; ++gridCell.z)
|
|
{
|
|
for (gridCell.x = extentStart.x; gridCell.x <= extentEnd.x; ++gridCell.x)
|
|
{
|
|
if (cellMap[i])
|
|
{
|
|
if (otherPrim->getCollideType() == Geometry::COLLIDE_BALL)
|
|
world->insertContact(new BallCellContact(myMegaClusterPrim, otherPrim, gridCell));
|
|
else if (otherPrim->getCollideType() == Geometry::COLLIDE_BULLET && otherPrim->getGeometry()->setUpBulletCollisionData())
|
|
world->insertContact(new BulletShapeCellContact(myMegaClusterPrim, otherPrim, gridCell, world));
|
|
else
|
|
world->insertContact(new PolyCellContact(myMegaClusterPrim, otherPrim, gridCell));
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return touchingTerrain;
|
|
}
|
|
|
|
void ContactManager::checkMegaClusterContact(Primitive* otherPrim, bool checkTerrain, bool checkWater, bool cellChangd)
|
|
{
|
|
if( !myMegaClusterPrim )
|
|
return;
|
|
|
|
// Skip Contact creation (for narrow phase) for any primitive that is set to not collide
|
|
// AND is not reporting touches
|
|
if (otherPrim->getPreventCollide() && !otherPrim->getOwner()->reportTouches() && !otherPrim->getDragging())
|
|
return;
|
|
|
|
unsigned int nonEmptyCellCount = getVoxelGrid()->getNonEmptyCellCount();
|
|
|
|
if( nonEmptyCellCount == 0 )
|
|
return;
|
|
|
|
Extents otherExtents = otherPrim->getExtentsWorld();
|
|
|
|
// Check if it overlaps with terrain and clamp it to the overlapped area if it does
|
|
if (!otherExtents.clampToOverlap(Voxel::getTerrainExtents()))
|
|
{
|
|
releasePair(myMegaClusterPrim, otherPrim);
|
|
return;
|
|
}
|
|
|
|
const Vector3int16 extentStart = Voxel::worldToCell_floor(otherExtents.min());
|
|
const Vector3int16 extentEnd = Voxel::worldToCell_floor(otherExtents.max());
|
|
|
|
const Vector3int16 extentSize(extentEnd - extentStart + Vector3int16::one());
|
|
|
|
bool touchingTerrain = false;
|
|
bool touchingWater = false;
|
|
|
|
if ( checkTerrain )
|
|
{
|
|
if ( extentSize.x * extentSize.y * extentSize.z > LARGE_TERRAIN_CONTACT_VOLUME )
|
|
touchingTerrain = checkMegaClusterBigTerrainContact(otherPrim);
|
|
else {
|
|
touchingTerrain = checkMegaClusterSmallTerrainContact(otherPrim, extentStart, extentEnd, extentSize, cellChangd);
|
|
}
|
|
}
|
|
|
|
if ( checkWater ) {
|
|
touchingWater = checkMegaClusterWaterContact(otherPrim, extentStart, extentEnd, extentSize);
|
|
}
|
|
if ( cellChangd )
|
|
world->ticklePrimitive(otherPrim, true);
|
|
if ( !touchingWater && !touchingTerrain )
|
|
releasePair(myMegaClusterPrim, otherPrim);
|
|
}
|
|
|
|
static TerrainPartitionSmooth::ChunkResult* findChunk(std::vector<TerrainPartitionSmooth::ChunkResult>& chunks, const Vector3int32& id)
|
|
{
|
|
for (size_t i = 0; i < chunks.size(); ++i)
|
|
if (chunks[i].id == id)
|
|
return &chunks[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool isAnyChunkTouchingWater(const std::vector<TerrainPartitionSmooth::ChunkResult>& chunks)
|
|
{
|
|
for (size_t i = 0; i < chunks.size(); ++i)
|
|
if (chunks[i].touchesWater)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void ContactManager::checkSmoothClusterContact(Primitive* otherPrim, bool cellChanged)
|
|
{
|
|
RBXASSERT(myMegaClusterPrim && getSmoothGrid());
|
|
|
|
if (!getSmoothGrid()->isAllocated())
|
|
return;
|
|
|
|
// Skip Contact creation (for narrow phase) for any primitive that is set to not collide
|
|
// AND is not reporting touches
|
|
if (otherPrim->getPreventCollide() && !otherPrim->getOwner()->reportTouches() && !otherPrim->getDragging())
|
|
return;
|
|
|
|
Geometry* otherGeom = otherPrim->getGeometry();
|
|
if (!otherGeom->setUpBulletCollisionData())
|
|
return;
|
|
|
|
Extents otherExtentsLocal(-0.5f * otherGeom->getSize(), 0.5f * otherGeom->getSize());
|
|
Extents otherExtentsWorld = otherExtentsLocal.toWorldSpace(otherPrim->getCoordinateFrameUnsafe());
|
|
|
|
tempChunks.clear();
|
|
std::vector<TerrainPartitionSmooth::ChunkResult>& chunks = tempChunks;
|
|
|
|
static_cast<SmoothClusterGeometry*>(myMegaClusterPrim->getGeometry())->getTerrainPartition()->findChunksTouchingExtents(otherExtentsWorld, &chunks);
|
|
|
|
bool touchingWater = isAnyChunkTouchingWater(chunks);
|
|
bool wasTouchingWater = false;
|
|
|
|
for (int i = 0; i < otherPrim->getNumContacts(); )
|
|
{
|
|
if (otherPrim->getContactOther(i) == myMegaClusterPrim)
|
|
{
|
|
Contact* contact = otherPrim->getContact(i);
|
|
Contact::ContactType contactType = contact->getContactType();
|
|
RBXASSERT(contactType == Contact::Contact_Cell || contactType == Contact::Contact_Buoyancy);
|
|
|
|
if (contactType == Contact::Contact_Cell)
|
|
{
|
|
TerrainPartitionSmooth::ChunkResult* chunk = findChunk(chunks, static_cast<CellContact*>(contact)->getGridFeature());
|
|
|
|
if (chunk && chunk->touchesSolid)
|
|
chunk->touchesSolid = false; // mark chunk as not-touching so that the code below does not create new contacts
|
|
else
|
|
{
|
|
world->destroyContact(contact);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (touchingWater)
|
|
wasTouchingWater = true;
|
|
else
|
|
{
|
|
world->destroyContact(contact);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
for (size_t i = 0; i < chunks.size(); ++i)
|
|
{
|
|
const TerrainPartitionSmooth::ChunkResult& chunk = chunks[i];
|
|
|
|
if (chunk.touchesSolid)
|
|
if (shared_ptr<btCollisionShape> shape = static_cast<SmoothClusterGeometry*>(myMegaClusterPrim->getGeometry())->getBulletChunkShape(chunk.id))
|
|
{
|
|
if (world->getUsingPGSSolver())
|
|
world->insertContact(new BulletCellContact(world, myMegaClusterPrim, otherPrim, chunk.id, shape));
|
|
else
|
|
world->insertContact(new BulletShapeCellContact(myMegaClusterPrim, otherPrim, chunk.id, shape, world));
|
|
}
|
|
}
|
|
|
|
if (touchingWater && !wasTouchingWater)
|
|
world->insertContact(BuoyancyContact::create(myMegaClusterPrim, otherPrim));
|
|
|
|
if (cellChanged)
|
|
world->ticklePrimitive(otherPrim, true);
|
|
}
|
|
|
|
void ContactManager::checkTerrainContact(Primitive* otherPrim)
|
|
{
|
|
if (myMegaClusterPrim)
|
|
{
|
|
if (myMegaClusterPrim->getGeometryType() == Geometry::GEOMETRY_SMOOTHCLUSTER)
|
|
checkSmoothClusterContact(otherPrim, false);
|
|
else
|
|
checkMegaClusterContact(otherPrim, true, true, false);
|
|
}
|
|
}
|
|
|
|
void ContactManager::terrainCellChanged(const Voxel::CellChangeInfo& info)
|
|
{
|
|
updatedTerrainRegions.insert(SpatialRegion::regionContainingVoxel(info.position));
|
|
}
|
|
|
|
void ContactManager::onTerrainRegionChanged(const Voxel2::Region& region)
|
|
{
|
|
std::vector<Vector3int32> chunks = region.expand(1).getChunkIds(TerrainPartitionSmooth::kChunkSizeLog2);
|
|
|
|
updatedTerrainChunks.insert(chunks.begin(), chunks.end());
|
|
}
|
|
|
|
Voxel::Grid* ContactManager::getVoxelGrid()
|
|
{
|
|
return boost::polymorphic_downcast<MegaClusterInstance*>(myMegaClusterPrim->getOwner())->getVoxelGrid();
|
|
}
|
|
|
|
Voxel2::Grid* ContactManager::getSmoothGrid()
|
|
{
|
|
return boost::polymorphic_downcast<MegaClusterInstance*>(myMegaClusterPrim->getOwner())->getSmoothGrid();
|
|
}
|
|
|
|
|
|
void ContactManager::applyDeferredMegaClusterChanges()
|
|
{
|
|
for (UpdatedTerrainRegionsSet::const_iterator itr = updatedTerrainRegions.begin(); itr != updatedTerrainRegions.end(); ++itr)
|
|
{
|
|
const SpatialRegion::Id regionId = *itr;
|
|
Extents extents(SpatialRegion::smallestCornerOfRegionInGlobalCoordStuds(regionId).toVector3(),
|
|
SpatialRegion::largestCornerOfRegionInGlobalCoordStuds(regionId).toVector3());
|
|
|
|
FASTLOG3(DFLog::DeferredVoxelUpdates, "Deferred voxel region update: starting region (%d,%d,%d)",
|
|
regionId.value().x, regionId.value().y, regionId.value().z);
|
|
DenseHashSet<Primitive*> primitives(NULL);
|
|
getPrimitivesOverlapping(extents, primitives);
|
|
size_t count = 0;
|
|
for (DenseHashSet<Primitive*>::const_iterator pitr = primitives.begin(); pitr!=primitives.end(); ++pitr) {
|
|
if (myMegaClusterPrim) {
|
|
releasePair(myMegaClusterPrim, *pitr);
|
|
|
|
world->destroyTerrainWeldJointsWithEmptyCells(myMegaClusterPrim, regionId, *pitr);
|
|
}
|
|
checkMegaClusterContact(*pitr, true, true, true);
|
|
++count;
|
|
}
|
|
FASTLOG1(DFLog::DeferredVoxelUpdates, "Done updating region. Updated %u parts", count);
|
|
}
|
|
|
|
updatedTerrainRegions.clear();
|
|
}
|
|
|
|
void ContactManager::applyDeferredSmoothClusterChanges()
|
|
{
|
|
DenseHashSet<Primitive*> primitives(NULL);
|
|
|
|
// Gather primitives from all chunks
|
|
for (auto& id: updatedTerrainChunks)
|
|
{
|
|
Voxel2::Region region = Voxel2::Region::fromChunk(id, TerrainPartitionSmooth::kChunkSizeLog2).expand(1);
|
|
|
|
Extents extents(region.begin().toVector3() * Voxel::kCELL_SIZE, region.end().toVector3() * Voxel::kCELL_SIZE);
|
|
|
|
getPrimitivesOverlapping(extents, primitives);
|
|
}
|
|
|
|
// Update chunks
|
|
for (auto& id: updatedTerrainChunks)
|
|
static_cast<SmoothClusterGeometry*>(myMegaClusterPrim->getGeometry())->updateChunk(id);
|
|
|
|
// Update primitives
|
|
for (Primitive* prim: primitives)
|
|
{
|
|
releasePair(myMegaClusterPrim, prim);
|
|
|
|
checkSmoothClusterContact(prim, true);
|
|
|
|
world->destroyTerrainWeldJointsNoTouch(myMegaClusterPrim, prim);
|
|
}
|
|
|
|
updatedTerrainChunks.clear();
|
|
|
|
static_cast<SmoothClusterGeometry*>(myMegaClusterPrim->getGeometry())->garbageCollectIncremental();
|
|
}
|
|
|
|
void ContactManager::applyDeferredTerrainChanges()
|
|
{
|
|
RBXPROFILER_SCOPE("Physics", "applyDeferredTerrainChanges");
|
|
|
|
if (myMegaClusterPrim)
|
|
{
|
|
if (myMegaClusterPrim->getGeometryType() == Geometry::GEOMETRY_SMOOTHCLUSTER)
|
|
applyDeferredSmoothClusterChanges();
|
|
else
|
|
applyDeferredMegaClusterChanges();
|
|
}
|
|
}
|
|
|
|
bool ContactManager::setUpbulletCollisionShapes(Primitive* p0, Primitive* p1)
|
|
{
|
|
// for CSG parts return false if decomp is not set
|
|
// for legacy parts make sure the bullet geometry is set
|
|
// return false if anything is amiss
|
|
|
|
if (p0->getGeometry()->setUpBulletCollisionData() && p1->getGeometry()->setUpBulletCollisionData())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|