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