/** * @file perlin.c * * @brief Handle creating noise based on perlin noise. * * Code tries to handle basically 2D/3D cases, without much genericness * because it needs to be pretty fast. Originally sped up the code from * about 20 seconds to 8 seconds per Nebulae image with the manual loop * unrolling. * * @note Tried to optimize a while back with SSE and the works, but because * of the nature of how it's implemented in non-linear fashion it just * wound up complicating the code without actually making it faster. */ #include #include #include #include "SDL.h" #include "lephisto.h" #include "log.h" #include "rng.h" #include "lfile.h" #include "perlin.h" #define NOISE_MAX_OCTAVES 4 /**< Default octaves for noise. */ #define NOISE_DEFAULT_HURST 0.5 /**< Default hurst for noise. */ #define NOISE_DEFAULT_LACUNARITY 2. /**< Default lacunarity for noise. */ /** * @brief Ponderates x between a and b. */ #define LERP(a, b, x) (a + x * (b - a)) /** * @brief Clamps x between a and b: a <= x <= b. */ #define CLAMP(a, b, x) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x))) /** * @brief Structure used for generating noise. */ typedef struct perlin_data_s { int ndim; /**< Dimension of the noise. */ unsigned char map[256]; /**< Randomized map of indexes into buffer. */ float buffer[256][3]; /**< Random 256x3 buffer. */ /* Fractal stuff. */ float H; float lacunarity; float exponent[NOISE_MAX_OCTAVES]; } perlin_data_t; /* Perlin data handling. */ static perlin_data_t* noise_new(int dim, float hurst, float lacunarity); static void noise_delete(perlin_data_t* noise); /* Normalizing. */ static void normalize3(float f[3]); static void normalize2(float f[2]); /* Noise processing. */ static float lattice3(perlin_data_t* pdata, int ix, float fx, int iy, float fy, int iz, float fz); static float lattice2(perlin_data_t* pdata, int ix, float fx, int iy, float fy); /* Basic perlin noise. */ static float noise_get3(perlin_data_t* pdata, float f[3]); static float noise_get2(perlin_data_t* pdata, float f[2]); /* Turbulence. */ static float noise_turbulence3(perlin_data_t* noise, float f[3], int octaves); static float noise_turbulence2(perlin_data_t* nouse, float f[2], int octaves); static float lattice3(perlin_data_t* pdata, int ix, float fx, int iy, float fy, int iz, float fz) { int nindex; float value; nindex = 0; nindex = pdata->map[(nindex + ix) & 0xFF]; nindex = pdata->map[(nindex + iy) & 0xFF]; nindex = pdata->map[(nindex + iz) & 0xFF]; value = pdata->buffer[nindex][0] * fx; value += pdata->buffer[nindex][1] * fy; value += pdata->buffer[nindex][2] * fz; return value; } static float lattice2(perlin_data_t* pdata, int ix, float fx, int iy, float fy) { int nIndex; float value; nIndex = 0; nIndex = pdata->map[(nIndex + ix) & 0xFF]; nIndex = pdata->map[(nIndex + iy) & 0xFF]; value = pdata->buffer[nIndex][0] * fx; value += pdata->buffer[nIndex][1] * fy; return value; } #define SWAP(a, b, t) t = a; a = b; b = t /**< Swaps two values. */ #define FLOOR(a) ((int) a - (a < 0 && a != (int)a)) /**< Limits to 0. */ #define CUBIC(a) (a * a * (3 - 2 * a)) /**< Does cubic filtering. */ /** * @brief Normalizes a 3d vector. * @param f Vector to normalize. */ static void normalize3(float f[3]) { float magnitude; magnitude = 1. / sqrtf(f[0]*f[0] + f[1]*f[1] + f[2]*f[2]); f[0] *= magnitude; f[1] *= magnitude; f[2] *= magnitude; } /** * @brief Normalizes a 2d vector. * @param f Vector to normalize. */ static void normalize2(float f[2]) { float magnitude; magnitude = 1. / sqrtf(f[0]*f[0] + f[1]*f[1]); f[0] *= magnitude; f[1] *= magnitude; } /** * @brief Creates a new perlin noise generator. * @param dim Dimension of the noise. * @param hurst * @param lacunarity */ static perlin_data_t* noise_new(int dim, float hurst, float lacunarity) { perlin_data_t* pdata; int i, j; unsigned char tmp; float f; /* Create the data. */ pdata = calloc(sizeof(perlin_data_t), 1); pdata->ndim = dim; /* Create the buffer and map. */ if(dim == 3) { for(i = 0; i < 256; i++) { pdata->map[i] = (unsigned char)i; pdata->buffer[i][0] = RNGF()-0.5; pdata->buffer[i][1] = RNGF()-0.5; pdata->buffer[i][2] = RNGF()-0.5; normalize3(pdata->buffer[i]); } } else if(dim == 2) { for(i = 0; i < 256; i++) { pdata->map[i] = (unsigned char)i; pdata->buffer[i][0] = RNGF()-0.5; pdata->buffer[i][1] = RNGF()-0.5; normalize2(pdata->buffer[i]); } } while(--i) { j = RNG(0, 255); SWAP(pdata->map[i], pdata->map[j], tmp); } f = 1.; pdata->H = hurst; pdata->lacunarity = lacunarity; for(i = 0; i < NOISE_MAX_OCTAVES; i++) { /*exponent[i] = powf(f, -H);*/ pdata->exponent[i] = 1. / f; f *= lacunarity; } return pdata; } /** * @brief Get some 3d perlin noise from the data. * * Somewhat optimized for speed, probably can't get optimized much more. * @param pdata Perlin data to use. * @param f Position of the noise to get. */ static float noise_get3(perlin_data_t* pdata, float f[3] ) { int n[3]; /* Indexes to pass to lattice function. */ float r[3]; /* Remainders to pass to lattice function. */ float w[3]; /* Cubic values to pass to interpolation function. */ float value; n[0] = FLOOR(f[0]); n[1] = FLOOR(f[1]); n[2] = FLOOR(f[2]); r[0] = f[0] - n[0]; r[1] = f[1] - n[1]; r[2] = f[2] - n[2]; w[0] = CUBIC(r[0]); w[1] = CUBIC(r[1]); w[2] = CUBIC(r[2]); /* * This is the big ugly part that is in dire need * of optimisation!!!! */ value = LERP(LERP(LERP(lattice3(pdata,n[0], r[0], n[1], r[1], n[2], r[2]), lattice3(pdata,n[0]+1, r[0]-1, n[1], r[1], n[2], r[2]), w[0]), LERP(lattice3(pdata,n[0], r[0], n[1]+1, r[1]-1, n[2], r[2]), lattice3(pdata,n[0]+1, r[0]-1, n[1]+1, r[1]-1, n[2], r[2]), w[0]), w[1]), LERP(LERP(lattice3(pdata,n[0], r[0], n[1], r[1], n[2]+1, r[2]-1), lattice3(pdata,n[0]+1, r[0]-1, n[1], r[1], n[2]+1, r[2]-1), w[0]), LERP(lattice3(pdata,n[0], r[0], n[1]+1, r[1]-1, n[2]+1, r[2]-1), lattice3(pdata,n[0]+1, r[0]-1, n[1]+1, r[1]-1, n[2]+1, r[2]-1), w[0]), w[1]), w[2]); return CLAMP(-0.99999f, 0.99999f, value); } /** * @brief Get some 2D perlin noise from the data. * * Somewhat optimized for speed, probably can't get optimized much more. * @param pdata Perlin data to use. * @param f position of the noise to get. */ static float noise_get2(perlin_data_t* pdata, float f[2]) { int n[2]; /* Indexes to pass to lattice function. */ float r[2]; /* Remainders to pass to lattice function. */ float w[2]; /* Cubic values to pass to interpolation function. */ float value; n[0] = FLOOR(f[0]); n[1] = FLOOR(f[1]); r[0] = f[0] - n[0]; r[1] = f[1] - n[1]; w[0] = CUBIC(r[0]); w[1] = CUBIC(r[1]); /* Much faster in 2d. */ value = LERP(LERP(lattice2(pdata, n[0], r[0], n[1], r[1]), lattice2(pdata, n[0]+1, r[0]-1, n[1], r[1]), w[0]), LERP(lattice2(pdata, n[0], r[0], n[1]+1, r[1]-1), lattice2(pdata, n[0]+1, r[0]-1, n[1]+1, r[1]-1), w[0]), w[1]); return CLAMP(-0.99999f, 0.99999f, value); } /** * @brief Get 3d tubulence noise for a position. * @param noise Perlin data to generate noise from. * @param f Position of the noise. * @param octaves to use. * @return The noise level at the position. */ static float noise_turbulence3(perlin_data_t* noise, float f[3], int octaves) { float tf[3]; perlin_data_t* pdata = (perlin_data_t*) noise; /* Init locals. */ float value = 0; int i; tf[0] = f[0]; tf[1] = f[1]; tf[2] = f[2]; /* Inner loop of spectral construction, where the fractal is built. */ for(i = 0; i < octaves; i++) { value += ABS(noise_get3(noise, tf)) * pdata->exponent[i]; tf[0] *= pdata->lacunarity; tf[1] *= pdata->lacunarity; tf[2] *= pdata->lacunarity; } return CLAMP(-0.99999f, 0.99999f, value); } /** * @brief Get 2d turbulence noise for a position. * @param noise Perlin data to generate noise from. * @param f Position of the noise. * @param octaves Octaves to use. * @return The noise level at the position. */ static float noise_turbulence2(perlin_data_t* noise, float f[2], int octaves) { float tf[2]; perlin_data_t* pdata = (perlin_data_t*) noise; /* Initialize locals. */ float value = 0; int i; tf[0] = f[0]; tf[1] = f[1]; /* Inner loop of spectral construction, where the fractal is built. */ for(i = 0; i < octaves; i++) { value += ABS(noise_get2(noise, tf)) * pdata->exponent[i]; tf[0] *= pdata->lacunarity; tf[1] *= pdata->lacunarity; } return CLAMP(-0.99999f, 0.99999f, value); } /** * @brief Free some noise data. * @param noise Noise data to free. */ void noise_delete(perlin_data_t* noise) { free(noise); } /** * @brief Generate radar inteference. * @param w Width to generate. * @param h Height to generate. * @param rug Rugosity of the interference. * @return The map generated. */ float* noise_genRadarInt(const int w, const int h, float rug) { int x, y; float f[2]; int octaves; float hurst; float lacunarity; perlin_data_t* noise; float* map; float value; /* Pretty default values. */ octaves = 3; hurst = NOISE_DEFAULT_HURST; lacunarity = NOISE_DEFAULT_LACUNARITY; /* Create noise and data. */ noise = noise_new(2, hurst, lacunarity); map = malloc(sizeof(float)*w*h); if(map == NULL) { WARN("Out of memory!"); return NULL; } /* Start to create the nebulae. */ for(y = 0; y < h; y++) { f[1] = rug * (float)y / (float)h; for(x = 0; x < w; x++) { f[0] = rug * (float)x / (float)w; /* Get the 2d noise. */ value = noise_get2(noise, f); /* Set the value to [0, 1]. */ map[y*w + x] = (value + 1.) / 2.; } } /* Clean up. */ noise_delete(noise); /* Results. */ return map; } /** * @brief Generate a 3d nebulae map. * @param w Width of the map. * @param h Height of the map. * @param n Number of slices of the map (2d planes). * @param rug Rugosity of the map. * @return The map generated. */ float* noise_genNebulaeMap(const int w, const int h, const int n, float rug) { int x, y, z; float f[3]; int octaves; float hurst; float lacunarity; perlin_data_t* noise; float* nebulae;; float value; float zoom; float max; unsigned int* t, s; /* Pretty default values. */ octaves = 3; hurst = NOISE_DEFAULT_HURST; lacunarity = NOISE_DEFAULT_LACUNARITY; zoom = rug * ((float)h/768.)*((float)w/1024.); /* Create noise and data. */ noise = noise_new(3, hurst, lacunarity); nebulae = malloc(sizeof(float)*w*h*n); if(nebulae == NULL) { WARN("Out of memory!"); return NULL; } /* Some debug information and time setting. */ s = SDL_GetTicks(); t = malloc(sizeof(unsigned int)*n); DEBUG("Generating Nebulae of size %dx%dx%d", w, h, n); /* Start to create the nebulae. */ max = 0.; for(z = 0; z < n; z++) { f[2] = zoom * (float)z / (float)n; for(y = 0; y < h; y++) { f[1] = zoom * (float)y / (float)h; for(x = 0; x < w; x++) { f[0] = zoom * (float)x / (float)w; value = noise_turbulence3(noise, f, octaves); if(max < value) max = value; nebulae[z*w*h + y*w+x] = value; } } /* More time magic debug. */ t[z] = SDL_GetTicks(); DEBUG(" Layer %d/%d generated in %dms", z+1, n, (z>0) ? t[z] - t[z-1] : t[z] - s); } /* Post filtering. */ value = 1. - max; for(z = 0; x < n; z++) for(y = 0; y < h; y++) for(x = 0; x < w; x++) nebulae[z*w*h + y*w + x] += value; /* Cleanup. */ noise_delete(noise); /* Results. */ DEBUG("Nebulae Generated in %dms", SDL_GetTicks() - s); return nebulae; } /** * @brief Generate tiny nebulae puffs. * @param w Width of the puff to generate. * @param h Height of the puff to generate. * @param rug Rugosity of the puff. * @return The puff generated. */ float* noise_genNebulaePuffMap(const int w, const int h, float rug) { int x, y, hw, hh; float d; float f[2]; int octaves; float hurst; float lacunarity; perlin_data_t* noise; float* nebulae; float value; float zoom; float max; /* Pretty default values. */ octaves = 3; hurst = NOISE_DEFAULT_HURST; lacunarity = NOISE_DEFAULT_LACUNARITY; zoom = rug; /* Create noise and data. */ noise = noise_new(2, hurst, lacunarity); nebulae = malloc(sizeof(float)*w*h); if(nebulae == NULL) { WARN("Out of memory!"); return NULL; } /* Start to create the nebulae. */ max = 0.; hw = w/2; hh = h/2; d = (float)MIN(hw, hh); for(y = 0; y < h; y++) { f[1] = zoom * (float)y / (float)h; for(x = 0; x < w; x++) { f[0] = zoom * (float)x / (float)w; /* Get the 2d noise. */ value = noise_turbulence2(noise, f, octaves); /* Make value also depend on distance from center. */ value *= (d - 1. - sqrtf((float)((x-hw)*(x-hw)+(y-hh)*(y-hh))))/d; if(value < 0.) value = 0.; /* Cap at maximum. */ if(max < value) max = value; /* Set the value. */ nebulae[y*w + x] = value; } } /* Clean up. */ noise_delete(noise); /* Results. */ return nebulae; }