2013-03-10 12:59:08 -07:00
|
|
|
// Copyright © 2008-2013 Pioneer Developers. See AUTHORS.txt for details
|
|
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
|
|
|
|
#include "libs.h"
|
|
|
|
#include "GeoPatchContext.h"
|
|
|
|
#include "GeoPatch.h"
|
2013-03-27 14:33:37 -07:00
|
|
|
#include "GeoPatchJobs.h"
|
2013-03-10 12:59:08 -07:00
|
|
|
#include "GeoSphere.h"
|
|
|
|
#include "perlin.h"
|
|
|
|
#include "Pi.h"
|
|
|
|
#include "RefCounted.h"
|
|
|
|
#include "graphics/Material.h"
|
|
|
|
#include "graphics/Renderer.h"
|
|
|
|
#include "graphics/Frustum.h"
|
|
|
|
#include "graphics/Graphics.h"
|
|
|
|
#include "graphics/VertexArray.h"
|
|
|
|
#include "graphics/gl2/GeoSphereMaterial.h"
|
|
|
|
#include "vcacheopt/vcacheopt.h"
|
|
|
|
#include <deque>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
// tri edge lengths
|
|
|
|
#define GEOPATCH_SUBDIVIDE_AT_CAMDIST 5.0
|
2013-03-10 15:19:01 -07:00
|
|
|
#define GEOPATCH_MAX_DEPTH 15 + (2*Pi::detail.fracmult) //15
|
2013-03-10 12:59:08 -07:00
|
|
|
|
|
|
|
GeoPatch::GeoPatch(const RefCountedPtr<GeoPatchContext> &ctx_, GeoSphere *gs,
|
|
|
|
const vector3d &v0_, const vector3d &v1_, const vector3d &v2_, const vector3d &v3_,
|
|
|
|
const int depth, const GeoPatchID &ID_)
|
2013-03-24 14:23:38 -07:00
|
|
|
: ctx(ctx_), v0(v0_), v1(v1_), v2(v2_), v3(v3_),
|
2013-03-25 16:02:33 -07:00
|
|
|
heights(NULL), normals(NULL), colors(NULL),
|
2013-03-10 12:59:08 -07:00
|
|
|
m_vbo(0), parent(NULL), geosphere(gs),
|
|
|
|
m_depth(depth), mPatchID(ID_),
|
2013-03-27 14:33:37 -07:00
|
|
|
mHasJobRequest(false)
|
2013-03-10 12:59:08 -07:00
|
|
|
{
|
|
|
|
for (int i=0; i<NUM_KIDS; ++i) {
|
|
|
|
edgeFriend[i] = NULL;
|
|
|
|
}
|
|
|
|
m_kidsLock = SDL_CreateMutex();
|
|
|
|
|
|
|
|
clipCentroid = (v0+v1+v2+v3) * 0.25;
|
|
|
|
centroid = clipCentroid.Normalized();
|
|
|
|
clipRadius = 0.0;
|
|
|
|
clipRadius = std::max(clipRadius, (v0-clipCentroid).Length());
|
|
|
|
clipRadius = std::max(clipRadius, (v1-clipCentroid).Length());
|
|
|
|
clipRadius = std::max(clipRadius, (v2-clipCentroid).Length());
|
|
|
|
clipRadius = std::max(clipRadius, (v3-clipCentroid).Length());
|
|
|
|
if (geosphere->m_sbody->type < SystemBody::TYPE_PLANET_ASTEROID) {
|
|
|
|
m_distMult = 10 / Clamp(depth, 1, 10);
|
|
|
|
} else {
|
|
|
|
m_distMult = 5 / Clamp(depth, 1, 5);
|
|
|
|
}
|
|
|
|
m_roughLength = GEOPATCH_SUBDIVIDE_AT_CAMDIST / pow(2.0, depth) * m_distMult;
|
|
|
|
m_needUpdateVBOs = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
GeoPatch::~GeoPatch() {
|
2013-03-24 13:55:35 -07:00
|
|
|
assert(!mHasJobRequest);
|
2013-03-10 12:59:08 -07:00
|
|
|
|
|
|
|
SDL_DestroyMutex(m_kidsLock);
|
|
|
|
for (int i=0; i<NUM_KIDS; i++) {
|
|
|
|
if (edgeFriend[i]) edgeFriend[i]->NotifyEdgeFriendDeleted(this);
|
|
|
|
}
|
2013-03-17 10:22:59 -07:00
|
|
|
for (int i=0; i<NUM_KIDS; i++) {
|
|
|
|
kids[i].Reset();
|
|
|
|
}
|
2013-03-25 16:02:33 -07:00
|
|
|
heights.Reset();
|
2013-03-17 10:22:59 -07:00
|
|
|
normals.Reset();
|
|
|
|
colors.Reset();
|
2013-03-10 12:59:08 -07:00
|
|
|
glDeleteBuffersARB(1, &m_vbo);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GeoPatch::_UpdateVBOs() {
|
|
|
|
if (m_needUpdateVBOs) {
|
|
|
|
m_needUpdateVBOs = false;
|
|
|
|
if (!m_vbo) glGenBuffersARB(1, &m_vbo);
|
|
|
|
glBindBufferARB(GL_ARRAY_BUFFER, m_vbo);
|
|
|
|
glBufferDataARB(GL_ARRAY_BUFFER, sizeof(GeoPatchContext::VBOVertex)*ctx->NUMVERTICES(), 0, GL_DYNAMIC_DRAW);
|
2013-03-25 16:02:33 -07:00
|
|
|
double xfrac=0.0, yfrac=0.0;
|
|
|
|
double *pHts = heights.Get();
|
2013-03-27 12:48:40 -07:00
|
|
|
const vector3f *pNorm = &normals[0];
|
2013-03-27 14:33:37 -07:00
|
|
|
const Color3ub *pColr = &colors[0];
|
2013-03-25 16:02:33 -07:00
|
|
|
GeoPatchContext::VBOVertex *pData = ctx->vbotemp;
|
|
|
|
for (int y=0; y<ctx->edgeLen; y++) {
|
|
|
|
xfrac = 0.0;
|
|
|
|
for (int x=0; x<ctx->edgeLen; x++) {
|
|
|
|
const double height = *pHts;
|
|
|
|
const vector3d p = (GetSpherePoint(xfrac, yfrac) * (height + 1.0)) - clipCentroid;
|
|
|
|
clipRadius = std::max(clipRadius, p.Length());
|
|
|
|
pData->x = float(p.x);
|
|
|
|
pData->y = float(p.y);
|
|
|
|
pData->z = float(p.z);
|
|
|
|
++pHts; // next height
|
|
|
|
|
2013-03-27 15:33:58 -07:00
|
|
|
pData->nx = pNorm->x;
|
|
|
|
pData->ny = pNorm->y;
|
|
|
|
pData->nz = pNorm->z;
|
2013-03-25 16:02:33 -07:00
|
|
|
++pNorm; // next normal
|
|
|
|
|
|
|
|
pData->col[0] = pColr->r;
|
|
|
|
pData->col[1] = pColr->g;
|
|
|
|
pData->col[2] = pColr->b;
|
|
|
|
pData->col[3] = 255;
|
|
|
|
++pColr; // next colour
|
|
|
|
|
|
|
|
++pData; // next vertex
|
|
|
|
|
|
|
|
xfrac += ctx->frac;
|
|
|
|
}
|
|
|
|
yfrac += ctx->frac;
|
2013-03-10 12:59:08 -07:00
|
|
|
}
|
|
|
|
glBufferDataARB(GL_ARRAY_BUFFER, sizeof(GeoPatchContext::VBOVertex)*ctx->NUMVERTICES(), ctx->vbotemp, GL_DYNAMIC_DRAW);
|
|
|
|
glBindBufferARB(GL_ARRAY_BUFFER, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GeoPatch::Render(vector3d &campos, const Graphics::Frustum &frustum) {
|
|
|
|
PiVerify(SDL_mutexP(m_kidsLock)==0);
|
|
|
|
if (kids[0]) {
|
|
|
|
for (int i=0; i<NUM_KIDS; i++) kids[i]->Render(campos, frustum);
|
|
|
|
SDL_mutexV(m_kidsLock);
|
2013-03-25 16:02:33 -07:00
|
|
|
} else if (heights.Valid()) {
|
2013-03-10 12:59:08 -07:00
|
|
|
SDL_mutexV(m_kidsLock);
|
|
|
|
_UpdateVBOs();
|
|
|
|
|
|
|
|
if (!frustum.TestPoint(clipCentroid, clipRadius))
|
|
|
|
return;
|
|
|
|
|
|
|
|
vector3d relpos = clipCentroid - campos;
|
|
|
|
glPushMatrix();
|
|
|
|
glTranslated(relpos.x, relpos.y, relpos.z);
|
|
|
|
|
|
|
|
Pi::statSceneTris += 2*(ctx->edgeLen-1)*(ctx->edgeLen-1);
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
|
|
glEnableClientState(GL_NORMAL_ARRAY);
|
|
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
|
|
|
|
|
|
// update the indices used for rendering
|
|
|
|
ctx->updateIndexBufferId(determineIndexbuffer());
|
|
|
|
|
|
|
|
glBindBufferARB(GL_ARRAY_BUFFER, m_vbo);
|
|
|
|
glVertexPointer(3, GL_FLOAT, sizeof(GeoPatchContext::VBOVertex), 0);
|
|
|
|
glNormalPointer(GL_FLOAT, sizeof(GeoPatchContext::VBOVertex), reinterpret_cast<void *>(3*sizeof(float)));
|
|
|
|
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(GeoPatchContext::VBOVertex), reinterpret_cast<void *>(6*sizeof(float)));
|
|
|
|
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, ctx->indices_vbo);
|
|
|
|
glDrawElements(GL_TRIANGLES, ctx->indices_tri_count*3, GL_UNSIGNED_SHORT, 0);
|
|
|
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
|
|
|
|
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
|
|
glDisableClientState(GL_NORMAL_ARRAY);
|
|
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
|
|
glPopMatrix();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GeoPatch::LODUpdate(const vector3d &campos) {
|
|
|
|
// there should be no LODUpdate'ing when we have active split requests
|
2013-03-24 13:55:35 -07:00
|
|
|
if(mHasJobRequest)
|
2013-03-10 12:59:08 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
bool canSplit = true;
|
2013-03-27 14:33:37 -07:00
|
|
|
bool canMerge = (kids[0]!=NULL);
|
2013-03-10 12:59:08 -07:00
|
|
|
|
|
|
|
// always split at first level
|
|
|
|
if (parent) {
|
|
|
|
for (int i=0; i<NUM_EDGES; i++) {
|
|
|
|
if (!edgeFriend[i]) {
|
|
|
|
canSplit = false;
|
|
|
|
break;
|
|
|
|
} else if (edgeFriend[i]->m_depth < m_depth) {
|
|
|
|
canSplit = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const float centroidDist = (campos - centroid).Length();
|
|
|
|
const bool errorSplit = (centroidDist < m_roughLength);
|
|
|
|
if( !(canSplit && (m_depth < GEOPATCH_MAX_DEPTH) && errorSplit) )
|
|
|
|
{
|
|
|
|
canSplit = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (canSplit) {
|
|
|
|
if (!kids[0]) {
|
|
|
|
// don't do anything if we can't handle anymore jobs
|
2013-03-17 10:22:59 -07:00
|
|
|
if( !Pi::jobs().canAddJob() ) {
|
2013-03-10 12:59:08 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-03-24 13:55:35 -07:00
|
|
|
mHasJobRequest = true;
|
|
|
|
SQuadSplitRequest *ssrd = new SQuadSplitRequest(v0, v1, v2, v3, centroid.Normalized(), m_depth,
|
2013-03-10 12:59:08 -07:00
|
|
|
geosphere->m_sbody->path, mPatchID, ctx->edgeLen,
|
2013-03-27 15:33:58 -07:00
|
|
|
ctx->frac, geosphere->m_terrain.Get());
|
2013-03-10 12:59:08 -07:00
|
|
|
assert(!mCurrentJob.Valid());
|
2013-03-20 13:21:26 -07:00
|
|
|
mCurrentJob.Reset(new QuadPatchJob(ssrd));
|
2013-03-24 14:23:38 -07:00
|
|
|
Pi::jobs().addJobMainThread(mCurrentJob.Get(), NULL);
|
2013-03-10 12:59:08 -07:00
|
|
|
} else {
|
|
|
|
for (int i=0; i<NUM_KIDS; i++) {
|
|
|
|
kids[i]->LODUpdate(campos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (canMerge) {
|
|
|
|
PiVerify(SDL_mutexP(m_kidsLock)==0);
|
|
|
|
for (int i=0; i<NUM_KIDS; i++) {
|
2013-03-17 10:22:59 -07:00
|
|
|
kids[i].Reset();
|
2013-03-10 12:59:08 -07:00
|
|
|
}
|
|
|
|
PiVerify(SDL_mutexV(m_kidsLock)!=-1);
|
|
|
|
}
|
|
|
|
}
|
2013-03-12 15:46:58 -07:00
|
|
|
|
2013-03-24 13:55:35 -07:00
|
|
|
void GeoPatch::RequestSinglePatch()
|
|
|
|
{
|
2013-03-25 16:02:33 -07:00
|
|
|
if( !heights.Valid() ) {
|
2013-03-24 13:55:35 -07:00
|
|
|
// don't do anything if we can't handle anymore jobs
|
|
|
|
if( !Pi::jobs().canAddJob() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mHasJobRequest = true;
|
|
|
|
SSingleSplitRequest *ssrd = new SSingleSplitRequest(v0, v1, v2, v3, centroid.Normalized(), m_depth,
|
2013-03-27 15:33:58 -07:00
|
|
|
geosphere->m_sbody->path, mPatchID, ctx->edgeLen, ctx->frac, geosphere->m_terrain.Get());
|
2013-03-24 13:55:35 -07:00
|
|
|
assert(!mCurrentJob.Valid());
|
|
|
|
mCurrentJob.Reset(new SinglePatchJob(ssrd));
|
2013-03-24 14:23:38 -07:00
|
|
|
Pi::jobs().addJobMainThread(mCurrentJob.Get(), NULL);
|
2013-03-24 13:55:35 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GeoPatch::ReceiveHeightmaps(const SQuadSplitResult *psr)
|
2013-03-10 12:59:08 -07:00
|
|
|
{
|
2013-03-14 14:52:10 -07:00
|
|
|
if (m_depth<psr->depth()) {
|
2013-03-10 12:59:08 -07:00
|
|
|
// this should work because each depth should have a common history
|
2013-03-14 14:52:10 -07:00
|
|
|
const uint32_t kidIdx = psr->data(0).patchID.GetPatchIdx(m_depth+1);
|
2013-03-10 12:59:08 -07:00
|
|
|
kids[kidIdx]->ReceiveHeightmaps(psr);
|
|
|
|
} else {
|
2013-03-24 13:55:35 -07:00
|
|
|
assert(mHasJobRequest);
|
2013-03-10 12:59:08 -07:00
|
|
|
const int nD = m_depth+1;
|
|
|
|
for (int i=0; i<NUM_KIDS; i++)
|
|
|
|
{
|
|
|
|
assert(NULL==kids[i]);
|
2013-03-24 13:55:35 -07:00
|
|
|
const SQuadSplitResult::SSplitResultData& data = psr->data(i);
|
2013-03-14 14:52:10 -07:00
|
|
|
assert(i==data.patchID.GetPatchIdx(nD));
|
|
|
|
assert(0==data.patchID.GetPatchIdx(nD+1));
|
2013-03-17 10:22:59 -07:00
|
|
|
kids[i].Reset(new GeoPatch(ctx, geosphere,
|
2013-03-14 14:52:10 -07:00
|
|
|
data.v0, data.v1, data.v2, data.v3,
|
2013-03-17 10:22:59 -07:00
|
|
|
nD, data.patchID));
|
2013-03-10 12:59:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// hm.. edges. Not right to pass this
|
|
|
|
// edgeFriend...
|
|
|
|
kids[0]->edgeFriend[0] = GetEdgeFriendForKid(0, 0);
|
2013-03-17 10:22:59 -07:00
|
|
|
kids[0]->edgeFriend[1] = kids[1].Get();
|
|
|
|
kids[0]->edgeFriend[2] = kids[3].Get();
|
2013-03-10 12:59:08 -07:00
|
|
|
kids[0]->edgeFriend[3] = GetEdgeFriendForKid(0, 3);
|
|
|
|
kids[1]->edgeFriend[0] = GetEdgeFriendForKid(1, 0);
|
|
|
|
kids[1]->edgeFriend[1] = GetEdgeFriendForKid(1, 1);
|
2013-03-17 10:22:59 -07:00
|
|
|
kids[1]->edgeFriend[2] = kids[2].Get();
|
|
|
|
kids[1]->edgeFriend[3] = kids[0].Get();
|
|
|
|
kids[2]->edgeFriend[0] = kids[1].Get();
|
2013-03-10 12:59:08 -07:00
|
|
|
kids[2]->edgeFriend[1] = GetEdgeFriendForKid(2, 1);
|
|
|
|
kids[2]->edgeFriend[2] = GetEdgeFriendForKid(2, 2);
|
2013-03-17 10:22:59 -07:00
|
|
|
kids[2]->edgeFriend[3] = kids[3].Get();
|
|
|
|
kids[3]->edgeFriend[0] = kids[0].Get();
|
|
|
|
kids[3]->edgeFriend[1] = kids[2].Get();
|
2013-03-10 12:59:08 -07:00
|
|
|
kids[3]->edgeFriend[2] = GetEdgeFriendForKid(3, 2);
|
|
|
|
kids[3]->edgeFriend[3] = GetEdgeFriendForKid(3, 3);
|
|
|
|
kids[0]->parent = kids[1]->parent = kids[2]->parent = kids[3]->parent = this;
|
|
|
|
|
|
|
|
for (int i=0; i<NUM_KIDS; i++)
|
|
|
|
{
|
2013-03-24 13:55:35 -07:00
|
|
|
const SQuadSplitResult::SSplitResultData& data = psr->data(i);
|
2013-03-25 16:02:33 -07:00
|
|
|
kids[i]->heights.Reset(data.heights);
|
2013-03-17 10:22:59 -07:00
|
|
|
kids[i]->normals.Reset(data.normals);
|
|
|
|
kids[i]->colors.Reset(data.colors);
|
2013-03-10 12:59:08 -07:00
|
|
|
}
|
|
|
|
PiVerify(SDL_mutexP(m_kidsLock)==0);
|
|
|
|
for (int i=0; i<NUM_EDGES; i++) { if(edgeFriend[i]) edgeFriend[i]->NotifyEdgeFriendSplit(this); }
|
|
|
|
for (int i=0; i<NUM_KIDS; i++) {
|
|
|
|
kids[i]->UpdateVBOs();
|
|
|
|
}
|
|
|
|
PiVerify(SDL_mutexV(m_kidsLock)!=-1);
|
|
|
|
assert(mCurrentJob.Valid());
|
2013-03-17 10:22:59 -07:00
|
|
|
delete mCurrentJob.Release();
|
2013-03-24 13:55:35 -07:00
|
|
|
mHasJobRequest = false;
|
2013-03-10 12:59:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-24 13:55:35 -07:00
|
|
|
void GeoPatch::ReceiveHeightmap(const SSingleSplitResult *psr)
|
2013-03-17 10:22:59 -07:00
|
|
|
{
|
2013-03-24 13:55:35 -07:00
|
|
|
assert(NULL==parent);
|
|
|
|
assert(mHasJobRequest);
|
2013-03-17 10:22:59 -07:00
|
|
|
{
|
2013-03-24 13:55:35 -07:00
|
|
|
const SSingleSplitResult::SSplitResultData& data = psr->data();
|
2013-03-25 16:02:33 -07:00
|
|
|
heights.Reset(data.heights);
|
2013-03-24 13:55:35 -07:00
|
|
|
normals.Reset(data.normals);
|
|
|
|
colors.Reset(data.colors);
|
2013-03-17 10:22:59 -07:00
|
|
|
}
|
2013-03-24 13:55:35 -07:00
|
|
|
assert(mCurrentJob.Valid());
|
|
|
|
delete mCurrentJob.Release();
|
|
|
|
mHasJobRequest = false;
|
2013-03-17 10:22:59 -07:00
|
|
|
}
|