|
|
|
@@ -7,7 +7,6 @@
|
|
|
|
|
#include <asio/strand.hpp>
|
|
|
|
|
#include <asio/use_awaitable.hpp>
|
|
|
|
|
#include <render_2D/plottable/Plottables.hpp>
|
|
|
|
|
#include <render_2D/scene/Render_Scene_2D.hpp>
|
|
|
|
|
#include <render_3D/Render_3D.hpp>
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <array>
|
|
|
|
@@ -15,43 +14,114 @@
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <mutex>
|
|
|
|
|
#include <numbers>
|
|
|
|
|
#include <span>
|
|
|
|
|
#include <stdexcept>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <variant>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
namespace aethera::web {
|
|
|
|
|
namespace {
|
|
|
|
|
using namespace render_2d;
|
|
|
|
|
using namespace render_3d;
|
|
|
|
|
using Scene_2D = Impl<Render_Scene_2D>;
|
|
|
|
|
using Scene_3D = Impl<Render_Scene_3D>;
|
|
|
|
|
using Frequency_Axis_Object = Impl<Frequency_Axis>;
|
|
|
|
|
using Numeric_Axis_Object = Impl<Numeric_Axis>;
|
|
|
|
|
using Time_Axis_Object = Impl<Time_Axis>;
|
|
|
|
|
template <typename Object, typename... Arguments>
|
|
|
|
|
std::unique_ptr<Object> build(Arguments&&... arguments) { typename Object::Builder builder(std::forward<Arguments>(arguments)...); auto result = builder.build(); if (!result) throw std::logic_error("gallery graph dependency graph is invalid"); return std::move(result).value(); }
|
|
|
|
|
template <typename Axis>
|
|
|
|
|
void configure_axis(Axis* axis, Axis_Orientation orientation, Point_F position, Axis_Pixel_Length length, Size canvas) { axis->template set<&Abs_Axis::Prop::orientation>(orientation); axis->template set<&Abs_Axis::Prop::position>(position); axis->template set<&Abs_Axis::Prop::pixel_length>(length); axis->template set<&Abs_Axis::Prop::canvas_size>(canvas); }
|
|
|
|
|
|
|
|
|
|
template <typename Builder>
|
|
|
|
|
auto finish_build(Builder&& builder) {
|
|
|
|
|
auto result = builder.build();
|
|
|
|
|
if (!result) throw std::logic_error("gallery scene topology validation failed");
|
|
|
|
|
return std::move(result).value();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Object, typename Builder>
|
|
|
|
|
std::shared_ptr<Object> finish_shared(Builder&& builder) {
|
|
|
|
|
return std::shared_ptr<Object>(finish_build(std::forward<Builder>(builder)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Integer>
|
|
|
|
|
void append_binary(std::string& output, Integer value) { const auto start = output.size(); output.resize(start + sizeof(Integer)); std::memcpy(output.data() + start, &value, sizeof(Integer)); }
|
|
|
|
|
std::string encode_frame(Image_View image, std::uint64_t sequence) { std::string output; output.reserve(24 + static_cast<std::size_t>(image.width) * image.height * 4); append_binary(output, std::uint32_t{0x41544852}); append_binary(output, std::uint16_t{1}); append_binary(output, std::uint16_t{}); append_binary(output, static_cast<std::uint32_t>(image.width)); append_binary(output, static_cast<std::uint32_t>(image.height)); append_binary(output, sequence); for (int y = 0; y < image.height; ++y) { const auto* row = reinterpret_cast<const std::uint8_t*>(image.data + static_cast<std::ptrdiff_t>(y) * image.stride); for (int x = 0; x < image.width; ++x) { const auto* pixel = row + x * 4; output.push_back(static_cast<char>(pixel[2])); output.push_back(static_cast<char>(pixel[1])); output.push_back(static_cast<char>(pixel[0])); output.push_back(static_cast<char>(pixel[3])); } } return output; }
|
|
|
|
|
std::string encode_frame(const render_3d::Pixel_Frame& frame, std::uint64_t sequence) { std::string output; output.reserve(24 + frame.rgba8.size()); append_binary(output, std::uint32_t{0x41544852}); append_binary(output, std::uint16_t{1}); append_binary(output, std::uint16_t{}); append_binary(output, frame.extent.width); append_binary(output, frame.extent.height); append_binary(output, sequence); output.append(reinterpret_cast<const char*>(frame.rgba8.data()), frame.rgba8.size()); return output; }
|
|
|
|
|
struct Schema_Query { Plot::Json_Handler handler{}; };
|
|
|
|
|
struct Prop_Write { std::string key{}; nlohmann::json value{}; Plot::Json_Handler handler{}; };
|
|
|
|
|
using Plot_Input = std::variant<Plot_Event, Schema_Query, Prop_Write>;
|
|
|
|
|
struct Plot_2D {
|
|
|
|
|
std::unique_ptr<Scene_2D> scene{}; /* 鏈€缁堜簩缁?Scene銆?*/
|
|
|
|
|
std::unique_ptr<Frequency_Axis_Object> frequency{}; /* 棰戠巼杞达紱涓嶇敤鏃朵粛涓虹┖銆?*/
|
|
|
|
|
std::unique_ptr<Numeric_Axis_Object> horizontal{}; /* 鏄熷骇鍥炬按骞虫暟鍊艰酱銆?*/
|
|
|
|
|
std::unique_ptr<Numeric_Axis_Object> vertical{}; /* 鍔熺巼鎴栨槦搴у浘鍨傜洿杞淬€?*/
|
|
|
|
|
std::unique_ptr<Time_Axis_Object> time{}; /* 鏃堕棿杞达紱涓嶇敤鏃朵负绌恒€?*/
|
|
|
|
|
std::unique_ptr<Root> plot{}; /* 鍏蜂綋 Plottable 鐨勫敮涓€鎵€鏈夋潈銆?*/
|
|
|
|
|
std::function<void(double)> update{}; /* 鏍规嵁娴忚鍣ㄦ椂閽熸洿鏂版潈濞?Prop銆?*/
|
|
|
|
|
std::unique_ptr<detail::Renderable_Descriptor> descriptor{}; /* 鐩存帴璇诲啓 plot 鐨?Structive 鍗忚瑙嗗浘銆?*/
|
|
|
|
|
void append_binary(std::string& output, Integer value) {
|
|
|
|
|
const auto start = output.size();
|
|
|
|
|
output.resize(start + sizeof(Integer));
|
|
|
|
|
std::memcpy(output.data() + start, &value, sizeof(Integer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string encode_frame(Image_View image, std::uint64_t sequence) {
|
|
|
|
|
std::string output;
|
|
|
|
|
output.reserve(24 + static_cast<std::size_t>(image.width) * image.height * 4);
|
|
|
|
|
append_binary(output, std::uint32_t{0x41544852});
|
|
|
|
|
append_binary(output, std::uint16_t{1});
|
|
|
|
|
append_binary(output, std::uint16_t{});
|
|
|
|
|
append_binary(output, static_cast<std::uint32_t>(image.width));
|
|
|
|
|
append_binary(output, static_cast<std::uint32_t>(image.height));
|
|
|
|
|
append_binary(output, sequence);
|
|
|
|
|
for (int y = 0; y < image.height; ++y) {
|
|
|
|
|
const auto* row = reinterpret_cast<const std::uint8_t*>(
|
|
|
|
|
image.data + static_cast<std::ptrdiff_t>(y) * image.stride);
|
|
|
|
|
for (int x = 0; x < image.width; ++x) {
|
|
|
|
|
const auto* pixel = row + x * 4;
|
|
|
|
|
output.push_back(static_cast<char>(pixel[2]));
|
|
|
|
|
output.push_back(static_cast<char>(pixel[1]));
|
|
|
|
|
output.push_back(static_cast<char>(pixel[0]));
|
|
|
|
|
output.push_back(static_cast<char>(pixel[3]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string encode_frame(const Pixel_Frame& frame, std::uint64_t sequence) {
|
|
|
|
|
std::string output;
|
|
|
|
|
output.reserve(24 + frame.rgba8.size());
|
|
|
|
|
append_binary(output, std::uint32_t{0x41544852});
|
|
|
|
|
append_binary(output, std::uint16_t{1});
|
|
|
|
|
append_binary(output, std::uint16_t{});
|
|
|
|
|
append_binary(output, frame.extent.width);
|
|
|
|
|
append_binary(output, frame.extent.height);
|
|
|
|
|
append_binary(output, sequence);
|
|
|
|
|
output.append(reinterpret_cast<const char*>(frame.rgba8.data()), frame.rgba8.size());
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Schema_Query { Plot::Json_Handler handler; };
|
|
|
|
|
struct Prop_Write {
|
|
|
|
|
std::string key;
|
|
|
|
|
nlohmann::json value;
|
|
|
|
|
Plot::Json_Handler handler;
|
|
|
|
|
};
|
|
|
|
|
template <typename Definition, typename... Fields, typename Holder, typename Object>
|
|
|
|
|
void bind_renderable_adapter(Holder& plot, Object& object) {
|
|
|
|
|
using Plot_Input = std::variant<Plot_Event, Schema_Query, Prop_Write>;
|
|
|
|
|
|
|
|
|
|
class Scene_View_Model final : public Plot::Scene_View {
|
|
|
|
|
public:
|
|
|
|
|
Scene_View_Model(std::vector<std::shared_ptr<void>> owned_objects,
|
|
|
|
|
std::unique_ptr<detail::Renderable_Descriptor> value_descriptor,
|
|
|
|
|
std::function<void(const Plot_Event&)> value_update)
|
|
|
|
|
: objects(std::move(owned_objects)),
|
|
|
|
|
descriptor(std::move(value_descriptor)),
|
|
|
|
|
update_scene(std::move(value_update)) {}
|
|
|
|
|
|
|
|
|
|
nlohmann::json schema() const override { return descriptor->schema(); }
|
|
|
|
|
nlohmann::json write_prop(std::string_view key, const nlohmann::json& value) override {
|
|
|
|
|
return descriptor->write_prop(key, value);
|
|
|
|
|
}
|
|
|
|
|
void update(const Plot_Event& event) override { update_scene(event); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::vector<std::shared_ptr<void>> objects;
|
|
|
|
|
std::unique_ptr<detail::Renderable_Descriptor> descriptor;
|
|
|
|
|
std::function<void(const Plot_Event&)> update_scene;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define AETHERA_PROP(Type, Name) detail::Prop_Field<&Type::Prop::Name, #Name>
|
|
|
|
|
#define AETHERA_STATE(Type, Name) detail::State_Field<typename Type::Base_Tag, &Type::State::Name, #Name>
|
|
|
|
|
|
|
|
|
|
template <typename Definition, typename... Fields, typename Object>
|
|
|
|
|
std::unique_ptr<Plot::Scene_View> make_scene_view(
|
|
|
|
|
Object& object,
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners,
|
|
|
|
|
std::function<void(const Plot_Event&)> update) {
|
|
|
|
|
using Tag = typename Definition::Base_Tag;
|
|
|
|
|
using State = typename Definition::State;
|
|
|
|
|
using Adapter = detail::Renderable_Adapter<Object, Fields...,
|
|
|
|
@@ -65,153 +135,442 @@ void bind_renderable_adapter(Holder& plot, Object& object) {
|
|
|
|
|
detail::State_Field<Tag, &State::paint_task_count, "paint_task_count">,
|
|
|
|
|
detail::State_Field<Tag, &State::prepare_execution_time_ns, "prepare_execution_time_ns">,
|
|
|
|
|
detail::State_Field<Tag, &State::paint_execution_time_ns, "paint_execution_time_ns">>;
|
|
|
|
|
plot.descriptor = detail::make_renderable_descriptor(Adapter{object});
|
|
|
|
|
return std::make_unique<Scene_View_Model>(
|
|
|
|
|
std::move(owners),
|
|
|
|
|
detail::make_renderable_descriptor(Adapter{object}),
|
|
|
|
|
std::move(update));
|
|
|
|
|
}
|
|
|
|
|
#define AETHERA_PROP(Type, Name) detail::Prop_Field<&Type::Prop::Name, #Name>
|
|
|
|
|
#define AETHERA_STATE(Type, Name) detail::State_Field<typename Type::Base_Tag, &Type::State::Name, #Name>
|
|
|
|
|
|
|
|
|
|
Frequency_Axis_Object::Builder frequency_axis_builder(Size canvas) {
|
|
|
|
|
Frequency_Axis_Object::Builder builder;
|
|
|
|
|
builder
|
|
|
|
|
.set(&Abs_Axis::Prop::orientation, Axis_Orientation::horizontal)
|
|
|
|
|
.set(&Abs_Axis::Prop::position, Point_F{64.0, 370.0})
|
|
|
|
|
.set(&Abs_Axis::Prop::pixel_length, 620.0)
|
|
|
|
|
.set(&Abs_Axis::Prop::canvas_size, canvas)
|
|
|
|
|
.set(&Numeric_Axis::Prop::coordinate_range, Axis_Range{0.0, 100.0});
|
|
|
|
|
return builder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Numeric_Axis_Object::Builder numeric_axis_builder(
|
|
|
|
|
Axis_Orientation orientation, Point_F position, Axis_Pixel_Length length,
|
|
|
|
|
Axis_Range range, Size canvas) {
|
|
|
|
|
Numeric_Axis_Object::Builder builder;
|
|
|
|
|
builder
|
|
|
|
|
.set(&Abs_Axis::Prop::orientation, orientation)
|
|
|
|
|
.set(&Abs_Axis::Prop::position, position)
|
|
|
|
|
.set(&Abs_Axis::Prop::pixel_length, length)
|
|
|
|
|
.set(&Abs_Axis::Prop::canvas_size, canvas)
|
|
|
|
|
.set(&Numeric_Axis::Prop::coordinate_range, range);
|
|
|
|
|
return builder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Time_Axis_Object::Builder time_axis_builder(
|
|
|
|
|
Axis_Orientation orientation, Point_F position, Axis_Pixel_Length length, Size canvas) {
|
|
|
|
|
Time_Axis_Object::Builder builder;
|
|
|
|
|
builder
|
|
|
|
|
.set(&Abs_Axis::Prop::orientation, orientation)
|
|
|
|
|
.set(&Abs_Axis::Prop::position, position)
|
|
|
|
|
.set(&Abs_Axis::Prop::pixel_length, length)
|
|
|
|
|
.set(&Abs_Axis::Prop::canvas_size, canvas);
|
|
|
|
|
return builder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename... Axes>
|
|
|
|
|
void resize_axes(Size viewport, Axes*... axes) {
|
|
|
|
|
(axes->template set<&Abs_Axis::Prop::canvas_size>(viewport), ...);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Definition>
|
|
|
|
|
Plot_2D make_plot_2d() {
|
|
|
|
|
Plot_2D result;
|
|
|
|
|
const Size canvas{720, 420};
|
|
|
|
|
result.scene = build<Scene_2D>();
|
|
|
|
|
result.scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
|
|
|
|
result.scene->set<&Render_Scene_2D::Prop::background>(Color{7, 13, 24, 255});
|
|
|
|
|
result.scene->activate_view();
|
|
|
|
|
auto make_frequency = [&] { result.frequency = build<Frequency_Axis_Object>(); configure_axis(result.frequency.get(), Axis_Orientation::horizontal, Point_F{64.0, 370.0}, 620.0, canvas); result.frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0}); };
|
|
|
|
|
auto make_vertical = [&](Axis_Range range) {
|
|
|
|
|
result.vertical = build<Numeric_Axis_Object>();
|
|
|
|
|
configure_axis(result.vertical.get(), Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas); result.vertical->set<&Numeric_Axis::Prop::coordinate_range>(range);
|
|
|
|
|
};
|
|
|
|
|
std::shared_ptr<Plot> build_2d_plot(asio::any_io_executor executor) {
|
|
|
|
|
constexpr Size canvas{720, 420};
|
|
|
|
|
Scene_2D::Builder scene_builder;
|
|
|
|
|
scene_builder
|
|
|
|
|
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
|
|
|
|
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
|
|
|
|
.set(&Render_Scene_2D::Prop::view_active, true);
|
|
|
|
|
auto scene = finish_build(std::move(scene_builder));
|
|
|
|
|
|
|
|
|
|
if constexpr (std::same_as<Definition, Spectrum>) {
|
|
|
|
|
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Spectrum>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Spectrum::Prop::max_hold_visible>(true); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_renderable_adapter<Spectrum, AETHERA_PROP(Spectrum, center_frequency), AETHERA_PROP(Spectrum, partition_count), AETHERA_PROP(Spectrum, max_hold_visible), AETHERA_PROP(Spectrum, min_hold_visible), AETHERA_PROP(Spectrum, max_marker_visible), AETHERA_PROP(Spectrum, min_marker_visible), AETHERA_PROP(Spectrum, sweep_region_visible), AETHERA_PROP(Spectrum, visible_range_only), AETHERA_PROP(Spectrum, frequency_range), AETHERA_PROP(Spectrum, sweep_frequency_range), AETHERA_PROP(Spectrum, partition_mode), AETHERA_PROP(Spectrum, interpolation_mode), AETHERA_PROP(Spectrum, max_brush), AETHERA_PROP(Spectrum, current_brush), AETHERA_PROP(Spectrum, min_brush), AETHERA_PROP(Spectrum, max_pen), AETHERA_PROP(Spectrum, current_pen), AETHERA_PROP(Spectrum, min_pen), AETHERA_PROP(Spectrum, selected_marker_pen), AETHERA_PROP(Spectrum, marker_pen), AETHERA_PROP(Spectrum, middle_frequency_pen), AETHERA_PROP(Spectrum, sweep_region_brush), AETHERA_PROP(Spectrum, custom_markers), AETHERA_PROP(Spectrum, selected_marker), AETHERA_STATE(Spectrum, sample_count), AETHERA_STATE(Spectrum, rendered_point_count), AETHERA_STATE(Spectrum, selectable_marker_count)>(result, *raw); result.update = [raw](double time) { std::array<double, 256> samples{}; for (std::size_t i = 0; i < samples.size(); ++i) { const double x = static_cast<double>(i) / samples.size(); samples[i] = -92.0 + 54.0 * std::exp(-180.0 * std::pow(x - 0.28 - 0.03 * std::sin(time * 0.001), 2.0)) + 42.0 * std::exp(-260.0 * std::pow(x - 0.68, 2.0)) + 2.5 * std::sin(i * 0.31 + time * 0.004); } raw->update_samples(samples); }; result.plot = std::move(object);
|
|
|
|
|
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
|
|
|
|
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas));
|
|
|
|
|
Impl<Spectrum>::Builder builder(scene.get(), frequency.get(), vertical.get());
|
|
|
|
|
builder
|
|
|
|
|
.set(&Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
|
|
|
|
.set(&Spectrum::Prop::max_hold_visible, true);
|
|
|
|
|
auto object = finish_shared<Impl<Spectrum>>(std::move(builder));
|
|
|
|
|
object->mark_dirty<Prepare_Data_Tag>();
|
|
|
|
|
object->mark_dirty<Paint_Tag>();
|
|
|
|
|
auto update = [raw = object.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
|
|
|
|
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
|
|
|
|
std::array<double, 256> samples{};
|
|
|
|
|
for (std::size_t i = 0; i < samples.size(); ++i) {
|
|
|
|
|
const double x = static_cast<double>(i) / samples.size();
|
|
|
|
|
samples[i] = -92.0 + 54.0 * std::exp(-180.0 * std::pow(x - 0.28 - 0.03 * std::sin(event.time_milliseconds * 0.001), 2.0))
|
|
|
|
|
+ 42.0 * std::exp(-260.0 * std::pow(x - 0.68, 2.0))
|
|
|
|
|
+ 2.5 * std::sin(i * 0.31 + event.time_milliseconds * 0.004);
|
|
|
|
|
}
|
|
|
|
|
raw->update_samples(samples);
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{frequency, vertical, object};
|
|
|
|
|
auto view = make_scene_view<Spectrum,
|
|
|
|
|
AETHERA_PROP(Spectrum, center_frequency), AETHERA_PROP(Spectrum, partition_count),
|
|
|
|
|
AETHERA_PROP(Spectrum, max_hold_visible), AETHERA_PROP(Spectrum, min_hold_visible),
|
|
|
|
|
AETHERA_PROP(Spectrum, max_marker_visible), AETHERA_PROP(Spectrum, min_marker_visible),
|
|
|
|
|
AETHERA_PROP(Spectrum, sweep_region_visible), AETHERA_PROP(Spectrum, visible_range_only),
|
|
|
|
|
AETHERA_PROP(Spectrum, frequency_range), AETHERA_PROP(Spectrum, sweep_frequency_range),
|
|
|
|
|
AETHERA_PROP(Spectrum, partition_mode), AETHERA_PROP(Spectrum, interpolation_mode),
|
|
|
|
|
AETHERA_PROP(Spectrum, max_brush), AETHERA_PROP(Spectrum, current_brush),
|
|
|
|
|
AETHERA_PROP(Spectrum, min_brush), AETHERA_PROP(Spectrum, max_pen),
|
|
|
|
|
AETHERA_PROP(Spectrum, current_pen), AETHERA_PROP(Spectrum, min_pen),
|
|
|
|
|
AETHERA_PROP(Spectrum, selected_marker_pen), AETHERA_PROP(Spectrum, marker_pen),
|
|
|
|
|
AETHERA_PROP(Spectrum, middle_frequency_pen), AETHERA_PROP(Spectrum, sweep_region_brush),
|
|
|
|
|
AETHERA_PROP(Spectrum, custom_markers), AETHERA_PROP(Spectrum, selected_marker),
|
|
|
|
|
AETHERA_STATE(Spectrum, sample_count), AETHERA_STATE(Spectrum, rendered_point_count),
|
|
|
|
|
AETHERA_STATE(Spectrum, selectable_marker_count)>(*object, std::move(owners), std::move(update));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
} else if constexpr (std::same_as<Definition, Frequency_Trace>) {
|
|
|
|
|
result.time = build<Time_Axis_Object>(); configure_axis(result.time.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); make_vertical({-1.2, 1.2}); auto object = build<Impl<Frequency_Trace>>(result.scene.get(), result.time.get(), result.vertical.get()); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_renderable_adapter<Frequency_Trace, AETHERA_PROP(Frequency_Trace, partition_count), AETHERA_PROP(Frequency_Trace, pen), AETHERA_PROP(Frequency_Trace, partition_mode), AETHERA_PROP(Frequency_Trace, samples), AETHERA_STATE(Frequency_Trace, sample_count), AETHERA_STATE(Frequency_Trace, rendered_point_count)>(result, *raw); auto tick = std::make_shared<std::uint64_t>(); result.update = [raw, tick](double time) { raw->append_sample((*tick)++, std::sin(time * 0.0025) * 0.8 + std::sin(time * 0.0007) * 0.2); }; result.plot = std::move(object);
|
|
|
|
|
auto time = finish_shared<Time_Axis_Object>(time_axis_builder(
|
|
|
|
|
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas));
|
|
|
|
|
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}, canvas));
|
|
|
|
|
Impl<Frequency_Trace>::Builder builder(scene.get(), time.get(), vertical.get());
|
|
|
|
|
auto object = finish_shared<Impl<Frequency_Trace>>(std::move(builder));
|
|
|
|
|
object->mark_dirty<Prepare_Data_Tag>();
|
|
|
|
|
object->mark_dirty<Paint_Tag>();
|
|
|
|
|
auto tick = std::make_shared<std::uint64_t>();
|
|
|
|
|
auto update = [raw = object.get(), time = time.get(), vertical = vertical.get(), tick](const Plot_Event& event) {
|
|
|
|
|
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, time, vertical);
|
|
|
|
|
raw->append_sample((*tick)++, std::sin(event.time_milliseconds * 0.0025) * 0.8
|
|
|
|
|
+ std::sin(event.time_milliseconds * 0.0007) * 0.2);
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{time, vertical, object, tick};
|
|
|
|
|
auto view = make_scene_view<Frequency_Trace,
|
|
|
|
|
AETHERA_PROP(Frequency_Trace, partition_count), AETHERA_PROP(Frequency_Trace, pen),
|
|
|
|
|
AETHERA_PROP(Frequency_Trace, partition_mode), AETHERA_PROP(Frequency_Trace, samples),
|
|
|
|
|
AETHERA_STATE(Frequency_Trace, sample_count), AETHERA_STATE(Frequency_Trace, rendered_point_count)>(
|
|
|
|
|
*object, std::move(owners), std::move(update));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
} else if constexpr (std::same_as<Definition, Sweep_Spectrum>) {
|
|
|
|
|
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Sweep_Spectrum>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Sweep_Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_renderable_adapter<Sweep_Spectrum, AETHERA_PROP(Sweep_Spectrum, bins_per_block), AETHERA_PROP(Sweep_Spectrum, block_count), AETHERA_PROP(Sweep_Spectrum, partition_count), AETHERA_PROP(Sweep_Spectrum, visible_range_only), AETHERA_PROP(Sweep_Spectrum, frequency_range), AETHERA_PROP(Sweep_Spectrum, partition_mode), AETHERA_PROP(Sweep_Spectrum, pen), AETHERA_PROP(Sweep_Spectrum, current_frequency_pen), AETHERA_PROP(Sweep_Spectrum, interpolation_mode), AETHERA_PROP(Sweep_Spectrum, blocks), AETHERA_STATE(Sweep_Spectrum, stored_block_count), AETHERA_STATE(Sweep_Spectrum, stored_point_count), AETHERA_STATE(Sweep_Spectrum, rendered_point_count)>(result, *raw); result.update = [raw](double time) { std::array<double, 64> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -90.0 + 35.0 * std::sin(i * 0.08 + time * 0.002); raw->append_block(values); }; result.plot = std::move(object);
|
|
|
|
|
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
|
|
|
|
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas));
|
|
|
|
|
Impl<Sweep_Spectrum>::Builder builder(scene.get(), frequency.get(), vertical.get());
|
|
|
|
|
builder.set(&Sweep_Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0});
|
|
|
|
|
auto object = finish_shared<Impl<Sweep_Spectrum>>(std::move(builder));
|
|
|
|
|
object->mark_dirty<Prepare_Data_Tag>();
|
|
|
|
|
object->mark_dirty<Paint_Tag>();
|
|
|
|
|
auto update = [raw = object.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
|
|
|
|
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
|
|
|
|
std::array<double, 64> values{};
|
|
|
|
|
for (std::size_t i = 0; i < values.size(); ++i)
|
|
|
|
|
values[i] = -90.0 + 35.0 * std::sin(i * 0.08 + event.time_milliseconds * 0.002);
|
|
|
|
|
raw->append_block(values);
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{frequency, vertical, object};
|
|
|
|
|
auto view = make_scene_view<Sweep_Spectrum,
|
|
|
|
|
AETHERA_PROP(Sweep_Spectrum, bins_per_block), AETHERA_PROP(Sweep_Spectrum, block_count),
|
|
|
|
|
AETHERA_PROP(Sweep_Spectrum, partition_count), AETHERA_PROP(Sweep_Spectrum, visible_range_only),
|
|
|
|
|
AETHERA_PROP(Sweep_Spectrum, frequency_range), AETHERA_PROP(Sweep_Spectrum, partition_mode),
|
|
|
|
|
AETHERA_PROP(Sweep_Spectrum, pen), AETHERA_PROP(Sweep_Spectrum, current_frequency_pen),
|
|
|
|
|
AETHERA_PROP(Sweep_Spectrum, interpolation_mode), AETHERA_PROP(Sweep_Spectrum, blocks),
|
|
|
|
|
AETHERA_STATE(Sweep_Spectrum, stored_block_count), AETHERA_STATE(Sweep_Spectrum, stored_point_count),
|
|
|
|
|
AETHERA_STATE(Sweep_Spectrum, rendered_point_count)>(*object, std::move(owners), std::move(update));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
} else if constexpr (std::same_as<Definition, Afterglow>) {
|
|
|
|
|
make_frequency(); make_vertical({-110.0, 0.0}); auto object = build<Impl<Afterglow>>(result.scene.get(), result.frequency.get(), result.vertical.get()); object->set<&Afterglow::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Afterglow::Prop::power_range>(Axis_Range{-110.0, 0.0}); object->set<&Afterglow::Prop::power_point_size>(96); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_renderable_adapter<Afterglow, AETHERA_PROP(Afterglow, frequency_point_size), AETHERA_PROP(Afterglow, power_point_size), AETHERA_PROP(Afterglow, partition_count), AETHERA_PROP(Afterglow, interpolate), AETHERA_PROP(Afterglow, attenuation_rate), AETHERA_PROP(Afterglow, frequency_range), AETHERA_PROP(Afterglow, power_range), AETHERA_PROP(Afterglow, partition_mode), AETHERA_PROP(Afterglow, color_map), AETHERA_PROP(Afterglow, spectra), AETHERA_STATE(Afterglow, history_count), AETHERA_STATE(Afterglow, latest_spectrum_point_count), AETHERA_STATE(Afterglow, rendered_cell_count)>(result, *raw); result.update = [raw](double time) { std::array<double, 192> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -95.0 + 62.0 * std::exp(-220.0 * std::pow(static_cast<double>(i) / values.size() - 0.5 - 0.18 * std::sin(time * 0.0008), 2.0)); raw->append_spectrum(values); }; result.plot = std::move(object);
|
|
|
|
|
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
|
|
|
|
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas));
|
|
|
|
|
Impl<Afterglow>::Builder builder(scene.get(), frequency.get(), vertical.get());
|
|
|
|
|
builder
|
|
|
|
|
.set(&Afterglow::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
|
|
|
|
.set(&Afterglow::Prop::power_range, Axis_Range{-110.0, 0.0})
|
|
|
|
|
.set(&Afterglow::Prop::power_point_size, 96);
|
|
|
|
|
auto object = finish_shared<Impl<Afterglow>>(std::move(builder));
|
|
|
|
|
object->mark_dirty<Prepare_Data_Tag>();
|
|
|
|
|
object->mark_dirty<Paint_Tag>();
|
|
|
|
|
auto update = [raw = object.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
|
|
|
|
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
|
|
|
|
std::array<double, 192> values{};
|
|
|
|
|
for (std::size_t i = 0; i < values.size(); ++i)
|
|
|
|
|
values[i] = -95.0 + 62.0 * std::exp(-220.0 * std::pow(
|
|
|
|
|
static_cast<double>(i) / values.size() - 0.5
|
|
|
|
|
- 0.18 * std::sin(event.time_milliseconds * 0.0008), 2.0));
|
|
|
|
|
raw->append_spectrum(values);
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{frequency, vertical, object};
|
|
|
|
|
auto view = make_scene_view<Afterglow,
|
|
|
|
|
AETHERA_PROP(Afterglow, frequency_point_size), AETHERA_PROP(Afterglow, power_point_size),
|
|
|
|
|
AETHERA_PROP(Afterglow, partition_count), AETHERA_PROP(Afterglow, interpolate),
|
|
|
|
|
AETHERA_PROP(Afterglow, attenuation_rate), AETHERA_PROP(Afterglow, frequency_range),
|
|
|
|
|
AETHERA_PROP(Afterglow, power_range), AETHERA_PROP(Afterglow, partition_mode),
|
|
|
|
|
AETHERA_PROP(Afterglow, color_map), AETHERA_PROP(Afterglow, spectra),
|
|
|
|
|
AETHERA_STATE(Afterglow, history_count), AETHERA_STATE(Afterglow, latest_spectrum_point_count),
|
|
|
|
|
AETHERA_STATE(Afterglow, rendered_cell_count)>(*object, std::move(owners), std::move(update));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
} else if constexpr (std::same_as<Definition, Waterfall>) {
|
|
|
|
|
make_frequency(); result.time = build<Time_Axis_Object>(); configure_axis(result.time.get(), Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas); auto object = build<Impl<Waterfall>>(result.scene.get(), result.frequency.get(), result.time.get()); object->set<&Waterfall::Prop::frequency_range>(Axis_Range{0.0, 100.0}); object->set<&Waterfall::Prop::power_range>(Axis_Range{-110.0, 0.0}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_renderable_adapter<Waterfall, AETHERA_PROP(Waterfall, tooltip_enabled), AETHERA_PROP(Waterfall, tooltip_font), AETHERA_PROP(Waterfall, tooltip_text_pen), AETHERA_PROP(Waterfall, tooltip_background_brush), AETHERA_PROP(Waterfall, frequency_bin_count), AETHERA_PROP(Waterfall, partition_count), AETHERA_PROP(Waterfall, visible_range_only), AETHERA_PROP(Waterfall, frequency_range), AETHERA_PROP(Waterfall, power_range), AETHERA_PROP(Waterfall, partition_mode), AETHERA_PROP(Waterfall, interpolation_mode), AETHERA_PROP(Waterfall, color_map), AETHERA_PROP(Waterfall, rows), AETHERA_STATE(Waterfall, row_count), AETHERA_STATE(Waterfall, stored_point_count), AETHERA_STATE(Waterfall, rendered_cell_count)>(result, *raw); auto tick = std::make_shared<std::uint64_t>(); result.update = [raw, tick](double time) { std::array<double, 192> values{}; for (std::size_t i = 0; i < values.size(); ++i) values[i] = -100.0 + 70.0 * std::exp(-240.0 * std::pow(static_cast<double>(i) / values.size() - 0.5 - 0.22 * std::sin(time * 0.0006), 2.0)); raw->append_row((*tick)++, values); }; result.plot = std::move(object);
|
|
|
|
|
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
|
|
|
|
auto time = finish_shared<Time_Axis_Object>(time_axis_builder(
|
|
|
|
|
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas));
|
|
|
|
|
Impl<Waterfall>::Builder builder(scene.get(), frequency.get(), time.get());
|
|
|
|
|
builder
|
|
|
|
|
.set(&Waterfall::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
|
|
|
|
.set(&Waterfall::Prop::power_range, Axis_Range{-110.0, 0.0});
|
|
|
|
|
auto object = finish_shared<Impl<Waterfall>>(std::move(builder));
|
|
|
|
|
object->mark_dirty<Prepare_Data_Tag>();
|
|
|
|
|
object->mark_dirty<Paint_Tag>();
|
|
|
|
|
auto tick = std::make_shared<std::uint64_t>();
|
|
|
|
|
auto update = [raw = object.get(), frequency = frequency.get(), time = time.get(), tick](const Plot_Event& event) {
|
|
|
|
|
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, time);
|
|
|
|
|
std::array<double, 192> values{};
|
|
|
|
|
for (std::size_t i = 0; i < values.size(); ++i)
|
|
|
|
|
values[i] = -100.0 + 70.0 * std::exp(-240.0 * std::pow(
|
|
|
|
|
static_cast<double>(i) / values.size() - 0.5
|
|
|
|
|
- 0.22 * std::sin(event.time_milliseconds * 0.0006), 2.0));
|
|
|
|
|
raw->append_row((*tick)++, values);
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{frequency, time, object, tick};
|
|
|
|
|
auto view = make_scene_view<Waterfall,
|
|
|
|
|
AETHERA_PROP(Waterfall, tooltip_enabled), AETHERA_PROP(Waterfall, tooltip_font),
|
|
|
|
|
AETHERA_PROP(Waterfall, tooltip_text_pen), AETHERA_PROP(Waterfall, tooltip_background_brush),
|
|
|
|
|
AETHERA_PROP(Waterfall, frequency_bin_count), AETHERA_PROP(Waterfall, partition_count),
|
|
|
|
|
AETHERA_PROP(Waterfall, visible_range_only), AETHERA_PROP(Waterfall, frequency_range),
|
|
|
|
|
AETHERA_PROP(Waterfall, power_range), AETHERA_PROP(Waterfall, partition_mode),
|
|
|
|
|
AETHERA_PROP(Waterfall, interpolation_mode), AETHERA_PROP(Waterfall, color_map),
|
|
|
|
|
AETHERA_PROP(Waterfall, rows), AETHERA_STATE(Waterfall, row_count),
|
|
|
|
|
AETHERA_STATE(Waterfall, stored_point_count), AETHERA_STATE(Waterfall, rendered_cell_count)>(
|
|
|
|
|
*object, std::move(owners), std::move(update));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
} else if constexpr (std::same_as<Definition, Constellation_Diagram>) {
|
|
|
|
|
result.horizontal = build<Numeric_Axis_Object>(); configure_axis(result.horizontal.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); result.horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-1.2, 1.2}); make_vertical({-1.2, 1.2}); auto object = build<Impl<Constellation_Diagram>>(result.scene.get(), result.horizontal.get(), result.vertical.get()); object->set<&Constellation_Diagram::Prop::i_range>(Axis_Range{-1.2, 1.2}); object->set<&Constellation_Diagram::Prop::q_range>(Axis_Range{-1.2, 1.2}); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_renderable_adapter<Constellation_Diagram, AETHERA_PROP(Constellation_Diagram, point_lifetime_ms), AETHERA_PROP(Constellation_Diagram, type), AETHERA_PROP(Constellation_Diagram, phase_offset_radians), AETHERA_PROP(Constellation_Diagram, i_range), AETHERA_PROP(Constellation_Diagram, q_range), AETHERA_PROP(Constellation_Diagram, point_color), AETHERA_PROP(Constellation_Diagram, anchor_color), AETHERA_PROP(Constellation_Diagram, points), AETHERA_STATE(Constellation_Diagram, point_count)>(result, *raw); result.update = [raw](double time) { const double phase = time * 0.003; raw->append_point({std::cos(phase) * 0.82 + 0.04 * std::sin(phase * 7.0), std::sin(phase) * 0.82 + 0.04 * std::cos(phase * 5.0)}); }; result.plot = std::move(object);
|
|
|
|
|
auto horizontal = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, {-1.2, 1.2}, canvas));
|
|
|
|
|
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}, canvas));
|
|
|
|
|
Impl<Constellation_Diagram>::Builder builder(scene.get(), horizontal.get(), vertical.get());
|
|
|
|
|
builder
|
|
|
|
|
.set(&Constellation_Diagram::Prop::i_range, Axis_Range{-1.2, 1.2})
|
|
|
|
|
.set(&Constellation_Diagram::Prop::q_range, Axis_Range{-1.2, 1.2});
|
|
|
|
|
auto object = finish_shared<Impl<Constellation_Diagram>>(std::move(builder));
|
|
|
|
|
object->mark_dirty<Prepare_Data_Tag>();
|
|
|
|
|
object->mark_dirty<Paint_Tag>();
|
|
|
|
|
auto update = [raw = object.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Event& event) {
|
|
|
|
|
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
|
|
|
|
|
const double phase = event.time_milliseconds * 0.003;
|
|
|
|
|
raw->append_point({std::cos(phase) * 0.82 + 0.04 * std::sin(phase * 7.0),
|
|
|
|
|
std::sin(phase) * 0.82 + 0.04 * std::cos(phase * 5.0)});
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{horizontal, vertical, object};
|
|
|
|
|
auto view = make_scene_view<Constellation_Diagram,
|
|
|
|
|
AETHERA_PROP(Constellation_Diagram, point_lifetime_ms), AETHERA_PROP(Constellation_Diagram, type),
|
|
|
|
|
AETHERA_PROP(Constellation_Diagram, phase_offset_radians), AETHERA_PROP(Constellation_Diagram, i_range),
|
|
|
|
|
AETHERA_PROP(Constellation_Diagram, q_range), AETHERA_PROP(Constellation_Diagram, point_color),
|
|
|
|
|
AETHERA_PROP(Constellation_Diagram, anchor_color), AETHERA_PROP(Constellation_Diagram, points),
|
|
|
|
|
AETHERA_STATE(Constellation_Diagram, point_count)>(*object, std::move(owners), std::move(update));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
} else if constexpr (std::same_as<Definition, Selection_Rectangle_Overlay>) {
|
|
|
|
|
result.horizontal = build<Numeric_Axis_Object>(); configure_axis(result.horizontal.get(), Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas); result.horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0}); make_vertical({0.0, 100.0}); auto object = build<Impl<Selection_Rectangle_Overlay>>(result.scene.get(), result.horizontal.get(), result.vertical.get()); auto* raw = object.get(); raw->mark_dirty<Prepare_Data_Tag>(); raw->mark_dirty<Paint_Tag>(); bind_renderable_adapter<Selection_Rectangle_Overlay, AETHERA_PROP(Selection_Rectangle_Overlay, label_font), AETHERA_PROP(Selection_Rectangle_Overlay, label_pen), AETHERA_PROP(Selection_Rectangle_Overlay, selection_brush), AETHERA_PROP(Selection_Rectangle_Overlay, selection_border_pen), AETHERA_PROP(Selection_Rectangle_Overlay, selected_regions), AETHERA_STATE(Selection_Rectangle_Overlay, selected_region_count)>(result, *raw); result.update = [](double) {}; result.plot = std::move(object);
|
|
|
|
|
} else {
|
|
|
|
|
static_assert(std::same_as<Definition, void>, "unsupported 2D Plot definition");
|
|
|
|
|
auto horizontal = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, {0.0, 100.0}, canvas));
|
|
|
|
|
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
|
|
|
|
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {0.0, 100.0}, canvas));
|
|
|
|
|
Impl<Selection_Rectangle_Overlay>::Builder builder(scene.get(), horizontal.get(), vertical.get());
|
|
|
|
|
auto object = finish_shared<Impl<Selection_Rectangle_Overlay>>(std::move(builder));
|
|
|
|
|
object->mark_dirty<Prepare_Data_Tag>();
|
|
|
|
|
object->mark_dirty<Paint_Tag>();
|
|
|
|
|
auto update = [horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Event& event) {
|
|
|
|
|
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{horizontal, vertical, object};
|
|
|
|
|
auto view = make_scene_view<Selection_Rectangle_Overlay,
|
|
|
|
|
AETHERA_PROP(Selection_Rectangle_Overlay, label_font),
|
|
|
|
|
AETHERA_PROP(Selection_Rectangle_Overlay, label_pen),
|
|
|
|
|
AETHERA_PROP(Selection_Rectangle_Overlay, selection_brush),
|
|
|
|
|
AETHERA_PROP(Selection_Rectangle_Overlay, selection_border_pen),
|
|
|
|
|
AETHERA_PROP(Selection_Rectangle_Overlay, selected_regions),
|
|
|
|
|
AETHERA_STATE(Selection_Rectangle_Overlay, selected_region_count)>(
|
|
|
|
|
*object, std::move(owners), std::move(update));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
struct Plot_3D {
|
|
|
|
|
std::unique_ptr<Impl<Point_Visual>> visual{}; /* 鐐瑰浘鍏冩潈濞佸璞°€?*/
|
|
|
|
|
std::unique_ptr<Impl<Render_Scene_3D>> scene{}; /* 寮傛 Datoviz Scene銆?*/
|
|
|
|
|
std::unique_ptr<detail::Renderable_Descriptor> descriptor{}; /* 鐩存帴璇诲啓 visual 鐨?Structive 鍗忚瑙嗗浘銆?*/
|
|
|
|
|
};
|
|
|
|
|
Plot_3D make_plot_3d() { Plot_3D result; result.visual = build<Impl<Point_Visual>>(); static_cast<void>(result.visual->update_items({render_3d::Point{.position = {-0.55F, -0.2F, 0.0F}, .color = Color::red_color(), .diameter_px = 24.0F}, render_3d::Point{.position = {0.0F, 0.5F, 0.0F}, .color = Color::green_color(), .diameter_px = 30.0F}, render_3d::Point{.position = {0.55F, -0.1F, 0.0F}, .color = Color{42, 120, 255, 255}, .diameter_px = 26.0F}})); bind_renderable_adapter<Point_Visual, AETHERA_PROP(Point_Visual, transform), AETHERA_PROP(Point_Visual, visible), AETHERA_PROP(Point_Visual, depth_test), AETHERA_PROP(Point_Visual, items), AETHERA_STATE(Point_Visual, item_count), AETHERA_STATE(Point_Visual, prepared_item_count), AETHERA_STATE(Point_Visual, prepared_revision)>(result, *result.visual); result.visual->advance(); result.scene = build<Impl<Render_Scene_3D>>(result.visual.get()); result.scene->activate_view(); return result; }
|
|
|
|
|
using Plot_Engine = std::variant<Plot_2D, Plot_3D>;
|
|
|
|
|
template <typename Definition>
|
|
|
|
|
Plot_Engine make_plot_engine_2d() { return Plot_Engine{std::in_place_type<Plot_2D>, make_plot_2d<Definition>()}; }
|
|
|
|
|
Plot_Engine make_plot_engine_3d() { return Plot_Engine{std::in_place_type<Plot_3D>, make_plot_3d()}; }
|
|
|
|
|
|
|
|
|
|
#undef AETHERA_STATE
|
|
|
|
|
#undef AETHERA_PROP
|
|
|
|
|
}
|
|
|
|
|
namespace {
|
|
|
|
|
struct Plot_Catalog_Entry {
|
|
|
|
|
std::string_view id; /* URL 与 WebSocket 共用的稳定标识。 */
|
|
|
|
|
std::string_view title; /* Gallery 展示名称。 */
|
|
|
|
|
std::string_view category; /* Gallery 分类。 */
|
|
|
|
|
std::string_view description; /* Gallery 用途说明。 */
|
|
|
|
|
std::string_view dimension; /* Gallery 维度标签。 */
|
|
|
|
|
Plot_Engine (*build)(); /* 直接构造完整 Scene/Renderable/descriptor 的 typed factory。 */
|
|
|
|
|
};
|
|
|
|
|
constexpr std::array plot_catalog{
|
|
|
|
|
Plot_Catalog_Entry{"spectrum", "Spectrum", "Curves", "Current, maximum and minimum spectrum curves with markers.", "2D", &make_plot_engine_2d<Spectrum>},
|
|
|
|
|
Plot_Catalog_Entry{"frequency_trace", "Frequency trace", "Curves", "Time ordered frequency samples rendered as a partitioned curve.", "2D", &make_plot_engine_2d<Frequency_Trace>},
|
|
|
|
|
Plot_Catalog_Entry{"sweep_spectrum", "Sweep spectrum", "Curves", "Incremental sweep blocks composed into one frequency curve.", "2D", &make_plot_engine_2d<Sweep_Spectrum>},
|
|
|
|
|
Plot_Catalog_Entry{"afterglow", "Afterglow", "Raster", "Persistent spectrum energy rendered as reusable color blocks.", "2D", &make_plot_engine_2d<Afterglow>},
|
|
|
|
|
Plot_Catalog_Entry{"waterfall", "Waterfall", "Raster", "Time ordered spectrum rows rendered as a color raster.", "2D", &make_plot_engine_2d<Waterfall>},
|
|
|
|
|
Plot_Catalog_Entry{"constellation", "Constellation", "Signals", "I/Q samples and modulation anchors.", "2D", &make_plot_engine_2d<Constellation_Diagram>},
|
|
|
|
|
Plot_Catalog_Entry{"selection_overlay", "Selection overlay", "Interaction", "Direct-paint selection rectangle over numeric axes.", "2D", &make_plot_engine_2d<Selection_Rectangle_Overlay>},
|
|
|
|
|
Plot_Catalog_Entry{"datoviz_point", "Datoviz point", "3D", "Asynchronous Vulkan point visual with GPU readback.", "3D", &make_plot_engine_3d}
|
|
|
|
|
};
|
|
|
|
|
const Plot_Catalog_Entry* find_plot(std::string_view id) {
|
|
|
|
|
const auto found = std::ranges::find(plot_catalog, id, &Plot_Catalog_Entry::id);
|
|
|
|
|
return found == plot_catalog.end() ? nullptr : &*found;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Plot::Private {
|
|
|
|
|
asio::strand<asio::any_io_executor> strand; /* 寮曟搸鍙?descriptor 鐨勪覆琛岃闂煙銆?*/
|
|
|
|
|
asio::experimental::concurrent_channel<void(asio::error_code, Plot_Input)> inputs; /* UI 杈撳叆闃熷垪銆?*/
|
|
|
|
|
Plot_Engine engine; /* Builder 已完成的 Scene、Renderable 与 adapter 唯一所有权。 */
|
|
|
|
|
std::mutex handlers_mutex; /* 淇濇姢璺?Drogon 绾跨▼鐨勫抚璁㈤槄闆嗗悎銆?*/
|
|
|
|
|
std::unordered_map<const void*, Frame_Handler> handlers; /* 褰撳墠 WebSocket 甯ф秷璐硅€呫€?*/
|
|
|
|
|
std::atomic_uint64_t frame_sequence{}; /* 浜岃繘鍒跺抚鍗忚搴忓彿銆?*/
|
|
|
|
|
explicit Private(asio::any_io_executor executor, Plot_Engine value) : strand(asio::make_strand(std::move(executor))), inputs(strand, 32), engine(std::move(value)) {}
|
|
|
|
|
void publish(std::string pixels) { std::vector<Frame_Handler> outputs; { std::lock_guard lock(handlers_mutex); outputs.reserve(handlers.size()); for (const auto& [owner, handler] : handlers) outputs.push_back(handler); } for (auto& output : outputs) output(pixels); }
|
|
|
|
|
detail::Renderable_Descriptor& descriptor() { return std::visit([](auto& value) -> detail::Renderable_Descriptor& { return *value.descriptor; }, engine); }
|
|
|
|
|
using Scene = std::variant<std::unique_ptr<Scene_2D>, std::unique_ptr<Scene_3D>>;
|
|
|
|
|
asio::strand<asio::any_io_executor> strand;
|
|
|
|
|
asio::experimental::concurrent_channel<void(asio::error_code, Plot_Input)> inputs;
|
|
|
|
|
Scene scene;
|
|
|
|
|
std::unique_ptr<Scene_View> view;
|
|
|
|
|
std::once_flag start_once;
|
|
|
|
|
std::mutex handlers_mutex;
|
|
|
|
|
std::unordered_map<const void*, Frame_Handler> handlers;
|
|
|
|
|
std::atomic_uint64_t frame_sequence{};
|
|
|
|
|
|
|
|
|
|
template <typename Scene_Object>
|
|
|
|
|
Private(asio::any_io_executor executor,
|
|
|
|
|
std::unique_ptr<Scene_Object> value_scene,
|
|
|
|
|
std::unique_ptr<Scene_View> value_view)
|
|
|
|
|
: strand(asio::make_strand(std::move(executor))), inputs(strand, 32),
|
|
|
|
|
scene(std::move(value_scene)), view(std::move(value_view)) {}
|
|
|
|
|
|
|
|
|
|
void publish(std::string pixels) {
|
|
|
|
|
std::vector<Frame_Handler> outputs;
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(handlers_mutex);
|
|
|
|
|
outputs.reserve(handlers.size());
|
|
|
|
|
for (const auto& [owner, handler] : handlers) outputs.push_back(handler);
|
|
|
|
|
}
|
|
|
|
|
for (auto& output : outputs) output(pixels);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Plot::Plot(std::unique_ptr<Private> private_data) : d(std::move(private_data)) {}
|
|
|
|
|
Plot::Plot(asio::any_io_executor executor,
|
|
|
|
|
std::unique_ptr<Scene_2D> scene,
|
|
|
|
|
std::unique_ptr<Scene_View> view)
|
|
|
|
|
: d(std::make_unique<Private>(std::move(executor), std::move(scene), std::move(view))) {}
|
|
|
|
|
|
|
|
|
|
Plot::Plot(asio::any_io_executor executor,
|
|
|
|
|
std::unique_ptr<Scene_3D> scene,
|
|
|
|
|
std::unique_ptr<Scene_View> view)
|
|
|
|
|
: d(std::make_unique<Private>(std::move(executor), std::move(scene), std::move(view))) {}
|
|
|
|
|
|
|
|
|
|
Plot::~Plot() { d->inputs.close(); }
|
|
|
|
|
void Plot::start() {
|
|
|
|
|
auto self = shared_from_this();
|
|
|
|
|
if (auto* engine_2d = std::get_if<Plot_2D>(&d->engine)) {
|
|
|
|
|
engine_2d->scene->set_frame_callback([weak = weak_from_this()](Image_View image) {
|
|
|
|
|
if (auto owner = weak.lock()) {
|
|
|
|
|
const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1;
|
|
|
|
|
owner->d->publish(encode_frame(image, sequence));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
std::get<Plot_3D>(d->engine).scene->set_frame_callback([weak = weak_from_this()](std::shared_ptr<const render_3d::Pixel_Frame> frame) {
|
|
|
|
|
if (auto owner = weak.lock()) {
|
|
|
|
|
const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1;
|
|
|
|
|
owner->d->publish(encode_frame(*frame, sequence));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
asio::co_spawn(d->strand, [self]() -> asio::awaitable<void> {
|
|
|
|
|
for (;;) {
|
|
|
|
|
asio::error_code error;
|
|
|
|
|
auto input = co_await self->d->inputs.async_receive(asio::redirect_error(asio::use_awaitable, error));
|
|
|
|
|
if (error) co_return;
|
|
|
|
|
if (auto* query = std::get_if<Schema_Query>(&input)) {
|
|
|
|
|
query->handler(self->d->descriptor().schema());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (auto* write = std::get_if<Prop_Write>(&input)) {
|
|
|
|
|
write->handler(self->d->descriptor().write_prop(write->key, write->value));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const auto value = std::get<Plot_Event>(input);
|
|
|
|
|
if (auto* engine_2d = std::get_if<Plot_2D>(&self->d->engine)) {
|
|
|
|
|
const Size viewport{static_cast<int>(std::clamp(value.width, 160U, 1920U)), static_cast<int>(std::clamp(value.height, 120U, 1080U))};
|
|
|
|
|
engine_2d->scene->set<&Render_Scene_2D::Prop::viewport>(viewport);
|
|
|
|
|
if (engine_2d->frequency) engine_2d->frequency->set<&Abs_Axis::Prop::canvas_size>(viewport);
|
|
|
|
|
if (engine_2d->horizontal) engine_2d->horizontal->set<&Abs_Axis::Prop::canvas_size>(viewport);
|
|
|
|
|
if (engine_2d->vertical) engine_2d->vertical->set<&Abs_Axis::Prop::canvas_size>(viewport);
|
|
|
|
|
if (engine_2d->time) engine_2d->time->set<&Abs_Axis::Prop::canvas_size>(viewport);
|
|
|
|
|
engine_2d->update(value.time_milliseconds);
|
|
|
|
|
engine_2d->scene->render();
|
|
|
|
|
} else {
|
|
|
|
|
auto& engine_3d = std::get<Plot_3D>(self->d->engine);
|
|
|
|
|
engine_3d.scene->set<&Render_Scene_3D::Prop::viewport>(render_3d::Extent{std::clamp(value.width, 160U, 1920U), std::clamp(value.height, 120U, 1080U)});
|
|
|
|
|
engine_3d.scene->render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Plot::ensure_started() {
|
|
|
|
|
std::call_once(d->start_once, [this] {
|
|
|
|
|
auto self = shared_from_this();
|
|
|
|
|
if (auto* scene = std::get_if<std::unique_ptr<Scene_2D>>(&d->scene)) {
|
|
|
|
|
(*scene)->set_frame_callback([weak = weak_from_this()](Image_View image) {
|
|
|
|
|
if (auto owner = weak.lock()) {
|
|
|
|
|
const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1;
|
|
|
|
|
owner->d->publish(encode_frame(image, sequence));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
std::get<std::unique_ptr<Scene_3D>>(d->scene)->set_frame_callback(
|
|
|
|
|
[weak = weak_from_this()](std::shared_ptr<const Pixel_Frame> frame) {
|
|
|
|
|
if (auto owner = weak.lock()) {
|
|
|
|
|
const auto sequence = owner->d->frame_sequence.fetch_add(1, std::memory_order_acq_rel) + 1;
|
|
|
|
|
owner->d->publish(encode_frame(*frame, sequence));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, [](std::exception_ptr exception) {
|
|
|
|
|
if (exception) std::rethrow_exception(exception);
|
|
|
|
|
asio::co_spawn(d->strand, [self]() -> asio::awaitable<void> {
|
|
|
|
|
for (;;) {
|
|
|
|
|
asio::error_code error;
|
|
|
|
|
auto input = co_await self->d->inputs.async_receive(
|
|
|
|
|
asio::redirect_error(asio::use_awaitable, error));
|
|
|
|
|
if (error) co_return;
|
|
|
|
|
if (auto* query = std::get_if<Schema_Query>(&input)) {
|
|
|
|
|
query->handler(self->d->view->schema());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (auto* write = std::get_if<Prop_Write>(&input)) {
|
|
|
|
|
write->handler(self->d->view->write_prop(write->key, write->value));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
auto event = std::get<Plot_Event>(input);
|
|
|
|
|
event.width = std::clamp(event.width, 160U, 1920U);
|
|
|
|
|
event.height = std::clamp(event.height, 120U, 1080U);
|
|
|
|
|
self->d->view->update(event);
|
|
|
|
|
if (auto* scene = std::get_if<std::unique_ptr<Scene_2D>>(&self->d->scene)) {
|
|
|
|
|
(*scene)->set<&Render_Scene_2D::Prop::viewport>(
|
|
|
|
|
Size{static_cast<int>(event.width), static_cast<int>(event.height)});
|
|
|
|
|
(*scene)->render();
|
|
|
|
|
} else {
|
|
|
|
|
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(self->d->scene);
|
|
|
|
|
scene_3d->set<&Render_Scene_3D::Prop::viewport>(Extent{event.width, event.height});
|
|
|
|
|
scene_3d->render();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [](std::exception_ptr exception) {
|
|
|
|
|
if (exception) std::rethrow_exception(exception);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
void Plot::attach(const void* owner, Frame_Handler handler) { { std::lock_guard lock(d->handlers_mutex); d->handlers.insert_or_assign(owner, std::move(handler)); } submit({}); }
|
|
|
|
|
void Plot::detach(const void* owner) { std::lock_guard lock(d->handlers_mutex); d->handlers.erase(owner); }
|
|
|
|
|
void Plot::submit(Plot_Event event) { static_cast<void>(d->inputs.try_send(asio::error_code{}, Plot_Input{event})); }
|
|
|
|
|
void Plot::async_schema(Json_Handler handler) { if (!d->inputs.try_send(asio::error_code{}, Plot_Input{Schema_Query{std::move(handler)}})) throw std::runtime_error("plot input queue is unavailable"); }
|
|
|
|
|
void Plot::async_write_prop(std::string key, nlohmann::json value, Json_Handler handler) { if (!d->inputs.try_send(asio::error_code{}, Plot_Input{Prop_Write{std::move(key), std::move(value), std::move(handler)}})) throw std::runtime_error("plot input queue is unavailable"); }
|
|
|
|
|
|
|
|
|
|
struct Plot_Registry::Private { asio::any_io_executor executor; std::mutex mutex; std::unordered_map<std::string, std::shared_ptr<Plot>> plots; explicit Private(asio::any_io_executor value) : executor(std::move(value)) {} };
|
|
|
|
|
Plot_Registry::Plot_Registry(asio::any_io_executor executor) : d(std::make_unique<Private>(std::move(executor))) {}
|
|
|
|
|
Plot_Registry::~Plot_Registry() = default;
|
|
|
|
|
std::shared_ptr<Plot> Plot_Registry::acquire(std::string_view plot_id) { const auto* entry = find_plot(plot_id); if (!entry) return {}; std::lock_guard lock(d->mutex); auto& plot = d->plots[std::string(plot_id)]; if (!plot) { plot = std::shared_ptr<Plot>(new Plot(std::make_unique<Plot::Private>(d->executor, entry->build()))); plot->start(); } return plot; }
|
|
|
|
|
nlohmann::json Plot_Registry::catalog() const { nlohmann::json result = nlohmann::json::array(); for (const auto& entry : plot_catalog) result.push_back({{"id", entry.id}, {"title", entry.title}, {"category", entry.category}, {"description", entry.description}, {"dimension", entry.dimension}, {"websocket", "/ws/plot/" + std::string(entry.id)}, {"schema", "/plot/" + std::string(entry.id) + "/schema"}}); return result; }
|
|
|
|
|
void Plot::attach(const void* owner, Frame_Handler handler) {
|
|
|
|
|
ensure_started();
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock(d->handlers_mutex);
|
|
|
|
|
d->handlers.insert_or_assign(owner, std::move(handler));
|
|
|
|
|
}
|
|
|
|
|
submit({});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Plot::detach(const void* owner) {
|
|
|
|
|
std::lock_guard lock(d->handlers_mutex);
|
|
|
|
|
d->handlers.erase(owner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Plot::submit(Plot_Event event) {
|
|
|
|
|
ensure_started();
|
|
|
|
|
static_cast<void>(d->inputs.try_send(asio::error_code{}, Plot_Input{event}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Plot::async_schema(Json_Handler handler) {
|
|
|
|
|
ensure_started();
|
|
|
|
|
if (!d->inputs.try_send(asio::error_code{}, Plot_Input{Schema_Query{std::move(handler)}}))
|
|
|
|
|
throw std::runtime_error("plot input queue is unavailable");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Plot::async_write_prop(std::string key, nlohmann::json value, Json_Handler handler) {
|
|
|
|
|
ensure_started();
|
|
|
|
|
if (!d->inputs.try_send(asio::error_code{}, Plot_Input{
|
|
|
|
|
Prop_Write{std::move(key), std::move(value), std::move(handler)}}))
|
|
|
|
|
throw std::runtime_error("plot input queue is unavailable");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Plot> make_spectrum_plot(asio::any_io_executor executor) {
|
|
|
|
|
return build_2d_plot<Spectrum>(std::move(executor));
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<Plot> make_frequency_trace_plot(asio::any_io_executor executor) {
|
|
|
|
|
return build_2d_plot<Frequency_Trace>(std::move(executor));
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<Plot> make_sweep_spectrum_plot(asio::any_io_executor executor) {
|
|
|
|
|
return build_2d_plot<Sweep_Spectrum>(std::move(executor));
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<Plot> make_afterglow_plot(asio::any_io_executor executor) {
|
|
|
|
|
return build_2d_plot<Afterglow>(std::move(executor));
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<Plot> make_waterfall_plot(asio::any_io_executor executor) {
|
|
|
|
|
return build_2d_plot<Waterfall>(std::move(executor));
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<Plot> make_constellation_plot(asio::any_io_executor executor) {
|
|
|
|
|
return build_2d_plot<Constellation_Diagram>(std::move(executor));
|
|
|
|
|
}
|
|
|
|
|
std::shared_ptr<Plot> make_selection_overlay_plot(asio::any_io_executor executor) {
|
|
|
|
|
return build_2d_plot<Selection_Rectangle_Overlay>(std::move(executor));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Plot> make_datoviz_point_plot(asio::any_io_executor executor) {
|
|
|
|
|
using Visual_Object = Impl<Point_Visual>;
|
|
|
|
|
Visual_Object::Builder visual_builder;
|
|
|
|
|
auto visual = finish_shared<Visual_Object>(std::move(visual_builder));
|
|
|
|
|
static_cast<void>(visual->update_items({
|
|
|
|
|
render_3d::Point{.position = {-0.55F, -0.2F, 0.0F}, .color = Color::red_color(), .diameter_px = 24.0F},
|
|
|
|
|
render_3d::Point{.position = {0.0F, 0.5F, 0.0F}, .color = Color::green_color(), .diameter_px = 30.0F},
|
|
|
|
|
render_3d::Point{.position = {0.55F, -0.1F, 0.0F}, .color = Color{42, 120, 255, 255}, .diameter_px = 26.0F}}));
|
|
|
|
|
visual->advance();
|
|
|
|
|
Scene_3D::Builder scene_builder(visual.get());
|
|
|
|
|
scene_builder
|
|
|
|
|
.set(&Render_Scene_3D::Prop::viewport, Extent{720, 420})
|
|
|
|
|
.set(&Render_Scene_3D::Prop::view_active, true);
|
|
|
|
|
auto scene = finish_build(std::move(scene_builder));
|
|
|
|
|
using Adapter = detail::Renderable_Adapter<Visual_Object,
|
|
|
|
|
detail::Prop_Field<&Point_Visual::Prop::transform, "transform">,
|
|
|
|
|
detail::Prop_Field<&Point_Visual::Prop::visible, "visible">,
|
|
|
|
|
detail::Prop_Field<&Point_Visual::Prop::depth_test, "depth_test">,
|
|
|
|
|
detail::Prop_Field<&Point_Visual::Prop::items, "items">,
|
|
|
|
|
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::item_count, "item_count">,
|
|
|
|
|
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_item_count, "prepared_item_count">,
|
|
|
|
|
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_revision, "prepared_revision">>;
|
|
|
|
|
std::vector<std::shared_ptr<void>> owners{visual};
|
|
|
|
|
auto view = std::make_unique<Scene_View_Model>(
|
|
|
|
|
std::move(owners), detail::make_renderable_descriptor(Adapter{*visual}),
|
|
|
|
|
[](const Plot_Event&) {});
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|