Merge pull request #86 from tldraw/utils-refactor
Split vectors and intersections into new packages
This commit is contained in:
commit
9f309bb485
58 changed files with 1801 additions and 880 deletions
17
.vscode/tasks.json
vendored
17
.vscode/tasks.json
vendored
|
@ -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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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] }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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": [
|
||||||
{
|
{
|
||||||
|
|
3
packages/intersect/README.md
Normal file
3
packages/intersect/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Dev Server
|
||||||
|
|
||||||
|
Dev server with fast refresh.
|
57
packages/intersect/package.json
Normal file
57
packages/intersect/package.json
Normal 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"
|
||||||
|
}
|
50
packages/intersect/scripts/build.js
Normal file
50
packages/intersect/scripts/build.js
Normal 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()
|
31
packages/intersect/scripts/dev.js
Normal file
31
packages/intersect/scripts/dev.js
Normal 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()
|
28
packages/intersect/scripts/pre-dev.js
Normal file
28
packages/intersect/scripts/pre-dev.js
Normal 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()
|
1147
packages/intersect/src/index.ts
Normal file
1147
packages/intersect/src/index.ts
Normal file
File diff suppressed because it is too large
Load diff
19
packages/intersect/tsconfig.build.json
Normal file
19
packages/intersect/tsconfig.build.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
11
packages/intersect/tsconfig.json
Normal file
11
packages/intersect/tsconfig.json
Normal 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" }]
|
||||||
|
}
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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.`)
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
3
packages/vec/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Dev Server
|
||||||
|
|
||||||
|
Dev server with fast refresh.
|
55
packages/vec/package.json
Normal file
55
packages/vec/package.json
Normal 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"
|
||||||
|
}
|
52
packages/vec/scripts/build.js
Normal file
52
packages/vec/scripts/build.js
Normal 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()
|
31
packages/vec/scripts/dev.js
Normal file
31
packages/vec/scripts/dev.js
Normal 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()
|
27
packages/vec/scripts/pre-dev.js
Normal file
27
packages/vec/scripts/pre-dev.js
Normal 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()
|
19
packages/vec/tsconfig.build.json
Normal file
19
packages/vec/tsconfig.build.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
10
packages/vec/tsconfig.json
Normal file
10
packages/vec/tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist", "docs"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/types",
|
||||||
|
"rootDir": "src",
|
||||||
|
"baseUrl": "src"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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/*"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue