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