using System.Runtime.InteropServices;
using RobotNet10.Shared;
using RobotNet10.Shared.Sensor;
namespace RobotNet10.RobotApp.MarkerDetection;
///
/// Extension methods to convert C# sensor structs to marker_detection C-compatible structs
///
public static class MarkerDetectionConversionExtensions
{
///
/// Convert C# Header to md_header_t
/// NOTE: Caller must free FrameId using Marshal.FreeHGlobal
///
public static md_header_t ToMarkerDetectionHeader(this Header header)
{
var mdHeader = new md_header_t
{
seq = header.Seq,
stamp = header.Stamp.ToMarkerDetectionUnixTime(),
frame_id = IntPtr.Zero
};
// Marshal frame_id string to unmanaged memory
if (!string.IsNullOrEmpty(header.FrameId))
{
mdHeader.frame_id = Marshal.StringToHGlobalAnsi(header.FrameId);
}
return mdHeader;
}
///
/// Convert DateTime to md_unix_time_t (Unix epoch time)
///
public static md_unix_time_t ToMarkerDetectionUnixTime(this DateTime timestamp)
{
var utc = timestamp.Kind == DateTimeKind.Utc
? timestamp
: timestamp.ToUniversalTime();
long ticksSinceEpoch = utc.Ticks - DateTime.UnixEpoch.Ticks;
long totalNanoseconds = ticksSinceEpoch * 100; // 1 tick = 100 ns
uint sec = (uint)(totalNanoseconds / 1_000_000_000);
uint nsec = (uint)(totalNanoseconds % 1_000_000_000);
return new md_unix_time_t
{
sec = sec,
nsec = nsec
};
}
///
/// Convert C# LaserScan to md_laserscan_t
/// NOTE: Caller must free all allocated memory using FreeMarkerDetectionLaserScan
///
public static md_laserscan_t ToMarkerDetectionLaserScan(this LaserScan scan)
{
var mdScan = new md_laserscan_t
{
header = scan.Header.ToMarkerDetectionHeader(),
angle_min = (float)scan.AngleMin,
angle_max = (float)scan.AngleMax,
angle_increment = (float)scan.AngleIncrement,
time_increment = (float)scan.TimeIncrement,
scan_time = (float)scan.ScanTime,
range_min = (float)scan.RangeMin,
range_max = (float)scan.RangeMax,
ranges_length = (nuint)(scan.Ranges?.Length ?? 0),
intensities_length = (nuint)(scan.Intensities?.Length ?? 0)
};
// Allocate and copy ranges array with validation
if (scan.Ranges != null && scan.Ranges.Length > 0)
{
// Sanitize ranges: replace NaN/Infinity/negative with max range
var sanitizedRanges = new float[scan.Ranges.Length];
int invalidCount = 0;
for (int i = 0; i < scan.Ranges.Length; i++)
{
float range = (float)scan.Ranges[i];
if (float.IsNaN(range) || float.IsInfinity(range) || range < 0)
{
sanitizedRanges[i] = (float)scan.RangeMax;
invalidCount++;
}
else
{
sanitizedRanges[i] = range;
}
}
if (invalidCount > 0)
{
Console.WriteLine($"[MarkerDetection] Sanitized {invalidCount}/{scan.Ranges.Length} invalid laser scan ranges");
}
int rangesSize = sanitizedRanges.Length * sizeof(float);
mdScan.ranges = Marshal.AllocHGlobal(rangesSize);
Marshal.Copy(sanitizedRanges, 0, mdScan.ranges, sanitizedRanges.Length);
}
else
{
mdScan.ranges = IntPtr.Zero;
}
// Allocate and copy intensities array
if (scan.Intensities != null && scan.Intensities.Length > 0)
{
int intensitiesSize = scan.Intensities.Length * sizeof(float);
mdScan.intensities = Marshal.AllocHGlobal(intensitiesSize);
Marshal.Copy(scan.Intensities, 0, mdScan.intensities, scan.Intensities.Length);
}
else
{
mdScan.intensities = IntPtr.Zero;
}
return mdScan;
}
#region Memory Management
///
/// Free memory allocated for md_laserscan_t
///
public static void FreeMarkerDetectionLaserScan(ref md_laserscan_t scan)
{
if (scan.header.frame_id != IntPtr.Zero)
{
Marshal.FreeHGlobal(scan.header.frame_id);
scan.header.frame_id = IntPtr.Zero;
}
if (scan.ranges != IntPtr.Zero)
{
Marshal.FreeHGlobal(scan.ranges);
scan.ranges = IntPtr.Zero;
}
if (scan.intensities != IntPtr.Zero)
{
Marshal.FreeHGlobal(scan.intensities);
scan.intensities = IntPtr.Zero;
}
}
///
/// Free memory allocated for md_rectangle_marker_options_t
///
public static void FreeMarkerDetectionRectangleOptions(ref md_rectangle_marker_options_t options)
{
if (options.marker_frame_id != IntPtr.Zero)
{
Marshal.FreeHGlobal(options.marker_frame_id);
options.marker_frame_id = IntPtr.Zero;
}
}
///
/// Free memory allocated for md_segment_marker_options_t
///
public static void FreeMarkerDetectionSegmentOptions(ref md_segment_marker_options_t options)
{
if (options.marker_frame_id != IntPtr.Zero)
{
Marshal.FreeHGlobal(options.marker_frame_id);
options.marker_frame_id = IntPtr.Zero;
}
}
#endregion
}