blob: da50cc5651d2ac3043f76380aa278795a6fb3c0c [file] [log] [blame]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This library provides interfaces and classes for handling 2D geometry
// operations.
/**
* Interface representing a point in 2D space.
*/
export interface Point2D {
readonly x: number;
readonly y: number;
}
/**
* Class representing a 2D vector with methods for vector operations.
*
* Note: This class is immutable in TypeScript (not enforced at runtime). Any
* method that modifies the vector returns a new instance, leaving the original
* unchanged.
*/
export class Vector2D implements Point2D {
readonly x: number;
readonly y: number;
constructor({x, y}: Point2D) {
this.x = x;
this.y = y;
}
/**
* Adds the given point to this vector and returns a new vector.
*
* @param point - The point to add.
* @returns A new Vector2D instance representing the result.
*/
add(point: Point2D): Vector2D {
return new Vector2D({x: this.x + point.x, y: this.y + point.y});
}
/**
* Subtracts the given point from this vector and returns a new vector.
*
* @param point - The point to subtract.
* @returns A new Vector2D instance representing the result.
*/
sub(point: Point2D): Vector2D {
return new Vector2D({x: this.x - point.x, y: this.y - point.y});
}
/**
* Scales this vector by the given scalar and returns a new vector.
*
* @param scalar - The scalar value to multiply the vector by.
* @returns A new Vector2D instance representing the scaled vector.
*/
scale(scalar: number): Vector2D {
return new Vector2D({x: this.x * scalar, y: this.y * scalar});
}
/**
* Computes the Manhattan distance, which is the sum of the absolute values of
* the x and y components of the vector. This represents the distance
* travelled along axes at right angles (grid-based distance).
*/
get manhattanDistance(): number {
return Math.abs(this.x) + Math.abs(this.y);
}
/**
* Computes the Euclidean magnitude (or length) of the vector. This is the
* straight-line distance from the origin (0, 0) to the point (x, y) in 2D
* space.
*/
get magnitude(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
}
/**
* Interface representing the vertical bounds of an object (top and bottom).
*/
export interface VerticalBounds {
readonly top: number;
readonly bottom: number;
}
/**
* Interface representing the horizontal bounds of an object (left and right).
*/
export interface HorizontalBounds {
readonly left: number;
readonly right: number;
}
/**
* Interface combining vertical and horizontal bounds to describe a 2D bounding
* box.
*/
export interface Bounds2D extends VerticalBounds, HorizontalBounds {}
/**
* Interface representing the size of a 2D object.
*/
export interface Size2D {
readonly width: number;
readonly height: number;
}
/**
* Class representing a 2D rectangle, implementing bounds and size interfaces.
*/
export class Rect2D implements Bounds2D, Size2D {
readonly left: number;
readonly top: number;
readonly right: number;
readonly bottom: number;
readonly width: number;
readonly height: number;
constructor({left, top, right, bottom}: Bounds2D) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
this.width = right - left;
this.height = bottom - top;
}
/**
* Returns a new rectangle representing the intersection with another
* rectangle.
*
* @param bounds - The bounds of the other rectangle to intersect with.
* @returns A new Rect2D instance representing the intersected rectangle.
*/
intersect(bounds: Bounds2D): Rect2D {
return new Rect2D({
top: Math.max(this.top, bounds.top),
left: Math.max(this.left, bounds.left),
bottom: Math.min(this.bottom, bounds.bottom),
right: Math.min(this.right, bounds.right),
});
}
/**
* Expands the rectangle by the given amount on all sides and returns a new
* rectangle.
*
* @param amount - The amount to expand the rectangle by.
* @returns A new Rect2D instance representing the expanded rectangle.
*/
expand(amount: number): Rect2D {
return new Rect2D({
top: this.top - amount,
left: this.left - amount,
bottom: this.bottom + amount,
right: this.right + amount,
});
}
/**
* Reframes the rectangle by shifting its origin by the given point.
*
* @param point - The point by which to shift the origin.
* @returns A new Rect2D instance representing the reframed rectangle.
*/
reframe(point: Point2D): Rect2D {
return new Rect2D({
left: this.left - point.x,
right: this.right - point.x,
top: this.top - point.y,
bottom: this.bottom - point.y,
});
}
/**
* Checks if this rectangle fully contains another set of bounds.
*
* @param bounds - The bounds to check containment for.
* @returns True if this rectangle contains the given bounds, false otherwise.
*/
contains(bounds: Bounds2D): boolean {
return !(
bounds.top < this.top ||
bounds.bottom > this.bottom ||
bounds.left < this.left ||
bounds.right > this.right
);
}
/**
* Translates the rectangle by the given point and returns a new rectangle.
*
* @param point - The point by which to translate the rectangle.
* @returns A new Rect2D instance representing the translated rectangle.
*/
translate(point: Point2D): Rect2D {
return new Rect2D({
top: this.top + point.y,
left: this.left + point.x,
bottom: this.bottom + point.y,
right: this.right + point.x,
});
}
}