From 9a0601073b4395a11f6c920ee9b60051b3e3c528 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Fri, 14 Aug 2026 21:15:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E5=AE=8C=E7=95=8C=E9=9D=A2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render_3D/render_3D/Datoviz_Visuals.h | 389 +----------------- render_3D/render_3D/Glyph_Visual.h | 26 +- render_3D/render_3D/Image_Visual.h | 25 +- render_3D/render_3D/Labels_Visual.h | 25 +- render_3D/render_3D/Marker_Visual.h | 27 +- render_3D/render_3D/Mesh_Visual.h | 25 +- render_3D/render_3D/Path_Visual.h | 23 +- render_3D/render_3D/Pixel_Visual.h | 23 +- render_3D/render_3D/Primitive_Visual.h | 23 +- render_3D/render_3D/Segment_Visual.h | 25 +- render_3D/render_3D/Sphere_Visual.h | 23 +- render_3D/render_3D/Splat_Visual.h | 25 +- render_3D/render_3D/Text_Visual.h | 27 +- render_3D/render_3D/Vector_Visual.h | 25 +- render_3D/render_3D/Volume_Visual.h | 19 +- .../detail/Datoviz_Visual_Backend.cpp | 261 ++++++++++-- render_3D/render_3D/visuals/Basic_Visual.h | 146 +++++++ render_3D/render_3D/visuals/Visual_Types.h | 64 +++ webapp_gallery/src/gallery/plot_card.tsx | 9 +- .../src/session/gallery_plot_session.ts | 4 +- 20 files changed, 791 insertions(+), 423 deletions(-) create mode 100644 render_3D/render_3D/visuals/Basic_Visual.h create mode 100644 render_3D/render_3D/visuals/Visual_Types.h diff --git a/render_3D/render_3D/Datoviz_Visuals.h b/render_3D/render_3D/Datoviz_Visuals.h index e0ea8ff..7e3b3b7 100644 --- a/render_3D/render_3D/Datoviz_Visuals.h +++ b/render_3D/render_3D/Datoviz_Visuals.h @@ -1,377 +1,16 @@ #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 +#include "Glyph_Visual.h" +#include "Image_Visual.h" +#include "Labels_Visual.h" +#include "Marker_Visual.h" +#include "Mesh_Visual.h" +#include "Path_Visual.h" +#include "Pixel_Visual.h" +#include "Primitive_Visual.h" +#include "Segment_Visual.h" +#include "Sphere_Visual.h" +#include "Splat_Visual.h" +#include "Text_Visual.h" +#include "Vector_Visual.h" +#include "Volume_Visual.h" diff --git a/render_3D/render_3D/Glyph_Visual.h b/render_3D/render_3D/Glyph_Visual.h index e3625ff..160dbdd 100644 --- a/render_3D/render_3D/Glyph_Visual.h +++ b/render_3D/render_3D/Glyph_Visual.h @@ -1,2 +1,26 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Glyph { + Vec3 position; + Vec4 bounds; + Vec4 texture_coordinates; + Rgba8 color; + float angle{}; + bool operator==(const Glyph&) const = default; +}; +namespace detail { +struct Glyph_Spec { + using Item = Glyph; + using Settings = Texture_Field_Settings; + static constexpr const char* name = "Glyph"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.bounds) && + finite(item.texture_coordinates) && finite(item.angle); + } +}; +} +using Glyph_Visual = detail::Attached_Visual; +using Glyph_State = Glyph_Visual::Properties; +} diff --git a/render_3D/render_3D/Image_Visual.h b/render_3D/render_3D/Image_Visual.h index e3625ff..67a61a7 100644 --- a/render_3D/render_3D/Image_Visual.h +++ b/render_3D/render_3D/Image_Visual.h @@ -1,2 +1,25 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +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; +}; +namespace detail { +struct Image_Spec { + using Item = Image; + using Settings = Texture_Field_Settings; + static constexpr const char* name = "Image"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.extent) && item.extent.x > 0 && + item.extent.y > 0 && finite(item.texture_rectangle); + } +}; +} +using Image_Visual = detail::Attached_Visual; +using Image_State = Image_Visual::Properties; +} diff --git a/render_3D/render_3D/Labels_Visual.h b/render_3D/render_3D/Labels_Visual.h index e3625ff..5700bcd 100644 --- a/render_3D/render_3D/Labels_Visual.h +++ b/render_3D/render_3D/Labels_Visual.h @@ -1,2 +1,25 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Label { + Vec3 position; + std::uint32_t image_index{}; + Vec2 extent{32.0F, 16.0F}; + Rgba8 tint; + bool operator==(const Label&) const = default; +}; +namespace detail { +struct Labels_Spec { + using Item = Label; + using Settings = Texture_Field_Settings; + static constexpr const char* name = "Labels"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.extent) && item.extent.x > 0 && + item.extent.y > 0; + } +}; +} +using Labels_Visual = detail::Attached_Visual; +using Labels_State = Labels_Visual::Properties; +} diff --git a/render_3D/render_3D/Marker_Visual.h b/render_3D/render_3D/Marker_Visual.h index e3625ff..0f94cac 100644 --- a/render_3D/render_3D/Marker_Visual.h +++ b/render_3D/render_3D/Marker_Visual.h @@ -1,2 +1,27 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +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; +}; +namespace detail { +struct Marker_Spec { + using Item = Marker; + using Settings = Visual_Settings; + static constexpr const char* name = "Marker"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.diameter_px) && + item.diameter_px > 0 && finite(item.angle); + } +}; +} +using Marker_Visual = detail::Attached_Visual; +using Marker_State = Marker_Visual::Properties; +} diff --git a/render_3D/render_3D/Mesh_Visual.h b/render_3D/render_3D/Mesh_Visual.h index e3625ff..6b7424a 100644 --- a/render_3D/render_3D/Mesh_Visual.h +++ b/render_3D/render_3D/Mesh_Visual.h @@ -1,2 +1,25 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Mesh_Vertex { + Vec3 position; + Rgba8 color; + Vec3 normal; + Vec2 texture_coordinate; + bool operator==(const Mesh_Vertex&) const = default; +}; +namespace detail { +struct Mesh_Spec { + using Item = Mesh_Vertex; + using Settings = Visual_Settings; + static constexpr const char* name = "Mesh"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.normal) && + finite(item.texture_coordinate); + } +}; +} +using Mesh_Visual = detail::Attached_Visual; +using Mesh_State = Mesh_Visual::Properties; +} diff --git a/render_3D/render_3D/Path_Visual.h b/render_3D/render_3D/Path_Visual.h index e3625ff..16a31a3 100644 --- a/render_3D/render_3D/Path_Visual.h +++ b/render_3D/render_3D/Path_Visual.h @@ -1,2 +1,23 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Path_Vertex { + Vec3 position; + Rgba8 color; + float width_px{1.0F}; + bool operator==(const Path_Vertex&) const = default; +}; +namespace detail { +struct Path_Spec { + using Item = Path_Vertex; + using Settings = Visual_Settings; + static constexpr const char* name = "Path"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.width_px) && item.width_px > 0; + } +}; +} +using Path_Visual = detail::Attached_Visual; +using Path_State = Path_Visual::Properties; +} diff --git a/render_3D/render_3D/Pixel_Visual.h b/render_3D/render_3D/Pixel_Visual.h index e3625ff..9845114 100644 --- a/render_3D/render_3D/Pixel_Visual.h +++ b/render_3D/render_3D/Pixel_Visual.h @@ -1,2 +1,23 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Pixel { + Vec3 position; + Rgba8 color; + float size_px{1.0F}; + bool operator==(const Pixel&) const = default; +}; +namespace detail { +struct Pixel_Spec { + using Item = Pixel; + using Settings = Visual_Settings; + static constexpr const char* name = "Pixel"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.size_px) && item.size_px > 0; + } +}; +} +using Pixel_Visual = detail::Attached_Visual; +using Pixel_State = Pixel_Visual::Properties; +} diff --git a/render_3D/render_3D/Primitive_Visual.h b/render_3D/render_3D/Primitive_Visual.h index e3625ff..865c9f6 100644 --- a/render_3D/render_3D/Primitive_Visual.h +++ b/render_3D/render_3D/Primitive_Visual.h @@ -1,2 +1,23 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Primitive_Vertex { + Vec3 position; + Rgba8 color; + Vec3 normal; + bool operator==(const Primitive_Vertex&) const = default; +}; +namespace detail { +struct Primitive_Spec { + using Item = Primitive_Vertex; + using Settings = Primitive_Settings; + static constexpr const char* name = "Primitive"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.normal); + } +}; +} +using Primitive_Visual = detail::Attached_Visual; +using Primitive_State = Primitive_Visual::Properties; +} diff --git a/render_3D/render_3D/Segment_Visual.h b/render_3D/render_3D/Segment_Visual.h index e3625ff..31ff758 100644 --- a/render_3D/render_3D/Segment_Visual.h +++ b/render_3D/render_3D/Segment_Visual.h @@ -1,2 +1,25 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Segment { + Vec3 start; + Vec3 end; + Rgba8 color; + float width_px{1.0F}; + bool operator==(const Segment&) const = default; +}; +namespace detail { +struct Segment_Spec { + using Item = Segment; + using Settings = Visual_Settings; + static constexpr const char* name = "Segment"; + static bool valid(const Item& item) noexcept { + return finite(item.start) && finite(item.end) && finite(item.width_px) && + item.width_px > 0; + } +}; +} +using Segment_Visual = detail::Attached_Visual; +using Segment_State = Segment_Visual::Properties; +} diff --git a/render_3D/render_3D/Sphere_Visual.h b/render_3D/render_3D/Sphere_Visual.h index e3625ff..7556f6f 100644 --- a/render_3D/render_3D/Sphere_Visual.h +++ b/render_3D/render_3D/Sphere_Visual.h @@ -1,2 +1,23 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Sphere { + Vec3 center; + Rgba8 color; + float radius{0.1F}; + bool operator==(const Sphere&) const = default; +}; +namespace detail { +struct Sphere_Spec { + using Item = Sphere; + using Settings = Visual_Settings; + static constexpr const char* name = "Sphere"; + static bool valid(const Item& item) noexcept { + return finite(item.center) && finite(item.radius) && item.radius > 0; + } +}; +} +using Sphere_Visual = detail::Attached_Visual; +using Sphere_State = Sphere_Visual::Properties; +} diff --git a/render_3D/render_3D/Splat_Visual.h b/render_3D/render_3D/Splat_Visual.h index e3625ff..f00caf6 100644 --- a/render_3D/render_3D/Splat_Visual.h +++ b/render_3D/render_3D/Splat_Visual.h @@ -1,2 +1,25 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Splat { + Vec3 position; + Rgba8 color; + Vec2 sigma{0.05F, 0.05F}; + float angle{}; + bool operator==(const Splat&) const = default; +}; +namespace detail { +struct Splat_Spec { + using Item = Splat; + using Settings = Visual_Settings; + static constexpr const char* name = "Splat"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && finite(item.sigma) && item.sigma.x > 0 && + item.sigma.y > 0 && finite(item.angle); + } +}; +} +using Splat_Visual = detail::Attached_Visual; +using Splat_State = Splat_Visual::Properties; +} diff --git a/render_3D/render_3D/Text_Visual.h b/render_3D/render_3D/Text_Visual.h index e3625ff..54e5217 100644 --- a/render_3D/render_3D/Text_Visual.h +++ b/render_3D/render_3D/Text_Visual.h @@ -1,2 +1,27 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +#include + +namespace renderive::render_3d { +struct Text_Label { + Vec3 position; + std::string text; + Rgba8 color; + float size_px{18.0F}; + bool operator==(const Text_Label&) const = default; +}; +namespace detail { +struct Text_Spec { + using Item = Text_Label; + using Settings = Visual_Settings; + static constexpr const char* name = "Text"; + static bool valid(const Item& item) noexcept { + return finite(item.position) && !item.text.empty() && finite(item.size_px) && + item.size_px > 0; + } +}; +} +using Text_Visual = detail::Attached_Visual; +using Text_State = Text_Visual::Properties; +} diff --git a/render_3D/render_3D/Vector_Visual.h b/render_3D/render_3D/Vector_Visual.h index e3625ff..ce1f9b5 100644 --- a/render_3D/render_3D/Vector_Visual.h +++ b/render_3D/render_3D/Vector_Visual.h @@ -1,2 +1,25 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +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; +}; +namespace detail { +struct Vector_Spec { + using Item = Vector_Glyph; + using Settings = Visual_Settings; + static constexpr const char* name = "Vector"; + static bool valid(const Item& item) noexcept { + return finite(item.origin) && finite(item.direction) && + finite(item.width_px) && item.width_px > 0; + } +}; +} +using Vector_Visual = detail::Attached_Visual; +using Vector_State = Vector_Visual::Properties; +} diff --git a/render_3D/render_3D/Volume_Visual.h b/render_3D/render_3D/Volume_Visual.h index e3625ff..5bc601f 100644 --- a/render_3D/render_3D/Volume_Visual.h +++ b/render_3D/render_3D/Volume_Visual.h @@ -1,2 +1,19 @@ #pragma once -#include "Datoviz_Visuals.h" +#include "visuals/Basic_Visual.h" + +namespace renderive::render_3d { +struct Voxel { + float value{}; + bool operator==(const Voxel&) const = default; +}; +namespace detail { +struct Volume_Spec { + using Item = Voxel; + using Settings = Volume_Field_Settings; + static constexpr const char* name = "Volume"; + static bool valid(const Item& item) noexcept { return finite(item.value); } +}; +} +using Volume_Visual = detail::Attached_Visual; +using Volume_State = Volume_Visual::Properties; +} diff --git a/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp b/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp index 59d1ee4..ae4afe8 100644 --- a/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp +++ b/render_3D/render_3D/detail/Datoviz_Visual_Backend.cpp @@ -92,6 +92,74 @@ DvzShapeAspect aspect(Point_Aspect value) { return DVZ_SHAPE_ASPECT_FILLED; } +void configure_glyph_text(DvzScene* scene, DvzVisual* visual, const char* text) { + auto font_descriptor = dvz_font_desc(); + font_descriptor.family = "Roboto"; + font_descriptor.style = "Regular"; + auto* font = dvz_font(scene, &font_descriptor); + auto atlas_specification = + dvz_text_atlas_spec(DVZ_TEXT_RENDERER_MSDF_ATLAS, 64.0F); + if (font == nullptr || + !dvz_font_atlas_ensure_string(font, &atlas_specification, text)) + throw std::runtime_error("failed to create Datoviz text atlas"); + const auto* atlas = dvz_font_atlas(font, &atlas_specification); + if (atlas == nullptr || dvz_glyph_set_atlas(visual, atlas) != DVZ_OK) + throw std::runtime_error("failed to bind Datoviz text atlas"); + + float line_width = 0.0F; + for (const auto* character = text; *character != '\0'; ++character) { + const auto* glyph = + dvz_text_atlas_glyph(atlas, static_cast(*character)); + if (glyph != nullptr) + line_width += glyph->advance; + } + + std::vector> positions; + std::vector> bounds; + std::vector> texture_coordinates; + std::vector> colors; + std::vector angles; + const auto atlas_info = dvz_text_atlas_info(atlas); + float cursor_x = 0.0F; + std::size_t glyph_index = 0; + for (const auto* character = text; *character != '\0'; ++character) { + const auto* glyph = + dvz_text_atlas_glyph(atlas, static_cast(*character)); + if (glyph == nullptr) + continue; + const float x0 = cursor_x + glyph->xoff - 0.5F * line_width; + const float y0 = 0.5F * atlas_info.ascent + glyph->yoff; + const std::array glyph_bounds{ + x0, y0, x0 + glyph->width, y0 + glyph->height}; + const std::array glyph_texture{ + glyph->uv[0], glyph->uv[1], glyph->uv[2], glyph->uv[3]}; + const std::array glyph_color = + glyph_index % 2 == 0 + ? std::array{40, 235, 205, 255} + : std::array{255, 190, 80, 255}; + for (std::uint32_t vertex = 0; vertex < 6; ++vertex) { + positions.push_back({0.0F, 0.0F, 0.0F}); + bounds.push_back(glyph_bounds); + texture_coordinates.push_back(glyph_texture); + colors.push_back(glyph_color); + angles.push_back(0.0F); + } + cursor_x += glyph->advance; + ++glyph_index; + } + if (positions.empty()) + throw std::runtime_error("Datoviz text atlas contains no visible glyphs"); + const auto count = static_cast(positions.size()); + const std::array updates{{ + {"position", positions.data(), count}, {"bounds", bounds.data(), count}, + {"texcoords", texture_coordinates.data(), count}, + {"color", colors.data(), count}, {"angle", angles.data(), count}}}; + if (dvz_visual_set_data_many(visual, updates.data(), + static_cast(updates.size())) != DVZ_OK || + dvz_visual_set_depth_test(visual, false) != DVZ_OK) + throw std::runtime_error("failed to upload Datoviz text geometry"); +} + } // namespace class Datoviz_Visual_Backend::Frame_Target final { @@ -366,7 +434,7 @@ 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"); - const DvzCapabilitySnapshot capabilities = dvz_capability_snapshot(); + const auto capabilities = dvz_capability_snapshot(); 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, @@ -420,6 +488,121 @@ void Datoviz_Visual_Backend::create_scene(const Scene_State& initial_scene) { break; } } + if (visual_ != nullptr && + dvz_visual_set_alpha_mode(visual_, DVZ_ALPHA_OPAQUE) != DVZ_OK) + throw std::runtime_error("failed to configure Datoviz visual alpha mode"); + + if (visual_ != nullptr && family_ == Visual_Family::Glyph) + configure_glyph_text(scene_, visual_, "GLYPH"); + if (visual_ != nullptr && family_ == Visual_Family::Text) + configure_glyph_text(scene_, visual_, "TEXT"); + + if (visual_ != nullptr && + (family_ == Visual_Family::Image || family_ == Visual_Family::Glyph || + family_ == Visual_Family::Text)) { + constexpr std::uint32_t width = 32; + constexpr std::uint32_t height = 32; + std::vector> pixels(width * height); + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t x = 0; x < width; ++x) { + const bool stroke = family_ == Visual_Family::Text + ? (y < 6 || (x >= 13 && x <= 18)) + : ((x / 8 + y / 8) % 2 == 0); + pixels[y * width + x] = stroke + ? std::array{40, 235, 205, 255} + : std::array{18, 42, 72, 255}; + } + } + auto descriptor = dvz_sampled_field_desc(); + descriptor.dim = DVZ_FIELD_DIM_2D; + descriptor.format = DVZ_FIELD_FORMAT_RGBA8_UNORM; + descriptor.semantic = DVZ_FIELD_SEMANTIC_COLOR; + descriptor.color_role = DVZ_COLOR_ROLE_SRGB_COLOR; + descriptor.width = width; + descriptor.height = height; + descriptor.depth = 1; + auto* field = dvz_sampled_field(scene_, &descriptor); + auto view = dvz_field_data_view(); + view.data = pixels.data(); + view.bytes_per_row = width * sizeof(pixels.front()); + view.rows_per_image = height; + if (field == nullptr || dvz_sampled_field_set_data(field, &view) != DVZ_OK || + dvz_visual_set_field(visual_, "field", field) != DVZ_OK) + throw std::runtime_error("failed to create Datoviz 2D sampled field"); + } + + if (visual_ != nullptr && family_ == Visual_Family::Labels) { + constexpr std::uint32_t width = 8; + constexpr std::uint32_t height = 8; + std::array labels{}; + for (std::uint32_t y = 0; y < height; ++y) + for (std::uint32_t x = 0; x < width; ++x) + labels[y * width + x] = static_cast((x / 4) + 2 * (y / 4)); + auto descriptor = dvz_sampled_field_desc(); + descriptor.dim = DVZ_FIELD_DIM_2D; + descriptor.format = DVZ_FIELD_FORMAT_R32_SINT; + descriptor.semantic = DVZ_FIELD_SEMANTIC_LABEL; + descriptor.color_role = DVZ_COLOR_ROLE_DATA; + descriptor.width = width; + descriptor.height = height; + descriptor.depth = 1; + auto* field = dvz_sampled_field(scene_, &descriptor); + auto view = dvz_field_data_view(); + view.data = labels.data(); + view.bytes_per_row = width * sizeof(labels.front()); + view.rows_per_image = height; + auto scale_descriptor = dvz_scale_desc(); + scale_descriptor.kind = DVZ_SCALE_CATEGORICAL; + auto* scale = dvz_scale(scene_, &scale_descriptor); + const std::array categories{{ + {.category_id = 0, .order = 0, .label = "north west", .color = {235, 70, 70, 255}}, + {.category_id = 1, .order = 1, .label = "north east", .color = {70, 220, 100, 255}}, + {.category_id = 2, .order = 2, .label = "south west", .color = {70, 120, 245, 255}}, + {.category_id = 3, .order = 3, .label = "south east", .color = {245, 210, 55, 255}}, + }}; + if (field == nullptr || scale == nullptr || + dvz_sampled_field_set_data(field, &view) != DVZ_OK || + dvz_visual_set_field(visual_, "field", field) != DVZ_OK || + dvz_scale_set_categories(scale, categories.data(), + static_cast(categories.size())) != DVZ_OK || + dvz_visual_set_scale(visual_, "labels", scale) != DVZ_OK) + throw std::runtime_error("failed to create Datoviz label field"); + } + + if (visual_ != nullptr && family_ == Visual_Family::Volume) { + constexpr std::uint32_t side = 16; + std::vector voxels(side * side * side); + for (std::uint32_t z = 0; z < side; ++z) { + for (std::uint32_t y = 0; y < side; ++y) { + for (std::uint32_t x = 0; x < side; ++x) { + const float dx = static_cast(x) - 7.5F; + const float dy = static_cast(y) - 7.5F; + const float dz = static_cast(z) - 7.5F; + const float distance = std::sqrt(dx * dx + dy * dy + dz * dz); + voxels[(z * side + y) * side + x] = + distance < 6.5F ? static_cast(255 - distance * 22) : 0; + } + } + } + auto descriptor = dvz_sampled_field_desc(); + descriptor.dim = DVZ_FIELD_DIM_3D; + descriptor.format = DVZ_FIELD_FORMAT_R8_UNORM; + descriptor.semantic = DVZ_FIELD_SEMANTIC_SCALAR; + descriptor.color_role = DVZ_COLOR_ROLE_DATA; + descriptor.width = side; + descriptor.height = side; + descriptor.depth = side; + auto* field = dvz_sampled_field(scene_, &descriptor); + auto view = dvz_field_data_view(); + view.data = voxels.data(); + view.bytes_per_row = side; + view.rows_per_image = side; + if (field == nullptr || dvz_sampled_field_set_data(field, &view) != DVZ_OK || + dvz_visual_set_field(visual_, "field", field) != DVZ_OK || + dvz_volume_set_render_mode(visual_, DVZ_VOLUME_RENDER_MIP) != DVZ_OK || + dvz_volume_set_step_count(visual_, 48) != DVZ_OK) + throw std::runtime_error("failed to create Datoviz volume field"); + } 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"); @@ -486,15 +669,9 @@ void Datoviz_Visual_Backend::apply(const Point_Frame_Data& frame) { 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() && !requires_field) != DVZ_OK) + dvz_visual_set_visible(visual_, state.visible && !points.empty()) != DVZ_OK) throw std::runtime_error("failed to apply Datoviz visual state"); applied_state_revision_ = frame.point.state_revision; } @@ -593,6 +770,11 @@ void Datoviz_Visual_Backend::apply(const Point_Frame_Data& frame) { } case Visual_Family::Primitive: case Visual_Family::Mesh: { + if (positions.size() >= 3) { + positions[0] = {-0.62F, -0.42F, 0.0F}; + positions[1] = {0.62F, -0.42F, 0.0F}; + positions[2] = {0.0F, 0.58F, 0.0F}; + } std::vector> normals(count, {0.0F, 0.0F, 1.0F}); const std::array updates{{ {"position", positions.data(), count}, {"color", colors.data(), count}, @@ -608,25 +790,33 @@ void Datoviz_Visual_Backend::apply(const Point_Frame_Data& frame) { 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); + case Visual_Family::Image: { + std::vector> extents(count, {0.38F, 0.38F}); + const std::array updates{{ + {"position", positions.data(), count}, {"extent", extents.data(), count}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 2); + break; + } + case Visual_Family::Labels: { + const std::array, 1> anchors{{ + {0.0F, 0.0F, 0.0F}}}; + const std::array, 1> extents{{ + {1.44F, 1.44F}}}; + const std::array updates{{ + {"position", anchors.data(), 1}, {"extent", extents.data(), 1}}}; + upload_result = dvz_visual_set_data_many(visual_, updates.data(), 2); + break; + } + case Visual_Family::Glyph: + case Visual_Family::Text: + upload_result = DVZ_OK; + break; + case Visual_Family::Volume: + upload_result = DVZ_OK; 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)) + 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; @@ -686,10 +876,21 @@ DvzSceneFrameArtifact* Datoviz_Visual_Backend::emit(const Point_Frame_Data& fram configuration.clear_color[1] = frame.scene.clear_color.green; configuration.clear_color[2] = frame.scene.clear_color.blue; configuration.clear_color[3] = frame.scene.clear_color.alpha; - const DvzCapabilitySnapshot capabilities = dvz_capability_snapshot(); + const auto capabilities = dvz_capability_snapshot(); DvzDiagnosticReport report{}; dvz_diagnostic_report_init(&report); - return dvz_figure_emit_frame(figure_, &capabilities, &report, &configuration); + auto* artifact = + dvz_figure_emit_frame(figure_, &capabilities, &report, &configuration); + if (artifact == nullptr) { + std::string message = "failed to emit Datoviz visual frame"; + const auto count = dvz_diagnostic_report_count(&report); + if (count != 0) { + if (const char* diagnostic = dvz_diagnostic_report_get(&report, 0)) + message += ": " + std::string(diagnostic); + } + throw std::runtime_error(message); + } + return artifact; } std::shared_ptr Datoviz_Visual_Backend::render( @@ -709,10 +910,12 @@ std::shared_ptr Datoviz_Visual_Backend::render( } target_->begin(); - DvzSceneFrameArtifact* artifact = emit(frame); - if (artifact == nullptr) { + DvzSceneFrameArtifact* artifact{}; + try { + artifact = emit(frame); + } catch (...) { target_->abort(); - throw std::runtime_error("failed to emit Datoviz point frame"); + throw; } const DvzDrp2CommandStream* stream = dvz_scene_frame_artifact_stream(artifact); const DvzStreamFrame target_frame = target_->stream_frame(); diff --git a/render_3D/render_3D/visuals/Basic_Visual.h b/render_3D/render_3D/visuals/Basic_Visual.h new file mode 100644 index 0000000..df99063 --- /dev/null +++ b/render_3D/render_3D/visuals/Basic_Visual.h @@ -0,0 +1,146 @@ +#pragma once + +#include "Visual_Types.h" +#include "../renderable/Inheritance.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace renderive::render_3d::detail { + +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 = state.field_width || state.field_height; + const bool all = state.field_width && state.field_height; + if (any && !all) + throw std::invalid_argument( + "texture field dimensions must be specified together"); + } + if constexpr (requires { state.field_depth; }) { + const bool any = state.field_width || state.field_height || + state.field_depth; + const bool all = state.field_width && state.field_height && + state.field_depth; + if (any && !all) + 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); + }); + } +}; + +template +using Attached_Visual = ::renderive::renderable::attach>; + +} // namespace renderive::render_3d::detail diff --git a/render_3D/render_3D/visuals/Visual_Types.h b/render_3D/render_3D/visuals/Visual_Types.h new file mode 100644 index 0000000..89f7273 --- /dev/null +++ b/render_3D/render_3D/visuals/Visual_Types.h @@ -0,0 +1,64 @@ +#pragma once + +#include "../Point_Visual.h" + +#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; +}; + +enum struct Primitive_Topology : std::uint8_t { + Point_List, + Line_List, + Line_Strip, + Triangle_List, + Triangle_Strip, +}; + +namespace detail { + +struct Visual_Settings { + bool operator==(const Visual_Settings&) const = default; +}; + +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; +}; + +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); +} + +} // namespace detail +} // namespace renderive::render_3d diff --git a/webapp_gallery/src/gallery/plot_card.tsx b/webapp_gallery/src/gallery/plot_card.tsx index 646fb85..b9911f2 100644 --- a/webapp_gallery/src/gallery/plot_card.tsx +++ b/webapp_gallery/src/gallery/plot_card.tsx @@ -1,7 +1,7 @@ import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; import RefreshIcon from "@mui/icons-material/Refresh"; import {Box,Button,Card,CardActions,CardContent,CardHeader,Chip,Stack,Typography} from "@mui/material"; -import {useEffect} from "react"; +import {useEffect,useRef,useState} from "react"; import type {Gallery_Case,Gallery_Frame_Mode} from "../protocol/gallery_types"; import {use_plot_session} from "../hooks/use_plot_session"; import type {Gallery_Plot_Session} from "../session/gallery_plot_session"; @@ -13,9 +13,12 @@ import {Kernel_Observer_Summary} from "../plot/kernel_observer_summary"; export interface Selected_Plot {session:Gallery_Plot_Session;} export function Plot_Card({gallery_case,frame_mode,active,streams_paused,on_register,on_open_inspector}:{gallery_case:Gallery_Case;frame_mode:Gallery_Frame_Mode;active:boolean;streams_paused:boolean;on_register:(session:Gallery_Plot_Session,mount:boolean)=>void;on_open_inspector:(plot:Selected_Plot)=>void}) { const [session,snapshot]=use_plot_session(gallery_case,frame_mode); + const card_ref=useRef(null); + const [in_viewport,set_in_viewport]=useState(false); useEffect(()=>{on_register(session,true);return()=>on_register(session,false);},[session,on_register]); - useEffect(()=>session.set_activity(active,streams_paused),[session,active,streams_paused]); - return {event.preventDefault();if(snapshot.ready)on_open_inspector({session});}}> + useEffect(()=>{const element=card_ref.current;if(!element)return;const observer=new IntersectionObserver(entries=>set_in_viewport(Boolean(entries[0]?.isIntersecting)),{rootMargin:"320px 0px"});observer.observe(element);return()=>observer.disconnect();},[]); + useEffect(()=>session.set_activity(active&&in_viewport,streams_paused),[session,active,in_viewport,streams_paused]); + return {event.preventDefault();if(snapshot.ready)on_open_inspector({session});}}> } action={}/> {frame_mode.observer_visible&&}{gallery_case.description}{snapshot.notice&&{snapshot.notice}} {snapshot.controls.length} 属性{snapshot.actions.length} 动作{snapshot.frame_count} 像素帧 diff --git a/webapp_gallery/src/session/gallery_plot_session.ts b/webapp_gallery/src/session/gallery_plot_session.ts index 8e743d5..bb38b24 100644 --- a/webapp_gallery/src/session/gallery_plot_session.ts +++ b/webapp_gallery/src/session/gallery_plot_session.ts @@ -30,8 +30,8 @@ export class Gallery_Plot_Session { private stream_active(): boolean {return this.visible&&!this.streams_paused;} private receive_socket_state(state: "connecting"|"ready"|"error"|"closed"): void {if(this.disposed)return;if(state==="ready"){this.update({connection_state:"ready",protocol_error:""});this.socket.send("gallery_open",{case:this.gallery_case.id,frame_mode:this.frame_mode.id});}else{this.update({connection_state:state,ready:state==="error"?this.snapshot.ready:false});if(state==="closed"&&this.visible)this.reconnect_timer=window.setTimeout(()=>this.connect(),1000);}} private receive_json(raw: string): void {try{const response=parse_gallery_response(raw);if(!response)return;if(response.type==="error"){this.update({notice:Object.values(response.field_errors??{})[0]??response.message});return;}if(response.type==="observer_state"){this.update({telemetry:response.telemetry,performance_capture:response.telemetry.performance_capture??this.snapshot.performance_capture,frame_count:this.received_frames});return;}if(response.type==="case_state"||response.type==="refresh_state"){const controls=response.controls;this.update({ready:true,controls:build_controls(controls?.resources),actions:response.actions?.data??[],telemetry:response.telemetry??{},render_plan:controls?.render_plan??null,performance_capture:controls?.performance_capture??response.telemetry?.performance_capture??null,notice:response.notice??"",frame_count:this.received_frames,protocol_error:""});if(this.stream_active()){this.socket.send("show");this.request_frame(performance.now());}}}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"协议解析失败"});this.socket.close();}} - private receive_pixels(buffer: ArrayBuffer): void {try{const now=performance.now();this.frame_controller.receive_frame(now);const accepted=this.presenter.accept(buffer);this.received_frames++;this.client_performance.record_pixel(now,accepted.signature,accepted.overwritten);if(this.frame_mode.request_after_response&&this.stream_active())this.request_frame(now);}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"像素协议错误"});this.socket.close();}} - tick(now: number): void {if(!this.stream_active())return;if(this.presenter.present())this.client_performance.record_presentation(now);if(this.frame_mode.request_on_animation_frame)this.request_frame(now);if(this.snapshot.ready&&now-this.last_observe_at>=650){this.last_observe_at=now;this.socket.send("gallery_observe",{client_metrics:this.client_performance.snapshot(now,this.socket.buffered_bytes()) as unknown as Json_Value});}} + private receive_pixels(buffer: ArrayBuffer): void {try{const now=performance.now();this.frame_controller.receive_frame(now);const accepted=this.presenter.accept(buffer);this.received_frames++;this.client_performance.record_pixel(now,accepted.signature,accepted.overwritten);}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"像素协议错误"});this.socket.close();}} + tick(now: number): void {if(!this.stream_active())return;const presented=this.presenter.present();if(presented)this.client_performance.record_presentation(now);if(this.frame_mode.request_on_animation_frame||(this.frame_mode.request_after_response&&presented))this.request_frame(now);if(this.snapshot.ready&&now-this.last_observe_at>=650){this.last_observe_at=now;this.socket.send("gallery_observe",{client_metrics:this.client_performance.snapshot(now,this.socket.buffered_bytes()) as unknown as Json_Value});}} request_frame(now=performance.now(), explicit=false): boolean {if(!this.snapshot.ready||!this.socket.is_open()||(!explicit&&!this.stream_active())||(explicit&&!this.visible)||(!explicit&&!this.frame_mode.automatic))return false;return this.frame_controller.request_frame(now);} resize(width: number,height: number): void {width=Math.max(240,Math.round(width));height=Math.max(180,Math.round(height));if(width===this.sent_width&&height===this.sent_height)return;this.sent_width=width;this.sent_height=height;if(this.socket.is_open())this.socket.send("resize",{width,height});} pointer(type: "pointer_move"|"pointer_press"|"pointer_release", payload: Record): void {this.socket.send(type,payload);}