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.
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"
30 #define WIN32_LEAN_AND_MEAN
34 #define FreeLibrary dlclose
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
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.
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:
98 // gluPerspective(FieldOfView, AspectRatio, Near, Far);
101 void KeyEvent(int key
, int scancode
, int action
, int mods
) override
103 if (action
!= GLFW_PRESS
)
108 case GLFW_KEY_ESCAPE
:
109 setShouldClose(true);
114 printf("View frustum culling is %s.\n", VI
.cull
? "ON" : "OFF");
118 VI_morph
= !VI_morph
;
119 printf("Geo-morphing is %s\n", VI_morph
? "ON" : "OFF");
127 TerrainT::ViewInfoT VI
;
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);
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");
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
);
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.
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
;
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.
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.
336 const float d
= (i
< 4) ? 1.0f
- 0.75f
*m
/8.0f
: 1.0f
;
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
));
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
));
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();
415 if (BenchMarkMode
.getValue() && FrameCounter
==FRAMES_FOR_BENCHMARK
)
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);
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
);
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());