2024-01-03 12:13:15 +00:00
import { VecModel } from '@tldraw/tlschema'
2023-04-25 11:01:25 +00:00
import { EASINGS } from './easings'
/** @public */
2024-01-03 12:13:15 +00:00
export type VecLike = Vec | VecModel
2023-04-25 11:01:25 +00:00
/** @public */
2024-01-03 12:13:15 +00:00
export class Vec {
2024-01-15 12:33:15 +00:00
constructor (
public x = 0 ,
public y = 0 ,
public z = 1
) { }
2023-04-25 11:01:25 +00:00
2023-11-16 12:07:33 +00:00
// eslint-disable-next-line no-restricted-syntax
2023-04-25 11:01:25 +00:00
get pressure() {
return this . z
}
set ( x = this . x , y = this . y , z = this . z ) {
this . x = x
this . y = y
this . z = z
return this
}
setTo ( { x = 0 , y = 0 , z = 1 } : VecLike ) {
this . x = x
this . y = y
this . z = z
return this
}
rot ( r : number ) {
if ( r === 0 ) return this
const { x , y } = this
const s = Math . sin ( r )
const c = Math . cos ( r )
this . x = x * c - y * s
this . y = x * s + y * c
return this
}
rotWith ( C : VecLike , r : number ) {
if ( r === 0 ) return this
const x = this . x - C . x
const y = this . y - C . y
const s = Math . sin ( r )
const c = Math . cos ( r )
this . x = C . x + ( x * c - y * s )
this . y = C . y + ( x * s + y * c )
return this
}
2024-01-03 12:13:15 +00:00
clone ( ) : Vec {
2023-04-25 11:01:25 +00:00
const { x , y , z } = this
2024-01-03 12:13:15 +00:00
return new Vec ( x , y , z )
2023-04-25 11:01:25 +00:00
}
sub ( V : VecLike ) {
this . x -= V . x
this . y -= V . y
return this
}
subXY ( x : number , y : number ) {
this . x -= x
this . y -= y
return this
}
subScalar ( n : number ) {
this . x -= n
this . y -= n
// this.z -= n
return this
}
add ( V : VecLike ) {
this . x += V . x
this . y += V . y
return this
}
addXY ( x : number , y : number ) {
this . x += x
this . y += y
return this
}
addScalar ( n : number ) {
this . x += n
this . y += n
// this.z += n
return this
}
clamp ( min : number , max? : number ) {
this . x = Math . max ( this . x , min )
this . y = Math . max ( this . y , min )
if ( max !== undefined ) {
this . x = Math . min ( this . x , max )
this . y = Math . min ( this . y , max )
}
return this
}
div ( t : number ) {
this . x /= t
this . y /= t
// this.z /= t
return this
}
divV ( V : VecLike ) {
this . x /= V . x
this . y /= V . y
// this.z /= V.z
return this
}
mul ( t : number ) {
this . x *= t
this . y *= t
// this.z *= t
return this
}
mulV ( V : VecLike ) {
this . x *= V . x
this . y *= V . y
// this.z *= V.z
return this
}
abs() {
this . x = Math . abs ( this . x )
this . y = Math . abs ( this . y )
return this
}
nudge ( B : VecLike , distance : number ) {
2024-01-03 12:13:15 +00:00
const tan = Vec . Tan ( B , this )
2023-04-25 11:01:25 +00:00
return this . add ( tan . mul ( distance ) )
}
neg() {
this . x *= - 1
this . y *= - 1
// this.z *= -1
return this
}
cross ( V : VecLike ) {
this . x = this . y * V . z ! - this . z * V . y
this . y = this . z * V . x - this . x * V . z !
// this.z = this.x * V.y - this.y * V.x
return this
}
dpr ( V : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Dpr ( this , V )
2023-04-25 11:01:25 +00:00
}
cpr ( V : VecLike ) {
2024-01-03 12:13:15 +00:00
return Vec . Cpr ( this , V )
2023-04-25 11:01:25 +00:00
}
len2 ( ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Len2 ( this )
2023-04-25 11:01:25 +00:00
}
len ( ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Len ( this )
2023-04-25 11:01:25 +00:00
}
pry ( V : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Pry ( this , V )
2023-04-25 11:01:25 +00:00
}
per() {
const { x , y } = this
this . x = y
this . y = - x
return this
}
uni() {
2024-01-03 12:13:15 +00:00
return Vec . Uni ( this )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
tan ( V : VecLike ) : Vec {
return Vec . Tan ( this , V )
2023-04-25 11:01:25 +00:00
}
dist ( V : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Dist ( this , V )
2023-04-25 11:01:25 +00:00
}
distanceToLineSegment ( A : VecLike , B : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . DistanceToLineSegment ( A , B , this )
2023-04-25 11:01:25 +00:00
}
slope ( B : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Slope ( this , B )
2023-04-25 11:01:25 +00:00
}
snapToGrid ( gridSize : number ) {
this . x = Math . round ( this . x / gridSize ) * gridSize
this . y = Math . round ( this . y / gridSize ) * gridSize
return this
}
angle ( B : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Angle ( this , B )
2023-04-25 11:01:25 +00:00
}
toAngle() {
2024-01-03 12:13:15 +00:00
return Vec . ToAngle ( this )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
lrp ( B : VecLike , t : number ) : Vec {
2023-04-25 11:01:25 +00:00
this . x = this . x + ( B . x - this . x ) * t
this . y = this . y + ( B . y - this . y ) * t
return this
}
equals ( B : VecLike ) {
2024-01-03 12:13:15 +00:00
return Vec . Equals ( this , B )
2023-04-25 11:01:25 +00:00
}
equalsXY ( x : number , y : number ) {
2024-01-03 12:13:15 +00:00
return Vec . EqualsXY ( this , x , y )
2023-04-25 11:01:25 +00:00
}
norm() {
const l = this . len ( )
this . x = l === 0 ? 0 : this.x / l
this . y = l === 0 ? 0 : this.y / l
return this
}
toFixed() {
2024-01-03 12:13:15 +00:00
return Vec . ToFixed ( this )
2023-04-25 11:01:25 +00:00
}
toString() {
2024-01-03 12:13:15 +00:00
return Vec . ToString ( Vec . ToFixed ( this ) )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
toJson ( ) : VecModel {
return Vec . ToJson ( this )
2023-04-25 11:01:25 +00:00
}
toArray ( ) : number [ ] {
2024-01-03 12:13:15 +00:00
return Vec . ToArray ( this )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Add ( A : VecLike , B : VecLike ) : Vec {
return new Vec ( A . x + B . x , A . y + B . y )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static AddXY ( A : VecLike , x : number , y : number ) : Vec {
return new Vec ( A . x + x , A . y + y )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Sub ( A : VecLike , B : VecLike ) : Vec {
return new Vec ( A . x - B . x , A . y - B . y )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static SubXY ( A : VecLike , x : number , y : number ) : Vec {
return new Vec ( A . x - x , A . y - y )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static AddScalar ( A : VecLike , n : number ) : Vec {
return new Vec ( A . x + n , A . y + n )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static SubScalar ( A : VecLike , n : number ) : Vec {
return new Vec ( A . x - n , A . y - n )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Div ( A : VecLike , t : number ) : Vec {
return new Vec ( A . x / t , A . y / t )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Mul ( A : VecLike , t : number ) : Vec {
return new Vec ( A . x * t , A . y * t )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static DivV ( A : VecLike , B : VecLike ) : Vec {
return new Vec ( A . x / B . x , A . y / B . y )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static MulV ( A : VecLike , B : VecLike ) : Vec {
return new Vec ( A . x * B . x , A . y * B . y )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Neg ( A : VecLike ) : Vec {
return new Vec ( - A . x , - A . y )
2023-04-25 11:01:25 +00:00
}
2024-01-24 10:19:20 +00:00
/ * *
* Get the perpendicular vector to A .
* /
2024-01-03 12:13:15 +00:00
static Per ( A : VecLike ) : Vec {
return new Vec ( A . y , - A . x )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Abs ( A : VecLike ) : Vec {
return new Vec ( Math . abs ( A . x ) , Math . abs ( A . y ) )
2023-04-25 11:01:25 +00:00
}
2024-04-08 13:31:05 +00:00
// Get the distance between two points.
2023-04-25 11:01:25 +00:00
static Dist ( A : VecLike , B : VecLike ) : number {
return Math . hypot ( A . y - B . y , A . x - B . x )
}
2024-04-13 13:30:30 +00:00
// Get whether a distance between two points is less than a number. This is faster to calulate than using `Vec.Dist(a, b) < n`.
static DistMin ( A : VecLike , B : VecLike , n : number ) : boolean {
return ( A . x - B . x ) * ( A . x - B . x ) + ( A . y - B . y ) * ( A . y - B . y ) < n * * 2
}
2024-04-08 13:31:05 +00:00
// Get the squared distance between two points. This is faster to calculate (no square root) so useful for "minimum distance" checks where the actual measurement does not matter.
static Dist2 ( A : VecLike , B : VecLike ) : number {
return ( A . x - B . x ) * ( A . x - B . x ) + ( A . y - B . y ) * ( A . y - B . y )
}
2024-01-24 10:19:20 +00:00
/ * *
* Dot product of two vectors which is used to calculate the angle between them .
* /
2023-04-25 11:01:25 +00:00
static Dpr ( A : VecLike , B : VecLike ) : number {
return A . x * B . x + A . y * B . y
}
static Cross ( A : VecLike , V : VecLike ) {
2024-01-03 12:13:15 +00:00
return new Vec (
2023-04-25 11:01:25 +00:00
A . y * V . z ! - A . z ! * V . y ,
A . z ! * V . x - A . x * V . z !
// A.z = A.x * V.y - A.y * V.x
)
}
2024-01-24 10:19:20 +00:00
/ * *
* Cross product of two vectors which is used to calculate the area of a parallelogram .
* /
2023-04-25 11:01:25 +00:00
static Cpr ( A : VecLike , B : VecLike ) {
return A . x * B . y - B . x * A . y
}
static Len2 ( A : VecLike ) : number {
return A . x * A . x + A . y * A . y
}
static Len ( A : VecLike ) : number {
2023-10-09 14:18:42 +00:00
return Math . hypot ( A . x , A . y )
2023-04-25 11:01:25 +00:00
}
2024-01-24 10:19:20 +00:00
/ * *
* Get the projection of A onto B .
* /
2023-04-25 11:01:25 +00:00
static Pry ( A : VecLike , B : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Dpr ( A , B ) / Vec . Len ( B )
2023-04-25 11:01:25 +00:00
}
2024-01-24 10:19:20 +00:00
/ * *
* Get the unit vector of A .
* /
2023-04-25 11:01:25 +00:00
static Uni ( A : VecLike ) {
2024-01-03 12:13:15 +00:00
return Vec . Div ( A , Vec . Len ( A ) )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Tan ( A : VecLike , B : VecLike ) : Vec {
return Vec . Uni ( Vec . Sub ( A , B ) )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Min ( A : VecLike , B : VecLike ) : Vec {
return new Vec ( Math . min ( A . x , B . x ) , Math . min ( A . y , B . y ) )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Max ( A : VecLike , B : VecLike ) : Vec {
return new Vec ( Math . max ( A . x , B . x ) , Math . max ( A . y , B . y ) )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static From ( { x , y , z = 1 } : VecModel ) {
return new Vec ( x , y , z )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static FromArray ( v : number [ ] ) : Vec {
return new Vec ( v [ 0 ] , v [ 1 ] )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Rot ( A : VecLike , r = 0 ) : Vec {
2023-04-25 11:01:25 +00:00
const s = Math . sin ( r )
const c = Math . cos ( r )
2024-01-03 12:13:15 +00:00
return new Vec ( A . x * c - A . y * s , A . x * s + A . y * c )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static RotWith ( A : VecLike , C : VecLike , r : number ) : Vec {
2023-04-25 11:01:25 +00:00
const x = A . x - C . x
const y = A . y - C . y
const s = Math . sin ( r )
const c = Math . cos ( r )
2024-01-03 12:13:15 +00:00
return new Vec ( C . x + ( x * c - y * s ) , C . y + ( x * s + y * c ) )
2023-04-25 11:01:25 +00:00
}
/ * *
* Get the nearest point on a line with a known unit vector that passes through point A
*
* ` ` ` ts
* Vec . nearestPointOnLineThroughPoint ( A , u , Point )
* ` ` `
*
* @param A - Any point on the line
* @param u - The unit vector for the line .
* @param P - A point not on the line to test .
* /
2024-01-03 12:13:15 +00:00
static NearestPointOnLineThroughPoint ( A : VecLike , u : VecLike , P : VecLike ) : Vec {
return Vec . Mul ( u , Vec . Sub ( P , A ) . pry ( u ) ) . add ( A )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static NearestPointOnLineSegment ( A : VecLike , B : VecLike , P : VecLike , clamp = true ) : Vec {
2024-04-13 13:30:30 +00:00
if ( Vec . Equals ( A , P ) ) return Vec . From ( P )
if ( Vec . Equals ( B , P ) ) return Vec . From ( P )
2024-01-03 12:13:15 +00:00
const u = Vec . Tan ( B , A )
const C = Vec . Add ( A , Vec . Mul ( u , Vec . Sub ( P , A ) . pry ( u ) ) )
2023-04-25 11:01:25 +00:00
if ( clamp ) {
2024-01-03 12:13:15 +00:00
if ( C . x < Math . min ( A . x , B . x ) ) return Vec . Cast ( A . x < B . x ? A : B )
if ( C . x > Math . max ( A . x , B . x ) ) return Vec . Cast ( A . x > B . x ? A : B )
if ( C . y < Math . min ( A . y , B . y ) ) return Vec . Cast ( A . y < B . y ? A : B )
if ( C . y > Math . max ( A . y , B . y ) ) return Vec . Cast ( A . y > B . y ? A : B )
2023-04-25 11:01:25 +00:00
}
return C
}
static DistanceToLineThroughPoint ( A : VecLike , u : VecLike , P : VecLike ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Dist ( P , Vec . NearestPointOnLineThroughPoint ( A , u , P ) )
2023-04-25 11:01:25 +00:00
}
static DistanceToLineSegment ( A : VecLike , B : VecLike , P : VecLike , clamp = true ) : number {
2024-01-03 12:13:15 +00:00
return Vec . Dist ( P , Vec . NearestPointOnLineSegment ( A , B , P , clamp ) )
2023-04-25 11:01:25 +00:00
}
static Snap ( A : VecLike , step = 1 ) {
2024-01-03 12:13:15 +00:00
return new Vec ( Math . round ( A . x / step ) * step , Math . round ( A . y / step ) * step )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Cast ( A : VecLike ) : Vec {
if ( A instanceof Vec ) return A
return Vec . From ( A )
2023-04-25 11:01:25 +00:00
}
static Slope ( A : VecLike , B : VecLike ) : number {
if ( A . x === B . y ) return NaN
return ( A . y - B . y ) / ( A . x - B . x )
}
2024-02-07 15:11:10 +00:00
static IsNaN ( A : VecLike ) : boolean {
return isNaN ( A . x ) || isNaN ( A . y )
}
2023-04-25 11:01:25 +00:00
static Angle ( A : VecLike , B : VecLike ) : number {
return Math . atan2 ( B . y - A . y , B . x - A . x )
}
2024-01-24 10:19:20 +00:00
/ * *
* Linearly interpolate between two points .
* @param A - The first point .
* @param B - The second point .
* @param t - The interpolation value between 0 and 1 .
* @returns The interpolated point .
* /
2024-01-03 12:13:15 +00:00
static Lrp ( A : VecLike , B : VecLike , t : number ) : Vec {
return Vec . Sub ( B , A ) . mul ( t ) . add ( A )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
static Med ( A : VecLike , B : VecLike ) : Vec {
return new Vec ( ( A . x + B . x ) / 2 , ( A . y + B . y ) / 2 )
2023-04-25 11:01:25 +00:00
}
static Equals ( A : VecLike , B : VecLike ) : boolean {
return Math . abs ( A . x - B . x ) < 0.0001 && Math . abs ( A . y - B . y ) < 0.0001
}
static EqualsXY ( A : VecLike , x : number , y : number ) : boolean {
return A . x === x && A . y === y
}
static Clockwise ( A : VecLike , B : VecLike , C : VecLike ) : boolean {
return ( C . x - A . x ) * ( B . y - A . y ) - ( B . x - A . x ) * ( C . y - A . y ) < 0
}
static Rescale ( A : VecLike , n : number ) {
2024-01-03 12:13:15 +00:00
const l = Vec . Len ( A )
return new Vec ( ( n * A . x ) / l , ( n * A . y ) / l )
2023-04-25 11:01:25 +00:00
}
static ScaleWithOrigin ( A : VecLike , scale : number , origin : VecLike ) {
2024-01-03 12:13:15 +00:00
return Vec . Sub ( A , origin ) . mul ( scale ) . add ( origin )
2023-04-25 11:01:25 +00:00
}
static ToFixed ( A : VecLike , n = 2 ) {
2024-01-03 12:13:15 +00:00
return new Vec ( + A . x . toFixed ( n ) , + A . y . toFixed ( n ) , + A . z ! . toFixed ( n ) )
2023-04-25 11:01:25 +00:00
}
static Nudge ( A : VecLike , B : VecLike , distance : number ) {
2024-01-03 12:13:15 +00:00
return Vec . Add ( A , Vec . Tan ( B , A ) . mul ( distance ) )
2023-04-25 11:01:25 +00:00
}
static ToString ( A : VecLike ) {
return ` ${ A . x } , ${ A . y } `
}
static ToAngle ( A : VecLike ) {
let r = Math . atan2 ( A . y , A . x )
if ( r < 0 ) r += Math . PI * 2
return r
}
2023-07-07 15:32:08 +00:00
static FromAngle ( r : number , length = 1 ) {
2024-01-03 12:13:15 +00:00
return new Vec ( Math . cos ( r ) * length , Math . sin ( r ) * length )
2023-07-07 15:32:08 +00:00
}
2023-04-25 11:01:25 +00:00
static ToArray ( A : VecLike ) {
return [ A . x , A . y , A . z ! ]
}
static ToJson ( A : VecLike ) {
const { x , y , z } = A
return { x , y , z }
}
static Average ( arr : VecLike [ ] ) {
const len = arr . length
2024-01-03 12:13:15 +00:00
const avg = new Vec ( 0 , 0 )
2024-03-04 17:48:35 +00:00
if ( len === 0 ) {
return avg
}
2023-04-25 11:01:25 +00:00
for ( let i = 0 ; i < len ; i ++ ) {
avg . add ( arr [ i ] )
}
return avg . div ( len )
}
2024-01-03 12:13:15 +00:00
static Clamp ( A : Vec , min : number , max? : number ) {
2023-04-25 11:01:25 +00:00
if ( max === undefined ) {
2024-01-03 12:13:15 +00:00
return new Vec ( Math . min ( Math . max ( A . x , min ) ) , Math . min ( Math . max ( A . y , min ) ) )
2023-04-25 11:01:25 +00:00
}
2024-01-03 12:13:15 +00:00
return new Vec ( Math . min ( Math . max ( A . x , min ) , max ) , Math . min ( Math . max ( A . y , min ) , max ) )
2023-04-25 11:01:25 +00:00
}
/ * *
* Get an array of points ( with simulated pressure ) between two points .
*
* @param A - The first point .
* @param B - The second point .
* @param steps - The number of points to return .
* /
2024-01-03 12:13:15 +00:00
static PointsBetween ( A : VecModel , B : VecModel , steps = 6 ) : Vec [ ] {
const results : Vec [ ] = [ ]
2023-04-25 11:01:25 +00:00
for ( let i = 0 ; i < steps ; i ++ ) {
const t = EASINGS . easeInQuad ( i / ( steps - 1 ) )
2024-01-03 12:13:15 +00:00
const point = Vec . Lrp ( A , B , t )
2023-04-25 11:01:25 +00:00
point . z = Math . min ( 1 , 0.5 + Math . abs ( 0.5 - ease ( t ) ) * 0.65 )
results . push ( point )
}
return results
}
static SnapToGrid ( A : VecLike , gridSize = 8 ) {
2024-01-03 12:13:15 +00:00
return new Vec ( Math . round ( A . x / gridSize ) * gridSize , Math . round ( A . y / gridSize ) * gridSize )
2023-04-25 11:01:25 +00:00
}
}
const ease = ( t : number ) = > ( t < 0.5 ? 2 * t * t : - 1 + ( 4 - 2 * t ) * t )