00001 // texcoord.hxx -- routine(s) to handle texture coordinate generation 00002 // 00003 // Written by Curtis Olson, started March 1999. 00004 // 00005 // Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt 00006 // 00007 // This library is free software; you can redistribute it and/or 00008 // modify it under the terms of the GNU Library General Public 00009 // License as published by the Free Software Foundation; either 00010 // version 2 of the License, or (at your option) any later version. 00011 // 00012 // This library is distributed in the hope that it will be useful, 00013 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 // Library General Public License for more details. 00016 // 00017 // You should have received a copy of the GNU General Public License 00018 // along with this program; if not, write to the Free Software 00019 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00020 // 00021 // $Id: texcoord_8cxx_source.html,v 1.3 2010/02/23 22:10:17 curt Exp $ 00022 00023 00024 /* The following is an explanation of our somewhat conveluted and 00025 tricky texture scaling/offset scheme: 00026 00027 MAX_TEX_COORD is a value I arrived at by trial and error for my 00028 voodoo2/3 video card. If you use texture coordinates that are too 00029 big, you quickly start getting into round off problems and the texture 00030 jumps and moves relative to the polygon. 00031 00032 The point of all of this code is that I wanted to be able to define 00033 this size in meters of a texture and have it be applied seamlessly to 00034 the terrain. I wanted to be able to change the defined size (in 00035 meters) of textures at run time. In other words I want to be able to 00036 scale the textures at run time and still have them seamlessly tile 00037 together across fans. 00038 00039 The problem is that I have to pregenerate all the texture coordinates 00040 when I create the scenery, and I didn't want to burn CPU doing this 00041 again when I load the scenery at run time. 00042 00043 It ended up taking me a lot of thought, a lot of trial and error, and 00044 a lot of fiddling around to come up with a scheme that worked. 00045 00046 ---------- 00047 00048 Ok, so think about what needs to be done to have the texture tile 00049 across a series of triangles and fans ... 00050 00051 Basically you want to use some function of lon/lat mod your max 00052 texture coordinate size to calculate the texture coordinate of each 00053 vertex. This should result in nice tiling across distinct triangles 00054 and fans. 00055 00056 Pretend our MAX_TEX_COORD = 4.0 and half of this is 2.0 00057 00058 Imagine the following two adjacent polygons with the "X" component of 00059 the initial texture coordinate based on longitude (Note they are drawn 00060 spaced apart, but in reality the two polygons are adjacent): 00061 00062 7.0 8.6 8.6 9.0 00063 *-----* *------* 00064 | | | | 00065 00066 Now, this exceeds our MAX_TEX_COORD of 4.0 so we have to scale these 00067 texture coordinates by some integer value. Let's say we always want 00068 to minimize the tex coordinates to minimize rounding error so we will 00069 offset the first polygon by 7.0 and the second by 8.0: 00070 00071 0.0 --- 1.6 and 0.6 --- 1.0 00072 00073 Our tiling is maintianed becuase the coordinates are continous (mod 00074 1.0) and we still get the double repeat across both polygons. 00075 00076 We want to be able to scale these values by an arbitrary constant and 00077 still have proper tiling. 00078 00079 Let's try doubling the coordinates: 00080 00081 0.0 --- 3.2 and 1.2 --- 2.0 00082 00083 Everything still tiles nicely (because the coordinates are continuous 00084 mod 1.0) and the texture is now repeated 4x across the two polygons. 00085 Before it was repeated 2x. 00086 00087 Let's try halving the coordinates: 00088 00089 0.0 --- 0.8 and 0.3 --- 0.5 00090 00091 Ooop! We lost continuity in texture coordinate space ... no we will 00092 have a visual discontinuity in the texture tiling! 00093 00094 Ok, so we need some other scheme to keep our texture coordinates 00095 smaller than MAX_TEX_COORD that preserves continuity in texture 00096 space. <Deep breath> let's try the scheme that I have coded up that 00097 you are asking about ... <fingers crossed> :-) 00098 00099 Going way back to the top before we shifted the texture coordinates. 00100 tmin for the first polygon is 7.0, this is then adjusted to: 00101 00102 (int)(tmin.x() / HALF_MAX_TEX_COORD) ) * HALF_MAX_TEX_COORD 00103 00104 = (int)(7.0/2.0) * 2.0 = 3.0 * 2.0 = 6.0 00105 00106 The two texture coordinates are offset by 6.0 which yields 1.0 -- 2.6 00107 00108 tmin for the second polygon is 8.6 which is adjusted to: 00109 00110 (int)(tmin.x() / HALF_MAX_TEX_COORD) ) * HALF_MAX_TEX_COORD 00111 = (int)( 8.6 / 2.0 ) * 2.0 = 4.0 * 2.0 = 8.0 00112 00113 The offset for the second polygon is 8.0 which yields 0.6 --- 1.0 00114 00115 So now we have: 00116 00117 1.0 --- 2.6 and 0.6 --- 1.0 00118 00119 This still tiles nicely and strethes our texture across completely, so 00120 far we haven't done any damage. 00121 00122 Now let's double the coordinates: 00123 00124 2.0 --- 5.2 and 1.2 --- 2.0 00125 00126 The texture is repeated 4x as it should be and is still continuous. 00127 00128 How about halfing the coordinates. This is where the first scheme 00129 broke down. Halving the coordinates yields 00130 00131 0.5 --- 1.3 and 0.3 --- 0.5 00132 00133 Woohoo, we still have texture space continuity (mod 1.0) and the 00134 texture is repeated 1x. 00135 00136 Note, it took me almost as long to re-figure this out and write this 00137 explanation as it did to figure out the scheme originally. I better 00138 enter this in the official comments in case I forget again. :-) 00139 00140 */ 00141 00142 #ifdef HAVE_CONFIG_H 00143 # include <simgear_config.h> 00144 #endif 00145 00146 #include <simgear/compiler.h> 00147 00148 // #include <iostream> 00149 00150 #include "texcoord.hxx" 00151 00152 #include <simgear/math/point3d.hxx> 00153 // using std::cout; 00154 // using std::endl; 00155 00156 00157 #define FG_STANDARD_TEXTURE_DIMENSION 1000.0 // meters 00158 #define MAX_TEX_COORD 8.0 00159 #define HALF_MAX_TEX_COORD ( MAX_TEX_COORD * 0.5 ) 00160 00161 00162 // return the basic unshifted/unmoded texture coordinate for a lat/lon 00163 static inline Point3D basic_tex_coord( const Point3D& p, 00164 double degree_width, 00165 double degree_height, 00166 double scale ) 00167 { 00168 return Point3D( p.x() * ( degree_width * scale / 00169 FG_STANDARD_TEXTURE_DIMENSION ), 00170 p.y() * ( degree_height * scale / 00171 FG_STANDARD_TEXTURE_DIMENSION ), 00172 0.0 ); 00173 } 00174 00175 00176 // traverse the specified fan/strip/list of vertices and attempt to 00177 // calculate "none stretching" texture coordinates 00178 point_list sgCalcTexCoords( const SGBucket& b, const point_list& geod_nodes, 00179 const int_list& fan, double scale ) 00180 { 00181 return sgCalcTexCoords(b.get_center_lat(), geod_nodes, fan, scale); 00182 } 00183 00184 point_list sgCalcTexCoords( double centerLat, const point_list& geod_nodes, 00185 const int_list& fan, double scale ) 00186 { 00187 // cout << "calculating texture coordinates for a specific fan of size = " 00188 // << fan.size() << endl; 00189 00190 // calculate perimeter based on center of this degree (not center 00191 // of bucket) 00192 double clat = (int)centerLat; 00193 if ( clat > 0 ) { 00194 clat = (int)clat + 0.5; 00195 } else { 00196 clat = (int)clat - 0.5; 00197 } 00198 00199 double clat_rad = clat * SGD_DEGREES_TO_RADIANS; 00200 double cos_lat = cos( clat_rad ); 00201 double local_radius = cos_lat * SG_EQUATORIAL_RADIUS_M; 00202 double local_perimeter = local_radius * SGD_2PI; 00203 double degree_width = local_perimeter / 360.0; 00204 00205 // cout << "clat = " << clat << endl; 00206 // cout << "clat (radians) = " << clat_rad << endl; 00207 // cout << "cos(lat) = " << cos_lat << endl; 00208 // cout << "local_radius = " << local_radius << endl; 00209 // cout << "local_perimeter = " << local_perimeter << endl; 00210 // cout << "degree_width = " << degree_width << endl; 00211 00212 double perimeter = SG_EQUATORIAL_RADIUS_M * SGD_2PI; 00213 double degree_height = perimeter / 360.0; 00214 // cout << "degree_height = " << degree_height << endl; 00215 00216 // find min/max of fan 00217 Point3D tmin, tmax, p, t; 00218 bool first = true; 00219 00220 int i; 00221 00222 for ( i = 0; i < (int)fan.size(); ++i ) { 00223 p = geod_nodes[ fan[i] ]; 00224 // cout << "point p = " << p << endl; 00225 00226 t = basic_tex_coord( p, degree_width, degree_height, scale ); 00227 // cout << "basic_tex_coord = " << t << endl; 00228 00229 if ( first ) { 00230 tmin = tmax = t; 00231 first = false; 00232 } else { 00233 if ( t.x() < tmin.x() ) { 00234 tmin.setx( t.x() ); 00235 } 00236 if ( t.y() < tmin.y() ) { 00237 tmin.sety( t.y() ); 00238 } 00239 if ( t.x() > tmax.x() ) { 00240 tmax.setx( t.x() ); 00241 } 00242 if ( t.y() > tmax.y() ) { 00243 tmax.sety( t.y() ); 00244 } 00245 } 00246 } 00247 00248 double dx = fabs( tmax.x() - tmin.x() ); 00249 double dy = fabs( tmax.y() - tmin.y() ); 00250 // cout << "dx = " << dx << " dy = " << dy << endl; 00251 00252 // Point3D mod_shift; 00253 if ( (dx > HALF_MAX_TEX_COORD) || (dy > HALF_MAX_TEX_COORD) ) { 00254 // structure is too big, we'll just have to shift it so that 00255 // tmin = (0,0). This messes up subsequent texture scaling, 00256 // but is the best we can do. 00257 // cout << "SHIFTING" << endl; 00258 if ( tmin.x() < 0 ) { 00259 tmin.setx( (double)( (int)tmin.x() - 1 ) ); 00260 } else { 00261 tmin.setx( (int)tmin.x() ); 00262 } 00263 if ( tmin.y() < 0 ) { 00264 tmin.sety( (double)( (int)tmin.y() - 1 ) ); 00265 } else { 00266 tmin.sety( (int)tmin.y() ); 00267 } 00268 // cout << "found tmin = " << tmin << endl; 00269 } else { 00270 if ( tmin.x() < 0 ) { 00271 tmin.setx( ( (int)(tmin.x() / HALF_MAX_TEX_COORD) - 1 ) 00272 * HALF_MAX_TEX_COORD ); 00273 } else { 00274 tmin.setx( ( (int)(tmin.x() / HALF_MAX_TEX_COORD) ) 00275 * HALF_MAX_TEX_COORD ); 00276 } 00277 if ( tmin.y() < 0 ) { 00278 tmin.sety( ( (int)(tmin.y() / HALF_MAX_TEX_COORD) - 1 ) 00279 * HALF_MAX_TEX_COORD ); 00280 } else { 00281 tmin.sety( ( (int)(tmin.y() / HALF_MAX_TEX_COORD) ) 00282 * HALF_MAX_TEX_COORD ); 00283 } 00284 #if 0 00285 // structure is small enough ... we can mod it so we can 00286 // properly scale the texture coordinates later. 00287 // cout << "MODDING" << endl; 00288 double x1 = fmod(tmin.x(), MAX_TEX_COORD); 00289 while ( x1 < 0 ) { x1 += MAX_TEX_COORD; } 00290 00291 double y1 = fmod(tmin.y(), MAX_TEX_COORD); 00292 while ( y1 < 0 ) { y1 += MAX_TEX_COORD; } 00293 00294 double x2 = fmod(tmax.x(), MAX_TEX_COORD); 00295 while ( x2 < 0 ) { x2 += MAX_TEX_COORD; } 00296 00297 double y2 = fmod(tmax.y(), MAX_TEX_COORD); 00298 while ( y2 < 0 ) { y2 += MAX_TEX_COORD; } 00299 00300 // At this point we know that the object is < 16 wide in 00301 // texture coordinate space. If the modulo of the tmin is > 00302 // the mod of the tmax at this point, then we know that the 00303 // starting tex coordinate for the tmax > 16 so we can shift 00304 // everything down by 16 and get it within the 0-32 range. 00305 00306 if ( x1 > x2 ) { 00307 mod_shift.setx( HALF_MAX_TEX_COORD ); 00308 } else { 00309 mod_shift.setx( 0.0 ); 00310 } 00311 00312 if ( y1 > y2 ) { 00313 mod_shift.sety( HALF_MAX_TEX_COORD ); 00314 } else { 00315 mod_shift.sety( 0.0 ); 00316 } 00317 #endif 00318 // cout << "mod_shift = " << mod_shift << endl; 00319 } 00320 00321 // generate tex_list 00322 Point3D adjusted_t; 00323 point_list tex; 00324 tex.clear(); 00325 for ( i = 0; i < (int)fan.size(); ++i ) { 00326 p = geod_nodes[ fan[i] ]; 00327 t = basic_tex_coord( p, degree_width, degree_height, scale ); 00328 // cout << "second t = " << t << endl; 00329 00330 adjusted_t = t - tmin; 00331 #if 0 00332 } else { 00333 adjusted_t.setx( fmod(t.x() + mod_shift.x(), MAX_TEX_COORD) ); 00334 while ( adjusted_t.x() < 0 ) { 00335 adjusted_t.setx( adjusted_t.x() + MAX_TEX_COORD ); 00336 } 00337 adjusted_t.sety( fmod(t.y() + mod_shift.y(), MAX_TEX_COORD) ); 00338 while ( adjusted_t.y() < 0 ) { 00339 adjusted_t.sety( adjusted_t.y() + MAX_TEX_COORD ); 00340 } 00341 // cout << "adjusted_t " << adjusted_t << endl; 00342 } 00343 #endif 00344 if ( adjusted_t.x() < SG_EPSILON ) { 00345 adjusted_t.setx( 0.0 ); 00346 } 00347 if ( adjusted_t.y() < SG_EPSILON ) { 00348 adjusted_t.sety( 0.0 ); 00349 } 00350 adjusted_t.setz( 0.0 ); 00351 // cout << "adjusted_t = " << adjusted_t << endl; 00352 00353 tex.push_back( adjusted_t ); 00354 } 00355 00356 return tex; 00357 }