|
|
|
@@ -2,6 +2,7 @@
|
|
|
|
|
#include "Renderable_Adapter.hpp"
|
|
|
|
|
#include <render_3D/Render_3D.hpp>
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <limits>
|
|
|
|
|
#include <numbers>
|
|
|
|
@@ -192,11 +193,19 @@ private:
|
|
|
|
|
std::uniform_real_distribution<float> second{};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <typename Visual_Object>
|
|
|
|
|
struct Random_Data_Generator {};
|
|
|
|
|
|
|
|
|
|
template <typename Visual_Object, typename Data_Generator = Random_Data_Generator>
|
|
|
|
|
class Visual_Scene_View final : public Plot::Scene_View {
|
|
|
|
|
public:
|
|
|
|
|
Visual_Scene_View(Scene_3D& scene, std::unique_ptr<Visual_Object> visual, std::string label)
|
|
|
|
|
: visual_(std::move(visual)) {
|
|
|
|
|
using Camera_Object = Impl<Camera_3D>;
|
|
|
|
|
using Axes_Object = Impl<Axes_3D>;
|
|
|
|
|
Visual_Scene_View(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 = {})
|
|
|
|
|
: data_generator_(std::move(data_generator)), camera_(std::move(camera)),
|
|
|
|
|
axes_(std::move(axes)), visual_(std::move(visual)) {
|
|
|
|
|
using Definition = typename Visual_Object::Attached_Object;
|
|
|
|
|
using Prop = typename Definition::Prop;
|
|
|
|
|
using State = typename Definition::State;
|
|
|
|
@@ -213,7 +222,20 @@ public:
|
|
|
|
|
detail::State_Field<Definition::Base_Tag, &State::item_count, "item_count", "Number of published input items.">,
|
|
|
|
|
detail::State_Field<Definition::Base_Tag, &State::prepared_item_count, "prepared_item_count", "Number of prepared backend items.">,
|
|
|
|
|
detail::State_Field<Definition::Base_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::control, "control", "Turntable rotate, zoom and pan capabilities, speed and limits.">,
|
|
|
|
|
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.">,
|
|
|
|
|
detail::Prop_Field<&Axes_3D::Prop::y_axis, "y_axis", "Y axis range, scale, ticks, label and unit.">,
|
|
|
|
|
detail::Prop_Field<&Axes_3D::Prop::z_axis, "z_axis", "Z axis range, scale, ticks, label and unit.">>;
|
|
|
|
|
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_}));
|
|
|
|
|
descriptors_.push_back(detail::make_renderable_descriptor("visual", std::move(label), "visual", Visual_Adapter{*visual_}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -232,11 +254,18 @@ public:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] nlohmann::json data_generator_schema() const override {
|
|
|
|
|
using Definition = typename Visual_Object::Attached_Object;
|
|
|
|
|
return generator_schema<Definition>();
|
|
|
|
|
if constexpr (std::same_as<Data_Generator, Random_Data_Generator>) {
|
|
|
|
|
using Definition = typename Visual_Object::Attached_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 = typename Visual_Object::Attached_Object;
|
|
|
|
|
using Prop = typename Definition::Prop;
|
|
|
|
|
using Items = std::remove_cvref_t<decltype(std::declval<Prop>().items)>;
|
|
|
|
@@ -280,34 +309,249 @@ public:
|
|
|
|
|
} catch (const std::exception& error) {
|
|
|
|
|
return {{"success", false}, {"error", error.what()}};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void update(const Plot_Frame_Request&) override {}
|
|
|
|
|
|
|
|
|
|
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::vector<std::unique_ptr<detail::Renderable_Descriptor>> descriptors_; /* Prop/State 协议描述。 */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <typename Definition, typename Build_Result>
|
|
|
|
|
std::shared_ptr<Plot> make_visual_plot(asio::any_io_executor executor, std::string label, Build_Result build_result) {
|
|
|
|
|
struct Scene_Components_3D {
|
|
|
|
|
plot::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 Definition, typename Build_Result,
|
|
|
|
|
typename Data_Generator = Random_Data_Generator>
|
|
|
|
|
std::shared_ptr<Plot> make_visual_plot(asio::any_io_executor executor, std::string label,
|
|
|
|
|
Build_Result build_result,
|
|
|
|
|
Scene_Components_3D components = {},
|
|
|
|
|
Data_Generator data_generator = {}) {
|
|
|
|
|
using Visual_Object = Impl<Definition>;
|
|
|
|
|
using Camera_Object = Impl<Camera_3D>;
|
|
|
|
|
using Axes_Object = Impl<Axes_3D>;
|
|
|
|
|
if (!build_result) throw std::logic_error("3D Gallery visual dependency graph is invalid");
|
|
|
|
|
auto visual = std::move(build_result).value();
|
|
|
|
|
auto scene_result = Scene_3D::Builder(visual.get())
|
|
|
|
|
.set(&Render_Scene_3D::Prop::viewport, Extent{720, 420})
|
|
|
|
|
.set(&Render_Scene_3D::Prop::clear_color, Linear_Color{0.018F, 0.027F, 0.047F, 1.0F})
|
|
|
|
|
.set(&Render_Scene_3D::Prop::view_active, true)
|
|
|
|
|
.build();
|
|
|
|
|
auto camera_result = Camera_Object::Builder{}
|
|
|
|
|
.set(&Camera_3D::Prop::initial_view, components.camera.initial_view)
|
|
|
|
|
.set(&Camera_3D::Prop::projection, components.camera.projection)
|
|
|
|
|
.set(&Camera_3D::Prop::control, components.camera.control)
|
|
|
|
|
.set(&Camera_3D::Prop::vertical_field_of_view_degrees,
|
|
|
|
|
components.camera.vertical_field_of_view_degrees)
|
|
|
|
|
.set(&Camera_3D::Prop::near_plane, components.camera.near_plane)
|
|
|
|
|
.set(&Camera_3D::Prop::far_plane, components.camera.far_plane)
|
|
|
|
|
.build();
|
|
|
|
|
auto axes_result = Axes_Object::Builder{}
|
|
|
|
|
.set(&Axes_3D::Prop::x_axis, components.axes[0])
|
|
|
|
|
.set(&Axes_3D::Prop::y_axis, components.axes[1])
|
|
|
|
|
.set(&Axes_3D::Prop::z_axis, components.axes[2])
|
|
|
|
|
.build();
|
|
|
|
|
if (!camera_result || !axes_result)
|
|
|
|
|
throw std::logic_error("3D Gallery component construction failed");
|
|
|
|
|
auto camera = std::move(camera_result).value();
|
|
|
|
|
auto axes = std::move(axes_result).value();
|
|
|
|
|
auto scene_builder = Scene_3D::Builder{};
|
|
|
|
|
scene_builder.add_camera(camera.get())
|
|
|
|
|
.add_axes(axes.get())
|
|
|
|
|
.add_renderable(visual.get())
|
|
|
|
|
.set(&Render_Scene_3D::Prop::viewport, Extent{720, 420})
|
|
|
|
|
.set(&Render_Scene_3D::Prop::clear_color, Linear_Color{0.018F, 0.027F, 0.047F, 1.0F})
|
|
|
|
|
.set(&Render_Scene_3D::Prop::view_active, true);
|
|
|
|
|
auto scene_result = scene_builder.build();
|
|
|
|
|
if (!scene_result) throw std::logic_error("3D Gallery scene dependency graph is invalid");
|
|
|
|
|
auto scene = std::move(scene_result).value();
|
|
|
|
|
auto view = std::make_unique<Visual_Scene_View<Visual_Object>>(*scene, std::move(visual), std::move(label));
|
|
|
|
|
auto view = std::make_unique<Visual_Scene_View<Visual_Object, Data_Generator>>(
|
|
|
|
|
*scene, std::move(camera), std::move(axes), std::move(visual),
|
|
|
|
|
std::move(label), std::move(data_generator));
|
|
|
|
|
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Mesh_Vertex> spectrogram_mesh(const Spectrogram_Parameters& parameters) {
|
|
|
|
|
struct Ridge { float center; float width; float phase; float speed; float strength; };
|
|
|
|
|
std::mt19937_64 engine{std::random_device{}()};
|
|
|
|
|
std::uniform_real_distribution<float> center_distribution{0.08F, 0.92F};
|
|
|
|
|
std::uniform_real_distribution<float> width_distribution{0.025F, 0.12F};
|
|
|
|
|
std::uniform_real_distribution<float> phase_distribution{0.0F, 2.0F * std::numbers::pi_v<float>};
|
|
|
|
|
std::uniform_real_distribution<float> speed_distribution{0.35F, 1.8F};
|
|
|
|
|
std::uniform_real_distribution<float> strength_distribution{0.38F, 0.9F};
|
|
|
|
|
std::normal_distribution<float> noise{0.0F, 0.035F};
|
|
|
|
|
std::vector<Ridge> ridges;
|
|
|
|
|
ridges.reserve(parameters.ridge_count);
|
|
|
|
|
for (std::size_t index = 0; index < parameters.ridge_count; ++index)
|
|
|
|
|
ridges.push_back({center_distribution(engine), width_distribution(engine),
|
|
|
|
|
phase_distribution(engine), speed_distribution(engine),
|
|
|
|
|
strength_distribution(engine)});
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
float level = 0.08F + 0.08F * std::sin(
|
|
|
|
|
2.0F * std::numbers::pi_v<float> * (0.7F * time + 0.3F * frequency));
|
|
|
|
|
for (const auto& ridge : ridges) {
|
|
|
|
|
const auto moving_center = std::clamp(
|
|
|
|
|
ridge.center + 0.055F * std::sin(ridge.phase +
|
|
|
|
|
ridge.speed * 2.0F * std::numbers::pi_v<float> * time),
|
|
|
|
|
0.02F, 0.98F);
|
|
|
|
|
const auto distance = (frequency - moving_center) / ridge.width;
|
|
|
|
|
const auto envelope = 0.55F + 0.45F * std::sin(
|
|
|
|
|
ridge.phase * 0.63F + (ridge.speed + 0.25F) *
|
|
|
|
|
2.0F * std::numbers::pi_v<float> * time);
|
|
|
|
|
level += ridge.strength * envelope * std::exp(-0.5F * distance * distance);
|
|
|
|
|
}
|
|
|
|
|
level = std::clamp(level + noise(engine), 0.0F, 1.0F);
|
|
|
|
|
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 {
|
|
|
|
|
[[nodiscard]] Json schema() const {
|
|
|
|
|
Json fields = Json::array();
|
|
|
|
|
fields.push_back(integer_field("time_sample_count", "时间采样数", "时间方向的网格采样数量;增大后表面沿时间方向更细密。", 80, 16, 256));
|
|
|
|
|
fields.push_back(integer_field("frequency_bin_count", "频率分箱数", "对数频率方向的网格分箱数量。", 96, 16, 256));
|
|
|
|
|
fields.push_back(integer_field("ridge_count", "谱峰轨迹数", "生成随时间漂移的窄带谱峰数量。", 5, 1, 12));
|
|
|
|
|
fields.push_back(number_field("time_span_seconds", "时间跨度", "X 轴覆盖的时间长度,单位秒。", 4.0, 0.1, 120.0, 0.1));
|
|
|
|
|
fields.push_back(number_field("minimum_frequency_hz", "最低频率", "对数频率轴的下界,必须大于零。", 10.0, 1.0, 1.0e9, 1.0));
|
|
|
|
|
fields.push_back(number_field("maximum_frequency_hz", "最高频率", "对数频率轴的上界,必须大于最低频率。", 20'000.0, 2.0, 1.0e9, 10.0));
|
|
|
|
|
fields.push_back(number_field("minimum_level_db", "最低声压级", "Z 轴色阶和高度的下界,单位 dB。", 18.0, -300.0, 300.0, 1.0));
|
|
|
|
|
fields.push_back(number_field("maximum_level_db", "最高声压级", "Z 轴色阶和高度的上界,必须大于下界。", 78.0, -300.0, 300.0, 1.0));
|
|
|
|
|
return {{"label", "生成三维频谱瀑布"},
|
|
|
|
|
{"description", "按时间采样、对数频率分箱和声压级范围生成连续 GPU Mesh 表面。"},
|
|
|
|
|
{"fields", std::move(fields)}};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] Json generate(Impl<Mesh_Visual>& visual, Impl<Axes_3D>& axes,
|
|
|
|
|
const Json& input) const {
|
|
|
|
|
try {
|
|
|
|
|
Spectrogram_Parameters parameters;
|
|
|
|
|
parameters.time_sample_count = input_count(input, "time_sample_count", 256);
|
|
|
|
|
parameters.frequency_bin_count = input_count(input, "frequency_bin_count", 256);
|
|
|
|
|
parameters.ridge_count = input_count(input, "ridge_count", 12);
|
|
|
|
|
parameters.time_span_seconds = input_number(input, "time_span_seconds");
|
|
|
|
|
parameters.minimum_frequency_hz = input_number(input, "minimum_frequency_hz");
|
|
|
|
|
parameters.maximum_frequency_hz = input_number(input, "maximum_frequency_hz");
|
|
|
|
|
parameters.minimum_level_db = input_number(input, "minimum_level_db");
|
|
|
|
|
parameters.maximum_level_db = input_number(input, "maximum_level_db");
|
|
|
|
|
if (!(parameters.time_span_seconds > 0.0) ||
|
|
|
|
|
!(parameters.minimum_frequency_hz > 0.0) ||
|
|
|
|
|
!(parameters.maximum_frequency_hz > parameters.minimum_frequency_hz) ||
|
|
|
|
|
!(parameters.maximum_level_db > parameters.minimum_level_db))
|
|
|
|
|
throw std::invalid_argument("spectrogram ranges are invalid");
|
|
|
|
|
auto mesh = spectrogram_mesh(parameters);
|
|
|
|
|
const auto vertex_count = mesh.size();
|
|
|
|
|
if (visual.update_items(std::move(mesh)) != Mesh_Visual::Update_Items_Result::updated)
|
|
|
|
|
throw std::invalid_argument("generated spectrogram mesh was rejected");
|
|
|
|
|
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<&Axes_3D::Prop::x_axis>(std::move(time_axis));
|
|
|
|
|
axes.set<&Axes_3D::Prop::y_axis>(std::move(frequency_axis));
|
|
|
|
|
axes.set<&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()}};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Plot> make_datoviz_point_plot(asio::any_io_executor executor) {
|
|
|
|
@@ -358,6 +602,31 @@ std::shared_ptr<Plot> make_datoviz_mesh_plot(asio::any_io_executor executor) {
|
|
|
|
|
return make_visual_plot<Mesh_Visual>(std::move(executor), "Mesh Visual", Impl<Mesh_Visual>::Builder{}.set(&Mesh_Visual::Prop::items, mesh).build());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Plot> make_datoviz_spectrogram_plot(asio::any_io_executor executor) {
|
|
|
|
|
const Spectrogram_Parameters parameters;
|
|
|
|
|
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.control = {0.15, 0.15, 0.10, 0.0015, -1.35, 1.35,
|
|
|
|
|
1.25, 12.0, true, true, true};
|
|
|
|
|
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>(
|
|
|
|
|
std::move(executor), "3D Spectrogram",
|
|
|
|
|
Impl<Mesh_Visual>::Builder{}
|
|
|
|
|
.set(&Mesh_Visual::Prop::items, spectrogram_mesh(parameters))
|
|
|
|
|
.build(),
|
|
|
|
|
std::move(components), Spectrogram_Data_Generator{});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Plot> make_datoviz_path_plot(asio::any_io_executor executor) {
|
|
|
|
|
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}); }
|
|
|
|
|