Fix issue in Rocket.lua script.
[Cafu-Engine.git] / CaTools / TerrainViewer.cpp
blobedac0da5a7a5645f8a5d6a1032e3503388a8d36d
1 /*
2 Cafu Engine, http://www.cafu.de/
3 Copyright (c) Carsten Fuchs and other contributors.
4 This project is licensed under the terms of the MIT license.
5 */
6 #include <stdio.h>
7 #include <float.h>
8 #include <string.h>
10 #include "ConsoleCommands/Console.hpp"
11 #include "ConsoleCommands/ConsoleStdout.hpp"
12 #include "FileSys/FileManImpl.hpp"
13 #include "MainWindow/glfwWindow.hpp"
14 #include "MaterialSystem/MapComposition.hpp"
15 #include "MaterialSystem/MaterialManager.hpp"
16 #include "MaterialSystem/MaterialManagerImpl.hpp"
17 #include "MaterialSystem/Mesh.hpp"
18 #include "MaterialSystem/Renderer.hpp"
19 #include "MaterialSystem/TextureMap.hpp"
20 #include "Math3D/Plane3.hpp"
21 #include "Math3D/Matrix.hpp"
22 #include "Templates/Array.hpp"
23 #include "Terrain/Terrain.hpp"
24 #include "Util/Util.hpp"
25 #include "PlatformAux.hpp"
27 #include "zlib.h"
29 #ifdef _WIN32
30 #define WIN32_LEAN_AND_MEAN
31 #include <windows.h>
32 #else
33 #include <dlfcn.h>
34 #define FreeLibrary dlclose
35 #endif
37 #include "GLFW/glfw3.h"
38 #include "tclap/CmdLine.h"
39 #include "tclap/StdOutput.h"
42 static cf::ConsoleStdoutT ConsoleStdout;
43 cf::ConsoleI* Console=&ConsoleStdout;
45 static cf::FileSys::FileManImplT FileManImpl;
46 cf::FileSys::FileManI* cf::FileSys::FileMan=&FileManImpl;
48 MaterialManagerI* MaterialManager=NULL;
51 #define DEG2RAD(x) ((3.1415927f / 180.0f) * (x))
52 #define SQR(x) ((x) * (x))
55 class ViewerWindowT : public cf::glfwWindowT
57 public:
59 ViewerWindowT(int width, int height, const char* title, GLFWmonitor* monitor=0)
60 : glfwWindowT(width, height, title, monitor)
62 VI.cull = true; // perform view culling when set
63 VI_morph = true; // perform geomorphing when set
66 void FramebufferSizeEvent(int width, int height) override
68 // The framebuffer's width and height are given in pixels (not in screen coordinates).
69 glViewport(0, 0, width, height);
71 // Avoid division by zero below.
72 if (height == 0)
73 return;
75 // Set the projection matrix.
76 // Note that the field of view is in y-direction, a value of 67.5° may correspond to 90° in x-direction.
77 const double FieldOfView = 67.5;
78 const double AspectRatio = double(width) / double(height);
79 const double Near = 100.0;
80 // const double Far = 100000.0;
81 const double cotanFOV = 1.0 / tan(FieldOfView / 2.0 / 180.0 * 3.14159265359);
83 // This is the OpenGL projection matrix with the "far" clip plane moved to infinity,
84 // according to the NVidia paper about robust stencil buffered shadows.
85 // Note that this matrix is actually transposed, as this is what 'glLoadMatrix()' expects.
86 const double ProjMatInf[4][4] = {
87 { cotanFOV/AspectRatio, 0.0, 0.0, 0.0 },
88 { 0.0, cotanFOV, 0.0, 0.0 },
89 { 0.0, 0.0, -1.0, -1.0 },
90 { 0.0, 0.0, -2.0*Near, 0.0 }
93 glMatrixMode(GL_PROJECTION);
94 glLoadMatrixd(&ProjMatInf[0][0]);
96 // A common (but not equivalent) alternative to the above:
97 // glLoadIdentity();
98 // gluPerspective(FieldOfView, AspectRatio, Near, Far);
101 void KeyEvent(int key, int scancode, int action, int mods) override
103 if (action != GLFW_PRESS)
104 return;
106 switch (key)
108 case GLFW_KEY_ESCAPE:
109 setShouldClose(true);
110 break;
112 case GLFW_KEY_C:
113 VI.cull = !VI.cull;
114 printf("View frustum culling is %s.\n", VI.cull ? "ON" : "OFF");
115 break;
117 case GLFW_KEY_M:
118 VI_morph = !VI_morph;
119 printf("Geo-morphing is %s\n", VI_morph ? "ON" : "OFF");
120 break;
125 public:
127 TerrainT::ViewInfoT VI;
128 bool VI_morph;
132 static void error_callback(int error, const char* description)
134 fprintf(stderr, "GLFW Error: %s\n", description);
138 int main(int ArgC, char* ArgV[])
140 // Initialize the FileMan by mounting the default file system.
141 // Note that specifying "./" (instead of "") as the file system description effectively prevents the use of
142 // absolute paths like "D:\abc\someDir\someFile.xy" or "/usr/bin/xy". This however should be fine for this application.
143 cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", "");
144 // cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE");
145 // cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE");
147 // Process the command line options.
148 // The "wireframe" material is defined in file "meta.cmat",
149 // SOARX is the new SOAR implementation.
150 TCLAP::StdOutput stdOutput(std::cout, std::cerr);
151 TCLAP::CmdLine cmd("Cafu Engine Terrain Viewer", stdOutput, ' ', "1.2");
153 // These may throw e.g. SpecificationException, but such exceptions are easily fixed permanently.
154 const TCLAP::ValueArg<std::string> TerrainName("t", "terrain", "Name of the terrain (heightmap image) to view.", true, "", "string", cmd);
155 const TCLAP::ValueArg<std::string> MaterialName("m", "material", "Name of the material to render the terrain with.", false, "wireframe", "string", cmd);
156 const TCLAP::ValueArg<std::string> BaseDirName("d", "base-dir", "Name of the base directory to search for material scripts.", false, "Games/DeathMatch", "string", cmd);
157 const TCLAP::SwitchArg BenchMarkMode("b", "benchmark", "Run program in benchmark mode.", cmd, false);
158 const TCLAP::SwitchArg UseSOARX("s", "soarx", "Use the SOARX implementation of the SOAR algorithm.", cmd, false);
160 TCLAP::VersionVisitor vv(&cmd, stdOutput);
161 const TCLAP::SwitchArg argVersion("", "version", "Displays version information and exits.", cmd, false, &vv);
163 TCLAP::HelpVisitor hv(&cmd, stdOutput);
164 const TCLAP::SwitchArg argHelp("h", "help", "Displays usage information and exits.", cmd, false, &hv);
168 cmd.parse(ArgC, ArgV);
170 catch (const TCLAP::ExitException& ee)
172 // ExitException is thrown after --help or --version was handled.
173 return ee.getExitStatus();
175 catch (const TCLAP::ArgException& e)
177 cmd.getOutput().failure(cmd, e, true);
178 return -1;
181 // Setup the global Material Manager pointer.
182 static MaterialManagerImplT MatManImpl;
184 MaterialManager=&MatManImpl;
186 // Register the material script files with the material manager.
187 MaterialManager->RegisterMaterialScriptsInDir(BaseDirName.getValue() + "/Materials", BaseDirName.getValue() + "/");
189 // Get the desired material.
190 MaterialT* TerrainMaterial=MaterialManager->GetMaterial(MaterialName.getValue());
192 if (TerrainMaterial==NULL)
194 printf("Sorry, material \"%s\" could not be retrieved\n", MaterialName.getValue().c_str());
195 printf("from the registered material script files. Possible causes:\n");
196 printf(" - the material is not defined in any of the script files (material name typo?)\n");
197 printf(" - the material script file(s) could not be opened (script file name typo?)\n");
198 printf(" - the material script file contains bugs, i.e. syntax errors.\n");
200 return 0;
206 const unsigned long FRAMES_FOR_BENCHMARK=1000;
207 const Vector3fT TerrainResolution(160.0f, 160.0f, 50.0f*255.0f);
208 TerrainT TerrainNew(TerrainName.getValue().c_str(), TerrainResolution);
209 const double STEPDIST_FOR_BENCHMARK=TerrainNew.GetSize()*1.2*TerrainResolution.x/FRAMES_FOR_BENCHMARK;
212 glfwSetErrorCallback(error_callback);
214 if (!glfwInit())
215 return -1;
217 // The default values for the window creations hints look just right for our purposes,
218 // see http://www.glfw.org/docs/latest/window_guide.html#window_hints_values for details.
219 ViewerWindowT win(1280, 1024, "Cafu Terrain Viewer 1.3", BenchMarkMode.getValue() ? glfwGetPrimaryMonitor() : NULL);
221 // TODO: Set a taskbar icon?
222 win.makeContextCurrent();
223 win.triggerFramebufferSizeEvent();
225 glfwSwapInterval(1); // enable v-sync
227 // Get the renderer with the highest preference number that is supported.
228 HMODULE RendererDLL;
229 MatSys::Renderer=PlatformAux::GetBestRenderer(RendererDLL);
231 if (MatSys::Renderer==NULL || RendererDLL==NULL)
232 throw std::runtime_error("No renderer loaded.");
234 MatSys::Renderer->Initialize();
237 // Get the TextureMapManager from the RendererDLL.
238 MatSys::TextureMapManagerI* TextureMapManager=PlatformAux::GetTextureMapManager(RendererDLL);
240 if (TextureMapManager==NULL)
242 FreeLibrary(RendererDLL);
243 throw std::runtime_error("No TextureMapManager obtained.");
246 MatSys::RenderMaterialT* TerrainRenderMat=MatSys::Renderer->RegisterMaterial(TerrainMaterial);
248 MatSys::Renderer->SetCurrentMaterial(TerrainRenderMat);
251 // As the terrain shaders require (cmat materials to specify) a lightmap, provide one here.
252 char Data[]={ 255, 255, 255, 255, 255, 255, 0, 0,
253 255, 255, 255, 255, 255, 255, 0, 0 };
255 MatSys::Renderer->SetCurrentLightMap(TextureMapManager->GetTextureMap2D(Data, 2, 2, 3, true, MapCompositionT(MapCompositionT::Linear, MapCompositionT::Linear)));
256 MatSys::Renderer->SetCurrentLightDirMap(NULL); // The MatSys provides a default for LightDirMaps when NULL is set.
258 MatSys::Renderer->ClearColor(0.0f, 0.0f, 0.4f, 0.0f);
262 /* This is how it used to be.
263 We now try it via the vertex indices directly... */
265 const float res0=160.0;
266 const float res1=160.0;
268 // BBmin and BBmax are BBs for the xy-plane, z is not needed here.
269 // Also note that the y-components have been multiplied by -1, or otherwise the texture gets flipped.
270 const float BBmin[2]={ -res0*0.5f*(TerrainNew.GetSize()-1), res1*0.5f*(TerrainNew.GetSize()-1) };
271 const float BBmax[2]={ res0*0.5f*(TerrainNew.GetSize()-1), -res1*0.5f*(TerrainNew.GetSize()-1) };
273 const float Plane1[4]={ 1.0f/(BBmax[0]-BBmin[0]), 0.0f, 0.0f, -BBmin[0]/(BBmax[0]-BBmin[0]) };
274 const float Plane2[4]={ 0.0f, 1.0f/(BBmax[1]-BBmin[1]), 0.0f, -BBmin[1]/(BBmax[1]-BBmin[1]) };
276 MatSys::Renderer->SetGenPurposeRenderingParam( 4, Plane1[0]);
277 MatSys::Renderer->SetGenPurposeRenderingParam( 5, Plane1[1]);
278 MatSys::Renderer->SetGenPurposeRenderingParam( 6, Plane1[2]);
279 MatSys::Renderer->SetGenPurposeRenderingParam( 7, Plane1[3]);
280 MatSys::Renderer->SetGenPurposeRenderingParam( 8, Plane2[0]);
281 MatSys::Renderer->SetGenPurposeRenderingParam( 9, Plane2[1]);
282 MatSys::Renderer->SetGenPurposeRenderingParam(10, Plane2[2]);
283 MatSys::Renderer->SetGenPurposeRenderingParam(11, Plane2[3]);
287 TerrainT::ViewInfoT& VI = win.VI;
289 // Master Game Loop
290 TimerT Timer;
291 unsigned long FrameCounter=0;
292 unsigned long GeomCRC=adler32(0, NULL, 0); // We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
293 Vector3fT ViewerPos=BenchMarkMode.getValue() ? Vector3fT(TerrainNew.GetVertices()[0])+Vector3fT(0, 0, 4000.0f) : Vector3fT(0, -500.0f, 1000.0f);
294 float Heading=BenchMarkMode.getValue() ? 55.0f : 0.0f;
295 float Pitch =BenchMarkMode.getValue() ? 25.0f : 0.0f;
297 while (!win.shouldClose())
299 MatSys::Renderer->BeginFrame(Timer.GetSecondsSinceCtor());
301 MatSys::Renderer->SetMatrix(MatSys::RendererI::WORLD_TO_VIEW, MatrixT::GetRotateXMatrix(-90.0f));
302 MatSys::Renderer->RotateX (MatSys::RendererI::WORLD_TO_VIEW, Pitch );
303 MatSys::Renderer->RotateZ (MatSys::RendererI::WORLD_TO_VIEW, Heading);
304 MatSys::Renderer->Translate(MatSys::RendererI::WORLD_TO_VIEW, -ViewerPos.x, -ViewerPos.y, -ViewerPos.z);
307 const float tau =4.0; // Error tolerance in pixel.
308 // const float tau_min=tau;
309 // const float tau_max=VI_morph ? (3.0/2.0)*tau_min : tau_min;
310 // The basic formula for fov_x is from soar/main.c, reshape_callback function, rearranged for fov_x.
311 // TODO: The 67.5 is a fixed value from framebuffer_size_callback() above.
312 unsigned int width;
313 unsigned int height;
315 win.getFramebufferSize(width, height); // This is the window size in pixels.
317 // printf("%u x %u %u x %u\n", width, height, w_, h_);
318 const float fov_x = 2.0f*atan(float(width)/float(height) * tan(DEG2RAD(67.5f)/2.0f));
319 const float kappa = tau / width * fov_x;
321 VI.nu =kappa>0.0 ? 1.0f/kappa : FLT_MAX; // inverse of error tolerance in radians
322 VI.nu_min=2.0f/3.0f*VI.nu; // lower morph parameter
323 VI.nu_max= VI.nu; // upper morph parameter
325 VI.viewpoint=ViewerPos;
327 // Set up VI.viewplanes (clip planes) for view frustum culling.
328 MatrixT mpv=MatSys::Renderer->GetMatrix(MatSys::Renderer->PROJECTION)*MatSys::Renderer->GetMatrixModelView();
330 // Compute view frustum planes.
331 for (unsigned long i=0; i<5; i++)
333 // m can be used to easily minify / shrink the view frustum.
334 // The values should be between 0 and 8: 0 is the default (no minification), 8 is the reasonable maximum.
335 const char m = 0;
336 const float d = (i < 4) ? 1.0f - 0.75f*m/8.0f : 1.0f;
337 float plane[4];
339 for (unsigned long j=0; j<4; j++)
340 plane[j]=((i & 1) ? mpv.m[i/2][j] : -mpv.m[i/2][j]) - d*mpv.m[3][j];
342 const float l=sqrt(SQR(plane[0])+SQR(plane[1])+SQR(plane[2]));
344 VI.viewplanes[i]=Plane3fT(Vector3fT(plane[0]/l, plane[1]/l, plane[2]/l), -plane[3]/l);
348 static MatSys::MeshT TerrainMesh(MatSys::MeshT::TriangleStrip);
349 TerrainMesh.Vertices.Overwrite();
351 if (UseSOARX.getValue())
353 ArrayT<Vector3fT>& VectorStrip=TerrainNew.ComputeVectorStrip(VI);
355 // With SOARX, the vector strip begins as usual and as expected with the first vector.
356 for (unsigned long VNr=0; VNr<VectorStrip.Size(); VNr++)
358 TerrainMesh.Vertices.PushBackEmpty();
359 TerrainMesh.Vertices[VNr].SetOrigin(VectorStrip[VNr]);
361 // Update the geometry-CRC. We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
362 GeomCRC=adler32(GeomCRC, (Bytef*)&VectorStrip[VNr].z, sizeof(VectorStrip[VNr].z));
365 else
367 if (win.VI_morph)
369 ArrayT<Vector3fT>& VectorStripNew=TerrainNew.ComputeVectorStripByMorphing(VI);
371 // Note that the first VectorT at VectorStrip[0] must be skipped!
372 for (unsigned long VNr=1; VNr<VectorStripNew.Size(); VNr++)
374 TerrainMesh.Vertices.PushBackEmpty();
375 TerrainMesh.Vertices[TerrainMesh.Vertices.Size()-1].SetOrigin(VectorStripNew[VNr]);
377 // Update the geometry-CRC. We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
378 GeomCRC=adler32(GeomCRC, (Bytef*)&VectorStripNew[VNr].z, sizeof(VectorStripNew[VNr].z));
381 else
383 ArrayT<unsigned long>& IdxStripNew=TerrainNew.ComputeIndexStripByRefinement(VI);
385 // Note that the first index at IdxStrip[0] must be skipped!
386 const TerrainT::VertexT* Vertices=TerrainNew.GetVertices();
388 for (unsigned long IdxNr=1; IdxNr<IdxStripNew.Size(); IdxNr++)
390 TerrainMesh.Vertices.PushBackEmpty();
391 TerrainMesh.Vertices[TerrainMesh.Vertices.Size()-1].SetOrigin(Vertices[IdxStripNew[IdxNr]]);
393 // Update the geometry-CRC. We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
394 GeomCRC=adler32(GeomCRC, (Bytef*)&IdxStripNew[IdxNr], sizeof(IdxStripNew[IdxNr]));
399 MatSys::Renderer->RenderMesh(TerrainMesh);
402 double DeltaTime=Timer.GetSecondsSinceLastCall();
404 float MoveSpeed=1000.0f*float(DeltaTime);
405 float RotSpeed = 90.0f*float(DeltaTime);
408 MatSys::Renderer->EndFrame();
410 win.swapBuffers();
411 glfwPollEvents();
413 FrameCounter++;
415 if (BenchMarkMode.getValue() && FrameCounter==FRAMES_FOR_BENCHMARK)
416 break;
418 if (BenchMarkMode.getValue())
420 const float vx = float(STEPDIST_FOR_BENCHMARK)*sin(Heading/180.0f*3.1415926f);
421 const float vy = float(STEPDIST_FOR_BENCHMARK)*cos(Heading/180.0f*3.1415926f);
423 ViewerPos=ViewerPos+Vector3fT(vx, vy, 0);
425 else
427 const float vx = MoveSpeed*sin(Heading/180.0f*3.1415926f);
428 const float vy = MoveSpeed*cos(Heading/180.0f*3.1415926f);
430 if (win.isKeyPressed(GLFW_KEY_UP ) || win.isKeyPressed(GLFW_KEY_W)) ViewerPos=ViewerPos+Vector3fT( vx, vy, 0);
431 if (win.isKeyPressed(GLFW_KEY_DOWN ) || win.isKeyPressed(GLFW_KEY_S)) ViewerPos=ViewerPos+Vector3fT(-vx, -vy, 0);
432 if ( win.isKeyPressed(GLFW_KEY_A)) ViewerPos=ViewerPos+Vector3fT(-vy, vx, 0);
433 if ( win.isKeyPressed(GLFW_KEY_D)) ViewerPos=ViewerPos+Vector3fT( vy, -vx, 0);
434 if (win.isKeyPressed(GLFW_KEY_INSERT ) || win.isKeyPressed(GLFW_KEY_R)) ViewerPos.z+=MoveSpeed;
435 if (win.isKeyPressed(GLFW_KEY_DELETE ) || win.isKeyPressed(GLFW_KEY_F)) ViewerPos.z-=MoveSpeed;
436 if (win.isKeyPressed(GLFW_KEY_LEFT ) ) Heading-=RotSpeed;
437 if (win.isKeyPressed(GLFW_KEY_RIGHT ) ) Heading+=RotSpeed;
438 if (win.isKeyPressed(GLFW_KEY_PAGE_UP ) ) Pitch-=RotSpeed;
439 if (win.isKeyPressed(GLFW_KEY_PAGE_DOWN) ) Pitch+=RotSpeed;
440 if (win.isKeyPressed(GLFW_KEY_END ) ) Pitch=0.0;
444 const double TotalTime=Timer.GetSecondsSinceCtor();
445 printf("Average frame-rate was: %.2f FPS (%lu frames in %.2f seconds)\n", double(FrameCounter)/TotalTime, FrameCounter, TotalTime);
446 printf("Geo-morphing was: %s\n", win.VI_morph ? " ON" : "OFF");
447 printf("Frustum culling was: %s\n", VI.cull ? " ON" : "OFF");
448 printf("Geometry CRC was: 0x%lX\n", GeomCRC);
450 // Clean-up.
451 MatSys::Renderer->FreeMaterial(TerrainRenderMat);
452 MatSys::Renderer->Release();
453 MatSys::Renderer=NULL;
454 FreeLibrary(RendererDLL);
456 catch (const TerrainT::InitError& /*E*/)
458 printf("\nEither \"%s\" could not be found, not be read,\n", TerrainName.getValue().c_str());
459 printf("is not square, is smaller than 3 pixels, or not of size 2^n+1. Sorry.\n");
461 catch (const std::runtime_error& re)
463 fprintf(stderr, "ERROR: %s\n", re.what());
464 glfwTerminate();
465 return -1;
468 glfwTerminate();
469 return 0;