Files
Renderive/Core/primitive/Curve.cpp
T
2026-08-02 19:26:50 +08:00

695 lines
32 KiB
C++

#include "Curve.h"
#include "Curve_Utils_p.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <memory>
#include <memory_resource>
#include <optional>
#include <rigtorp/MPMCQueue.h>
#include <system_error>
#include <variant>
#include "../base/Memory.h"
#include "../base/global.h"
#include "../render/Canvas.h"
#include "../render/Frame_Prepare_Subflow.h"
#include "../render/Render_Frame_Snapshot.h"
namespace renderive {
enum class Curve_Input_Mode : std::uint8_t {
None,
Samples,
Points
};
struct Curve_Input_Data : Input_Data {};
struct Curve_Render_State : Render_State, Curve_Prop {};
struct Curve_Sample_Source {
Curve_Sample_Source() : values(memory_resource(Memory_Domain::Curve)) {}
explicit Curve_Sample_Source(std::span<const double> input) : values(memory_resource(Memory_Domain::Curve)) {
values.assign(input.begin(), input.end());
}
explicit Curve_Sample_Source(std::pmr::vector<double>&& input) : values(std::move(input)) {}
std::pmr::vector<double> values;
};
struct Curve_Point_Source {
Curve_Point_Source() : values(memory_resource(Memory_Domain::Curve)) {}
explicit Curve_Point_Source(std::span<const PointF> input) : values(memory_resource(Memory_Domain::Curve)) {
values.assign(input.begin(), input.end());
}
explicit Curve_Point_Source(std::pmr::vector<PointF>&& input) : values(std::move(input)) {}
std::pmr::vector<PointF> values;
};
using Curve_Source = std::variant<std::monostate, Curve_Sample_Source, Curve_Point_Source>;
static Curve_Input_Mode curve_source_mode(const Curve_Source& source) {
if (std::holds_alternative<Curve_Sample_Source>(source))
return Curve_Input_Mode::Samples;
if (std::holds_alternative<Curve_Point_Source>(source))
return Curve_Input_Mode::Points;
return Curve_Input_Mode::None;
}
static std::size_t curve_sample_count(const Curve_Source& source) {
const auto* samples = std::get_if<Curve_Sample_Source>(&source);
return samples ? samples->values.size() : 0u;
}
static std::size_t curve_point_count(const Curve_Source& source) {
const auto* points = std::get_if<Curve_Point_Source>(&source);
return points ? points->values.size() : 0u;
}
struct Curve_Input_Block {
std::shared_ptr<const Curve_Source> source = std::make_shared<Curve_Source>();
std::shared_ptr<Update_State> completion_state;
static std::shared_ptr<Curve_Input_Block> samples_block(std::span<const double> values, std::shared_ptr<Update_State> state = {}) {
auto block = std::make_shared<Curve_Input_Block>();
block->source = std::make_shared<Curve_Source>(Curve_Sample_Source(values));
block->completion_state = std::move(state);
return block;
}
static std::shared_ptr<Curve_Input_Block> samples_block(std::pmr::vector<double>&& values, std::shared_ptr<Update_State> state = {}) {
auto block = std::make_shared<Curve_Input_Block>();
block->source = std::make_shared<Curve_Source>(Curve_Sample_Source(std::move(values)));
block->completion_state = std::move(state);
return block;
}
static std::shared_ptr<Curve_Input_Block> points_block(std::span<const PointF> values, std::shared_ptr<Update_State> state = {}) {
auto block = std::make_shared<Curve_Input_Block>();
block->source = std::make_shared<Curve_Source>(Curve_Point_Source(values));
block->completion_state = std::move(state);
return block;
}
static std::shared_ptr<Curve_Input_Block> points_block(std::pmr::vector<PointF>&& values, std::shared_ptr<Update_State> state = {}) {
auto block = std::make_shared<Curve_Input_Block>();
block->source = std::make_shared<Curve_Source>(Curve_Point_Source(std::move(values)));
block->completion_state = std::move(state);
return block;
}
};
struct Curve_Axis_Geometry_Key {
Orientation orientation = Orientation::Horizontal;
Range coord_range{0.0, 1.0};
Range pixel_range{0.0, 1.0};
bool operator==(const Curve_Axis_Geometry_Key& other) const {
return orientation == other.orientation && coord_range == other.coord_range && pixel_range == other.pixel_range;
}
};
struct Curve_Geometry_Key {
std::uint64_t input_generation{};
std::uint64_t state_version{};
Curve_Input_Mode mode = Curve_Input_Mode::None;
Line_Sampling_Strategy sample_mode = Line_Sampling_Strategy::Pixel_Min_Max_Envelope;
Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Value;
bool visible_range_only{};
Range data_range{0.0, 1.0};
std::size_t sample_count{};
std::size_t point_count{};
Curve_Axis_Geometry_Key domain;
Curve_Axis_Geometry_Key value;
bool operator==(const Curve_Geometry_Key& other) const {
return input_generation == other.input_generation &&
state_version == other.state_version &&
mode == other.mode &&
sample_mode == other.sample_mode &&
interpolation_mode == other.interpolation_mode &&
visible_range_only == other.visible_range_only &&
data_range == other.data_range &&
sample_count == other.sample_count &&
point_count == other.point_count &&
domain == other.domain &&
value == other.value;
}
};
struct Curve_Geometry_Result {
struct Segment {
explicit Segment(std::pmr::memory_resource* resource = memory_resource(Memory_Domain::Curve)) : points(resource) {}
std::pmr::vector<PointF> points;
};
Curve_Geometry_Result() : segments(memory_resource(Memory_Domain::Curve)), update_states(memory_resource(Memory_Domain::Update_Completion)) {}
Axis_Mapping_2D mapping;
std::pmr::vector<Segment> segments;
std::pmr::vector<std::shared_ptr<Update_State>> update_states;
};
struct Curve_Geometry_Request {
Curve_Geometry_Request() : update_states(memory_resource(Memory_Domain::Update_Completion)) {}
Curve_Input_Mode mode = Curve_Input_Mode::None;
Axis_Mapping_2D mapping;
Range data_range{0.0, 1.0};
bool visible_range_only{};
Line_Interpolation_Mode interpolation_mode = Line_Interpolation_Mode::Linear_Value;
Line_Sampling_Strategy sample_mode = Line_Sampling_Strategy::Pixel_Min_Max_Envelope;
std::shared_ptr<const Curve_Source> source;
std::pmr::vector<std::shared_ptr<Update_State>> update_states;
};
struct Curve_Geometry_Partition {
std::size_t source_begin{};
std::size_t source_end{};
int pixel_begin{};
int pixel_end{};
bool joins_previous{};
};
struct Curve_Geometry_Partial_Result {
Curve_Geometry_Partial_Result()
: segments(memory_resource(Memory_Domain::Curve)) {}
std::pmr::vector<Curve_Geometry_Result::Segment> segments;
std::size_t source_begin{};
std::size_t source_end{};
bool begins_with_continuous_point{};
bool ends_with_continuous_point{};
bool joins_previous{};
};
struct Curve_Geometry_Job {
explicit Curve_Geometry_Job(std::shared_ptr<const Curve_Geometry_Request> request, Curve_Geometry_Key key)
: request(std::move(request)),
key(std::move(key)),
update_states(memory_resource(Memory_Domain::Update_Completion)) {}
~Curve_Geometry_Job() {
for (auto& state : update_states) {
if (state)
state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
}
update_states.clear();
}
std::shared_ptr<const Curve_Geometry_Request> request;
Curve_Geometry_Key key;
std::vector<Curve_Geometry_Partition> partitions;
std::vector<Curve_Geometry_Partial_Result> results;
std::pmr::vector<std::shared_ptr<Update_State>> update_states;
};
static constexpr std::size_t Min_Curve_Items_Per_Task = 4096;
static constexpr std::size_t Max_Curve_Partition_Count = 8;
static Curve_Axis_Geometry_Key curve_axis_key(const Axis_Frame_Snapshot& axis) {
return {axis.orientation, axis.coord_range, axis.pixel_range};
}
static void complete_curve_result_states(Curve_Geometry_Result& result, std::error_code error, Update_Outcome outcome) {
for (const auto& state : result.update_states) {
if (state)
state->complete_all(error, outcome);
}
result.update_states.clear();
}
static bool finite_point(const PointF& point) {
return std::isfinite(point.x) && std::isfinite(point.y);
}
static void append_curve_point_segment(Curve_Geometry_Result& result, Curve_Geometry_Result::Segment*& segment, const Axis_Mapping_2D& mapping, const PointF& point) {
if (!finite_point(point)) {
segment = nullptr;
return;
}
PointF mapped = mapping.map(point);
if (!finite_point(mapped)) {
segment = nullptr;
return;
}
if (!segment) {
result.segments.emplace_back(memory_resource(Memory_Domain::Curve));
segment = &result.segments.back();
}
if (!segment->points.empty() && segment->points.back().x == mapped.x && segment->points.back().y == mapped.y)
return;
segment->points.push_back(mapped);
}
static void append_curve_sample_run(Curve_Geometry_Result& result, const Curve_Geometry_Request& request, const Curve_Sample_Source& samples, int run_start, int run_end) {
if (run_start > run_end)
return;
auto coord_at = [&](int index) {
return source_index_to_coordinate(request.data_range, static_cast<int>(samples.values.size()), index);
};
if (run_start == run_end) {
double coord = coord_at(run_start);
if (request.visible_range_only && !request.mapping.domain.coord_range.contains(coord))
return;
PointF point = request.mapping.map(coord, samples.values[static_cast<std::size_t>(run_start)]);
if (!finite_point(point))
return;
result.segments.emplace_back(memory_resource(Memory_Domain::Curve));
result.segments.back().points.push_back(point);
return;
}
int run_count = run_end - run_start + 1;
Range run_range{coord_at(run_start), coord_at(run_end)};
auto value_at = [&samples, run_start](int i) {
return samples.values[static_cast<std::size_t>(run_start + i)];
};
std::pmr::vector<PointF> points(memory_resource(Memory_Domain::Curve));
if (request.sample_mode == Line_Sampling_Strategy::Pixel_Min_Max_Envelope) {
points = Curve_Utils::build_pixel_envelope_points(request.mapping, run_range, run_count, run_count, request.visible_range_only, value_at, memory_resource(Memory_Domain::Curve));
}
else {
points = Curve_Utils::build_resampled_points(request.mapping, run_range, run_count, run_count, request.visible_range_only, request.interpolation_mode, value_at, memory_resource(Memory_Domain::Curve));
}
if (points.empty())
return;
result.segments.emplace_back(memory_resource(Memory_Domain::Curve));
result.segments.back().points = std::move(points);
}
std::size_t resolve_curve_partition_count(std::size_t source_count, std::size_t worker_count) {
if (source_count == 0)
return 0;
if (worker_count <= 1 || source_count < Min_Curve_Items_Per_Task)
return 1;
std::size_t by_size = (source_count + Min_Curve_Items_Per_Task - 1) / Min_Curve_Items_Per_Task;
return std::max<std::size_t>(1, std::min({worker_count, by_size, Max_Curve_Partition_Count}));
}
static std::size_t curve_request_source_count(const Curve_Geometry_Request& request) {
if (!request.source)
return 0;
if (const auto* data_points = std::get_if<Curve_Point_Source>(request.source.get()))
return data_points->values.size();
if (const auto* samples = std::get_if<Curve_Sample_Source>(request.source.get()))
return samples->values.size();
return 0;
}
static bool curve_sample_valid(const Curve_Geometry_Request& request, std::size_t index) {
if (!request.source)
return false;
if (const auto* data_points = std::get_if<Curve_Point_Source>(request.source.get())) {
if (index >= data_points->values.size())
return false;
PointF point = data_points->values[index];
return finite_point(point) && finite_point(request.mapping.map(point));
}
if (const auto* samples = std::get_if<Curve_Sample_Source>(request.source.get())) {
if (index >= samples->values.size())
return false;
double value = samples->values[index];
if (!std::isfinite(value))
return false;
double coord = source_index_to_coordinate(request.data_range, static_cast<int>(samples->values.size()), static_cast<double>(index));
return finite_point(request.mapping.map(coord, value));
}
return false;
}
static std::shared_ptr<Curve_Geometry_Job> create_curve_geometry_job(std::shared_ptr<Curve_Geometry_Request> request, Curve_Geometry_Key key, std::size_t worker_count) {
if (!request)
return {};
std::size_t source_count = curve_request_source_count(*request);
std::size_t partition_count = resolve_curve_partition_count(source_count, worker_count);
std::pmr::vector<std::shared_ptr<Update_State>> update_states = std::move(request->update_states);
auto job = std::make_shared<Curve_Geometry_Job>(
std::shared_ptr<const Curve_Geometry_Request>(std::move(request)),
std::move(key));
job->update_states = std::move(update_states);
job->partitions.reserve(partition_count);
job->results.resize(partition_count);
for (std::size_t i = 0; i < partition_count; ++i) {
std::size_t begin = source_count * i / partition_count;
std::size_t end = source_count * (i + 1) / partition_count;
Curve_Geometry_Partition partition;
partition.source_begin = begin;
partition.source_end = end;
partition.joins_previous = begin > 0 && curve_sample_valid(*job->request, begin - 1) && curve_sample_valid(*job->request, begin);
job->partitions.push_back(partition);
}
return job;
}
static void build_curve_partition(Curve_Geometry_Job& job, std::size_t partition_index) {
if (!job.request || partition_index >= job.partitions.size() || partition_index >= job.results.size())
return;
const Curve_Geometry_Request& request = *job.request;
const Curve_Geometry_Partition& partition = job.partitions[partition_index];
Curve_Geometry_Partial_Result partial;
partial.source_begin = partition.source_begin;
partial.source_end = partition.source_end;
partial.joins_previous = partition.joins_previous;
Curve_Geometry_Result result;
result.mapping = request.mapping;
if (!request.source || partition.source_begin >= partition.source_end) {
job.results[partition_index] = std::move(partial);
return;
}
if (const auto* data_points = std::get_if<Curve_Point_Source>(request.source.get())) {
result.segments.reserve(partition.source_end - partition.source_begin);
Curve_Geometry_Result::Segment* segment = nullptr;
for (std::size_t i = partition.source_begin; i < partition.source_end && i < data_points->values.size(); ++i)
append_curve_point_segment(result, segment, request.mapping, data_points->values[i]);
}
else if (const auto* samples = std::get_if<Curve_Sample_Source>(request.source.get())) {
if (samples->values.size() >= 2 && request.data_range.length() != 0.0) {
std::size_t compute_begin = partition.source_begin;
if (partition.joins_previous && compute_begin > 0)
--compute_begin;
int run_start = -1;
std::size_t end = std::min(partition.source_end, samples->values.size());
for (std::size_t i = compute_begin; i < end; ++i) {
double value = samples->values[i];
if (std::isfinite(value)) {
if (run_start < 0)
run_start = static_cast<int>(i);
continue;
}
if (run_start >= 0)
append_curve_sample_run(result, request, *samples, run_start, static_cast<int>(i) - 1);
run_start = -1;
}
if (run_start >= 0)
append_curve_sample_run(result, request, *samples, run_start, static_cast<int>(end) - 1);
}
}
partial.begins_with_continuous_point = partition.source_begin < partition.source_end && curve_sample_valid(request, partition.source_begin);
partial.ends_with_continuous_point = partition.source_end > partition.source_begin && curve_sample_valid(request, partition.source_end - 1);
partial.segments = std::move(result.segments);
job.results[partition_index] = std::move(partial);
}
static void append_curve_segment(Curve_Geometry_Result& result, Curve_Geometry_Result::Segment&& source_segment) {
if (!source_segment.points.empty())
result.segments.push_back(std::move(source_segment));
}
static void merge_curve_first_segment(Curve_Geometry_Result& result, Curve_Geometry_Result::Segment& source_segment) {
if (source_segment.points.empty())
return;
if (result.segments.empty()) {
result.segments.push_back(std::move(source_segment));
return;
}
auto& target_points = result.segments.back().points;
for (const PointF& point : source_segment.points) {
if (!target_points.empty() && target_points.back().x == point.x && target_points.back().y == point.y)
continue;
target_points.push_back(point);
}
}
static Curve_Geometry_Result merge_curve_geometry(Curve_Geometry_Job& job) {
Curve_Geometry_Result result;
if (job.request)
result.mapping = job.request->mapping;
bool previous_can_join = false;
for (std::size_t i = 0; i < job.results.size(); ++i) {
Curve_Geometry_Partial_Result& partial = job.results[i];
if (partial.segments.empty()) {
previous_can_join = false;
continue;
}
bool join = previous_can_join && partial.joins_previous;
if (join) {
merge_curve_first_segment(result, partial.segments.front());
for (std::size_t s = 1; s < partial.segments.size(); ++s)
append_curve_segment(result, std::move(partial.segments[s]));
}
else {
for (auto& segment : partial.segments)
append_curve_segment(result, std::move(segment));
}
previous_can_join = partial.ends_with_continuous_point;
}
result.update_states = std::move(job.update_states);
return result;
}
struct Curve_Private : Typed_Render_Data<Curve, Curve_Render_State, Curve_Input_Data>, Frame_Prepare_Subtask_Source {
static constexpr std::size_t Input_Queue_Capacity = 8;
std::shared_ptr<const Curve_Source> source = std::make_shared<Curve_Source>();
rigtorp::MPMCQueue<std::shared_ptr<Curve_Input_Block>> input_queue;
std::atomic_uint64_t dropped_input_count{0};
std::pmr::vector<std::shared_ptr<Update_State>> pending_geometry_update_states{memory_resource(Memory_Domain::Update_Completion)};
std::optional<Curve_Geometry_Result> geometry_result;
std::optional<Curve_Geometry_Key> submitted_geometry_key;
std::uint64_t input_generation{};
Curve_Private()
: input_queue(Input_Queue_Capacity) {}
~Curve_Private() override {
if (geometry_result)
complete_curve_result_states(*geometry_result, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
std::shared_ptr<Curve_Input_Block> block;
while (input_queue.try_pop(block)) {
if (block && block->completion_state)
block->completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
}
for (auto& state : pending_geometry_update_states) {
if (state)
state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
}
pending_geometry_update_states.clear();
}
void enqueue_input(std::shared_ptr<Curve_Input_Block> block) {
if (!block)
return;
if (!input_queue.try_push(block)) {
if (block->completion_state)
block->completion_state->complete_all({}, Update_Outcome::Superseded);
dropped_input_count.fetch_add(1, std::memory_order_relaxed);
return;
}
if (block->completion_state)
block->completion_state->complete_until(Update_Stage::Input_Released);
if (q())
q()->mark_render_dirty();
}
void consume_input_queue() {
std::shared_ptr<Curve_Input_Block> selected;
std::shared_ptr<Curve_Input_Block> block;
while (input_queue.try_pop(block)) {
if (!block)
continue;
if (selected && selected->completion_state)
selected->completion_state->complete_all({}, Update_Outcome::Superseded);
selected = std::move(block);
}
if (!selected)
return;
source = std::move(selected->source);
++input_generation;
if (selected->completion_state) {
selected->completion_state->complete_until(Update_Stage::Committed);
pending_geometry_update_states.push_back(std::move(selected->completion_state));
}
}
void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override {
(void)snapshot;
(void)frame_view;
}
bool build_prepare_subtasks(Render_Task_Subflow& subflow, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override {
consume_input_queue();
auto job = create_geometry_job_if_needed(snapshot, frame_view);
if (!job)
return true;
std::vector<Render_Task_Graph_Node> partition_tasks;
partition_tasks.reserve(job->partitions.size());
for (std::size_t i = 0; i < job->partitions.size(); ++i) {
partition_tasks.push_back(subflow.emplace(Render_Task_Kind::Curve, Task([job, i]() {
build_curve_partition(*job, i);
})));
}
auto merge = subflow.emplace(Render_Task_Kind::Compose, Task([this, job]() {
if (q() && q()->get_retiring())
return;
Curve_Geometry_Result result = merge_curve_geometry(*job);
if (q() && q()->get_retiring()) {
complete_curve_result_states(result, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
return;
}
geometry_result = std::move(result);
submitted_geometry_key = job->key;
}));
for (auto task : partition_tasks)
subflow.precede(task, merge);
return true;
}
std::shared_ptr<Curve_Geometry_Job> create_geometry_job_if_needed(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) {
Curve_Render_State* s = render_state(frame_view);
if (!s)
return {};
auto domain_axis = s->domain_axis.lock();
auto value_axis = s->value_axis.lock();
if (!domain_axis || !value_axis) {
for (auto& state : pending_geometry_update_states) {
if (state)
state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
}
pending_geometry_update_states.clear();
return {};
}
Axis_Mapping_2D mapping = Axis_Render_Access::mapping(domain_axis.get(), value_axis.get(), snapshot);
Curve_Geometry_Key key;
key.input_generation = input_generation;
key.state_version = s->version;
key.mode = source ? curve_source_mode(*source) : Curve_Input_Mode::None;
key.sample_mode = s->sample_mode;
key.interpolation_mode = s->interpolation_mode;
key.visible_range_only = s->visible_range_only;
key.data_range = s->data_range;
key.sample_count = source ? curve_sample_count(*source) : 0u;
key.point_count = source ? curve_point_count(*source) : 0u;
key.domain = curve_axis_key(mapping.domain);
key.value = curve_axis_key(mapping.value);
if (submitted_geometry_key && key == *submitted_geometry_key)
return {};
auto request = std::make_shared<Curve_Geometry_Request>();
request->mode = key.mode;
request->mapping = mapping;
request->data_range = s->data_range;
request->visible_range_only = s->visible_range_only;
request->interpolation_mode = s->interpolation_mode;
request->sample_mode = s->sample_mode;
request->source = source;
request->update_states = std::move(pending_geometry_update_states);
pending_geometry_update_states = std::pmr::vector<std::shared_ptr<Update_State>>(memory_resource(Memory_Domain::Update_Completion));
std::size_t worker_count = std::max<std::size_t>(1, Global::instance()->render_scheduler().render_worker_count());
return create_curve_geometry_job(std::move(request), std::move(key), worker_count);
}
void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override {
Curve_Render_State* s = render_state(frame_view);
if (!s)
return;
if (!geometry_result)
return;
if (snapshot.frame_update_states) {
for (const auto& state : geometry_result->update_states)
snapshot.frame_update_states->push_back(state);
geometry_result->update_states.clear();
}
if (geometry_result->segments.empty())
return;
canvas.save();
canvas.set_antialias(s->antialias);
for (const auto& segment : geometry_result->segments) {
if (segment.points.empty())
continue;
Curve_Utils::draw_polyline(&canvas, segment.points, s->pen);
Curve_Utils::draw_fill_to_value(&canvas, segment.points, geometry_result->mapping, geometry_result->mapping.value.coord_range.target, s->fill_brush);
}
canvas.restore();
}
};
RENDERIVE_DEFINE_RENDERABLE_BINDING(Curve, Curve_Private, Curve_Render_State, Curve_Input_Data, "Curve")
void Curve::update_samples(std::span<const double> values) {
if (!ok())
return;
d()->enqueue_input(Curve_Input_Block::samples_block(values));
}
void Curve::update_samples(std::pmr::vector<double>&& values) {
if (!ok())
return;
d()->enqueue_input(Curve_Input_Block::samples_block(std::move(values)));
}
Update_Ticket Curve::submit_samples(std::span<const double> values) {
auto state = make_update_state();
if (!ok()) {
state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
return Update_Ticket(state);
}
d()->enqueue_input(Curve_Input_Block::samples_block(values, state));
return Update_Ticket(state);
}
Update_Ticket Curve::submit_samples(std::pmr::vector<double>&& values) {
auto state = make_update_state();
if (!ok()) {
state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
return Update_Ticket(state);
}
d()->enqueue_input(Curve_Input_Block::samples_block(std::move(values), state));
return Update_Ticket(state);
}
void Curve::update_points(std::span<const PointF> points) {
if (!ok())
return;
d()->enqueue_input(Curve_Input_Block::points_block(points));
}
void Curve::update_points(std::pmr::vector<PointF>&& points) {
if (!ok())
return;
d()->enqueue_input(Curve_Input_Block::points_block(std::move(points)));
}
Update_Ticket Curve::submit_points(std::span<const PointF> points) {
auto state = make_update_state();
if (!ok()) {
state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
return Update_Ticket(state);
}
d()->enqueue_input(Curve_Input_Block::points_block(points, state));
return Update_Ticket(state);
}
Update_Ticket Curve::submit_points(std::pmr::vector<PointF>&& points) {
auto state = make_update_state();
if (!ok()) {
state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
return Update_Ticket(state);
}
d()->enqueue_input(Curve_Input_Block::points_block(std::move(points), state));
return Update_Ticket(state);
}
RENDERIVE_DEFINE_WEAK_STATE_PROP(Curve, Curve_Render_State, Abs_Axis, domain_axis)
RENDERIVE_DEFINE_WEAK_STATE_PROP(Curve, Curve_Render_State, Abs_Axis, value_axis)
Range Curve::data_range() {
return d()->edit_state_value(&Curve_Render_State::data_range);
}
void Curve::set_data_range(Range value) {
if (value.length() == 0.0)
return;
d()->set_state_value(&Curve_Render_State::data_range, value);
}
RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Pen, pen)
RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Brush, fill_brush)
RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, bool, visible_range_only)
RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, bool, antialias)
RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Line_Interpolation_Mode, interpolation_mode)
RENDERIVE_DEFINE_STATE_PROP(Curve, Curve_Render_State, Line_Sampling_Strategy, sample_mode)
Curve::Builder::Builder(std::shared_ptr<Renderable> parent, std::shared_ptr<Abs_Axis> domain_axis, std::shared_ptr<Abs_Axis> value_axis) {
ASSERT(parent, "Curve build error! parent == nullptr");
ASSERT(domain_axis && value_axis, "Curve build error! axis == nullptr");
ASSERT(domain_axis->attached_to_plot(), "Curve build error! domain_axis->plot == nullptr");
ASSERT(domain_axis->shares_plot_with(*value_axis), "Curve build error! domain_axis->plot != value_axis->plot");
ASSERT(parent->shares_plot_with(*domain_axis), "Curve build error! parent->plot != axis->plot");
ASSERT(domain_axis->orientation() != value_axis->orientation(), "Curve build error! axes must be orthogonal");
this->parent = parent;
this->domain_axis = domain_axis;
this->value_axis = value_axis;
}
std::shared_ptr<Curve> Curve::Builder::build() {
auto ret = renderive::make_shared<Curve>();
ret->init(parent);
Curve_Private* pd = ret->d();
Render_Edit_Lease<Curve_Private, Curve_Render_State> edit(pd);
Curve_Render_State* sc = edit.operator->();
PROP_R(std::weak_ptr<Abs_Axis>, domain_axis)
PROP_R(std::weak_ptr<Abs_Axis>, value_axis)
PROP_R(Range, data_range)
PROP_R(Pen, pen)
PROP_R(Brush, fill_brush)
PROP_R(bool, visible_range_only)
PROP_R(bool, antialias)
PROP_R(Line_Interpolation_Mode, interpolation_mode)
PROP_R(Line_Sampling_Strategy, sample_mode)
if (sc->data_range.length() == 0.0)
sc->data_range = {0.0, 1.0};
return ret;
}
void Curve_Utils::draw_polyline(Canvas* canvas, std::span<const PointF> points, const Pen& pen) {
if (points.empty() || pen.style == Line_Style::None)
return;
canvas->set_pen(pen);
canvas->draw_polyline(points);
}
void Curve_Utils::draw_fill_to_value(Canvas* canvas, std::span<const PointF> points, const Axis_Mapping_2D& mapping, double baseline_value, const Brush& brush) {
if (points.empty() || brush.style == Brush_Style::None)
return;
double first_domain = mapping.domain.pixel_to_coord(mapping.domain_pixel(points.front()));
double last_domain = mapping.domain.pixel_to_coord(mapping.domain_pixel(points.back()));
std::pmr::vector<PointF> fill_points(points.size() + 2, frame_memory_resource());
fill_points.front() = mapping.map(first_domain, baseline_value);
fill_points.back() = mapping.map(last_domain, baseline_value);
for (int i = 0; i < points.size(); ++i)
fill_points[i + 1] = points[i];
canvas->set_brush(brush);
canvas->fill_polygon(std::span<const PointF>(fill_points.data(), fill_points.size()));
}
void Curve_Utils::append_envelope_point(std::pmr::vector<PointF>& points, const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int index, double value) {
if (index < 0)
return;
if (!std::isfinite(value))
return;
PointF point = mapping.map(source_index_to_coordinate(data_range, total_count, index), value);
if (!points.empty() && points.back().x == point.x && points.back().y == point.y)
return;
points.push_back(point);
}
} // namespace renderive