Files
Renderive/Core/primitive/Curve_Utils_p.h
T
2026-08-02 14:20:27 +08:00

125 lines
7.7 KiB
C++

#pragma once
#include <algorithm>
#include <array>
#include <cmath>
#include <iterator>
#include <limits>
#include <memory_resource>
#include <span>
#include "../Axis/Abs_Axis_p.h"
#include "../base/Frame_Memory.h"
#include "../plottable/Interpolation_p.h"
#include "Curve.h"
namespace renderive {
struct Pixel_Envelope_Bucket {
bool has_samples = false;
int first_index = -1;
int last_index = -1;
int minimum_index = -1;
int maximum_index = -1;
double minimum_value = std::numeric_limits<double>::max();
double maximum_value = std::numeric_limits<double>::lowest();
};
class Curve_Utils {
public:
template <typename Point_At>
static std::pmr::vector<PointF> create_points(int count, Point_At point_at, std::pmr::memory_resource* resource = frame_memory_resource()) {
std::pmr::vector<PointF> points(resource);
points.reserve(count);
for (int i = 0; i < count; ++i)
points.push_back(point_at(i));
return points;
}
template <typename Value_At>
static std::pmr::vector<PointF> build_resampled_points(Abs_Axis* domain_axis, Abs_Axis* value_axis, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at) {
return build_sample_line_points(Axis_Render_Access::mapping(domain_axis, value_axis), sample_domain_range, source_sample_count, available_sample_count, visible_range_only, mode, value_at, frame_memory_resource());
}
template <typename Value_At>
static std::pmr::vector<PointF> build_resampled_points(const Axis_Mapping_2D& mapping, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) {
return build_sample_line_points(mapping, sample_domain_range, source_sample_count, available_sample_count, visible_range_only, mode, value_at, resource);
}
template <typename Value_At>
static std::pmr::vector<PointF> build_pixel_envelope_points(const Axis_Mapping_2D& mapping, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) {
Visible_Sample_Window window = compute_visible_sample_window(mapping.domain, sample_domain_range, source_sample_count, available_sample_count, visible_range_only);
if (!window)
return {};
double first_index = std::clamp(std::min(window.first_index, window.last_index), 0.0, static_cast<double>(available_sample_count - 1));
double last_index = std::clamp(std::max(window.first_index, window.last_index), first_index, static_cast<double>(available_sample_count - 1));
int first_source = std::clamp(static_cast<int>(std::ceil(first_index)), 0, available_sample_count - 1);
int last_source = std::clamp(static_cast<int>(std::floor(last_index)), first_source, available_sample_count - 1);
double first_pixel = mapping.domain.coord_to_pixel(source_index_to_coordinate(sample_domain_range, source_sample_count, first_index));
double last_pixel = mapping.domain.coord_to_pixel(source_index_to_coordinate(sample_domain_range, source_sample_count, last_index));
int pixel_floor_min = static_cast<int>(std::floor(std::min(first_pixel, last_pixel)));
int pixel_floor_max = static_cast<int>(std::floor(std::max(first_pixel, last_pixel)));
int bucket_count = std::max(1, pixel_floor_max - pixel_floor_min + 1);
bucket_count = std::min(bucket_count, mapping.domain.pixel_size() + 1);
std::pmr::vector<Pixel_Envelope_Bucket> buckets(bucket_count, resource);
for (int i = first_source; i <= last_source; ++i) {
double value = value_at(i);
if (!std::isfinite(value))
continue;
double coord = source_index_to_coordinate(sample_domain_range, source_sample_count, i);
double pixel = mapping.domain.coord_to_pixel(coord);
int bucket_index = std::clamp(static_cast<int>(std::floor(pixel)) - pixel_floor_min, 0, bucket_count - 1);
Pixel_Envelope_Bucket& bucket = buckets[bucket_index];
if (!bucket.has_samples) {
bucket.has_samples = true;
bucket.first_index = i;
bucket.minimum_index = i;
bucket.maximum_index = i;
bucket.minimum_value = value;
bucket.maximum_value = value;
}
bucket.last_index = i;
if (value < bucket.minimum_value) {
bucket.minimum_value = value;
bucket.minimum_index = i;
}
if (value > bucket.maximum_value) {
bucket.maximum_value = value;
bucket.maximum_index = i;
}
}
std::pmr::vector<PointF> points(resource);
points.reserve(bucket_count * 4 + 2);
double last_appended_index = -std::numeric_limits<double>::infinity();
auto append_index = [&](double index) {
if (std::abs(index - last_appended_index) <= 1e-9)
return;
double value = interpolate_sample_value(index, available_sample_count, Line_Interpolation_Mode::Linear_Value, value_at);
if (!std::isfinite(value))
return;
PointF point = mapping.map(source_index_to_coordinate(sample_domain_range, source_sample_count, index), value);
if (!points.empty() && points.back().x == point.x && points.back().y == point.y)
return;
points.push_back(point);
last_appended_index = index;
};
append_index(first_index);
int first_bucket = std::clamp(static_cast<int>(std::floor(first_pixel)) - pixel_floor_min, 0, bucket_count - 1);
int last_bucket = std::clamp(static_cast<int>(std::floor(last_pixel)) - pixel_floor_min, 0, bucket_count - 1);
bool bucket_forward = first_bucket <= last_bucket;
for (int b = bucket_forward ? 0 : bucket_count - 1; bucket_forward ? b < bucket_count : b >= 0; bucket_forward ? ++b : --b) {
const Pixel_Envelope_Bucket& bucket = buckets[b];
if (!bucket.has_samples)
continue;
std::array<int, 4> indexes{bucket.first_index, bucket.minimum_index, bucket.maximum_index, bucket.last_index};
std::sort(indexes.begin(), indexes.end());
auto end = std::unique(indexes.begin(), indexes.end());
for (auto it = indexes.begin(); it != end; ++it)
append_index(static_cast<double>(*it));
}
append_index(last_index);
return points;
}
template <typename Value_At>
static std::pmr::vector<PointF> build_pixel_envelope_points(Abs_Axis* domain_axis, Abs_Axis* value_axis, const Range& sample_domain_range, int source_sample_count, int available_sample_count, bool visible_range_only, Value_At value_at) {
return build_pixel_envelope_points(Axis_Render_Access::mapping(domain_axis, value_axis), sample_domain_range, source_sample_count, available_sample_count, visible_range_only, value_at, frame_memory_resource());
}
static void draw_polyline(Canvas* painter, std::span<const PointF> points, const Pen& pen);
static void draw_fill_to_value(Canvas* painter, std::span<const PointF> points, const Axis_Mapping_2D& mapping, double baseline_value, const Brush& brush);
private:
static void append_envelope_point(std::pmr::vector<PointF>& points, const Axis_Mapping_2D& mapping, const Range& sample_domain_range, int source_sample_count, int index, double value);
};
} // namespace renderive