diff --git a/tools/noise-generator/generator.cpp b/tools/noise-generator/generator.cpp index af22b91a..9845a371 100644 --- a/tools/noise-generator/generator.cpp +++ b/tools/noise-generator/generator.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -10,10 +11,32 @@ using namespace psemek; template -util::array generate(std::array const & size, int octaves, int minoctave, float power, - bool tile, float gamma, bool remap, std::uint64_t seed) +auto moore_neighbourhood() { - std::cout << "Generating " << D << "D noise\n"; + if constexpr (D == 2) + { + std::array, 9> result; + for (int dy = -1; dy <= 1; ++dy) + for (int dx = -1; dx <= 1; ++dx) + result[(dy + 1) * 3 + (dx + 1)] = {dx, dy}; + return result; + } + else if constexpr (D == 3) + { + std::array, 27> result; + for (int dz = -1; dz <= 1; ++dz) + for (int dy = -1; dy <= 1; ++dy) + for (int dx = -1; dx <= 1; ++dx) + result[(dz + 1) * 9 + (dy + 1) * 3 + (dx + 1)] = {dx, dy, dz}; + return result; + } +} + +template +util::array generate_perlin(std::array const & size, int octaves, int minoctave, float power, + bool invert, bool tile, float gamma, bool remap, std::uint64_t seed) +{ + std::cout << "Generating " << D << "D perlin noise\n"; std::cout << "Size "; for (int d = 0; d < D; ++d) { @@ -24,6 +47,7 @@ util::array generate(std::array const & size, i std::cout << "\nOctaves: " << minoctave << ".." << (minoctave + octaves - 1) << "\n"; std::cout << "Power: " << power << "\n"; + std::cout << "Invert: " << (invert ? "true" : "false") << "\n"; std::cout << "Tile: " << (tile ? "true" : "false") << "\n"; std::cout << "Gamma: " << gamma << "\n"; std::cout << "Remap: " << (remap ? "true" : "false") << "\n"; @@ -93,6 +117,147 @@ util::array generate(std::array const & size, i float v = result_float(idx); if (remap) v = math::unlerp(value_range, v); + if (invert) + v = 1.f - v; + v = std::pow(v, gamma); + result(idx) = static_cast(std::clamp(v * 255.f, 0.f, 255.f)); + } + + return result; +} + +template +util::array generate_worley(std::array const & size, int octaves, int minoctave, float power, + bool invert, bool tile, float gamma, bool remap, std::uint64_t seed) +{ + std::cout << "Generating " << D << "D worley noise\n"; + std::cout << "Size "; + for (int d = 0; d < D; ++d) + { + if (d > 0) + std::cout << "x"; + std::cout << size[d]; + } + + std::cout << "\nOctaves: " << minoctave << ".." << (minoctave + octaves - 1) << "\n"; + std::cout << "Power: " << power << "\n"; + std::cout << "Invert: " << (invert ? "true" : "false") << "\n"; + std::cout << "Tile: " << (tile ? "true" : "false") << "\n"; + std::cout << "Gamma: " << gamma << "\n"; + std::cout << "Remap: " << (remap ? "true" : "false") << "\n"; + std::cout << "Seed: " << std::hex << std::setw(16) << std::setfill('0') << seed << "\n"; + std::cout << std::flush; + + random::generator rng{seed, 0}; + + std::vector, D>> octave_points; + std::vector octave_weights; + float sum_octave_weights = 0.f; + + for (int o = minoctave; o < minoctave + octaves; ++o) + { + std::array octave_size; + for (int d = 0; d < D; ++d) + octave_size[d] = (1 << o); + + util::array, D> octave(octave_size); + + for (auto idx : octave.indices()) + { + auto & p = octave(idx); + for (int d = 0; d < D; ++d) + p[d] = idx[d] + random::uniform(rng); + } + + octave_points.push_back(std::move(octave)); + + float weight = std::pow(static_cast(1 << o), - power); + octave_weights.push_back(weight); + sum_octave_weights += weight; + } + + for (auto & w : octave_weights) + w /= sum_octave_weights; + + util::array result_float(size); + math::interval value_range; + + for (auto idx : result_float.indices()) + { + math::point p; + + for (int d = 0; d < D; ++d) + p[d] = (idx[d] + 0.5f) / size[d] * (1 << minoctave); + + float v = 0.f; + + for (int o = 0; o < octaves; ++o) + { + math::point i; + for (int d = 0; d < D; ++d) + i[d] = std::floor(p[d]); + + int octave_size = 1 << (o + minoctave); + + float closest_distance = std::numeric_limits::infinity(); + + for (auto n : moore_neighbourhood()) + { + math::point j = i + n; + + if (tile) + { + for (int d = 0; d < D; ++d) + j[d] = (j[d] + octave_size) % octave_size; + } + else + { + bool good = true; + for (int d = 0; d < D; ++d) + { + good &= (j[d] >= 0); + good &= (j[d] < octave_size); + } + + if (!good) + continue; + } + + math::vector r = octave_points[o](j) - p; + + if (tile) + { + for (int d = 0; d < D; ++d) + { + if (i[d] + n[d] == -1) + r[d] -= octave_size; + else if (i[d] + n[d] == octave_size) + r[d] += octave_size; + } + } + + math::make_min(closest_distance, math::length(r)); + } + + v += closest_distance * octave_weights[o] / std::sqrt(2.f); + + for (int d = 0; d < D; ++d) + p[d] *= 2.f; + } + + result_float(idx) = v; + value_range |= v; + } + + util::array result(size); + + for (auto idx : result.indices()) + { + float v = result_float(idx); + if (remap) + v = math::unlerp(value_range, v); + if (invert) + v = 1.f - v; v = std::pow(v, gamma); result(idx) = static_cast(std::clamp(v * 255.f, 0.f, 255.f)); } @@ -104,6 +269,10 @@ char const options_usage[] = R"( Available options: + type + Noise type (perlin or worley) + Default = worley + dim Noise dimension (2 or 3) Default = 2 @@ -132,6 +301,10 @@ Available options: Octaves weight power (0.0 means all octaves have equal contributions), can be negative Default = 1.0 + invert + Replace noise values with 1-value (makes sense for worley noise only) + Default = false + tile Whether to make the noise tileable (seamless) Default = true @@ -200,6 +373,7 @@ std::optional parse_bool(std::string const & str) int main(int argc, char * argv[]) { util::hash_map values; + values["type"] = "perlin"; values["dim"] = "2"; values["sizex"] = "256"; values["sizey"] = "256"; @@ -207,6 +381,7 @@ int main(int argc, char * argv[]) values["octaves"] = "8"; values["minoctave"] = "0"; values["power"] = "1.0"; + values["invert"] = "false"; values["tile"] = "true"; values["gamma"] = "1.0"; values["remap"] = "true"; @@ -243,6 +418,7 @@ int main(int argc, char * argv[]) values[key] = value; } + auto type = values["type"]; auto dim = parse_int(values["dim"]); auto sizex = parse_int(values["sizex"]); auto sizey = parse_int(values["sizey"]); @@ -250,6 +426,7 @@ int main(int argc, char * argv[]) auto octaves = parse_int(values["octaves"]); auto minoctave = parse_int(values["minoctave"]); auto power = parse_float(values["power"]); + auto invert = parse_bool(values["invert"]); auto tile = parse_bool(values["tile"]); auto gamma = parse_float(values["gamma"]); auto remap = parse_bool(values["remap"]); @@ -262,6 +439,12 @@ int main(int argc, char * argv[]) if (!seed) seed = parse_seed(values["seed"], 10); if (!seed) seed = parse_seed(values["seed"], 16); + if (type != "perlin" && type != "worley") + { + std::cerr << "Unknown noise type: " << values["type"] << "\n"; + return EXIT_FAILURE; + } + if (!dim || (*dim != 2 && *dim != 3)) { std::cerr << "Unknown noise dimension: " << values["dim"] << "\n"; return EXIT_FAILURE; @@ -297,6 +480,11 @@ int main(int argc, char * argv[]) return EXIT_FAILURE; } + if (!invert) { + std::cerr << "Invert must be true or false: " << values["invert"] << "\n"; + return EXIT_FAILURE; + } + if (!tile) { std::cerr << "Tile must be true or false: " << values["tile"] << "\n"; return EXIT_FAILURE; @@ -319,7 +507,11 @@ int main(int argc, char * argv[]) if (*dim == 2) { - auto result = generate<2>({*sizex, *sizey}, *octaves, *minoctave, *power, *tile, *gamma, *remap, *seed); + util::array result; + if (type == "perlin") + result = generate_perlin<2>({*sizex, *sizey}, *octaves, *minoctave, *power, *invert, *tile, *gamma, *remap, *seed); + else if (type == "worley") + result = generate_worley<2>({*sizex, *sizey}, *octaves, *minoctave, *power, *invert, *tile, *gamma, *remap, *seed); io::file_ostream out{std::filesystem::path(output_path)}; @@ -347,7 +539,11 @@ int main(int argc, char * argv[]) if (*dim == 3) { - auto result = generate<3>({*sizex, *sizey, *sizez}, *octaves, *minoctave, *power, *tile, *gamma, *remap, *seed); + util::array result; + if (type == "perlin") + generate_perlin<3>({*sizex, *sizey, *sizez}, *octaves, *minoctave, *power, *invert, *tile, *gamma, *remap, *seed); + else if (type == "worley") + result = generate_worley<3>({*sizex, *sizey, *sizez}, *octaves, *minoctave, *power, *invert, *tile, *gamma, *remap, *seed); io::file_ostream out{std::filesystem::path(output_path)}; std::cout << "Saving raw image to " << output_path << std::endl;