三维频谱做好
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
#include "axis/Axes_3D.hpp"
|
||||
#include "camera/Camera_3D.hpp"
|
||||
#include "scene/Render_Scene_3D.hpp"
|
||||
#include "visual/Visuals.hpp"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <plot/Axis.hpp>
|
||||
#include <render_common.hpp>
|
||||
|
||||
namespace aethera::render_3d {
|
||||
|
||||
struct Axes_3D : Def<Axes_3D, Root> {
|
||||
struct Prop : Prev_Prop {
|
||||
plot::Axis_Descriptor x_axis{{0.0, 4.0}, plot::Axis_Scale::time,
|
||||
"Time", "s", 6, 1, true, true, true};
|
||||
plot::Axis_Descriptor y_axis{{10.0, 20'000.0}, plot::Axis_Scale::logarithmic,
|
||||
"Frequency", "Hz", 6, 1, true, true, true};
|
||||
plot::Axis_Descriptor z_axis{{18.0, 78.0}, plot::Axis_Scale::linear,
|
||||
"SPL", "dB", 7, 0, true, true, true};
|
||||
bool operator==(const Prop&) const = default;
|
||||
};
|
||||
struct State : Prev_State {
|
||||
bool operator==(const State&) const = default;
|
||||
};
|
||||
struct Private : Prev_Private {};
|
||||
};
|
||||
|
||||
} // namespace aethera::render_3d
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <plot/Camera.hpp>
|
||||
#include <render_common.hpp>
|
||||
|
||||
namespace aethera::render_3d {
|
||||
|
||||
struct Camera_3D : Def<Camera_3D, Root> {
|
||||
struct Prop : Prev_Prop {
|
||||
plot::Camera_View initial_view{};
|
||||
plot::Camera_Projection projection{plot::Camera_Projection::perspective};
|
||||
plot::Camera_Control control{};
|
||||
double vertical_field_of_view_degrees{45.0};
|
||||
double near_plane{0.01};
|
||||
double far_plane{100.0};
|
||||
bool operator==(const Prop&) const = default;
|
||||
};
|
||||
struct State : Prev_State {
|
||||
bool operator==(const State&) const = default;
|
||||
};
|
||||
struct Private : Prev_Private {};
|
||||
};
|
||||
|
||||
} // namespace aethera::render_3d
|
||||
@@ -10,7 +10,10 @@
|
||||
#include <utility>
|
||||
namespace aethera::render_3d::detail {
|
||||
namespace {
|
||||
float wheel_step(double pixel, double angle) { return static_cast<float>(pixel != 0.0 ? pixel : angle / 120.0); }
|
||||
float wheel_step(double pixel, double angle) {
|
||||
if (angle != 0.0) return static_cast<float>(angle / 120.0);
|
||||
return static_cast<float>(pixel / 100.0);
|
||||
}
|
||||
Mouse_Button held_button(Mouse_Button_Mask buttons) { if ((buttons & 1U) != 0) return Mouse_Button::left; if ((buttons & 2U) != 0) return Mouse_Button::right; if ((buttons & 4U) != 0) return Mouse_Button::middle; return Mouse_Button::none; }
|
||||
void record_datoviz_trace(Frame_3D* frame, const Datoviz_Frame_Trace& trace) {
|
||||
if (trace.apply_ns) frame->record(Frame_Trace_Measurement::backend_apply_ns, trace.apply_ns);
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#pragma once
|
||||
#include "../visual/Prepared_Visual.hpp"
|
||||
#include <event.hpp>
|
||||
#include <plot/Axis.hpp>
|
||||
#include <plot/Camera.hpp>
|
||||
namespace aethera::render_3d::detail {
|
||||
struct Scene_3D_Parameters {
|
||||
Extent viewport{560, 320}; /* 离屏渲染目标尺寸,单位为像素。 */
|
||||
Linear_Color clear_color{}; /* 每帧开始时写入的线性背景色。 */
|
||||
plot::Camera_Descriptor camera{}; /* Camera 组件发布的本帧配置快照。 */
|
||||
plot::Axis_Descriptor x_axis{}; /* 三维数据 X 轴的业务范围与标签策略。 */
|
||||
plot::Axis_Descriptor y_axis{}; /* 三维数据 Y 轴的业务范围与标签策略。 */
|
||||
plot::Axis_Descriptor z_axis{}; /* 三维数据 Z 轴的业务范围与标签策略。 */
|
||||
bool operator==(const Scene_3D_Parameters&) const = default;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,12 +12,18 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
namespace aethera::render_3d::detail {
|
||||
namespace {
|
||||
constexpr std::uint64_t color_target_id = 0x5256504f494e54ULL;
|
||||
DvzCapabilitySnapshot offscreen_capabilities() {
|
||||
auto capabilities = dvz_capability_snapshot();
|
||||
capabilities.supports_color_blending = true;
|
||||
return capabilities;
|
||||
}
|
||||
std::uint64_t trace_now_ns() noexcept {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
@@ -73,6 +79,30 @@ DvzShapeAspect aspect(Point_Aspect value) {
|
||||
}
|
||||
return DVZ_SHAPE_ASPECT_FILLED;
|
||||
}
|
||||
|
||||
float axis_position(const plot::Axis_Descriptor& axis, double coordinate) {
|
||||
const auto origin = axis.range.origin;
|
||||
const auto target = axis.range.target;
|
||||
if (!std::isfinite(origin) || !std::isfinite(target) || origin == target)
|
||||
throw std::invalid_argument("3D axis range must be finite and non-empty");
|
||||
double ratio{};
|
||||
if (axis.scale == plot::Axis_Scale::logarithmic) {
|
||||
if (!(origin > 0.0) || !(target > 0.0) || !(coordinate > 0.0))
|
||||
throw std::invalid_argument("logarithmic 3D axis coordinates must be positive");
|
||||
ratio = (std::log10(coordinate) - std::log10(origin)) /
|
||||
(std::log10(target) - std::log10(origin));
|
||||
} else {
|
||||
ratio = (coordinate - origin) / (target - origin);
|
||||
}
|
||||
return static_cast<float>(-1.0 + 2.0 * ratio);
|
||||
}
|
||||
|
||||
std::string axis_title(const plot::Axis_Descriptor& axis) {
|
||||
if (axis.label.empty()) return axis.unit;
|
||||
if (axis.unit.empty()) return axis.label;
|
||||
return axis.label + " (" + axis.unit + ")";
|
||||
}
|
||||
|
||||
void configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) {
|
||||
auto font_descriptor = dvz_font_desc();
|
||||
font_descriptor.family = "Roboto";
|
||||
@@ -139,6 +169,11 @@ void configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text)
|
||||
dvz_visual_set_depth_test(visual, false) != DVZ_OK)
|
||||
throw std::runtime_error("failed to upload Datoviz text geometry");
|
||||
}
|
||||
|
||||
bool supports_item_interaction(Visual_Family family) noexcept {
|
||||
return family == Visual_Family::point || family == Visual_Family::pixel ||
|
||||
family == Visual_Family::marker;
|
||||
}
|
||||
} // namespace
|
||||
class Datoviz_Render_Context final {
|
||||
public:
|
||||
@@ -593,7 +628,7 @@ void Datoviz_Visual_Backend::require_domain() const {
|
||||
void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_Parameters& initial_scene) {
|
||||
scene_ = dvz_scene();
|
||||
if (scene_ == nullptr) throw std::runtime_error("failed to create Datoviz scene");
|
||||
const auto capabilities = dvz_capability_snapshot();
|
||||
const auto capabilities = offscreen_capabilities();
|
||||
if (dvz_scene_set_capabilities(scene_, &capabilities) != DVZ_OK) throw std::runtime_error("failed to configure Datoviz scene capabilities");
|
||||
figure_ = dvz_figure(scene_, initial_scene.viewport.width,
|
||||
initial_scene.viewport.height, 0);
|
||||
@@ -745,33 +780,33 @@ void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_P
|
||||
if (figure_ == nullptr || panel_ == nullptr || visual_ == nullptr ||
|
||||
dvz_panel_add_visual(panel_, visual_, nullptr) != DVZ_OK)
|
||||
throw std::runtime_error("failed to create Datoviz visual family");
|
||||
DvzVisual* axes = dvz_segment(scene_, 0);
|
||||
const std::array<vec3, 3> axis_starts{{{0.0F, 0.0F, 0.0F}, {0.0F, 0.0F, 0.0F}, {0.0F, 0.0F, 0.0F}}};
|
||||
const std::array<vec3, 3> axis_ends{{{1.15F, 0.0F, 0.0F}, {0.0F, 1.15F, 0.0F}, {0.0F, 0.0F, 1.15F}}};
|
||||
const std::array<DvzColor, 3> axis_colors{{{238, 72, 89, 230}, {64, 210, 143, 230}, {70, 132, 245, 230}}};
|
||||
const std::array<float, 3> axis_widths{{3.0F, 3.0F, 3.0F}};
|
||||
const std::array<DvzVisualDataUpdate, 4> axis_updates{{
|
||||
{"position_start", axis_starts.data(), 3}, {"position_end", axis_ends.data(), 3},
|
||||
{"color", axis_colors.data(), 3}, {"stroke_width_px", axis_widths.data(), 3}}};
|
||||
if (axes == nullptr || dvz_visual_set_data_many(axes, axis_updates.data(), 4) != DVZ_OK ||
|
||||
dvz_segment_set_caps(axes, DVZ_SEGMENT_CAP_BUTT, DVZ_SEGMENT_CAP_BUTT) != DVZ_OK ||
|
||||
dvz_panel_add_visual(panel_, axes, nullptr) != DVZ_OK)
|
||||
throw std::runtime_error("failed to create Datoviz coordinate axes");
|
||||
DvzCameraDesc camera = dvz_camera_desc();
|
||||
camera.view.eye[0] = 0.0F;
|
||||
camera.view.eye[1] = 0.0F;
|
||||
camera.view.eye[2] = 4.0F;
|
||||
camera.view.target[0] = 0.0F;
|
||||
camera.view.target[1] = 0.0F;
|
||||
camera.view.target[2] = 0.0F;
|
||||
camera.projection.near_clip = 0.01F;
|
||||
camera.projection.far_clip = 100.0F;
|
||||
if (dvz_panel_set_camera_desc(panel_, &camera) != DVZ_OK) throw std::runtime_error("failed to create Datoviz point camera");
|
||||
DvzController* controller = dvz_arcball(scene_, nullptr);
|
||||
arcball_ = controller != nullptr ? dvz_controller_arcball(controller) : nullptr;
|
||||
if (controller == nullptr || arcball_ == nullptr ||
|
||||
dvz_panel_bind_controller(panel_, controller, DVZ_DIM_MASK_XYZ) != DVZ_OK)
|
||||
throw std::runtime_error("failed to bind Datoviz arcball controller");
|
||||
if (supports_item_interaction(family)) {
|
||||
if (dvz_visual_set_query_capabilities(
|
||||
visual_, DVZ_QUERY_CAPABILITY_ITEM) != DVZ_OK)
|
||||
throw std::runtime_error("failed to enable Datoviz item queries");
|
||||
item_interaction_ = dvz_item_interaction(panel_, nullptr);
|
||||
if (item_interaction_ == nullptr)
|
||||
throw std::runtime_error("failed to create Datoviz item interaction");
|
||||
}
|
||||
axes_visual_ = dvz_segment(scene_, 0);
|
||||
axes_text_ = dvz_text(panel_, 0);
|
||||
if (axes_visual_ == nullptr || axes_text_ == nullptr ||
|
||||
dvz_segment_set_caps(axes_visual_, DVZ_SEGMENT_CAP_BUTT,
|
||||
DVZ_SEGMENT_CAP_BUTT) != DVZ_OK ||
|
||||
dvz_visual_set_alpha_mode(axes_visual_, DVZ_ALPHA_OPAQUE) != DVZ_OK ||
|
||||
dvz_panel_add_visual(panel_, axes_visual_, nullptr) != DVZ_OK)
|
||||
throw std::runtime_error("failed to create Datoviz 3D axes visuals");
|
||||
DvzTextPlacement axes_placement = dvz_text_placement();
|
||||
axes_placement.mode = DVZ_TEXT_PLACEMENT_DATA;
|
||||
axes_placement.anchor = DVZ_SCENE_ANCHOR_DATA;
|
||||
axes_placement.depth_test = false;
|
||||
DvzTextStyle axes_style = dvz_text_style();
|
||||
axes_style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS;
|
||||
axes_style.size_px = 12.0F;
|
||||
if (dvz_text_set_placement(axes_text_, &axes_placement) != DVZ_OK ||
|
||||
dvz_text_set_style(axes_text_, &axes_style) != DVZ_OK)
|
||||
throw std::runtime_error("failed to configure Datoviz 3D axes text");
|
||||
apply_axes(initial_scene);
|
||||
input_router_ = dvz_input_router();
|
||||
gesture_handler_ = input_router_ != nullptr
|
||||
? dvz_pointer_gesture_handler(input_router_)
|
||||
@@ -779,12 +814,188 @@ void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_P
|
||||
if (input_router_ == nullptr || gesture_handler_ == nullptr ||
|
||||
dvz_panel_connect_input(panel_, input_router_) != DVZ_OK)
|
||||
throw std::runtime_error("failed to connect Datoviz point input");
|
||||
apply_camera(initial_scene.camera);
|
||||
DvzInputResizeEvent resize{
|
||||
initial_scene.viewport.width, initial_scene.viewport.height,
|
||||
initial_scene.viewport.width, initial_scene.viewport.height, 1.0F, 1.0F
|
||||
};
|
||||
dvz_input_emit_resize(input_router_, &resize);
|
||||
}
|
||||
|
||||
void Datoviz_Visual_Backend::apply_axes(const Scene_3D_Parameters& scene) {
|
||||
const std::array descriptors{scene.x_axis, scene.y_axis, scene.z_axis};
|
||||
if (applied_axes_ && *applied_axes_ == descriptors) return;
|
||||
|
||||
using Position = std::array<float, 3>;
|
||||
std::vector<Position> starts;
|
||||
std::vector<Position> ends;
|
||||
std::vector<DvzColor> colors;
|
||||
std::vector<float> widths;
|
||||
const DvzColor axis_color{190, 207, 226, 255};
|
||||
const DvzColor grid_color{58, 70, 86, 255};
|
||||
const DvzColor tick_color{145, 164, 188, 255};
|
||||
const auto segment = [&](Position start, Position end, DvzColor color,
|
||||
float width) {
|
||||
starts.push_back(start);
|
||||
ends.push_back(end);
|
||||
colors.push_back(color);
|
||||
widths.push_back(width);
|
||||
};
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::vector<DvzTextItem> text_items;
|
||||
const auto text = [&](std::string value, Position position,
|
||||
std::array<float, 2> offset,
|
||||
std::array<float, 2> anchor, float size,
|
||||
DvzColor color) {
|
||||
strings.push_back(std::move(value));
|
||||
DvzTextItem item{};
|
||||
item.struct_size = sizeof(DvzTextItem);
|
||||
item.position[0] = position[0];
|
||||
item.position[1] = position[1];
|
||||
item.position[2] = position[2];
|
||||
item.offset[0] = offset[0];
|
||||
item.offset[1] = offset[1];
|
||||
item.anchor[0] = anchor[0];
|
||||
item.anchor[1] = anchor[1];
|
||||
item.size_px = size;
|
||||
item.color = color;
|
||||
text_items.push_back(item);
|
||||
};
|
||||
|
||||
const auto append_axis = [&](const plot::Axis_Descriptor& axis,
|
||||
std::size_t dimension) {
|
||||
if (!axis.visible) return;
|
||||
const auto ticks = plot::axis_ticks(axis);
|
||||
if (dimension == 0)
|
||||
segment({-1, -1, -1}, {1, -1, -1}, axis_color, 2.2F);
|
||||
else if (dimension == 1)
|
||||
segment({-1, -1, -1}, {-1, 1, -1}, axis_color, 2.2F);
|
||||
else
|
||||
segment({-1, -1, -1}, {-1, -1, 1}, axis_color, 2.2F);
|
||||
|
||||
for (const auto& tick : ticks) {
|
||||
const auto position = axis_position(axis, tick.coordinate);
|
||||
if (dimension == 0) {
|
||||
segment({position, -1, -1}, {position, -1.055F, -1},
|
||||
tick_color, 1.4F);
|
||||
if (axis.grid_visible)
|
||||
segment({position, -1, -1}, {position, 1, -1},
|
||||
grid_color, 1.0F);
|
||||
if (axis.labels_visible)
|
||||
text(tick.label, {position, -1, -1}, {0, 12}, {.5F, 0},
|
||||
11, tick_color);
|
||||
} else if (dimension == 1) {
|
||||
segment({-1, position, -1}, {-1.055F, position, -1},
|
||||
tick_color, 1.4F);
|
||||
if (axis.grid_visible)
|
||||
segment({-1, position, -1}, {1, position, -1},
|
||||
grid_color, 1.0F);
|
||||
if (axis.labels_visible)
|
||||
text(tick.label, {-1, position, -1}, {-10, 0}, {1, .5F},
|
||||
11, tick_color);
|
||||
} else {
|
||||
segment({-1, -1, position}, {-1.055F, -1, position},
|
||||
tick_color, 1.4F);
|
||||
if (axis.grid_visible) {
|
||||
segment({-1, -1, position}, {1, -1, position},
|
||||
grid_color, 1.0F);
|
||||
segment({-1, -1, position}, {-1, 1, position},
|
||||
grid_color, 1.0F);
|
||||
}
|
||||
if (axis.labels_visible)
|
||||
text(tick.label, {-1, -1, position}, {-10, 0}, {1, .5F},
|
||||
11, tick_color);
|
||||
}
|
||||
}
|
||||
|
||||
const auto title = axis_title(axis);
|
||||
if (title.empty()) return;
|
||||
if (dimension == 0)
|
||||
text(title, {0, -1, -1}, {0, 34}, {.5F, 0}, 14, axis_color);
|
||||
else if (dimension == 1)
|
||||
text(title, {-1, 0, -1}, {-78, 0}, {1, .5F}, 14, axis_color);
|
||||
else
|
||||
text(title, {-1, -1, 0}, {-78, 0}, {1, .5F}, 14, axis_color);
|
||||
};
|
||||
append_axis(descriptors[0], 0);
|
||||
append_axis(descriptors[1], 1);
|
||||
append_axis(descriptors[2], 2);
|
||||
|
||||
const auto segment_count = static_cast<std::uint32_t>(starts.size());
|
||||
const std::array<DvzVisualDataUpdate, 4> updates{{
|
||||
{"position_start", starts.data(), segment_count},
|
||||
{"position_end", ends.data(), segment_count},
|
||||
{"color", colors.data(), segment_count},
|
||||
{"stroke_width_px", widths.data(), segment_count}}};
|
||||
if (dvz_visual_set_data_many(axes_visual_, updates.data(),
|
||||
static_cast<std::uint32_t>(updates.size())) != DVZ_OK ||
|
||||
dvz_visual_set_visible(axes_visual_, !starts.empty()) != DVZ_OK)
|
||||
throw std::runtime_error("failed to upload Datoviz 3D axes geometry");
|
||||
|
||||
for (std::size_t index = 0; index < text_items.size(); ++index)
|
||||
text_items[index].string = strings[index].c_str();
|
||||
if (dvz_text_set_items(
|
||||
axes_text_, text_items.empty() ? nullptr : text_items.data(),
|
||||
static_cast<std::uint32_t>(text_items.size())) != DVZ_OK)
|
||||
throw std::runtime_error("failed to upload Datoviz 3D axes labels");
|
||||
applied_axes_ = descriptors;
|
||||
}
|
||||
|
||||
void Datoviz_Visual_Backend::apply_camera(const plot::Camera_Descriptor& source) {
|
||||
if (applied_camera_ && *applied_camera_ == source) return;
|
||||
if (turntable_ != nullptr) {
|
||||
if (input_router_ != nullptr)
|
||||
(void)dvz_turntable_disconnect(turntable_, input_router_);
|
||||
dvz_turntable_destroy(turntable_);
|
||||
turntable_ = nullptr;
|
||||
}
|
||||
DvzCameraDesc camera = dvz_camera_desc();
|
||||
const auto assign = [](vec3 target, plot::Spatial_Point value) {
|
||||
target[0] = static_cast<float>(value.x);
|
||||
target[1] = static_cast<float>(value.y);
|
||||
target[2] = static_cast<float>(value.z);
|
||||
};
|
||||
assign(camera.view.eye, source.initial_view.eye);
|
||||
assign(camera.view.target, source.initial_view.target);
|
||||
assign(camera.view.up, source.initial_view.up);
|
||||
camera.projection.type = source.projection == plot::Camera_Projection::orthographic
|
||||
? DVZ_CAMERA_ORTHOGRAPHIC
|
||||
: DVZ_CAMERA_PERSPECTIVE;
|
||||
camera.projection.fov_y = static_cast<float>(
|
||||
source.vertical_field_of_view_degrees * std::numbers::pi / 180.0);
|
||||
camera.projection.near_clip = static_cast<float>(source.near_plane);
|
||||
camera.projection.far_clip = static_cast<float>(source.far_plane);
|
||||
const auto dx = source.initial_view.eye.x - source.initial_view.target.x;
|
||||
const auto dy = source.initial_view.eye.y - source.initial_view.target.y;
|
||||
const auto dz = source.initial_view.eye.z - source.initial_view.target.z;
|
||||
const auto distance = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
camera.projection.ortho_height = static_cast<float>(
|
||||
2.0 * distance *
|
||||
std::tan(source.vertical_field_of_view_degrees * std::numbers::pi / 360.0));
|
||||
if (dvz_panel_set_camera_desc(panel_, &camera) != DVZ_OK)
|
||||
throw std::runtime_error("failed to apply Datoviz Camera component");
|
||||
DvzTurntableDesc turntable = dvz_turntable_desc();
|
||||
turntable.initial_view = camera.view;
|
||||
turntable.yaw_speed = static_cast<float>(source.control.yaw_speed);
|
||||
turntable.pitch_speed = static_cast<float>(source.control.pitch_speed);
|
||||
turntable.zoom_speed = static_cast<float>(source.control.zoom_speed);
|
||||
turntable.pan_speed = static_cast<float>(source.control.pan_speed);
|
||||
turntable.min_pitch = static_cast<float>(source.control.minimum_pitch);
|
||||
turntable.max_pitch = static_cast<float>(source.control.maximum_pitch);
|
||||
turntable.min_distance = static_cast<float>(source.control.minimum_distance);
|
||||
turntable.max_distance = static_cast<float>(source.control.maximum_distance);
|
||||
turntable.controller_flags = DVZ_TURNTABLE_FLAGS_WRAP_YAW |
|
||||
DVZ_TURNTABLE_FLAGS_CLAMP_DISTANCE;
|
||||
if (source.control.pan_enabled)
|
||||
turntable.controller_flags |= DVZ_TURNTABLE_FLAGS_ALLOW_PAN;
|
||||
turntable_ = dvz_turntable_create(&turntable);
|
||||
if (turntable_ == nullptr ||
|
||||
dvz_turntable_set_camera(turntable_, dvz_panel_camera(panel_)) != DVZ_OK ||
|
||||
dvz_turntable_connect(turntable_, input_router_) != DVZ_OK)
|
||||
throw std::runtime_error("failed to connect Datoviz Turntable to Panel Camera");
|
||||
applied_camera_ = source;
|
||||
}
|
||||
void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepared_Visual& point) {
|
||||
require_domain();
|
||||
if (!point.data) throw std::logic_error("3D prepared visual has no immutable payload");
|
||||
@@ -799,6 +1010,8 @@ void Datoviz_Visual_Backend::apply(const Scene_3D_Parameters& scene, const Prepa
|
||||
};
|
||||
dvz_input_emit_resize(input_router_, &resize);
|
||||
}
|
||||
apply_camera(scene.camera);
|
||||
apply_axes(scene);
|
||||
if (point.revision == applied_visual_revision_) return;
|
||||
{
|
||||
mat4 transform{};
|
||||
@@ -958,6 +1171,13 @@ void Datoviz_Visual_Backend::dispatch_pointer(
|
||||
::aethera::Mouse_Button mouse_button,
|
||||
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
|
||||
require_domain();
|
||||
if (applied_camera_) {
|
||||
if (mouse_button == ::aethera::Mouse_Button::left &&
|
||||
!applied_camera_->control.rotate_enabled) return;
|
||||
if ((mouse_button == ::aethera::Mouse_Button::middle ||
|
||||
mouse_button == ::aethera::Mouse_Button::right) &&
|
||||
!applied_camera_->control.pan_enabled) return;
|
||||
}
|
||||
const float width = static_cast<float>(viewport.width);
|
||||
const float height = static_cast<float>(viewport.height);
|
||||
const DvzPointerEventType type =
|
||||
@@ -975,6 +1195,7 @@ void Datoviz_Visual_Backend::dispatch_wheel(
|
||||
float x, float y, float delta_x, float delta_y,
|
||||
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
|
||||
require_domain();
|
||||
if (applied_camera_ && !applied_camera_->control.zoom_enabled) return;
|
||||
dvz_pointer_emit_wheel(
|
||||
input_router_, x, y, static_cast<float>(viewport.width),
|
||||
static_cast<float>(viewport.height), delta_x, delta_y,
|
||||
@@ -984,10 +1205,8 @@ void Datoviz_Visual_Backend::dispatch_key(
|
||||
const ::aethera::Key_Event& event) {
|
||||
require_domain();
|
||||
if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) {
|
||||
if (arcball_ == nullptr || target_extent_.empty()) throw std::runtime_error("failed to reset Datoviz arcball camera");
|
||||
const float width = static_cast<float>(target_extent_.width); const float height = static_cast<float>(target_extent_.height);
|
||||
const auto emit = [&](DvzPointerEventType type) { dvz_pointer_emit_position(input_router_, type, width * 0.5F, height * 0.5F, width, height, DVZ_POINTER_BUTTON_LEFT, DVZ_KEY_MODIFIER_NONE, 1.0F, dvz_input_timestamp_ns(), nullptr); };
|
||||
emit(DVZ_POINTER_EVENT_PRESS); emit(DVZ_POINTER_EVENT_RELEASE); emit(DVZ_POINTER_EVENT_PRESS); emit(DVZ_POINTER_EVENT_RELEASE);
|
||||
if (turntable_ == nullptr || dvz_turntable_reset(turntable_) != DVZ_OK)
|
||||
throw std::runtime_error("failed to reset Datoviz Turntable Camera");
|
||||
return;
|
||||
}
|
||||
const DvzKeyboardEventType type =
|
||||
@@ -1013,7 +1232,7 @@ DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(
|
||||
configuration.clear_color[1] = scene.clear_color.green;
|
||||
configuration.clear_color[2] = scene.clear_color.blue;
|
||||
configuration.clear_color[3] = scene.clear_color.alpha;
|
||||
const auto capabilities = dvz_capability_snapshot();
|
||||
const auto capabilities = offscreen_capabilities();
|
||||
DvzDiagnosticReport report{};
|
||||
dvz_diagnostic_report_init(&report);
|
||||
auto* artifact =
|
||||
@@ -1038,6 +1257,37 @@ std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::sub
|
||||
trace.observed = observe;
|
||||
std::uint64_t phase_started = observe ? trace_now_ns() : 0;
|
||||
apply(scene, point);
|
||||
if (item_interaction_ != nullptr) {
|
||||
static_cast<void>(dvz_figure_process_queries(figure_, runtime_, nullptr));
|
||||
DvzQueryResult query{};
|
||||
bool resolved{};
|
||||
while (dvz_scene_poll_query(scene_, &query)) resolved = true;
|
||||
if (resolved) {
|
||||
if (hover_readout_ != nullptr) {
|
||||
dvz_pinned_readout_destroy(hover_readout_);
|
||||
hover_readout_ = nullptr;
|
||||
}
|
||||
if (query.hit) {
|
||||
if (query.value_kind == DVZ_QUERY_VALUE_NONE) {
|
||||
if (query.has_data_position || query.has_visual_position) {
|
||||
query.value_kind = DVZ_QUERY_VALUE_VEC3;
|
||||
const auto& position = query.has_data_position
|
||||
? query.data_position
|
||||
: query.visual_position;
|
||||
std::ranges::copy(position, query.vector);
|
||||
constexpr char position_label[] = "Position";
|
||||
std::ranges::copy(position_label, query.label);
|
||||
} else {
|
||||
query.value_kind = DVZ_QUERY_VALUE_SCALAR;
|
||||
query.scalar = static_cast<double>(query.item_id);
|
||||
constexpr char item_label[] = "Item";
|
||||
std::ranges::copy(item_label, query.label);
|
||||
}
|
||||
}
|
||||
hover_readout_ = dvz_pinned_readout_query(panel_, &query);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (observe) trace.apply_ns = trace_now_ns() - phase_started;
|
||||
if (target_ == nullptr || target_extent_ != scene.viewport) {
|
||||
target_.reset();
|
||||
@@ -1142,6 +1392,20 @@ void Datoviz_Visual_Backend::destroy() {
|
||||
runtime_ = nullptr;
|
||||
}
|
||||
target_.reset();
|
||||
if (turntable_ != nullptr) {
|
||||
if (input_router_ != nullptr)
|
||||
(void)dvz_turntable_disconnect(turntable_, input_router_);
|
||||
dvz_turntable_destroy(turntable_);
|
||||
turntable_ = nullptr;
|
||||
}
|
||||
if (item_interaction_ != nullptr) {
|
||||
dvz_item_interaction_destroy(item_interaction_);
|
||||
item_interaction_ = nullptr;
|
||||
}
|
||||
if (hover_readout_ != nullptr) {
|
||||
dvz_pinned_readout_destroy(hover_readout_);
|
||||
hover_readout_ = nullptr;
|
||||
}
|
||||
if (panel_ != nullptr && input_router_ != nullptr) (void)dvz_panel_connect_input(panel_, nullptr);
|
||||
if (gesture_handler_ != nullptr) {
|
||||
dvz_pointer_gesture_handler_destroy(gesture_handler_);
|
||||
@@ -1152,7 +1416,10 @@ void Datoviz_Visual_Backend::destroy() {
|
||||
input_router_ = nullptr;
|
||||
}
|
||||
visual_ = nullptr;
|
||||
arcball_ = nullptr;
|
||||
axes_visual_ = nullptr;
|
||||
axes_text_ = nullptr;
|
||||
applied_camera_.reset();
|
||||
applied_axes_.reset();
|
||||
panel_ = nullptr;
|
||||
figure_ = nullptr;
|
||||
if (scene_ != nullptr) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <datoviz/stream/frame_stream.h>
|
||||
#include <datoviz/vk/gpu_ctx.h>
|
||||
#include <datoviz/vklite.h>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -52,6 +53,8 @@ private:
|
||||
class Frame_Target;
|
||||
void require_domain() const;
|
||||
void create_scene(Visual_Family visual_family, const Scene_3D_Parameters& initial_scene);
|
||||
void apply_camera(const plot::Camera_Descriptor& camera);
|
||||
void apply_axes(const Scene_3D_Parameters& scene);
|
||||
void apply(const Scene_3D_Parameters& scene, const Prepared_Visual& visual);
|
||||
[[nodiscard]] DvzSceneFrameArtifact* emit(const Scene_3D_Parameters& scene);
|
||||
void destroy();
|
||||
@@ -62,13 +65,19 @@ private:
|
||||
DvzFigure* figure_{}; /* 当前离屏 Figure。 */
|
||||
DvzPanel* panel_{}; /* 承载唯一 Visual 的全屏 Panel。 */
|
||||
DvzVisual* visual_{}; /* Builder 指定 family 的唯一 Visual。 */
|
||||
DvzArcball* arcball_{}; /* 当前 Panel 相机的交互状态;由 Scene Controller 拥有。 */
|
||||
DvzVisual* axes_visual_{}; /* 三维主轴、刻度和网格 Segment Visual。 */
|
||||
DvzText* axes_text_{}; /* 随相机变换的三维刻度与轴标题。 */
|
||||
DvzItemInteraction* item_interaction_{}; /* Datoviz 原生图元悬停与选择控制器。 */
|
||||
DvzPinnedReadout* hover_readout_{}; /* 最近一次命中的 Datoviz 数据提示卡。 */
|
||||
DvzTurntable* turntable_{}; /* 直接驱动 Panel Camera 的独立 Turntable。 */
|
||||
DvzInputRouter* input_router_{}; /* Scene 输入事件路由器。 */
|
||||
DvzPointerGestureHandler* gesture_handler_{}; /* 指针手势解析器。 */
|
||||
std::unique_ptr<Frame_Target> target_; /* 当前尺寸对应的离屏提交和读回目标。 */
|
||||
Extent target_extent_{}; /* target_ 当前适配的像素尺寸。 */
|
||||
std::uint64_t target_generation_{}; /* 每次重建 target_ 时递增的资源代次。 */
|
||||
std::uint64_t applied_visual_revision_{}; /* 已上传到 Datoviz 的 Prepared 版本。 */
|
||||
std::optional<plot::Camera_Descriptor> applied_camera_{}; /* 已应用到 Panel 的 Camera 配置。 */
|
||||
std::optional<std::array<plot::Axis_Descriptor, 3>> applied_axes_{}; /* 已生成 Visual 的轴描述快照。 */
|
||||
};
|
||||
} // namespace aethera::render_3d::detail
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
#include "../axis/Axes_3D.hpp"
|
||||
#include "../base/Frame_3D.hpp"
|
||||
#include "../camera/Camera_3D.hpp"
|
||||
#include "../detail/Backend_Types.hpp"
|
||||
#include "../visual/Visuals.hpp"
|
||||
#include <scene.hpp>
|
||||
#include <array>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -24,13 +27,28 @@ struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dependency_Graph_Type<Submi
|
||||
template <typename Object>
|
||||
struct Builder : Prev_Builder<Object> {
|
||||
using Base = Prev_Builder<Object>;
|
||||
using Final_Builder = typename Object::Builder;
|
||||
Builder();
|
||||
template <Attached Visual_Object>
|
||||
explicit Builder(Visual_Object* visual, std::uint32_t gpu_index = 0, bool validation_enabled = false);
|
||||
Final_Builder& add_renderable(Visual_Object* visual);
|
||||
template <Attached Camera_Object>
|
||||
requires std::derived_from<Camera_Object, Camera_3D>
|
||||
Final_Builder& add_camera(Camera_Object* camera);
|
||||
template <Attached Axes_Object>
|
||||
requires std::derived_from<Axes_Object, Axes_3D>
|
||||
Final_Builder& add_axes(Axes_Object* axes);
|
||||
Final_Builder& use_gpu(std::uint32_t gpu_index, bool validation_enabled = false);
|
||||
[[nodiscard]] std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> build();
|
||||
private:
|
||||
Root* visual{}; /* 不拥有的唯一 Visual;生命周期必须覆盖 Scene。 */
|
||||
Visual_Family visual_family{Visual_Family::point}; /* Visual 编译期 Spec 对应的后端 family。 */
|
||||
void (*bind_visual)(Root*, std::shared_ptr<void>){}; /* 把 Scene 拥有的弱提交上下文绑定到最终 Visual Private。 */
|
||||
Root* camera{}; /* 不拥有的 Camera 组件;生命周期必须覆盖 Scene。 */
|
||||
Root* axes{}; /* 不拥有的三轴组件;生命周期必须覆盖 Scene。 */
|
||||
using Camera_Read = plot::Camera_Descriptor (*)(const Root*);
|
||||
using Axes_Read = std::array<plot::Axis_Descriptor, 3> (*)(const Root*);
|
||||
Camera_Read read_camera{};
|
||||
Axes_Read read_axes{};
|
||||
std::uint32_t gpu_index{}; /* Datoviz 使用的物理 GPU 下标。 */
|
||||
bool validation_enabled{}; /* 是否启用 Vulkan 验证。 */
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ struct Scene_Paint_Context {
|
||||
std::shared_ptr<Async_Render_Backend> backend{}; /* Scene 拥有、异步命令延长生命周期的后端。 */
|
||||
Object* scene{}; /* 仅在 Scene 拥有本上下文期间读取当前 Prop。 */
|
||||
Frame_3D* frame{}; /* 当前 Submit 图对应的外部帧;process 返回后清空。 */
|
||||
Scene_3D_Parameters parameters{}; /* 本帧从 Scene 组件读取的一致快照。 */
|
||||
};
|
||||
}
|
||||
struct Render_Scene_3D::Private : Prev_Private {
|
||||
@@ -23,10 +24,19 @@ struct Render_Scene_3D::Private : Prev_Private {
|
||||
};
|
||||
std::shared_ptr<detail::Async_Render_Backend> backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */
|
||||
std::shared_ptr<void> paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */
|
||||
Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */
|
||||
Root* axes_component{}; /* Builder 绑定的三轴组件。 */
|
||||
plot::Camera_Descriptor (*read_camera)(const Root*){}; /* 读取 Camera 当前配置。 */
|
||||
std::array<plot::Axis_Descriptor, 3> (*read_axes)(const Root*){}; /* 读取三轴当前配置。 */
|
||||
Frame_3D* active_frame{}; /* 当前同步 process 借用的外部帧;提交完成后清空。 */
|
||||
const Dispatch* dispatch{}; /* 最终 Scene 类型对应的静态公开分派表。 */
|
||||
/* Builder 内部初始化后端;必须在绑定 Visual Paint 目标之前调用一次。 */
|
||||
template <Attached Object> void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family);
|
||||
template <Attached Object>
|
||||
void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled,
|
||||
Visual_Family visual_family, Root* camera, Root* axes,
|
||||
plot::Camera_Descriptor (*camera_reader)(const Root*),
|
||||
std::array<plot::Axis_Descriptor, 3> (*axes_reader)(const Root*));
|
||||
template <Attached Object> [[nodiscard]] detail::Scene_3D_Parameters parameters(Object* object) const;
|
||||
/* CRTP 覆盖:绑定 Scene 机制和 Render_Scene_3D 公开薄壳。 */
|
||||
template <Attached Object> void bind_private_crtp(Object* object);
|
||||
/* CRTP 覆盖:先执行 Kernel Prepare Taskflow,再运行只负责异步入队的三维 Submit 阶段。 */
|
||||
@@ -36,20 +46,105 @@ struct Render_Scene_3D::Private : Prev_Private {
|
||||
template <Attached Object> [[nodiscard]] Render_Result render(Object* object, Frame_3D* frame);
|
||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||
};
|
||||
template <typename Object>
|
||||
Render_Scene_3D::Builder<Object>::Builder() : Base() {}
|
||||
template <typename Object> template <Attached Visual_Object>
|
||||
Render_Scene_3D::Builder<Object>::Builder(Visual_Object* visual_value, std::uint32_t gpu_index_value, bool validation_enabled_value) : Base(), visual(visual_value), visual_family(Visual_Object::Attached_Object::Specification::family), gpu_index(gpu_index_value), validation_enabled(validation_enabled_value) {
|
||||
bind_visual = [](Root* root, std::shared_ptr<void> context) { auto* object = static_cast<Visual_Object*>(root); Base::private_access(object).template get<typename Visual_Object::Attached_Object::Base_Tag>().bind_paint_target(std::move(context), [](void* raw_context, const detail::Prepared_Visual& prepared) { auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context); const auto& prop = submission.scene->template read_prop<Render_Scene_3D::Base_Tag>(); submission.backend->render(prepared, detail::Scene_3D_Parameters{prop.viewport, prop.clear_color}, submission.frame); }); };
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::add_renderable(Visual_Object* visual_value) {
|
||||
if (!visual_value) throw std::invalid_argument("Render_Scene_3D cannot attach a null Visual");
|
||||
if (visual) throw std::logic_error("Render_Scene_3D currently accepts one primary Visual");
|
||||
visual = visual_value;
|
||||
visual_family = Visual_Object::Attached_Object::Specification::family;
|
||||
bind_visual = [](Root* root, std::shared_ptr<void> context) {
|
||||
auto* object = static_cast<Visual_Object*>(root);
|
||||
Base::private_access(object).template get<typename Visual_Object::Attached_Object::Base_Tag>()
|
||||
.bind_paint_target(std::move(context), [](void* raw_context, const detail::Prepared_Visual& prepared) {
|
||||
auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context);
|
||||
submission.backend->render(prepared, submission.parameters, submission.frame);
|
||||
});
|
||||
};
|
||||
this->template add_dependency_node<Prepare_Data_Tag>(visual_value);
|
||||
this->template add_dependency_node<Submit_Tag>(visual_value);
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
}
|
||||
template <typename Object> template <Attached Camera_Object>
|
||||
requires std::derived_from<Camera_Object, Camera_3D>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::add_camera(Camera_Object* camera_value) {
|
||||
if (!camera_value) throw std::invalid_argument("Render_Scene_3D cannot attach a null Camera");
|
||||
if (camera) throw std::logic_error("Render_Scene_3D accepts one Camera component");
|
||||
camera = camera_value;
|
||||
read_camera = [](const Root* root) {
|
||||
const auto& prop = static_cast<const Camera_Object*>(root)->template read_prop<Camera_3D::Base_Tag>();
|
||||
return plot::Camera_Descriptor{prop.initial_view, prop.projection, prop.control,
|
||||
prop.vertical_field_of_view_degrees,
|
||||
prop.near_plane, prop.far_plane};
|
||||
};
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
}
|
||||
template <typename Object> template <Attached Axes_Object>
|
||||
requires std::derived_from<Axes_Object, Axes_3D>
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::add_axes(Axes_Object* axes_value) {
|
||||
if (!axes_value) throw std::invalid_argument("Render_Scene_3D cannot attach null Axes");
|
||||
if (axes) throw std::logic_error("Render_Scene_3D accepts one Axes component");
|
||||
axes = axes_value;
|
||||
read_axes = [](const Root* root) {
|
||||
const auto& prop = static_cast<const Axes_Object*>(root)->template read_prop<Axes_3D::Base_Tag>();
|
||||
return std::array<plot::Axis_Descriptor, 3>{prop.x_axis, prop.y_axis, prop.z_axis};
|
||||
};
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
}
|
||||
template <typename Object>
|
||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_3D::Builder<Object>::build() { if (!visual || !bind_visual) throw std::invalid_argument("Render_Scene_3D requires one Visual"); auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto scene = std::move(result).value(); auto& private_data = Base::private_access(scene.get()).template get<Render_Scene_3D::Base_Tag>(); private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, visual_family); bind_visual(visual, private_data.paint_context); return std::move(scene); }
|
||||
template <Attached Object> void Render_Scene_3D::Private::initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family) { const auto& prop = object->template read_prop<Render_Scene_3D::Base_Tag>(); backend = std::make_shared<detail::Async_Render_Backend>(gpu_index, validation_enabled, visual_family, detail::Scene_3D_Parameters{prop.viewport, prop.clear_color}); paint_context = std::make_shared<detail::Scene_Paint_Context<Object>>(detail::Scene_Paint_Context<Object>{backend, object, nullptr}); }
|
||||
typename Render_Scene_3D::Builder<Object>::Final_Builder&
|
||||
Render_Scene_3D::Builder<Object>::use_gpu(std::uint32_t gpu_index_value,
|
||||
bool validation_enabled_value) {
|
||||
gpu_index = gpu_index_value;
|
||||
validation_enabled = validation_enabled_value;
|
||||
return static_cast<Final_Builder&>(*this);
|
||||
}
|
||||
template <typename Object>
|
||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_3D::Builder<Object>::build() {
|
||||
if (!visual || !bind_visual) throw std::invalid_argument("Render_Scene_3D requires one Visual");
|
||||
if (!camera || !read_camera) throw std::invalid_argument("Render_Scene_3D requires one Camera component");
|
||||
if (!axes || !read_axes) throw std::invalid_argument("Render_Scene_3D requires one Axes component");
|
||||
auto result = Base::build();
|
||||
if (!result) return std::unexpected(result.error());
|
||||
auto scene = std::move(result).value();
|
||||
auto& private_data = Base::private_access(scene.get()).template get<Render_Scene_3D::Base_Tag>();
|
||||
private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, visual_family,
|
||||
camera, axes, read_camera, read_axes);
|
||||
bind_visual(visual, private_data.paint_context);
|
||||
return scene;
|
||||
}
|
||||
template <Attached Object>
|
||||
detail::Scene_3D_Parameters Render_Scene_3D::Private::parameters(Object* object) const {
|
||||
const auto& prop = object->template read_prop<Render_Scene_3D::Base_Tag>();
|
||||
const auto axis = read_axes(axes_component);
|
||||
return {prop.viewport, prop.clear_color, read_camera(camera_component), axis[0], axis[1], axis[2]};
|
||||
}
|
||||
template <Attached Object>
|
||||
void Render_Scene_3D::Private::initialize_backend(
|
||||
Object* object, std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family,
|
||||
Root* camera, Root* axes, plot::Camera_Descriptor (*camera_reader)(const Root*),
|
||||
std::array<plot::Axis_Descriptor, 3> (*axes_reader)(const Root*)) {
|
||||
camera_component = camera;
|
||||
axes_component = axes;
|
||||
read_camera = camera_reader;
|
||||
read_axes = axes_reader;
|
||||
const auto initial = parameters(object);
|
||||
backend = std::make_shared<detail::Async_Render_Backend>(gpu_index, validation_enabled,
|
||||
visual_family, initial);
|
||||
paint_context = std::make_shared<detail::Scene_Paint_Context<Object>>(
|
||||
detail::Scene_Paint_Context<Object>{backend, object, nullptr, initial});
|
||||
}
|
||||
inline void Render_Scene_3D::Private::dispatch_events(Extent viewport) { this->consume_events([&](const std::shared_ptr<Event>& event) { const auto result = backend->dispatch_event(event, viewport); return result != detail::Async_Render_Backend::Dispatch_Event_Result::queue_full && result != detail::Async_Render_Backend::Dispatch_Event_Result::backend_unavailable; }); }
|
||||
template <Attached Object, typename Callback>
|
||||
void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requires std::invocable<Callback> {
|
||||
Frame_3D* frame = active_frame;
|
||||
if (!frame) throw std::logic_error("3D Scene process has no external frame");
|
||||
camera_component->advance_object();
|
||||
axes_component->advance_object();
|
||||
const auto& prop = object->template read_prop<Render_Scene_3D::Base_Tag>();
|
||||
if (!prop.view_active || prop.viewport.empty() || !backend || !backend->available()) return;
|
||||
frame->mark(Frame_Trace_Marker::scene_render_started);
|
||||
@@ -57,6 +152,7 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
|
||||
dispatch_events(prop.viewport);
|
||||
frame->mark(Frame_Trace_Marker::event_dispatch_finished);
|
||||
auto context = std::static_pointer_cast<detail::Scene_Paint_Context<Object>>(paint_context);
|
||||
context->parameters = parameters(object);
|
||||
struct Frame_Context_Scope {
|
||||
Frame_3D*& target; /* Visual Submit 回调读取的当前外部帧槽位。 */
|
||||
Frame_3D* previous{}; /* 嵌套调用前的帧;析构时恢复。 */
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
明白,只定义 **用户看到的交互能力**,不定义数据、不定义内部实现。下面按图类型分类。
|
||||
|
||||
# 3D Plot Interaction Requirement
|
||||
|
||||
## 1. 3D Waterfall Plot(三维瀑布图)
|
||||
|
||||
### 基础交互
|
||||
|
||||
* 旋转视角
|
||||
* 缩放视图
|
||||
* 平移视图
|
||||
* 重置视角
|
||||
|
||||
### 时间交互
|
||||
|
||||
* 时间窗口调整
|
||||
* 时间范围拖动
|
||||
* 实时滚动
|
||||
* 暂停刷新
|
||||
* 继续刷新
|
||||
* 查看历史区域
|
||||
|
||||
### 数据查看
|
||||
|
||||
* 鼠标悬停显示当前位置数据
|
||||
* 点击显示详细信息
|
||||
* 添加标记点
|
||||
* 删除标记点
|
||||
* 显示峰值位置
|
||||
|
||||
### 分析交互
|
||||
|
||||
* 查看指定时间截面
|
||||
* 查看指定频率截面
|
||||
* 切换俯视图
|
||||
* 切换三维视图
|
||||
* 调整高度显示比例
|
||||
|
||||
---
|
||||
|
||||
# 2. 3D Surface Plot(三维曲面图)
|
||||
|
||||
### 基础交互
|
||||
|
||||
* 旋转曲面
|
||||
* 缩放曲面
|
||||
* 平移曲面
|
||||
* 重置视角
|
||||
|
||||
### 显示控制
|
||||
|
||||
* 显示/隐藏网格
|
||||
* 显示/隐藏坐标轴
|
||||
* 显示/隐藏刻度
|
||||
* 切换曲面显示模式
|
||||
* 调整透明度
|
||||
|
||||
### 数据观察
|
||||
|
||||
* 鼠标悬停查看点信息
|
||||
* 点击选择点
|
||||
* 显示局部峰值
|
||||
* 显示局部最低值
|
||||
|
||||
### 分析交互
|
||||
|
||||
* 显示等高线
|
||||
* 显示投影平面
|
||||
* 调整高度比例
|
||||
* 调整颜色范围
|
||||
|
||||
---
|
||||
|
||||
# 3. 3D Scatter Plot(三维散点图)
|
||||
|
||||
### 基础交互
|
||||
|
||||
* 旋转观察
|
||||
* 缩放
|
||||
* 平移
|
||||
* 重置视角
|
||||
|
||||
### 点操作
|
||||
|
||||
* 悬停显示点信息
|
||||
* 点击选择点
|
||||
* 多选点
|
||||
* 框选区域
|
||||
* 取消选择
|
||||
|
||||
### 显示控制
|
||||
|
||||
* 调整点大小
|
||||
* 调整点透明度
|
||||
* 显示/隐藏点分类
|
||||
* 显示/隐藏辅助线
|
||||
|
||||
### 分析交互
|
||||
|
||||
* 点过滤
|
||||
* 区域过滤
|
||||
* 聚类显示
|
||||
* 高亮指定点集合
|
||||
|
||||
---
|
||||
|
||||
# 4. 3D Point Cloud(三维点云图)
|
||||
|
||||
### 基础交互
|
||||
|
||||
* 旋转点云
|
||||
* 缩放点云
|
||||
* 平移点云
|
||||
* 重置视角
|
||||
|
||||
### 点云浏览
|
||||
|
||||
* 自动调整观察距离
|
||||
* 点密度调整
|
||||
* 显示级别调整
|
||||
* 局部放大
|
||||
|
||||
### 选择操作
|
||||
|
||||
* 单点选择
|
||||
* 区域选择
|
||||
* 框选
|
||||
* 删除选择
|
||||
* 隐藏选择
|
||||
|
||||
### 显示控制
|
||||
|
||||
* 点大小调整
|
||||
* 颜色模式切换
|
||||
* 透明度调整
|
||||
|
||||
---
|
||||
|
||||
# 5. 3D Volume Plot(三维体数据图)
|
||||
|
||||
### 基础交互
|
||||
|
||||
* 旋转体数据
|
||||
* 缩放
|
||||
* 平移
|
||||
* 重置视角
|
||||
|
||||
### 切割查看
|
||||
|
||||
* X方向切片
|
||||
* Y方向切片
|
||||
* Z方向切片
|
||||
* 移动切片位置
|
||||
* 多切片显示
|
||||
|
||||
### 显示控制
|
||||
|
||||
* 调整透明度
|
||||
* 调整显示范围
|
||||
* 调整颜色映射
|
||||
* 隐藏低强度区域
|
||||
|
||||
### 分析交互
|
||||
|
||||
* 区域选择
|
||||
* 局部放大
|
||||
* 数据点查看
|
||||
|
||||
---
|
||||
|
||||
# 6. 3D Polar Plot(三维极坐标图)
|
||||
|
||||
### 基础交互
|
||||
|
||||
* 旋转方向
|
||||
* 缩放
|
||||
* 平移
|
||||
* 重置视角
|
||||
|
||||
### 方向查看
|
||||
|
||||
* 查看指定角度
|
||||
* 查看指定方向数据
|
||||
* 显示方向标记
|
||||
|
||||
### 显示控制
|
||||
|
||||
* 切换极坐标/直角坐标
|
||||
* 显示辅助圆环
|
||||
* 显示角度刻度
|
||||
|
||||
---
|
||||
|
||||
# 7. 通用 3D Plot 交互
|
||||
|
||||
所有 3D 图统一支持:
|
||||
|
||||
## Camera
|
||||
|
||||
* Rotate
|
||||
* Zoom
|
||||
* Pan
|
||||
* Reset View
|
||||
|
||||
## Selection
|
||||
|
||||
* Hover
|
||||
* Select
|
||||
* Multi Select
|
||||
* Clear Selection
|
||||
|
||||
## Marker
|
||||
|
||||
* Add Marker
|
||||
* Move Marker
|
||||
* Delete Marker
|
||||
|
||||
## Axis
|
||||
|
||||
* Axis Visibility
|
||||
* Axis Range
|
||||
* Scale Adjustment
|
||||
|
||||
## Display
|
||||
|
||||
* Show/Hide Grid
|
||||
* Show/Hide Label
|
||||
* Transparency Control
|
||||
|
||||
## Navigation
|
||||
|
||||
* Fit View
|
||||
* Full Screen
|
||||
* Save View
|
||||
* Restore View
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user