| 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-2012 Carsten Fuchs Software. |
|---|
| 6 | |
|---|
| 7 | Cafu is free software: you can redistribute it and/or modify it under the terms |
|---|
| 8 | of the GNU General Public License as published by the Free Software Foundation, |
|---|
| 9 | either version 3 of the License, or (at your option) any later version. |
|---|
| 10 | |
|---|
| 11 | Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
|---|
| 12 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
|---|
| 13 | PURPOSE. See the GNU General Public License for more details. |
|---|
| 14 | |
|---|
| 15 | You should have received a copy of the GNU General Public License |
|---|
| 16 | along with Cafu. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 17 | |
|---|
| 18 | For support and more information about Cafu, visit us at <http://www.cafu.de>. |
|---|
| 19 | ================================================================================= |
|---|
| 20 | */ |
|---|
| 21 | |
|---|
| 22 | #include <float.h> |
|---|
| 23 | #include <math.h> |
|---|
| 24 | #include <stdio.h> |
|---|
| 25 | #include <string.h> |
|---|
| 26 | |
|---|
| 27 | #if defined _WIN32 && defined (_MSC_VER) |
|---|
| 28 | #define WIN32_LEAN_AND_MEAN |
|---|
| 29 | #include <windows.h> |
|---|
| 30 | #if (_MSC_VER<1300) |
|---|
| 31 | #define for if (false) ; else for |
|---|
| 32 | #endif |
|---|
| 33 | #endif |
|---|
| 34 | |
|---|
| 35 | #include <GL/gl.h> |
|---|
| 36 | #include <GL/glu.h> |
|---|
| 37 | #include "MaterialSystem/Common/OpenGLEx.hpp" // for glext.h |
|---|
| 38 | |
|---|
| 39 | #include "ConsoleCommands/Console.hpp" |
|---|
| 40 | #include "ConsoleCommands/ConsoleStdout.hpp" |
|---|
| 41 | #include "FileSys/FileManImpl.hpp" |
|---|
| 42 | #include "Templates/Array.hpp" |
|---|
| 43 | #include "Math3D/Plane3.hpp" |
|---|
| 44 | #include "Bitmap/Bitmap.hpp" |
|---|
| 45 | #include "OpenGL/OpenGLWindow.hpp" |
|---|
| 46 | #include "Util/Util.hpp" |
|---|
| 47 | #include "Terrain/Terrain.hpp" |
|---|
| 48 | |
|---|
| 49 | |
|---|
| 50 | static cf::ConsoleStdoutT ConsoleStdout; |
|---|
| 51 | cf::ConsoleI* Console=&ConsoleStdout; |
|---|
| 52 | |
|---|
| 53 | static cf::FileSys::FileManImplT FileManImpl; |
|---|
| 54 | cf::FileSys::FileManI* cf::FileSys::FileMan=&FileManImpl; |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | // ******************** common.h ************************************************ |
|---|
| 58 | |
|---|
| 59 | // Miscellaneous math macros. |
|---|
| 60 | #define DEG2RAD(x) ((3.1415927f / 180.0f) * (x)) |
|---|
| 61 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) |
|---|
| 62 | #define SQR(x) ((x) * (x)) |
|---|
| 63 | |
|---|
| 64 | // ******************** index.h ************************************************ |
|---|
| 65 | |
|---|
| 66 | #define LINEAR_INDEX(i, j, m) ((i) + (j) + ((j) << (m))) |
|---|
| 67 | #define LINEAR_SPLIT(i, j, k) (((j) + (k)) / 2) |
|---|
| 68 | #define LINEAR_CHILD_L(i, j, k) LINEAR_SPLIT(i, j, k), j, i |
|---|
| 69 | #define LINEAR_CHILD_R(i, j, k) LINEAR_SPLIT(i, j, k), i, k |
|---|
| 70 | |
|---|
| 71 | // Standard row-major (linear) matrix layout. |
|---|
| 72 | #define I_SW(m) LINEAR_INDEX(0 << (m), 0 << (m), m) |
|---|
| 73 | #define I_SE(m) LINEAR_INDEX(1 << (m), 0 << (m), m) |
|---|
| 74 | #define I_NE(m) LINEAR_INDEX(1 << (m), 1 << (m), m) |
|---|
| 75 | #define I_NW(m) LINEAR_INDEX(0 << (m), 1 << (m), m) |
|---|
| 76 | #define I_C(m) LINEAR_INDEX(1 << ((m) - 1), 1 << ((m) - 1), m) |
|---|
| 77 | #define ROOT_S(m) I_C(m), I_SW(m), I_SE(m) |
|---|
| 78 | #define ROOT_E(m) I_C(m), I_SE(m), I_NE(m) |
|---|
| 79 | #define ROOT_N(m) I_C(m), I_NE(m), I_NW(m) |
|---|
| 80 | #define ROOT_W(m) I_C(m), I_NW(m), I_SW(m) |
|---|
| 81 | #define TRIANGLE(i, j, k) i, j, k |
|---|
| 82 | #define SPLIT(i, j, k) LINEAR_SPLIT(i, j, k) |
|---|
| 83 | #define CHILD_L(i, j, k) LINEAR_CHILD_L(i, j, k) |
|---|
| 84 | #define CHILD_R(i, j, k) LINEAR_CHILD_R(i, j, k) |
|---|
| 85 | |
|---|
| 86 | |
|---|
| 87 | // ******************** terrain.h ************************************************ |
|---|
| 88 | |
|---|
| 89 | typedef unsigned long VERTEXid; |
|---|
| 90 | |
|---|
| 91 | // TODO: struct VERTEX : public VectorT ??? |
|---|
| 92 | struct VERTEX |
|---|
| 93 | { |
|---|
| 94 | VectorT p; // vertex position |
|---|
| 95 | float e; // error |
|---|
| 96 | float r; // bounding sphere radius |
|---|
| 97 | }; |
|---|
| 98 | |
|---|
| 99 | |
|---|
| 100 | // ******************** End Of Headers.h ************************************************ |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | struct TerrainOldT |
|---|
| 104 | { |
|---|
| 105 | ArrayT<VERTEX> Vertices; |
|---|
| 106 | unsigned long Size; // Lateral dimensions of the terrain / heightmap, e.g. 257, 513, 1025, ... |
|---|
| 107 | unsigned long Levels; // Size == 2^(Levels/2)+1 |
|---|
| 108 | |
|---|
| 109 | |
|---|
| 110 | unsigned long GetIdx(unsigned long i, unsigned long j) |
|---|
| 111 | { |
|---|
| 112 | return i+Size*j; |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | |
|---|
| 116 | // i - column index |
|---|
| 117 | // j - row index |
|---|
| 118 | // di - non-negative col offset to bisected edge endpoint |
|---|
| 119 | // dj - row offset to bisected edge endpoint |
|---|
| 120 | // n - one less array width/height (zero for leaves) |
|---|
| 121 | void ComputeVertexLoD(unsigned long i, unsigned long j, int di, int dj, unsigned long n) |
|---|
| 122 | { |
|---|
| 123 | VERTEX& Vertex=Vertices[GetIdx(i, j)]; |
|---|
| 124 | |
|---|
| 125 | // Compute actual error and initialize radius to zero. The error is simply the vertical difference between the vertex and the bisected edge, |
|---|
| 126 | // i.e. the error between two consecutive levels of refinement. This object-space error can be replaced with any measure of error, |
|---|
| 127 | // as long as the nesting step below is performed. |
|---|
| 128 | Vertex.e=float(fabs(Vertex.p.z-0.5*(Vertices[GetIdx(i-di, j-dj)].p.z+Vertices[GetIdx(i+di, j+dj)].p.z))); |
|---|
| 129 | Vertex.r=0.0f; |
|---|
| 130 | |
|---|
| 131 | // If the vertex is not a leaf node, ensure that the error and radius are nested using information from its four children. |
|---|
| 132 | // Note that the offsets (+di, +dj) and (-di, -dj) from (i, j) initially get us to the two vertices of the bisected edge. |
|---|
| 133 | // By "rotating" (di, dj) 45 degrees (in a topological sense), we arrive at one of the children of (i, j) (assuming we're not on a boundary). |
|---|
| 134 | // Successive 90-degree rotations then allow us to visit all four children. |
|---|
| 135 | if (n==0) return; |
|---|
| 136 | |
|---|
| 137 | dj=(di+dj)/2; // dj' = (di+dj)/2 |
|---|
| 138 | di-=dj; // di' = (di-dj)/2 |
|---|
| 139 | |
|---|
| 140 | for (unsigned long k=0; k<4; k++) |
|---|
| 141 | { |
|---|
| 142 | // Test whether child vertex exists. |
|---|
| 143 | if ((i!=0 || di>=0) && (i!=Size-1 || di<=0) && |
|---|
| 144 | (j!=0 || dj>=0) && (j!=Size-1 || dj<=0)) |
|---|
| 145 | { |
|---|
| 146 | // Inflate error and radius as needed. |
|---|
| 147 | VERTEX& Child=Vertices[GetIdx(i+di, j+dj)]; |
|---|
| 148 | |
|---|
| 149 | Vertex.e=MAX(Vertex.e, Child.e); |
|---|
| 150 | Vertex.r=MAX(Vertex.r, float(length(Vertex.p-Child.p))+Child.r); |
|---|
| 151 | } |
|---|
| 152 | |
|---|
| 153 | // di' = -dj |
|---|
| 154 | // dj' = +di |
|---|
| 155 | dj += di; |
|---|
| 156 | di -= dj; |
|---|
| 157 | dj += di; |
|---|
| 158 | } |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | |
|---|
| 162 | TerrainOldT(const char* FileNameHeightMap) /*throw (BitmapT::LoadErrorT)*/ |
|---|
| 163 | { |
|---|
| 164 | BitmapT HeightMap(FileNameHeightMap); |
|---|
| 165 | |
|---|
| 166 | // Compute Levels and Size, and thereby perform some sanity checks. |
|---|
| 167 | if (HeightMap.SizeX!=HeightMap.SizeY) throw BitmapT::LoadErrorT(); |
|---|
| 168 | if (HeightMap.SizeX<3) throw BitmapT::LoadErrorT(); |
|---|
| 169 | if (HeightMap.SizeX>(1UL << 30/2)+1) throw BitmapT::LoadErrorT(); |
|---|
| 170 | |
|---|
| 171 | Levels=1; |
|---|
| 172 | while ((1UL << Levels)+1<HeightMap.SizeX) Levels++; |
|---|
| 173 | Levels*=2; |
|---|
| 174 | |
|---|
| 175 | Size=(1UL << (Levels/2))+1; |
|---|
| 176 | |
|---|
| 177 | if (Size!=HeightMap.SizeX) throw BitmapT::LoadErrorT(); |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | // Copy the vertex coordinates from HeightMap into the p components of the Vertices. |
|---|
| 181 | // Note that we pick the red channel of the RBG HeightMap.Data as the relevant channel. |
|---|
| 182 | // Moreover, note that the y-axis of the HeightMap.Data points down in screen-space and thus towards us in world space. |
|---|
| 183 | // Our world-space y-axis points opposite (away from us), though, and therefore we access the HeightMap.Data at (i, Size-j-1). |
|---|
| 184 | Vertices.PushBackEmpty(Size*Size); |
|---|
| 185 | |
|---|
| 186 | for (unsigned long j=0; j<Size; j++) |
|---|
| 187 | for (unsigned long i=0; i<Size; i++) |
|---|
| 188 | { |
|---|
| 189 | const float Resolution[3]={ 160.0, 160.0, 25.5*255.0 }; |
|---|
| 190 | |
|---|
| 191 | Vertices[GetIdx(i, j)].p.x=Resolution[0]*(i-0.5*(Size-1)); |
|---|
| 192 | Vertices[GetIdx(i, j)].p.y=Resolution[1]*(j-0.5*(Size-1)); |
|---|
| 193 | Vertices[GetIdx(i, j)].p.z=Resolution[2]*((HeightMap.Data[GetIdx(i, Size-j-1)] & 0xFF)/255.0); |
|---|
| 194 | |
|---|
| 195 | // Note! The extra-brackets around ( (HeightMap.Data[GetIdx(i, Size-j-1)] & 0xFF)/255.0 ) in the above line |
|---|
| 196 | // are needed to be 100% (bitwise) rounding error compatible to the new terrain lib! |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| 199 | |
|---|
| 200 | // Compute error and radius bottom-up, level-by-level. |
|---|
| 201 | // Unfortunately, the paper explains this only rather briefly in section V.B. on page 15. |
|---|
| 202 | const unsigned long n=Size-1; |
|---|
| 203 | unsigned long s; |
|---|
| 204 | int a, b, c; |
|---|
| 205 | |
|---|
| 206 | for (a=c=1, b=2, s=0; (unsigned long)a!=n; a=c=b, b*=2, s=n) |
|---|
| 207 | { |
|---|
| 208 | unsigned long j; |
|---|
| 209 | |
|---|
| 210 | // Process level in black quadtree. |
|---|
| 211 | for (j=a; j<n; j+=b) |
|---|
| 212 | for (unsigned long i=0; i<=n; i+=b) |
|---|
| 213 | { |
|---|
| 214 | ComputeVertexLoD(i, j, 0, a, s); |
|---|
| 215 | ComputeVertexLoD(j, i, a, 0, s); |
|---|
| 216 | } |
|---|
| 217 | |
|---|
| 218 | // Process level in white quadtree. |
|---|
| 219 | for (j=a; j<n; c=-c, j+=b) |
|---|
| 220 | for (unsigned long i=a; i<n; c=-c, i+=b) |
|---|
| 221 | ComputeVertexLoD(i, j, a, c, n); |
|---|
| 222 | } |
|---|
| 223 | |
|---|
| 224 | // Lock center and corner vertices. |
|---|
| 225 | Vertices[GetIdx(0, 0)].e=Vertices[GetIdx(n, 0)].e=Vertices[GetIdx(0, n)].e=Vertices[GetIdx(n, n)].e=Vertices[GetIdx(n/2, n/2)].e=float(HUGE_VAL); |
|---|
| 226 | Vertices[GetIdx(0, 0)].r=Vertices[GetIdx(n, 0)].r=Vertices[GetIdx(0, n)].r=Vertices[GetIdx(n, n)].r=Vertices[GetIdx(n/2, n/2)].r=float(HUGE_VAL); |
|---|
| 227 | } |
|---|
| 228 | |
|---|
| 229 | |
|---|
| 230 | // ComputeIndexStrip() const; |
|---|
| 231 | // ComputeVectorStrip() const; |
|---|
| 232 | }; |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | struct VDinfo |
|---|
| 236 | { |
|---|
| 237 | bool cull; // perform view culling when set |
|---|
| 238 | bool morph; // perform geomorphing when set |
|---|
| 239 | float nu; // inverse of error tolerance in radians |
|---|
| 240 | float nu_min; // lower morph parameter |
|---|
| 241 | float nu_max; // upper morph parameter |
|---|
| 242 | VectorT viewpoint; // viewpoint |
|---|
| 243 | Plane3T<double> viewplanes[5]; // view frustum planes (without the "far" plane) |
|---|
| 244 | }; |
|---|
| 245 | |
|---|
| 246 | |
|---|
| 247 | const unsigned long SPHERE_MASK_UNDECIDED=0x20; // 0010 0000 initial visibility mask |
|---|
| 248 | const unsigned long SPHERE_MASK_VISIBLE =0x3F; // 0011 1111 guaranteed visible |
|---|
| 249 | |
|---|
| 250 | |
|---|
| 251 | // vp - vertex |
|---|
| 252 | // vdp - view-dependent parameters |
|---|
| 253 | // mask - visibility mask |
|---|
| 254 | static unsigned long sphere_visible(const VERTEX& vp, const VDinfo& vdp, unsigned long mask) |
|---|
| 255 | { |
|---|
| 256 | // Compare the radius of the vertex's bounding sphere against the signed distance to each plane of the view volume. |
|---|
| 257 | // If the sphere is completely on the exterior side of a plane, it is invisible. |
|---|
| 258 | // Otherwise, if it is entirely on the interior side, then a flag is set for this plane, and no further |
|---|
| 259 | // tests are made between this plane and the vertex's descendants since the bounding spheres are nested. |
|---|
| 260 | for (unsigned long i=0; i<5; i++) |
|---|
| 261 | if (!(mask & (1u << i))) |
|---|
| 262 | { |
|---|
| 263 | const float d=float(vdp.viewplanes[i].GetDistance(vp.p)); |
|---|
| 264 | |
|---|
| 265 | if (d> vp.r) return 0; // completely outside view volume |
|---|
| 266 | if (d<-vp.r) mask|=1u << i; // completely on interior side of view plane |
|---|
| 267 | } |
|---|
| 268 | |
|---|
| 269 | return mask; |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | |
|---|
| 273 | // ******************************** BEGIN MORPH ************************************************** |
|---|
| 274 | //@p-r-i-v-a-t-e---t-y-p-e-s------------------------------------------------- |
|---|
| 275 | |
|---|
| 276 | ArrayT<VectorT> VectorStrip; |
|---|
| 277 | VERTEXid VectorStripHead; // ID of most recent vertex. |
|---|
| 278 | VERTEXid VectorStripTail; // ID of second most recent vertex. |
|---|
| 279 | bool VectorStripParity; // Parity of most recent vertex. |
|---|
| 280 | |
|---|
| 281 | |
|---|
| 282 | //@p-r-i-v-a-t-e---f-u-n-c-t-i-o-n-s----------------------------------------- |
|---|
| 283 | |
|---|
| 284 | // vv - array of vertices |
|---|
| 285 | // v - index of vertex to append |
|---|
| 286 | // p - parity of vertex |
|---|
| 287 | // z - elevation of morphed vertex |
|---|
| 288 | static void tstrip_vector_append(const VERTEX* vv, VERTEXid v, bool Parity, float z) |
|---|
| 289 | { |
|---|
| 290 | if (v!=VectorStripTail && v!=VectorStripHead) |
|---|
| 291 | { |
|---|
| 292 | if (Parity==VectorStripParity) |
|---|
| 293 | { |
|---|
| 294 | VectorStrip.PushBack(VectorStrip[VectorStrip.Size()-2]); // turn corner; duplicate vertex |
|---|
| 295 | } |
|---|
| 296 | else |
|---|
| 297 | { |
|---|
| 298 | VectorStripParity=Parity; |
|---|
| 299 | VectorStripTail =VectorStripHead; |
|---|
| 300 | } |
|---|
| 301 | |
|---|
| 302 | VectorStripHead=v; |
|---|
| 303 | |
|---|
| 304 | // append new vertex |
|---|
| 305 | VectorStrip.PushBack(VectorT(vv[v].p.x, vv[v].p.y, z)); |
|---|
| 306 | |
|---|
| 307 | // TriangleCount++; // Count triangles for statistics. |
|---|
| 308 | } |
|---|
| 309 | } |
|---|
| 310 | |
|---|
| 311 | |
|---|
| 312 | // zmp - pointer to morphed elevation |
|---|
| 313 | // vp - pointer to vertex |
|---|
| 314 | // vdp - view-dependent parameters |
|---|
| 315 | // zl - elevation of left endpoint of split edge |
|---|
| 316 | // zr - elevation of right endpoint of split edge |
|---|
| 317 | static bool vertex_morph(float* zmp, const VERTEX* vp, const VDinfo& vdp, float zl, float zr) |
|---|
| 318 | { |
|---|
| 319 | // Compute the elevation of the morphed vertex. The return value indicates whether the vertex is active or not. |
|---|
| 320 | const VectorT View=vp->p-vdp.viewpoint; |
|---|
| 321 | const float d =float(dot(View, View)); // d=length(vp->p-vdp.viewpoint)^2 |
|---|
| 322 | const float dmax=SQR(vdp.nu_max * vp->e + vp->r); |
|---|
| 323 | |
|---|
| 324 | if (dmax>d) |
|---|
| 325 | { |
|---|
| 326 | const float dmin=SQR(vdp.nu_min * vp->e + vp->r); |
|---|
| 327 | |
|---|
| 328 | *zmp=dmin>d ? float(vp->p.z) : ((dmax-d) * float(vp->p.z) + (d-dmin) * 0.5f * (zl+zr)) / (dmax-dmin); |
|---|
| 329 | return true; |
|---|
| 330 | } |
|---|
| 331 | else return false; |
|---|
| 332 | } |
|---|
| 333 | |
|---|
| 334 | |
|---|
| 335 | // vv - vertex array |
|---|
| 336 | // vdp - view-dependent parameters |
|---|
| 337 | // l - refinement level |
|---|
| 338 | // i - triangle apex |
|---|
| 339 | // j - supplemental vertex #1 |
|---|
| 340 | // k - supplemental vertex #2 |
|---|
| 341 | // za - elevation of apex |
|---|
| 342 | // zl - elevation of left corner |
|---|
| 343 | // zr - elevation of right corner |
|---|
| 344 | static void submesh_morph(const VERTEX* vv, const VDinfo& vdp, unsigned long l, TRIANGLE(VERTEXid i, VERTEXid j, VERTEXid k), float za, float zl, float zr) |
|---|
| 345 | { |
|---|
| 346 | float zm=0.0f; |
|---|
| 347 | const bool refine=(l!=0) && vertex_morph(&zm, vv + SPLIT(i, j, k), vdp, zl, zr); |
|---|
| 348 | |
|---|
| 349 | // Recursively refine and morph the mesh. |
|---|
| 350 | if (refine) submesh_morph(vv, vdp, l-1, CHILD_L(i, j, k), zm, zl, za); tstrip_vector_append(vv, i, l & 1, za); |
|---|
| 351 | if (refine) submesh_morph(vv, vdp, l-1, CHILD_R(i, j, k), zm, za, zr); |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | |
|---|
| 355 | // vv - vertex array |
|---|
| 356 | // vdp - view-dependent parameters |
|---|
| 357 | // l - refinement level |
|---|
| 358 | // i - triangle apex |
|---|
| 359 | // j - supplemental vertex #1 |
|---|
| 360 | // k - supplemental vertex #2 |
|---|
| 361 | // za - elevation of apex |
|---|
| 362 | // zl - elevation of left corner |
|---|
| 363 | // zr - elevation of right corner |
|---|
| 364 | // m - visibility mask |
|---|
| 365 | static void submesh_morph_visible(const VERTEX* vv, const VDinfo& vdp, unsigned long l, TRIANGLE(VERTEXid i, VERTEXid j, VERTEXid k), float za, float zl, float zr, unsigned long m) |
|---|
| 366 | { |
|---|
| 367 | // Recursively refine, morph, and cull the mesh. |
|---|
| 368 | if (m==SPHERE_MASK_VISIBLE) |
|---|
| 369 | { |
|---|
| 370 | submesh_morph(vv, vdp, l, TRIANGLE(i, j, k), za, zl, zr); |
|---|
| 371 | return; |
|---|
| 372 | } |
|---|
| 373 | |
|---|
| 374 | m=sphere_visible(vv[SPLIT(i, j, k)], vdp, m); |
|---|
| 375 | |
|---|
| 376 | float zm=0.0f; |
|---|
| 377 | const bool refine=(l!=0) && vertex_morph(&zm, vv+SPLIT(i, j, k), vdp, zl, zr) && m; |
|---|
| 378 | |
|---|
| 379 | if (refine) submesh_morph_visible(vv, vdp, l-1, CHILD_L(i, j, k), zm, zl, za, m); tstrip_vector_append(vv, i, l & 1, za); |
|---|
| 380 | if (refine) submesh_morph_visible(vv, vdp, l-1, CHILD_R(i, j, k), zm, za, zr, m); |
|---|
| 381 | } |
|---|
| 382 | |
|---|
| 383 | |
|---|
| 384 | // vdp - view-dependent parameters |
|---|
| 385 | // m - initial visibility mask |
|---|
| 386 | ArrayT<VectorT>& mesh_morph(const TerrainOldT& Terrain, const VDinfo& vdp, unsigned long m) |
|---|
| 387 | { |
|---|
| 388 | const unsigned long n =Terrain.Levels; |
|---|
| 389 | const VERTEX* vv =&Terrain.Vertices[0]; |
|---|
| 390 | const float zc =float(vv[I_C (n/2)].p.z); |
|---|
| 391 | const float zsw=float(vv[I_SW(n/2)].p.z); |
|---|
| 392 | const float zse=float(vv[I_SE(n/2)].p.z); |
|---|
| 393 | const float zne=float(vv[I_NE(n/2)].p.z); |
|---|
| 394 | const float znw=float(vv[I_NW(n/2)].p.z); |
|---|
| 395 | |
|---|
| 396 | // Initialize vector strip. |
|---|
| 397 | VectorStrip.Overwrite(); |
|---|
| 398 | VectorStrip.PushBack(vv[I_SW(n/2)].p); VectorStripTail=I_SW(n/2); |
|---|
| 399 | VectorStrip.PushBack(vv[I_SW(n/2)].p); VectorStripHead=I_SW(n/2); |
|---|
| 400 | VectorStripParity=1; |
|---|
| 401 | |
|---|
| 402 | // Top-level function for constructing a morphed mesh. |
|---|
| 403 | submesh_morph_visible(vv, vdp, n-1, ROOT_S(n/2), zc, zsw, zse, m); tstrip_vector_append(vv, I_SE(n/2), 0, zse); |
|---|
| 404 | submesh_morph_visible(vv, vdp, n-1, ROOT_E(n/2), zc, zse, zne, m); tstrip_vector_append(vv, I_NE(n/2), 0, zne); |
|---|
| 405 | submesh_morph_visible(vv, vdp, n-1, ROOT_N(n/2), zc, zne, znw, m); tstrip_vector_append(vv, I_NW(n/2), 0, znw); |
|---|
| 406 | submesh_morph_visible(vv, vdp, n-1, ROOT_W(n/2), zc, znw, zsw, m); VectorStrip.PushBack(vv[I_SW(n/2)].p); |
|---|
| 407 | |
|---|
| 408 | return VectorStrip; |
|---|
| 409 | } |
|---|
| 410 | |
|---|
| 411 | // ******************************** END MORPH **************************************************** |
|---|
| 412 | |
|---|
| 413 | |
|---|
| 414 | //@p-r-i-v-a-t-e---f-u-n-c-t-i-o-n-s----------------------------------------- |
|---|
| 415 | |
|---|
| 416 | ArrayT<VERTEXid> IndexStrip; |
|---|
| 417 | bool IndexStripParity; |
|---|
| 418 | |
|---|
| 419 | |
|---|
| 420 | // vp - pointer to vertex |
|---|
| 421 | // vdp - view-dependent parameters |
|---|
| 422 | static bool vertex_active(const VERTEX& vp, const VDinfo& vdp) |
|---|
| 423 | { |
|---|
| 424 | const float d =float(length(vp.p-vdp.viewpoint)); |
|---|
| 425 | const float d2=d*d; |
|---|
| 426 | |
|---|
| 427 | return SQR(vdp.nu*vp.e+vp.r) > d2; |
|---|
| 428 | } |
|---|
| 429 | |
|---|
| 430 | |
|---|
| 431 | // v - index of vertex to append |
|---|
| 432 | // p - parity of vertex |
|---|
| 433 | void tstrip_index_append(VERTEXid v, bool Parity) |
|---|
| 434 | { |
|---|
| 435 | const VERTEXid tail=IndexStrip[IndexStrip.Size()-2]; |
|---|
| 436 | const VERTEXid head=IndexStrip[IndexStrip.Size()-1]; |
|---|
| 437 | |
|---|
| 438 | // Add vertex to end of triangle strip vertex buffer. |
|---|
| 439 | if (v!=tail && v!=head) |
|---|
| 440 | { |
|---|
| 441 | if (Parity==IndexStripParity) IndexStrip.PushBack(tail); // turn corner; duplicate vertex |
|---|
| 442 | IndexStrip.PushBack(v); // append new vertex |
|---|
| 443 | IndexStripParity=Parity; // store parity here for easy access |
|---|
| 444 | |
|---|
| 445 | // TriangleCount++; // Count triangles for statistics. |
|---|
| 446 | } |
|---|
| 447 | } |
|---|
| 448 | |
|---|
| 449 | |
|---|
| 450 | // vv - vertex array |
|---|
| 451 | // vdp - view-dependent parameters |
|---|
| 452 | // l - refinement level |
|---|
| 453 | // i - triangle apex |
|---|
| 454 | // j - supplemental vertex #1 |
|---|
| 455 | // k - supplemental vertex #2 |
|---|
| 456 | void submesh_refine(const VERTEX* vv, const VDinfo& vdp, unsigned long l, TRIANGLE(VERTEXid i, VERTEXid j, VERTEXid k)) |
|---|
| 457 | { |
|---|
| 458 | const bool refine=(l!=0) && vertex_active(vv[SPLIT(i, j, k)], vdp); |
|---|
| 459 | |
|---|
| 460 | // Recursively refine the mesh. Since the refinement condition is the |
|---|
| 461 | // same for both branches and can be somewhat expensive to evaluate, |
|---|
| 462 | // it is evaluated and tested *before* making the recursive calls. |
|---|
| 463 | if (refine) submesh_refine(vv, vdp, l-1, CHILD_L(i, j, k)); tstrip_index_append(i, l & 1); |
|---|
| 464 | if (refine) submesh_refine(vv, vdp, l-1, CHILD_R(i, j, k)); |
|---|
| 465 | } |
|---|
| 466 | |
|---|
| 467 | |
|---|
| 468 | // vv - vertex array |
|---|
| 469 | // vdp - view-dependent parameters |
|---|
| 470 | // l - refinement level |
|---|
| 471 | // i - triangle apex |
|---|
| 472 | // j - supplemental vertex #1 |
|---|
| 473 | // k - supplemental vertex #2 |
|---|
| 474 | // m - visibility mask |
|---|
| 475 | void submesh_refine_visible(const VERTEX* vv, const VDinfo& vdp, unsigned long l, TRIANGLE(VERTEXid i, VERTEXid j, VERTEXid k), unsigned long m) |
|---|
| 476 | { |
|---|
| 477 | // If the sphere is contained inside the view volume, then transition to |
|---|
| 478 | // submesh_refine() and make no further view culling tests. Otherwise, continue culling. |
|---|
| 479 | if (m==SPHERE_MASK_VISIBLE) |
|---|
| 480 | { |
|---|
| 481 | submesh_refine(vv, vdp, l, TRIANGLE(i, j, k)); |
|---|
| 482 | return; |
|---|
| 483 | } |
|---|
| 484 | |
|---|
| 485 | m=sphere_visible(vv[SPLIT(i, j, k)], vdp, m); |
|---|
| 486 | const bool refine=(l!=0) && vertex_active(vv[SPLIT(i, j, k)], vdp) && m; |
|---|
| 487 | |
|---|
| 488 | if (refine) submesh_refine_visible(vv, vdp, l-1, CHILD_L(i, j, k), m); tstrip_index_append(i, l & 1); |
|---|
| 489 | if (refine) submesh_refine_visible(vv, vdp, l-1, CHILD_R(i, j, k), m); |
|---|
| 490 | } |
|---|
| 491 | |
|---|
| 492 | |
|---|
| 493 | // vdp - view-dependent parameters |
|---|
| 494 | // m - initial visibility mask |
|---|
| 495 | ArrayT<VERTEXid>& mesh_refine(const TerrainOldT& Terrain, const VDinfo& vdp, unsigned long m) |
|---|
| 496 | { |
|---|
| 497 | const unsigned long n =Terrain.Levels; |
|---|
| 498 | const VERTEX* vv=&Terrain.Vertices[0]; |
|---|
| 499 | |
|---|
| 500 | // Initialize index strip. |
|---|
| 501 | IndexStrip.Overwrite(); |
|---|
| 502 | IndexStrip.PushBack(I_SW(n/2)); |
|---|
| 503 | IndexStrip.PushBack(I_SW(n/2)); |
|---|
| 504 | IndexStripParity=1; |
|---|
| 505 | |
|---|
| 506 | // Top-level function for constructing an indexed mesh. |
|---|
| 507 | submesh_refine_visible(vv, vdp, n-1, ROOT_S(n/2), m); tstrip_index_append(I_SE(n/2), 0); |
|---|
| 508 | submesh_refine_visible(vv, vdp, n-1, ROOT_E(n/2), m); tstrip_index_append(I_NE(n/2), 0); |
|---|
| 509 | submesh_refine_visible(vv, vdp, n-1, ROOT_N(n/2), m); tstrip_index_append(I_NW(n/2), 0); |
|---|
| 510 | submesh_refine_visible(vv, vdp, n-1, ROOT_W(n/2), m); IndexStrip.PushBack(I_SW(n/2)); |
|---|
| 511 | |
|---|
| 512 | return IndexStrip; |
|---|
| 513 | } |
|---|
| 514 | |
|---|
| 515 | |
|---|
| 516 | //@p-u-b-l-i-c---f-u-n-c-t-i-o-n-s------------------------------------------- |
|---|
| 517 | |
|---|
| 518 | void Usage() |
|---|
| 519 | { |
|---|
| 520 | printf("\nUSAGE: TerrainViewer TerrainName\n"); |
|---|
| 521 | printf("\n"); |
|---|
| 522 | |
|---|
| 523 | exit(1); |
|---|
| 524 | } |
|---|
| 525 | |
|---|
| 526 | |
|---|
| 527 | int main(int ArgC, char* ArgV[]) |
|---|
| 528 | { |
|---|
| 529 | const char* TerrainName=NULL; |
|---|
| 530 | const char* TextureName=NULL; |
|---|
| 531 | |
|---|
| 532 | // Initialize the FileMan by mounting the default file system. |
|---|
| 533 | // Note that specifying "./" (instead of "") as the file system description effectively prevents the use of |
|---|
| 534 | // absolute paths like "D:\abc\someDir\someFile.xy" or "/usr/bin/xy". This however should be fine for this application. |
|---|
| 535 | cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", ""); |
|---|
| 536 | cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE"); |
|---|
| 537 | cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE"); |
|---|
| 538 | |
|---|
| 539 | for (int ArgNr=1; ArgNr<ArgC; ArgNr++) |
|---|
| 540 | { |
|---|
| 541 | if (ArgV[ArgNr][0]=='-') Usage(); |
|---|
| 542 | else if (TerrainName==NULL) TerrainName=ArgV[ArgNr]; |
|---|
| 543 | else if (TextureName==NULL) TextureName=ArgV[ArgNr]; |
|---|
| 544 | else Usage(); |
|---|
| 545 | } |
|---|
| 546 | |
|---|
| 547 | if (!TerrainName) |
|---|
| 548 | { |
|---|
| 549 | printf("\nHmm. Specifying a terrain name wouldn't hurt...\n"); |
|---|
| 550 | Usage(); |
|---|
| 551 | } |
|---|
| 552 | |
|---|
| 553 | |
|---|
| 554 | try |
|---|
| 555 | { |
|---|
| 556 | TerrainOldT TerrainOld(TerrainName); |
|---|
| 557 | TerrainT TerrainNew(TerrainName, Vector3fT(160.0f, 160.0f, 25.5f*255.0f)); |
|---|
| 558 | |
|---|
| 559 | |
|---|
| 560 | // Compare the old and new terrains. |
|---|
| 561 | const TerrainT::VertexT* NewVertices=TerrainNew.GetVertices(); |
|---|
| 562 | |
|---|
| 563 | printf("Comparing terrains...\n"); |
|---|
| 564 | for (unsigned long VNr=0; VNr<TerrainOld.Vertices.Size(); VNr++) |
|---|
| 565 | { |
|---|
| 566 | if (TerrainOld.Vertices[VNr].p.x!=NewVertices[VNr].x) { printf("COMPARE 1 FAILED! %lu, %f != %f\n", VNr, TerrainOld.Vertices[VNr].p.x, NewVertices[VNr].x); return 1; }; |
|---|
| 567 | if (TerrainOld.Vertices[VNr].p.y!=NewVertices[VNr].y) { printf("COMPARE 2 FAILED! %lu, %f != %f\n", VNr, TerrainOld.Vertices[VNr].p.y, NewVertices[VNr].y); return 1; }; |
|---|
| 568 | if (TerrainOld.Vertices[VNr].p.z!=NewVertices[VNr].z) { printf("COMPARE 3 FAILED! %lu, %f != %f\n", VNr, TerrainOld.Vertices[VNr].p.z, NewVertices[VNr].z); return 1; }; |
|---|
| 569 | if (TerrainOld.Vertices[VNr].e !=NewVertices[VNr].e) { printf("COMPARE 4 FAILED! %lu, %f != %f\n", VNr, TerrainOld.Vertices[VNr].e, NewVertices[VNr].e); return 1; }; |
|---|
| 570 | if (TerrainOld.Vertices[VNr].r !=NewVertices[VNr].r) { printf("COMPARE 5 FAILED! %lu, %f != %f\n", VNr, TerrainOld.Vertices[VNr].r, NewVertices[VNr].r); return 1; }; |
|---|
| 571 | } |
|---|
| 572 | printf("OK!\n"); |
|---|
| 573 | |
|---|
| 574 | // Open OpenGL-Window. |
|---|
| 575 | const char* ErrorMsg=SingleOpenGLWindow->Open("Cafu Terrain Viewer 1.0", 800, 600, 32, false); |
|---|
| 576 | |
|---|
| 577 | if (ErrorMsg) |
|---|
| 578 | { |
|---|
| 579 | printf("\nUnable to open OpenGL window: %s\n", ErrorMsg); |
|---|
| 580 | return 0; |
|---|
| 581 | } |
|---|
| 582 | |
|---|
| 583 | glClearColor(0.0f, 0.0f, 0.4f, 0.0f); |
|---|
| 584 | |
|---|
| 585 | if (TextureName) |
|---|
| 586 | { |
|---|
| 587 | try |
|---|
| 588 | { |
|---|
| 589 | const float res0=160.0; |
|---|
| 590 | const float res1=160.0; |
|---|
| 591 | |
|---|
| 592 | // BBmin and BBmax are BBs for the xy-plane, z is not needed here. |
|---|
| 593 | // Also note that the y-components have been multiplied by -1, or otherwise the texture gets flipped. |
|---|
| 594 | const float BBmin[2]={ -res0*0.5f*(TerrainOld.Size-1), res1*0.5f*(TerrainOld.Size-1) }; |
|---|
| 595 | const float BBmax[2]={ res0*0.5f*(TerrainOld.Size-1), -res1*0.5f*(TerrainOld.Size-1) }; |
|---|
| 596 | |
|---|
| 597 | const float Plane1[4]={ 1.0f/(BBmax[0]-BBmin[0]), 0.0, 0.0, -BBmin[0]/(BBmax[0]-BBmin[0]) }; |
|---|
| 598 | const float Plane2[4]={ 0.0, 1.0f/(BBmax[1]-BBmin[1]), 0.0, -BBmin[1]/(BBmax[1]-BBmin[1]) }; |
|---|
| 599 | |
|---|
| 600 | BitmapT Texture(TextureName); |
|---|
| 601 | |
|---|
| 602 | glEnable(GL_TEXTURE_GEN_S); |
|---|
| 603 | glEnable(GL_TEXTURE_GEN_T); |
|---|
| 604 | |
|---|
| 605 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); |
|---|
| 606 | |
|---|
| 607 | gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA8, Texture.SizeX, Texture.SizeY, GL_RGBA, GL_UNSIGNED_BYTE, &Texture.Data[0]); |
|---|
| 608 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
|---|
| 609 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
|---|
| 610 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
|---|
| 611 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
|---|
| 612 | |
|---|
| 613 | glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); |
|---|
| 614 | glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); |
|---|
| 615 | |
|---|
| 616 | glTexGenfv(GL_S, GL_OBJECT_PLANE, Plane1); |
|---|
| 617 | glTexGenfv(GL_T, GL_OBJECT_PLANE, Plane2); |
|---|
| 618 | |
|---|
| 619 | glEnable(GL_TEXTURE_2D); |
|---|
| 620 | glColor3f(1.0, 1.0, 1.0); |
|---|
| 621 | } |
|---|
| 622 | catch (const BitmapT::LoadErrorT& /*E*/) {} |
|---|
| 623 | } |
|---|
| 624 | |
|---|
| 625 | |
|---|
| 626 | VDinfo VDI; |
|---|
| 627 | |
|---|
| 628 | VDI.cull =false; // perform view culling when set |
|---|
| 629 | VDI.morph=true; // perform geomorphing when set |
|---|
| 630 | |
|---|
| 631 | // Master Game Loop |
|---|
| 632 | TimerT Timer; |
|---|
| 633 | VectorT ViewerPos(0.0, -500.0, 50.0); |
|---|
| 634 | float Heading=0.0; |
|---|
| 635 | float Pitch =0.0; |
|---|
| 636 | |
|---|
| 637 | while (true) |
|---|
| 638 | { |
|---|
| 639 | // Rufe die Nachrichten der Windows-Nachrichtenschlange ab. |
|---|
| 640 | if (SingleOpenGLWindow->HandleWindowMessages()) break; |
|---|
| 641 | |
|---|
| 642 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|---|
| 643 | |
|---|
| 644 | glLoadIdentity(); |
|---|
| 645 | glRotatef(-90.0, 1.0, 0.0, 0.0); |
|---|
| 646 | glRotatef(Pitch , 1.0, 0.0, 0.0); |
|---|
| 647 | glRotatef(Heading, 0.0, 0.0, 1.0); |
|---|
| 648 | glTranslatef(float(-ViewerPos.x), float(-ViewerPos.y), float(-ViewerPos.z)); |
|---|
| 649 | |
|---|
| 650 | if (TextureName==NULL) glColor3f(0.6f, 1.0f, 0.5f); |
|---|
| 651 | if (TextureName==NULL) glDisable(GL_POLYGON_OFFSET_FILL); |
|---|
| 652 | if (TextureName==NULL) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); |
|---|
| 653 | |
|---|
| 654 | |
|---|
| 655 | const float tau =4.0; // Error tolerance in pixel. |
|---|
| 656 | // const float tau_min=tau; |
|---|
| 657 | // const float tau_max=VDI.morph ? (3.0/2.0)*tau_min : tau_min; |
|---|
| 658 | // The basic formula for fov_x is from soar/main.c, reshape_callback function, rearranged for fov_x. |
|---|
| 659 | // The 67.5 is a fixed value from OpenGLWindow.cpp. |
|---|
| 660 | const float fov_x =float(2.0*atan(float(SingleOpenGLWindow->GetWidth())/float(SingleOpenGLWindow->GetHeight())*tan(DEG2RAD(67.5)/2.0))); |
|---|
| 661 | const float kappa =tau/SingleOpenGLWindow->GetWidth() * fov_x; |
|---|
| 662 | |
|---|
| 663 | VDI.nu =kappa>0.0 ? 1.0f/kappa : FLT_MAX; // inverse of error tolerance in radians |
|---|
| 664 | VDI.nu_min=2.0f/3.0f*VDI.nu; // lower morph parameter |
|---|
| 665 | VDI.nu_max= VDI.nu; // upper morph parameter |
|---|
| 666 | |
|---|
| 667 | VDI.viewpoint=ViewerPos; |
|---|
| 668 | |
|---|
| 669 | // Set up VDI.viewplanes (clip planes) for view frustum culling. |
|---|
| 670 | GLdouble mp[16]; glGetDoublev(GL_PROJECTION_MATRIX, mp); |
|---|
| 671 | GLdouble mv[16]; glGetDoublev(GL_MODELVIEW_MATRIX, mv); |
|---|
| 672 | double mpv[4][4]; |
|---|
| 673 | |
|---|
| 674 | // Multiply modelview and projection matrices. |
|---|
| 675 | for (unsigned long i=0; i<4; i++) |
|---|
| 676 | for (unsigned long j=0; j<4; j++) |
|---|
| 677 | { |
|---|
| 678 | mpv[i][j]=0.0; |
|---|
| 679 | |
|---|
| 680 | for (unsigned long k=0; k<4; k++) |
|---|
| 681 | mpv[i][j]+=mp[4*k+i] * mv[4*j+k]; |
|---|
| 682 | } |
|---|
| 683 | |
|---|
| 684 | // Compute view frustum planes. |
|---|
| 685 | for (unsigned long i=0; i<5; i++) |
|---|
| 686 | { |
|---|
| 687 | // m can be used to easily minify / shrink the view frustum. |
|---|
| 688 | // The values should be between 0 and 8: 0 is the default (no minification), 8 is the reasonable maximum. |
|---|
| 689 | const char m=0; |
|---|
| 690 | const double d=(i<4 && m>0) ? 1.0-0.75*m/8.0 : 1.0; |
|---|
| 691 | double plane[4]; |
|---|
| 692 | |
|---|
| 693 | for (unsigned long j=0; j<4; j++) |
|---|
| 694 | plane[j]=((i & 1) ? mpv[i/2][j] : -mpv[i/2][j]) - d*mpv[3][j]; |
|---|
| 695 | |
|---|
| 696 | const double l=sqrt(SQR(plane[0])+SQR(plane[1])+SQR(plane[2])); |
|---|
| 697 | |
|---|
| 698 | VDI.viewplanes[i]=Plane3T<double>(VectorT(plane[0]/l, plane[1]/l, plane[2]/l), -plane[3]/l); |
|---|
| 699 | } |
|---|
| 700 | |
|---|
| 701 | |
|---|
| 702 | TerrainT::ViewInfoT VI; |
|---|
| 703 | VI.cull =VDI.cull; |
|---|
| 704 | VI.nu =VDI.nu; |
|---|
| 705 | VI.nu_min =VDI.nu_min; |
|---|
| 706 | VI.nu_max =VDI.nu_max; |
|---|
| 707 | VI.viewpoint=VDI.viewpoint.AsVectorOfFloat(); |
|---|
| 708 | for (unsigned long i=0; i<5; i++) |
|---|
| 709 | VI.viewplanes[i]=Plane3fT(VDI.viewplanes[i].Normal.AsVectorOfFloat(), float(VDI.viewplanes[i].Dist)); |
|---|
| 710 | |
|---|
| 711 | glBegin(GL_TRIANGLE_STRIP); |
|---|
| 712 | if (VDI.morph) |
|---|
| 713 | { |
|---|
| 714 | ArrayT<Vector3fT>& VectorStripNew=TerrainNew.ComputeVectorStripByMorphing(VI); |
|---|
| 715 | |
|---|
| 716 | // Compare the old and new vector strips! |
|---|
| 717 | { |
|---|
| 718 | ArrayT<VectorT>& VectorStrip=mesh_morph(TerrainOld, VDI, VDI.cull ? SPHERE_MASK_UNDECIDED : SPHERE_MASK_VISIBLE); |
|---|
| 719 | |
|---|
| 720 | if (VectorStrip.Size()!=VectorStripNew.Size()) |
|---|
| 721 | { |
|---|
| 722 | printf("Compare of vstrips sizes failed! %lu %lu\n", VectorStrip.Size(), VectorStripNew.Size()); |
|---|
| 723 | } |
|---|
| 724 | else |
|---|
| 725 | { |
|---|
| 726 | for (unsigned long v=0; v<VectorStrip.Size(); v++) |
|---|
| 727 | { |
|---|
| 728 | if (VectorStrip[v].x!=VectorStripNew[v].x) { printf("Compare of vstrips 1 failed! %lu %f %f\n", v, VectorStrip[v].x, VectorStripNew[v].x); } |
|---|
| 729 | if (VectorStrip[v].y!=VectorStripNew[v].y) { printf("Compare of vstrips 2 failed! %lu %f %f\n", v, VectorStrip[v].y, VectorStripNew[v].y); } |
|---|
| 730 | if (fabs(VectorStrip[v].z-VectorStripNew[v].z)>0.001) { printf("Compare of vstrips 3 failed! diff==%.10f\n", VectorStrip[v].z-VectorStripNew[v].z); } |
|---|
| 731 | } |
|---|
| 732 | } |
|---|
| 733 | } |
|---|
| 734 | |
|---|
| 735 | // Note that the first VectorT at VectorStrip[0] must be skipped! |
|---|
| 736 | for (unsigned long VNr=1; VNr<VectorStripNew.Size(); VNr++) glVertex3f(float(VectorStripNew[VNr].x), float(VectorStripNew[VNr].y), float(VectorStripNew[VNr].z)); |
|---|
| 737 | } |
|---|
| 738 | else |
|---|
| 739 | { |
|---|
| 740 | ArrayT<VERTEXid>& IdxStripNew=TerrainNew.ComputeIndexStripByRefinement(VI); |
|---|
| 741 | |
|---|
| 742 | // Compare the old and new index strips! |
|---|
| 743 | { |
|---|
| 744 | ArrayT<VERTEXid>& IdxStrip=mesh_refine(TerrainOld, VDI, VDI.cull ? SPHERE_MASK_UNDECIDED : SPHERE_MASK_VISIBLE); |
|---|
| 745 | |
|---|
| 746 | if (IdxStrip.Size()!=IdxStripNew.Size()) { printf("Compare of istrips sizes failed!\n"); return 1; } |
|---|
| 747 | for (unsigned long v=0; v<IdxStrip.Size(); v++) |
|---|
| 748 | if (IdxStrip[v]!=IdxStripNew[v]) { printf("Compare of istrips failed!\n"); return 1; } |
|---|
| 749 | } |
|---|
| 750 | |
|---|
| 751 | // Note that the first index at IdxStrip[0] must be skipped! |
|---|
| 752 | const TerrainT::VertexT* Vertices=TerrainNew.GetVertices(); |
|---|
| 753 | |
|---|
| 754 | for (unsigned long IdxNr=1; IdxNr<IdxStripNew.Size(); IdxNr++) glVertex3f(float(Vertices[IdxStripNew[IdxNr]].x), float(Vertices[IdxStripNew[IdxNr]].y), float(Vertices[IdxStripNew[IdxNr]].z)); |
|---|
| 755 | } |
|---|
| 756 | glEnd(); |
|---|
| 757 | |
|---|
| 758 | if (TextureName==NULL) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); |
|---|
| 759 | if (TextureName==NULL) glEnable(GL_POLYGON_OFFSET_FILL); |
|---|
| 760 | |
|---|
| 761 | |
|---|
| 762 | double DeltaTime=Timer.GetSecondsSinceLastCall(); |
|---|
| 763 | |
|---|
| 764 | float MoveSpeed=float(1000.0*DeltaTime); |
|---|
| 765 | float RotSpeed =float( 90.0*DeltaTime); |
|---|
| 766 | |
|---|
| 767 | |
|---|
| 768 | SingleOpenGLWindow->SwapBuffers(); |
|---|
| 769 | |
|---|
| 770 | CaKeyboardEventT KE; |
|---|
| 771 | bool QuitProgram=false; |
|---|
| 772 | |
|---|
| 773 | while (SingleOpenGLWindow->GetNextKeyboardEvent(KE)>0) |
|---|
| 774 | { |
|---|
| 775 | if (KE.Type!=CaKeyboardEventT::CKE_KEYDOWN) continue; |
|---|
| 776 | if (KE.Key==CaKeyboardEventT::CK_ESCAPE) QuitProgram=true; |
|---|
| 777 | if (KE.Key==CaKeyboardEventT::CK_C ) { VDI.cull =!VDI.cull; printf("View frustum culling is %s.\n", VDI.cull ? "ON" : "OFF"); } |
|---|
| 778 | if (KE.Key==CaKeyboardEventT::CK_M ) { VDI.morph=!VDI.morph; printf("Geo-morphing is %s\n", VDI.morph ? "ON" : "OFF"); } |
|---|
| 779 | } |
|---|
| 780 | |
|---|
| 781 | if (QuitProgram) break; |
|---|
| 782 | |
|---|
| 783 | |
|---|
| 784 | const float vx=float(MoveSpeed*sin(Heading/180.0*3.1415926)); |
|---|
| 785 | const float vy=float(MoveSpeed*cos(Heading/180.0*3.1415926)); |
|---|
| 786 | |
|---|
| 787 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_UP ] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_W]) ViewerPos=ViewerPos+VectorT( vx, vy, 0); |
|---|
| 788 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_DOWN ] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_S]) ViewerPos=ViewerPos+VectorT(-vx, -vy, 0); |
|---|
| 789 | if ( SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_A]) ViewerPos=ViewerPos+VectorT(-vy, vx, 0); |
|---|
| 790 | if ( SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_D]) ViewerPos=ViewerPos+VectorT( vy, -vx, 0); |
|---|
| 791 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_INSERT] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_R]) ViewerPos.z+=MoveSpeed; |
|---|
| 792 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_DELETE] || SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_F]) ViewerPos.z-=MoveSpeed; |
|---|
| 793 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_LEFT ] ) Heading-=RotSpeed; |
|---|
| 794 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_RIGHT ] ) Heading+=RotSpeed; |
|---|
| 795 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_PGUP ] ) Pitch-=RotSpeed; |
|---|
| 796 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_PGDN ] ) Pitch+=RotSpeed; |
|---|
| 797 | if (SingleOpenGLWindow->GetKeyboardState()[CaKeyboardEventT::CK_END ] ) Pitch=0.0; |
|---|
| 798 | } |
|---|
| 799 | |
|---|
| 800 | SingleOpenGLWindow->Close(); |
|---|
| 801 | } |
|---|
| 802 | catch (const BitmapT::LoadErrorT& /*E*/) |
|---|
| 803 | { |
|---|
| 804 | printf("\nEither \"%s\" could not be found, not be read,\n", TerrainName); |
|---|
| 805 | printf("is not square, is smaller than 3 pixels, or not of size 2^n+1. Sorry.\n"); |
|---|
| 806 | } |
|---|
| 807 | |
|---|
| 808 | return 0; |
|---|
| 809 | } |
|---|