File: RoamMeshDrawer.cpp

package info (click to toggle)
spring 98.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 41,928 kB
  • ctags: 60,665
  • sloc: cpp: 356,167; ansic: 39,434; python: 12,228; java: 12,203; awk: 5,856; sh: 1,719; xml: 997; perl: 405; php: 253; objc: 194; makefile: 72; sed: 2
file content (403 lines) | stat: -rw-r--r-- 11,396 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */

//
// ROAM Simplistic Implementation
// Added to Spring by Peter Sarkozy (mysterme AT gmail DOT com)
// Billion thanks to Bryan Turner (Jan, 2000)
//                    brturn@bellsouth.net
//
// Based on the Tread Marks engine by Longbow Digital Arts
//                               (www.LongbowDigitalArts.com)
// Much help and hints provided by Seumas McNally, LDA.


#include "RoamMeshDrawer.h"
#include "Game/Camera.h"
#include "Map/ReadMap.h"
#include "Map/SMF/SMFReadMap.h"
#include "Map/SMF/SMFGroundDrawer.h"
#include "Rendering/GlobalRendering.h"
#include "Rendering/ShadowHandler.h"
#include "Sim/Misc/GlobalConstants.h"
#include "System/Rectangle.h"
#include "System/ThreadPool.h"
#include "System/TimeProfiler.h"
#include "System/Config/ConfigHandler.h"
#include "System/Log/ILog.h"

#ifdef DRAW_DEBUG_IN_MINIMAP
	#include "Game/UI/MiniMap.h"
#endif

#include <cmath>


// ---------------------------------------------------------------------
// Log Section
//
#define LOG_SECTION_ROAM "RoamMeshDrawer"
LOG_REGISTER_SECTION_GLOBAL(LOG_SECTION_ROAM)

// use the specific section for all LOG*() calls in this source file
#ifdef LOG_SECTION_CURRENT
	#undef LOG_SECTION_CURRENT
#endif
#define LOG_SECTION_CURRENT LOG_SECTION_ROAM


bool CRoamMeshDrawer::forceRetessellate = false;


// ---------------------------------------------------------------------
// Ctor
//
CRoamMeshDrawer::CRoamMeshDrawer(CSMFReadMap* rm, CSMFGroundDrawer* gd)
	: CEventClient("[CRoamMeshDrawer]", 271989, false)
	, smfReadMap(rm)
	, smfGroundDrawer(gd)
	, lastGroundDetail(0)
{
	eventHandler.AddClient(this);

	// Set ROAM upload mode (VA,DL,VBO)
	Patch::SwitchRenderMode(configHandler->GetInt("ROAM"));

	numPatchesX = gs->mapx / PATCH_SIZE;
	numPatchesY = gs->mapy / PATCH_SIZE;
	// assert((numPatchesX == smfReadMap->numBigTexX) && (numPatchesY == smfReadMap->numBigTexY));

	roamPatches.resize(numPatchesX * numPatchesY);
	patchVisGrid.resize(numPatchesX * numPatchesY, 0);

	// Initialize all terrain patches
	for (int Y = 0; Y < numPatchesY; ++Y) {
		for (int X = 0; X < numPatchesX; ++X) {
			Patch& patch = roamPatches[Y * numPatchesX + X];
			patch.Init(
					smfGroundDrawer,
					X * PATCH_SIZE,
					Y * PATCH_SIZE);
			patch.ComputeVariance();
		}
	}

	CTriNodePool::InitPools();
}

CRoamMeshDrawer::~CRoamMeshDrawer()
{
	configHandler->Set("ROAM", (int)Patch::renderMode);

	CTriNodePool::FreePools();
}


/**
 * Retessellates the current terrain
 */
