PageRenderTime 79ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/CaTools/TerrainViewer.cpp

https://bitbucket.org/lennonchan/cafu
C++ | 455 lines | 290 code | 102 blank | 63 comment | 57 complexity | 6731b16b0ffb7e206b89c318381801b9 MD5 | raw file
  1. /*
  2. =================================================================================
  3. This file is part of Cafu, the open-source game engine and graphics engine
  4. for multiplayer, cross-platform, real-time 3D action.
  5. Copyright (C) 2002-2013 Carsten Fuchs Software.
  6. Cafu is free software: you can redistribute it and/or modify it under the terms
  7. of the GNU General Public License as published by the Free Software Foundation,
  8. either version 3 of the License, or (at your option) any later version.
  9. Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  10. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  11. PURPOSE. See the GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with Cafu. If not, see <http://www.gnu.org/licenses/>.
  14. For support and more information about Cafu, visit us at <http://www.cafu.de>.
  15. =================================================================================
  16. */
  17. /***************************/
  18. /*** Cafu Terrain Viewer ***/
  19. /***************************/
  20. #include <stdio.h>
  21. #include <float.h>
  22. #include <string.h>
  23. #include "ConsoleCommands/Console.hpp"
  24. #include "ConsoleCommands/ConsoleStdout.hpp"
  25. #include "FileSys/FileManImpl.hpp"
  26. #include "MaterialSystem/MapComposition.hpp"
  27. #include "MaterialSystem/MaterialManager.hpp"
  28. #include "MaterialSystem/MaterialManagerImpl.hpp"
  29. #include "MaterialSystem/Mesh.hpp"
  30. #include "MaterialSystem/Renderer.hpp"
  31. #include "MaterialSystem/TextureMap.hpp"
  32. #include "Math3D/Plane3.hpp"
  33. #include "Math3D/Matrix.hpp"
  34. #include "OpenGL/OpenGLWindow.hpp"
  35. #include "Templates/Array.hpp"
  36. #include "Terrain/Terrain.hpp"
  37. #include "Util/Util.hpp"
  38. #include "PlatformAux.hpp"
  39. #include "zlib.h"
  40. #ifdef _WIN32
  41. #define WIN32_LEAN_AND_MEAN
  42. #include <windows.h>
  43. #include <direct.h>
  44. #if defined(_MSC_VER)
  45. #if (_MSC_VER<1300)
  46. #define for if (false) ; else for
  47. #endif
  48. #endif
  49. #else
  50. #include <dlfcn.h>
  51. #define FreeLibrary dlclose
  52. #endif
  53. static cf::ConsoleStdoutT ConsoleStdout;
  54. cf::ConsoleI* Console=&ConsoleStdout;
  55. static cf::FileSys::FileManImplT FileManImpl;
  56. cf::FileSys::FileManI* cf::FileSys::FileMan=&FileManImpl;
  57. MaterialManagerI* MaterialManager=NULL;
  58. #define DEG2RAD(x) ((3.1415927f / 180.0f) * (x))
  59. #define SQR(x) ((x) * (x))
  60. std::string BaseDirectoryName="Games/DeathMatch";
  61. void Usage()
  62. {
  63. printf("\n");
  64. printf("\nUSAGE: TerrainViewer -t=HeightMapName -m=MaterialName [OPTIONS]\n");
  65. printf("\n");
  66. printf("\nMANDATORY PARAMETERS:\n");
  67. printf("\n");
  68. printf("-t=MyHeightMap specifies the name of the desired terrain (heightmap image).\n");
  69. printf("\n");
  70. printf("-m=MyMaterial specifies the name of the desired material, which must be\n");
  71. printf(" defined in one of the material script files (see below).\n");
  72. printf("\n");
  73. printf("\nOPTIONS:\n");
  74. printf("\n");
  75. printf("-bd=base/dir specifies the base directory from which I look both for material\n");
  76. printf(" scripts and the materials associated textures.\n");
  77. printf(" The default (if you don't use -bd) is %s\n", BaseDirectoryName.c_str());
  78. printf("\n");
  79. printf("-ms=MyMatScript.cmat specifies the material script that contains a definition\n");
  80. printf(" of \"MyMaterial\" (see above). You may use -ms several times, making me look\n");
  81. printf(" into each specified script for a definition of the material.\n");
  82. printf(" If you do not use -ms at all, I'll look into ALL material scripts that I can\n");
  83. printf(" find in %s/Materials, so you probably don't need it as well.\n", BaseDirectoryName.c_str());
  84. printf("\n");
  85. printf("-bm runs a simple benchmark. Mostly intended for development purposes.\n");
  86. printf("\n");
  87. exit(0);
  88. }
  89. int main(int ArgC, char* ArgV[])
  90. {
  91. const char* TerrainName =NULL;
  92. const char* MaterialName ="wireframe"; // The "wireframe" material is defined in file "meta.cmat".
  93. bool BenchMarkMode=false;
  94. bool UseSOARX =false; // SOARX is the new SOAR implementation.
  95. ArrayT<const char*> MaterialScriptNames;
  96. printf("\nCafu Engine Terrain Viewer (%s)\n\n", __DATE__);
  97. // Initialize the FileMan by mounting the default file system.
  98. // Note that specifying "./" (instead of "") as the file system description effectively prevents the use of
  99. // absolute paths like "D:\abc\someDir\someFile.xy" or "/usr/bin/xy". This however should be fine for this application.
  100. cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", "");
  101. cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE");
  102. cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE");
  103. // Process the command line options.
  104. for (int ArgNr=1; ArgNr<ArgC; ArgNr++)
  105. {
  106. if (_strnicmp(ArgV[ArgNr], "-t=" , 3)==0) TerrainName=ArgV[ArgNr]+3;
  107. else if (_strnicmp(ArgV[ArgNr], "-m=" , 3)==0) MaterialName=ArgV[ArgNr]+3;
  108. else if (_strnicmp(ArgV[ArgNr], "-bd=", 4)==0) BaseDirectoryName=ArgV[ArgNr]+4;
  109. else if (_strnicmp(ArgV[ArgNr], "-ms=", 4)==0) MaterialScriptNames.PushBack(ArgV[ArgNr]+4);
  110. else if (_stricmp (ArgV[ArgNr], "-bm" )==0) BenchMarkMode=true;
  111. else if (_stricmp (ArgV[ArgNr], "-sx" )==0) UseSOARX=true;
  112. else
  113. {
  114. printf("Sorry, I don't know what option \"%s\" means.\n", ArgV[ArgNr]);
  115. Usage();
  116. }
  117. }
  118. if (TerrainName==NULL)
  119. {
  120. printf("Hmm. Specifying a terrain name wouldn't hurt...\n");
  121. printf("Please use the -t option in order to specify the desired terrain!\n");
  122. Usage();
  123. }
  124. // Setup the global Material Manager pointer.
  125. static MaterialManagerImplT MatManImpl;
  126. MaterialManager=&MatManImpl;
  127. // Register the material script files with the material manager.
  128. if (MaterialScriptNames.Size()==0)
  129. {
  130. // The -ms option has not been used, so register all material script files in BaseDirectoryName/Materials.
  131. MaterialManager->RegisterMaterialScriptsInDir(BaseDirectoryName+"/Materials", BaseDirectoryName+"/");
  132. }
  133. else
  134. {
  135. // Material script files have been specified - register them now.
  136. for (unsigned long MSNNr=0; MSNNr<MaterialScriptNames.Size(); MSNNr++)
  137. MaterialManager->RegisterMaterialScript(BaseDirectoryName+"/"+MaterialScriptNames[MSNNr], BaseDirectoryName+"/");
  138. }
  139. // Get the desired material.
  140. MaterialT* TerrainMaterial=MaterialManager->GetMaterial(MaterialName);
  141. if (TerrainMaterial==NULL)
  142. {
  143. printf("Sorry, I have not been able to get material \"%s\"\n", MaterialName);
  144. printf("from the registered material script files. Possible causes:\n");
  145. printf("- the material is not defined in any of the script files (material name typo?)\n");
  146. printf("- the material script file(s) could not be opened (script file name typo?)\n");
  147. printf("- the material script file contains bugs, i.e. syntax errors.\n");
  148. return 0;
  149. }
  150. try
  151. {
  152. const unsigned long FRAMES_FOR_BENCHMARK=1000;
  153. const Vector3fT TerrainResolution(160.0f, 160.0f, 50.0f*255.0f);
  154. TerrainT TerrainNew(TerrainName, TerrainResolution);
  155. const double STEPDIST_FOR_BENCHMARK=TerrainNew.GetSize()*1.2*TerrainResolution.x/FRAMES_FOR_BENCHMARK;
  156. // Open OpenGL-Window.
  157. const char* ErrorMsg=SingleOpenGLWindow->Open("Cafu Terrain Viewer 1.2", BenchMarkMode ? 1280 : 800, BenchMarkMode ? 1024 : 600, 32, BenchMarkMode);
  158. if (ErrorMsg)
  159. {
  160. printf("\nUnable to open OpenGL window: %s\n", ErrorMsg);
  161. return 0;
  162. }
  163. // Get the renderer with the highest preference number that is supported.
  164. HMODULE RendererDLL;
  165. MatSys::Renderer=PlatformAux::GetBestRenderer(RendererDLL);
  166. if (MatSys::Renderer==NULL || RendererDLL==NULL) { printf("No renderer loaded.\n"); SingleOpenGLWindow->Close(); return 0; }
  167. MatSys::Renderer->Initialize();
  168. // Get the TextureMapManager from the RendererDLL.
  169. MatSys::TextureMapManagerI* TextureMapManager=PlatformAux::GetTextureMapManager(RendererDLL);
  170. if (TextureMapManager==NULL) { printf("No TextureMapManager obtained.\n"); FreeLibrary(RendererDLL); SingleOpenGLWindow->Close(); return 0; }
  171. MatSys::RenderMaterialT* TerrainRenderMat=MatSys::Renderer->RegisterMaterial(TerrainMaterial);
  172. MatSys::Renderer->SetCurrentMaterial(TerrainRenderMat);
  173. // As the terrain shaders require (cmat materials to specify) a lightmap, provide one here.
  174. char Data[]={ 255, 255, 255, 255, 255, 255, 0, 0,
  175. 255, 255, 255, 255, 255, 255, 0, 0 };
  176. MatSys::Renderer->SetCurrentLightMap(TextureMapManager->GetTextureMap2D(Data, 2, 2, 3, true, MapCompositionT(MapCompositionT::Linear, MapCompositionT::Linear)));
  177. MatSys::Renderer->SetCurrentLightDirMap(NULL); // The MatSys provides a default for LightDirMaps when NULL is set.
  178. MatSys::Renderer->ClearColor(0.0f, 0.0f, 0.4f, 0.0f);
  179. /* This is how it used to be.
  180. We now try it via the vertex indices directly... */
  181. const float res0=160.0;
  182. const float res1=160.0;
  183. // BBmin and BBmax are BBs for the xy-plane, z is not needed here.
  184. // Also note that the y-components have been multiplied by -1, or otherwise the texture gets flipped.
  185. const float BBmin[2]={ -res0*0.5f*(TerrainNew.GetSize()-1), res1*0.5f*(TerrainNew.GetSize()-1) };
  186. const float BBmax[2]={ res0*0.5f*(TerrainNew.GetSize()-1), -res1*0.5f*(TerrainNew.GetSize()-1) };
  187. const float Plane1[4]={ 1.0f/(BBmax[0]-BBmin[0]), 0.0f, 0.0f, -BBmin[0]/(BBmax[0]-BBmin[0]) };
  188. const float Plane2[4]={ 0.0f, 1.0f/(BBmax[1]-BBmin[1]), 0.0f, -BBmin[1]/(BBmax[1]-BBmin[1]) };
  189. MatSys::Renderer->SetGenPurposeRenderingParam( 4, Plane1[0]);
  190. MatSys::Renderer->SetGenPurposeRenderingParam( 5, Plane1[1]);
  191. MatSys::Renderer->SetGenPurposeRenderingParam( 6, Plane1[2]);
  192. MatSys::Renderer->SetGenPurposeRenderingParam( 7, Plane1[3]);
  193. MatSys::Renderer->SetGenPurposeRenderingParam( 8, Plane2[0]);
  194. MatSys::Renderer->SetGenPurposeRenderingParam( 9, Plane2[1]);
  195. MatSys::Renderer->SetGenPurposeRenderingParam(10, Plane2[2]);
  196. MatSys::Renderer->SetGenPurposeRenderingParam(11, Plane2[3]);
  197. TerrainT::ViewInfoT VI;
  198. VI.cull=true; // perform view culling when set
  199. bool VI_morph=true; // perform geomorphing when set
  200. // Master Game Loop
  201. TimerT Timer;
  202. unsigned long FrameCounter=0;
  203. unsigned long GeomCRC=adler32(0, NULL, 0); // We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
  204. Vector3fT ViewerPos=BenchMarkMode ? Vector3fT(TerrainNew.GetVertices()[0])+Vector3fT(0, 0, 4000.0f) : Vector3fT(0, -500.0f, 1000.0f);
  205. float Heading=BenchMarkMode ? 55.0f : 0.0f;
  206. float Pitch =BenchMarkMode ? 25.0f : 0.0f;
  207. while (true)
  208. {
  209. // Rufe die Nachrichten der Windows-Nachrichtenschlange ab.
  210. if (SingleOpenGLWindow->HandleWindowMessages()) break;
  211. MatSys::Renderer->BeginFrame(Timer.GetSecondsSinceCtor());
  212. // MatSys::Renderer->GetModifyMatrix(MatSys::RendererI::MODEL_TO_WORLD)=MatrixT();
  213. MatSys::Renderer->SetMatrix(MatSys::RendererI::WORLD_TO_VIEW, MatrixT::GetRotateXMatrix(-90.0f));
  214. MatSys::Renderer->RotateX (MatSys::RendererI::WORLD_TO_VIEW, Pitch );
  215. MatSys::Renderer->RotateZ (MatSys::RendererI::WORLD_TO_VIEW, Heading);
  216. MatSys::Renderer->Translate(MatSys::RendererI::WORLD_TO_VIEW, -ViewerPos.x, -ViewerPos.y, -ViewerPos.z);
  217. // if (TextureName==NULL) glColor3f(0.6, 1.0, 0.5);
  218. // if (TextureName==NULL) glDisable(GL_POLYGON_OFFSET_FILL);
  219. // if (TextureName==NULL) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  220. const float tau =4.0; // Error tolerance in pixel.
  221. // const float tau_min=tau;
  222. // const float tau_max=VI_morph ? (3.0/2.0)*tau_min : tau_min;
  223. // The basic formula for fov_x is from soar/main.c, reshape_callback function, rearranged for fov_x.
  224. // The 67.5 is a fixed value from OpenGLWindow.cpp.
  225. const float fov_x =2.0f*atan(float(SingleOpenGLWindow->GetWidth())/float(SingleOpenGLWindow->GetHeight())*tan(DEG2RAD(67.5f)/2.0f));
  226. const float kappa =tau/SingleOpenGLWindow->GetWidth() * fov_x;
  227. VI.nu =kappa>0.0 ? 1.0f/kappa : FLT_MAX; // inverse of error tolerance in radians
  228. VI.nu_min=2.0f/3.0f*VI.nu; // lower morph parameter
  229. VI.nu_max= VI.nu; // upper morph parameter
  230. VI.viewpoint=ViewerPos;
  231. // Set up VI.viewplanes (clip planes) for view frustum culling.
  232. MatrixT mpv=MatSys::Renderer->GetMatrix(MatSys::Renderer->PROJECTION)*MatSys::Renderer->GetMatrixModelView();
  233. // Compute view frustum planes.
  234. for (unsigned long i=0; i<5; i++)
  235. {
  236. // m can be used to easily minify / shrink the view frustum.
  237. // The values should be between 0 and 8: 0 is the default (no minification), 8 is the reasonable maximum.
  238. const char m=0;
  239. const float d=(i<4 && m>0) ? 1.0f-0.75f*m/8.0f : 1.0f;
  240. float plane[4];
  241. for (unsigned long j=0; j<4; j++)
  242. plane[j]=((i & 1) ? mpv.m[i/2][j] : -mpv.m[i/2][j]) - d*mpv.m[3][j];
  243. const float l=sqrt(SQR(plane[0])+SQR(plane[1])+SQR(plane[2]));
  244. VI.viewplanes[i]=Plane3fT(Vector3fT(plane[0]/l, plane[1]/l, plane[2]/l), -plane[3]/l);
  245. }
  246. static MatSys::MeshT TerrainMesh(MatSys::MeshT::TriangleStrip);
  247. TerrainMesh.Vertices.Overwrite();
  248. if (UseSOARX)
  249. {
  250. ArrayT<Vector3fT>& VectorStrip=TerrainNew.ComputeVectorStrip(VI);
  251. // With SOARX, the vector strip begins as usual and as expected with the first vector.
  252. for (unsigned long VNr=0; VNr<VectorStrip.Size(); VNr++)
  253. {
  254. TerrainMesh.Vertices.PushBackEmpty();
  255. TerrainMesh.Vertices[VNr].SetOrigin(VectorStrip[VNr]);
  256. // Update the geometry-CRC. We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
  257. GeomCRC=adler32(GeomCRC, (Bytef*)&VectorStrip[VNr].z, sizeof(VectorStrip[VNr].z));
  258. }
  259. }
  260. else
  261. {
  262. if (VI_morph)
  263. {
  264. ArrayT<Vector3fT>& VectorStripNew=TerrainNew.ComputeVectorStripByMorphing(VI);
  265. // Note that the first VectorT at VectorStrip[0] must be skipped!
  266. for (unsigned long VNr=1; VNr<VectorStripNew.Size(); VNr++)
  267. {
  268. TerrainMesh.Vertices.PushBackEmpty();
  269. TerrainMesh.Vertices[TerrainMesh.Vertices.Size()-1].SetOrigin(VectorStripNew[VNr]);
  270. // Update the geometry-CRC. We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
  271. GeomCRC=adler32(GeomCRC, (Bytef*)&VectorStripNew[VNr].z, sizeof(VectorStripNew[VNr].z));
  272. }
  273. }
  274. else
  275. {
  276. ArrayT<unsigned long>& IdxStripNew=TerrainNew.ComputeIndexStripByRefinement(VI);
  277. // Note that the first index at IdxStrip[0] must be skipped!
  278. const TerrainT::VertexT* Vertices=TerrainNew.GetVertices();
  279. for (unsigned long IdxNr=1; IdxNr<IdxStripNew.Size(); IdxNr++)
  280. {
  281. TerrainMesh.Vertices.PushBackEmpty();
  282. TerrainMesh.Vertices[TerrainMesh.Vertices.Size()-1].SetOrigin(Vertices[IdxStripNew[IdxNr]]);
  283. // Update the geometry-CRC. We use Adler-32 instead of CRC-32, as Adler is faster but just as reliable.
  284. GeomCRC=adler32(GeomCRC, (Bytef*)&IdxStripNew[IdxNr], sizeof(IdxStripNew[IdxNr]));
  285. }
  286. }
  287. }
  288. MatSys::Renderer->RenderMesh(TerrainMesh);
  289. // if (TextureName==NULL) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  290. // if (TextureName==NULL) glEnable(GL_POLYGON_OFFSET_FILL);
  291. double DeltaTime=Timer.GetSecondsSinceLastCall();
  292. float MoveSpeed=1000.0f*float(DeltaTime);
  293. float RotSpeed = 90.0f*float(DeltaTime);
  294. MatSys::Renderer->EndFrame();
  295. SingleOpenGLWindow->SwapBuffers();
  296. FrameCounter++;
  297. CaKeyboardEventT KE;
  298. bool QuitProgram=false;
  299. while (SingleOpenGLWindow->GetNextKeyboardEvent(KE)>0)
  300. {
  301. if (KE.Type!=CaKeyboardEventT::CKE_KEYDOWN) continue;
  302. if (KE.Key==CaKeyboardEventT::CK_ESCAPE) QuitProgram=true;
  303. if (KE.Key==CaKeyboardEventT::CK_C ) { VI.cull =!VI.cull; printf("View frustum culling is %s.\n", VI.cull ? "ON" : "OFF"); }
  304. if (KE.Key==CaKeyboardEventT::CK_M ) { VI_morph=!VI_morph; printf("Geo-morphing is %s\n", VI_morph ? "ON" : "OFF"); }
  305. }
  306. if (QuitProgram) break;
  307. if (BenchMarkMode && FrameCounter==FRAMES_FOR_BENCHMARK) break;
  308. if (BenchMarkMode)
  309. {
  310. const float vx=float(STEPDIST_FOR_BENCHMARK)*sin(Heading/180.0f*3.1415926f);
  311. const float vy=float(STEPDIST_FOR_BENCHMARK)*cos(Heading/180.0f*3.1415926f);
  312. ViewerPos=ViewerPos+Vector3fT(vx, vy, 0);
  313. }
  314. else
  315. {
  316. const float vx=MoveSpeed*sin(Heading/180.0f*3.1415926f);
  317. const float vy=MoveSpeed*cos(Heading/180.0f*3.1415926f);
  318. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_UP ] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_W]) ViewerPos=ViewerPos+Vector3fT( vx, vy, 0);
  319. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_DOWN ] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_S]) ViewerPos=ViewerPos+Vector3fT(-vx, -vy, 0);
  320. if ( SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_A]) ViewerPos=ViewerPos+Vector3fT(-vy, vx, 0);
  321. if ( SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_D]) ViewerPos=ViewerPos+Vector3fT( vy, -vx, 0);
  322. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_INSERT] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_R]) ViewerPos.z+=MoveSpeed;
  323. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_DELETE] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_F]) ViewerPos.z-=MoveSpeed;
  324. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_LEFT ] ) Heading-=RotSpeed;
  325. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_RIGHT ] ) Heading+=RotSpeed;
  326. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_PGUP ] ) Pitch-=RotSpeed;
  327. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_PGDN ] ) Pitch+=RotSpeed;
  328. if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_END ] ) Pitch=0.0;
  329. }
  330. }
  331. const double TotalTime=Timer.GetSecondsSinceCtor();
  332. printf("Average frame-rate was: %.2f FPS (%lu frames in %.2f seconds)\n", double(FrameCounter)/TotalTime, FrameCounter, TotalTime);
  333. printf("Geo-morphing was: %s\n", VI_morph ? " ON" : "OFF");
  334. printf("Frustum culling was: %s\n", VI.cull ? " ON" : "OFF");
  335. printf("Geometry CRC was: 0x%lX\n", GeomCRC);
  336. // Clean-up.
  337. MatSys::Renderer->FreeMaterial(TerrainRenderMat);
  338. MatSys::Renderer->Release();
  339. MatSys::Renderer=NULL;
  340. FreeLibrary(RendererDLL);
  341. SingleOpenGLWindow->Close();
  342. }
  343. catch (const TerrainT::InitError& /*E*/)
  344. {
  345. printf("\nEither \"%s\" could not be found, not be read,\n", TerrainName);
  346. printf("is not square, is smaller than 3 pixels, or not of size 2^n+1. Sorry.\n");
  347. }
  348. return 0;
  349. }