cloudy-raytracer/common/noise/perlinnoise.cpp

109 lines
3.2 KiB
C++

#include <random>
#include <algorithm>
#include "perlinnoise.h"
Vector3d PerlinNoise::randomGradient()
{
Vector3d v;
v.x = distribution(generator);
v.y = distribution(generator);
v.z = distribution(generator);
return normalized(v);
}
float PerlinNoise::getGradientValue(int x, int y, int z)
{
Vector3d position = Vector3d(x, y, z);
// Translate position to grid
position /= (float) size;
position *= (float) gridSize;
// Get the grid coordinates
int x_min = floor(position.x);
int y_min = floor(position.y);
int z_min = floor(position.z);
// Get the grid coordinates of the next grid point
int x_max = x_min + 1;
int y_max = y_min + 1;
int z_max = z_min + 1;
// Get the fractional part of the position
Vector3d delta = position - Vector3d(x_min, y_min, z_min);
// Interpolate the gradient values
float n000 = getCornerValue(position, Vector3d(x_min, y_min, z_min));
float n100 = getCornerValue(position, Vector3d(x_max, y_min, z_min));
float n010 = getCornerValue(position, Vector3d(x_min, y_max, z_min));
float n110 = getCornerValue(position, Vector3d(x_max, y_max, z_min));
float n001 = getCornerValue(position, Vector3d(x_min, y_min, z_max));
float n101 = getCornerValue(position, Vector3d(x_max, y_min, z_max));
float n011 = getCornerValue(position, Vector3d(x_min, y_max, z_max));
float n111 = getCornerValue(position, Vector3d(x_max, y_max, z_max));
// Smooth the interpolation
float ix0 = interpolate(n000, n100, delta.x);
float ix1 = interpolate(n010, n110, delta.x);
float ix2 = interpolate(n001, n101, delta.x);
float ix3 = interpolate(n011, n111, delta.x);
float iy0 = interpolate(ix0, ix1, delta.y);
float iy1 = interpolate(ix2, ix3, delta.y);
float noise = interpolate(iy0, iy1, delta.z);
return noise;
}
PerlinNoise::PerlinNoise(int size, int gridSize) : Noise(size), gridSize(gridSize)
{
// Init random
std::random_device rd;
this->generator = std::mt19937(rd());
this->distribution = std::normal_distribution<float>(0.0f, 1.0f);
generateNoise();
}
void PerlinNoise::generateNoise()
{
// Generate gradients
gradients.clear();
gradients.resize(pow(gridSize, 3));
for (int i = 0; i < gradients.size(); i++)
{
gradients[i] = randomGradient();
}
// Generate each pixel
for (int x = 0; x < size; x++)
{
for (int y = 0; y < size; y++)
{
for (int z = 0; z < size; z++)
{
setNoise(x, y, z, getGradientValue(x, y, z));
}
}
}
// Normalize cloudNoise map to [0, 1]
float min = *std::min_element(noiseMap.begin(), noiseMap.end());
float max = *std::max_element(noiseMap.begin(), noiseMap.end());
std::for_each(noiseMap.begin(), noiseMap.end(), [min, max](float &value)
{
value = (value - min) / (max - min);
});
}
float PerlinNoise::getCornerValue(Vector3d position, Vector3d corner)
{
int x = (int) corner.x % gridSize;
int y = (int) corner.y % gridSize;
int z = (int) corner.z % gridSize;
Vector3d gradient = gradients[x + y * gridSize + z * gridSize * gridSize];
return dotProduct(gradient, position - corner);
}