Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+504
View File
@@ -0,0 +1,504 @@
#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;
}
+149
View File
@@ -0,0 +1,149 @@
#pragma once
#include "aabbox.h"
#include "util/refcount.h"
#include <vector>
#include <functional>
enum PhysItemType {
PIT_Object = 0x01,
PIT_Node = 0x02,
//A group is both the type of PIT_Group, and the type of objects in the group
PIT_Group = 0x80,
};
class Object;
struct PhysicsGrid;
struct PhysicsItem;
struct PhysicsGroup;
class SaveFile;
namespace scene {
class Node;
};
namespace net {
struct Message;
};
struct PhysicsItem {
AABBoxd bound;
union {
Object* object;
scene::Node* node;
PhysicsGroup* group;
};
unsigned maskID;
PhysItemType type;
PhysicsGrid* gridLocation;
};
struct PhysicsGroup {
unsigned itemCount;
PhysicsItem* items;
};
struct PhysBisect {
std::vector<PhysicsItem*> items;
PhysBisect *a, *b, *parent;
unsigned childrenA, childrenB;
bool xSplit;
double dimSplit, fuzz;
unsigned depth;
PhysBisect() : a(0), b(0), parent(0), depth(0), childrenA(0), childrenB(0), xSplit(true) {}
inline int classify(const AABBoxd& box) const {
if(xSplit) {
if(box.maximum.x < dimSplit - fuzz)
return -1;
if(box.minimum.x > dimSplit + fuzz)
return 1;
return 0;
}
else {
if(box.maximum.z < dimSplit - fuzz)
return -1;
if(box.minimum.z > dimSplit + fuzz)
return 1;
return 0;
}
}
int classifyContainer(const AABBoxd& box) const {
if(xSplit) {
double center = (box.minimum.x + box.maximum.x) * 0.5;
if(center >= dimSplit && box.minimum.x > dimSplit - fuzz)
return 1;
else if(center < dimSplit && box.maximum.x < dimSplit + fuzz)
return -1;
return 0;
}
else {
double center = (box.minimum.z + box.maximum.z) * 0.5;
if(center >= dimSplit && box.minimum.z > dimSplit - fuzz)
return 1;
else if(center < dimSplit && box.maximum.z < dimSplit + fuzz)
return -1;
return 0;
}
}
void findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) const;
void split();
void fuse();
void fuse(std::vector<PhysicsItem*>& into);
};
struct PhysicsGrid {
PhysBisect groups[32];
mutable threads::ReadWriteMutex mutex;
AABBoxd bound, fuzzBound;
unsigned itemCount;
void removeItem(PhysicsItem* item);
void addItem(PhysicsItem* item);
void updateItem(PhysicsItem* item, const AABBoxd& newBound);
bool empty() const;
};
class PhysicsWorld : public AtomicRefCounted {
//Physical space that items exist withing
PhysicsGrid* grid;
//Catch-all for items that do not exist within the grid, or can't fit inside the grid
PhysicsGrid outside;
//Access mutex
threads::ReadWriteMutex mutex;
//Size of a grid square
double gridSize, halfGridSize;
//Grid fuzz margin
double gridFuzz;
//Number of grid squares on a side
unsigned gridCount;
unsigned getBox(double x) const;
unsigned getLowerBound(double x) const;
unsigned getUpperBound(double x) const;
void findInBoxInZone(const PhysicsGrid& zone, const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask);
public:
PhysicsWorld(double GridSize = 3000.0, double GridFuzz = 350.0, unsigned GridCount = 60);
~PhysicsWorld();
//Calls <callback> for each of the PhysicsItems within the box
//Optionally filters by owner of the item (Mask 1 permits unowned objects)
void findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask = 0xffffffff);
void updateItem(PhysicsItem& item, const AABBoxd& newBox);
PhysicsItem* registerItem(const AABBoxd& bounds, Object* obj);
PhysicsItem* registerItem(const AABBoxd& bounds, scene::Node* node);
void registerItem(PhysicsItem& item);
void unregisterItem(PhysicsItem& item);
void writeSetup(net::Message& msg);
void writeSetup(SaveFile& file);
static PhysicsWorld* fromMessage(net::Message& msg);
static PhysicsWorld* fromSave(SaveFile& file);
};