]> git.mxchange.org Git - simgear.git/blob - simgear/misc/texcoord.cxx
Removal of PLIB/SG from SimGear
[simgear.git] / simgear / misc / texcoord.cxx
1 // texcoord.hxx -- routine(s) to handle texture coordinate generation
2 //
3 // Written by Curtis Olson, started March 1999.
4 //
5 // Copyright (C) 1999  Curtis L. Olson  - http://www.flightgear.org/~curt
6 //
7 // This library is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU Library General Public
9 // License as published by the Free Software Foundation; either
10 // version 2 of the License, or (at your option) any later version.
11 //
12 // This library is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // Library General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23
24 /* The following is an explanation of our somewhat conveluted and
25    tricky texture scaling/offset scheme:
26
27 MAX_TEX_COORD is a value I arrived at by trial and error for my
28 voodoo2/3 video card.  If you use texture coordinates that are too
29 big, you quickly start getting into round off problems and the texture
30 jumps and moves relative to the polygon.
31
32 The point of all of this code is that I wanted to be able to define
33 this size in meters of a texture and have it be applied seamlessly to
34 the terrain.  I wanted to be able to change the defined size (in
35 meters) of textures at run time.  In other words I want to be able to
36 scale the textures at run time and still have them seamlessly tile
37 together across fans.
38
39 The problem is that I have to pregenerate all the texture coordinates
40 when I create the scenery, and I didn't want to burn CPU doing this
41 again when I load the scenery at run time.
42
43 It ended up taking me a lot of thought, a lot of trial and error, and
44 a lot of fiddling around to come up with a scheme that worked.
45
46 ----------
47
48 Ok, so think about what needs to be done to have the texture tile
49 across a series of triangles and fans ...
50
51 Basically you want to use some function of lon/lat mod your max
52 texture coordinate size to calculate the texture coordinate of each
53 vertex.  This should result in nice tiling across distinct triangles
54 and fans.
55
56 Pretend our MAX_TEX_COORD = 4.0 and half of this is 2.0
57
58 Imagine the following two adjacent polygons with the "X" component of
59 the initial texture coordinate based on longitude (Note they are drawn
60 spaced apart, but in reality the two polygons are adjacent):
61
62     7.0   8.6   8.6    9.0
63      *-----*     *------*
64      |     |     |      |
65
66 Now, this exceeds our MAX_TEX_COORD of 4.0 so we have to scale these
67 texture coordinates by some integer value.  Let's say we always want
68 to minimize the tex coordinates to minimize rounding error so we will
69 offset the first polygon by 7.0 and the second by 8.0:
70
71     0.0 --- 1.6 and 0.6 --- 1.0
72
73 Our tiling is maintianed becuase the coordinates are continous (mod
74 1.0) and we still get the double repeat across both polygons.
75
76 We want to be able to scale these values by an arbitrary constant and
77 still have proper tiling.
78
79 Let's try doubling the coordinates:
80
81     0.0 --- 3.2 and 1.2 --- 2.0
82
83 Everything still tiles nicely (because the coordinates are continuous
84 mod 1.0) and the texture is now repeated 4x across the two polygons.
85 Before it was repeated 2x.
86
87 Let's try halving the coordinates:
88
89     0.0 --- 0.8 and 0.3 --- 0.5
90
91 Ooop!  We lost continuity in texture coordinate space ... no we will
92 have a visual discontinuity in the texture tiling!
93
94 Ok, so we need some other scheme to keep our texture coordinates
95 smaller than MAX_TEX_COORD that preserves continuity in texture
96 space.  <Deep breath> let's try the scheme that I have coded up that
97 you are asking about ... <fingers crossed> :-)
98
99 Going way back to the top before we shifted the texture coordinates.
100 tmin for the first polygon is 7.0, this is then adjusted to:
101
102     (int)(tmin.x() / HALF_MAX_TEX_COORD) ) * HALF_MAX_TEX_COORD
103
104     = (int)(7.0/2.0) * 2.0 = 3.0 * 2.0 = 6.0
105
106 The two texture coordinates are offset by 6.0 which yields 1.0 -- 2.6
107
108 tmin for the second polygon is 8.6 which is adjusted to:
109
110     (int)(tmin.x() / HALF_MAX_TEX_COORD) ) * HALF_MAX_TEX_COORD
111     = (int)( 8.6 / 2.0 ) * 2.0 = 4.0 * 2.0 = 8.0
112
113 The offset for the second polygon is 8.0 which yields 0.6 --- 1.0
114
115 So now we have:
116
117     1.0 --- 2.6 and 0.6 --- 1.0
118
119 This still tiles nicely and strethes our texture across completely, so
120 far we haven't done any damage.
121
122 Now let's double the coordinates:
123
124      2.0 --- 5.2 and 1.2 --- 2.0
125
126 The texture is repeated 4x as it should be and is still continuous.
127
128 How about halfing the coordinates.  This is where the first scheme
129 broke down.  Halving the coordinates yields
130
131     0.5 --- 1.3 and 0.3 --- 0.5
132
133 Woohoo, we still have texture space continuity (mod 1.0) and the
134 texture is repeated 1x.
135
136 Note, it took me almost as long to re-figure this out and write this
137 explanation as it did to figure out the scheme originally.  I better
138 enter this in the official comments in case I forget again. :-)
139
140 */
141
142 #ifdef HAVE_CONFIG_H
143 #  include <simgear_config.h>
144 #endif
145
146 #include <simgear/compiler.h>
147
148 // #include <iostream>
149
150 #include "texcoord.hxx"
151
152 // using std::cout;
153 // using std::endl;
154
155
156 #define FG_STANDARD_TEXTURE_DIMENSION 1000.0 // meters
157 #define MAX_TEX_COORD 8.0
158 #define HALF_MAX_TEX_COORD ( MAX_TEX_COORD * 0.5 )
159
160
161 // return the basic unshifted/unmoded texture coordinate for a lat/lon
162 static inline SGVec2f basic_tex_coord( const SGGeod& p, 
163                                        double degree_width,
164                                        double degree_height,
165                                        double scale )
166 {
167     return SGVec2f( p.getLongitudeDeg() * ( degree_width * scale / 
168                               FG_STANDARD_TEXTURE_DIMENSION ),
169                     p.getLatitudeDeg() * ( degree_height * scale /
170                               FG_STANDARD_TEXTURE_DIMENSION )
171                     );
172 }
173
174
175 // traverse the specified fan/strip/list of vertices and attempt to
176 // calculate "none stretching" texture coordinates
177 std::vector<SGVec2f> sgCalcTexCoords( const SGBucket& b, const std::vector<SGGeod>& geod_nodes,
178                             const int_list& fan, double scale )
179 {
180     return sgCalcTexCoords(b.get_center_lat(), geod_nodes, fan, scale);
181 }
182
183 std::vector<SGVec2f> sgCalcTexCoords( double centerLat, const std::vector<SGGeod>& geod_nodes,
184                             const int_list& fan, double scale )
185 {
186     // cout << "calculating texture coordinates for a specific fan of size = "
187     //      << fan.size() << endl;
188
189     // calculate perimeter based on center of this degree (not center
190     // of bucket)
191     double clat = (int)centerLat;
192     if ( clat > 0 ) {
193         clat = (int)clat + 0.5;
194     } else {
195         clat = (int)clat - 0.5;
196     }
197
198     double clat_rad = clat * SGD_DEGREES_TO_RADIANS;
199     double cos_lat = cos( clat_rad );
200     double local_radius = cos_lat * SG_EQUATORIAL_RADIUS_M;
201     double local_perimeter = local_radius * SGD_2PI;
202     double degree_width = local_perimeter / 360.0;
203
204     // cout << "clat = " << clat << endl;
205     // cout << "clat (radians) = " << clat_rad << endl;
206     // cout << "cos(lat) = " << cos_lat << endl;
207     // cout << "local_radius = " << local_radius << endl;
208     // cout << "local_perimeter = " << local_perimeter << endl;
209     // cout << "degree_width = " << degree_width << endl;
210
211     double perimeter = SG_EQUATORIAL_RADIUS_M * SGD_2PI;
212     double degree_height = perimeter / 360.0;
213     // cout << "degree_height = " << degree_height << endl;
214
215     // find min/max of fan
216     SGVec2f tmin, tmax;
217     bool first = true;
218
219     int i;
220
221     for ( i = 0; i < (int)fan.size(); ++i ) {
222       SGGeod p = geod_nodes[ fan[i] ];
223         // cout << "point p = " << p << endl;
224
225         SGVec2f t = basic_tex_coord( p, degree_width, degree_height, scale );
226         // cout << "basic_tex_coord = " << t << endl;
227
228         if ( first ) {
229             tmin = tmax = t;
230             first = false;
231         } else {
232             if ( t.x() < tmin.x() ) {
233                 tmin.x() = t.x();
234             }
235             if ( t.y() < tmin.y() ) {
236                 tmin.y() = t.y();
237             }
238             if ( t.x() > tmax.x() ) {
239                 tmax.x() = t.x();
240             }
241             if ( t.y() > tmax.y() ) {
242                 tmax.y() = t.y();
243             }
244         }
245     }
246
247     double dx = fabs( tmax.x() - tmin.x() );
248     double dy = fabs( tmax.y() - tmin.y() );
249     // cout << "dx = " << dx << " dy = " << dy << endl;
250
251     // Point3D mod_shift;
252     if ( (dx > HALF_MAX_TEX_COORD) || (dy > HALF_MAX_TEX_COORD) ) {
253         // structure is too big, we'll just have to shift it so that
254         // tmin = (0,0).  This messes up subsequent texture scaling,
255         // but is the best we can do.
256         // cout << "SHIFTING" << endl;
257         if ( tmin.x() < 0 ) {
258             tmin.x() = (double)( (int)tmin.x() - 1 ) ;
259         } else {
260             tmin.x() = (int)tmin.x();
261         }
262   
263         if ( tmin.y() < 0 ) {
264             tmin.y() = (double)( (int)tmin.y() - 1 );
265         } else {
266             tmin.y() = (int)tmin.y();
267         }
268         // cout << "found tmin = " << tmin << endl;
269     } else {
270         if ( tmin.x() < 0 ) {
271             tmin.x() =  ( (int)(tmin.x() / HALF_MAX_TEX_COORD) - 1 )
272                        * HALF_MAX_TEX_COORD ;
273         } else {
274             tmin.x() =  ( (int)(tmin.x() / HALF_MAX_TEX_COORD) )
275                        * HALF_MAX_TEX_COORD ;
276         }
277         if ( tmin.y() < 0 ) {
278             tmin.y() =  ( (int)(tmin.y() / HALF_MAX_TEX_COORD) - 1 )
279                        * HALF_MAX_TEX_COORD ;
280         } else {
281             tmin.y() =  ( (int)(tmin.y() / HALF_MAX_TEX_COORD) )
282                        * HALF_MAX_TEX_COORD ;
283         }
284 #if 0
285         // structure is small enough ... we can mod it so we can
286         // properly scale the texture coordinates later.
287         // cout << "MODDING" << endl;
288         double x1 = fmod(tmin.x(), MAX_TEX_COORD);
289         while ( x1 < 0 ) { x1 += MAX_TEX_COORD; }
290
291         double y1 = fmod(tmin.y(), MAX_TEX_COORD);
292         while ( y1 < 0 ) { y1 += MAX_TEX_COORD; }
293
294         double x2 = fmod(tmax.x(), MAX_TEX_COORD);
295         while ( x2 < 0 ) { x2 += MAX_TEX_COORD; }
296
297         double y2 = fmod(tmax.y(), MAX_TEX_COORD);
298         while ( y2 < 0 ) { y2 += MAX_TEX_COORD; }
299         
300         // At this point we know that the object is < 16 wide in
301         // texture coordinate space.  If the modulo of the tmin is >
302         // the mod of the tmax at this point, then we know that the
303         // starting tex coordinate for the tmax > 16 so we can shift
304         // everything down by 16 and get it within the 0-32 range.
305
306         if ( x1 > x2 ) {
307             mod_shift.setx( HALF_MAX_TEX_COORD );
308         } else {
309             mod_shift.setx( 0.0 );
310         }
311
312         if ( y1 > y2 ) {
313             mod_shift.sety( HALF_MAX_TEX_COORD );
314         } else {
315             mod_shift.sety( 0.0 );
316         }
317 #endif
318         // cout << "mod_shift = " << mod_shift << endl;
319     }
320
321     // generate tex_list
322     SGVec2f adjusted_t;
323     std::vector<SGVec2f> tex;
324     tex.clear();
325     for ( i = 0; i < (int)fan.size(); ++i ) {
326         SGGeod p = geod_nodes[ fan[i] ];
327         SGVec2f t = basic_tex_coord( p, degree_width, degree_height, scale );
328         // cout << "second t = " << t << endl;
329
330         adjusted_t = t - tmin;
331 #if 0
332         } else {
333             adjusted_t.setx( fmod(t.x() + mod_shift.x(), MAX_TEX_COORD) );
334             while ( adjusted_t.x() < 0 ) { 
335                 adjusted_t.setx( adjusted_t.x() + MAX_TEX_COORD );
336             }
337             adjusted_t.sety( fmod(t.y() + mod_shift.y(), MAX_TEX_COORD) );
338             while ( adjusted_t.y() < 0 ) {
339                 adjusted_t.sety( adjusted_t.y() + MAX_TEX_COORD );
340             }
341             // cout << "adjusted_t " << adjusted_t << endl;
342         }
343 #endif
344         if ( adjusted_t.x() < SG_EPSILON ) {
345             adjusted_t.x() = 0.0;
346         }
347         if ( adjusted_t.y() < SG_EPSILON ) {
348             adjusted_t.y() = 0.0;
349         }
350
351         // cout << "adjusted_t = " << adjusted_t << endl;
352         
353         tex.push_back( adjusted_t );
354     }
355
356     return tex;
357 }