VLink  2.0.0
A high-performance communication middleware
occupancy_grid.h
Go to the documentation of this file.
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 occupancy_grid.h
26  * @brief Zero-copy 2-D occupancy / cost-map grid container with typed cell storage.
27  *
28  * @details
29  * @c OccupancyGrid is the canonical 2-D map message for VLink autonomous-driving
30  * stacks: local costmaps, global occupancy, lane-level reachability, signed
31  * distance fields, log-odds buffers, etc. Each grid carries the cell buffer,
32  * the world-to-grid transform, value range, default value, occupied / free
33  * thresholds, a 40-byte @c Header for sequencing, and a map identifier so
34  * that multiple map types can share one topic.
35  *
36  * @par Cell-value semantics
37  * | @c CellType | C++ type | Bytes | Free | Occupied | Unknown |
38  * | -------------- | ---------- | ----- | ------ | -------- | ------------------------------- |
39  * | @c kCellInt8 | @c int8_t | 1 | 0 | 100 | -1 (ROS @c nav_msgs convention) |
40  * | @c kCellUint8 | @c uint8_t | 1 | 0 | 254 | 255 |
41  * | @c kCellUint16 | @c uint16_t| 2 | 0 | 65534 | 65535 |
42  * | @c kCellFloat32| @c float | 4 | <= ft | >= ot | NaN (ft / ot = thresholds) |
43  *
44  * @par Coordinate system
45  * @code
46  * +y
47  * ^
48  * | (origin_x, origin_y) lies at the bottom-left
49  * | corner of the cell at column 0 / row 0. The
50  * | grid is rotated by origin_yaw radians (REP-103)
51  * | around that origin.
52  * |
53  * +---->-->-->-->--> +x
54  * origin_x,origin_y each cell spans resolution metres
55  *
56  * world(x, y) = origin + R(origin_yaw) * (column, row) * resolution
57  * @endcode
58  *
59  * @par Wire format
60  * @c OccupancyGrid is POD; @c memcpy is the canonical serialiser. The
61  * @c sizeof value is locked by @c static_assert and forms a permanent contract:
62  * @c vlink::zerocopy::* containers offer NO forward and NO backward binary
63  * compatibility -- every field, including reserved bytes, is wire-locked.
64  * @code
65  * static_assert(sizeof(OccupancyGrid) == 152, "Sizeof must be 152 bytes.");
66  * @endcode
67  *
68  * @par Memory layout
69  * @code
70  * Offset Size Field
71  * ------ ---- --------------------------------
72  * 0 40 Header header
73  * 40 8 uint8_t* data_
74  * 48 8 size_t size_
75  * 56 8 uint64_t update_time_ns_
76  * 64 16 char map_id_[16]
77  * 80 4 uint32_t channel_
78  * 84 4 uint32_t freq_
79  * 88 4 uint32_t width_
80  * 92 4 uint32_t height_
81  * 96 4 uint32_t valid_cell_count_
82  * 100 4 float resolution_
83  * 104 4 float origin_x_
84  * 108 4 float origin_y_
85  * 112 4 float origin_z_
86  * 116 4 float origin_yaw_
87  * 120 4 float value_min_
88  * 124 4 float value_max_
89  * 128 4 int32_t default_value_
90  * 132 4 float occupied_threshold_
91  * 136 4 float free_threshold_
92  * 140 1 CellType cell_type_
93  * 141 1 bool is_owner_
94  * 142 2 uint16_t reserved_buf_
95  * 144 4 uint32_t reserved_buf2_
96  * 148 4 uint32_t reserved_buf3_
97  * ------ ---- --------------------------------
98  * Total 152 bytes (alignas 8)
99  *
100  * Wire envelope:
101  * [ magic_begin (4) | version (4) | OccupancyGrid (152) | cell bytes (width*height*cell_size) | magic_end (4) ]
102  * @endcode
103  *
104  * @par Reserved bytes
105  * @c reserved_buf_, @c reserved_buf2_, @c reserved_buf3_ are exposed through
106  * @c get_reserved* helpers and survive @c clear() and the copy / move helpers.
107  * These slots MUST NOT be repurposed by application code: future library
108  * revisions may bind them to real fields, silently breaking peers that abused
109  * the slot.
110  *
111  * @par Example
112  * @code
113  * vlink::zerocopy::OccupancyGrid og;
114  * og.set_width(400);
115  * og.set_height(400);
116  * og.set_resolution(0.05F);
117  * og.set_origin_x(-10.0F);
118  * og.set_origin_y(-10.0F);
119  * og.set_cell_type(vlink::zerocopy::OccupancyGrid::kCellInt8);
120  * og.set_default_value(-1);
121  * og.set_occupied_threshold(0.65F);
122  * og.set_free_threshold(0.20F);
123  * og.create(400 * 400);
124  *
125  * vlink::Bytes wire;
126  * og >> wire;
127  * @endcode
128  */
129 
130 #pragma once
131 
132 #include <cstdint>
133 #include <string_view>
134 
135 #include "../base/bytes.h"
136 #include "./header.h"
137 
138 namespace vlink {
139 
140 namespace zerocopy {
141 
142 /**
143  * @struct OccupancyGrid
144  * @brief 152-byte POD container holding a typed 2-D occupancy / cost grid plus pose metadata.
145  *
146  * @details
147  * Cells are stored in row-major order with a stride of @c cell_size() bytes.
148  * The struct size is locked at 152 bytes on 64-bit targets via
149  * @c static_assert. Copy semantics are deep; the move constructor / assignment
150  * transfer ownership without allocation.
151  */
152 struct VLINK_EXPORT_AND_ALIGNED(8) OccupancyGrid final {
153  /**
154  * @brief Per-cell storage type tag (drives @c cell_size()).
155  */
156  enum CellType : uint8_t {
157  kCellUnknown = 0, ///< Uninitialised / unspecified cell type.
158  kCellInt8 = 1, ///< Signed 8-bit cell (ROS @c nav_msgs/OccupancyGrid style).
159  kCellUint8 = 2, ///< Unsigned 8-bit cell (0..255 costmap / grayscale).
160  kCellUint16 = 3, ///< Unsigned 16-bit cell (high-resolution costmap).
161  kCellFloat32 = 4, ///< IEEE-754 single-precision cell (log-odds, probability, SDF).
162  };
163 
164  /**
165  * @brief Default-constructs an empty grid and asserts the 152-byte contract.
166  */
167  OccupancyGrid() noexcept;
168 
169  /**
170  * @brief Frees the owned cell buffer when @c is_owner() is @c true.
171  */
172  ~OccupancyGrid() noexcept;
173 
174  /**
175  * @brief Deep-copies @p target into a freshly allocated grid.
176  *
177  * @param target Source grid to clone.
178  */
179  OccupancyGrid(const OccupancyGrid& target) noexcept;
180 
181  /**
182  * @brief Steals @p target's allocation and metadata; @p target ends empty.
183  *
184  * @param target Source grid moved from.
185  */
186  OccupancyGrid(OccupancyGrid&& target) noexcept;
187 
188  /**
189  * @brief Deep-copy-assigns @p target; self-assignment is a no-op.
190  *
191  * @param target Source grid to clone.
192  * @return Reference to @c *this.
193  */
194  OccupancyGrid& operator=(const OccupancyGrid& target) noexcept;
195 
196  /**
197  * @brief Move-assigns @p target; self-assignment is a no-op.
198  *
199  * @param target Source grid moved from.
200  * @return Reference to @c *this.
201  */
202  OccupancyGrid& operator=(OccupancyGrid&& target) noexcept;
203 
204  /**
205  * @brief Deserialises an @c OccupancyGrid from @p bytes with zero-copy borrowing semantics.
206  *
207  * @param bytes Wire buffer previously produced by @c operator>>.
208  * @return @c true on success; @c false on magic mismatch or size mismatch.
209  */
210  bool operator<<(const Bytes& bytes) noexcept;
211 
212  /**
213  * @brief Serialises the struct snapshot plus cell bytes into @p bytes.
214  *
215  * @param bytes Output buffer; resized automatically when its size differs from the serialized size.
216  * @return Always @c true.
217  */
218  bool operator>>(Bytes& bytes) const noexcept;
219 
220  /**
221  * @brief Validates that @p bytes carries a well-formed @c OccupancyGrid envelope.
222  *
223  * @param bytes Wire buffer to inspect.
224  * @return @c true when both magic sentinels match and the minimum size holds.
225  */
226  [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
227 
228  /**
229  * @brief Total bytes that @c operator>> would write for this grid.
230  *
231  * @return @c sizeof(magic_begin) + @c sizeof(version) + @c sizeof(OccupancyGrid) + @c size() + @c sizeof(magic_end).
232  */
233  [[nodiscard]] size_t get_serialized_size() const noexcept;
234 
235  /**
236  * @brief Whether the cell buffer pointer is non-null and the byte size is positive.
237  *
238  * @return @c true when the grid holds usable cell data.
239  */
240  [[nodiscard]] bool is_valid() const noexcept;
241 
242  /**
243  * @brief Borrows @p target's cell buffer without copying.
244  *
245  * @param target Source grid whose buffer must outlive @c *this.
246  * @return @c false on self-borrow, otherwise @c true.
247  */
248  bool shallow_copy(const OccupancyGrid& target) noexcept;
249 
250  /**
251  * @brief Allocates (or reuses) an owned buffer and copies @p target's cells.
252  *
253  * @param target Source grid to clone.
254  * @return @c false on self-copy, otherwise @c true.
255  */
256  bool deep_copy(const OccupancyGrid& target) noexcept;
257 
258  /**
259  * @brief Transfers ownership from @p target; @p target ends empty.
260  *
261  * @param target Source grid moved from.
262  * @return @c false on self-move, otherwise @c true.
263  */
264  bool move_copy(OccupancyGrid& target) noexcept;
265 
266  /**
267  * @brief Allocates an uninitialised owned cell buffer of @p size bytes.
268  *
269  * @details
270  * The caller is responsible for keeping @p size consistent with
271  * @c width() * @c height() * @c cell_size().
272  *
273  * @param size Byte count; must be non-zero.
274  * @return @c false when @p size is zero, otherwise @c true.
275  */
276  bool create(size_t size) noexcept;
277 
278  /**
279  * @brief Releases the owned buffer (if any) and resets metadata except reserved fields.
280  */
281  void clear() noexcept;
282 
283  /**
284  * @brief Borrows an externally owned cell buffer without copying.
285  *
286  * @param data Non-null source pointer that must outlive @c *this.
287  * @param size Buffer length in bytes; must be non-zero.
288  * @return @c false on invalid arguments or unchanged pointer, otherwise @c true.
289  */
290  bool shallow_copy(uint8_t* data, size_t size) noexcept;
291 
292  /**
293  * @brief Copies @p size bytes from @p data into an owned cell buffer.
294  *
295  * @param data Non-null source pointer.
296  * @param size Number of bytes to copy; must be non-zero.
297  * @return @c false on invalid arguments or aliasing, otherwise @c true.
298  */
299  bool deep_copy(uint8_t* data, size_t size) noexcept;
300 
301  /**
302  * @brief Compatibility alias for @c deep_copy(uint8_t*, size_t).
303  *
304  * @param data Source pointer.
305  * @param size Number of bytes.
306  * @return Result of the delegated @c deep_copy call.
307  */
308  bool fill_data(uint8_t* data, size_t size) noexcept;
309 
310  /**
311  * @brief Producer-side timestamp recording when the map was last updated.
312  *
313  * @return Stored nanosecond timestamp.
314  */
315  [[nodiscard]] uint64_t update_time_ns() const noexcept;
316 
317  /**
318  * @brief Unique map identifier (e.g. @c "local", @c "global", @c "lane").
319  *
320  * @return Non-owning view into the embedded buffer.
321  */
322  [[nodiscard]] std::string_view map_id() const noexcept;
323 
324  /**
325  * @brief Sensor / producer channel identifier.
326  *
327  * @return Stored channel id.
328  */
329  [[nodiscard]] uint32_t channel() const noexcept;
330 
331  /**
332  * @brief Nominal publish frequency in Hz.
333  *
334  * @return Stored value.
335  */
336  [[nodiscard]] uint32_t freq() const noexcept;
337 
338  /**
339  * @brief Grid width in cells (columns).
340  *
341  * @return Stored width.
342  */
343  [[nodiscard]] uint32_t width() const noexcept;
344 
345  /**
346  * @brief Grid height in cells (rows).
347  *
348  * @return Stored height.
349  */
350  [[nodiscard]] uint32_t height() const noexcept;
351 
352  /**
353  * @brief Number of cells whose value differs from @c default_value() (producer-supplied hint).
354  *
355  * @return Stored count; zero means "not provided".
356  */
357  [[nodiscard]] uint32_t valid_cell_count() const noexcept;
358 
359  /**
360  * @brief Cell side length in metres.
361  *
362  * @return Stored resolution.
363  */
364  [[nodiscard]] float resolution() const noexcept;
365 
366  /**
367  * @brief World-frame X coordinate of the bottom-left grid corner.
368  *
369  * @return Stored origin X.
370  */
371  [[nodiscard]] float origin_x() const noexcept;
372 
373  /**
374  * @brief World-frame Y coordinate of the bottom-left grid corner.
375  *
376  * @return Stored origin Y.
377  */
378  [[nodiscard]] float origin_y() const noexcept;
379 
380  /**
381  * @brief World-frame Z coordinate of the 2-D plane.
382  *
383  * @return Stored origin Z.
384  */
385  [[nodiscard]] float origin_z() const noexcept;
386 
387  /**
388  * @brief Yaw rotation of the grid origin in radians (REP-103).
389  *
390  * @return Stored origin yaw.
391  */
392  [[nodiscard]] float origin_yaw() const noexcept;
393 
394  /**
395  * @brief Lower bound of cell values used for normalisation / visualisation.
396  *
397  * @return Stored minimum.
398  */
399  [[nodiscard]] float value_min() const noexcept;
400 
401  /**
402  * @brief Upper bound of cell values used for normalisation / visualisation.
403  *
404  * @return Stored maximum.
405  */
406  [[nodiscard]] float value_max() const noexcept;
407 
408  /**
409  * @brief Value used for unknown / uninitialised cells.
410  *
411  * @return Stored default value (e.g. -1 for ROS int8 occupancy).
412  */
413  [[nodiscard]] int32_t default_value() const noexcept;
414 
415  /**
416  * @brief Threshold above which a cell is considered occupied.
417  *
418  * @return Stored threshold.
419  */
420  [[nodiscard]] float occupied_threshold() const noexcept;
421 
422  /**
423  * @brief Threshold below which a cell is considered free.
424  *
425  * @return Stored threshold.
426  */
427  [[nodiscard]] float free_threshold() const noexcept;
428 
429  /**
430  * @brief Per-cell storage type tag.
431  *
432  * @return @c CellType enum value.
433  */
434  [[nodiscard]] CellType cell_type() const noexcept;
435 
436  /**
437  * @brief Byte size of one cell derived from @c cell_type().
438  *
439  * @return Cell stride in bytes (0 for @c kCellUnknown).
440  */
441  [[nodiscard]] uint8_t cell_size() const noexcept;
442 
443  /**
444  * @brief Read-only pointer to the cell bytes.
445  *
446  * @return Pointer to payload start.
447  */
448  [[nodiscard]] const uint8_t* data() const noexcept;
449 
450  /**
451  * @brief Cell buffer size in bytes.
452  *
453  * @return Byte count, or 0 when empty.
454  */
455  [[nodiscard]] size_t size() const noexcept;
456 
457  /**
458  * @brief Whether this grid owns its cell buffer.
459  *
460  * @return @c true when the destructor would free the buffer.
461  */
462  [[nodiscard]] bool is_owner() const noexcept;
463 
464  /**
465  * @brief Stores the producer-side map timestamp.
466  *
467  * @param update_time_ns Timestamp in nanoseconds.
468  */
469  void set_update_time_ns(uint64_t update_time_ns) noexcept;
470 
471  /**
472  * @brief Stores the unique map identifier (truncated to @c sizeof(map_id) - 1 bytes).
473  *
474  * @param map_id Identifier string.
475  */
476  void set_map_id(std::string_view map_id) noexcept;
477 
478  /**
479  * @brief Stores the sensor / producer channel identifier.
480  *
481  * @param channel Channel id.
482  */
483  void set_channel(uint32_t channel) noexcept;
484 
485  /**
486  * @brief Stores the nominal publish frequency.
487  *
488  * @param freq Frequency in Hz.
489  */
490  void set_freq(uint32_t freq) noexcept;
491 
492  /**
493  * @brief Stores the grid width.
494  *
495  * @param width Number of columns.
496  */
497  void set_width(uint32_t width) noexcept;
498 
499  /**
500  * @brief Stores the grid height.
501  *
502  * @param height Number of rows.
503  */
504  void set_height(uint32_t height) noexcept;
505 
506  /**
507  * @brief Stores the count of cells differing from the default value.
508  *
509  * @param valid_cell_count Producer-supplied hint.
510  */
511  void set_valid_cell_count(uint32_t valid_cell_count) noexcept;
512 
513  /**
514  * @brief Stores the cell resolution.
515  *
516  * @param resolution Metres per cell.
517  */
518  void set_resolution(float resolution) noexcept;
519 
520  /**
521  * @brief Stores the world-frame X origin.
522  *
523  * @param origin_x World X coordinate of the bottom-left corner.
524  */
525  void set_origin_x(float origin_x) noexcept;
526 
527  /**
528  * @brief Stores the world-frame Y origin.
529  *
530  * @param origin_y World Y coordinate of the bottom-left corner.
531  */
532  void set_origin_y(float origin_y) noexcept;
533 
534  /**
535  * @brief Stores the world-frame Z value of the 2-D plane.
536  *
537  * @param origin_z World Z coordinate of the plane.
538  */
539  void set_origin_z(float origin_z) noexcept;
540 
541  /**
542  * @brief Stores the yaw rotation of the grid origin.
543  *
544  * @param origin_yaw Rotation in radians (REP-103).
545  */
546  void set_origin_yaw(float origin_yaw) noexcept;
547 
548  /**
549  * @brief Stores the lower bound for cell values.
550  *
551  * @param value_min Stored minimum.
552  */
553  void set_value_min(float value_min) noexcept;
554 
555  /**
556  * @brief Stores the upper bound for cell values.
557  *
558  * @param value_max Stored maximum.
559  */
560  void set_value_max(float value_max) noexcept;
561 
562  /**
563  * @brief Stores the value used for unknown cells.
564  *
565  * @param default_value Default cell value.
566  */
567  void set_default_value(int32_t default_value) noexcept;
568 
569  /**
570  * @brief Stores the occupied threshold.
571  *
572  * @param occupied_threshold Stored threshold.
573  */
574  void set_occupied_threshold(float occupied_threshold) noexcept;
575 
576  /**
577  * @brief Stores the free threshold.
578  *
579  * @param free_threshold Stored threshold.
580  */
581  void set_free_threshold(float free_threshold) noexcept;
582 
583  /**
584  * @brief Stores the per-cell storage type tag.
585  *
586  * @param cell_type @c CellType enum value.
587  */
588  void set_cell_type(CellType cell_type) noexcept;
589 
590  /**
591  * @brief Returns the byte size of one cell of @p type.
592  *
593  * @param type Cell type tag.
594  * @return Cell stride in bytes (0 for @c kCellUnknown).
595  */
596  [[nodiscard]] static uint8_t cell_size_of(CellType type) noexcept;
597 
598  /**
599  * @brief Mutable accessor for the first wire-locked reserved field (@c uint16_t).
600  *
601  * @return Reference to @c reserved_buf_.
602  */
603  uint16_t& get_reserved() noexcept { return reserved_buf_; }
604 
605  /**
606  * @brief Mutable accessor for the second wire-locked reserved field (@c uint32_t).
607  *
608  * @return Reference to @c reserved_buf2_.
609  */
610  uint32_t& get_reserved2() noexcept { return reserved_buf2_; }
611 
612  /**
613  * @brief Mutable accessor for the third wire-locked reserved field (@c uint32_t).
614  *
615  * @return Reference to @c reserved_buf3_.
616  */
617  uint32_t& get_reserved3() noexcept { return reserved_buf3_; }
618 
619  Header header; ///< Sequencing and timestamp metadata prefix.
620 
621  static constexpr bool kZerocopyTypes{true}; ///< Marker probed by the VLink type-trait machinery.
622 
623  private:
624  uint8_t* data_{nullptr};
625  size_t size_{0};
626  uint64_t update_time_ns_{0};
627  char map_id_[16]{0};
628  uint32_t channel_{0};
629  uint32_t freq_{0};
630  uint32_t width_{0};
631  uint32_t height_{0};
632  uint32_t valid_cell_count_{0};
633  float resolution_{0};
634  float origin_x_{0};
635  float origin_y_{0};
636  float origin_z_{0};
637  float origin_yaw_{0};
638  float value_min_{0};
639  float value_max_{0};
640  int32_t default_value_{0};
641  float occupied_threshold_{0};
642  float free_threshold_{0};
643  CellType cell_type_{kCellUnknown};
644  bool is_owner_{false};
645  uint16_t reserved_buf_{0};
646  uint32_t reserved_buf2_{0};
647  uint32_t reserved_buf3_{0};
648 
649  static constexpr uint32_t kMagicNumberBegin{0x98B7F17A};
650  static constexpr uint32_t kMagicNumberEnd{0x98B7F17F};
651 };
652 
653 } // namespace zerocopy
654 
655 } // namespace vlink
Fixed-size timestamp and sequencing prefix embedded by every VLink zero-copy container.
40-byte timestamp / sequencing metadata prefix shared by all zero-copy containers.
152-byte POD container holding a typed 2-D occupancy / cost grid plus pose metadata.