VLink  2.0.0
A high-performance communication middleware
point_cloud.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file point_cloud.h
26  * @brief Zero-copy, schema-aware 3-D point cloud container with per-field type protocol.
27  *
28  * @details
29  * @c PointCloud carries a contiguous array of fixed-size point records together
30  * with a compact, self-describing schema (the embedded @c Protocol). Schemas
31  * are encoded as two @c uint64_t nibble-packed integers plus a comma-separated
32  * 152-byte name string, so a receiver can introspect every field without
33  * sharing an external IDL. The 40-byte @c Header prefix carries sequencing
34  * and dual-timestamp metadata.
35  *
36  * @par Point format diagram
37  * @code
38  * Per-point layout (tightly packed, no padding):
39  *
40  * XYZ float : [ x: float32 | y: float32 | z: float32 ] (12 B)
41  * XYZI float : [ x | y | z | intensity: float32 ] (16 B)
42  * XYZRGB : [ x | y | z | r: u8 | g: u8 | b: u8 | pad: u8 ] (16 B)
43  * XYZ + features : [ x | y | z | t: u32 | label: u8 | conf: f32 | ... ] (variable)
44  * XYZ double : [ x: float64 | y: float64 | z: float64 ] (24 B)
45  *
46  * Schema is encoded into two uint64_t integers, one nibble per field
47  * (high nibble = first field):
48  *
49  * size_num: 0x0408... (each nibble is the field byte-size)
50  * type_num: 0x0A0B... (each nibble is a PointCloud::Type enum value)
51  * names: "x,y,z,intensity,label" (stored in 152 bytes)
52  * @endcode
53  *
54  * @par Supported field types
55  * | Enum | C++ type | Bytes |
56  * | ------------------ | ----------- | ----- |
57  * | @c kBoolType | @c bool | 1 |
58  * | @c kInt8Type | @c int8_t | 1 |
59  * | @c kUint8Type | @c uint8_t | 1 |
60  * | @c kInt16Type | @c int16_t | 2 |
61  * | @c kUint16Type | @c uint16_t | 2 |
62  * | @c kInt32Type | @c int32_t | 4 |
63  * | @c kUint32Type | @c uint32_t | 4 |
64  * | @c kInt64Type | @c int64_t | 8 |
65  * | @c kUint64Type | @c uint64_t | 8 |
66  * | @c kFloatType | @c float | 4 |
67  * | @c kDoubleType | @c double | 8 |
68  *
69  * @par Protocol nested struct
70  * | Member | Type | Purpose |
71  * | --------------- | -------------- | ---------------------------------------------- |
72  * | @c size_num | @c uint64_t | Per-field byte sizes packed in nibbles |
73  * | @c type_num | @c uint64_t | Per-field @c Type tags packed in nibbles |
74  * | @c names[152] | @c char[] | Comma-separated field names (3..16 fields) |
75  *
76  * @par Wire format
77  * @c PointCloud is POD; @c memcpy of the struct snapshot plus the packed point
78  * buffer forms the wire payload. The @c sizeof value is locked by
79  * @c static_assert and forms a permanent contract: @c vlink::zerocopy::*
80  * containers offer NO forward and NO backward binary compatibility -- every
81  * field, including reserved bytes and the @c Protocol descriptor, is part of
82  * the wire contract.
83  * @code
84  * static_assert(sizeof(PointCloud) == 256, "Sizeof must be 256 bytes.");
85  * @endcode
86  *
87  * @par Memory layout
88  * @code
89  * Offset Size Field
90  * ------ ---- ----------------------------------------------------------------
91  * 0 40 Header header
92  * 40 168 Protocol protocol_ (size_num + type_num + names[152])
93  * 208 8 uint8_t* data_
94  * 216 8 size_t capacity_
95  * 224 8 size_t size_
96  * 232 4 uint32_t reserved_buf_
97  * 236 4 uint32_t reserved_buf2_
98  * 240 2 uint16_t pack_size_
99  * 242 2 uint16_t extent_
100  * 244 1 uint8_t downsample_
101  * 245 1 uint8_t reserved_buf3_
102  * 246 1 bool vertical_
103  * 247 1 bool is_owner_
104  * 248 8 uint64_t index_
105  * ------ ---- ----------------------------------------------------------------
106  * Total 256 bytes (alignas 8)
107  *
108  * Wire envelope:
109  * [ magic_begin (4) | version (4) | PointCloud (256) | point data (size_ * pack_size_) | magic_end (4) ]
110  * @endcode
111  *
112  * @par Reserved bytes
113  * @c reserved_buf_ (offset 232), @c reserved_buf2_ (offset 236) and @c reserved_buf3_
114  * (offset 245) are wire-locked padding bytes. They travel through the wire format
115  * unchanged but MUST NOT be repurposed by application code because future library
116  * revisions may bind them to real fields. The @c uint8_t @c downsample_ at
117  * offset 244 records the voxel-grid downsampling level applied to the payload (see
118  * @c downsample()): @c 0 disables downsampling, @c 255 is the most aggressive.
119  *
120  * @par Optional storage compression
121  * The @c extent and @c vertical create options trade fidelity for footprint along
122  * two independent axes. When @c extent_ > 0 the three leading XYZ
123  * coordinates are linearly quantised with @c Quantize::encode<int16_t>(extent, value)
124  * and restored with @c Quantize::decode<float/double>(extent, stored), halving
125  * (or better) their in-memory footprint; the
126  * embedded schema is rewritten so the XYZ fields advertise @c kInt16Type and
127  * @c get_value_v3f() / @c get_value_v3d() transparently dequantise back to floating
128  * point. The generic @c get_value<int16_t>() returns the raw @c int16_t storage,
129  * whereas @c get_value<float>() / @c get_value<double>() also dequantise (matching
130  * @c v3f / @c v3d). Points whose XYZ fall outside @c (-extent, +extent) cannot be represented and are
131  * discarded on insert rather than saturated. When @c vertical_ is @c true the serialised wire
132  * payload is emitted in structure-of-arrays order (@c xx..x @c yy..y @c zz..z, one field column after
133  * another) which packs better under a downstream entropy coder; the in-memory
134  * buffer stays interleaved (row-major) and @c operator<< restores that layout, so
135  * the field accessors are unaffected.
136  *
137  * @par Optional voxel-grid downsampling
138  * @c downsample() collapses spatially-close points by mapping each point's quantised
139  * XYZ onto a voxel grid and keeping the first point seen in each voxel, compacting the
140  * survivors to the front of the buffer and shrinking @c size(). It applies only to
141  * quantised clouds (@c extent_ > 0) and records the requested level into the
142  * @c downsample_ marker, which @c get_downsample() reports. The marker is
143  * producer-set, not an automatically maintained invariant: freshly created clouds
144  * record @c 0, and @c clear() resets it to @c 0; it is NOT recomputed when points are
145  * subsequently added or modified.
146  *
147  * @par Example -- typical fill and publish
148  * @code
149  * vlink::zerocopy::PointCloud pc;
150  * pc.create_v3f<float>(1000, {"intensity"}); // schema: x, y, z, intensity (all f32)
151  *
152  * pc.push_value_v3f(1.0F, 2.0F, 3.0F, 0.8F);
153  * pc.push_value_v3f(4.0F, 5.0F, 6.0F, 0.5F);
154  *
155  * auto key_map = pc.get_key_map();
156  * float intensity = pc.get_value<float>(0, key_map, "intensity");
157  *
158  * vlink::Bytes wire;
159  * pc >> wire;
160  *
161  * vlink::zerocopy::PointCloud rx;
162  * rx << wire;
163  * @endcode
164  */
165 
166 #pragma once
167 
168 #include <cstdint>
169 #include <iostream>
170 #include <string>
171 #include <string_view>
172 #include <type_traits>
173 #include <unordered_map>
174 #include <vector>
175 
176 #include "../base/bytes.h"
177 #include "../base/quantize.h"
178 #include "./header.h"
179 
180 namespace vlink {
181 
182 namespace zerocopy {
183 
184 /**
185  * @struct PointCloud
186  * @brief 256-byte POD container holding a schema-described array of N-field point records.
187  *
188  * @details
189  * The struct size is locked at 256 bytes on 64-bit targets via
190  * @c static_assert. Points are stored in tightly packed row-major order; one
191  * row equals @c pack_size() bytes. Schemas are inspected by calling
192  * @c get_key_map() / @c get_key_list() and field values read through the
193  * @c get_value family of templates.
194  */
195 struct VLINK_EXPORT_AND_ALIGNED(8) PointCloud final {
196  /**
197  * @brief Fundamental field type tag used by the embedded @c Protocol.
198  */
199  enum Type : uint8_t {
200  kUnknownType = 0, ///< Uninitialised / unsupported type.
201  kBoolType = 1, ///< @c bool (1 byte).
202  kInt8Type = 2, ///< @c int8_t (1 byte).
203  kUint8Type = 3, ///< @c uint8_t (1 byte).
204  kInt16Type = 4, ///< @c int16_t (2 bytes).
205  kUint16Type = 5, ///< @c uint16_t (2 bytes).
206  kInt32Type = 6, ///< @c int32_t (4 bytes).
207  kUint32Type = 7, ///< @c uint32_t (4 bytes).
208  kInt64Type = 8, ///< @c int64_t (8 bytes).
209  kUint64Type = 9, ///< @c uint64_t (8 bytes).
210  kFloatType = 10, ///< @c float (4 bytes).
211  kDoubleType = 11, ///< @c double (8 bytes).
212  };
213 
214  /**
215  * @struct Key
216  * @brief Descriptor for one named field within a packed point record.
217  */
218  struct Key final {
219  std::string name; ///< Field name (e.g. @c "x", @c "intensity").
220  uint8_t type{kUnknownType}; ///< @c Type tag for the field.
221  uint8_t size{0}; ///< Field byte size derived from @c type.
222  };
223 
224  /**
225  * @brief Maps a field name to its byte offset inside one packed point record.
226  */
227  using KeyMap = std::unordered_map<std::string, uint16_t>;
228 
229  /**
230  * @brief Ordered list of field descriptors for one packed point record.
231  */
232  using KeyList = std::vector<Key>;
233 
234  /**
235  * @struct Vector3f
236  * @brief Compact single-precision XYZ vector (12 bytes, 4-byte aligned).
237  */
238  struct VLINK_EXPORT_AND_ALIGNED(4) Vector3f final {
239  float x{0}; ///< X component.
240  float y{0}; ///< Y component.
241  float z{0}; ///< Z component.
242 
243  /**
244  * @brief Default-constructs a zeroed vector and asserts the 12-byte contract.
245  */
246  Vector3f() noexcept;
247 
248  /**
249  * @brief Value constructor.
250  *
251  * @param _x X component.
252  * @param _y Y component.
253  * @param _z Z component.
254  */
255  explicit Vector3f(float _x, float _y, float _z) noexcept;
256 
257  /**
258  * @brief Formats the vector as @c "(x, y, z)" on @p ostream.
259  *
260  * @param ostream Output stream.
261  * @param v3f Vector to format.
262  * @return Reference to @p ostream.
263  */
264  friend std::ostream& operator<<(std::ostream& ostream, const Vector3f& v3f) noexcept;
265  };
266 
267  /**
268  * @struct Vector3d
269  * @brief Compact double-precision XYZ vector (24 bytes, 8-byte aligned).
270  */
271  struct VLINK_EXPORT_AND_ALIGNED(8) Vector3d final {
272  double x{0}; ///< X component.
273  double y{0}; ///< Y component.
274  double z{0}; ///< Z component.
275 
276  /**
277  * @brief Default-constructs a zeroed vector and asserts the 24-byte contract.
278  */
279  Vector3d() noexcept;
280 
281  /**
282  * @brief Value constructor.
283  *
284  * @param _x X component.
285  * @param _y Y component.
286  * @param _z Z component.
287  */
288  explicit Vector3d(double _x, double _y, double _z) noexcept;
289 
290  /**
291  * @brief Formats the vector as @c "(x, y, z)" on @p ostream.
292  *
293  * @param ostream Output stream.
294  * @param v3d Vector to format.
295  * @return Reference to @p ostream.
296  */
297  friend std::ostream& operator<<(std::ostream& ostream, const Vector3d& v3d) noexcept;
298  };
299 
300  /**
301  * @brief Default-constructs an empty cloud and asserts the 256-byte contract.
302  */
303  PointCloud() noexcept;
304 
305  /**
306  * @brief Frees the owned point buffer when @c is_owner() is @c true.
307  */
308  ~PointCloud() noexcept;
309 
310  /**
311  * @brief Deep-copies @p target into a freshly allocated cloud.
312  *
313  * @param target Source cloud to clone.
314  */
315  PointCloud(const PointCloud& target) noexcept;
316 
317  /**
318  * @brief Steals @p target's allocation and metadata; @p target ends empty.
319  *
320  * @param target Source cloud moved from.
321  */
322  PointCloud(PointCloud&& target) noexcept;
323 
324  /**
325  * @brief Deep-copy-assigns @p target; self-assignment is a no-op.
326  *
327  * @param target Source cloud to clone.
328  * @return Reference to @c *this.
329  */
330  PointCloud& operator=(const PointCloud& target) noexcept;
331 
332  /**
333  * @brief Move-assigns @p target; self-assignment is a no-op.
334  *
335  * @param target Source cloud moved from.
336  * @return Reference to @c *this.
337  */
338  PointCloud& operator=(PointCloud&& target) noexcept;
339 
340  /**
341  * @brief Deserialises a @c PointCloud from @p bytes with zero-copy borrowing semantics.
342  *
343  * @param bytes Wire buffer produced by @c operator>>.
344  * @return @c true on success; @c false on magic mismatch or size mismatch.
345  */
346  bool operator<<(const Bytes& bytes) noexcept;
347 
348  /**
349  * @brief Serialises the struct snapshot plus point bytes into @p bytes.
350  *
351  * @param bytes Output buffer; resized automatically when its size differs from the serialized size.
352  * @return Always @c true.
353  */
354  bool operator>>(Bytes& bytes) const noexcept;
355 
356  /**
357  * @brief Validates that @p bytes carries a well-formed @c PointCloud envelope.
358  *
359  * @param bytes Wire buffer to inspect.
360  * @return @c true when both magic sentinels match and the minimum size holds.
361  */
362  [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
363 
364  /**
365  * @brief Whether the data pointer is non-null and both @c size() and @c pack_size() are positive.
366  *
367  * @return @c true when the cloud holds usable points.
368  */
369  [[nodiscard]] bool is_valid() const noexcept;
370 
371  /**
372  * @brief Borrows @p target's point buffer and protocol without copying.
373  *
374  * @param target Source cloud whose buffer must outlive @c *this.
375  * @return @c false on self-borrow, otherwise @c true.
376  */
377  bool shallow_copy(const PointCloud& target) noexcept;
378 
379  /**
380  * @brief Allocates (or reuses) an owned buffer and copies @p target's protocol and point bytes.
381  *
382  * @param target Source cloud to clone.
383  * @return @c false on self-copy, otherwise @c true.
384  */
385  bool deep_copy(const PointCloud& target) noexcept;
386 
387  /**
388  * @brief Transfers ownership from @p target; @p target ends empty.
389  *
390  * @param target Source cloud moved from.
391  * @return @c false on self-move, otherwise @c true.
392  */
393  bool move_copy(PointCloud& target) noexcept;
394 
395  /**
396  * @brief Total bytes that @c operator>> would write for this cloud.
397  *
398  * @return @c sizeof(magic_begin) + @c sizeof(version) + @c sizeof(PointCloud) + @c size() * @c pack_size() + @c
399  * sizeof(magic_end).
400  */
401  [[nodiscard]] size_t get_serialized_size() const noexcept;
402 
403  /**
404  * @brief Builds a @c KeyMap (and optional @c KeyList) from the embedded protocol descriptor.
405  *
406  * @param key_list Optional output; filled with ordered field descriptors when non-null.
407  * @return Map from field name to byte offset within one packed point.
408  */
409  [[nodiscard]] KeyMap get_key_map(KeyList* key_list = nullptr) const noexcept;
410 
411  /**
412  * @brief Number of points currently stored.
413  *
414  * @return Point count, or 0 when empty.
415  */
416  [[nodiscard]] size_t size() const noexcept;
417 
418  /**
419  * @brief Byte size of one packed point record (sum of field byte sizes).
420  *
421  * @return Stride in bytes, or 0 when no schema has been set.
422  */
423  [[nodiscard]] size_t pack_size() const noexcept;
424 
425  /**
426  * @brief Whether this cloud owns its point buffer.
427  *
428  * @return @c true when the destructor would free the buffer.
429  */
430  [[nodiscard]] bool is_owner() const noexcept;
431 
432  /**
433  * @brief Quantisation extent currently configured for XYZ (maximum absolute coordinate).
434  *
435  * @return Extent value; @c 0 when quantisation is disabled.
436  */
437  [[nodiscard]] uint16_t get_extent() const noexcept;
438 
439  /**
440  * @brief Whether serialisation emits the payload in vertical (structure-of-arrays) column order.
441  *
442  * @return @c true when vertical layout is enabled.
443  */
444  [[nodiscard]] bool get_vertical() const noexcept;
445 
446  /**
447  * @brief Sets whether serialisation emits the payload in vertical (structure-of-arrays) column order.
448  *
449  * @details
450  * This toggles only the wire payload layout used by @c operator>>. The owned or borrowed
451  * in-memory point buffer remains in row-major order, and the schema, extent, downsample marker,
452  * logical size, and point data are left unchanged.
453  *
454  * @param vertical @c true to serialise field columns contiguously; @c false for row-major payloads.
455  */
456  void set_vertical(bool vertical) noexcept;
457 
458  /**
459  * @brief Voxel-grid downsampling level recorded for this cloud (producer-set marker; see @c downsample()).
460  *
461  * @return Level value in @c [0, 255]; @c 0 when downsampling is disabled.
462  */
463  [[nodiscard]] uint8_t get_downsample() const noexcept;
464 
465  /**
466  * @brief Voxel-grid downsamples a quantised owned cloud in place.
467  *
468  * @details
469  * Keeps the first point per occupied voxel, compacts survivors to the front, and shrinks @c size().
470  * @p level controls voxel size in @c [0,255]; @c 0 is a no-op that clears the marker. Successful
471  * positive calls record @p level in @c downsample_, while rejected positive calls leave the marker
472  * unchanged. Only clouds created with @c extent > 0 and owned storage can be downsampled.
473  *
474  * @param level Downsampling aggressiveness in @c [0,255]; @c 0 disables downsampling.
475  * @return @c true on success or level-@c 0 no-op; @c false when downsampling is not applicable.
476  */
477  bool downsample(uint8_t level) noexcept;
478 
479  /**
480  * @brief Raw @c Protocol::size_num integer (nibbles = per-field byte sizes).
481  *
482  * @return Stored value.
483  */
484  [[nodiscard]] uint64_t get_protocol_size_num() const noexcept;
485 
486  /**
487  * @brief Raw @c Protocol::type_num integer (nibbles = per-field @c Type tags).
488  *
489  * @return Stored value.
490  */
491  [[nodiscard]] uint64_t get_protocol_type_num() const noexcept;
492 
493  /**
494  * @brief Comma-separated field sizes for diagnostic output (e.g. @c "4,4,4,1").
495  *
496  * @return Printable size string.
497  */
498  [[nodiscard]] std::string get_protocol_size_str() const noexcept;
499 
500  /**
501  * @brief Raw comma-separated field name string (e.g. @c "x,y,z,intensity").
502  *
503  * @return Copy of the embedded name string. Keep the encoded string below
504  * 152 bytes if string-returning helpers must round-trip.
505  */
506  [[nodiscard]] std::string get_protocol_name_str() const noexcept;
507 
508  /**
509  * @brief Comma-separated field types for diagnostic output (e.g. @c "float,float,float,uint8").
510  *
511  * @return Printable type string.
512  */
513  [[nodiscard]] std::string get_protocol_type_str() const noexcept;
514 
515  /**
516  * @brief Read-only pointer to the packed point buffer (one row per point).
517  *
518  * @return Pointer to the first point.
519  */
520  [[nodiscard]] const uint8_t* get_internal_data() const noexcept;
521 
522  /**
523  * @brief Pre-allocated point capacity (@c capacity_ / @c pack_size_).
524  *
525  * @return Reserved point count; 0 for borrowed / deserialised buffers.
526  */
527  [[nodiscard]] size_t get_reserved_size() const noexcept;
528 
529  /**
530  * @brief Reads the first three @c float fields of point @p loop_index as XYZ.
531  *
532  * @param x Output X value.
533  * @param y Output Y value.
534  * @param z Output Z value.
535  * @param loop_index Zero-based point index.
536  * @return @c false on out-of-range.
537  */
538  bool get_value_v3f(float& x, float& y, float& z, size_t loop_index) const noexcept;
539 
540  /**
541  * @brief Reads the first three @c float fields of point @p loop_index into a @c Vector3f.
542  *
543  * @param v3f Output vector.
544  * @param loop_index Zero-based point index.
545  * @return @c false on out-of-range.
546  */
547  bool get_value_v3f(Vector3f& v3f, size_t loop_index) const noexcept;
548 
549  /**
550  * @brief Returns the first three @c float fields of point @p loop_index as a @c Vector3f.
551  *
552  * @param loop_index Zero-based point index.
553  * @return Vector; zero-initialised on out-of-range.
554  */
555  [[nodiscard]] Vector3f get_value_v3f(size_t loop_index) const noexcept;
556 
557  /**
558  * @brief Reads the first three @c double fields of point @p loop_index as XYZ.
559  *
560  * @param x Output X value.
561  * @param y Output Y value.
562  * @param z Output Z value.
563  * @param loop_index Zero-based point index.
564  * @return @c false on out-of-range.
565  */
566  bool get_value_v3d(double& x, double& y, double& z, size_t loop_index) const noexcept;
567 
568  /**
569  * @brief Reads the first three @c double fields of point @p loop_index into a @c Vector3d.
570  *
571  * @param v3d Output vector.
572  * @param loop_index Zero-based point index.
573  * @return @c false on out-of-range.
574  */
575  bool get_value_v3d(Vector3d& v3d, size_t loop_index) const noexcept;
576 
577  /**
578  * @brief Returns the first three @c double fields of point @p loop_index as a @c Vector3d.
579  *
580  * @param loop_index Zero-based point index.
581  * @return Vector; zero-initialised on out-of-range.
582  */
583  [[nodiscard]] Vector3d get_value_v3d(size_t loop_index) const noexcept;
584 
585  /**
586  * @brief Reads a field at byte @p offset within point @p loop_index into @p t.
587  *
588  * @tparam T Field type; must be fundamental.
589  * @param t Output value.
590  * @param loop_index Zero-based point index.
591  * @param offset Byte offset within one packed record.
592  * @return @c false (and zeroes @p t) on out-of-bounds access.
593  */
594  template <typename T>
595  bool get_value(T& t, size_t loop_index, uint16_t offset) const noexcept;
596 
597  /**
598  * @brief Returns a field at byte @p offset within point @p loop_index by value.
599  *
600  * @tparam T Field type; must be fundamental.
601  * @param loop_index Zero-based point index.
602  * @param offset Byte offset within one packed record.
603  * @return Field value; zero-initialised on out-of-bounds access.
604  */
605  template <typename T>
606  [[nodiscard]] T get_value(size_t loop_index, uint16_t offset) const noexcept;
607 
608  /**
609  * @brief Reads a named field from point @p loop_index using a cached @c KeyMap.
610  *
611  * @tparam T Field type; must be fundamental.
612  * @param t Output value.
613  * @param loop_index Zero-based point index.
614  * @param key_map Map obtained from @c get_key_map().
615  * @param key NUL-terminated full field name to look up.
616  * @return @c false (and zeroes @p t) on unknown key or out-of-bounds access.
617  */
618  template <typename T>
619  bool get_value(T& t, size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept;
620 
621  /**
622  * @brief Returns a named field from point @p loop_index using a cached @c KeyMap.
623  *
624  * @tparam T Field type; must be fundamental.
625  * @param loop_index Zero-based point index.
626  * @param key_map Map obtained from @c get_key_map().
627  * @param key NUL-terminated full field name.
628  * @return Field value; zero-initialised on unknown key or out-of-bounds access.
629  */
630  template <typename T>
631  [[nodiscard]] T get_value(size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept;
632 
633  /**
634  * @brief Returns a field value cast to @c double, dispatched on the runtime @c Type tag.
635  *
636  * @param loop_index Zero-based point index.
637  * @param offset Byte offset within one packed record.
638  * @param type @c Type tag for this field.
639  * @return Field value as @c double, or 0 on unknown type.
640  */
641  [[nodiscard]] double get_value_for_double_float(size_t loop_index, uint16_t offset, uint8_t type) const noexcept;
642 
643  /**
644  * @brief Returns a named field value cast to @c double using a cached @c KeyMap.
645  *
646  * @param loop_index Zero-based point index.
647  * @param key_map Map obtained from @c get_key_map().
648  * @param key NUL-terminated full field name.
649  * @param type @c Type tag for this field.
650  * @return Field value as @c double, or 0 on unknown key / type.
651  */
652  [[nodiscard]] double get_value_for_double_float(size_t loop_index, KeyMap& key_map, std::string_view key,
653  uint8_t type) const noexcept;
654 
655  /**
656  * @brief Returns a field value formatted as a printable string.
657  *
658  * @param loop_index Zero-based point index.
659  * @param offset Byte offset within one packed record.
660  * @param type @c Type tag.
661  * @return Stringified value, or empty on unknown type.
662  */
663  [[nodiscard]] std::string get_value_for_print(size_t loop_index, uint16_t offset, uint8_t type) const noexcept;
664 
665  /**
666  * @brief Returns a named field value formatted as a printable string.
667  *
668  * @param loop_index Zero-based point index.
669  * @param key_map Map obtained from @c get_key_map().
670  * @param key NUL-terminated full field name.
671  * @param type @c Type tag.
672  * @return Stringified value; empty for unknown @p type.
673  */
674  [[nodiscard]] std::string get_value_for_print(size_t loop_index, KeyMap& key_map, std::string_view key,
675  uint8_t type) const noexcept;
676 
677  /**
678  * @brief Creates a cloud from pre-built protocol integers and a name string.
679  *
680  * @details
681  * Low-level entry point used when @p size_num and @p type_num are already
682  * known (e.g. reconstructed from a wire buffer). Prefer the type-safe
683  * @c create<T...>() template for new producers.
684  *
685  * @param size Maximum number of points to pre-allocate.
686  * @param size_num Protocol size encoding.
687  * @param type_num Protocol type encoding.
688  * @param key_str Comma-separated field names (3..16 fields, up to 152 bytes).
689  * @param extent Optional XYZ quantisation extent; @c 0 disables quantisation.
690  * @param vertical When @c true the serialised payload uses structure-of-arrays column order.
691  * @return @c false on protocol validation failure (including XYZ that is not @c float / @c double / @c int16_t when
692  * @p extent is non-zero).
693  */
694  bool create(size_t size, uint64_t size_num, uint64_t type_num, std::string_view key_str, uint16_t extent = 0,
695  bool vertical = false) noexcept;
696 
697  /**
698  * @brief Creates a cloud with a type-safe variadic field schema.
699  *
700  * @tparam T Field types; must all be fundamental. 3..16 types required.
701  * @param _size Maximum number of points.
702  * @param keys Field names in the same order as @c T... .
703  * @param extent Optional XYZ quantisation extent; @c 0 disables quantisation.
704  * @param vertical When @c true the serialised payload uses structure-of-arrays column order.
705  * @return @c false when key count is wrong or schema packing fails.
706  */
707  template <typename... T>
708  bool create(size_t _size, const std::vector<std::string>& keys = {}, uint16_t extent = 0,
709  bool vertical = false) noexcept;
710 
711  /**
712  * @brief Creates a cloud with @c float XYZ followed by additional variadic fields.
713  *
714  * @tparam T Types for additional fields beyond XYZ.
715  * @param _size Maximum number of points.
716  * @param keys Names for the additional fields (excluding x, y, z).
717  * @param extent Optional XYZ quantisation extent; @c 0 disables quantisation.
718  * @param vertical When @c true the serialised payload uses structure-of-arrays column order.
719  * @return @c false on schema packing failure.
720  */
721  template <typename... T>
722  bool create_v3f(size_t _size, const std::vector<std::string>& keys = {}, uint16_t extent = 0,
723  bool vertical = false) noexcept;
724 
725  /**
726  * @brief Creates a cloud with @c double XYZ followed by additional variadic fields.
727  *
728  * @tparam T Types for additional fields beyond XYZ.
729  * @param _size Maximum number of points.
730  * @param keys Names for the additional fields (excluding x, y, z).
731  * @param extent Optional XYZ quantisation extent; @c 0 disables quantisation.
732  * @param vertical When @c true the serialised payload uses structure-of-arrays column order.
733  * @return @c false on schema packing failure.
734  */
735  template <typename... T>
736  bool create_v3d(size_t _size, const std::vector<std::string>& keys = {}, uint16_t extent = 0,
737  bool vertical = false) noexcept;
738 
739  /**
740  * @brief Bulk-fills the cloud from a pre-packed external buffer.
741  *
742  * @param src_data Source buffer in packed row order; must be non-null.
743  * @param _size Number of points to copy; must be non-zero.
744  * @return @c false on invalid arguments or capacity overflow.
745  */
746  bool fill_packed_data(const uint8_t* src_data, size_t _size) noexcept;
747 
748  /**
749  * @brief Appends one point with the given field values (must match schema).
750  *
751  * @tparam T Field value types; must all be fundamental. 3..16 required.
752  * @param args Field values in schema order.
753  * @return @c false on capacity exhaustion or pack-size mismatch.
754  */
755  template <typename... T>
756  bool push_value(T... args) noexcept;
757 
758  /**
759  * @brief Appends one point with @c float XYZ followed by additional fields.
760  *
761  * @tparam T Types for additional fields beyond XYZ.
762  * @param x X coordinate.
763  * @param y Y coordinate.
764  * @param z Z coordinate.
765  * @param args Additional field values.
766  * @return @c false on capacity exhaustion, pack-size mismatch, or any XYZ coordinate outside
767  * @c (-extent, +extent) on a quantised cloud (the out-of-range point is discarded).
768  */
769  template <typename... T>
770  bool push_value_v3f(float x, float y, float z, T... args) noexcept;
771 
772  /**
773  * @brief Appends one point from a @c Vector3f plus additional fields.
774  *
775  * @tparam T Types for additional fields beyond XYZ.
776  * @param v3f XYZ coordinates.
777  * @param args Additional field values.
778  * @return @c false on capacity exhaustion, pack-size mismatch, or any XYZ coordinate outside
779  * @c (-extent, +extent) on a quantised cloud (the out-of-range point is discarded).
780  */
781  template <typename... T>
782  bool push_value_v3f(Vector3f v3f, T... args) noexcept;
783 
784  /**
785  * @brief Appends one point with @c double XYZ followed by additional fields.
786  *
787  * @tparam T Types for additional fields beyond XYZ.
788  * @param x X coordinate.
789  * @param y Y coordinate.
790  * @param z Z coordinate.
791  * @param args Additional field values.
792  * @return @c false on capacity exhaustion, pack-size mismatch, or any XYZ coordinate outside
793  * @c (-extent, +extent) on a quantised cloud (the out-of-range point is discarded).
794  */
795  template <typename... T>
796  bool push_value_v3d(double x, double y, double z, T... args) noexcept;
797 
798  /**
799  * @brief Appends one point from a @c Vector3d plus additional fields.
800  *
801  * @tparam T Types for additional fields beyond XYZ.
802  * @param v3d XYZ coordinates.
803  * @param args Additional field values.
804  * @return @c false on capacity exhaustion, pack-size mismatch, or any XYZ coordinate outside
805  * @c (-extent, +extent) on a quantised cloud (the out-of-range point is discarded).
806  */
807  template <typename... T>
808  bool push_value_v3d(Vector3d v3d, T... args) noexcept;
809 
810  /**
811  * @brief Sets the logical point count without reallocating.
812  *
813  * @details
814  * Updates the internal write cursor to @c size * @c pack_size_. Required
815  * before random overwrites via @c set_value().
816  *
817  * @param size New logical point count; must not exceed allocated capacity.
818  * @return @c false on capacity overflow or invalid state.
819  */
820  bool resize(size_t size) noexcept;
821 
822  /**
823  * @brief Overwrites the record at @p loop_index with new field values.
824  *
825  * @details
826  * Requires the logical size and internal write cursor to be consistent;
827  * call @c resize() (or finish a full @c push_value() sequence) first.
828  *
829  * @tparam T Field value types; must all be fundamental. 3..16 required.
830  * @param loop_index Zero-based point index to overwrite.
831  * @param args Field values in schema order.
832  * @return @c false on out-of-range, cursor inconsistency, or pack-size mismatch.
833  */
834  template <typename... T>
835  bool set_value(size_t loop_index, T... args) noexcept;
836 
837  /**
838  * @brief Overwrites point @p loop_index with @c float XYZ plus additional fields.
839  *
840  * @tparam T Types for additional fields beyond XYZ.
841  * @param loop_index Zero-based point index.
842  * @param x X coordinate.
843  * @param y Y coordinate.
844  * @param z Z coordinate.
845  * @param args Additional field values.
846  * @return @c false on failure, or when any XYZ coordinate is outside @c (-extent, +extent) on a
847  * quantised cloud (the out-of-range point is discarded).
848  */
849  template <typename... T>
850  bool set_value_v3f(size_t loop_index, float x, float y, float z, T... args) noexcept;
851 
852  /**
853  * @brief Overwrites point @p loop_index from a @c Vector3f plus additional fields.
854  *
855  * @tparam T Types for additional fields beyond XYZ.
856  * @param loop_index Zero-based point index.
857  * @param v3f XYZ coordinates.
858  * @param args Additional field values.
859  * @return @c false on failure, or when any XYZ coordinate is outside @c (-extent, +extent) on a
860  * quantised cloud (the out-of-range point is discarded).
861  */
862  template <typename... T>
863  bool set_value_v3f(size_t loop_index, Vector3f v3f, T... args) noexcept;
864 
865  /**
866  * @brief Overwrites point @p loop_index with @c double XYZ plus additional fields.
867  *
868  * @tparam T Types for additional fields beyond XYZ.
869  * @param loop_index Zero-based point index.
870  * @param x X coordinate.
871  * @param y Y coordinate.
872  * @param z Z coordinate.
873  * @param args Additional field values.
874  * @return @c false on failure, or when any XYZ coordinate is outside @c (-extent, +extent) on a
875  * quantised cloud (the out-of-range point is discarded).
876  */
877  template <typename... T>
878  bool set_value_v3d(size_t loop_index, double x, double y, double z, T... args) noexcept;
879 
880  /**
881  * @brief Overwrites point @p loop_index from a @c Vector3d plus additional fields.
882  *
883  * @tparam T Types for additional fields beyond XYZ.
884  * @param loop_index Zero-based point index.
885  * @param v3d XYZ coordinates.
886  * @param args Additional field values.
887  * @return @c false on failure, or when any XYZ coordinate is outside @c (-extent, +extent) on a
888  * quantised cloud (the out-of-range point is discarded).
889  */
890  template <typename... T>
891  bool set_value_v3d(size_t loop_index, Vector3d v3d, T... args) noexcept;
892 
893  /**
894  * @brief Resets the logical count, optionally releasing every resource.
895  *
896  * @details
897  * With @p force == @c false (default) only the write cursor and logical
898  * count are zeroed so the buffer can be refilled in place. With
899  * @p force == @c true the owned buffer is freed and every protocol /
900  * header field is cleared.
901  *
902  * @param force When @c true, free the buffer and reset all state.
903  */
904  void clear(bool force = false) noexcept;
905 
906  /**
907  * @brief Mutable reference to the first wire-locked reserved field (@c uint32_t at offset 232).
908  *
909  * @details
910  * Lets application code stash a small flag or minor sub-type id that travels through the
911  * wire format unchanged. It is part of the binary contract and MUST NOT be repurposed once
912  * a future library revision binds it to a real field.
913  *
914  * @return Reference to @c reserved_buf_.
915  */
916  uint32_t& get_reserved() noexcept { return reserved_buf_; }
917 
918  /**
919  * @brief Mutable reference to the second wire-locked reserved field (@c uint32_t at offset 236).
920  *
921  * @details
922  * Lets application code stash a small flag or minor sub-type id that travels through the
923  * wire format unchanged. It is part of the binary contract and MUST NOT be repurposed once
924  * a future library revision binds it to a real field.
925  *
926  * @return Reference to @c reserved_buf2_.
927  */
928  uint32_t& get_reserved2() noexcept { return reserved_buf2_; }
929 
930  /**
931  * @brief Mutable reference to the third wire-locked reserved field (@c uint8_t at offset 245).
932  *
933  * @details
934  * Lets application code stash a small flag or minor sub-type id that travels through the
935  * wire format unchanged. It is part of the binary contract and MUST NOT be repurposed once
936  * a future library revision binds it to a real field.
937  *
938  * @return Reference to @c reserved_buf3_.
939  */
940  uint8_t& get_reserved3() noexcept { return reserved_buf3_; }
941 
942  Header header; ///< Sequencing and timestamp metadata prefix.
943 
944  static constexpr bool kZerocopyTypes{true}; ///< Marker probed by the VLink type-trait machinery.
945 
946  private:
947  struct VLINK_EXPORT_AND_ALIGNED(8) Protocol final {
948  uint64_t size_num{0};
949  uint64_t type_num{0};
950  char names[152]{0};
951 
952  Protocol() noexcept = default;
953 
954  template <typename... T>
955  static uint64_t get_size_num() noexcept;
956 
957  template <typename... T>
958  static uint64_t get_type_num() noexcept;
959 
960  template <typename T>
961  static constexpr uint8_t get_type() noexcept;
962 
963  constexpr uint64_t get_pack_size() const noexcept;
964 
965  static bool check_valid(uint64_t _size_num, std::string_view _names) noexcept;
966 
967  static std::string get_names(const std::vector<std::string>& keys) noexcept;
968 
969  KeyList get_key_list() const noexcept;
970 
971  std::string get_size_for_print() const noexcept;
972 
973  std::string get_type_for_print() const noexcept;
974  };
975 
976  template <typename T>
977  static bool xyz_in_extent(T x, T y, T z, uint16_t extent) noexcept;
978 
979  bool compress_protocol_xyz() noexcept;
980 
981  Protocol protocol_;
982  uint8_t* data_{nullptr};
983  size_t capacity_{0};
984  size_t size_{0};
985  uint32_t reserved_buf_{0};
986  uint32_t reserved_buf2_{0};
987  uint16_t pack_size_{0};
988  uint16_t extent_{0};
989  uint8_t downsample_{0};
990  uint8_t reserved_buf3_{0};
991  bool vertical_{false};
992  bool is_owner_{false};
993  uint64_t index_{0};
994 
995  static constexpr uint32_t kMagicNumberBegin{0x98B7F16A};
996  static constexpr uint32_t kMagicNumberEnd{0x98B7F16F};
997 };
998 
999 ////////////////////////////////////////////////////////////////
1000 /// Details
1001 ////////////////////////////////////////////////////////////////
1002 
1003 template <typename T>
1004 inline bool PointCloud::get_value(T& t, size_t loop_index, uint16_t offset) const noexcept {
1005  static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
1006 
1007  if (extent_ != 0 && offset < sizeof(int16_t) * 3) {
1008  if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
1009  int16_t value = 0;
1010 
1011  size_t p = (loop_index * pack_size_) + offset;
1012 
1013  if VUNLIKELY (p + sizeof(value) > size_ * pack_size_) {
1014  t = 0;
1015  return false;
1016  }
1017 
1018  std::memcpy(&value, data_ + p, sizeof(value));
1019  t = Quantize::decode<T>(extent_, value);
1020 
1021  return true;
1022  }
1023  }
1024 
1025  size_t p = (loop_index * pack_size_) + offset;
1026 
1027  if VUNLIKELY (p + sizeof(T) > size_ * pack_size_) {
1028  std::memset(&t, 0, sizeof(T));
1029  return false;
1030  }
1031 
1032  std::memcpy(&t, data_ + p, sizeof(T));
1033 
1034  return true;
1035 }
1036 
1037 template <typename T>
1038 inline T PointCloud::get_value(size_t loop_index, uint16_t offset) const noexcept {
1039  static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
1040 
1041  T t;
1042 
1043  get_value(t, loop_index, offset);
1044 
1045  return t;
1046 }
1047 
1048 template <typename T>
1049 inline bool PointCloud::get_value(T& t, size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept {
1050  static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
1051 
1052  auto iter = key_map.find(key.data());
1053 
1054  if VUNLIKELY (iter == key_map.end()) {
1055  std::memset(&t, 0, sizeof(T));
1056  return false;
1057  }
1058 
1059  return get_value<T>(t, loop_index, iter->second);
1060 }
1061 
1062 template <typename T>
1063 inline T PointCloud::get_value(size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept {
1064  static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
1065 
1066  T t;
1067 
1068  get_value(t, loop_index, key_map, key);
1069 
1070  return t;
1071 }
1072 
1073 template <typename... T>
1074 inline bool PointCloud::create(size_t size, const std::vector<std::string>& keys, uint16_t extent,
1075  bool vertical) noexcept {
1076  static_assert((std::is_fundamental_v<T> && ...), "All types must be fundamental.");
1077 
1078  static_assert(sizeof...(T) >= 3 && sizeof...(T) <= 16, "The number of keys ranges is [3 ~ 16].");
1079 
1080  if VUNLIKELY (sizeof...(T) != keys.size()) {
1081  return false;
1082  }
1083 
1084  uint64_t size_num = Protocol::get_size_num<T...>();
1085 
1086  if VUNLIKELY (size_num == 0) {
1087  return false;
1088  }
1089 
1090  std::string key_str = Protocol::get_names(keys);
1091 
1092  if VUNLIKELY (key_str.empty()) {
1093  return false;
1094  }
1095 
1096  uint64_t type_num = Protocol::get_type_num<T...>();
1097 
1098  if VUNLIKELY (type_num == 0) {
1099  return false;
1100  }
1101 
1102  Protocol new_protocol{};
1103  new_protocol.size_num = size_num;
1104  std::memset(new_protocol.names, 0, sizeof(new_protocol.names));
1105  std::memcpy(new_protocol.names, key_str.c_str(), key_str.size());
1106  new_protocol.type_num = type_num;
1107 
1108  if (extent != 0) {
1109  PointCloud protocol_probe;
1110  protocol_probe.protocol_ = new_protocol;
1111 
1112  if VUNLIKELY (!protocol_probe.compress_protocol_xyz()) {
1113  return false;
1114  }
1115 
1116  new_protocol = protocol_probe.protocol_;
1117  }
1118 
1119  if (is_owner_ && data_ && capacity_ != 0) {
1120  Bytes::bytes_free(data_, capacity_);
1121  }
1122 
1123  data_ = nullptr;
1124  is_owner_ = false;
1125  capacity_ = 0;
1126  size_ = 0;
1127  index_ = 0;
1128 
1129  protocol_ = new_protocol;
1130  extent_ = extent;
1131  vertical_ = vertical;
1132  downsample_ = 0;
1133 
1134  pack_size_ = protocol_.get_pack_size();
1135  capacity_ = size * pack_size_;
1136 
1137  if VLIKELY (capacity_ != 0) {
1138  data_ = Bytes::bytes_malloc(capacity_);
1139  is_owner_ = true;
1140  }
1141 
1142  return true;
1143 }
1144 
1145 template <typename... T>
1146 inline bool PointCloud::create_v3f(size_t _size, const std::vector<std::string>& keys, uint16_t extent,
1147  bool vertical) noexcept {
1148  std::vector<std::string> target_keys{"x", "y", "z"};
1149 
1150  target_keys.insert(target_keys.end(), keys.begin(), keys.end());
1151 
1152  return create<float, float, float, T...>(_size, target_keys, extent, vertical);
1153 }
1154 
1155 template <typename... T>
1156 inline bool PointCloud::create_v3d(size_t _size, const std::vector<std::string>& keys, uint16_t extent,
1157  bool vertical) noexcept {
1158  std::vector<std::string> target_keys{"x", "y", "z"};
1159 
1160  target_keys.insert(target_keys.end(), keys.begin(), keys.end());
1161 
1162  return create<double, double, double, T...>(_size, target_keys, extent, vertical);
1163 }
1164 
1165 inline bool PointCloud::fill_packed_data(const uint8_t* src_data, size_t _size) noexcept {
1166  bool is_fill_success = false;
1167 
1168  if VUNLIKELY (_size == 0 || !src_data) {
1169  is_fill_success = false;
1170  } else if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1171  is_fill_success = false;
1172  } else if VUNLIKELY (_size * pack_size_ > capacity_) {
1173  is_fill_success = false;
1174  } else {
1175  std::memcpy(data_, src_data, _size * pack_size_);
1176  size_ = _size;
1177  index_ = _size * pack_size_;
1178  is_fill_success = true;
1179  }
1180 
1181  return is_fill_success;
1182 }
1183 
1184 template <typename... T>
1185 inline bool PointCloud::push_value(T... args) noexcept {
1186  static_assert((std::is_fundamental_v<T> && ...), "All types must be fundamental.");
1187 
1188  static_assert(sizeof...(T) >= 3 && sizeof...(T) <= 16, "The number of keys ranges is [3 ~ 16].");
1189 
1190  if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1191  return false;
1192  }
1193 
1194  if VUNLIKELY (size_ * pack_size_ >= capacity_) {
1195  return false;
1196  }
1197 
1198  constexpr size_t kTargetPackSize = (sizeof(T) + ...);
1199 
1200  if VUNLIKELY (kTargetPackSize != pack_size_) {
1201  return false;
1202  }
1203 
1204  auto* target_ptr = data_ + index_;
1205 
1206  (
1207  [&]() {
1208  std::memcpy(target_ptr, &args, sizeof(args));
1209  target_ptr += sizeof(args);
1210  }(),
1211  ...);
1212 
1213  index_ += pack_size_;
1214  ++size_;
1215 
1216  return true;
1217 }
1218 
1219 template <typename... T>
1220 inline bool PointCloud::push_value_v3f(float x, float y, float z, T... args) noexcept {
1221  if (extent_ != 0) {
1222  if VUNLIKELY (!xyz_in_extent(x, y, z, extent_)) {
1223  return false;
1224  }
1225 
1226  auto qx = Quantize::encode<int16_t>(extent_, x);
1227  auto qy = Quantize::encode<int16_t>(extent_, y);
1228  auto qz = Quantize::encode<int16_t>(extent_, z);
1229 
1230  return push_value(qx, qy, qz, args...);
1231  }
1232 
1233  return push_value(x, y, z, args...);
1234 }
1235 
1236 template <typename... T>
1237 inline bool PointCloud::push_value_v3f(Vector3f v3f, T... args) noexcept {
1238  return push_value_v3f(v3f.x, v3f.y, v3f.z, args...);
1239 }
1240 
1241 template <typename... T>
1242 inline bool PointCloud::push_value_v3d(double x, double y, double z, T... args) noexcept {
1243  if (extent_ != 0) {
1244  if VUNLIKELY (!xyz_in_extent(x, y, z, extent_)) {
1245  return false;
1246  }
1247 
1248  auto qx = Quantize::encode<int16_t>(extent_, x);
1249  auto qy = Quantize::encode<int16_t>(extent_, y);
1250  auto qz = Quantize::encode<int16_t>(extent_, z);
1251 
1252  return push_value(qx, qy, qz, args...);
1253  }
1254 
1255  return push_value(x, y, z, args...);
1256 }
1257 
1258 template <typename... T>
1259 inline bool PointCloud::push_value_v3d(Vector3d v3d, T... args) noexcept {
1260  return push_value_v3d(v3d.x, v3d.y, v3d.z, args...);
1261 }
1262 
1263 inline bool PointCloud::resize(size_t size) noexcept {
1264  if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1265  return false;
1266  }
1267 
1268  if VUNLIKELY (size * pack_size_ > capacity_) {
1269  return false;
1270  }
1271 
1272  size_ = size;
1273  index_ = size_ * pack_size_;
1274 
1275  return true;
1276 }
1277 
1278 template <typename... T>
1279 bool PointCloud::set_value(size_t loop_index, T... args) noexcept {
1280  static_assert((std::is_fundamental_v<T> && ...), "All types must be fundamental.");
1281 
1282  static_assert(sizeof...(T) >= 3 && sizeof...(T) <= 16, "The number of keys ranges is [3 ~ 16].");
1283 
1284  if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1285  return false;
1286  }
1287 
1288  if VUNLIKELY (loop_index >= size_ || size_ * pack_size_ > capacity_) {
1289  return false;
1290  }
1291 
1292  if VUNLIKELY (index_ != size_ * pack_size_) {
1293  std::cerr << "[PointCloud::set_value] Invalid buffer state: "
1294  << "The current buffer size does not match the capacity. "
1295  << "Please call resize(size) before using set_value()." << std::endl;
1296  return false;
1297  }
1298 
1299  constexpr size_t kTargetPackSize = (sizeof(T) + ...);
1300 
1301  if VUNLIKELY (kTargetPackSize != pack_size_) {
1302  return false;
1303  }
1304 
1305  auto* target_ptr = data_ + (loop_index * pack_size_);
1306 
1307  (
1308  [&]() {
1309  std::memcpy(target_ptr, &args, sizeof(args));
1310  target_ptr += sizeof(args);
1311  }(),
1312  ...);
1313 
1314  return true;
1315 }
1316 
1317 template <typename... T>
1318 inline bool PointCloud::set_value_v3f(size_t loop_index, float x, float y, float z, T... args) noexcept {
1319  if (extent_ != 0) {
1320  if VUNLIKELY (!xyz_in_extent(x, y, z, extent_)) {
1321  return false;
1322  }
1323 
1324  auto qx = Quantize::encode<int16_t>(extent_, x);
1325  auto qy = Quantize::encode<int16_t>(extent_, y);
1326  auto qz = Quantize::encode<int16_t>(extent_, z);
1327 
1328  return set_value(loop_index, qx, qy, qz, args...);
1329  }
1330 
1331  return set_value(loop_index, x, y, z, args...);
1332 }
1333 
1334 template <typename... T>
1335 inline bool PointCloud::set_value_v3f(size_t loop_index, Vector3f v3f, T... args) noexcept {
1336  return set_value_v3f(loop_index, v3f.x, v3f.y, v3f.z, args...);
1337 }
1338 
1339 template <typename... T>
1340 inline bool PointCloud::set_value_v3d(size_t loop_index, double x, double y, double z, T... args) noexcept {
1341  if (extent_ != 0) {
1342  if VUNLIKELY (!xyz_in_extent(x, y, z, extent_)) {
1343  return false;
1344  }
1345 
1346  auto qx = Quantize::encode<int16_t>(extent_, x);
1347  auto qy = Quantize::encode<int16_t>(extent_, y);
1348  auto qz = Quantize::encode<int16_t>(extent_, z);
1349 
1350  return set_value(loop_index, qx, qy, qz, args...);
1351  }
1352 
1353  return set_value(loop_index, x, y, z, args...);
1354 }
1355 
1356 template <typename... T>
1357 inline bool PointCloud::set_value_v3d(size_t loop_index, Vector3d v3d, T... args) noexcept {
1358  return set_value_v3d(loop_index, v3d.x, v3d.y, v3d.z, args...);
1359 }
1360 
1361 template <typename... T>
1362 inline uint64_t PointCloud::Protocol::get_size_num() noexcept {
1363  uint64_t target_num = 0;
1364 
1365  uint64_t key_shift = sizeof...(T) * 4;
1366 
1367  (
1368  [&](auto type) {
1369  key_shift -= 4;
1370  target_num |= (static_cast<uint64_t>(sizeof(type)) & 0xF) << key_shift;
1371  }(T{}),
1372  ...);
1373 
1374  return target_num;
1375 }
1376 
1377 template <typename... T>
1378 inline uint64_t PointCloud::Protocol::get_type_num() noexcept {
1379  uint64_t target_num = 0;
1380 
1381  uint64_t key_shift = sizeof...(T) * 4;
1382 
1383  (
1384  [&](auto type) {
1385  key_shift -= 4;
1386  target_num |= (static_cast<uint64_t>(get_type<decltype(type)>()) & 0xF) << key_shift;
1387  }(T{}),
1388  ...);
1389 
1390  return target_num;
1391 }
1392 
1393 template <typename T>
1394 inline constexpr uint8_t PointCloud::Protocol::get_type() noexcept {
1395  if constexpr (std::is_same_v<T, bool>) {
1396  return kBoolType;
1397  } else if constexpr (std::is_same_v<T, int8_t>) {
1398  return kInt8Type;
1399  } else if constexpr (std::is_same_v<T, uint8_t>) {
1400  return kUint8Type;
1401  } else if constexpr (std::is_same_v<T, int16_t>) {
1402  return kInt16Type;
1403  } else if constexpr (std::is_same_v<T, uint16_t>) {
1404  return kUint16Type;
1405  } else if constexpr (std::is_same_v<T, int32_t>) {
1406  return kInt32Type;
1407  } else if constexpr (std::is_same_v<T, uint32_t>) {
1408  return kUint32Type;
1409  } else if constexpr (std::is_same_v<T, int64_t>) {
1410  return kInt64Type;
1411  } else if constexpr (std::is_same_v<T, uint64_t>) {
1412  return kUint64Type;
1413  } else if constexpr (std::is_same_v<T, float>) {
1414  return kFloatType;
1415  } else if constexpr (std::is_same_v<T, double>) {
1416  return kDoubleType;
1417  } else {
1418  return kUnknownType;
1419  }
1420 }
1421 
1422 inline constexpr uint64_t PointCloud::Protocol::get_pack_size() const noexcept {
1423  int sum = 0;
1424 
1425  auto target_num = size_num;
1426 
1427  while (target_num > 0) {
1428  sum += target_num & 0xF;
1429  target_num >>= 4;
1430  }
1431 
1432  return sum;
1433 }
1434 
1435 template <typename T>
1436 inline bool PointCloud::xyz_in_extent(T x, T y, T z, uint16_t extent) noexcept {
1437  return x > -extent && x < extent && y > -extent && y < extent && z > -extent && z < extent;
1438 }
1439 
1440 } // namespace zerocopy
1441 
1442 } // namespace vlink
Fixed-size timestamp and sequencing prefix embedded by every VLink zero-copy container.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLIKELY(...)
Short alias for VLINK_LIKELY.
Definition: macros.h:284
40-byte timestamp / sequencing metadata prefix shared by all zero-copy containers.
Definition: point_cloud.h:195
256-byte POD container holding a schema-described array of N-field point records.