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