Files
Aethera/mcp/core/runtime/Gallery_Plots_3D.cpp
2026-09-03 16:57:18 +08:00

951 lines
59 KiB
C++

#include "Gallery_Plots.hpp"
#include "Renderable_Adapter.hpp"
#include <render_3D/Render_3D.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <numbers>
#include <optional>
#include <random>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
namespace aethera::web {
namespace {
using namespace render_3d;
using Scene_3D = Render_Scene_3D;
using Json = nlohmann::json;
Json number_field(std::string key, std::string label, std::string description,
double value, double minimum, double maximum, double step) {
return {
{"key", std::move(key)}, {"label", std::move(label)}, {"description", std::move(description)},
{"editor", "number"}, {"editable", true}, {"value", value},
{"minimum", minimum}, {"maximum", maximum}, {"step", step}
};
}
Json integer_field(std::string key, std::string label, std::string description,
std::size_t value, std::size_t minimum, std::size_t maximum) {
auto field = number_field(std::move(key), std::move(label), std::move(description),
static_cast<double>(value), static_cast<double>(minimum),
static_cast<double>(maximum), 1.0);
field["editor"] = "integer";
return field;
}
template <typename Definition>
Json generator_schema() {
Json fields = Json::array();
if constexpr (std::same_as<Definition, Volume_Visual>) {
fields.push_back(integer_field("width", "体数据宽度", "体素网格 X 方向尺寸;总量为宽×高×深。", 64, 1, 256));
fields.push_back(integer_field("height", "体数据高度", "体素网格 Y 方向尺寸;总量为宽×高×深。", 64, 1, 256));
fields.push_back(integer_field("depth", "体数据深度", "体素网格 Z 方向尺寸;总量为宽×高×深。", 64, 1, 256));
fields.push_back(number_field("value_min", "体素值下界", "每个体素随机标量值的下界。", 0.0, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(number_field("value_max", "体素值上界", "每个体素随机标量值的上界,必须大于下界。", 1.0, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(integer_field("seed", "随机种子", "固定种子可复现体素压力数据,便于跨策略对比。", 42, 1, 4'294'967'295ULL));
return {{"label", "生成体素标量场"}, {"description", "按三维网格尺寸生成连续体数据,不使用随机位置。"}, {"fields", std::move(fields)}};
}
fields.push_back(integer_field("count", "图元数量", "本次替换到 Visual 的图元数量;用于逐级提升 CPU Prepare、GPU 上传与绘制压力。", 10'000, 1, 5'000'000));
fields.push_back(integer_field("seed", "随机种子", "固定种子可复现相同空间分布,确保多图与传输模式的性能结果可比较。", 42, 1, 4'294'967'295ULL));
fields.push_back(number_field("x_min", "Scene X 下界", "随机位置在 Scene X 轴上的下界。", -1.0, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(number_field("x_max", "Scene X 上界", "随机位置在 Scene X 轴上的上界,必须大于下界。", 1.0, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(number_field("y_min", "Scene Y 下界", "随机位置在 Scene Y 轴上的下界。", -1.0, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(number_field("y_max", "Scene Y 上界", "随机位置在 Scene Y 轴上的上界,必须大于下界。", 1.0, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(number_field("z_min", "Scene Z 下界", "随机位置在 Scene Z 轴上的下界。", -1.0, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(number_field("z_max", "Scene Z 上界", "随机位置在 Scene Z 轴上的上界,必须大于下界。", 1.0, -1'000'000.0, 1'000'000.0, 0.01));
std::string label{"生成三维图元"};
std::string description{"按各轴独立范围随机生成 Scene 坐标。"};
if constexpr (std::same_as<Definition, Point_Visual>) {
label = "生成三维点";
fields.push_back(number_field("diameter_min", "点直径下界", "随机点直径下界,单位为屏幕像素。", 2.0, 0.1, 4096.0, 0.1));
fields.push_back(number_field("diameter_max", "点直径上界", "随机点直径上界,单位为屏幕像素。", 12.0, 0.1, 4096.0, 0.1));
}
else if constexpr (std::same_as<Definition, Splat_Visual>) {
label = "生成三维高斯 Splat";
fields.push_back(number_field("sigma_min", "标准差下界", "高斯主轴标准差下界,使用 Scene 坐标。", 0.01, 0.0001, 1000.0, 0.001));
fields.push_back(number_field("sigma_max", "标准差上界", "高斯主轴标准差上界,使用 Scene 坐标。", 0.08, 0.0001, 1000.0, 0.001));
fields.push_back(number_field("angle_min", "旋转角下界", "Splat 主轴旋转角下界,单位为弧度。", -3.14159, -1000.0, 1000.0, 0.01));
fields.push_back(number_field("angle_max", "旋转角上界", "Splat 主轴旋转角上界,单位为弧度。", 3.14159, -1000.0, 1000.0, 0.01));
}
else if constexpr (std::same_as<Definition, Pixel_Visual>) {
label = "生成三维像素";
fields.push_back(number_field("size_min", "像素边长下界", "方形像素边长下界,单位为屏幕像素。", 1.0, 0.1, 4096.0, 0.1));
fields.push_back(number_field("size_max", "像素边长上界", "方形像素边长上界,单位为屏幕像素。", 6.0, 0.1, 4096.0, 0.1));
}
else if constexpr (std::same_as<Definition, Marker_Visual>) {
label = "生成三维标记";
fields.push_back(number_field("diameter_min", "标记直径下界", "标记直径下界,单位为屏幕像素。", 4.0, 0.1, 4096.0, 0.1));
fields.push_back(number_field("diameter_max", "标记直径上界", "标记直径上界,单位为屏幕像素。", 18.0, 0.1, 4096.0, 0.1));
}
else if constexpr (std::same_as<Definition, Sphere_Visual>) {
label = "生成三维球体";
fields.push_back(number_field("radius_min", "球体半径下界", "球体半径下界,使用 Scene 坐标。", 0.01, 0.0001, 1000.0, 0.001));
fields.push_back(number_field("radius_max", "球体半径上界", "球体半径上界,使用 Scene 坐标。", 0.08, 0.0001, 1000.0, 0.001));
}
else if constexpr (std::same_as<Definition, Segment_Visual>) {
label = "生成三维线段";
description = "起点和终点分别在各轴范围内随机生成。";
fields.push_back(number_field("width_min", "线宽下界", "线段宽度下界,单位为屏幕像素。", 1.0, 0.1, 4096.0, 0.1));
fields.push_back(number_field("width_max", "线宽上界", "线段宽度上界,单位为屏幕像素。", 5.0, 0.1, 4096.0, 0.1));
}
else if constexpr (std::same_as<Definition, Vector_Visual>) {
label = "生成三维向量";
description = "原点按 Scene 范围随机生成,方向分量使用单独范围。";
fields.push_back(number_field("direction_min", "方向分量下界", "向量 X/Y/Z 方向分量的随机下界。", -0.3, -1'000'000.0, 1'000'000.0, 0.01));
fields.push_back(number_field("direction_max", "方向分量上界", "向量 X/Y/Z 方向分量的随机上界。", 0.3, -1'000'000.0, 1'000'000.0, 0.01));
}
else if constexpr (std::same_as<Definition, Primitive_Visual>) label = "生成三维图元顶点";
else if constexpr (std::same_as<Definition, Mesh_Visual>) label = "生成三维网格顶点";
else if constexpr (std::same_as<Definition, Path_Visual>) {
label = "生成三维路径顶点";
fields.push_back(number_field("width_min", "路径宽度下界", "路径宽度下界,单位为屏幕像素。", 1.0, 0.1, 4096.0, 0.1));
fields.push_back(number_field("width_max", "路径宽度上界", "路径宽度上界,单位为屏幕像素。", 5.0, 0.1, 4096.0, 0.1));
}
else if constexpr (std::same_as<Definition, Image_Visual>) {
label = "生成三维图像实例";
fields.push_back(number_field("extent_min", "图像尺寸下界", "图像宽高的 Scene 坐标下界。", 0.02, 0.0001, 1000.0, 0.001));
fields.push_back(number_field("extent_max", "图像尺寸上界", "图像宽高的 Scene 坐标上界。", 0.2, 0.0001, 1000.0, 0.001));
}
else if constexpr (std::same_as<Definition, Labels_Visual>) {
label = "生成三维标签实例";
fields.push_back(number_field("extent_min", "标签尺寸下界", "标签宽高的屏幕像素下界。", 8.0, 0.1, 4096.0, 0.1));
fields.push_back(number_field("extent_max", "标签尺寸上界", "标签宽高的屏幕像素上界。", 48.0, 0.1, 4096.0, 0.1));
}
else if constexpr (std::same_as<Definition, Glyph_Visual>) {
label = "生成三维字形实例";
fields.push_back(number_field("angle_min", "字形旋转下界", "字形旋转角下界,单位为弧度。", -3.14159, -1000.0, 1000.0, 0.01));
fields.push_back(number_field("angle_max", "字形旋转上界", "字形旋转角上界,单位为弧度。", 3.14159, -1000.0, 1000.0, 0.01));
}
else if constexpr (std::same_as<Definition, Text_Visual>) {
label = "生成三维文本实例";
fields.push_back(number_field("size_min", "字号下界", "随机文本字号下界,单位为屏幕像素。", 10.0, 0.1, 4096.0, 0.1));
fields.push_back(number_field("size_max", "字号上界", "随机文本字号上界,单位为屏幕像素。", 28.0, 0.1, 4096.0, 0.1));
}
return {{"label", std::move(label)}, {"description", std::move(description)}, {"fields", std::move(fields)}};
}
double input_number(const Json& input, std::string_view key) {
const auto& value = input.at(key);
if (!value.is_number()) throw std::invalid_argument(std::string(key) + " must be a number");
const auto result = value.get<double>();
if (!std::isfinite(result)) throw std::invalid_argument(std::string(key) + " must be finite");
return result;
}
std::size_t input_count(const Json& input, std::string_view key, std::size_t maximum = 1'000'000) {
const auto value = input_number(input, key);
if (value < 1.0 || value > static_cast<double>(maximum) || std::floor(value) != value) throw std::invalid_argument(std::string(key) + " is outside the supported integer range");
return static_cast<std::size_t>(value);
}
std::pair<float, float> input_range(const Json& input, std::string_view minimum_key,
std::string_view maximum_key) {
const auto minimum = input_number(input, minimum_key);
const auto maximum = input_number(input, maximum_key);
if (minimum >= maximum) throw std::invalid_argument(std::string(maximum_key) + " must be greater than " + std::string(minimum_key));
if (minimum < -std::numeric_limits<float>::max() || maximum > std::numeric_limits<float>::max()) throw std::invalid_argument("generator range exceeds float coordinates");
return {static_cast<float>(minimum), static_cast<float>(maximum)};
}
template <typename Definition>
struct Item_Randomizer {
public:
explicit Item_Randomizer(const Json& input) {
const auto [x_min, x_max] = input_range(input, "x_min", "x_max");
const auto [y_min, y_max] = input_range(input, "y_min", "y_max");
const auto [z_min, z_max] = input_range(input, "z_min", "z_max");
x = std::uniform_real_distribution<float>(x_min, x_max);
y = std::uniform_real_distribution<float>(y_min, y_max);
z = std::uniform_real_distribution<float>(z_min, z_max);
const auto set_first = [&](std::string_view minimum, std::string_view maximum) {
const auto [lower, upper] = input_range(input, minimum, maximum);
first = std::uniform_real_distribution<float>(lower, upper);
};
const auto set_second = [&](std::string_view minimum, std::string_view maximum) {
const auto [lower, upper] = input_range(input, minimum, maximum);
second = std::uniform_real_distribution<float>(lower, upper);
};
if constexpr (std::same_as<Definition, Point_Visual> || std::same_as<Definition, Marker_Visual>) set_first("diameter_min", "diameter_max");
else if constexpr (std::same_as<Definition, Splat_Visual>) {
set_first("sigma_min", "sigma_max");
set_second("angle_min", "angle_max");
}
else if constexpr (std::same_as<Definition, Pixel_Visual> || std::same_as<Definition, Text_Visual>) set_first("size_min", "size_max");
else if constexpr (std::same_as<Definition, Sphere_Visual>) set_first("radius_min", "radius_max");
else if constexpr (std::same_as<Definition, Segment_Visual> || std::same_as<Definition, Path_Visual>) set_first("width_min", "width_max");
else if constexpr (std::same_as<Definition, Vector_Visual>) set_first("direction_min", "direction_max");
else if constexpr (std::same_as<Definition, Image_Visual> || std::same_as<Definition, Labels_Visual>) set_first("extent_min", "extent_max");
else if constexpr (std::same_as<Definition, Glyph_Visual>) set_first("angle_min", "angle_max");
}
void operator()(typename Definition::Item& item, std::mt19937_64& engine) {
const auto vector = [&] {
return Vec3{x(engine), y(engine), z(engine)};
};
if constexpr (requires { item.position = vector(); }) item.position = vector();
else if constexpr (requires { item.center = vector(); }) item.center = vector();
else if constexpr (requires { item.origin = vector(); }) item.origin = vector();
else if constexpr (requires { item.start = vector(); item.end = vector(); }) {
item.start = vector();
item.end = vector();
}
if constexpr (std::same_as<Definition, Point_Visual>) item.diameter_px = first(engine);
else if constexpr (std::same_as<Definition, Splat_Visual>) {
item.sigma = {first(engine), first(engine)};
item.angle = second(engine);
}
else if constexpr (std::same_as<Definition, Pixel_Visual>) item.size_px = first(engine);
else if constexpr (std::same_as<Definition, Marker_Visual>) item.diameter_px = first(engine);
else if constexpr (std::same_as<Definition, Sphere_Visual>) item.radius = first(engine);
else if constexpr (std::same_as<Definition, Segment_Visual>) item.width_px = first(engine);
else if constexpr (std::same_as<Definition, Vector_Visual>) {
item.direction = {first(engine), first(engine), first(engine)};
}
else if constexpr (std::same_as<Definition, Path_Visual>) item.width_px = first(engine);
else if constexpr (std::same_as<Definition, Image_Visual> || std::same_as<Definition, Labels_Visual>) item.extent = {first(engine), first(engine)};
else if constexpr (std::same_as<Definition, Glyph_Visual>) item.angle = first(engine);
else if constexpr (std::same_as<Definition, Text_Visual>) item.size_px = first(engine);
}
private:
std::uniform_real_distribution<float> x{};
std::uniform_real_distribution<float> y{};
std::uniform_real_distribution<float> z{};
std::uniform_real_distribution<float> first{};
std::uniform_real_distribution<float> second{};
};
struct Random_Data_Generator {
static std::uint64_t mix(std::uint64_t value) noexcept {
value += 0x9E3779B97F4A7C15ULL;
value = (value ^ (value >> 30U)) * 0xBF58476D1CE4E5B9ULL;
value = (value ^ (value >> 27U)) * 0x94D049BB133111EBULL;
return value ^ (value >> 31U);
}
static float signed_unit(std::uint64_t sequence, std::size_t index,
std::uint64_t lane) noexcept {
const auto bits = mix(sequence ^
(static_cast<std::uint64_t>(index) << 8U) ^ lane);
return static_cast<float>(bits >> 40U) /
static_cast<float>(1U << 24U) * 2.0F - 1.0F;
}
static void push(Vec3& value, std::uint64_t sequence,
std::size_t index, std::uint64_t lane,
float amount = 0.018F) noexcept {
value.x = std::clamp(value.x + amount * signed_unit(sequence, index, lane),
-0.96F, 0.96F);
value.y = std::clamp(value.y + amount * signed_unit(sequence, index, lane + 1U),
-0.96F, 0.96F);
value.z = std::clamp(value.z + amount * signed_unit(sequence, index, lane + 2U),
-0.96F, 0.96F);
}
template <typename Visual_Object>
void update(Visual_Object& visual, Axes_3D&, Marker_Visual*,
const Gallery_Frame_Request& request) {
using Definition = Visual_Object;
const auto sequence = request.sequence != 0
? request.sequence
: static_cast<std::uint64_t>(request.time_milliseconds * 10.0);
using Items = std::remove_cvref_t<decltype(std::declval<typename Definition::Frame>().items)>;
Items items;
visual.template get<typename Definition::Frame_Tag>([&](const typename Definition::Frame& frame) { items = frame.items; });
for (std::size_t index = 0; index < items.size(); ++index) {
auto& item = items[index];
if constexpr (requires { item.position; })
push(item.position, sequence, index, 0U);
else if constexpr (requires { item.center; })
push(item.center, sequence, index, 0U);
else if constexpr (requires { item.origin; }) {
push(item.origin, sequence, index, 0U);
if constexpr (requires { item.direction; })
push(item.direction, sequence, index, 3U, 0.008F);
}
else if constexpr (requires { item.start; item.end; }) {
push(item.start, sequence, index, 0U);
push(item.end, sequence, index, 3U);
}
if constexpr (requires { item.value; })
item.value = std::clamp(
item.value + 0.07F * signed_unit(sequence, index, 7U),
0.0F, 1.0F);
if constexpr (std::same_as<Definition, Text_Visual>) {
if (index == 0)
item.text = "Aethera frame " +
std::to_string(sequence % 10'000U);
}
}
visual.template set<typename Definition::Frame_Tag>([items = std::move(items)](typename Definition::Frame& frame) mutable { frame.items = std::move(items); });
}
};
template <typename Visual_Object, typename Data_Generator = Random_Data_Generator>
struct Visual_Gallery_Component final : public Gallery_Component {
public:
using Camera_Object = Camera_3D;
using Axes_Object = Axes_3D;
using Marker_Object = Marker_Visual;
Visual_Gallery_Component(Scene_3D& scene, std::unique_ptr<Camera_Object> camera,
std::unique_ptr<Axes_Object> axes,
std::unique_ptr<Visual_Object> visual, std::string label,
Data_Generator data_generator = {},
std::unique_ptr<Marker_Object> markers = {}) : data_generator_(std::move(data_generator)), camera_(std::move(camera)),
axes_(std::move(axes)), visual_(std::move(visual)), markers_(std::move(markers)) {
using Definition = Visual_Object;
using Prop = typename Definition::Prop;
using State = typename Definition::State;
using Scene_Adapter = detail::Renderable_Adapter<Scene_3D,
detail::Prop_Field < &Render_Scene_3D::Prop::clear_color, "clear_color", "Linear scene clear color.">,
detail::Prop_Field < &Render_Scene_3D::Prop::view_active, "view_active", "Whether the scene publishes rendered frames." >>;
using Visual_Adapter = detail::Renderable_Adapter<Visual_Object,
detail::Prop_Field < &Prop::transform, "transform", "World transform applied to the complete visual.">,
detail::Prop_Field < &Prop::visible, "visible", "Whether the visual participates in rendering." >,
detail::Prop_Field < &Prop::depth_test, "depth_test", "Whether fragments use depth testing." >,
detail::State_Field < State_Tag, &State::item_count, "item_count", "Number of published input items." >,
detail::State_Field < State_Tag, &State::prepared_item_count, "prepared_item_count", "Number of prepared backend items." >,
detail::State_Field<State_Tag, &State::prepared_revision, "prepared_revision", "Property revision represented by prepared GPU data."> >;
using Camera_Adapter = detail::Renderable_Adapter<Camera_Object,
detail::Prop_Field < &Camera_3D::Prop::initial_view, "initial_view", "Initial eye, target and world-up vectors used by reset view.">,
detail::Prop_Field < &Camera_3D::Prop::projection, "projection", "Perspective or orthographic camera projection." >,
detail::Prop_Field < &Camera_3D::Prop::controller, "controller", "Datoviz native camera controller: turntable, arcball, fly or panzoom." >,
detail::Prop_Field < &Camera_3D::Prop::turntable_control, "turntable_control", "Turntable orbit, zoom and pan speeds and limits." >,
detail::Prop_Field < &Camera_3D::Prop::arcball_control, "arcball_control", "Arcball free rotation and optional constraint axis." >,
detail::Prop_Field < &Camera_3D::Prop::fly_control, "fly_control", "Fly camera movement mode, keyboard speed and pointer look settings." >,
detail::Prop_Field < &Camera_3D::Prop::panzoom_control, "panzoom_control", "Planar panzoom axis locks and aspect-ratio policy." >,
detail::Prop_Field < &Camera_3D::Prop::vertical_field_of_view_degrees, "vertical_field_of_view_degrees", "Vertical field of view in degrees." >,
detail::Prop_Field < &Camera_3D::Prop::near_plane, "near_plane", "Nearest visible camera distance." >,
detail::Prop_Field<&Camera_3D::Prop::far_plane, "far_plane", "Farthest visible camera distance."> >;
using Axes_Adapter = detail::Renderable_Adapter<Axes_Object,
detail::Prop_Field < &Axes_3D::Prop::x_axis, "x_axis", "X axis range, scale, ticks, label and unit.", "axis3d">,
detail::Prop_Field < &Axes_3D::Prop::y_axis, "y_axis", "Y axis range, scale, ticks, label and unit.", "axis3d" >,
detail::Prop_Field<&Axes_3D::Prop::z_axis, "z_axis", "Z axis range, scale, ticks, label and unit.", "axis3d"> >;
descriptors_.push_back(detail::make_renderable_descriptor("scene", "3D 场景", "scene", Scene_Adapter{scene}));
descriptors_.push_back(detail::make_renderable_descriptor("camera", "相机控制", "camera", Camera_Adapter{*camera_}));
descriptors_.push_back(detail::make_renderable_descriptor("axes", "三维坐标轴", "axes", Axes_Adapter{*axes_}));
if constexpr (std::same_as<Definition, Marker_Visual>) {
using Marker_Adapter = detail::Renderable_Adapter<Visual_Object,
detail::Prop_Field < &Prop::transform, "transform", "World transform applied to the complete marker collection." >,
detail::Prop_Field < &Prop::visible, "visible", "Whether markers participate in rendering." >,
detail::Prop_Field < &Prop::depth_test, "depth_test", "Whether marker fragments use depth testing." >,
detail::State_Field < State_Tag, &State::item_count, "item_count", "Number of markers in the Scene." >,
detail::State_Field < State_Tag, &State::prepared_item_count, "prepared_item_count", "Number of prepared marker items." >,
detail::State_Field<State_Tag, &State::prepared_revision, "prepared_revision", "Marker GPU data revision."> >;
descriptors_.push_back(detail::make_renderable_descriptor(
"visual", std::move(label), "marker", Marker_Adapter{*visual_}));
}
else {
descriptors_.push_back(detail::make_renderable_descriptor(
"visual", std::move(label), "visual", Visual_Adapter{*visual_}));
}
if (markers_) {
using Marker_Prop = Marker_Visual::Prop;
using Marker_State = Marker_Visual::State;
using Marker_Adapter = detail::Renderable_Adapter<Marker_Object,
detail::Prop_Field < &Marker_Prop::visible, "visible", "Whether the spectrogram marker layer is visible." >,
detail::Prop_Field < &Marker_Prop::depth_test, "depth_test", "Whether markers may be occluded by the surface." >,
detail::State_Field<State_Tag, &Marker_State::item_count, "item_count", "Number of spectrogram markers.">,
detail::State_Field<State_Tag, &Marker_State::prepared_item_count, "prepared_item_count", "Number of markers prepared for Datoviz.">,
detail::State_Field<State_Tag, &Marker_State::prepared_revision, "prepared_revision", "Marker GPU data revision."> >;
descriptors_.push_back(detail::make_renderable_descriptor(
"markers", "频谱标记", "marker", Marker_Adapter{*markers_}));
}
}
[[nodiscard]] nlohmann::json schema() const override {
nlohmann::json components = nlohmann::json::array();
for (const auto& descriptor : descriptors_) components.push_back(descriptor->schema());
return {{"protocol", "aethera.plot.inspector"}, {"version", 3}, {"components", std::move(components)}};
}
[[nodiscard]] nlohmann::json write_prop(std::string_view component, std::string_view key, const nlohmann::json& value) override {
const auto found = std::ranges::find_if(descriptors_, [&](const auto& descriptor) {
return descriptor->id() == component;
});
if (found == descriptors_.end()) return {{"success", false}, {"error", "unknown component"}};
auto result = (*found)->write_prop(key, value);
result["component"] = component;
return result;
}
[[nodiscard]] nlohmann::json component_state(std::string_view component) const override {
const auto found = std::ranges::find_if(descriptors_, [&](const auto& descriptor) {
return descriptor->id() == component;
});
if (found == descriptors_.end())
return {{"success", false}, {"error", "unknown component"}};
return (*found)->state();
}
[[nodiscard]] nlohmann::json capture_components(
const std::vector<std::string>& components) const override {
nlohmann::json result = nlohmann::json::object();
for (const auto& component : components) {
const auto found = std::ranges::find_if(descriptors_, [&](const auto& descriptor) {
return descriptor->id() == component;
});
if (found != descriptors_.end())
result[component] = (*found)->values();
}
return result;
}
[[nodiscard]] nlohmann::json data_generator_schema() const override {
if constexpr (std::same_as<Data_Generator, Random_Data_Generator>) {
using Definition = Visual_Object;
return generator_schema<Definition>();
}
else {
return data_generator_.schema();
}
}
[[nodiscard]] nlohmann::json generate_data(const nlohmann::json& input) override {
if constexpr (!std::same_as<Data_Generator, Random_Data_Generator>) {
return data_generator_.generate(*visual_, *axes_, input);
}
else {
using Definition = Visual_Object;
using Frame = typename Definition::Frame;
using Items = std::remove_cvref_t<decltype(std::declval<Frame>().items)>;
try {
std::mt19937_64 engine{input_count(input, "seed", 4'294'967'295ULL)};
Items generated;
std::size_t count{};
std::uint32_t field_width{};
std::uint32_t field_height{};
std::uint32_t field_depth{};
if constexpr (std::same_as<Definition, Volume_Visual>) {
const auto width = input_count(input, "width", 256);
const auto height = input_count(input, "height", 256);
const auto depth = input_count(input, "depth", 256);
if (width > 1'000'000 / height || width * height > 1'000'000 / depth) throw std::invalid_argument("volume dimensions exceed 1,000,000 voxels");
count = width * height * depth;
const auto [minimum, maximum] = input_range(input, "value_min", "value_max");
std::uniform_real_distribution<float> distribution(minimum, maximum);
generated.resize(count);
for (auto& item : generated) item.value = distribution(engine);
field_width = static_cast<std::uint32_t>(width);
field_height = static_cast<std::uint32_t>(height);
field_depth = static_cast<std::uint32_t>(depth);
}
else {
count = input_count(input, "count", 5'000'000);
std::optional<typename Items::value_type> prototype;
visual_->template get<typename Definition::Frame_Tag>([&](const Frame& frame) {
if (!frame.items.empty()) prototype = frame.items.front();
});
if (!prototype) throw std::invalid_argument("visual has no item template");
Item_Randomizer<Definition> randomize(input);
generated.reserve(count);
for (std::size_t index = 0; index < count; ++index) {
auto item = *prototype;
randomize(item, engine);
generated.push_back(std::move(item));
}
}
visual_->template set<typename Definition::Frame_Tag>([generated = std::move(generated), field_width, field_height, field_depth](Frame& frame) mutable {
frame.items = std::move(generated);
if constexpr (std::same_as<Definition, Volume_Visual>) {
frame.field_width = field_width;
frame.field_height = field_height;
frame.field_depth = field_depth;
}
});
return {{"success", true}, {"generated_count", count}};
}
catch (const std::exception& error) {
return {{"success", false}, {"error", error.what()}};
}
}
}
void update(const Gallery_Frame_Request& request) override {
if constexpr (requires(Data_Generator& generator, Visual_Object& visual,
Axes_Object& axes,
Marker_Object* markers,
const Gallery_Frame_Request& frame) {
generator.update(visual, axes, markers, frame);
}) {
data_generator_.update(
*visual_, *axes_, markers_.get(), request);
}
}
private:
[[no_unique_address]] Data_Generator data_generator_; /* Plot 业务数据生成策略。 */
std::unique_ptr<Camera_Object> camera_; /* Scene 引用的 Camera 唯一所有者。 */
std::unique_ptr<Axes_Object> axes_; /* Scene 引用的 Axes 唯一所有者。 */
std::unique_ptr<Visual_Object> visual_; /* Scene 引用的 Visual 唯一所有者。 */
std::unique_ptr<Marker_Object> markers_; /* 可选的独立 Marker Visual 所有者。 */
std::vector<std::unique_ptr<detail::Renderable_Descriptor>> descriptors_; /* Prop/State 协议描述。 */
};
struct Scene_Components_3D {
Camera_Descriptor camera{};
std::array<plot::Axis_Descriptor, 3> axes{
plot::Axis_Descriptor{{-1.0, 1.0}, plot::Axis_Scale::linear, "X", "", 5, 2, true, true, true},
plot::Axis_Descriptor{{-1.0, 1.0}, plot::Axis_Scale::linear, "Y", "", 5, 2, true, true, true},
plot::Axis_Descriptor{{-1.0, 1.0}, plot::Axis_Scale::linear, "Z", "", 5, 2, true, true, true}
};
};
template <typename Visual>
std::unique_ptr<Visual> initialize_visual_frame(std::unique_ptr<Visual> visual, typename Visual::Frame frame) {
visual->template set<typename Visual::Frame_Tag>([frame = std::move(frame)](typename Visual::Frame& output) mutable { output = std::move(frame); });
return visual;
}
template <typename Definition, typename Data_Generator = Random_Data_Generator>
Gallery_Build make_visual_plot(std::string label, std::unique_ptr<Definition> visual, Scene_Components_3D components = {}, Data_Generator data_generator = {}, std::unique_ptr<Marker_Visual> markers = {}) {
using Visual_Object = Definition;
using Camera_Object = Camera_3D;
using Axes_Object = Axes_3D;
auto camera_result = Camera_Object::Builder{}
.set<Prop_Tag>(&Camera_3D::Prop::initial_view, components.camera.initial_view)
.set<Prop_Tag>(&Camera_3D::Prop::projection, components.camera.projection)
.set<Prop_Tag>(&Camera_3D::Prop::controller, components.camera.controller)
.set<Prop_Tag>(&Camera_3D::Prop::turntable_control, components.camera.turntable_control)
.set<Prop_Tag>(&Camera_3D::Prop::arcball_control, components.camera.arcball_control)
.set<Prop_Tag>(&Camera_3D::Prop::fly_control, components.camera.fly_control)
.set<Prop_Tag>(&Camera_3D::Prop::panzoom_control, components.camera.panzoom_control)
.set<Prop_Tag>(&Camera_3D::Prop::vertical_field_of_view_degrees,
components.camera.vertical_field_of_view_degrees)
.set<Prop_Tag>(&Camera_3D::Prop::near_plane, components.camera.near_plane)
.set<Prop_Tag>(&Camera_3D::Prop::far_plane, components.camera.far_plane)
.build();
auto axes_result = Axes_Object::Builder{}
.set<Prop_Tag>(&Axes_3D::Prop::x_axis, components.axes[0])
.set<Prop_Tag>(&Axes_3D::Prop::y_axis, components.axes[1])
.set<Prop_Tag>(&Axes_3D::Prop::z_axis, components.axes[2])
.build();
auto camera = std::move(camera_result);
auto axes = std::move(axes_result);
Dag_Relation<render_3d::scene::Renderable>::Builder dag_builder;
dag_builder.add({1}, model_proxy_view<render_3d::scene::Renderable>(*visual));
if (markers) dag_builder.add({2}, model_proxy_view<render_3d::scene::Renderable>(*markers));
auto graph = dag_builder.build();
if (!graph) throw std::logic_error("3D Gallery renderable DAG is invalid");
Scene_3D::Builder scene_builder(*camera, *axes);
scene_builder
.set<Prop_Tag>(&Render_Scene_3D::Prop::viewport, Extent{720, 420})
.set<Prop_Tag>(&Render_Scene_3D::Prop::clear_color, Linear_Color{0.018F, 0.027F, 0.047F, 1.0F})
.set<Prop_Tag>(&Render_Scene_3D::Prop::view_active, true)
.relation<aethera::scene::Renderable>([graph = std::move(*graph)](auto& relation) mutable { relation = std::move(graph); });
auto scene = scene_builder.build();
auto view = std::make_unique<Visual_Gallery_Component<Visual_Object, Data_Generator>>(
*scene, std::move(camera), std::move(axes), std::move(visual),
std::move(label), std::move(data_generator), std::move(markers));
return {std::move(view), Gallery_Scene{std::move(scene)}};
}
Color color(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t alpha = 255) {
return {red, green, blue, alpha};
}
struct Spectrogram_Parameters {
std::size_t time_sample_count{80};
std::size_t frequency_bin_count{96};
std::size_t ridge_count{5};
double time_span_seconds{4.0};
double minimum_frequency_hz{10.0};
double maximum_frequency_hz{20'000.0};
double minimum_level_db{18.0};
double maximum_level_db{78.0};
double animation_speed{1.0};
std::size_t update_every_n_frames{1};
bool animation_enabled{true};
};
Color spectrogram_color(float value) {
struct Stop {
float position;
std::array<float, 3> rgb;
};
static constexpr std::array stops{
Stop{0.0F, {22, 10, 54}}, Stop{0.22F, {76, 18, 112}},
Stop{0.45F, {151, 40, 103}}, Stop{0.68F, {226, 83, 61}},
Stop{0.86F, {252, 169, 52}}, Stop{1.0F, {252, 246, 164}}
};
value = std::clamp(value, 0.0F, 1.0F);
for (std::size_t index = 1; index < stops.size(); ++index) {
if (value > stops[index].position) continue;
const auto& lower = stops[index - 1];
const auto& upper = stops[index];
const auto ratio = (value - lower.position) /
(upper.position - lower.position);
const auto channel = [&](std::size_t component) {
return static_cast<std::uint8_t>(std::lround(
std::lerp(lower.rgb[component], upper.rgb[component], ratio)));
};
return color(channel(0), channel(1), channel(2));
}
return color(252, 246, 164);
}
Vec3 face_normal(Vec3 first, Vec3 second, Vec3 third) {
const Vec3 a{second.x - first.x, second.y - first.y, second.z - first.z};
const Vec3 b{third.x - first.x, third.y - first.y, third.z - first.z};
Vec3 result{
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
};
const auto length = std::sqrt(result.x * result.x + result.y * result.y +
result.z * result.z);
if (!(length > 0.0F)) return {0, 0, 1};
result.x /= length;
result.y /= length;
result.z /= length;
return result;
}
float spectrogram_level(float time, float frequency, double animation_seconds,
std::size_t ridge_count) {
struct Ridge {
float center;
float width;
float phase;
float speed;
float strength;
};
static constexpr std::array ridges{
Ridge{0.12F, 0.035F, 0.20F, 0.42F, 0.72F},
Ridge{0.28F, 0.060F, 1.45F, 0.67F, 0.58F},
Ridge{0.47F, 0.045F, 2.70F, 0.53F, 0.82F},
Ridge{0.68F, 0.075F, 4.10F, 0.31F, 0.52F},
Ridge{0.84F, 0.030F, 5.20F, 0.78F, 0.68F},
Ridge{0.57F, 0.022F, 0.90F, 1.05F, 0.44F},
Ridge{0.36F, 0.028F, 3.60F, 0.91F, 0.40F},
Ridge{0.76F, 0.050F, 2.10F, 0.58F, 0.46F},
Ridge{0.20F, 0.024F, 4.80F, 0.72F, 0.38F},
Ridge{0.92F, 0.020F, 1.90F, 0.84F, 0.36F},
Ridge{0.52F, 0.090F, 5.70F, 0.27F, 0.32F},
Ridge{0.08F, 0.018F, 3.00F, 1.12F, 0.30F}
};
const float animation = static_cast<float>(animation_seconds);
float level = 0.075F + 0.055F * std::sin(
2.0F * std::numbers::pi_v<float> *
(0.78F * time + 0.24F * frequency + 0.11F * animation));
const auto active_ridges = std::min(ridge_count, ridges.size());
for (std::size_t index = 0; index < active_ridges; ++index) {
const auto& ridge = ridges[index];
const auto moving_center = std::clamp(
ridge.center + 0.065F * std::sin(
ridge.phase + ridge.speed * animation + 1.3F * time),
0.015F, 0.985F);
const auto distance = (frequency - moving_center) / ridge.width;
const auto envelope = 0.52F + 0.48F * std::sin(
ridge.phase * 0.61F + 2.2F * time +
(ridge.speed + 0.18F) * animation);
level += ridge.strength * envelope * std::exp(-0.5F * distance * distance);
}
const float detail = 0.025F * std::sin(
31.0F * frequency + 8.0F * time + 1.7F * animation) +
0.018F * std::cos(
19.0F * frequency - 13.0F * time + animation);
return std::clamp(level + detail, 0.0F, 1.0F);
}
std::vector<Mesh_Vertex> spectrogram_mesh(const Spectrogram_Parameters& parameters,
double animation_seconds = 0.0) {
struct Sample {
Vec3 position;
Color color;
};
std::vector<Sample> samples(parameters.time_sample_count *
parameters.frequency_bin_count);
for (std::size_t time_index = 0; time_index < parameters.time_sample_count;
++time_index) {
const auto time = static_cast<float>(time_index) /
static_cast<float>(parameters.time_sample_count - 1);
for (std::size_t frequency_index = 0;
frequency_index < parameters.frequency_bin_count; ++frequency_index) {
const auto frequency = static_cast<float>(frequency_index) /
static_cast<float>(parameters.frequency_bin_count - 1);
const float level = spectrogram_level(
time, frequency, animation_seconds, parameters.ridge_count);
samples[time_index * parameters.frequency_bin_count + frequency_index] = {
{
-1.0F + 2.0F * time, -1.0F + 2.0F * frequency,
-1.0F + 2.0F * level
},
spectrogram_color(level)
};
}
}
std::vector<Mesh_Vertex> mesh;
mesh.reserve((parameters.time_sample_count - 1) *
(parameters.frequency_bin_count - 1) * 6);
const auto append_triangle = [&](const Sample& first, const Sample& second,
const Sample& third) {
const auto normal = face_normal(first.position, second.position, third.position);
mesh.push_back({first.position, first.color, normal, {0, 0}});
mesh.push_back({second.position, second.color, normal, {0, 0}});
mesh.push_back({third.position, third.color, normal, {0, 0}});
};
for (std::size_t time_index = 0; time_index + 1 < parameters.time_sample_count;
++time_index) {
for (std::size_t frequency_index = 0;
frequency_index + 1 < parameters.frequency_bin_count; ++frequency_index) {
const auto current = time_index * parameters.frequency_bin_count + frequency_index;
const auto next_time = current + parameters.frequency_bin_count;
append_triangle(samples[current], samples[next_time], samples[next_time + 1]);
append_triangle(samples[current], samples[next_time + 1], samples[current + 1]);
}
}
return mesh;
}
struct Spectrogram_Data_Generator {
Spectrogram_Parameters parameters{};
explicit Spectrogram_Data_Generator(Spectrogram_Parameters value = {}) : parameters(std::move(value)) {}
[[nodiscard]] Json schema() const {
Json fields = Json::array();
fields.push_back(integer_field("time_sample_count", "时间采样数", "时间方向网格采样数;GPU 顶点数约为 6×(时间采样数-1)×(频率分箱数-1)。", parameters.time_sample_count, 16, 1024));
fields.push_back(integer_field("frequency_bin_count", "频率分箱数", "对数频率方向分箱数;与时间采样数共同决定三角形和每次上传的数据量。", parameters.frequency_bin_count, 16, 1024));
fields.push_back(integer_field("ridge_count", "谱峰轨迹数", "生成随时间漂移的窄带谱峰数量。", 5, 1, 12));
fields.push_back(number_field("time_span_seconds", "时间跨度", "X 轴时间范围,单位秒。", parameters.time_span_seconds, 0.1, 3600.0, 0.1));
fields.push_back(number_field("minimum_frequency_hz", "最低频率", "对数频率轴下界,必须大于零。", parameters.minimum_frequency_hz, 0.001, 1.0e12, 1.0));
fields.push_back(number_field("maximum_frequency_hz", "最高频率", "对数频率轴上界,必须大于最低频率。", parameters.maximum_frequency_hz, 0.002, 1.0e12, 10.0));
fields.push_back(number_field("minimum_level_db", "最低声压级", "Z 轴色阶与高度下界,单位 dB。", parameters.minimum_level_db, -1000.0, 1000.0, 1.0));
fields.push_back(number_field("maximum_level_db", "最高声压级", "Z 轴色阶与高度上界,必须大于下界。", parameters.maximum_level_db, -1000.0, 1000.0, 1.0));
fields.push_back(number_field("animation_speed", "动态速度倍率", "谱峰随时间运动的倍率;0 表示保持当前相位。", parameters.animation_speed, 0.0, 100.0, 0.1));
fields.push_back(integer_field("update_every_n_frames", "数据更新帧间隔", "每 N 个渲染请求重建并上传一次 Mesh;可分离固定几何绘制与持续数据上传压力。", parameters.update_every_n_frames, 1, 10'000));
fields.push_back({{"key", "animation_enabled"}, {"label", "持续生成动态数据"}, {"description", "关闭后保留生成的数据集,仅测试固定 Mesh 的重复绘制;开启后按更新间隔持续重建。"}, {"editor", "boolean"}, {"editable", true}, {"value", parameters.animation_enabled}});
return {
{"label", "生成三维频谱瀑布"},
{"description", "按时间采样、对数频率分箱和声压级范围生成连续 GPU Mesh 表面。"},
{"fields", std::move(fields)}
};
}
[[nodiscard]] Json generate(Mesh_Visual& visual, Axes_3D& axes,
const Json& input) {
try {
Spectrogram_Parameters next;
next.time_sample_count = input_count(input, "time_sample_count", 1024);
next.frequency_bin_count = input_count(input, "frequency_bin_count", 1024);
next.ridge_count = input_count(input, "ridge_count", 12);
next.time_span_seconds = input_number(input, "time_span_seconds");
next.minimum_frequency_hz = input_number(input, "minimum_frequency_hz");
next.maximum_frequency_hz = input_number(input, "maximum_frequency_hz");
next.minimum_level_db = input_number(input, "minimum_level_db");
next.maximum_level_db = input_number(input, "maximum_level_db");
next.animation_speed = input_number(input, "animation_speed");
next.update_every_n_frames = input_count(input, "update_every_n_frames", 10'000);
const auto animation = input.at("animation_enabled");
if (!animation.is_boolean()) throw std::invalid_argument("animation_enabled must be boolean");
next.animation_enabled = animation.get<bool>();
if (!(next.time_span_seconds > 0.0) ||
!(next.minimum_frequency_hz > 0.0) ||
!(next.maximum_frequency_hz > next.minimum_frequency_hz) ||
!(next.maximum_level_db > next.minimum_level_db))
throw std::invalid_argument("spectrogram ranges are invalid");
const auto cells = (next.time_sample_count - 1) * (next.frequency_bin_count - 1);
if (cells > 1'400'000) throw std::invalid_argument("spectrogram exceeds the 8,400,000 vertex stress-test limit");
parameters = next;
auto mesh = spectrogram_mesh(parameters);
const auto vertex_count = mesh.size();
visual.template set<Mesh_Frame_Tag>([mesh = std::move(mesh)](Mesh_Visual::Frame& frame) mutable { frame.items = std::move(mesh); });
plot::Axis_Descriptor time_axis{
{0.0, parameters.time_span_seconds},
plot::Axis_Scale::time, "Time", "s", 6, 1, true, true, true
};
plot::Axis_Descriptor frequency_axis{
{
parameters.minimum_frequency_hz,
parameters.maximum_frequency_hz
},
plot::Axis_Scale::logarithmic,
"Frequency", "Hz", 5, 0, true, true, true
};
plot::Axis_Descriptor level_axis{
{
parameters.minimum_level_db,
parameters.maximum_level_db
},
plot::Axis_Scale::linear,
"SPL", "dB", 7, 0, true, true, true
};
axes.set<Prop_Tag>(&Axes_3D::Prop::x_axis , std::move(time_axis));
axes.set<Prop_Tag>(&Axes_3D::Prop::y_axis , std::move(frequency_axis));
axes.set<Prop_Tag>(&Axes_3D::Prop::z_axis , std::move(level_axis));
return {
{"success", true}, {"generated_count", vertex_count},
{"triangle_count", vertex_count / 3}
};
}
catch (const std::exception& error) {
return {{"success", false}, {"error", error.what()}};
}
}
void update(Mesh_Visual& visual, Axes_3D&,
Marker_Visual* markers,
const Gallery_Frame_Request& request) {
if (!parameters.animation_enabled ||
request.sequence % parameters.update_every_n_frames != 0)
return;
const double animation_seconds = request.time_milliseconds / 1000.0 * parameters.animation_speed;
auto mesh = spectrogram_mesh(parameters, animation_seconds);
visual.template set<Mesh_Frame_Tag>([mesh = std::move(mesh)](Mesh_Visual::Frame& frame) mutable { frame.items = std::move(mesh); });
if (!markers) return;
std::vector<Marker> items;
markers->template get<Marker_Frame_Tag>([&](const Marker_Visual::Frame& frame) { items = frame.items; });
for (auto& marker : items) {
const float time = std::clamp((marker.position.x + 1.0F) * 0.5F, 0.0F, 1.0F);
const float frequency = std::clamp((marker.position.y + 1.0F) * 0.5F, 0.0F, 1.0F);
marker.position.z = -1.0F + 2.0F * spectrogram_level(
time, frequency, animation_seconds, parameters.ridge_count);
}
markers->template set<Marker_Frame_Tag>([items = std::move(items)](Marker_Visual::Frame& frame) mutable { frame.items = std::move(items); });
}
};
}
Gallery_Build make_datoviz_point_plot() {
return make_visual_plot<Point_Visual>("Point Visual",
initialize_visual_frame<Point_Visual>(
Point_Visual::Builder{}.build(),
Point_Frame{{{{-0.65F, -0.25F, 0.05F}, color(255, 91, 110), 28.0F}, {{0.0F, 0.58F, 0.25F}, color(82, 226, 190), 34.0F}, {{0.62F, -0.12F, -0.15F}, color(75, 145, 255), 30.0F}}}));
}
Gallery_Build make_datoviz_splat_plot() {
return make_visual_plot<Splat_Visual>("Splat Visual",
initialize_visual_frame<Splat_Visual>(
Splat_Visual::Builder{}.build(),
Splat_Frame{{{{-0.48F, 0.0F, 0.1F}, color(255, 98, 115, 210), {0.18F, 0.08F}, 0.45F}, {{0.28F, 0.15F, 0.0F}, color(66, 218, 188, 210), {0.12F, 0.22F}, -0.3F}, {{0.15F, -0.38F, 0.2F}, color(76, 132, 255, 210), {0.2F, 0.1F}, 0.9F}}}));
}
Gallery_Build make_datoviz_pixel_plot() {
std::vector<Pixel> pixels;
for (int y = -8; y <= 8; ++y) for (int x = -12; x <= 12; ++x) pixels.push_back({{x / 13.0F, y / 9.0F, 0.12F * std::sin(x * .45F) * std::cos(y * .35F)}, color(static_cast<std::uint8_t>(90 + 6 * (x + 12)), static_cast<std::uint8_t>(100 + 8 * (y + 8)), 230), 5.0F});
return make_visual_plot<Pixel_Visual>("Pixel Visual",
initialize_visual_frame<Pixel_Visual>(
Pixel_Visual::Builder{}.build(),
Pixel_Frame{std::move(pixels)}));
}
Gallery_Build make_datoviz_marker_plot() {
return make_visual_plot<Marker_Visual>("Marker Visual",
initialize_visual_frame<Marker_Visual>(
Marker_Visual::Builder{}.build(),
Marker_Frame{{{{-0.7F, 0.0F, 0.0F}, color(255, 93, 115), 34.0F, 0.0F, Marker_Shape::disc}, {{-0.35F, 0.25F, 0.1F}, color(91, 226, 193), 36.0F, 0.25F, Marker_Shape::square}, {{0.0F, -0.2F, 0.2F}, color(100, 158, 255), 38.0F, 0.5F, Marker_Shape::triangle}, {{0.35F, 0.25F, 0.1F}, color(250, 195, 92), 40.0F, 0.75F, Marker_Shape::diamond}, {{0.7F, 0.0F, 0.0F}, color(201, 132, 255), 42.0F, 1.0F, Marker_Shape::cross}}}));
}
Gallery_Build make_datoviz_sphere_plot() {
return make_visual_plot<Sphere_Visual>("Sphere Visual",
initialize_visual_frame<Sphere_Visual>(
Sphere_Visual::Builder{}.build(),
Sphere_Frame{{{{-0.48F, -0.2F, 0.0F}, color(255, 91, 110), 0.28F}, {{0.08F, 0.25F, 0.18F}, color(82, 226, 190), 0.36F}, {{0.55F, -0.18F, -0.12F}, color(75, 145, 255), 0.24F}}}));
}
Gallery_Build make_datoviz_segment_plot() {
std::vector<Segment> segments;
for (int index = 0; index < 12; ++index) {
const float angle = static_cast<float>(index) * std::numbers::pi_v<float> / 6.0F;
segments.push_back({{0.0F, 0.0F, 0.0F}, {0.82F * std::cos(angle), 0.82F * std::sin(angle), 0.18F * std::sin(2 * angle)}, color(static_cast<std::uint8_t>(80 + index * 13), static_cast<std::uint8_t>(220 - index * 8), 240), 4.0F});
}
return make_visual_plot<Segment_Visual>("Segment Visual",
initialize_visual_frame<Segment_Visual>(
Segment_Visual::Builder{}.build(),
Segment_Frame{std::move(segments)}));
}
Gallery_Build make_datoviz_vector_plot() {
return make_visual_plot<Vector_Visual>("Vector Visual",
initialize_visual_frame<Vector_Visual>(
Vector_Visual::Builder{}.build(),
Vector_Frame{{{{-0.55F, -0.35F, 0.0F}, {0.55F, 0.2F, 0.25F}, color(255, 98, 115), 4.0F}, {{-0.1F, 0.0F, 0.0F}, {0.2F, 0.62F, 0.18F}, color(80, 225, 190), 5.0F}, {{0.35F, -0.25F, 0.0F}, {-0.12F, 0.25F, 0.65F}, color(78, 145, 255), 4.0F}}}));
}
Gallery_Build make_datoviz_primitive_plot() {
return make_visual_plot<Primitive_Visual>("Primitive Visual",
initialize_visual_frame<Primitive_Visual>(
Primitive_Visual::Builder{}.build(),
Primitive_Frame{{{{-0.72F, -0.55F, 0.0F}, color(255, 86, 110), {0, 0, 1}}, {{0.72F, -0.55F, 0.0F}, color(75, 145, 255), {0, 0, 1}}, {{0.0F, 0.72F, 0.25F}, color(82, 226, 190), {0, 0, 1}}}}));
}
Gallery_Build make_datoviz_mesh_plot() {
const std::vector<Mesh_Vertex> mesh{{{-0.65F, -0.55F, 0.0F}, color(255, 94, 112), {0, 0, 1}, {0, 0}}, {{0.65F, -0.55F, 0.0F}, color(75, 145, 255), {0, 0, 1}, {1, 0}}, {{0.65F, 0.55F, 0.0F}, color(82, 226, 190), {0, 0, 1}, {1, 1}}, {{-0.65F, -0.55F, 0.0F}, color(255, 94, 112), {0, 0, 1}, {0, 0}}, {{0.65F, 0.55F, 0.0F}, color(82, 226, 190), {0, 0, 1}, {1, 1}}, {{-0.65F, 0.55F, 0.0F}, color(244, 190, 86), {0, 0, 1}, {0, 1}}};
return make_visual_plot<Mesh_Visual>("Mesh Visual",
initialize_visual_frame<Mesh_Visual>(
Mesh_Visual::Builder{}.build(), Mesh_Frame{mesh}));
}
Gallery_Build make_datoviz_spectrogram_plot() {
const Spectrogram_Parameters parameters;
auto marker_result = initialize_visual_frame<Marker_Visual>(
Marker_Visual::Builder{}
.set<Prop_Tag>(&Marker_Visual::Prop::depth_test, false)
.build(),
Marker_Frame{std::vector<Marker>{
{
{-0.35F, -0.18F, 0.72F}, color(255, 244, 170), 23.0F, 0.0F,
Marker_Shape::diamond, false
},
{
{0.28F, 0.34F, 0.86F}, color(88, 236, 211), 21.0F, 0.0F,
Marker_Shape::cross, false
}
}});
auto markers = std::move(marker_result);
Scene_Components_3D components;
components.camera.initial_view = {
{3.35, -3.55, 2.45}, {0.0, 0.0, -0.05},
{0.0, 0.0, 1.0}
};
components.camera.turntable_control = {
0.15, 0.15, 0.10, 0.0015,
-1.35, 1.35, 1.25, 12.0,
true, true, true, false
};
components.camera.vertical_field_of_view_degrees = 41.0;
components.axes = {
plot::Axis_Descriptor{
{0.0, parameters.time_span_seconds},
plot::Axis_Scale::time, "Time", "s", 6, 1, true, true, true
},
plot::Axis_Descriptor{
{
parameters.minimum_frequency_hz,
parameters.maximum_frequency_hz
},
plot::Axis_Scale::logarithmic,
"Frequency", "Hz", 5, 0, true, true, true
},
plot::Axis_Descriptor{
{
parameters.minimum_level_db,
parameters.maximum_level_db
},
plot::Axis_Scale::linear,
"SPL", "dB", 7, 0, true, true, true
}
};
return make_visual_plot<Mesh_Visual>(
"3D Spectrogram",
initialize_visual_frame<Mesh_Visual>(
Mesh_Visual::Builder{}.build(),
Mesh_Frame{spectrogram_mesh(parameters)}),
std::move(components), Spectrogram_Data_Generator{parameters}, std::move(markers));
}
Gallery_Build make_datoviz_path_plot() {
std::vector<Path_Vertex> path;
for (int index = 0; index < 64; ++index) {
const float t = static_cast<float>(index) / 63.0F;
path.push_back({{-0.9F + 1.8F * t, 0.48F * std::sin(t * 4.0F * std::numbers::pi_v<float>), 0.25F * std::cos(t * 2.0F * std::numbers::pi_v<float>)}, color(static_cast<std::uint8_t>(70 + 170 * t), static_cast<std::uint8_t>(220 - 80 * t), 245), 5.0F});
}
return make_visual_plot<Path_Visual>("Path Visual",
initialize_visual_frame<Path_Visual>(
Path_Visual::Builder{}.build(),
Path_Frame{std::move(path)}));
}
Gallery_Build make_datoviz_image_plot() {
std::vector<Color> pixels(32U * 32U);
for (std::uint32_t y = 0; y < 32U; ++y)
for (std::uint32_t x = 0; x < 32U; ++x)
pixels[y * 32U + x] = (x / 8U + y / 8U) % 2U == 0U
? color(40, 235, 205) : color(18, 42, 72);
return make_visual_plot<Image_Visual>("Image Visual",
initialize_visual_frame<Image_Visual>(
Image_Visual::Builder{}.build(),
Image_Frame{{{{0.0F, 0.0F, 0.0F}, {1.45F, 1.0F}, {0, 0, 1, 1}}},
32U, 32U, std::move(pixels)}));
}
Gallery_Build make_datoviz_labels_plot() {
std::vector<std::int32_t> labels(8U * 8U);
for (std::uint32_t y = 0; y < 8U; ++y)
for (std::uint32_t x = 0; x < 8U; ++x)
labels[y * 8U + x] = static_cast<std::int32_t>(
x / 4U + 2U * (y / 4U));
return make_visual_plot<Labels_Visual>("Labels Visual",
initialize_visual_frame<Labels_Visual>(
Labels_Visual::Builder{}.build(),
Labels_Frame{{{{-0.55F, 0.28F, 0.0F}, {72, 34}, {0, 0, 0.5F, 0.5F}}, {{0.5F, 0.25F, 0.1F}, {72, 34}, {0.5F, 0, 1, 0.5F}}, {{-0.45F, -0.32F, 0.1F}, {72, 34}, {0, 0.5F, 0.5F, 1}}, {{0.55F, -0.3F, 0.0F}, {72, 34}, {0.5F, 0.5F, 1, 1}}},
8U, 8U, std::move(labels)}));
}
Gallery_Build make_datoviz_glyph_plot() {
return make_visual_plot<Glyph_Visual>("Glyph Visual",
initialize_visual_frame<Glyph_Visual>(
Glyph_Visual::Builder{}.build(),
Glyph_Frame{{{{-0.55F, -0.15F, 0.0F}, {-0.18F, -0.18F, 0.18F, 0.18F}, {0, 0, 1, 1}, color(255, 100, 120), -0.2F}, {{0.0F, 0.22F, 0.1F}, {-0.22F, -0.22F, 0.22F, 0.22F}, {0, 0, 1, 1}, color(82, 226, 190), 0.25F}, {{0.55F, -0.15F, 0.0F}, {-0.2F, -0.2F, 0.2F, 0.2F}, {0, 0, 1, 1}, color(80, 145, 255), 0.55F}}}));
}
Gallery_Build make_datoviz_text_plot() {
return make_visual_plot<Text_Visual>("Text Visual",
initialize_visual_frame<Text_Visual>(
Text_Visual::Builder{}.build(),
Text_Frame{{{{-0.72F, 0.28F, 0.0F}, "Aethera", color(82, 226, 190), 28.0F}, {{-0.62F, -0.15F, 0.1F}, "Datoviz Visual", color(90, 155, 255), 22.0F}}}));
}
Gallery_Build make_datoviz_volume_plot() {
std::vector<Voxel> voxels(16U * 16U * 16U);
for (std::size_t index = 0; index < voxels.size(); ++index) voxels[index].value = static_cast<float>(index % 256U) / 255.0F;
return make_visual_plot<Volume_Visual>("Volume Visual",
initialize_visual_frame<Volume_Visual>(
Volume_Visual::Builder{}.build(),
Volume_Frame{std::move(voxels), 16U, 16U, 16U}));
}
}