@ue-too/board
Version:
<h1 align="center"> uē-tôo </h1> <p align="center"> pan, zoom, rotate, and more with your html canvas. </p>
310 lines (309 loc) • 10.3 kB
TypeScript
import { Point } from "@ue-too/math";
/**
* Position boundaries for camera movement in world space.
* Allows optional constraints on x and y axes independently.
*
* @property min - Minimum position constraints (both x and y are optional)
* @property max - Maximum position constraints (both x and y are optional)
*
* @remarks
* All coordinates are in world space. Each axis (x, y) can be:
* - Fully constrained: both min and max defined
* - Partially constrained: only min or max defined
* - Unconstrained: neither min nor max defined
*
* This allows for flexible boundary configurations like:
* - Horizontal-only boundaries (x constrained, y free)
* - Vertical-only boundaries (y constrained, x free)
* - One-sided boundaries (e.g., minimum x but no maximum)
*
* @example
* ```typescript
* // Fully constrained rectangular boundary
* const rect: Boundaries = {
* min: { x: -1000, y: -1000 },
* max: { x: 1000, y: 1000 }
* };
*
* // Horizontal constraints only
* const horizontal: Boundaries = {
* min: { x: -500 },
* max: { x: 500 }
* };
*
* // One-sided constraint (can't go below y=0)
* const floor: Boundaries = {
* min: { y: 0 }
* };
* ```
*
* @category Camera
*/
export type Boundaries = {
min?: {
x?: number;
y?: number;
};
max?: {
x?: number;
y?: number;
};
};
/**
* Checks if a point is within the specified boundaries.
*
* @param point - Point to check in world coordinates
* @param boundaries - Optional boundary constraints
* @returns True if point is within boundaries or no boundaries specified, false otherwise
*
* @remarks
* Returns true if:
* - No boundaries are defined (undefined)
* - Point satisfies all defined constraints
*
* Each axis is checked independently. A missing constraint on an axis means
* that axis is unbounded.
*
* @example
* ```typescript
* const bounds: Boundaries = {
* min: { x: -100, y: -50 },
* max: { x: 100, y: 50 }
* };
*
* withinBoundaries({ x: 0, y: 0 }, bounds); // true (inside)
* withinBoundaries({ x: 150, y: 0 }, bounds); // false (x too large)
* withinBoundaries({ x: 0, y: -100 }, bounds); // false (y too small)
* withinBoundaries({ x: 100, y: 50 }, bounds); // true (on boundary)
* withinBoundaries({ x: 0, y: 0 }, undefined); // true (no bounds)
* ```
*
* @category Camera
*/
export declare function withinBoundaries(point: Point, boundaries: Boundaries | undefined): boolean;
/**
* Validates that boundaries are logically consistent.
*
* @param boundaries - The boundaries to validate
* @returns True if boundaries are valid or undefined, false if min >= max on any axis
*
* @remarks
* Returns false if:
* - On any axis, both min and max are defined AND min >= max
*
* Returns true if:
* - Boundaries are undefined
* - Only min or max is defined on an axis
* - Both are defined and min < max on all axes
*
* @example
* ```typescript
* isValidBoundaries({ min: { x: 0, y: 0 }, max: { x: 100, y: 100 } }); // true
* isValidBoundaries({ min: { x: 100 }, max: { x: 0 } }); // false (min > max)
* isValidBoundaries({ min: { x: 50, y: 50 }, max: { x: 50, y: 60 } }); // false (x min == max)
* isValidBoundaries({ min: { x: 0 } }); // true (partial)
* isValidBoundaries(undefined); // true
* ```
*
* @category Camera
*/
export declare function isValidBoundaries(boundaries: Boundaries | undefined): boolean;
/**
* Checks if boundaries have all four constraints (min/max for both x and y) defined.
*
* @param boundaries - The boundaries to check
* @returns True if all four constraints are defined, false otherwise
*
* @remarks
* Returns true only if boundaries define a complete rectangular region:
* - min.x, min.y, max.x, and max.y are all defined
*
* @example
* ```typescript
* boundariesFullyDefined({
* min: { x: 0, y: 0 },
* max: { x: 100, y: 100 }
* }); // true
*
* boundariesFullyDefined({
* min: { x: 0, y: 0 },
* max: { x: 100 } // missing max.y
* }); // false
*
* boundariesFullyDefined({ min: { x: 0 } }); // false
* boundariesFullyDefined(undefined); // false
* ```
*
* @category Camera
*/
export declare function boundariesFullyDefined(boundaries: Boundaries | undefined): boolean;
/**
* Clamps a point to stay within specified boundaries.
*
* @param point - Point to clamp in world coordinates
* @param boundaries - Optional boundary constraints
* @returns Clamped point, or original if already within bounds or no boundaries
*
* @remarks
* Each axis is clamped independently:
* - If a min constraint exists on an axis, ensures point >= min
* - If a max constraint exists on an axis, ensures point <= max
* - If no constraint exists on an axis, that axis is unchanged
*
* @example
* ```typescript
* const bounds: Boundaries = {
* min: { x: -100, y: -50 },
* max: { x: 100, y: 50 }
* };
*
* clampPoint({ x: 0, y: 0 }, bounds); // { x: 0, y: 0 } (inside)
* clampPoint({ x: 150, y: 0 }, bounds); // { x: 100, y: 0 } (clamped x)
* clampPoint({ x: 0, y: -100 }, bounds); // { x: 0, y: -50 } (clamped y)
* clampPoint({ x: 200, y: -200 }, bounds); // { x: 100, y: -50 } (both clamped)
* clampPoint({ x: 0, y: 0 }, undefined); // { x: 0, y: 0 } (no bounds)
* ```
*
* @category Camera
*/
export declare function clampPoint(point: Point, boundaries: Boundaries | undefined): Point;
/**
* Calculates the width (x-axis span) of the boundaries.
*
* @param boundaries - The boundaries to measure
* @returns Width in world units, or undefined if x boundaries are not fully defined
*
* @remarks
* Returns undefined if boundaries don't have both min.x and max.x defined.
* Result is always non-negative for valid boundaries (max.x - min.x).
*
* @example
* ```typescript
* translationWidthOf({
* min: { x: -100, y: -50 },
* max: { x: 100, y: 50 }
* }); // 200
*
* translationWidthOf({ min: { x: 0 } }); // undefined (no max.x)
* translationWidthOf(undefined); // undefined
* ```
*
* @category Camera
*/
export declare function translationWidthOf(boundaries: Boundaries | undefined): number | undefined;
/**
* Calculates half the width (x-axis half-span) of the boundaries.
*
* @param boundaries - The boundaries to measure
* @returns Half-width in world units, or undefined if x boundaries are not fully defined
*
* @remarks
* Useful for calculating radius or offset from center for x-axis.
* Equivalent to `translationWidthOf(boundaries) / 2`.
*
* @example
* ```typescript
* halfTranslationWidthOf({
* min: { x: -100, y: -50 },
* max: { x: 100, y: 50 }
* }); // 100
* ```
*
* @category Camera
*/
export declare function halfTranslationWidthOf(boundaries: Boundaries | undefined): number | undefined;
/**
* Calculates the height (y-axis span) of the boundaries.
*
* @param boundaries - The boundaries to measure
* @returns Height in world units, or undefined if y boundaries are not fully defined
*
* @remarks
* Returns undefined if boundaries don't have both min.y and max.y defined.
* Result is always non-negative for valid boundaries (max.y - min.y).
*
* @example
* ```typescript
* translationHeightOf({
* min: { x: -100, y: -50 },
* max: { x: 100, y: 50 }
* }); // 100
*
* translationHeightOf({ min: { y: 0 } }); // undefined (no max.y)
* translationHeightOf(undefined); // undefined
* ```
*
* @category Camera
*/
export declare function translationHeightOf(boundaries: Boundaries | undefined): number | undefined;
/**
* Calculates half the height (y-axis half-span) of the boundaries.
*
* @param boundaries - The boundaries to measure
* @returns Half-height in world units, or undefined if y boundaries are not fully defined
*
* @remarks
* Useful for calculating radius or offset from center for y-axis.
* Equivalent to `translationHeightOf(boundaries) / 2`.
*
* @example
* ```typescript
* halfTranslationHeightOf({
* min: { x: -100, y: -50 },
* max: { x: 100, y: 50 }
* }); // 50
* ```
*
* @category Camera
*/
export declare function halfTranslationHeightOf(boundaries: Boundaries | undefined): number | undefined;
/**
* Clamps camera position to ensure the entire viewport stays within boundaries.
* More restrictive than {@link clampPoint} as it considers viewport size and rotation.
*
* @param point - Proposed camera position in world coordinates
* @param viewPortWidth - Width of the viewport in CSS pixels
* @param viewPortHeight - Height of the viewport in CSS pixels
* @param boundaries - Optional boundary constraints in world space
* @param cameraZoomLevel - Current camera zoom level
* @param cameraRotation - Current camera rotation in radians
* @returns Adjusted camera position that keeps entire viewport within boundaries
*
* @remarks
* This function ensures no part of the viewport extends outside the boundaries.
* It accounts for:
* - Viewport dimensions (width/height)
* - Camera rotation (viewport corners rotate around camera center)
* - Zoom level (affects world-space size of viewport)
*
* The algorithm:
* 1. Calculates all four viewport corners in world space
* 2. Clamps each corner to boundaries
* 3. Finds the maximum displacement needed across all corners
* 4. Adjusts camera position by that displacement
*
* Use this for "edge-stop" behavior where viewport cannot scroll past boundaries.
* For "center-stop" behavior, use {@link clampPoint} instead.
*
* @example
* ```typescript
* const bounds: Boundaries = {
* min: { x: 0, y: 0 },
* max: { x: 1000, y: 1000 }
* };
*
* // Camera at center of bounds, viewport extends outside
* const adjusted = clampPointEntireViewPort(
* { x: 100, y: 100 }, // camera position
* 800, 600, // viewport size
* bounds,
* 1.0, // zoom
* 0 // rotation
* );
* // Returns position that prevents viewport from exceeding bounds
* ```
*
* @category Camera
* @see {@link clampPoint} for clamping camera center only
*/
export declare function clampPointEntireViewPort(point: Point, viewPortWidth: number, viewPortHeight: number, boundaries: Boundaries | undefined, cameraZoomLevel: number, cameraRotation: number): Point;