改完界面bug
This commit is contained in:
@@ -1,377 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "Point_Visual.h"
|
||||
|
||||
#include <renderive/real_time_data/Double_Buffer_Strategy.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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 <class T>
|
||||
concept Finite_Visual_Item = requires(const T& item) {
|
||||
{ T::valid(item) } -> std::convertible_to<bool>;
|
||||
};
|
||||
|
||||
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 <class Spec>
|
||||
struct Visual_Data_Tag {};
|
||||
|
||||
template <class Spec>
|
||||
struct Visual_Data_Observation {
|
||||
std::uint64_t revision{};
|
||||
std::size_t item_count{};
|
||||
};
|
||||
|
||||
template <class Spec>
|
||||
struct Basic_Visual : Renderable<Basic_Visual<Spec>> {
|
||||
using Base = Renderable<Basic_Visual<Spec>>;
|
||||
using Item = typename Spec::Item;
|
||||
using Payload = std::shared_ptr<const std::vector<Item>>;
|
||||
using Buffer = ::Multi_Double_Buffer_Strategy<
|
||||
::Double_Buffer_Layout<::Buffered_Data<Visual_Data_Tag<Spec>, Payload>>,
|
||||
std::mutex>;
|
||||
|
||||
struct State : Base::template next_State<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 <class Product, class Properties>
|
||||
using Business_Builder = Renderable_Builder<Product, Properties, State_Validator>;
|
||||
|
||||
struct Impl : Base::template next_Impl<Impl>, Buffer {
|
||||
struct Observer : Base::template next_Impl<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<Spec>>())
|
||||
impl.observe_data(data->revision, data->item_count);
|
||||
}
|
||||
};
|
||||
|
||||
explicit Impl(std::vector<Item> items) { update(std::move(items)); }
|
||||
|
||||
void update(std::vector<Item> 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<Visual_Data_Tag<Spec>>(
|
||||
std::make_shared<const std::vector<Item>>(std::move(items)));
|
||||
this->report_observation(Visual_Data_Observation<Spec>{
|
||||
this->template cache_revision<Visual_Data_Tag<Spec>>(), count});
|
||||
}
|
||||
};
|
||||
|
||||
explicit Basic_Visual(const State& state, std::vector<Item> items = {})
|
||||
: Base(With_Attached_Impl<Impl>{}, state, std::move(items)) {
|
||||
State_Validator{}(state);
|
||||
}
|
||||
~Basic_Visual() override = default;
|
||||
|
||||
void update_items(std::vector<Item> items) {
|
||||
this->template d_func<Impl>().update(std::move(items));
|
||||
this->d_func().changed();
|
||||
}
|
||||
|
||||
void edit_items(const std::function<void(std::vector<Item>&)>& edit) {
|
||||
if (!edit)
|
||||
throw std::invalid_argument("visual item edit is empty");
|
||||
const auto& current = this->template d_func<Impl>()
|
||||
.template cache_buffer_value<Visual_Data_Tag<Spec>>();
|
||||
auto next = current ? *current : std::vector<Item>{};
|
||||
edit(next);
|
||||
update_items(std::move(next));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t item_count() const noexcept {
|
||||
const auto& data = this->template d_func<Impl>()
|
||||
.template cache_buffer_value<Visual_Data_Tag<Spec>>();
|
||||
return data ? data->size() : 0U;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint64_t data_revision() const noexcept {
|
||||
return this->template d_func<Impl>()
|
||||
.template cache_revision<Visual_Data_Tag<Spec>>();
|
||||
}
|
||||
|
||||
protected:
|
||||
template <class State_Type, auto Member,
|
||||
Property_Member_Assignable<State_Type, Member> Value>
|
||||
void set_state(Value&& value) {
|
||||
auto next = Base::template state<State_Type>();
|
||||
next.*Member = std::forward<Value>(value);
|
||||
State_Validator{}(next);
|
||||
Base::template update_state<State_Type>(
|
||||
[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<detail::Basic_Visual<detail::Name##_Spec>>; \
|
||||
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"
|
||||
|
||||
@@ -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<detail::Glyph_Spec>;
|
||||
using Glyph_State = Glyph_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Image_Spec>;
|
||||
using Image_State = Image_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Labels_Spec>;
|
||||
using Labels_State = Labels_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Marker_Spec>;
|
||||
using Marker_State = Marker_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Mesh_Spec>;
|
||||
using Mesh_State = Mesh_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Path_Spec>;
|
||||
using Path_State = Path_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Pixel_Spec>;
|
||||
using Pixel_State = Pixel_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Primitive_Spec>;
|
||||
using Primitive_State = Primitive_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Segment_Spec>;
|
||||
using Segment_State = Segment_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Sphere_Spec>;
|
||||
using Sphere_State = Sphere_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Splat_Spec>;
|
||||
using Splat_State = Splat_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,27 @@
|
||||
#pragma once
|
||||
#include "Datoviz_Visuals.h"
|
||||
#include "visuals/Basic_Visual.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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<detail::Text_Spec>;
|
||||
using Text_State = Text_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Vector_Spec>;
|
||||
using Vector_State = Vector_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<detail::Volume_Spec>;
|
||||
using Volume_State = Volume_Visual::Properties;
|
||||
}
|
||||
|
||||
@@ -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<std::uint8_t>(*character));
|
||||
if (glyph != nullptr)
|
||||
line_width += glyph->advance;
|
||||
}
|
||||
|
||||
std::vector<std::array<float, 3>> positions;
|
||||
std::vector<std::array<float, 4>> bounds;
|
||||
std::vector<std::array<float, 4>> texture_coordinates;
|
||||
std::vector<std::array<std::uint8_t, 4>> colors;
|
||||
std::vector<float> 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<std::uint8_t>(*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<float, 4> glyph_bounds{
|
||||
x0, y0, x0 + glyph->width, y0 + glyph->height};
|
||||
const std::array<float, 4> glyph_texture{
|
||||
glyph->uv[0], glyph->uv[1], glyph->uv[2], glyph->uv[3]};
|
||||
const std::array<std::uint8_t, 4> glyph_color =
|
||||
glyph_index % 2 == 0
|
||||
? std::array<std::uint8_t, 4>{40, 235, 205, 255}
|
||||
: std::array<std::uint8_t, 4>{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<std::uint32_t>(positions.size());
|
||||
const std::array<DvzVisualDataUpdate, 5> 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<std::uint32_t>(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<std::array<std::uint8_t, 4>> 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<std::uint8_t, 4>{40, 235, 205, 255}
|
||||
: std::array<std::uint8_t, 4>{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<std::int32_t, width * height> 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<std::int32_t>((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<DvzScaleCategory, 4> 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<std::uint32_t>(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<std::uint8_t> 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<float>(x) - 7.5F;
|
||||
const float dy = static_cast<float>(y) - 7.5F;
|
||||
const float dz = static_cast<float>(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<std::uint8_t>(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<std::array<float, 3>> normals(count, {0.0F, 0.0F, 1.0F});
|
||||
const std::array<DvzVisualDataUpdate, 3> 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<std::array<float, 2>> extents(count, {0.38F, 0.38F});
|
||||
const std::array<DvzVisualDataUpdate, 2> 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<std::array<float, 3>, 1> anchors{{
|
||||
{0.0F, 0.0F, 0.0F}}};
|
||||
const std::array<std::array<float, 2>, 1> extents{{
|
||||
{1.44F, 1.44F}}};
|
||||
const std::array<DvzVisualDataUpdate, 2> 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<const Pixel_Frame> Datoviz_Visual_Backend::render(
|
||||
@@ -709,10 +910,12 @@ std::shared_ptr<const Pixel_Frame> 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();
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
#include "Visual_Types.h"
|
||||
#include "../renderable/Inheritance.h"
|
||||
|
||||
#include <renderive/real_time_data/Double_Buffer_Strategy.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace renderive::render_3d::detail {
|
||||
|
||||
template <class Spec>
|
||||
struct Visual_Data_Tag {};
|
||||
|
||||
template <class Spec>
|
||||
struct Visual_Data_Observation {
|
||||
std::uint64_t revision{};
|
||||
std::size_t item_count{};
|
||||
};
|
||||
|
||||
template <class Spec>
|
||||
struct Basic_Visual : Renderable<Basic_Visual<Spec>> {
|
||||
using Base = Renderable<Basic_Visual<Spec>>;
|
||||
using Item = typename Spec::Item;
|
||||
using Payload = std::shared_ptr<const std::vector<Item>>;
|
||||
using Buffer = ::Multi_Double_Buffer_Strategy<
|
||||
::Double_Buffer_Layout<::Buffered_Data<Visual_Data_Tag<Spec>, Payload>>,
|
||||
std::mutex>;
|
||||
|
||||
struct State : Base::template next_State<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 <class Product, class Properties>
|
||||
using Business_Builder = Renderable_Builder<Product, Properties, State_Validator>;
|
||||
|
||||
struct Impl : Base::template next_Impl<Impl>, Buffer {
|
||||
struct Observer : Base::template next_Impl<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<Spec>>())
|
||||
impl.observe_data(data->revision, data->item_count);
|
||||
}
|
||||
};
|
||||
|
||||
explicit Impl(std::vector<Item> items) { update(std::move(items)); }
|
||||
|
||||
void update(std::vector<Item> 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<Visual_Data_Tag<Spec>>(
|
||||
std::make_shared<const std::vector<Item>>(std::move(items)));
|
||||
this->report_observation(Visual_Data_Observation<Spec>{
|
||||
this->template cache_revision<Visual_Data_Tag<Spec>>(), count});
|
||||
}
|
||||
};
|
||||
|
||||
explicit Basic_Visual(const State& state, std::vector<Item> items = {})
|
||||
: Base(With_Attached_Impl<Impl>{}, state, std::move(items)) {
|
||||
State_Validator{}(state);
|
||||
}
|
||||
~Basic_Visual() override = default;
|
||||
|
||||
void update_items(std::vector<Item> items) {
|
||||
this->template d_func<Impl>().update(std::move(items));
|
||||
this->d_func().changed();
|
||||
}
|
||||
|
||||
void edit_items(const std::function<void(std::vector<Item>&)>& edit) {
|
||||
if (!edit)
|
||||
throw std::invalid_argument("visual item edit is empty");
|
||||
const auto& current = this->template d_func<Impl>()
|
||||
.template cache_buffer_value<Visual_Data_Tag<Spec>>();
|
||||
auto next = current ? *current : std::vector<Item>{};
|
||||
edit(next);
|
||||
update_items(std::move(next));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t item_count() const noexcept {
|
||||
const auto& data = this->template d_func<Impl>()
|
||||
.template cache_buffer_value<Visual_Data_Tag<Spec>>();
|
||||
return data ? data->size() : 0U;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint64_t data_revision() const noexcept {
|
||||
return this->template d_func<Impl>()
|
||||
.template cache_revision<Visual_Data_Tag<Spec>>();
|
||||
}
|
||||
|
||||
protected:
|
||||
template <class State_Type, auto Member,
|
||||
Property_Member_Assignable<State_Type, Member> Value>
|
||||
void set_state(Value&& value) {
|
||||
auto next = Base::template state<State_Type>();
|
||||
next.*Member = std::forward<Value>(value);
|
||||
State_Validator{}(next);
|
||||
Base::template update_state<State_Type>(
|
||||
[next = std::move(next)](State_Type& target) mutable {
|
||||
target = std::move(next);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Spec>
|
||||
using Attached_Visual = ::renderive::renderable::attach<Basic_Visual<Spec>>;
|
||||
|
||||
} // namespace renderive::render_3d::detail
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Point_Visual.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
@@ -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<HTMLDivElement|null>(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 <Card sx={{overflow:"hidden",display:"flex",flexDirection:"column",minWidth:0}} onContextMenu={event=>{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 <Card ref={card_ref} sx={{overflow:"hidden",display:"flex",flexDirection:"column",minWidth:0}} onContextMenu={event=>{event.preventDefault();if(snapshot.ready)on_open_inspector({session});}}>
|
||||
<CardHeader title={gallery_case.title} subheader={gallery_case.component} avatar={<Chip size="small" label={gallery_case.category}/>} action={<Plot_Status snapshot={snapshot} active={active&&!streams_paused}/>}/>
|
||||
<Plot_Canvas session={session}/><Performance_Strip telemetry={snapshot.telemetry}/>{frame_mode.observer_visible&&<Kernel_Observer_Summary telemetry={snapshot.telemetry}/>}<CardContent sx={{flexGrow:1}}><Typography color="text.secondary">{gallery_case.description}</Typography>{snapshot.notice&&<Typography variant="caption" color="secondary.main">{snapshot.notice}</Typography>}</CardContent>
|
||||
<CardActions sx={{justifyContent:"space-between",px:2,pb:2}}><Stack direction="row" spacing={2}><Typography variant="caption">{snapshot.controls.length} 属性</Typography><Typography variant="caption">{snapshot.actions.length} 动作</Typography><Typography variant="caption">{snapshot.frame_count} 像素帧</Typography></Stack><Box><Button size="small" startIcon={<RefreshIcon/>} onClick={()=>session.request_frame(performance.now(),true)}>{frame_mode.frame_button_label}</Button><Button size="small" startIcon={<MoreHorizIcon/>} disabled={!snapshot.ready} onClick={()=>on_open_inspector({session})}>测试菜单</Button></Box></CardActions>
|
||||
|
||||
@@ -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<string, Json_Value>): void {this.socket.send(type,payload);}
|
||||
|
||||
Reference in New Issue
Block a user