1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// This library provides interfaces and classes for handling 2D geometry 16// operations. 17 18/** 19 * Interface representing a point in 2D space. 20 */ 21export interface Point2D { 22 readonly x: number; 23 readonly y: number; 24} 25 26/** 27 * Class representing a 2D vector with methods for vector operations. 28 * 29 * Note: This class is immutable in TypeScript (not enforced at runtime). Any 30 * method that modifies the vector returns a new instance, leaving the original 31 * unchanged. 32 */ 33export class Vector2D implements Point2D { 34 readonly x: number; 35 readonly y: number; 36 37 constructor({x, y}: Point2D) { 38 this.x = x; 39 this.y = y; 40 } 41 42 /** 43 * Adds the given point to this vector and returns a new vector. 44 * 45 * @param point - The point to add. 46 * @returns A new Vector2D instance representing the result. 47 */ 48 add(point: Point2D): Vector2D { 49 return new Vector2D({x: this.x + point.x, y: this.y + point.y}); 50 } 51 52 /** 53 * Subtracts the given point from this vector and returns a new vector. 54 * 55 * @param point - The point to subtract. 56 * @returns A new Vector2D instance representing the result. 57 */ 58 sub(point: Point2D): Vector2D { 59 return new Vector2D({x: this.x - point.x, y: this.y - point.y}); 60 } 61 62 /** 63 * Scales this vector by the given scalar and returns a new vector. 64 * 65 * @param scalar - The scalar value to multiply the vector by. 66 * @returns A new Vector2D instance representing the scaled vector. 67 */ 68 scale(scalar: number): Vector2D { 69 return new Vector2D({x: this.x * scalar, y: this.y * scalar}); 70 } 71 72 /** 73 * Computes the Manhattan distance, which is the sum of the absolute values of 74 * the x and y components of the vector. This represents the distance 75 * travelled along axes at right angles (grid-based distance). 76 */ 77 get manhattanDistance(): number { 78 return Math.abs(this.x) + Math.abs(this.y); 79 } 80 81 /** 82 * Computes the Euclidean magnitude (or length) of the vector. This is the 83 * straight-line distance from the origin (0, 0) to the point (x, y) in 2D 84 * space. 85 */ 86 get magnitude(): number { 87 return Math.sqrt(this.x * this.x + this.y * this.y); 88 } 89} 90 91/** 92 * Interface representing the vertical bounds of an object (top and bottom). 93 */ 94export interface VerticalBounds { 95 readonly top: number; 96 readonly bottom: number; 97} 98 99/** 100 * Interface representing the horizontal bounds of an object (left and right). 101 */ 102export interface HorizontalBounds { 103 readonly left: number; 104 readonly right: number; 105} 106 107/** 108 * Interface combining vertical and horizontal bounds to describe a 2D bounding 109 * box. 110 */ 111export interface Bounds2D extends VerticalBounds, HorizontalBounds {} 112 113/** 114 * Interface representing the size of a 2D object. 115 */ 116export interface Size2D { 117 readonly width: number; 118 readonly height: number; 119} 120 121/** 122 * Class representing a 2D rectangle, implementing bounds and size interfaces. 123 */ 124export class Rect2D implements Bounds2D, Size2D { 125 readonly left: number; 126 readonly top: number; 127 readonly right: number; 128 readonly bottom: number; 129 readonly width: number; 130 readonly height: number; 131 132 constructor({left, top, right, bottom}: Bounds2D) { 133 this.left = left; 134 this.top = top; 135 this.right = right; 136 this.bottom = bottom; 137 this.width = right - left; 138 this.height = bottom - top; 139 } 140 141 /** 142 * Returns a new rectangle representing the intersection with another 143 * rectangle. 144 * 145 * @param bounds - The bounds of the other rectangle to intersect with. 146 * @returns A new Rect2D instance representing the intersected rectangle. 147 */ 148 intersect(bounds: Bounds2D): Rect2D { 149 return new Rect2D({ 150 top: Math.max(this.top, bounds.top), 151 left: Math.max(this.left, bounds.left), 152 bottom: Math.min(this.bottom, bounds.bottom), 153 right: Math.min(this.right, bounds.right), 154 }); 155 } 156 157 /** 158 * Expands the rectangle by the given amount on all sides and returns a new 159 * rectangle. 160 * 161 * @param amount - The amount to expand the rectangle by. 162 * @returns A new Rect2D instance representing the expanded rectangle. 163 */ 164 expand(amount: number): Rect2D { 165 return new Rect2D({ 166 top: this.top - amount, 167 left: this.left - amount, 168 bottom: this.bottom + amount, 169 right: this.right + amount, 170 }); 171 } 172 173 /** 174 * Reframes the rectangle by shifting its origin by the given point. 175 * 176 * @param point - The point by which to shift the origin. 177 * @returns A new Rect2D instance representing the reframed rectangle. 178 */ 179 reframe(point: Point2D): Rect2D { 180 return new Rect2D({ 181 left: this.left - point.x, 182 right: this.right - point.x, 183 top: this.top - point.y, 184 bottom: this.bottom - point.y, 185 }); 186 } 187 188 /** 189 * Checks if this rectangle fully contains another set of bounds. 190 * 191 * @param bounds - The bounds to check containment for. 192 * @returns True if this rectangle contains the given bounds, false otherwise. 193 */ 194 contains(bounds: Bounds2D): boolean { 195 return !( 196 bounds.top < this.top || 197 bounds.bottom > this.bottom || 198 bounds.left < this.left || 199 bounds.right > this.right 200 ); 201 } 202 203 /** 204 * Translates the rectangle by the given point and returns a new rectangle. 205 * 206 * @param point - The point by which to translate the rectangle. 207 * @returns A new Rect2D instance representing the translated rectangle. 208 */ 209 translate(point: Point2D): Rect2D { 210 return new Rect2D({ 211 top: this.top + point.y, 212 left: this.left + point.x, 213 bottom: this.bottom + point.y, 214 right: this.right + point.x, 215 }); 216 } 217} 218