Merge pull request #86 from tldraw/utils-refactor

Split vectors and intersections into new packages
This commit is contained in:
Steve Ruiz 2021-09-12 13:26:08 +01:00 committed by GitHub
commit 9f309bb485
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 1801 additions and 880 deletions

17
.vscode/tasks.json vendored
View file

@ -2,17 +2,12 @@
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "tsc", "label": "Check for type errors",
"type": "shell", "type": "typescript",
"command": "./node_modules/.bin/tsc", "tsconfig": "tsconfig.json",
"args": ["--noEmit"], "option": "watch",
"presentation": { "problemMatcher": ["$tsc-watch"],
"reveal": "never", "group": "build"
"echo": false,
"focus": false,
"panel": "dedicated"
},
"problemMatcher": "$tsc-watch"
} }
] ]
} }

View file

@ -13,7 +13,9 @@
"packages/core", "packages/core",
"packages/tldraw", "packages/tldraw",
"packages/dev", "packages/dev",
"packages/www" "packages/www",
"packages/vec",
"packages/intersect"
], ],
"scripts": { "scripts": {
"test": "jest", "test": "jest",
@ -22,7 +24,7 @@
"start": "lerna run start:pre && lerna run start --stream --parallel", "start": "lerna run start:pre && lerna run start --stream --parallel",
"start:www": "yarn build:packages && lerna run start --parallel & cd packages/www && yarn dev", "start:www": "yarn build:packages && lerna run start --parallel & cd packages/www && yarn dev",
"build": "yarn build:packages && cd packages/www && yarn build", "build": "yarn build:packages && cd packages/www && yarn build",
"build:packages": "cd packages/core && yarn build && cd ../tldraw && yarn build", "build:packages": "cd packages/vec && yarn build && cd ../intersect && yarn build && cd ../core && yarn build && cd ../tldraw && yarn build",
"publish:patch": "yarn build:packages && lerna publish patch", "publish:patch": "yarn build:packages && lerna publish patch",
"docs": "lerna run docs --stream", "docs": "lerna run docs --stream",
"docs:watch": "lerna run docs:watch --stream" "docs:watch": "lerna run docs:watch --stream"
@ -105,11 +107,15 @@
}, },
"testEnvironment": "jsdom", "testEnvironment": "jsdom",
"modulePathIgnorePatterns": [ "modulePathIgnorePatterns": [
"<rootDir>/packages/vec/dist/",
"<rootDir>/packages/intersect/dist/",
"<rootDir>/packages/core/dist/", "<rootDir>/packages/core/dist/",
"<rootDir>/packages/tldraw/dist/", "<rootDir>/packages/tldraw/dist/",
"<rootDir>/packages/tldraw/test-utils/" "<rootDir>/packages/tldraw/test-utils/"
], ],
"moduleNameMapper": { "moduleNameMapper": {
"@tldraw/vec": "<rootDir>/packages/vec/src",
"@tldraw/intersect": "<rootDir>/packages/intersect/src",
"@tldraw/core": "<rootDir>/packages/core/src", "@tldraw/core": "<rootDir>/packages/core/src",
"@tldraw/tldraw": "<rootDir>/packages/tldraw/src", "@tldraw/tldraw": "<rootDir>/packages/tldraw/src",
"\\~(.*)": "<rootDir>/packages/tldraw/src/$1", "\\~(.*)": "<rootDir>/packages/tldraw/src/$1",

View file

@ -55,7 +55,9 @@
"react-dom": "^17.0.2" "react-dom": "^17.0.2"
}, },
"dependencies": { "dependencies": {
"@tldraw/vec": "^0.0.86",
"@tldraw/intersect": "^0.0.86",
"@use-gesture/react": "^10.0.0-beta.24" "@use-gesture/react": "^10.0.0-beta.24"
}, },
"gitHead": "55da8880eb3d8ab5fb62b5eb7853065922c95dcf" "gitHead": "55da8880eb3d8ab5fb62b5eb7853065922c95dcf"
} }

View file

@ -1,6 +1,7 @@
/* eslint-disable */ /* eslint-disable */
const fs = require('fs') const fs = require('fs')
const esbuild = require('esbuild') const esbuild = require('esbuild')
const { gzip } = require('zlib')
const name = process.env.npm_package_name || '' const name = process.env.npm_package_name || ''
@ -25,9 +26,10 @@ async function main() {
jsxFragment: 'React.Fragment', jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.build.json', tsconfig: './tsconfig.build.json',
external: ['react', 'react-dom'], external: ['react', 'react-dom'],
metafile: true,
}) })
esbuild.buildSync({ const esmResult = esbuild.buildSync({
entryPoints: ['./src/index.ts'], entryPoints: ['./src/index.ts'],
outdir: 'dist/esm', outdir: 'dist/esm',
minify: true, minify: true,
@ -38,9 +40,23 @@ async function main() {
jsxFactory: 'React.createElement', jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment', jsxFragment: 'React.Fragment',
external: ['react', 'react-dom'], external: ['react', 'react-dom'],
metafile: true,
}) })
console.log(`${name}: Built package.`) let esmSize = 0
Object.values(esmResult.metafile.outputs).forEach((output) => {
esmSize += output.bytes
})
fs.readFile('./dist/esm/index.js', (_err, data) => {
gzip(data, (_err, result) => {
console.log(
`${name}: Built package. ${(esmSize / 1000).toFixed(2)}kb (${(
result.length / 1000
).toFixed(2)}kb minified)`
)
})
})
} catch (e) { } catch (e) {
console.log(`× ${name}: Build failed due to an error.`) console.log(`× ${name}: Build failed due to an error.`)
console.log(e) console.log(e)

View file

@ -1,5 +1,5 @@
import * as React from 'react' import * as React from 'react'
import { Vec } from '+utils' import { Vec } from '@tldraw/vec'
import type { TLShape } from '+types' import type { TLShape } from '+types'
import { Handle } from './handle' import { Handle } from './handle'

View file

@ -11,7 +11,8 @@ import type {
TLBinding, TLBinding,
TLBounds, TLBounds,
} from '+types' } from '+types'
import { Utils, Vec } from '+utils' import { Utils } from '+utils'
import { Vec } from '@tldraw/vec'
function addToShapeTree<T extends TLShape, M extends Record<string, unknown>>( function addToShapeTree<T extends TLShape, M extends Record<string, unknown>>(
shape: T, shape: T,

View file

@ -2,8 +2,8 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import * as React from 'react' import * as React from 'react'
import { useTLContext } from './useTLContext' import { useTLContext } from './useTLContext'
import Utils, { Vec } from '+utils'
import { useGesture } from '@use-gesture/react' import { useGesture } from '@use-gesture/react'
import { Vec } from '@tldraw/vec'
// Capture zoom gestures (pinches, wheels and pans) // Capture zoom gestures (pinches, wheels and pans)
export function useZoomEvents<T extends Element>(ref: React.RefObject<T>) { export function useZoomEvents<T extends Element>(ref: React.RefObject<T>) {

View file

@ -1,6 +1,7 @@
import type React from 'react' import type React from 'react'
import type { TLKeyboardInfo, TLPointerInfo } from './types' import type { TLKeyboardInfo, TLPointerInfo } from './types'
import { Vec, Utils } from './utils' import { Utils } from './utils'
import { Vec } from '@tldraw/vec'
const DOUBLE_CLICK_DURATION = 250 const DOUBLE_CLICK_DURATION = 250
@ -354,14 +355,11 @@ export class Inputs {
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent, e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent,
offset = [0, 0] offset = [0, 0]
): number[] { ): number[] {
return [ return [+e.clientX.toFixed(2) - offset[0], +e.clientY.toFixed(2) - offset[1]]
Number(e.clientX.toPrecision(5)) - offset[0],
Number(e.clientY.toPrecision(5)) - offset[1],
]
} }
static getPressure(e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent) { static getPressure(e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent) {
return 'pressure' in e ? Number(e.pressure.toPrecision(5)) || 0.5 : 0.5 return 'pressure' in e ? +e.pressure.toFixed(2) || 0.5 : 0.5
} }
static commandKey(): string { static commandKey(): string {

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import * as React from 'react' import * as React from 'react'
import { TLShapeUtil, TLShape, TLShapeProps, TLBounds, TLRenderInfo, TLTransformInfo } from '+types' import { TLShapeUtil, TLShape, TLShapeProps, TLBounds, TLRenderInfo, TLTransformInfo } from '+types'
import Utils, { Intersect } from '+utils' import Utils from '+utils'
export interface BoxShape extends TLShape { export interface BoxShape extends TLShape {
size: number[] size: number[]
@ -62,15 +62,6 @@ export class Box extends TLShapeUtil<BoxShape, SVGGElement> {
return Utils.pointInBounds(point, this.getBounds(shape)) return Utils.pointInBounds(point, this.getBounds(shape))
} }
hitTestBounds(shape: BoxShape, bounds: TLBounds) {
const rotatedCorners = Utils.getRotatedCorners(this.getBounds(shape), shape.rotation)
return (
rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) ||
Intersect.polyline.bounds(rotatedCorners, bounds).length > 0
)
}
transform(shape: BoxShape, bounds: TLBounds, _info: TLTransformInfo<BoxShape>): BoxShape { transform(shape: BoxShape, bounds: TLBounds, _info: TLTransformInfo<BoxShape>): BoxShape {
return { ...shape, point: [bounds.minX, bounds.minY] } return { ...shape, point: [bounds.minX, bounds.minY] }
} }

View file

@ -2,8 +2,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* --------------------- Primary -------------------- */ /* --------------------- Primary -------------------- */
import { Intersect, Vec } from '+utils' import { Vec } from '@tldraw/vec'
import React, { ForwardedRef } from 'react' import React, { ForwardedRef } from 'react'
import { intersectPolylineBounds } from '@tldraw/intersect'
export type Patch<T> = Partial<{ [P in keyof T]: T | Partial<T> | Patch<T[P]> }> export type Patch<T> = Partial<{ [P in keyof T]: T | Partial<T> | Patch<T[P]> }>
@ -426,7 +427,7 @@ export abstract class TLShapeUtil<T extends TLShape, E extends Element> {
point[1] < bounds.minY || point[1] < bounds.minY ||
point[1] > bounds.maxY point[1] > bounds.maxY
) )
) || Intersect.polyline.bounds(corners, bounds).length > 0 ) || intersectPolylineBounds(corners, bounds).length > 0
) )
} }
} }

