1 /*
2  * Modification History
3  *
4  * 2006-July-27   Jason Rohrer
5  * Created.
6  */
7 
8 
9 
10 #include "SoilMap.h"
11 #include "landscape.h"
12 #include "features.h"
13 #include "glCommon.h"
14 
15 #include "minorGems/graphics/Image.h"
16 
17 #include "minorGems/graphics/filters/BoxBlurFilter.h"
18 
19 
20 #include "minorGems/util/random/StdRandomSource.h"
21 
22 extern StdRandomSource globalRandomSource;
23 
24 #include <time.h>
25 
26 //#include "minorGems/io/file/FileOutputStream.h"
27 //#include "minorGems/graphics/converters/TGAImageConverter.h"
28 
29 
baseCosFunction(double inX)30 double baseCosFunction( double inX ) {
31     return cos( inX * M_PI + M_PI ) * 0.5 + 0.5;
32     }
33 
34 
35 
multiCosFunction(double inX)36 double multiCosFunction( double inX ) {
37     return
38         baseCosFunction(
39             baseCosFunction(
40                 baseCosFunction(
41                     baseCosFunction( inX ) ) ) );
42     }
43 
44 
45 
46 double maxMultiCosFunction = multiCosFunction( 1 );
47 
48 
49 
imageSmothingFunction(double inX)50 double imageSmothingFunction( double inX ) {
51 
52     return multiCosFunction( inX ) / maxMultiCosFunction;
53     }
54 
55 
56 
57 double islandTimeSeed = globalRandomSource.getRandomInt();
58 
59 
60 
SoilMap(Vector3D * inCornerA,Vector3D * inCornerB)61 SoilMap::SoilMap( Vector3D *inCornerA, Vector3D *inCornerB )
62     : mCornerA( inCornerA ), mCornerB( inCornerB ) {
63 
64 
65     // pick a new seed each time a soil map is generated
66     islandTimeSeed = globalRandomSource.getRandomInt();
67 
68 
69     int textureSize = 64;
70 
71     Image *textureImage = new Image( textureSize, textureSize, 4, false );
72 
73     double *channels[4];
74 
75 
76     for( int i=0; i<4; i++ ) {
77         channels[i] = textureImage->getChannel( i );
78         }
79 
80     // 0, 0 in images is at cornerA
81     // (textureSize-1), (textureSize-1) in image is at cornerB
82 
83     double xSpan = mCornerB.mX - mCornerA.mX;
84     double xStart = mCornerA.mX;
85 
86     double ySpan = mCornerB.mY - mCornerA.mY;
87     double yStart = mCornerA.mY;
88 
89 
90     Vector3D position( 0, 0, 0 );
91 
92 
93     int numPixels = textureSize * textureSize;
94 
95     double *landscapeValues = new double[ numPixels ];
96 
97     // track max and min so that we can normalize values
98     mMaxLandscapeValue = DBL_MIN;
99     mMinLandscapeValue = DBL_MAX;
100 
101     int x;
102 
103     for( x=0; x<textureSize; x++ ) {
104 
105         double worldX =  xSpan * ( (double)x / (double)textureSize ) + xStart;
106 
107         for( int y=0; y<textureSize; y++ ) {
108 
109             double worldY =
110                 ySpan * ( (double)y / (double)textureSize ) + yStart;
111 
112             int pixelIndex = x + y * textureSize;
113 
114             position.setCoordinates( worldX, worldY, 0 );
115 
116             // don't normalize here, since we still need to find max
117             // and min that will make normalization work
118             double landscapeValue = getSoilCondition( &position, false );
119 
120             landscapeValues[ pixelIndex ] = landscapeValue;
121 
122             if( landscapeValue < mMinLandscapeValue ) {
123                 mMinLandscapeValue = landscapeValue;
124                 }
125             if( landscapeValue > mMaxLandscapeValue ) {
126                 mMaxLandscapeValue = landscapeValue;
127                 }
128 
129             }
130         }
131 
132     // normalize and map into image colors
133 
134     for( int pixelIndex = 0; pixelIndex < numPixels; pixelIndex++ ) {
135 
136         // normalize
137         double landscapeValue = landscapeValues[ pixelIndex ];
138 
139         landscapeValue -= mMinLandscapeValue;
140         landscapeValue =
141             landscapeValue / ( mMaxLandscapeValue - mMinLandscapeValue );
142 
143 
144         // change from continuous to binary
145 
146         // continuous is linear
147         // binary is a step function
148         // cos is like a smoothed step function (looks better)
149         //   though the underlying landscape parameter is binary for ouside
150         //   calls to getSoilCondition
151         landscapeValue = imageSmothingFunction( landscapeValue );
152         /*
153         if( landscapeValue < 0.5 ) {
154             landscapeValue = 0;
155             }
156         else {
157             landscapeValue = 1;
158             }
159         */
160 
161         Color *blend = mapSoilToColor( landscapeValue );
162 
163 
164         channels[0][ pixelIndex ] = blend->r;
165         channels[1][ pixelIndex ] = blend->g;
166         channels[2][ pixelIndex ] = blend->b;
167         channels[3][ pixelIndex ] = 1;
168 
169         delete blend;
170         }
171 
172     delete [] landscapeValues;
173 
174 
175     int blurRadius = 1;
176     BoxBlurFilter blur( blurRadius );
177 
178     // blur image (skip bluring alpha)
179     textureImage->filter( &blur, 0 );
180     textureImage->filter( &blur, 1 );
181     textureImage->filter( &blur, 2 );
182 
183 
184     mTexture = new SingleTextureGL( textureImage, false );
185 
186 
187     // now generate grit
188 
189     // set all pixels to white
190     int p;
191     for( p=0; p<numPixels; p++ ) {
192         channels[0][p] = 1;
193         }
194     // 1 and 2 same as channel zero
195     memcpy( channels[1], channels[0], numPixels * sizeof( double ) );
196     memcpy( channels[2], channels[0], numPixels * sizeof( double ) );
197 
198     // fill alpha with random noise
199     for( int pixelIndex = 0; pixelIndex < numPixels; pixelIndex++ ) {
200 
201         channels[3][pixelIndex] =
202             globalRandomSource.getRandomBoundedDouble( 0, 1 );
203         }
204 
205     // wrap
206     mGritTexture = new SingleTextureGL( textureImage, true );
207 
208 
209 
210     /*
211     File outFileB( NULL, "landTerrain.tga" );
212     FileOutputStream *outStreamB = new FileOutputStream( &outFileB );
213 
214     TGAImageConverter converter;
215 
216     converter.formatImage( textureImage, outStreamB );
217     delete outStreamB;
218 
219     exit( 0 );
220     */
221 
222 
223     delete textureImage;
224 
225     // now compute boundary texture
226 
227     // higher rez
228     textureSize = 128;
229 
230     textureImage = new Image( textureSize, textureSize, 4, false );
231 
232     for( int i=0; i<4; i++ ) {
233         channels[i] = textureImage->getChannel( i );
234         }
235 
236     numPixels = textureSize * textureSize;
237 
238 
239     // set all pixels to white and solid
240     for( p=0; p<numPixels; p++ ) {
241         channels[0][p] = 1;
242         }
243     // 1, 2, and 3 same as channel zero
244     memcpy( channels[1], channels[0], numPixels * sizeof( double ) );
245     memcpy( channels[2], channels[0], numPixels * sizeof( double ) );
246     memcpy( channels[3], channels[0], numPixels * sizeof( double ) );
247 
248 
249     double radius = textureSize / 3;
250 
251     double halfTextureSize = textureSize * 0.5;
252 
253     int y;
254     for( y=0; y<textureSize; y++ ) {
255 
256         double yRelative = y - halfTextureSize;
257 
258         for( x=0; x<textureSize; x++ ) {
259 
260             double alphaValue;
261 
262 
263             double xRelative = x - halfTextureSize;
264 
265             double pixelRadius = sqrt( xRelative * xRelative +
266                                        yRelative * yRelative );
267 
268             int pixelIndex = y * textureSize + x;
269 
270 
271             // call copied from getSoilCondition below
272             double randomRadiusModifier = variableRoughnessLandscape(
273                 // try using x and y directly
274                 10 * xRelative, 10 * yRelative, islandTimeSeed,
275                 0.01, 0.001, 0.25, 0.65, 5 );
276 
277             randomRadiusModifier -= 0.25;
278 
279             if( pixelRadius <= radius + 20 * randomRadiusModifier ) {
280                 alphaValue = 0;
281                 }
282             else {
283                 alphaValue = 1;
284                 }
285 
286             channels[3][ pixelIndex ] = alphaValue;
287             }
288         }
289 
290     // blur alpha
291 
292     blur.setRadius( 2 );
293     textureImage->filter( &blur, 3 );
294 
295 
296     // wrap to hide edge
297     mWaterBoundaryTexture = new SingleTextureGL( textureImage, true );
298 
299 
300     /*
301     File outFileB( NULL, "boundary.tga" );
302     FileOutputStream *outStreamB = new FileOutputStream( &outFileB );
303 
304     TGAImageConverter converter;
305 
306     converter.formatImage( textureImage, outStreamB );
307     delete outStreamB;
308 
309     exit( 0 );
310     */
311 
312     mWaterBoundaryImage = textureImage;
313     mWaterBoundaryImageAlpha = channels[3];
314     mWaterBoundaryImageNumPixels = numPixels;
315     mWaterBoundaryImageSize = textureSize;
316     }
317 
318 
319 
~SoilMap()320 SoilMap::~SoilMap() {
321     delete mTexture;
322     delete mWaterBoundaryTexture;
323     delete mWaterBoundaryImage;
324 
325     delete mGritTexture;
326     }
327 
328 
329 
getSoilCondition(Vector3D * inPosition,char inNormalize)330 double SoilMap::getSoilCondition( Vector3D *inPosition, char inNormalize ) {
331 
332     // call copied from customHighDetailLandscape in
333     // subreal forever
334     double landscapeValue = variableRoughnessLandscape(
335         10*inPosition->mX, 10*inPosition->mY, islandTimeSeed,
336         0.01, 0.001, 0.25, 0.65, 5 );
337 
338     if( inNormalize ) {
339         landscapeValue -= mMinLandscapeValue;
340         landscapeValue =
341             landscapeValue / ( mMaxLandscapeValue - mMinLandscapeValue );
342 
343         // clip
344         if( landscapeValue < 0 ) {
345             landscapeValue = 0;
346             }
347         if( landscapeValue > 1 ) {
348             landscapeValue = 1;
349             }
350 
351         // change from continous to binary
352 
353         if( landscapeValue < 0.5 ) {
354             landscapeValue = 0;
355             }
356         else {
357             landscapeValue = 1;
358             }
359         }
360 
361     return landscapeValue;
362     }
363 
364 
365 
mapSoilToColor(double inSoilCondition)366 Color *SoilMap::mapSoilToColor( double inSoilCondition ) {
367     Color greenColor( 0.5, 0.5, 0.2 );
368     Color brownColor( 0.3, 0.2, 0 );
369 
370     return Color::linearSum( &greenColor,
371                              &brownColor,
372                              inSoilCondition );
373     }
374 
375 
376 
mapPointToBoundaryPixel(Vector3D * inPoint,int * outX,int * outY)377 void SoilMap::mapPointToBoundaryPixel( Vector3D *inPoint,
378                                        int *outX, int *outY ) {
379 
380     // first, map inPoint coordinates to 0,1
381 
382     double pointX = inPoint->mX;
383     double pointY = inPoint->mY;
384 
385 
386     pointX = (pointX - mCornerA.mX) / ( mCornerB.mX - mCornerA.mX );
387     pointY = (pointY - mCornerA.mY) / ( mCornerB.mY - mCornerA.mY );
388 
389     *outX = (int)( pointX * ( mWaterBoundaryImageSize - 1 ) );
390     *outY = (int)( pointY * ( mWaterBoundaryImageSize - 1 ) );
391     }
392 
393 
394 
isInBounds(Vector3D * inPosition)395 char SoilMap::isInBounds( Vector3D *inPosition ) {
396     int x, y;
397 
398     mapPointToBoundaryPixel( inPosition, &x, &y );
399 
400     if( y < 0 || y >= mWaterBoundaryImageSize ||
401         x < 0 || x >= mWaterBoundaryImageSize ) {
402 
403         // completely outside image
404         return false;
405         }
406 
407     double alphaValue =
408         mWaterBoundaryImageAlpha[ y * mWaterBoundaryImageSize + x ];
409 
410     if( alphaValue == 0 ) {
411         return true;
412         }
413     else {
414         return false;
415         }
416     }
417 
418 
419 
getClosestBoundaryPoint(Vector3D * inPosition)420 Vector3D *SoilMap::getClosestBoundaryPoint( Vector3D *inPosition ) {
421     int x, y;
422 
423 
424     char onLand = isInBounds( inPosition );
425 
426 
427     mapPointToBoundaryPixel( inPosition, &x, &y );
428 
429     // for now, examine all pixels
430     int closestX = -1;
431     int closestY = -1;
432     double closestDistance = DBL_MAX;
433 
434     for( int yIndex=0; yIndex<mWaterBoundaryImageSize; yIndex++ ) {
435 
436         // optimization:
437         // distance to x,y is at least as big as distance between
438         // y and yIndex
439 
440         int yDistance = y - yIndex;
441         if( yDistance < 0 ) {
442             yDistance = -yDistance;
443             }
444 
445         if( yDistance < closestDistance ) {
446 
447             // can't rule out this entire row based on y distance
448             // compute xDistance for each pixel in row
449 
450             for( int xIndex=0; xIndex<mWaterBoundaryImageSize; xIndex++ ) {
451 
452                 int xDistance = x - xIndex;
453                 if( xDistance < 0 ) {
454                     xDistance = -xDistance;
455                     }
456 
457                 if( xDistance < closestDistance ) {
458                     // can't rule out based on xDistance alone
459 
460                     // compute true distance
461                     double distance = sqrt( xDistance * xDistance +
462                                             yDistance * yDistance );
463 
464                     if( distance < closestDistance ) {
465 
466                         int pixelIndex =
467                             yIndex * mWaterBoundaryImageSize
468                             + xIndex;
469 
470                         // note:  use 0.5 as water threshold to make
471                         // sure we return a point that is *really* in
472                         // the water, even with round-off errors (and not
473                         // just a point right on the edge of the water)
474 
475                         if( // closest water point if on land
476                             onLand &&
477                             mWaterBoundaryImageAlpha[ pixelIndex ] > 0.5
478                             ||  // OR
479                             // closest land point if on water
480                             !onLand &&
481                             mWaterBoundaryImageAlpha[ pixelIndex ] == 0 ) {
482 
483 
484                             closestDistance = distance;
485 
486                             closestY = yIndex;
487                             closestX = xIndex;
488                             }
489                         }
490 
491                     }
492 
493                 }
494             }
495         }
496 
497 
498     // have closest x and y
499 
500     // map back into world
501     double worldX = (double)closestX / (double)mWaterBoundaryImageSize;
502     worldX = worldX * (mCornerB.mX - mCornerA.mX) + mCornerA.mX;
503 
504     double worldY = (double)closestY / (double)mWaterBoundaryImageSize;
505     worldY = worldY * (mCornerB.mY - mCornerA.mY) + mCornerA.mY;
506 
507     return new Vector3D( worldX, worldY, 0 );
508     }
509 
510 
511 
draw(Vector3D * inPosition,double inScale)512 void SoilMap::draw( Vector3D *inPosition, double inScale ) {
513 
514     glColor4f( 1, 1, 1, 1 );
515 
516     if( Features::drawSoil ) {
517         mTexture->enable();
518         glBegin( GL_QUADS ); {
519 
520             glTexCoord2f( 0, 0 );
521             glVertex2d( mCornerA.mX, mCornerA.mY );
522 
523             glTexCoord2f( 1, 0 );
524             glVertex2d( mCornerB.mX, mCornerA.mY );
525 
526             glTexCoord2f( 1, 1 );
527             glVertex2d( mCornerB.mX, mCornerB.mY );
528 
529             glTexCoord2f( 0, 1 );
530             glVertex2d( mCornerA.mX, mCornerB.mY );
531             }
532         glEnd();
533         mTexture->disable();
534         }
535     else {
536         // draw some grid points for motion reference
537 
538         glPointSize( 2 );
539 
540         double spacing = 3;
541 
542         glColor4f( 1, 0.5, 0, 0.25 );
543 
544         double xStart, xEnd, yStart, yEnd;
545 
546         xStart = mCornerA.mX;
547         yStart = mCornerA.mY;
548 
549         xEnd = mCornerB.mX;
550         yEnd = mCornerB.mY;
551 
552         Vector3D position( 0, 0, 0 );
553 
554         glBegin( GL_POINTS ); {
555             for( double y=yStart; y<=yEnd; y+=spacing ) {
556                 for( double x=xStart; x<=xEnd; x+=spacing ) {
557 
558                     position.setCoordinates( x, y, 0 );
559 
560                     // skip drawing out-of-bounds points (in water)
561                     if( isInBounds( &position ) ) {
562                         Color *drawColor =
563                             mapSoilToColor( getSoilCondition( &position ) );
564 
565 
566 
567                         setGLColor( drawColor );
568                         glVertex2d( x, y );
569 
570                         delete drawColor;
571                         }
572 
573                     }
574                 }
575             }
576         glEnd();
577         }
578 
579 
580     if( Features::drawWater ) {
581         // cover with water
582         glColor4f( 0, 0, 1, 1 );
583         mWaterBoundaryTexture->enable();
584         glBegin( GL_QUADS ); {
585 
586             glTexCoord2f( 0, 0 );
587             glVertex2d( mCornerA.mX, mCornerA.mY );
588 
589             glTexCoord2f( 1, 0 );
590             glVertex2d( mCornerB.mX, mCornerA.mY );
591 
592             glTexCoord2f( 1, 1 );
593             glVertex2d( mCornerB.mX, mCornerB.mY );
594 
595             glTexCoord2f( 0, 1 );
596             glVertex2d( mCornerA.mX, mCornerB.mY );
597             }
598         glEnd();
599         mWaterBoundaryTexture->disable();
600         }
601 
602 
603     if( Features::drawSurfaceNoise ) {
604         // cover with high-res grit
605         double textureMappingRadius = 12;
606 
607         // stretch twice as large as underlying textures
608 
609         glColor4f( 0, 0, 0, 0.1 );
610         mGritTexture->enable();
611         glBegin( GL_QUADS ); {
612 
613             glTexCoord2f( 0, 0 );
614             glVertex2d( 2 * mCornerA.mX, 2 * mCornerA.mY );
615 
616             glTexCoord2f( textureMappingRadius, 0 );
617             glVertex2d( 2 * mCornerB.mX, 2 * mCornerA.mY );
618 
619             glTexCoord2f( textureMappingRadius, textureMappingRadius );
620             glVertex2d( 2 * mCornerB.mX, 2 * mCornerB.mY );
621 
622             glTexCoord2f( 0, textureMappingRadius );
623             glVertex2d( 2 * mCornerA.mX, 2 * mCornerB.mY );
624             }
625         glEnd();
626         mGritTexture->disable();
627         }
628     }
629 
630 
631