]> git.mxchange.org Git - simgear.git/blob - simgear/misc/texcoord.cxx
Give calc_tex_coords() a more conformant name: sgCalcTexCoords()
[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 <simgear/compiler.h>
144
145 // #include STL_IOSTREAM
146
147 #include "texcoord.hxx"
148
149 // SG_USING_STD(cout);
150 // SG_USING_STD(endl);
151
152
153 #define FG_STANDARD_TEXTURE_DIMENSION 1000.0 // meters
154 #define MAX_TEX_COORD 8.0
155 #define HALF_MAX_TEX_COORD ( MAX_TEX_COORD * 0.5 )
156
157
158 // return the basic unshifted/unmoded texture coordinate for a lat/lon
159 static inline Point3D basic_tex_coord( const Point3D& p, 
160                                        double degree_width,
161                                        double degree_height,
162                                        double scale )
163 {
164     return Point3D( p.x() * ( degree_width * scale / 
165                               FG_STANDARD_TEXTURE_DIMENSION ),
166                     p.y() * ( degree_height * scale /
167                               FG_STANDARD_TEXTURE_DIMENSION ),
168                     0.0 );
169 }
170
171
172 // traverse the specified fan/strip/list of vertices and attempt to
173 // calculate "none stretching" texture coordinates
174 point_list sgCalcTexCoords( const SGBucket& b, const point_list& geod_nodes,
175                             const int_list& fan, double scale )
176 {
177     // cout << "calculating texture coordinates for a specific fan of size = "
178     //      << fan.size() << endl;
179
180     // calculate perimeter based on center of this degree (not center
181     // of bucket)
182     double clat = (int)b.get_center_lat();
183     if ( clat > 0 ) {
184         clat = (int)clat + 0.5;
185     } else {
186         clat = (int)clat - 0.5;
187     }
188
189     double clat_rad = clat * SGD_DEGREES_TO_RADIANS;
190     double cos_lat = cos( clat_rad );
191     double local_radius = cos_lat * SG_EQUATORIAL_RADIUS_M;
192     double local_perimeter = 2.0 * local_radius * SGD_PI;
193     double degree_width = local_perimeter / 360.0;
194
195     // cout << "clat = " << clat << endl;
196     // cout << "clat (radians) = " << clat_rad << endl;
197     // cout << "cos(lat) = " << cos_lat << endl;
198     // cout << "local_radius = " << local_radius << endl;
199     // cout << "local_perimeter = " << local_perimeter << endl;
200     // cout << "degree_width = " << degree_width << endl;
201
202     double perimeter = 2.0 * SG_EQUATORIAL_RADIUS_M * SGD_PI;
203     double degree_height = perimeter / 360.0;
204     // cout << "degree_height = " << degree_height << endl;
205
206     // find min/max of fan
207     Point3D tmin, tmax, p, t;
208     bool first = true;
209
210     int i;
211
212     for ( i = 0; i < (int)fan.size(); ++i ) {
213         p = geod_nodes[ fan[i] ];
214         // cout << "point p = " << p << endl;
215
216         t = basic_tex_coord( p, degree_width, degree_height, scale );
217         // cout << "basic_tex_coord = " << t << endl;
218
219         if ( first ) {
220             tmin = tmax = t;
221             first = false;
222         } else {
223             if ( t.x() < tmin.x() ) {
224                 tmin.setx( t.x() );
225             }
226             if ( t.y() < tmin.y() ) {
227                 tmin.sety( t.y() );
228             }
229             if ( t.x() > tmax.x() ) {
230                 tmax.setx( t.x() );
231             }
232             if ( t.y() > tmax.y() ) {
233                 tmax.sety( t.y() );
234             }
235         }
236     }
237
238     double dx = fabs( tmax.x() - tmin.x() );
239     double dy = fabs( tmax.y() - tmin.y() );
240     // cout << "dx = " << dx << " dy = " << dy << endl;
241
242     // Point3D mod_shift;
243     if ( (dx > HALF_MAX_TEX_COORD) || (dy > HALF_MAX_TEX_COORD) ) {
244         // structure is too big, we'll just have to shift it so that
245         // tmin = (0,0).  This messes up subsequent texture scaling,
246         // but is the best we can do.
247         // cout << "SHIFTING" << endl;
248         if ( tmin.x() < 0 ) {
249             tmin.setx( (double)( (int)tmin.x() - 1 ) );
250         } else {
251             tmin.setx( (int)tmin.x() );
252         }
253         if ( tmin.y() < 0 ) {
254             tmin.sety( (double)( (int)tmin.y() - 1 ) );
255         } else {
256             tmin.sety( (int)tmin.y() );
257         }
258         // cout << "found tmin = " << tmin << endl;
259     } else {
260         if ( tmin.x() < 0 ) {
261             tmin.setx( ( (int)(tmin.x() / HALF_MAX_TEX_COORD) - 1 )
262                        * HALF_MAX_TEX_COORD );
263         } else {
264             tmin.setx( ( (int)(tmin.x() / HALF_MAX_TEX_COORD) )
265                        * HALF_MAX_TEX_COORD );
266         }
267         if ( tmin.y() < 0 ) {
268             tmin.sety( ( (int)(tmin.y() / HALF_MAX_TEX_COORD) - 1 )
269                        * HALF_MAX_TEX_COORD );
270         } else {
271             tmin.sety( ( (int)(tmin.y() / HALF_MAX_TEX_COORD) )
272                        * HALF_MAX_TEX_COORD );
273         }
274 #if 0
275         // structure is small enough ... we can mod it so we can
276         // properly scale the texture coordinates later.
277         // cout << "MODDING" << endl;
278         double x1 = fmod(tmin.x(), MAX_TEX_COORD);
279         while ( x1 < 0 ) { x1 += MAX_TEX_COORD; }
280
281         double y1 = fmod(tmin.y(), MAX_TEX_COORD);
282         while ( y1 < 0 ) { y1 += MAX_TEX_COORD; }
283
284         double x2 = fmod(tmax.x(), MAX_TEX_COORD);
285         while ( x2 < 0 ) { x2 += MAX_TEX_COORD; }
286
287         double y2 = fmod(tmax.y(), MAX_TEX_COORD);
288         while ( y2 < 0 ) { y2 += MAX_TEX_COORD; }
289         
290         // At this point we know that the object is < 16 wide in
291         // texture coordinate space.  If the modulo of the tmin is >
292         // the mod of the tmax at this point, then we know that the
293         // starting tex coordinate for the tmax > 16 so we can shift
294         // everything down by 16 and get it within the 0-32 range.
295
296         if ( x1 > x2 ) {
297             mod_shift.setx( HALF_MAX_TEX_COORD );
298         } else {
299             mod_shift.setx( 0.0 );
300         }
301
302         if ( y1 > y2 ) {
303             mod_shift.sety( HALF_MAX_TEX_COORD );
304         } else {
305             mod_shift.sety( 0.0 );
306         }
307 #endif
308         // cout << "mod_shift = " << mod_shift << endl;
309     }
310
311     // generate tex_list
312     Point3D adjusted_t;
313     point_list tex;
314     tex.clear();
315     for ( i = 0; i < (int)fan.size(); ++i ) {
316         p = geod_nodes[ fan[i] ];
317         t = basic_tex_coord( p, degree_width, degree_height, scale );
318         // cout << "second t = " << t << endl;
319
320         adjusted_t = t - tmin;
321 #if 0
322         } else {
323             adjusted_t.setx( fmod(t.x() + mod_shift.x(), MAX_TEX_COORD) );
324             while ( adjusted_t.x() < 0 ) { 
325                 adjusted_t.setx( adjusted_t.x() + MAX_TEX_COORD );
326             }
327             adjusted_t.sety( fmod(t.y() + mod_shift.y(), MAX_TEX_COORD) );
328             while ( adjusted_t.y() < 0 ) {
329                 adjusted_t.sety( adjusted_t.y() + MAX_TEX_COORD );
330             }
331             // cout << "adjusted_t " << adjusted_t << endl;
332         }
333 #endif
334         if ( adjusted_t.x() < SG_EPSILON ) {
335             adjusted_t.setx( 0.0 );
336         }
337         if ( adjusted_t.y() < SG_EPSILON ) {
338             adjusted_t.sety( 0.0 );
339         }
340         adjusted_t.setz( 0.0 );
341         // cout << "adjusted_t = " << adjusted_t << endl;
342         
343         tex.push_back( adjusted_t );
344     }
345
346     return tex;
347 }