1 /**************************************************************************/ 2 /* Copyright 2009 Tim Day */ 3 /* */ 4 /* This file is part of Fracplanet */ 5 /* */ 6 /* Fracplanet is free software: you can redistribute it and/or modify */ 7 /* it under the terms of the GNU General Public License as published by */ 8 /* the Free Software Foundation, either version 3 of the License, or */ 9 /* (at your option) any later version. */ 10 /* */ 11 /* Fracplanet is distributed in the hope that it will be useful, */ 12 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 /* GNU General Public License for more details. */ 15 /* */ 16 /* You should have received a copy of the GNU General Public License */ 17 /* along with Fracplanet. If not, see <http://www.gnu.org/licenses/>. */ 18 /**************************************************************************/ 19 20 /*! \file 21 \brief Interface for class Geometry and derived classes. 22 */ 23 24 #ifndef _geometry_h_ 25 #define _geometry_h_ 26 27 #include "common.h" 28 #include "scan.h" 29 #include "vertex.h" 30 #include "xyz.h" 31 32 //! Class to provide abstract interface to different world geometries. 33 /*! This is an abstract base class providing methods which will differ between world geometries. 34 For example, the direction of "up" at a given point will vary depending on whether we are generating a flat world or a spherical one. 35 \todo Most of these methods should have their implementation moved geometry.cpp 36 */ 37 class Geometry : public ScanConverter 38 { 39 public: 40 41 //! Constructor. Geometry(uint seed)42 Geometry(uint seed) 43 :_r01(seed) 44 {} 45 46 //! Destructor. ~Geometry()47 virtual ~Geometry() 48 {} 49 50 //! Return the height of the given point. 51 virtual float height(const XYZ& p) const 52 =0; 53 54 //! Move the specified point vertically until. 55 virtual void set_height(XYZ& p,float v) const 56 =0; 57 58 //! Return a point halfway between the two given points. 59 virtual const XYZ midpoint(const XYZ& v0,const XYZ& v1) const 60 =0; 61 62 //! Really only meaningful for spherical geometries. 63 virtual float normalised_latitude(const XYZ&p) const 64 =0; 65 66 //! Return the direction of "up" at the specified point. 67 virtual const XYZ up(const XYZ& p) const 68 =0; 69 //! Return the direction of "north" at the specified point. 70 virtual const XYZ north(const XYZ& p) const 71 =0; 72 //! Return the direction of "east" at the specified point. 73 virtual const XYZ east(const XYZ& p) const 74 =0; 75 76 //! Add a random variation to a point 77 virtual const XYZ perturb(const XYZ& v,const XYZ& variation) const 78 =0; 79 80 //! Nasty hack to work around height setting possibly not being exact. 81 /*! In some geometries (e.g spherical, but not flat) modifying a point to be at a particular height does not guarantee that exact value will be returned on a susequent height query. 82 If this is the case, a non-zero epsilon value can be returned and used as an error tolerence when comparing two heights for equivalence. 83 */ 84 virtual float epsilon() const 85 =0; 86 87 //! Multiplier for width of a scan-converted image. scan_convert_image_aspect_ratio()88 virtual uint scan_convert_image_aspect_ratio() const 89 { 90 return 1; 91 } 92 93 protected: 94 95 //! Common scan-converter code 96 static void scan_convert_common 97 ( 98 const boost::array<XYZ,3>& v, 99 const ScanConvertBackend& backend 100 ); 101 102 //! Random number generator used for perturbations and the like. 103 /*! Declared mutable so it can be used in const methods. 104 \todo Perhaps theres a better place for the geometry random number generator to live: having it here creates the anomaly of having to pass random seeds into apparently non-random objects like icosahedrons etc. 105 */ 106 mutable Random01 _r01; 107 }; 108 109 //! Concrete class providing a flat geometry (in the XY-plane, with Z up). 110 class GeometryFlat : public Geometry 111 { 112 public: 113 GeometryFlat(uint seed)114 GeometryFlat(uint seed) 115 :Geometry(seed) 116 {} 117 ~GeometryFlat()118 ~GeometryFlat() 119 {} 120 121 //! Height is just the z co-ordinate of a point. height(const XYZ & p)122 float height(const XYZ& p) const 123 { 124 return p.z; 125 } 126 127 //! Setting a height is simply assigning to the z-coordinate. set_height(XYZ & p,float v)128 void set_height(XYZ& p,float v) const 129 { 130 p.z=v; 131 } 132 133 //! The mid-point between two points is simply their average. midpoint(const XYZ & v0,const XYZ & v1)134 const XYZ midpoint(const XYZ& v0,const XYZ& v1) const 135 { 136 return 0.5f*(v0+v1); 137 } 138 139 //! This doesn't really mean anything here, so return zero, which would correspond to the equator of a spherical geometry. normalised_latitude(const XYZ &)140 float normalised_latitude(const XYZ&) const 141 { 142 return 0.0f; 143 } 144 145 //! Returns unit z vector. (Up is the same everywhere in this geometry). up(const XYZ &)146 const XYZ up(const XYZ&) const 147 { 148 return XYZ(0.0f,0.0f,1.0f); 149 } 150 151 //! Returns unit y vector. (North is the same everywhere in this geometry). north(const XYZ &)152 const XYZ north(const XYZ&) const 153 { 154 return XYZ(0.0f,1.0f,0.0f); 155 } 156 157 //! Returns unit x vector. (East is the same everywhere in this geometry). east(const XYZ &)158 const XYZ east(const XYZ&) const 159 { 160 return XYZ(1.0f,0.0f,0.0f); 161 } 162 163 //! Add a random variation to a point. perturb(const XYZ & p,const XYZ & variation)164 const XYZ perturb(const XYZ& p,const XYZ& variation) const 165 { 166 // The correct thing to do would be to return p+RandomXYZInEllipsoid(_r01,variation); 167 // however, this uses a variable number of random number calls which means small parameter changes can have big effects on generated terrain. 168 169 // This, on the other hand, always uses the same number of random numbers, but isn't statistically equivalent: 170 return p+RandomXYZInBox(_r01,variation); 171 } 172 173 //! Returns zero. Heights are stored exactly once assigned so no need for non-zero epsilon. epsilon()174 float epsilon() const 175 { 176 return 0.0f; // No need 'cos heights are stored exactly 177 } 178 179 virtual void scan_convert 180 ( 181 const boost::array<XYZ,3>& v, 182 const ScanConvertBackend& 183 ) const; 184 }; 185 186 //! Concrete class providing a flat geometry (a sphere with nominal radius 1, equator in the XY-plane, Z axis through the poles). 187 class GeometrySpherical : public Geometry 188 { 189 public: 190 //! Constructor. GeometrySpherical(uint seed)191 GeometrySpherical(uint seed) 192 :Geometry(seed) 193 {} 194 195 //! Destructor. ~GeometrySpherical()196 ~GeometrySpherical() 197 {} 198 199 //! Height is relative to the surface of the unit radius sphere. height(const XYZ & p)200 float height(const XYZ& p) const 201 { 202 return p.magnitude()-1.0f; 203 } 204 205 //! The height set is relative to the surface of the unit radius sphere. set_height(XYZ & p,float h)206 void set_height(XYZ& p,float h) const 207 { 208 const float m=p.magnitude(); 209 p*=((1.0f+h)/m); 210 } 211 212 //! Don't just take the mid-point of the straight-line path through the sphere's surface: must work relative to the sphere's surface. midpoint(const XYZ & v0,const XYZ & v1)213 const XYZ midpoint(const XYZ& v0,const XYZ& v1) const 214 { 215 const float h0=v0.magnitude(); 216 const float h1=v1.magnitude(); 217 const float h_av=0.5f*(h0+h1); 218 219 const XYZ m(0.5f*(v0+v1)); 220 return (h_av/m.magnitude())*m; 221 } 222 223 //! Normalised latitude is 1.0 at the north pole, -1.0 at the south pole normalised_latitude(const XYZ & p)224 float normalised_latitude(const XYZ& p) const 225 { 226 return p.z; 227 } 228 229 //! Up is normal to the sphere. up(const XYZ & p)230 const XYZ up(const XYZ& p) const 231 { 232 return p.normalised(); 233 } 234 235 //! North is perpendicular to "up" and "east" 236 /*! \warning Returns zero vector at the poles 237 */ north(const XYZ & p)238 const XYZ north(const XYZ& p) const 239 { 240 if (p.x==0.0f && p.y==0.0f) 241 return XYZ(0.0f,0.0f,0.0f); 242 else 243 return (up(p)*east(p)).normalised(); 244 } 245 246 //! East is perpendicular to "up" and the polar vector. 247 /*! \warning Returns zero vector at the poles 248 */ east(const XYZ & p)249 const XYZ east(const XYZ& p) const 250 { 251 if (p.x==0.0f && p.y==0.0f) 252 return XYZ(0.0f,0.0f,0.0f); 253 else 254 return (XYZ(0.0f,0.0f,1.0f)*up(p)).normalised(); 255 } 256 257 //! Add a random variation to a point. 258 /*! In spherical geometry, the variation ellipsoid tracks the surface (ie z corresponds to up, north to y) 259 */ perturb(const XYZ & p,const XYZ & variation)260 const XYZ perturb(const XYZ& p,const XYZ& variation) const 261 { 262 // The correct thing to do would be to use const RandomXYZInEllipsoid v(_r01,variation); 263 // however, this uses a variable number of random number calls which means small parameter changes can have big effects on generated terrain. 264 265 // This, on the other hand, always uses the same number of random numbers, but isn't statistically equivalent: 266 const RandomXYZInBox v(_r01,variation); 267 return p+v.x*east(p)+v.y*north(p)+v.z*up(p); 268 } 269 270 //! This needs to return something small for the lake flooding algorithm to work. epsilon()271 float epsilon() const 272 { 273 return 0.000001f; 274 } 275 276 void scan_convert 277 ( 278 const boost::array<XYZ,3>& v, 279 const ScanConvertBackend& 280 ) const; 281 282 //! Return 2.0 for spheres because vertical range is +/- pi/2, horizontal is +/- pi scan_convert_image_aspect_ratio()283 uint scan_convert_image_aspect_ratio() const 284 { 285 return 2; 286 } 287 }; 288 289 #endif 290