MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Morphology.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace MayaFlux::Kinesis {
6
7// =============================================================================
8// PositionCarrying concept
9// =============================================================================
10
11/**
12 * @concept PositionCarrying
13 * @brief Satisfied by any type exposing a @c position field convertible to glm::vec3.
14 *
15 * Matched by all Kakshya vertex types (Vertex, PointVertex, LineVertex,
16 * MeshVertex, TextureQuadVertex), any user-defined struct following the same
17 * convention, and glm::vec3 itself when wrapped in a trivial aggregate.
18 *
19 * Functions constrained by PositionCarrying accept arbitrary point sets
20 * without specialisation: mesh vertices, point clouds, particle systems,
21 * Forma geometry, CV observations, or any future vertex kind.
22 */
23template <typename T>
24concept PositionCarrying = requires(const T& v) {
25 { v.position } -> std::convertible_to<glm::vec3>;
26};
27
28// =============================================================================
29// centroid
30// =============================================================================
31
32/**
33 * @brief Arithmetic centroid (uniform weight) of a PositionCarrying span.
34 *
35 * Computes the first geometric moment with measure 1 per point.
36 * Returns the zero vector for an empty span.
37 *
38 * @tparam T Any type satisfying PositionCarrying.
39 * @param pts Non-owning span of points.
40 * @return Mean position.
41 */
42template <PositionCarrying T>
43[[nodiscard]] glm::vec3 centroid(std::span<T> pts) noexcept
44{
45 if (pts.empty())
46 return glm::vec3(0.0F);
47 glm::vec3 acc(0.0F);
48 for (const auto& p : pts)
49 acc += static_cast<glm::vec3>(p.position);
50 return acc / static_cast<float>(pts.size());
51}
52
53/**
54 * @brief Scalar-weighted centroid of a PositionCarrying span.
55 *
56 * Each point contributes @p weight(point) to the measure. Suitable for
57 * mesh vertices weighted by deformation magnitude, particles weighted by
58 * energy (size / thickness field), or any domain-specific scalar.
59 *
60 * Falls back to the zero vector when total weight is zero or the span is empty.
61 *
62 * @tparam T Any type satisfying PositionCarrying.
63 * @tparam WeightFn Callable: @c const T& -> float (or any float-convertible type).
64 * @param pts Non-owning span of points.
65 * @param weight Per-point weight callable.
66 * @return Weighted mean position.
67 */
68template <PositionCarrying T, std::invocable<const T&> WeightFn>
69 requires std::convertible_to<std::invoke_result_t<WeightFn, const T&>, float>
70[[nodiscard]] glm::vec3 centroid(std::span<T> pts, WeightFn weight) noexcept
71{
72 if (pts.empty())
73 return glm::vec3(0.0F);
74 glm::vec3 acc(0.0F);
75 float total = 0.0F;
76 for (const auto& p : pts) {
77 const auto w = static_cast<float>(std::invoke(weight, p));
78 acc += static_cast<glm::vec3>(p.position) * w;
79 total += w;
80 }
81 return total > 0.0F ? acc / total : glm::vec3(0.0F);
82}
83
84/**
85 * @brief Fully explicit weighted centroid for arbitrary point types.
86 *
87 * Neither PositionCarrying nor any field convention is required. Both the
88 * position and the weight are extracted via caller-supplied callables. Use
89 * this overload for Nexus QueryResult spans, CV observation structs, Eigen
90 * column references, or any type that does not follow the vertex convention.
91 *
92 * Falls back to the zero vector when total weight is zero or the span is empty.
93 *
94 * @tparam T Element type. No concept constraint.
95 * @tparam PosFn Callable: @c const T& -> glm::vec3 (or convertible).
96 * @tparam WeightFn Callable: @c const T& -> float (or convertible).
97 * @param pts Non-owning span of elements.
98 * @param pos Position extractor.
99 * @param weight Weight extractor.
100 * @return Weighted mean position.
101 */
102template <typename T,
103 std::invocable<const T&> PosFn,
104 std::invocable<const T&> WeightFn>
105 requires std::convertible_to<std::invoke_result_t<PosFn, const T&>, glm::vec3>
106 && std::convertible_to<std::invoke_result_t<WeightFn, const T&>, float>
107[[nodiscard]] glm::vec3 centroid(std::span<T> pts, PosFn pos, WeightFn weight) noexcept
108{
109 if (pts.empty())
110 return glm::vec3(0.0F);
111 glm::vec3 acc(0.0F);
112 float total = 0.0F;
113 for (const auto& p : pts) {
114 const auto w = static_cast<float>(std::invoke(weight, p));
115 acc += static_cast<glm::vec3>(std::invoke(pos, p)) * w;
116 total += w;
117 }
118 return total > 0.0F ? acc / total : glm::vec3(0.0F);
119}
120
121/**
122 * @brief Arithmetic centroid of a plain position span.
123 *
124 * @param pts Non-owning span of positions.
125 * @return Mean position, or the zero vector for an empty span.
126 */
127[[nodiscard]] inline glm::vec3 centroid(std::span<const glm::vec3> pts) noexcept
128{
129 if (pts.empty())
130 return glm::vec3(0.0F);
131 glm::vec3 acc(0.0F);
132 for (const auto& p : pts)
133 acc += p;
134 return acc / static_cast<float>(pts.size());
135}
136
137/**
138 * @brief Position of one record in interleaved vertex bytes.
139 *
140 * @param bytes Interleaved records.
141 * @param stride Bytes per record.
142 * @param offset Byte offset of the vec3 position within a record.
143 * @param index Record index.
144 * @return The position, or nullopt when the position does not lie inside
145 * @p bytes or @p stride is too small to hold it.
146 */
147[[nodiscard]] inline std::optional<glm::vec3> position_at(
148 std::span<const std::byte> bytes,
149 size_t stride,
150 size_t offset,
151 size_t index) noexcept
152{
153 if (stride < offset + sizeof(glm::vec3)
154 || index * stride + offset + sizeof(glm::vec3) > bytes.size())
155 return std::nullopt;
156 glm::vec3 p;
157 std::memcpy(&p, bytes.data() + index * stride + offset, sizeof(glm::vec3));
158 return p;
159}
160
161/**
162 * @brief Arithmetic centroid of the positions in interleaved vertex bytes.
163 *
164 * Every Kakshya vertex record is 60 bytes with the position at offset 0, but
165 * any stride and offset work, so readback bytes, node vertex data and MeshData
166 * all go through here without a typed copy.
167 *
168 * @param bytes Interleaved records. A trailing partial record is ignored.
169 * @param stride Bytes per record.
170 * @param offset Byte offset of the vec3 position within a record.
171 * @return Mean position, or the zero vector when no whole record fits or
172 * @p stride is too small to hold a position.
173 */
174[[nodiscard]] inline glm::vec3 centroid(
175 std::span<const std::byte> bytes,
176 size_t stride,
177 size_t offset = 0) noexcept
178{
179 if (stride < offset + sizeof(glm::vec3))
180 return glm::vec3(0.0F);
181 const size_t count = bytes.size() / stride;
182 if (count == 0)
183 return glm::vec3(0.0F);
184 glm::vec3 acc(0.0F);
185 for (size_t i = 0; i < count; ++i)
186 acc += position_at(bytes, stride, offset, i).value_or(glm::vec3(0.0F));
187 return acc / static_cast<float>(count);
188}
189
190/**
191 * @brief Area-weighted centroid of the triangles of an indexed mesh.
192 *
193 * Each triangle contributes its own centroid weighted by its area, so dense
194 * tessellation does not pull the result the way a vertex mean does. A triangle
195 * with an index outside @p bytes is skipped.
196 *
197 * @param bytes Interleaved vertex records.
198 * @param stride Bytes per record.
199 * @param offset Byte offset of the vec3 position within a record.
200 * @param indices Triangle list, three indices per face.
201 * @return Surface centroid, or the vertex centroid when there is no
202 * triangle of nonzero area.
203 */
204[[nodiscard]] inline glm::vec3 surface_centroid(
205 std::span<const std::byte> bytes,
206 size_t stride,
207 size_t offset,
208 std::span<const uint32_t> indices) noexcept
209{
210 glm::vec3 acc(0.0F);
211 float total = 0.0F;
212 for (size_t t = 0; t + 2 < indices.size(); t += 3) {
213 const auto a = position_at(bytes, stride, offset, indices[t]);
214 const auto b = position_at(bytes, stride, offset, indices[t + 1]);
215 const auto c = position_at(bytes, stride, offset, indices[t + 2]);
216 if (!a || !b || !c)
217 continue;
218 const float area = 0.5F * glm::length(glm::cross(*b - *a, *c - *a));
219 acc += (*a + *b + *c) * (area / 3.0F);
220 total += area;
221 }
222 return total > 0.0F ? acc / total : centroid(bytes, stride, offset);
223}
224
225// =============================================================================
226// aabb
227// =============================================================================
228
229/**
230 * @brief Axis-aligned bounding box of a PositionCarrying span.
231 *
232 * Returns AABB3D{zero, zero} for an empty span. No allocation; single
233 * linear pass.
234 *
235 * @tparam T Any type satisfying PositionCarrying.
236 * @param pts Non-owning span of points.
237 * @return Tightest AABB enclosing all positions in @p pts.
238 */
239template <PositionCarrying T>
240[[nodiscard]] AABB3D aabb(std::span<T> pts) noexcept
241{
242 if (pts.empty())
243 return AABB3D { .min = glm::vec3(0.0F), .max = glm::vec3(0.0F) };
244 constexpr float inf = std::numeric_limits<float>::max();
245 AABB3D box { .min = glm::vec3(inf), .max = glm::vec3(-inf) };
246 for (const auto& p : pts) {
247 const glm::vec3 q = static_cast<glm::vec3>(p.position);
248 box.min = glm::min(box.min, q);
249 box.max = glm::max(box.max, q);
250 }
251 return box;
252}
253
254/**
255 * @brief Axis-aligned bounding box of a plain position span.
256 *
257 * Counterpart of the plain position centroid, for positions already decoded
258 * from vertices, for example by Kakshya::VertexInsertion.
259 *
260 * @param pts Non-owning span of positions.
261 * @return Tightest box, or AABB3D{zero, zero} for an empty span.
262 */
263[[nodiscard]] inline AABB3D aabb(std::span<const glm::vec3> pts) noexcept
264{
265 if (pts.empty())
266 return AABB3D { .min = glm::vec3(0.0F), .max = glm::vec3(0.0F) };
267 constexpr float inf = std::numeric_limits<float>::max();
268 AABB3D box { .min = glm::vec3(inf), .max = glm::vec3(-inf) };
269 for (const auto& p : pts) {
270 box.min = glm::min(box.min, p);
271 box.max = glm::max(box.max, p);
272 }
273 return box;
274}
275
276} // namespace MayaFlux::Kinesis
uint64_t offset
Definition Composite.cpp:10
std::vector< uint32_t > indices
uint32_t total
size_t a
double weight
size_t b
double q
uint32_t index
Definition VKDevice.cpp:122
size_t count
Satisfied by any type exposing a position field convertible to glm::vec3.
std::optional< glm::vec3 > position_at(std::span< const std::byte > bytes, size_t stride, size_t offset, size_t index) noexcept
Position of one record in interleaved vertex bytes.
glm::vec2 centroid(const Memory::HistoryBuffer< glm::vec2 > &history, size_t window) noexcept
Mean position across a window.
glm::vec3 surface_centroid(std::span< const std::byte > bytes, size_t stride, size_t offset, std::span< const uint32_t > indices) noexcept
Area-weighted centroid of the triangles of an indexed mesh.
AABB3D aabb(std::span< T > pts) noexcept
Axis-aligned bounding box of a PositionCarrying span.
Axis-aligned bounding box in 3D world space.
Definition Bounds.hpp:188