@abd2oo1/math-engine
v1.1.4
Published
A lightweight 2D math engine for spatial applications such as seating chart libraries, canvas editors, and game engines. Built with TypeScript, zero dependencies.
Readme
math-engine-2d
A lightweight 2D math engine for spatial applications such as seating chart libraries, canvas editors, and game engines. Built with TypeScript, zero dependencies.
Installation
npm install math-engine-2dUsage
import math from 'math-engine-2d'
// use any module
math.vector.add({ x: 1, y: 2 }, { x: 3, y: 4 }) // { x: 4, y: 6 }
math.circle.pointInsideCircle({ x: 1, y: 1 }, { x: 0, y: 0, r: 5 }) // trueModules
vector
2D vector operations. All functions take { x, y } objects and return new vectors without mutating the input.
add(a, b)
Adds two vectors together.
math.vector.add({ x: 1, y: 2 }, { x: 3, y: 4 })
// { x: 4, y: 6 }subtract(a, b)
Subtracts vector b from vector a.
math.vector.subtract({ x: 5, y: 5 }, { x: 2, y: 3 })
// { x: 3, y: 2 }scale(v, t)
Scales a vector by a scalar value.
math.vector.scale({ x: 2, y: 3 }, 2)
// { x: 4, y: 6 }dot(a, b)
Returns the dot product of two vectors. Used for finding the angle between two rows or projecting a point onto a line.
math.vector.dot({ x: 1, y: 0 }, { x: 0, y: 1 })
// 0magnitude(v)
Returns the length of a vector.
math.vector.magnitude({ x: 3, y: 4 })
// 5normalize(v)
Returns a unit vector (length = 1) in the same direction.
math.vector.normalize({ x: 3, y: 4 })
// { x: 0.6, y: 0.8 }lerp(a, b, t)
Linearly interpolates between two vectors by t (0 to 1). Used for placing seats evenly along a straight row.
math.vector.lerp({ x: 0, y: 0 }, { x: 100, y: 0 }, 0.5)
// { x: 50, y: 0 }distance(a, b)
Returns the distance between two points.
math.vector.distance({ x: 0, y: 0 }, { x: 3, y: 4 })
// 5perpendicular(v)
Returns a vector perpendicular to the given vector. Used for row orientation and normal directions.
math.vector.perpendicular({ x: 1, y: 0 })
// { x: 0, y: 1 }circle
Operations on circles { x, y, r } where x, y is the center and r is the radius.
pointInsideCircle(point, circle)
Checks if a point is inside a circle. Used for seat hit testing.
math.circle.pointInsideCircle({ x: 1, y: 1 }, { x: 0, y: 0, r: 5 })
// truepointOutsideCircle(point, circle)
Checks if a point is outside a circle.
math.circle.pointOutsideCircle({ x: 10, y: 10 }, { x: 0, y: 0, r: 5 })
// truepointOnCircle(point, circle)
Checks if a point lies exactly on the circle boundary, with floating point tolerance.
math.circle.pointOnCircle({ x: 5, y: 0 }, { x: 0, y: 0, r: 5 })
// truedistanceBetweenCircles(a, b)
Returns the distance between two circle centers.
math.circle.distanceBetweenCircles({ x: 0, y: 0, r: 5 }, { x: 10, y: 0, r: 5 })
// 10circlesOverlap(a, b)
Checks if two circles overlap. Used for detecting if two seats are too close.
math.circle.circlesOverlap({ x: 0, y: 0, r: 5 }, { x: 8, y: 0, r: 5 })
// truepointOnCircleEdge(circle, angle)
Returns the point on the circle edge at a given angle in radians. Used for placing seats on a curved row.
math.circle.pointOnCircleEdge({ x: 0, y: 0, r: 100 }, 0)
// { x: 100, y: 0 }
math.circle.pointOnCircleEdge({ x: 0, y: 0, r: 100 }, Math.PI / 2)
// { x: 0, y: 100 }circleBounds(circle)
Returns the bounding box of a circle. Used for culling — skipping seats that are off screen.
math.circle.circleBounds({ x: 50, y: 50, r: 20 })
// { left: 30, top: 30, right: 70, bottom: 70 }rectangle
Operations on rectangles { x, y, width, height } where x, y is the top-left corner.
pointInsideRectangle(point, rect)
Checks if a point is inside a rectangle. Used for drag selection.
math.rectangle.pointInsideRectangle({ x: 50, y: 50 }, { x: 0, y: 0, width: 100, height: 100 })
// truerectangleContainsRectangle(a, b)
Checks if rectangle a fully contains rectangle b. Used for checking if a section is inside a pricing zone.
math.rectangle.rectangleContainsRectangle(
{ x: 0, y: 0, width: 200, height: 200 },
{ x: 50, y: 50, width: 50, height: 50 }
)
// truerectangleIntersectsCircle(rect, circle)
Checks if a rectangle overlaps or touches a circle. Used for drag selection — does the box touch this seat?
math.rectangle.rectangleIntersectsCircle(
{ x: 0, y: 0, width: 100, height: 100 },
{ x: 110, y: 50, r: 20 }
)
// truerectangleCenter(rect)
Returns the center point of a rectangle. Used for rotating a section around its center.
math.rectangle.rectangleCenter({ x: 0, y: 0, width: 100, height: 100 })
// { x: 50, y: 50 }rectangleBounds(rect)
Returns the bounding box of a rectangle.
math.rectangle.rectangleBounds({ x: 10, y: 20, width: 100, height: 50 })
// { left: 10, top: 20, right: 110, bottom: 70 }bound
Axis-Aligned Bounding Box (AABB) operations. All bounds use { left, top, right, bottom }.
createBounds(x, y, width, height)
Creates a bounding box from position and size.
math.bound.createBounds(10, 20, 100, 50)
// { left: 10, top: 20, right: 110, bottom: 70 }pointInBounds(point, bounds)
Checks if a point is inside a bounding box. Used for drag selection.
math.bound.pointInBounds({ x: 50, y: 50 }, { left: 0, top: 0, right: 100, bottom: 100 })
// trueboundsIntersect(a, b)
Checks if two bounding boxes overlap. Used for section collision detection.
math.bound.boundsIntersect(
{ left: 0, top: 0, right: 100, bottom: 100 },
{ left: 50, top: 50, right: 150, bottom: 150 }
)
// trueexpandBounds(bounds, padding)
Expands a bounding box by a padding value on all sides.
math.bound.expandBounds({ left: 10, top: 10, right: 90, bottom: 90 }, 10)
// { left: 0, top: 0, right: 100, bottom: 100 }mergeBounds(a, b)
Merges two bounding boxes into one that contains both. Used for wrapping an entire section.
math.bound.mergeBounds(
{ left: 0, top: 0, right: 50, bottom: 50 },
{ left: 50, top: 50, right: 100, bottom: 100 }
)
// { left: 0, top: 0, right: 100, bottom: 100 }boundsCenter(bounds)
Returns the center point of a bounding box.
math.bound.boundsCenter({ left: 0, top: 0, right: 100, bottom: 100 })
// { x: 50, y: 50 }boundsSize(bounds)
Returns the width and height of a bounding box.
math.bound.boundsSize({ left: 0, top: 0, right: 100, bottom: 50 })
// { width: 100, height: 50 }moveBounds(bounds, dx, dy)
Moves a bounding box by dx and dy.
math.bound.moveBounds({ left: 0, top: 0, right: 100, bottom: 100 }, 50, 50)
// { left: 50, top: 50, right: 150, bottom: 150 }selectionBounds(point1, point2)
Creates a bounding box from two points. Used for drag selection on canvas.
math.bound.selectionBounds({ x: 20, y: 80 }, { x: 80, y: 20 })
// { left: 20, top: 20, right: 80, bottom: 80 }boundsContainsBounds(a, b)
Checks if bounding box a fully contains bounding box b.
math.bound.boundsContainsBounds(
{ left: 0, top: 0, right: 200, bottom: 200 },
{ left: 50, top: 50, right: 100, bottom: 100 }
)
// trueboundsFromPoints(points)
Computes a bounding box that wraps all given points. Used for wrapping a row of seats.
math.bound.boundsFromPoints([
{ x: 10, y: 20 },
{ x: 80, y: 5 },
{ x: 50, y: 90 }
])
// { left: 10, top: 5, right: 80, bottom: 90 }intersect
Intersection tests between shapes.
pointInRect(point, bounds)
Checks if a point is inside a bounding box.
math.intersect.pointInRect({ x: 50, y: 50 }, { left: 0, top: 0, right: 100, bottom: 100 })
// truerectIntersect(a, b)
Checks if two bounding boxes overlap.
math.intersect.rectIntersect(
{ left: 0, top: 0, right: 100, bottom: 100 },
{ left: 80, top: 80, right: 200, bottom: 200 }
)
// truelineIntersect(a, b, c, d)
Checks if two line segments intersect. Used for detecting if two row lines cross.
math.intersect.lineIntersect(
{ x: 0, y: 0 }, { x: 100, y: 100 },
{ x: 100, y: 0 }, { x: 0, y: 100 }
)
// truecircleIntersect(a, b)
Checks if two circles overlap. Used for detecting if two seats are too close.
math.intersect.circleIntersect(
{ x: 0, y: 0, r: 10 },
{ x: 15, y: 0, r: 10 }
)
// truepointInCircle(point, circle)
Checks if a point is inside a circle. Used for seat hit testing.
math.intersect.pointInCircle({ x: 3, y: 3 }, { x: 0, y: 0, r: 10 })
// truecircleIntersectRect(circle, bounds)
Checks if a circle overlaps or touches a rectangle. Used for partial seat selection.
math.intersect.circleIntersectRect(
{ x: 105, y: 50, r: 10 },
{ left: 0, top: 0, right: 100, bottom: 100 }
)
// truepointToSegmentDistance(point, a, b)
Returns the shortest distance from a point to a line segment. Used for snapping a seat to a row line.
math.intersect.pointToSegmentDistance(
{ x: 50, y: 50 },
{ x: 0, y: 0 },
{ x: 100, y: 0 }
)
// 50pointInPolygon(point, polygon)
Checks if a point is inside a polygon using ray casting. Used for pricing zones.
const zone = [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 100, y: 100 },
{ x: 0, y: 100 }
]
math.intersect.pointInPolygon({ x: 50, y: 50 }, zone)
// truetrigonometry
Trigonometry utilities for angles, arcs, and rotations. Angles are in radians unless stated otherwise.
degToRad(deg)
Converts degrees to radians.
math.trigonometry.degToRad(180)
// 3.14159...radToDeg(rad)
Converts radians to degrees.
math.trigonometry.radToDeg(Math.PI)
// 180pointOnCircle(center, radius, angle)
Returns the point on a circle at a given angle. Used for placing seats on a curved row.
math.trigonometry.pointOnCircle({ x: 0, y: 0 }, 100, 0)
// { x: 100, y: 0 }angleBetweenTwoPoints(point1, point2)
Returns the angle between two points in radians using atan2.
math.trigonometry.angleBetweenTwoPoints({ x: 0, y: 0 }, { x: 100, y: 0 })
// 0rotatePoint(point, origin, angle)
Rotates a point around an origin by a given angle. Used for rotating entire sections.
math.trigonometry.rotatePoint(
{ x: 100, y: 0 },
{ cx: 0, cy: 0 },
Math.PI / 2
)
// { x: 0, y: 100 }distanceBetweenTwoPoints(point1, point2)
Returns the distance between two points.
math.trigonometry.distanceBetweenTwoPoints({ x: 0, y: 0 }, { x: 3, y: 4 })
// 5normalizeAngle(angle)
Keeps an angle within the range [0, 2π]. Used to prevent angle overflow when looping around arcs.
math.trigonometry.normalizeAngle(7)
// 0.716...arcAngleStep(seatWidth, radius)
Returns the angle step needed to space seats evenly on an arc. Used for curved row layout.
math.trigonometry.arcAngleStep(30, 200)
// 0.15angleBetweenTwoVectors(a, b)
Returns the angle between two vectors in radians. Used for finding row orientation.
math.trigonometry.angleBetweenTwoVectors({ x: 1, y: 0 }, { x: 0, y: 1 })
// 1.5707... (90°)angle
Additional angle utilities.
degreesToRadians(deg)
Converts degrees to radians.
math.angle.degreesToRadians(90)
// 1.5707...radiansToDegrees(rad)
Converts radians to degrees.
math.angle.radiansToDegrees(Math.PI)
// 180arcAngleStep(seatWidth, radius)
Returns the angle step for even seat spacing on a curved row.
math.angle.arcAngleStep(30, 200)
// 0.15angleBetweenTwoAngles(a, b)
Returns the shortest angular distance between two angles.
math.angle.angleBetweenTwoAngles(0.1, 5.9)
// -0.483...rotateApointAroundAnotherPoint(origin, point, angle)
Rotates a point around an origin point by a given angle.
math.angle.rotateApointAroundAnotherPoint(
{ x: 0, y: 0 },
{ x: 100, y: 0 },
Math.PI / 2
)
// { x: 0, y: 100 }utils
General utility functions.
clamp(value, min, max)
Clamps a value between a minimum and maximum. Returns min if below, max if above, otherwise the value itself.
math.utils.clamp(150, 0, 100) // 100
math.utils.clamp(-10, 0, 100) // 0
math.utils.clamp(50, 0, 100) // 50lrep(start, end, percentage)
Linearly interpolates between two numbers by a percentage (0 to 1).
math.utils.lrep(0, 100, 0.5)
// 50lrepBetweenTwoPoints(startPoint, endPoint, percentage)
Linearly interpolates between two 2D points by a percentage (0 to 1).
math.utils.lrepBetweenTwoPoints({ x: 0, y: 0 }, { x: 100, y: 100 }, 0.5)
// { x: 50, y: 50 }Example — Curved Row Layout
import math from 'math-engine-2d'
const center = { x: 300, y: 300 }
const radius = 200
const seatWidth = 30
const totalSeats = 10
const step = math.trigonometry.arcAngleStep(seatWidth, radius)
const seats = []
for (let i = 0; i < totalSeats; i++) {
const angle = i * step
const position = math.trigonometry.pointOnCircle(center, radius, angle)
seats.push(position)
}Example — Seat Hit Testing
import math from 'math-engine-2d'
const seat = { x: 100, y: 100, r: 15 }
const mouseClick = { x: 105, y: 108 }
if (math.circle.pointInsideCircle(mouseClick, seat)) {
console.log('seat selected')
}Example — Drag Selection
import math from 'math-engine-2d'
const selectionStart = { x: 50, y: 50 }
const selectionEnd = { x: 200, y: 200 }
const box = math.bound.selectionBounds(selectionStart, selectionEnd)
const seats = [
{ x: 100, y: 100, r: 15 },
{ x: 300, y: 300, r: 15 },
]
const selected = seats.filter(seat =>
math.intersect.circleIntersectRect(seat, box)
)License
MIT
