From 9c68029ade407f3867f0077f7bd1f12c95033d6b Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 14 Aug 2026 19:24:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A7=E7=BB=93=E6=9E=84=E6=94=B9=E5=AE=8C?= =?UTF-8?q?=20=20=E4=B8=AD=E9=97=B4=E5=B1=82=E5=8A=A0=E5=AE=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render_3D/render_3D/Datoviz_Visuals.cpp | 4 + render_3D/render_3D/Datoviz_Visuals.h | 377 ++++++++++++++++++ render_3D/render_3D/Glyph_Visual.h | 2 + render_3D/render_3D/Image_Visual.h | 2 + render_3D/render_3D/Labels_Visual.h | 2 + render_3D/render_3D/Marker_Visual.h | 2 + render_3D/render_3D/Mesh_Visual.h | 2 + render_3D/render_3D/Path_Visual.h | 2 + render_3D/render_3D/Pixel_Visual.h | 2 + render_3D/render_3D/Point_Scene.cpp | 9 +- render_3D/render_3D/Point_Scene.h | 19 + render_3D/render_3D/Primitive_Visual.h | 2 + render_3D/render_3D/Segment_Visual.h | 2 + render_3D/render_3D/Sphere_Visual.h | 2 + render_3D/render_3D/Splat_Visual.h | 2 + render_3D/render_3D/Text_Visual.h | 2 + render_3D/render_3D/Vector_Visual.h | 2 + render_3D/render_3D/Volume_Visual.h | 2 + ...Backend.cpp => Datoviz_Visual_Backend.cpp} | 231 +++++++++-- ...int_Backend.h => Datoviz_Visual_Backend.h} | 14 +- .../tests/Datoviz_Visual_Family_Tests.cpp | 57 +++ web_server/app/Gallery_Plot_Session.cpp | 2 +- web_server/app/Gallery_Protocol.cpp | 24 +- web_server/app/render_3D/Gallery_Scene3D.cpp | 21 +- web_server/tests/Datoviz_Gallery_Tests.cpp | 14 + web_server/tests/Web_Bridge_Tests.cpp | 4 +- 26 files changed, 747 insertions(+), 57 deletions(-) create mode 100644 render_3D/render_3D/Datoviz_Visuals.cpp create mode 100644 render_3D/render_3D/Datoviz_Visuals.h create mode 100644 render_3D/render_3D/Glyph_Visual.h create mode 100644 render_3D/render_3D/Image_Visual.h create mode 100644 render_3D/render_3D/Labels_Visual.h create mode 100644 render_3D/render_3D/Marker_Visual.h create mode 100644 render_3D/render_3D/Mesh_Visual.h create mode 100644 render_3D/render_3D/Path_Visual.h create mode 100644 render_3D/render_3D/Pixel_Visual.h create mode 100644 render_3D/render_3D/Primitive_Visual.h create mode 100644 render_3D/render_3D/Segment_Visual.h create mode 100644 render_3D/render_3D/Sphere_Visual.h create mode 100644 render_3D/render_3D/Splat_Visual.h create mode 100644 render_3D/render_3D/Text_Visual.h create mode 100644 render_3D/render_3D/Vector_Visual.h create mode 100644 render_3D/render_3D/Volume_Visual.h rename render_3D/render_3D/detail/{Datoviz_Point_Backend.cpp => Datoviz_Visual_Backend.cpp} (71%) rename render_3D/render_3D/detail/{Datoviz_Point_Backend.h => Datoviz_Visual_Backend.h} (74%) create mode 100644 render_3D/tests/Datoviz_Visual_Family_Tests.cpp diff --git a/render_3D/render_3D/Datoviz_Visuals.cpp b/render_3D/render_3D/Datoviz_Visuals.cpp new file mode 100644 index 0000000..c3125e1 --- /dev/null +++ b/render_3D/render_3D/Datoviz_Visuals.cpp @@ -0,0 +1,4 @@ +#include "Datoviz_Visuals.h" + +// The visual family is template-based intentionally: every concrete type retains its own +// State/Impl/Observer chain while sharing the storage and validation mechanism. diff --git a/render_3D/render_3D/Datoviz_Visuals.h b/render_3D/render_3D/Datoviz_Visuals.h new file mode 100644 index 0000000..e0ea8ff --- /dev/null +++ b/render_3D/render_3D/Datoviz_Visuals.h @@ -0,0 +1,377 @@ +#pragma once + +#include "Point_Visual.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace renderive::render_3d { + +struct Vec2 { + float x{}; + float y{}; + bool operator==(const Vec2&) const = default; +}; + +struct Vec4 { + float x{}; + float y{}; + float z{}; + float w{}; + bool operator==(const Vec4&) const = default; +}; + +struct Splat { + Vec3 position; + Rgba8 color; + Vec2 sigma{0.05F, 0.05F}; + float angle{}; + bool operator==(const Splat&) const = default; +}; + +struct Pixel { + Vec3 position; + Rgba8 color; + float size_px{1.0F}; + bool operator==(const Pixel&) const = default; +}; + +enum struct Marker_Shape : std::uint8_t { Disc, Square, Triangle, Diamond, Cross }; +struct Marker { + Vec3 position; + Rgba8 color; + float diameter_px{12.0F}; + float angle{}; + Marker_Shape shape{Marker_Shape::Disc}; + bool operator==(const Marker&) const = default; +}; + +struct Sphere { + Vec3 center; + Rgba8 color; + float radius{0.1F}; + bool operator==(const Sphere&) const = default; +}; + +struct Segment { + Vec3 start; + Vec3 end; + Rgba8 color; + float width_px{1.0F}; + bool operator==(const Segment&) const = default; +}; + +struct Vector_Glyph { + Vec3 origin; + Vec3 direction{1.0F, 0.0F, 0.0F}; + Rgba8 color; + float width_px{1.0F}; + bool operator==(const Vector_Glyph&) const = default; +}; + +struct Primitive_Vertex { + Vec3 position; + Rgba8 color; + Vec3 normal; + bool operator==(const Primitive_Vertex&) const = default; +}; + +struct Mesh_Vertex { + Vec3 position; + Rgba8 color; + Vec3 normal; + Vec2 texture_coordinate; + bool operator==(const Mesh_Vertex&) const = default; +}; + +struct Path_Vertex { + Vec3 position; + Rgba8 color; + float width_px{1.0F}; + bool operator==(const Path_Vertex&) const = default; +}; + +struct Image { + Vec3 position; + Vec2 extent{1.0F, 1.0F}; + Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; + Rgba8 tint; + bool operator==(const Image&) const = default; +}; + +struct Label { + Vec3 position; + std::uint32_t image_index{}; + Vec2 extent{32.0F, 16.0F}; + Rgba8 tint; + bool operator==(const Label&) const = default; +}; + +struct Glyph { + Vec3 position; + Vec4 bounds; + Vec4 texture_coordinates; + Rgba8 color; + float angle{}; + bool operator==(const Glyph&) const = default; +}; + +struct Text_Label { + Vec3 position; + std::string text; + Rgba8 color; + float size_px{18.0F}; + bool operator==(const Text_Label&) const = default; +}; + +struct Voxel { + float value{}; + bool operator==(const Voxel&) const = default; +}; + +enum struct Primitive_Topology : std::uint8_t { + Point_List, + Line_List, + Line_Strip, + Triangle_List, + Triangle_Strip, +}; + +namespace detail { + +template +concept Finite_Visual_Item = requires(const T& item) { + { T::valid(item) } -> std::convertible_to; +}; + +inline bool finite(float value) noexcept { return std::isfinite(value); } +inline bool finite(Vec2 value) noexcept { return finite(value.x) && finite(value.y); } +inline bool finite(Vec3 value) noexcept { + return finite(value.x) && finite(value.y) && finite(value.z); +} +inline bool finite(Vec4 value) noexcept { + return finite(value.x) && finite(value.y) && finite(value.z) && finite(value.w); +} + +struct Visual_Settings {}; +struct Primitive_Settings { + Primitive_Topology topology{Primitive_Topology::Triangle_List}; + bool operator==(const Primitive_Settings&) const = default; +}; +struct Texture_Field_Settings { + std::uint32_t field_width{}; + std::uint32_t field_height{}; + bool operator==(const Texture_Field_Settings&) const = default; +}; +struct Volume_Field_Settings : Texture_Field_Settings { + std::uint32_t field_depth{}; + bool operator==(const Volume_Field_Settings&) const = default; +}; + +#define RENDERIVE_VISUAL_SPEC(Name, Item_Type, Settings_Type, Expression) \ + struct Name##_Spec { \ + using Item = Item_Type; \ + using Settings = Settings_Type; \ + static bool valid(const Item& item) noexcept { return (Expression); } \ + static constexpr const char* name = #Name; \ + } + +RENDERIVE_VISUAL_SPEC(Splat, Splat, Visual_Settings, + finite(item.position) && finite(item.sigma) && + item.sigma.x > 0 && item.sigma.y > 0 && finite(item.angle)); +RENDERIVE_VISUAL_SPEC(Pixel, Pixel, Visual_Settings, + finite(item.position) && finite(item.size_px) && item.size_px > 0); +RENDERIVE_VISUAL_SPEC(Marker, Marker, Visual_Settings, + finite(item.position) && finite(item.diameter_px) && + item.diameter_px > 0 && finite(item.angle)); +RENDERIVE_VISUAL_SPEC(Sphere, Sphere, Visual_Settings, + finite(item.center) && finite(item.radius) && item.radius > 0); +RENDERIVE_VISUAL_SPEC(Segment, Segment, Visual_Settings, + finite(item.start) && finite(item.end) && finite(item.width_px) && + item.width_px > 0); +RENDERIVE_VISUAL_SPEC(Vector, Vector_Glyph, Visual_Settings, + finite(item.origin) && finite(item.direction) && + finite(item.width_px) && item.width_px > 0); +RENDERIVE_VISUAL_SPEC(Primitive, Primitive_Vertex, Primitive_Settings, + finite(item.position) && finite(item.normal)); +RENDERIVE_VISUAL_SPEC(Mesh, Mesh_Vertex, Visual_Settings, + finite(item.position) && finite(item.normal) && + finite(item.texture_coordinate)); +RENDERIVE_VISUAL_SPEC(Path, Path_Vertex, Visual_Settings, + finite(item.position) && finite(item.width_px) && item.width_px > 0); +RENDERIVE_VISUAL_SPEC(Image, Image, Texture_Field_Settings, + finite(item.position) && finite(item.extent) && + item.extent.x > 0 && item.extent.y > 0 && + finite(item.texture_rectangle)); +RENDERIVE_VISUAL_SPEC(Labels, Label, Texture_Field_Settings, + finite(item.position) && finite(item.extent) && + item.extent.x > 0 && item.extent.y > 0); +RENDERIVE_VISUAL_SPEC(Glyph, Glyph, Texture_Field_Settings, + finite(item.position) && finite(item.bounds) && + finite(item.texture_coordinates) && finite(item.angle)); +RENDERIVE_VISUAL_SPEC(Text, Text_Label, Visual_Settings, + finite(item.position) && !item.text.empty() && + finite(item.size_px) && item.size_px > 0); +RENDERIVE_VISUAL_SPEC(Volume, Voxel, Volume_Field_Settings, finite(item.value)); + +#undef RENDERIVE_VISUAL_SPEC + +template +struct Visual_Data_Tag {}; + +template +struct Visual_Data_Observation { + std::uint64_t revision{}; + std::size_t item_count{}; +}; + +template +struct Basic_Visual : Renderable> { + using Base = Renderable>; + using Item = typename Spec::Item; + using Payload = std::shared_ptr>; + using Buffer = ::Multi_Double_Buffer_Strategy< + ::Double_Buffer_Layout<::Buffered_Data, Payload>>, + std::mutex>; + + struct State : Base::template next_State, Spec::Settings { + Matrix4 transform; + bool visible{true}; + bool depth_test{true}; + bool operator==(const State&) const = default; + }; + + struct State_Validator { + void operator()(const State& state) const { + for (float value : state.transform.values) { + if (!finite(value)) + throw std::invalid_argument("visual transform must be finite"); + } + if constexpr (requires { state.field_width; state.field_height; }) { + const bool any_dimension = state.field_width || state.field_height; + const bool all_dimensions = state.field_width && state.field_height; + if (any_dimension && !all_dimensions) + throw std::invalid_argument( + "texture field dimensions must be specified together"); + } + if constexpr (requires { state.field_depth; }) { + const bool any_dimension = state.field_width || state.field_height || + state.field_depth; + const bool all_dimensions = state.field_width && state.field_height && + state.field_depth; + if (any_dimension && !all_dimensions) + throw std::invalid_argument( + "volume field dimensions must be specified together"); + } + } + }; + + template + using Business_Builder = Renderable_Builder; + + struct Impl : Base::template next_Impl, Buffer { + struct Observer : Base::template next_Impl::next_Observer { + static void handle(Impl& impl, + const Renderable_Event_View& observation) noexcept { + if (const auto* data = observation.template payload_if< + Visual_Data_Observation>()) + impl.observe_data(data->revision, data->item_count); + } + }; + + explicit Impl(std::vector items) { update(std::move(items)); } + + void update(std::vector items) { + for (const auto& item : items) { + if (!Spec::valid(item)) + throw std::invalid_argument(std::string(Spec::name) + + " payload contains invalid data"); + } + const auto count = items.size(); + this->template write>( + std::make_shared>(std::move(items))); + this->report_observation(Visual_Data_Observation{ + this->template cache_revision>(), count}); + } + }; + + explicit Basic_Visual(const State& state, std::vector items = {}) + : Base(With_Attached_Impl{}, state, std::move(items)) { + State_Validator{}(state); + } + ~Basic_Visual() override = default; + + void update_items(std::vector items) { + this->template d_func().update(std::move(items)); + this->d_func().changed(); + } + + void edit_items(const std::function&)>& edit) { + if (!edit) + throw std::invalid_argument("visual item edit is empty"); + const auto& current = this->template d_func() + .template cache_buffer_value>(); + auto next = current ? *current : std::vector{}; + edit(next); + update_items(std::move(next)); + } + + [[nodiscard]] std::size_t item_count() const noexcept { + const auto& data = this->template d_func() + .template cache_buffer_value>(); + return data ? data->size() : 0U; + } + + [[nodiscard]] std::uint64_t data_revision() const noexcept { + return this->template d_func() + .template cache_revision>(); + } + +protected: + template Value> + void set_state(Value&& value) { + auto next = Base::template state(); + next.*Member = std::forward(value); + State_Validator{}(next); + Base::template update_state( + [next = std::move(next)](State_Type& target) mutable { + target = std::move(next); + }); + } +}; + +} // namespace detail + +#define RENDERIVE_VISUAL_ALIAS(Name) \ + using Name##_Visual = \ + ::renderive::renderable::attach>; \ + using Name##_State = Name##_Visual::Properties + +RENDERIVE_VISUAL_ALIAS(Splat); +RENDERIVE_VISUAL_ALIAS(Pixel); +RENDERIVE_VISUAL_ALIAS(Marker); +RENDERIVE_VISUAL_ALIAS(Sphere); +RENDERIVE_VISUAL_ALIAS(Segment); +RENDERIVE_VISUAL_ALIAS(Vector); +RENDERIVE_VISUAL_ALIAS(Primitive); +RENDERIVE_VISUAL_ALIAS(Mesh); +RENDERIVE_VISUAL_ALIAS(Path); +RENDERIVE_VISUAL_ALIAS(Image); +RENDERIVE_VISUAL_ALIAS(Labels); +RENDERIVE_VISUAL_ALIAS(Glyph); +RENDERIVE_VISUAL_ALIAS(Text); +RENDERIVE_VISUAL_ALIAS(Volume); + +#undef RENDERIVE_VISUAL_ALIAS + +} // namespace renderive::render_3d diff --git a/render_3D/render_3D/Glyph_Visual.h b/render_3D/render_3D/Glyph_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Glyph_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Image_Visual.h b/render_3D/render_3D/Image_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Image_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Labels_Visual.h b/render_3D/render_3D/Labels_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Labels_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Marker_Visual.h b/render_3D/render_3D/Marker_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Marker_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Mesh_Visual.h b/render_3D/render_3D/Mesh_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Mesh_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Path_Visual.h b/render_3D/render_3D/Path_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Path_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Pixel_Visual.h b/render_3D/render_3D/Pixel_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Pixel_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Point_Scene.cpp b/render_3D/render_3D/Point_Scene.cpp index 3f571f8..63a87aa 100644 --- a/render_3D/render_3D/Point_Scene.cpp +++ b/render_3D/render_3D/Point_Scene.cpp @@ -1,6 +1,6 @@ #include "Point_Scene.h" -#include "detail/Datoviz_Point_Backend.h" +#include "detail/Datoviz_Visual_Backend.h" #include "detail/Point_Core.h" #include "detail/Render_Domain.h" @@ -57,9 +57,10 @@ struct Point_Scene::Impl { if (!visual) throw std::invalid_argument("Point_Scene requires a Point_Visual"); render_domain.invoke([&] { - backend = std::make_unique( + backend = std::make_unique( options.gpu_index, options.validation_enabled, - detail::Scene_State{options.viewport, options.clear_color}); + detail::Scene_State{options.viewport, options.clear_color}, + options.visual_family); }); } @@ -90,7 +91,7 @@ struct Point_Scene::Impl { detail::Input_Collector input; detail::Point_Frame_Strategy scheduler; detail::Render_Domain render_domain; - std::unique_ptr backend; + std::unique_ptr backend; mutable std::mutex frame_mutex; std::shared_ptr latest; std::atomic accepting{true}; diff --git a/render_3D/render_3D/Point_Scene.h b/render_3D/render_3D/Point_Scene.h index 8c3b904..3ad9642 100644 --- a/render_3D/render_3D/Point_Scene.h +++ b/render_3D/render_3D/Point_Scene.h @@ -32,6 +32,24 @@ enum class Frame_Mode : std::uint8_t { Playback, }; +enum struct Visual_Family : std::uint8_t { + Point, + Splat, + Pixel, + Marker, + Sphere, + Segment, + Vector, + Primitive, + Mesh, + Path, + Image, + Labels, + Glyph, + Text, + Volume, +}; + struct Scene_Options { Extent viewport{560, 320}; Clear_Color clear_color; @@ -39,6 +57,7 @@ struct Scene_Options { double maximum_frames_per_second{60.0}; std::uint32_t gpu_index{}; bool validation_enabled{}; + Visual_Family visual_family{Visual_Family::Point}; }; struct Pixel_Frame { diff --git a/render_3D/render_3D/Primitive_Visual.h b/render_3D/render_3D/Primitive_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Primitive_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Segment_Visual.h b/render_3D/render_3D/Segment_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Segment_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Sphere_Visual.h b/render_3D/render_3D/Sphere_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Sphere_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Splat_Visual.h b/render_3D/render_3D/Splat_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Splat_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Text_Visual.h b/render_3D/render_3D/Text_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Text_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Vector_Visual.h b/render_3D/render_3D/Vector_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Vector_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/Volume_Visual.h b/render_3D/render_3D/Volume_Visual.h new file mode 100644 index 0000000..e3625ff --- /dev/null +++ b/render_3D/render_3D/Volume_Visual.h @@ -0,0 +1,2 @@ +#pragma once +#include "Datoviz_Visuals.h" diff --git a/render_3D/render_3D/detail/Datoviz_Point_Backend.cpp b/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp similarity index 71% rename from render_3D/render_3D/detail/Datoviz_Point_Backend.cpp rename to render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp index ca5f048..59d1ee4 100644 --- a/render_3D/render_3D/detail/Datoviz_Point_Backend.cpp +++ b/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp @@ -1,4 +1,4 @@ -#include "Datoviz_Point_Backend.h" +#include "Datoviz_Visual_Backend.h" #include @@ -94,7 +94,7 @@ DvzShapeAspect aspect(Point_Aspect value) { } // namespace -class Datoviz_Point_Backend::Frame_Target final { +class Datoviz_Visual_Backend::Frame_Target final { public: Frame_Target(DvzGpuCtx* gpu_context, Extent extent, std::uint64_t generation) : gpu_context_(gpu_context), extent_(extent), generation_(generation) { @@ -331,10 +331,10 @@ private: bool recording_{}; }; -Datoviz_Point_Backend::Datoviz_Point_Backend( +Datoviz_Visual_Backend::Datoviz_Visual_Backend( std::uint32_t gpu_index, bool validation_enabled, - const Scene_State& initial_scene) - : domain_thread_(std::this_thread::get_id()) { + const Scene_State& initial_scene, Visual_Family family) + : domain_thread_(std::this_thread::get_id()), family_(family) { try { DvzGpuCtxConfig configuration = dvz_gpu_ctx_config(); dvz_gpu_ctx_config_validation(&configuration, validation_enabled); @@ -355,14 +355,14 @@ Datoviz_Point_Backend::Datoviz_Point_Backend( } } -Datoviz_Point_Backend::~Datoviz_Point_Backend() { destroy(); } +Datoviz_Visual_Backend::~Datoviz_Visual_Backend() { destroy(); } -void Datoviz_Point_Backend::require_domain() const { +void Datoviz_Visual_Backend::require_domain() const { if (std::this_thread::get_id() != domain_thread_) throw std::logic_error("Datoviz objects may only be used on the render domain"); } -void Datoviz_Point_Backend::create_scene(const Scene_State& initial_scene) { +void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { scene_ = dvz_scene(); if (scene_ == nullptr) throw std::runtime_error("failed to create Datoviz scene"); @@ -372,10 +372,57 @@ void Datoviz_Point_Backend::create_scene(const Scene_State& initial_scene) { figure_ = dvz_figure(scene_, initial_scene.viewport.width, initial_scene.viewport.height, 0); panel_ = figure_ != nullptr ? dvz_panel_full(figure_) : nullptr; - visual_ = panel_ != nullptr ? dvz_point(scene_, 0) : nullptr; + if (panel_ != nullptr) { + switch (family_) { + case Visual_Family::Point: + visual_ = dvz_point(scene_, 0); + break; + case Visual_Family::Splat: + visual_ = dvz_splat(scene_, 0); + break; + case Visual_Family::Pixel: + visual_ = dvz_pixel(scene_, 0); + break; + case Visual_Family::Marker: + visual_ = dvz_marker(scene_, 0); + break; + case Visual_Family::Sphere: + visual_ = dvz_sphere(scene_, 0); + break; + case Visual_Family::Segment: + visual_ = dvz_segment(scene_, 0); + break; + case Visual_Family::Vector: + visual_ = dvz_vector(scene_, 0); + break; + case Visual_Family::Primitive: + visual_ = dvz_primitive( + scene_, DVZ_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0); + break; + case Visual_Family::Mesh: + visual_ = dvz_mesh(scene_, 0); + break; + case Visual_Family::Path: + visual_ = dvz_path(scene_, 0); + break; + case Visual_Family::Image: + visual_ = dvz_image(scene_, 0); + break; + case Visual_Family::Labels: + visual_ = dvz_labels(scene_, 0); + break; + case Visual_Family::Glyph: + case Visual_Family::Text: + visual_ = dvz_glyph(scene_, 0); + break; + case Visual_Family::Volume: + visual_ = dvz_volume(scene_, 0); + break; + } + } if (figure_ == nullptr || panel_ == nullptr || visual_ == nullptr || dvz_panel_add_visual(panel_, visual_, nullptr) != DVZ_OK) - throw std::runtime_error("failed to create Datoviz point visual"); + throw std::runtime_error("failed to create Datoviz visual family"); DvzCameraDesc camera = dvz_camera_desc(); camera.view.eye[0] = 0.0F; @@ -407,7 +454,7 @@ void Datoviz_Point_Backend::create_scene(const Scene_State& initial_scene) { dvz_input_emit_resize(input_router_, &resize); } -void Datoviz_Point_Backend::apply(const Point_Frame_Data& frame) { +void Datoviz_Visual_Backend::apply(const Point_Frame_Data& frame) { require_domain(); const auto& points = *frame.point.data; if (frame.scene_revision != applied_scene_revision_) { @@ -423,23 +470,32 @@ void Datoviz_Point_Backend::apply(const Point_Frame_Data& frame) { if (frame.point.state_revision != applied_state_revision_) { const auto& state = frame.point.state; - DvzPointStyleDesc style = dvz_point_style_desc(); - style.edge_color.r = state.style.edge_color.red; - style.edge_color.g = state.style.edge_color.green; - style.edge_color.b = state.style.edge_color.blue; - style.edge_color.a = state.style.edge_color.alpha; - style.stroke_width_px = state.style.stroke_width_px; - style.aspect = aspect(state.style.aspect); mat4 transform{}; for (std::size_t row = 0; row < 4; ++row) { for (std::size_t column = 0; column < 4; ++column) transform[row][column] = state.transform.values[row * 4 + column]; } - if (dvz_point_set_style(visual_, &style) != DVZ_OK || - dvz_visual_set_transform(visual_, transform) != DVZ_OK || + if (family_ == Visual_Family::Point) { + DvzPointStyleDesc style = dvz_point_style_desc(); + style.edge_color.r = state.style.edge_color.red; + style.edge_color.g = state.style.edge_color.green; + style.edge_color.b = state.style.edge_color.blue; + style.edge_color.a = state.style.edge_color.alpha; + style.stroke_width_px = state.style.stroke_width_px; + style.aspect = aspect(state.style.aspect); + if (dvz_point_set_style(visual_, &style) != DVZ_OK) + throw std::runtime_error("failed to apply Datoviz point style"); + } + const bool requires_field = family_ == Visual_Family::Image || + family_ == Visual_Family::Labels || + family_ == Visual_Family::Glyph || + family_ == Visual_Family::Text || + family_ == Visual_Family::Volume; + if (dvz_visual_set_transform(visual_, transform) != DVZ_OK || dvz_visual_set_depth_test(visual_, state.depth_test) != DVZ_OK || - dvz_visual_set_visible(visual_, state.visible && !points.empty()) != DVZ_OK) - throw std::runtime_error("failed to apply Datoviz point state"); + dvz_visual_set_visible( + visual_, state.visible && !points.empty() && !requires_field) != DVZ_OK) + throw std::runtime_error("failed to apply Datoviz visual state"); applied_state_revision_ = frame.point.state_revision; } @@ -450,35 +506,134 @@ void Datoviz_Point_Backend::apply(const Point_Frame_Data& frame) { } else { std::vector> positions; std::vector> colors; - std::vector diameters; + std::vector sizes; positions.reserve(points.size()); colors.reserve(points.size()); - diameters.reserve(points.size()); + sizes.reserve(points.size()); for (const auto& point : points) { positions.push_back( {point.position.x, point.position.y, point.position.z}); colors.push_back({point.color.red, point.color.green, point.color.blue, point.color.alpha}); - diameters.push_back(point.diameter_px); + sizes.push_back(point.diameter_px); } if (positions.size() > std::numeric_limits::max()) throw std::length_error("Datoviz point payload is too large"); const auto count = static_cast(positions.size()); - const std::array updates{{ - {"position", positions.data(), count}, - {"color", colors.data(), count}, - {"diameter_px", diameters.data(), count}, - }}; - if (dvz_visual_set_data_many(visual_, updates.data(), - static_cast(updates.size())) != DVZ_OK || - dvz_visual_set_visible(visual_, frame.point.state.visible) != DVZ_OK) - throw std::runtime_error("failed to upload Datoviz point payload"); + DvzResult upload_result = DVZ_OK; + switch (family_) { + case Visual_Family::Point: { + const std::array updates{{ + {"position", positions.data(), count}, + {"color", colors.data(), count}, + {"diameter_px", sizes.data(), count}, + }}; + upload_result = dvz_visual_set_data_many( + visual_, updates.data(), static_cast(updates.size())); + break; + } + case Visual_Family::Splat: { + std::vector> sigma(count); + std::vector angles(count, 0.0F); + for (std::size_t index = 0; index < count; ++index) + sigma[index] = {sizes[index] * 0.35F, sizes[index] * 0.18F}; + const std::array updates{{ + {"position", positions.data(), count}, {"color", colors.data(), count}, + {"sigma", sigma.data(), count}, {"angle", angles.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 4); + break; + } + case Visual_Family::Pixel: { + const std::array updates{{ + {"position", positions.data(), count}, {"color", colors.data(), count}, + {"pixel_size_px", sizes.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3); + break; + } + case Visual_Family::Marker: { + std::vector angles(count, 0.0F); + std::vector shapes(count, DVZ_MARKER_SHAPE_DIAMOND); + const std::array updates{{ + {"position", positions.data(), count}, {"color", colors.data(), count}, + {"diameter_px", sizes.data(), count}, {"angle", angles.data(), count}, + {"shape", shapes.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 5); + break; + } + case Visual_Family::Sphere: { + std::vector radii(count); + for (std::size_t index = 0; index < count; ++index) + radii[index] = sizes[index] * 0.0025F; + const std::array updates{{ + {"position", positions.data(), count}, {"color", colors.data(), count}, + {"radius", radii.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3); + break; + } + case Visual_Family::Segment: { + auto ends = positions; + std::vector widths(count, 4.0F); + for (auto& end : ends) + end[1] += 0.28F; + const std::array updates{{ + {"position_start", positions.data(), count}, + {"position_end", ends.data(), count}, {"color", colors.data(), count}, + {"stroke_width_px", widths.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 4); + break; + } + case Visual_Family::Vector: { + std::vector> vectors(count, {0.18F, 0.28F, 0.0F}); + std::vector widths(count, 3.0F); + const std::array updates{{ + {"position", positions.data(), count}, {"vector", vectors.data(), count}, + {"color", colors.data(), count}, {"stroke_width_px", widths.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 4); + break; + } + case Visual_Family::Primitive: + case Visual_Family::Mesh: { + std::vector> normals(count, {0.0F, 0.0F, 1.0F}); + const std::array updates{{ + {"position", positions.data(), count}, {"color", colors.data(), count}, + {"normal", normals.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3); + break; + } + case Visual_Family::Path: { + std::vector widths(count, 4.0F); + const std::array updates{{ + {"position", positions.data(), count}, {"color", colors.data(), count}, + {"stroke_width_px", widths.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 3); + break; + } + case Visual_Family::Image: + case Visual_Family::Labels: + case Visual_Family::Glyph: + case Visual_Family::Text: + case Visual_Family::Volume: + // These families obtain their authoritative bulk data from a sampled field or + // font atlas. They are created and routed here, but stay hidden until that + // resource is published rather than accepting a fake vertex substitute. + upload_result = dvz_visual_set_visible(visual_, false); + break; + } + const bool requires_field = family_ == Visual_Family::Image || + family_ == Visual_Family::Labels || + family_ == Visual_Family::Glyph || + family_ == Visual_Family::Text || + family_ == Visual_Family::Volume; + if (upload_result != DVZ_OK || + (!requires_field && dvz_visual_set_visible( + visual_, frame.point.state.visible) != DVZ_OK)) + throw std::runtime_error("failed to upload Datoviz visual payload"); } applied_data_revision_ = frame.point.data_revision; } } -void Datoviz_Point_Backend::dispatch(const Input_Command& command, Extent viewport) { +void Datoviz_Visual_Backend::dispatch(const Input_Command& command, Extent viewport) { require_domain(); const float width = static_cast(viewport.width); const float height = static_cast(viewport.height); @@ -519,7 +674,7 @@ void Datoviz_Point_Backend::dispatch(const Input_Command& command, Extent viewpo } } -DvzSceneFrameArtifact* Datoviz_Point_Backend::emit(const Point_Frame_Data& frame) { +DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(const Point_Frame_Data& frame) { DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config(); configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL; configuration.external_color_target = true; @@ -537,7 +692,7 @@ DvzSceneFrameArtifact* Datoviz_Point_Backend::emit(const Point_Frame_Data& frame return dvz_figure_emit_frame(figure_, &capabilities, &report, &configuration); } -std::shared_ptr Datoviz_Point_Backend::render( +std::shared_ptr Datoviz_Visual_Backend::render( const Point_Frame_Data& frame) { require_domain(); if (frame.scene.viewport.empty()) @@ -587,7 +742,7 @@ std::shared_ptr Datoviz_Point_Backend::render( return output; } -void Datoviz_Point_Backend::destroy() noexcept { +void Datoviz_Visual_Backend::destroy() noexcept { if (std::this_thread::get_id() != domain_thread_) std::terminate(); if (runtime_ != nullptr) { diff --git a/render_3D/render_3D/detail/Datoviz_Point_Backend.h b/render_3D/render_3D/detail/Datoviz_Visual_Backend.h similarity index 74% rename from render_3D/render_3D/detail/Datoviz_Point_Backend.h rename to render_3D/render_3D/detail/Datoviz_Visual_Backend.h index 8ff355c..09b98d9 100644 --- a/render_3D/render_3D/detail/Datoviz_Point_Backend.h +++ b/render_3D/render_3D/detail/Datoviz_Visual_Backend.h @@ -17,14 +17,15 @@ namespace renderive::render_3d::detail { -class Datoviz_Point_Backend final { +class Datoviz_Visual_Backend final { public: - Datoviz_Point_Backend(std::uint32_t gpu_index, bool validation_enabled, - const Scene_State& initial_scene); - ~Datoviz_Point_Backend(); + Datoviz_Visual_Backend(std::uint32_t gpu_index, bool validation_enabled, + const Scene_State& initial_scene, + Visual_Family family = Visual_Family::Point); + ~Datoviz_Visual_Backend(); - Datoviz_Point_Backend(const Datoviz_Point_Backend&) = delete; - Datoviz_Point_Backend& operator=(const Datoviz_Point_Backend&) = delete; + Datoviz_Visual_Backend(const Datoviz_Visual_Backend&) = delete; + Datoviz_Visual_Backend& operator=(const Datoviz_Visual_Backend&) = delete; [[nodiscard]] std::shared_ptr render(const Point_Frame_Data& frame); @@ -53,6 +54,7 @@ private: std::uint64_t applied_scene_revision_{}; std::uint64_t applied_state_revision_{}; std::uint64_t applied_data_revision_{}; + Visual_Family family_{Visual_Family::Point}; }; } // namespace renderive::render_3d::detail diff --git a/render_3D/tests/Datoviz_Visual_Family_Tests.cpp b/render_3D/tests/Datoviz_Visual_Family_Tests.cpp new file mode 100644 index 0000000..d8bc7a3 --- /dev/null +++ b/render_3D/tests/Datoviz_Visual_Family_Tests.cpp @@ -0,0 +1,57 @@ +#include "render_3D/Datoviz_Visuals.h" + +#include + +#include + +namespace renderive::render_3d { +namespace { + +template +void exercise(Item item) { + static_assert(std::derived_from); + auto visual = typename Visual::Builder{}.build(std::vector{item}); + ASSERT_NE(visual, nullptr); + EXPECT_EQ(visual->item_count(), 1U); + const auto revision = visual->data_revision(); + visual->edit_items([](auto& items) { items.push_back(items.front()); }); + EXPECT_EQ(visual->item_count(), 2U); + EXPECT_GT(visual->data_revision(), revision); + const auto observation = visual->observation(); + EXPECT_EQ(observation.item_count, 2U); + EXPECT_EQ(observation.data_revision, visual->data_revision()); +} + +TEST(DatovizVisualFamilies, EveryPublicFamilyUsesRenderableStorage) { + exercise(Splat{}); + exercise(Pixel{}); + exercise(Marker{}); + exercise(Sphere{}); + exercise(Segment{.end = {1, 0, 0}}); + exercise(Vector_Glyph{}); + exercise(Primitive_Vertex{}); + exercise(Mesh_Vertex{}); + exercise(Path_Vertex{}); + exercise(Image{}); + exercise(Label{}); + exercise(Glyph{}); + exercise(Text_Label{.text = "Renderive"}); + exercise(Voxel{}); +} + +TEST(DatovizVisualFamilies, StateValidationAndBuilderPropertiesAreShared) { + auto volume = Volume_Visual::Builder{} + .set<&Volume_State::field_width>(4U) + .set<&Volume_State::field_height>(4U) + .set<&Volume_State::field_depth>(4U) + .build(std::vector(64)); + ASSERT_NE(volume, nullptr); + EXPECT_EQ(volume->get<&Volume_State::field_depth>(), 4U); + EXPECT_THROW((Volume_Visual::Builder{} + .set<&Volume_State::field_width>(4U) + .build()), + std::invalid_argument); +} + +} // namespace +} // namespace renderive::render_3d diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index f6b8b41..b0933d2 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -118,7 +118,7 @@ Gallery_Key_Input gallery_input(const Key_Event& event) { return input; } std::unique_ptr make_gallery_scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) { - if (case_id == "datoviz_3d") + if (case_id.starts_with("datoviz_")) return make_gallery_scene_3d(session_id, std::move(case_id), mode, automatic_low_latency); return make_gallery_scene_2d(session_id, std::move(case_id), mode, automatic_low_latency); } diff --git a/web_server/app/Gallery_Protocol.cpp b/web_server/app/Gallery_Protocol.cpp index 7a0afa0..344a1f0 100644 --- a/web_server/app/Gallery_Protocol.cpp +++ b/web_server/app/Gallery_Protocol.cpp @@ -54,8 +54,22 @@ const std::vector& cases() { "鼠标框选、多区域保留、标注样式和清空。", 70}, {"constellation", "星座图", "Constellation_Diagram", "点图控件", "PSK4/PSK8/PSK16 模式、相位、寿命、坐标范围和方形拟合。", 80}, - {"datoviz_3d", "Datoviz 3D", "Mesh / Texture / MSDF Text", "3D", - "Render3D 通过 Kernel 帧策略驱动迁移后的 Datoviz Vulkan 后端;3D 的已发布帧禁止自动覆盖。", 90} + {"datoviz_3d", "Datoviz Visuals", "Point_Visual", "3D", + "Renderive 3D Renderable、双缓冲实时数据与 Datoviz Vulkan 后端。", 90}, + {"datoviz_splat", "Splat", "Splat_Visual", "Datoviz Visuals", "各向异性高斯 splat。", 91}, + {"datoviz_pixel", "Pixel", "Pixel_Visual", "Datoviz Visuals", "屏幕空间像素。", 92}, + {"datoviz_marker", "Marker", "Marker_Visual", "Datoviz Visuals", "可旋转的参数化标记。", 93}, + {"datoviz_sphere", "Sphere", "Sphere_Visual", "Datoviz Visuals", "带深度的球体。", 94}, + {"datoviz_segment", "Segment", "Segment_Visual", "Datoviz Visuals", "独立线段。", 95}, + {"datoviz_vector", "Vector", "Vector_Visual", "Datoviz Visuals", "方向向量。", 96}, + {"datoviz_primitive", "Primitive", "Primitive_Visual", "Datoviz Visuals", "可选拓扑的基础图元。", 97}, + {"datoviz_mesh", "Mesh", "Mesh_Visual", "Datoviz Visuals", "法线与纹理坐标网格。", 98}, + {"datoviz_path", "Path", "Path_Visual", "Datoviz Visuals", "连续描边路径。", 99}, + {"datoviz_image", "Image", "Image_Visual", "Datoviz Visuals", "采样图像实例。", 100}, + {"datoviz_labels", "Labels", "Labels_Visual", "Datoviz Visuals", "图集标签实例。", 101}, + {"datoviz_glyph", "Glyph", "Glyph_Visual", "Datoviz Visuals", "MSDF 字形。", 102}, + {"datoviz_text", "Text", "Text_Visual", "Datoviz Visuals", "基于 glyph 的高层文字注记。", 103}, + {"datoviz_volume", "Volume", "Volume_Visual", "Datoviz Visuals", "三维标量场。", 104} }; return value; } @@ -83,7 +97,7 @@ void append_session_actions(Gallery_Frame_Mode frame_mode, throw std::invalid_argument("unknown gallery frame mode"); } std::size_t session_control_count(std::string_view case_id, Gallery_Frame_Mode frame_mode) { - if (case_id == "datoviz_3d") + if (case_id.starts_with("datoviz_")) return 0; switch (frame_mode) { case Gallery_Frame_Mode::Manual: @@ -126,7 +140,7 @@ void append_3d_actions(Gallery_Frame_Mode frame_mode, std::vector& } std::vector registered_actions(std::string_view case_id, Gallery_Frame_Mode frame_mode) { std::vector result; - if (case_id == "datoviz_3d") { + if (case_id.starts_with("datoviz_")) { append_3d_actions(frame_mode, result); return result; } @@ -554,7 +568,7 @@ std::string Gallery_Protocol::catalog_json() { Json entry = adminive::to_frontend_json(item); entry["control_count_by_mode"] = Json::object(); entry["action_count_by_mode"] = Json::object(); - const std::size_t renderable_controls = item.id == "datoviz_3d" ? 0 : gallery_renderable_control_count(item.id); + const std::size_t renderable_controls = item.id.starts_with("datoviz_") ? 0 : gallery_renderable_control_count(item.id); for (const auto mode : frame_modes) { const std::string key = gallery_enum_id(mode); const std::size_t controls = diff --git a/web_server/app/render_3D/Gallery_Scene3D.cpp b/web_server/app/render_3D/Gallery_Scene3D.cpp index 1b2d0db..1145e42 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.cpp +++ b/web_server/app/render_3D/Gallery_Scene3D.cpp @@ -101,6 +101,24 @@ std::string mode_id(Gallery_Frame_Mode mode) { return ::renderive::Key::Unknown; } +Visual_Family visual_family(std::string_view case_id) { + if (case_id == "datoviz_splat") return Visual_Family::Splat; + if (case_id == "datoviz_pixel") return Visual_Family::Pixel; + if (case_id == "datoviz_marker") return Visual_Family::Marker; + if (case_id == "datoviz_sphere") return Visual_Family::Sphere; + if (case_id == "datoviz_segment") return Visual_Family::Segment; + if (case_id == "datoviz_vector") return Visual_Family::Vector; + if (case_id == "datoviz_primitive") return Visual_Family::Primitive; + if (case_id == "datoviz_mesh") return Visual_Family::Mesh; + if (case_id == "datoviz_path") return Visual_Family::Path; + if (case_id == "datoviz_image") return Visual_Family::Image; + if (case_id == "datoviz_labels") return Visual_Family::Labels; + if (case_id == "datoviz_glyph") return Visual_Family::Glyph; + if (case_id == "datoviz_text") return Visual_Family::Text; + if (case_id == "datoviz_volume") return Visual_Family::Volume; + return Visual_Family::Point; +} + class Gallery_Scene3D final : public Gallery_Scene_Interface { public: Gallery_Scene3D(std::uint64_t session_id, std::string case_id, @@ -110,7 +128,8 @@ public: visual_(Point_Visual::Builder{}.build(initial_points())), scene_(Scene_Options{.viewport = {560, 320}, .frame_mode = render_frame_mode(mode), - .maximum_frames_per_second = 30.0}, + .maximum_frames_per_second = 30.0, + .visual_family = visual_family(case_id_)}, visual_) { if (!visual_) throw std::runtime_error("failed to create Point_Visual"); diff --git a/web_server/tests/Datoviz_Gallery_Tests.cpp b/web_server/tests/Datoviz_Gallery_Tests.cpp index 76fbe0a..2f20695 100644 --- a/web_server/tests/Datoviz_Gallery_Tests.cpp +++ b/web_server/tests/Datoviz_Gallery_Tests.cpp @@ -179,6 +179,20 @@ TEST(RenderiveWebDatovizGallery, RgbaEncoderPreservesRowsAndRemovesPadding) { TEST(RenderiveWebDatovizGallery, CatalogUsesOnlyThreeDimensionalActions) { const auto catalog = nlohmann::json::parse(Gallery_Protocol::catalog_json()); + const std::set visual_cases{ + "datoviz_3d", "datoviz_splat", "datoviz_pixel", "datoviz_marker", + "datoviz_sphere", "datoviz_segment", "datoviz_vector", + "datoviz_primitive", "datoviz_mesh", "datoviz_path", "datoviz_image", + "datoviz_labels", "datoviz_glyph", "datoviz_text", "datoviz_volume"}; + for (const auto& id : visual_cases) { + EXPECT_NE(std::find_if( + catalog.at("cases").begin(), catalog.at("cases").end(), + [&id](const nlohmann::json& value) { + return value.at("id") == id; + }), + catalog.at("cases").end()) + << id; + } const auto item = std::find_if( catalog.at("cases").begin(), catalog.at("cases").end(), [](const nlohmann::json& value) { return value.at("id") == "datoviz_3d"; }); diff --git a/web_server/tests/Web_Bridge_Tests.cpp b/web_server/tests/Web_Bridge_Tests.cpp index 5083e2a..4f3483b 100644 --- a/web_server/tests/Web_Bridge_Tests.cpp +++ b/web_server/tests/Web_Bridge_Tests.cpp @@ -228,10 +228,10 @@ TEST(RenderiveWebGallery, AdminiveCatalogCoversEveryControlCaseAndThreeModes) { EXPECT_EQ(catalog.at("protocol"), "renderive.control-gallery"); EXPECT_EQ(catalog.at("protocol_version"), 4); EXPECT_EQ(catalog.at("case_descriptor").at("protocol"), "adminive.resource"); - EXPECT_EQ(catalog.at("cases").size(), 9U); + EXPECT_EQ(catalog.at("cases").size(), 23U); EXPECT_EQ(catalog.at("frame_modes").size(), 3U); EXPECT_EQ(catalog.at("coverage").at("page_count"), 3); - EXPECT_EQ(catalog.at("coverage").at("canvas_count"), 27); + EXPECT_EQ(catalog.at("coverage").at("canvas_count"), 69); EXPECT_GT(catalog.at("coverage").at("manual_control_count").get(), 180U); EXPECT_GT(catalog.at("coverage").at("manual_action_count").get(), 50U); EXPECT_EQ(catalog.at("coverage").at("frequency_modes"),