void CRoamMeshDrawer::Update()
{
	//FIXME this retessellates with the current camera frustum, shadow pass and others don't have to see the same patches!

	// CCamera* cam = (inShadowPass)? camera: cam2;
	CCamera* cam = cam2;

	// Update Patch visibility
	Patch::UpdateVisibility(cam, roamPatches, numPatchesX);

	// Check if a retessellation is needed
#define RETESSELLATE_MODE 1
	bool retessellate = false;

	{
		SCOPED_TIMER("ROAM::ComputeVariance");
		for (int i = 0; i < (numPatchesX * numPatchesY); ++i) { //FIXME multithread?
			Patch& p = roamPatches[i];
		#if (RETESSELLATE_MODE == 2)
			if (p.IsVisible()) {
				if (patchVisGrid[i] == 0) {
					patchVisGrid[i] = 1;
					retessellate = true;
				}
				if (p.IsDirty()) {
					//FIXME don't retessellate on small heightmap changes?
					p.ComputeVariance();
					retessellate = true;
				}
			} else {
				patchVisGrid[i] = 0;
			}
		#else
			if (char(p.IsVisible()) != patchVisGrid[i]) {
				patchVisGrid[i] = char(p.IsVisible());
				retessellate = true;
			}
			if (p.IsVisible() && p.IsDirty()) {
				//FIXME don't retessellate on small heightmap changes?
				p.ComputeVariance();
				retessellate = true;
			}
		#endif
		}
	}

	// Further conditions that can cause a retessellation
#if (RETESSELLATE_MODE == 2)
	static const float maxCamDeltaDistSq = 500.0f * 500.0f;
	retessellate |= ((cam->GetPos() - lastCamPos).SqLength() > maxCamDeltaDistSq);
#endif
	retessellate |= forceRetessellate;
	retessellate |= (lastGroundDetail != smfGroundDrawer->GetGroundDetail());

	bool retessellateAgain = false;

	// Retessellate
	if (retessellate) {
		{ SCOPED_TIMER("ROAM::Tessellate");
			//FIXME this tessellates with current camera + viewRadius
			//  so it doesn't retessellate patches that are e.g. only vis. in the shadow frustum
			Reset();
			retessellateAgain = Tessellate(cam->GetPos(), smfGroundDrawer->GetGroundDetail());
		}

		{ SCOPED_TIMER("ROAM::GenerateIndexArray");
			for_mt(0, roamPatches.size(), [&](const int i){
				Patch* it = &roamPatches[i];
				if (it->IsVisible()) {
					it->GenerateIndices();
				}
			});
		}

		{ SCOPED_TIMER("ROAM::Upload");
			for (std::vector<Patch>::iterator it = roamPatches.begin(); it != roamPatches.end(); ++it) {
				if (it->IsVisible()) {
					it->Upload();
				}
			}
		}

		/*{
			int tricount = 0;
			for (std::vector<Patch>::iterator it = roamPatches.begin(); it != roamPatches.end(); it++) {
				if (it->IsVisible()) {
					tricount += it->GetTriCount();
				}
			}

			LOG_L(L_DEBUG, "ROAM dbg: Framechange, fram=%i tris=%i, viewrad=%i, cd=%f, camera=(%5.0f, %5.0f, %5.0f) camera2=  (%5.0f, %5.0f, %5.0f)",
				globalRendering->drawFrame,
				tricount,
				smfGroundDrawer->viewRadius,
				(cam->pos - lastCamPos).SqLength();,
				camera->GetPos().x,
				camera->GetPos().y,
				camera->GetPos().z,
				cam2->pos.x,
				cam2->pos.y,
				cam2->pos.z
				);
		}*/

		lastGroundDetail = smfGroundDrawer->GetGroundDetail();
		lastCamPos = cam->GetPos();
		forceRetessellate = retessellateAgain;
	}
}


// ---------------------------------------------------------------------
// Render Mesh
//
void CRoamMeshDrawer::DrawMesh(const DrawPass::e& drawPass)
{
	const bool inShadowPass = (drawPass == DrawPass::Shadow);

	// FIXME: this only updates the *visibilty* of patches
	//  It doesn't update the *tessellation*, neither are indices sent to the GPU.
	//  It just re-uses the last tessellation pattern which may have been created
	//  with a totally different camera.
	CCamera* cam = (inShadowPass)? camera: cam2;

	Patch::UpdateVisibility(cam, roamPatches, numPatchesX);

	for (std::vector<Patch>::iterator it = roamPatches.begin(); it != roamPatches.end(); ++it) {
		if (it->IsVisible()) {
			if (!inShadowPass)
				it->SetSquareTexture();

			it->Draw();
		}
	}
}

void CRoamMeshDrawer::DrawBorderMesh(const DrawPass::e& drawPass)
{
	const bool inShadowPass = (drawPass == DrawPass::Shadow);

	for (int py = 0; py < numPatchesY; ++py) {
		for (int px = 0; px < numPatchesX; ++px) {
			if (IsInteriorPatch(px, py))
				continue;

			Patch& p = roamPatches[py * numPatchesX + px];

			if (!p.IsVisible())
				continue;

			if (!inShadowPass)
				p.SetSquareTexture();

			p.DrawBorder();
		}
	}
}

#ifdef DRAW_DEBUG_IN_MINIMAP
void CRoamMeshDrawer::DrawInMiniMap()
{
	glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		glOrtho(0.0f, 1.0f, 0.0f, 1.0f, 0.0, -1.0);
		minimap->ApplyConstraintsMatrix();
	glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
		glTranslatef3(UpVector);
		glScalef(1.0f / gs->mapx, -1.0f / gs->mapy, 1.0f);

	glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
	for (std::vector<Patch>::iterator it = roamPatches.begin(); it != roamPatches.end(); ++it) {
		if (!it->IsVisible()) {
			glRectf(it->m_WorldX, it->m_WorldY, it->m_WorldX + PATCH_SIZE, it->m_WorldY + PATCH_SIZE);
		}
	}

	glMatrixMode(GL_PROJECTION);
		glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
}
#endif


