feat: project skeleton, core types and math with transform tests
This commit is contained in:
1 parent
e4051bc6b5
commit
8a7e9e97f8
21 files changed
+515
-2
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
Language: Cpp
|
||||||
|
Standard: c++20
|
||||||
|
IndentWidth: 2
|
||||||
|
ColumnLimit: 100
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.superpowers/
|
||||||
|
build/
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.24)
|
||||||
|
project(plot VERSION 0.1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
include(plot_options)
|
||||||
|
|
||||||
|
add_library(plot_core
|
||||||
|
src/core/status.cpp
|
||||||
|
src/math/transform.cpp
|
||||||
|
)
|
||||||
|
add_library(plot::core ALIAS plot_core)
|
||||||
|
|
||||||
|
target_include_directories(plot_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_compile_features(plot_core PUBLIC cxx_std_20)
|
||||||
|
target_link_libraries(plot_core PRIVATE ${CMAKE_DL_LIBS})
|
||||||
|
plot_apply_warnings(plot_core)
|
||||||
|
|
||||||
|
if(PLOT_BUILD_TESTS)
|
||||||
|
enable_testing()
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"version": 5,
|
||||||
|
"cmakeMinimumRequired": {
|
||||||
|
"major": 3,
|
||||||
|
"minor": 24,
|
||||||
|
"patch": 0
|
||||||
|
},
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "linux-debug",
|
||||||
|
"displayName": "Linux Debug",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/build/debug",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux-release",
|
||||||
|
"displayName": "Linux Release",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/build/release",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "linux-debug",
|
||||||
|
"configurePreset": "linux-debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux-release",
|
||||||
|
"configurePreset": "linux-release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Shared build options and warning policy for the plot project.
|
||||||
|
#
|
||||||
|
# Included by the top-level CMakeLists.txt; every subdirectory target is
|
||||||
|
# expected to run plot_apply_warnings(<target>) so the core stays warning-clean
|
||||||
|
# under -Wall -Wextra -Wpedantic -Wconversion -Wshadow.
|
||||||
|
|
||||||
|
if(CMAKE_VERSION VERSION_LESS 3.24)
|
||||||
|
message(FATAL_ERROR "plot requires CMake 3.24 or newer (found ${CMAKE_VERSION})")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Adapters and examples pull in third-party SDKs, so they default to OFF; the
|
||||||
|
# core library and its tests build on a bare toolchain.
|
||||||
|
option(PLOT_BUILD_TESTS "Build the plot test suite" ON)
|
||||||
|
option(PLOT_BUILD_EXAMPLES "Build the example programs" OFF)
|
||||||
|
option(PLOT_BUILD_GLFW "Build the GLFW adapter and demo" OFF)
|
||||||
|
option(PLOT_BUILD_IMGUI "Build the Dear ImGui adapter and demo" OFF)
|
||||||
|
option(PLOT_BUILD_QT6 "Build the Qt6 adapter and demo" OFF)
|
||||||
|
option(PLOT_BUILD_GTK4 "Build the GTK4 adapter and demo" OFF)
|
||||||
|
|
||||||
|
# Apply the project warning set to a first-party target. Warnings are never
|
||||||
|
# suppressed per file.
|
||||||
|
function(plot_apply_warnings target)
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(${target} PRIVATE /W4 /permissive-)
|
||||||
|
else()
|
||||||
|
target_compile_options(${target} PRIVATE
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Wpedantic
|
||||||
|
-Wconversion
|
||||||
|
-Wshadow)
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
@@ -247,8 +247,11 @@ Implement `from_range` and `log10_positive` in `src/math/transform.cpp`. `from_r
|
|||||||
```cmake
|
```cmake
|
||||||
add_executable(plot_tests main.cpp test_transform.cpp)
|
add_executable(plot_tests main.cpp test_transform.cpp)
|
||||||
target_link_libraries(plot_tests PRIVATE plot::core)
|
target_link_libraries(plot_tests PRIVATE plot::core)
|
||||||
add_test(NAME transform COMMAND plot_tests)
|
add_test(NAME plot_tests COMMAND plot_tests)
|
||||||
```
|
```
|
||||||
|
Every later task adds its new `test_*.cpp` to the `plot_tests` source list; there is
|
||||||
|
exactly one `add_test` entry, because the harness runs every registered case in one
|
||||||
|
binary. The top-level `CMakeLists.txt` calls `enable_testing()` when `PLOT_BUILD_TESTS`.
|
||||||
|
|
||||||
Top-level `CMakeLists.txt`: `cmake_minimum_required(VERSION 3.24)`, `project(plot VERSION 0.1.0 LANGUAGES CXX)`, include `cmake/plot_options.cmake`, declare the six `PLOT_BUILD_*` options from the plan header, build `plot_core` from `src/**/*.cpp` (explicit list), `add_library(plot::core ALIAS plot_core)`, `target_include_directories(plot_core PUBLIC include)`, `target_compile_features(plot_core PUBLIC cxx_std_20)`, `target_link_libraries(plot_core PRIVATE ${CMAKE_DL_LIBS})`, then `add_subdirectory(tests)` when `PLOT_BUILD_TESTS`.
|
Top-level `CMakeLists.txt`: `cmake_minimum_required(VERSION 3.24)`, `project(plot VERSION 0.1.0 LANGUAGES CXX)`, include `cmake/plot_options.cmake`, declare the six `PLOT_BUILD_*` options from the plan header, build `plot_core` from `src/**/*.cpp` (explicit list), `add_library(plot::core ALIAS plot_core)`, `target_include_directories(plot_core PUBLIC include)`, `target_compile_features(plot_core PUBLIC cxx_std_20)`, `target_link_libraries(plot_core PRIVATE ${CMAKE_DL_LIBS})`, then `add_subdirectory(tests)` when `PLOT_BUILD_TESTS`.
|
||||||
|
|
||||||
@@ -688,7 +691,7 @@ Implementation rules for `src/data/minmax_pyramid.cpp`:
|
|||||||
|
|
||||||
`StreamSeries`: `RingBuffer<f32>` of rounded-up capacity, atomic `produced_`, `f64 x0_`, `f64 dx_`; `push` is the only producer entry point and MUST NOT touch the pyramid; `acquire()` reads `produced_` once, grows the pyramid by the newly produced samples, and updates bounds. `x_bounds()` derives from the oldest retained index and `dx_`, so a wrap shifts the window forward.
|
`StreamSeries`: `RingBuffer<f32>` of rounded-up capacity, atomic `produced_`, `f64 x0_`, `f64 dx_`; `push` is the only producer entry point and MUST NOT touch the pyramid; `acquire()` reads `produced_` once, grows the pyramid by the newly produced samples, and updates bounds. `x_bounds()` derives from the oldest retained index and `dx_`, so a wrap shifts the window forward.
|
||||||
|
|
||||||
Register the new tests in `tests/CMakeLists.txt` with `add_test(NAME ring_buffer COMMAND plot_tests)` etc. (the harness runs all cases in one binary; the multiple `add_test` names just label subsets in ctest output).
|
Add `test_ring_buffer.cpp`, `test_minmax_pyramid.cpp` and `test_series.cpp` to the existing `plot_tests` target's source list in `tests/CMakeLists.txt`. Do not add new `add_test` entries — one `add_test(NAME plot_tests ...)` runs every registered case.
|
||||||
|
|
||||||
- [ ] **Step 4: Run tests to verify they pass**
|
- [ ] **Step 4: Run tests to verify they pass**
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
|
||||||
|
/// RGBA colour with components in [0,1].
|
||||||
|
struct Color {
|
||||||
|
f32 r = 0.0f;
|
||||||
|
f32 g = 0.0f;
|
||||||
|
f32 b = 0.0f;
|
||||||
|
f32 a = 1.0f;
|
||||||
|
|
||||||
|
/// Build from a packed 0xRRGGBB literal, with an optional alpha override.
|
||||||
|
static constexpr Color hex(u32 rgb, f32 alpha = 1.0f) noexcept {
|
||||||
|
constexpr f32 kInv255 = 1.0f / 255.0f;
|
||||||
|
return Color{static_cast<f32>((rgb >> 16) & 0xFFu) * kInv255,
|
||||||
|
static_cast<f32>((rgb >> 8) & 0xFFu) * kInv255,
|
||||||
|
static_cast<f32>(rgb & 0xFFu) * kInv255, alpha};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace plot
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
|
||||||
|
/// Axis-aligned rectangle in pixels, origin at the top-left.
|
||||||
|
struct Rect {
|
||||||
|
f32 x = 0.0f;
|
||||||
|
f32 y = 0.0f;
|
||||||
|
f32 w = 0.0f;
|
||||||
|
f32 h = 0.0f;
|
||||||
|
|
||||||
|
constexpr f32 right() const noexcept { return x + w; }
|
||||||
|
constexpr f32 bottom() const noexcept { return y + h; }
|
||||||
|
|
||||||
|
/// Half-open containment: the right/bottom edges are outside the rect.
|
||||||
|
constexpr bool contains(f32 px, f32 py) const noexcept {
|
||||||
|
return px >= x && px < right() && py >= y && py < bottom();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace plot
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
|
||||||
|
/// `std::span` re-exported so public headers and callers share one spelling.
|
||||||
|
template <class T, std::size_t Extent = std::dynamic_extent>
|
||||||
|
using Span = std::span<T, Extent>;
|
||||||
|
|
||||||
|
} // namespace plot
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
|
||||||
|
enum class ErrorCode {
|
||||||
|
Ok,
|
||||||
|
InvalidArgument,
|
||||||
|
OutOfMemory,
|
||||||
|
GLFailure,
|
||||||
|
Unsupported,
|
||||||
|
NotImplemented,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Never-null human-readable name of an error code.
|
||||||
|
const char* to_string(ErrorCode code) noexcept;
|
||||||
|
|
||||||
|
/// Result of a fallible operation. Falsy unless `code == ErrorCode::Ok`.
|
||||||
|
struct Status {
|
||||||
|
ErrorCode code = ErrorCode::Ok;
|
||||||
|
const char* message = "";
|
||||||
|
|
||||||
|
constexpr explicit operator bool() const noexcept { return code == ErrorCode::Ok; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace plot
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
|
||||||
|
using f32 = float;
|
||||||
|
using f64 = double;
|
||||||
|
using i32 = std::int32_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
using usize = std::size_t;
|
||||||
|
|
||||||
|
/// Sentinel for "no index", e.g. an unmatched cursor id.
|
||||||
|
constexpr u64 kInvalid = ~u64{0};
|
||||||
|
|
||||||
|
/// Closed numeric interval on one axis.
|
||||||
|
struct Range {
|
||||||
|
f64 min = 0.0;
|
||||||
|
f64 max = 1.0;
|
||||||
|
|
||||||
|
constexpr f64 span() const noexcept { return max - min; }
|
||||||
|
constexpr f64 clamp(f64 v) const noexcept { return v < min ? min : (v > max ? max : v); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace plot
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
namespace plot::math {
|
||||||
|
|
||||||
|
/// Column-major 3x3 matrix of `f32`, the layout OpenGL expects for `mat3`
|
||||||
|
/// uniforms.
|
||||||
|
struct Mat3 {
|
||||||
|
f32 m[9] = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
|
||||||
|
static constexpr Mat3 identity() noexcept { return Mat3{}; }
|
||||||
|
|
||||||
|
/// Column-major product `(*this) * rhs`.
|
||||||
|
constexpr Mat3 operator*(const Mat3& rhs) const noexcept {
|
||||||
|
Mat3 out{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
for (usize col = 0; col < 3; ++col) {
|
||||||
|
for (usize row = 0; row < 3; ++row) {
|
||||||
|
f32 acc = 0.0f;
|
||||||
|
for (usize k = 0; k < 3; ++k) acc += m[k * 3 + row] * rhs.m[col * 3 + k];
|
||||||
|
out.m[col * 3 + row] = acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace plot::math
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
using math::Mat3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cmath>
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
namespace plot::math {
|
||||||
|
|
||||||
|
/// Affine data->screen mapping: `screen = data * s + t`, applied after `log10`
|
||||||
|
/// when the corresponding log flag is set.
|
||||||
|
struct Transform {
|
||||||
|
f64 sx = 1.0, tx = 0.0;
|
||||||
|
f64 sy = 1.0, ty = 0.0;
|
||||||
|
bool log_x = false, log_y = false;
|
||||||
|
|
||||||
|
f64 to_screen_x(f64 x) const noexcept {
|
||||||
|
const f64 v = log_x ? log10_positive(x) : x;
|
||||||
|
return v * sx + tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 to_screen_y(f64 y) const noexcept {
|
||||||
|
const f64 v = log_y ? log10_positive(y) : y;
|
||||||
|
return v * sy + ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 to_data_x(f64 screen_x) const noexcept {
|
||||||
|
const f64 v = (screen_x - tx) / sx;
|
||||||
|
return log_x ? std::pow(10.0, v) : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 to_data_y(f64 screen_y) const noexcept {
|
||||||
|
const f64 v = (screen_y - ty) / sy;
|
||||||
|
return log_y ? std::pow(10.0, v) : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set scale/offset so `x` and `y` map onto `[px0,px1]` and `[py0,py1]`.
|
||||||
|
/// A degenerate range is widened by +/-0.5 in the (possibly logarithmic)
|
||||||
|
/// coordinate so the resulting mapping always stays invertible.
|
||||||
|
static Transform from_range(Range x, Range y, f64 px0, f64 px1, f64 py0, f64 py1, bool lx,
|
||||||
|
bool ly) noexcept;
|
||||||
|
|
||||||
|
/// `log10` of `v`, with `v` clamped to at least 1e-300 first.
|
||||||
|
static f64 log10_positive(f64 v) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace plot::math
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
using math::Transform;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cmath>
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
namespace plot::math {
|
||||||
|
|
||||||
|
/// 2D vector of `f32`, used for pixel offsets and normalised device coords.
|
||||||
|
struct Vec2 {
|
||||||
|
f32 x = 0.0f;
|
||||||
|
f32 y = 0.0f;
|
||||||
|
|
||||||
|
constexpr Vec2 operator+(const Vec2& o) const noexcept { return {x + o.x, y + o.y}; }
|
||||||
|
constexpr Vec2 operator-(const Vec2& o) const noexcept { return {x - o.x, y - o.y}; }
|
||||||
|
constexpr Vec2 operator*(f32 s) const noexcept { return {x * s, y * s}; }
|
||||||
|
constexpr Vec2 operator/(f32 s) const noexcept { return {x / s, y / s}; }
|
||||||
|
|
||||||
|
constexpr f32 dot(const Vec2& o) const noexcept { return x * o.x + y * o.y; }
|
||||||
|
|
||||||
|
f32 length() const noexcept { return std::sqrt(x * x + y * y); }
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Vec2 operator*(f32 s, const Vec2& v) noexcept { return v * s; }
|
||||||
|
|
||||||
|
} // namespace plot::math
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
using math::Vec2;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// Umbrella header: pulls in every public component of the plot library.
|
||||||
|
/// Later layers (gl, data, render, plot, input) are added here as they land.
|
||||||
|
|
||||||
|
#include <plot/core/color.hpp>
|
||||||
|
#include <plot/core/rect.hpp>
|
||||||
|
#include <plot/core/span.hpp>
|
||||||
|
#include <plot/core/status.hpp>
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
#include <plot/math/mat3.hpp>
|
||||||
|
#include <plot/math/transform.hpp>
|
||||||
|
#include <plot/math/vec2.hpp>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include <plot/core/status.hpp>
|
||||||
|
|
||||||
|
namespace plot {
|
||||||
|
|
||||||
|
const char* to_string(ErrorCode code) noexcept {
|
||||||
|
switch (code) {
|
||||||
|
case ErrorCode::Ok:
|
||||||
|
return "Ok";
|
||||||
|
case ErrorCode::InvalidArgument:
|
||||||
|
return "InvalidArgument";
|
||||||
|
case ErrorCode::OutOfMemory:
|
||||||
|
return "OutOfMemory";
|
||||||
|
case ErrorCode::GLFailure:
|
||||||
|
return "GLFailure";
|
||||||
|
case ErrorCode::Unsupported:
|
||||||
|
return "Unsupported";
|
||||||
|
case ErrorCode::NotImplemented:
|
||||||
|
return "NotImplemented";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace plot
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include <plot/math/transform.hpp>
|
||||||
|
|
||||||
|
namespace plot::math {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/// Resolve the endpoints of one axis in transform space, widening a degenerate
|
||||||
|
/// range by +/-0.5 so the mapping stays invertible.
|
||||||
|
void axis_ends(Range r, bool log, f64& lo, f64& hi) noexcept {
|
||||||
|
lo = log ? Transform::log10_positive(r.min) : r.min;
|
||||||
|
hi = log ? Transform::log10_positive(r.max) : r.max;
|
||||||
|
if (!(hi > lo)) {
|
||||||
|
lo -= 0.5;
|
||||||
|
hi += 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
f64 Transform::log10_positive(f64 v) noexcept {
|
||||||
|
constexpr f64 kMin = 1e-300;
|
||||||
|
return std::log10(v < kMin ? kMin : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform Transform::from_range(Range x, Range y, f64 px0, f64 px1, f64 py0, f64 py1, bool lx,
|
||||||
|
bool ly) noexcept {
|
||||||
|
Transform t;
|
||||||
|
t.log_x = lx;
|
||||||
|
t.log_y = ly;
|
||||||
|
|
||||||
|
f64 x_lo = 0.0, x_hi = 0.0;
|
||||||
|
axis_ends(x, lx, x_lo, x_hi);
|
||||||
|
t.sx = (px1 - px0) / (x_hi - x_lo);
|
||||||
|
t.tx = px0 - x_lo * t.sx;
|
||||||
|
|
||||||
|
f64 y_lo = 0.0, y_hi = 0.0;
|
||||||
|
axis_ends(y, ly, y_lo, y_hi);
|
||||||
|
t.sy = (py1 - py0) / (y_hi - y_lo);
|
||||||
|
t.ty = py0 - y_lo * t.sy;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace plot::math
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
add_executable(plot_tests
|
||||||
|
main.cpp
|
||||||
|
test_transform.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(plot_tests PRIVATE plot::core)
|
||||||
|
plot_apply_warnings(plot_tests)
|
||||||
|
|
||||||
|
add_test(NAME plot_tests COMMAND plot_tests)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cmath>
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <plot/core/types.hpp>
|
||||||
|
|
||||||
|
using plot::f32;
|
||||||
|
using plot::f64;
|
||||||
|
using plot::u32;
|
||||||
|
using plot::u64;
|
||||||
|
|
||||||
|
namespace plottest {
|
||||||
|
|
||||||
|
struct Case {
|
||||||
|
const char* name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::vector<Case>& cases() {
|
||||||
|
static std::vector<Case> c;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Reg {
|
||||||
|
Reg(const char* n, std::function<void()> f) { cases().push_back({n, std::move(f)}); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Failure {
|
||||||
|
std::string msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int run_all() {
|
||||||
|
int failed = 0;
|
||||||
|
for (auto& c : cases()) {
|
||||||
|
try {
|
||||||
|
c.fn();
|
||||||
|
std::printf("[ ok ] %s\n", c.name);
|
||||||
|
} catch (const Failure& f) {
|
||||||
|
std::printf("[FAIL] %s: %s\n", c.name, f.msg.c_str());
|
||||||
|
++failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf("%zu cases, %d failed\n", cases().size(), failed);
|
||||||
|
return failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace plottest
|
||||||
|
|
||||||
|
#define PLOT_TEST(name) \
|
||||||
|
static void name(); \
|
||||||
|
static ::plottest::Reg reg_##name(#name, name); \
|
||||||
|
static void name()
|
||||||
|
|
||||||
|
#define PLOT_CHECK(cond) \
|
||||||
|
do { \
|
||||||
|
if (!(cond)) \
|
||||||
|
throw ::plottest::Failure{std::string(__FILE__) + ":" + std::to_string(__LINE__) + \
|
||||||
|
" CHECK(" #cond ")"}; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define PLOT_CHECK_NEAR(a, b, eps) \
|
||||||
|
do { \
|
||||||
|
const double _a = (a), _b = (b); \
|
||||||
|
if (!(std::fabs(_a - _b) <= (eps))) \
|
||||||
|
throw ::plottest::Failure{std::string(__FILE__) + ":" + std::to_string(__LINE__) + \
|
||||||
|
" CHECK_NEAR(" #a ", " #b ") got " + std::to_string(_a) + \
|
||||||
|
" vs " + std::to_string(_b)}; \
|
||||||
|
} while (0)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#include "harness.hpp"
|
||||||
|
|
||||||
|
int main() { return plottest::run_all(); }
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#include "harness.hpp"
|
||||||
|
|
||||||
|
#include <plot/math/transform.hpp>
|
||||||
|
|
||||||
|
using plot::Transform;
|
||||||
|
|
||||||
|
PLOT_TEST(linear_round_trip) {
|
||||||
|
Transform t{2.0, 10.0, 3.0, -1.0, false, false};
|
||||||
|
PLOT_CHECK_NEAR(t.to_screen_x(0.0), 10.0, 1e-12);
|
||||||
|
PLOT_CHECK_NEAR(t.to_screen_x(5.0), 20.0, 1e-12);
|
||||||
|
PLOT_CHECK_NEAR(t.to_data_x(20.0), 5.0, 1e-12);
|
||||||
|
PLOT_CHECK_NEAR(t.to_data_y(t.to_screen_y(7.5)), 7.5, 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
PLOT_TEST(log_x_round_trip) {
|
||||||
|
Transform t{100.0, 0.0, 1.0, 0.0, true, false};
|
||||||
|
PLOT_CHECK_NEAR(t.to_screen_x(1.0), 0.0, 1e-12);
|
||||||
|
PLOT_CHECK_NEAR(t.to_screen_x(10.0), 100.0, 1e-12);
|
||||||
|
PLOT_CHECK_NEAR(t.to_data_x(100.0), 10.0, 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
PLOT_TEST(zoom_anchor_is_invariant) {
|
||||||
|
// Anchoring a zoom at a data coordinate must leave that coordinate's screen
|
||||||
|
// position unchanged.
|
||||||
|
Transform t{1.0, 0.0, 1.0, 0.0, false, false};
|
||||||
|
const double anchor_data = 3.25;
|
||||||
|
const double before = t.to_screen_x(anchor_data);
|
||||||
|
t.sx *= 4.0;
|
||||||
|
t.tx = before - anchor_data * t.sx;
|
||||||
|
PLOT_CHECK_NEAR(t.to_screen_x(anchor_data), before, 1e-12);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user