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