1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "BaseGroundDrawer.h"
4
5 #include "Game/Camera.h"
6 #include "Game/GlobalUnsynced.h"
7 #include "Game/SelectedUnitsHandler.h"
8 #include "Game/UI/GuiHandler.h"
9 #include "Ground.h"
10 #include "HeightLinePalette.h"
11 #include "MetalMap.h"
12 #include "ReadMap.h"
13 #include "MapInfo.h"
14 #include "Rendering/IPathDrawer.h"
15 #include "Rendering/Env/ITreeDrawer.h"
16 #include "Sim/Misc/LosHandler.h"
17 #include "Sim/Misc/RadarHandler.h"
18 #include "System/Config/ConfigHandler.h"
19 #include "System/FastMath.h"
20 #include "System/myMath.h"
21
22 CONFIG(float, GroundLODScaleReflection).defaultValue(1.0f);
23 CONFIG(float, GroundLODScaleRefraction).defaultValue(1.0f);
24 CONFIG(float, GroundLODScaleTerrainReflection).defaultValue(1.0f);
25 CONFIG(bool, HighResLos).defaultValue(false).description("Controls whether LOS (\"L view\") edges are rendered in high resolution. Resource heavy!");
26 CONFIG(int, ExtraTextureUpdateRate).defaultValue(45);
27
CBaseGroundDrawer()28 CBaseGroundDrawer::CBaseGroundDrawer()
29 {
30 LODScaleReflection = configHandler->GetFloat("GroundLODScaleReflection");
31 LODScaleRefraction = configHandler->GetFloat("GroundLODScaleRefraction");
32 LODScaleTerrainReflection = configHandler->GetFloat("GroundLODScaleTerrainReflection");
33
34 memset(&infoTextureIDs[0], 0, sizeof(infoTextureIDs));
35
36 drawMode = drawNormal;
37 drawLineOfSight = false;
38 drawRadarAndJammer = true;
39 drawMapEdges = false;
40 drawDeferred = false;
41 wireframe = false;
42 advShading = false;
43 highResInfoTex = false;
44 updateTextureState = 0;
45
46 infoTexPBO.Bind();
47 infoTexPBO.New(gs->pwr2mapx * gs->pwr2mapy * 4);
48 infoTexPBO.Unbind();
49
50 highResInfoTexWanted = false;
51
52 highResLosTex = configHandler->GetBool("HighResLos");
53 extraTextureUpdateRate = std::max(4, configHandler->GetInt("ExtraTextureUpdateRate") - 1);
54
55 jamColor[0] = (int)(losColorScale * 0.25f);
56 jamColor[1] = (int)(losColorScale * 0.0f);
57 jamColor[2] = (int)(losColorScale * 0.0f);
58
59 losColor[0] = (int)(losColorScale * 0.15f);
60 losColor[1] = (int)(losColorScale * 0.05f);
61 losColor[2] = (int)(losColorScale * 0.40f);
62
63 radarColor[0] = (int)(losColorScale * 0.05f);
64 radarColor[1] = (int)(losColorScale * 0.15f);
65 radarColor[2] = (int)(losColorScale * -0.20f);
66
67 alwaysColor[0] = (int)(losColorScale * 0.25f);
68 alwaysColor[1] = (int)(losColorScale * 0.25f);
69 alwaysColor[2] = (int)(losColorScale * 0.25f);
70
71 heightLinePal = new CHeightLinePalette();
72 groundTextures = NULL;
73 }
74
75
~CBaseGroundDrawer()76 CBaseGroundDrawer::~CBaseGroundDrawer()
77 {
78 for (unsigned int n = 0; n < NUM_INFOTEXTURES; n++) {
79 glDeleteTextures(1, &infoTextureIDs[n]);
80 }
81
82 delete heightLinePal;
83 }
84
85
86
DrawTrees(bool drawReflection) const87 void CBaseGroundDrawer::DrawTrees(bool drawReflection) const
88 {
89 glEnable(GL_ALPHA_TEST);
90 glAlphaFunc(GL_GREATER, 0.005f);
91 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
92 glEnable(GL_BLEND);
93
94 if (treeDrawer->drawTrees) {
95 if (DrawExtraTex()) {
96 glActiveTextureARB(GL_TEXTURE1_ARB);
97 glEnable(GL_TEXTURE_2D);
98 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD_SIGNED_ARB);
99 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
100 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
101 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
102 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB);
103 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_TEXTURE);
104 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
105 glBindTexture(GL_TEXTURE_2D, infoTextureIDs[drawMode]);
106 SetTexGen(1.0f / (gs->pwr2mapx * SQUARE_SIZE), 1.0f / (gs->pwr2mapy * SQUARE_SIZE), 0, 0);
107 glActiveTextureARB(GL_TEXTURE0_ARB);
108 }
109
110 treeDrawer->Draw(drawReflection);
111
112 if (DrawExtraTex()) {
113 glActiveTextureARB(GL_TEXTURE1_ARB);
114 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
115 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);
116 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
117
118 glDisable(GL_TEXTURE_GEN_S);
119 glDisable(GL_TEXTURE_GEN_T);
120 glDisable(GL_TEXTURE_2D);
121 glActiveTextureARB(GL_TEXTURE0_ARB);
122 }
123 }
124
125 glDisable(GL_ALPHA_TEST);
126 glDisable(GL_BLEND);
127 }
128
129
130
131 // XXX this part of extra textures is a mess really ...
DisableExtraTexture()132 void CBaseGroundDrawer::DisableExtraTexture()
133 {
134 highResInfoTexWanted = (drawLineOfSight && highResLosTex);
135 updateTextureState = 0;
136
137 if (drawLineOfSight) {
138 // return to LOS-mode if it was active before
139 SetDrawMode(drawLos);
140
141 while (!UpdateExtraTexture(drawMode));
142 } else {
143 SetDrawMode(drawNormal);
144 }
145 }
146
147
SetHeightTexture()148 void CBaseGroundDrawer::SetHeightTexture()
149 {
150 if (drawMode == drawHeight) {
151 DisableExtraTexture();
152 } else {
153 SetDrawMode(drawHeight);
154
155 highResInfoTexWanted = true;
156 updateTextureState = 0;
157
158 while (!UpdateExtraTexture(drawMode));
159 }
160 }
161
162
163
SetMetalTexture()164 void CBaseGroundDrawer::SetMetalTexture()
165 {
166 if (drawMode == drawMetal) {
167 DisableExtraTexture();
168 } else {
169 SetDrawMode(drawMetal);
170
171 highResInfoTexWanted = false;
172 updateTextureState = 0;
173
174 while (!UpdateExtraTexture(drawMode));
175 }
176 }
177
178
TogglePathTexture(BaseGroundDrawMode mode)179 void CBaseGroundDrawer::TogglePathTexture(BaseGroundDrawMode mode)
180 {
181 switch (mode) {
182 case drawPathTrav:
183 case drawPathHeat:
184 case drawPathFlow:
185 case drawPathCost: {
186 if (drawMode == mode) {
187 DisableExtraTexture();
188 } else {
189 SetDrawMode(mode);
190
191 highResInfoTexWanted = false;
192 updateTextureState = 0;
193
194 while (!UpdateExtraTexture(drawMode));
195 }
196 } break;
197
198 default: {
199 } break;
200 }
201 }
202
203
ToggleLosTexture()204 void CBaseGroundDrawer::ToggleLosTexture()
205 {
206 if (drawMode == drawLos) {
207 drawLineOfSight = false;
208 DisableExtraTexture();
209 } else {
210 drawLineOfSight = true;
211
212 SetDrawMode(drawLos);
213 highResInfoTexWanted = highResLosTex;
214 updateTextureState = 0;
215
216 while (!UpdateExtraTexture(drawMode));
217 }
218 }
219
220
ToggleRadarAndJammer()221 void CBaseGroundDrawer::ToggleRadarAndJammer()
222 {
223 drawRadarAndJammer = !drawRadarAndJammer;
224
225 if (drawMode != drawLos)
226 return;
227
228 updateTextureState = 0;
229 while (!UpdateExtraTexture(drawMode));
230 }
231
232
233
InterpolateLos(const unsigned short * p,int xsize,int ysize,int mip,int factor,int x,int y)234 static inline int InterpolateLos(
235 const unsigned short* p,
236 int xsize,
237 int ysize,
238 int mip,
239 int factor,
240 int x,
241 int y
242 ) {
243 const int x1 = x >> mip;
244 const int y1 = y >> mip;
245 const int s1 = (p[(y1 * xsize) + x1] != 0); // top left
246 if (mip > 0) {
247 int x2 = x1 + 1;
248 int y2 = y1 + 1;
249 if (x2 >= xsize) { x2 = xsize - 1; }
250 if (y2 >= ysize) { y2 = ysize - 1; }
251 const int s2 = (p[(y1 * xsize) + x2] != 0); // top right
252 const int s3 = (p[(y2 * xsize) + x1] != 0); // bottom left
253 const int s4 = (p[(y2 * xsize) + x2] != 0); // bottom right
254 const int size = (1 << mip);
255 const float fracx = float(x % size) / size;
256 const float fracy = float(y % size) / size;
257 const float c1 = (s2 - s1) * fracx + s1;
258 const float c2 = (s4 - s3) * fracx + s3;
259 return factor * ((c2 - c1) * fracy + c1);
260 }
261 return factor * s1;
262 }
263
264
265 // Gradually calculate the extra texture based on updateTextureState:
266 // updateTextureState < extraTextureUpdateRate: Calculate the texture color values and copy them into buffer
267 // updateTextureState = extraTextureUpdateRate: Copy the buffer into a texture
268 // NOTE:
269 // when switching TO an info-texture drawing mode
270 // the texture is calculated in its entirety, not
271 // over multiple frames
UpdateExtraTexture(unsigned int texDrawMode)272 bool CBaseGroundDrawer::UpdateExtraTexture(unsigned int texDrawMode)
273 {
274 assert(texDrawMode != drawNormal);
275
276 if (mapInfo->map.voidWater && readMap->IsUnderWater())
277 return true;
278
279 if (updateTextureState < extraTextureUpdateRate) {
280 const int pwr2mapx_half = gs->pwr2mapx >> 1;
281
282 int starty;
283 int endy;
284 int offset;
285
286 // pointer to GPU-memory mapped into our address space
287 unsigned char* infoTexMem = NULL;
288
289 infoTexPBO.Bind();
290
291 if (highResInfoTexWanted) {
292 starty = updateTextureState * gs->mapy / extraTextureUpdateRate;
293 endy = (updateTextureState + 1) * gs->mapy / extraTextureUpdateRate;
294
295 offset = starty * gs->pwr2mapx * 4;
296 infoTexMem = reinterpret_cast<unsigned char*>(infoTexPBO.MapBuffer(offset, (endy - starty) * gs->pwr2mapx * 4));
297 } else {
298 starty = updateTextureState * gs->hmapy / extraTextureUpdateRate;
299 endy = (updateTextureState + 1) * gs->hmapy / extraTextureUpdateRate;
300
301 offset = starty * pwr2mapx_half * 4;
302 infoTexMem = reinterpret_cast<unsigned char*>(infoTexPBO.MapBuffer(offset, (endy - starty) * pwr2mapx_half * 4));
303 }
304
305 switch (texDrawMode) {
306 case drawPathTrav:
307 case drawPathHeat:
308 case drawPathFlow:
309 case drawPathCost: {
310 pathDrawer->UpdateExtraTexture(texDrawMode, starty, endy, offset, infoTexMem);
311 } break;
312
313 case drawMetal: {
314 const CMetalMap* metalMap = readMap->metalMap;
315
316 const unsigned short* myAirLos = &losHandler->airLosMaps[gu->myAllyTeam].front();
317 const unsigned char* extraTex = metalMap->GetDistributionMap();
318 const unsigned char* extraTexPal = metalMap->GetTexturePalette();
319 const float* extractDepthMap = metalMap->GetExtractionMap();
320
321 for (int y = starty; y < endy; ++y) {
322 const int y_pwr2mapx_half = y*pwr2mapx_half;
323 const int y_hmapx = y * gs->hmapx;
324
325 for (int x = 0; x < gs->hmapx; ++x) {
326 const int a = (y_pwr2mapx_half + x) * 4 - offset;
327 const int alx = ((x*2) >> losHandler->airMipLevel);
328 const int aly = ((y*2) >> losHandler->airMipLevel);
329
330 if (myAirLos[alx + (aly * losHandler->airSizeX)]) {
331 infoTexMem[a + COLOR_R] = (unsigned char)std::min(255.0f, 900.0f * fastmath::apxsqrt(fastmath::apxsqrt(extractDepthMap[y_hmapx + x])));
332 } else {
333 infoTexMem[a + COLOR_R] = 0;
334 }
335
336 infoTexMem[a + COLOR_G] = (extraTexPal[extraTex[y_hmapx + x]*3 + 1]);
337 infoTexMem[a + COLOR_B] = (extraTexPal[extraTex[y_hmapx + x]*3 + 2]);
338 infoTexMem[a + COLOR_A] = 255;
339 }
340 }
341
342 break;
343 }
344
345 case drawHeight: {
346 const unsigned char* extraTexPal = heightLinePal->GetData();
347
348 // NOTE:
349 // PBO/ExtraTexture resolution is always gs->pwr2mapx * gs->pwr2mapy
350 // (we don't use it fully) while the CORNER heightmap has dimensions
351 // (gs->mapx + 1) * (gs->mapy + 1). In case POT(gs->mapx) == gs->mapx
352 // we would therefore get a buffer overrun in our PBO when iterating
353 // over column (gs->mapx + 1) or row (gs->mapy + 1) so we select the
354 // easiest solution and just skip those squares.
355 const float* heightMap = readMap->GetCornerHeightMapUnsynced();
356
357 for (int y = starty; y < endy; ++y) {
358 const int y_pwr2mapx = y * gs->pwr2mapx;
359 const int y_mapx = y * gs->mapxp1;
360
361 for (int x = 0; x < gs->mapx; ++x) {
362 const float height = heightMap[y_mapx + x];
363 const unsigned int value = (((unsigned int)(height * 8.0f)) % 255) * 3;
364 const int i = (y_pwr2mapx + x) * 4 - offset;
365
366 infoTexMem[i + COLOR_R] = 64 + (extraTexPal[value ] >> 1);
367 infoTexMem[i + COLOR_G] = 64 + (extraTexPal[value + 1] >> 1);
368 infoTexMem[i + COLOR_B] = 64 + (extraTexPal[value + 2] >> 1);
369 infoTexMem[i + COLOR_A] = 255;
370 }
371 }
372
373 break;
374 }
375
376 case drawLos: {
377 const unsigned short* myLos = &losHandler->losMaps[gu->myAllyTeam].front();
378 const unsigned short* myAirLos = &losHandler->airLosMaps[gu->myAllyTeam].front();
379 const unsigned short* myRadar = &radarHandler->radarMaps[gu->myAllyTeam].front();
380 const unsigned short* myJammer = &radarHandler->jammerMaps[gu->myAllyTeam].front();
381 #ifdef RADARHANDLER_SONAR_JAMMER_MAPS
382 const unsigned short* mySonar = &radarHandler->sonarMaps[gu->myAllyTeam].front();
383 const unsigned short* mySonarJammer = &radarHandler->sonarJammerMaps[gu->myAllyTeam].front();
384 #endif
385
386 const int lowRes = highResInfoTexWanted ? 0 : -1;
387 const int endx = highResInfoTexWanted ? gs->mapx : gs->hmapx;
388 const int pwr2mapx = gs->pwr2mapx >> (-lowRes);
389 const int losSizeX = losHandler->losSizeX;
390 const int losSizeY = losHandler->losSizeY;
391 const int airSizeX = losHandler->airSizeX;
392 const int airSizeY = losHandler->airSizeY;
393 const int losMipLevel = losHandler->losMipLevel + lowRes;
394 const int airMipLevel = losHandler->airMipLevel + lowRes;
395
396 if (drawRadarAndJammer) {
397 const int rxsize = radarHandler->xsize;
398 const int rzsize = radarHandler->zsize;
399
400 for (int y = starty; y < endy; ++y) {
401 for (int x = 0; x < endx; ++x) {
402 int totalLos = 255;
403
404 if (!gs->globalLOS[gu->myAllyTeam]) {
405 const int inLos = InterpolateLos(myLos, losSizeX, losSizeY, losMipLevel, 128, x, y);
406 const int inAir = InterpolateLos(myAirLos, airSizeX, airSizeY, airMipLevel, 128, x, y);
407 totalLos = inLos + inAir;
408 }
409 #ifdef RADARHANDLER_SONAR_JAMMER_MAPS
410 const bool useRadar = (CGround::GetHeightReal(xPos, zPos, false) >= 0.0f);
411 const unsigned short* radarMap = useRadar ? myRadar : mySonar;
412 const unsigned short* jammerMap = useRadar ? myJammer : mySonarJammer;
413 #else
414 const unsigned short* radarMap = myRadar;
415 const unsigned short* jammerMap = myJammer;
416 #endif
417 const int inRadar = InterpolateLos(radarMap, rxsize, rzsize, 3 + lowRes, 255, x, y);
418 const int inJam = InterpolateLos(jammerMap, rxsize, rzsize, 3 + lowRes, 255, x, y);
419
420 const int a = ((y * pwr2mapx) + x) * 4 - offset;
421
422 for (int c = 0; c < 3; c++) {
423 int val = alwaysColor[c] * 255;
424 val += (jamColor[c] * inJam);
425 val += (losColor[c] * totalLos);
426 val += (radarColor[c] * inRadar);
427 infoTexMem[a + (2 - c)] = (val / losColorScale);
428 }
429
430 infoTexMem[a + COLOR_A] = 255;
431 }
432 }
433 } else {
434 for (int y = starty; y < endy; ++y) {
435 const int y_pwr2mapx = y * pwr2mapx;
436 for (int x = 0; x < endx; ++x) {
437 const int inLos = InterpolateLos(myLos, losSizeX, losSizeY, losMipLevel, 32, x, y);
438 const int inAir = InterpolateLos(myAirLos, airSizeX, airSizeY, airMipLevel, 32, x, y);
439 const int value = 64 + inLos + inAir;
440 const int a = (y_pwr2mapx + x) * 4 - offset;
441
442 infoTexMem[a + COLOR_R] = value;
443 infoTexMem[a + COLOR_G] = value;
444 infoTexMem[a + COLOR_B] = value;
445 infoTexMem[a + COLOR_A] = 255;
446 }
447 }
448 }
449 break;
450 }
451
452 case drawNormal: {
453 assert(false);
454 } break;
455 }
456
457 infoTexPBO.UnmapBuffer();
458 /*
459 glBindTexture(GL_TEXTURE_2D, infoTextureIDs[texDrawMode]);
460
461 if (highResInfoTex) {
462 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, starty, gs->pwr2mapx, (endy-starty), GL_BGRA, GL_UNSIGNED_BYTE, infoTexPBO.GetPtr(offset));
463 } else {
464 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, starty, gs->pwr2mapx>>1, (endy-starty), GL_BGRA, GL_UNSIGNED_BYTE, infoTexPBO.GetPtr(offset));
465 }
466 */
467
468 infoTexPBO.Unbind();
469 }
470
471
472 if (updateTextureState == extraTextureUpdateRate) {
473 // entire texture has been updated, now check if it
474 // needs to be regenerated as well (eg. if switching
475 // between textures of different resolutions)
476 //
477 if (infoTextureIDs[texDrawMode] != 0 && highResInfoTexWanted != highResInfoTex) {
478 glDeleteTextures(1, &infoTextureIDs[texDrawMode]);
479 infoTextureIDs[texDrawMode] = 0;
480 }
481
482 if (infoTextureIDs[texDrawMode] == 0) {
483 // generate new texture
484 infoTexPBO.Bind();
485 glGenTextures(1, &infoTextureIDs[texDrawMode]);
486 glBindTexture(GL_TEXTURE_2D, infoTextureIDs[texDrawMode]);
487
488 // XXX maybe use GL_RGB5 as internalformat?
489 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
490 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
491 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
492 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
493
494 if (highResInfoTexWanted) {
495 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, gs->pwr2mapx, gs->pwr2mapy, 0, GL_BGRA, GL_UNSIGNED_BYTE, infoTexPBO.GetPtr());
496 } else {
497 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, gs->pwr2mapx>>1, gs->pwr2mapy>>1, 0, GL_BGRA, GL_UNSIGNED_BYTE, infoTexPBO.GetPtr());
498 }
499
500 infoTexPBO.Invalidate();
501 infoTexPBO.Unbind();
502
503 highResInfoTex = highResInfoTexWanted;
504 updateTextureState = 0;
505 return true;
506 }
507
508 infoTexPBO.Bind();
509 glBindTexture(GL_TEXTURE_2D, infoTextureIDs[texDrawMode]);
510
511 if (highResInfoTex) {
512 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, gs->pwr2mapx, gs->pwr2mapy, GL_BGRA, GL_UNSIGNED_BYTE, infoTexPBO.GetPtr());
513 } else {
514 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, gs->pwr2mapx>>1, gs->pwr2mapy>>1, GL_BGRA, GL_UNSIGNED_BYTE, infoTexPBO.GetPtr());
515 }
516 infoTexPBO.Invalidate();
517 infoTexPBO.Unbind();
518
519 updateTextureState = 0;
520 return true;
521 }
522
523 updateTextureState++;
524 return false;
525 }
526
527
528
GetInfoTexSize() const529 int2 CBaseGroundDrawer::GetInfoTexSize() const
530 {
531 if (highResInfoTex)
532 return (int2(gs->pwr2mapx, gs->pwr2mapy));
533
534 return (int2(gs->pwr2mapx >> 1, gs->pwr2mapy >> 1));
535 }
536
537
UpdateCamRestraints(CCamera * cam)538 void CBaseGroundDrawer::UpdateCamRestraints(CCamera* cam)
539 {
540 // add restraints for camera sides
541 cam->GetFrustumSides(readMap->GetCurrMinHeight() - 100.0f, readMap->GetCurrMaxHeight() + 30.0f, SQUARE_SIZE);
542
543 // CAMERA DISTANCE IS ALREADY CHECKED IN CGroundDrawer::GridVisibility()!
544 /*
545 // add restraint for maximum view distance (use flat z-dir as side)
546 // this is supposed to prevent (far) terrain from first being drawn
547 // and then immediately z-clipped away
548 const float3& camDir3D = cam->forward;
549
550 // prevent colinearity in top-down view
551 if (math::fabs(camDir3D.dot(UpVector)) < 0.95f) {
552 float3 camDir2D = float3(camDir3D.x, 0.0f, camDir3D.z).SafeANormalize();
553 float3 camOffset = camDir2D * globalRendering->viewRange * 1.05f;
554
555 // FIXME magic constants
556 static const float miny = 0.0f;
557 static const float maxy = 255.0f / 3.5f;
558 cam->GetFrustumSide(camDir2D, camOffset, miny, maxy, SQUARE_SIZE, (camDir3D.y > 0.0f), false);
559 }
560 */
561 }
562