Files
BlitzNext/Runtime/blitz3d/terrainrep.cpp
T
Michael Fabian 'Xaymar' Dirks 2196cb8419 runtime: Formatting
2019-01-18 17:04:17 +01:00

654 lines
15 KiB
C++

#include "terrainrep.hpp"
#include <queue>
#include "std.hpp"
extern gxRuntime* gx_runtime;
extern gxGraphics* gx_graphics;
extern float stats3d[10];
static Vector eye_vec;
static Plane eye_plane;
static const Vector up_normal(0, 1, 0);
static TerrainRep::Tri* tri_pool;
static const TerrainRep* curr;
static Frustum frustum;
static int out_cnt, proc_cnt, clip_cnt;
static float proj_epsilon = FLT_EPSILON; //.01f;
struct TerrainRep::Cell {
unsigned char height;
};
struct TerrainRep::Error {
unsigned char error, bound;
};
int TerrainRep::getSize() const
{
return cell_size;
}
float TerrainRep::getHeight(int x, int z) const
{
return cells[((z & cell_mask) << cell_shift) | (x & cell_mask)].height / 255.0f;
}
struct TerrainRep::Vert {
short x, z;
Vector v;
float src_y;
Vert() {}
Vert(int x, int z) : x(x), z(z), v((float)x, curr->getHeight(x, z), (float)z)
{
src_y = v.y;
}
Vert(int x, int z, float sy) : x(x), z(z), v((float)x, curr->getHeight(x, z), (float)z), src_y(sy) {}
};
static int vert_cnt, max_verts;
static TerrainRep::Vert *verts, *next_vert;
struct TerrainRep::Tri {
int id;
short clip, v0, v1, v2;
Tri * e0, *e1, *e2;
float proj_err;
Tri() {}
Tri(int id, int clip, int v0, int v1, int v2, Tri* e0 = 0, Tri* e1 = 0, Tri* e2 = 0)
: id(id), clip(clip), v0(v0), v1(v1), v2(v2), e0(e0), e1(e1), e2(e2), proj_err(0)
{}
void* operator new(size_t sz)
{
static const int GROW = 64;
if (!tri_pool) {
tri_pool = new Tri[GROW];
for (int k = 0; k < GROW - 1; ++k)
tri_pool[k].e0 = &tri_pool[k + 1];
tri_pool[GROW - 1].e0 = 0;
}
Tri* t = tri_pool;
tri_pool = t->e0;
return t;
}
void operator delete(void* q)
{
Tri* t = (Tri*)q;
t->e0 = tri_pool;
tri_pool = t;
}
void unlink()
{
if (e0) {
if (e0->e0 == this)
e0->e0 = 0;
else if (e0->e1 == this)
e0->e1 = 0;
else
e0->e2 = 0;
}
if (e1) {
if (e1->e0 == this)
e1->e0 = 0;
else if (e1->e1 == this)
e1->e1 = 0;
else
e1->e2 = 0;
}
if (e2) {
if (e2->e0 == this)
e2->e0 = 0;
else if (e2->e1 == this)
e2->e1 = 0;
else
e2->e2 = 0;
}
}
};
struct TriComp {
bool operator()(TerrainRep::Tri* a, TerrainRep::Tri* b) const
{
return a->proj_err < b->proj_err;
}
};
struct TriQue : public priority_queue<TerrainRep::Tri*, vector<TerrainRep::Tri*>, TriComp> {
vector<TerrainRep::Tri*>& getVector()
{
return c;
}
const vector<TerrainRep::Tri*>& getVector() const
{
return c;
}
};
static TriQue tri_que;
static vector<TerrainRep::Tri*> tris;
static bool clip(const Line& l, const Box& box)
{
static const Vector normals[] = {Vector(1, 0, 0), Vector(0, 0, 1), Vector(0, -1, 0),
Vector(-1, 0, 0), Vector(0, 0, -1), Vector(0, 1, 0)};
Vector v0 = l.o, v1 = l.o + l.d;
for (int k = 0; k < 6; ++k) {
Vector t = box.corner(k);
const Vector& n = normals[k];
float d0 = n.dot(v0 - t), d1 = n.dot(v1 - t);
if (d0 < 0) {
if (d1 < 0)
return false;
v0 += (v1 - v0) * (d0 / (d0 - d1));
} else if (d1 < 0) {
v1 += (v0 - v1) * (d1 / (d1 - d0));
}
}
return true;
}
TerrainRep::TerrainRep(int n)
: cell_shift(n), cell_size(1 << n), cell_mask((1 << n) - 1), end_tri_id((1 << n) * (1 << n) * 2), shading(false),
mesh(0), detail(0), morph(true)
{
cells = new Cell[cell_size * cell_size];
errors = new Error[end_tri_id];
setDetail(2000, false);
clear();
}
TerrainRep::~TerrainRep()
{
if (mesh)
gx_graphics->freeMesh(mesh);
delete[] errors;
delete[] cells;
}
void TerrainRep::clear()
{
memset(cells, 0, cell_size * cell_size * sizeof(Cell));
memset(errors, 0, end_tri_id * sizeof(Error));
errs_valid = true;
}
void TerrainRep::setDetail(int n, bool m)
{
morph = m;
if (n == detail)
return;
detail = n;
n += 32;
if (n > max_verts) {
delete[] verts;
max_verts = n;
verts = new Vert[max_verts];
}
if (mesh)
gx_graphics->freeMesh(mesh);
mesh_verts = mesh_tris = n;
mesh = gx_graphics->createMesh(mesh_verts, mesh_tris, 0);
}
void TerrainRep::setShading(bool t)
{
shading = t;
}
void TerrainRep::setHeight(int x, int z, float h, bool realtime)
{
cells[((z & cell_mask) << cell_shift) | (x & cell_mask)].height = h * 255.0f;
if (!errs_valid)
return;
if (realtime) {
Vert v0(0, 0), v1(cell_size, 0), v2(cell_size, cell_size), v3(0, cell_size);
calcErr(2, x, z, v1, v2, v0);
calcErr(3, x, z, v3, v0, v2);
return;
}
errs_valid = false;
}
Vector TerrainRep::getNormal(int x, int z) const
{
Vector vt(x, getHeight(x, z), z), v0(x, getHeight(x, z - 1), z - 1), v1(x + 1, getHeight(x + 1, z), z),
v2(x, getHeight(x, z + 1), z + 1), v3(x - 1, getHeight(x - 1, z), z);
return (Plane(vt, v1, v0).n + Plane(vt, v2, v1).n + Plane(vt, v3, v2).n + Plane(vt, v0, v3).n).normalized();
}
void TerrainRep::insert(Tri* t)
{
++proc_cnt;
//quicker clip check for 'thin' triangles...
if (t->id >= end_tri_id || !errors[t->id].error) {
if (t->clip & 63) {
++clip_cnt;
Vector e0(verts[t->v0].v), e1(verts[t->v1].v), e2(verts[t->v2].v);
for (int n = 0; n < 6; ++n) {
if (!(t->clip & (1 << n)))
continue;
const Plane& p = frustum.getPlane(n);
if (p.distance(e0) < 0 && p.distance(e1) < 0 && p.distance(e2) < 0) {
t->unlink();
delete t;
++out_cnt;
return;
}
}
}
t->clip |= 128;
tris.push_back(t);
++out_cnt;
return;
}
//clip?
if (t->id < end_tri_id / 2 && (t->clip & 63)) {
++clip_cnt;
Vector e0(verts[t->v0].v), e1(verts[t->v1].v), e2(verts[t->v2].v);
Vector e3(e0), e4(e1), e5(e2);
e0.y = e1.y = e2.y = 0;
e3.y = e4.y = e5.y = errors[t->id].bound / 255.0f;
for (int n = 0; n < 6; ++n) {
int mask = 1 << n;
if (!(t->clip & mask))
continue;
const Plane& p = frustum.getPlane(n);
int q = (p.distance(e0) >= 0) + (p.distance(e1) >= 0) + (p.distance(e2) >= 0) + (p.distance(e3) >= 0)
+ (p.distance(e4) >= 0) + (p.distance(e5) >= 0);
if (!q) {
t->unlink();
delete t;
++out_cnt;
return;
}
if (q == 6)
t->clip &= ~mask;
}
}
if (t->clip & 128) {
t->clip |= 128;
tris.push_back(t);
} else {
Vector v = Vector(verts[t->v1].v + verts[t->v2].v) / 2;
// float d=eye_plane.distance( v );
float d = eye_vec.distance(v);
if (d < FLT_EPSILON)
d = FLT_EPSILON;
t->proj_err = errors[t->id].error / d;
if (t->proj_err > proj_epsilon) {
tri_que.push(t);
} else {
t->clip |= 128;
tris.push_back(t);
}
}
++out_cnt;
}
void TerrainRep::split(Tri* t)
{
if (t->e2 && t->e2->e2 != t)
split(t->e2);
int tv = vert_cnt++;
if (tv >= max_verts) {
max_verts += max_verts / 2 + 32;
Vert* t = verts;
verts = new Vert[max_verts];
memcpy(verts, t, sizeof(Vert) * tv);
next_vert = verts + tv;
}
Vert* vert = next_vert++;
vert->v.x = vert->x = (verts[t->v1].x + verts[t->v2].x) / 2;
vert->v.z = vert->z = (verts[t->v1].z + verts[t->v2].z) / 2;
vert->src_y = (verts[t->v1].v.y + verts[t->v2].v.y) / 2;
vert->v.y = getHeight(vert->x, vert->z);
Tri* tl = new Tri(t->id * 2, t->clip, tv, t->v2, t->v0, 0, 0, t->e0);
if (Tri* p = tl->e2) {
if (p->e0 == t)
p->e0 = tl;
else if (p->e1 == t)
p->e1 = tl;
else
p->e2 = tl;
}
Tri* tr = new Tri(t->id * 2 + 1, t->clip, tv, t->v0, t->v1, 0, tl, t->e1);
tl->e0 = tr;
if (Tri* p = tr->e2) {
if (p->e0 == t)
p->e0 = tr;
else if (p->e1 == t)
p->e1 = tr;
else
p->e2 = tr;
}
if (Tri* b = t->e2) {
Tri* br = new Tri(b->id * 2, b->clip, tv, b->v2, b->v0, 0, tr, b->e0);
tr->e0 = br;
if (Tri* p = br->e2) {
if (p->e0 == b)
p->e0 = br;
else if (p->e1 == b)
p->e1 = br;
else
p->e2 = br;
}
Tri* bl = new Tri(b->id * 2 + 1, b->clip, tv, b->v0, b->v1, tl, br, b->e1);
tl->e1 = br->e0 = bl;
if (Tri* p = bl->e2) {
if (p->e0 == b)
p->e0 = bl;
else if (p->e1 == b)
p->e1 = bl;
else
p->e2 = bl;
}
b->id = 0;
--out_cnt;
insert(br);
insert(bl);
}
t->id = 0;
--out_cnt;
insert(tl);
insert(tr);
}
TerrainRep::Error TerrainRep::calcErr(int id, const Vert& v0, const Vert& v1, const Vert& v2) const
{
Error et;
float y = v0.v.y;
if (v1.v.y > y)
y = v1.v.y;
if (v2.v.y > y)
y = v2.v.y;
et.error = 0;
et.bound = y >= 1 ? 255 : ceil(y * 255.0f);
if (id >= end_tri_id)
return et;
Vert tv((v1.x + v2.x) / 2, (v1.z + v2.z) / 2);
float e = fabs(tv.v.y - (v1.v.y + v2.v.y) / 2);
et.error = e >= 1 ? 255 : ceil((e - FLT_EPSILON) * 255.0f);
Error el = calcErr(id * 2, tv, v2, v0);
Error er = calcErr(id * 2 + 1, tv, v0, v1);
if (el.error > et.error)
et.error = el.error;
if (er.error > et.error)
et.error = er.error;
if (el.bound > et.bound)
et.bound = el.bound;
if (er.bound > et.bound)
et.bound = er.bound;
return errors[id] = et;
}
TerrainRep::Error TerrainRep::calcErr(int id, int x, int z, const Vert& v0, const Vert& v1, const Vert& v2) const
{
Error et;
float y = v0.v.y;
if (v1.v.y > y)
y = v1.v.y;
if (v2.v.y > y)
y = v2.v.y;
et.error = 0;
et.bound = y >= 1 ? 255 : ceil(y * 255.0f);
if (id >= end_tri_id)
return et;
//is x/z inside this triangle?
int dx, dz;
dx = -(v1.z - v0.z);
dz = (v1.x - v0.x);
if ((x - v0.x) * dx + (z - v0.z) * dz < 0)
return errors[id];
dx = -(v2.z - v1.z);
dz = (v2.x - v1.x);
if ((x - v1.x) * dx + (z - v1.z) * dz < 0)
return errors[id];
dx = -(v0.z - v2.z);
dz = (v0.x - v2.x);
if ((x - v2.x) * dx + (z - v2.z) * dz < 0)
return errors[id];
Vert tv((v1.x + v2.x) / 2, (v1.z + v2.z) / 2);
float e = fabs(tv.v.y - (v1.v.y + v2.v.y) / 2);
et.error = e >= 1 ? 255 : ceil((e - FLT_EPSILON) * 255.0f);
Error el = calcErr(id * 2, x, z, tv, v2, v0);
Error er = calcErr(id * 2 + 1, x, z, tv, v0, v1);
if (el.error > et.error)
et.error = el.error;
if (er.error > et.error)
et.error = er.error;
if (el.bound > et.bound)
et.bound = el.bound;
if (er.bound > et.bound)
et.bound = er.bound;
return errors[id] = et;
}
void TerrainRep::validateErrs() const
{
if (errs_valid)
return;
Vert v0(0, 0), v1(cell_size, 0), v2(cell_size, cell_size), v3(0, cell_size);
calcErr(2, v1, v2, v0);
calcErr(3, v3, v0, v2);
errs_valid = true;
}
void TerrainRep::render(Model* model, const RenderContext& rc)
{
curr = this;
validateErrs();
new (&frustum) Frustum(rc.getWorldFrustum(), -model->getRenderTform());
eye_plane = frustum.getPlane(Frustum::PLANE_NEAR);
eye_vec = frustum.getVertex(Frustum::VERT_EYE);
vert_cnt = 4;
next_vert = verts;
out_cnt = proc_cnt = clip_cnt = 0;
tri_que.getVector().clear();
tris.clear();
new (next_vert++) Vert(0, 0);
new (next_vert++) Vert(cell_size, 0);
new (next_vert++) Vert(cell_size, cell_size);
new (next_vert++) Vert(0, cell_size);
Tri* t0 = new Tri(2, 0x3f, 1, 2, 0);
Tri* t1 = new Tri(3, 0x3f, 3, 0, 2);
t0->e2 = t1;
t1->e2 = t0;
insert(t0);
insert(t1);
while (tri_que.size() && out_cnt < detail) {
Tri* t = tri_que.top();
tri_que.pop();
if (t->id)
split(t);
delete t;
}
int k;
const vector<Tri*>& q_tris = tri_que.getVector();
if (!mesh)
out_cnt = 0;
if (!out_cnt) {
for (k = 0; k < tris.size(); ++k)
delete tris[k];
for (k = 0; k < q_tris.size(); ++k)
delete q_tris[k];
return;
}
int err_cnt = 0;
for (k = 0; k < q_tris.size(); ++k) {
Tri* t = q_tris[k];
if (t->id) {
tris.push_back(t);
++err_cnt;
} else
delete t;
}
if (morph) {
if (int morph_cnt = err_cnt / 4) {
if (morph_cnt > vert_cnt)
morph_cnt = vert_cnt;
float t = 0, morph_step = 1.0f / morph_cnt;
for (int vn = vert_cnt - morph_cnt; vn < vert_cnt; ++vn) {
verts[vn].v.y += (verts[vn].src_y - verts[vn].v.y) * t;
t += morph_step;
}
}
}
int tri_cnt = tris.size();
if (vert_cnt > mesh_verts || tri_cnt > mesh_tris) {
int vc = vert_cnt + 32;
if (vc > mesh_verts)
mesh_verts = vc;
int tc = tri_cnt + 32;
if (tc > mesh_tris)
mesh_tris = tc;
if (mesh)
gx_graphics->freeMesh(mesh);
mesh = gx_graphics->createMesh(mesh_verts, mesh_tris, 0);
}
mesh->lock(true);
int tc = 0, vc = 0;
if (!shading) {
for (k = 0; k < vert_cnt; ++k) {
const Vert& t = verts[k];
const Vector& v = t.v;
float tex_coords[2][2] = {{v.x, cell_size - v.z}, {v.x, cell_size - v.z}};
mesh->setVertex(vc++, &v.x, &up_normal.x, tex_coords);
}
} else {
for (k = 0; k < vert_cnt; ++k) {
const Vert& t = verts[k];
const Vector& v = t.v;
float tex_coords[2][2] = {{v.x, cell_size - v.z}, {v.x, cell_size - v.z}};
Vector normal = getNormal(v.x, v.z);
mesh->setVertex(vc++, &v.x, &normal.x, tex_coords);
}
}
for (k = 0; k < tri_cnt; ++k) {
Tri* t = tris[k];
if (t->id)
mesh->setTriangle(tc++, t->v0, t->v2, t->v1);
delete t;
}
mesh->unlock();
static int mvc, mtc;
if (vc > mvc)
mvc = vc;
if (tc > mtc)
mtc = tc;
stats3d[1] = mvc;
stats3d[2] = mtc;
model->enqueue(mesh, 0, vc, 0, tc);
}
bool TerrainRep::collide(const Line& line, Collision* curr_coll, const Transform& tform, int id, const Vert& v0,
const Vert& v1, const Vert& v2, const Line& l) const
{
Box b(v0.v);
b.update(v1.v);
b.update(v2.v);
if (id >= end_tri_id || !errors[id].error) {
return ::clip(l, b) ? curr_coll->triangleCollide(line, 0, tform * v0.v, tform * v2.v, tform * v1.v) : false;
}
b.a.y = 0;
b.b.y = errors[id].bound / 255.0f;
if (!::clip(l, b))
return false;
Vert tv((v1.x + v2.x) / 2, (v1.z + v2.z) / 2);
return collide(line, curr_coll, tform, id * 2, tv, v2, v0, l)
| collide(line, curr_coll, tform, id * 2 + 1, tv, v0, v1, l);
}
bool TerrainRep::collide(const Line& line, float radius, Collision* curr_coll, const Transform& tform, int id,
const Vert& v0, const Vert& v1, const Vert& v2, const Box& box) const
{
Box b(v0.v);
b.update(v1.v);
b.update(v2.v);
if (id >= end_tri_id || !errors[id].error) {
if (v0.v == v1.v || v0.v == v2.v || v1.v == v2.v) {
gx_runtime->debugLog("OUCH!");
}
return b.overlaps(box) ? curr_coll->triangleCollide(line, radius, tform * v0.v, tform * v2.v, tform * v1.v)
: false;
}
b.a.y = 0;
b.b.y = errors[id].bound / 255.0f;
if (!b.overlaps(box))
return false;
Vert tv((v1.x + v2.x) / 2, (v1.z + v2.z) / 2);
return collide(line, radius, curr_coll, tform, id * 2, tv, v2, v0, box)
| collide(line, radius, curr_coll, tform, id * 2 + 1, tv, v0, v1, box);
}
bool TerrainRep::collide(const Line& line, float radius, Collision* curr_coll, const Transform& tform) const
{
curr = this;
validateErrs();
Vert v0(0, 0), v1(cell_size, 0), v2(cell_size, cell_size), v3(0, cell_size);
if (!radius) {
Line l = -tform * line;
return collide(line, curr_coll, tform, 2, v1, v2, v0, l) | collide(line, curr_coll, tform, 3, v3, v0, v2, l);
}
//create local box
Box b(line);
b.expand(radius);
Box box = -tform * b;
return collide(line, radius, curr_coll, tform, 2, v1, v2, v0, box)
| collide(line, radius, curr_coll, tform, 3, v3, v0, v2, box);
}