diff --git a/RGB.NET.Core/Devices/AbstractRGBDevice.cs b/RGB.NET.Core/Devices/AbstractRGBDevice.cs index c12c931..8a50ae9 100644 --- a/RGB.NET.Core/Devices/AbstractRGBDevice.cs +++ b/RGB.NET.Core/Devices/AbstractRGBDevice.cs @@ -27,6 +27,14 @@ namespace RGB.NET.Core /// IRGBDeviceInfo IRGBDevice.DeviceInfo => DeviceInfo; + private Point _location = new Point(0, 0); + /// + public Point Location + { + get => _location; + set => SetProperty(ref _location, value); + } + private Size _size = Size.Invalid; /// public Size Size @@ -35,19 +43,22 @@ namespace RGB.NET.Core protected set { if (SetProperty(ref _size, value)) + { OnPropertyChanged(nameof(ActualSize)); + OnPropertyChanged(nameof(DeviceRectangle)); + } } } /// public Size ActualSize => Size * Scale; - private Point _location = new Point(0, 0); - /// - public Point Location + public Rectangle DeviceRectangle { - get => _location; - set => SetProperty(ref _location, value); + get + { + return new Rectangle(Location, new Rectangle(new Rectangle(Location, ActualSize).Rotate(Rotation)).Size); + } } private Scale _scale = new Scale(1); @@ -58,7 +69,24 @@ namespace RGB.NET.Core set { if (SetProperty(ref _scale, value)) + { OnPropertyChanged(nameof(ActualSize)); + OnPropertyChanged(nameof(DeviceRectangle)); + } + } + } + + private Rotation _rotation = new Rotation(0); + /// + public Rotation Rotation + { + get => _rotation; + set + { + if (SetProperty(ref _rotation, value)) + { + OnPropertyChanged(nameof(DeviceRectangle)); + } } } @@ -86,11 +114,11 @@ namespace RGB.NET.Core Led IRGBDevice.this[LedId ledId] => LedMapping.TryGetValue(ledId, out Led led) ? led : null; /// - Led IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.ActualLedRectangle.Contains(location)); + Led IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.LedRectangle.Contains(location)); /// IEnumerable IRGBDevice.this[Rectangle referenceRect, double minOverlayPercentage] - => LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.ActualLedRectangle) >= minOverlayPercentage); + => LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.LedRectangle) >= minOverlayPercentage); #endregion @@ -143,11 +171,21 @@ namespace RGB.NET.Core /// The to initialize. /// The representing the position of the to initialize. /// - protected virtual Led InitializeLed(LedId ledId, Rectangle ledRectangle) + [Obsolete("Use InitializeLed(LedId ledId, Point location, Size size) instead.")] + protected virtual Led InitializeLed(LedId ledId, Rectangle rectangle) => InitializeLed(ledId, rectangle.Location, rectangle.Size); + + /// + /// Initializes the with the specified id. + /// + /// The to initialize. + /// The location of the to initialize. + /// The size of the to initialize. + /// The initialized led. + protected virtual Led InitializeLed(LedId ledId, Point location, Size size) { if ((ledId == LedId.Invalid) || LedMapping.ContainsKey(ledId)) return null; - Led led = new Led(this, ledId, ledRectangle, CreateLedCustomData(ledId)); + Led led = new Led(this, ledId, location, size, CreateLedCustomData(ledId)); LedMapping.Add(ledId, led); return led; } @@ -190,11 +228,12 @@ namespace RGB.NET.Core if (Enum.TryParse(layoutLed.Id, true, out LedId ledId)) { if (!LedMapping.TryGetValue(ledId, out Led led) && createMissingLeds) - led = InitializeLed(ledId, new Rectangle()); + led = InitializeLed(ledId, new Point(), new Size()); if (led != null) { - led.LedRectangle = new Rectangle(new Point(layoutLed.X, layoutLed.Y), new Size(layoutLed.Width, layoutLed.Height)); + led.Location = new Point(layoutLed.X, layoutLed.Y); + led.Size = new Size(layoutLed.Width, layoutLed.Height); led.Shape = layoutLed.Shape; led.ShapeData = layoutLed.ShapeData; diff --git a/RGB.NET.Core/Devices/IRGBDevice.cs b/RGB.NET.Core/Devices/IRGBDevice.cs index 07d28ee..c64f2ef 100644 --- a/RGB.NET.Core/Devices/IRGBDevice.cs +++ b/RGB.NET.Core/Devices/IRGBDevice.cs @@ -32,12 +32,19 @@ namespace RGB.NET.Core /// Gets the actual (scaled and rotated) of the . /// Size ActualSize { get; } + + Rectangle DeviceRectangle { get; } /// /// Gets or sets the scale of the . /// Scale Scale { get; set; } + /// + /// Gets or sets the rotation of the . + /// + Rotation Rotation { get; set; } + /// /// Gets or sets the of the . /// diff --git a/RGB.NET.Core/Extensions/RectangleExtensions.cs b/RGB.NET.Core/Extensions/RectangleExtensions.cs index 7d2f85a..f16842e 100644 --- a/RGB.NET.Core/Extensions/RectangleExtensions.cs +++ b/RGB.NET.Core/Extensions/RectangleExtensions.cs @@ -110,6 +110,44 @@ namespace RGB.NET.Core public static bool Contains(this Rectangle rect, Rectangle rect2) => (rect.Location.X <= rect2.Location.X) && ((rect2.Location.X + rect2.Size.Width) <= (rect.Location.X + rect.Size.Width)) && (rect.Location.Y <= rect2.Location.Y) && ((rect2.Location.Y + rect2.Size.Height) <= (rect.Location.Y + rect.Size.Height)); + public static Point Translate(this Point point, double x = 0, double y = 0) => new Point(point.X + x, point.Y + y); + + public static Point Rotate(this Point point, Rotation rotation, Point origin = new Point()) + { + double sin = Math.Sin(rotation.Radians); + double cos = Math.Cos(rotation.Radians); + + point = new Point(point.X - origin.X, point.Y - origin.Y); + point = new Point((point.X * cos) - (point.Y * sin), (point.X * sin) + (point.Y * cos)); + return new Point(point.X + origin.X, point.Y + origin.Y); ; + } + + public static Rectangle Translate(this Rectangle rect, Point point) => rect.Translate(point.X, point.Y); + public static Rectangle Translate(this Rectangle rect, double x = 0, double y = 0) => new Rectangle(rect.Location.Translate(x, y), rect.Size); + + public static Point[] Rotate(this Rectangle rect, Rotation rotation, Point origin = new Point()) + { + Point[] points = { + rect.Location, // top left + new Point(rect.Location.X + rect.Size.Width, rect.Location.Y), // top right + new Point(rect.Location.X + rect.Size.Width, rect.Location.Y + rect.Size.Height), // bottom right + new Point(rect.Location.X, rect.Location.Y + rect.Size.Height), // bottom right + }; + + double sin = Math.Sin(rotation.Radians); + double cos = Math.Cos(rotation.Radians); + + for (int i = 0; i < points.Length; i++) + { + Point point = points[i]; + point = new Point(point.X - origin.X, point.Y - origin.Y); + point = new Point((point.X * cos) - (point.Y * sin), (point.X * sin) + (point.Y * cos)); + points[i] = new Point(point.X + origin.X, point.Y + origin.Y); + } + + return points; + } + #endregion } } diff --git a/RGB.NET.Core/Leds/Led.cs b/RGB.NET.Core/Leds/Led.cs index a52cc6d..d172269 100644 --- a/RGB.NET.Core/Leds/Led.cs +++ b/RGB.NET.Core/Leds/Led.cs @@ -45,29 +45,53 @@ namespace RGB.NET.Core set => SetProperty(ref _shapeData, value); } - private Rectangle _ledRectangle; - /// - /// Gets a rectangle representing the physical location of the relative to the . - /// - public Rectangle LedRectangle + public Point Location { get; set; } + + public Size Size { get; set; } + + public Point ActualLocation { - get => _ledRectangle; - set + get { - if (SetProperty(ref _ledRectangle, value)) + Point point = (Location * Device.Scale); + if (!Device.Rotation.Radians.EqualsInTolerance(0)) { - OnPropertyChanged(nameof(ActualLedRectangle)); - OnPropertyChanged(nameof(AbsoluteLedRectangle)); + Point deviceCenter = new Rectangle(Device.ActualSize).Center; + Point actualDeviceCenter = Device.DeviceRectangle.Center; + Point centerOffset = new Point(actualDeviceCenter.X - deviceCenter.X, actualDeviceCenter.Y - deviceCenter.Y); + point = point.Rotate(Device.Rotation, new Rectangle(Device.ActualSize).Center) + centerOffset; } + + return point; } } - public Rectangle ActualLedRectangle => new Rectangle(LedRectangle.Location * Device.Scale, LedRectangle.Size * Device.Scale); + public Size ActualSize => Size * Device.Scale; /// - /// Gets a rectangle representing the physical location of the on the . + /// Gets a rectangle representing the logical location of the relative to the . /// - public Rectangle AbsoluteLedRectangle => new Rectangle(ActualLedRectangle.Location + Device.Location, ActualLedRectangle.Size); + public Rectangle LedRectangle + { + get + { + Rectangle rect = new Rectangle(Location * Device.Scale, Size * Device.Scale); + if (!Device.Rotation.Radians.EqualsInTolerance(0)) + { + Point deviceCenter = new Rectangle(Device.ActualSize).Center; + Point actualDeviceCenter = Device.DeviceRectangle.Center; + Point centerOffset = new Point(actualDeviceCenter.X - deviceCenter.X, actualDeviceCenter.Y - deviceCenter.Y); + rect = new Rectangle(rect.Rotate(Device.Rotation, new Rectangle(Device.ActualSize).Center)).Translate(centerOffset); + } + + return rect; + } + } + + /// + /// Gets a rectangle representing the logical location of the on the . + /// + public Rectangle AbsoluteLedRectangle => LedRectangle.Translate(Device.Location); /// /// Indicates whether the is about to change it's color. @@ -149,13 +173,15 @@ namespace RGB.NET.Core /// /// The the is associated with. /// The of the . - /// The representing the physical location of the relative to the . + /// The physical location of the relative to the . + /// The size of the . /// The provider-specific data associated with this led. - internal Led(IRGBDevice device, LedId id, Rectangle ledRectangle, object customData = null) + internal Led(IRGBDevice device, LedId id, Point location, Size size, object customData = null) { this.Device = device; this.Id = id; - this.LedRectangle = ledRectangle; + this.Location = location; + this.Size = size; this.CustomData = customData; device.PropertyChanged += DevicePropertyChanged; @@ -171,10 +197,12 @@ namespace RGB.NET.Core { OnPropertyChanged(nameof(AbsoluteLedRectangle)); } - else if ((e.PropertyName == nameof(IRGBDevice.Scale))) + else if ((e.PropertyName == nameof(IRGBDevice.Scale)) || (e.PropertyName == nameof(IRGBDevice.Rotation))) { - OnPropertyChanged(nameof(ActualLedRectangle)); + OnPropertyChanged(nameof(LedRectangle)); OnPropertyChanged(nameof(AbsoluteLedRectangle)); + OnPropertyChanged(nameof(ActualLocation)); + OnPropertyChanged(nameof(ActualSize)); } } @@ -185,7 +213,7 @@ namespace RGB.NET.Core public override string ToString() => $"{Id} {Color}"; /// - /// Updates the to the requested . + /// Updates the to the requested . /// internal void Update() { @@ -199,7 +227,7 @@ namespace RGB.NET.Core } /// - /// Resets the back to default. + /// Resets the back to default. /// internal void Reset() { @@ -225,7 +253,7 @@ namespace RGB.NET.Core /// Converts a to a . /// /// The to convert. - public static implicit operator Rectangle(Led led) => led?.ActualLedRectangle ?? new Rectangle(); + public static implicit operator Rectangle(Led led) => led?.LedRectangle ?? new Rectangle(); #endregion } diff --git a/RGB.NET.Core/Positioning/Rectangle.cs b/RGB.NET.Core/Positioning/Rectangle.cs index 7bf5330..d3b1618 100644 --- a/RGB.NET.Core/Positioning/Rectangle.cs +++ b/RGB.NET.Core/Positioning/Rectangle.cs @@ -53,11 +53,18 @@ namespace RGB.NET.Core : this(new Point(x, y), new Size(width, height)) { } + /// + /// Initializes a new instance of the class using the (0,0) and the given . + /// + /// The size of of this . + public Rectangle(Size size) : this(new Point(), size) + { } + /// /// Initializes a new instance of the class using the given and . /// - /// - /// + /// The location of this of this . + /// The size of of this . public Rectangle(Point location, Size size) { this.Location = location; diff --git a/RGB.NET.Core/Positioning/Rotation.cs b/RGB.NET.Core/Positioning/Rotation.cs new file mode 100644 index 0000000..92e6c5b --- /dev/null +++ b/RGB.NET.Core/Positioning/Rotation.cs @@ -0,0 +1,54 @@ +using System; + +namespace RGB.NET.Core +{ + public struct Rotation + { + #region Constants + + private const double TWO_PI = Math.PI * 2.0; + private const double RADIANS_DEGREES_CONVERSION = 180.0 / Math.PI; + private const double DEGREES_RADIANS_CONVERSION = Math.PI / 180.0; + + #endregion + + #region Properties & Fields + + public double Degrees { get; } + public double Radians { get; } + + #endregion + + #region Constructors + + public Rotation(double degrees) + : this(degrees, degrees * DEGREES_RADIANS_CONVERSION) + { } + + private Rotation(double degrees, double radians) + { + this.Degrees = degrees % 360.0; + this.Radians = radians % TWO_PI; + } + + #endregion + + #region Methods + + public static Rotation FromDegrees(double degrees) => new Rotation(degrees); + public static Rotation FromRadians(double radians) => new Rotation(radians * RADIANS_DEGREES_CONVERSION, radians); + + public bool Equals(Rotation other) => Degrees.EqualsInTolerance(other.Degrees); + public override bool Equals(object obj) => obj is Rotation other && Equals(other); + public override int GetHashCode() => Degrees.GetHashCode(); + + #endregion + + #region Operators + + public static implicit operator Rotation(double rotation) => new Rotation(rotation); + public static implicit operator double(Rotation rotation) => rotation.Degrees; + + #endregion + } +} diff --git a/RGB.NET.Core/RGBSurface.cs b/RGB.NET.Core/RGBSurface.cs index 058eda5..7ba3183 100644 --- a/RGB.NET.Core/RGBSurface.cs +++ b/RGB.NET.Core/RGBSurface.cs @@ -161,10 +161,10 @@ namespace RGB.NET.Core Rectangle brushRectangle = new Rectangle(leds.Select(led => led.AbsoluteLedRectangle)); Point offset = new Point(-brushRectangle.Location.X, -brushRectangle.Location.Y); brushRectangle = brushRectangle.SetLocation(new Point(0, 0)); - brush.PerformRender(brushRectangle, leds.Select(x => new BrushRenderTarget(x, GetDeviceLedLocation(x, offset)))); + brush.PerformRender(brushRectangle, leds.Select(led => new BrushRenderTarget(led, led.AbsoluteLedRectangle.Translate(offset)))); break; case BrushCalculationMode.Absolute: - brush.PerformRender(SurfaceRectangle, leds.Select(x => new BrushRenderTarget(x, x.AbsoluteLedRectangle))); + brush.PerformRender(SurfaceRectangle, leds.Select(led => new BrushRenderTarget(led, led.AbsoluteLedRectangle))); break; default: throw new ArgumentException(); @@ -177,12 +177,6 @@ namespace RGB.NET.Core renders.Key.Led.Color = renders.Value; } - private Rectangle GetDeviceLedLocation(Led led, Point extraOffset) - { - Rectangle absoluteRectangle = led.AbsoluteLedRectangle; - return (absoluteRectangle.Location + extraOffset) + absoluteRectangle.Size; - } - /// /// Attaches the given . /// @@ -226,7 +220,7 @@ namespace RGB.NET.Core private void UpdateSurfaceRectangle() { - Rectangle devicesRectangle = new Rectangle(_devices.Select(d => new Rectangle(d.Location, d.ActualSize))); + Rectangle devicesRectangle = new Rectangle(_devices.Select(d => d.DeviceRectangle)); SurfaceRectangle = SurfaceRectangle.SetSize(new Size(devicesRectangle.Location.X + devicesRectangle.Size.Width, devicesRectangle.Location.Y + devicesRectangle.Size.Height)); } diff --git a/RGB.NET.Groups/Groups/RectangleLedGroup.cs b/RGB.NET.Groups/Groups/RectangleLedGroup.cs index 9958a0d..1b23c26 100644 --- a/RGB.NET.Groups/Groups/RectangleLedGroup.cs +++ b/RGB.NET.Groups/Groups/RectangleLedGroup.cs @@ -59,7 +59,7 @@ namespace RGB.NET.Groups /// (optional) The minimal percentage overlay a must have with the to be taken into the . (default: 0.5) /// (optional) Specifies whether this should be automatically attached or not. (default: true) public RectangleLedGroup(Led fromLed, Led toLed, double minOverlayPercentage = 0.5, bool autoAttach = true) - : this(new Rectangle(fromLed.ActualLedRectangle, toLed.ActualLedRectangle), minOverlayPercentage, autoAttach) + : this(new Rectangle(fromLed.LedRectangle, toLed.LedRectangle), minOverlayPercentage, autoAttach) { } /// @@ -105,7 +105,7 @@ namespace RGB.NET.Groups /// Gets a list containing all of this . /// /// The list containing all of this . - public override IEnumerable GetLeds() => _ledCache ??= RGBSurface.Instance.Leds.Where(x => x.AbsoluteLedRectangle.CalculateIntersectPercentage(Rectangle) >= MinOverlayPercentage).ToList(); + public override IEnumerable GetLeds() => _ledCache ??= RGBSurface.Instance.Leds.Where(led => led.AbsoluteLedRectangle.CalculateIntersectPercentage(Rectangle) >= MinOverlayPercentage).ToList(); private void InvalidateCache() => _ledCache = null;