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  }