View file

@ -1,7 +1,5 @@
import { Utils } from './utils' import { Utils } from './utils'
export { Intersect } from './intersect'
export { Utils } from './utils' export { Utils } from './utils'
export { Svg } from './svg' export { Svg } from './svg'
export { Vec } from './vec'
export default Utils export default Utils

View file

@ -1,680 +0,0 @@
import type { TLBounds, TLIntersection } from '../types'
import { Vec } from './vec'
import { Utils } from './utils'
/* ----------------- Start Copy Here ---------------- */
function getIntersection(message: string, ...points: number[][]): TLIntersection {
const didIntersect = points.length > 0
return { didIntersect, message, points }
}
export class Intersect {
static ray = {
// Intersect a ray with a ray.
ray(p0: number[], n0: number[], p1: number[], n1: number[]): TLIntersection {
const dx = p1[0] - p0[0]
const dy = p1[1] - p0[1]
const det = n1[0] * n0[1] - n1[1] * n0[0]
const u = (dy * n1[0] - dx * n1[1]) / det
const v = (dy * n0[0] - dx * n0[1]) / det
if (u < 0 || v < 0) return getIntersection('miss')
const m0 = n0[1] / n0[0]
const m1 = n1[1] / n1[0]
const b0 = p0[1] - m0 * p0[0]
const b1 = p1[1] - m1 * p1[0]
const x = (b1 - b0) / (m0 - m1)
const y = m0 * x + b0
return Number.isFinite(x)
? getIntersection('intersection', [x, y])
: getIntersection('parallel')
},
// Interseg a ray with a line segment.
lineSegment(origin: number[], direction: number[], a1: number[], a2: number[]): TLIntersection {
const [x, y] = origin
const [dx, dy] = direction
const [x1, y1] = a1
const [x2, y2] = a2
if (dy / dx !== (y2 - y1) / (x2 - x1)) {
const d = dx * (y2 - y1) - dy * (x2 - x1)
if (d !== 0) {
const r = ((y - y1) * (x2 - x1) - (x - x1) * (y2 - y1)) / d
const s = ((y - y1) * dx - (x - x1) * dy) / d
if (r >= 0 && s >= 0 && s <= 1) {
return getIntersection('intersection', [x + r * dx, y + r * dy])
}
}
}
return getIntersection('no intersection')
},
// Intersect a ray with a rectangle.
rectangle(
origin: number[],
direction: number[],
point: number[],
size: number[],
rotation = 0
): TLIntersection[] {
return Intersect.rectangle.ray(point, size, rotation, origin, direction)
},
// Intersect a ray with an ellipse.
ellipse(
origin: number[],
direction: number[],
center: number[],
rx: number,
ry: number,
rotation: number
): TLIntersection {
const a1 = origin
const a2 = Vec.mul(direction, 999999999)
return Intersect.lineSegment.ellipse(a1, a2, center, rx, ry, rotation)
},
// Intersect a ray with a bounding box.
bounds(
origin: number[],
direction: number[],
bounds: TLBounds,
rotation = 0
): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.ray.rectangle(origin, direction, [minX, minY], [width, height], rotation)
},
}
static lineSegment = {
// Intersect a line segment with a ray.
ray(a1: number[], a2: number[], origin: number[], direction: number[]): TLIntersection {
return Intersect.ray.lineSegment(origin, direction, a1, a2)
},
// Intersect a line segment with a line segment.
lineSegment(a1: number[], a2: number[], b1: number[], b2: number[]): TLIntersection {
const AB = Vec.sub(a1, b1)
const BV = Vec.sub(b2, b1)
const AV = Vec.sub(a2, a1)
const ua_t = BV[0] * AB[1] - BV[1] * AB[0]
const ub_t = AV[0] * AB[1] - AV[1] * AB[0]
const u_b = BV[1] * AV[0] - BV[0] * AV[1]
if (ua_t === 0 || ub_t === 0) {
return getIntersection('coincident')
}
if (u_b === 0) {
return getIntersection('parallel')
}
if (u_b !== 0) {
const ua = ua_t / u_b
const ub = ub_t / u_b
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
return getIntersection('intersection', Vec.add(a1, Vec.mul(AV, ua)))
}
}
return getIntersection('no intersection')
},
// Intersect a line segment with a rectangle
rectangle(a1: number[], a2: number[], point: number[], size: number[]): TLIntersection[] {
return Intersect.rectangle.lineSegment(point, size, a1, a2)
},
// Intersect a line segment with an arc.
arc(
a1: number[],
a2: number[],
center: number[],
radius: number,
start: number[],
end: number[]
): TLIntersection {
const sa = Vec.angle(center, start)
const ea = Vec.angle(center, end)
const ellipseTest = Intersect.ellipse.lineSegment(center, radius, radius, 0, a1, a2)
if (!ellipseTest.didIntersect) return getIntersection('No intersection')
const points = ellipseTest.points.filter((point) =>
Utils.isAngleBetween(sa, ea, Vec.angle(center, point))
)
if (points.length === 0) {
return getIntersection('No intersection')
}
return getIntersection('intersection', ...points)
},
// Intersect a line segment with a circle.
circle(a1: number[], a2: number[], c: number[], r: number): TLIntersection {
const a = (a2[0] - a1[0]) * (a2[0] - a1[0]) + (a2[1] - a1[1]) * (a2[1] - a1[1])
const b = 2 * ((a2[0] - a1[0]) * (a1[0] - c[0]) + (a2[1] - a1[1]) * (a1[1] - c[1]))
const cc =
c[0] * c[0] +
c[1] * c[1] +
a1[0] * a1[0] +
a1[1] * a1[1] -
2 * (c[0] * a1[0] + c[1] * a1[1]) -
r * r
const deter = b * b - 4 * a * cc
if (deter < 0) {
return getIntersection('outside')
}
if (deter === 0) {
return getIntersection('tangent')
}
const e = Math.sqrt(deter)
const u1 = (-b + e) / (2 * a)
const u2 = (-b - e) / (2 * a)
if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
return getIntersection('outside')
} else {
return getIntersection('inside')
}
}
const results: number[][] = []
if (0 <= u1 && u1 <= 1) results.push(Vec.lrp(a1, a2, u1))
if (0 <= u2 && u2 <= 1) results.push(Vec.lrp(a1, a2, u2))
return getIntersection('intersection', ...results)
},
// Intersect a line segment with an ellipse.
ellipse(
a1: number[],
a2: number[],
center: number[],
rx: number,
ry: number,
rotation = 0
): TLIntersection {
// If the ellipse or line segment are empty, return no tValues.
if (rx === 0 || ry === 0 || Vec.isEqual(a1, a2)) {
return getIntersection('No intersection')
}
// Get the semimajor and semiminor axes.
rx = rx < 0 ? rx : -rx
ry = ry < 0 ? ry : -ry
// Rotate points and translate so the ellipse is centered at the origin.
a1 = Vec.sub(Vec.rotWith(a1, center, -rotation), center)
a2 = Vec.sub(Vec.rotWith(a2, center, -rotation), center)
// Calculate the quadratic parameters.
const diff = Vec.sub(a2, a1)
const A = (diff[0] * diff[0]) / rx / rx + (diff[1] * diff[1]) / ry / ry
const B = (2 * a1[0] * diff[0]) / rx / rx + (2 * a1[1] * diff[1]) / ry / ry
const C = (a1[0] * a1[0]) / rx / rx + (a1[1] * a1[1]) / ry / ry - 1
// Make a list of t values (normalized points on the line where intersections occur).
const tValues: number[] = []
// Calculate the discriminant.
const discriminant = B * B - 4 * A * C
if (discriminant === 0) {
// One real solution.
tValues.push(-B / 2 / A)
} else if (discriminant > 0) {
const root = Math.sqrt(discriminant)
// Two real solutions.
tValues.push((-B + root) / 2 / A)
tValues.push((-B - root) / 2 / A)
}
// Filter to only points that are on the segment.
// Solve for points, then counter-rotate points.
const points = tValues
.filter((t) => t >= 0 && t <= 1)
.map((t) => Vec.add(center, Vec.add(a1, Vec.mul(Vec.sub(a2, a1), t))))
.map((p) => Vec.rotWith(p, center, rotation))
return getIntersection('intersection', ...points)
},
// Intersect a line segment with a bounding box.
bounds(a1: number[], a2: number[], bounds: TLBounds): TLIntersection[] {
return Intersect.bounds.lineSegment(bounds, a1, a2)
},
// Intersect a line segment with a polyline
polyline(a1: number[], a2: number[], points: number[][]): TLIntersection[] {
const intersections: TLIntersection[] = []
for (let i = 1; i < points.length + 1; i++) {
const int = Intersect.lineSegment.lineSegment(
a1,
a2,
points[i - 1],
points[i % points.length]
)
if (int) {
intersections.push(int)
}
}
return intersections
},
}
static rectangle = {
// Intersect a rectangle with a ray.
ray(
point: number[],
size: number[],
rotation: number,
origin: number[],
direction: number[]
): TLIntersection[] {
const sideIntersections = Utils.getRectangleSides(point, size, rotation).reduce<
TLIntersection[]
>((acc, [message, [a1, a2]]) => {
const intersection = Intersect.ray.lineSegment(origin, direction, a1, a2)
if (intersection) {
acc.push(getIntersection(message, ...intersection.points))
}
return acc
}, [])
return sideIntersections.filter((int) => int.didIntersect)
},
// Intersect a rectangle with a line segment.
lineSegment(point: number[], size: number[], a1: number[], a2: number[]): TLIntersection[] {
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
(acc, [message, [b1, b2]]) => {
const intersection = Intersect.lineSegment.lineSegment(a1, a2, b1, b2)
if (intersection) {
acc.push(getIntersection(message, ...intersection.points))
}
return acc
},
[]
)
return sideIntersections.filter((int) => int.didIntersect)
},
// Intersect a rectangle with a rectangle.
rectangle(
point1: number[],
size1: number[],
point2: number[],
size2: number[]
): TLIntersection[] {
const sideIntersections = Utils.getRectangleSides(point1, size1).reduce<TLIntersection[]>(
(acc, [message, [a1, a2]]) => {
const intersections = Intersect.rectangle.lineSegment(point2, size2, a1, a2)
acc.push(
...intersections.map((int) =>
getIntersection(`${message} ${int.message}`, ...int.points)
)
)
return acc
},
[]
)
return sideIntersections.filter((int) => int.didIntersect)
},
// Intersect a rectangle with an arc.
arc(
point: number[],
size: number[],
center: number[],
radius: number,
start: number[],
end: number[]
): TLIntersection[] {
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
(acc, [message, [a1, a2]]) => {
const intersection = Intersect.arc.lineSegment(center, radius, start, end, a1, a2)
if (intersection) {
acc.push({ ...intersection, message })
}
return acc
},
[]
)
return sideIntersections.filter((int) => int.didIntersect)
},
// Intersect a rectangle with a circle.
circle(point: number[], size: number[], c: number[], r: number): TLIntersection[] {
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
(acc, [message, [a1, a2]]) => {
const intersection = Intersect.lineSegment.circle(a1, a2, c, r)
if (intersection) {
acc.push({ ...intersection, message })
}
return acc
},
[]
)
return sideIntersections.filter((int) => int.didIntersect)
},
// Intersect a rectangle with an ellipse.
ellipse(
point: number[],
size: number[],
c: number[],
rx: number,
ry: number,
rotation = 0
): TLIntersection[] {
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
(acc, [message, [a1, a2]]) => {
const intersection = Intersect.lineSegment.ellipse(a1, a2, c, rx, ry, rotation)
if (intersection) {
acc.push({ ...intersection, message })
}
return acc
},
[]
)
return sideIntersections.filter((int) => int.didIntersect)
},
// Intersect a rectangle with a bounding box.
bounds(point: number[], size: number[], bounds: TLBounds): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.rectangle.rectangle(point, size, [minX, minY], [width, height])
},
// Intersect a rectangle with a polyline
polyline(point: number[], size: number[], points: number[][]): TLIntersection[] {
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
(acc, [message, [a1, a2]]) => {
const intersections = Intersect.lineSegment.polyline(a1, a2, points)
if (intersections.length > 0) {
acc.push(getIntersection(message, ...intersections.flatMap((i) => i.points)))
}
return acc
},
[]
)
return sideIntersections.filter((int) => int.didIntersect)
},
}
static arc = {
// Intersect an arc with a line segment.
lineSegment(
center: number[],
radius: number,
start: number[],
end: number[],
a1: number[],
a2: number[]
): TLIntersection {
return Intersect.lineSegment.arc(a1, a2, center, radius, start, end)
},
// Intersect an arc with a rectangle.
rectangle(
center: number[],
radius: number,
start: number[],
end: number[],
point: number[],
size: number[]
): TLIntersection[] {
return Intersect.rectangle.arc(point, size, center, radius, start, end)
},
// Intersect an arc with a bounding box.
bounds(
center: number[],
radius: number,
start: number[],
end: number[],
bounds: TLBounds
): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.arc.rectangle(center, radius, start, end, [minX, minY], [width, height])
},
}
static circle = {
// Intersect a circle with a line segment.
lineSegment(c: number[], r: number, a1: number[], a2: number[]): TLIntersection {
return Intersect.lineSegment.circle(a1, a2, c, r)
},
// Intersect a circle with a circle.
circle(c1: number[], r1: number, c2: number[], r2: number): TLIntersection {
let dx = c2[0] - c1[0],
dy = c2[1] - c1[1]
const d = Math.sqrt(dx * dx + dy * dy),
x = (d * d - r2 * r2 + r1 * r1) / (2 * d),
y = Math.sqrt(r1 * r1 - x * x)
dx /= d
dy /= d
return getIntersection(
'intersection',
[c1[0] + dx * x - dy * y, c1[1] + dy * x + dx * y],
[c1[0] + dx * x + dy * y, c1[1] + dy * x - dx * y]
)
},
// Intersect a circle with a rectangle.
rectangle(c: number[], r: number, point: number[], size: number[]): TLIntersection[] {
return Intersect.rectangle.circle(point, size, c, r)
},
// Intersect a circle with a bounding box.
bounds(c: number[], r: number, bounds: TLBounds): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.circle.rectangle(c, r, [minX, minY], [width, height])
},
}
static ellipse = {
// Intersect an ellipse with a ray.
ray(
center: number[],
rx: number,
ry: number,
rotation: number,
point: number[],
direction: number[]
): TLIntersection {
return Intersect.ray.ellipse(point, direction, center, rx, ry, rotation)
},
// Intersect an ellipse with a line segment.
lineSegment(
center: number[],
rx: number,
ry: number,
rotation = 0,
a1: number[],
a2: number[]
): TLIntersection {
if (rx === ry) {
return Intersect.lineSegment.circle(a1, a2, center, rx)
}
return Intersect.lineSegment.ellipse(a1, a2, center, rx, ry, rotation)
},
// Intersect an ellipse with a rectangle.
rectangle(
center: number[],
rx: number,
ry: number,
rotation = 0,
point: number[],
size: number[]
): TLIntersection[] {
if (rx === ry) {
return Intersect.rectangle.circle(point, size, center, rx)
}
return Intersect.rectangle.ellipse(point, size, center, rx, ry, rotation)
},
// Get an intersection between an ellipse and a second ellipse.
// Adapted from https://gist.github.com/drawable/92792f59b6ff8869d8b1
ellipse(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_c1: number[],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_rx1: number,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ry1: number,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_r1: number,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_c2: number[],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_rx2: number,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ry2: number,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_r2: number
): TLIntersection {
// TODO
return getIntersection('no intersection')
},
circle(
c: number[],
rx: number,
ry: number,
rotation: number,
c2: number[],
r2: number
): TLIntersection {
return Intersect.ellipse.ellipse(c, rx, ry, rotation, c2, r2, r2, 0)
},
// Intersect an ellipse with a bounding box.
bounds(
c: number[],
rx: number,
ry: number,
rotation: number,
bounds: TLBounds
): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.ellipse.rectangle(c, rx, ry, rotation, [minX, minY], [width, height])
},
}
static bounds = {
ray(bounds: TLBounds, origin: number[], direction: number[]): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.ray.rectangle(origin, direction, [minX, minY], [width, height])
},
lineSegment(bounds: TLBounds, a1: number[], a2: number[]): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.lineSegment.rectangle(a1, a2, [minX, minY], [width, height])
},
rectangle(bounds: TLBounds, point: number[], size: number[]): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.rectangle.rectangle(point, size, [minX, minY], [width, height])
},
bounds(bounds1: TLBounds, bounds2: TLBounds): TLIntersection[] {
return Intersect.rectangle.rectangle(
[bounds1.minX, bounds1.minY],
[bounds1.width, bounds1.height],
[bounds2.minX, bounds2.minY],
[bounds2.width, bounds2.height]
)
},
arc(
bounds: TLBounds,
center: number[],
radius: number,
start: number[],
end: number[]
): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.arc.rectangle(center, radius, start, end, [minX, minY], [width, height])
},
circle(bounds: TLBounds, c: number[], r: number): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.circle.rectangle(c, r, [minX, minY], [width, height])
},
ellipse(bounds: TLBounds, c: number[], rx: number, ry: number, rotation = 0): TLIntersection[] {
const { minX, minY, width, height } = bounds
return Intersect.ellipse.rectangle(c, rx, ry, rotation, [minX, minY], [width, height])
},
polyline(bounds: TLBounds, points: number[][]): TLIntersection[] {
return Intersect.polyline.bounds(points, bounds)
},
}
static polyline = {
// Intersect a polyline with a line segment.
lineSegment(points: number[][], a1: number[], a2: number[]): TLIntersection[] {
return Intersect.lineSegment.polyline(a1, a2, points)
},
// Interesct a polyline with a rectangle.
rectangle(points: number[][], point: number[], size: number[]): TLIntersection[] {
return Intersect.rectangle.polyline(point, size, points)
},
// Intersect a polyline with a bounding box.
bounds(points: number[][], bounds: TLBounds): TLIntersection[] {
return Intersect.rectangle.polyline(
[bounds.minX, bounds.minY],
[bounds.width, bounds.height],
points
)
},
}
}
export default Intersect

