001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.math.geometry; 006 007import static edu.wpi.first.units.Units.Meters; 008 009import edu.wpi.first.math.geometry.proto.Rectangle2dProto; 010import edu.wpi.first.math.geometry.struct.Rectangle2dStruct; 011import edu.wpi.first.units.measure.Distance; 012import edu.wpi.first.util.protobuf.ProtobufSerializable; 013import edu.wpi.first.util.struct.StructSerializable; 014import java.util.Objects; 015 016/** 017 * Represents a 2d rectangular space containing translational, rotational, and scaling components. 018 */ 019public class Rectangle2d implements ProtobufSerializable, StructSerializable { 020 private final Pose2d m_center; 021 private final double m_xWidth; 022 private final double m_yWidth; 023 024 /** 025 * Constructs a rectangle at the specified position with the specified width and height. 026 * 027 * @param center The position (translation and rotation) of the rectangle. 028 * @param xWidth The x size component of the rectangle, in unrotated coordinate frame. 029 * @param yWidth The y size component of the rectangle, in unrotated coordinate frame. 030 */ 031 public Rectangle2d(Pose2d center, double xWidth, double yWidth) { 032 if (xWidth < 0 || yWidth < 0) { 033 throw new IllegalArgumentException("Rectangle2d dimensions cannot be less than 0!"); 034 } 035 036 m_center = center; 037 m_xWidth = xWidth; 038 m_yWidth = yWidth; 039 } 040 041 /** 042 * Constructs a rectangle at the specified position with the specified width and height. The X and 043 * Y widths will be converted to and tracked as meters. 044 * 045 * @param center The position (translation and rotation) of the rectangle. 046 * @param xWidth The x size component of the rectangle, in unrotated coordinate frame. 047 * @param yWidth The y size component of the rectangle, in unrotated coordinate frame. 048 */ 049 public Rectangle2d(Pose2d center, Distance xWidth, Distance yWidth) { 050 this(center, xWidth.in(Meters), yWidth.in(Meters)); 051 } 052 053 /** 054 * Creates an unrotated rectangle from the given corners. The corners should be diagonally 055 * opposite of each other. 056 * 057 * @param cornerA The first corner of the rectangle. 058 * @param cornerB The second corner of the rectangle. 059 */ 060 public Rectangle2d(Translation2d cornerA, Translation2d cornerB) { 061 this( 062 new Pose2d(cornerA.plus(cornerB).div(2.0), Rotation2d.kZero), 063 Math.abs(cornerA.getX() - cornerB.getX()), 064 Math.abs(cornerA.getY() - cornerB.getY())); 065 } 066 067 /** 068 * Returns the center of the rectangle. 069 * 070 * @return The center of the rectangle. 071 */ 072 public Pose2d getCenter() { 073 return m_center; 074 } 075 076 /** 077 * Returns the rotational component of the rectangle. 078 * 079 * @return The rotational component of the rectangle. 080 */ 081 public Rotation2d getRotation() { 082 return m_center.getRotation(); 083 } 084 085 /** 086 * Returns the x size component of the rectangle. 087 * 088 * @return The x size component of the rectangle. 089 */ 090 public double getXWidth() { 091 return m_xWidth; 092 } 093 094 /** 095 * Returns the y size component of the rectangle. 096 * 097 * @return The y size component of the rectangle. 098 */ 099 public double getYWidth() { 100 return m_yWidth; 101 } 102 103 /** 104 * Returns the X size component of the rectangle in a measure. 105 * 106 * @return The x size component of the rectangle in a measure. 107 */ 108 public Distance getMeasureXWidth() { 109 return Meters.of(m_xWidth); 110 } 111 112 /** 113 * Returns the Y size component of the rectangle in a measure. 114 * 115 * @return The y size component of the rectangle in a measure. 116 */ 117 public Distance getMeasureYWidth() { 118 return Meters.of(m_yWidth); 119 } 120 121 /** 122 * Transforms the center of the rectangle and returns the new rectangle. 123 * 124 * @param other The transform to transform by. 125 * @return The transformed rectangle 126 */ 127 public Rectangle2d transformBy(Transform2d other) { 128 return new Rectangle2d(m_center.transformBy(other), m_xWidth, m_yWidth); 129 } 130 131 /** 132 * Rotates the center of the rectangle and returns the new rectangle. 133 * 134 * @param other The rotation to transform by. 135 * @return The rotated rectangle. 136 */ 137 public Rectangle2d rotateBy(Rotation2d other) { 138 return new Rectangle2d(m_center.rotateBy(other), m_xWidth, m_yWidth); 139 } 140 141 /** 142 * Checks if a point is intersected by the rectangle's perimeter. 143 * 144 * @param point The point to check. 145 * @return True, if the rectangle's perimeter intersects the point. 146 */ 147 public boolean intersects(Translation2d point) { 148 // Move the point into the rectangle's coordinate frame 149 point = point.minus(m_center.getTranslation()); 150 point = point.rotateBy(m_center.getRotation().unaryMinus()); 151 152 if (Math.abs(Math.abs(point.getX()) - m_xWidth / 2.0) <= 1E-9) { 153 // Point rests on left/right perimeter 154 return Math.abs(point.getY()) <= m_yWidth / 2.0; 155 } else if (Math.abs(Math.abs(point.getY()) - m_yWidth / 2.0) <= 1E-9) { 156 // Point rests on top/bottom perimeter 157 return Math.abs(point.getX()) <= m_xWidth / 2.0; 158 } 159 160 return false; 161 } 162 163 /** 164 * Checks if a point is contained within the rectangle. This is inclusive, if the point lies on 165 * the perimeter it will return true. 166 * 167 * @param point The point to check. 168 * @return True, if the rectangle contains the point or the perimeter intersects the point. 169 */ 170 public boolean contains(Translation2d point) { 171 // Rotate the point into the rectangle's coordinate frame 172 point = point.rotateAround(m_center.getTranslation(), m_center.getRotation().unaryMinus()); 173 174 // Check if within bounding box 175 return point.getX() >= (m_center.getX() - m_xWidth / 2.0) 176 && point.getX() <= (m_center.getX() + m_xWidth / 2.0) 177 && point.getY() >= (m_center.getY() - m_yWidth / 2.0) 178 && point.getY() <= (m_center.getY() + m_yWidth / 2.0); 179 } 180 181 /** 182 * Returns the distance between the perimeter of the rectangle and the point. 183 * 184 * @param point The point to check. 185 * @return The distance (0, if the point is contained by the rectangle) 186 */ 187 public double getDistance(Translation2d point) { 188 return nearest(point).getDistance(point); 189 } 190 191 /** 192 * Returns the distance between the perimeter of the rectangle and the point in a measure. 193 * 194 * @param point The point to check. 195 * @return The distance (0, if the point is contained by the rectangle) in a measure. 196 */ 197 public Distance getMeasureDistance(Translation2d point) { 198 return Meters.of(getDistance(point)); 199 } 200 201 /** 202 * Returns the nearest point that is contained within the rectangle. 203 * 204 * @param point The point that this will find the nearest point to. 205 * @return A new point that is nearest to {@code point} and contained in the rectangle. 206 */ 207 public Translation2d nearest(Translation2d point) { 208 // Check if already in rectangle 209 if (contains(point)) { 210 return point; 211 } 212 213 // Rotate the point by the inverse of the rectangle's rotation 214 point = point.rotateAround(m_center.getTranslation(), m_center.getRotation().unaryMinus()); 215 216 // Find nearest point 217 point = 218 new Translation2d( 219 Math.clamp( 220 point.getX(), m_center.getX() - m_xWidth / 2.0, m_center.getX() + m_xWidth / 2.0), 221 Math.clamp( 222 point.getY(), m_center.getY() - m_yWidth / 2.0, m_center.getY() + m_yWidth / 2.0)); 223 224 // Undo rotation 225 return point.rotateAround(m_center.getTranslation(), m_center.getRotation()); 226 } 227 228 @Override 229 public String toString() { 230 return String.format("Rectangle2d(center: %s, x: %.2f, y: %.2f)", m_center, m_xWidth, m_yWidth); 231 } 232 233 /** 234 * Checks equality between this Rectangle2d and another object. 235 * 236 * @param obj The other object. 237 * @return Whether the two objects are equal or not. 238 */ 239 @Override 240 public boolean equals(Object obj) { 241 if (obj instanceof Rectangle2d) { 242 return ((Rectangle2d) obj).getCenter().equals(m_center) 243 && ((Rectangle2d) obj).getXWidth() == m_xWidth 244 && ((Rectangle2d) obj).getYWidth() == m_yWidth; 245 } 246 return false; 247 } 248 249 @Override 250 public int hashCode() { 251 return Objects.hash(m_center, m_xWidth, m_yWidth); 252 } 253 254 /** Rectangle2d protobuf for serialization. */ 255 public static final Rectangle2dProto proto = new Rectangle2dProto(); 256 257 /** Rectangle2d struct for serialization. */ 258 public static final Rectangle2dStruct struct = new Rectangle2dStruct(); 259}