114 lines
5.1 KiB
C++
114 lines
5.1 KiB
C++
#pragma once
|
|
#include "../axis/Axis.h"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <optional>
|
|
namespace renderive::detail {
|
|
inline bool axes_are_orthogonal(const Axis_Transform& first,
|
|
const Axis_Transform& second) noexcept {
|
|
return first.orientation != second.orientation;
|
|
}
|
|
|
|
inline PointF mapped_point(const Axis_Transform& first,
|
|
double first_coordinate,
|
|
const Axis_Transform& second,
|
|
double second_coordinate) noexcept {
|
|
if (!axes_are_orthogonal(first, second))
|
|
return {};
|
|
const double first_pixel = first.coord_to_pixel(first_coordinate);
|
|
const double second_pixel = second.coord_to_pixel(second_coordinate);
|
|
return first.orientation == Orientation::Horizontal
|
|
? PointF{first_pixel, second_pixel}
|
|
: PointF{second_pixel, first_pixel};
|
|
}
|
|
|
|
inline RectF mapped_rect(const Axis_Transform& first,
|
|
const Axis_Transform& second,
|
|
Range first_range,
|
|
Range second_range) noexcept {
|
|
if (!axes_are_orthogonal(first, second))
|
|
return {};
|
|
const PointF origin = mapped_point(first, first_range.origin, second, second_range.origin);
|
|
const PointF target = mapped_point(first, first_range.target, second, second_range.target);
|
|
return {std::min(origin.x, target.x), std::min(origin.y, target.y),
|
|
std::abs(target.x - origin.x), std::abs(target.y - origin.y)};
|
|
}
|
|
|
|
struct Axis_Raster_Layout {
|
|
int width{};
|
|
int height{};
|
|
RectF target;
|
|
bool first_reversed{};
|
|
bool second_reversed{};
|
|
bool first_is_horizontal{};
|
|
|
|
[[nodiscard]] bool valid() const noexcept {
|
|
return width > 0 && height > 0 && !target.empty();
|
|
}
|
|
|
|
[[nodiscard]] std::size_t index(int first, int second,
|
|
int first_count, int second_count) const noexcept {
|
|
if (first_reversed)
|
|
first = first_count - 1 - first;
|
|
if (second_reversed)
|
|
second = second_count - 1 - second;
|
|
const int x = first_is_horizontal ? first : second;
|
|
const int y = first_is_horizontal ? second : first;
|
|
return static_cast<std::size_t>(y) * width + x;
|
|
}
|
|
};
|
|
|
|
inline Axis_Raster_Layout axis_raster_layout(const Axis_Transform& first,
|
|
const Axis_Transform& second,
|
|
Range first_range,
|
|
Range second_range,
|
|
int first_count,
|
|
int second_count) noexcept {
|
|
if (!axes_are_orthogonal(first, second) || first_count <= 0 || second_count <= 0)
|
|
return {};
|
|
const bool first_horizontal = first.orientation == Orientation::Horizontal;
|
|
return {
|
|
first_horizontal ? first_count : second_count,
|
|
first_horizontal ? second_count : first_count,
|
|
mapped_rect(first, second, first_range, second_range),
|
|
first.coord_to_pixel(first_range.origin) > first.coord_to_pixel(first_range.target),
|
|
second.coord_to_pixel(second_range.origin) > second.coord_to_pixel(second_range.target),
|
|
first_horizontal
|
|
};
|
|
}
|
|
inline double normalized_value(double value, Range range) {
|
|
if(range.length() == 0.0)
|
|
return 0.0;
|
|
return std::clamp((value - range.origin) / range.length(), 0.0, 1.0);
|
|
}
|
|
struct Frequency_Columns {
|
|
int first{};
|
|
int last{};
|
|
Range range;
|
|
};
|
|
inline std::optional<Frequency_Columns> frequency_columns(Range data_range, Range visible_range, int column_count, bool visible_only) {
|
|
if(column_count <= 0)
|
|
return std::nullopt;
|
|
if(!visible_only || column_count == 1 || data_range.length() == 0.0)
|
|
return Frequency_Columns{0, column_count - 1, data_range};
|
|
const auto [data_low, data_high] = std::minmax(data_range.origin, data_range.target);
|
|
const auto [visible_low, visible_high] = std::minmax(visible_range.origin, visible_range.target);
|
|
const double clipped_low = std::max(data_low, visible_low);
|
|
const double clipped_high = std::min(data_high, visible_high);
|
|
if(clipped_low > clipped_high)
|
|
return std::nullopt;
|
|
const auto position = [data_range, column_count](double coordinate) { return (coordinate - data_range.origin) / data_range.length() * static_cast<double>(column_count - 1); };
|
|
const auto [position_low, position_high] = std::minmax(position(clipped_low), position(clipped_high));
|
|
int first = std::clamp(static_cast<int>(std::floor(position_low)), 0, column_count - 1);
|
|
int last = std::clamp(static_cast<int>(std::ceil(position_high)), first, column_count - 1);
|
|
if(first == last) {
|
|
if(last + 1 < column_count)
|
|
++last;
|
|
else if(first > 0)
|
|
--first;
|
|
}
|
|
const auto coordinate = [data_range, column_count](int index) { return data_range.origin + data_range.length() * static_cast<double>(index) / static_cast<double>(column_count - 1); };
|
|
return Frequency_Columns{first, last, {coordinate(first), coordinate(last)}};
|
|
}
|
|
}
|