505 lines
12 KiB
C++
505 lines
12 KiB
C++
#include "physics_world.h"
|
|
#include <vec2.h>
|
|
#include <math.h>
|
|
#include "threads.h"
|
|
#include "obj/object.h"
|
|
#include "empire.h"
|
|
#include "network/message.h"
|
|
#include "util/save_file.h"
|
|
#include <assert.h>
|
|
|
|
#include "main/references.h"
|
|
#include "main/logging.h"
|
|
|
|
#include "compat/intrin.h"
|
|
|
|
#define SPLIT_COUNT 16
|
|
#define REBALANCE_RATIO 4
|
|
#define MAX_DEPTH 8
|
|
|
|
PhysicsWorld::PhysicsWorld(double GridSize, double GridFuzz, unsigned GridCount)
|
|
: gridSize(GridSize), halfGridSize(GridSize * 0.5), gridFuzz(GridFuzz), gridCount(GridCount)
|
|
{
|
|
while(gridCount > 200) {
|
|
gridCount = (gridCount + 1)/2;
|
|
gridSize *= 2.0;
|
|
halfGridSize *= 2.0;
|
|
}
|
|
|
|
outside.bound.reset(AABBoxd::fromCircle(vec3d(),1.0e12));
|
|
outside.fuzzBound = outside.bound;
|
|
outside.itemCount = 0;
|
|
|
|
grid = new PhysicsGrid[gridCount*gridCount];
|
|
for(unsigned x = 0; x < gridCount; ++x) {
|
|
for(unsigned y = 0; y < gridCount; ++y) {
|
|
auto& zone = grid[x*gridCount + y];
|
|
zone.itemCount = 0;
|
|
|
|
for(unsigned i = 0; i < 32; ++i)
|
|
zone.groups[i].fuzz = gridFuzz;
|
|
|
|
vec2d pos = vec2d(vec2i(x,y) - vec2i(gridCount / 2)) * gridSize;
|
|
|
|
zone.bound.reset(vec3d(pos.x,-100000.0,pos.y));
|
|
zone.bound.addPoint(vec3d(pos.x+gridSize,100000.0,pos.y+gridSize));
|
|
|
|
zone.fuzzBound.reset(zone.bound.minimum - vec3d(gridFuzz));
|
|
zone.fuzzBound.addPoint(zone.bound.maximum + vec3d(gridFuzz));
|
|
}
|
|
}
|
|
}
|
|
|
|
PhysicsWorld::~PhysicsWorld() {
|
|
delete[] grid;
|
|
}
|
|
|
|
//Gets the grid index for x
|
|
unsigned PhysicsWorld::getBox(double x) const {
|
|
int index = int(floor(x / gridSize)) + int(gridCount / 2);
|
|
if(index <= 0)
|
|
return 0;
|
|
if(index >= (int)gridCount-1)
|
|
return gridCount-1;
|
|
return index;
|
|
}
|
|
|
|
//Gets the smallest grid index that could contain x
|
|
unsigned PhysicsWorld::getLowerBound(double x) const {
|
|
double dIndex = floor(x / gridSize);
|
|
double pct = (x / gridSize) - dIndex;
|
|
|
|
int index = int(dIndex) + int(gridCount / 2);
|
|
if(pct < gridFuzz / gridSize)
|
|
index -= 1;
|
|
|
|
if(index <= 0)
|
|
return 0;
|
|
if(index >= (int)gridCount-1)
|
|
return gridCount-1;
|
|
return index;
|
|
}
|
|
|
|
//Gets the largest grid index that could contain x
|
|
unsigned PhysicsWorld::getUpperBound(double x) const {
|
|
double dIndex = floor(x / gridSize);
|
|
double pct = (x / gridSize) - dIndex;
|
|
|
|
int index = int(dIndex) + int(gridCount / 2);
|
|
if(pct > 1.0 - (gridFuzz / gridSize))
|
|
index += 1;
|
|
|
|
if(index <= 0)
|
|
return 0;
|
|
if(index >= (int)gridCount-1)
|
|
return gridCount-1;
|
|
return index;
|
|
}
|
|
|
|
void PhysBisect::findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) const {
|
|
if(!items.empty()) {
|
|
auto* pItems = &items.front();
|
|
for(auto* end = pItems + items.size(), *next = pItems + 1; pItems != end; pItems = next, ++next) {
|
|
auto& item = **pItems;
|
|
//Prefetch all 3 cache lines that the next item's bound occupies
|
|
if(next != end) {
|
|
auto* pNextItem = *next;
|
|
PREFETCH((const char*)pNextItem + offsetof(PhysicsItem,bound));
|
|
PREFETCH((const char*)pNextItem + offsetof(PhysicsItem,bound) + (2 * sizeof(double)));
|
|
PREFETCH((const char*)pNextItem + offsetof(PhysicsItem,bound) + (4 * sizeof(double)));
|
|
}
|
|
if(box.overlaps(item.bound))
|
|
callback(item);
|
|
}
|
|
}
|
|
|
|
if(a) {
|
|
int dir = classify(box);
|
|
|
|
if(dir != 1)
|
|
a->findInBox(box, callback, ownerMask);
|
|
if(dir != -1)
|
|
b->findInBox(box, callback, ownerMask);
|
|
}
|
|
}
|
|
|
|
void PhysBisect::fuse() {
|
|
if(!a)
|
|
return;
|
|
|
|
a->fuse(items);
|
|
b->fuse(items);
|
|
a = b = 0;
|
|
childrenA = childrenB = 0;
|
|
}
|
|
|
|
void PhysBisect::fuse(std::vector<PhysicsItem*>& into) {
|
|
if(a) {
|
|
a->fuse(into);
|
|
b->fuse(into);
|
|
}
|
|
|
|
into.insert(into.end(), items.begin(), items.end());
|
|
delete this;
|
|
}
|
|
|
|
void PhysBisect::split() {
|
|
if(depth == MAX_DEPTH)
|
|
return;
|
|
a = new PhysBisect();
|
|
b = new PhysBisect();
|
|
|
|
a->fuzz = b->fuzz = fuzz * 0.5;
|
|
a->depth = b->depth = depth + 1;
|
|
|
|
a->parent = b->parent = this;
|
|
a->xSplit = b->xSplit = !xSplit;
|
|
|
|
if(xSplit) {
|
|
//Estimate a median value to split around
|
|
double values[10];
|
|
if(items.size() <= 10) {
|
|
for(unsigned i = 0, cnt = (unsigned)items.size(); i < cnt; ++i) {
|
|
auto* item = items[i];
|
|
values[i] = (item->bound.minimum.x + item->bound.maximum.x) * 0.5;
|
|
}
|
|
std::sort(values, values + items.size());
|
|
dimSplit = values[items.size() / 2];
|
|
}
|
|
else {
|
|
unsigned step = (unsigned)items.size() / 10;
|
|
for(unsigned i = 0; i < 10; ++i) {
|
|
auto* item = items[i * step];
|
|
values[i] = (item->bound.minimum.x + item->bound.maximum.x) * 0.5;
|
|
}
|
|
std::sort(values, values + 10);
|
|
dimSplit = values[5];
|
|
}
|
|
|
|
for(int i = (int)items.size() - 1; i >= 0; --i) {
|
|
PhysicsItem* item = items[i];
|
|
int location = classifyContainer(item->bound);
|
|
if(location == -1) {
|
|
a->items.push_back(item);
|
|
items.erase(items.begin() + i);
|
|
childrenA++;
|
|
}
|
|
else if(location == 1) {
|
|
b->items.push_back(item);
|
|
items.erase(items.begin() + i);
|
|
childrenB++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
double values[10];
|
|
if(items.size() <= 10) {
|
|
for(unsigned i = 0, cnt = (unsigned)items.size(); i < cnt; ++i) {
|
|
auto* item = items[i];
|
|
values[i] = (item->bound.minimum.z + item->bound.maximum.z) * 0.5;
|
|
}
|
|
std::sort(values, values + items.size());
|
|
dimSplit = values[items.size() / 2];
|
|
}
|
|
else {
|
|
unsigned step = (unsigned)items.size() / 10;
|
|
for(unsigned i = 0; i < 10; ++i) {
|
|
auto* item = items[i * step];
|
|
values[i] = (item->bound.minimum.z + item->bound.maximum.z) * 0.5;
|
|
}
|
|
std::sort(values, values + 10);
|
|
dimSplit = values[5];
|
|
}
|
|
|
|
for(int i = (int)items.size() - 1; i >= 0; --i) {
|
|
PhysicsItem* item = items[i];
|
|
int location = classifyContainer(item->bound);
|
|
if(location == -1) {
|
|
a->items.push_back(item);
|
|
items.erase(items.begin() + i);
|
|
childrenA++;
|
|
}
|
|
else if(location == 1) {
|
|
b->items.push_back(item);
|
|
items.erase(items.begin() + i);
|
|
childrenB++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(a->items.size() > SPLIT_COUNT)
|
|
a->split();
|
|
if(b->items.size() > SPLIT_COUNT)
|
|
b->split();
|
|
}
|
|
|
|
void PhysicsWorld::findInBoxInZone(const PhysicsGrid& zone, const AABBoxd& box,
|
|
const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) {
|
|
threads::ReadLock lock(zone.mutex);
|
|
//if(ownerMask == ~0u) {
|
|
//findInBoxInZone(zone, box, callback);
|
|
// zone.findInBox(box, callback, ~0);
|
|
// return;
|
|
//}
|
|
|
|
for(unsigned i = 0; i < 32; ++i)
|
|
if(ownerMask & (1 << i))
|
|
zone.groups[i].findInBox(box, callback, 0);
|
|
}
|
|
|
|
void PhysicsWorld::findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) {
|
|
//double start = devices.driver->getAccurateTime();
|
|
unsigned fromX = getLowerBound(box.minimum.x), toX = getUpperBound(box.maximum.x);
|
|
unsigned fromY = getLowerBound(box.minimum.z), toY = getUpperBound(box.maximum.z);
|
|
|
|
for(unsigned x = fromX; x <= toX; ++x) {
|
|
for(unsigned y = fromY; y <= toY; ++y) {
|
|
const PhysicsGrid& zone = grid[x * gridCount + y];
|
|
if(!zone.empty() && box.overlaps(zone.fuzzBound))
|
|
findInBoxInZone(zone, box, callback, ownerMask);
|
|
}
|
|
}
|
|
|
|
if(!outside.empty())
|
|
findInBoxInZone(outside, box, callback, ownerMask);
|
|
//double end = devices.driver->getAccurateTime();
|
|
//error("%d\n", (int)((end - start) * 1e6));
|
|
}
|
|
|
|
bool PhysicsGrid::empty() const {
|
|
return itemCount == 0;
|
|
}
|
|
|
|
void PhysicsGrid::removeItem(PhysicsItem* item) {
|
|
mutex.writeLock();
|
|
itemCount -= 1;
|
|
PhysBisect* bisect = &groups[item->maskID];
|
|
while(bisect->a) {
|
|
int location = bisect->classifyContainer(item->bound);
|
|
if(location == -1) {
|
|
bisect->childrenA--;
|
|
bisect = bisect->a;
|
|
}
|
|
else if(location == 1) {
|
|
bisect->childrenB--;
|
|
bisect = bisect->b;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
bool itemRemoved = false;
|
|
|
|
for(auto i = bisect->items.begin(), end = bisect->items.end(); i != end; ++i) {
|
|
if((*i) == item) {
|
|
itemRemoved = true;
|
|
bisect->items.erase(i);
|
|
|
|
while(bisect->items.empty() && bisect->parent && bisect->a == 0) {
|
|
auto parent = bisect->parent;
|
|
parent->fuse();
|
|
if(parent->items.size() > SPLIT_COUNT)
|
|
parent->split();
|
|
bisect = parent;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex.release();
|
|
|
|
if(!itemRemoved)
|
|
throw "Unable to locate physics item";
|
|
}
|
|
|
|
void PhysicsGrid::addItem(PhysicsItem* item) {
|
|
mutex.writeLock();
|
|
itemCount += 1;
|
|
PhysBisect* bisect = &groups[item->maskID];
|
|
while(bisect->a) {
|
|
int location = bisect->classifyContainer(item->bound);
|
|
if(location == -1) {
|
|
bisect->childrenA++;
|
|
bisect = bisect->a;
|
|
}
|
|
else if(location == 1) {
|
|
bisect->childrenB++;
|
|
bisect = bisect->b;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
bisect->items.push_back(item);
|
|
if(bisect->a) {
|
|
PhysBisect* refuse = 0;
|
|
while(bisect->childrenA > REBALANCE_RATIO * bisect->childrenB || bisect->childrenB > REBALANCE_RATIO * bisect->childrenA) {
|
|
refuse = bisect;
|
|
bisect = bisect->parent;
|
|
if(bisect == 0)
|
|
break;
|
|
}
|
|
|
|
if(refuse) {
|
|
refuse->fuse();
|
|
if(refuse->items.size() > SPLIT_COUNT)
|
|
refuse->split();
|
|
}
|
|
}
|
|
else if(bisect->items.size() > SPLIT_COUNT) {
|
|
bisect->split();
|
|
}
|
|
mutex.release();
|
|
}
|
|
|
|
void PhysicsGrid::updateItem(PhysicsItem* item, const AABBoxd& newBound) {
|
|
unsigned maskID = 1;
|
|
if(item->type == PIT_Object)
|
|
maskID = (item->object->owner ? item->object->owner->id : 1);
|
|
if(maskID >= 32)
|
|
maskID = 0;
|
|
|
|
if(maskID == item->maskID) {
|
|
PhysBisect* bisect = &groups[item->maskID];
|
|
|
|
bool sameLocation = true;
|
|
|
|
mutex.readLock();
|
|
while(bisect->a) {
|
|
int location = bisect->classifyContainer(item->bound);
|
|
int newLoc = bisect->classifyContainer(newBound);
|
|
if(location != newLoc) {
|
|
sameLocation = false;
|
|
break;
|
|
}
|
|
|
|
if(location == -1)
|
|
bisect = bisect->a;
|
|
else if(location == 1)
|
|
bisect = bisect->b;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if(sameLocation) {
|
|
item->bound = newBound;
|
|
mutex.release();
|
|
return;
|
|
}
|
|
|
|
mutex.release();
|
|
}
|
|
|
|
//Container has changed
|
|
mutex.writeLock();
|
|
|
|
removeItem(item);
|
|
item->bound = newBound;
|
|
item->maskID = maskID;
|
|
addItem(item);
|
|
|
|
mutex.release();
|
|
}
|
|
|
|
//TODO: Handle things too big for the grid, or outside the grid
|
|
void PhysicsWorld::updateItem(PhysicsItem& item, const AABBoxd& newBox) {
|
|
if(newBox.isWithin(item.gridLocation->fuzzBound)) {
|
|
//TODO: Handle transition back from 'outside' if it is now able to be positioned in the grid
|
|
item.gridLocation->updateItem(&item, newBox);
|
|
}
|
|
else {
|
|
item.gridLocation->removeItem(&item);
|
|
|
|
vec3d center = newBox.getCenter();
|
|
unsigned x = getBox(center.x), y = getBox(center.z);
|
|
item.bound = newBox;
|
|
|
|
PhysicsGrid* newGrid = &grid[x*gridCount + y];
|
|
if(!newBox.isWithin(newGrid->fuzzBound))
|
|
newGrid = &outside;
|
|
item.gridLocation = newGrid;
|
|
newGrid->addItem(&item);
|
|
}
|
|
}
|
|
|
|
void PhysicsWorld::registerItem(PhysicsItem& item) {
|
|
assert((item.type & PIT_Object) == 0 || item.object);
|
|
|
|
vec3d center = item.bound.getCenter();
|
|
unsigned x = getBox(center.x), y = getBox(center.z);
|
|
|
|
PhysicsGrid* newGrid = &grid[x*gridCount + y];
|
|
if(!item.bound.isWithin(newGrid->fuzzBound))
|
|
newGrid = &outside;
|
|
|
|
item.gridLocation = newGrid;
|
|
newGrid->addItem(&item);
|
|
}
|
|
|
|
PhysicsItem* PhysicsWorld::registerItem(const AABBoxd& bounds, Object* obj) {
|
|
PhysicsItem* item = new PhysicsItem;
|
|
item->bound = bounds;
|
|
item->object = obj;
|
|
item->type = PIT_Object;
|
|
|
|
unsigned maskID = (obj->owner ? obj->owner->id : 1);
|
|
if(maskID >= 32)
|
|
maskID = 0;
|
|
item->maskID = maskID;
|
|
|
|
registerItem(*item);
|
|
|
|
return item;
|
|
}
|
|
|
|
PhysicsItem* PhysicsWorld::registerItem(const AABBoxd& bounds, scene::Node* node) {
|
|
PhysicsItem* item = new PhysicsItem;
|
|
item->bound = bounds;
|
|
item->node = node;
|
|
item->type = PIT_Node;
|
|
item->maskID = 0;
|
|
|
|
registerItem(*item);
|
|
|
|
return item;
|
|
}
|
|
|
|
void PhysicsWorld::unregisterItem(PhysicsItem& item) {
|
|
item.gridLocation->removeItem(&item);
|
|
}
|
|
|
|
PhysicsWorld* PhysicsWorld::fromMessage(net::Message& msg) {
|
|
double gridSize, gridFuzz;
|
|
unsigned gridCount;
|
|
|
|
msg >> gridSize;
|
|
msg >> gridFuzz;
|
|
msg >> gridCount;
|
|
|
|
return new PhysicsWorld(gridSize, gridFuzz, gridCount);
|
|
}
|
|
|
|
PhysicsWorld* PhysicsWorld::fromSave(SaveFile& file) {
|
|
double gridSize, gridFuzz;
|
|
unsigned gridCount;
|
|
|
|
file >> gridSize;
|
|
file >> gridFuzz;
|
|
file >> gridCount;
|
|
|
|
return new PhysicsWorld(gridSize, gridFuzz, gridCount);
|
|
}
|
|
|
|
void PhysicsWorld::writeSetup(net::Message& msg) {
|
|
msg << gridSize;
|
|
msg << gridFuzz;
|
|
msg << gridCount;
|
|
}
|
|
|
|
void PhysicsWorld::writeSetup(SaveFile& file) {
|
|
file << gridSize;
|
|
file << gridFuzz;
|
|
file << gridCount;
|
|
}
|