using RobotNet10.Shared.Geometry; using RobotNet10.Shared.Numbers; using QuaternionGeometry = RobotNet10.Shared.Geometry.Quaternion; namespace RobotNet10.RobotApp.Client.Shared.SLAM; /// /// DTO cho Pose với covariance (3x3 matrix flattened to 9 values) /// public class PoseDto { public RobotNet10.Shared.Numbers.Vector3 Position { get; set; } = new(); public QuaternionGeometry Orientation { get; set; } = new(); /// /// Covariance matrix (3x3) flattened to 9 values: [xx, xy, xθ, yx, yy, yθ, θx, θy, θθ] /// null if covariance is not available /// public double[]? Covariance { get; set; } public double Score { get; set; } public DateTime Timestamp { get; set; } } /// /// DTO cho OccupancyGrid (optimized sparse format for SignalR transmission) /// Chỉ chứa các cell đã biết (known cells) và occupied cells để giảm bandwidth /// public class OccupancyGridDto { public double Resolution { get; set; } public int Width { get; set; } public int Height { get; set; } public Pose Origin { get; set; } = new(); /// /// Sparse format: chỉ chứa các cell đã biết (value >= 0) /// Format: byte[] với mỗi cell = 5 bytes: [index_byte0, index_byte1, index_byte2, index_byte3, value] /// Index: 4 bytes little-endian (32-bit unsigned integer, max 4,294,967,295) /// Value: 1 byte (0-100 = occupancy probability, 0 = free, 100 = occupied) /// Index được tính: y * Width + x (row-major order) /// public byte[] KnownCells { get; set; } = []; /// /// Version number để detect changes (increment mỗi khi grid thay đổi) /// public long Version { get; set; } /// /// Thời gian cuối cùng update grid base (từ CartographerService.LastUpdatedBase). /// public DateTime LastBaseUpdated { get; set; } /// /// Thời gian cuối cùng update OccupancyGridUpdating (từ CartographerService.LastUpdatedUpdating). /// public DateTime LastUpdated { get; set; } /// /// Trajectory nodes (từ pose graph) kèm theo grid; dùng để vẽ trajectory polyline. /// Có khi lấy grid base hoặc grid updating. /// public TrajectoryNodeDto[]? TrajectoryNodes { get; set; } } /// /// DTO cho TrajectoryNode /// public class TrajectoryNodeDto { public int NodeId { get; set; } public Pose Pose { get; set; } = new(); public DateTime Timestamp { get; set; } } /// /// DTO cho MapInfo /// public class MapInfoDto { public string Name { get; set; } = string.Empty; public DateTime CreatedDate { get; set; } public double Resolution { get; set; } /// /// Width of map in meters /// public double Width { get; set; } /// /// Height of map in meters /// public double Height { get; set; } public int TrajectoryNodeCount { get; set; } /// /// Origin X coordinate in meters /// public double OriginX { get; set; } /// /// Origin Y coordinate in meters /// public double OriginY { get; set; } /// /// Indicates if the map is currently being processed on the server /// public bool IsProcessing { get; set; } } /// /// DTO cho error information /// public class ErrorDto { public string Message { get; set; } = string.Empty; public DateTime Timestamp { get; set; } } /// /// Strategy for merging multiple submaps into a single occupancy grid. /// public enum SubmapMergeStrategyDto { /// /// Porter-Duff Source-Over compositing (Cairo-style). /// PorterDuff = 0, /// /// Sum log-odds from all submaps (Bayesian approach). /// LogOddsSum = 1, /// /// Take maximum probability (most pessimistic/conservative). /// MaxProbability = 2 } /// /// DTO cho OccupancyGridConfiguration - dùng để customize cách render occupancy grid /// public class OccupancyGridConfigurationDto { #region Merge Strategy /// /// Strategy for merging overlapping cells from multiple submaps. /// Default: LogOddsSum (clearer free/occupied distinction) /// public SubmapMergeStrategyDto MergeStrategy { get; set; } = SubmapMergeStrategyDto.LogOddsSum; /// /// Clamp log-odds to prevent extreme values from dominating. /// Range: [1, 20], Default: 10 /// public double LogOddsClamp { get; set; } = 10.0; /// /// When true, use average log-odds instead of sum. /// Default: true /// public bool UseLogOddsAverage { get; set; } = true; #endregion #region Threshold Configuration /// /// Threshold for classifying a cell as FREE. /// Higher value = stricter (fewer free cells). /// Range: [0, 255], Default: 100 /// public int FreeSpaceThreshold { get; set; } = 100; /// /// Threshold for classifying a cell as OCCUPIED. /// Higher value = stricter (fewer occupied cells, thinner walls). /// Range: [0, 255], Default: 0 /// public int OccupiedSpaceThreshold { get; set; } = 0; #endregion #region Output Mode /// /// When true, output only binary values (0=free, 100=occupied, -1=unknown). /// When false, output gradient values (0-100) based on probability. /// Default: true /// public bool UseBinaryOutput { get; set; } = true; #endregion #region Wall Thinning (Post-processing) /// /// Enable morphological erosion to thin walls in the occupancy grid. /// Default: false /// public bool EnableWallThinning { get; set; } = false; /// /// Number of erosion iterations for wall thinning. /// Range: [1, 5], Default: 1 /// public int WallThinningIterations { get; set; } = 1; /// /// Minimum wall thickness to preserve (in pixels) during wall thinning. /// Range: [1, 10], Default: 1 /// public int MinWallThicknessPixels { get; set; } = 1; #endregion #region Ambiguous Cell Handling /// /// How to handle ambiguous cells (probability ~0.5). /// Values: -1 = Unknown, 0 = Free, 100 = Occupied /// Default: -1 /// public sbyte AmbiguousCellValue { get; set; } = -1; /// /// Lower bound of the ambiguous range (probability). /// Default: 0.35 /// public double AmbiguousRangeLower { get; set; } = 0.35; /// /// Upper bound of the ambiguous range (probability). /// Default: 0.65 /// public double AmbiguousRangeUpper { get; set; } = 0.65; #endregion #region Advanced Options /// /// Apply median filter to reduce noise. /// Default: false /// public bool EnableMedianFilter { get; set; } = false; /// /// Kernel size for median filter (must be odd number). /// Range: [3, 7], Default: 3 /// public int MedianFilterKernelSize { get; set; } = 3; #endregion }