1162 lines
28 KiB
C++
1162 lines
28 KiB
C++
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <etr_config.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include "quadtree.h"
|
|
#include "textures.h"
|
|
#include "course.h"
|
|
#include "ogl.h"
|
|
|
|
#include <climits>
|
|
|
|
#define TERRAIN_ERROR_SCALE 0.1
|
|
#define VERTEX_FORCE_THRESHOLD 100
|
|
#define ERROR_MAGNIFICATION_THRESHOLD 20
|
|
#define ERROR_MAGNIFICATION_AMOUNT 3
|
|
#define ENV_MAP_ALPHA 50
|
|
#define colorval(j,ch) \
|
|
VNCArray[j*STRIDE_GL_ARRAY+STRIDE_GL_ARRAY-4+(ch)]
|
|
|
|
#define setalphaval(i) colorval(VertexIndices[i], 3) = \
|
|
( terrain <= VertexTerrains[i] ) ? 255 : 0
|
|
|
|
#define update_min_max( idx ) \
|
|
if ( idx > VertexArrayMaxIdx ) { \
|
|
VertexArrayMaxIdx = idx; \
|
|
} \
|
|
if ( idx < VertexArrayMinIdx ) { \
|
|
VertexArrayMinIdx = idx; \
|
|
}
|
|
|
|
static void make_tri_list(void(*tri_func)(int, int, int, int), unsigned char EnabledFlags, int flags, int terrain) {
|
|
if ((EnabledFlags & 1) == 0) {
|
|
tri_func(0, 2, 8, terrain);
|
|
} else {
|
|
if (flags & 8) tri_func(0, 1, 8, terrain);
|
|
if (flags & 1) tri_func(0, 2, 1, terrain);
|
|
}
|
|
if ((EnabledFlags & 2) == 0) {
|
|
tri_func(0, 4, 2, terrain);
|
|
} else {
|
|
if (flags & 1) tri_func(0, 3, 2, terrain);
|
|
if (flags & 2) tri_func(0, 4, 3, terrain);
|
|
}
|
|
if ((EnabledFlags & 4) == 0) {
|
|
tri_func(0, 6, 4, terrain);
|
|
} else {
|
|
if (flags & 2) tri_func(0, 5, 4, terrain);
|
|
if (flags & 4) tri_func(0, 6, 5, terrain);
|
|
}
|
|
if ((EnabledFlags & 8) == 0) {
|
|
tri_func(0, 8, 6, terrain);
|
|
} else {
|
|
if (flags & 4) tri_func(0, 7, 6, terrain);
|
|
if (flags & 8) tri_func(0, 8, 7, terrain);
|
|
}
|
|
}
|
|
|
|
GLuint *quadsquare::VertexArrayIndices = NULL;
|
|
GLuint quadsquare::VertexArrayCounter;
|
|
GLuint quadsquare::VertexArrayMinIdx;
|
|
GLuint quadsquare::VertexArrayMaxIdx;
|
|
|
|
quadsquare::quadsquare(quadcornerdata* pcd) {
|
|
pcd->Square = this;
|
|
Static = false;
|
|
ForceEastVert = false;
|
|
ForceSouthVert = false;
|
|
Dirty = true;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
Child[i] = (quadsquare*) NULL;
|
|
}
|
|
|
|
EnabledFlags = 0;
|
|
|
|
for (int i = 0; i < 2; i++) SubEnabledCount[i] = 0;
|
|
|
|
Vertex[0].Y = 0.25 * (pcd->Verts[0].Y
|
|
+ pcd->Verts[1].Y + pcd->Verts[2].Y + pcd->Verts[3].Y);
|
|
Vertex[1].Y = 0.5 * (pcd->Verts[3].Y + pcd->Verts[0].Y);
|
|
Vertex[2].Y = 0.5 * (pcd->Verts[0].Y + pcd->Verts[1].Y);
|
|
Vertex[3].Y = 0.5 * (pcd->Verts[1].Y + pcd->Verts[2].Y);
|
|
Vertex[4].Y = 0.5 * (pcd->Verts[2].Y + pcd->Verts[3].Y);
|
|
|
|
for (int i = 0; i < 2; i++) Error[i] = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
Error[i+2] = fabs((Vertex[0].Y + pcd->Verts[i].Y)
|
|
- (Vertex[i+1].Y + Vertex[((i+1)&3) + 1].Y)) * 0.25;
|
|
}
|
|
|
|
MinY = MaxY = pcd->Verts[0].Y;
|
|
for (int i = 1; i < 4; i++) {
|
|
float y = pcd->Verts[i].Y;
|
|
if (y < MinY) MinY = y;
|
|
if (y > MaxY) MaxY = y;
|
|
}
|
|
}
|
|
|
|
quadsquare::~quadsquare() {
|
|
for (int i = 0; i < 4; i++) {
|
|
if (Child[i]) delete Child[i];
|
|
}
|
|
}
|
|
|
|
void quadsquare::SetStatic(const quadcornerdata &cd) {
|
|
if (Static == false) {
|
|
Static = true;
|
|
if (cd.Parent && cd.Parent->Square) {
|
|
cd.Parent->Square->SetStatic(*cd.Parent);
|
|
}
|
|
}
|
|
}
|
|
|
|
int quadsquare::CountNodes() {
|
|
int count = 1;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (Child[i]) count += Child[i]->CountNodes();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
float quadsquare::GetHeight(const quadcornerdata &cd, float x, float z) {
|
|
int half = 1 << cd.Level;
|
|
|
|
float lx = (x - cd.xorg) / float(half);
|
|
float lz = (z - cd.zorg) / float(half);
|
|
|
|
int ix = (int) floor(lx);
|
|
int iz = (int) floor(lz);
|
|
|
|
if (ix < 0) ix = 0;
|
|
if (ix > 1) ix = 1;
|
|
if (iz < 0) iz = 0;
|
|
if (iz > 1) iz = 1;
|
|
|
|
int index = (ix ^ (iz ^ 1)) + (iz << 1);
|
|
if (Child[index] && Child[index]->Static) {
|
|
quadcornerdata q;
|
|
SetupCornerData(&q, cd, index);
|
|
return Child[index]->GetHeight(q, x, z);
|
|
}
|
|
|
|
lx -= ix;
|
|
if (lx < 0) lx = 0;
|
|
if (lx > 1) lx = 1;
|
|
|
|
lz -= iz;
|
|
if (lx < 0) lz = 0;
|
|
if (lz > 1) lz = 1;
|
|
|
|
float s00, s01, s10, s11;
|
|
switch (index) {
|
|
default:
|
|
case 0:
|
|
s00 = Vertex[2].Y;
|
|
s01 = cd.Verts[0].Y;
|
|
s10 = Vertex[0].Y;
|
|
s11 = Vertex[1].Y;
|
|
break;
|
|
case 1:
|
|
s00 = cd.Verts[1].Y;
|
|
s01 = Vertex[2].Y;
|
|
s10 = Vertex[3].Y;
|
|
s11 = Vertex[0].Y;
|
|
break;
|
|
case 2:
|
|
s00 = Vertex[3].Y;
|
|
s01 = Vertex[0].Y;
|
|
s10 = cd.Verts[2].Y;
|
|
s11 = Vertex[4].Y;
|
|
break;
|
|
case 3:
|
|
s00 = Vertex[0].Y;
|
|
s01 = Vertex[1].Y;
|
|
s10 = Vertex[4].Y;
|
|
s11 = cd.Verts[3].Y;
|
|
break;
|
|
}
|
|
|
|
return (s00 * (1-lx) + s01 * lx) * (1 - lz) + (s10 * (1-lx) + s11 * lx) * lz;
|
|
}
|
|
|
|
quadsquare* quadsquare::GetNeighbor(int dir, const quadcornerdata& cd) {
|
|
if (cd.Parent == 0) return 0;
|
|
quadsquare* p = 0;
|
|
|
|
int index = cd.ChildIndex ^ 1 ^ ((dir & 1) << 1);
|
|
bool SameParent = ((dir - cd.ChildIndex) & 2) ? true : false;
|
|
|
|
if (SameParent) {
|
|
p = cd.Parent->Square;
|
|
} else {
|
|
p = cd.Parent->Square->GetNeighbor(dir, *cd.Parent);
|
|
if (p == 0) return 0;
|
|
}
|
|
quadsquare* n = p->Child[index];
|
|
return n;
|
|
}
|
|
|
|
float quadsquare::RecomputeError(const quadcornerdata& cd) {
|
|
int half = 1 << cd.Level;
|
|
int whole = half << 1;
|
|
float terrain_error;
|
|
float maxerror = 0;
|
|
float e;
|
|
|
|
size_t numTerr = Course.TerrList.size();
|
|
if (cd.ChildIndex & 1) {
|
|
e = fabs(Vertex[0].Y - (cd.Verts[1].Y + cd.Verts[3].Y) * 0.5);
|
|
} else {
|
|
e = fabs(Vertex[0].Y - (cd.Verts[0].Y + cd.Verts[2].Y) * 0.5);
|
|
}
|
|
if (e > maxerror) maxerror = e;
|
|
|
|
MaxY = Vertex[0].Y;
|
|
MinY = Vertex[0].Y;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
float y = cd.Verts[i].Y;
|
|
if (y < MinY) MinY = y;
|
|
if (y > MaxY) MaxY = y;
|
|
}
|
|
|
|
|
|
e = fabs(Vertex[1].Y - (cd.Verts[0].Y + cd.Verts[3].Y) * 0.5);
|
|
if (e > maxerror) maxerror = e;
|
|
Error[0] = e;
|
|
|
|
e = fabs(Vertex[4].Y - (cd.Verts[2].Y + cd.Verts[3].Y) * 0.5);
|
|
if (e > maxerror) maxerror = e;
|
|
Error[1] = e;
|
|
|
|
if (cd.Level == 0 && cd.xorg <= RowSize-1 && cd.zorg <= NumRows-1) {
|
|
|
|
int x = cd.xorg + half;
|
|
int z = cd.zorg + whole;
|
|
int idx = x + z * RowSize;
|
|
bool different_terrains = false;
|
|
|
|
terrain_error = 0.f;
|
|
|
|
if (x < RowSize && z < NumRows) {
|
|
|
|
if (x < RowSize - 1) {
|
|
if (Terrain[idx] != Terrain[idx+1]) {
|
|
different_terrains = true;
|
|
}
|
|
}
|
|
if (z >= 1) {
|
|
idx -= RowSize;
|
|
if (Terrain[idx] != Terrain[idx+1]) {
|
|
different_terrains = true;
|
|
}
|
|
}
|
|
|
|
if (different_terrains) {
|
|
ForceSouthVert = true;
|
|
terrain_error = TERRAIN_ERROR_SCALE * whole * whole;
|
|
} else {
|
|
ForceSouthVert = false;
|
|
}
|
|
|
|
if (terrain_error > Error[0]) {
|
|
Error[0] = terrain_error;
|
|
}
|
|
if (Error[0] > maxerror) {
|
|
maxerror = Error[0];
|
|
}
|
|
}
|
|
|
|
x = cd.xorg + whole;
|
|
z = cd.zorg + half;
|
|
idx = x + z * RowSize;
|
|
terrain_error = 0;
|
|
different_terrains = false;
|
|
|
|
if (x < RowSize && z < NumRows) {
|
|
|
|
if (z >= 1) {
|
|
if (Terrain[idx] != Terrain[idx-RowSize]) {
|
|
different_terrains = true;
|
|
}
|
|
}
|
|
if (z >= 1 && x < RowSize - 1) {
|
|
idx += 1;
|
|
if (Terrain[idx] != Terrain[idx-RowSize]) {
|
|
different_terrains = true;
|
|
}
|
|
}
|
|
|
|
if (different_terrains) {
|
|
ForceEastVert = true;
|
|
terrain_error = TERRAIN_ERROR_SCALE * whole * whole;
|
|
} else {
|
|
ForceEastVert = false;
|
|
}
|
|
|
|
if (terrain_error > Error[1]) {
|
|
Error[1] = terrain_error;
|
|
}
|
|
if (Error[1] > maxerror) {
|
|
maxerror = Error[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
float y = Vertex[1 + i].Y;
|
|
if (y < MinY) MinY = y;
|
|
if (y > MaxY) MaxY = y;
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
quadcornerdata q;
|
|
if (Child[i]) {
|
|
SetupCornerData(&q, cd, i);
|
|
Error[i+2] = Child[i]->RecomputeError(q);
|
|
|
|
if (Child[i]->MinY < MinY) MinY = Child[i]->MinY;
|
|
if (Child[i]->MaxY > MaxY) MaxY = Child[i]->MaxY;
|
|
} else {
|
|
Error[i+2] = fabs((Vertex[0].Y + cd.Verts[i].Y)
|
|
- (Vertex[i+1].Y + Vertex[((i+1)&3) + 1].Y)) * 0.25;
|
|
}
|
|
if (Error[i+2] > maxerror) maxerror = Error[i+2];
|
|
}
|
|
|
|
int *terrain_count = new int[numTerr];
|
|
memset(terrain_count, 0, sizeof(*terrain_count)*numTerr);
|
|
|
|
for (int i=cd.xorg; i<=cd.xorg+whole; i++) {
|
|
for (int j=cd.zorg; j<=cd.zorg+whole; j++) {
|
|
|
|
if (i < 0 || i >= RowSize ||
|
|
j < 0 || j >= NumRows) {
|
|
continue;
|
|
}
|
|
|
|
int terrain = (int) Terrain[i + RowSize*j];
|
|
terrain_count[ terrain ] += 1;
|
|
}
|
|
}
|
|
|
|
int max_count = 0;
|
|
int total = 0;
|
|
for (size_t t=0; t<numTerr; t++) {
|
|
if (terrain_count[t] > max_count) {
|
|
max_count = terrain_count[t];
|
|
}
|
|
total += terrain_count[t];
|
|
}
|
|
|
|
delete [] terrain_count;
|
|
|
|
if (total > 0) {
|
|
terrain_error = (1.0 - max_count / total);
|
|
if (numTerr > 1) {
|
|
terrain_error *= numTerr / (numTerr - 1.0);
|
|
}
|
|
} else terrain_error = 0;
|
|
terrain_error *= whole * whole;
|
|
terrain_error *= TERRAIN_ERROR_SCALE;
|
|
|
|
if (terrain_error > maxerror) maxerror = terrain_error;
|
|
if (terrain_error > Error[0]) Error[0] = terrain_error;
|
|
if (terrain_error > Error[1]) Error[1] = terrain_error;
|
|
|
|
Dirty = false;
|
|
|
|
return maxerror;
|
|
}
|
|
|
|
void quadsquare::ResetTree() {
|
|
for (int i = 0; i < 4; i++) {
|
|
if (Child[i]) {
|
|
Child[i]->ResetTree();
|
|
if (Child[i]->Static == false) {
|
|
delete Child[i];
|
|
Child[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
EnabledFlags = 0;
|
|
SubEnabledCount[0] = 0;
|
|
SubEnabledCount[1] = 0;
|
|
Dirty = true;
|
|
}
|
|
|
|
|
|
void quadsquare::StaticCullData(const quadcornerdata& cd, float ThresholdDetail) {
|
|
ResetTree();
|
|
if (Dirty) RecomputeError(cd);
|
|
for (int level = 0; level <= cd.Level; level++) {
|
|
StaticCullAux(cd, ThresholdDetail, level);
|
|
}
|
|
}
|
|
|
|
|
|
void quadsquare::StaticCullAux(const quadcornerdata& cd, float ThresholdDetail, int TargetLevel) {
|
|
quadcornerdata q;
|
|
|
|
if (cd.Level > TargetLevel) {
|
|
for (int j = 0; j < 4; j++) {
|
|
int i;
|
|
if (j < 2) i = 1 - j;
|
|
else i = j;
|
|
|
|
if (Child[i]) {
|
|
SetupCornerData(&q, cd, i);
|
|
Child[i]->StaticCullAux(q, ThresholdDetail, TargetLevel);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
float size = 2 << cd.Level; // Edge length.
|
|
if (Child[0] == NULL && Child[3] == NULL && Error[0] * ThresholdDetail < size) {
|
|
quadsquare* s = GetNeighbor(0, cd);
|
|
if (s == NULL || (s->Child[1] == NULL && s->Child[2] == NULL)) {
|
|
|
|
float y = (cd.Verts[0].Y + cd.Verts[3].Y) * 0.5;
|
|
Vertex[1].Y = y;
|
|
Error[0] = 0;
|
|
if (s) s->Vertex[3].Y = y;
|
|
|
|
Dirty = true;
|
|
}
|
|
}
|
|
|
|
if (Child[2] == NULL && Child[3] == NULL && Error[1] * ThresholdDetail < size) {
|
|
quadsquare* s = GetNeighbor(3, cd);
|
|
if (s == NULL || (s->Child[0] == NULL && s->Child[1] == NULL)) {
|
|
float y = (cd.Verts[2].Y + cd.Verts[3].Y) * 0.5;
|
|
Vertex[4].Y = y;
|
|
Error[1] = 0;
|
|
|
|
if (s) s->Vertex[2].Y = y;
|
|
Dirty = true;
|
|
}
|
|
}
|
|
|
|
bool StaticChildren = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (Child[i]) {
|
|
StaticChildren = true;
|
|
if (Child[i]->Dirty) Dirty = true;
|
|
}
|
|
}
|
|
|
|
if (StaticChildren == false && cd.Parent != NULL) {
|
|
bool NecessaryEdges = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
float diff = fabs(Vertex[i+1].Y - (cd.Verts[i].Y
|
|
+ cd.Verts[(i+3)&3].Y) * 0.5);
|
|
if (diff > 0.00001) {
|
|
NecessaryEdges = true;
|
|
}
|
|
}
|
|
|
|
if (!NecessaryEdges) {
|
|
size *= 1.414213562;
|
|
if (cd.Parent->Square->Error[2 + cd.ChildIndex] * ThresholdDetail < size) {
|
|
delete cd.Parent->Square->Child[cd.ChildIndex];
|
|
cd.Parent->Square->Child[cd.ChildIndex] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void quadsquare::EnableEdgeVertex(int index, bool IncrementCount, const quadcornerdata& cd) {
|
|
int ct = 0;
|
|
int stack[32];
|
|
|
|
|
|
if ((EnabledFlags & (1 << index)) && IncrementCount == false) return;
|
|
|
|
EnabledFlags |= 1 << index;
|
|
if (IncrementCount == true && (index == 0 || index == 3)) {
|
|
SubEnabledCount[index & 1]++;
|
|
}
|
|
quadsquare* p = this;
|
|
const quadcornerdata* pcd = &cd;
|
|
for (;;) {
|
|
int ci = pcd->ChildIndex;
|
|
if (pcd->Parent == NULL || pcd->Parent->Square == NULL) return;
|
|
p = pcd->Parent->Square;
|
|
pcd = pcd->Parent;
|
|
|
|
bool SameParent = ((index - ci) & 2) ? true : false;
|
|
|
|
ci = ci ^ 1 ^ ((index & 1) << 1);
|
|
|
|
stack[ct] = ci;
|
|
ct++;
|
|
|
|
if (SameParent) break;
|
|
}
|
|
|
|
p = p->EnableDescendant(ct, stack, *pcd);
|
|
|
|
index ^= 2;
|
|
p->EnabledFlags |= (1 << index);
|
|
if (IncrementCount == true && (index == 0 || index == 3)) {
|
|
p->SubEnabledCount[index & 1]++;
|
|
}
|
|
}
|
|
|
|
|
|
quadsquare* quadsquare::EnableDescendant(int count, int path[], const quadcornerdata& cd) {
|
|
count--;
|
|
int ChildIndex = path[count];
|
|
|
|
if ((EnabledFlags & (16 << ChildIndex)) == 0) {
|
|
EnableChild(ChildIndex, cd);
|
|
}
|
|
|
|
if (count > 0) {
|
|
quadcornerdata q;
|
|
SetupCornerData(&q, cd, ChildIndex);
|
|
return Child[ChildIndex]->EnableDescendant(count, path, q);
|
|
} else {
|
|
return Child[ChildIndex];
|
|
}
|
|
}
|
|
|
|
|
|
void quadsquare::CreateChild(int index, const quadcornerdata& cd) {
|
|
if (Child[index] == 0) {
|
|
quadcornerdata q;
|
|
SetupCornerData(&q, cd, index);
|
|
|
|
Child[index] = new quadsquare(&q);
|
|
}
|
|
}
|
|
|
|
void quadsquare::EnableChild(int index, const quadcornerdata& cd) {
|
|
if ((EnabledFlags & (16 << index)) == 0) {
|
|
EnabledFlags |= (16 << index);
|
|
EnableEdgeVertex(index, true, cd);
|
|
EnableEdgeVertex((index + 1) & 3, true, cd);
|
|
|
|
if (Child[index] == 0) {
|
|
CreateChild(index, cd);
|
|
}
|
|
}
|
|
}
|
|
|
|
void quadsquare::NotifyChildDisable(const quadcornerdata& cd, int index) {
|
|
EnabledFlags &= ~(16 << index);
|
|
quadsquare* s;
|
|
|
|
if (index & 2) s = this;
|
|
else s = GetNeighbor(1, cd);
|
|
if (s) {
|
|
s->SubEnabledCount[1]--;
|
|
}
|
|
|
|
if (index == 1 || index == 2) s = GetNeighbor(2, cd);
|
|
else s = this;
|
|
if (s) {
|
|
s->SubEnabledCount[0]--;
|
|
}
|
|
}
|
|
|
|
static float DetailThreshold = 100;
|
|
|
|
|
|
bool quadsquare::VertexTest(int x, float y, int z, float error,
|
|
const float Viewer[3], int level, vertex_loc_t vertex_loc) {
|
|
float dx = fabs(x - Viewer[0]) * fabs(ScaleX);
|
|
float dy = fabs(y - Viewer[1]);
|
|
float dz = fabs(z - Viewer[2]) * fabs(ScaleZ);
|
|
float d = max(dx, max(dy, dz));
|
|
|
|
if (vertex_loc == South && ForceSouthVert && d < VERTEX_FORCE_THRESHOLD) {
|
|
return true;
|
|
}
|
|
if (vertex_loc == East && ForceEastVert && d < VERTEX_FORCE_THRESHOLD) {
|
|
return true;
|
|
}
|
|
if (d < ERROR_MAGNIFICATION_THRESHOLD) {
|
|
error *= ERROR_MAGNIFICATION_AMOUNT;
|
|
}
|
|
|
|
return error * DetailThreshold > d;
|
|
}
|
|
|
|
bool quadsquare::BoxTest(int x, int z, float size, float miny, float maxy, float error, const float Viewer[3]) {
|
|
float half = size * 0.5;
|
|
float dx = (fabs(x + half - Viewer[0]) - half) * fabs(ScaleX);
|
|
float dy = fabs((miny + maxy) * 0.5 - Viewer[1]) - (maxy - miny) * 0.5;
|
|
float dz = (fabs(z + half - Viewer[2]) - half) * fabs(ScaleZ);
|
|
float d = max(dx, max(dy , dz));
|
|
|
|
if (d < ERROR_MAGNIFICATION_THRESHOLD) {
|
|
error *= ERROR_MAGNIFICATION_AMOUNT;
|
|
}
|
|
|
|
if (error * DetailThreshold > d) {
|
|
return true;
|
|
}
|
|
if ((x < RowSize-1 && x+size >= RowSize) ||
|
|
(z < NumRows-1 && z+size >= NumRows)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void quadsquare::Update(const quadcornerdata& cd, const TVector3d& ViewerLocation, float Detail) {
|
|
float Viewer[3];
|
|
|
|
DetailThreshold = Detail;
|
|
Viewer[0] = ViewerLocation.x / ScaleX;
|
|
Viewer[1] = ViewerLocation.y;
|
|
Viewer[2] = ViewerLocation.z / ScaleZ;
|
|
UpdateAux(cd, Viewer, 0, SomeClip);
|
|
}
|
|
|
|
|
|
void quadsquare::UpdateAux(const quadcornerdata& cd,
|
|
const float ViewerLocation[3], float CenterError, clip_result_t vis) {
|
|
if (vis != NoClip) {
|
|
vis = ClipSquare(cd);
|
|
|
|
if (vis == NotVisible) {
|
|
return;
|
|
}
|
|
}
|
|
if (Dirty) {
|
|
RecomputeError(cd);
|
|
}
|
|
|
|
int half = 1 << cd.Level;
|
|
int whole = half << 1;
|
|
if ((EnabledFlags & 1) == 0 &&
|
|
VertexTest(cd.xorg + whole, Vertex[1].Y, cd.zorg + half,
|
|
Error[0], ViewerLocation, cd.Level, East) == true) {
|
|
EnableEdgeVertex(0, false, cd);
|
|
}
|
|
|
|
if ((EnabledFlags & 8) == 0 &&
|
|
VertexTest(cd.xorg + half, Vertex[4].Y, cd.zorg + whole,
|
|
Error[1], ViewerLocation, cd.Level, South) == true) {
|
|
EnableEdgeVertex(3, false, cd);
|
|
}
|
|
|
|
if (cd.Level > 0) {
|
|
if ((EnabledFlags & 32) == 0) {
|
|
if (BoxTest(cd.xorg, cd.zorg, half, MinY, MaxY, Error[3],
|
|
ViewerLocation) == true) EnableChild(1, cd);
|
|
}
|
|
if ((EnabledFlags & 16) == 0) {
|
|
if (BoxTest(cd.xorg + half, cd.zorg, half, MinY, MaxY,
|
|
Error[2], ViewerLocation) == true) EnableChild(0, cd);
|
|
}
|
|
if ((EnabledFlags & 64) == 0) {
|
|
if (BoxTest(cd.xorg, cd.zorg + half, half, MinY, MaxY,
|
|
Error[4], ViewerLocation) == true) EnableChild(2, cd);
|
|
}
|
|
if ((EnabledFlags & 128) == 0) {
|
|
if (BoxTest(cd.xorg + half, cd.zorg + half, half, MinY, MaxY,
|
|
Error[5], ViewerLocation) == true) EnableChild(3, cd);
|
|
}
|
|
|
|
quadcornerdata q;
|
|
|
|
if (EnabledFlags & 32) {
|
|
SetupCornerData(&q, cd, 1);
|
|
Child[1]->UpdateAux(q, ViewerLocation, Error[3], vis);
|
|
}
|
|
if (EnabledFlags & 16) {
|
|
SetupCornerData(&q, cd, 0);
|
|
Child[0]->UpdateAux(q, ViewerLocation, Error[2], vis);
|
|
}
|
|
if (EnabledFlags & 64) {
|
|
SetupCornerData(&q, cd, 2);
|
|
Child[2]->UpdateAux(q, ViewerLocation, Error[4], vis);
|
|
}
|
|
if (EnabledFlags & 128) {
|
|
SetupCornerData(&q, cd, 3);
|
|
Child[3]->UpdateAux(q, ViewerLocation, Error[5], vis);
|
|
}
|
|
}
|
|
if ((EnabledFlags & 1) &&
|
|
SubEnabledCount[0] == 0 &&
|
|
VertexTest(cd.xorg + whole, Vertex[1].Y, cd.zorg + half,
|
|
Error[0], ViewerLocation, cd.Level, East) == false) {
|
|
EnabledFlags &= ~1;
|
|
quadsquare* s = GetNeighbor(0, cd);
|
|
if (s) s->EnabledFlags &= ~4;
|
|
}
|
|
|
|
if ((EnabledFlags & 8) &&
|
|
SubEnabledCount[1] == 0 &&
|
|
VertexTest(cd.xorg + half, Vertex[4].Y, cd.zorg + whole,
|
|
Error[1], ViewerLocation, cd.Level, South) == false) {
|
|
EnabledFlags &= ~8;
|
|
quadsquare* s = GetNeighbor(3, cd);
|
|
if (s) s->EnabledFlags &= ~2;
|
|
}
|
|
|
|
if (EnabledFlags == 0 &&
|
|
cd.Parent != NULL &&
|
|
BoxTest(cd.xorg, cd.zorg, whole, MinY, MaxY, CenterError,
|
|
ViewerLocation) == false) {
|
|
cd.Parent->Square->NotifyChildDisable(*cd.Parent, cd.ChildIndex);
|
|
}
|
|
}
|
|
|
|
GLuint VertexIndices[9];
|
|
int VertexTerrains[9];
|
|
|
|
void quadsquare::InitVert(int i, int x, int z) {
|
|
if (x >= RowSize) x = RowSize-1;
|
|
if (z >= NumRows) z = NumRows - 1;
|
|
|
|
int idx = x + RowSize * z;
|
|
|
|
VertexIndices[i] = idx;
|
|
VertexTerrains[i] = Terrain[idx];
|
|
}
|
|
|
|
GLubyte *VNCArray;
|
|
|
|
void quadsquare::DrawTris() {
|
|
int tmp_min_idx = VertexArrayMinIdx;
|
|
|
|
if (glLockArraysEXT_p) {
|
|
if (tmp_min_idx == 0) tmp_min_idx = 1;
|
|
glLockArraysEXT_p(tmp_min_idx, VertexArrayMaxIdx - tmp_min_idx + 1);
|
|
}
|
|
|
|
glDrawElements(GL_TRIANGLES, VertexArrayCounter,
|
|
GL_UNSIGNED_INT, VertexArrayIndices);
|
|
if (glUnlockArraysEXT_p) glUnlockArraysEXT_p();
|
|
}
|
|
|
|
void quadsquare::InitArrayCounters() {
|
|
VertexArrayCounter = 0;
|
|
VertexArrayMinIdx = INT_MAX;
|
|
VertexArrayMaxIdx = 0;
|
|
}
|
|
|
|
void quadsquare::Render(const quadcornerdata& cd, GLubyte *vnc_array) {
|
|
VNCArray = vnc_array;
|
|
bool fog_on;
|
|
int nx, ny;
|
|
Course.GetDivisions(&nx, &ny);
|
|
const TTerrType *TerrList = &Course.TerrList[0];
|
|
|
|
size_t numTerrains = Course.TerrList.size();
|
|
// fog_on = is_fog_on ();
|
|
fog_on = true;
|
|
for (size_t j=0; j<numTerrains; j++) {
|
|
if (TerrList[j].texture != NULL) {
|
|
InitArrayCounters();
|
|
RenderAux(cd, SomeClip, (int)j);
|
|
if (VertexArrayCounter == 0) continue;
|
|
|
|
Course.TerrList[j].texture->Bind();
|
|
DrawTris();
|
|
}
|
|
}
|
|
|
|
if (param.perf_level > 1) {
|
|
InitArrayCounters();
|
|
RenderAux(cd, SomeClip, -1);
|
|
|
|
if (VertexArrayCounter != 0) {
|
|
glDisable(GL_FOG);
|
|
for (GLuint i=0; i<VertexArrayCounter; i++) {
|
|
colorval(VertexArrayIndices[i], 0) = 0;
|
|
colorval(VertexArrayIndices[i], 1) = 0;
|
|
colorval(VertexArrayIndices[i], 2) = 0;
|
|
colorval(VertexArrayIndices[i], 3) = 255;
|
|
}
|
|
Course.TerrList[0].texture->Bind();
|
|
DrawTris();
|
|
if (fog_on) glEnable(GL_FOG);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
for (GLuint i=0; i<VertexArrayCounter; i++) {
|
|
colorval(VertexArrayIndices[i], 0) = 255;
|
|
colorval(VertexArrayIndices[i], 1) = 255;
|
|
colorval(VertexArrayIndices[i], 2) = 255;
|
|
}
|
|
|
|
for (size_t j=0; j<numTerrains; j++) {
|
|
if (TerrList[j].texture > 0) {
|
|
Course.TerrList[j].texture->Bind();
|
|
|
|
for (GLuint i=0; i<VertexArrayCounter; i++) {
|
|
colorval(VertexArrayIndices[i], 3) =
|
|
(Terrain[VertexArrayIndices[i]] == (char)j) ? 255 : 0;
|
|
}
|
|
DrawTris();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
clip_result_t quadsquare::ClipSquare(const quadcornerdata& cd) {
|
|
if (cd.xorg >= RowSize-1) {
|
|
return NotVisible;
|
|
}
|
|
|
|
if (cd.zorg >= NumRows-1) {
|
|
return NotVisible;
|
|
}
|
|
|
|
int whole = 2 << cd.Level;
|
|
TVector3d min, max;
|
|
min.x = cd.xorg * ScaleX;
|
|
min.y = MinY;
|
|
min.z = cd.zorg * ScaleZ;
|
|
max.x = (cd.xorg + whole) * ScaleX;
|
|
max.y = MaxY;
|
|
max.z = (cd.zorg + whole) * ScaleZ;
|
|
if (min.x > max.x) {
|
|
double tmp = min.x;
|
|
min.x = max.x;
|
|
max.x = tmp;
|
|
}
|
|
|
|
if (min.z > max.z) {
|
|
double tmp = min.z;
|
|
min.z = max.z;
|
|
max.z = tmp;
|
|
}
|
|
|
|
clip_result_t clip_result = clip_aabb_to_view_frustum(min, max);
|
|
|
|
if (clip_result == NotVisible || clip_result == SomeClip) {
|
|
return clip_result;
|
|
}
|
|
|
|
if (cd.xorg + whole >= RowSize) {
|
|
return SomeClip;
|
|
}
|
|
if (cd.zorg + whole >= NumRows) {
|
|
return SomeClip;
|
|
}
|
|
|
|
return clip_result;
|
|
}
|
|
|
|
|
|
inline void quadsquare::MakeTri(int a, int b, int c, int terrain) {
|
|
if ((VertexTerrains[a] == terrain ||
|
|
VertexTerrains[b] == terrain ||
|
|
VertexTerrains[c] == terrain)) {
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[a];
|
|
setalphaval(a);
|
|
update_min_max(VertexIndices[a]);
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[b];
|
|
setalphaval(b);
|
|
update_min_max(VertexIndices[b]);
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[c];
|
|
setalphaval(c);
|
|
update_min_max(VertexIndices[c]);
|
|
}
|
|
}
|
|
|
|
|
|
inline void quadsquare::MakeSpecialTri(int a, int b, int c, int terrain) {
|
|
if (VertexTerrains[a] != VertexTerrains[b] &&
|
|
VertexTerrains[a] != VertexTerrains[c] &&
|
|
VertexTerrains[b] != VertexTerrains[c]) {
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[a];
|
|
update_min_max(VertexIndices[a]);
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[b];
|
|
update_min_max(VertexIndices[b]);
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[c];
|
|
update_min_max(VertexIndices[c]);
|
|
}
|
|
}
|
|
|
|
inline void quadsquare::MakeNoBlendTri(int a, int b, int c, int terrain) {
|
|
if ((VertexTerrains[a] == terrain ||
|
|
VertexTerrains[b] == terrain ||
|
|
VertexTerrains[c] == terrain) &&
|
|
(VertexTerrains[a] >= terrain &&
|
|
VertexTerrains[b] >= terrain &&
|
|
VertexTerrains[c] >= terrain)) {
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[a];
|
|
setalphaval(a);
|
|
update_min_max(VertexIndices[a]);
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[b];
|
|
setalphaval(b);
|
|
update_min_max(VertexIndices[b]);
|
|
VertexArrayIndices[VertexArrayCounter++] = VertexIndices[c];
|
|
setalphaval(c);
|
|
update_min_max(VertexIndices[c]);
|
|
}
|
|
}
|
|
|
|
void quadsquare::RenderAux(const quadcornerdata& cd, clip_result_t vis, int terrain) {
|
|
int half = 1 << cd.Level;
|
|
int whole = 2 << cd.Level;
|
|
if (vis != NoClip) {
|
|
vis = ClipSquare(cd);
|
|
if (vis == NotVisible) return;
|
|
}
|
|
|
|
int flags = 0;
|
|
int mask = 1;
|
|
quadcornerdata q;
|
|
|
|
for (int i = 0; i < 4; i++, mask <<= 1) {
|
|
if (EnabledFlags & (16 << i)) {
|
|
SetupCornerData(&q, cd, i);
|
|
Child[i]->RenderAux(q, vis, terrain);
|
|
} else {
|
|
flags |= mask;
|
|
}
|
|
}
|
|
|
|
if (flags == 0) return;
|
|
|
|
InitVert(0, cd.xorg + half, cd.zorg + half);
|
|
InitVert(1, cd.xorg + whole, cd.zorg + half);
|
|
InitVert(2, cd.xorg + whole, cd.zorg);
|
|
InitVert(3, cd.xorg + half, cd.zorg);
|
|
InitVert(4, cd.xorg, cd.zorg);
|
|
InitVert(5, cd.xorg, cd.zorg + half);
|
|
InitVert(6, cd.xorg, cd.zorg + whole);
|
|
InitVert(7, cd.xorg + half, cd.zorg + whole);
|
|
InitVert(8, cd.xorg + whole, cd.zorg + whole);
|
|
if (terrain == -1) {
|
|
make_tri_list(MakeSpecialTri, EnabledFlags, flags, terrain);
|
|
} else if (param.perf_level > 1) {
|
|
make_tri_list(MakeTri, EnabledFlags, flags, terrain);
|
|
} else {
|
|
make_tri_list(MakeNoBlendTri, EnabledFlags, flags, terrain);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void quadsquare::SetupCornerData(quadcornerdata* q, const quadcornerdata& cd, int ChildIndex) {
|
|
int half = 1 << cd.Level;
|
|
|
|
q->Parent = &cd;
|
|
q->Square = Child[ChildIndex];
|
|
q->Level = cd.Level - 1;
|
|
q->ChildIndex = ChildIndex;
|
|
|
|
switch (ChildIndex) {
|
|
default:
|
|
case 0:
|
|
q->xorg = cd.xorg + half;
|
|
q->zorg = cd.zorg;
|
|
q->Verts[0] = cd.Verts[0];
|
|
q->Verts[1] = Vertex[2];
|
|
q->Verts[2] = Vertex[0];
|
|
q->Verts[3] = Vertex[1];
|
|
break;
|
|
|
|
case 1:
|
|
q->xorg = cd.xorg;
|
|
q->zorg = cd.zorg;
|
|
q->Verts[0] = Vertex[2];
|
|
q->Verts[1] = cd.Verts[1];
|
|
q->Verts[2] = Vertex[3];
|
|
q->Verts[3] = Vertex[0];
|
|
break;
|
|
|
|
case 2:
|
|
q->xorg = cd.xorg;
|
|
q->zorg = cd.zorg + half;
|
|
q->Verts[0] = Vertex[0];
|
|
q->Verts[1] = Vertex[3];
|
|
q->Verts[2] = cd.Verts[2];
|
|
q->Verts[3] = Vertex[4];
|
|
break;
|
|
|
|
case 3:
|
|
q->xorg = cd.xorg + half;
|
|
q->zorg = cd.zorg + half;
|
|
q->Verts[0] = Vertex[1];
|
|
q->Verts[1] = Vertex[0];
|
|
q->Verts[2] = Vertex[4];
|
|
q->Verts[3] = cd.Verts[3];
|
|
break;
|
|
}
|
|
}
|
|
|
|
int quadsquare::RowSize;
|
|
int quadsquare::NumRows;
|
|
|
|
void quadsquare::AddHeightMap(const quadcornerdata& cd, const HeightMapInfo& hm) {
|
|
RowSize = hm.RowWidth;
|
|
NumRows = hm.ZSize;
|
|
|
|
if (cd.Parent == NULL) {
|
|
if (VertexArrayIndices != NULL) {
|
|
delete VertexArrayIndices;
|
|
}
|
|
VertexArrayIndices = new GLuint[6 * RowSize * NumRows];
|
|
}
|
|
int BlockSize = 2 << cd.Level;
|
|
if (cd.xorg > hm.XOrigin + ((hm.XSize + 2) << hm.Scale) ||
|
|
cd.xorg + BlockSize < hm.XOrigin - (1 << hm.Scale) ||
|
|
cd.zorg > hm.ZOrigin + ((hm.ZSize + 2) << hm.Scale) ||
|
|
cd.zorg + BlockSize < hm.ZOrigin - (1 << hm.Scale)) {
|
|
return;
|
|
}
|
|
|
|
if (cd.Parent && cd.Parent->Square) {
|
|
cd.Parent->Square->EnableChild(cd.ChildIndex, *cd.Parent);
|
|
}
|
|
|
|
int half = 1 << cd.Level;
|
|
for (int i = 0; i < 4; i++) {
|
|
quadcornerdata q;
|
|
SetupCornerData(&q, cd, i);
|
|
|
|
if (Child[i] == NULL && cd.Level > hm.Scale) {
|
|
Child[i] = new quadsquare(&q);
|
|
}
|
|
if (Child[i]) {
|
|
Child[i]->AddHeightMap(q, hm);
|
|
}
|
|
}
|
|
float s[5];
|
|
s[0] = hm.Sample(cd.xorg + half, cd.zorg + half);
|
|
s[1] = hm.Sample(cd.xorg + half*2, cd.zorg + half);
|
|
s[2] = hm.Sample(cd.xorg + half, cd.zorg);
|
|
s[3] = hm.Sample(cd.xorg, cd.zorg + half);
|
|
s[4] = hm.Sample(cd.xorg + half, cd.zorg + half*2);
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
if (s[i] != 0) {
|
|
Dirty = true;
|
|
Vertex[i].Y += s[i];
|
|
}
|
|
}
|
|
|
|
if (!Dirty) {
|
|
for (int i = 0; i < 4; i++) {
|
|
if (Child[i] && Child[i]->Dirty) {
|
|
Dirty = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Dirty) SetStatic(cd);
|
|
}
|
|
|
|
double quadsquare::ScaleX;
|
|
double quadsquare::ScaleZ;
|
|
void quadsquare::SetScale(double x, double z) {
|
|
ScaleX = x;
|
|
ScaleZ = z;
|
|
}
|
|
|
|
char* quadsquare::Terrain;
|
|
void quadsquare::SetTerrain(char *t) {
|
|
Terrain = t;
|
|
}
|
|
|
|
float HeightMapInfo::Sample(int x, int z) const {
|
|
if (x >= XSize) {
|
|
x = XSize - 1;
|
|
}
|
|
if (z >= ZSize) {
|
|
z = ZSize - 1;
|
|
}
|
|
return Data[ x + z * RowWidth ];
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
// global calls
|
|
// --------------------------------------------------------------------
|
|
|
|
#define CULL_DETAIL_FACTOR 25
|
|
|
|
static quadsquare *root = (quadsquare*) NULL;
|
|
static quadcornerdata root_corner_data = {(quadcornerdata*)NULL };
|
|
|
|
void ResetQuadtree() {
|
|
if (root != NULL) {
|
|
delete root;
|
|
root = (quadsquare*) NULL;
|
|
}
|
|
}
|
|
|
|
static int get_root_level(int nx, int nz) {
|
|
int xlev = (int)(log(static_cast<double>(nx)) / log(2.0));
|
|
int zlev = (int)(log(static_cast<double>(nz)) / log(2.0));
|
|
|
|
return max(xlev, zlev);
|
|
}
|
|
|
|
|
|
void InitQuadtree(double *elevation, int nx, int nz,
|
|
double scalex, double scalez, const TVector3d& view_pos, double detail) {
|
|
HeightMapInfo hm;
|
|
|
|
hm.Data = elevation;
|
|
hm.XOrigin = 0;
|
|
hm.ZOrigin = 0;
|
|
hm.XSize = nx;
|
|
hm.ZSize = nz;
|
|
hm.RowWidth = hm.XSize;
|
|
hm.Scale = 0;
|
|
|
|
root_corner_data.Square = (quadsquare*)NULL;
|
|
root_corner_data.ChildIndex = 0;
|
|
root_corner_data.Level = get_root_level(nx, nz);
|
|
root_corner_data.xorg = 0;
|
|
root_corner_data.zorg = 0;
|
|
|
|
for (int i=0; i<4; i++) {
|
|
root_corner_data.Verts[i].Y = 0;
|
|
root_corner_data.Verts[i].Y = 0;
|
|
}
|
|
|
|
root = new quadsquare(&root_corner_data);
|
|
root->AddHeightMap(root_corner_data, hm);
|
|
root->SetScale(scalex, scalez);
|
|
root->SetTerrain(Course.terrain);
|
|
|
|
root->StaticCullData(root_corner_data, CULL_DETAIL_FACTOR);
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
root->Update(root_corner_data, view_pos, detail);
|
|
}
|
|
}
|
|
|
|
void UpdateQuadtree(const TVector3d& view_pos, float detail) {
|
|
root->Update(root_corner_data, view_pos, detail);
|
|
}
|
|
|
|
void RenderQuadtree() {
|
|
GLubyte *vnc_array = Course.GetGLArrays();
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glVertexPointer(3, GL_FLOAT, STRIDE_GL_ARRAY, vnc_array);
|
|
|
|
glEnableClientState(GL_NORMAL_ARRAY);
|
|
glNormalPointer(GL_FLOAT, STRIDE_GL_ARRAY,
|
|
vnc_array + 4 * sizeof(GLfloat));
|
|
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
glColorPointer(4, GL_UNSIGNED_BYTE, STRIDE_GL_ARRAY,
|
|
vnc_array + 8 * sizeof(GLfloat));
|
|
|
|
root->Render(root_corner_data, vnc_array);
|
|
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_NORMAL_ARRAY);
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
}
|