using System; using System.Numerics; using Windows.Foundation; namespace Sonex.Client.Controls.SceneCanvas; public abstract class SceneCanvasObject { private Rect _boundsMeters; private double _rotationRadians; private string _id = CreateGuidId(); private int _layoutId; public string Id { get => _id; set { if (Guid.TryParse(value, out var parsed)) { _id = parsed.ToString("N"); return; } _id = CreateGuidId(); } } public bool IsVisible { get; set; } = true; public int LayoutId { get => _layoutId; set => _layoutId = value; } public int Layer { get => _layoutId; set => _layoutId = value; } public int ZIndex { get; set; } public bool CanMove { get; set; } = true; public bool CanResize { get; set; } public bool CanRotate { get; set; } public double PositionZMeters { get; set; } public double HeightMeters { get; set; } public Rect BoundsMeters { get => _boundsMeters; set => _boundsMeters = NormalizeRect(value); } public double RotationRadians { get => _rotationRadians; set => _rotationRadians = double.IsFinite(value) ? value : 0d; } public virtual Rect SpatialBoundsMeters => CalculateSpatialBounds(BoundsMeters, RotationRadians); internal int LastQueryToken { get; set; } protected SceneCanvasObject() { _boundsMeters = new Rect(0, 0, 0, 0); } protected SceneCanvasObject(Rect boundsMeters) { _boundsMeters = NormalizeRect(boundsMeters); } public virtual bool HitTest(Vector2 worldPointMeters, float toleranceMeters = 0.05f) { if (Math.Abs(RotationRadians) > 0.0001d) { var center = new Vector2( (float)(BoundsMeters.X + (BoundsMeters.Width * 0.5d)), (float)(BoundsMeters.Y + (BoundsMeters.Height * 0.5d))); worldPointMeters = RotatePoint(worldPointMeters, center, -RotationRadians); } var left = BoundsMeters.X - toleranceMeters; var right = BoundsMeters.X + BoundsMeters.Width + toleranceMeters; var top = BoundsMeters.Y - toleranceMeters; var bottom = BoundsMeters.Y + BoundsMeters.Height + toleranceMeters; return worldPointMeters.X >= left && worldPointMeters.X <= right && worldPointMeters.Y >= top && worldPointMeters.Y <= bottom; } // The drawing session is already transformed from world meters to screen space. public abstract void OnDraw(SceneCanvasDrawContext context); public abstract SceneCanvasObject Clone(); protected void CopyBasePropertiesTo(SceneCanvasObject target) { if (target == null) { return; } target.IsVisible = IsVisible; target.LayoutId = LayoutId; target.ZIndex = ZIndex; target.CanMove = CanMove; target.CanResize = CanResize; target.CanRotate = CanRotate; target.PositionZMeters = PositionZMeters; target.HeightMeters = HeightMeters; target.BoundsMeters = BoundsMeters; target.RotationRadians = RotationRadians; } private static Rect CalculateSpatialBounds(Rect bounds, double rotationRadians) { if (Math.Abs(rotationRadians) <= 0.0001d) { return bounds; } var center = new Vector2( (float)(bounds.X + (bounds.Width * 0.5d)), (float)(bounds.Y + (bounds.Height * 0.5d))); var c1 = RotatePoint(new Vector2((float)bounds.X, (float)bounds.Y), center, rotationRadians); var c2 = RotatePoint(new Vector2((float)(bounds.X + bounds.Width), (float)bounds.Y), center, rotationRadians); var c3 = RotatePoint(new Vector2((float)(bounds.X + bounds.Width), (float)(bounds.Y + bounds.Height)), center, rotationRadians); var c4 = RotatePoint(new Vector2((float)bounds.X, (float)(bounds.Y + bounds.Height)), center, rotationRadians); var minX = Math.Min(Math.Min(c1.X, c2.X), Math.Min(c3.X, c4.X)); var minY = Math.Min(Math.Min(c1.Y, c2.Y), Math.Min(c3.Y, c4.Y)); var maxX = Math.Max(Math.Max(c1.X, c2.X), Math.Max(c3.X, c4.X)); var maxY = Math.Max(Math.Max(c1.Y, c2.Y), Math.Max(c3.Y, c4.Y)); return new Rect(minX, minY, Math.Max(0d, maxX - minX), Math.Max(0d, maxY - minY)); } private static Vector2 RotatePoint(Vector2 point, Vector2 center, double angleRadians) { var translated = point - center; var sin = Math.Sin(angleRadians); var cos = Math.Cos(angleRadians); var rotated = new Vector2( (float)((translated.X * cos) - (translated.Y * sin)), (float)((translated.X * sin) + (translated.Y * cos))); return rotated + center; } private static Rect NormalizeRect(Rect rect) { var x = double.IsFinite(rect.X) ? rect.X : 0d; var y = double.IsFinite(rect.Y) ? rect.Y : 0d; var width = double.IsFinite(rect.Width) ? rect.Width : 0d; var height = double.IsFinite(rect.Height) ? rect.Height : 0d; if (width < 0d) { x += width; width = -width; } if (height < 0d) { y += height; height = -height; } return new Rect(x, y, width, height); } private static string CreateGuidId() { return Guid.NewGuid().ToString("N"); } }