#include "image.hpp" #include "noise.hpp" #include "noise/fractal/fbm.hpp" #include "noise/fractal/hetero.hpp" #include "noise/fractal/hmf.hpp" #include "noise/fractal/rmf.hpp" #include "noise/fractal/runtime.hpp" #include "noise/lerp.hpp" #include "noise/basis/constant.hpp" #include "noise/basis/value.hpp" #include "noise/basis/perlin.hpp" #include "noise/basis/worley.hpp" #include "noise/turbulence.hpp" #include "noise/basis/runtime.hpp" #include "extent.hpp" #include "colour.hpp" #include "netpbm.hpp" #include "types.hpp" #include "cmdopt.hpp" #include "region.hpp" #include "random.hpp" /////////////////////////////////////////////////////////////////////////////// template struct util::noise::fractal::fbm>; template struct util::noise::fractal::hmf>; template struct util::noise::fractal::rmf>; template struct util::noise::fractal::hetero>; /////////////////////////////////////////////////////////////////////////////// enum basis_t { VALUE, PERLIN, WORLEY }; //----------------------------------------------------------------------------- enum fractal_t { FBM, HMF, RMF, HETERO, }; //----------------------------------------------------------------------------- enum lerp_t { LINEAR, CUBIC, QUINTIC, COSINE, TRUNC }; //----------------------------------------------------------------------------- std::istream& operator>> (std::istream &is, basis_t &b) { std::string name; is >> name; b = name == "value" ? VALUE : name == "perlin" ? PERLIN : name == "worley" ? WORLEY : (is.setstate (std::istream::failbit), b); return is; } //----------------------------------------------------------------------------- std::ostream& operator<< (std::ostream &os, basis_t b) { switch (b) { case VALUE: os << "value"; return os; case PERLIN: os << "perlin"; return os; case WORLEY: os << "worley"; return os; default: unreachable (); } } //----------------------------------------------------------------------------- std::istream& operator>> (std::istream &is, fractal_t &f) { std::string name; is >> name; f = name == "fbm" ? FBM : name == "hmf" ? HMF : name == "rmf" ? RMF : name == "hetero" ? HETERO : (is.setstate (std::istream::failbit), f); return is; } //----------------------------------------------------------------------------- std::ostream& operator<< (std::ostream &os, fractal_t f) { switch (f) { case FBM: os << "fbm"; return os; case HMF: os << "hmf"; return os; case RMF: os << "rmf"; return os; case HETERO: os << "hetero"; return os; default: unreachable (); }; } //----------------------------------------------------------------------------- std::istream& operator>> (std::istream &is, lerp_t &l) { std::string name; is >> name; l = name == "linear" ? LINEAR : name == "cubic" ? CUBIC : name == "quintic" ? QUINTIC : name == "cosine" ? COSINE : name == "trunc" ? TRUNC : (is.setstate (std::istream::failbit), l); return is; } //----------------------------------------------------------------------------- std::ostream& operator<< (std::ostream &os, lerp_t &l) { switch (l) { case LINEAR: os << "linear"; return os; case CUBIC: os << "cubic"; return os; case QUINTIC: os << "quintic"; return os; case COSINE: os << "cosine"; return os; case TRUNC: os << "trunc"; return os; default: unreachable (); } } void diamond (util::image::buffer &img, util::region2u target, float scale, float persistence) { } void midpoint (util::image::buffer &img, util::region2u target, float scale, float persistence) { CHECK_GE (target.area (), 9); auto w = target.w; auto h = target.h; // 0--1 // | | // 2--3 auto v0 = img[target.p+util::vector2u{0, 0 }]; auto v1 = img[target.p+util::vector2u{w-1,0 }]; auto v2 = img[target.p+util::vector2u{0, h-1}]; auto v3 = img[target.p+util::vector2u{w-1,h-1}]; // do the centre auto avg = (v0 + v1 + v2 + v3) / 4; auto val = avg + scale * (util::random () * 2 - 1); auto pos = target.p + target.e / 2; img[pos] = val; // average the sides auto p01 = target.p + util::vector2u{w/2, 0}; auto p13 = target.p + util::vector2u{w-1, h/2}; auto p32 = target.p + util::vector2u{w/2, h -1}; auto p20 = target.p + util::vector2u{0, h/2}; img[p01] = (v0 + v1) / 2; img[p13] = (v1 + v3) / 2; img[p32] = (v3 + v2) / 2; img[p20] = (v2 + v0) / 2; // recurse if (target.area () > 9) { auto e = target.e / 2 + 1; midpoint (img, {target.p + util::vector2u{ 0, 0 }, e}, scale * persistence, persistence); midpoint (img, {target.p + util::vector2u{ w/2, 0 }, e}, scale * persistence, persistence); midpoint (img, {target.p + util::vector2u{ 0, h/2 }, e}, scale * persistence, persistence); midpoint (img, {target.p + util::vector2u{ w/2, h/2 }, e}, scale * persistence, persistence); } } /////////////////////////////////////////////////////////////////////////////// int main (int argc, char **argv) { //{ // util::extent2u size { 1025 }; // util::image::buffer img (size); // img[{0, 0}] = 0.7f; // img[{0, size.w-1}] = 0.1f; // img[{size.h-1, 0}] = 0.5f; // img[{size.h-1, size.w-1}] = 0.2f; // midpoint (img, { {0, 0}, size }, 1, 0.65f); // auto range = std::minmax_element (img.begin (), img.end ()); // auto offset = *range.first; // auto div = *range.second - *range.first; // std::cerr << "range: [" << *range.first << ", " << *range.second << "]\n"; // std::transform (img.begin (), img.end (), img.begin (), [offset,div] (auto i) { return (i - offset) / div; }); // util::pgm::write (img.cast (), std::cout); //} //return 0; #ifndef ENABLE_DEBUGGING if (isatty (fileno (stdout))) { std::cerr << "cowardly refusing to dump binary data to console\n"; return EXIT_FAILURE; } #endif // setup default variables #ifdef ENABLE_DEBUGGING util::extent2u res {320, 240}; #else util::extent2u res {1920, 1080}; #endif uint64_t seed = time (nullptr); basis_t basis = PERLIN; fractal_t fractal = FBM; lerp_t lerp = QUINTIC; unsigned octaves = 8; float H; float scale = 1.f; float turbulence = 0.f; unsigned single = 0; // fill variables from arguments util::cmdopt::parser args; args.add> ('w', "width", "output image width", res.w); args.add> ('h', "height", "output image height", res.h); args.add> ('s', "seed", "random seed", seed); args.add> ('b', "basis", "primary basis function", basis); args.add> ('f', "fractal", "primary fractal function", fractal); args.add> ('l', "lerp", "interpolation algorithm", lerp); args.add> ('o', "octaves", "total fractal iterations", octaves); args.add> ('1', "single", "single octave", single); args.add> ('H', "hurst", "Hurst exponent", H); args.add> ('x', "scale", "frequency multiplier", scale); args.add> ('t', "turbulence","turbulence scale", turbulence); args.scan (argc, argv); util::noise::turbulence< float, util::noise::fractal::runtime< float, util::noise::basis::runtime >, util::noise::fractal::fbm< float, util::noise::basis::perlin< float, util::lerp::cubic > > > t (seed, { turbulence, turbulence }); auto &f = t.data; switch (fractal) { using namespace util::noise; case FBM: f.reset>> (seed); break; case HMF: f.reset>> (seed); break; case RMF: f.reset>> (seed); break; case HETERO: f.reset>> (seed); break; default: unreachable (); } auto &b = f.basis (); switch (basis) { using namespace util::noise; case PERLIN: { switch (lerp) { case LINEAR: b.reset> (seed); break; case CUBIC: b.reset> (seed); break; case QUINTIC: b.reset> (seed); break; case COSINE: b.reset> (seed); break; case TRUNC: b.reset> (seed); break; default: unreachable (); } break; } case VALUE: { switch (lerp) { case LINEAR: b.reset> (seed); break; case CUBIC: b.reset> (seed); break; case QUINTIC: b.reset> (seed); break; case COSINE: b.reset> (seed); break; case TRUNC: b.reset> (seed); break; default: unreachable (); } break; } case WORLEY: { b.reset> (seed); break; } default: unreachable (); } t.seed (seed); f.octaves (octaves); f.frequency (scale / res.w); t.perturb[0].frequency ( scale / res.w); t.perturb[1].frequency ( scale / res.w); util::image::buffer img (res); // XXX: offset slightly to avoid origin artefacts in some basis functions static const auto OFFSET = util::vector2f { -100 }; { for (size_t y = 0; y < res.h; ++y) for (size_t x = 0; x < res.w; ++x) img.data ()[y * img.s + x] = t (util::point2f {float (x), float (y)} + OFFSET); } // working on the assumption that all octave images are based on summation, // subtract the image with one less octave from our current image to leave // us with the highest octave contribution only. this is hideously // inefficient, but it's not an operation we care about in general. if (single && f.octaves () != 1) { auto oldoctaves = f.octaves (); f.octaves (oldoctaves - 1); auto prev = img.clone (); for (size_t y = 0; y < res.h; ++y) for (size_t x = 0; x < res.w; ++x) prev.data ()[y * img.s + x] = t (util::point2f {float (x), float (y)} + OFFSET); CHECK_EQ (img.stride (), prev.stride ()); for (size_t i = 0; i < img.size (); ++i) img[i] -= prev[i]; f.octaves (oldoctaves); } // rescale into the range [0, 1] auto range = std::minmax_element (img.begin (), img.end ()); auto offset = *range.first; auto div = *range.second - *range.first; std::transform (img.begin (), img.end (), img.begin (), [offset,div] (auto i) { return (i - offset) / div; }); // write the images to disk util::pgm::write (img.cast (), std::cout); }