// ---------------------------------------------------------------------
// Reset all patches, recompute variance if needed
//
void CRoamMeshDrawer::Reset()
{
	// Set the next free triangle pointer back to the beginning
	CTriNodePool::ResetAll();

	// Go through the patches performing resets, compute variances, and linking.
	for (int Y = 0; Y < numPatchesY; ++Y) {
		for (int X = 0; X < numPatchesX; ++X) {
			Patch& patch = roamPatches[Y * numPatchesX + X];

			// Reset the patch
			patch.Reset();

			// Link all the patches together. (leave borders NULL)
			if (X > 0)
				patch.GetBaseLeft()->LeftNeighbor = roamPatches[Y * numPatchesX + X - 1].GetBaseRight();

			if (X < (numPatchesX - 1))
				patch.GetBaseRight()->LeftNeighbor = roamPatches[Y * numPatchesX + X + 1].GetBaseLeft();

			if (Y > 0)
				patch.GetBaseLeft()->RightNeighbor = roamPatches[(Y - 1) * numPatchesX + X].GetBaseRight();

			if (Y < (numPatchesY - 1))
				patch.GetBaseRight()->RightNeighbor = roamPatches[(Y + 1) * numPatchesX + X].GetBaseLeft();
		}
	}
}

// ---------------------------------------------------------------------
// Create an approximate mesh of the landscape.
//
bool CRoamMeshDrawer::Tessellate(const float3& campos, int viewradius)
{
	// Perform Tessellation
	// hint: threading just helps a little with huge cpu usage in retessellation, still better than nothing

	//  _____
	// |0|_|_|..
	// |_|_|_|..
	// |_|_|8|..
	//  .....
	// split the patches in 3x3 sized blocks. The tessellation itself can
	// extend into the neighbor patches (check Patch::Split). So we could
	// not multi-thread the whole loop w/o mutexes (in ::Split).
	// But instead we take a safety distance between the thread's working
	// area (which is 2 patches), so they don't conflict with each other.
	bool forceTess = false;

	for (int idx = 0; idx < 9; ++idx) {
		for_mt(0, roamPatches.size(), [&](const int i){
			Patch* it = &roamPatches[i];

			const int X = it->m_WorldX;
			const int Z = it->m_WorldY;
			const int subindex = (X % 3) + (Z % 3) * 3;

			if ((subindex == idx) && it->IsVisible()) {
				if (!it->Tessellate(campos, viewradius))
					forceTess = true;
			}
		});

		if (forceTess)
			return true;
	}

	return false;
}

bool CRoamMeshDrawer::IsInteriorPatch(int px, int py) const {
	// using % will crash if numPatchesX == 1 or numPatchesY == 1
	if ((px == 0) || (px == numPatchesX - 1)) return false;
	if ((py == 0) || (py == numPatchesY - 1)) return false;
	return true;
}



// ---------------------------------------------------------------------
// UnsyncedHeightMapUpdate event
//
void CRoamMeshDrawer::UnsyncedHeightMapUpdate(const SRectangle& rect)
{
	const int margin = 2;
	const float INV_PATCH_SIZE = 1.0f / PATCH_SIZE;

	// hint: the -+1 are cause Patches share 1 pixel border (no vertex holes!)
	const int xstart = std::max(0,           (int)math::floor((rect.x1 - margin) * INV_PATCH_SIZE));
	const int xend   = std::min(numPatchesX, (int)math::ceil( (rect.x2 + margin) * INV_PATCH_SIZE));
	const int zstart = std::max(0,           (int)math::floor((rect.z1 - margin) * INV_PATCH_SIZE));
	const int zend   = std::min(numPatchesY, (int)math::ceil( (rect.z2 + margin) * INV_PATCH_SIZE));

	for (int z = zstart; z < zend; ++z) {
		for (int x = xstart; x < xend; ++x) {
			Patch& p = roamPatches[z * numPatchesX + x];

			// clamp the update rect to the patch constraints
			SRectangle prect(
				std::max(rect.x1 - margin - p.m_WorldX, 0),
				std::max(rect.z1 - margin - p.m_WorldY, 0),
				std::min(rect.x2 + margin - p.m_WorldX, PATCH_SIZE),
				std::min(rect.z2 + margin - p.m_WorldY, PATCH_SIZE)
			);

			p.UpdateHeightMap(prect);
		}
	}

	LOG_L(L_DEBUG, "UnsyncedHeightMapUpdate, fram=%i, numpatches=%i, xi1=%i xi2=%i zi1=%i zi2=%i, x1=%i x2=%i z1=%i z2=%i",
		globalRendering->drawFrame,
		(xend - xstart) * (zend - zstart),
		xstart, xend, zstart, zend,
		rect.x1, rect.x2, rect.z1, rect.z2
   	);
}