Skip to content

Commit 78b6eb2

Browse files
GDBobbykarnkaul
andauthored
Texture Generation (#3)
Example Addition - texture2d This implementation example takes arguments, in particular, count, step, and image_size_factor, which is multiplied by 256 for the final image resolution. The library polls for noise at the scaled coordinates of each pixel of the image, then writes it to a generated ppm file. added Perlin's fade function to the detail::interpolate function argument_parsing.hpp added to common/ common argument parsing amongst all examples --------- Co-authored-by: Karn Kaul <karnkaul@gmail.com>
1 parent 9bf2a15 commit 78b6eb2

File tree

10 files changed

+181
-40
lines changed

10 files changed

+181
-40
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ out/*
55
screenshots
66
.cache
77
.DS_Store
8+
*.bak
89

910
CMakeSettings.json
1011
compile_commands.json

examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
add_subdirectory(histogram)
2+
add_subdirectory(texture2d)

examples/common/argument_parsing.hpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <noiz/noise2.hpp>
2+
3+
#include <charconv>
4+
#include <format>
5+
#include <iostream>
6+
7+
namespace {
8+
template <typename Type>
9+
concept NumberT = std::integral<Type> || std::floating_point<Type>;
10+
11+
template <NumberT Type>
12+
auto parse_as(Type& out, std::string_view const value) -> bool {
13+
if (value.empty()) { return false; }
14+
15+
auto const* end = value.data() + value.size();
16+
auto const [ptr, ec] = std::from_chars(value.data(), end, out);
17+
18+
return ec == std::errc{} && ptr == end;
19+
}
20+
21+
struct Args {
22+
std::span<char const* const> args{};
23+
24+
[[nodiscard]] constexpr auto next() -> std::string_view {
25+
if (args.empty()) { return {}; }
26+
auto const* ret = args.front();
27+
args = args.subspan(1);
28+
return ret;
29+
}
30+
31+
template <NumberT Type>
32+
auto next_as(Type& out, std::string_view const key) -> bool {
33+
auto value = next();
34+
if (value.empty()) { return true; }
35+
36+
if (!parse_as(out, value)) {
37+
std::cerr << std::format("invalid argument: '{}' for '{}'\n", value, key);
38+
return false;
39+
}
40+
41+
return true;
42+
}
43+
};
44+
} // namespace
45+
46+
//the config function is implemented per-example, currently have 2 implementations

examples/histogram/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
1010
target_sources(${PROJECT_NAME} PRIVATE
1111
histogram.cpp
1212
)
13+
14+
target_include_directories(${PROJECT_NAME} PRIVATE
15+
../common
16+
)

examples/histogram/histogram.cpp

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,9 @@
55
#include <iostream>
66
#include <vector>
77

8-
namespace {
9-
template <typename Type>
10-
concept NumberT = std::integral<Type> || std::floating_point<Type>;
11-
12-
template <NumberT Type>
13-
auto parse_as(Type& out, std::string_view const value) -> bool {
14-
if (value.empty()) { return false; }
15-
16-
auto const* end = value.data() + value.size();
17-
auto const [ptr, ec] = std::from_chars(value.data(), end, out);
18-
19-
return ec == std::errc{} && ptr == end;
20-
}
21-
22-
struct Args {
23-
std::span<char const* const> args{};
24-
25-
[[nodiscard]] constexpr auto next() -> std::string_view {
26-
if (args.empty()) { return {}; }
27-
auto const* ret = args.front();
28-
args = args.subspan(1);
29-
return ret;
30-
}
8+
#include <argument_parsing.hpp>
319

32-
template <NumberT Type>
33-
auto next_as(Type& out, std::string_view const key) -> bool {
34-
auto value = next();
35-
if (value.empty()) { return true; }
36-
37-
if (!parse_as(out, value)) {
38-
std::cerr << std::format("invalid argument: '{}' for '{}'\n", value, key);
39-
return false;
40-
}
41-
42-
return true;
43-
}
44-
};
10+
namespace {
4511

4612
struct Config {
4713
noiz::Seed seed{noiz::detail::Generator::make_random_seed()};

examples/texture2d/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
project(noiz-texture2d)
2+
3+
add_executable(${PROJECT_NAME})
4+
5+
target_link_libraries(${PROJECT_NAME} PRIVATE
6+
noiz::noiz-lib
7+
noiz::noiz-compile-options
8+
)
9+
10+
target_sources(${PROJECT_NAME} PRIVATE
11+
texture2d.cpp
12+
)
13+
14+
target_include_directories(${PROJECT_NAME} PRIVATE
15+
../common
16+
)

examples/texture2d/texture2d.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#include <noiz/noise2.hpp>
2+
3+
#include <argument_parsing.hpp>
4+
5+
6+
#include <charconv>
7+
#include <filesystem>
8+
#include <format>
9+
#include <iostream>
10+
#include <vector>
11+
#include <fstream>
12+
13+
constexpr int32_t constant_base_resolution = 256;
14+
constexpr int32_t constant_max_resolution_factor = 16;
15+
16+
void write_ppm_file_header(std::ofstream& out_file, uint16_t image_size){
17+
out_file << "P6" << std::endl;
18+
out_file << image_size << " " << image_size << std::endl;
19+
out_file << 255 << std::endl; //255 is max color value, 8 bits
20+
}
21+
22+
struct Config {
23+
noiz::Seed seed{noiz::detail::Generator::make_random_seed()};
24+
noiz::GridExtent2 grid_extent{50, 1}; // NOLINT
25+
float step{0.1f}; // NOLINT
26+
int image_size_factor{1};
27+
28+
// syntax: [count] [step]
29+
auto parse_args(Args args) -> bool {
30+
if (!args.next_as(step, "step")) { return false; }
31+
if (!args.next_as(image_size_factor, "image_size_factor")) { return false; }
32+
if (image_size_factor < 0 || image_size_factor > constant_max_resolution_factor) {
33+
std::cerr << std::format("invalid image size factor: '{}'\n", image_size_factor);
34+
return false;
35+
}
36+
if (!args.args.empty()) {
37+
return false;
38+
}
39+
grid_extent.x = constant_base_resolution * image_size_factor;
40+
grid_extent.y = constant_base_resolution * image_size_factor;
41+
return true;
42+
}
43+
};
44+
45+
// pulls noise from noiz::lib, writes a pixel to ppm file
46+
class Texture_2D {
47+
public:
48+
explicit Texture_2D(int image_size_factor) : image_size{static_cast<uint16_t>(constant_base_resolution * image_size_factor)} {}
49+
50+
void build_and_write_image_noise(noiz::Noise2f& noise, float const& step) const {
51+
52+
std::ofstream out_file{"noise.ppm", std::ios::binary};
53+
write_ppm_file_header(out_file, image_size);
54+
uint8_t grayscale_color; //0 is black, 255 is white
55+
for (int y = 0; y < image_size; y++) {
56+
for(int x = 0; x < image_size; x++) {
57+
// add noise at point
58+
float adjusted_noise = noise.at(noiz::Vec2f{.x = static_cast<float>(x) * step, .y = static_cast<float>(y) * step});
59+
adjusted_noise = (adjusted_noise + 1.f) * 0.5f;
60+
grayscale_color = static_cast<uint8_t>((adjusted_noise) * 255.f);
61+
for(int i = 0; i < 3; i++) {
62+
//ppm cant use 1 channel grayscale, forced to 3 channel
63+
out_file.write(reinterpret_cast<char*>(&grayscale_color), sizeof(char)); //sizeof(char) == 1
64+
}
65+
}
66+
}
67+
out_file.close();
68+
}
69+
70+
private:
71+
uint16_t image_size;
72+
};
73+
74+
auto main(int argc, char** argv) -> int {
75+
auto config = Config{};
76+
77+
// skip exe name (argv[0])
78+
auto const args = Args{std::span{argv, static_cast<std::size_t>(argc)}.subspan(1)};
79+
80+
// handle --help
81+
if (!args.args.empty() && args.args.front() == std::string_view{"--help"}) {
82+
std::cout << std::format("Usage: {} [step(=0.1)] [image_size_factor(=1)]\n", std::filesystem::path{*argv}.stem().string());
83+
std::cout << "\t output image resolution is 256x256, with each dimension multiplied by image_size_factor, maximum scaling factor is 16[image size of 4096]" << std::endl;
84+
return EXIT_SUCCESS;
85+
}
86+
87+
// parse args, if any
88+
if (!config.parse_args(args)) {
89+
std::cout << args.args.front() << std::endl;
90+
return EXIT_FAILURE;
91+
}
92+
93+
94+
auto noise = noiz::Noise2f{noiz::Seed{config.seed}, config.grid_extent};
95+
96+
// build and write noise to image
97+
auto texture2d = Texture_2D{config.image_size_factor};
98+
texture2d.build_and_write_image_noise(noise, config.step);
99+
}

noiz/include/noiz/detail/data2.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ constexpr auto compute_dot_products(CornerCell2<Type> const& corner, Cell2<Type>
3333

3434
template <std::floating_point Type>
3535
constexpr auto interpolate(Vec2<Type> const point, TCell2<Type> const& dot_products) -> Type {
36-
auto const uv = point.fract();
36+
auto const uv = point.fract().fade();
3737
auto const a = std::lerp(dot_products.left_top, dot_products.right_top, uv.x);
3838
auto const b = std::lerp(dot_products.left_bottom, dot_products.right_bottom, uv.x);
3939
return std::lerp(a, b, uv.y);

noiz/include/noiz/noise2.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ class Noise2 {
1717

1818
[[nodiscard]] auto at(Vec2<Type> point) const -> Type {
1919
point = point.modulo(detail::to_vec2<Type>(m_grid.grid_extent));
20-
auto const corner = m_grid.at(detail::to_index2(point));
21-
auto const offset = detail::compute_offsets(corner, point);
22-
auto const dots = detail::compute_dot_products(corner, offset);
20+
auto const corners = m_grid.at(detail::to_index2(point));
21+
auto const offsets = detail::compute_offsets(corners, point);
22+
auto const dots = detail::compute_dot_products(corners, offsets);
23+
2324
return detail::interpolate(point, dots);
2425
}
2526

noiz/include/noiz/vec2.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ struct Vec2 {
3939

4040
[[nodiscard]] constexpr auto fract() const -> Vec2<Type> { return Vec2<Type>{.x = x - std::floor(x), .y = y - std::floor(y)}; }
4141

42+
[[nodiscard]] constexpr auto fade() const -> Vec2<Type> {
43+
return Vec2<Type>{
44+
.x = x * x * x * (x * (x * 6 - 15) + 10),
45+
.y = y * y * y * (y * (y * 6 - 15) + 10)
46+
};
47+
}
48+
4249
constexpr auto operator+=(Vec2 const rhs) -> Vec2& {
4350
x += rhs.x;
4451
y += rhs.y;

0 commit comments

Comments
 (0)