github.com/richardwilkes/toolbox@v1.121.0/xmath/geom/rect.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 // Package geom provides geometry primitives. 11 package geom 12 13 import ( 14 "github.com/richardwilkes/toolbox/xmath" 15 ) 16 17 // Rect defines a rectangle. 18 type Rect[T xmath.Numeric] struct { 19 Point[T] `json:",inline"` 20 Size[T] `json:",inline"` 21 } 22 23 // NewRect creates a new Rect. 24 func NewRect[T xmath.Numeric](x, y, width, height T) Rect[T] { 25 return Rect[T]{Point: NewPoint[T](x, y), Size: NewSize[T](width, height)} 26 } 27 28 // ConvertRect converts a Rect of type F into one of type T. 29 func ConvertRect[T, F xmath.Numeric](r Rect[F]) Rect[T] { 30 return NewRect(T(r.X), T(r.Y), T(r.Width), T(r.Height)) 31 } 32 33 // Empty returns true if either the width or height is zero or less. 34 func (r Rect[T]) Empty() bool { 35 return r.Width <= 0 || r.Height <= 0 36 } 37 38 // Center returns the center of the Rect. 39 func (r Rect[T]) Center() Point[T] { 40 return NewPoint(r.CenterX(), r.CenterY()) 41 } 42 43 // CenterX returns the center x-coordinate of the Rect. 44 func (r Rect[T]) CenterX() T { 45 return r.X + r.Width/2 46 } 47 48 // CenterY returns the center y-coordinate of the Rect. 49 func (r Rect[T]) CenterY() T { 50 return r.Y + r.Height/2 51 } 52 53 // Right returns the right edge, or X + Width. 54 func (r Rect[T]) Right() T { 55 return r.X + r.Width 56 } 57 58 // Bottom returns the bottom edge, or Y + Height. 59 func (r Rect[T]) Bottom() T { 60 return r.Y + r.Height 61 } 62 63 // TopLeft returns the top-left point of the Rect. 64 func (r Rect[T]) TopLeft() Point[T] { 65 return r.Point 66 } 67 68 // TopRight returns the top-right point of the Rect. 69 func (r Rect[T]) TopRight() Point[T] { 70 return NewPoint(r.Right(), r.Y) 71 } 72 73 // BottomRight returns the bottom-right point of the Rect. 74 func (r Rect[T]) BottomRight() Point[T] { 75 return NewPoint(r.Right(), r.Bottom()) 76 } 77 78 // BottomLeft returns the bottom-left point of the Rect. 79 func (r Rect[T]) BottomLeft() Point[T] { 80 return NewPoint(r.X, r.Bottom()) 81 } 82 83 // Contains returns true if this Rect fully contains the passed in Rect. 84 func (r Rect[T]) Contains(in Rect[T]) bool { 85 if r.Empty() || in.Empty() { 86 return false 87 } 88 right := r.Right() 89 bottom := r.Bottom() 90 inRight := in.Right() - 1 91 inBottom := in.Bottom() - 1 92 return r.X <= in.X && r.Y <= in.Y && in.X < right && in.Y < bottom && r.X <= inRight && 93 r.Y <= inBottom && inRight < right && inBottom < bottom 94 } 95 96 // IntersectsLine returns true if this rect and the line described by start and end intersect. 97 func (r Rect[T]) IntersectsLine(start, end Point[T]) bool { 98 if r.Empty() { 99 return false 100 } 101 if start.In(r) || end.In(r) { 102 return true 103 } 104 if len(LineIntersection[T](start, end, r.Point, r.TopRight())) != 0 { 105 return true 106 } 107 if len(LineIntersection[T](start, end, r.Point, r.BottomLeft())) != 0 { 108 return true 109 } 110 if len(LineIntersection[T](start, end, r.TopRight(), r.BottomRight())) != 0 { 111 return true 112 } 113 if len(LineIntersection[T](start, end, r.BottomLeft(), r.BottomRight())) != 0 { 114 return true 115 } 116 return false 117 } 118 119 // Intersects returns true if this Rect and the other Rect intersect. 120 func (r Rect[T]) Intersects(other Rect[T]) bool { 121 if r.Empty() || other.Empty() { 122 return false 123 } 124 return r.X < other.Right() && r.Y < other.Bottom() && r.Right() > other.X && r.Bottom() > other.Y 125 } 126 127 // Intersect returns the result of intersecting this Rect with another Rect. 128 func (r Rect[T]) Intersect(other Rect[T]) Rect[T] { 129 if r.Empty() || other.Empty() { 130 return Rect[T]{} 131 } 132 x := max(r.X, other.X) 133 y := max(r.Y, other.Y) 134 w := min(r.Right(), other.Right()) - x 135 h := min(r.Bottom(), other.Bottom()) - y 136 if w <= 0 || h <= 0 { 137 return Rect[T]{} 138 } 139 return NewRect(x, y, w, h) 140 } 141 142 // Union returns the result of unioning this Rect with another Rect. 143 func (r Rect[T]) Union(other Rect[T]) Rect[T] { 144 e1 := r.Empty() 145 e2 := other.Empty() 146 switch { 147 case e1 && e2: 148 return Rect[T]{} 149 case e1: 150 return other 151 case e2: 152 return r 153 default: 154 x := min(r.X, other.X) 155 y := min(r.Y, other.Y) 156 return NewRect(x, y, max(r.Right(), other.Right())-x, max(r.Bottom(), other.Bottom())-y) 157 } 158 } 159 160 // Align returns a new Rect aligned with integer coordinates that would encompass the original rectangle. 161 func (r Rect[T]) Align() Rect[T] { 162 return Rect[T]{Point: r.Point.Floor(), Size: r.Size.Ceil()} 163 } 164 165 // Expand returns a new Rect that expands this Rect to encompass the provided Point. If the Rect has a negative width or 166 // height, then the Rect's upper-left corner will be set to the Point and its width and height will be set to 0. 167 func (r Rect[T]) Expand(pt Point[T]) Rect[T] { 168 if r.Width < 0 || r.Height < 0 { 169 return Rect[T]{Point: pt} 170 } 171 x := min(r.X, pt.X) 172 y := min(r.Y, pt.Y) 173 return NewRect(x, y, max(r.Right(), pt.X)-x, max(r.Bottom(), pt.Y)-y) 174 } 175 176 // Inset returns a new Rect which has been inset by the specified Insets. 177 func (r Rect[T]) Inset(insets Insets[T]) Rect[T] { 178 return NewRect(r.X+insets.Left, r.Y+insets.Top, max(r.Width-insets.Width(), 0), max(r.Height-insets.Height(), 0)) 179 } 180 181 // String implements fmt.Stringer. 182 func (r Rect[T]) String() string { 183 return r.Point.String() + "," + r.Size.String() 184 }