xref: /aosp_15_r20/external/perfetto/ui/src/base/geom.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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