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 "GeoPatch.h"
5 
6 #include "GeoPatchContext.h"
7 #include "GeoPatchJobs.h"
8 #include "GeoSphere.h"
9 #include "MathUtil.h"
10 #include "Pi.h"
11 #include "RefCounted.h"
12 #include "Sphere.h"
13 #include "galaxy/SystemBody.h"
14 #include "graphics/Frustum.h"
15 #include "graphics/Graphics.h"
16 #include "graphics/Material.h"
17 #include "graphics/Renderer.h"
18 #include "graphics/VertexBuffer.h"
19 #include "perlin.h"
20 #include "vcacheopt/vcacheopt.h"
21 #include <algorithm>
22 #include <deque>
23 
24 #ifdef DEBUG_BOUNDING_SPHERES
25 #include "graphics/RenderState.h"
26 #endif
27 
28 // tri edge lengths
29 static const double GEOPATCH_SUBDIVIDE_AT_CAMDIST = 5.0;
30 
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_)31 GeoPatch::GeoPatch(const RefCountedPtr<GeoPatchContext> &ctx_, GeoSphere *gs,
32 	const vector3d &v0_, const vector3d &v1_, const vector3d &v2_, const vector3d &v3_,
33 	const int depth, const GeoPatchID &ID_) :
34 	m_ctx(ctx_),
35 	m_v0(v0_),
36 	m_v1(v1_),
37 	m_v2(v2_),
38 	m_v3(v3_),
39 	m_heights(nullptr),
40 	m_normals(nullptr),
41 	m_colors(nullptr),
42 	m_parent(nullptr),
43 	m_geosphere(gs),
44 	m_depth(depth),
45 	m_PatchID(ID_),
46 	m_HasJobRequest(false)
47 {
48 
49 	m_clipCentroid = (m_v0 + m_v1 + m_v2 + m_v3) * 0.25;
50 	m_centroid = m_clipCentroid.Normalized();
51 	m_clipRadius = 0.0;
52 	m_clipRadius = std::max(m_clipRadius, (m_v0 - m_clipCentroid).Length());
53 	m_clipRadius = std::max(m_clipRadius, (m_v1 - m_clipCentroid).Length());
54 	m_clipRadius = std::max(m_clipRadius, (m_v2 - m_clipCentroid).Length());
55 	m_clipRadius = std::max(m_clipRadius, (m_v3 - m_clipCentroid).Length());
56 	double distMult;
57 	if (m_geosphere->GetSystemBody()->GetType() < SystemBody::TYPE_PLANET_ASTEROID) {
58 		distMult = 10.0 / Clamp(m_depth, 1, 10);
59 	} else {
60 		distMult = 5.0 / Clamp(m_depth, 1, 5);
61 	}
62 	m_roughLength = GEOPATCH_SUBDIVIDE_AT_CAMDIST / pow(2.0, m_depth) * distMult;
63 	m_needUpdateVBOs = false;
64 }
65 
~GeoPatch()66 GeoPatch::~GeoPatch()
67 {
68 	m_HasJobRequest = false;
69 	for (int i = 0; i < NUM_KIDS; i++) {
70 		m_kids[i].reset();
71 	}
72 	m_heights.reset();
73 	m_normals.reset();
74 	m_colors.reset();
75 }
76 
UpdateVBOs(Graphics::Renderer * renderer)77 void GeoPatch::UpdateVBOs(Graphics::Renderer *renderer)
78 {
79 	PROFILE_SCOPED()
80 	if (m_needUpdateVBOs) {
81 		assert(renderer);
82 		m_needUpdateVBOs = false;
83 
84 		//create buffer and upload data
85 		Graphics::VertexBufferDesc vbd;
86 		vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION;
87 		vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3;
88 		vbd.attrib[1].semantic = Graphics::ATTRIB_NORMAL;
89 		vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_FLOAT3;
90 		vbd.attrib[2].semantic = Graphics::ATTRIB_DIFFUSE;
91 		vbd.attrib[2].format = Graphics::ATTRIB_FORMAT_UBYTE4;
92 		vbd.attrib[3].semantic = Graphics::ATTRIB_UV0;
93 		vbd.attrib[3].format = Graphics::ATTRIB_FORMAT_FLOAT2;
94 		vbd.numVertices = m_ctx->NUMVERTICES();
95 		vbd.usage = Graphics::BUFFER_USAGE_STATIC;
96 		m_vertexBuffer.reset(renderer->CreateVertexBuffer(vbd));
97 
98 		GeoPatchContext::VBOVertex *VBOVtxPtr = m_vertexBuffer->Map<GeoPatchContext::VBOVertex>(Graphics::BUFFER_MAP_WRITE);
99 		assert(m_vertexBuffer->GetDesc().stride == sizeof(GeoPatchContext::VBOVertex));
100 
101 		const Sint32 edgeLen = m_ctx->GetEdgeLen();
102 		const double frac = m_ctx->GetFrac();
103 		const double *pHts = m_heights.get();
104 		const vector3f *pNorm = m_normals.get();
105 		const Color3ub *pColr = m_colors.get();
106 
107 		double minh = DBL_MAX;
108 
109 		// ----------------------------------------------------
110 		// inner loops
111 		for (Sint32 y = 1; y < edgeLen - 1; y++) {
112 			for (Sint32 x = 1; x < edgeLen - 1; x++) {
113 				const double height = *pHts;
114 				minh = std::min(height, minh);
115 				const double xFrac = double(x - 1) * frac;
116 				const double yFrac = double(y - 1) * frac;
117 				const vector3d p((GetSpherePoint(xFrac, yFrac) * (height + 1.0)) - m_clipCentroid);
118 				m_clipRadius = std::max(m_clipRadius, p.Length());
119 
120 				GeoPatchContext::VBOVertex *vtxPtr = &VBOVtxPtr[x + (y * edgeLen)];
121 				vtxPtr->pos = vector3f(p);
122 				++pHts; // next height
123 
124 				const vector3f norma(pNorm->Normalized());
125 				vtxPtr->norm = norma;
126 				++pNorm; // next normal
127 
128 				vtxPtr->col[0] = pColr->r;
129 				vtxPtr->col[1] = pColr->g;
130 				vtxPtr->col[2] = pColr->b;
131 				vtxPtr->col[3] = 255;
132 				++pColr; // next colour
133 
134 				// uv coords
135 				vtxPtr->uv.x = 1.0f - xFrac;
136 				vtxPtr->uv.y = yFrac;
137 
138 				++vtxPtr; // next vertex
139 			}
140 		}
141 		const double minhScale = (minh + 1.0) * 0.999995;
142 		// ----------------------------------------------------
143 		const Sint32 innerLeft = 1;
144 		const Sint32 innerRight = edgeLen - 2;
145 		const Sint32 outerLeft = 0;
146 		const Sint32 outerRight = edgeLen - 1;
147 		// vertical edges
148 		// left-edge
149 		for (Sint32 y = 1; y < edgeLen - 1; y++) {
150 			const Sint32 x = innerLeft - 1;
151 			const double xFrac = double(x - 1) * frac;
152 			const double yFrac = double(y - 1) * frac;
153 			const vector3d p((GetSpherePoint(xFrac, yFrac) * minhScale) - m_clipCentroid);
154 
155 			GeoPatchContext::VBOVertex *vtxPtr = &VBOVtxPtr[outerLeft + (y * edgeLen)];
156 			GeoPatchContext::VBOVertex *vtxInr = &VBOVtxPtr[innerLeft + (y * edgeLen)];
157 			vtxPtr->pos = vector3f(p);
158 			vtxPtr->norm = vtxInr->norm;
159 			vtxPtr->col = vtxInr->col;
160 			vtxPtr->uv = vtxInr->uv;
161 		}
162 		// right-edge
163 		for (Sint32 y = 1; y < edgeLen - 1; y++) {
164 			const Sint32 x = innerRight + 1;
165 			const double xFrac = double(x - 1) * frac;
166 			const double yFrac = double(y - 1) * frac;
167 			const vector3d p((GetSpherePoint(xFrac, yFrac) * minhScale) - m_clipCentroid);
168 
169 			GeoPatchContext::VBOVertex *vtxPtr = &VBOVtxPtr[outerRight + (y * edgeLen)];
170 			GeoPatchContext::VBOVertex *vtxInr = &VBOVtxPtr[innerRight + (y * edgeLen)];
171 			vtxPtr->pos = vector3f(p);
172 			vtxPtr->norm = vtxInr->norm;
173 			vtxPtr->col = vtxInr->col;
174 			vtxPtr->uv = vtxInr->uv;
175 		}
176 		// ----------------------------------------------------
177 		const Sint32 innerTop = 1;
178 		const Sint32 innerBottom = edgeLen - 2;
179 		const Sint32 outerTop = 0;
180 		const Sint32 outerBottom = edgeLen - 1;
181 		// horizontal edges
182 		// top-edge
183 		for (Sint32 x = 1; x < edgeLen - 1; x++) {
184 			const Sint32 y = innerTop - 1;
185 			const double xFrac = double(x - 1) * frac;
186 			const double yFrac = double(y - 1) * frac;
187 			const vector3d p((GetSpherePoint(xFrac, yFrac) * minhScale) - m_clipCentroid);
188 
189 			GeoPatchContext::VBOVertex *vtxPtr = &VBOVtxPtr[x + (outerTop * edgeLen)];
190 			GeoPatchContext::VBOVertex *vtxInr = &VBOVtxPtr[x + (innerTop * edgeLen)];
191 			vtxPtr->pos = vector3f(p);
192 			vtxPtr->norm = vtxInr->norm;
193 			vtxPtr->col = vtxInr->col;
194 			vtxPtr->uv = vtxInr->uv;
195 		}
196 		// bottom-edge
197 		for (Sint32 x = 1; x < edgeLen - 1; x++) {
198 			const Sint32 y = innerBottom + 1;
199 			const double xFrac = double(x - 1) * frac;
200 			const double yFrac = double(y - 1) * frac;
201 			const vector3d p((GetSpherePoint(xFrac, yFrac) * minhScale) - m_clipCentroid);
202 
203 			GeoPatchContext::VBOVertex *vtxPtr = &VBOVtxPtr[x + (outerBottom * edgeLen)];
204 			GeoPatchContext::VBOVertex *vtxInr = &VBOVtxPtr[x + (innerBottom * edgeLen)];
205 			vtxPtr->pos = vector3f(p);
206 			vtxPtr->norm = vtxInr->norm;
207 			vtxPtr->col = vtxInr->col;
208 			vtxPtr->uv = vtxInr->uv;
209 		}
210 		// ----------------------------------------------------
211 		// corners
212 		{
213 			// top left
214 			GeoPatchContext::VBOVertex *tarPtr = &VBOVtxPtr[0];
215 			GeoPatchContext::VBOVertex *srcPtr = &VBOVtxPtr[1];
216 			(*tarPtr) = (*srcPtr);
217 		}
218 		{
219 			// top right
220 			GeoPatchContext::VBOVertex *tarPtr = &VBOVtxPtr[(edgeLen - 1)];
221 			GeoPatchContext::VBOVertex *srcPtr = &VBOVtxPtr[(edgeLen - 2)];
222 			(*tarPtr) = (*srcPtr);
223 		}
224 		{
225 			// bottom left
226 			GeoPatchContext::VBOVertex *tarPtr = &VBOVtxPtr[(edgeLen - 1) * edgeLen];
227 			GeoPatchContext::VBOVertex *srcPtr = &VBOVtxPtr[(edgeLen - 2) * edgeLen];
228 			(*tarPtr) = (*srcPtr);
229 		}
230 		{
231 			// bottom right
232 			GeoPatchContext::VBOVertex *tarPtr = &VBOVtxPtr[(edgeLen - 1) + ((edgeLen - 1) * edgeLen)];
233 			GeoPatchContext::VBOVertex *srcPtr = &VBOVtxPtr[(edgeLen - 1) + ((edgeLen - 2) * edgeLen)];
234 			(*tarPtr) = (*srcPtr);
235 		}
236 
237 		// ----------------------------------------------------
238 		// end of mapping
239 		m_vertexBuffer->Unmap();
240 
241 		// Don't need this anymore so throw it away
242 		m_normals.reset();
243 		m_colors.reset();
244 
245 #ifdef DEBUG_BOUNDING_SPHERES
246 		RefCountedPtr<Graphics::Material> mat(Pi::renderer->CreateMaterial(Graphics::MaterialDescriptor()));
247 		switch (m_depth) {
248 		case 0: mat->diffuse = Color::WHITE; break;
249 		case 1: mat->diffuse = Color::RED; break;
250 		case 2: mat->diffuse = Color::GREEN; break;
251 		case 3: mat->diffuse = Color::BLUE; break;
252 		default: mat->diffuse = Color::BLACK; break;
253 		}
254 		m_boundsphere.reset(new Graphics::Drawables::Sphere3D(Pi::renderer, mat, Pi::renderer->CreateRenderState(Graphics::RenderStateDesc()), 2, m_clipRadius));
255 #endif
256 	}
257 }
258 
259 // the default sphere we do the horizon culling against
260 static const SSphere s_sph;
Render(Graphics::Renderer * renderer,const vector3d & campos,const matrix4x4d & modelView,const Graphics::Frustum & frustum)261 void GeoPatch::Render(Graphics::Renderer *renderer, const vector3d &campos, const matrix4x4d &modelView, const Graphics::Frustum &frustum)
262 {
263 	PROFILE_SCOPED()
264 	// must update the VBOs to calculate the clipRadius...
265 	UpdateVBOs(renderer);
266 	// ...before doing the furstum culling that relies on it.
267 	if (!frustum.TestPoint(m_clipCentroid, m_clipRadius))
268 		return; // nothing below this patch is visible
269 
270 	// only want to horizon cull patches that can actually be over the horizon!
271 	const vector3d camDir(campos - m_clipCentroid);
272 	const vector3d camDirNorm(camDir.Normalized());
273 	const vector3d cenDir(m_clipCentroid.Normalized());
274 	const double dotProd = camDirNorm.Dot(cenDir);
275 
276 	if (dotProd < 0.25 && (camDir.LengthSqr() > (m_clipRadius * m_clipRadius))) {
277 		SSphere obj;
278 		obj.m_centre = m_clipCentroid;
279 		obj.m_radius = m_clipRadius;
280 
281 		if (!s_sph.HorizonCulling(campos, obj)) {
282 			return; // nothing below this patch is visible
283 		}
284 	}
285 
286 	if (m_kids[0]) {
287 		for (int i = 0; i < NUM_KIDS; i++)
288 			m_kids[i]->Render(renderer, campos, modelView, frustum);
289 	} else if (m_heights) {
290 		RefCountedPtr<Graphics::Material> mat = m_geosphere->GetSurfaceMaterial();
291 		Graphics::RenderState *rs = m_geosphere->GetSurfRenderState();
292 
293 		const vector3d relpos = m_clipCentroid - campos;
294 		renderer->SetTransform(matrix4x4f(modelView * matrix4x4d::Translation(relpos)));
295 
296 		Pi::statSceneTris += (m_ctx->GetNumTris());
297 		++Pi::statNumPatches;
298 
299 		// per-patch detail texture scaling value
300 		m_geosphere->GetMaterialParameters().patchDepth = m_depth;
301 
302 		renderer->DrawBufferIndexed(m_vertexBuffer.get(), m_ctx->GetIndexBuffer(), rs, mat.Get());
303 #ifdef DEBUG_BOUNDING_SPHERES
304 		if (m_boundsphere.get()) {
305 			renderer->SetWireFrameMode(true);
306 			m_boundsphere->Draw(renderer);
307 			renderer->SetWireFrameMode(false);
308 		}
309 #endif
310 		renderer->GetStats().AddToStatCount(Graphics::Stats::STAT_PATCHES, 1);
311 	}
312 }
313 
LODUpdate(const vector3d & campos,const Graphics::Frustum & frustum)314 void GeoPatch::LODUpdate(const vector3d &campos, const Graphics::Frustum &frustum)
315 {
316 	// there should be no LOD update when we have active split requests
317 	if (m_HasJobRequest)
318 		return;
319 
320 	bool canSplit = true;
321 	bool canMerge = bool(m_kids[0]);
322 
323 	// always split at first level
324 	double centroidDist = DBL_MAX;
325 	if (m_parent) {
326 		centroidDist = (campos - m_centroid).Length();		 // distance from camera to centre of the patch
327 		const bool tooFar = (centroidDist >= m_roughLength); // check if the distance is greater than the rough length, which is how far it should be before it can split
328 		if (m_depth >= std::min(GEOPATCH_MAX_DEPTH, m_geosphere->GetMaxDepth()) || tooFar) {
329 			canSplit = false; // we're too deep in the quadtree or too far away so cannot split
330 		}
331 	}
332 
333 	if (canSplit) {
334 		if (!m_kids[0]) {
335 			// Test if this patch is visible
336 			if (!frustum.TestPoint(m_clipCentroid, m_clipRadius))
337 				return; // nothing below this patch is visible
338 
339 			// only want to horizon cull patches that can actually be over the horizon!
340 			const vector3d camDir(campos - m_clipCentroid);
341 			const vector3d camDirNorm(camDir.Normalized());
342 			const vector3d cenDir(m_clipCentroid.Normalized());
343 			const double dotProd = camDirNorm.Dot(cenDir);
344 
345 			if (dotProd < 0.25 && (camDir.LengthSqr() > (m_clipRadius * m_clipRadius))) {
346 				SSphere obj;
347 				obj.m_centre = m_clipCentroid;
348 				obj.m_radius = m_clipRadius;
349 
350 				if (!s_sph.HorizonCulling(campos, obj)) {
351 					return; // nothing below this patch is visible
352 				}
353 			}
354 
355 			// we can see this patch so submit the jobs!
356 			assert(!m_HasJobRequest);
357 			m_HasJobRequest = true;
358 
359 			SQuadSplitRequest *ssrd = new SQuadSplitRequest(m_v0, m_v1, m_v2, m_v3, m_centroid.Normalized(), m_depth,
360 				m_geosphere->GetSystemBody()->GetPath(), m_PatchID, m_ctx->GetEdgeLen() - 2,
361 				m_ctx->GetFrac(), m_geosphere->GetTerrain());
362 
363 			// add to the GeoSphere to be processed at end of all LODUpdate requests
364 			m_geosphere->AddQuadSplitRequest(centroidDist, ssrd, this);
365 		} else {
366 			for (int i = 0; i < NUM_KIDS; i++) {
367 				m_kids[i]->LODUpdate(campos, frustum);
368 			}
369 		}
370 	} else if (canMerge) {
371 		for (int i = 0; i < NUM_KIDS; i++) {
372 			canMerge &= m_kids[i]->canBeMerged();
373 		}
374 		if (canMerge) {
375 			for (int i = 0; i < NUM_KIDS; i++) {
376 				m_kids[i].reset();
377 			}
378 		}
379 	}
380 }
381 
RequestSinglePatch()382 void GeoPatch::RequestSinglePatch()
383 {
384 	if (!m_heights) {
385 		assert(!m_HasJobRequest);
386 		m_HasJobRequest = true;
387 		SSingleSplitRequest *ssrd = new SSingleSplitRequest(m_v0, m_v1, m_v2, m_v3, m_centroid.Normalized(), m_depth,
388 			m_geosphere->GetSystemBody()->GetPath(), m_PatchID, m_ctx->GetEdgeLen() - 2, m_ctx->GetFrac(), m_geosphere->GetTerrain());
389 		m_job = Pi::GetAsyncJobQueue()->Queue(new SinglePatchJob(ssrd));
390 	}
391 }
392 
ReceiveHeightmaps(SQuadSplitResult * psr)393 void GeoPatch::ReceiveHeightmaps(SQuadSplitResult *psr)
394 {
395 	PROFILE_SCOPED()
396 	assert(NULL != psr);
397 	if (m_depth < psr->depth()) {
398 		// this should work because each depth should have a common history
399 		const Uint32 kidIdx = psr->data(0).patchID.GetPatchIdx(m_depth + 1);
400 		if (m_kids[kidIdx]) {
401 			m_kids[kidIdx]->ReceiveHeightmaps(psr);
402 		} else {
403 			psr->OnCancel();
404 		}
405 	} else {
406 		assert(m_HasJobRequest);
407 		const int newDepth = m_depth + 1;
408 		for (int i = 0; i < NUM_KIDS; i++) {
409 			assert(!m_kids[i]);
410 			const SQuadSplitResult::SSplitResultData &data = psr->data(i);
411 			assert(i == data.patchID.GetPatchIdx(newDepth));
412 			assert(0 == data.patchID.GetPatchIdx(newDepth + 1));
413 			m_kids[i].reset(new GeoPatch(m_ctx, m_geosphere,
414 				data.v0, data.v1, data.v2, data.v3,
415 				newDepth, data.patchID));
416 		}
417 		m_kids[0]->m_parent = m_kids[1]->m_parent = m_kids[2]->m_parent = m_kids[3]->m_parent = this;
418 
419 		for (int i = 0; i < NUM_KIDS; i++) {
420 			const SQuadSplitResult::SSplitResultData &data = psr->data(i);
421 			m_kids[i]->m_heights.reset(data.heights);
422 			m_kids[i]->m_normals.reset(data.normals);
423 			m_kids[i]->m_colors.reset(data.colors);
424 		}
425 		for (int i = 0; i < NUM_KIDS; i++) {
426 			m_kids[i]->NeedToUpdateVBOs();
427 		}
428 		m_HasJobRequest = false;
429 	}
430 }
431 
ReceiveHeightmap(const SSingleSplitResult * psr)432 void GeoPatch::ReceiveHeightmap(const SSingleSplitResult *psr)
433 {
434 	PROFILE_SCOPED()
435 	assert(nullptr == m_parent);
436 	assert(nullptr != psr);
437 	assert(m_HasJobRequest);
438 	{
439 		const SSingleSplitResult::SSplitResultData &data = psr->data();
440 		m_heights.reset(data.heights);
441 		m_normals.reset(data.normals);
442 		m_colors.reset(data.colors);
443 	}
444 	m_HasJobRequest = false;
445 }
446 
ReceiveJobHandle(Job::Handle job)447 void GeoPatch::ReceiveJobHandle(Job::Handle job)
448 {
449 	assert(!m_job.HasJob());
450 	m_job = static_cast<Job::Handle &&>(job);
451 }
452