Files
2018-07-17 14:15:37 +02:00

297 lines
8.0 KiB
C++

#include "util/elevation_map.h"
#include "main/references.h"
#include "compat/misc.h"
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include "BiPatch/bilinear.h"
#include "main/logging.h"
ElevationMap::ElevationMap()
: generated(false), grid(0) {
}
ElevationMap::~ElevationMap() {
if(generated)
free(grid);
}
void ElevationMap::clear() {
points.clear();
if(generated) {
free(grid);
grid = 0;
generated = false;
}
}
void ElevationMap::addPoint(const vec3d& point, double radius) {
Point p = {point, radius};
points.push_back(p);
}
void ElevationMap::generate(const vec2d& interval, double power) {
//Clear previous grid
if(generated) {
free(grid);
grid = 0;
}
//A grid with no points is pretty pointless (haha)
if(points.empty()) {
generated = true;
grid = 0;
gridStart = vec3d();
gridSize = vec2d();
gridInterval = vec2d();
return;
}
double start = devices.driver->getAccurateTime();
//Get the extents of the grid
vec2d topLeft(points[0].center.x, points[0].center.z);
vec2d botRight = topLeft;
double avgHeight = 0.0;
foreach(p, points) {
if(p->center.x - p->radius < topLeft.x)
topLeft.x = p->center.x - p->radius;
if(p->center.x + p->radius > botRight.x)
botRight.x = p->center.x + p->radius;
if(p->center.z - p->radius < topLeft.y)
topLeft.y = p->center.z - p->radius;
if(p->center.z + p->radius > botRight.y)
botRight.y = p->center.z + p->radius;
avgHeight += p->center.y;
}
avgHeight /= points.size();
gridSize = botRight - topLeft;
gridStart = vec3d(topLeft.x, avgHeight, topLeft.y);
gridInterval = interval;
gridResolution.x = (int)ceil(gridSize.x / gridInterval.x);
gridResolution.y = (int)ceil(gridSize.y / gridInterval.y);
minHeight = HUGE_VAL;
maxHeight = -HUGE_VAL;
//Create the big grid for optimization
std::vector<std::vector<Point>> bigGrid;
unsigned bigGridSize = std::max((unsigned)sqrt((double)points.size()), 1u);
bigGrid.resize(bigGridSize * bigGridSize);
double bigInterval = gridSize.x / bigGridSize;
foreach(p, points) {
unsigned bigX = std::min((unsigned)((p->center.x - topLeft.x) / bigInterval), bigGridSize - 1);
unsigned bigY = std::min((unsigned)((p->center.z - topLeft.y) / bigInterval), bigGridSize - 1);
bigGrid[bigX + (bigY * bigGridSize)].push_back(*p);
}
//Interpolate within the grid
grid = (float*)calloc(gridResolution.x * gridResolution.y, sizeof(float));
generated = true;
vec2d realPos;
double height;
double totalWeight = 0.0;
auto calcBucket = [&](unsigned bigX, unsigned bigY) {
if(bigX >= bigGridSize || bigY >= bigGridSize)
return;
auto& bucket = bigGrid[bigX + (bigY * bigGridSize)];
foreach(it, bucket) {
//Check if the point is inside a system
vec2d flatPos(it->center.x, it->center.z);
double dist = realPos.distanceTo(flatPos);
if(dist < it->radius) {
height = it->center.y;
totalWeight = 1.0;
break;
}
//Do inverse distance weighting
double w = 1.0 / pow(dist - it->radius, power);
height += it->center.y * w;
totalWeight += w;
}
};
for(int iy = 0; iy < gridResolution.y; ++iy) {
for(int ix = 0; ix < gridResolution.x; ++ix) {
realPos = vec2d(gridStart.x + ix * gridInterval.x,
gridStart.z + iy * gridInterval.y);
height = 0.0;
totalWeight = 0.0;
unsigned bigX = std::min((unsigned)((realPos.x - gridStart.x) / bigInterval), bigGridSize - 1);
unsigned bigY = std::min((unsigned)((realPos.y - gridStart.z) / bigInterval), bigGridSize - 1);
calcBucket(bigX - 1, bigY - 1);
calcBucket(bigX, bigY - 1);
calcBucket(bigX + 1, bigY - 1);
calcBucket(bigX - 1, bigY);
calcBucket(bigX, bigY);
calcBucket(bigX + 1, bigY);
calcBucket(bigX - 1, bigY + 1);
calcBucket(bigX, bigY + 1);
calcBucket(bigX + 1, bigY + 1);
if(totalWeight == 0) {
height = avgHeight;
}
else {
height /= totalWeight;
if(height < minHeight)
minHeight = height;
if(height > maxHeight)
maxHeight = height;
}
grid[iy * gridResolution.x + ix] = (float)height;
}
}
if(maxHeight == minHeight) {
maxHeight += 1.0;
minHeight -= 1.0;
}
double time = devices.driver->getAccurateTime() - start;
info("Elevation grid took %.3gms to calculate.", time * 1000.0);
}
double ElevationMap::lookup(int x, int y) {
if(gridResolution.x == 0 || gridResolution.y == 0)
return 0.0;
x = std::max(std::min(x, gridResolution.x - 1), 0);
y = std::max(std::min(y, gridResolution.y - 1), 0);
return (double)grid[y * gridResolution.x + x];
}
double ElevationMap::get(vec2d point) {
return get(point.x, point.y);
}
double ElevationMap::get(double x, double y) {
//Move coordinates to grid coordinates
x = (x - gridStart.x) / gridInterval.x;
y = (y - gridStart.z) / gridInterval.y;
//Get the coordinates of the nearby points
int lx = (int)floor(x);
int ly = (int)floor(y);
int rx = lx + 1;
int ry = ly + 1;
//Interpolate the values
double value = 0.0;
value += lookup(lx, ly) * ((double)rx - x) * ((double)ry - y);
value += lookup(rx, ly) * (x - (double)lx) * ((double)ry - y);
value += lookup(lx, ry) * ((double)rx - x) * (y - (double)ly);
value += lookup(rx, ry) * (x - (double)lx) * (y - (double)ly);
return value;
}
bool ElevationMap::getClosestPoint(const line3dd& inLine, vec3d& closestPoint) {
if(!generated)
return false;
//Limit the line to the confines of the 3d grid
// Always make sure the end goes to the other plane, or a line that stops before the region will never collide
vec3d start = inLine.start, end = inLine.end;
if(inLine.start.y > inLine.end.y) {
if(start.y > maxHeight)
inLine.intersectY(start, maxHeight, false);
inLine.intersectY(end, minHeight, false);
}
else {
if(start.y < minHeight)
inLine.intersectY(start, minHeight, false);
inLine.intersectY(end, maxHeight, false);
}
line3dd line(start, end);
vec3d lineDir = line.end - line.start;
BiPatch::Vector rayOrigin(line.start.x, line.start.y, line.start.z);
BiPatch::Vector rayDir(lineDir.x, lineDir.y, lineDir.z);
rayDir.normalize();
BiPatch::Vector uv;
//Flatten the line to intelligently chose grid spaces to test
line3dd flatLine(start, end);
flatLine.start.y = 0;
flatLine.end.y = 0;
vec3d flatPoint = flatLine.start;
vec3d flatDir = flatLine.getDirection();
int x = (int)floor((flatLine.start.x - gridStart.x) / gridInterval.x);
int y = (int)floor((flatLine.start.z - gridStart.z) / gridInterval.y);
for(unsigned checks = 0; checks < 1000; ++checks) {
//See if we have a collision
double absx = gridStart.x + (gridInterval.x * double(x));
double absy = gridStart.z + (gridInterval.y * double(y));
BiPatch::Vector tl(absx, lookup(x, y), absy);
BiPatch::Vector tr(absx + gridInterval.x, lookup(x+1, y), absy);
BiPatch::Vector bl(absx, lookup(x, y+1), absy + gridInterval.y);
BiPatch::Vector br(absx + gridInterval.x, lookup(x+1, y+1), absy + gridInterval.y);
BiPatch::BilinearPatch bp(tl, tr, bl, br);
if(bp.RayPatchIntersection(rayOrigin, rayDir, uv)) {
BiPatch::Vector point = bp.SrfEval(uv.x(), uv.y());
vec3d intersect(point.x(), point.y(), point.z());
//Once we have a collision, it must be the closest point
double dot = (intersect - line.start).dot(line.getDirection());
if(dot >= -0.0001) {
closestPoint = intersect;
return true;
}
}
//Step to the next grid section
if(flatDir.z > 0) {
vec3d intersect;
flatLine.intersectZ(intersect, absy + gridInterval.y, false);
if(intersect.x < absx + gridInterval.x && intersect.x > absx) {
y += 1;
}
else if(flatDir.x > 0) {
x += 1;
flatLine.intersectX(intersect, absx + gridInterval.x, false);
}
else {
x -= 1;
flatLine.intersectX(intersect, absx, false);
}
flatPoint = intersect;
}
else {//if(flatDir.z <= 0) {
vec3d intersect;
flatLine.intersectZ(intersect, absy, false);
if(intersect.x < absx + gridInterval.x && intersect.x > absx) {
y -= 1;
}
else if(flatDir.x > 0) {
x += 1;
flatLine.intersectX(intersect, absx + gridInterval.x, false);
}
else {
x -= 1;
flatLine.intersectX(intersect, absx, false);
}
flatPoint = intersect;
}
}
return false;
}