#pragma once #include #include #include "core/rng.hpp" #include "effects/sampling.hpp" namespace effector { // One layer of value noise: random heights on a lattice `spacing` pixels // apart, blended smoothly between the lattice points. Positions are read from // (0, 0) to the width and height it was built for. class NoiseLayer { public: NoiseLayer(Rng& rng, int width, int height, double spacing) : spacing_(spacing), cols_(static_cast(width / spacing) + 2), rows_(static_cast(height / spacing) + 2), lattice_(static_cast(cols_) * rows_) { for (auto& value : lattice_) value = unitDouble(rng.next()) * 2.0 - 1.0; } // The height at (x, y) in [-1, 1], and its slope per pixel along x and y. void sample(double x, double y, double& value, double& slopeX, double& slopeY) const { const double gx = x / spacing_; const double gy = y / spacing_; const int ix = std::min(static_cast(gx), cols_ - 2); const int iy = std::min(static_cast(gy), rows_ - 2); const double fx = gx - ix; const double fy = gy - iy; const double sx = fx * fx * (3.0 - 2.0 * fx); const double sy = fy * fy * (3.0 - 2.0 * fy); const double a = at(ix, iy); const double b = at(ix + 1, iy); const double c = at(ix, iy + 1); const double d = at(ix + 1, iy + 1); const double cross = a - b - c + d; value = a + (b - a) * sx + (c - a) * sy + cross * sx * sy; slopeX = 6.0 * fx * (1.0 - fx) * ((b - a) + cross * sy) / spacing_; slopeY = 6.0 * fy * (1.0 - fy) * ((c - a) + cross * sx) / spacing_; } private: double at(int x, int y) const { return lattice_[static_cast(y) * cols_ + x]; } double spacing_; int cols_; int rows_; std::vector lattice_; }; // Where one layer of a field is read from, against where it is asked for. struct NoiseShift { double x = 0.0; double y = 0.0; }; // Octaves of noise summed, each twice as fine and half as strong as the one // before, and scaled back so the total stays in [-1, 1]. class NoiseField { public: NoiseField(Rng& rng, int width, int height, double scale, int octaves) { double spacing = scale; double weight = 1.0; for (int i = 0; i < octaves; ++i) { layers_.emplace_back(rng, width, height, std::max(1.0, spacing)); weights_.push_back(weight); total_ += weight; spacing *= 0.5; weight *= 0.5; } } // As above, with each layer read that far off from (x, y), one shift per // octave -- which is how a field is set moving: its layers slide apart. void sample(double x, double y, double& value, double& slopeX, double& slopeY, const std::vector* shifts = nullptr) const { value = slopeX = slopeY = 0.0; for (std::size_t i = 0; i < layers_.size(); ++i) { double v = 0.0, sx = 0.0, sy = 0.0; if (shifts != nullptr) { layers_[i].sample(x + (*shifts)[i].x, y + (*shifts)[i].y, v, sx, sy); } else { layers_[i].sample(x, y, v, sx, sy); } value += v * weights_[i]; slopeX += sx * weights_[i]; slopeY += sy * weights_[i]; } value /= total_; slopeX /= total_; slopeY /= total_; } private: std::vector layers_; std::vector weights_; double total_ = 0.0; }; // The push the warp effects give a pixel: one field for a swirl, which flows // along its contours, two for a push along x and y. The fields are drawn in // full, in that order, when this is built. class WarpField { public: WarpField(Rng& rng, int width, int height, double scale, int octaves, bool swirl) : first_(rng, width, height, scale, octaves), scale_(scale), swirl_(swirl) { if (!swirl) second_.emplace_back(rng, width, height, scale, octaves); } // How far the pixel at (x, y) is pushed, at `strength` pixels at most; // `shifts` (one list per field) slides the fields' layers as NoiseField does. void push(double x, double y, double strength, double& dx, double& dy, const std::vector* firstShifts = nullptr, const std::vector* secondShifts = nullptr) const { double value = 0.0, slopeX = 0.0, slopeY = 0.0; first_.sample(x, y, value, slopeX, slopeY, firstShifts); if (swirl_) { // Along the contour: the slope turned a quarter, which never piles // pixels up or tears them apart. The slope of a field `scale` wide // is about 1/scale, hence the factor. dx = slopeY * scale_ * strength * 0.5; dy = -slopeX * scale_ * strength * 0.5; } else { double other = 0.0; second_.front().sample(x, y, other, slopeX, slopeY, secondShifts); dx = value * strength; dy = other * strength; } } private: NoiseField first_; std::vector second_; double scale_; bool swirl_; }; } // namespace effector