Quote: "However, how would you extend it to 3D? The changes to my code are "obvious". How would you change yours?"
I haven't tried it, but I'd imagine for linear interpolation at least you'd just do the standard 4 samples(2 per axis) on a 2D(X/Z) axis(while reading the current height), then do the same but for the plane above you then linearly interpolate between the two results based on the distance from the lower Y value.
Also if anyone's interested(while not totally related to these boards) I ported my code as best I could to GDK, without any compiler optimizations it's almost exactly 2x the speed on my machine, code's below:
#include "DarkGDK.h"
#include "Windows.h"
#include "Winuser.h"
#include <string>
#include "Math.h"
int displayX = GetSystemMetrics(SM_CXSCREEN);
int displayY = GetSystemMetrics(SM_CYSCREEN);
#define startingRes 512
#define totalOctaves 8
float* noiseMap[totalOctaves+1];
char globalString[256];
//#define INTERPOLATION_LINEAR
//#define INTERPOLATION_COSINE
#define INTERPOLATION_CUBIC
// Prototypes
float Sample( int octaveID, int x, int y, int octaveRes );
float LinearInterpolate( float a, float b, float x );
float CubicInterpolate( float aMinOne, float a, float b, float bAddOne, float acrossAB );
float CosineInterpolate( float a, float b, float x );
void DarkGDK ( void )
{
// Set display
dbSetWindowPosition( 0, 0 );
dbSetDisplayMode( displayX, displayY, 32 );
dbSetWindowLayout( 0, 0, 0 );
// Init
dbSyncOn();
dbRandomize(1337);
int benchmark1Start = dbTimer();
// Generate noise for all octaves
for( int octaveID = 1; octaveID <= totalOctaves; octaveID ++ )
{
int octaveRes = startingRes / pow( 2.0f, (float) (octaveID - 1) );
noiseMap[octaveID] = new float[octaveRes * octaveRes];
int resLoopMax = octaveRes - 1;
for( int x = 0; x <= resLoopMax; x ++ )
{
for( int y = 0; y <= resLoopMax; y ++ )
{
noiseMap[octaveID][x+y*octaveRes] = ( ((float) dbRnd(2000)) - 1000.0f ) * 0.001;
}
}
}
int benchmark1Elapsed = dbTimer() - benchmark1Start;
int benchmark2Start = dbTimer();
int memblockID = 1;
dbMakeMemblock( memblockID, 12 + startingRes * startingRes * 4 );
dbWriteMemblockDword( memblockID, 0, startingRes );
dbWriteMemblockDword( memblockID, 4, startingRes );
dbWriteMemblockDword( memblockID, 8, 32 );
float minHeight = -1.9;
float maxHeight = 1.9;
float minInv = -minHeight;
float heightRange = maxHeight - minHeight;
float _255DivRange = 255.0 / heightRange;
float sResReciprocal = 1.0 / (startingRes);
int resLoopMax = startingRes - 1;
for( int x = 0; x <= resLoopMax; x ++ )
{
for( int y = 0; y <= resLoopMax; y ++ )
{
float fX = ((float) x) * sResReciprocal;
float fY = ((float) y) * sResReciprocal;
float height = 0.0;
float strength = 1.0;
float mHeight;
for ( int octaveID = totalOctaves; octaveID >= 1; octaveID -- )
{
int octaveRes = startingRes / pow( 2.0f, (float) (octaveID - 1) );
float octavefX = fX * (float) octaveRes;
float octavefY = fY * (float) octaveRes;
int octaveX = (int) octavefX;
int octaveY = (int) octavefY;
octavefX = fmod( octavefX, 1.0f);
octavefY = fmod( octavefY, 1.0f);
// BICUBIC ///////////
#ifdef INTERPOLATION_CUBIC
float sample1 = Sample( octaveID, octaveX - 1, octaveY - 1, octaveRes );
float sample2 = Sample( octaveID, octaveX , octaveY - 1, octaveRes );
float sample3 = Sample( octaveID, octaveX + 1, octaveY - 1, octaveRes );
float sample4 = Sample( octaveID, octaveX + 2, octaveY - 1, octaveRes );
float mHeight1 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
sample1 = Sample( octaveID, octaveX - 1, octaveY , octaveRes );
sample2 = Sample( octaveID, octaveX , octaveY , octaveRes );
sample3 = Sample( octaveID, octaveX + 1, octaveY , octaveRes );
sample4 = Sample( octaveID, octaveX + 2, octaveY , octaveRes );
float mHeight2 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
sample1 = Sample( octaveID, octaveX - 1, octaveY + 1, octaveRes );
sample2 = Sample( octaveID, octaveX , octaveY + 1, octaveRes );
sample3 = Sample( octaveID, octaveX + 1, octaveY + 1, octaveRes );
sample4 = Sample( octaveID, octaveX + 2, octaveY + 1, octaveRes );
float mHeight3 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
sample1 = Sample( octaveID, octaveX - 1, octaveY + 2, octaveRes );
sample2 = Sample( octaveID, octaveX , octaveY + 2, octaveRes );
sample3 = Sample( octaveID, octaveX + 1, octaveY + 2, octaveRes );
sample4 = Sample( octaveID, octaveX + 2, octaveY + 2, octaveRes );
float mHeight4 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
mHeight = CubicInterpolate( mHeight1, mHeight2, mHeight3, mHeight4, octavefY );
#endif
//////////////////////
// COSINE ////////////
#ifdef INTERPOLATION_COSINE
float sample1 = Sample( octaveID, octaveX , octaveY, octaveRes );
float sample2 = Sample( octaveID, octaveX + 1, octaveY, octaveRes );
float mHeightX = CosineInterpolate( sample1, sample2, octavefX );
sample1 = Sample( octaveID, octaveX , octaveY + 1, octaveRes);
sample2 = Sample( octaveID, octaveX + 1, octaveY + 1, octaveRes);
float mHeightY = CosineInterpolate( sample1, sample2, octavefX );
mHeight = CosineInterpolate( mHeightX, mHeightY, octavefY );
#endif
/////////////////////
// BILINEAR /////////
#ifdef INTERPOLATION_LINEAR
float sample1 = Sample( octaveID, octaveX , octaveY, octaveRes );
float sample2 = Sample( octaveID, octaveX + 1, octaveY, octaveRes );
float mHeightX = LinearInterpolate( sample1, sample2, octavefX );
sample1 = Sample( octaveID, octaveX , octaveY + 1, octaveRes);
sample2 = Sample( octaveID, octaveX + 1, octaveY + 1, octaveRes);
float mHeightY = LinearInterpolate( sample1, sample2, octavefX );
mHeight = LinearInterpolate( mHeightX, mHeightY, octavefY );
#endif
/////////////////////
height = height + mHeight * strength;
strength = strength * 0.5f;
}
height = (height + minInv) * _255DivRange;
if (height > 255)
height = 255;
if (height < 0)
height = 0;
int iHeight = (int) height;
dbWriteMemblockByte( memblockID, 12 + ( x + y * startingRes ) * 4 , iHeight ); // B
dbWriteMemblockByte( memblockID, 12 + ( x + y * startingRes ) * 4 + 1, iHeight ); // G
dbWriteMemblockByte( memblockID, 12 + ( x + y * startingRes ) * 4 + 2, iHeight ); // R
dbWriteMemblockByte( memblockID, 12 + ( x + y * startingRes ) * 4 + 3, 255 ); // A
}
}
dbMakeImageFromMemblock( 1, memblockID );
int benchmark2Elapsed = dbTimer() - benchmark1Start;
// Output image and info
while ( LoopGDK() )
{
sprintf( globalString, "Time to generate noise: %ims", benchmark1Elapsed );
dbText( 5, 5, globalString );
sprintf( globalString, "Time to add octaves: %ims", benchmark2Elapsed );
dbText( 5, 15, globalString );
dbPasteImage( 1, 64, 64 );
dbSync();
}
return;
}
float Sample( int octaveID, int x, int y, int octaveRes )
{
// Wrap
if (x < 0)
x += octaveRes;
if (y < 0)
y += octaveRes;
if (x > octaveRes-1)
x -= octaveRes;
if (y > octaveRes-1)
y -= octaveRes;
return noiseMap[octaveID][x+y*octaveRes];
}
float LinearInterpolate( float a, float b, float x )
{
return a * (1.0 - x) + b * x;
}
float CosineInterpolate( float a, float b, float x )
{
float f = (1.0 - cos(x * 3.141592654)) * 0.5;
return a * (1.0 - f) + b * f;
}
float CubicInterpolate( float aMinOne, float a, float b, float bAddOne, float acrossAB )
{
float P, Q, R, S;
P = (bAddOne - b) - (aMinOne - a);
Q = (aMinOne - a) - P;
R = b - aMinOne;
S = a;
return P * pow( acrossAB, 3.0f ) + Q * pow( acrossAB, 2.0f ) + R * acrossAB + S;
}
You can pick between the 3 interpolation methods in the defines.
I also made a far more optimized version of this just to see how fast I could get the routine, with compiler optimizations I managed to get it down to 427ms to generate a 512^2px image with 8 octaves, which is far faster than in DBP as I get 6800ms in that, however my DBP code isn't quite as optimized, but there's only so much you can do there.
#include "DarkGDK.h"
#include "Windows.h"
#include "Winuser.h"
#include <string>
#include "Math.h"
// Get desktop res
int displayX = GetSystemMetrics(SM_CXSCREEN);
int displayY = GetSystemMetrics(SM_CYSCREEN);
// defines
#define startingRes 512
#define totalOctaves 8
//#define INTERPOLATION_LINEAR
//#define INTERPOLATION_COSINE // 561
#define INTERPOLATION_CUBIC // 1459 - 1155 - 845 - 805 - 653 - 506 - 427
// Globals
float minHeight = 0.0f;
float maxHeight = 0.0f;
// Arrays
float* noiseMap[totalOctaves+1];
char globalString[256];
// Prototypes
float MyRand( int x );
float Sample( int octaveID, int x, int y, int octaveRes );
float LinearInterpolate( float a, float b, float x );
float CubicInterpolate( float aMinOne, float a, float b, float bAddOne, float acrossAB );
float CosineInterpolate( float a, float b, float x );
void DarkGDK ( void )
{
// Set display
dbSetWindowPosition( 0, 0 );
dbSetDisplayMode( displayX, displayY, 32 );
dbSetWindowLayout( 0, 0, 0 );
// Init
dbSyncOn();
// BENCHMARK ///////
int benchmark1Start = dbTimer();
////////////////////
// Calculate weight/opacity/strength of all layers
float strength[totalOctaves];
float currentStrength = 1.0;
float maxPossibleRange = 0.0;
for ( int octaveID = totalOctaves; octaveID >= 1; octaveID -- )
{
strength[octaveID] = currentStrength;
maxPossibleRange += currentStrength;
currentStrength *= 0.75f;
}
// This method is faster than evlauating the actual min and max heights used
minHeight = -maxPossibleRange;
maxHeight = maxPossibleRange;
// Generate noise for all octaves
for( int octaveID = 1; octaveID <= totalOctaves; octaveID ++ )
{
int octaveRes = startingRes / pow( 2.0f, (float) (octaveID - 1) );
noiseMap[octaveID] = new float[octaveRes * octaveRes];
int resLoopMax = octaveRes - 1;
//float strength = 1.0f / (float) (1 << (totalOctaves - octaveID));
for( int x = 0; x <= resLoopMax; x ++ )
{
for( int y = 0; y <= resLoopMax; y ++ )
{
//noiseMap[octaveID][x+y*octaveRes] = ( ((float) dbRnd(2000)) - 1000.0f ) * 0.001;
noiseMap[octaveID][x+y*octaveRes] = MyRand(x+y*octaveRes) * strength[octaveID];
}
}
}
// BENCHMARK ///////
int benchmark1Elapsed = dbTimer() - benchmark1Start;
int benchmark2Start = dbTimer();
////////////////////
int memblockID = 1;
dbMakeMemblock( memblockID, 12 + startingRes * startingRes * 4 );
DWORD* memblockPtr = (DWORD*) dbGetMemblockPtr(memblockID);
// Write memblock header
memblockPtr[0] = (DWORD) startingRes;
memblockPtr[1] = (DWORD) startingRes;
memblockPtr[2] = (DWORD) 32;
float minInv = -minHeight;
float heightRange = maxHeight - minHeight;
float _255DivRange = 255.0 / heightRange;
float sResReciprocal = 1.0 / (startingRes);
DWORD BGRA;
int resLoopMax = startingRes - 1;
//sprintf( globalString, "This: %f and this %f", minHeight, maxHeight );
//MessageBox( NULL, globalString, "Hallo", MB_OK );
float mHeight;
for( int x = 0; x <= resLoopMax; x ++ )
{
for( int y = 0; y <= resLoopMax; y ++ )
{
float fX = ((float) x) * sResReciprocal;
float fY = ((float) y) * sResReciprocal;
float height = 0.0;
for ( int octaveID = totalOctaves; octaveID >= 2; octaveID -- )
{
int octaveRes = startingRes / (1 << (octaveID - 1));
float octavefX = fX * (float) octaveRes;
float octavefY = fY * (float) octaveRes;
int octaveX = (int) octavefX;
int octaveY = (int) octavefY;
//octavefX = fmod( octavefX, 1.0f);
//octavefY = fmod( octavefY, 1.0f);
octavefX = 0.0000152587890625f * (float)(((int)(octavefX * 65536.0f)) % 65536);
octavefY = 0.0000152587890625f * (float)(((int)(octavefY * 65536.0f)) % 65536);
// BICUBIC ///////////
#ifdef INTERPOLATION_CUBIC
float sample1 = Sample( octaveID, octaveX - 1, octaveY - 1, octaveRes );
float sample2 = Sample( octaveID, octaveX , octaveY - 1, octaveRes );
float sample3 = Sample( octaveID, octaveX + 1, octaveY - 1, octaveRes );
float sample4 = Sample( octaveID, octaveX + 2, octaveY - 1, octaveRes );
float mHeight1 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
sample1 = Sample( octaveID, octaveX - 1, octaveY , octaveRes );
sample2 = Sample( octaveID, octaveX , octaveY , octaveRes );
sample3 = Sample( octaveID, octaveX + 1, octaveY , octaveRes );
sample4 = Sample( octaveID, octaveX + 2, octaveY , octaveRes );
float mHeight2 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
sample1 = Sample( octaveID, octaveX - 1, octaveY + 1, octaveRes );
sample2 = Sample( octaveID, octaveX , octaveY + 1, octaveRes );
sample3 = Sample( octaveID, octaveX + 1, octaveY + 1, octaveRes );
sample4 = Sample( octaveID, octaveX + 2, octaveY + 1, octaveRes );
float mHeight3 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
sample1 = Sample( octaveID, octaveX - 1, octaveY + 2, octaveRes );
sample2 = Sample( octaveID, octaveX , octaveY + 2, octaveRes );
sample3 = Sample( octaveID, octaveX + 1, octaveY + 2, octaveRes );
sample4 = Sample( octaveID, octaveX + 2, octaveY + 2, octaveRes );
float mHeight4 = CubicInterpolate( sample1, sample2, sample3, sample4, octavefX );
mHeight = CubicInterpolate( mHeight1, mHeight2, mHeight3, mHeight4, octavefY );
#endif
//////////////////////
// COSINE ////////////
#ifdef INTERPOLATION_COSINE
float sample1 = Sample( octaveID, octaveX , octaveY, octaveRes );
float sample2 = Sample( octaveID, octaveX + 1, octaveY, octaveRes );
float mHeightX = CosineInterpolate( sample1, sample2, octavefX );
sample1 = Sample( octaveID, octaveX , octaveY + 1, octaveRes);
sample2 = Sample( octaveID, octaveX + 1, octaveY + 1, octaveRes);
float mHeightY = CosineInterpolate( sample1, sample2, octavefX );
mHeight = CosineInterpolate( mHeightX, mHeightY, octavefY );
#endif
/////////////////////
// BILINEAR /////////
#ifdef INTERPOLATION_LINEAR
float sample1 = Sample( octaveID, octaveX , octaveY, octaveRes );
float sample2 = Sample( octaveID, octaveX + 1, octaveY, octaveRes );
float mHeightX = LinearInterpolate( sample1, sample2, octavefX );
sample1 = Sample( octaveID, octaveX , octaveY + 1, octaveRes);
sample2 = Sample( octaveID, octaveX + 1, octaveY + 1, octaveRes);
float mHeightY = LinearInterpolate( sample1, sample2, octavefX );
mHeight = LinearInterpolate( mHeightX, mHeightY, octavefY );
#endif
/////////////////////
height = height + mHeight;
}
// Highest frequency noise
height = height + Sample( 1, x, y, startingRes);
height = (height + minInv) * _255DivRange;
int iHeight = (int) height;
BGRA = iHeight | (iHeight<<8) | (iHeight<<16) | (255<<24);
memblockPtr[3 + x + y * startingRes] = BGRA;
}
}
// BENCHMARK ///////
int benchmark2Elapsed = dbTimer() - benchmark1Start;
////////////////////
// Make image from memblock
dbMakeImageFromMemblock( 1, memblockID );
// Output image and info
while ( LoopGDK() )
{
sprintf( globalString, "Time to generate noise: %ims", benchmark1Elapsed );
dbText( 5, 5, globalString );
sprintf( globalString, "Time to add octaves: %ims", benchmark2Elapsed );
dbText( 5, 15, globalString );
dbPasteImage( 1, 64, 64 );
dbSync();
}
return;
}
float MyRand( int x )
{
x = (x<<13) ^ x;
return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f);
}
float Sample( int octaveID, int x, int y, int octaveRes )
{
// Wrap
if (x < 0)
x += octaveRes;
if (y < 0)
y += octaveRes;
if (x > octaveRes-1)
x -= octaveRes;
if (y > octaveRes-1)
y -= octaveRes;
return noiseMap[octaveID][x+y*octaveRes];
}
float LinearInterpolate( float a, float b, float x )
{
return a * (1.0 - x) + b * x;
}
float CosineInterpolate( float a, float b, float x )
{
x = (1.0 - cos(x * 3.141592654)) * 0.5;
return a * (1.0 - x) + b * x;
}
float CubicInterpolate( float aMinOne, float a, float b, float bAddOne, float acrossAB )
{
float T;
bAddOne = (bAddOne - b) - (aMinOne - a);
T = acrossAB * acrossAB;
return bAddOne * (T * acrossAB) + ((aMinOne - a) - bAddOne) * T + (b - aMinOne) * acrossAB + a;
}