A collection of functions for working with points in a 2D coordinate system, along with additional utility functions.
- What's new
- How to use — install, imports, develops, publishes
- Examples
- Point class
- Vector class
- Function reference
- Changelog
- New
vectorsdomain —magnitude,normalize,dotProduct,crossProduct,add,subtract,multiply,project,reflect,perpendicular,fromAngle, plus a chainableVectorclass. See Vector operations.
Upgrading from 2.3.x? Version 2.4.0 changed angle's behavior for any point1 not at the origin (it was returning incorrect results), and pointWithoutCollision now returns Point | null instead of Point | string — see CHANGELOG.
See CHANGELOG.md for full notes.
Use Node 18+. The repo pins Node 20 for development (.nvmrc — nvm use recommended).
npm:
npm install pointscapepnpm / Yarn:
pnpm add pointscape
# or
yarn add pointscapeMost functions work with plain { x, y } objects — no class allocation needed.
import{collision,collisionInArray}from"pointscape";constplayer={x: 0,y: 0};constenemy={x: 3,y: 4};// exactly 5 units awaycollision(player,enemy,5);// true — within radiuscollision(player,enemy,4);// false — outside radius// Optional callback fires on hitcollision(player,enemy,5,()=>console.log("hit!"));// Batch: all targets within radius 3 of originconsttargets=[{x: 1,y: 0},{x: 5,y: 0},{x: 3,y: 0},];collisionInArray({x: 0,y: 0},targets,3);// [{ x: 1, y: 0 }, { x: 3, y: 0 }]import{nearest,farthest}from"pointscape";constorigin={x: 0,y: 0};constpts=[{x: 5,y: 0},{x: 1,y: 0},{x: 10,y: 0},];nearest(origin,pts);// { x: 1, y: 0 }farthest(origin,pts);// { x: 10, y: 0 }nearest(origin,[]);// nullimport{move,lerp}from"pointscape";move({x: 5,y: 5},3,-2);// { x: 8, y: 3 }// 25 % of the way from A to B — useful for smooth animationlerp({x: 0,y: 0},{x: 20,y: 40},0.25);// { x: 5, y: 10 }// Midpoint (equivalent to middle())lerp({x: 0,y: 0},{x: 20,y: 40},0.5);// { x: 10, y: 20 }import{distance,middle,angle,center,perimeter}from"pointscape";consta={x: 0,y: 0};constb={x: 10,y: 10};distance(a,b);// 14.142135623730951middle(a,b);// { x: 5, y: 5 }angle(a,b);// 0.7853981633974483 (π/4 radians)constpoints=[a,b,{x: 0,y: 10},{x: 10,y: 0}];center(points);// { x: 5, y: 5 }perimeter(points);// 48.2842712474619Argument order changed in 2.3.0 — points array is always first.
import{rotate,scale,degreesToRadians}from"pointscape";constbox=[{x: 10,y: 0},{x: 10,y: 10},{x: 0,y: 10},{x: 0,y: 0},];// Rotate 90 ° around the originrotate(box,{x: 0,y: 0},degreesToRadians(90));// Each vertex rotated — e.g. { x: 10, y: 0 } becomes { x: 0, y: 10 }// Double width, halve heightscale(box,2,0.5);// [{ x: 20, y: 0 }, { x: 20, y: 5 }, { x: 0, y: 5 }, { x: 0, y: 0 }]import{area,triangleArea,circleArea}from"pointscape";// Polygon area — rectangle 4 × 3area([{x: 0,y: 0},{x: 4,y: 0},{x: 4,y: 3},{x: 0,y: 3},]);// 12triangleArea({x: 0,y: 0},{x: 6,y: 0},{x: 3,y: 4});// 12circleArea(5);// 78.53981633974483import{square,rectangle,triangle,pentagon}from"pointscape";// 4 vertices of a 10-unit square (clockwise from origin)square({x: 0,y: 0},10);// [{ x:0,y:0 }, { x:10,y:0 }, { x:10,y:-10 }, { x:0,y:-10 }]// Rectangle 10 wide × 5 tallrectangle({x: 0,y: 0},10,5);// [{ x:0,y:0 }, { x:10,y:0 }, { x:10,y:-5 }, { x:0,y:-5 }]// Triangle vertices, size 10, default direction "left"triangle({x: 0,y: 0},10);// Pentagon centred at (50, 50) with radius 30pentagon({x: 50,y: 50},30);// 5 evenly-spaced verticesThe Point class wraps any { x, y } and exposes all geometric operations as chainable methods. Every method that returns a point returns a Point instance, so calls can be chained.
import{Point}from"pointscape";consta=newPoint(10,20);constb=newPoint(50,80);// Basic methodsa.distanceTo(b);// 72.11...a.angleTo(b);// number in radiansa.equals(b);// falsea.clone();// Point(10, 20)a.toString();// "Point(10, 20)"// Factory — convert a plain { x, y } to a Point instanceconstc=Point.from({x: 0,y: 0});// Chainable — move() and lerp() return Point instancesa.move(5,-5)// Point(15, 15).lerp(b,0.5)// Point(32.5, 47.5).distanceTo(b);// number// Rotate a point around a centera.rotateAround({x: 0,y: 0},Math.PI);// Point(-10, -20)// Array-based methods — return Point or Point | nulla.nearestFromPoints([b,c]);// Point | nulla.farthestFromPoints([b,c]);// Point | nullPlain { x: number; y: number } objects work for every function — you don't need to allocate a Point instance unless you want the method API.
Point and Vector share the same { x, y } shape, but Vector represents a direction + magnitude (velocity, displacement, a surface normal) rather than a position — that's why it gets its own arithmetic.
import{magnitude,normalize,dotProduct,crossProduct,add,subtract,multiply,project,reflect,perpendicular,fromAngle,}from"pointscape";constvelocity={x: 3,y: 4};magnitude(velocity);// 5normalize(velocity);// { x: 0.6, y: 0.8 }add({x: 1,y: 2},{x: 3,y: 4});// { x: 4, y: 6 }subtract({x: 5,y: 7},{x: 2,y: 3});// { x: 3, y: 4 }multiply(velocity,2);// { x: 6, y: 8 }dotProduct({x: 1,y: 0},{x: 0,y: 1});// 0 — perpendicular vectorscrossProduct({x: 2,y: 3},{x: 4,y: 5});// -2// The displacement between two points is just a subtractionconsttarget={x: 10,y: 10};constorigin={x: 0,y: 0};constheading=subtract(target,origin);// { x: 10, y: 10 }// Bounce a velocity off a surface with the given unit normalreflect({x: 1,y: -1},{x: 0,y: 1});// { x: 1, y: 1 }// Build a velocity vector from a heading angle + speedfromAngle(Math.PI/2,5);// { x: ~0, y: 5 }perpendicular({x: 1,y: 0});// { x: 0, y: 1 } — 90° counter-clockwiseproject({x: 3,y: 4},{x: 1,y: 0});// { x: 3, y: 0 }The Vector class mirrors Point, but exposes arithmetic operations as chainable methods.
import{Vector}from"pointscape";consta=newVector(3,4);constb=newVector(1,0);a.magnitude();// 5a.normalize();// Vector(0.6, 0.8)a.dot(b);// 3a.cross(b);// -4a.equals(b);// false// Chainable — arithmetic methods return Vector instancesa.add(b).multiply(2).magnitude();// number// FactoriesVector.from({x: 0,y: 0});Vector.fromAngle(Math.PI/2,5);// Vector(~0, 5)import{inLine,cross,positionInCircle}from"pointscape";// Is (5, 5) on the line from (0, 0) to (10, 10)?inLine({x: 5,y: 5},{start: {x: 0,y: 0},end: {x: 10,y: 10}});// true// Do two diagonals intersect?cross({start: {x: 0,y: 0},end: {x: 10,y: 10}},{start: {x: 0,y: 10},end: {x: 10,y: 0}});// true// Point on a circle of radius 10 at angle 0 (rightmost)positionInCircle({x: 0,y: 0},10,0);// { x: 10, y: 0 }import{sort}from"pointscape";constpts=[{x: 3,y: 1},{x: 1,y: 3},{x: 2,y: 2},];sort([...pts]);// by x → [{ x:1,y:3 }, { x:2,y:2 }, { x:3,y:1 }]sort([...pts],"y");// by y → [{ x:3,y:1 }, { x:2,y:2 }, { x:1,y:3 }]// Note: sort mutates the array in place.import{degreesToRadians,radiansToDegrees,roundToPrecision,average,inRange,}from"pointscape";degreesToRadians(180);// 3.141592653589793radiansToDegrees(Math.PI);// 180roundToPrecision(3.14159,2);// 3.14roundToPrecision(3.14159,0);// 3average([10,20,30,40]);// 25inRange(5,0,10);// trueinRange(15,0,10);// falseimport{intersection,difference,removeDuplicates,chunk,sample,}from"pointscape";intersection([1,2,3,4],[3,4,5,6]);// [3, 4]difference([1,2,3,4],[3,4,5,6]);// [1, 2]removeDuplicates([1,1,2,3,3,4]);// [1, 2, 3, 4]chunk([1,2,3,4,5,6],2);// [[1, 2], [3, 4], [5, 6]]sample([10,20,30,40,50]);// one random element, e.g. 30sample([10,20,30,40,50],3);// 3 consecutive elements, e.g. [20, 30, 40]import{randomNumber,randomBoolean,uniqueId}from"pointscape";randomNumber(1,10);// random integer in [1, 10], e.g. 7randomBoolean();// true or falseuniqueId();// e.g. "110e8400-e29b-41d4-a716-446655440000"uniqueId(["id-1","id-2"]);// UUID guaranteed not to collide with the provided ids- distance
interfacePoint{x: number;y: number;}(point1: Point,point2: Point)=>number;Returns the distance beetween two points, each point is an object with x and y properties.
- area
(points: Point[])=>number;Returns the area enclosed by the given points. Takes an array of points as argument, where each point is an object with x and y properties.
- triangleArea
(point1: Point,point2: Point,point3: Point)=>number;Returns the area of the triangle formed by three points.
- collision
(point1: Point,point2: Point,collisionDistance: number,[callback]?: ()=>void)=>boolean;Returns true if the Euclidean distance between the two points is less than or equal to collisionDistance (treat collisionDistance as a non-negative radius for a circle around each position). Optionally invokes callback when a collision is detected.
- collisionInArray
(initialPoint: Point,points: Point[],radius: number)=>Point[]Returns every point in points whose distance to initialPoint is within radius, using the same circular rule as collision.
- positionInCircle
(point: Point,radius: number,angleInRadians: number)=>Point;Returns the x and y coordinates for the current point in the circle, given its center point, radius, and angle.
- angle
(point1: Point,point2: Point)=>number;Returns the angle formed by the connection of two points.
- middle
(point1: Point,point2: Point)=>Point;Returns the midpoint between two points.
- nearest
(point: Point,points: Point[])=>Point|null;Returns the nearest point to the given point from the array, or null if points is empty.
- perimeter
(points: Point[])=>number;Returns the perimeter of the figure formed by the given points.
- pointWithoutCollision(minX, maxX, minY, maxY, distance, points)
interfaceBounds{min: number;max: number;}(xBounds: Bounds,yBounds: Bounds,distance: number,points: Point[])=>Point|null;Returns a point that doesn't collide with any of the given points within the specified distance, or null if no such point could be found within the retry budget.
- randomPoint
([xBounds]: Bounds,[yBounds]: Bounds)=>Point;Returns a random point within the given dimensions, if provided, otherwise in 100 units on both axes.
- randomPointInDistance
(point: Point,distance: number)=>Point;Returns a random point within the given distance from the specified point.
- randomPoints
([xBounds]: Bounds,[yBounds]: Bounds,quantity: number,)=>Point[]Returns a specified quantity of random points within the given dimensions, if dimensions are provided, otherwise in the range of 100.
- possibleConnections
(pointsCount: number)=>number;Returns the quantity of possible connections among given quantity of points.
- circleArea
(radius: number)=>number;Returns the area of the circle given its radius.
- center
(points: Point[])=>Point;Returns the center of given points.
- farthest
(point: Point,points: Point[])=>Point|null;Returns the farthest point from the given point among the given array; null if the array is empty.
- rotate
(points: Point[],centerPoint: Point,angleInRadians: number)=>Point[]Returns the points rotated around the given center point.
- sort
(points: Point[],coordinate?: "x"|"y")=>Point[]Mutates points in place (Array.sort) and returns the same array reference. Omit coordinate to sort primarily by x; "y" sorts by y.
- scale
(points: Point[],scaleFactorX: number,scaleFactorY: number)=>Point[]Returns the scaled points.
- inLine
interfaceLine{start: Point;end: Point;}(point: Point,line: Line)=>boolean;Returns boolean value indicating whether or not the given coordinates are on line defined by two other points.
- cross
(line1: Line,line2: Line)=>boolean;Returns boolean value indicating if two lines each defined by two points intersect.
- move
(point: Point,xStep: number,yStep: number)=>Point;Returns a point of with the new coordinates.
- lerp
(point1: Point,point2: Point,t: number)=>Point;Returns the linearly interpolated point between point1 and point2 at parameter t (0 = point1, 1 = point2, 0.5 = midpoint). Values of t outside [0, 1] extrapolate beyond the two points.
- square
(point: Point,size: number,[direction]: "left"|"right"|"down"|"up")=>Point[]Returns an array of points representing a shape of square.Takes four parameters: starting coordinates (x and y), size of square side, and direction which should be one of the values "left", "right", "up", "down".
- rectangle(point, width, height, [direction])
(point: Point,width: number,height: number,[direction]: "left"|"right"|"down"|"up")=>Point[]Returns vertices of a rectangle starting at point, advancing width then height along the directional path (same directional convention as square).
- triangle(point, size, [direction])
(point: Point,size: number,[direction]: "left"|"right"|"down"|"up")=>Point[]Returns an array of points representing a shape of triangle.Takes same parameters as square function.
- pentagon(center, radius, [angle])
(centerPoint: Point,radius: number,angle?: number)=>Point[]Returns vertices of a pentagon around centerPoint with radius and rotation angle (degrees, default 0).
Point and Vector are structurally identical ({ x, y }); Vector represents a direction + magnitude rather than a position. Use subtract to get the displacement vector between two points.
- magnitude
(v: Vector)=>number;Returns the length of the vector.
- normalize
(v: Vector)=>Vector;Returns a unit vector (length 1) in the same direction. Returns { x: 0, y: 0 } for the zero vector.
- dotProduct
(v1: Vector,v2: Vector)=>number;Returns the dot product of two vectors. Zero when the vectors are perpendicular.
- crossProduct
(v1: Vector,v2: Vector)=>number;Returns the scalar (2D) cross product — the signed area of the parallelogram formed by the two vectors.
- add
(v1: Vector,v2: Vector)=>Vector;Returns the component-wise sum of two vectors.
- subtract
(v1: Vector,v2: Vector)=>Vector;Returns the component-wise difference of two vectors. Also how you get the displacement vector from v2 to v1 when passing two points.
- multiply
(v: Vector,scalar: number)=>Vector;Scales a vector by a scalar.
- project
(v: Vector,onto: Vector)=>Vector;Returns the component of v parallel to onto. Returns { x: 0, y: 0 } if onto is the zero vector.
- reflect
(v: Vector,normal: Vector)=>Vector;Reflects v off a surface with the given unitnormal.
- perpendicular
(v: Vector)=>Vector;Rotates the vector 90° counter-clockwise.
- fromAngle
(angleInRadians: number,magnitude?: number)=>Vector;Builds a vector from an angle (radians) and magnitude (default 1).
- degreesToRadians
(degrees: number)=>number;Converts degrees to radians.
- radiansToDegrees
(radians: number)=>number;Converts radians to degrees.
- inRange
(number: number,min: number,max: number)=>boolean;Returns true if the given number is within the specified range.
- roundToPrecision
(number: number,precision: -100|-10|0|10|100|number)=>number;Rounds the number to the given precision.
- average
(numbers: number[])=>number;Returns the average of all numbers in an array.
- intersection
<T>(arr1: T[],arr2: T[])=>T[]Returns the array of intersection of two arrays.
- difference
<T>(arr1: T[],arr2: T[])=>T[]Returns the array of difference of two arrays.
- chunk
<T>(arr: T[],perArr: number)=>T[][]Returns an array splited into chunks based on elements count per chunk.
- removeDuplicates(arr)
<T>(arr: T[])=>T[]Returns the array without duplicates.
- sample
<T>(arr: T[],size?: number)=>T|T[]Returns a random sample from an array with optional size argument for sampling length. If not specified, it returns only one element.
- randomNumber
(min: number,max: number)=>number;Returns a random number within the given range.
- randomBoolean
()=>boolean;Returns a random boolean value.
- uniqueId
([otherids]: string[])=>stringReturns a unique ID that's different from the provided IDs, or a random ID if no other IDs are given.
Use named imports so bundlers can tree-shake unused functions:
import{distance,collision,nearest,Point,Vector}from"pointscape";importtype{Bounds,Line,PointType,VectorType}from"pointscape";Functions take any { x: number; y: number }; you do not have to allocate a Point or Vector class instance. PointType/VectorType match instances of Point/Vector (the classes exported from this package) — the underlying Point/Vector types are structurally identical, but kept distinct to signal position vs. direction+magnitude.
Open a PR against the default branch; CI runs npm ci, tests, and npm run build on every push/PR (see .github/workflows/ci.yml).
git clone https://github.com/Arman2409/pointscape.git
cd pointscape
nvm use # optional: picks Node from .nvmrc
npm ci
npm test
npm run build- Bump
versioninpackage.jsonand updateCHANGELOG.md. npm loginwith an account that [maintainspointscape\*\*](https://www.npmjs.com/package/pointscape) on the public registry (https://registry.npmjs.org/`).npm publish— theprepackscript rebuildsdist/first.
Preview tarball contents locally: npm run publish:dry-run.
See also npm files publish guide — only dist/, readme, changelog, and license are published.