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