1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3
4 #include "GeoSphere.h"
5
6 #include "GameConfig.h"
7 #include "GeoPatch.h"
8 #include "GeoPatchContext.h"
9 #include "GeoPatchJobs.h"
10 #include "Pi.h"
11 #include "RefCounted.h"
12 #include "galaxy/AtmosphereParameters.h"
13 #include "galaxy/StarSystem.h"
14 #include "graphics/Frustum.h"
15 #include "graphics/Graphics.h"
16 #include "graphics/Material.h"
17 #include "graphics/RenderState.h"
18 #include "graphics/Renderer.h"
19 #include "graphics/Texture.h"
20 #include "graphics/TextureBuilder.h"
21 #include "graphics/VertexArray.h"
22 #include "perlin.h"
23 #include "utils.h"
24 #include "vcacheopt/vcacheopt.h"
25 #include <algorithm>
26 #include <deque>
27
28 RefCountedPtr<GeoPatchContext> GeoSphere::s_patchContext;
29
30 // must be odd numbers
31 static const int detail_edgeLen[5] = {
32 //7, 15, 25, 35, 55 -- old non power-of-2+1 values
33 // some detail settings duplicated intentionally
34 // in real terms provides only 3 settings
35 // however this value is still used for gas giants
36 // with 5 distinct settings elsewhere
37 9, 17, 17, 33, 33
38 };
39
40 static const double gs_targetPatchTriLength(100.0);
41 static std::vector<GeoSphere *> s_allGeospheres;
42
Init()43 void GeoSphere::Init()
44 {
45 s_patchContext.Reset(new GeoPatchContext(detail_edgeLen[Pi::detail.planets > 4 ? 4 : Pi::detail.planets]));
46 }
47
Uninit()48 void GeoSphere::Uninit()
49 {
50 assert(s_patchContext.Unique());
51 s_patchContext.Reset();
52 }
53
print_info(const SystemBody * sbody,const Terrain * terrain)54 static void print_info(const SystemBody *sbody, const Terrain *terrain)
55 {
56 Output(
57 "%s:\n"
58 " height fractal: %s\n"
59 " colour fractal: %s\n"
60 " seed: %u\n",
61 sbody->GetName().c_str(), terrain->GetHeightFractalName(), terrain->GetColorFractalName(), sbody->GetSeed());
62 }
63
64 // static
UpdateAllGeoSpheres()65 void GeoSphere::UpdateAllGeoSpheres()
66 {
67 PROFILE_SCOPED()
68 for (std::vector<GeoSphere *>::iterator i = s_allGeospheres.begin(); i != s_allGeospheres.end(); ++i) {
69 (*i)->Update();
70 }
71 }
72
73 // static
OnChangeDetailLevel()74 void GeoSphere::OnChangeDetailLevel()
75 {
76 s_patchContext.Reset(new GeoPatchContext(detail_edgeLen[Pi::detail.planets > 4 ? 4 : Pi::detail.planets]));
77
78 // reinit the geosphere terrain data
79 for (std::vector<GeoSphere *>::iterator i = s_allGeospheres.begin(); i != s_allGeospheres.end(); ++i) {
80 // clearout anything we don't need
81 (*i)->Reset();
82
83 // reinit the terrain with the new settings
84 (*i)->m_terrain.Reset(Terrain::InstanceTerrain((*i)->GetSystemBody()));
85 print_info((*i)->GetSystemBody(), (*i)->m_terrain.Get());
86 }
87 }
88
89 //static
OnAddQuadSplitResult(const SystemPath & path,SQuadSplitResult * res)90 bool GeoSphere::OnAddQuadSplitResult(const SystemPath &path, SQuadSplitResult *res)
91 {
92 // Find the correct GeoSphere via it's system path, and give it the split result
93 for (std::vector<GeoSphere *>::iterator i = s_allGeospheres.begin(), iEnd = s_allGeospheres.end(); i != iEnd; ++i) {
94 if (path == (*i)->GetSystemBody()->GetPath()) {
95 (*i)->AddQuadSplitResult(res);
96 return true;
97 }
98 }
99 // GeoSphere not found to return the data to, cancel and delete it instead
100 if (res) {
101 res->OnCancel();
102 delete res;
103 }
104 return false;
105 }
106
107 //static
OnAddSingleSplitResult(const SystemPath & path,SSingleSplitResult * res)108 bool GeoSphere::OnAddSingleSplitResult(const SystemPath &path, SSingleSplitResult *res)
109 {
110 // Find the correct GeoSphere via it's system path, and give it the split result
111 for (std::vector<GeoSphere *>::iterator i = s_allGeospheres.begin(), iEnd = s_allGeospheres.end(); i != iEnd; ++i) {
112 if (path == (*i)->GetSystemBody()->GetPath()) {
113 (*i)->AddSingleSplitResult(res);
114 return true;
115 }
116 }
117 // GeoSphere not found to return the data to, cancel and delete it instead
118 if (res) {
119 res->OnCancel();
120 delete res;
121 }
122 return false;
123 }
124
Reset()125 void GeoSphere::Reset()
126 {
127 {
128 std::deque<SSingleSplitResult *>::iterator iter = mSingleSplitResults.begin();
129 while (iter != mSingleSplitResults.end()) {
130 // finally pass SplitResults
131 SSingleSplitResult *psr = (*iter);
132 assert(psr);
133
134 psr->OnCancel();
135
136 // tidyup
137 delete psr;
138
139 // Next!
140 ++iter;
141 }
142 mSingleSplitResults.clear();
143 }
144
145 {
146 std::deque<SQuadSplitResult *>::iterator iter = mQuadSplitResults.begin();
147 while (iter != mQuadSplitResults.end()) {
148 // finally pass SplitResults
149 SQuadSplitResult *psr = (*iter);
150 assert(psr);
151
152 psr->OnCancel();
153
154 // tidyup
155 delete psr;
156
157 // Next!
158 ++iter;
159 }
160 mQuadSplitResults.clear();
161 }
162
163 for (int p = 0; p < NUM_PATCHES; p++) {
164 // delete patches
165 if (m_patches[p]) {
166 m_patches[p].reset();
167 }
168 }
169
170 CalculateMaxPatchDepth();
171
172 m_initStage = eBuildFirstPatches;
173 }
174
GeoSphere(const SystemBody * body)175 GeoSphere::GeoSphere(const SystemBody *body) :
176 BaseSphere(body),
177 m_hasTempCampos(false),
178 m_tempCampos(0.0),
179 m_tempFrustum(800, 600, 0.5, 1.0, 1000.0),
180 m_initStage(eBuildFirstPatches),
181 m_maxDepth(0)
182 {
183 print_info(body, m_terrain.Get());
184
185 s_allGeospheres.push_back(this);
186
187 CalculateMaxPatchDepth();
188
189 //SetUpMaterials is not called until first Render since light count is zero :)
190 }
191
~GeoSphere()192 GeoSphere::~GeoSphere()
193 {
194 // update thread should not be able to access us now, so we can safely continue to delete
195 assert(std::count(s_allGeospheres.begin(), s_allGeospheres.end(), this) == 1);
196 s_allGeospheres.erase(std::find(s_allGeospheres.begin(), s_allGeospheres.end(), this));
197 }
198
AddQuadSplitResult(SQuadSplitResult * res)199 bool GeoSphere::AddQuadSplitResult(SQuadSplitResult *res)
200 {
201 bool result = false;
202 assert(res);
203 assert(mQuadSplitResults.size() < MAX_SPLIT_OPERATIONS);
204 if (mQuadSplitResults.size() < MAX_SPLIT_OPERATIONS) {
205 mQuadSplitResults.push_back(res);
206 result = true;
207 }
208 return result;
209 }
210
AddSingleSplitResult(SSingleSplitResult * res)211 bool GeoSphere::AddSingleSplitResult(SSingleSplitResult *res)
212 {
213 bool result = false;
214 assert(res);
215 assert(mSingleSplitResults.size() < MAX_SPLIT_OPERATIONS);
216 if (mSingleSplitResults.size() < MAX_SPLIT_OPERATIONS) {
217 mSingleSplitResults.push_back(res);
218 result = true;
219 }
220 return result;
221 }
222
ProcessSplitResults()223 void GeoSphere::ProcessSplitResults()
224 {
225 // now handle the single split results that define the base level of the quad tree
226 {
227 std::deque<SSingleSplitResult *>::iterator iter = mSingleSplitResults.begin();
228 while (iter != mSingleSplitResults.end()) {
229 // finally pass SplitResults
230 SSingleSplitResult *psr = (*iter);
231 assert(psr);
232
233 const int32_t faceIdx = psr->face();
234 if (m_patches[faceIdx]) {
235 m_patches[faceIdx]->ReceiveHeightmap(psr);
236 } else {
237 psr->OnCancel();
238 }
239
240 // tidyup
241 delete psr;
242
243 // Next!
244 ++iter;
245 }
246 mSingleSplitResults.clear();
247 }
248
249 // now handle the quad split results
250 {
251 std::deque<SQuadSplitResult *>::iterator iter = mQuadSplitResults.begin();
252 while (iter != mQuadSplitResults.end()) {
253 // finally pass SplitResults
254 SQuadSplitResult *psr = (*iter);
255 assert(psr);
256
257 const int32_t faceIdx = psr->face();
258 if (m_patches[faceIdx]) {
259 m_patches[faceIdx]->ReceiveHeightmaps(psr);
260 } else {
261 psr->OnCancel();
262 }
263
264 // tidyup
265 delete psr;
266
267 // Next!
268 ++iter;
269 }
270 mQuadSplitResults.clear();
271 }
272 }
273
BuildFirstPatches()274 void GeoSphere::BuildFirstPatches()
275 {
276 assert(!m_patches[0]);
277 if (m_patches[0])
278 return;
279
280 CalculateMaxPatchDepth();
281
282 // generate root face patches of the cube/sphere
283 static const vector3d p1 = (vector3d(1, 1, 1)).Normalized();
284 static const vector3d p2 = (vector3d(-1, 1, 1)).Normalized();
285 static const vector3d p3 = (vector3d(-1, -1, 1)).Normalized();
286 static const vector3d p4 = (vector3d(1, -1, 1)).Normalized();
287 static const vector3d p5 = (vector3d(1, 1, -1)).Normalized();
288 static const vector3d p6 = (vector3d(-1, 1, -1)).Normalized();
289 static const vector3d p7 = (vector3d(-1, -1, -1)).Normalized();
290 static const vector3d p8 = (vector3d(1, -1, -1)).Normalized();
291
292 const uint64_t maxShiftDepth = GeoPatchID::MAX_SHIFT_DEPTH;
293
294 m_patches[0].reset(new GeoPatch(s_patchContext, this, p1, p2, p3, p4, 0, (0ULL << maxShiftDepth)));
295 m_patches[1].reset(new GeoPatch(s_patchContext, this, p4, p3, p7, p8, 0, (1ULL << maxShiftDepth)));
296 m_patches[2].reset(new GeoPatch(s_patchContext, this, p1, p4, p8, p5, 0, (2ULL << maxShiftDepth)));
297 m_patches[3].reset(new GeoPatch(s_patchContext, this, p2, p1, p5, p6, 0, (3ULL << maxShiftDepth)));
298 m_patches[4].reset(new GeoPatch(s_patchContext, this, p3, p2, p6, p7, 0, (4ULL << maxShiftDepth)));
299 m_patches[5].reset(new GeoPatch(s_patchContext, this, p8, p7, p6, p5, 0, (5ULL << maxShiftDepth)));
300
301 for (int i = 0; i < NUM_PATCHES; i++) {
302 m_patches[i]->RequestSinglePatch();
303 }
304
305 m_initStage = eRequestedFirstPatches;
306 }
307
CalculateMaxPatchDepth()308 void GeoSphere::CalculateMaxPatchDepth()
309 {
310 const double circumference = 2.0 * M_PI * m_sbody->GetRadius();
311 // 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).
312 double edgeMetres = circumference / double(s_patchContext->GetEdgeLen() * 8);
313 // find out what depth we reach the desired resolution
314 while (edgeMetres > gs_targetPatchTriLength && m_maxDepth < GEOPATCH_MAX_DEPTH) {
315 edgeMetres *= 0.5;
316 ++m_maxDepth;
317 }
318 }
319
Update()320 void GeoSphere::Update()
321 {
322 switch (m_initStage) {
323 case eBuildFirstPatches:
324 BuildFirstPatches();
325 break;
326 case eRequestedFirstPatches: {
327 ProcessSplitResults();
328 uint8_t numValidPatches = 0;
329 for (int i = 0; i < NUM_PATCHES; i++) {
330 if (m_patches[i]->HasHeightData()) {
331 ++numValidPatches;
332 }
333 }
334 m_initStage = (NUM_PATCHES == numValidPatches) ? eReceivedFirstPatches : eRequestedFirstPatches;
335 } break;
336 case eReceivedFirstPatches: {
337 for (int i = 0; i < NUM_PATCHES; i++) {
338 m_patches[i]->NeedToUpdateVBOs();
339 }
340 m_initStage = eDefaultUpdateState;
341 } break;
342 case eDefaultUpdateState:
343 if (m_hasTempCampos) {
344 ProcessSplitResults();
345 for (int i = 0; i < NUM_PATCHES; i++) {
346 m_patches[i]->LODUpdate(m_tempCampos, m_tempFrustum);
347 }
348 ProcessQuadSplitRequests();
349 }
350 break;
351 }
352 }
353
AddQuadSplitRequest(double dist,SQuadSplitRequest * pReq,GeoPatch * pPatch)354 void GeoSphere::AddQuadSplitRequest(double dist, SQuadSplitRequest *pReq, GeoPatch *pPatch)
355 {
356 mQuadSplitRequests.push_back(TDistanceRequest(dist, pReq, pPatch));
357 }
358
ProcessQuadSplitRequests()359 void GeoSphere::ProcessQuadSplitRequests()
360 {
361 std::sort(mQuadSplitRequests.begin(), mQuadSplitRequests.end(), [](TDistanceRequest &a, TDistanceRequest &b) { return a.mDistance < b.mDistance; });
362
363 for (auto iter : mQuadSplitRequests) {
364 SQuadSplitRequest *ssrd = iter.mpRequest;
365 iter.mpRequester->ReceiveJobHandle(Pi::GetAsyncJobQueue()->Queue(new QuadPatchJob(ssrd)));
366 }
367 mQuadSplitRequests.clear();
368 }
369
Render(Graphics::Renderer * renderer,const matrix4x4d & modelView,vector3d campos,const float radius,const std::vector<Camera::Shadow> & shadows)370 void GeoSphere::Render(Graphics::Renderer *renderer, const matrix4x4d &modelView, vector3d campos, const float radius, const std::vector<Camera::Shadow> &shadows)
371 {
372 PROFILE_SCOPED()
373 // store this for later usage in the update method.
374 m_tempCampos = campos;
375 m_hasTempCampos = true;
376
377 if (m_initStage < eDefaultUpdateState)
378 return;
379
380 matrix4x4d trans = modelView;
381 trans.Translate(-campos.x, -campos.y, -campos.z);
382 renderer->SetTransform(matrix4x4f(trans)); //need to set this for the following line to work
383 matrix4x4d modv = matrix4x4d(renderer->GetTransform());
384 matrix4x4d proj = matrix4x4d(renderer->GetProjection());
385 Graphics::Frustum frustum(modv, proj);
386 m_tempFrustum = frustum;
387
388 // no frustum test of entire geosphere, since Space::Render does this
389 // for each body using its GetBoundingRadius() value
390
391 //First draw - create materials (they do not change afterwards)
392 if (!m_surfaceMaterial)
393 SetUpMaterials();
394
395 {
396 //Update material parameters
397 //XXX no need to calculate AP every frame
398 m_materialParameters.atmosphere = GetSystemBody()->CalcAtmosphereParams();
399 m_materialParameters.atmosphere.center = trans * vector3d(0.0);
400 m_materialParameters.atmosphere.planetRadius = radius;
401
402 m_materialParameters.shadows = shadows;
403
404 m_materialParameters.maxPatchDepth = GetMaxDepth();
405
406 m_surfaceMaterial->specialParameter0 = &m_materialParameters;
407
408 if (m_materialParameters.atmosphere.atmosDensity > 0.0) {
409 m_atmosphereMaterial->specialParameter0 = &m_materialParameters;
410
411 // make atmosphere sphere slightly bigger than required so
412 // that the edges of the pixel shader atmosphere jizz doesn't
413 // show ugly polygonal angles
414 DrawAtmosphereSurface(renderer, trans, campos,
415 m_materialParameters.atmosphere.atmosRadius * 1.01,
416 m_atmosRenderState, m_atmosphereMaterial);
417 }
418 }
419
420 Color ambient;
421 Color &emission = m_surfaceMaterial->emissive;
422
423 // save old global ambient
424 const Color oldAmbient = renderer->GetAmbientColor();
425
426 if ((GetSystemBody()->GetSuperType() == SystemBody::SUPERTYPE_STAR) || (GetSystemBody()->GetType() == SystemBody::TYPE_BROWN_DWARF)) {
427 // stars should emit light and terrain should be visible from distance
428 ambient.r = ambient.g = ambient.b = 51;
429 ambient.a = 255;
430 emission = StarSystem::starRealColors[GetSystemBody()->GetType()];
431 emission.a = 255;
432 }
433
434 else {
435 // give planet some ambient lighting if the viewer is close to it
436 double camdist = 0.1 / campos.LengthSqr();
437 // why the fuck is this returning 0.1 when we are sat on the planet??
438 // JJ: Because campos is relative to a unit-radius planet - 1.0 at the surface
439 // XXX oh well, it is the value we want anyway...
440 ambient.r = ambient.g = ambient.b = camdist * 255;
441 ambient.a = 255;
442 }
443
444 renderer->SetAmbientColor(ambient);
445
446 renderer->SetTransform(matrix4x4f(modelView));
447
448 for (int i = 0; i < NUM_PATCHES; i++) {
449 m_patches[i]->Render(renderer, campos, modelView, frustum);
450 }
451
452 renderer->SetAmbientColor(oldAmbient);
453
454 renderer->GetStats().AddToStatCount(Graphics::Stats::STAT_PLANETS, 1);
455 }
456
SetUpMaterials()457 void GeoSphere::SetUpMaterials()
458 {
459 //solid
460 Graphics::RenderStateDesc rsd;
461 m_surfRenderState = Pi::renderer->CreateRenderState(rsd);
462
463 //blended
464 rsd.blendMode = Graphics::BLEND_ALPHA_ONE;
465 rsd.cullMode = Graphics::CULL_NONE;
466 rsd.depthWrite = false;
467 m_atmosRenderState = Pi::renderer->CreateRenderState(rsd);
468
469 // Request material for this star or planet, with or without
470 // atmosphere. Separate material for surface and sky.
471 Graphics::MaterialDescriptor surfDesc;
472 const Uint32 effect_flags = m_terrain->GetSurfaceEffects();
473 if (effect_flags & Terrain::EFFECT_LAVA)
474 surfDesc.effect = Graphics::EFFECT_GEOSPHERE_TERRAIN_WITH_LAVA;
475 else if (effect_flags & Terrain::EFFECT_WATER)
476 surfDesc.effect = Graphics::EFFECT_GEOSPHERE_TERRAIN_WITH_WATER;
477 else
478 surfDesc.effect = Graphics::EFFECT_GEOSPHERE_TERRAIN;
479
480 if ((GetSystemBody()->GetType() == SystemBody::TYPE_BROWN_DWARF) ||
481 (GetSystemBody()->GetType() == SystemBody::TYPE_STAR_M)) {
482 //dim star (emits and receives light)
483 surfDesc.lighting = true;
484 surfDesc.quality &= ~Graphics::HAS_ATMOSPHERE;
485 } else if (GetSystemBody()->GetSuperType() == SystemBody::SUPERTYPE_STAR) {
486 //normal star
487 surfDesc.lighting = false;
488 surfDesc.quality &= ~Graphics::HAS_ATMOSPHERE;
489 surfDesc.effect = Graphics::EFFECT_GEOSPHERE_STAR;
490 } else {
491 //planetoid with or without atmosphere
492 const AtmosphereParameters ap(GetSystemBody()->CalcAtmosphereParams());
493 surfDesc.lighting = true;
494 if (ap.atmosDensity > 0.0) {
495 surfDesc.quality |= Graphics::HAS_ATMOSPHERE;
496 } else {
497 surfDesc.quality &= ~Graphics::HAS_ATMOSPHERE;
498 }
499 }
500
501 surfDesc.quality |= Graphics::HAS_ECLIPSES;
502 m_surfaceMaterial.Reset(Pi::renderer->CreateMaterial(surfDesc));
503
504 m_texHi.Reset(Graphics::TextureBuilder::Model("textures/high.dds").GetOrCreateTexture(Pi::renderer, "model"));
505 m_texLo.Reset(Graphics::TextureBuilder::Model("textures/low.dds").GetOrCreateTexture(Pi::renderer, "model"));
506 m_surfaceMaterial->texture0 = m_texHi.Get();
507 m_surfaceMaterial->texture1 = m_texLo.Get();
508
509 {
510 Graphics::MaterialDescriptor skyDesc;
511 skyDesc.effect = Graphics::EFFECT_GEOSPHERE_SKY;
512 skyDesc.lighting = true;
513 skyDesc.quality |= Graphics::HAS_ECLIPSES;
514 m_atmosphereMaterial.Reset(Pi::renderer->CreateMaterial(skyDesc));
515 m_atmosphereMaterial->texture0 = nullptr;
516 m_atmosphereMaterial->texture1 = nullptr;
517 }
518 }
519