From 9bf2a1598f5a907066b6c98e75f86f3537f02076 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Sat, 23 Sep 2023 15:03:44 -0700 Subject: [PATCH] Add examples/histogram (#1) * Add examples/histogram * Add build CI. * Fix tests, update CMake presets. * Fix non-constexpr function. --- .github/workflows/ci.yml | 29 +++++++ CMakeLists.txt | 5 ++ CMakePresets.json | 44 ++++++----- examples/CMakeLists.txt | 1 + examples/histogram/CMakeLists.txt | 12 +++ examples/histogram/histogram.cpp | 126 ++++++++++++++++++++++++++++++ noiz/include/noiz/vec2.hpp | 2 +- tests/tests/CMakeLists.txt | 2 +- tests/tests/test_vec2.cpp | 2 - 9 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 examples/CMakeLists.txt create mode 100644 examples/histogram/CMakeLists.txt create mode 100644 examples/histogram/histogram.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e0c6d32 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: ci +on: [push] +jobs: + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: init + run: sudo apt update -yqq && sudo apt install -yqq ninja-build g++-13 clang-15 + - name: configure gcc + run: cmake -S . --preset=default -B build -DCMAKE_CXX_COMPILER=g++-13 + - name: configure clang + run: cmake -S . --preset=ninja-clang -B clang -DCMAKE_CXX_COMPILER=clang++-15 + - name: build gcc + run: cmake --build build --config=Release + - name: build clang + run: cmake --build clang --config=Release + - name: test + run: cd build && ctest -C Release + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: configure + run: cmake -S . --preset=vs22 -B build + - name: build + run: cmake --build build --config=Release + - name: test + run: cd build && ctest -C Release diff --git a/CMakeLists.txt b/CMakeLists.txt index c74f79b..25d2a4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) endif() option(NOIZ_BUILD_TESTS "Build noiz tests" ${is_project_root}) +option(NOIZ_BUILD_EXAMPLES "Build noiz examples" ${is_project_root}) add_library(noiz-compile-options INTERFACE) add_library(noiz::noiz-compile-options ALIAS noiz-compile-options) @@ -29,6 +30,10 @@ endif() add_subdirectory(noiz) +if(NOIZ_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + if(NOIZ_BUILD_TESTS) enable_testing() add_subdirectory(tests) diff --git a/CMakePresets.json b/CMakePresets.json index d0396b7..22ad359 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -36,6 +36,7 @@ }, { "name": "ninja-asan", + "description": "ASan build configuration using Ninja Multi-config", "inherits": "ninja-ubsan", "binaryDir": "${sourceDir}/out/asan", "cacheVariables": { @@ -45,36 +46,21 @@ { "name": "ninja-tsan", "inherits": "ninja-ubsan", + "description": "TSan build configuration using Ninja Multi-config", "binaryDir": "${sourceDir}/out/tsan", "cacheVariables": { "CMAKE_CXX_FLAGS": "-fsanitize=thread -O1" } }, { - "name": "vs19", - "description": "Build configuration using Visual Studio 16 (2019)", - "generator": "Visual Studio 16 2019", + "name": "vs22", + "description": "Build configuration using Visual Studio 17 (2022)", + "generator": "Visual Studio 17 2022", "binaryDir": "${sourceDir}/out/vs", "architecture": { "value": "x64", "strategy": "external" } - }, - { - "name": "vs22", - "description": "Build configuration using Visual Studio 17 (2022)", - "inherits": "vs19", - "generator": "Visual Studio 17 2022" - }, - { - "name": "pre-install", - "description": "Debug build using Ninja Multi-config and install prefix", - "inherits": "default", - "binaryDir": "${sourceDir}/out/pre", - "cacheVariables": { - "CMAKE_PREFIX_PATH": "${sourceDir}/out/install", - "RR_PREINSTALLED": "ON" - } } ], "buildPresets": [ @@ -92,6 +78,16 @@ "name": "RelWithDebInfo", "configurePreset": "default", "configuration": "RelWithDebInfo" + }, + { + "name": "UBSan-Debug", + "configurePreset": "ninja-ubsan", + "configuration": "Debug" + }, + { + "name": "ASan-Debug", + "configurePreset": "ninja-asan", + "configuration": "Debug" } ], "testPresets": [ @@ -109,6 +105,16 @@ "name": "RelWithDebInfo", "configurePreset": "default", "configuration": "RelWithDebInfo" + }, + { + "name": "UBSan Debug", + "configurePreset": "ninja-ubsan", + "configuration": "Debug" + }, + { + "name": "ASan Debug", + "configurePreset": "ninja-asan", + "configuration": "Debug" } ] } \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..e71d3af --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(histogram) diff --git a/examples/histogram/CMakeLists.txt b/examples/histogram/CMakeLists.txt new file mode 100644 index 0000000..cf80f2b --- /dev/null +++ b/examples/histogram/CMakeLists.txt @@ -0,0 +1,12 @@ +project(noiz-histogram) + +add_executable(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PRIVATE + noiz::noiz-lib + noiz::noiz-compile-options +) + +target_sources(${PROJECT_NAME} PRIVATE + histogram.cpp +) diff --git a/examples/histogram/histogram.cpp b/examples/histogram/histogram.cpp new file mode 100644 index 0000000..2b0b056 --- /dev/null +++ b/examples/histogram/histogram.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include + +namespace { +template +concept NumberT = std::integral || std::floating_point; + +template +auto parse_as(Type& out, std::string_view const value) -> bool { + if (value.empty()) { return false; } + + auto const* end = value.data() + value.size(); + auto const [ptr, ec] = std::from_chars(value.data(), end, out); + + return ec == std::errc{} && ptr == end; +} + +struct Args { + std::span args{}; + + [[nodiscard]] constexpr auto next() -> std::string_view { + if (args.empty()) { return {}; } + auto const* ret = args.front(); + args = args.subspan(1); + return ret; + } + + template + auto next_as(Type& out, std::string_view const key) -> bool { + auto value = next(); + if (value.empty()) { return true; } + + if (!parse_as(out, value)) { + std::cerr << std::format("invalid argument: '{}' for '{}'\n", value, key); + return false; + } + + return true; + } +}; + +struct Config { + noiz::Seed seed{noiz::detail::Generator::make_random_seed()}; + noiz::GridExtent2 grid_extent{50, 1}; // NOLINT + int count{100}; // NOLINT + float step{0.1f}; // NOLINT + + // syntax: [count] [step] + auto parse_args(Args args) -> bool { + if (!args.next_as(count, "count")) { return false; } + if (!args.next_as(step, "step")) { return false; } + if (!args.args.empty()) { + std::cerr << std::format("unrecognized argument: '{}'\n", args.next()); + return false; + } + return true; + } +}; + +// contains logic to store data points and draw them +class Histogram { + public: + explicit Histogram(std::size_t const levels = 10) : rows(levels) {} + + auto add(float const value) -> Histogram& { + // compute height relative to number of rows/levels + auto const height = static_cast((value + 1.0f) * 0.5f * static_cast(rows.size())); + // add a new column + for (std::size_t index = 0; index < rows.size(); ++index) { + // set output "pixel" value (0 or 1) + auto const ch = index > height ? symbol : background; + // append to each row + rows.at(index) += ch; + } + return *this; + } + + [[nodiscard]] auto build() const -> std::string { + auto ret = std::string{}; + for (auto const& row : rows) { + ret += row; + ret += '\n'; + } + return ret; + } + + char symbol{'x'}; + char background{'-'}; + + private: + std::vector rows{}; +}; +} // namespace + +auto main(int argc, char** argv) -> int { + auto config = Config{}; + auto noise = noiz::Noise2f{noiz::Seed{config.seed}, config.grid_extent}; + + // 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: {} [count(=100)] [step(=0.1)]\n", std::filesystem::path{*argv}.stem().string()); + return EXIT_SUCCESS; + } + + // parse args, if any + if (!config.parse_args(args)) { return EXIT_FAILURE; } + + // build histogram + auto histogram = Histogram{}; + for (int i = 0; i < config.count; ++i) { + // build point on line (y = 0) + auto const point = noiz::Vec2f{.x = static_cast(i) * config.step}; + // add noise at point + histogram.add(noise.at(point)); + } + + // output histogram + std::cout << histogram.build(); +} diff --git a/noiz/include/noiz/vec2.hpp b/noiz/include/noiz/vec2.hpp index 52b89be..9ca205e 100644 --- a/noiz/include/noiz/vec2.hpp +++ b/noiz/include/noiz/vec2.hpp @@ -30,7 +30,7 @@ struct Vec2 { return ret; } - [[nodiscard]] constexpr auto is_normalized() const -> bool { return std::abs(sqr_magnitude() - Type(1)) < epsilon_v; } + [[nodiscard]] auto is_normalized() const -> bool { return std::abs(sqr_magnitude() - Type(1)) < epsilon_v; } [[nodiscard]] auto modulo(Vec2 const extent) const -> Vec2 { assert(extent.x > Type(0) && extent.y > Type(0)); diff --git a/tests/tests/CMakeLists.txt b/tests/tests/CMakeLists.txt index 13731bb..72277af 100644 --- a/tests/tests/CMakeLists.txt +++ b/tests/tests/CMakeLists.txt @@ -5,4 +5,4 @@ file(GLOB SOURCES CONFIGURE_DEPENDS "*.cpp") target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) target_link_libraries(${PROJECT_NAME} PRIVATE noiz-test) -add_test(NAME noiz-test-${NAME} COMMAND noiz-test-${NAME}) +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/tests/tests/test_vec2.cpp b/tests/tests/test_vec2.cpp index c6e29f1..f93ae7b 100644 --- a/tests/tests/test_vec2.cpp +++ b/tests/tests/test_vec2.cpp @@ -11,8 +11,6 @@ static_assert(noiz::Vec2f{4.0f, 9.0f} / noiz::Vec2f{2.0f, 3.0f} == noiz::Vec2f{2 static_assert(noiz::dot(noiz::Vec2f{2.0f, 3.0f}, noiz::Vec2f{4.0f, 5.0f}) == 23.0f); // NOLINT static_assert(noiz::Vec2f{2.0f, 3.0f}.sqr_magnitude() == 13.0f); // NOLINT -static_assert(noiz::Vec2f{1.0f, 0.0f}.is_normalized()); - ADD_TEST(vec2_magnitude) { auto const vec = noiz::Vec2f{3.0f, 4.0f}; // NOLINT EXPECT(vec.magnitude() == 5.0f); // NOLINT