Permalink
Fetching contributors…
Cannot retrieve contributors at this time
541 lines (459 sloc) 15.3 KB
// Copyright © 2008-2018 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "libs.h"
#include "GeoSphere.h"
#include "GeoPatchContext.h"
#include "GeoPatch.h"
#include "GeoPatchJobs.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/Texture.h"
#include "graphics/TextureBuilder.h"
#include "graphics/VertexArray.h"
#include "vcacheopt/vcacheopt.h"
#include <deque>
#include <algorithm>
RefCountedPtr<GeoPatchContext> GeoSphere::s_patchContext;
// must be odd numbers
static const int detail_edgeLen[5] = {
7, 15, 25, 35, 55
};
static const double gs_targetPatchTriLength(100.0);
#define PRINT_VECTOR(_v) Output("%f,%f,%f\n", (_v).x, (_v).y, (_v).z);
// static const int geo_sphere_edge_friends[NUM_PATCHES][4] = {
// { 3, 4, 1, 2 },
// { 0, 4, 5, 2 },
// { 0, 1, 5, 3 },
// { 0, 2, 5, 4 },
// { 0, 3, 5, 1 },
// { 1, 4, 3, 2 }
// };
static std::vector<GeoSphere*> s_allGeospheres;
void GeoSphere::Init()
{
s_patchContext.Reset(new GeoPatchContext(detail_edgeLen[Pi::detail.planets > 4 ? 4 : Pi::detail.planets]));
}
void GeoSphere::Uninit()
{
assert (s_patchContext.Unique());
s_patchContext.Reset();
}
static void print_info(const SystemBody *sbody, const Terrain *terrain)
{
Output(
"%s:\n"
" height fractal: %s\n"
" colour fractal: %s\n"
" seed: %u\n",
sbody->GetName().c_str(), terrain->GetHeightFractalName(), terrain->GetColorFractalName(), sbody->GetSeed());
}
// static
void GeoSphere::UpdateAllGeoSpheres()
{
PROFILE_SCOPED()
for(std::vector<GeoSphere*>::iterator i = s_allGeospheres.begin(); i != s_allGeospheres.end(); ++i)
{
(*i)->Update();
}
}
// static
void GeoSphere::OnChangeDetailLevel()
{
s_patchContext.Reset(new GeoPatchContext(detail_edgeLen[Pi::detail.planets > 4 ? 4 : Pi::detail.planets]));
// reinit the geosphere terrain data
for(std::vector<GeoSphere*>::iterator i = s_allGeospheres.begin(); i != s_allGeospheres.end(); ++i)
{
// clearout anything we don't need
(*i)->Reset();
// reinit the terrain with the new settings
(*i)->m_terrain.Reset(Terrain::InstanceTerrain((*i)->GetSystemBody()));
print_info((*i)->GetSystemBody(), (*i)->m_terrain.Get());
}
}
//static
bool GeoSphere::OnAddQuadSplitResult(const SystemPath &path, SQuadSplitResult *res)
{
// Find the correct GeoSphere via it's system path, and give it the split result
for(std::vector<GeoSphere*>::iterator i=s_allGeospheres.begin(), iEnd=s_allGeospheres.end(); i!=iEnd; ++i) {
if( path == (*i)->GetSystemBody()->GetPath() ) {
(*i)->AddQuadSplitResult(res);
return true;
}
}
// GeoSphere not found to return the data to, cancel and delete it instead
if( res ) {
res->OnCancel();
delete res;
}
return false;
}
//static
bool GeoSphere::OnAddSingleSplitResult(const SystemPath &path, SSingleSplitResult *res)
{
// Find the correct GeoSphere via it's system path, and give it the split result
for(std::vector<GeoSphere*>::iterator i=s_allGeospheres.begin(), iEnd=s_allGeospheres.end(); i!=iEnd; ++i) {
if( path == (*i)->GetSystemBody()->GetPath() ) {
(*i)->AddSingleSplitResult(res);
return true;
}
}
// GeoSphere not found to return the data to, cancel and delete it instead
if( res ) {
res->OnCancel();
delete res;
}
return false;
}
void GeoSphere::Reset()
{
{
std::deque<SSingleSplitResult*>::iterator iter = mSingleSplitResults.begin();
while(iter!=mSingleSplitResults.end())
{
// finally pass SplitResults
SSingleSplitResult *psr = (*iter);
assert(psr);
psr->OnCancel();
// tidyup
delete psr;
// Next!
++iter;
}
mSingleSplitResults.clear();
}
{
std::deque<SQuadSplitResult*>::iterator iter = mQuadSplitResults.begin();
while(iter!=mQuadSplitResults.end())
{
// finally pass SplitResults
SQuadSplitResult *psr = (*iter);
assert(psr);
psr->OnCancel();
// tidyup
delete psr;
// Next!
++iter;
}
mQuadSplitResults.clear();
}
for (int p=0; p<NUM_PATCHES; p++) {
// delete patches
if (m_patches[p]) {
m_patches[p].reset();
}
}
CalculateMaxPatchDepth();
m_initStage = eBuildFirstPatches;
}
GeoSphere::GeoSphere(const SystemBody *body) : BaseSphere(body),
m_hasTempCampos(false), m_tempCampos(0.0), m_tempFrustum(800, 600, 0.5, 1.0, 1000.0),
m_initStage(eBuildFirstPatches), m_maxDepth(0)
{
print_info(body, m_terrain.Get());
s_allGeospheres.push_back(this);
CalculateMaxPatchDepth();
//SetUpMaterials is not called until first Render since light count is zero :)
}
GeoSphere::~GeoSphere()
{
// update thread should not be able to access us now, so we can safely continue to delete
assert(std::count(s_allGeospheres.begin(), s_allGeospheres.end(), this) == 1);
s_allGeospheres.erase(std::find(s_allGeospheres.begin(), s_allGeospheres.end(), this));
}
bool GeoSphere::AddQuadSplitResult(SQuadSplitResult *res)
{
bool result = false;
assert(res);
assert(mQuadSplitResults.size()<MAX_SPLIT_OPERATIONS);
if(mQuadSplitResults.size()<MAX_SPLIT_OPERATIONS) {
mQuadSplitResults.push_back(res);
result = true;
}
return result;
}
bool GeoSphere::AddSingleSplitResult(SSingleSplitResult *res)
{
bool result = false;
assert(res);
assert(mSingleSplitResults.size()<MAX_SPLIT_OPERATIONS);
if(mSingleSplitResults.size()<MAX_SPLIT_OPERATIONS) {
mSingleSplitResults.push_back(res);
result = true;
}
return result;
}
void GeoSphere::ProcessSplitResults()
{
// now handle the single split results that define the base level of the quad tree
{
std::deque<SSingleSplitResult*>::iterator iter = mSingleSplitResults.begin();
while(iter!=mSingleSplitResults.end())
{
// finally pass SplitResults
SSingleSplitResult *psr = (*iter);
assert(psr);
const int32_t faceIdx = psr->face();
if( m_patches[faceIdx] ) {
m_patches[faceIdx]->ReceiveHeightmap(psr);
} else {
psr->OnCancel();
}
// tidyup
delete psr;
// Next!
++iter;
}
mSingleSplitResults.clear();
}
// now handle the quad split results
{
std::deque<SQuadSplitResult*>::iterator iter = mQuadSplitResults.begin();
while(iter!=mQuadSplitResults.end())
{
// finally pass SplitResults
SQuadSplitResult *psr = (*iter);
assert(psr);
const int32_t faceIdx = psr->face();
if( m_patches[faceIdx] ) {
m_patches[faceIdx]->ReceiveHeightmaps(psr);
} else {
psr->OnCancel();
}
// tidyup
delete psr;
// Next!
++iter;
}
mQuadSplitResults.clear();
}
}
void GeoSphere::BuildFirstPatches()
{
assert(!m_patches[0]);
if(m_patches[0])
return;
CalculateMaxPatchDepth();
// generate root face patches of the cube/sphere
static const vector3d p1 = (vector3d( 1, 1, 1)).Normalized();
static const vector3d p2 = (vector3d(-1, 1, 1)).Normalized();
static const vector3d p3 = (vector3d(-1,-1, 1)).Normalized();
static const vector3d p4 = (vector3d( 1,-1, 1)).Normalized();
static const vector3d p5 = (vector3d( 1, 1,-1)).Normalized();
static const vector3d p6 = (vector3d(-1, 1,-1)).Normalized();
static const vector3d p7 = (vector3d(-1,-1,-1)).Normalized();
static const vector3d p8 = (vector3d( 1,-1,-1)).Normalized();
const uint64_t maxShiftDepth = GeoPatchID::MAX_SHIFT_DEPTH;
m_patches[0].reset(new GeoPatch(s_patchContext, this, p1, p2, p3, p4, 0, (0ULL << maxShiftDepth)));
m_patches[1].reset(new GeoPatch(s_patchContext, this, p4, p3, p7, p8, 0, (1ULL << maxShiftDepth)));
m_patches[2].reset(new GeoPatch(s_patchContext, this, p1, p4, p8, p5, 0, (2ULL << maxShiftDepth)));
m_patches[3].reset(new GeoPatch(s_patchContext, this, p2, p1, p5, p6, 0, (3ULL << maxShiftDepth)));
m_patches[4].reset(new GeoPatch(s_patchContext, this, p3, p2, p6, p7, 0, (4ULL << maxShiftDepth)));
m_patches[5].reset(new GeoPatch(s_patchContext, this, p8, p7, p6, p5, 0, (5ULL << maxShiftDepth)));
for (int i=0; i<NUM_PATCHES; i++) {
m_patches[i]->RequestSinglePatch();
}
m_initStage = eRequestedFirstPatches;
}
void GeoSphere::CalculateMaxPatchDepth()
{
const double circumference = 2.0 * M_PI * m_sbody->GetRadius();
// calculate length of each edge segment (quad) times 4 due to that being the number around the sphere (1 per side, 4 sides for Root).
double edgeMetres = circumference / double(s_patchContext->GetEdgeLen() * 8);
// find out what depth we reach the desired resolution
while (edgeMetres>gs_targetPatchTriLength && m_maxDepth<GEOPATCH_MAX_DEPTH) {
edgeMetres *= 0.5;
++m_maxDepth;
}
}
void GeoSphere::Update()
{
switch(m_initStage)
{
case eBuildFirstPatches:
BuildFirstPatches();
break;
case eRequestedFirstPatches:
{
ProcessSplitResults();
uint8_t numValidPatches = 0;
for (int i=0; i<NUM_PATCHES; i++) {
if(m_patches[i]->HasHeightData()) {
++numValidPatches;
}
}
m_initStage = (NUM_PATCHES==numValidPatches) ? eReceivedFirstPatches : eRequestedFirstPatches;
} break;
case eReceivedFirstPatches:
{
for (int i=0; i<NUM_PATCHES; i++) {
m_patches[i]->NeedToUpdateVBOs();
}
m_initStage = eDefaultUpdateState;
} break;
case eDefaultUpdateState:
if(m_hasTempCampos) {
ProcessSplitResults();
for (int i=0; i<NUM_PATCHES; i++) {
m_patches[i]->LODUpdate(m_tempCampos, m_tempFrustum);
}
ProcessQuadSplitRequests();
}
break;
}
}
void GeoSphere::AddQuadSplitRequest(double dist, SQuadSplitRequest *pReq, GeoPatch *pPatch)
{
mQuadSplitRequests.push_back(TDistanceRequest(dist, pReq, pPatch));
}
void GeoSphere::ProcessQuadSplitRequests()
{
class RequestDistanceSort {
public:
bool operator()(const TDistanceRequest &a, const TDistanceRequest &b)
{
return a.mDistance < b.mDistance;
}
};
std::sort(mQuadSplitRequests.begin(), mQuadSplitRequests.end(), RequestDistanceSort());
for(auto iter : mQuadSplitRequests) {
SQuadSplitRequest *ssrd = iter.mpRequest;
iter.mpRequester->ReceiveJobHandle(Pi::GetAsyncJobQueue()->Queue(new QuadPatchJob(ssrd)));
}
mQuadSplitRequests.clear();
}
void GeoSphere::Render(Graphics::Renderer *renderer, const matrix4x4d &modelView, vector3d campos, const float radius, const std::vector<Camera::Shadow> &shadows)
{
PROFILE_SCOPED()
// store this for later usage in the update method.
m_tempCampos = campos;
m_hasTempCampos = true;
if(m_initStage < eDefaultUpdateState)
return;
matrix4x4d trans = modelView;
trans.Translate(-campos.x, -campos.y, -campos.z);
renderer->SetTransform(trans); //need to set this for the following line to work
matrix4x4d modv;
matrix4x4d proj;
matrix4x4ftod(renderer->GetCurrentModelView(), modv);
matrix4x4ftod(renderer->GetCurrentProjection(), proj);
Graphics::Frustum frustum( modv, proj );
m_tempFrustum = frustum;
// no frustum test of entire geosphere, since Space::Render does this
// for each body using its GetBoundingRadius() value
//First draw - create materials (they do not change afterwards)
if (!m_surfaceMaterial)
SetUpMaterials();
{
//Update material parameters
//XXX no need to calculate AP every frame
m_materialParameters.atmosphere = GetSystemBody()->CalcAtmosphereParams();
m_materialParameters.atmosphere.center = trans * vector3d(0.0, 0.0, 0.0);
m_materialParameters.atmosphere.planetRadius = radius;
m_materialParameters.shadows = shadows;
m_materialParameters.maxPatchDepth = GetMaxDepth();
m_surfaceMaterial->specialParameter0 = &m_materialParameters;
if (m_materialParameters.atmosphere.atmosDensity > 0.0) {
m_atmosphereMaterial->specialParameter0 = &m_materialParameters;
// make atmosphere sphere slightly bigger than required so
// that the edges of the pixel shader atmosphere jizz doesn't
// show ugly polygonal angles
DrawAtmosphereSurface(renderer, trans, campos,
m_materialParameters.atmosphere.atmosRadius*1.01,
m_atmosRenderState, m_atmosphereMaterial);
}
}
Color ambient;
Color &emission = m_surfaceMaterial->emissive;
// save old global ambient
const Color oldAmbient = renderer->GetAmbientColor();
if ((GetSystemBody()->GetSuperType() == SystemBody::SUPERTYPE_STAR) || (GetSystemBody()->GetType() == SystemBody::TYPE_BROWN_DWARF)) {
// stars should emit light and terrain should be visible from distance
ambient.r = ambient.g = ambient.b = 51;
ambient.a = 255;
emission = StarSystem::starRealColors[GetSystemBody()->GetType()];
emission.a = 255;
}
else {
// give planet some ambient lighting if the viewer is close to it
double camdist = campos.Length();
camdist = 0.1 / (camdist*camdist);
// why the fuck is this returning 0.1 when we are sat on the planet??
// JJ: Because campos is relative to a unit-radius planet - 1.0 at the surface
// XXX oh well, it is the value we want anyway...
ambient.r = ambient.g = ambient.b = camdist * 255;
ambient.a = 255;
}
renderer->SetAmbientColor(ambient);
renderer->SetTransform(modelView);
for (int i=0; i<NUM_PATCHES; i++) {
m_patches[i]->Render(renderer, campos, modelView, frustum);
}
renderer->SetAmbientColor(oldAmbient);
renderer->GetStats().AddToStatCount(Graphics::Stats::STAT_PLANETS, 1);
}
void GeoSphere::SetUpMaterials()
{
//solid
Graphics::RenderStateDesc rsd;
m_surfRenderState = Pi::renderer->CreateRenderState(rsd);
//blended
rsd.blendMode = Graphics::BLEND_ALPHA_ONE;
rsd.cullMode = Graphics::CULL_NONE;
rsd.depthWrite = false;
m_atmosRenderState = Pi::renderer->CreateRenderState(rsd);
// Request material for this star or planet, with or without
// atmosphere. Separate material for surface and sky.
Graphics::MaterialDescriptor surfDesc;
const Uint32 effect_flags = m_terrain->GetSurfaceEffects();
if (effect_flags & Terrain::EFFECT_LAVA)
surfDesc.effect = Graphics::EFFECT_GEOSPHERE_TERRAIN_WITH_LAVA;
else if (effect_flags & Terrain::EFFECT_WATER)
surfDesc.effect = Graphics::EFFECT_GEOSPHERE_TERRAIN_WITH_WATER;
else
surfDesc.effect = Graphics::EFFECT_GEOSPHERE_TERRAIN;
if ((GetSystemBody()->GetType() == SystemBody::TYPE_BROWN_DWARF) ||
(GetSystemBody()->GetType() == SystemBody::TYPE_STAR_M)) {
//dim star (emits and receives light)
surfDesc.lighting = true;
surfDesc.quality &= ~Graphics::HAS_ATMOSPHERE;
}
else if (GetSystemBody()->GetSuperType() == SystemBody::SUPERTYPE_STAR) {
//normal star
surfDesc.lighting = false;
surfDesc.quality &= ~Graphics::HAS_ATMOSPHERE;
surfDesc.effect = Graphics::EFFECT_GEOSPHERE_STAR;
} else {
//planetoid with or without atmosphere
const SystemBody::AtmosphereParameters ap(GetSystemBody()->CalcAtmosphereParams());
surfDesc.lighting = true;
if(ap.atmosDensity > 0.0) {
surfDesc.quality |= Graphics::HAS_ATMOSPHERE;
} else {
surfDesc.quality &= ~Graphics::HAS_ATMOSPHERE;
}
}
surfDesc.quality |= Graphics::HAS_ECLIPSES;
const bool bEnableDetailMaps = (Pi::config->Int("DisableDetailMaps") == 0);
if (bEnableDetailMaps) {
surfDesc.quality |= Graphics::HAS_DETAIL_MAPS;
}
m_surfaceMaterial.Reset(Pi::renderer->CreateMaterial(surfDesc));
m_texHi.Reset( Graphics::TextureBuilder::Model("textures/high.dds").GetOrCreateTexture(Pi::renderer, "model") );
m_texLo.Reset( Graphics::TextureBuilder::Model("textures/low.dds").GetOrCreateTexture(Pi::renderer, "model") );
m_surfaceMaterial->texture0 = m_texHi.Get();
m_surfaceMaterial->texture1 = m_texLo.Get();
{
Graphics::MaterialDescriptor skyDesc;
skyDesc.effect = Graphics::EFFECT_GEOSPHERE_SKY;
skyDesc.lighting = true;
skyDesc.quality |= Graphics::HAS_ECLIPSES;
m_atmosphereMaterial.Reset(Pi::renderer->CreateMaterial(skyDesc));
m_atmosphereMaterial->texture0 = nullptr;
m_atmosphereMaterial->texture1 = nullptr;
}
}