View file

@ -4,7 +4,7 @@
/* eslint-disable no-redeclare */ /* eslint-disable no-redeclare */
import type React from 'react' import type React from 'react'
import { TLBezierCurveSegment, TLBounds, TLBoundsCorner, TLBoundsEdge } from '../types' import { TLBezierCurveSegment, TLBounds, TLBoundsCorner, TLBoundsEdge } from '../types'
import vec from './vec' import { Vec } from '@tldraw/vec'
import './polyfills' import './polyfills'
import type { Patch } from '+index' import type { Patch } from '+index'
@ -204,10 +204,10 @@ export class Utils {
static getRectangleSides(point: number[], size: number[], rotation = 0): [string, number[][]][] { static getRectangleSides(point: number[], size: number[], rotation = 0): [string, number[][]][] {
const center = [point[0] + size[0] / 2, point[1] + size[1] / 2] const center = [point[0] + size[0] / 2, point[1] + size[1] / 2]
const tl = vec.rotWith(point, center, rotation) const tl = Vec.rotWith(point, center, rotation)
const tr = vec.rotWith(vec.add(point, [size[0], 0]), center, rotation) const tr = Vec.rotWith(Vec.add(point, [size[0], 0]), center, rotation)
const br = vec.rotWith(vec.add(point, size), center, rotation) const br = Vec.rotWith(Vec.add(point, size), center, rotation)
const bl = vec.rotWith(vec.add(point, [0, size[1]]), center, rotation) const bl = Vec.rotWith(Vec.add(point, [0, size[1]]), center, rotation)
return [ return [
['top', [tl, tr]], ['top', [tl, tr]],
@ -258,10 +258,10 @@ export class Utils {
P: number[], P: number[],
side: number side: number
): number[] | null { ): number[] | null {
const B = vec.lrp(C, P, 0.5) const B = Vec.lrp(C, P, 0.5)
const r1 = vec.dist(C, B) const r1 = Vec.dist(C, B)
const delta = vec.sub(B, C) const delta = Vec.sub(B, C)
const d = vec.len(delta) const d = Vec.len(delta)
if (!(d <= r + r1 && d >= Math.abs(r - r1))) { if (!(d <= r + r1 && d >= Math.abs(r - r1))) {
return null return null
@ -269,11 +269,11 @@ export class Utils {
const a = (r * r - r1 * r1 + d * d) / (2.0 * d) const a = (r * r - r1 * r1 + d * d) / (2.0 * d)
const n = 1 / d const n = 1 / d
const p = vec.add(C, vec.mul(delta, a * n)) const p = Vec.add(C, Vec.mul(delta, a * n))
const h = Math.sqrt(r * r - a * a) const h = Math.sqrt(r * r - a * a)
const k = vec.mul(vec.per(delta), h * n) const k = Vec.mul(Vec.per(delta), h * n)
return side === 0 ? vec.add(p, k) : vec.sub(p, k) return side === 0 ? Vec.add(p, k) : Vec.sub(p, k)
} }
/** /**
@ -292,8 +292,8 @@ export class Utils {
C1: number[], C1: number[],
r1: number r1: number
): number[][] | null { ): number[][] | null {
const a0 = vec.angle(C0, C1) const a0 = Vec.angle(C0, C1)
const d = vec.dist(C0, C1) const d = Vec.dist(C0, C1)
// Circles are overlapping, no tangents // Circles are overlapping, no tangents
if (d < Math.abs(r1 - r0)) { if (d < Math.abs(r1 - r0)) {
@ -319,8 +319,8 @@ export class Utils {
* @param P The point. * @param P The point.
*/ */
static getClosestPointOnCircle(C: number[], r: number, P: number[]): number[] { static getClosestPointOnCircle(C: number[], r: number, P: number[]): number[] {
const v = vec.sub(C, P) const v = Vec.sub(C, P)
return vec.sub(C, vec.mul(vec.div(v, vec.len(v)), r)) return Vec.sub(C, Vec.mul(Vec.div(v, Vec.len(v)), r))
} }
/** /**
@ -411,7 +411,7 @@ export class Utils {
* @param B * @param B
*/ */
static getSweep(C: number[], A: number[], B: number[]): number { static getSweep(C: number[], A: number[], B: number[]): number {
return Utils.angleDelta(vec.angle(C, A), vec.angle(C, B)) return Utils.angleDelta(Vec.angle(C, A), Vec.angle(C, B))
} }
/** /**
@ -766,7 +766,7 @@ export class Utils {
const len = pts.length const len = pts.length
const res: number[][] = [] // results const res: number[][] = [] // results
let t1x: number, // tension vectors let t1x: number, // tension Vectors
t2x: number, t2x: number,
t1y: number, t1y: number,
t2y: number, t2y: number,
@ -871,7 +871,7 @@ export class Utils {
* @returns * @returns
*/ */
static pointInCircle(A: number[], C: number[], r: number): boolean { static pointInCircle(A: number[], C: number[], r: number): boolean {
return vec.dist(A, C) <= r return Vec.dist(A, C) <= r
} }
/** /**
@ -887,7 +887,7 @@ export class Utils {
rotation = rotation || 0 rotation = rotation || 0
const cos = Math.cos(rotation) const cos = Math.cos(rotation)
const sin = Math.sin(rotation) const sin = Math.sin(rotation)
const delta = vec.sub(A, C) const delta = Vec.sub(A, C)
const tdx = cos * delta[0] + sin * delta[1] const tdx = cos * delta[0] + sin * delta[1]
const tdy = sin * delta[0] - cos * delta[1] const tdy = sin * delta[0] - cos * delta[1]
@ -914,10 +914,10 @@ export class Utils {
points.forEach((a, i) => { points.forEach((a, i) => {
const b = points[(i + 1) % points.length] const b = points[(i + 1) % points.length]
if (a[1] <= p[1]) { if (a[1] <= p[1]) {
if (b[1] > p[1] && vec.cross(a, b, p) > 0) { if (b[1] > p[1] && Vec.cross(a, b, p) > 0) {
wn += 1 wn += 1
} }
} else if (b[1] <= p[1] && vec.cross(a, b, p) < 0) { } else if (b[1] <= p[1] && Vec.cross(a, b, p) < 0) {
wn -= 1 wn -= 1
} }
}) })
@ -1024,7 +1024,7 @@ export class Utils {
if (rotation !== 0) { if (rotation !== 0) {
return Utils.getBoundsFromPoints( return Utils.getBoundsFromPoints(
points.map((pt) => vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], rotation)) points.map((pt) => Vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], rotation))
) )
} }
@ -1074,8 +1074,8 @@ export class Utils {
* @param rotation * @param rotation
*/ */
static rotateBounds(bounds: TLBounds, center: number[], rotation: number): TLBounds { static rotateBounds(bounds: TLBounds, center: number[], rotation: number): TLBounds {
const [minX, minY] = vec.rotWith([bounds.minX, bounds.minY], center, rotation) const [minX, minY] = Vec.rotWith([bounds.minX, bounds.minY], center, rotation)
const [maxX, maxY] = vec.rotWith([bounds.maxX, bounds.maxY], center, rotation) const [maxX, maxY] = Vec.rotWith([bounds.maxX, bounds.maxY], center, rotation)
return { return {
minX, minX,
@ -1158,7 +1158,7 @@ export class Utils {
[b.maxX, b.minY], [b.maxX, b.minY],
[b.maxX, b.maxY], [b.maxX, b.maxY],
[b.minX, b.maxY], [b.minX, b.maxY],
].map((point) => vec.rotWith(point, center, rotation)) ].map((point) => Vec.rotWith(point, center, rotation))
} }
static getTransformedBoundingBox( static getTransformedBoundingBox(
@ -1192,7 +1192,7 @@ export class Utils {
// Counter rotate the delta. This lets us make changes as if // Counter rotate the delta. This lets us make changes as if
// the (possibly rotated) boxes were axis aligned. // the (possibly rotated) boxes were axis aligned.
const [dx, dy] = vec.rot(delta, -rotation) const [dx, dy] = Vec.rot(delta, -rotation)
/* /*
1. Delta 1. Delta
@ -1299,67 +1299,67 @@ new box's aspect ratio matches the original aspect ratio.
/* /*
3. Rotation 3. Rotation
If the bounds are rotated, get a vector from the rotated anchor If the bounds are rotated, get a Vector from the rotated anchor
corner in the inital bounds to the rotated anchor corner in the corner in the inital bounds to the rotated anchor corner in the
result's bounds. Subtract this vector from the result's corners, result's bounds. Subtract this Vector from the result's corners,
so that the two anchor points (initial and result) will be equal. so that the two anchor points (initial and result) will be equal.
*/ */
if (rotation % (Math.PI * 2) !== 0) { if (rotation % (Math.PI * 2) !== 0) {
let cv = [0, 0] let cv = [0, 0]
const c0 = vec.med([ax0, ay0], [ax1, ay1]) const c0 = Vec.med([ax0, ay0], [ax1, ay1])
const c1 = vec.med([bx0, by0], [bx1, by1]) const c1 = Vec.med([bx0, by0], [bx1, by1])
switch (handle) { switch (handle) {
case TLBoundsCorner.TopLeft: { case TLBoundsCorner.TopLeft: {
cv = vec.sub(vec.rotWith([bx1, by1], c1, rotation), vec.rotWith([ax1, ay1], c0, rotation)) cv = Vec.sub(Vec.rotWith([bx1, by1], c1, rotation), Vec.rotWith([ax1, ay1], c0, rotation))
break break
} }
case TLBoundsCorner.TopRight: { case TLBoundsCorner.TopRight: {
cv = vec.sub(vec.rotWith([bx0, by1], c1, rotation), vec.rotWith([ax0, ay1], c0, rotation)) cv = Vec.sub(Vec.rotWith([bx0, by1], c1, rotation), Vec.rotWith([ax0, ay1], c0, rotation))
break break
} }
case TLBoundsCorner.BottomRight: { case TLBoundsCorner.BottomRight: {
cv = vec.sub(vec.rotWith([bx0, by0], c1, rotation), vec.rotWith([ax0, ay0], c0, rotation)) cv = Vec.sub(Vec.rotWith([bx0, by0], c1, rotation), Vec.rotWith([ax0, ay0], c0, rotation))
break break
} }
case TLBoundsCorner.BottomLeft: { case TLBoundsCorner.BottomLeft: {
cv = vec.sub(vec.rotWith([bx1, by0], c1, rotation), vec.rotWith([ax1, ay0], c0, rotation)) cv = Vec.sub(Vec.rotWith([bx1, by0], c1, rotation), Vec.rotWith([ax1, ay0], c0, rotation))
break break
} }
case TLBoundsEdge.Top: { case TLBoundsEdge.Top: {
cv = vec.sub( cv = Vec.sub(
vec.rotWith(vec.med([bx0, by1], [bx1, by1]), c1, rotation), Vec.rotWith(Vec.med([bx0, by1], [bx1, by1]), c1, rotation),
vec.rotWith(vec.med([ax0, ay1], [ax1, ay1]), c0, rotation) Vec.rotWith(Vec.med([ax0, ay1], [ax1, ay1]), c0, rotation)
) )
break break
} }
case TLBoundsEdge.Left: { case TLBoundsEdge.Left: {
cv = vec.sub( cv = Vec.sub(
vec.rotWith(vec.med([bx1, by0], [bx1, by1]), c1, rotation), Vec.rotWith(Vec.med([bx1, by0], [bx1, by1]), c1, rotation),
vec.rotWith(vec.med([ax1, ay0], [ax1, ay1]), c0, rotation) Vec.rotWith(Vec.med([ax1, ay0], [ax1, ay1]), c0, rotation)
) )
break break
} }
case TLBoundsEdge.Bottom: { case TLBoundsEdge.Bottom: {
cv = vec.sub( cv = Vec.sub(
vec.rotWith(vec.med([bx0, by0], [bx1, by0]), c1, rotation), Vec.rotWith(Vec.med([bx0, by0], [bx1, by0]), c1, rotation),
vec.rotWith(vec.med([ax0, ay0], [ax1, ay0]), c0, rotation) Vec.rotWith(Vec.med([ax0, ay0], [ax1, ay0]), c0, rotation)
) )
break break
} }
case TLBoundsEdge.Right: { case TLBoundsEdge.Right: {
cv = vec.sub( cv = Vec.sub(
vec.rotWith(vec.med([bx0, by0], [bx0, by1]), c1, rotation), Vec.rotWith(Vec.med([bx0, by0], [bx0, by1]), c1, rotation),
vec.rotWith(vec.med([ax0, ay0], [ax0, ay1]), c0, rotation) Vec.rotWith(Vec.med([ax0, ay0], [ax0, ay1]), c0, rotation)
) )
break break
} }
} }
;[bx0, by0] = vec.sub([bx0, by0], cv) ;[bx0, by0] = Vec.sub([bx0, by0], cv)
;[bx1, by1] = vec.sub([bx1, by1], cv) ;[bx1, by1] = Vec.sub([bx1, by1], cv)
} }
/* /*
@ -1500,10 +1500,10 @@ left past the initial left edge) then swap points on that axis.
* @param rotation * @param rotation
*/ */
static getRotatedSize(size: number[], rotation: number): number[] { static getRotatedSize(size: number[], rotation: number): number[] {
const center = vec.div(size, 2) const center = Vec.div(size, 2)
const points = [[0, 0], [size[0], 0], size, [0, size[1]]].map((point) => const points = [[0, 0], [size[0], 0], size, [0, size[1]]].map((point) =>
vec.rotWith(point, center, rotation) Vec.rotWith(point, center, rotation)
) )
const bounds = Utils.getBoundsFromPoints(points) const bounds = Utils.getBoundsFromPoints(points)
@ -1534,7 +1534,7 @@ left past the initial left edge) then swap points on that axis.
*/ */
static removeDuplicatePoints(points: number[][]) { static removeDuplicatePoints(points: number[][]) {
return points.reduce<number[][]>((acc, pt, i) => { return points.reduce<number[][]>((acc, pt, i) => {
if (i === 0 || !vec.isEqual(pt, acc[i - 1])) { if (i === 0 || !Vec.isEqual(pt, acc[i - 1])) {
acc.push(pt) acc.push(pt)
} }
return acc return acc

View file

@ -3,13 +3,17 @@
"include": ["src"], "include": ["src"],
"exclude": ["node_modules", "dist", "docs"], "exclude": ["node_modules", "dist", "docs"],
"compilerOptions": { "compilerOptions": {
"composite": true,
"emitDeclarationOnly": true,
"rootDir": "src",
"outDir": "./dist/types", "outDir": "./dist/types",
"rootDir": "src",
"baseUrl": "src", "baseUrl": "src",
"paths": { "paths": {
"+*": ["./*"] "+*": ["./*"]
} }
} },
"references": [
{
"path": "../../packages/intersect"
},
{ "path": "../../packages/vec" }
]
} }

View file

@ -17,6 +17,7 @@
"README.md", "README.md",
"src" "src"
], ],
"sideEffects": false,
"dependencies": { "dependencies": {
"@tldraw/tldraw": "^0.0.86", "@tldraw/tldraw": "^0.0.86",
"idb": "^6.1.2", "idb": "^6.1.2",
@ -37,4 +38,4 @@
"typescript": "4.2.3" "typescript": "4.2.3"
}, },
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
} }

View file

@ -3,7 +3,8 @@
"include": ["src"], "include": ["src"],
"exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts", "dist"], "exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts", "dist"],
"compilerOptions": { "compilerOptions": {
"composite": true "composite": true,
"rootDir": "."
}, },
"references": [ "references": [
{ {

View file

@ -0,0 +1,3 @@
# Dev Server
Dev server with fast refresh.

View file

@ -0,0 +1,57 @@
{
"name": "@tldraw/intersect",
"version": "0.0.86",
"private": false,
"description": "A tiny little drawing app (intersect)",
"author": "@steveruizok",
"repository": {
"type": "git",
"url": "git+https://github.com/tldraw/tldraw.git",
"directory": "packages/intersect"
},
"license": "MIT",
"keywords": [],
"files": [
"dist/**/*"
],
"sideEffects": false,
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"typings": "./dist/types/index.d.ts",
"scripts": {
"start:pre": "node scripts/pre-dev && yarn types:pre",
"start": "node scripts/dev & yarn types:dev",
"build": "node scripts/build && yarn types:build",
"types:pre": "tsc",
"types:dev": "tsc --watch",
"types:build": "tsc --project tsconfig.build.json",
"lint": "eslint src/ --ext .ts,.tsx",
"clean": "rm -rf dist",
"ts-node": "ts-node",
"docs": "typedoc",
"docs:watch": "typedoc --watch"
},
"devDependencies": {
"@babel/core": "^7.15.5",
"@babel/preset-env": "^7.15.4",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.10",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"esbuild": "^0.12.24",
"eslint": "^7.32.0",
"lerna": "^4.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typedoc": "^0.21.9",
"typescript": "^4.4.2"
},
"peerDependencies": {},
"dependencies": {
"@tldraw/vec": "^0.0.86"
},
"gitHead": "55da8880eb3d8ab5fb62b5eb7853065922c95dcf"
}

View file

@ -0,0 +1,50 @@
/* eslint-disable */
const fs = require('fs')
const esbuild = require('esbuild')
const name = process.env.npm_package_name || ''
async function main() {
if (fs.existsSync('./dist')) {
fs.rmSync('./dist', { recursive: true }, (e) => {
if (e) {
throw e
}
})
}
try {
esbuild.buildSync({
entryPoints: ['./src/index.ts'],
outdir: 'dist/cjs',
minify: true,
bundle: true,
format: 'cjs',
target: 'es6',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.build.json',
external: ['react', 'react-dom'],
})
esbuild.buildSync({
entryPoints: ['./src/index.ts'],
outdir: 'dist/esm',
minify: true,
bundle: true,
format: 'esm',
target: 'es6',
tsconfig: './tsconfig.build.json',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
external: ['react', 'react-dom'],
})
console.log(`${name}: Built package.`)
} catch (e) {
console.log(`× ${name}: Build failed due to an error.`)
console.log(e)
}
}
main()

View file

@ -0,0 +1,31 @@
/* eslint-disable */
const esbuild = require('esbuild')
const name = process.env.npm_package_name || ''
async function main() {
esbuild.build({
entryPoints: ['./src/index.ts'],
outdir: 'dist/cjs',
minify: false,
bundle: true,
format: 'cjs',
target: 'es6',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.json',
external: ['react', 'react-dom'],
incremental: true,
watch: {
onRebuild(error) {
if (error) {
console.log(`× ${name}: An error in prevented the rebuild.`)
return
}
console.log(`${name}: Rebuilt.`)
},
},
})
}
main()

View file

@ -0,0 +1,28 @@
/* eslint-disable */
const fs = require('fs')
const esbuild = require('esbuild')
async function main() {
if (fs.existsSync('./dist')) {
fs.rmSync('./dist', { recursive: true }, (e) => {
if (e) {
throw e
}
})
}
esbuild.build({
entryPoints: ['./src/index.ts'],
outdir: 'dist/cjs',
minify: false,
bundle: true,
format: 'cjs',
target: 'es6',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.json',
external: ['react', 'react-dom'],
})
}
main()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"exclude": [
"node_modules",
"**/*.test.tsx",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.spec.ts",
"src/test",
"dist",
"docs"
],
"compilerOptions": {
"composite": false,
"incremental": false,
"declarationMap": false,
"sourceMap": false
}
}

View file

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"exclude": ["node_modules", "dist", "docs"],
"compilerOptions": {
"outDir": "./dist/types",
"rootDir": "src",
"baseUrl": "src"
},
"references": [{ "path": "../../packages/vec" }]
}

View file

@ -65,9 +65,11 @@
"@radix-ui/react-tooltip": "^0.0.20", "@radix-ui/react-tooltip": "^0.0.20",
"@stitches/react": "^1.0.0", "@stitches/react": "^1.0.0",
"@tldraw/core": "^0.0.86", "@tldraw/core": "^0.0.86",
"@tldraw/vec": "^0.0.86",
"@tldraw/intersect": "^0.0.86",
"perfect-freehand": "^0.5.3", "perfect-freehand": "^0.5.3",
"react-hotkeys-hook": "^3.4.0", "react-hotkeys-hook": "^3.4.0",
"rko": "^0.5.25" "rko": "^0.5.25"
}, },
"gitHead": "55da8880eb3d8ab5fb62b5eb7853065922c95dcf" "gitHead": "55da8880eb3d8ab5fb62b5eb7853065922c95dcf"
} }

View file

@ -1,6 +1,7 @@
/* eslint-disable */ /* eslint-disable */
const fs = require('fs') const fs = require('fs')
const esbuild = require('esbuild') const esbuild = require('esbuild')
const { gzip } = require('zlib')
const name = process.env.npm_package_name || '' const name = process.env.npm_package_name || ''
@ -25,9 +26,10 @@ async function main() {
jsxFragment: 'React.Fragment', jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.json', tsconfig: './tsconfig.json',
external: ['react', 'react-dom'], external: ['react', 'react-dom'],
metafile: true,
}) })
esbuild.buildSync({ const esmResult = esbuild.buildSync({
entryPoints: ['./src/index.ts'], entryPoints: ['./src/index.ts'],
outdir: 'dist/esm', outdir: 'dist/esm',
minify: true, minify: true,
@ -38,6 +40,22 @@ async function main() {
jsxFactory: 'React.createElement', jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment', jsxFragment: 'React.Fragment',
external: ['react', 'react-dom'], external: ['react', 'react-dom'],
metafile: true,
})
let esmSize = 0
Object.values(esmResult.metafile.outputs).forEach((output) => {
esmSize += output.bytes
})
fs.readFile('./dist/esm/index.js', (_err, data) => {
gzip(data, (_err, result) => {
console.log(
`${name}: Built package. ${(esmSize / 1000).toFixed(2)}kb (${(
result.length / 1000
).toFixed(2)}kb minified)`
)
})
}) })
console.log(`${name}: Built package.`) console.log(`${name}: Built package.`)

View file

@ -3,13 +3,13 @@ import {
SVGContainer, SVGContainer,
TLBounds, TLBounds,
Utils, Utils,
Vec,
TLTransformInfo, TLTransformInfo,
Intersect,
TLHandle, TLHandle,
TLPointerInfo, TLPointerInfo,
TLShapeProps, TLShapeProps,
} from '@tldraw/core' } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import getStroke from 'perfect-freehand' import getStroke from 'perfect-freehand'
import { defaultStyle, getPerfectDashProps, getShapeStyle } from '~shape/shape-styles' import { defaultStyle, getPerfectDashProps, getShapeStyle } from '~shape/shape-styles'
import { import {
@ -22,6 +22,14 @@ import {
TLDrawShape, TLDrawShape,
ArrowBinding, ArrowBinding,
} from '~types' } from '~types'
import {
intersectArcBounds,
intersectCircleCircle,
intersectCircleLineSegment,
intersectLineSegmentBounds,
intersectRayBounds,
intersectRayEllipse,
} from '@tldraw/intersect'
export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGSVGElement> { export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGSVGElement> {
type = TLDrawShapeType.Arrow as const type = TLDrawShapeType.Arrow as const
@ -299,12 +307,12 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGSVGElement> {
} }
if (Vec.isEqual(Vec.med(start.point, end.point), bend.point)) { if (Vec.isEqual(Vec.med(start.point, end.point), bend.point)) {
return Intersect.lineSegment.bounds(sp, ep, brushBounds).length > 0 return intersectLineSegmentBounds(sp, ep, brushBounds).length > 0
} else { } else {
const [cx, cy, r] = getCtp(shape) const [cx, cy, r] = getCtp(shape)
const cp = Vec.add(shape.point, [cx, cy]) const cp = Vec.add(shape.point, [cx, cy])
return Intersect.arc.bounds(cp, r, sp, ep, brushBounds).length > 0 return intersectArcBounds(cp, r, sp, ep, brushBounds).length > 0
} }
} }
@ -430,15 +438,13 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGSVGElement> {
const direction = Vec.uni(Vec.sub(Vec.add(anchor, shape.point), origin)) const direction = Vec.uni(Vec.sub(Vec.add(anchor, shape.point), origin))
if ([TLDrawShapeType.Rectangle, TLDrawShapeType.Text].includes(target.type)) { if ([TLDrawShapeType.Rectangle, TLDrawShapeType.Text].includes(target.type)) {
let hits = Intersect.ray let hits = intersectRayBounds(origin, direction, intersectBounds, target.rotation)
.bounds(origin, direction, intersectBounds, target.rotation)
.filter((int) => int.didIntersect) .filter((int) => int.didIntersect)
.map((int) => int.points[0]) .map((int) => int.points[0])
.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin)) .sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))
if (hits.length < 2) { if (hits.length < 2) {
hits = Intersect.ray hits = intersectRayBounds(origin, Vec.neg(direction), intersectBounds)
.bounds(origin, Vec.neg(direction), intersectBounds)
.filter((int) => int.didIntersect) .filter((int) => int.didIntersect)
.map((int) => int.points[0]) .map((int) => int.points[0])
.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin)) .sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))
@ -451,16 +457,14 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGSVGElement> {
handlePoint = Vec.sub(hits[0], shape.point) handlePoint = Vec.sub(hits[0], shape.point)
} else if (target.type === TLDrawShapeType.Ellipse) { } else if (target.type === TLDrawShapeType.Ellipse) {
const hits = Intersect.ray const hits = intersectRayEllipse(
.ellipse( origin,
origin, direction,
direction, center,
center, target.radius[0] + binding.distance,
target.radius[0] + binding.distance, target.radius[1] + binding.distance,
target.radius[1] + binding.distance, target.rotation || 0
target.rotation || 0 ).points.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))
)
.points.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))
if (!hits[0]) { if (!hits[0]) {
console.warn('No intersections') console.warn('No intersections')
@ -701,7 +705,7 @@ function getCurvedArrowHeadPoints(
r2: number, r2: number,
sweep: boolean sweep: boolean
) { ) {
const ints = Intersect.circle.circle(A, r1 * 0.618, C, r2).points const ints = intersectCircleCircle(A, r1 * 0.618, C, r2).points
if (!ints) { if (!ints) {
console.warn('Could not find an intersection for the arrow head.') console.warn('Could not find an intersection for the arrow head.')
return { left: A, right: A } return { left: A, right: A }
@ -714,7 +718,7 @@ function getCurvedArrowHeadPoints(
} }
function getStraightArrowHeadPoints(A: number[], B: number[], r: number) { function getStraightArrowHeadPoints(A: number[], B: number[], r: number) {
const ints = Intersect.circle.lineSegment(A, r, A, B).points const ints = intersectCircleLineSegment(A, r, A, B).points
if (!ints) { if (!ints) {
console.warn('Could not find an intersection for the arrow head.') console.warn('Could not find an intersection for the arrow head.')
return { left: A, right: A } return { left: A, right: A }

View file

@ -1,5 +1,7 @@
import * as React from 'react' import * as React from 'react'
import { SVGContainer, TLBounds, Utils, Vec, TLTransformInfo, Intersect } from '@tldraw/core' import { SVGContainer, TLBounds, Utils, TLTransformInfo } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { intersectBoundsBounds, intersectBoundsPolyline } from '@tldraw/intersect'
import getStroke, { getStrokePoints } from 'perfect-freehand' import getStroke, { getStrokePoints } from 'perfect-freehand'
import { defaultStyle, getShapeStyle } from '~shape/shape-styles' import { defaultStyle, getShapeStyle } from '~shape/shape-styles'
import { import {
@ -202,10 +204,10 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGSVGElement> {
return ( return (
Utils.boundsContain(brushBounds, bounds) || Utils.boundsContain(brushBounds, bounds) ||
((Utils.boundsContain(bounds, brushBounds) || ((Utils.boundsContain(bounds, brushBounds) ||
Intersect.bounds.bounds(bounds, brushBounds).length > 0) && intersectBoundsBounds(bounds, brushBounds).length > 0) &&
Intersect.polyline.bounds( intersectBoundsPolyline(
shape.points, Utils.translateBounds(brushBounds, Vec.neg(shape.point)),
Utils.translateBounds(brushBounds, Vec.neg(shape.point)) shape.points
).length > 0) ).length > 0)
) )
} }
@ -220,7 +222,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGSVGElement> {
return ( return (
Utils.boundsContain(brushBounds, rBounds) || Utils.boundsContain(brushBounds, rBounds) ||
Intersect.bounds.polyline( intersectBoundsPolyline(
Utils.translateBounds(brushBounds, Vec.neg(shape.point)), Utils.translateBounds(brushBounds, Vec.neg(shape.point)),
rotatedBounds rotatedBounds
).length > 0 ).length > 0

View file

@ -1,13 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { import { SVGContainer, Utils, TLTransformInfo, TLBounds, TLShapeProps } from '@tldraw/core'
SVGContainer, import { Vec } from '@tldraw/vec'
Utils,
TLTransformInfo,
TLBounds,
Intersect,
TLShapeProps,
Vec,
} from '@tldraw/core'
import { import {
ArrowShape, ArrowShape,
DashStyle, DashStyle,
@ -18,6 +11,11 @@ import {
} from '~types' } from '~types'
import { defaultStyle, getPerfectDashProps, getShapeStyle } from '~shape/shape-styles' import { defaultStyle, getPerfectDashProps, getShapeStyle } from '~shape/shape-styles'
import getStroke from 'perfect-freehand' import getStroke from 'perfect-freehand'
import {
intersectLineSegmentEllipse,
intersectPolylineBounds,
intersectRayEllipse,
} from '@tldraw/intersect'
// TODO // TODO
// [ ] Improve indicator shape for drawn shapes // [ ] Improve indicator shape for drawn shapes
@ -181,7 +179,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGSVGElement> {
return ( return (
rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) || rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) ||
Intersect.polyline.bounds(rotatedCorners, bounds).length > 0 intersectPolylineBounds(rotatedCorners, bounds).length > 0
) )
} }
@ -226,15 +224,24 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGSVGElement> {
// .map((int) => int.points[0]) // .map((int) => int.points[0])
// .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0] // .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]
let intersection = Intersect.ray let intersection = intersectRayEllipse(
.ellipse(origin, direction, center, shape.radius[0], shape.radius[1], shape.rotation || 0) origin,
direction,
.points.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))[0] center,
shape.radius[0],
shape.radius[1],
shape.rotation || 0
).points.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))[0]
if (!intersection) { if (!intersection) {
intersection = Intersect.lineSegment intersection = intersectLineSegmentEllipse(
.ellipse(point, center, center, shape.radius[0], shape.radius[1], shape.rotation || 0) point,
.points.sort((a, b) => Vec.dist(a, point) - Vec.dist(b, point))[0] center,
center,
shape.radius[0],
shape.radius[1],
shape.rotation || 0
).points.sort((a, b) => Vec.dist(a, point) - Vec.dist(b, point))[0]
} }
// The anchor is a point between the handle and the intersection // The anchor is a point between the handle and the intersection
@ -258,7 +265,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGSVGElement> {
distance = 16 distance = 16
} else { } else {
// Find the distance between the point and the ellipse // Find the distance between the point and the ellipse
const innerIntersection = Intersect.lineSegment.ellipse( const innerIntersection = intersectLineSegmentEllipse(
point, point,
center, center,
center, center,

View file

@ -1,5 +1,7 @@
import * as React from 'react' import * as React from 'react'
import { SVGContainer, TLBounds, Utils, Vec, Intersect, TLShapeProps } from '@tldraw/core' import { SVGContainer, TLBounds, Utils, TLShapeProps } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { intersectRayBounds, intersectPolylineBounds } from '@tldraw/intersect'
import { defaultStyle, getPerfectDashProps } from '~shape/shape-styles' import { defaultStyle, getPerfectDashProps } from '~shape/shape-styles'
import { import {
GroupShape, GroupShape,
@ -179,8 +181,7 @@ export class Group extends TLDrawShapeUtil<GroupShape, SVGSVGElement> {
// origin through point and expanded bounds. // origin through point and expanded bounds.
// TODO: Make this a ray vs rounded rect intersection // TODO: Make this a ray vs rounded rect intersection
const intersection = Intersect.ray const intersection = intersectRayBounds(origin, direction, expandedBounds)
.bounds(origin, direction, expandedBounds)
.filter((int) => int.didIntersect) .filter((int) => int.didIntersect)
.map((int) => int.points[0]) .map((int) => int.points[0])
.sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0] .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]
@ -228,7 +229,7 @@ export class Group extends TLDrawShapeUtil<GroupShape, SVGSVGElement> {
return ( return (
rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) || rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) ||
Intersect.polyline.bounds(rotatedCorners, bounds).length > 0 intersectPolylineBounds(rotatedCorners, bounds).length > 0
) )
} }

View file

@ -1,15 +1,9 @@
import * as React from 'react' import * as React from 'react'
import { import { TLBounds, Utils, TLTransformInfo, TLShapeProps, HTMLContainer } from '@tldraw/core'
TLBounds, import { Vec } from '@tldraw/vec'
Utils,
Vec,
TLTransformInfo,
Intersect,
TLShapeProps,
HTMLContainer,
} from '@tldraw/core'
import { defaultStyle, getShapeStyle } from '~shape/shape-styles' import { defaultStyle, getShapeStyle } from '~shape/shape-styles'
import { PostItShape, TLDrawShapeUtil, TLDrawShapeType, TLDrawToolType, ArrowShape } from '~types' import { PostItShape, TLDrawShapeUtil, TLDrawShapeType, TLDrawToolType, ArrowShape } from '~types'
import { intersectPolylineBounds, intersectRayBounds } from '@tldraw/intersect'
// TODO // TODO
// [ ] - Make sure that fill does not extend drawn shape at corners // [ ] - Make sure that fill does not extend drawn shape at corners
@ -154,8 +148,7 @@ export class PostIt extends TLDrawShapeUtil<PostItShape, HTMLDivElement> {
// origin through point and expanded bounds. // origin through point and expanded bounds.
// TODO: Make this a ray vs rounded rect intersection // TODO: Make this a ray vs rounded rect intersection
const intersection = Intersect.ray const intersection = intersectRayBounds(origin, direction, expandedBounds)
.bounds(origin, direction, expandedBounds)
.filter((int) => int.didIntersect) .filter((int) => int.didIntersect)
.map((int) => int.points[0]) .map((int) => int.points[0])
.sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0] .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]
@ -198,7 +191,7 @@ export class PostIt extends TLDrawShapeUtil<PostItShape, HTMLDivElement> {
return ( return (
rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) || rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) ||
Intersect.polyline.bounds(rotatedCorners, bounds).length > 0 intersectPolylineBounds(rotatedCorners, bounds).length > 0
) )
} }

View file

@ -1,14 +1,7 @@
import * as React from 'react' import * as React from 'react'
import { import { TLBounds, Utils, TLTransformInfo, TLShapeProps, SVGContainer } from '@tldraw/core'
TLBounds, import { intersectRayBounds } from '@tldraw/intersect'
Utils, import { Vec } from '@tldraw/vec'
Vec,
TLTransformInfo,
Intersect,
TLShapeProps,
SVGContainer,
HTMLContainer,
} from '@tldraw/core'
import getStroke from 'perfect-freehand' import getStroke from 'perfect-freehand'
import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles' import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles'
import { import {
@ -234,8 +227,7 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGSVGElement> {
// origin through point and expanded bounds. // origin through point and expanded bounds.
// TODO: Make this a ray vs rounded rect intersection // TODO: Make this a ray vs rounded rect intersection
const intersection = Intersect.ray const intersection = intersectRayBounds(origin, direction, expandedBounds)
.bounds(origin, direction, expandedBounds)
.filter((int) => int.didIntersect) .filter((int) => int.didIntersect)
.map((int) => int.points[0]) .map((int) => int.points[0])
.sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0] .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]
@ -278,12 +270,7 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGSVGElement> {
bounds: TLBounds, bounds: TLBounds,
{ initialShape, transformOrigin, scaleX, scaleY }: TLTransformInfo<RectangleShape> { initialShape, transformOrigin, scaleX, scaleY }: TLTransformInfo<RectangleShape>
) { ) {
if (!shape.rotation && !shape.isAspectRatioLocked) { if (shape.rotation || shape.isAspectRatioLocked) {
return {
point: Vec.round([bounds.minX, bounds.minY]),
size: Vec.round([bounds.width, bounds.height]),
}
} else {
const size = Vec.round( const size = Vec.round(
Vec.mul(initialShape.size, Math.min(Math.abs(scaleX), Math.abs(scaleY))) Vec.mul(initialShape.size, Math.min(Math.abs(scaleX), Math.abs(scaleY)))
) )
@ -309,6 +296,11 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGSVGElement> {
point, point,
rotation, rotation,
} }
} else {
return {
point: Vec.round([bounds.minX, bounds.minY]),
size: Vec.round([bounds.width, bounds.height]),
}
} }
} }

View file

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as React from 'react' import * as React from 'react'
import { HTMLContainer, TLBounds, Utils, Vec, TLTransformInfo, Intersect } from '@tldraw/core' import { HTMLContainer, TLBounds, Utils, TLTransformInfo } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { getShapeStyle, getFontStyle, defaultStyle } from '~shape/shape-styles' import { getShapeStyle, getFontStyle, defaultStyle } from '~shape/shape-styles'
import { import {
TextShape, TextShape,
@ -12,6 +13,7 @@ import {
} from '~types' } from '~types'
import styled from '~styles' import styled from '~styles'
import TextAreaUtils from './text-utils' import TextAreaUtils from './text-utils'
import { intersectPolylineBounds, intersectRayBounds } from '@tldraw/intersect'
const LETTER_SPACING = -1.5 const LETTER_SPACING = -1.5
@ -255,7 +257,7 @@ export class Text extends TLDrawShapeUtil<TextShape, HTMLDivElement> {
return ( return (
rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) || rotatedCorners.every((point) => Utils.pointInBounds(point, bounds)) ||
Intersect.polyline.bounds(rotatedCorners, bounds).length > 0 intersectPolylineBounds(rotatedCorners, bounds).length > 0
) )
} }
@ -373,8 +375,7 @@ export class Text extends TLDrawShapeUtil<TextShape, HTMLDivElement> {
// origin through point and expanded bounds. // origin through point and expanded bounds.
// TODO: Make this a ray vs rounded rect intersection // TODO: Make this a ray vs rounded rect intersection
const intersection = Intersect.ray const intersection = intersectRayBounds(origin, direction, expandedBounds)
.bounds(origin, direction, expandedBounds)
.filter((int) => int.didIntersect) .filter((int) => int.didIntersect)
.map((int) => int.points[0]) .map((int) => int.points[0])
.sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0] .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]

View file

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Utils, Vec } from '@tldraw/core' import { Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'
import type { Data, PagePartial, TLDrawCommand } from '~types' import type { Data, PagePartial, TLDrawCommand } from '~types'

View file

@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { ArrowShape, Data, PagePartial, TLDrawCommand, TLDrawShape } from '~types' import type { ArrowShape, Data, PagePartial, TLDrawCommand, TLDrawShape } from '~types'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'
import { Utils, Vec } from '@tldraw/core' import { Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
export function moveToPage( export function moveToPage(
data: Data, data: Data,

View file

@ -1,4 +1,5 @@
import { Utils, Vec } from '@tldraw/core' import { Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import type { TLDrawCommand, Data } from '~types' import type { TLDrawCommand, Data } from '~types'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'

View file

@ -1,4 +1,4 @@
import { Vec } from '@tldraw/core' import { Vec } from '@tldraw/vec'
import type { Data, TLDrawCommand, PagePartial } from '~types' import type { Data, TLDrawCommand, PagePartial } from '~types'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'

View file

@ -7,7 +7,8 @@ import {
Session, Session,
TLDrawStatus, TLDrawStatus,
} from '~types' } from '~types'
import { Vec, Utils, TLHandle } from '@tldraw/core' import { Vec } from '@tldraw/vec'
import { Utils } from '@tldraw/core'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'
export class ArrowSession implements Session { export class ArrowSession implements Session {

View file

@ -1,4 +1,5 @@
import { brushUpdater, Utils, Vec } from '@tldraw/core' import { brushUpdater, Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { Data, Session, TLDrawPatch, TLDrawStatus } from '~types' import { Data, Session, TLDrawPatch, TLDrawStatus } from '~types'
import { getShapeUtils } from '~shape' import { getShapeUtils } from '~shape'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'

View file

@ -1,4 +1,5 @@
import { Utils, Vec } from '@tldraw/core' import { Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { Data, DrawShape, Session, TLDrawStatus } from '~types' import { Data, DrawShape, Session, TLDrawStatus } from '~types'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'

View file

@ -1,4 +1,4 @@
import { Vec } from '@tldraw/core' import { Vec } from '@tldraw/vec'
import { ShapesWithProp, TLDrawStatus } from '~types' import { ShapesWithProp, TLDrawStatus } from '~types'
import type { Session } from '~types' import type { Session } from '~types'
import type { Data } from '~types' import type { Data } from '~types'

View file

@ -1,4 +1,5 @@
import { Utils, Vec } from '@tldraw/core' import { Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { Session, TLDrawShape, TLDrawStatus } from '~types' import { Session, TLDrawShape, TLDrawStatus } from '~types'
import type { Data } from '~types' import type { Data } from '~types'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'

View file

@ -1,4 +1,5 @@
import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core' import { TLBoundsCorner, TLBoundsEdge, Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { TLDrawShape, TLDrawStatus } from '~types' import { TLDrawShape, TLDrawStatus } from '~types'
import type { Session } from '~types' import type { Session } from '~types'
import type { Data } from '~types' import type { Data } from '~types'

View file

@ -1,4 +1,5 @@
import { TLBoundsCorner, TLBoundsEdge, Utils, Vec } from '@tldraw/core' import { TLBoundsCorner, TLBoundsEdge, Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { Session, TLDrawShape, TLDrawStatus } from '~types' import { Session, TLDrawShape, TLDrawStatus } from '~types'
import type { Data } from '~types' import type { Data } from '~types'
import { TLDR } from '~state/tldr' import { TLDR } from '~state/tldr'

View file

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import { TLPageState, Utils, Vec } from '@tldraw/core' import { TLPageState, Utils } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { import {
TLDrawShape, TLDrawShape,
TLDrawBinding, TLDrawBinding,

View file

@ -1,4 +1,4 @@
import { TLBounds, TLTransformInfo, Vec, Utils, TLPageState } from '@tldraw/core' import { TLBounds, TLTransformInfo, Utils, TLPageState } from '@tldraw/core'
import { getShapeUtils } from '~shape' import { getShapeUtils } from '~shape'
import type { import type {
Data, Data,
@ -11,6 +11,7 @@ import type {
TLDrawCommand, TLDrawCommand,
TLDrawPatch, TLDrawPatch,
} from '~types' } from '~types'
import { Vec } from '@tldraw/vec'
export class TLDR { export class TLDR {
static getShapeUtils<T extends TLDrawShape>( static getShapeUtils<T extends TLDrawShape>(

View file

@ -11,11 +11,11 @@ import {
TLPointerEventHandler, TLPointerEventHandler,
TLWheelEventHandler, TLWheelEventHandler,
Utils, Utils,
Vec,
brushUpdater, brushUpdater,
TLPointerInfo, TLPointerInfo,
TLBounds, TLBounds,
} from '@tldraw/core' } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import { import {
FlipType, FlipType,
TextShape, TextShape,

View file

@ -6,12 +6,16 @@
"strict": true, "strict": true,
"composite": true, "composite": true,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"rootDir": "src", "rootDir": ".",
"outDir": "./dist/types", "outDir": "./dist/types",
"baseUrl": "src", "baseUrl": "src",
"paths": { "paths": {
"~*": ["./*"] "~*": ["./*"]
} }
}, },
"references": [{ "path": "../../packages/core" }] "references": [
{ "path": "../../packages/intersect" },
{ "path": "../../packages/vec" },
{ "path": "../../packages/core" }
]
} }

3
packages/vec/README.md Normal file
View file

@ -0,0 +1,3 @@
# Dev Server
Dev server with fast refresh.

55
packages/vec/package.json Normal file
View file

@ -0,0 +1,55 @@
{
"name": "@tldraw/vec",
"version": "0.0.86",
"private": false,
"description": "A tiny little drawing app (vec)",
"author": "@steveruizok",
"repository": {
"type": "git",
"url": "git+https://github.com/tldraw/tldraw.git",
"directory": "packages/vec"
},
"license": "MIT",
"keywords": [],
"files": [
"dist/**/*"
],
"sideEffects": false,
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"typings": "./dist/types/index.d.ts",
"scripts": {
"start:pre": "node scripts/pre-dev && yarn types:pre",
"start": "node scripts/dev & yarn types:dev",
"build": "node scripts/build && yarn types:build",
"types:pre": "tsc",
"types:dev": "tsc --watch",
"types:build": "tsc --project tsconfig.build.json",
"lint": "eslint src/ --ext .ts,.tsx",
"clean": "rm -rf dist",
"ts-node": "ts-node",
"docs": "typedoc",
"docs:watch": "typedoc --watch"
},
"devDependencies": {
"@babel/core": "^7.15.5",
"@babel/preset-env": "^7.15.4",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.10",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"esbuild": "^0.12.24",
"eslint": "^7.32.0",
"lerna": "^4.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typedoc": "^0.21.9",
"typescript": "^4.4.2"
},
"peerDependencies": {},
"dependencies": {},
"gitHead": "55da8880eb3d8ab5fb62b5eb7853065922c95dcf"
}

View file

@ -0,0 +1,52 @@
/* eslint-disable */
const fs = require('fs')
const esbuild = require('esbuild')
const name = process.env.npm_package_name || ''
async function main() {
if (fs.existsSync('./dist')) {
fs.rmSync('./dist', { recursive: true }, (e) => {
if (e) {
throw e
}
})
}
try {
esbuild.buildSync({
entryPoints: ['./src/index.ts'],
outdir: 'dist/cjs',
minify: true,
bundle: true,
sourcemap: true,
format: 'cjs',
target: 'es6',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.build.json',
external: ['react', 'react-dom'],
})
esbuild.buildSync({
entryPoints: ['./src/index.ts'],
outdir: 'dist/esm',
minify: true,
bundle: true,
sourcemap: true,
format: 'esm',
target: 'es6',
tsconfig: './tsconfig.build.json',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
external: ['react', 'react-dom'],
})
console.log(`${name}: Built package.`)
} catch (e) {
console.log(`× ${name}: Build failed due to an error.`)
console.log(e)
}
}
main()

View file

@ -0,0 +1,31 @@
/* eslint-disable */
const esbuild = require('esbuild')
const name = process.env.npm_package_name || ''
async function main() {
esbuild.build({
entryPoints: ['./src/index.ts'],
outdir: 'dist/cjs',
minify: false,
bundle: true,
format: 'cjs',
target: 'es6',
jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.json',
external: ['react', 'react-dom'],
incremental: true,
watch: {
onRebuild(error) {
if (error) {
console.log(`× ${name}: An error in prevented the rebuild.`)
return
}
console.log(`${name}: Rebuilt.`)
},
},
})
}
main()

View file

@ -0,0 +1,27 @@
/* eslint-disable */
const fs = require('fs')
const esbuild = require('esbuild')
async function main() {
if (fs.existsSync('./dist')) {
fs.rmSync('./dist', { recursive: true }, (e) => {
if (e) {
throw e
}
})
}
esbuild.build({
entryPoints: ['./src/index.ts'],
outdir: 'dist/cjs',
minify: true,
bundle: true,
sourcemap: true,
format: 'cjs',
target: 'es6',
tsconfig: './tsconfig.json',
external: ['react', 'react-dom'],
})
}
main()

View file

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"exclude": [
"node_modules",
"**/*.test.tsx",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.spec.ts",
"src/test",
"dist",
"docs"
],
"compilerOptions": {
"composite": false,
"incremental": false,
"declarationMap": false,
"sourceMap": false
}
}

View file

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"exclude": ["node_modules", "dist", "docs"],
"compilerOptions": {
"outDir": "./dist/types",
"rootDir": "src",
"baseUrl": "src"
}
}

View file

@ -3,7 +3,9 @@
// For references // For references
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"composite": true,
"sourceMap": true, "sourceMap": true,
"emitDeclarationOnly": true,
// Other // Other
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"esModuleInterop": true, "esModuleInterop": true,

View file

@ -3,12 +3,19 @@
"extends": "./tsconfig.base.json", "extends": "./tsconfig.base.json",
"exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts"], "exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts"],
"files": [], "files": [],
"references": [{ "path": "./packages/tldraw" }, { "path": "./packages/core" }], "references": [
{ "path": "./packages/vec" },
{ "path": "./packages/intersect" },
{ "path": "./packages/tldraw" },
{ "path": "./packages/core" }
],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@tldraw/tldraw": ["./packages/tldraw/dist"], "@tldraw/tldraw": ["./packages/tldraw"],
"@tldraw/core": ["./packages/core/dist"], "@tldraw/core": ["./packages/core"],
"@tldraw/vec": ["./packages/vec"],
"@tldraw/intersect": ["./packages/intersect"],
"+*": ["./packages/core/src/*"], "+*": ["./packages/core/src/*"],
"~*": ["./packages/tldraw/src/*"] "~*": ["./packages/tldraw/src/*"]
} }