diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7642978..8bf7f0b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(histogram) add_subdirectory(texture2d) +add_subdirectory(point-cloud) \ No newline at end of file diff --git a/examples/common/argument_parsing.hpp b/examples/common/argument_parsing.hpp index 525c524..059d790 100644 --- a/examples/common/argument_parsing.hpp +++ b/examples/common/argument_parsing.hpp @@ -1,5 +1,3 @@ -#include - #include #include #include diff --git a/examples/point-cloud/CMakeLists.txt b/examples/point-cloud/CMakeLists.txt new file mode 100644 index 0000000..15e043f --- /dev/null +++ b/examples/point-cloud/CMakeLists.txt @@ -0,0 +1,16 @@ +project(noiz-point-cloud) + +add_executable(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PRIVATE + noiz::noiz-lib + noiz::noiz-compile-options +) + +target_sources(${PROJECT_NAME} PRIVATE + pointcloud.cpp +) + +target_include_directories(${PROJECT_NAME} PRIVATE + ../common +) diff --git a/examples/point-cloud/pointcloud.cpp b/examples/point-cloud/pointcloud.cpp new file mode 100644 index 0000000..6d6c720 --- /dev/null +++ b/examples/point-cloud/pointcloud.cpp @@ -0,0 +1,137 @@ +#include +#include + +#include + + +#include +#include +#include +#include +#include +#include + +constexpr int32_t constant_base_resolution = 128; +constexpr int32_t constant_max_resolution_factor = 32; + +struct Config { + noiz::Seed seed{noiz::detail::Generator::make_random_seed()}; + noiz::GridExtent3 grid_extent{256, 256, 256}; // NOLINT + float step{0.1f}; // NOLINT + int image_size_factor{1}; + + // syntax: [count] [step] + auto parse_args(Args args) -> bool { + if (!args.next_as(step, "step")) { return false; } + if (!args.next_as(image_size_factor, "image_size_factor")) { return false; } + if (image_size_factor < 0 || image_size_factor > constant_max_resolution_factor) { + std::cerr << std::format("invalid image size factor: '{}'\n", image_size_factor); + return false; + } + if (!args.args.empty()) { + return false; + } + const int scaled_resolution = constant_base_resolution * image_size_factor; + grid_extent.x = scaled_resolution; + grid_extent.y = scaled_resolution; + grid_extent.z = scaled_resolution; + return true; + } +}; + +// pulls noise from noiz::lib, writes a pixel to ppm file +class Point_Cloud { + public: + explicit Point_Cloud(int image_size_factor) : image_size{static_cast(constant_base_resolution * image_size_factor)} {} + + void build_and_write_object_with_noise(noiz::Noise3f& noise, float const& step) const { + + std::ofstream out_file{"hybrid_multifractal_noise.obj", std::ios::binary}; + + float vertex_z; + float vertex_y; + + noiz::Noise_Processor3 noise_processor{noise}; + +#if 0 //internal testing + noiz::Noise2f noise2; + noiz::Noise_Processor2 noise_processor2{noise2}; + + noiz::Vec3f testing_point; + noise_processor.basic_processing(testing_point); + noise_processor.billowy_processing(testing_point); + noise_processor.hybrid_multi_fractal_processing(testing_point); + noise_processor.rigid_processing(testing_point); + noise_processor.turbulence_processing(testing_point); + noise_processor.raw_noise(testing_point); + + noiz::Vec2f testing_point2; + noise_processor2.basic_processing(testing_point2); + noise_processor2.billowy_processing(testing_point2); + noise_processor2.hybrid_multi_fractal_processing(testing_point2); + noise_processor2.rigid_processing(testing_point2); + noise_processor2.turbulence_processing(testing_point2); + noise_processor2.raw_noise(testing_point2); +#endif + + for(int z = 0; z < image_size; z++){ + vertex_z = (static_cast(z) / static_cast(image_size)) - 0.5f; + + for (int y = 0; y < image_size; y++) { + vertex_y = (static_cast(y) / static_cast(image_size)) - 0.5f; + for(int x = 0; x < image_size; x++) { + // add noise at point + //raw noise +#if 0 //raw noise + const float noise_value = noise.at(noiz::Vec3f{.x = static_cast(x) * step, .y = static_cast(y) * step, .z = static_cast(z) * step}); + if(noise_value > 0.0f){ //this should render a half of the points with raw noise + + //no point in assigning x here? just write it directly to + out_file << "v " << ((static_cast(x) / static_cast(image_size)) - 0.5f) << " " << vertex_y << " " << vertex_z << '\n'; + } +#else //hybrid multi fractal noise + const float noise_value = noise_processor.hybrid_multi_fractal_processing( + noiz::Vec3f{.x = static_cast(x) * step, .y = static_cast(y) * step, .z = static_cast(z) * step} + ); + if(noise_value > 2.0f){ //this should render a half of the points with hmf noise + + //no point in assigning x here? just write it directly to + out_file << "v " << ((static_cast(x) / static_cast(image_size)) - 0.5f) << " " << vertex_y << " " << vertex_z << '\n'; + } +#endif + } + } + } + out_file.close(); + } + + private: + uint16_t image_size; +}; + +auto main(int argc, char** argv) -> int { + auto config = Config{}; + + // skip exe name (argv[0]) + auto const args = Args{std::span{argv, static_cast(argc)}.subspan(1)}; + + // handle --help + if (!args.args.empty() && args.args.front() == std::string_view{"--help"}) { + std::cout << std::format("Usage: {} [step(=0.1)] [image_size_factor(=1)]\n", std::filesystem::path{*argv}.stem().string()); + std::cout << "\t output image resolution is 128x128, with each dimension multiplied by image_size_factor, maximum scaling factor is 32[image size of 4096]" << std::endl; + return EXIT_SUCCESS; + } + + // parse args, if any + if (!config.parse_args(args)) { + std::cout << args.args.front() << std::endl; + return EXIT_FAILURE; + } + + + auto noise = noiz::Noise3f{noiz::Seed{config.seed}, config.grid_extent}; + + // build and write noise to image + auto point_cloud = Point_Cloud{config.image_size_factor}; + point_cloud.build_and_write_object_with_noise(noise, config.step); +} diff --git a/noiz/include/noiz/cell3.hpp b/noiz/include/noiz/cell3.hpp new file mode 100644 index 0000000..5d6623e --- /dev/null +++ b/noiz/include/noiz/cell3.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include "index3.hpp" +#include "vec3.hpp" + +namespace noiz { +template +struct Corner3 { + Vec3 location{}; + Vec3 gradient{}; +}; + +template +struct TCell3 { + Type left_top_below{}; + Type right_top_below{}; + Type left_bottom_below{}; + Type right_bottom_below{}; + + Type left_top_above{}; + Type right_top_above{}; + Type left_bottom_above{}; + Type right_bottom_above{}; +}; + +template +using Cell3 = TCell3>; + +template +using CornerCell3 = TCell3>; +} // namespace noiz diff --git a/noiz/include/noiz/detail/data3.hpp b/noiz/include/noiz/detail/data3.hpp new file mode 100644 index 0000000..8507eec --- /dev/null +++ b/noiz/include/noiz/detail/data3.hpp @@ -0,0 +1,58 @@ +#pragma once +#include "generator.hpp" +#include "grid3.hpp" + +namespace noiz::detail { +template +auto make_populated_grid(Index3 const grid_extent, Seed seed = Generator::make_random_seed()) -> Grid3 { + auto ret = make_grid3(grid_extent); + auto generator = Generator{seed}; + for (auto& corner : ret.corners) { generator.next(corner.gradient); } + return ret; +} + +template +constexpr auto compute_offsets(CornerCell3 const& corner, Vec3 const point) -> Cell3 { + return Cell3{ + .left_top_below = point - corner.left_top_below.location, + .right_top_below = point - corner.right_top_below.location, + .left_bottom_below = point - corner.left_bottom_below.location, + .right_bottom_below = point - corner.right_bottom_below.location, + .left_top_above = point - corner.left_top_above.location, + .right_top_above = point - corner.right_top_above.location, + .left_bottom_above = point - corner.left_bottom_above.location, + .right_bottom_above = point - corner.right_bottom_above.location, + }; +} + +template +constexpr auto compute_dot_products(CornerCell3 const& corner, Cell3 const& offset) -> TCell3 { + return TCell3{ + .left_top_below = dot(corner.left_top_below.gradient, offset.left_top_below), + .right_top_below = dot(corner.right_top_below.gradient, offset.right_top_below), + .left_bottom_below = dot(corner.left_bottom_below.gradient, offset.left_bottom_below), + .right_bottom_below = dot(corner.right_bottom_below.gradient, offset.right_bottom_below), + + .left_top_above = dot(corner.left_top_above.gradient, offset.left_top_above), + .right_top_above = dot(corner.right_top_above.gradient, offset.right_top_above), + .left_bottom_above = dot(corner.left_bottom_above.gradient, offset.left_bottom_above), + .right_bottom_above = dot(corner.right_bottom_above.gradient, offset.right_bottom_above), + }; +} + +template +constexpr auto interpolate(Vec3 const point, TCell3 const& dot_products) -> Type { + auto const uvw = point.fract().fade(); + + auto const below_a = std::lerp(dot_products.left_top_below, dot_products.right_top_below, uvw.x); + auto const below_b = std::lerp(dot_products.left_bottom_below, dot_products.right_bottom_below, uvw.x); + auto const below = std::lerp(below_a, below_b, uvw.y); + + auto const above_a = std::lerp(dot_products.left_top_above, dot_products.right_top_above, uvw.x); + auto const above_b = std::lerp(dot_products.left_bottom_above, dot_products.right_bottom_above, uvw.x); + auto const above = std::lerp(above_a, above_b, uvw.y); + + //i might need to swap the position of below and above, not really sure + return std::lerp(below, above, uvw.z); +} +} // namespace noiz::detail diff --git a/noiz/include/noiz/detail/generator.hpp b/noiz/include/noiz/detail/generator.hpp index 3eaeed4..e7a98c6 100644 --- a/noiz/include/noiz/detail/generator.hpp +++ b/noiz/include/noiz/detail/generator.hpp @@ -2,6 +2,7 @@ #include #include "../seed.hpp" #include "../vec2.hpp" +#include "../vec3.hpp" namespace noiz::detail { /// @@ -36,16 +37,28 @@ class Generator { out = Vec2{.x = distribution(m_engine), .y = distribution(m_engine)}.normalized(); } + template + void next(Vec3& out) { + auto distribution = std::uniform_real_distribution{Type{-1}, Type{1}}; + out = Vec3{.x = distribution(m_engine), .y = distribution(m_engine), .z = distribution(m_engine)}.normalized(); + } + /// /// \brief Obtain the next random unit Vec. /// \returns The next random unit Vec. /// template - auto next() -> Vec2 { + auto next2() -> Vec2 { auto ret = Vec2{}; next(ret); return ret; } + template + auto next3() -> Vec3 { + auto ret = Vec3{}; + next(ret); + return ret; + } private: std::default_random_engine m_engine{}; diff --git a/noiz/include/noiz/detail/grid3.hpp b/noiz/include/noiz/detail/grid3.hpp new file mode 100644 index 0000000..3d3a033 --- /dev/null +++ b/noiz/include/noiz/detail/grid3.hpp @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include "../cell3.hpp" + +namespace noiz::detail { +template +[[nodiscard]] constexpr auto to_index3(Vec3 const point) -> Index3 { + return Index3{.x = static_cast(point.x), .y = static_cast(point.y), .z = static_cast(point.z)}; +} + +template +[[nodiscard]] constexpr auto to_vec3(Index3 const index) -> Vec3 { + return Vec3{.x = static_cast(index.x), .y = static_cast(index.y), .z = static_cast(index.z)}; +} + +template +struct Grid3 { + std::vector> corners{}; + Index3 grid_extent{}; + + [[nodiscard]] auto at(CellIndex3 const index) const -> CornerCell3 { + return CornerCell3{ + corners.at(index.ltb), corners.at(index.rtb), corners.at(index.lbb), corners.at(index.rbb), + corners.at(index.lta), corners.at(index.rta), corners.at(index.lba), corners.at(index.rba) + }; + } + + [[nodiscard]] auto at(Index3 index) const -> CornerCell3 { return at(CellIndex3::make(index, grid_extent)); } +}; + +template +[[nodiscard]] auto make_grid3(Index3 grid_extent) -> Grid3 { + auto const corner_count = (grid_extent.x + 1) * (grid_extent.y + 1) * (grid_extent.z + 1); + if (corner_count <= 0) { return {}; } + + auto ret = Grid3{ + .corners = std::vector>(static_cast(corner_count)), + .grid_extent = grid_extent, + }; + + for(int depth = 0; depth <= grid_extent.z; ++depth){ + for (int row = 0; row <= grid_extent.y; ++row) { + for (int col = 0; col <= grid_extent.x; ++col) { + auto const index3 = Index3{.x = col, .y = row, .z = depth}; + auto const index = static_cast(index3.flatten(grid_extent)); + ret.corners.at(index).location = to_vec3(Index3{.x = col, .y = row, .z = depth}); + } + } + } + + return ret; +} +} // namespace noiz::detail diff --git a/noiz/include/noiz/index3.hpp b/noiz/include/noiz/index3.hpp new file mode 100644 index 0000000..3b997fc --- /dev/null +++ b/noiz/include/noiz/index3.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include + +namespace noiz { +struct Index3 { + int x{}; + int y{}; + int z{}; + + [[nodiscard]] constexpr auto modulo(Index3 const extent) const -> Index3 { + assert(extent.x > 0 && extent.y > 0); + return Index3{.x = x % extent.x, .y = y % extent.y, .z = z % extent.z}; + } + + [[nodiscard]] constexpr auto flatten(Index3 const extent) const -> int64_t { + return z * ((extent.x + 1) * (extent.y + 1)) + y * (extent.x + 1) + x; + } +}; + +struct CellIndex3 { + std::size_t ltb{}; + std::size_t rtb{}; + std::size_t lbb{}; + std::size_t rbb{}; + + std::size_t lta{}; + std::size_t rta{}; + std::size_t lba{}; + std::size_t rba{}; + + static constexpr auto make(Index3 index, Index3 const grid_extent) -> CellIndex3 { + index = index.modulo(grid_extent); + auto const cell_index = index.flatten(grid_extent); + assert(cell_index >= 0); + auto ret = CellIndex3{}; + ret.ltb = static_cast(cell_index); + ret.rtb = ret.ltb + 1; + ret.lbb = ret.ltb + static_cast(grid_extent.x + 1); + ret.rbb = ret.rtb + static_cast(grid_extent.x + 1); + + ret.lta = ret.ltb + static_cast((grid_extent.x + 1) * (grid_extent.y + 1)); + ret.rta = ret.rtb + static_cast((grid_extent.x + 1) * (grid_extent.y + 1)); + ret.lba = ret.lbb + static_cast((grid_extent.x + 1) * (grid_extent.y + 1)); + ret.rba = ret.rbb + static_cast((grid_extent.x + 1) * (grid_extent.y + 1)); + return ret; + } +}; +} // namespace noiz diff --git a/noiz/include/noiz/noise-processing2.hpp b/noiz/include/noiz/noise-processing2.hpp new file mode 100644 index 0000000..9b56218 --- /dev/null +++ b/noiz/include/noiz/noise-processing2.hpp @@ -0,0 +1,132 @@ +#pragma once +#include "noise2.hpp" + +namespace noiz{ +//static functions, maybe make members of noise2? not really sure +template +class Noise_Processor2 { +public: + explicit Noise_Processor2(noiz::Noise2& noise) : noise{noise}{} + //if this is multi-threaded, accessing these variables directly could lead to strange behavior + //i.e. a handful of threads processing noise, then another comes in and changes octave?? + //not sure if thats a use case worth covering? + noiz::Noise2& noise; + + //default values + uint8_t octave{ 6 }; + Type step = (Type)0.1; + Type persistence = (Type)1.5; + Type lacunarity = (Type)2.0; + Type frequency = (Type)1.0; + Type amplitude = (Type)1.0; + + //hybrid multifractal variables, i have no idea what H is or id expand it to be mroe descriptive + Type hbf_H = (Type)0.25; + Type hbf_offset = (Type)0.7; + std::vector hbf_exponent_array; + + //could construct an object of noiz::noise2 here and it would simplify these functions a little bit. + + auto raw_noise(noiz::Vec2 const& point) -> Type{ + //redundant + return noise.at(point * step); + } + + auto basic_processing(noiz::Vec2 const& point) -> Type { + + Type total = Type(0); + Type frequency = (Type)2; + Type amplitude = (Type)1; + Type normalizer = (Type)0; + for(int i = 0; i < octave; i++){ + total += noise.at(point * step * frequency) * amplitude; + normalizer += amplitude; + amplitude *= persistence; + frequency *= lacunarity; + } + return total/normalizer; + } + + auto turbulence_processing(noiz::Vec2 const& point) -> Type { + Type amplitude = this->amplitude; + Type frequency = this->frequency; + + Type sum = 0.f; + Type normalizer = 0.f; + for (int i = 0; i < octave; i++) { + sum += noise.at(point * (step * frequency)) * amplitude; + normalizer += amplitude; + amplitude *= persistence; + frequency *= lacunarity; + } + //normally return sum; + //but im doing some additional adjustment in this function + return sum; + } + + auto billowy_processing(noiz::Vec2 const& point) -> Type { + return std::abs(noise.at(point * step)); + } + + auto rigid_processing(noiz::Vec2 const& point) -> Type { + return 1.f - std::abs(noise.at(point * step)); + } + + auto hybrid_multi_fractal_processing(noiz::Vec2 const& point) -> Type { + //https://www.classes.cs.uchicago.edu/archive/2015/fall/23700-1/final-project/MusgraveTerrain00.pdf + + //this function assumes the octave could be a floating point value + + //double HybridMultifractal( Vector point, double H, double lacunarity, + //double octaves, double offset ) + noiz::Vec2 tempPoint = point; + + Type frequency, result, signal, weight;//, remainder; if octave is floating + //double Noise3(); what is this?? + /* precompute and store spectral weights */ + if (hbf_exponent_array.size() != octave) { + hbf_exponent_array.resize(octave); + /* seize required memory for exponent_array */ + //exponent_array = (double*)malloc(octave * sizeof(double)); + frequency = 1.0; + for (uint16_t i = 0; i < octave; i++) { + /* compute weight for each frequency */ + hbf_exponent_array[i] = std::pow(frequency, -hbf_H); + frequency *= lacunarity; + } + } + /* get first octave of function */ + result = (noise.at(tempPoint * step) + hbf_offset) * hbf_exponent_array[0]; + weight = result; + /* increase frequency */ + tempPoint = tempPoint * lacunarity; + /* spectral construction inner loop, where the fractal is built */ + for (int i = 1; i < octave; i++) { + /* prevent divergence */ + if (weight > 1.0) weight = 1.0; + /* get next higher frequency */ + signal = (noise.at(tempPoint * step) + hbf_offset) * hbf_exponent_array[i]; + /* add it in, weighted by previous freq's local value */ + result += weight * signal; + /* update the (monotonically decreasing) weighting value */ + /* (this is why H must specify a high fractal dimension) */ + weight *= signal; + /* increase frequency */ + tempPoint = tempPoint * lacunarity; + } /* for */ + // take care of remainder in “octaves” + + /* octave is currently an int, may swap for this + remainder = octave - (int)octave; + if (remainder) + // “i” and spatial freq. are preset in loop above + result += remainder * noise.at(point) * hbf_exponent_array[octave - 1]; + */ + + return result; + } + +}; +using Noise_Processor2f = Noise_Processor2; +using Noise_Processor2d = Noise_Processor2; +} //namespace noiz \ No newline at end of file diff --git a/noiz/include/noiz/noise-processing3.hpp b/noiz/include/noiz/noise-processing3.hpp new file mode 100644 index 0000000..3e32c92 --- /dev/null +++ b/noiz/include/noiz/noise-processing3.hpp @@ -0,0 +1,154 @@ +#pragma once +#include "noise3.hpp" + + +namespace noiz{ +//static functions, maybe make members of noise2? not really sure +template +class Noise_Processor3 { +public: + explicit Noise_Processor3(noiz::Noise3& noise) : noise{noise}{} + //if this is multi-threaded, accessing these variables directly could lead to strange behavior + //i.e. a handful of threads processing noise, then another comes in and changes octave?? + //not sure if thats a use case worth covering? + noiz::Noise3& noise; + + //default values + uint8_t octave{ 6 }; + Type step = (Type)0.1; + Type persistence = (Type)1.5; + Type lacunarity = (Type)2.0; + Type frequency = (Type)1.0; + Type amplitude = (Type)1.0; + + //hybrid multifractal variables, i have no idea what H is or id expand it to be mroe descriptive + Type hbf_H = (Type)0.25; + Type hbf_offset = (Type)0.7; + std::vector hbf_exponent_array; + + auto raw_noise(noiz::Vec3 const& point) -> Type{ + //redundant + return noise.at(point * step); + } + + auto basic_processing(noiz::Vec3 const& point) -> Type { + + Type total = Type(0); + Type frequency = (Type)2; + Type amplitude = (Type)1; + Type normalizer = (Type)0; + for(int i = 0; i < octave; i++){ + total += noise.at(point * step * frequency) * amplitude; + normalizer += amplitude; + amplitude *= persistence; + frequency *= lacunarity; + } + return total/normalizer; + } + + auto turbulence_processing(noiz::Vec3 const& point) -> Type { + Type amplitude = this->amplitude; + Type frequency = this->frequency; + + Type sum = 0.f; + Type normalizer = 0.f; + for (int i = 0; i < octave; i++) { + sum += noise.at(point * (step * frequency)) * amplitude; + normalizer += amplitude; + amplitude *= persistence; + frequency *= lacunarity; + } + //normally return sum; + //but im doing some additional adjustment in this function + return sum; + } + + auto billowy_processing(noiz::Vec3 const& point) -> Type { + return std::abs(noise.at(point * step)); + } + + auto rigid_processing(noiz::Vec3 const& point) -> Type { + return 1.f - std::abs(noise.at(point * step)); + } + + auto hybrid_multi_fractal_processing(noiz::Vec3 const& point) -> Type { + //https://www.classes.cs.uchicago.edu/archive/2015/fall/23700-1/final-project/MusgraveTerrain00.pdf + + //this function assumes the octave could be a floating point value + + //double HybridMultifractal( Vector point, double H, double lacunarity, + //double octaves, double offset ) + noiz::Vec3 tempPoint = point; + + Type frequency, result, signal, weight;//, remainder; if octave is floating + + /* precompute and store spectral weights */ + if (hbf_exponent_array.size() != octave) { + hbf_exponent_array.resize(octave); + /* seize required memory for exponent_array */ + //exponent_array = (double*)malloc(octave * sizeof(double)); + frequency = 1.0; + for (uint16_t i = 0; i < octave; i++) { + /* compute weight for each frequency */ + hbf_exponent_array[i] = std::pow(frequency, -hbf_H); + frequency *= lacunarity; + } + } + /* get first octave of function */ + result = (noise.at(tempPoint * step) + hbf_offset) * hbf_exponent_array[0]; + weight = result; + /* increase frequency */ + tempPoint = tempPoint * lacunarity; + /* spectral construction inner loop, where the fractal is built */ + for (uint16_t i = 1; i < octave; i++) { + /* prevent divergence */ + if (weight > 1.0) {weight = 1.0;} + /* get next higher frequency */ + signal = (noise.at(tempPoint * step) + hbf_offset) * hbf_exponent_array[i]; + /* add it in, weighted by previous freq's local value */ + result += weight * signal; + /* update the (monotonically decreasing) weighting value */ + /* (this is why H must specify a high fractal dimension) */ + weight *= signal; + /* increase frequency */ + tempPoint = tempPoint * lacunarity; + } + + /* octave is currently an int, may swap for this + * take care of remainder in “octaves” + remainder = octave - (int)octave; + if (remainder) + // “i” and spatial freq. are preset in loop above + result += remainder * noise.at(point) * hbf_exponent_array[octave - 1]; + */ + + + //values from 0 to ~4 can be expected. highest value ive seen is 3.7 + return result; + } + +/* + auto blended_basic_processing(noiz::Vec3 const& point, std::vector> const& noise){ + *each noise source would need its own set of variables + + std::vector + std::vector + std::vector + ... + + Type sum = 0; + for(int i = 0; i < noise.size(); i++){ + sum += basic_processing(point, noise[i]); + } + return sum / noise.size(); + + *this is probably beyond the scope of this lib + *end user could construct multiple objects of this class and it would be simpler than trying to implement it here + *probably simpler for the end user too, regardless of what kinda trickery is implemented + } +*/ + +}; +using Noise_Processor3f = Noise_Processor3; +using Noise_Processor3d = Noise_Processor3; +} //namespace noiz \ No newline at end of file diff --git a/noiz/include/noiz/noise3.hpp b/noiz/include/noiz/noise3.hpp new file mode 100644 index 0000000..6d211cf --- /dev/null +++ b/noiz/include/noiz/noise3.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "detail/data3.hpp" + +namespace noiz { +using GridExtent3 = Index3; + +template +class Noise3 { + public: + static constexpr GridExtent3 grid_extent_v{256, 256, 256}; + + explicit Noise3(GridExtent3 grid_extent = grid_extent_v) : Noise3(detail::Generator::make_random_seed(), grid_extent) {} + + explicit Noise3(Seed generator_seed, GridExtent3 grid_extent) : m_grid(detail::make_populated_grid(grid_extent, generator_seed)) {} + + [[nodiscard]] auto grid_extent() const -> GridExtent3 { return m_grid.grid_extent; } + + [[nodiscard]] auto at(Vec3 point) const -> Type { + point = point.modulo(detail::to_vec3(m_grid.grid_extent)); + auto const corners = m_grid.at(detail::to_index3(point)); + auto const offsets = detail::compute_offsets(corners, point); + auto const dots = detail::compute_dot_products(corners, offsets); + + return detail::interpolate(point, dots); + } + + private: + detail::Grid3 m_grid{}; +}; + +using Noise3f = Noise3; +using Noise3d = Noise3; +} // namespace noiz diff --git a/noiz/include/noiz/vec2.hpp b/noiz/include/noiz/vec2.hpp index 7e1a7af..b8b5082 100644 --- a/noiz/include/noiz/vec2.hpp +++ b/noiz/include/noiz/vec2.hpp @@ -20,8 +20,8 @@ struct Vec2 { void normalize() { auto const mag = magnitude(); assert(mag > Type(0)); - x /= mag; - y /= mag; + + *this = *this / mag; } [[nodiscard]] auto normalized() const -> Vec2 { @@ -75,6 +75,11 @@ struct Vec2 { friend constexpr auto operator*(Vec2 lhs, Vec2 const rhs) -> Vec2 { return lhs *= rhs; } friend constexpr auto operator/(Vec2 lhs, Vec2 const rhs) -> Vec2 { return lhs /= rhs; } + //i think this forces the factor/divisor to be the same type as the vec, aka float==float, or double==double, + //wont allow float*double? not really sure + friend constexpr auto operator*(Vec2 lhs, Type const factor) -> Vec2 {return Vec2{.x = lhs.x * factor, .y = lhs.y * factor};} + friend constexpr auto operator/(Vec2 lhs, Type const divisor) -> Vec2 {return Vec2{.x = lhs.x / divisor, .y = lhs.y / divisor};} + auto operator==(Vec2 const&) const -> bool = default; }; diff --git a/noiz/include/noiz/vec3.hpp b/noiz/include/noiz/vec3.hpp new file mode 100644 index 0000000..2d3a79f --- /dev/null +++ b/noiz/include/noiz/vec3.hpp @@ -0,0 +1,110 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace noiz { +template +struct Vec3 { + static constexpr auto epsilon_v = Type(0.001); + + Type x{}; + Type y{}; + Type z{}; + + [[nodiscard]] constexpr auto sqr_magnitude() const -> Type { return x * x + y * y + z * z; } + + [[nodiscard]] auto magnitude() const -> Type { return std::sqrt(sqr_magnitude()); } + + void normalize() { + auto const mag = magnitude(); + assert(mag > Type(0)); + + *this = *this / mag; + } + + [[nodiscard]] auto normalized() const -> Vec3 { + auto ret = *this; + ret.normalize(); + return ret; + } + + [[nodiscard]] auto is_normalized() const -> bool { return std::abs(sqr_magnitude() - Type(1)) < epsilon_v; } + + [[nodiscard]] auto modulo(Vec3 const extent) const -> Vec3 { + assert(extent.x > Type(0) && extent.y > Type(0)); + return Vec3{.x = std::fmod(x, extent.x), .y = std::fmod(y, extent.y), .z = std::fmod(z, extent.z)}; + } + + [[nodiscard]] constexpr auto fract() const -> Vec3 { return Vec3{.x = x - std::floor(x), .y = y - std::floor(y), .z = z - std::floor(z)}; } + + [[nodiscard]] constexpr auto fade() const -> Vec3 { + return Vec3{ + .x = x * x * x * (x * (x * 6 - 15) + 10), + .y = y * y * y * (y * (y * 6 - 15) + 10), + .z = z * z * z * (z * (z * 6 - 15) + 10) + }; + } + + constexpr auto operator+=(Vec3 const rhs) -> Vec3& { + x += rhs.x; + y += rhs.y; + z += rhs.z; + return *this; + } + + constexpr auto operator-=(Vec3 const rhs) -> Vec3& { + x -= rhs.x; + y -= rhs.y; + z -= rhs.z; + return *this; + } + + constexpr auto operator*=(Vec3 const rhs) -> Vec3& { + x *= rhs.x; + y *= rhs.y; + z *= rhs.z; + return *this; + } + + constexpr auto operator/=(Vec3 const rhs) -> Vec3& { + x /= rhs.x; + y /= rhs.y; + z /= rhs.z; + return *this; + } + + friend constexpr auto operator+(Vec3 lhs, Vec3 const rhs) -> Vec3 { return lhs += rhs; } + friend constexpr auto operator-(Vec3 lhs, Vec3 const rhs) -> Vec3 { return lhs -= rhs; } + friend constexpr auto operator*(Vec3 lhs, Vec3 const rhs) -> Vec3 { return lhs *= rhs; } + friend constexpr auto operator/(Vec3 lhs, Vec3 const rhs) -> Vec3 { return lhs /= rhs; } + friend constexpr auto operator*(Vec3 lhs, Type const factor) -> Vec3 {return Vec3{.x = lhs.x * factor, .y = lhs.y * factor, .z = lhs.z * factor};} + friend constexpr auto operator/(Vec3 lhs, Type const divisor) -> Vec3 {return Vec3{.x = lhs.x / divisor, .y = lhs.y / divisor, .z = lhs.z / divisor};} + + auto operator==(Vec3 const&) const -> bool = default; +}; + +template +constexpr auto dot(Vec3 const lhs, Vec3 const rhs) -> Type { + return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; +} + +template +constexpr auto compare(Vec3 const lhs, Vec3 const rhs, Type const epsilon = Type(0.001)) { + return (std::abs(lhs.x - rhs.x) < epsilon) && (std::abs(lhs.y - rhs.y) < epsilon) && (std::abs(lhs.z - rhs.z) < epsilon); +} + +template +constexpr auto lerp(Vec3 const lhs, Vec3 const rhs, Type alpha) -> Vec3 { + return Vec3{ + .x = std::lerp(lhs.x, rhs.x, alpha), + .y = std::lerp(lhs.y, rhs.y, alpha), + .z = std::lerp(lhs.z, rhs.z, alpha) + }; +} + +using Vec3f = Vec3; +using Vec3d = Vec3; +} // namespace noiz