优化
This commit is contained in:
@@ -1,12 +1,42 @@
|
||||
#pragma once
|
||||
#include "../visual/Prepared_Visual.hpp"
|
||||
#include "../visual/Visuals.hpp"
|
||||
#include <event.hpp>
|
||||
#include <plot/Axis.hpp>
|
||||
#include "../camera/Camera.hpp"
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
namespace aethera::render_3d::detail {
|
||||
using Prepared_Visual_Payload = std::variant<
|
||||
std::shared_ptr<const Point_Prepared_Data>,
|
||||
std::shared_ptr<const Splat_Prepared_Data>,
|
||||
std::shared_ptr<const Pixel_Prepared_Data>,
|
||||
std::shared_ptr<const Marker_Prepared_Data>,
|
||||
std::shared_ptr<const Sphere_Prepared_Data>,
|
||||
std::shared_ptr<const Segment_Prepared_Data>,
|
||||
std::shared_ptr<const Vector_Prepared_Data>,
|
||||
std::shared_ptr<const Primitive_Prepared_Data>,
|
||||
std::shared_ptr<const Mesh_Prepared_Data>,
|
||||
std::shared_ptr<const Path_Prepared_Data>,
|
||||
std::shared_ptr<const Image_Prepared_Data>,
|
||||
std::shared_ptr<const Labels_Prepared_Data>,
|
||||
std::shared_ptr<const Glyph_Prepared_Data>,
|
||||
std::shared_ptr<const Text_Prepared_Data>,
|
||||
std::shared_ptr<const Volume_Prepared_Data>>;
|
||||
/* Scene 到 Backend 的唯一异构边界;Visual 对象内部不保存此信封。 */
|
||||
struct Prepared_Visual {
|
||||
Matrix4 transform{};
|
||||
bool visible{true};
|
||||
bool depth_test{true};
|
||||
std::uint64_t revision{};
|
||||
Prepared_Visual_Payload data{};
|
||||
};
|
||||
template <typename Data>
|
||||
Prepared_Visual erase_prepared_visual(const Prepared_Visual_For<Data>& source) {
|
||||
return {source.transform, source.visible, source.depth_test, source.revision,
|
||||
source.data};
|
||||
}
|
||||
using Visual_Identity = std::uintptr_t;
|
||||
struct Visual_Registration {
|
||||
Visual_Identity identity{}; /* Scene 内稳定对象身份,仅供后端匹配原生 Visual。 */
|
||||
|
||||
@@ -183,24 +183,51 @@ bool uses_external_attributes(Visual_Family family) noexcept {
|
||||
return family == Visual_Family::mesh || family == Visual_Family::marker;
|
||||
}
|
||||
|
||||
bool has_coordinate_labels(const Prepared_Visual_Data& data) noexcept {
|
||||
template <typename Data>
|
||||
const Data& prepared_data(const Prepared_Visual& visual) {
|
||||
const auto* value = std::get_if<std::shared_ptr<const Data>>(&visual.data);
|
||||
if (value == nullptr || !*value)
|
||||
throw std::logic_error("3D prepared Visual payload does not match its family");
|
||||
return **value;
|
||||
}
|
||||
|
||||
bool has_payload(const Prepared_Visual& visual) noexcept {
|
||||
return std::visit([](const auto& value) { return static_cast<bool>(value); },
|
||||
visual.data);
|
||||
}
|
||||
|
||||
std::size_t prepared_item_count(const Prepared_Visual& visual) noexcept {
|
||||
return std::visit([](const auto& value) -> std::size_t {
|
||||
if (!value) return 0;
|
||||
if constexpr (requires { value->positions; }) return value->positions.size();
|
||||
else if constexpr (requires { value->centers; }) return value->centers.size();
|
||||
else if constexpr (requires { value->starts; }) return value->starts.size();
|
||||
else if constexpr (requires { value->origins; }) return value->origins.size();
|
||||
else return value->values.size();
|
||||
}, visual.data);
|
||||
}
|
||||
|
||||
bool has_coordinate_labels(const Prepared_Visual& visual) noexcept {
|
||||
const auto* payload = std::get_if<std::shared_ptr<const Marker_Prepared_Data>>(
|
||||
&visual.data);
|
||||
if (payload == nullptr || !*payload) return false;
|
||||
return std::ranges::any_of(
|
||||
data.coordinate_label_visibility,
|
||||
(*payload)->coordinate_label_visibility,
|
||||
[](std::uint8_t visible) { return visible != 0; });
|
||||
}
|
||||
|
||||
bool same_visual_structure(const Prepared_Visual& applied,
|
||||
const Prepared_Visual& incoming) noexcept {
|
||||
if (applied.family != incoming.family ||
|
||||
if (applied.data.index() != incoming.data.index() ||
|
||||
applied.transform != incoming.transform ||
|
||||
applied.visible != incoming.visible ||
|
||||
applied.depth_test != incoming.depth_test ||
|
||||
!applied.data || !incoming.data ||
|
||||
applied.data->positions.size() != incoming.data->positions.size())
|
||||
!has_payload(applied) || !has_payload(incoming) ||
|
||||
prepared_item_count(applied) != prepared_item_count(incoming))
|
||||
return false;
|
||||
if (incoming.family == Visual_Family::marker &&
|
||||
(has_coordinate_labels(*applied.data) ||
|
||||
has_coordinate_labels(*incoming.data)))
|
||||
if ((std::holds_alternative<std::shared_ptr<const Marker_Prepared_Data>>(
|
||||
incoming.data)) &&
|
||||
(has_coordinate_labels(applied) || has_coordinate_labels(incoming)))
|
||||
return applied.revision == incoming.revision;
|
||||
return true;
|
||||
}
|
||||
@@ -1327,8 +1354,8 @@ void Datoviz_Visual_Backend::apply(
|
||||
}
|
||||
|
||||
void Datoviz_Visual_Backend::ensure_external_attributes(
|
||||
Visual_Instance& target, const Prepared_Visual_Data& data) {
|
||||
const auto item_count = data.positions.size();
|
||||
Visual_Instance& target, const Prepared_Visual& visual) {
|
||||
const auto item_count = prepared_item_count(visual);
|
||||
if (item_count == 0) return;
|
||||
if (item_count > std::numeric_limits<std::uint32_t>::max())
|
||||
throw std::length_error("Datoviz external attribute item count exceeds uint32");
|
||||
@@ -1393,12 +1420,13 @@ void Datoviz_Visual_Backend::ensure_external_attributes(
|
||||
}
|
||||
|
||||
void Datoviz_Visual_Backend::upload_external_attributes(
|
||||
Visual_Instance& target, const Prepared_Visual_Data& data,
|
||||
Visual_Instance& target, const Prepared_Visual& visual,
|
||||
std::uint8_t target_index) {
|
||||
if (data.positions.empty()) return;
|
||||
const auto item_count = prepared_item_count(visual);
|
||||
if (item_count == 0) return;
|
||||
const auto upload = [&](External_Attribute& attribute, const void* source,
|
||||
std::size_t count) {
|
||||
if (source == nullptr || count != data.positions.size())
|
||||
if (source == nullptr || count != item_count)
|
||||
throw std::logic_error("Datoviz external attribute fields have different item counts");
|
||||
const auto byte_count = static_cast<DvzSize>(count * attribute.stride);
|
||||
const auto byte_offset = static_cast<DvzSize>(target_index) *
|
||||
@@ -1406,12 +1434,16 @@ void Datoviz_Visual_Backend::upload_external_attributes(
|
||||
dvz_buffer_upload(attribute.gpu_buffer, byte_offset, byte_count, source);
|
||||
};
|
||||
auto attribute = target.attributes.begin();
|
||||
upload(*attribute++, data.positions.data(), data.positions.size());
|
||||
upload(*attribute++, data.colors.data(), data.colors.size());
|
||||
if (target.family == Visual_Family::mesh)
|
||||
if (target.family == Visual_Family::mesh) {
|
||||
const auto& data = prepared_data<Mesh_Prepared_Data>(visual);
|
||||
upload(*attribute++, data.positions.data(), data.positions.size());
|
||||
upload(*attribute++, data.colors.data(), data.colors.size());
|
||||
upload(*attribute++, data.normals.data(), data.normals.size());
|
||||
else {
|
||||
upload(*attribute++, data.sizes.data(), data.sizes.size());
|
||||
} else {
|
||||
const auto& data = prepared_data<Marker_Prepared_Data>(visual);
|
||||
upload(*attribute++, data.positions.data(), data.positions.size());
|
||||
upload(*attribute++, data.colors.data(), data.colors.size());
|
||||
upload(*attribute++, data.diameters.data(), data.diameters.size());
|
||||
upload(*attribute++, data.angles.data(), data.angles.size());
|
||||
upload(*attribute++, data.shapes.data(), data.shapes.size());
|
||||
}
|
||||
@@ -1466,19 +1498,18 @@ void Datoviz_Visual_Backend::register_external_attributes(
|
||||
void Datoviz_Visual_Backend::apply_visual(
|
||||
Visual_Instance& target, const Prepared_Visual& point,
|
||||
std::uint8_t target_index, bool bind_target) {
|
||||
if (point.family != target.family)
|
||||
throw std::logic_error("3D Visual family changed after Scene construction");
|
||||
if (!point.data) throw std::logic_error("3D prepared visual has no immutable payload");
|
||||
if (!has_payload(point)) throw std::logic_error("3D prepared visual has no immutable payload");
|
||||
if (!uses_external_attributes(target.family) &&
|
||||
point.revision == target.applied_revision) return;
|
||||
const auto& data = *point.data;
|
||||
const auto item_count = prepared_item_count(point);
|
||||
auto* visual = target.visual;
|
||||
const bool structure_changed = !target.applied ||
|
||||
!same_visual_structure(*target.applied, point);
|
||||
const bool coordinate_text_changed = target.coordinate_text != nullptr &&
|
||||
(!target.applied || has_coordinate_labels(*target.applied->data) ||
|
||||
has_coordinate_labels(data));
|
||||
(!target.applied || has_coordinate_labels(*target.applied) ||
|
||||
has_coordinate_labels(point));
|
||||
if (coordinate_text_changed) {
|
||||
const auto& data = prepared_data<Marker_Prepared_Data>(point);
|
||||
std::vector<std::string> strings;
|
||||
std::vector<DvzTextItem> items;
|
||||
if (point.visible) {
|
||||
@@ -1517,34 +1548,35 @@ void Datoviz_Visual_Backend::apply_visual(
|
||||
mat4 transform{};
|
||||
for (std::size_t row = 0; row < 4; ++row)
|
||||
for (std::size_t column = 0; column < 4; ++column) transform[row][column] = point.transform.values[row * 4 + column];
|
||||
if (point.family == Visual_Family::point) {
|
||||
if (target.family == Visual_Family::point) {
|
||||
const auto& data = prepared_data<Point_Prepared_Data>(point);
|
||||
DvzPointStyleDesc style = dvz_point_style_desc();
|
||||
style.edge_color.r = point.point_style.edge_color.red;
|
||||
style.edge_color.g = point.point_style.edge_color.green;
|
||||
style.edge_color.b = point.point_style.edge_color.blue;
|
||||
style.edge_color.a = point.point_style.edge_color.alpha;
|
||||
style.stroke_width_px = point.point_style.stroke_width_px;
|
||||
style.aspect = aspect(point.point_style.aspect);
|
||||
style.edge_color.r = data.style.edge_color.red;
|
||||
style.edge_color.g = data.style.edge_color.green;
|
||||
style.edge_color.b = data.style.edge_color.blue;
|
||||
style.edge_color.a = data.style.edge_color.alpha;
|
||||
style.stroke_width_px = data.style.stroke_width_px;
|
||||
style.aspect = aspect(data.style.aspect);
|
||||
if (dvz_point_set_style(visual, &style) != DVZ_OK) throw std::runtime_error("failed to apply Datoviz point style");
|
||||
}
|
||||
if (dvz_visual_set_transform(visual, transform) != DVZ_OK ||
|
||||
dvz_visual_set_depth_test(visual, point.depth_test) != DVZ_OK ||
|
||||
dvz_visual_set_visible(
|
||||
visual, point.visible && !data.positions.empty()) != DVZ_OK)
|
||||
visual, point.visible && item_count != 0) != DVZ_OK)
|
||||
throw std::runtime_error("failed to apply Datoviz visual state");
|
||||
}
|
||||
if (data.positions.empty()) {
|
||||
if (item_count == 0) {
|
||||
if (dvz_visual_set_visible(visual, false) != DVZ_OK) throw std::runtime_error("failed to hide empty Datoviz point visual");
|
||||
target.applied_revision = point.revision;
|
||||
target.applied = point;
|
||||
return;
|
||||
}
|
||||
const auto count = static_cast<std::uint32_t>(data.positions.size());
|
||||
const auto count = static_cast<std::uint32_t>(item_count);
|
||||
if (uses_external_attributes(target.family)) {
|
||||
ensure_external_attributes(target, data);
|
||||
ensure_external_attributes(target, point);
|
||||
if (bind_target)
|
||||
bind_external_attributes(target, target_index, count);
|
||||
upload_external_attributes(target, data, target_index);
|
||||
upload_external_attributes(target, point, target_index);
|
||||
if (structure_changed &&
|
||||
dvz_visual_set_visible(visual, point.visible) != DVZ_OK)
|
||||
throw std::runtime_error("failed to apply Datoviz external visual visibility");
|
||||
@@ -1553,24 +1585,26 @@ void Datoviz_Visual_Backend::apply_visual(
|
||||
return;
|
||||
}
|
||||
DvzResult result = DVZ_OK;
|
||||
switch (point.family) {
|
||||
switch (target.family) {
|
||||
case Visual_Family::point: {
|
||||
const auto& data = prepared_data<Point_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 3> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"diameter_px", data.sizes.data(), count}
|
||||
{"diameter_px", data.diameters.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::splat: {
|
||||
const auto& data = prepared_data<Splat_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 4> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"sigma", data.sigma.data(), count},
|
||||
{"sigma", data.sigmas.data(), count},
|
||||
{"angle", data.angles.data(), count}
|
||||
}
|
||||
};
|
||||
@@ -1578,6 +1612,7 @@ void Datoviz_Visual_Backend::apply_visual(
|
||||
break;
|
||||
}
|
||||
case Visual_Family::pixel: {
|
||||
const auto& data = prepared_data<Pixel_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 3> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
@@ -1589,11 +1624,12 @@ void Datoviz_Visual_Backend::apply_visual(
|
||||
break;
|
||||
}
|
||||
case Visual_Family::marker: {
|
||||
const auto& data = prepared_data<Marker_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 5> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"diameter_px", data.sizes.data(), count},
|
||||
{"diameter_px", data.diameters.data(), count},
|
||||
{"angle", data.angles.data(), count},
|
||||
{"shape", data.shapes.data(), count}
|
||||
}
|
||||
@@ -1602,42 +1638,57 @@ void Datoviz_Visual_Backend::apply_visual(
|
||||
break;
|
||||
}
|
||||
case Visual_Family::sphere: {
|
||||
const auto& data = prepared_data<Sphere_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 3> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"position", data.centers.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"radius", data.sizes.data(), count}
|
||||
{"radius", data.radii.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::segment: {
|
||||
const auto& data = prepared_data<Segment_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 4> updates{
|
||||
{
|
||||
{"position_start", data.positions.data(), count},
|
||||
{"position_end", data.secondary_positions.data(), count},
|
||||
{"position_start", data.starts.data(), count},
|
||||
{"position_end", data.ends.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"stroke_width_px", data.sizes.data(), count}
|
||||
{"stroke_width_px", data.widths.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 4);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::vector: {
|
||||
const auto& data = prepared_data<Vector_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 4> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"vector", data.secondary_positions.data(), count},
|
||||
{"position", data.origins.data(), count},
|
||||
{"vector", data.directions.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"stroke_width_px", data.sizes.data(), count}
|
||||
{"stroke_width_px", data.widths.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 4);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::primitive:
|
||||
case Visual_Family::primitive: {
|
||||
const auto& data = prepared_data<Primitive_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 3> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"normal", data.normals.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::mesh: {
|
||||
const auto& data = prepared_data<Mesh_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 3> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
@@ -1649,18 +1700,30 @@ void Datoviz_Visual_Backend::apply_visual(
|
||||
break;
|
||||
}
|
||||
case Visual_Family::path: {
|
||||
const auto& data = prepared_data<Path_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 3> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"color", data.colors.data(), count},
|
||||
{"stroke_width_px", data.sizes.data(), count}
|
||||
{"stroke_width_px", data.widths.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 3);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::image:
|
||||
case Visual_Family::image: {
|
||||
const auto& data = prepared_data<Image_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 2> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
{"extent", data.extents.data(), count}
|
||||
}
|
||||
};
|
||||
result = dvz_visual_set_data_many(visual, updates.data(), 2);
|
||||
break;
|
||||
}
|
||||
case Visual_Family::labels: {
|
||||
const auto& data = prepared_data<Labels_Prepared_Data>(point);
|
||||
const std::array<DvzVisualDataUpdate, 2> updates{
|
||||
{
|
||||
{"position", data.positions.data(), count},
|
||||
@@ -1851,12 +1914,12 @@ Datoviz_Visual_Backend::reuse_recorded_target(
|
||||
throw std::logic_error(
|
||||
"recorded Datoviz frame contains an unknown Visual identity");
|
||||
if (!uses_external_attributes(visual->family)) continue;
|
||||
if (!source.visual.data)
|
||||
if (!has_payload(source.visual))
|
||||
throw std::logic_error(
|
||||
"recorded Datoviz Visual has no immutable payload");
|
||||
if (!source.visual.data->positions.empty())
|
||||
if (prepared_item_count(source.visual) != 0)
|
||||
upload_external_attributes(
|
||||
*visual, *source.visual.data, target_index);
|
||||
*visual, source.visual, target_index);
|
||||
visual->applied_revision = source.visual.revision;
|
||||
visual->applied = source.visual;
|
||||
}
|
||||
|
||||
@@ -93,9 +93,9 @@ private:
|
||||
void apply_visual(Visual_Instance& target, const Prepared_Visual& visual,
|
||||
std::uint8_t target_index, bool bind_target);
|
||||
void ensure_external_attributes(Visual_Instance& target,
|
||||
const Prepared_Visual_Data& data);
|
||||
const Prepared_Visual& visual);
|
||||
void upload_external_attributes(Visual_Instance& target,
|
||||
const Prepared_Visual_Data& data,
|
||||
const Prepared_Visual& visual,
|
||||
std::uint8_t target_index);
|
||||
void bind_external_attributes(Visual_Instance& target,
|
||||
std::uint8_t target_index,
|
||||
|
||||
@@ -59,20 +59,23 @@ Render_Scene_3D::Builder<Object>::add_renderable(Visual_Object* visual_value) {
|
||||
binding.object = visual_value;
|
||||
binding.family = Visual_Object::Attached_Object::Specification::family;
|
||||
binding.bind = [](Root* root, std::shared_ptr<void> context) {
|
||||
using Definition = typename Visual_Object::Attached_Object;
|
||||
using Prepared = typename Definition::Prepared_Visual;
|
||||
auto* object = static_cast<Visual_Object*>(root);
|
||||
Base::private_access(object).template get<typename Visual_Object::Attached_Object::Base_Tag>()
|
||||
Base::private_access(object).template get<typename Definition::Base_Tag>()
|
||||
.bind_paint_target(std::move(context), [](void* raw_context, const Root* identity,
|
||||
const detail::Prepared_Visual& prepared) {
|
||||
const Prepared& prepared) {
|
||||
auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context);
|
||||
const auto visual_identity = reinterpret_cast<detail::Visual_Identity>(identity);
|
||||
auto erased = detail::erase_prepared_visual(prepared);
|
||||
auto& visuals = *submission.visuals;
|
||||
const auto found = std::ranges::find(
|
||||
visuals, visual_identity,
|
||||
&detail::Prepared_Visual_Instance::identity);
|
||||
if (found == visuals.end())
|
||||
visuals.push_back({visual_identity, prepared});
|
||||
visuals.push_back({visual_identity, std::move(erased)});
|
||||
else
|
||||
found->visual = prepared;
|
||||
found->visual = std::move(erased);
|
||||
});
|
||||
};
|
||||
visuals.push_back(binding);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#pragma once
|
||||
#include "Prepared_Visual.hpp"
|
||||
#include <renderable.hpp>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
namespace aethera::render_3d {
|
||||
template <typename Spec>
|
||||
struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable, Tagged_Buffer<detail::Prepared_Visual_Tag, detail::Prepared_Visual>> {
|
||||
struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable,
|
||||
Tagged_Buffer<detail::Prepared_Visual_Tag,
|
||||
detail::Prepared_Visual_For<typename Spec::Prepared_Data>>> {
|
||||
using Specification = Spec;
|
||||
using Item = typename Spec::Item;
|
||||
using Prepared_Data = typename Spec::Prepared_Data;
|
||||
using Prepared_Visual = detail::Prepared_Visual_For<Prepared_Data>;
|
||||
struct Prop : Basic_Visual::Prev_Prop, Spec::Settings {
|
||||
Matrix4 transform{}; /* 本地坐标到 Scene 坐标的变换。 */
|
||||
bool visible{true}; /* 是否向后端提交可见图元。 */
|
||||
@@ -24,14 +26,6 @@ struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable, Tagged_Buffer<detail::
|
||||
bool operator==(const State&) const;
|
||||
};
|
||||
struct Private;
|
||||
enum class Update_Items_Result : std::uint8_t { updated, invalid_data };
|
||||
/* 原子替换外部可读写图元集合;无效数据保持原值。 */
|
||||
[[nodiscard]] Update_Items_Result update_items(std::vector<Item> items);
|
||||
enum class Edit_Items_Result : std::uint8_t { updated, empty_edit, invalid_data };
|
||||
/* 在当前 Prop 副本上编辑图元集合,通过验证后原子提交。 */
|
||||
[[nodiscard]] Edit_Items_Result edit_items(const std::function<void(std::vector<Item>&)>& edit);
|
||||
/* 返回当前发布 Prop 中的图元数。 */
|
||||
[[nodiscard]] std::size_t item_count() const;
|
||||
};
|
||||
}
|
||||
#include "Basic_Visual.ipp"
|
||||
|
||||
@@ -4,51 +4,95 @@
|
||||
namespace aethera::render_3d {
|
||||
template <typename Spec>
|
||||
struct Basic_Visual<Spec>::Private : Basic_Visual::Prev_Private {
|
||||
using Enqueue_Paint = void (*)(void*, const Root*, const detail::Prepared_Visual&);
|
||||
using Enqueue_Paint = void (*)(void*, const Root*, const Prepared_Visual&);
|
||||
struct Paint_Target {
|
||||
std::weak_ptr<void> context{}; /* Scene 拥有的异步提交上下文;Scene 销毁后自动失效。 */
|
||||
Enqueue_Paint enqueue{}; /* 只入队不等待 GPU 完成的 Paint 入口。 */
|
||||
std::weak_ptr<void> context{}; /* Scene 拥有的异步提交上下文;Scene 销毁后自动失效。 */
|
||||
Enqueue_Paint enqueue{}; /* 只入队不等待 GPU 完成的 Paint 入口。 */
|
||||
};
|
||||
Paint_Target paint_target{}; /* Builder 绑定的所属 Scene 异步 Paint 目标。 */
|
||||
std::uint64_t next_revision{1}; /* 下一份 Prepared_Visual 的单调版本。 */
|
||||
using Update_Run = Update_Items_Result (*)(Root*, std::vector<Item>);
|
||||
using Edit_Run = Edit_Items_Result (*)(Root*, const std::function<void(std::vector<Item>&)>&);
|
||||
using Count_Run = std::size_t (*)(const Root*);
|
||||
struct Dispatch {
|
||||
Update_Run update_items; /* 替换最终对象的 Prop 图元。 */
|
||||
Edit_Run edit_items; /* 编辑最终对象的 Prop 图元。 */
|
||||
Count_Run item_count; /* 查询最终对象当前 Prop 图元数。 */
|
||||
};
|
||||
const Dispatch* dispatch{}; /* 最终对象类型对应的静态公开分派表。 */
|
||||
/* CRTP 覆盖:绑定 Renderable 能力和 Basic_Visual 公开薄壳。 */
|
||||
template <Attached Object> void bind_private_crtp(Object* object);
|
||||
Paint_Target paint_target{}; /* Builder 绑定的所属 Scene 异步 Paint 目标。 */
|
||||
std::uint64_t next_revision{1}; /* 下一份 Prepared_Visual 的单调版本。 */
|
||||
/* Scene Builder 调用:绑定只入队的异步 Paint 目标,目标生命周期必须覆盖 Visual。 */
|
||||
void bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue);
|
||||
/* CRTP 数据能力:从当前 Prop 构建后端字段,不访问其他缓冲角色。 */
|
||||
template <Attached Object> void prepare_data(Object* object);
|
||||
template <Attached Object>
|
||||
void prepare_data(Object* object);
|
||||
/* CRTP 子图能力:每帧提交一次后端渲染;比较双槽 revision 后直接使用最新 Prepared 数据。 */
|
||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& prop);
|
||||
template <Attached Object>
|
||||
[[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& prop);
|
||||
/* CRTP 覆盖:绑定 Scene 后每帧执行 Paint;dirty 只表示 Prepared 数据是否更新,不控制帧生成。 */
|
||||
template <Attached Object> [[nodiscard]] bool should_paint(Object* object, const State& state, bool dirty);
|
||||
template <Attached Object>
|
||||
[[nodiscard]] bool should_paint(Object* object, const State& state, bool dirty);
|
||||
/* CRTP Prop hook:本层字段改变时标记 Prepare。 */
|
||||
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> props);
|
||||
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||
void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> props);
|
||||
/* CRTP 推进 hook:从 Prop 计算公开计数,不复制图元集合。 */
|
||||
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||
template <typename Object, typename Prop_Type, typename State_Type>
|
||||
void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||
[[nodiscard]] static bool valid_items(const std::vector<Item>& items);
|
||||
};
|
||||
template <typename Spec> bool Basic_Visual<Spec>::Prop::operator==(const Prop&) const = default;
|
||||
template <typename Spec> bool Basic_Visual<Spec>::State::operator==(const State&) const = default;
|
||||
template <typename Spec> bool Basic_Visual<Spec>::Private::valid_items(const std::vector<Item>& items) { return std::ranges::all_of(items, [](const Item& item) { return Spec::valid(item); }); }
|
||||
template <typename Spec> void Basic_Visual<Spec>::Private::bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue) { paint_target = {std::move(context), enqueue}; }
|
||||
template <typename Spec> template <Attached Object> void Basic_Visual<Spec>::Private::prepare_data(Object* object) { using Visual_Tag = typename Basic_Visual::Base_Tag; const auto& prop = object->template read_prop<Visual_Tag>(); if (!valid_items(prop.items) || !detail::finite(prop.transform)) throw std::invalid_argument("3D visual property contains invalid data"); auto data = std::make_shared<detail::Prepared_Visual_Data>(); Spec::prepare(prop.items, *data); auto& output = object->template pending_buffer<detail::Prepared_Visual_Tag>(); output = {}; output.family = Spec::family; output.transform = prop.transform; output.visible = prop.visible; output.depth_test = prop.depth_test; output.revision = next_revision++; output.data = std::move(data); if constexpr (requires { prop.style; }) output.point_style = prop.style; object->template update_state<&State::prepared_item_count, &State::prepared_revision>([&](State_Access<typename Object::State> states) { auto& state = states.template get<Visual_Tag>(); state.prepared_item_count = prop.items.size(); state.prepared_revision = output.revision; }); }
|
||||
template <typename Spec> template <Attached Object> tf::Taskflow Basic_Visual<Spec>::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { if (auto context = paint_target.context.lock(); context && paint_target.enqueue) { const auto& pending = object->template pending_buffer<detail::Prepared_Visual_Tag>(); const auto& current = object->template current_buffer<detail::Prepared_Visual_Tag>(); paint_target.enqueue(context.get(), object, pending.revision >= current.revision ? pending : current); } }).name("render_3d.paint.publish"); return graph; }
|
||||
template <typename Spec> template <Attached Object> bool Basic_Visual<Spec>::Private::should_paint(Object*, const State&, bool dirty) { return dirty && paint_target.enqueue != nullptr && !paint_target.context.expired(); }
|
||||
template <typename Spec> template <typename Object, typename Owner, typename Member, typename Prop_Type> void Basic_Visual<Spec>::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||
template <typename Spec> template <typename Object, typename Prop_Type, typename State_Type> void Basic_Visual<Spec>::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { using Visual_Tag = typename Basic_Visual::Base_Tag; pending_states.template get<Visual_Tag>().item_count = static_cast<const Prop&>(*current_prop).items.size(); }
|
||||
template <typename Spec> template <Attached Object> const typename Basic_Visual<Spec>::Private::Dispatch& Basic_Visual<Spec>::Private::dispatch_for() { using Visual_Tag = typename Basic_Visual::Base_Tag; static const Dispatch value{[](Root* root, std::vector<Item> items) { if (!Private::valid_items(items)) return Update_Items_Result::invalid_data; static_cast<Object*>(root)->template set<&Prop::items>(std::move(items)); return Update_Items_Result::updated; }, [](Root* root, const std::function<void(std::vector<Item>&)>& edit) { if (!edit) return Edit_Items_Result::empty_edit; auto* object = static_cast<Object*>(root); auto items = object->template read_prop<Visual_Tag>().items; edit(items); if (!Private::valid_items(items)) return Edit_Items_Result::invalid_data; object->template set<&Prop::items>(std::move(items)); return Edit_Items_Result::updated; }, [](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Visual_Tag>().items.size(); }}; return value; }
|
||||
template <typename Spec> template <Attached Object> void Basic_Visual<Spec>::Private::bind_private_crtp(Object* object) { Basic_Visual::Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
||||
template <typename Spec> typename Basic_Visual<Spec>::Update_Items_Result Basic_Visual<Spec>::update_items(std::vector<Item> items) { return static_cast<Private&>(*this->d).dispatch->update_items(this, std::move(items)); }
|
||||
template <typename Spec> typename Basic_Visual<Spec>::Edit_Items_Result Basic_Visual<Spec>::edit_items(const std::function<void(std::vector<Item>&)>& edit) { return static_cast<Private&>(*this->d).dispatch->edit_items(this, edit); }
|
||||
template <typename Spec> std::size_t Basic_Visual<Spec>::item_count() const { return static_cast<const Private&>(*this->d).dispatch->item_count(this); }
|
||||
template <typename Spec>
|
||||
bool Basic_Visual<Spec>::Prop::operator==(const Prop&) const = default;
|
||||
template <typename Spec>
|
||||
bool Basic_Visual<Spec>::State::operator==(const State&) const = default;
|
||||
template <typename Spec>
|
||||
bool Basic_Visual<Spec>::Private::valid_items(const std::vector<Item>& items) {
|
||||
return std::ranges::all_of(items, [](const Item& item) {
|
||||
return Spec::valid(item);
|
||||
});
|
||||
}
|
||||
template <typename Spec>
|
||||
void Basic_Visual<Spec>::Private::bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue) {
|
||||
paint_target = {std::move(context), enqueue};
|
||||
}
|
||||
template <typename Spec>
|
||||
template <Attached Object>
|
||||
void Basic_Visual<Spec>::Private::prepare_data(Object* object) {
|
||||
using Visual_Tag = typename Basic_Visual::Base_Tag;
|
||||
const auto& prop = object->template read_prop<Visual_Tag>();
|
||||
if (!valid_items(prop.items) || !detail::finite(prop.transform)) throw std::invalid_argument("3D visual property contains invalid data");
|
||||
auto data = std::make_shared<Prepared_Data>();
|
||||
Spec::prepare(prop.items, *data);
|
||||
if constexpr (requires { data->style = prop.style; }) data->style = prop.style;
|
||||
auto& output = object->template pending_buffer<detail::Prepared_Visual_Tag>();
|
||||
output = {};
|
||||
output.transform = prop.transform;
|
||||
output.visible = prop.visible;
|
||||
output.depth_test = prop.depth_test;
|
||||
output.revision = next_revision++;
|
||||
output.data = std::move(data);
|
||||
object->template update_state<&State::prepared_item_count, &State::prepared_revision>([&](State_Access<typename Object::State> states) {
|
||||
auto& state = states.template get<Visual_Tag>();
|
||||
state.prepared_item_count = prop.items.size();
|
||||
state.prepared_revision = output.revision;
|
||||
});
|
||||
}
|
||||
template <typename Spec>
|
||||
template <Attached Object>
|
||||
tf::Taskflow Basic_Visual<Spec>::Private::build_paint_graph(Object* object, const Prop&) {
|
||||
tf::Taskflow graph;
|
||||
graph.emplace([this, object] {
|
||||
if (auto context = paint_target.context.lock(); context && paint_target.enqueue) {
|
||||
const auto& pending = object->template pending_buffer<detail::Prepared_Visual_Tag>();
|
||||
const auto& current = object->template current_buffer<detail::Prepared_Visual_Tag>();
|
||||
paint_target.enqueue(context.get(), object, pending.revision >= current.revision ? pending : current);
|
||||
}
|
||||
}).name("render_3d.paint.publish");
|
||||
return graph;
|
||||
}
|
||||
template <typename Spec>
|
||||
template <Attached Object>
|
||||
bool Basic_Visual<Spec>::Private::should_paint(Object*, const State&, bool dirty) {
|
||||
return dirty && paint_target.enqueue != nullptr && !paint_target.context.expired();
|
||||
}
|
||||
template <typename Spec>
|
||||
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||
void Basic_Visual<Spec>::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) {
|
||||
if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>();
|
||||
}
|
||||
template <typename Spec>
|
||||
template <typename Object, typename Prop_Type, typename State_Type>
|
||||
void Basic_Visual<Spec>::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) {
|
||||
using Visual_Tag = typename Basic_Visual::Base_Tag;
|
||||
pending_states.template get<Visual_Tag>().item_count = static_cast<const Prop&>(*current_prop).items.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,16 @@
|
||||
#include <memory>
|
||||
namespace aethera::render_3d::detail {
|
||||
struct Prepared_Visual_Tag {};
|
||||
struct Prepared_Visual_Data {
|
||||
std::vector<std::array<float, 3>> positions{}; /* 主位置字段。 */
|
||||
std::vector<std::array<std::uint8_t, 4>> colors{}; /* RGBA8 颜色字段。 */
|
||||
std::vector<float> sizes{}; /* 点径、半径或线宽字段。 */
|
||||
std::vector<std::array<float, 2>> sigma{}; /* Splat 椭圆标准差字段。 */
|
||||
std::vector<float> angles{}; /* Splat、Marker 旋转角字段。 */
|
||||
std::vector<std::uint32_t> shapes{}; /* Marker 形状字段。 */
|
||||
std::vector<std::uint8_t> coordinate_label_visibility{}; /* Marker XYZ 标签逐项开关。 */
|
||||
std::vector<std::array<float, 3>> secondary_positions{}; /* 线段终点或向量字段。 */
|
||||
std::vector<std::array<float, 3>> normals{}; /* Primitive、Mesh 法线字段。 */
|
||||
std::vector<std::array<float, 2>> extents{}; /* Image、Labels 尺寸字段。 */
|
||||
};
|
||||
struct Prepared_Visual {
|
||||
Visual_Family family{Visual_Family::point}; /* Datoviz Visual 类型。 */
|
||||
Matrix4 transform{}; /* 本帧提交的对象变换。 */
|
||||
Point_Style point_style{}; /* point family 使用的点样式。 */
|
||||
bool visible{true}; /* 本帧是否绘制。 */
|
||||
bool depth_test{true}; /* 本帧是否启用深度测试。 */
|
||||
std::uint64_t revision{}; /* 数据或样式改变时递增的提交版本。 */
|
||||
std::shared_ptr<const Prepared_Visual_Data> data{}; /* Prepare 发布的不可变 GPU 字段;异步提交只共享所有权。 */
|
||||
using Prepared_Position = std::array<float, 3>;
|
||||
using Prepared_Color = std::array<std::uint8_t, 4>;
|
||||
|
||||
/* 每个 Visual 的双缓冲只保存自己 Spec::Prepared_Data 的强类型结果。 */
|
||||
template <typename Data>
|
||||
struct Prepared_Visual_For {
|
||||
Matrix4 transform{};
|
||||
bool visible{true};
|
||||
bool depth_test{true};
|
||||
std::uint64_t revision{};
|
||||
std::shared_ptr<const Data> data{};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,134 +2,258 @@
|
||||
#include "Basic_Visual.hpp"
|
||||
namespace aethera::render_3d {
|
||||
struct Point {
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* 点填充颜色。 */
|
||||
Pixel_Distance diameter_px{8.0F}; /* 点直径,单位为像素;必须为正数。 */
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* 点填充颜色。 */
|
||||
Pixel_Distance diameter_px{8.0F}; /* 点直径,单位为像素;必须为正数。 */
|
||||
bool operator==(const Point&) const = default;
|
||||
};
|
||||
struct Splat {
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* Splat 颜色。 */
|
||||
Vec2 sigma{0.05F, 0.05F}; /* 两个主轴方向的标准差;必须为正数。 */
|
||||
Coordinate_3D angle{}; /* 主轴旋转角,单位为弧度。 */
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* Splat 颜色。 */
|
||||
Vec2 sigma{0.05F, 0.05F}; /* 两个主轴方向的标准差;必须为正数。 */
|
||||
Coordinate_3D angle{}; /* 主轴旋转角,单位为弧度。 */
|
||||
bool operator==(const Splat&) const = default;
|
||||
};
|
||||
struct Pixel {
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* 像素颜色。 */
|
||||
Pixel_Distance size_px{1.0F}; /* 方形像素边长,单位为像素;必须为正数。 */
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* 像素颜色。 */
|
||||
Pixel_Distance size_px{1.0F}; /* 方形像素边长,单位为像素;必须为正数。 */
|
||||
bool operator==(const Pixel&) const = default;
|
||||
};
|
||||
struct Marker {
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* 标记颜色。 */
|
||||
Pixel_Distance diameter_px{12.0F}; /* 标记直径,单位为像素;必须为正数。 */
|
||||
Coordinate_3D angle{}; /* 标记旋转角,单位为弧度。 */
|
||||
Marker_Shape shape{Marker_Shape::disc}; /* 标记图形。 */
|
||||
bool coordinate_label_visible{false}; /* 是否显示由此 Marker 实际位置派生的 XYZ 标签。 */
|
||||
Vec3 position{}; /* Scene 坐标位置。 */
|
||||
Color color{Color::white()}; /* 标记颜色。 */
|
||||
Pixel_Distance diameter_px{12.0F}; /* 标记直径,单位为像素;必须为正数。 */
|
||||
Coordinate_3D angle{}; /* 标记旋转角,单位为弧度。 */
|
||||
Marker_Shape shape{Marker_Shape::disc}; /* 标记图形。 */
|
||||
bool coordinate_label_visible{false}; /* 是否显示由此 Marker 实际位置派生的 XYZ 标签。 */
|
||||
bool operator==(const Marker&) const = default;
|
||||
};
|
||||
struct Sphere {
|
||||
Vec3 center{}; /* 球心 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 球体颜色。 */
|
||||
Coordinate_3D radius{0.1F}; /* Scene 坐标半径;必须为正数。 */
|
||||
Vec3 center{}; /* 球心 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 球体颜色。 */
|
||||
Coordinate_3D radius{0.1F}; /* Scene 坐标半径;必须为正数。 */
|
||||
bool operator==(const Sphere&) const = default;
|
||||
};
|
||||
struct Segment {
|
||||
Vec3 start{}; /* 线段起点 Scene 坐标。 */
|
||||
Vec3 end{}; /* 线段终点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 线段颜色。 */
|
||||
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
|
||||
Vec3 start{}; /* 线段起点 Scene 坐标。 */
|
||||
Vec3 end{}; /* 线段终点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 线段颜色。 */
|
||||
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
|
||||
bool operator==(const Segment&) const = default;
|
||||
};
|
||||
struct Vector_Glyph {
|
||||
Vec3 origin{}; /* 向量起点 Scene 坐标。 */
|
||||
Vec3 direction{1.0F, 0.0F, 0.0F}; /* 向量方向和长度。 */
|
||||
Color color{Color::white()}; /* 向量颜色。 */
|
||||
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
|
||||
Vec3 origin{}; /* 向量起点 Scene 坐标。 */
|
||||
Vec3 direction{1.0F, 0.0F, 0.0F}; /* 向量方向和长度。 */
|
||||
Color color{Color::white()}; /* 向量颜色。 */
|
||||
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
|
||||
bool operator==(const Vector_Glyph&) const = default;
|
||||
};
|
||||
struct Primitive_Vertex {
|
||||
Vec3 position{}; /* 顶点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 顶点颜色。 */
|
||||
Vec3 normal{}; /* 顶点法线。 */
|
||||
Vec3 position{}; /* 顶点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 顶点颜色。 */
|
||||
Vec3 normal{}; /* 顶点法线。 */
|
||||
bool operator==(const Primitive_Vertex&) const = default;
|
||||
};
|
||||
struct Mesh_Vertex {
|
||||
Vec3 position{}; /* 顶点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 顶点颜色。 */
|
||||
Vec3 normal{}; /* 顶点法线。 */
|
||||
Vec2 texture_coordinate{}; /* 纹理坐标。 */
|
||||
Vec3 position{}; /* 顶点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 顶点颜色。 */
|
||||
Vec3 normal{}; /* 顶点法线。 */
|
||||
Vec2 texture_coordinate{}; /* 纹理坐标。 */
|
||||
bool operator==(const Mesh_Vertex&) const = default;
|
||||
};
|
||||
struct Path_Vertex {
|
||||
Vec3 position{}; /* 路径顶点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 路径颜色。 */
|
||||
Pixel_Distance width_px{1.0F}; /* 路径宽度,单位为像素;必须为正数。 */
|
||||
Vec3 position{}; /* 路径顶点 Scene 坐标。 */
|
||||
Color color{Color::white()}; /* 路径颜色。 */
|
||||
Pixel_Distance width_px{1.0F}; /* 路径宽度,单位为像素;必须为正数。 */
|
||||
bool operator==(const Path_Vertex&) const = default;
|
||||
};
|
||||
struct Image {
|
||||
Vec3 position{}; /* 图像中心 Scene 坐标。 */
|
||||
Vec2 extent{1.0F, 1.0F}; /* Scene 坐标尺寸;两个分量必须为正数。 */
|
||||
Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; /* 纹理采样矩形。 */
|
||||
Color tint{Color::white()}; /* 图像颜色调制。 */
|
||||
Vec3 position{}; /* 图像中心 Scene 坐标。 */
|
||||
Vec2 extent{1.0F, 1.0F}; /* Scene 坐标尺寸;两个分量必须为正数。 */
|
||||
Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; /* 纹理采样矩形。 */
|
||||
Color tint{Color::white()}; /* 图像颜色调制。 */
|
||||
bool operator==(const Image&) const = default;
|
||||
};
|
||||
struct Label {
|
||||
Vec3 position{}; /* 标签中心 Scene 坐标。 */
|
||||
std::uint32_t image_index{}; /* 纹理数组图像下标。 */
|
||||
Vec2 extent{32.0F, 16.0F}; /* 标签尺寸,单位为像素;两个分量必须为正数。 */
|
||||
Color tint{Color::white()}; /* 标签颜色调制。 */
|
||||
Vec3 position{}; /* 标签中心 Scene 坐标。 */
|
||||
std::uint32_t image_index{}; /* 纹理数组图像下标。 */
|
||||
Vec2 extent{32.0F, 16.0F}; /* 标签尺寸,单位为像素;两个分量必须为正数。 */
|
||||
Color tint{Color::white()}; /* 标签颜色调制。 */
|
||||
bool operator==(const Label&) const = default;
|
||||
};
|
||||
struct Glyph {
|
||||
Vec3 position{}; /* 字形基准 Scene 坐标。 */
|
||||
Vec4 bounds{}; /* 字形边界。 */
|
||||
Vec4 texture_coordinates{}; /* 字形纹理坐标。 */
|
||||
Color color{Color::white()}; /* 字形颜色。 */
|
||||
Coordinate_3D angle{}; /* 字形旋转角,单位为弧度。 */
|
||||
Vec3 position{}; /* 字形基准 Scene 坐标。 */
|
||||
Vec4 bounds{}; /* 字形边界。 */
|
||||
Vec4 texture_coordinates{}; /* 字形纹理坐标。 */
|
||||
Color color{Color::white()}; /* 字形颜色。 */
|
||||
Coordinate_3D angle{}; /* 字形旋转角,单位为弧度。 */
|
||||
bool operator==(const Glyph&) const = default;
|
||||
};
|
||||
struct Text_Label {
|
||||
Vec3 position{}; /* 文本基准 Scene 坐标。 */
|
||||
std::string text{}; /* UTF-8 文本;不能为空。 */
|
||||
Color color{Color::white()}; /* 文本颜色。 */
|
||||
Pixel_Distance size_px{18.0F}; /* 字号,单位为像素;必须为正数。 */
|
||||
Vec3 position{}; /* 文本基准 Scene 坐标。 */
|
||||
std::string text{}; /* UTF-8 文本;不能为空。 */
|
||||
Color color{Color::white()}; /* 文本颜色。 */
|
||||
Pixel_Distance size_px{18.0F}; /* 字号,单位为像素;必须为正数。 */
|
||||
bool operator==(const Text_Label&) const = default;
|
||||
};
|
||||
struct Voxel {
|
||||
Coordinate_3D value{}; /* 标量体数据值。 */
|
||||
Coordinate_3D value{}; /* 标量体数据值。 */
|
||||
bool operator==(const Voxel&) const = default;
|
||||
};
|
||||
namespace detail {
|
||||
struct Point_Settings : Visual_Settings {
|
||||
Point_Style style{}; /* Point family 的边缘样式。 */
|
||||
Point_Style style{}; /* Point family 的边缘样式。 */
|
||||
bool operator==(const Point_Settings&) const = default;
|
||||
};
|
||||
#define AETHERA_VISUAL_SPEC(Name, Item_Type, Settings_Type, Family_Value) \
|
||||
struct Name##_Spec { \
|
||||
using Item = Item_Type; \
|
||||
using Settings = Settings_Type; \
|
||||
static constexpr Visual_Family family = Visual_Family::Family_Value; \
|
||||
[[nodiscard]] static bool valid(const Item& item) noexcept; \
|
||||
static void prepare(const std::vector<Item>& items, Prepared_Visual_Data& output); \
|
||||
}
|
||||
AETHERA_VISUAL_SPEC(Point, Point, Point_Settings, point);
|
||||
AETHERA_VISUAL_SPEC(Splat, Splat, Visual_Settings, splat);
|
||||
AETHERA_VISUAL_SPEC(Pixel, Pixel, Visual_Settings, pixel);
|
||||
AETHERA_VISUAL_SPEC(Marker, Marker, Visual_Settings, marker);
|
||||
AETHERA_VISUAL_SPEC(Sphere, Sphere, Visual_Settings, sphere);
|
||||
AETHERA_VISUAL_SPEC(Segment, Segment, Visual_Settings, segment);
|
||||
AETHERA_VISUAL_SPEC(Vector, Vector_Glyph, Visual_Settings, vector);
|
||||
AETHERA_VISUAL_SPEC(Primitive, Primitive_Vertex, Primitive_Settings, primitive);
|
||||
AETHERA_VISUAL_SPEC(Mesh, Mesh_Vertex, Visual_Settings, mesh);
|
||||
AETHERA_VISUAL_SPEC(Path, Path_Vertex, Visual_Settings, path);
|
||||
AETHERA_VISUAL_SPEC(Image, Image, Texture_Field_Settings, image);
|
||||
AETHERA_VISUAL_SPEC(Labels, Label, Texture_Field_Settings, labels);
|
||||
AETHERA_VISUAL_SPEC(Glyph, Glyph, Texture_Field_Settings, glyph);
|
||||
AETHERA_VISUAL_SPEC(Text, Text_Label, Visual_Settings, text);
|
||||
AETHERA_VISUAL_SPEC(Volume, Voxel, Volume_Field_Settings, volume);
|
||||
#undef AETHERA_VISUAL_SPEC
|
||||
struct Point_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> diameters{};
|
||||
Point_Style style{};
|
||||
};
|
||||
struct Splat_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<std::array<float, 2>> sigmas{};
|
||||
std::vector<float> angles{};
|
||||
};
|
||||
struct Pixel_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> sizes{};
|
||||
};
|
||||
struct Marker_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> diameters{};
|
||||
std::vector<float> angles{};
|
||||
std::vector<std::uint32_t> shapes{};
|
||||
std::vector<std::uint8_t> coordinate_label_visibility{};
|
||||
};
|
||||
struct Sphere_Prepared_Data {
|
||||
std::vector<Prepared_Position> centers{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> radii{};
|
||||
};
|
||||
struct Segment_Prepared_Data {
|
||||
std::vector<Prepared_Position> starts{};
|
||||
std::vector<Prepared_Position> ends{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> widths{};
|
||||
};
|
||||
struct Vector_Prepared_Data {
|
||||
std::vector<Prepared_Position> origins{};
|
||||
std::vector<Prepared_Position> directions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> widths{};
|
||||
};
|
||||
struct Primitive_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<Prepared_Position> normals{};
|
||||
};
|
||||
struct Mesh_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<Prepared_Position> normals{};
|
||||
};
|
||||
struct Path_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> widths{};
|
||||
};
|
||||
struct Image_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<std::array<float, 2>> extents{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
};
|
||||
struct Labels_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<std::array<float, 2>> extents{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
};
|
||||
struct Glyph_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> angles{};
|
||||
};
|
||||
struct Text_Prepared_Data {
|
||||
std::vector<Prepared_Position> positions{};
|
||||
std::vector<Prepared_Color> colors{};
|
||||
std::vector<float> sizes{};
|
||||
};
|
||||
struct Volume_Prepared_Data {
|
||||
std::vector<float> values{};
|
||||
};
|
||||
template <typename Item_Type, typename Settings_Type, Visual_Family Family,
|
||||
typename Prepared_Type>
|
||||
struct Visual_Spec {
|
||||
using Item = Item_Type;
|
||||
using Settings = Settings_Type;
|
||||
using Prepared_Data = Prepared_Type;
|
||||
static constexpr Visual_Family family = Family;
|
||||
};
|
||||
struct Point_Spec : Visual_Spec<Point, Point_Settings, Visual_Family::point, Point_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Splat_Spec : Visual_Spec<Splat, Visual_Settings, Visual_Family::splat, Splat_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Pixel_Spec : Visual_Spec<Pixel, Visual_Settings, Visual_Family::pixel, Pixel_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Marker_Spec : Visual_Spec<Marker, Visual_Settings, Visual_Family::marker, Marker_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Sphere_Spec : Visual_Spec<Sphere, Visual_Settings, Visual_Family::sphere, Sphere_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Segment_Spec : Visual_Spec<Segment, Visual_Settings, Visual_Family::segment, Segment_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Vector_Spec : Visual_Spec<Vector_Glyph, Visual_Settings, Visual_Family::vector, Vector_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Primitive_Spec : Visual_Spec<Primitive_Vertex, Primitive_Settings, Visual_Family::primitive, Primitive_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Mesh_Spec : Visual_Spec<Mesh_Vertex, Visual_Settings, Visual_Family::mesh, Mesh_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Path_Spec : Visual_Spec<Path_Vertex, Visual_Settings, Visual_Family::path, Path_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Image_Spec : Visual_Spec<Image, Texture_Field_Settings, Visual_Family::image, Image_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Labels_Spec : Visual_Spec<Label, Texture_Field_Settings, Visual_Family::labels, Labels_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Glyph_Spec : Visual_Spec<Glyph, Texture_Field_Settings, Visual_Family::glyph, Glyph_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Text_Spec : Visual_Spec<Text_Label, Visual_Settings, Visual_Family::text, Text_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
struct Volume_Spec : Visual_Spec<Voxel, Volume_Field_Settings, Visual_Family::volume, Volume_Prepared_Data> {
|
||||
[[nodiscard]] static bool valid(const Item&) noexcept;
|
||||
static void prepare(const std::vector<Item>&, Prepared_Data&);
|
||||
};
|
||||
}
|
||||
using Point_Visual = Basic_Visual<detail::Point_Spec>;
|
||||
using Splat_Visual = Basic_Visual<detail::Splat_Spec>;
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
#pragma once
|
||||
namespace aethera::render_3d::detail {
|
||||
inline std::array<std::uint8_t, 4> channels(Color value) { return {value.red, value.green, value.blue, value.alpha}; }
|
||||
inline void reserve_common(Prepared_Visual_Data& output, std::size_t count) { output.positions.reserve(count); output.colors.reserve(count); output.sizes.reserve(count); }
|
||||
inline bool Point_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F; }
|
||||
inline void Point_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.diameter_px); } }
|
||||
inline bool Splat_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.sigma) && item.sigma.x > 0.0F && item.sigma.y > 0.0F && finite(item.angle); }
|
||||
inline void Splat_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.sigma.reserve(items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sigma.push_back({item.sigma.x, item.sigma.y}); output.angles.push_back(item.angle); } }
|
||||
inline bool Pixel_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.size_px) && item.size_px > 0.0F; }
|
||||
inline void Pixel_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
|
||||
inline bool Marker_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F && finite(item.angle); }
|
||||
inline void Marker_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.angles.reserve(items.size()); output.shapes.reserve(items.size()); output.coordinate_label_visibility.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.diameter_px); output.angles.push_back(item.angle); output.shapes.push_back(static_cast<std::uint32_t>(item.shape)); output.coordinate_label_visibility.push_back(item.coordinate_label_visible); } }
|
||||
inline bool Sphere_Spec::valid(const Item& item) noexcept { return finite(item.center) && finite(item.radius) && item.radius > 0.0F; }
|
||||
inline void Sphere_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.center.x, item.center.y, item.center.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.radius); } }
|
||||
inline bool Segment_Spec::valid(const Item& item) noexcept { return finite(item.start) && finite(item.end) && finite(item.width_px) && item.width_px > 0.0F; }
|
||||
inline void Segment_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.secondary_positions.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.start.x, item.start.y, item.start.z}); output.secondary_positions.push_back({item.end.x, item.end.y, item.end.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.width_px); } }
|
||||
inline bool Vector_Spec::valid(const Item& item) noexcept { return finite(item.origin) && finite(item.direction) && finite(item.width_px) && item.width_px > 0.0F; }
|
||||
inline void Vector_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.secondary_positions.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.origin.x, item.origin.y, item.origin.z}); output.secondary_positions.push_back({item.direction.x, item.direction.y, item.direction.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.width_px); } }
|
||||
inline bool Primitive_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal); }
|
||||
inline void Primitive_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
|
||||
inline bool Mesh_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal) && finite(item.texture_coordinate); }
|
||||
inline void Mesh_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
|
||||
inline bool Path_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.width_px) && item.width_px > 0.0F; }
|
||||
inline void Path_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.width_px); } }
|
||||
inline bool Image_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && finite(item.texture_rectangle); }
|
||||
inline void Image_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.extents.reserve(items.size()); output.colors.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
|
||||
inline bool Labels_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F; }
|
||||
inline void Labels_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.extents.reserve(items.size()); output.colors.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
|
||||
inline bool Glyph_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.bounds) && finite(item.texture_coordinates) && finite(item.angle); }
|
||||
inline void Glyph_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.colors.reserve(items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.angles.push_back(item.angle); } }
|
||||
inline bool Text_Spec::valid(const Item& item) noexcept { return finite(item.position) && !item.text.empty() && finite(item.size_px) && item.size_px > 0.0F; }
|
||||
inline void Text_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.colors.reserve(items.size()); output.sizes.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
|
||||
inline bool Volume_Spec::valid(const Item& item) noexcept { return finite(item.value); }
|
||||
inline void Volume_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.sizes.reserve(items.size()); for (const auto& item : items) output.sizes.push_back(item.value); }
|
||||
inline Prepared_Color channels(Color value) {
|
||||
return {value.red, value.green, value.blue, value.alpha};
|
||||
}
|
||||
template <typename Data>
|
||||
void reserve_position_color(Data& output, std::size_t count) {
|
||||
output.positions.reserve(count);
|
||||
output.colors.reserve(count);
|
||||
}
|
||||
inline bool Point_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F; }
|
||||
inline void Point_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.diameters.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.diameters.push_back(item.diameter_px); } }
|
||||
inline bool Splat_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.sigma) && item.sigma.x > 0.0F && item.sigma.y > 0.0F && finite(item.angle); }
|
||||
inline void Splat_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sigmas.reserve(items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sigmas.push_back({item.sigma.x, item.sigma.y}); output.angles.push_back(item.angle); } }
|
||||
inline bool Pixel_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.size_px) && item.size_px > 0.0F; }
|
||||
inline void Pixel_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sizes.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
|
||||
inline bool Marker_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F && finite(item.angle); }
|
||||
inline void Marker_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.diameters.reserve(items.size()); output.angles.reserve(items.size()); output.shapes.reserve(items.size()); output.coordinate_label_visibility.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.diameters.push_back(item.diameter_px); output.angles.push_back(item.angle); output.shapes.push_back(static_cast<std::uint32_t>(item.shape)); output.coordinate_label_visibility.push_back(item.coordinate_label_visible); } }
|
||||
inline bool Sphere_Spec::valid(const Item& item) noexcept { return finite(item.center) && finite(item.radius) && item.radius > 0.0F; }
|
||||
inline void Sphere_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.centers.reserve(items.size()); output.colors.reserve(items.size()); output.radii.reserve(items.size()); for (const auto& item : items) { output.centers.push_back({item.center.x, item.center.y, item.center.z}); output.colors.push_back(channels(item.color)); output.radii.push_back(item.radius); } }
|
||||
inline bool Segment_Spec::valid(const Item& item) noexcept { return finite(item.start) && finite(item.end) && finite(item.width_px) && item.width_px > 0.0F; }
|
||||
inline void Segment_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.starts.reserve(items.size()); output.ends.reserve(items.size()); output.colors.reserve(items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.starts.push_back({item.start.x, item.start.y, item.start.z}); output.ends.push_back({item.end.x, item.end.y, item.end.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } }
|
||||
inline bool Vector_Spec::valid(const Item& item) noexcept { return finite(item.origin) && finite(item.direction) && finite(item.width_px) && item.width_px > 0.0F; }
|
||||
inline void Vector_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.origins.reserve(items.size()); output.directions.reserve(items.size()); output.colors.reserve(items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.origins.push_back({item.origin.x, item.origin.y, item.origin.z}); output.directions.push_back({item.direction.x, item.direction.y, item.direction.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } }
|
||||
inline bool Primitive_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal); }
|
||||
inline void Primitive_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
|
||||
inline bool Mesh_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal) && finite(item.texture_coordinate); }
|
||||
inline void Mesh_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
|
||||
inline bool Path_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.width_px) && item.width_px > 0.0F; }
|
||||
inline void Path_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } }
|
||||
inline bool Image_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && finite(item.texture_rectangle); }
|
||||
inline void Image_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.extents.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
|
||||
inline bool Labels_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F; }
|
||||
inline void Labels_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.extents.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
|
||||
inline bool Glyph_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.bounds) && finite(item.texture_coordinates) && finite(item.angle); }
|
||||
inline void Glyph_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.angles.push_back(item.angle); } }
|
||||
inline bool Text_Spec::valid(const Item& item) noexcept { return finite(item.position) && !item.text.empty() && finite(item.size_px) && item.size_px > 0.0F; }
|
||||
inline void Text_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sizes.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
|
||||
inline bool Volume_Spec::valid(const Item& item) noexcept { return finite(item.value); }
|
||||
inline void Volume_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.values.reserve(items.size()); for (const auto& item : items) output.values.push_back(item.value); }
|
||||
}
|
||||
|
||||
@@ -7,15 +7,14 @@
|
||||
namespace aethera::render_3d {
|
||||
namespace {
|
||||
template <typename Visual, typename Item>
|
||||
void verify_visual(Item valid, Item invalid) {
|
||||
void verify_visual(Item valid, Item) {
|
||||
using Object = Impl<Visual>;
|
||||
auto built = typename Object::Builder{}.build();
|
||||
ASSERT_TRUE(built.has_value());
|
||||
auto object = std::move(built).value();
|
||||
EXPECT_EQ(object->update_items({valid}), Visual::Update_Items_Result::updated);
|
||||
EXPECT_EQ(object->update_items({invalid}), Visual::Update_Items_Result::invalid_data);
|
||||
object->template set<&Visual::Prop::items>(std::vector<Item>{valid});
|
||||
object->advance();
|
||||
EXPECT_EQ(object->item_count(), 1U);
|
||||
EXPECT_EQ(object->template read_prop<typename Visual::Base_Tag>().items.size(), 1U);
|
||||
EXPECT_EQ(object->template read_state<typename Visual::Base_Tag>().item_count, 1U);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +50,7 @@ TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) {
|
||||
auto visual_result = typename Visual_Object::Builder{}.build();
|
||||
ASSERT_TRUE(visual_result.has_value());
|
||||
auto visual = std::move(visual_result).value();
|
||||
ASSERT_EQ(visual->update_items({Point{.position = {0.0F, 0.0F, 0.0F}, .color = Color::red_color(), .diameter_px = 8.0F}}), Point_Visual::Update_Items_Result::updated);
|
||||
visual->set<&Point_Visual::Prop::items>(std::vector<Point>{Point{.position = {0.0F, 0.0F, 0.0F}, .color = Color::red_color(), .diameter_px = 8.0F}});
|
||||
visual->advance();
|
||||
try {
|
||||
auto scene_result = typename Scene_Object::Builder(visual.get()).build();
|
||||
|
||||
Reference in New Issue
Block a user