pioneer/src/GeoSphere.cpp

519 lines
15 KiB
C++

// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "GeoSphere.h"
#include "GameConfig.h"
#include "GeoPatch.h"
#include "GeoPatchContext.h"
#include "GeoPatchJobs.h"
#include "Pi.h"
#include "RefCounted.h"
#include "galaxy/AtmosphereParameters.h"
#include "galaxy/StarSystem.h"
#include "graphics/Frustum.h"
#include "graphics/Graphics.h"
#include "graphics/Material.h"
#include "graphics/RenderState.h"
#include "graphics/Renderer.h"
#include "graphics/Texture.h"
#include "graphics/TextureBuilder.h"
#include "graphics/VertexArray.h"
#include "perlin.h"
#include "utils.h"
#include "vcacheopt/vcacheopt.h"
#include <algorithm>
#include <deque>
RefCountedPtr<GeoPatchContext> GeoSphere::s_patchContext;
// must be odd numbers
static const int detail_edgeLen[5] = {
//7, 15, 25, 35, 55 -- old non power-of-2+1 values
// some detail settings duplicated intentionally
// in real terms provides only 3 settings
// however this value is still used for gas giants
// with 5 distinct settings elsewhere
9, 17, 17, 33, 33
};
static const double gs_targetPatchTriLength(100.0);
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()
{
std::sort(mQuadSplitRequests.begin(), mQuadSplitRequests.end(), [](TDistanceRequest &a, TDistanceRequest &b) { return a.mDistance < b.mDistance; });
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(matrix4x4f(trans)); //need to set this for the following line to work
matrix4x4d modv = matrix4x4d(renderer->GetTransform());
matrix4x4d proj = matrix4x4d(renderer->GetProjection());
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);
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 = 0.1 / campos.LengthSqr();
// 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(matrix4x4f(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 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;
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;
}
}