[tech debt] Primitives renaming party / cleanup (#2396)
This PR: - renames Vec2d to Vec - renames Vec2dModel to VecModel - renames Box2d to Box - renames Box2dModel to BoxModel - renames Matrix2d to Mat - renames Matrix2dModel to MatModel - removes unused primitive helpers - removes unused exports - removes a few redundant tests in dgreensp ### Change Type - [x] `major` — Breaking change ### Release Notes - renames Vec2d to Vec - renames Vec2dModel to VecModel - renames Box2d to Box - renames Box2dModel to BoxModel - renames Matrix2d to Mat - renames Matrix2dModel to MatModel - removes unused primitive helpers
This commit is contained in:
parent
afd5af1cb6
commit
6b1005ef71
159 changed files with 4757 additions and 5319 deletions
|
@ -1,5 +1,5 @@
|
|||
import test, { Page, expect } from '@playwright/test'
|
||||
import { Box2dModel, Editor } from '@tldraw/tldraw'
|
||||
import { BoxModel, Editor } from '@tldraw/tldraw'
|
||||
import { setupPage } from '../shared-e2e'
|
||||
|
||||
export function sleep(ms: number) {
|
||||
|
@ -29,7 +29,7 @@ const measureTextSpansOptions = {
|
|||
textAlign: 'start' as 'start' | 'middle' | 'end',
|
||||
}
|
||||
|
||||
function formatLines(spans: { box: Box2dModel; text: string }[]) {
|
||||
function formatLines(spans: { box: BoxModel; text: string }[]) {
|
||||
const lines = []
|
||||
|
||||
let currentLine: string[] | null = null
|
||||
|
@ -85,7 +85,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should get a single text span', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) => editor.textMeasure.measureTextSpans('testing', options),
|
||||
|
@ -97,7 +97,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should wrap a word when it has to', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) => editor.textMeasure.measureTextSpans('testing', { ...options, width: 50 }),
|
||||
|
@ -109,7 +109,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should preserve whitespace at line breaks', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) => editor.textMeasure.measureTextSpans('testing testing', options),
|
||||
|
@ -121,7 +121,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should preserve whitespace at the end of wrapped lines', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) => editor.textMeasure.measureTextSpans('testing testing ', options),
|
||||
|
@ -136,7 +136,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('preserves whitespace at the end of unwrapped lines', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) =>
|
||||
|
@ -149,7 +149,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('preserves whitespace at the start of an unwrapped line', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) =>
|
||||
|
@ -162,7 +162,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should place starting whitespace on its own line if it has to', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) => editor.textMeasure.measureTextSpans(' testing testing', options),
|
||||
|
@ -174,7 +174,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should handle multiline text', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) =>
|
||||
|
@ -192,7 +192,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should break long strings of text', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) =>
|
||||
|
@ -212,7 +212,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should return an empty array if the text is empty', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(async (options) => editor.textMeasure.measureTextSpans('', options), measureTextSpansOptions)
|
||||
|
||||
|
@ -221,7 +221,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should handle trailing newlines', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) => editor.textMeasure.measureTextSpans('hi\n\n\n', options),
|
||||
|
@ -233,7 +233,7 @@ test.describe('text measurement', () => {
|
|||
|
||||
test('should handle only newlines', async () => {
|
||||
const spans = await page.evaluate<
|
||||
{ text: string; box: Box2dModel }[],
|
||||
{ text: string; box: BoxModel }[],
|
||||
typeof measureTextSpansOptions
|
||||
>(
|
||||
async (options) => editor.textMeasure.measureTextSpans('\n\n\n', options),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Tldraw, Vec2d, useContainer, useEditor } from '@tldraw/tldraw'
|
||||
import { Tldraw, Vec, useContainer, useEditor } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/tldraw.css'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
|
@ -32,7 +32,7 @@ function SneakyFloatyHook() {
|
|||
if (sx !== x || sy !== y) {
|
||||
x = sx
|
||||
y = sy
|
||||
editor.setCamera(new Vec2d(-x, -y))
|
||||
editor.setCamera(new Vec(-x, -y))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Box2d, StateNode, atom, copyAs, exportAs } from '@tldraw/tldraw'
|
||||
import { Box, StateNode, atom, copyAs, exportAs } from '@tldraw/tldraw'
|
||||
|
||||
// There's a guide at the bottom of this file!
|
||||
|
||||
|
@ -6,7 +6,7 @@ export class ScreenshotDragging extends StateNode {
|
|||
static override id = 'dragging'
|
||||
|
||||
// [1]
|
||||
screenshotBox = atom('screenshot brush', new Box2d())
|
||||
screenshotBox = atom('screenshot brush', new Box())
|
||||
|
||||
// [2]
|
||||
override onEnter = () => {
|
||||
|
@ -30,7 +30,7 @@ export class ScreenshotDragging extends StateNode {
|
|||
inputs: { shiftKey, altKey, originPagePoint, currentPagePoint },
|
||||
} = this.editor
|
||||
|
||||
const box = Box2d.FromPoints([originPagePoint, currentPagePoint])
|
||||
const box = Box.FromPoints([originPagePoint, currentPagePoint])
|
||||
|
||||
if (shiftKey) {
|
||||
if (box.w > box.h * (16 / 9)) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {
|
||||
Box2d,
|
||||
Box,
|
||||
TLEditorComponents,
|
||||
TLUiAssetUrlOverrides,
|
||||
TLUiOverrides,
|
||||
Tldraw,
|
||||
Vec2d,
|
||||
Vec,
|
||||
toolbarItem,
|
||||
useEditor,
|
||||
useValue,
|
||||
|
@ -69,11 +69,11 @@ function ScreenshotBox() {
|
|||
// "page space", i.e. uneffected by scale, and relative to the tldraw
|
||||
// page's top left corner.
|
||||
const zoomLevel = editor.getZoomLevel()
|
||||
const { x, y } = Vec2d.Sub(
|
||||
const { x, y } = Vec.Sub(
|
||||
editor.pageToScreen({ x: box.x, y: box.y }),
|
||||
editor.getViewportScreenBounds()
|
||||
)
|
||||
return new Box2d(x, y, box.w * zoomLevel, box.h * zoomLevel)
|
||||
return new Box(x, y, box.w * zoomLevel, box.h * zoomLevel)
|
||||
},
|
||||
[editor]
|
||||
)
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
TLOnBeforeUpdateHandler,
|
||||
TLOnHandleChangeHandler,
|
||||
TLOnResizeHandler,
|
||||
Vec2d,
|
||||
Vec,
|
||||
deepCopy,
|
||||
getDefaultColorTheme,
|
||||
resizeBox,
|
||||
|
@ -128,21 +128,21 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
|||
const MIN_DISTANCE = slantedLength / 5
|
||||
const MAX_DISTANCE = slantedLength / 1.5
|
||||
|
||||
const handleInShapeSpace = new Vec2d(handles.handle.x * w, handles.handle.y * h)
|
||||
const handleInShapeSpace = new Vec(handles.handle.x * w, handles.handle.y * h)
|
||||
|
||||
const distanceToIntersection = handleInShapeSpace.dist(segmentsIntersection)
|
||||
const center = new Vec2d(w / 2, h / 2)
|
||||
const vHandle = Vec2d.Sub(handleInShapeSpace, center).uni()
|
||||
const center = new Vec(w / 2, h / 2)
|
||||
const vHandle = Vec.Sub(handleInShapeSpace, center).uni()
|
||||
|
||||
let newPoint = handleInShapeSpace
|
||||
|
||||
if (insideShape) {
|
||||
newPoint = Vec2d.Add(segmentsIntersection, vHandle.mul(MIN_DISTANCE))
|
||||
newPoint = Vec.Add(segmentsIntersection, vHandle.mul(MIN_DISTANCE))
|
||||
} else {
|
||||
if (distanceToIntersection <= MIN_DISTANCE) {
|
||||
newPoint = Vec2d.Add(segmentsIntersection, vHandle.mul(MIN_DISTANCE))
|
||||
newPoint = Vec.Add(segmentsIntersection, vHandle.mul(MIN_DISTANCE))
|
||||
} else if (distanceToIntersection >= MAX_DISTANCE) {
|
||||
newPoint = Vec2d.Add(segmentsIntersection, vHandle.mul(MAX_DISTANCE))
|
||||
newPoint = Vec.Add(segmentsIntersection, vHandle.mul(MAX_DISTANCE))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { Vec2d, VecLike, lerp, pointInPolygon } from '@tldraw/tldraw'
|
||||
import { Vec, VecLike, lerp, pointInPolygon } from '@tldraw/tldraw'
|
||||
import { SpeechBubbleShape } from './SpeechBubbleUtil'
|
||||
|
||||
export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec2d[] => {
|
||||
export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec[] => {
|
||||
const { w, h, handles } = shape.props
|
||||
|
||||
const handleInShapeSpace = new Vec2d(handles.handle.x * w, handles.handle.y * h)
|
||||
const handleInShapeSpace = new Vec(handles.handle.x * w, handles.handle.y * h)
|
||||
|
||||
const [tl, tr, br, bl] = [new Vec2d(0, 0), new Vec2d(w, 0), new Vec2d(w, h), new Vec2d(0, h)]
|
||||
const [tl, tr, br, bl] = [new Vec(0, 0), new Vec(w, 0), new Vec(w, h), new Vec(0, h)]
|
||||
|
||||
const offsetH = w / 10
|
||||
const offsetV = h / 10
|
||||
|
||||
const { adjustedIntersection, intersectionSegmentIndex } = getHandleIntersectionPoint(shape)
|
||||
|
||||
let vertices: Vec2d[]
|
||||
let vertices: Vec[]
|
||||
|
||||
// Inject the tail segments into the geometry of the shape
|
||||
switch (intersectionSegmentIndex) {
|
||||
|
@ -21,9 +21,9 @@ export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec2d[] => {
|
|||
// top
|
||||
vertices = [
|
||||
tl,
|
||||
new Vec2d(adjustedIntersection.x - offsetH, adjustedIntersection.y),
|
||||
new Vec2d(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec2d(adjustedIntersection.x + offsetH, adjustedIntersection.y),
|
||||
new Vec(adjustedIntersection.x - offsetH, adjustedIntersection.y),
|
||||
new Vec(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec(adjustedIntersection.x + offsetH, adjustedIntersection.y),
|
||||
tr,
|
||||
br,
|
||||
bl,
|
||||
|
@ -34,9 +34,9 @@ export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec2d[] => {
|
|||
vertices = [
|
||||
tl,
|
||||
tr,
|
||||
new Vec2d(adjustedIntersection.x, adjustedIntersection.y - offsetV),
|
||||
new Vec2d(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec2d(adjustedIntersection.x, adjustedIntersection.y + offsetV),
|
||||
new Vec(adjustedIntersection.x, adjustedIntersection.y - offsetV),
|
||||
new Vec(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec(adjustedIntersection.x, adjustedIntersection.y + offsetV),
|
||||
br,
|
||||
bl,
|
||||
]
|
||||
|
@ -47,9 +47,9 @@ export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec2d[] => {
|
|||
tl,
|
||||
tr,
|
||||
br,
|
||||
new Vec2d(adjustedIntersection.x + offsetH, adjustedIntersection.y),
|
||||
new Vec2d(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec2d(adjustedIntersection.x - offsetH, adjustedIntersection.y),
|
||||
new Vec(adjustedIntersection.x + offsetH, adjustedIntersection.y),
|
||||
new Vec(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec(adjustedIntersection.x - offsetH, adjustedIntersection.y),
|
||||
bl,
|
||||
]
|
||||
break
|
||||
|
@ -60,9 +60,9 @@ export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec2d[] => {
|
|||
tr,
|
||||
br,
|
||||
bl,
|
||||
new Vec2d(adjustedIntersection.x, adjustedIntersection.y + offsetV),
|
||||
new Vec2d(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec2d(adjustedIntersection.x, adjustedIntersection.y - offsetV),
|
||||
new Vec(adjustedIntersection.x, adjustedIntersection.y + offsetV),
|
||||
new Vec(handleInShapeSpace.x, handleInShapeSpace.y),
|
||||
new Vec(adjustedIntersection.x, adjustedIntersection.y - offsetV),
|
||||
]
|
||||
break
|
||||
default:
|
||||
|
@ -74,10 +74,10 @@ export const getSpeechBubbleVertices = (shape: SpeechBubbleShape): Vec2d[] => {
|
|||
|
||||
export function getHandleIntersectionPoint(shape: SpeechBubbleShape) {
|
||||
const { w, h, handles } = shape.props
|
||||
const handleInShapeSpace = new Vec2d(handles.handle.x * w, handles.handle.y * h)
|
||||
const handleInShapeSpace = new Vec(handles.handle.x * w, handles.handle.y * h)
|
||||
|
||||
const center = new Vec2d(w / 2, h / 2)
|
||||
const corners = [new Vec2d(0, 0), new Vec2d(w, 0), new Vec2d(w, h), new Vec2d(0, h)]
|
||||
const center = new Vec(w / 2, h / 2)
|
||||
const corners = [new Vec(0, 0), new Vec(w, 0), new Vec(w, h), new Vec(0, h)]
|
||||
const segments = [
|
||||
[corners[0], corners[1]],
|
||||
[corners[1], corners[2]],
|
||||
|
@ -85,8 +85,8 @@ export function getHandleIntersectionPoint(shape: SpeechBubbleShape) {
|
|||
[corners[3], corners[0]],
|
||||
]
|
||||
|
||||
let segmentsIntersection: Vec2d | null = null
|
||||
let intersectionSegment: Vec2d[] | null = null
|
||||
let segmentsIntersection: Vec | null = null
|
||||
let intersectionSegment: Vec[] | null = null
|
||||
|
||||
// If the point inside of the box's corners?
|
||||
const insideShape = pointInPolygon(handleInShapeSpace, corners)
|
||||
|
@ -94,7 +94,7 @@ export function getHandleIntersectionPoint(shape: SpeechBubbleShape) {
|
|||
// We want to be sure we get an intersection, so if the point is
|
||||
// inside the shape, push it away from the center by a big distance
|
||||
const pointToCheck = insideShape
|
||||
? Vec2d.Add(handleInShapeSpace, Vec2d.Sub(handleInShapeSpace, center).uni().mul(1000000))
|
||||
? Vec.Add(handleInShapeSpace, Vec.Sub(handleInShapeSpace, center).uni().mul(1000000))
|
||||
: handleInShapeSpace
|
||||
|
||||
// Test each segment for an intersection
|
||||
|
@ -120,11 +120,11 @@ export function getHandleIntersectionPoint(shape: SpeechBubbleShape) {
|
|||
const intersectionSegmentIndex = segments.indexOf(intersectionSegment)
|
||||
|
||||
// a normalised vector from start to end, so this can work in any direction
|
||||
const unit = Vec2d.Sub(end, start).uni()
|
||||
const unit = Vec.Sub(end, start).uni()
|
||||
|
||||
// Where is the intersection relative to the start?
|
||||
const totalDistance = Vec2d.Dist(start, end)
|
||||
const distance = Vec2d.Dist(segmentsIntersection, start)
|
||||
const totalDistance = Vec.Dist(start, end)
|
||||
const distance = Vec.Dist(segmentsIntersection, start)
|
||||
|
||||
// make it stick to the middle
|
||||
const middleRelative = mapRange(0, totalDistance, -1, 1, distance) // absolute -> -1 to 1
|
||||
|
@ -170,7 +170,7 @@ function intersectLineSegmentLineSegment(a1: VecLike, a2: VecLike, b1: VecLike,
|
|||
const ua = ua_t / u_b
|
||||
const ub = ub_t / u_b
|
||||
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
|
||||
return Vec2d.AddXY(a1, ua * AVx, ua * AVy)
|
||||
return Vec.AddXY(a1, ua * AVx, ua * AVy)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
TLEditorComponents,
|
||||
track,
|
||||
useEditor,
|
||||
Vec2d,
|
||||
Vec,
|
||||
} from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/tldraw.css'
|
||||
import { useState } from 'react'
|
||||
|
@ -62,7 +62,7 @@ const MyComponentInFront = track(() => {
|
|||
|
||||
if (!selectionRotatedPageBounds) return null
|
||||
|
||||
const pageCoordinates = Vec2d.Sub(
|
||||
const pageCoordinates = Vec.Sub(
|
||||
editor.pageToScreen(selectionRotatedPageBounds.point),
|
||||
editor.getViewportScreenBounds()
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -145,6 +145,15 @@ export {
|
|||
type TLEditorOptions,
|
||||
type TLResizeShapeOptions,
|
||||
} from './lib/editor/Editor'
|
||||
export type {
|
||||
TLAfterChangeHandler,
|
||||
TLAfterCreateHandler,
|
||||
TLAfterDeleteHandler,
|
||||
TLBatchCompleteHandler,
|
||||
TLBeforeChangeHandler,
|
||||
TLBeforeCreateHandler,
|
||||
TLBeforeDeleteHandler,
|
||||
} from './lib/editor/managers/SideEffectManager'
|
||||
export {
|
||||
SnapManager,
|
||||
type GapsSnapLine,
|
||||
|
@ -181,16 +190,12 @@ export {
|
|||
type TLShapeUtilFlag,
|
||||
} from './lib/editor/shapes/ShapeUtil'
|
||||
export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
|
||||
export { getArrowheadPathForType } from './lib/editor/shapes/shared/arrow/arrowheads'
|
||||
export {
|
||||
getCurvedArrowHandlePath,
|
||||
getSolidCurvedArrowPath,
|
||||
} from './lib/editor/shapes/shared/arrow/curved-arrow'
|
||||
type TLArcInfo,
|
||||
type TLArrowInfo,
|
||||
type TLArrowPoint,
|
||||
} from './lib/editor/shapes/shared/arrow/arrow-types'
|
||||
export { getArrowTerminalsInArrowSpace } from './lib/editor/shapes/shared/arrow/shared'
|
||||
export {
|
||||
getSolidStraightArrowPath,
|
||||
getStraightArrowHandlePath,
|
||||
} from './lib/editor/shapes/shared/arrow/straight-arrow'
|
||||
export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox'
|
||||
export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
|
||||
export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode'
|
||||
|
@ -259,7 +264,7 @@ export { useSelectionEvents } from './lib/hooks/useSelectionEvents'
|
|||
export { useTLStore } from './lib/hooks/useTLStore'
|
||||
export { useTransform } from './lib/hooks/useTransform'
|
||||
export {
|
||||
Box2d,
|
||||
Box,
|
||||
ROTATE_CORNER_TO_SELECTION_CORNER,
|
||||
rotateSelectionHandle,
|
||||
type BoxLike,
|
||||
|
@ -267,9 +272,9 @@ export {
|
|||
type SelectionCorner,
|
||||
type SelectionEdge,
|
||||
type SelectionHandle,
|
||||
} from './lib/primitives/Box2d'
|
||||
export { Matrix2d, type Matrix2dModel } from './lib/primitives/Matrix2d'
|
||||
export { Vec2d, type VecLike } from './lib/primitives/Vec2d'
|
||||
} from './lib/primitives/Box'
|
||||
export { Mat, type MatLike, type MatModel } from './lib/primitives/Mat'
|
||||
export { Vec, type VecLike } from './lib/primitives/Vec'
|
||||
export { EASINGS } from './lib/primitives/easings'
|
||||
export { Arc2d } from './lib/primitives/geometry/Arc2d'
|
||||
export { Circle2d } from './lib/primitives/geometry/Circle2d'
|
||||
|
@ -279,24 +284,29 @@ export { Edge2d } from './lib/primitives/geometry/Edge2d'
|
|||
export { Ellipse2d } from './lib/primitives/geometry/Ellipse2d'
|
||||
export { Geometry2d } from './lib/primitives/geometry/Geometry2d'
|
||||
export { Group2d } from './lib/primitives/geometry/Group2d'
|
||||
export { Point2d } from './lib/primitives/geometry/Point2d'
|
||||
export { Polygon2d } from './lib/primitives/geometry/Polygon2d'
|
||||
export { Polyline2d } from './lib/primitives/geometry/Polyline2d'
|
||||
export { Rectangle2d } from './lib/primitives/geometry/Rectangle2d'
|
||||
export { Stadium2d } from './lib/primitives/geometry/Stadium2d'
|
||||
export {
|
||||
intersectCircleCircle,
|
||||
intersectCirclePolygon,
|
||||
intersectCirclePolyline,
|
||||
intersectLineSegmentCircle,
|
||||
intersectLineSegmentLineSegment,
|
||||
intersectLineSegmentPolygon,
|
||||
intersectLineSegmentPolyline,
|
||||
intersectPolygonBounds,
|
||||
intersectPolygonPolygon,
|
||||
linesIntersect,
|
||||
polygonsIntersect,
|
||||
} from './lib/primitives/intersect'
|
||||
export {
|
||||
EPSILON,
|
||||
HALF_PI,
|
||||
PI,
|
||||
PI2,
|
||||
SIN,
|
||||
TAU,
|
||||
angleDelta,
|
||||
approximately,
|
||||
areAnglesCompatible,
|
||||
average,
|
||||
|
@ -305,24 +315,11 @@ export {
|
|||
clampRadians,
|
||||
clockwiseAngleDist,
|
||||
degreesToRadians,
|
||||
getArcLength,
|
||||
getPointOnCircle,
|
||||
getPolygonVertices,
|
||||
getStarBounds,
|
||||
getSweep,
|
||||
isAngleBetween,
|
||||
isSafeFloat,
|
||||
lerpAngles,
|
||||
longAngleDist,
|
||||
perimeterOfEllipse,
|
||||
pointInBounds,
|
||||
pointInCircle,
|
||||
pointInEllipse,
|
||||
pointInPolygon,
|
||||
pointInPolyline,
|
||||
pointInRect,
|
||||
pointNearToLineSegment,
|
||||
pointNearToPolyline,
|
||||
precise,
|
||||
radiansToDegrees,
|
||||
rangeIntersection,
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useFixSafariDoubleTapZoomPencilEvents } from '../hooks/useFixSafariDoub
|
|||
import { useGestureEvents } from '../hooks/useGestureEvents'
|
||||
import { useHandleEvents } from '../hooks/useHandleEvents'
|
||||
import { useScreenBounds } from '../hooks/useScreenBounds'
|
||||
import { Matrix2d } from '../primitives/Matrix2d'
|
||||
import { Mat } from '../primitives/Mat'
|
||||
import { toDomPrecision } from '../primitives/utils'
|
||||
import { debugFlags } from '../utils/debug-flags'
|
||||
import { GeometryDebuggingView } from './GeometryDebuggingView'
|
||||
|
@ -273,7 +273,7 @@ function HandlesWrapper() {
|
|||
|
||||
return (
|
||||
<Handles>
|
||||
<g transform={Matrix2d.toCssString(transform)}>
|
||||
<g transform={Mat.toCssString(transform)}>
|
||||
{handlesToDisplay.map((handle) => {
|
||||
return (
|
||||
<HandleWrapper
|
||||
|
@ -520,7 +520,7 @@ function UiLogger() {
|
|||
)
|
||||
}
|
||||
|
||||
export function SelectionForegroundWrapper() {
|
||||
function SelectionForegroundWrapper() {
|
||||
const editor = useEditor()
|
||||
const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
|
||||
editor,
|
||||
|
@ -535,7 +535,7 @@ export function SelectionForegroundWrapper() {
|
|||
return <SelectionForeground bounds={selectionBounds} rotation={selectionRotation} />
|
||||
}
|
||||
|
||||
export function SelectionBackgroundWrapper() {
|
||||
function SelectionBackgroundWrapper() {
|
||||
const editor = useEditor()
|
||||
const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
|
||||
editor,
|
||||
|
@ -550,13 +550,13 @@ export function SelectionBackgroundWrapper() {
|
|||
return <SelectionBackground bounds={selectionBounds} rotation={selectionRotation} />
|
||||
}
|
||||
|
||||
export function OnTheCanvasWrapper() {
|
||||
function OnTheCanvasWrapper() {
|
||||
const { OnTheCanvas } = useEditorComponents()
|
||||
if (!OnTheCanvas) return null
|
||||
return <OnTheCanvas />
|
||||
}
|
||||
|
||||
export function InFrontOfTheCanvasWrapper() {
|
||||
function InFrontOfTheCanvasWrapper() {
|
||||
const { InFrontOfTheCanvas } = useEditorComponents()
|
||||
if (!InFrontOfTheCanvas) return null
|
||||
return <InFrontOfTheCanvas />
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ShapeUtil } from '../editor/shapes/ShapeUtil'
|
|||
import { nearestMultiple } from '../hooks/useDPRMultiple'
|
||||
import { useEditor } from '../hooks/useEditor'
|
||||
import { useEditorComponents } from '../hooks/useEditorComponents'
|
||||
import { Matrix2d } from '../primitives/Matrix2d'
|
||||
import { Mat } from '../primitives/Mat'
|
||||
import { toDomPrecision } from '../primitives/utils'
|
||||
import { OptionalErrorBoundary } from './ErrorBoundary'
|
||||
|
||||
|
@ -56,7 +56,7 @@ export const Shape = track(function Shape({
|
|||
if (!shape) return // probably the shape was just deleted
|
||||
|
||||
const pageTransform = editor.getShapePageTransform(id)
|
||||
const transform = Matrix2d.toCssString(pageTransform)
|
||||
const transform = Mat.toCssString(pageTransform)
|
||||
setProperty('transform', transform)
|
||||
},
|
||||
[editor, setProperty]
|
||||
|
|
|
@ -24,7 +24,7 @@ const EvenInnererIndicator = ({ shape, util }: { shape: TLShape; util: ShapeUtil
|
|||
return useStateTracking('Indicator:' + shape.type, () => util.indicator(shape))
|
||||
}
|
||||
|
||||
export const InnerIndicator = ({ editor, id }: { editor: Editor; id: TLShapeId }) => {
|
||||
const InnerIndicator = ({ editor, id }: { editor: Editor; id: TLShapeId }) => {
|
||||
const shape = useValue('shape', () => new ShapeWithPropsEquality(editor.store.get(id)), [
|
||||
editor,
|
||||
id,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Box2dModel } from '@tldraw/tlschema'
|
||||
import { BoxModel } from '@tldraw/tlschema'
|
||||
import { ComponentType, useRef } from 'react'
|
||||
import { useTransform } from '../../hooks/useTransform'
|
||||
import { toDomPrecision } from '../../primitives/utils'
|
||||
|
||||
/** @public */
|
||||
export type TLBrushComponent = ComponentType<{
|
||||
brush: Box2dModel
|
||||
brush: BoxModel
|
||||
color?: string
|
||||
opacity?: number
|
||||
className?: string
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { Vec2dModel } from '@tldraw/tlschema'
|
||||
import { VecModel } from '@tldraw/tlschema'
|
||||
import classNames from 'classnames'
|
||||
import { ComponentType, useRef } from 'react'
|
||||
import { useTransform } from '../../hooks/useTransform'
|
||||
import { Box2d } from '../../primitives/Box2d'
|
||||
import { Vec2d } from '../../primitives/Vec2d'
|
||||
import { Box } from '../../primitives/Box'
|
||||
import { Vec } from '../../primitives/Vec'
|
||||
import { clamp } from '../../primitives/utils'
|
||||
|
||||
/** @public */
|
||||
export type TLCollaboratorHintComponent = ComponentType<{
|
||||
className?: string
|
||||
point: Vec2dModel
|
||||
viewport: Box2d
|
||||
point: VecModel
|
||||
viewport: Box
|
||||
zoom: number
|
||||
opacity?: number
|
||||
color: string
|
||||
|
@ -32,7 +32,7 @@ export const DefaultCollaboratorHint: TLCollaboratorHintComponent = ({
|
|||
clamp(point.x, viewport.minX + 5 / zoom, viewport.maxX - 5 / zoom),
|
||||
clamp(point.y, viewport.minY + 5 / zoom, viewport.maxY - 5 / zoom),
|
||||
1 / zoom,
|
||||
Vec2d.Angle(viewport.center, point)
|
||||
Vec.Angle(viewport.center, point)
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Vec2dModel } from '@tldraw/tlschema'
|
||||
import { VecModel } from '@tldraw/tlschema'
|
||||
import classNames from 'classnames'
|
||||
import { ComponentType, memo, useRef } from 'react'
|
||||
import { useTransform } from '../../hooks/useTransform'
|
||||
|
@ -6,7 +6,7 @@ import { useTransform } from '../../hooks/useTransform'
|
|||
/** @public */
|
||||
export type TLCursorComponent = ComponentType<{
|
||||
className?: string
|
||||
point: Vec2dModel | null
|
||||
point: VecModel | null
|
||||
zoom: number
|
||||
color?: string
|
||||
name: string | null
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from 'react'
|
||||
import { useTransform } from '../../hooks/useTransform'
|
||||
import { Box2d } from '../../primitives/Box2d'
|
||||
import { Box } from '../../primitives/Box'
|
||||
import { toDomPrecision } from '../../primitives/utils'
|
||||
|
||||
/** @public */
|
||||
export type TLSelectionBackgroundComponent = React.ComponentType<{
|
||||
bounds: Box2d
|
||||
bounds: Box
|
||||
rotation: number
|
||||
}>
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@ import classNames from 'classnames'
|
|||
import { ComponentType, useRef } from 'react'
|
||||
import { useEditor } from '../../hooks/useEditor'
|
||||
import { useTransform } from '../../hooks/useTransform'
|
||||
import { Box2d } from '../../primitives/Box2d'
|
||||
import { Box } from '../../primitives/Box'
|
||||
import { toDomPrecision } from '../../primitives/utils'
|
||||
|
||||
/** @public */
|
||||
export type TLSelectionForegroundComponent = ComponentType<{
|
||||
bounds: Box2d
|
||||
bounds: Box
|
||||
rotation: number
|
||||
}>
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ const Versions = {
|
|||
Initial: 0,
|
||||
} as const
|
||||
|
||||
export const CURRENT_SESSION_STATE_SNAPSHOT_VERSION = Versions.Initial
|
||||
const CURRENT_SESSION_STATE_SNAPSHOT_VERSION = Versions.Initial
|
||||
|
||||
/**
|
||||
* The state of the editor instance, not including any document state.
|
||||
|
|
|
@ -76,9 +76,9 @@ import {
|
|||
SVG_PADDING,
|
||||
ZOOMS,
|
||||
} from '../constants'
|
||||
import { Box2d } from '../primitives/Box2d'
|
||||
import { MatLike, Matrix2d, Matrix2dModel } from '../primitives/Matrix2d'
|
||||
import { Vec2d, VecLike } from '../primitives/Vec2d'
|
||||
import { Box } from '../primitives/Box'
|
||||
import { Mat, MatLike, MatModel } from '../primitives/Mat'
|
||||
import { Vec, VecLike } from '../primitives/Vec'
|
||||
import { EASINGS } from '../primitives/easings'
|
||||
import { Geometry2d } from '../primitives/geometry/Geometry2d'
|
||||
import { Group2d } from '../primitives/geometry/Group2d'
|
||||
|
@ -140,7 +140,7 @@ export type TLAnimationOptions = Partial<{
|
|||
|
||||
/** @public */
|
||||
export type TLResizeShapeOptions = Partial<{
|
||||
initialBounds: Box2d
|
||||
initialBounds: Box
|
||||
scaleOrigin: VecLike
|
||||
scaleAxisRotation: number
|
||||
initialShape: TLShape
|
||||
|
@ -1630,11 +1630,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
@computed getSelectionPageBounds(): Box2d | null {
|
||||
@computed getSelectionPageBounds(): Box | null {
|
||||
const selectedShapeIds = this.getCurrentPageState().selectedShapeIds
|
||||
if (selectedShapeIds.length === 0) return null
|
||||
|
||||
return Box2d.Common(compact(selectedShapeIds.map((id) => this.getShapePageBounds(id))))
|
||||
return Box.Common(compact(selectedShapeIds.map((id) => this.getShapePageBounds(id))))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1665,7 +1665,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @readonly
|
||||
* @public
|
||||
*/
|
||||
@computed getSelectionRotatedPageBounds(): Box2d | undefined {
|
||||
@computed getSelectionRotatedPageBounds(): Box | undefined {
|
||||
const selectedShapeIds = this.getSelectedShapeIds()
|
||||
|
||||
if (selectedShapeIds.length === 0) {
|
||||
|
@ -1685,14 +1685,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
// need to 'un-rotate' all the outlines of the existing nodes so we can fit them inside a box
|
||||
const boxFromRotatedVertices = Box2d.FromPoints(
|
||||
const boxFromRotatedVertices = Box.FromPoints(
|
||||
this.getSelectedShapeIds()
|
||||
.flatMap((id) => {
|
||||
const pageTransform = this.getShapePageTransform(id)
|
||||
if (!pageTransform) return []
|
||||
return pageTransform.applyToPoints(this.getShapeGeometry(id).vertices)
|
||||
})
|
||||
.map((p) => Vec2d.Rot(p, -selectionRotation))
|
||||
.map((p) => Vec.Rot(p, -selectionRotation))
|
||||
)
|
||||
// now position box so that it's top-left corner is in the right place
|
||||
boxFromRotatedVertices.point = boxFromRotatedVertices.point.rot(selectionRotation)
|
||||
|
@ -2124,7 +2124,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (animation) {
|
||||
const { width, height } = this.getViewportScreenBounds()
|
||||
return this._animateToViewport(new Box2d(-x, -y, width / z, height / z), animation)
|
||||
return this._animateToViewport(new Box(-x, -y, width / z, height / z), animation)
|
||||
} else {
|
||||
this._setCamera({ x, y, z })
|
||||
}
|
||||
|
@ -2200,7 +2200,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const ids = [...this.getCurrentPageShapeIds()]
|
||||
if (ids.length <= 0) return this
|
||||
|
||||
const pageBounds = Box2d.Common(compact(ids.map((id) => this.getShapePageBounds(id))))
|
||||
const pageBounds = Box.Common(compact(ids.map((id) => this.getShapePageBounds(id))))
|
||||
this.zoomToBounds(pageBounds, undefined, animation)
|
||||
return this
|
||||
}
|
||||
|
@ -2349,7 +2349,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (!this.getInstanceState().canMoveCamera) return this
|
||||
|
||||
if (ids.length <= 0) return this
|
||||
const selectionBounds = Box2d.Common(compact(ids.map((id) => this.getShapePageBounds(id))))
|
||||
const selectionBounds = Box.Common(compact(ids.map((id) => this.getShapePageBounds(id))))
|
||||
|
||||
const viewportPageBounds = this.getViewportPageBounds()
|
||||
|
||||
|
@ -2407,7 +2407,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
zoomToBounds(bounds: Box2d, targetZoom?: number, animation?: TLAnimationOptions): this {
|
||||
zoomToBounds(bounds: Box, targetZoom?: number, animation?: TLAnimationOptions): this {
|
||||
if (!this.getInstanceState().canMoveCamera) return this
|
||||
|
||||
const viewportScreenBounds = this.getViewportScreenBounds()
|
||||
|
@ -2473,8 +2473,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
elapsed: number
|
||||
duration: number
|
||||
easing: (t: number) => number
|
||||
start: Box2d
|
||||
end: Box2d
|
||||
start: Box
|
||||
end: Box
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -2510,7 +2510,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
private _animateToViewport(targetViewportPage: Box2d, opts = {} as TLAnimationOptions) {
|
||||
private _animateToViewport(targetViewportPage: Box, opts = {} as TLAnimationOptions) {
|
||||
const { duration = 0, easing = EASINGS.easeInOutCubic } = opts
|
||||
const animationSpeed = this.user.getAnimationSpeed()
|
||||
const viewportPageBounds = this.getViewportPageBounds()
|
||||
|
@ -2579,7 +2579,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const moveCamera = (elapsed: number) => {
|
||||
const { x: cx, y: cy, z: cz } = this.getCamera()
|
||||
const movementVec = Vec2d.Mul(direction, (currentSpeed * elapsed) / cz)
|
||||
const movementVec = Vec.Mul(direction, (currentSpeed * elapsed) / cz)
|
||||
|
||||
// Apply friction
|
||||
currentSpeed *= 1 - friction
|
||||
|
@ -2709,7 +2709,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (!container) return this
|
||||
const rect = container.getBoundingClientRect()
|
||||
const screenBounds = new Box2d(
|
||||
const screenBounds = new Box(
|
||||
rect.left || rect.x,
|
||||
rect.top || rect.y,
|
||||
Math.max(rect.width, 1),
|
||||
|
@ -2761,7 +2761,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
@computed getViewportScreenBounds() {
|
||||
const { x, y, w, h } = this.getInstanceState().screenBounds
|
||||
return new Box2d(x, y, w, h)
|
||||
return new Box(x, y, w, h)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2780,7 +2780,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
@computed getViewportPageBounds() {
|
||||
const { w, h } = this.getViewportScreenBounds()
|
||||
const { x: cx, y: cy, z: cz } = this.getCamera()
|
||||
return new Box2d(-cx, -cy, w / cz, h / cz)
|
||||
return new Box(-cx, -cy, w / cz, h / cz)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2898,10 +2898,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
// Get the bounds of the follower (me) and the leader (them)
|
||||
const { center, width, height } = this.getViewportPageBounds()
|
||||
const leaderScreen = Box2d.From(leaderPresence.screenBounds)
|
||||
const leaderScreen = Box.From(leaderPresence.screenBounds)
|
||||
const leaderWidth = leaderScreen.width / leaderPresence.camera.z
|
||||
const leaderHeight = leaderScreen.height / leaderPresence.camera.z
|
||||
const leaderCenter = new Vec2d(
|
||||
const leaderCenter = new Vec(
|
||||
leaderWidth / 2 - leaderPresence.camera.x,
|
||||
leaderHeight / 2 - leaderPresence.camera.y
|
||||
)
|
||||
|
@ -2924,10 +2924,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
// Figure out where to move the camera
|
||||
const displacement = leaderCenter.sub(center)
|
||||
const targetCenter = Vec2d.Add(center, Vec2d.Mul(displacement, chaseProportion))
|
||||
const targetCenter = Vec.Add(center, Vec.Mul(displacement, chaseProportion))
|
||||
|
||||
// Now let's assess whether we've caught up to the leader or not
|
||||
const distance = Vec2d.Sub(targetCenter, center).len()
|
||||
const distance = Vec.Sub(targetCenter, center).len()
|
||||
const zoomChange = Math.abs(targetZoom - this.getCamera().z)
|
||||
|
||||
// If we're chasing the leader...
|
||||
|
@ -3049,7 +3049,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
backgroundIndex: number
|
||||
opacity: number
|
||||
isCulled: boolean
|
||||
maskedPageBounds: Box2d | undefined
|
||||
maskedPageBounds: Box | undefined
|
||||
}[] = []
|
||||
|
||||
let nextIndex = MAX_SHAPES_PER_PAGE * 2
|
||||
|
@ -3165,7 +3165,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
private readonly _renderingBounds = atom('rendering viewport', new Box2d())
|
||||
private readonly _renderingBounds = atom('rendering viewport', new Box())
|
||||
|
||||
/**
|
||||
* The current rendering bounds in the current page space, expanded slightly. Used for determining which shapes
|
||||
|
@ -3178,7 +3178,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
private readonly _renderingBoundsExpanded = atom('rendering viewport expanded', new Box2d())
|
||||
private readonly _renderingBoundsExpanded = atom('rendering viewport expanded', new Box())
|
||||
|
||||
/**
|
||||
* Update the rendering bounds. This should be called when the viewport has stopped changing, such
|
||||
|
@ -3791,7 +3791,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
@computed private _getShapeOutlineSegmentsCache(): ComputedCache<Vec2d[][], TLShape> {
|
||||
@computed private _getShapeOutlineSegmentsCache(): ComputedCache<Vec[][], TLShape> {
|
||||
return this.store.createComputedCache('outline-segments', (shape) => {
|
||||
return this.getShapeUtil(shape).getOutlineSegments(shape)
|
||||
})
|
||||
|
@ -3810,7 +3810,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getShapeOutlineSegments<T extends TLShape>(shape: T | T['id']): Vec2d[][] {
|
||||
getShapeOutlineSegments<T extends TLShape>(shape: T | T['id']): Vec[][] {
|
||||
return (
|
||||
this._getShapeOutlineSegmentsCache().get(typeof shape === 'string' ? shape : shape.id) ??
|
||||
EMPTY_ARRAY
|
||||
|
@ -3855,11 +3855,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getShapeLocalTransform(shape: TLShape | TLShapeId): Matrix2d {
|
||||
getShapeLocalTransform(shape: TLShape | TLShapeId): Mat {
|
||||
const id = typeof shape === 'string' ? shape : shape.id
|
||||
const freshShape = this.getShape(id)
|
||||
if (!freshShape) throw Error('Editor.getTransform: shape not found')
|
||||
return Matrix2d.Identity().translate(freshShape.x, freshShape.y).rotate(freshShape.rotation)
|
||||
return Mat.Identity().translate(freshShape.x, freshShape.y).rotate(freshShape.rotation)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3867,8 +3867,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
@computed private _getShapePageTransformCache(): ComputedCache<Matrix2d, TLShape> {
|
||||
return this.store.createComputedCache<Matrix2d, TLShape>('pageTransformCache', (shape) => {
|
||||
@computed private _getShapePageTransformCache(): ComputedCache<Mat, TLShape> {
|
||||
return this.store.createComputedCache<Mat, TLShape>('pageTransformCache', (shape) => {
|
||||
if (isPageId(shape.parentId)) {
|
||||
return this.getShapeLocalTransform(shape)
|
||||
}
|
||||
|
@ -3878,8 +3878,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// In the future we should look at creating a store update mechanism that understands and preserves
|
||||
// ordering.
|
||||
const parentTransform =
|
||||
this._getShapePageTransformCache().get(shape.parentId) ?? Matrix2d.Identity()
|
||||
return Matrix2d.Compose(parentTransform, this.getShapeLocalTransform(shape)!)
|
||||
this._getShapePageTransformCache().get(shape.parentId) ?? Mat.Identity()
|
||||
return Mat.Compose(parentTransform, this.getShapeLocalTransform(shape)!)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3895,11 +3895,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getShapeParentTransform(shape: TLShape | TLShapeId): Matrix2d {
|
||||
getShapeParentTransform(shape: TLShape | TLShapeId): Mat {
|
||||
const id = typeof shape === 'string' ? shape : shape.id
|
||||
const freshShape = this.getShape(id)
|
||||
if (!freshShape || isPageId(freshShape.parentId)) return Matrix2d.Identity()
|
||||
return this._getShapePageTransformCache().get(freshShape.parentId) ?? Matrix2d.Identity()
|
||||
if (!freshShape || isPageId(freshShape.parentId)) return Mat.Identity()
|
||||
return this._getShapePageTransformCache().get(freshShape.parentId) ?? Mat.Identity()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3915,20 +3915,20 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getShapePageTransform(shape: TLShape | TLShapeId): Matrix2d {
|
||||
getShapePageTransform(shape: TLShape | TLShapeId): Mat {
|
||||
const id = typeof shape === 'string' ? shape : this.getShape(shape)!.id
|
||||
return this._getShapePageTransformCache().get(id) ?? Matrix2d.Identity()
|
||||
return this._getShapePageTransformCache().get(id) ?? Mat.Identity()
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@computed private _getShapePageBoundsCache(): ComputedCache<Box2d, TLShape> {
|
||||
return this.store.createComputedCache<Box2d, TLShape>('pageBoundsCache', (shape) => {
|
||||
@computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
|
||||
return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
|
||||
const pageTransform = this._getShapePageTransformCache().get(shape.id)
|
||||
|
||||
if (!pageTransform) return new Box2d()
|
||||
if (!pageTransform) return new Box()
|
||||
|
||||
const result = Box2d.FromPoints(
|
||||
Matrix2d.applyToPoints(pageTransform, this.getShapeGeometry(shape).vertices)
|
||||
const result = Box.FromPoints(
|
||||
Mat.applyToPoints(pageTransform, this.getShapeGeometry(shape).vertices)
|
||||
)
|
||||
|
||||
return result
|
||||
|
@ -3948,7 +3948,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getShapePageBounds(shape: TLShape | TLShapeId): Box2d | undefined {
|
||||
getShapePageBounds(shape: TLShape | TLShapeId): Box | undefined {
|
||||
return this._getShapePageBoundsCache().get(typeof shape === 'string' ? shape : shape.id)
|
||||
}
|
||||
|
||||
|
@ -3968,7 +3968,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const pageTransform = this._getShapePageTransformCache().get(shape.id)
|
||||
if (!pageTransform) return undefined
|
||||
|
||||
const localMask = Matrix2d.applyToPoints(Matrix2d.Inverse(pageTransform), pageMask)
|
||||
const localMask = Mat.applyToPoints(Mat.Inverse(pageTransform), pageMask)
|
||||
|
||||
return `polygon(${localMask.map((p) => `${p.x}px ${p.y}px`).join(',')})`
|
||||
})
|
||||
|
@ -3994,7 +3994,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
@computed private _getShapeMaskCache(): ComputedCache<Vec2d[], TLShape> {
|
||||
@computed private _getShapeMaskCache(): ComputedCache<Vec[], TLShape> {
|
||||
return this.store.createComputedCache('pageMaskCache', (shape) => {
|
||||
if (isPageId(shape.parentId)) return undefined
|
||||
|
||||
|
@ -4005,7 +4005,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (frameAncestors.length === 0) return undefined
|
||||
|
||||
const pageMask = frameAncestors
|
||||
.map<Vec2d[] | undefined>((s) =>
|
||||
.map<Vec[] | undefined>((s) =>
|
||||
// Apply the frame transform to the frame outline to get the frame outline in the current page space
|
||||
this._getShapePageTransformCache()
|
||||
.get(s.id)!
|
||||
|
@ -4015,7 +4015,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (!(b && acc)) return undefined
|
||||
const intersection = intersectPolygonPolygon(acc, b)
|
||||
if (intersection) {
|
||||
return intersection.map(Vec2d.Cast)
|
||||
return intersection.map(Vec.Cast)
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
@ -4057,7 +4057,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getShapeMaskedPageBounds(shape: TLShapeId | TLShape): Box2d | undefined {
|
||||
getShapeMaskedPageBounds(shape: TLShapeId | TLShape): Box | undefined {
|
||||
if (typeof shape !== 'string') shape = shape.id
|
||||
const pageBounds = this._getShapePageBoundsCache().get(shape)
|
||||
if (!pageBounds) return
|
||||
|
@ -4066,12 +4066,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (pageMask.length === 0) return undefined
|
||||
|
||||
const { corners } = pageBounds
|
||||
if (corners.every((p, i) => Vec2d.Equals(p, pageMask[i]))) return pageBounds.clone()
|
||||
if (corners.every((p, i) => Vec.Equals(p, pageMask[i]))) return pageBounds.clone()
|
||||
|
||||
// todo: find out why intersect polygon polygon for identical polygons produces zero w/h intersections
|
||||
const intersection = intersectPolygonPolygon(pageMask, corners)
|
||||
if (!intersection) return
|
||||
return Box2d.FromPoints(intersection)
|
||||
return Box.FromPoints(intersection)
|
||||
}
|
||||
|
||||
return pageBounds
|
||||
|
@ -4217,8 +4217,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
@computed getCurrentPageBounds(): Box2d | undefined {
|
||||
let commonBounds: Box2d | undefined
|
||||
@computed getCurrentPageBounds(): Box | undefined {
|
||||
let commonBounds: Box | undefined
|
||||
|
||||
this.getCurrentPageShapeIds().forEach((shapeId) => {
|
||||
const bounds = this.getShapeMaskedPageBounds(shapeId)
|
||||
|
@ -4504,7 +4504,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getPointInShapeSpace(shape: TLShape | TLShapeId, point: VecLike): Vec2d {
|
||||
getPointInShapeSpace(shape: TLShape | TLShapeId, point: VecLike): Vec {
|
||||
const id = typeof shape === 'string' ? shape : shape.id
|
||||
return this._getShapePageTransformCache().get(id)!.clone().invert().applyToPoint(point)
|
||||
}
|
||||
|
@ -4522,14 +4522,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getPointInParentSpace(shape: TLShapeId | TLShape, point: VecLike): Vec2d {
|
||||
getPointInParentSpace(shape: TLShapeId | TLShape, point: VecLike): Vec {
|
||||
const id = typeof shape === 'string' ? shape : shape.id
|
||||
const freshShape = this.getShape(id)
|
||||
if (!freshShape) return new Vec2d(0, 0)
|
||||
if (isPageId(freshShape.parentId)) return Vec2d.From(point)
|
||||
if (!freshShape) return new Vec(0, 0)
|
||||
if (isPageId(freshShape.parentId)) return Vec.From(point)
|
||||
|
||||
const parentTransform = this.getShapePageTransform(freshShape.parentId)
|
||||
if (!parentTransform) return Vec2d.From(point)
|
||||
if (!parentTransform) return Vec.From(point)
|
||||
return parentTransform.clone().invert().applyToPoint(point)
|
||||
}
|
||||
|
||||
|
@ -4769,7 +4769,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const changes: TLShapePartial[] = []
|
||||
|
||||
const parentTransform = isPageId(parentId)
|
||||
? Matrix2d.Identity()
|
||||
? Mat.Identity()
|
||||
: this.getShapePageTransform(parentId)!
|
||||
|
||||
const parentPageRotation = parentTransform.rotation()
|
||||
|
@ -5095,7 +5095,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
throw Error(`Could not find a shape with the id ${id}.`)
|
||||
}
|
||||
|
||||
const localDelta = Vec2d.Cast(offset)
|
||||
const localDelta = Vec.Cast(offset)
|
||||
const parentTransform = this.getShapeParentTransform(shape)
|
||||
if (parentTransform) localDelta.rot(-parentTransform.rotation())
|
||||
|
||||
|
@ -5177,7 +5177,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (offset && initialIds.has(id)) {
|
||||
const parentTransform = this.getShapeParentTransform(shape)
|
||||
const vec = new Vec2d(offset.x, offset.y).rot(-parentTransform!.rotation())
|
||||
const vec = new Vec(offset.x, offset.y).rot(-parentTransform!.rotation())
|
||||
ox = vec.x
|
||||
oy = vec.y
|
||||
}
|
||||
|
@ -5250,9 +5250,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
: getCurvedArrowInfo(this, newShape)
|
||||
|
||||
if (info?.isValid && infoAfter?.isValid && !getIsArrowStraight(shape)) {
|
||||
const mpA = Vec2d.Med(info.start.handle, info.end.handle)
|
||||
const distA = Vec2d.Dist(info.middle, mpA)
|
||||
const distB = Vec2d.Dist(infoAfter.middle, mpA)
|
||||
const mpA = Vec.Med(info.start.handle, info.end.handle)
|
||||
const distA = Vec.Dist(info.middle, mpA)
|
||||
const distB = Vec.Dist(infoAfter.middle, mpA)
|
||||
if (newShape.props.bend < 0) {
|
||||
newShape.props.bend += distB - distA
|
||||
} else {
|
||||
|
@ -5566,7 +5566,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
.flat()
|
||||
)
|
||||
|
||||
const scaleOriginPage = Box2d.Common(
|
||||
const scaleOriginPage = Box.Common(
|
||||
compact(shapesToFlip.map((id) => this.getShapePageBounds(id)))
|
||||
).center
|
||||
|
||||
|
@ -5718,7 +5718,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const parent = this.getShapeParent(shape)
|
||||
const localDelta = parent
|
||||
? Vec2d.Rot(delta, -this.getShapePageTransform(parent)!.decompose().rotation)
|
||||
? Vec.Rot(delta, -this.getShapePageTransform(parent)!.decompose().rotation)
|
||||
: delta
|
||||
|
||||
const translateStartChanges = this.getShapeUtil(shape).onTranslateStart?.(shape)
|
||||
|
@ -5780,11 +5780,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return true
|
||||
})
|
||||
)
|
||||
const shapePageBounds: Record<string, Box2d> = {}
|
||||
const nextShapePageBounds: Record<string, Box2d> = {}
|
||||
const shapePageBounds: Record<string, Box> = {}
|
||||
const nextShapePageBounds: Record<string, Box> = {}
|
||||
|
||||
let shape: TLShape,
|
||||
bounds: Box2d,
|
||||
bounds: Box,
|
||||
area = 0
|
||||
|
||||
for (let i = 0; i < shapesToPack.length; i++) {
|
||||
|
@ -5795,7 +5795,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
area += bounds.width * bounds.height
|
||||
}
|
||||
|
||||
const commonBounds = Box2d.Common(compact(Object.values(shapePageBounds)))
|
||||
const commonBounds = Box.Common(compact(Object.values(shapePageBounds)))
|
||||
|
||||
const maxWidth = commonBounds.width
|
||||
|
||||
|
@ -5806,12 +5806,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth)
|
||||
|
||||
// first shape fills the width and is infinitely tall
|
||||
const spaces: Box2d[] = [new Box2d(commonBounds.x, commonBounds.y, startWidth, Infinity)]
|
||||
const spaces: Box[] = [new Box(commonBounds.x, commonBounds.y, startWidth, Infinity)]
|
||||
|
||||
let width = 0
|
||||
let height = 0
|
||||
let space: Box2d
|
||||
let last: Box2d
|
||||
let space: Box
|
||||
let last: Box
|
||||
|
||||
for (let i = 0; i < shapesToPack.length; i++) {
|
||||
shape = shapesToPack[i]
|
||||
|
@ -5846,7 +5846,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
} else {
|
||||
// split the space into two spaces
|
||||
spaces.push(
|
||||
new Box2d(
|
||||
new Box(
|
||||
space.x + (bounds.width + gap),
|
||||
space.y,
|
||||
space.width - (bounds.width + gap),
|
||||
|
@ -5860,10 +5860,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
}
|
||||
|
||||
const commonAfter = Box2d.Common(Object.values(nextShapePageBounds))
|
||||
const centerDelta = Vec2d.Sub(commonBounds.center, commonAfter.center)
|
||||
const commonAfter = Box.Common(Object.values(nextShapePageBounds))
|
||||
const centerDelta = Vec.Sub(commonBounds.center, commonAfter.center)
|
||||
|
||||
let nextBounds: Box2d
|
||||
let nextBounds: Box
|
||||
|
||||
const changes: TLShapePartial<any>[] = []
|
||||
|
||||
|
@ -5872,7 +5872,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
bounds = shapePageBounds[shape.id]
|
||||
nextBounds = nextShapePageBounds[shape.id]
|
||||
|
||||
const delta = Vec2d.Sub(nextBounds.point, bounds.point).add(centerDelta)
|
||||
const delta = Vec.Sub(nextBounds.point, bounds.point).add(centerDelta)
|
||||
const parentTransform = this.getShapeParentTransform(shape)
|
||||
if (parentTransform) delta.rot(-parentTransform.rotation())
|
||||
|
||||
|
@ -5933,7 +5933,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const shapePageBounds = Object.fromEntries(
|
||||
shapesToAlign.map((shape) => [shape.id, this.getShapePageBounds(shape)])
|
||||
)
|
||||
const commonBounds = Box2d.Common(compact(Object.values(shapePageBounds)))
|
||||
const commonBounds = Box.Common(compact(Object.values(shapePageBounds)))
|
||||
|
||||
const changes: TLShapePartial[] = []
|
||||
|
||||
|
@ -5972,7 +5972,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const parent = this.getShapeParent(shape)
|
||||
const localDelta = parent
|
||||
? Vec2d.Rot(delta, -this.getShapePageTransform(parent)!.decompose().rotation)
|
||||
? Vec.Rot(delta, -this.getShapePageTransform(parent)!.decompose().rotation)
|
||||
: delta
|
||||
|
||||
const translateChanges = this.getShapeUtil(shape).onTranslateStart?.(shape)
|
||||
|
@ -6066,7 +6066,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const parent = this.getShapeParent(shape)
|
||||
const localDelta = parent
|
||||
? Vec2d.Rot(delta, -this.getShapePageTransform(parent)!.rotation())
|
||||
? Vec.Rot(delta, -this.getShapePageTransform(parent)!.rotation())
|
||||
: delta
|
||||
const translateStartChanges = this.getShapeUtil(shape).onTranslateStart?.(shape)
|
||||
|
||||
|
@ -6114,7 +6114,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const shapesToStretch = compact(ids.map((id) => this.getShape(id))) // always fresh shapes
|
||||
const shapeBounds = Object.fromEntries(ids.map((id) => [id, this.getShapeGeometry(id).bounds]))
|
||||
const shapePageBounds = Object.fromEntries(ids.map((id) => [id, this.getShapePageBounds(id)!]))
|
||||
const commonBounds = Box2d.Common(compact(Object.values(shapePageBounds)))
|
||||
const commonBounds = Box.Common(compact(Object.values(shapePageBounds)))
|
||||
|
||||
switch (operation) {
|
||||
case 'vertical': {
|
||||
|
@ -6124,16 +6124,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (pageRotation % PI2) continue
|
||||
const bounds = shapeBounds[shape.id]
|
||||
const pageBounds = shapePageBounds[shape.id]
|
||||
const localOffset = new Vec2d(0, commonBounds.minY - pageBounds.minY)
|
||||
const localOffset = new Vec(0, commonBounds.minY - pageBounds.minY)
|
||||
const parentTransform = this.getShapeParentTransform(shape)
|
||||
if (parentTransform) localOffset.rot(-parentTransform.rotation())
|
||||
|
||||
const { x, y } = Vec2d.Add(localOffset, shape)
|
||||
const { x, y } = Vec.Add(localOffset, shape)
|
||||
this.updateShapes([{ id: shape.id, type: shape.type, x, y }], { squashing: true })
|
||||
const scale = new Vec2d(1, commonBounds.height / pageBounds.height)
|
||||
const scale = new Vec(1, commonBounds.height / pageBounds.height)
|
||||
this.resizeShape(shape.id, scale, {
|
||||
initialBounds: bounds,
|
||||
scaleOrigin: new Vec2d(pageBounds.center.x, commonBounds.minY),
|
||||
scaleOrigin: new Vec(pageBounds.center.x, commonBounds.minY),
|
||||
scaleAxisRotation: 0,
|
||||
})
|
||||
}
|
||||
|
@ -6147,16 +6147,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const pageBounds = shapePageBounds[shape.id]
|
||||
const pageRotation = this.getShapePageTransform(shape)!.rotation()
|
||||
if (pageRotation % PI2) continue
|
||||
const localOffset = new Vec2d(commonBounds.minX - pageBounds.minX, 0)
|
||||
const localOffset = new Vec(commonBounds.minX - pageBounds.minX, 0)
|
||||
const parentTransform = this.getShapeParentTransform(shape)
|
||||
if (parentTransform) localOffset.rot(-parentTransform.rotation())
|
||||
|
||||
const { x, y } = Vec2d.Add(localOffset, shape)
|
||||
const { x, y } = Vec.Add(localOffset, shape)
|
||||
this.updateShapes([{ id: shape.id, type: shape.type, x, y }], { squashing: true })
|
||||
const scale = new Vec2d(commonBounds.width / pageBounds.width, 1)
|
||||
const scale = new Vec(commonBounds.width / pageBounds.width, 1)
|
||||
this.resizeShape(shape.id, scale, {
|
||||
initialBounds: bounds,
|
||||
scaleOrigin: new Vec2d(commonBounds.minX, pageBounds.center.y),
|
||||
scaleOrigin: new Vec(commonBounds.minX, pageBounds.center.y),
|
||||
scaleAxisRotation: 0,
|
||||
})
|
||||
}
|
||||
|
@ -6186,8 +6186,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const id = typeof shape === 'string' ? shape : shape.id
|
||||
if (this.getInstanceState().isReadonly) return this
|
||||
|
||||
if (!Number.isFinite(scale.x)) scale = new Vec2d(1, scale.y)
|
||||
if (!Number.isFinite(scale.y)) scale = new Vec2d(scale.x, 1)
|
||||
if (!Number.isFinite(scale.x)) scale = new Vec(1, scale.y)
|
||||
if (!Number.isFinite(scale.y)) scale = new Vec(scale.x, 1)
|
||||
|
||||
const initialShape = options.initialShape ?? this.getShape(id)
|
||||
if (!initialShape) return this
|
||||
|
@ -6196,7 +6196,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (!scaleOrigin) return this
|
||||
|
||||
const pageTransform = options.initialPageTransform
|
||||
? Matrix2d.Cast(options.initialPageTransform)
|
||||
? Mat.Cast(options.initialPageTransform)
|
||||
: this.getShapePageTransform(id)
|
||||
if (!pageTransform) return this
|
||||
|
||||
|
@ -6229,16 +6229,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (util.isAspectRatioLocked(initialShape)) {
|
||||
if (Math.abs(scale.x) > Math.abs(scale.y)) {
|
||||
scale = new Vec2d(scale.x, Math.sign(scale.y) * Math.abs(scale.x))
|
||||
scale = new Vec(scale.x, Math.sign(scale.y) * Math.abs(scale.x))
|
||||
} else {
|
||||
scale = new Vec2d(Math.sign(scale.x) * Math.abs(scale.y), scale.y)
|
||||
scale = new Vec(Math.sign(scale.x) * Math.abs(scale.y), scale.y)
|
||||
}
|
||||
}
|
||||
|
||||
if (util.onResize && util.canResize(initialShape)) {
|
||||
// get the model changes from the shape util
|
||||
const newPagePoint = this._scalePagePoint(
|
||||
Matrix2d.applyToPoint(pageTransform, new Vec2d(0, 0)),
|
||||
Mat.applyToPoint(pageTransform, new Vec(0, 0)),
|
||||
scaleOrigin,
|
||||
scale,
|
||||
scaleAxisRotation
|
||||
|
@ -6247,7 +6247,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const newLocalPoint = this.getPointInParentSpace(initialShape.id, newPagePoint)
|
||||
|
||||
// resize the shape's local bounding box
|
||||
const myScale = new Vec2d(scale.x, scale.y)
|
||||
const myScale = new Vec(scale.x, scale.y)
|
||||
// the shape is aligned with the rest of the shapes in the selection, but may be
|
||||
// 90deg offset from the main rotation of the selection, in which case
|
||||
// we need to flip the width and height scale factors
|
||||
|
@ -6260,7 +6260,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
// adjust initial model for situations where the parent has moved during the resize
|
||||
// e.g. groups
|
||||
const initialPagePoint = Matrix2d.applyToPoint(pageTransform, new Vec2d())
|
||||
const initialPagePoint = Mat.applyToPoint(pageTransform, new Vec())
|
||||
|
||||
// need to adjust the shape's x and y points in case the parent has moved since start of resizing
|
||||
const { x, y } = this.getPointInParentSpace(initialShape.id, initialPagePoint)
|
||||
|
@ -6290,7 +6290,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
{ squashing: true }
|
||||
)
|
||||
} else {
|
||||
const initialPageCenter = Matrix2d.applyToPoint(pageTransform, initialBounds.center)
|
||||
const initialPageCenter = Mat.applyToPoint(pageTransform, initialBounds.center)
|
||||
// get the model changes from the shape util
|
||||
const newPageCenter = this._scalePagePoint(
|
||||
initialPageCenter,
|
||||
|
@ -6305,7 +6305,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
)
|
||||
const newPageCenterInParentSpace = this.getPointInParentSpace(initialShape.id, newPageCenter)
|
||||
|
||||
const delta = Vec2d.Sub(newPageCenterInParentSpace, initialPageCenterInParentSpace)
|
||||
const delta = Vec.Sub(newPageCenterInParentSpace, initialPageCenterInParentSpace)
|
||||
// apply the changes to the model
|
||||
this.updateShapes(
|
||||
[
|
||||
|
@ -6330,13 +6330,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
scale: VecLike,
|
||||
scaleAxisRotation: number
|
||||
) {
|
||||
const relativePoint = Vec2d.RotWith(point, scaleOrigin, -scaleAxisRotation).sub(scaleOrigin)
|
||||
const relativePoint = Vec.RotWith(point, scaleOrigin, -scaleAxisRotation).sub(scaleOrigin)
|
||||
|
||||
// calculate the new point position relative to the scale origin
|
||||
const newRelativePagePoint = Vec2d.MulV(relativePoint, scale)
|
||||
const newRelativePagePoint = Vec.MulV(relativePoint, scale)
|
||||
|
||||
// and rotate it back to page coords to get the new page point of the resized shape
|
||||
const destination = Vec2d.Add(newRelativePagePoint, scaleOrigin).rotWith(
|
||||
const destination = Vec.Add(newRelativePagePoint, scaleOrigin).rotWith(
|
||||
scaleOrigin,
|
||||
scaleAxisRotation
|
||||
)
|
||||
|
@ -6349,7 +6349,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
id: TLShapeId,
|
||||
scale: VecLike,
|
||||
options: {
|
||||
initialBounds: Box2d
|
||||
initialBounds: Box
|
||||
scaleOrigin: VecLike
|
||||
scaleAxisRotation: number
|
||||
initialShape: TLShape
|
||||
|
@ -6362,7 +6362,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// and then after applying the scale to the shape we also rotate it if required and translate it so that it's center
|
||||
// point ends up in the right place.
|
||||
|
||||
const shapeScale = new Vec2d(scale.x, scale.y)
|
||||
const shapeScale = new Vec(scale.x, scale.y)
|
||||
|
||||
// // make sure we are constraining aspect ratio, and using the smallest scale axis to avoid shapes getting bigger
|
||||
// // than the selection bounding box
|
||||
|
@ -6381,14 +6381,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// then if the shape is flipped in one axis only, we need to apply an extra rotation
|
||||
// to make sure the shape is mirrored correctly
|
||||
if (Math.sign(scale.x) * Math.sign(scale.y) < 0) {
|
||||
let { rotation } = Matrix2d.Decompose(options.initialPageTransform)
|
||||
let { rotation } = Mat.Decompose(options.initialPageTransform)
|
||||
rotation -= 2 * rotation
|
||||
this.updateShapes([{ id, type, rotation }], { squashing: true })
|
||||
}
|
||||
|
||||
// Next we need to translate the shape so that it's center point ends up in the right place.
|
||||
// To do that we first need to calculate the center point of the shape in the current page space before the scale was applied.
|
||||
const preScaleShapePageCenter = Matrix2d.applyToPoint(
|
||||
const preScaleShapePageCenter = Mat.applyToPoint(
|
||||
options.initialPageTransform,
|
||||
options.initialBounds.center
|
||||
)
|
||||
|
@ -6407,10 +6407,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const currentPageCenter = pageBounds.center
|
||||
const shapePageTransformOrigin = pageTransform.point()
|
||||
if (!currentPageCenter || !shapePageTransformOrigin) return this
|
||||
const pageDelta = Vec2d.Sub(postScaleShapePageCenter, currentPageCenter)
|
||||
const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
|
||||
|
||||
// and finally figure out what the shape's new position should be
|
||||
const postScaleShapePagePoint = Vec2d.Add(shapePageTransformOrigin, pageDelta)
|
||||
const postScaleShapePagePoint = Vec.Add(shapePageTransformOrigin, pageDelta)
|
||||
const { x, y } = this.getPointInParentSpace(id, postScaleShapePagePoint)
|
||||
|
||||
this.updateShapes([{ id, type, x, y }], { squashing: true })
|
||||
|
@ -6819,7 +6819,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
const shapesToGroup = compact(this._getUnlockedShapeIds(ids).map((id) => this.getShape(id)))
|
||||
const sortedShapeIds = shapesToGroup.sort(sortByIndex).map((s) => s.id)
|
||||
const pageBounds = Box2d.Common(compact(shapesToGroup.map((id) => this.getShapePageBounds(id))))
|
||||
const pageBounds = Box.Common(compact(shapesToGroup.map((id) => this.getShapePageBounds(id))))
|
||||
|
||||
const { x, y } = pageBounds.point
|
||||
|
||||
|
@ -7610,7 +7610,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (!ids) return
|
||||
if (ids.length === 0) return
|
||||
|
||||
const pageTransforms: Record<string, Matrix2dModel> = {}
|
||||
const pageTransforms: Record<string, MatModel> = {}
|
||||
|
||||
let shapesForContent = dedupe(
|
||||
ids
|
||||
|
@ -7687,9 +7687,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
: getCurvedArrowInfo(this, shape)
|
||||
|
||||
if (info?.isValid && infoAfter?.isValid && !getIsArrowStraight(shape)) {
|
||||
const mpA = Vec2d.Med(info.start.handle, info.end.handle)
|
||||
const distA = Vec2d.Dist(info.middle, mpA)
|
||||
const distB = Vec2d.Dist(infoAfter.middle, mpA)
|
||||
const mpA = Vec.Med(info.start.handle, info.end.handle)
|
||||
const distA = Vec.Dist(info.middle, mpA)
|
||||
const distB = Vec.Dist(infoAfter.middle, mpA)
|
||||
if (shape.props.bend < 0) {
|
||||
shape.props.bend += distB - distA
|
||||
} else {
|
||||
|
@ -8007,19 +8007,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
const newCreatedShapes = newShapes.map((s) => this.getShape(s.id)!)
|
||||
const bounds = Box2d.Common(newCreatedShapes.map((s) => this.getShapePageBounds(s)!))
|
||||
const bounds = Box.Common(newCreatedShapes.map((s) => this.getShapePageBounds(s)!))
|
||||
|
||||
if (point === undefined) {
|
||||
if (!isPageId(pasteParentId)) {
|
||||
// Put the shapes in the middle of the (on screen) parent
|
||||
const shape = this.getShape(pasteParentId)!
|
||||
point = Matrix2d.applyToPoint(
|
||||
point = Mat.applyToPoint(
|
||||
this.getShapePageTransform(shape),
|
||||
this.getShapeGeometry(shape).bounds.center
|
||||
)
|
||||
} else {
|
||||
const viewportPageBounds = this.getViewportPageBounds()
|
||||
if (preservePosition || viewportPageBounds.includes(Box2d.From(bounds))) {
|
||||
if (preservePosition || viewportPageBounds.includes(Box.From(bounds))) {
|
||||
// Otherwise, put shapes where they used to be
|
||||
point = bounds.center
|
||||
} else {
|
||||
|
@ -8047,17 +8047,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
}
|
||||
|
||||
const pageCenter = Box2d.Common(
|
||||
const pageCenter = Box.Common(
|
||||
compact(rootShapes.map(({ id }) => this.getShapePageBounds(id)))
|
||||
).center
|
||||
|
||||
const offset = Vec2d.Sub(point, pageCenter)
|
||||
const offset = Vec.Sub(point, pageCenter)
|
||||
|
||||
this.updateShapes(
|
||||
rootShapes.map(({ id }) => {
|
||||
const s = this.getShape(id)!
|
||||
const localRotation = this.getShapeParentTransform(id).decompose().rotation
|
||||
const localDelta = Vec2d.Rot(offset, -localRotation)
|
||||
const localDelta = Vec.Rot(offset, -localRotation)
|
||||
|
||||
return { id: s.id, type: s.type, x: s.x + localDelta.x, y: s.y + localDelta.y }
|
||||
})
|
||||
|
@ -8301,17 +8301,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
inputs = {
|
||||
/** The most recent pointer down's position in the current page space. */
|
||||
originPagePoint: new Vec2d(),
|
||||
originPagePoint: new Vec(),
|
||||
/** The most recent pointer down's position in screen space. */
|
||||
originScreenPoint: new Vec2d(),
|
||||
originScreenPoint: new Vec(),
|
||||
/** The previous pointer position in the current page space. */
|
||||
previousPagePoint: new Vec2d(),
|
||||
previousPagePoint: new Vec(),
|
||||
/** The previous pointer position in screen space. */
|
||||
previousScreenPoint: new Vec2d(),
|
||||
previousScreenPoint: new Vec(),
|
||||
/** The most recent pointer position in the current page space. */
|
||||
currentPagePoint: new Vec2d(),
|
||||
currentPagePoint: new Vec(),
|
||||
/** The most recent pointer position in screen space. */
|
||||
currentScreenPoint: new Vec2d(),
|
||||
currentScreenPoint: new Vec(),
|
||||
/** A set containing the currently pressed keys. */
|
||||
keys: new Set<string>(),
|
||||
/** A set containing the currently pressed buttons. */
|
||||
|
@ -8335,7 +8335,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
/** Whether the user is panning. */
|
||||
isPanning: false,
|
||||
/** Velocity of mouse pointer, in pixels per millisecond */
|
||||
pointerVelocity: new Vec2d(),
|
||||
pointerVelocity: new Vec(),
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8777,7 +8777,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (this.inputs.isPanning && this.inputs.isPointing) {
|
||||
// Handle panning
|
||||
const { currentScreenPoint, previousScreenPoint } = this.inputs
|
||||
this.pan(Vec2d.Sub(currentScreenPoint, previousScreenPoint))
|
||||
this.pan(Vec.Sub(currentScreenPoint, previousScreenPoint))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'
|
|||
import { TLArrowShape, TLShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import { Editor } from '../Editor'
|
||||
|
||||
export type TLArrowBindingsIndex = Record<
|
||||
type TLArrowBindingsIndex = Record<
|
||||
TLShapeId,
|
||||
undefined | { arrowId: TLShapeId; handleId: 'start' | 'end' }[]
|
||||
>
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
DRAG_DISTANCE,
|
||||
MULTI_CLICK_DURATION,
|
||||
} from '../../constants'
|
||||
import { Vec2d } from '../../primitives/Vec2d'
|
||||
import { Vec } from '../../primitives/Vec'
|
||||
import { uniqueId } from '../../utils/uniqueId'
|
||||
import type { Editor } from '../Editor'
|
||||
import { TLClickEventInfo, TLPointerEventInfo } from '../types/event-types'
|
||||
|
@ -26,9 +26,9 @@ export class ClickManager {
|
|||
|
||||
private _clickTimeout?: any
|
||||
|
||||
private _clickScreenPoint?: Vec2d
|
||||
private _clickScreenPoint?: Vec
|
||||
|
||||
private _previousScreenPoint?: Vec2d
|
||||
private _previousScreenPoint?: Vec
|
||||
|
||||
private _getClickTimeout = (state: TLClickState, id = uniqueId()) => {
|
||||
this._clickId = id
|
||||
|
@ -103,7 +103,7 @@ export class ClickManager {
|
|||
transformPointerDownEvent = (info: TLPointerEventInfo): TLPointerEventInfo | TLClickEventInfo => {
|
||||
if (!this._clickState) return info
|
||||
|
||||
this._clickScreenPoint = Vec2d.From(info.point)
|
||||
this._clickScreenPoint = Vec.From(info.point)
|
||||
|
||||
if (
|
||||
this._previousScreenPoint &&
|
||||
|
@ -173,7 +173,7 @@ export class ClickManager {
|
|||
transformPointerUpEvent = (info: TLPointerEventInfo): TLPointerEventInfo | TLClickEventInfo => {
|
||||
if (!this._clickState) return info
|
||||
|
||||
this._clickScreenPoint = Vec2d.From(info.point)
|
||||
this._clickScreenPoint = Vec.From(info.point)
|
||||
|
||||
switch (this._clickState) {
|
||||
case 'pendingTriple': {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TLScribble, Vec2dModel } from '@tldraw/tlschema'
|
||||
import { Vec2d } from '../../primitives/Vec2d'
|
||||
import { TLScribble, VecModel } from '@tldraw/tlschema'
|
||||
import { Vec } from '../../primitives/Vec'
|
||||
import { uniqueId } from '../../utils/uniqueId'
|
||||
import { Editor } from '../Editor'
|
||||
import { TLTickEvent } from '../types/event-types'
|
||||
|
@ -9,8 +9,8 @@ type ScribbleItem = {
|
|||
scribble: TLScribble
|
||||
timeoutMs: number
|
||||
delayRemaining: number
|
||||
prev: null | Vec2dModel
|
||||
next: null | Vec2dModel
|
||||
prev: null | VecModel
|
||||
next: null | VecModel
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -87,7 +87,7 @@ export class ScribbleManager {
|
|||
if (!item) throw Error(`Scribble with id ${id} not found`)
|
||||
const { prev } = item
|
||||
const point = { x, y, z: 0.5 }
|
||||
if (!prev || Vec2d.Dist(prev, point) >= 1) {
|
||||
if (!prev || Vec.Dist(prev, point) >= 1) {
|
||||
item.next = point
|
||||
}
|
||||
return item
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { atom, computed, EMPTY_ARRAY } from '@tldraw/state'
|
||||
import { TLGroupShape, TLParentId, TLShape, TLShapeId, Vec2dModel } from '@tldraw/tlschema'
|
||||
import { TLGroupShape, TLParentId, TLShape, TLShapeId, VecModel } from '@tldraw/tlschema'
|
||||
import { dedupe, deepCopy } from '@tldraw/utils'
|
||||
import {
|
||||
Box2d,
|
||||
Box,
|
||||
flipSelectionHandleX,
|
||||
flipSelectionHandleY,
|
||||
isSelectionCorner,
|
||||
SelectionCorner,
|
||||
SelectionEdge,
|
||||
} from '../../primitives/Box2d'
|
||||
import { Matrix2d } from '../../primitives/Matrix2d'
|
||||
} from '../../primitives/Box'
|
||||
import { Mat } from '../../primitives/Mat'
|
||||
import { rangeIntersection, rangesOverlap } from '../../primitives/utils'
|
||||
import { Vec2d, VecLike } from '../../primitives/Vec2d'
|
||||
import { Vec, VecLike } from '../../primitives/Vec'
|
||||
import { uniqueId } from '../../utils/uniqueId'
|
||||
import type { Editor } from '../Editor'
|
||||
|
||||
|
@ -36,16 +36,6 @@ export type GapsSnapLine = {
|
|||
/** @public */
|
||||
export type SnapLine = PointsSnapLine | GapsSnapLine
|
||||
|
||||
export type SnapInteractionType =
|
||||
| {
|
||||
type: 'translate'
|
||||
lockedAxis: 'x' | 'y' | null
|
||||
initialSelectionSnapPoints: Vec2d[]
|
||||
}
|
||||
| {
|
||||
type: 'resize'
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface SnapPoint {
|
||||
id: string
|
||||
|
@ -82,7 +72,7 @@ type NearestSnap =
|
|||
|
||||
type GapNode = {
|
||||
id: TLShapeId
|
||||
pageBounds: Box2d
|
||||
pageBounds: Box
|
||||
isClosed: boolean
|
||||
}
|
||||
|
||||
|
@ -108,14 +98,14 @@ type Gap = {
|
|||
// ◄─────────────────────────►
|
||||
startNode: GapNode
|
||||
endNode: GapNode
|
||||
startEdge: [Vec2d, Vec2d]
|
||||
endEdge: [Vec2d, Vec2d]
|
||||
startEdge: [Vec, Vec]
|
||||
endEdge: [Vec, Vec]
|
||||
length: number
|
||||
breadthIntersection: [number, number]
|
||||
}
|
||||
|
||||
interface SnapData {
|
||||
nudge: Vec2d
|
||||
nudge: Vec
|
||||
}
|
||||
|
||||
const round = (x: number) => {
|
||||
|
@ -235,7 +225,7 @@ export class SnapManager {
|
|||
if (!pageTransfrorm) return undefined
|
||||
const snapPoints = this.editor.getShapeGeometry(shape).snapPoints
|
||||
return snapPoints.map((point, i) => {
|
||||
const { x, y } = Matrix2d.applyToPoint(pageTransfrorm, point)
|
||||
const { x, y } = Mat.applyToPoint(pageTransfrorm, point)
|
||||
return { x, y, id: `${shape.id}:${i}` }
|
||||
})
|
||||
})
|
||||
|
@ -336,12 +326,12 @@ export class SnapManager {
|
|||
startNode,
|
||||
endNode,
|
||||
startEdge: [
|
||||
new Vec2d(startNode.pageBounds.maxX, startNode.pageBounds.minY),
|
||||
new Vec2d(startNode.pageBounds.maxX, startNode.pageBounds.maxY),
|
||||
new Vec(startNode.pageBounds.maxX, startNode.pageBounds.minY),
|
||||
new Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY),
|
||||
],
|
||||
endEdge: [
|
||||
new Vec2d(endNode.pageBounds.minX, endNode.pageBounds.minY),
|
||||
new Vec2d(endNode.pageBounds.minX, endNode.pageBounds.maxY),
|
||||
new Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),
|
||||
new Vec(endNode.pageBounds.minX, endNode.pageBounds.maxY),
|
||||
],
|
||||
length: endNode.pageBounds.minX - startNode.pageBounds.maxX,
|
||||
breadthIntersection: rangeIntersection(
|
||||
|
@ -380,12 +370,12 @@ export class SnapManager {
|
|||
startNode,
|
||||
endNode,
|
||||
startEdge: [
|
||||
new Vec2d(startNode.pageBounds.minX, startNode.pageBounds.maxY),
|
||||
new Vec2d(startNode.pageBounds.maxX, startNode.pageBounds.maxY),
|
||||
new Vec(startNode.pageBounds.minX, startNode.pageBounds.maxY),
|
||||
new Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY),
|
||||
],
|
||||
endEdge: [
|
||||
new Vec2d(endNode.pageBounds.minX, endNode.pageBounds.minY),
|
||||
new Vec2d(endNode.pageBounds.maxX, endNode.pageBounds.minY),
|
||||
new Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),
|
||||
new Vec(endNode.pageBounds.maxX, endNode.pageBounds.minY),
|
||||
],
|
||||
length: endNode.pageBounds.minY - startNode.pageBounds.maxY,
|
||||
breadthIntersection: rangeIntersection(
|
||||
|
@ -410,8 +400,8 @@ export class SnapManager {
|
|||
}: {
|
||||
lockedAxis: 'x' | 'y' | null
|
||||
initialSelectionSnapPoints: SnapPoint[]
|
||||
initialSelectionPageBounds: Box2d
|
||||
dragDelta: Vec2d
|
||||
initialSelectionPageBounds: Box
|
||||
dragDelta: Vec
|
||||
}): SnapData {
|
||||
const snapThreshold = this.getSnapThreshold()
|
||||
const visibleSnapPointsNotInSelection = this.getSnappablePoints()
|
||||
|
@ -428,7 +418,7 @@ export class SnapManager {
|
|||
|
||||
const nearestSnapsX: NearestSnap[] = []
|
||||
const nearestSnapsY: NearestSnap[] = []
|
||||
const minOffset = new Vec2d(snapThreshold, snapThreshold)
|
||||
const minOffset = new Vec(snapThreshold, snapThreshold)
|
||||
|
||||
this.collectPointSnaps({
|
||||
minOffset,
|
||||
|
@ -446,7 +436,7 @@ export class SnapManager {
|
|||
})
|
||||
|
||||
// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)
|
||||
const nudge = new Vec2d(
|
||||
const nudge = new Vec(
|
||||
lockedAxis === 'x' ? 0 : nearestSnapsX[0]?.nudge ?? 0,
|
||||
lockedAxis === 'y' ? 0 : nearestSnapsY[0]?.nudge ?? 0
|
||||
)
|
||||
|
@ -501,7 +491,7 @@ export class SnapManager {
|
|||
if (isClosed) outline.push(outline[0])
|
||||
const pageTransform = this.editor.getShapePageTransform(id)
|
||||
if (!pageTransform) throw Error('No page transform')
|
||||
return Matrix2d.applyToPoints(pageTransform, outline)
|
||||
return Mat.applyToPoints(pageTransform, outline)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -509,16 +499,16 @@ export class SnapManager {
|
|||
handlePoint,
|
||||
additionalSegments,
|
||||
}: {
|
||||
handlePoint: Vec2d
|
||||
additionalSegments: Vec2d[][]
|
||||
}): Vec2d | null {
|
||||
handlePoint: Vec
|
||||
additionalSegments: Vec[][]
|
||||
}): Vec | null {
|
||||
const snapThreshold = this.getSnapThreshold()
|
||||
const outlinesInPageSpace = this.getOutlinesInPageSpace()
|
||||
|
||||
// Find the nearest point that is within the snap threshold
|
||||
let minDistance = snapThreshold
|
||||
let nearestPoint: Vec2d | null = null
|
||||
let C: Vec2dModel, D: Vec2dModel, nearest: Vec2d, distance: number
|
||||
let nearestPoint: Vec | null = null
|
||||
let C: VecModel, D: VecModel, nearest: Vec, distance: number
|
||||
|
||||
const allSegments = [...outlinesInPageSpace, ...additionalSegments]
|
||||
for (const outline of allSegments) {
|
||||
|
@ -526,8 +516,8 @@ export class SnapManager {
|
|||
C = outline[i]
|
||||
D = outline[i + 1]
|
||||
|
||||
nearest = Vec2d.NearestPointOnLineSegment(C, D, handlePoint)
|
||||
distance = Vec2d.Dist(handlePoint, nearest)
|
||||
nearest = Vec.NearestPointOnLineSegment(C, D, handlePoint)
|
||||
distance = Vec.Dist(handlePoint, nearest)
|
||||
|
||||
if (isNaN(distance)) continue
|
||||
if (distance < minDistance) {
|
||||
|
@ -547,7 +537,7 @@ export class SnapManager {
|
|||
},
|
||||
])
|
||||
|
||||
return Vec2d.Sub(nearestPoint, handlePoint)
|
||||
return Vec.Sub(nearestPoint, handlePoint)
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -561,9 +551,9 @@ export class SnapManager {
|
|||
isResizingFromCenter,
|
||||
}: {
|
||||
// the page bounds when the pointer went down, before any dragging
|
||||
initialSelectionPageBounds: Box2d
|
||||
initialSelectionPageBounds: Box
|
||||
// how far the pointer has been dragged
|
||||
dragDelta: Vec2d
|
||||
dragDelta: Vec
|
||||
|
||||
handle: SelectionCorner | SelectionEdge
|
||||
isAspectRatioLocked: boolean
|
||||
|
@ -576,7 +566,7 @@ export class SnapManager {
|
|||
box: unsnappedResizedPageBounds,
|
||||
scaleX,
|
||||
scaleY,
|
||||
} = Box2d.Resize(
|
||||
} = Box.Resize(
|
||||
initialSelectionPageBounds,
|
||||
originalHandle,
|
||||
isResizingFromCenter ? dragDelta.x * 2 : dragDelta.x,
|
||||
|
@ -607,7 +597,7 @@ export class SnapManager {
|
|||
|
||||
const nearestSnapsX: NearestPointsSnap[] = []
|
||||
const nearestSnapsY: NearestPointsSnap[] = []
|
||||
const minOffset = new Vec2d(snapThreshold, snapThreshold)
|
||||
const minOffset = new Vec(snapThreshold, snapThreshold)
|
||||
|
||||
this.collectPointSnaps({
|
||||
minOffset,
|
||||
|
@ -618,7 +608,7 @@ export class SnapManager {
|
|||
})
|
||||
|
||||
// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)
|
||||
const nudge = new Vec2d(
|
||||
const nudge = new Vec(
|
||||
isXLocked ? 0 : nearestSnapsX[0]?.nudge ?? 0,
|
||||
isYLocked ? 0 : nearestSnapsY[0]?.nudge ?? 0
|
||||
)
|
||||
|
@ -657,10 +647,10 @@ export class SnapManager {
|
|||
|
||||
// now resize the box after nudging, calculate the snaps again, and return the snap lines to match
|
||||
// the fully resized box
|
||||
const snappedDelta = Vec2d.Add(dragDelta, nudge)
|
||||
const snappedDelta = Vec.Add(dragDelta, nudge)
|
||||
|
||||
// first figure out the new bounds of the selection
|
||||
const { box: snappedResizedPageBounds } = Box2d.Resize(
|
||||
const { box: snappedResizedPageBounds } = Box.Resize(
|
||||
initialSelectionPageBounds,
|
||||
originalHandle,
|
||||
isResizingFromCenter ? snappedDelta.x * 2 : snappedDelta.x,
|
||||
|
@ -706,7 +696,7 @@ export class SnapManager {
|
|||
}: {
|
||||
selectionSnapPoints: SnapPoint[]
|
||||
otherNodeSnapPoints: SnapPoint[]
|
||||
minOffset: Vec2d
|
||||
minOffset: Vec
|
||||
nearestSnapsX: NearestSnap[]
|
||||
nearestSnapsY: NearestSnap[]
|
||||
}) {
|
||||
|
@ -714,7 +704,7 @@ export class SnapManager {
|
|||
// which are closest to it in each axis
|
||||
for (const thisSnapPoint of selectionSnapPoints) {
|
||||
for (const otherSnapPoint of otherNodeSnapPoints) {
|
||||
const offset = Vec2d.Sub(thisSnapPoint, otherSnapPoint)
|
||||
const offset = Vec.Sub(thisSnapPoint, otherSnapPoint)
|
||||
const offsetX = Math.abs(offset.x)
|
||||
const offsetY = Math.abs(offset.y)
|
||||
|
||||
|
@ -756,8 +746,8 @@ export class SnapManager {
|
|||
nearestSnapsX,
|
||||
nearestSnapsY,
|
||||
}: {
|
||||
selectionPageBounds: Box2d
|
||||
minOffset: Vec2d
|
||||
selectionPageBounds: Box
|
||||
minOffset: Vec
|
||||
nearestSnapsX: NearestSnap[]
|
||||
nearestSnapsY: NearestSnap[]
|
||||
}) {
|
||||
|
@ -1078,10 +1068,10 @@ export class SnapManager {
|
|||
type: 'points',
|
||||
points: dedupe(
|
||||
snapGroup
|
||||
.map((snap) => Vec2d.From(snap.otherPoint))
|
||||
.map((snap) => Vec.From(snap.otherPoint))
|
||||
// be sure to nudge over the selection snap points
|
||||
.concat(snapGroup.map((snap) => Vec2d.From(snap.thisPoint))),
|
||||
(a: Vec2d, b: Vec2d) => a.equals(b)
|
||||
.concat(snapGroup.map((snap) => Vec.From(snap.thisPoint))),
|
||||
(a: Vec, b: Vec) => a.equals(b)
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
@ -1091,13 +1081,13 @@ export class SnapManager {
|
|||
nearestSnapsX,
|
||||
nearestSnapsY,
|
||||
}: {
|
||||
selectionPageBounds: Box2d
|
||||
selectionPageBounds: Box
|
||||
nearestSnapsX: NearestSnap[]
|
||||
nearestSnapsY: NearestSnap[]
|
||||
}): GapsSnapLine[] {
|
||||
const { vertical, horizontal } = this.getVisibleGaps()
|
||||
|
||||
const selectionSides: Record<SelectionEdge, [Vec2d, Vec2d]> = {
|
||||
const selectionSides: Record<SelectionEdge, [Vec, Vec]> = {
|
||||
top: selectionPageBounds.sides[0],
|
||||
right: selectionPageBounds.sides[1],
|
||||
// need bottom and left to be sorted asc, which .sides is not.
|
||||
|
@ -1175,7 +1165,7 @@ export class SnapManager {
|
|||
startEdge: selectionSides.right,
|
||||
endEdge: startEdge.map((v) =>
|
||||
v.clone().addXY(-startNode.pageBounds.width, 0)
|
||||
) as [Vec2d, Vec2d],
|
||||
) as [Vec, Vec],
|
||||
},
|
||||
{ startEdge, endEdge },
|
||||
...findAdjacentGaps(
|
||||
|
@ -1198,7 +1188,7 @@ export class SnapManager {
|
|||
{
|
||||
startEdge: endEdge.map((v) =>
|
||||
v.clone().addXY(snap.gap.endNode.pageBounds.width, 0)
|
||||
) as [Vec2d, Vec2d],
|
||||
) as [Vec, Vec],
|
||||
endEdge: selectionSides.left,
|
||||
},
|
||||
],
|
||||
|
@ -1279,7 +1269,7 @@ export class SnapManager {
|
|||
startEdge: selectionSides.bottom,
|
||||
endEdge: startEdge.map((v) =>
|
||||
v.clone().addXY(0, -startNode.pageBounds.height)
|
||||
) as [Vec2d, Vec2d],
|
||||
) as [Vec, Vec],
|
||||
},
|
||||
{ startEdge, endEdge },
|
||||
...findAdjacentGaps(
|
||||
|
@ -1302,7 +1292,7 @@ export class SnapManager {
|
|||
{
|
||||
startEdge: endEdge.map((v) =>
|
||||
v.clone().addXY(0, endNode.pageBounds.height)
|
||||
) as [Vec2d, Vec2d],
|
||||
) as [Vec, Vec],
|
||||
endEdge: selectionSides.top,
|
||||
},
|
||||
],
|
||||
|
@ -1320,7 +1310,7 @@ export class SnapManager {
|
|||
|
||||
function getResizeSnapPointsForHandle(
|
||||
handle: SelectionCorner | SelectionEdge | 'any',
|
||||
selectionPageBounds: Box2d
|
||||
selectionPageBounds: Box
|
||||
): SnapPoint[] {
|
||||
const { minX, maxX, minY, maxY } = selectionPageBounds
|
||||
const result: SnapPoint[] = []
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Box2dModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
|
||||
import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
|
||||
import { uniqueId } from '../../utils/uniqueId'
|
||||
import { Editor } from '../Editor'
|
||||
|
||||
|
@ -72,7 +72,7 @@ export class TextManager {
|
|||
minWidth?: string
|
||||
padding: string
|
||||
}
|
||||
): Box2dModel => {
|
||||
): BoxModel => {
|
||||
const elm = this.getTextElement()
|
||||
|
||||
elm.setAttribute('dir', 'ltr')
|
||||
|
@ -103,7 +103,7 @@ export class TextManager {
|
|||
measureElementTextNodeSpans(
|
||||
element: HTMLElement,
|
||||
{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}
|
||||
): { spans: { box: Box2dModel; text: string }[]; didTruncate: boolean } {
|
||||
): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {
|
||||
const spans = []
|
||||
|
||||
// Measurements of individual spans are relative to the containing element
|
||||
|
@ -195,7 +195,7 @@ export class TextManager {
|
|||
measureTextSpans(
|
||||
textToMeasure: string,
|
||||
opts: TLMeasureTextSpanOpts
|
||||
): { text: string; box: Box2dModel }[] {
|
||||
): { text: string; box: BoxModel }[] {
|
||||
if (textToMeasure === '') return []
|
||||
|
||||
const shouldTruncateToFirstLine =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Vec2d } from '../../primitives/Vec2d'
|
||||
import { Vec } from '../../primitives/Vec'
|
||||
import { Editor } from '../Editor'
|
||||
|
||||
export class TickManager {
|
||||
|
@ -48,7 +48,7 @@ export class TickManager {
|
|||
cancelAnimationFrame(this.raf)
|
||||
}
|
||||
|
||||
private prevPoint = new Vec2d()
|
||||
private prevPoint = new Vec()
|
||||
|
||||
private updatePointerVelocity = (elapsed: number) => {
|
||||
const {
|
||||
|
@ -60,11 +60,11 @@ export class TickManager {
|
|||
|
||||
if (elapsed === 0) return
|
||||
|
||||
const delta = Vec2d.Sub(currentScreenPoint, prevPoint)
|
||||
const delta = Vec.Sub(currentScreenPoint, prevPoint)
|
||||
this.prevPoint = currentScreenPoint.clone()
|
||||
|
||||
const length = delta.len()
|
||||
const direction = length ? delta.div(length) : new Vec2d(0, 0)
|
||||
const direction = length ? delta.div(length) : new Vec(0, 0)
|
||||
|
||||
// consider adjusting this with an easing rather than a linear interpolation
|
||||
const next = pointerVelocity.clone().lrp(direction.mul(length / elapsed), 0.5)
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { atom } from '@tldraw/state'
|
||||
import { getAtomManager } from './getRecordManager'
|
||||
|
||||
describe('atom manager', () => {
|
||||
it('manages an atom object', () => {
|
||||
const cb = jest.fn()
|
||||
const A = atom('abc', { a: 1, b: 2, c: 3 })
|
||||
const manager = getAtomManager(A, cb)
|
||||
|
||||
expect(A.lastChangedEpoch).toBe(0)
|
||||
|
||||
manager.a = 2
|
||||
expect(manager.a).toBe(2)
|
||||
expect(A.lastChangedEpoch).toBe(1)
|
||||
|
||||
manager.b = 4
|
||||
expect(manager.b).toBe(4)
|
||||
expect(A.lastChangedEpoch).toBe(2)
|
||||
|
||||
manager.b
|
||||
expect(A.get()).toMatchObject({ a: 2, b: 4, c: 3 })
|
||||
|
||||
expect(cb).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
|
@ -1,28 +0,0 @@
|
|||
import { Atom, computed } from '@tldraw/state'
|
||||
|
||||
export function getAtomManager<T extends { [key: string]: any }>(
|
||||
atom: Atom<T>,
|
||||
transform?: (prev: T, next: T) => T
|
||||
): T {
|
||||
const update = (value: Partial<T>) => {
|
||||
const curr = atom.get()
|
||||
const next = { ...curr, ...value }
|
||||
const final = transform?.(atom.get(), atom.get()) ?? next
|
||||
atom.set(final)
|
||||
}
|
||||
|
||||
return Object.defineProperties(
|
||||
{} as T,
|
||||
Object.keys(atom.get()).reduce((acc, key) => {
|
||||
acc[key as keyof T] = computed(atom, key, {
|
||||
get() {
|
||||
return atom.get()[key as keyof T]
|
||||
},
|
||||
set(value: T[keyof T]) {
|
||||
update({ [key]: value } as any)
|
||||
},
|
||||
})
|
||||
return acc
|
||||
}, {} as { [key in keyof T]: PropertyDescriptor })
|
||||
)
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Migrations } from '@tldraw/store'
|
||||
import { ShapeProps, TLHandle, TLShape, TLShapePartial, TLUnknownShape } from '@tldraw/tlschema'
|
||||
import { Box2d } from '../../primitives/Box2d'
|
||||
import { Vec2d } from '../../primitives/Vec2d'
|
||||
import { Box } from '../../primitives/Box'
|
||||
import { Vec } from '../../primitives/Vec'
|
||||
import { Geometry2d } from '../../primitives/geometry/Geometry2d'
|
||||
import type { Editor } from '../Editor'
|
||||
import { SvgExportContext } from '../types/SvgExportContext'
|
||||
|
@ -214,7 +214,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
getOutlineSegments(shape: Shape): Vec2d[][] {
|
||||
getOutlineSegments(shape: Shape): Vec[][] {
|
||||
return [this.editor.getShapeGeometry(shape).vertices]
|
||||
}
|
||||
|
||||
|
@ -554,12 +554,12 @@ export type TLResizeMode = 'scale_shape' | 'resize_bounds'
|
|||
* @public
|
||||
*/
|
||||
export type TLResizeInfo<T extends TLShape> = {
|
||||
newPoint: Vec2d
|
||||
newPoint: Vec
|
||||
handle: TLResizeHandle
|
||||
mode: TLResizeMode
|
||||
scaleX: number
|
||||
scaleY: number
|
||||
initialBounds: Box2d
|
||||
initialBounds: Box
|
||||
initialShape: T
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useValue } from '@tldraw/state'
|
||||
import { useEditor } from '../../../hooks/useEditor'
|
||||
import { Box2d } from '../../../primitives/Box2d'
|
||||
import { Box } from '../../../primitives/Box'
|
||||
import { getPerfectDashProps } from '../shared/getPerfectDashProps'
|
||||
|
||||
export function DashedOutlineBox({ bounds, className }: { bounds: Box2d; className: string }) {
|
||||
export function DashedOutlineBox({ bounds, className }: { bounds: Box; className: string }) {
|
||||
const editor = useEditor()
|
||||
|
||||
const zoomLevel = useValue('zoom level', () => editor.getZoomLevel(), [editor])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TLArrowShapeArrowheadStyle } from '@tldraw/tlschema'
|
||||
import { VecLike } from '../../../../primitives/Vec2d'
|
||||
import { VecLike } from '../../../../primitives/Vec'
|
||||
|
||||
/** @public */
|
||||
export type TLArrowPoint = {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { TLArrowShape } from '@tldraw/tlschema'
|
||||
import { Box2d } from '../../../../primitives/Box2d'
|
||||
import { Matrix2d } from '../../../../primitives/Matrix2d'
|
||||
import { Vec2d, VecLike } from '../../../../primitives/Vec2d'
|
||||
import { Mat } from '../../../../primitives/Mat'
|
||||
import { Vec, VecLike } from '../../../../primitives/Vec'
|
||||
import { intersectCirclePolygon, intersectCirclePolyline } from '../../../../primitives/intersect'
|
||||
import {
|
||||
PI,
|
||||
|
@ -37,9 +36,9 @@ export function getCurvedArrowInfo(
|
|||
|
||||
const terminalsInArrowSpace = getArrowTerminalsInArrowSpace(editor, shape)
|
||||
|
||||
const med = Vec2d.Med(terminalsInArrowSpace.start, terminalsInArrowSpace.end) // point between start and end
|
||||
const u = Vec2d.Sub(terminalsInArrowSpace.end, terminalsInArrowSpace.start).uni() // unit vector between start and end
|
||||
const middle = Vec2d.Add(med, u.per().mul(-bend)) // middle handle
|
||||
const med = Vec.Med(terminalsInArrowSpace.start, terminalsInArrowSpace.end) // point between start and end
|
||||
const u = Vec.Sub(terminalsInArrowSpace.end, terminalsInArrowSpace.start).uni() // unit vector between start and end
|
||||
const middle = Vec.Add(med, u.per().mul(-bend)) // middle handle
|
||||
|
||||
const startShapeInfo = getBoundShapeInfoForTerminal(editor, shape.props.start)
|
||||
const endShapeInfo = getBoundShapeInfoForTerminal(editor, shape.props.end)
|
||||
|
@ -50,7 +49,7 @@ export function getCurvedArrowInfo(
|
|||
const b = terminalsInArrowSpace.end.clone()
|
||||
const c = middle.clone()
|
||||
|
||||
if (Vec2d.Equals(a, b)) {
|
||||
if (Vec.Equals(a, b)) {
|
||||
return {
|
||||
isStraight: true,
|
||||
start: {
|
||||
|
@ -73,8 +72,8 @@ export function getCurvedArrowInfo(
|
|||
const distFn = isClockwise ? clockwiseAngleDist : counterClockwiseAngleDist
|
||||
|
||||
const handleArc = getArcInfo(a, b, c)
|
||||
const handle_aCA = Vec2d.Angle(handleArc.center, a)
|
||||
const handle_aCB = Vec2d.Angle(handleArc.center, b)
|
||||
const handle_aCA = Vec.Angle(handleArc.center, a)
|
||||
const handle_aCB = Vec.Angle(handleArc.center, b)
|
||||
const handle_dAB = distFn(handle_aCA, handle_aCB)
|
||||
|
||||
if (
|
||||
|
@ -98,15 +97,15 @@ export function getCurvedArrowInfo(
|
|||
let minLength = MIN_ARROW_LENGTH
|
||||
|
||||
if (startShapeInfo && !startShapeInfo.isExact) {
|
||||
const startInPageSpace = Matrix2d.applyToPoint(arrowPageTransform, tempA)
|
||||
const centerInPageSpace = Matrix2d.applyToPoint(arrowPageTransform, handleArc.center)
|
||||
const endInPageSpace = Matrix2d.applyToPoint(arrowPageTransform, tempB)
|
||||
const startInPageSpace = Mat.applyToPoint(arrowPageTransform, tempA)
|
||||
const centerInPageSpace = Mat.applyToPoint(arrowPageTransform, handleArc.center)
|
||||
const endInPageSpace = Mat.applyToPoint(arrowPageTransform, tempB)
|
||||
|
||||
const inverseTransform = Matrix2d.Inverse(startShapeInfo.transform)
|
||||
const inverseTransform = Mat.Inverse(startShapeInfo.transform)
|
||||
|
||||
const startInStartShapeLocalSpace = Matrix2d.applyToPoint(inverseTransform, startInPageSpace)
|
||||
const centerInStartShapeLocalSpace = Matrix2d.applyToPoint(inverseTransform, centerInPageSpace)
|
||||
const endInStartShapeLocalSpace = Matrix2d.applyToPoint(inverseTransform, endInPageSpace)
|
||||
const startInStartShapeLocalSpace = Mat.applyToPoint(inverseTransform, startInPageSpace)
|
||||
const centerInStartShapeLocalSpace = Mat.applyToPoint(inverseTransform, centerInPageSpace)
|
||||
const endInStartShapeLocalSpace = Mat.applyToPoint(inverseTransform, endInPageSpace)
|
||||
|
||||
const { isClosed } = startShapeInfo
|
||||
const fn = isClosed ? intersectCirclePolygon : intersectCirclePolyline
|
||||
|
@ -148,7 +147,7 @@ export function getCurvedArrowInfo(
|
|||
|
||||
if (point) {
|
||||
tempA.setTo(
|
||||
editor.getPointInShapeSpace(shape, Matrix2d.applyToPoint(startShapeInfo.transform, point))
|
||||
editor.getPointInShapeSpace(shape, Mat.applyToPoint(startShapeInfo.transform, point))
|
||||
)
|
||||
|
||||
startShapeInfo.didIntersect = true
|
||||
|
@ -167,15 +166,15 @@ export function getCurvedArrowInfo(
|
|||
|
||||
if (endShapeInfo && !endShapeInfo.isExact) {
|
||||
// get points in shape's coordinates?
|
||||
const startInPageSpace = Matrix2d.applyToPoint(arrowPageTransform, tempA)
|
||||
const endInPageSpace = Matrix2d.applyToPoint(arrowPageTransform, tempB)
|
||||
const centerInPageSpace = Matrix2d.applyToPoint(arrowPageTransform, handleArc.center)
|
||||
const startInPageSpace = Mat.applyToPoint(arrowPageTransform, tempA)
|
||||
const endInPageSpace = Mat.applyToPoint(arrowPageTransform, tempB)
|
||||
const centerInPageSpace = Mat.applyToPoint(arrowPageTransform, handleArc.center)
|
||||
|
||||
const inverseTransform = Matrix2d.Inverse(endShapeInfo.transform)
|
||||
const inverseTransform = Mat.Inverse(endShapeInfo.transform)
|
||||
|
||||
const startInEndShapeLocalSpace = Matrix2d.applyToPoint(inverseTransform, startInPageSpace)
|
||||
const centerInEndShapeLocalSpace = Matrix2d.applyToPoint(inverseTransform, centerInPageSpace)
|
||||
const endInEndShapeLocalSpace = Matrix2d.applyToPoint(inverseTransform, endInPageSpace)
|
||||
const startInEndShapeLocalSpace = Mat.applyToPoint(inverseTransform, startInPageSpace)
|
||||
const centerInEndShapeLocalSpace = Mat.applyToPoint(inverseTransform, centerInPageSpace)
|
||||
const endInEndShapeLocalSpace = Mat.applyToPoint(inverseTransform, endInPageSpace)
|
||||
|
||||
const isClosed = endShapeInfo.isClosed
|
||||
const fn = isClosed ? intersectCirclePolygon : intersectCirclePolyline
|
||||
|
@ -222,7 +221,7 @@ export function getCurvedArrowInfo(
|
|||
if (point) {
|
||||
// Set b to target local point -> page point -> shape local point
|
||||
tempB.setTo(
|
||||
editor.getPointInShapeSpace(shape, Matrix2d.applyToPoint(endShapeInfo.transform, point))
|
||||
editor.getPointInShapeSpace(shape, Mat.applyToPoint(endShapeInfo.transform, point))
|
||||
)
|
||||
|
||||
endShapeInfo.didIntersect = true
|
||||
|
@ -239,8 +238,8 @@ export function getCurvedArrowInfo(
|
|||
|
||||
// Apply arrowhead offsets
|
||||
|
||||
let aCA = Vec2d.Angle(handleArc.center, tempA) // angle center -> a
|
||||
let aCB = Vec2d.Angle(handleArc.center, tempB) // angle center -> b
|
||||
let aCA = Vec.Angle(handleArc.center, tempA) // angle center -> a
|
||||
let aCB = Vec.Angle(handleArc.center, tempB) // angle center -> b
|
||||
let dAB = distFn(aCA, aCB) // angle distance between a and b
|
||||
let lAB = dAB * handleArc.radius // length of arc between a and b
|
||||
|
||||
|
@ -252,17 +251,17 @@ export function getCurvedArrowInfo(
|
|||
|
||||
if (offsetA !== 0) {
|
||||
const n = (offsetA / lAB) * (isClockwise ? 1 : -1)
|
||||
const u = Vec2d.FromAngle(aCA + dAB * n)
|
||||
const u = Vec.FromAngle(aCA + dAB * n)
|
||||
tA.setTo(handleArc.center).add(u.mul(handleArc.radius))
|
||||
}
|
||||
|
||||
if (offsetB !== 0) {
|
||||
const n = (offsetB / lAB) * (isClockwise ? -1 : 1)
|
||||
const u = Vec2d.FromAngle(aCB + dAB * n)
|
||||
const u = Vec.FromAngle(aCB + dAB * n)
|
||||
tB.setTo(handleArc.center).add(u.mul(handleArc.radius))
|
||||
}
|
||||
|
||||
const distAB = Vec2d.Dist(tA, tB)
|
||||
const distAB = Vec.Dist(tA, tB)
|
||||
if (distAB < minLength) {
|
||||
if (offsetA !== 0 && offsetB !== 0) {
|
||||
offsetA *= -1.5
|
||||
|
@ -278,20 +277,20 @@ export function getCurvedArrowInfo(
|
|||
|
||||
if (offsetA !== 0) {
|
||||
const n = (offsetA / lAB) * (isClockwise ? 1 : -1)
|
||||
const u = Vec2d.FromAngle(aCA + dAB * n)
|
||||
const u = Vec.FromAngle(aCA + dAB * n)
|
||||
tempA.setTo(handleArc.center).add(u.mul(handleArc.radius))
|
||||
}
|
||||
|
||||
if (offsetB !== 0) {
|
||||
const n = (offsetB / lAB) * (isClockwise ? -1 : 1)
|
||||
const u = Vec2d.FromAngle(aCB + dAB * n)
|
||||
const u = Vec.FromAngle(aCB + dAB * n)
|
||||
tempB.setTo(handleArc.center).add(u.mul(handleArc.radius))
|
||||
}
|
||||
|
||||
// Did we miss intersections? This happens when we have overlapping shapes.
|
||||
if (startShapeInfo && endShapeInfo && !startShapeInfo.isExact && !endShapeInfo.isExact) {
|
||||
aCA = Vec2d.Angle(handleArc.center, tempA) // angle center -> a
|
||||
aCB = Vec2d.Angle(handleArc.center, tempB) // angle center -> b
|
||||
aCA = Vec.Angle(handleArc.center, tempA) // angle center -> a
|
||||
aCB = Vec.Angle(handleArc.center, tempB) // angle center -> b
|
||||
dAB = distFn(aCA, aCB) // angle distance between a and b
|
||||
lAB = dAB * handleArc.radius // length of arc between a and b
|
||||
const relationship = getBoundShapeRelationships(
|
||||
|
@ -314,7 +313,7 @@ export function getCurvedArrowInfo(
|
|||
distFn(handle_aCA, aCA) > distFn(handle_aCA, aCB)
|
||||
) {
|
||||
const n = Math.min(0.9, MIN_ARROW_LENGTH / lAB) * (isClockwise ? 1 : -1)
|
||||
const u = Vec2d.FromAngle(aCA + dAB * n)
|
||||
const u = Vec.FromAngle(aCA + dAB * n)
|
||||
tempB.setTo(handleArc.center).add(u.mul(handleArc.radius))
|
||||
}
|
||||
}
|
||||
|
@ -359,87 +358,6 @@ export function getCurvedArrowInfo(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a solid path for a curved arrow's handles.
|
||||
*
|
||||
* @param info - The arrow info.
|
||||
* @public
|
||||
*/
|
||||
export function getCurvedArrowHandlePath(info: TLArrowInfo & { isStraight: false }) {
|
||||
const {
|
||||
start,
|
||||
end,
|
||||
handleArc: { radius, largeArcFlag, sweepFlag },
|
||||
} = info
|
||||
return `M${start.handle.x},${start.handle.y} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.handle.x},${end.handle.y}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a solid path for a curved arrow's body.
|
||||
*
|
||||
* @param info - The arrow info.
|
||||
* @public
|
||||
*/
|
||||
export function getSolidCurvedArrowPath(info: TLArrowInfo & { isStraight: false }) {
|
||||
const {
|
||||
start,
|
||||
end,
|
||||
bodyArc: { radius, largeArcFlag, sweepFlag },
|
||||
} = info
|
||||
return `M${start.point.x},${start.point.y} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.point.x},${end.point.y}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a point along an arc.
|
||||
*
|
||||
* @param center - The arc's center.
|
||||
* @param radius - The arc's radius.
|
||||
* @param startAngle - The start point of the arc.
|
||||
* @param size - The size of the arc.
|
||||
* @param t - The point along the arc to get.
|
||||
*/
|
||||
export function getPointOnArc(
|
||||
center: VecLike,
|
||||
radius: number,
|
||||
startAngle: number,
|
||||
size: number,
|
||||
t: number
|
||||
) {
|
||||
const angle = startAngle + size * t
|
||||
return new Vec2d(center.x + radius * Math.cos(angle), center.y + radius * Math.sin(angle))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bounding box for an arc.
|
||||
*
|
||||
* @param center - The arc's center.
|
||||
* @param radius - The arc's radius.
|
||||
* @param start - The start point of the arc.
|
||||
* @param size - The size of the arc.
|
||||
*/
|
||||
export function getArcBoundingBox(center: VecLike, radius: number, start: VecLike, size: number) {
|
||||
let minX = Infinity
|
||||
let minY = Infinity
|
||||
let maxX = -Infinity
|
||||
let maxY = -Infinity
|
||||
|
||||
const startAngle = Vec2d.Angle(center, start)
|
||||
|
||||
// Test 20 points along the arc
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const angle = startAngle + size * (i / 19)
|
||||
const x = center.x + radius * Math.cos(angle)
|
||||
const y = center.y + radius * Math.sin(angle)
|
||||
|
||||
minX = Math.min(x, minX)
|
||||
minY = Math.min(y, minY)
|
||||
maxX = Math.max(x, maxX)
|
||||
maxY = Math.max(y, maxY)
|
||||
}
|
||||
|
||||
return new Box2d(minX, minY, maxX - minX, maxY - minY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info about an arc formed by three points.
|
||||
*
|
||||
|
@ -447,7 +365,7 @@ export function getArcBoundingBox(center: VecLike, radius: number, start: VecLik
|
|||
* @param b - The end of the arc
|
||||
* @param c - A point on the arc
|
||||
*/
|
||||
export function getArcInfo(a: VecLike, b: VecLike, c: VecLike): TLArcInfo {
|
||||
function getArcInfo(a: VecLike, b: VecLike, c: VecLike): TLArcInfo {
|
||||
// find a circle from the three points
|
||||
const u = -2 * (a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y)
|
||||
|
||||
|
@ -464,10 +382,10 @@ export function getArcInfo(a: VecLike, b: VecLike, c: VecLike): TLArcInfo {
|
|||
u,
|
||||
}
|
||||
|
||||
const radius = Vec2d.Dist(center, a)
|
||||
const radius = Vec.Dist(center, a)
|
||||
|
||||
// Whether to draw the arc clockwise or counter-clockwise (are the points clockwise?)
|
||||
const sweepFlag = +Vec2d.Clockwise(a, c, b)
|
||||
const sweepFlag = +Vec.Clockwise(a, c, b)
|
||||
|
||||
// The base angle of the arc in radians
|
||||
const ab = Math.hypot(a.y - b.y, a.x - b.x)
|
||||
|
@ -498,19 +416,19 @@ export function getArcInfo(a: VecLike, b: VecLike, c: VecLike): TLArcInfo {
|
|||
function placeCenterHandle(
|
||||
center: VecLike,
|
||||
radius: number,
|
||||
tempA: Vec2d,
|
||||
tempB: Vec2d,
|
||||
tempC: Vec2d,
|
||||
tempA: Vec,
|
||||
tempB: Vec,
|
||||
tempC: Vec,
|
||||
originalArcLength: number,
|
||||
isClockwise: boolean
|
||||
) {
|
||||
const aCA = Vec2d.Angle(center, tempA) // angle center -> a
|
||||
const aCB = Vec2d.Angle(center, tempB) // angle center -> b
|
||||
const aCA = Vec.Angle(center, tempA) // angle center -> a
|
||||
const aCB = Vec.Angle(center, tempB) // angle center -> b
|
||||
let dAB = clockwiseAngleDist(aCA, aCB) // angle distance between a and b
|
||||
if (!isClockwise) dAB = PI2 - dAB
|
||||
|
||||
const n = 0.5 * (isClockwise ? 1 : -1)
|
||||
const u = Vec2d.FromAngle(aCA + dAB * n)
|
||||
const u = Vec.FromAngle(aCA + dAB * n)
|
||||
tempC.setTo(center).add(u.mul(radius))
|
||||
|
||||
if (dAB > originalArcLength) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TLArrowShape, TLArrowShapeTerminal, TLShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import { Matrix2d } from '../../../../primitives/Matrix2d'
|
||||
import { Vec2d } from '../../../../primitives/Vec2d'
|
||||
import { Mat } from '../../../../primitives/Mat'
|
||||
import { Vec } from '../../../../primitives/Vec'
|
||||
import { Group2d } from '../../../../primitives/geometry/Group2d'
|
||||
import { Editor } from '../../../Editor'
|
||||
|
||||
|
@ -13,8 +13,8 @@ export type BoundShapeInfo<T extends TLShape = TLShape> = {
|
|||
didIntersect: boolean
|
||||
isExact: boolean
|
||||
isClosed: boolean
|
||||
transform: Matrix2d
|
||||
outline: Vec2d[]
|
||||
transform: Mat
|
||||
outline: Vec[]
|
||||
}
|
||||
|
||||
export function getBoundShapeInfoForTerminal(
|
||||
|
@ -45,36 +45,36 @@ export function getBoundShapeInfoForTerminal(
|
|||
}
|
||||
}
|
||||
|
||||
export function getArrowTerminalInArrowSpace(
|
||||
function getArrowTerminalInArrowSpace(
|
||||
editor: Editor,
|
||||
arrowPageTransform: Matrix2d,
|
||||
arrowPageTransform: Mat,
|
||||
terminal: TLArrowShapeTerminal,
|
||||
forceImprecise: boolean
|
||||
) {
|
||||
if (terminal.type === 'point') {
|
||||
return Vec2d.From(terminal)
|
||||
return Vec.From(terminal)
|
||||
}
|
||||
|
||||
const boundShape = editor.getShape(terminal.boundShapeId)
|
||||
|
||||
if (!boundShape) {
|
||||
// this can happen in multiplayer contexts where the shape is being deleted
|
||||
return new Vec2d(0, 0)
|
||||
return new Vec(0, 0)
|
||||
} else {
|
||||
// Find the actual local point of the normalized terminal on
|
||||
// the bound shape and transform it to page space, then transform
|
||||
// it to arrow space
|
||||
const { point, size } = editor.getShapeGeometry(boundShape).bounds
|
||||
const shapePoint = Vec2d.Add(
|
||||
const shapePoint = Vec.Add(
|
||||
point,
|
||||
Vec2d.MulV(
|
||||
Vec.MulV(
|
||||
// if the parent is the bound shape, then it's ALWAYS precise
|
||||
terminal.isPrecise || forceImprecise ? terminal.normalizedAnchor : { x: 0.5, y: 0.5 },
|
||||
size
|
||||
)
|
||||
)
|
||||
const pagePoint = Matrix2d.applyToPoint(editor.getShapePageTransform(boundShape)!, shapePoint)
|
||||
const arrowPoint = Matrix2d.applyToPoint(Matrix2d.Inverse(arrowPageTransform), pagePoint)
|
||||
const pagePoint = Mat.applyToPoint(editor.getShapePageTransform(boundShape)!, shapePoint)
|
||||
const arrowPoint = Mat.applyToPoint(Mat.Inverse(arrowPageTransform), pagePoint)
|
||||
return arrowPoint
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { TLArrowShape } from '@tldraw/tlschema'
|
||||
import { Box2d } from '../../../../primitives/Box2d'
|
||||
import { Matrix2d, Matrix2dModel } from '../../../../primitives/Matrix2d'
|
||||
import { Vec2d, VecLike } from '../../../../primitives/Vec2d'
|
||||
import { Mat, MatModel } from '../../../../primitives/Mat'
|
||||
import { Vec, VecLike } from '../../../../primitives/Vec'
|
||||
import {
|
||||
intersectLineSegmentPolygon,
|
||||
intersectLineSegmentPolyline,
|
||||
|
@ -25,9 +24,9 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArr
|
|||
|
||||
const a = terminalsInArrowSpace.start.clone()
|
||||
const b = terminalsInArrowSpace.end.clone()
|
||||
const c = Vec2d.Med(a, b)
|
||||
const c = Vec.Med(a, b)
|
||||
|
||||
if (Vec2d.Equals(a, b)) {
|
||||
if (Vec.Equals(a, b)) {
|
||||
return {
|
||||
isStraight: true,
|
||||
start: {
|
||||
|
@ -46,7 +45,7 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArr
|
|||
}
|
||||
}
|
||||
|
||||
const uAB = Vec2d.Sub(b, a).uni()
|
||||
const uAB = Vec.Sub(b, a).uni()
|
||||
|
||||
// Update the arrowhead points using intersections with the bound shapes, if any.
|
||||
|
||||
|
@ -110,8 +109,8 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArr
|
|||
}
|
||||
}
|
||||
|
||||
const u = Vec2d.Sub(b, a).uni()
|
||||
const didFlip = !Vec2d.Equals(u, uAB)
|
||||
const u = Vec.Sub(b, a).uni()
|
||||
const didFlip = !Vec.Equals(u, uAB)
|
||||
|
||||
// If the arrow is bound non-exact to a start shape and the
|
||||
// start point has an arrowhead, then offset the start point
|
||||
|
@ -151,7 +150,7 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArr
|
|||
|
||||
const tA = a.clone().add(u.clone().mul(offsetA * (didFlip ? -1 : 1)))
|
||||
const tB = b.clone().sub(u.clone().mul(offsetB * (didFlip ? -1 : 1)))
|
||||
const distAB = Vec2d.Dist(tA, tB)
|
||||
const distAB = Vec.Dist(tA, tB)
|
||||
|
||||
if (distAB < minLength) {
|
||||
if (offsetA !== 0 && offsetB !== 0) {
|
||||
|
@ -179,14 +178,14 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArr
|
|||
if (startShapeInfo && endShapeInfo) {
|
||||
// If we have two bound shapes...then make the arrow a short arrow from
|
||||
// the start point towards where the end point should be.
|
||||
b.setTo(Vec2d.Add(a, u.clone().mul(-MIN_ARROW_LENGTH)))
|
||||
b.setTo(Vec.Add(a, u.clone().mul(-MIN_ARROW_LENGTH)))
|
||||
}
|
||||
c.setTo(Vec2d.Med(terminalsInArrowSpace.start, terminalsInArrowSpace.end))
|
||||
c.setTo(Vec.Med(terminalsInArrowSpace.start, terminalsInArrowSpace.end))
|
||||
} else {
|
||||
c.setTo(Vec2d.Med(a, b))
|
||||
c.setTo(Vec.Med(a, b))
|
||||
}
|
||||
|
||||
const length = Vec2d.Dist(a, b)
|
||||
const length = Vec.Dist(a, b)
|
||||
|
||||
return {
|
||||
isStraight: true,
|
||||
|
@ -208,9 +207,9 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): TLArr
|
|||
|
||||
/** Get an intersection point from A -> B with bound shape (target) from shape (arrow). */
|
||||
function updateArrowheadPointWithBoundShape(
|
||||
point: Vec2d,
|
||||
opposite: Vec2d,
|
||||
arrowPageTransform: Matrix2dModel,
|
||||
point: Vec,
|
||||
opposite: Vec,
|
||||
arrowPageTransform: MatModel,
|
||||
targetShapeInfo?: BoundShapeInfo
|
||||
) {
|
||||
if (targetShapeInfo === undefined) {
|
||||
|
@ -224,12 +223,12 @@ function updateArrowheadPointWithBoundShape(
|
|||
}
|
||||
|
||||
// From and To in page space
|
||||
const pageFrom = Matrix2d.applyToPoint(arrowPageTransform, opposite)
|
||||
const pageTo = Matrix2d.applyToPoint(arrowPageTransform, point)
|
||||
const pageFrom = Mat.applyToPoint(arrowPageTransform, opposite)
|
||||
const pageTo = Mat.applyToPoint(arrowPageTransform, point)
|
||||
|
||||
// From and To in local space of the target shape
|
||||
const targetFrom = Matrix2d.applyToPoint(Matrix2d.Inverse(targetShapeInfo.transform), pageFrom)
|
||||
const targetTo = Matrix2d.applyToPoint(Matrix2d.Inverse(targetShapeInfo.transform), pageTo)
|
||||
const targetFrom = Mat.applyToPoint(Mat.Inverse(targetShapeInfo.transform), pageFrom)
|
||||
const targetTo = Mat.applyToPoint(Mat.Inverse(targetShapeInfo.transform), pageTo)
|
||||
|
||||
const isClosed = targetShapeInfo.isClosed
|
||||
const fn = isClosed ? intersectLineSegmentPolygon : intersectLineSegmentPolyline
|
||||
|
@ -240,7 +239,7 @@ function updateArrowheadPointWithBoundShape(
|
|||
|
||||
if (intersection !== null) {
|
||||
targetInt =
|
||||
intersection.sort((p1, p2) => Vec2d.Dist(p1, targetFrom) - Vec2d.Dist(p2, targetFrom))[0] ??
|
||||
intersection.sort((p1, p2) => Vec.Dist(p1, targetFrom) - Vec.Dist(p2, targetFrom))[0] ??
|
||||
(isClosed ? undefined : targetTo)
|
||||
}
|
||||
|
||||
|
@ -249,34 +248,10 @@ function updateArrowheadPointWithBoundShape(
|
|||
return
|
||||
}
|
||||
|
||||
const pageInt = Matrix2d.applyToPoint(targetShapeInfo.transform, targetInt)
|
||||
const arrowInt = Matrix2d.applyToPoint(Matrix2d.Inverse(arrowPageTransform), pageInt)
|
||||
const pageInt = Mat.applyToPoint(targetShapeInfo.transform, targetInt)
|
||||
const arrowInt = Mat.applyToPoint(Mat.Inverse(arrowPageTransform), pageInt)
|
||||
|
||||
point.setTo(arrowInt)
|
||||
|
||||
targetShapeInfo.didIntersect = true
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getStraightArrowHandlePath(info: TLArrowInfo & { isStraight: true }) {
|
||||
return getArrowPath(info.start.handle, info.end.handle)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getSolidStraightArrowPath(info: TLArrowInfo & { isStraight: true }) {
|
||||
return getArrowPath(info.start.point, info.end.point)
|
||||
}
|
||||
|
||||
function getArrowPath(start: VecLike, end: VecLike) {
|
||||
return `M${start.x},${start.y}L${end.x},${end.y}`
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getStraightArrowBoundingBox(start: VecLike, end: VecLike) {
|
||||
return new Box2d(
|
||||
Math.min(start.x, end.x),
|
||||
Math.min(start.y, end.y),
|
||||
Math.abs(start.x - end.x),
|
||||
Math.abs(start.y - end.y)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Vec2dModel } from '@tldraw/tlschema'
|
||||
import { Box2d } from '../../../primitives/Box2d'
|
||||
import { Vec2d } from '../../../primitives/Vec2d'
|
||||
import { VecModel } from '@tldraw/tlschema'
|
||||
import { Box } from '../../../primitives/Box'
|
||||
import { Vec } from '../../../primitives/Vec'
|
||||
import { TLResizeHandle } from '../../types/selection-types'
|
||||
import { TLBaseBoxShape } from '../BaseBoxShapeUtil'
|
||||
import { TLResizeMode } from '../ShapeUtil'
|
||||
|
@ -17,12 +17,12 @@ export type ResizeBoxOptions = Partial<{
|
|||
export function resizeBox(
|
||||
shape: TLBaseBoxShape,
|
||||
info: {
|
||||
newPoint: Vec2dModel
|
||||
newPoint: VecModel
|
||||
handle: TLResizeHandle
|
||||
mode: TLResizeMode
|
||||
scaleX: number
|
||||
scaleY: number
|
||||
initialBounds: Box2d
|
||||
initialBounds: Box
|
||||
initialShape: TLBaseBoxShape
|
||||
},
|
||||
opts = {} as ResizeBoxOptions
|
||||
|
@ -33,7 +33,7 @@ export function resizeBox(
|
|||
let w = shape.props.w * scaleX
|
||||
let h = shape.props.h * scaleY
|
||||
|
||||
const offset = new Vec2d(0, 0)
|
||||
const offset = new Vec(0, 0)
|
||||
|
||||
if (w > 0) {
|
||||
if (w < minWidth) {
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { TLShape, Vec2dModel } from '@tldraw/tlschema'
|
||||
import { Box2d } from '../../../primitives/Box2d'
|
||||
import { Vec2d } from '../../../primitives/Vec2d'
|
||||
|
||||
export function resizeScaled(
|
||||
shape: Extract<TLShape, { props: { scale: number } }>,
|
||||
{
|
||||
initialBounds,
|
||||
scaleX,
|
||||
scaleY,
|
||||
newPoint,
|
||||
}: {
|
||||
newPoint: Vec2dModel
|
||||
initialBounds: Box2d
|
||||
scaleX: number
|
||||
scaleY: number
|
||||
}
|
||||
) {
|
||||
// Compute the new scale (to apply to the scale prop)
|
||||
const scaleDelta = Math.max(0.01, Math.min(Math.abs(scaleX), Math.abs(scaleY)))
|
||||
|
||||
// Compute the offset (if flipped X or flipped Y)
|
||||
const offset = new Vec2d(0, 0)
|
||||
|
||||
if (scaleX < 0) {
|
||||
offset.x = -(initialBounds.width * scaleDelta)
|
||||
}
|
||||
if (scaleY < 0) {
|
||||
offset.y = -(initialBounds.height * scaleDelta)
|
||||
}
|
||||
|
||||
// Apply the offset to the new point
|
||||
const { x, y } = Vec2d.Add(newPoint, offset.rot(shape.rotation))
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
props: {
|
||||
scale: scaleDelta * shape.props.scale,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { createShapeId } from '@tldraw/tlschema'
|
||||
import { Vec2d } from '../../../../primitives/Vec2d'
|
||||
import { Vec } from '../../../../primitives/Vec'
|
||||
import { TLBaseBoxShape } from '../../../shapes/BaseBoxShapeUtil'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
@ -96,7 +96,7 @@ export class Pointing extends StateNode {
|
|||
|
||||
const shape = this.editor.getShape<TLBaseBoxShape>(id)!
|
||||
const { w, h } = this.editor.getShapeUtil(shape).getDefaultProps() as TLBaseBoxShape['props']
|
||||
const delta = new Vec2d(w / 2, h / 2)
|
||||
const delta = new Vec(w / 2, h / 2)
|
||||
|
||||
const parentTransform = this.editor.getShapeParentTransform(shape)
|
||||
if (parentTransform) delta.rot(-parentTransform.rotation())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TLHandle, TLShape, Vec2dModel } from '@tldraw/tlschema'
|
||||
import { VecLike } from '../../primitives/Vec2d'
|
||||
import { TLHandle, TLShape, VecModel } from '@tldraw/tlschema'
|
||||
import { VecLike } from '../../primitives/Vec'
|
||||
import { TLSelectionHandle } from './selection-types'
|
||||
|
||||
/** @public */
|
||||
|
@ -80,16 +80,16 @@ export type TLKeyboardEventInfo = TLBaseEventInfo & {
|
|||
export type TLPinchEventInfo = TLBaseEventInfo & {
|
||||
type: 'pinch'
|
||||
name: TLPinchEventName
|
||||
point: Vec2dModel
|
||||
delta: Vec2dModel
|
||||
point: VecModel
|
||||
delta: VecModel
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type TLWheelEventInfo = TLBaseEventInfo & {
|
||||
type: 'wheel'
|
||||
name: 'wheel'
|
||||
delta: Vec2dModel
|
||||
point: Vec2dModel
|
||||
delta: VecModel
|
||||
point: VecModel
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { EmbedDefinition } from '@tldraw/tlschema'
|
||||
import { VecLike } from '../../primitives/Vec2d'
|
||||
import { VecLike } from '../../primitives/Vec'
|
||||
import { TLContent } from './clipboard-types'
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Box2d } from '../../primitives/Box2d'
|
||||
import { Box } from '../../primitives/Box'
|
||||
|
||||
/** @public */
|
||||
export type RequiredKeys<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>
|
||||
|
@ -7,7 +7,7 @@ export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>
|
|||
|
||||
/** @public */
|
||||
export type TLSvgOptions = {
|
||||
bounds: Box2d
|
||||
bounds: Box
|
||||
scale: number
|
||||
background: boolean
|
||||
padding: number
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { RotateCorner, SelectionCorner, SelectionEdge } from '../../primitives/Box2d'
|
||||
import { RotateCorner, SelectionCorner, SelectionEdge } from '../../primitives/Box'
|
||||
|
||||
/** @public */
|
||||
export type TLSelectionHandle = SelectionCorner | SelectionEdge | RotateCorner
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { AnyHandlerEventTypes, EventTypes, GestureKey, Handler } from '@use
|
|||
import { createUseGesture, pinchAction, wheelAction } from '@use-gesture/react'
|
||||
import * as React from 'react'
|
||||
import { TLWheelEventInfo } from '../editor/types/event-types'
|
||||
import { Vec2d } from '../primitives/Vec2d'
|
||||
import { Vec } from '../primitives/Vec'
|
||||
import { preventDefault } from '../utils/dom'
|
||||
import { normalizeWheel } from '../utils/normalizeWheel'
|
||||
import { useEditor } from './useEditor'
|
||||
|
@ -122,7 +122,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
|
|||
type: 'wheel',
|
||||
name: 'wheel',
|
||||
delta,
|
||||
point: new Vec2d(event.clientX, event.clientY).sub({
|
||||
point: new Vec(event.clientX, event.clientY).sub({
|
||||
x: container.left,
|
||||
y: container.top,
|
||||
}),
|
||||
|
@ -138,8 +138,8 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
|
|||
let initZoom = 1 // the browser's zoom level when the pinch starts
|
||||
let currZoom = 1 // the current zoom level according to the pinch gesture recognizer
|
||||
let currDistanceBetweenFingers = 0
|
||||
const initPointBetweenFingers = new Vec2d()
|
||||
const prevPointBetweenFingers = new Vec2d()
|
||||
const initPointBetweenFingers = new Vec()
|
||||
const prevPointBetweenFingers = new Vec()
|
||||
|
||||
const onPinchStart: PinchHandler = (gesture) => {
|
||||
const elm = ref.current
|
||||
|
@ -186,7 +186,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
|
|||
// How far have the two touch points moved towards or away from eachother?
|
||||
const touchDistance = Math.abs(currDistanceBetweenFingers - initDistanceBetweenFingers)
|
||||
// How far has the point between the touches moved?
|
||||
const originDistance = Vec2d.Dist(initPointBetweenFingers, prevPointBetweenFingers)
|
||||
const originDistance = Vec.Dist(initPointBetweenFingers, prevPointBetweenFingers)
|
||||
|
||||
switch (pinchState) {
|
||||
case 'not sure': {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useLayoutEffect } from 'react'
|
||||
import { VecLike } from '../primitives/Vec2d'
|
||||
import { VecLike } from '../primitives/Vec'
|
||||
|
||||
/** @public */
|
||||
export function useTransform(
|
||||
|
|
24
packages/editor/src/lib/primitives/Box.test.ts
Normal file
24
packages/editor/src/lib/primitives/Box.test.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { Box } from './Box'
|
||||
import { Vec } from './Vec'
|
||||
|
||||
describe('Box', () => {
|
||||
it('Creates a box', () => {
|
||||
const mat3 = new Box(0, 0, 100, 100)
|
||||
expect(mat3).toMatchObject({
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
})
|
||||
})
|
||||
|
||||
it('can have the point set with a Vec', () => {
|
||||
const box = new Box(0, 0, 100, 100)
|
||||
|
||||
expect(box).toMatchObject({ x: 0, y: 0 })
|
||||
|
||||
box.point = new Vec(19, 23)
|
||||
|
||||
expect(box).toMatchObject({ x: 19, y: 23 })
|
||||
})
|
||||
})
|
|
@ -1,9 +1,9 @@
|
|||
import { Box2dModel } from '@tldraw/tlschema'
|
||||
import { Vec2d, VecLike } from './Vec2d'
|
||||
import { BoxModel } from '@tldraw/tlschema'
|
||||
import { Vec, VecLike } from './Vec'
|
||||
import { PI, PI2, toPrecision } from './utils'
|
||||
|
||||
/** @public */
|
||||
export type BoxLike = Box2dModel | Box2d
|
||||
export type BoxLike = BoxModel | Box
|
||||
|
||||
/** @public */
|
||||
export type SelectionEdge = 'top' | 'right' | 'bottom' | 'left'
|
||||
|
@ -23,7 +23,7 @@ export type RotateCorner =
|
|||
| 'mobile_rotate'
|
||||
|
||||
/** @public */
|
||||
export class Box2d {
|
||||
export class Box {
|
||||
constructor(x = 0, y = 0, w = 0, h = 0) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
|
@ -38,11 +38,11 @@ export class Box2d {
|
|||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get point() {
|
||||
return new Vec2d(this.x, this.y)
|
||||
return new Vec(this.x, this.y)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
set point(val: Vec2d) {
|
||||
set point(val: Vec) {
|
||||
this.x = val.x
|
||||
this.y = val.y
|
||||
}
|
||||
|
@ -114,11 +114,11 @@ export class Box2d {
|
|||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get center() {
|
||||
return new Vec2d(this.midX, this.midY)
|
||||
return new Vec(this.midX, this.midY)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
set center(v: Vec2d) {
|
||||
set center(v: Vec) {
|
||||
this.minX = v.x - this.width / 2
|
||||
this.minY = v.y - this.height / 2
|
||||
}
|
||||
|
@ -126,26 +126,26 @@ export class Box2d {
|
|||
// eslint-disable-next-line no-restricted-syntax
|
||||
get corners() {
|
||||
return [
|
||||
new Vec2d(this.minX, this.minY),
|
||||
new Vec2d(this.maxX, this.minY),
|
||||
new Vec2d(this.maxX, this.maxY),
|
||||
new Vec2d(this.minX, this.maxY),
|
||||
new Vec(this.minX, this.minY),
|
||||
new Vec(this.maxX, this.minY),
|
||||
new Vec(this.maxX, this.maxY),
|
||||
new Vec(this.minX, this.maxY),
|
||||
]
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get snapPoints() {
|
||||
return [
|
||||
new Vec2d(this.minX, this.minY),
|
||||
new Vec2d(this.maxX, this.minY),
|
||||
new Vec2d(this.maxX, this.maxY),
|
||||
new Vec2d(this.minX, this.maxY),
|
||||
new Vec(this.minX, this.minY),
|
||||
new Vec(this.maxX, this.minY),
|
||||
new Vec(this.maxX, this.maxY),
|
||||
new Vec(this.minX, this.maxY),
|
||||
this.center,
|
||||
]
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get sides(): Array<[Vec2d, Vec2d]> {
|
||||
get sides(): Array<[Vec, Vec]> {
|
||||
const { corners } = this
|
||||
return [
|
||||
[corners[0], corners[1]],
|
||||
|
@ -156,8 +156,8 @@ export class Box2d {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get size(): Vec2d {
|
||||
return new Vec2d(this.w, this.h)
|
||||
get size(): Vec {
|
||||
return new Vec(this.w, this.h)
|
||||
}
|
||||
|
||||
toFixed() {
|
||||
|
@ -168,7 +168,7 @@ export class Box2d {
|
|||
return this
|
||||
}
|
||||
|
||||
setTo(B: Box2d) {
|
||||
setTo(B: Box) {
|
||||
this.x = B.x
|
||||
this.y = B.y
|
||||
this.w = B.w
|
||||
|
@ -184,7 +184,7 @@ export class Box2d {
|
|||
return this
|
||||
}
|
||||
|
||||
expand(A: Box2d) {
|
||||
expand(A: Box) {
|
||||
const minX = Math.min(this.minX, A.minX)
|
||||
const minY = Math.min(this.minY, A.minY)
|
||||
const maxX = Math.max(this.maxX, A.maxX)
|
||||
|
@ -215,7 +215,7 @@ export class Box2d {
|
|||
|
||||
clone() {
|
||||
const { x, y, w, h } = this
|
||||
return new Box2d(x, y, w, h)
|
||||
return new Box(x, y, w, h)
|
||||
}
|
||||
|
||||
translate(delta: VecLike) {
|
||||
|
@ -235,44 +235,44 @@ export class Box2d {
|
|||
this.height = Math.max(1, maxY - minY)
|
||||
}
|
||||
|
||||
collides(B: Box2d) {
|
||||
return Box2d.Collides(this, B)
|
||||
collides(B: Box) {
|
||||
return Box.Collides(this, B)
|
||||
}
|
||||
|
||||
contains(B: Box2d) {
|
||||
return Box2d.Contains(this, B)
|
||||
contains(B: Box) {
|
||||
return Box.Contains(this, B)
|
||||
}
|
||||
|
||||
includes(B: Box2d) {
|
||||
return Box2d.Includes(this, B)
|
||||
includes(B: Box) {
|
||||
return Box.Includes(this, B)
|
||||
}
|
||||
|
||||
containsPoint(V: VecLike, margin = 0) {
|
||||
return Box2d.ContainsPoint(this, V, margin)
|
||||
return Box.ContainsPoint(this, V, margin)
|
||||
}
|
||||
|
||||
getHandlePoint(handle: SelectionCorner | SelectionEdge) {
|
||||
switch (handle) {
|
||||
case 'top_left':
|
||||
return new Vec2d(this.minX, this.minY)
|
||||
return new Vec(this.minX, this.minY)
|
||||
case 'top_right':
|
||||
return new Vec2d(this.maxX, this.minY)
|
||||
return new Vec(this.maxX, this.minY)
|
||||
case 'bottom_left':
|
||||
return new Vec2d(this.minX, this.maxY)
|
||||
return new Vec(this.minX, this.maxY)
|
||||
case 'bottom_right':
|
||||
return new Vec2d(this.maxX, this.maxY)
|
||||
return new Vec(this.maxX, this.maxY)
|
||||
case 'top':
|
||||
return new Vec2d(this.midX, this.minY)
|
||||
return new Vec(this.midX, this.minY)
|
||||
case 'right':
|
||||
return new Vec2d(this.maxX, this.midY)
|
||||
return new Vec(this.maxX, this.midY)
|
||||
case 'bottom':
|
||||
return new Vec2d(this.midX, this.maxY)
|
||||
return new Vec(this.midX, this.maxY)
|
||||
case 'left':
|
||||
return new Vec2d(this.minX, this.midY)
|
||||
return new Vec(this.minX, this.midY)
|
||||
}
|
||||
}
|
||||
|
||||
toJson(): Box2dModel {
|
||||
toJson(): BoxModel {
|
||||
return { x: this.minX, y: this.minY, w: this.w, h: this.h }
|
||||
}
|
||||
|
||||
|
@ -336,7 +336,7 @@ export class Box2d {
|
|||
this.height = Math.abs(b1y - b0y)
|
||||
}
|
||||
|
||||
union(box: Box2dModel) {
|
||||
union(box: BoxModel) {
|
||||
const minX = Math.min(this.minX, box.x)
|
||||
const minY = Math.min(this.minY, box.y)
|
||||
const maxX = Math.max(this.maxX, box.w + box.x)
|
||||
|
@ -350,12 +350,12 @@ export class Box2d {
|
|||
return this
|
||||
}
|
||||
|
||||
static From(box: Box2dModel) {
|
||||
return new Box2d(box.x, box.y, box.w, box.h)
|
||||
static From(box: BoxModel) {
|
||||
return new Box(box.x, box.y, box.w, box.h)
|
||||
}
|
||||
|
||||
static FromPoints(points: VecLike[]) {
|
||||
if (points.length === 0) return new Box2d()
|
||||
if (points.length === 0) return new Box()
|
||||
let minX = Infinity
|
||||
let minY = Infinity
|
||||
let maxX = -Infinity
|
||||
|
@ -369,35 +369,35 @@ export class Box2d {
|
|||
maxY = Math.max(point.y, maxY)
|
||||
}
|
||||
|
||||
return new Box2d(minX, minY, maxX - minX, maxY - minY)
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY)
|
||||
}
|
||||
|
||||
static Expand(A: Box2d, B: Box2d) {
|
||||
static Expand(A: Box, B: Box) {
|
||||
const minX = Math.min(B.minX, A.minX)
|
||||
const minY = Math.min(B.minY, A.minY)
|
||||
const maxX = Math.max(B.maxX, A.maxX)
|
||||
const maxY = Math.max(B.maxY, A.maxY)
|
||||
|
||||
return new Box2d(minX, minY, maxX - minX, maxY - minY)
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY)
|
||||
}
|
||||
|
||||
static ExpandBy(A: Box2d, n: number) {
|
||||
return new Box2d(A.minX - n, A.minY - n, A.width + n * 2, A.height + n * 2)
|
||||
static ExpandBy(A: Box, n: number) {
|
||||
return new Box(A.minX - n, A.minY - n, A.width + n * 2, A.height + n * 2)
|
||||
}
|
||||
|
||||
static Collides = (A: Box2d, B: Box2d) => {
|
||||
static Collides = (A: Box, B: Box) => {
|
||||
return !(A.maxX < B.minX || A.minX > B.maxX || A.maxY < B.minY || A.minY > B.maxY)
|
||||
}
|
||||
|
||||
static Contains = (A: Box2d, B: Box2d) => {
|
||||
static Contains = (A: Box, B: Box) => {
|
||||
return A.minX < B.minX && A.minY < B.minY && A.maxY > B.maxY && A.maxX > B.maxX
|
||||
}
|
||||
|
||||
static Includes = (A: Box2d, B: Box2d) => {
|
||||
return Box2d.Collides(A, B) || Box2d.Contains(A, B)
|
||||
static Includes = (A: Box, B: Box) => {
|
||||
return Box.Collides(A, B) || Box.Contains(A, B)
|
||||
}
|
||||
|
||||
static ContainsPoint = (A: Box2d, B: VecLike, margin = 0) => {
|
||||
static ContainsPoint = (A: Box, B: VecLike, margin = 0) => {
|
||||
return !(
|
||||
B.x < A.minX - margin ||
|
||||
B.y < A.minY - margin ||
|
||||
|
@ -406,7 +406,7 @@ export class Box2d {
|
|||
)
|
||||
}
|
||||
|
||||
static Common = (boxes: Box2d[]): Box2d => {
|
||||
static Common = (boxes: Box[]): Box => {
|
||||
let minX = Infinity
|
||||
let minY = Infinity
|
||||
let maxX = -Infinity
|
||||
|
@ -420,10 +420,10 @@ export class Box2d {
|
|||
maxY = Math.max(maxY, B.maxY)
|
||||
}
|
||||
|
||||
return new Box2d(minX, minY, maxX - minX, maxY - minY)
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY)
|
||||
}
|
||||
|
||||
static Sides = (A: Box2d, inset = 0) => {
|
||||
static Sides = (A: Box, inset = 0) => {
|
||||
const { corners } = A
|
||||
if (inset) {
|
||||
// TODO: Inset the corners by the inset amount.
|
||||
|
@ -438,7 +438,7 @@ export class Box2d {
|
|||
}
|
||||
|
||||
static Resize(
|
||||
box: Box2d,
|
||||
box: Box,
|
||||
handle: SelectionCorner | SelectionEdge | string,
|
||||
dx: number,
|
||||
dy: number,
|
||||
|
@ -550,7 +550,7 @@ export class Box2d {
|
|||
b0y = t
|
||||
}
|
||||
|
||||
const final = new Box2d(b0x, b0y, Math.abs(b1x - b0x), Math.abs(b1y - b0y))
|
||||
const final = new Box(b0x, b0y, Math.abs(b1x - b0x), Math.abs(b1y - b0y))
|
||||
|
||||
return {
|
||||
box: final,
|
||||
|
@ -559,11 +559,11 @@ export class Box2d {
|
|||
}
|
||||
}
|
||||
|
||||
equals(other: Box2d | Box2dModel) {
|
||||
return Box2d.Equals(this, other)
|
||||
equals(other: Box | BoxModel) {
|
||||
return Box.Equals(this, other)
|
||||
}
|
||||
|
||||
static Equals(a: Box2d | Box2dModel, b: Box2d | Box2dModel) {
|
||||
static Equals(a: Box | BoxModel, b: Box | BoxModel) {
|
||||
return b.x === a.x && b.y === a.y && b.w === a.w && b.h === a.h
|
||||
}
|
||||
|
||||
|
@ -573,8 +573,8 @@ export class Box2d {
|
|||
return this
|
||||
}
|
||||
|
||||
static ZeroFix(other: Box2d | Box2dModel) {
|
||||
return new Box2d(other.x, other.y, Math.max(1, other.w), Math.max(1, other.h))
|
||||
static ZeroFix(other: Box | BoxModel) {
|
||||
return new Box(other.x, other.y, Math.max(1, other.w), Math.max(1, other.h))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { Box2d } from './Box2d'
|
||||
import { Vec2d } from './Vec2d'
|
||||
|
||||
describe('Box2d', () => {
|
||||
it('Creates a box', () => {
|
||||
const mat3 = new Box2d(0, 0, 100, 100)
|
||||
expect(mat3).toMatchObject({
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
})
|
||||
})
|
||||
|
||||
it('can have the point set with a vec2d', () => {
|
||||
const box = new Box2d(0, 0, 100, 100)
|
||||
|
||||
expect(box).toMatchObject({ x: 0, y: 0 })
|
||||
|
||||
box.point = new Vec2d(19, 23)
|
||||
|
||||
expect(box).toMatchObject({ x: 19, y: 23 })
|
||||
})
|
||||
})
|
46
packages/editor/src/lib/primitives/Mat.test.ts
Normal file
46
packages/editor/src/lib/primitives/Mat.test.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Mat } from './Mat'
|
||||
|
||||
describe('Mat', () => {
|
||||
it('Creates a matrix', () => {
|
||||
const mat3 = new Mat(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
expect(mat3).toMatchObject(Mat.Identity())
|
||||
})
|
||||
|
||||
it('Multiplies a matrix', () => {
|
||||
const m1 = new Mat(1, 2, 3, 4, 5, 6)
|
||||
const m2 = new Mat(1, 2, 3, 4, 5, 6)
|
||||
expect(m1.multiply(m2)).toMatchObject({
|
||||
a: 7,
|
||||
b: 10,
|
||||
c: 15,
|
||||
d: 22,
|
||||
e: 28,
|
||||
f: 40,
|
||||
})
|
||||
})
|
||||
|
||||
it('Composes matrices', () => {
|
||||
const m1 = new Mat(1, 2, 3, 4, 5, 6)
|
||||
const m2 = new Mat(1, 2, 3, 4, 5, 6)
|
||||
expect(Mat.Compose(m1, m2)).toMatchObject({
|
||||
a: 7,
|
||||
b: 10,
|
||||
c: 15,
|
||||
d: 22,
|
||||
e: 28,
|
||||
f: 40,
|
||||
})
|
||||
})
|
||||
|
||||
it('Inverts a matrix', () => {
|
||||
const m1 = new Mat(1, 2, 3, 4, 5, 6)
|
||||
expect(m1.invert()).toMatchObject({
|
||||
a: -2,
|
||||
b: 1,
|
||||
c: 1.5,
|
||||
d: -0.5,
|
||||
e: 1,
|
||||
f: -2,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,21 +1,12 @@
|
|||
import { Box2d } from './Box2d'
|
||||
import { clampRadians, TAU, toDomPrecision } from './utils'
|
||||
import { Vec2d, VecLike } from './Vec2d'
|
||||
import { Box } from './Box'
|
||||
import { clampRadians, HALF_PI, toDomPrecision } from './utils'
|
||||
import { Vec, VecLike } from './Vec'
|
||||
|
||||
/** @public */
|
||||
export type MatLike = Matrix2dModel | Matrix2d
|
||||
export type MatLike = MatModel | Mat
|
||||
|
||||
/** @public */
|
||||
export interface MatrixInfo {
|
||||
x: number
|
||||
y: number
|
||||
scaleX: number
|
||||
scaleY: number
|
||||
rotation: number
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface Matrix2dModel {
|
||||
export interface MatModel {
|
||||
a: number
|
||||
b: number
|
||||
c: number
|
||||
|
@ -25,11 +16,11 @@ export interface Matrix2dModel {
|
|||
}
|
||||
|
||||
// function getIdentity() {
|
||||
// return new Matrix2d(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
// return new Mat(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
// }
|
||||
|
||||
/** @public */
|
||||
export class Matrix2d {
|
||||
export class Mat {
|
||||
constructor(a: number, b: number, c: number, d: number, e: number, f: number) {
|
||||
this.a = a
|
||||
this.b = b
|
||||
|
@ -46,7 +37,7 @@ export class Matrix2d {
|
|||
e = 0.0
|
||||
f = 0.0
|
||||
|
||||
equals(m: Matrix2d | Matrix2dModel) {
|
||||
equals(m: Mat | MatModel) {
|
||||
return (
|
||||
this.a === m.a &&
|
||||
this.b === m.b &&
|
||||
|
@ -67,8 +58,8 @@ export class Matrix2d {
|
|||
return this
|
||||
}
|
||||
|
||||
multiply(m: Matrix2d | Matrix2dModel) {
|
||||
const m2: Matrix2dModel = m
|
||||
multiply(m: Mat | MatModel) {
|
||||
const m2: MatModel = m
|
||||
const { a, b, c, d, e, f } = this
|
||||
this.a = a * m2.a + c * m2.b
|
||||
this.c = a * m2.c + c * m2.d
|
||||
|
@ -81,16 +72,16 @@ export class Matrix2d {
|
|||
|
||||
rotate(r: number, cx?: number, cy?: number) {
|
||||
if (r === 0) return this
|
||||
if (cx === undefined) return this.multiply(Matrix2d.Rotate(r))
|
||||
return this.translate(cx, cy!).multiply(Matrix2d.Rotate(r)).translate(-cx, -cy!)
|
||||
if (cx === undefined) return this.multiply(Mat.Rotate(r))
|
||||
return this.translate(cx, cy!).multiply(Mat.Rotate(r)).translate(-cx, -cy!)
|
||||
}
|
||||
|
||||
translate(x: number, y: number): Matrix2d {
|
||||
return this.multiply(Matrix2d.Translate(x, y!))
|
||||
translate(x: number, y: number): Mat {
|
||||
return this.multiply(Mat.Translate(x, y!))
|
||||
}
|
||||
|
||||
scale(x: number, y: number) {
|
||||
return this.multiply(Matrix2d.Scale(x, y))
|
||||
return this.multiply(Mat.Scale(x, y))
|
||||
}
|
||||
|
||||
invert() {
|
||||
|
@ -106,85 +97,77 @@ export class Matrix2d {
|
|||
}
|
||||
|
||||
applyToPoint(point: VecLike) {
|
||||
return Matrix2d.applyToPoint(this, point)
|
||||
return Mat.applyToPoint(this, point)
|
||||
}
|
||||
|
||||
applyToPoints(points: VecLike[]) {
|
||||
return Matrix2d.applyToPoints(this, points)
|
||||
return Mat.applyToPoints(this, points)
|
||||
}
|
||||
|
||||
rotation() {
|
||||
return Matrix2d.Rotation(this)
|
||||
return Mat.Rotation(this)
|
||||
}
|
||||
|
||||
point() {
|
||||
return Matrix2d.Point(this)
|
||||
return Mat.Point(this)
|
||||
}
|
||||
|
||||
decomposed() {
|
||||
return Matrix2d.Decompose(this)
|
||||
return Mat.Decompose(this)
|
||||
}
|
||||
|
||||
toCssString() {
|
||||
return Matrix2d.toCssString(this)
|
||||
return Mat.toCssString(this)
|
||||
}
|
||||
|
||||
setTo(model: Matrix2dModel) {
|
||||
setTo(model: MatModel) {
|
||||
Object.assign(this, model)
|
||||
return this
|
||||
}
|
||||
|
||||
decompose() {
|
||||
return Matrix2d.Decompose(this)
|
||||
return Mat.Decompose(this)
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Matrix2d(this.a, this.b, this.c, this.d, this.e, this.f)
|
||||
return new Mat(this.a, this.b, this.c, this.d, this.e, this.f)
|
||||
}
|
||||
|
||||
/* --------------------- Static --------------------- */
|
||||
|
||||
static Identity() {
|
||||
return new Matrix2d(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
return new Mat(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
}
|
||||
|
||||
static Translate(x: number, y: number) {
|
||||
return new Matrix2d(1.0, 0.0, 0.0, 1.0, x, y)
|
||||
return new Mat(1.0, 0.0, 0.0, 1.0, x, y)
|
||||
}
|
||||
|
||||
static Rotate(r: number, cx?: number, cy?: number) {
|
||||
if (r === 0) return Matrix2d.Identity()
|
||||
if (r === 0) return Mat.Identity()
|
||||
|
||||
const cosAngle = Math.cos(r)
|
||||
const sinAngle = Math.sin(r)
|
||||
|
||||
const rotationMatrix2d = new Matrix2d(cosAngle, sinAngle, -sinAngle, cosAngle, 0.0, 0.0)
|
||||
const rotationMatrix = new Mat(cosAngle, sinAngle, -sinAngle, cosAngle, 0.0, 0.0)
|
||||
|
||||
if (cx === undefined) return rotationMatrix2d
|
||||
if (cx === undefined) return rotationMatrix
|
||||
|
||||
return Matrix2d.Compose(
|
||||
Matrix2d.Translate(cx, cy!),
|
||||
rotationMatrix2d,
|
||||
Matrix2d.Translate(-cx, -cy!)
|
||||
)
|
||||
return Mat.Compose(Mat.Translate(cx, cy!), rotationMatrix, Mat.Translate(-cx, -cy!))
|
||||
}
|
||||
|
||||
static Scale: {
|
||||
(x: number, y: number): Matrix2dModel
|
||||
(x: number, y: number, cx: number, cy: number): Matrix2dModel
|
||||
(x: number, y: number): MatModel
|
||||
(x: number, y: number, cx: number, cy: number): MatModel
|
||||
} = (x: number, y: number, cx?: number, cy?: number) => {
|
||||
const scaleMatrix2d = new Matrix2d(x, 0, 0, y, 0, 0)
|
||||
const scaleMatrix = new Mat(x, 0, 0, y, 0, 0)
|
||||
|
||||
if (cx === undefined) return scaleMatrix2d
|
||||
if (cx === undefined) return scaleMatrix
|
||||
|
||||
return Matrix2d.Compose(
|
||||
Matrix2d.Translate(cx, cy!),
|
||||
scaleMatrix2d,
|
||||
Matrix2d.Translate(-cx, -cy!)
|
||||
)
|
||||
return Mat.Compose(Mat.Translate(cx, cy!), scaleMatrix, Mat.Translate(-cx, -cy!))
|
||||
}
|
||||
|
||||
static Multiply(m1: Matrix2dModel, m2: Matrix2dModel): Matrix2dModel {
|
||||
static Multiply(m1: MatModel, m2: MatModel): MatModel {
|
||||
return {
|
||||
a: m1.a * m2.a + m1.c * m2.b,
|
||||
c: m1.a * m2.c + m1.c * m2.d,
|
||||
|
@ -195,7 +178,7 @@ export class Matrix2d {
|
|||
}
|
||||
}
|
||||
|
||||
static Inverse(m: Matrix2dModel): Matrix2dModel {
|
||||
static Inverse(m: MatModel): MatModel {
|
||||
const denom = m.a * m.d - m.b * m.c
|
||||
return {
|
||||
a: m.d / denom,
|
||||
|
@ -207,7 +190,7 @@ export class Matrix2d {
|
|||
}
|
||||
}
|
||||
|
||||
static Absolute(m: MatLike): Matrix2dModel {
|
||||
static Absolute(m: MatLike): MatModel {
|
||||
const denom = m.a * m.d - m.b * m.c
|
||||
return {
|
||||
a: m.d / denom,
|
||||
|
@ -220,7 +203,7 @@ export class Matrix2d {
|
|||
}
|
||||
|
||||
static Compose(...matrices: MatLike[]) {
|
||||
const matrix = Matrix2d.Identity()
|
||||
const matrix = Mat.Identity()
|
||||
for (let i = 0, n = matrices.length; i < n; i++) {
|
||||
matrix.multiply(matrices[i])
|
||||
}
|
||||
|
@ -228,7 +211,7 @@ export class Matrix2d {
|
|||
}
|
||||
|
||||
static Point(m: MatLike) {
|
||||
return new Vec2d(m.e, m.f)
|
||||
return new Vec(m.e, m.f)
|
||||
}
|
||||
|
||||
static Rotation(m: MatLike): number {
|
||||
|
@ -239,7 +222,7 @@ export class Matrix2d {
|
|||
rotation = Math.acos(m.a / hypotAc) * (m.c > 0 ? -1 : 1)
|
||||
} else if (m.b !== 0 || m.d !== 0) {
|
||||
const hypotBd = Math.hypot(m.b, m.d)
|
||||
rotation = TAU + Math.acos(m.b / hypotBd) * (m.d > 0 ? -1 : 1)
|
||||
rotation = HALF_PI + Math.acos(m.b / hypotBd) * (m.d > 0 ? -1 : 1)
|
||||
} else {
|
||||
rotation = 0
|
||||
}
|
||||
|
@ -247,7 +230,7 @@ export class Matrix2d {
|
|||
return clampRadians(rotation)
|
||||
}
|
||||
|
||||
static Decompose(m: MatLike): MatrixInfo {
|
||||
static Decompose(m: MatLike) {
|
||||
let scaleX, scaleY, rotation
|
||||
|
||||
if (m.a !== 0 || m.c !== 0) {
|
||||
|
@ -259,7 +242,7 @@ export class Matrix2d {
|
|||
const hypotBd = Math.hypot(m.b, m.d)
|
||||
scaleX = (m.a * m.d - m.b * m.c) / hypotBd
|
||||
scaleY = hypotBd
|
||||
rotation = TAU + Math.acos(m.b / hypotBd) * (m.d > 0 ? -1 : 1)
|
||||
rotation = HALF_PI + Math.acos(m.b / hypotBd) * (m.d > 0 ? -1 : 1)
|
||||
} else {
|
||||
scaleX = 0
|
||||
scaleY = 0
|
||||
|
@ -292,7 +275,7 @@ export class Matrix2d {
|
|||
}
|
||||
|
||||
static applyToPoint(m: MatLike, point: VecLike) {
|
||||
return new Vec2d(
|
||||
return new Vec(
|
||||
m.a * point.x + m.c * point.y + m.e,
|
||||
m.b * point.x + m.d * point.y + m.f,
|
||||
point.z
|
||||
|
@ -303,28 +286,28 @@ export class Matrix2d {
|
|||
return [m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f]
|
||||
}
|
||||
|
||||
static applyToPoints(m: MatLike, points: VecLike[]): Vec2d[] {
|
||||
static applyToPoints(m: MatLike, points: VecLike[]): Vec[] {
|
||||
return points.map(
|
||||
(point) =>
|
||||
new Vec2d(m.a * point.x + m.c * point.y + m.e, m.b * point.x + m.d * point.y + m.f, point.z)
|
||||
new Vec(m.a * point.x + m.c * point.y + m.e, m.b * point.x + m.d * point.y + m.f, point.z)
|
||||
)
|
||||
}
|
||||
|
||||
static applyToBounds(m: MatLike, box: Box2d) {
|
||||
return new Box2d(m.e + box.minX, m.f + box.minY, box.width, box.height)
|
||||
static applyToBounds(m: MatLike, box: Box) {
|
||||
return new Box(m.e + box.minX, m.f + box.minY, box.width, box.height)
|
||||
}
|
||||
|
||||
static From(m: MatLike) {
|
||||
return new Matrix2d(m.a, m.b, m.c, m.d, m.e, m.f)
|
||||
return new Mat(m.a, m.b, m.c, m.d, m.e, m.f)
|
||||
}
|
||||
|
||||
static Cast(m: MatLike) {
|
||||
return m instanceof Matrix2d ? m : Matrix2d.From(m)
|
||||
return m instanceof Mat ? m : Mat.From(m)
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function decomposeMatrix2d(m: MatLike) {
|
||||
export function decomposeMatrix(m: MatLike) {
|
||||
return {
|
||||
x: m.e,
|
||||
y: m.f,
|
|
@ -1,46 +0,0 @@
|
|||
import { Matrix2d } from './Matrix2d'
|
||||
|
||||
describe('Matrix2d', () => {
|
||||
it('Creates a matrix', () => {
|
||||
const mat3 = new Matrix2d(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
expect(mat3).toMatchObject(Matrix2d.Identity())
|
||||
})
|
||||
|
||||
it('Multiplies a matrix', () => {
|
||||
const m1 = new Matrix2d(1, 2, 3, 4, 5, 6)
|
||||
const m2 = new Matrix2d(1, 2, 3, 4, 5, 6)
|
||||
expect(m1.multiply(m2)).toMatchObject({
|
||||
a: 7,
|
||||
b: 10,
|
||||
c: 15,
|
||||
d: 22,
|
||||
e: 28,
|
||||
f: 40,
|
||||
})
|
||||
})
|
||||
|
||||
it('Composes matrices', () => {
|
||||
const m1 = new Matrix2d(1, 2, 3, 4, 5, 6)
|
||||
const m2 = new Matrix2d(1, 2, 3, 4, 5, 6)
|
||||
expect(Matrix2d.Compose(m1, m2)).toMatchObject({
|
||||
a: 7,
|
||||
b: 10,
|
||||
c: 15,
|
||||
d: 22,
|
||||
e: 28,
|
||||
f: 40,
|
||||
})
|
||||
})
|
||||
|
||||
it('Inverts a matrix', () => {
|
||||
const m1 = new Matrix2d(1, 2, 3, 4, 5, 6)
|
||||
expect(m1.invert()).toMatchObject({
|
||||
a: -2,
|
||||
b: 1,
|
||||
c: 1.5,
|
||||
d: -0.5,
|
||||
e: 1,
|
||||
f: -2,
|
||||
})
|
||||
})
|
||||
})
|
373
packages/editor/src/lib/primitives/Vec.test.ts
Normal file
373
packages/editor/src/lib/primitives/Vec.test.ts
Normal file
|
@ -0,0 +1,373 @@
|
|||
import { Vec } from './Vec'
|
||||
|
||||
describe('iteratable', () => {
|
||||
it('Constructs', () => {
|
||||
const v = new Vec(1, 2)
|
||||
const { x, y } = v
|
||||
expect(x).toBeCloseTo(1)
|
||||
expect(y).toBeCloseTo(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Clamp', () => {
|
||||
it('Clamps a vector between a range.', () => {
|
||||
expect(Vec.Clamp(new Vec(9, 5), 7, 10)).toMatchObject(new Vec(9, 7))
|
||||
expect(Vec.Clamp(new Vec(-9, 5), 0, 10)).toMatchObject(new Vec(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Clamp', () => {
|
||||
it('Clamps a vector between a range.', () => {
|
||||
expect(Vec.Clamp(new Vec(9, 5), 7, 10)).toMatchObject(new Vec(9, 7))
|
||||
expect(Vec.Clamp(new Vec(-9, 5), 0, 10)).toMatchObject(new Vec(0, 5))
|
||||
})
|
||||
it('Clamps a vector between a range.', () => {
|
||||
expect(Vec.Clamp(new Vec(9, 5), 10)).toMatchObject(new Vec(10, 10))
|
||||
expect(Vec.Clamp(new Vec(-9, 5), 10)).toMatchObject(new Vec(10, 10))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Neg', () => {
|
||||
it('Negates a vector.', () => {
|
||||
expect(Vec.Neg(new Vec(9, 5))).toMatchObject(new Vec(-9, -5))
|
||||
expect(Vec.Neg(new Vec(-9, 0))).toMatchObject(new Vec(9, -0))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Add', () => {
|
||||
it('Adds two vectors.', () => {
|
||||
expect(Vec.Add(new Vec(9, 5), new Vec(2, 1))).toMatchObject(new Vec(11, 6))
|
||||
expect(Vec.Add(new Vec(-9, 5), new Vec(2, -1))).toMatchObject(new Vec(-7, 4))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.AddScalar', () => {
|
||||
it('Adds a scalar to a vector.', () => {
|
||||
expect(Vec.AddScalar(new Vec(9, 5), 2)).toMatchObject(new Vec(11, 7))
|
||||
expect(Vec.AddScalar(new Vec(-9, 5), 2)).toMatchObject(new Vec(-7, 7))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Sub', () => {
|
||||
it('Subtracts two vectors.', () => {
|
||||
expect(Vec.Sub(new Vec(9, 5), new Vec(2, 1))).toMatchObject(new Vec(7, 4))
|
||||
expect(Vec.Sub(new Vec(-9, 5), new Vec(2, -1))).toMatchObject(new Vec(-11, 6))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.SubScalar', () => {
|
||||
it('Subtracts a scalar from a vector.', () => {
|
||||
expect(Vec.SubScalar(new Vec(9, 5), 2)).toMatchObject(new Vec(7, 3))
|
||||
expect(Vec.SubScalar(new Vec(-9, 5), 2)).toMatchObject(new Vec(-11, 3))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Mul', () => {
|
||||
it('Get a vector multiplied by a scalar.', () => {
|
||||
expect(Vec.Mul(new Vec(9, 9), 3)).toMatchObject(new Vec(27, 27))
|
||||
expect(Vec.Mul(new Vec(10, 10), 2)).toMatchObject(new Vec(20, 20))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.DivV', () => {
|
||||
it('Get a vector multiplied by a vector.', () => {
|
||||
expect(Vec.MulV(new Vec(16, 12), new Vec(2, 4))).toMatchObject(new Vec(32, 48))
|
||||
expect(Vec.MulV(new Vec(5, 15), new Vec(5, 3))).toMatchObject(new Vec(25, 45))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Div', () => {
|
||||
it('Get a vector divided by a scalar.', () => {
|
||||
expect(Vec.Div(new Vec(9, 9), 3)).toMatchObject(new Vec(3, 3))
|
||||
expect(Vec.Div(new Vec(10, 10), 2)).toMatchObject(new Vec(5, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.DivV', () => {
|
||||
it('Get a vector divided by a vector.', () => {
|
||||
expect(Vec.DivV(new Vec(16, 12), new Vec(2, 4))).toMatchObject(new Vec(8, 3))
|
||||
expect(Vec.DivV(new Vec(5, 15), new Vec(5, 3))).toMatchObject(new Vec(1, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Per', () => {
|
||||
it('Gets the perpendicular rotation of a vector.', () => {
|
||||
expect(Vec.Per(new Vec(1, -1))).toMatchObject(new Vec(-1, -1))
|
||||
expect(Vec.Per(new Vec(-1, 1))).toMatchObject(new Vec(1, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Dpr', () => {
|
||||
it('Gets the dot product of two vectors.', () => {
|
||||
expect(Vec.Dpr(new Vec(1, 0), new Vec(1, 0))).toEqual(1)
|
||||
expect(Vec.Dpr(new Vec(1, 0), new Vec(0, 0))).toEqual(0)
|
||||
expect(Vec.Dpr(new Vec(1, 0), new Vec(-1, 0))).toEqual(-1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Cpr', () => {
|
||||
it('Gets the cross product (outer product) of two vectors.', () => {
|
||||
expect(Vec.Cpr(new Vec(0, 1), new Vec(1, 1))).toEqual(-1)
|
||||
expect(Vec.Cpr(new Vec(1, 1), new Vec(1, 1))).toEqual(0)
|
||||
expect(Vec.Cpr(new Vec(1, 1), new Vec(0, 1))).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Len2', () => {
|
||||
it('Gets the length of a vector squared.', () => {
|
||||
expect(Vec.Len2(new Vec(0, 0))).toEqual(0)
|
||||
expect(Vec.Len2(new Vec(0, 1))).toEqual(1)
|
||||
expect(Vec.Len2(new Vec(1, 1))).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Len', () => {
|
||||
it('Gets the length of a vector.', () => {
|
||||
expect(Vec.Len(new Vec(0, 0))).toEqual(0)
|
||||
expect(Vec.Len(new Vec(0, 1))).toEqual(1)
|
||||
expect(Vec.Len(new Vec(1, 1))).toEqual(1.4142135623730951)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Pry', () => {
|
||||
it('Projects a vector A over vector B.', () => {
|
||||
expect(Vec.Pry(new Vec(0, 0), new Vec(0, 10))).toEqual(0)
|
||||
expect(Vec.Pry(new Vec(0, 0), new Vec(10, 10))).toEqual(0)
|
||||
expect(Vec.Pry(new Vec(10, 10), new Vec(0, 10))).toEqual(10)
|
||||
expect(Vec.Pry(new Vec(10, 10), new Vec(10, 10))).toEqual(14.14213562373095)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Uni', () => {
|
||||
it('Gets the normalized vector.', () => {
|
||||
expect(Vec.Uni(new Vec(0, 10))).toMatchObject(new Vec(0, 1))
|
||||
expect(Vec.Uni(new Vec(10, 10))).toMatchObject(new Vec(0.7071067811865475, 0.7071067811865475))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Tan', () => {
|
||||
it('Gets the tangent between two vectors.', () => {
|
||||
expect(Vec.Tan(new Vec(0, 0), new Vec(0, 10))).toMatchObject(new Vec(0, -1))
|
||||
expect(Vec.Tan(new Vec(0, 0), new Vec(10, 10))).toMatchObject(
|
||||
new Vec(-0.7071067811865475, -0.7071067811865475)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Dist2', () => {
|
||||
it('Finds the squared distance between two points.', () => {
|
||||
expect(Vec.Dist2(new Vec(0, 0), new Vec(0, 10))).toEqual(100)
|
||||
expect(Vec.Dist2(new Vec(0, 0), new Vec(10, 10))).toEqual(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Dist', () => {
|
||||
it('Finds the distance between two points.', () => {
|
||||
expect(Vec.Dist(new Vec(0, 0), new Vec(0, 10))).toEqual(10)
|
||||
expect(Vec.Dist(new Vec(0, 0), new Vec(10, 10))).toEqual(14.142135623730951)
|
||||
})
|
||||
})
|
||||
|
||||
// describe('Vec.Ang2', () => {
|
||||
// it('Finds the angle in radians between two vectors.', () => {
|
||||
// expect(Vec.Ang2(new Vec(1, 0), new Vec(0, 1))).toEqual(Math.PI / 2)
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('Vec.Ang3', () => {
|
||||
// it('Gets the angle of ∠ABC', () => {
|
||||
// expect(Vec.Ang3([5, 0], new Vec(0, 0), new Vec(0, 5))).toEqual(Math.PI / 2)
|
||||
// expect(Vec.Ang3(new Vec(1, 0), new Vec(0, 0), new Vec(0, 1))).toEqual(Math.PI / 2)
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('Vec.Angle', () => {
|
||||
it('Finds the angle in radians between two points.', () => {
|
||||
expect(Vec.Angle(new Vec(0, 0), new Vec(10, 10))).toEqual(Math.PI / 4)
|
||||
expect(Vec.Angle(new Vec(0, 0), new Vec(10, 0))).toEqual(0)
|
||||
expect(Vec.Angle(new Vec(0, 0), new Vec(0, 10))).toEqual(Math.PI / 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Med', () => {
|
||||
it('Finds the midpoint between two vectors.', () => {
|
||||
expect(Vec.Med(new Vec(0, 0), new Vec(10, 10))).toMatchObject(new Vec(5, 5))
|
||||
expect(Vec.Med(new Vec(0, 0), new Vec(10, 0))).toMatchObject(new Vec(5, 0))
|
||||
expect(Vec.Med(new Vec(0, 0), new Vec(0, 10))).toMatchObject(new Vec(0, 5))
|
||||
expect(Vec.Med(new Vec(-100, 0), new Vec(0, 100))).toMatchObject(new Vec(-50, 50))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Rot', () => {
|
||||
it('Rotates a vector by a rotation in radians.', () => {
|
||||
const { x, y } = Vec.Rot(new Vec(1, 0), Math.PI / 4)
|
||||
expect(x).toBeCloseTo(0.7, 1)
|
||||
expect(y).toBeCloseTo(0.7, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.RotWith', () => {
|
||||
it('Rotates a vector around a second vector by a rotation in radians.', () => {
|
||||
expect(Vec.RotWith(new Vec(1, 0), new Vec(0, 0), Math.PI / 4)).toMatchObject(
|
||||
new Vec(0.7071067811865476, 0.7071067811865475)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Equals', () => {
|
||||
it('Gets whether two vectors are identical.', () => {
|
||||
expect(Vec.Equals(new Vec(1, 2), new Vec(1, 2))).toEqual(true)
|
||||
expect(Vec.Equals(new Vec(1, 2), new Vec(1, 3))).toEqual(false)
|
||||
expect(Vec.Equals(new Vec(-0, 2), new Vec(0, 2))).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Int', () => {
|
||||
it('Interpolate from A to B', () => {
|
||||
expect(Vec.Lrp(new Vec(0, 0), new Vec(10, 10), 0.5)).toMatchObject(new Vec(5, 5))
|
||||
expect(Vec.Lrp(new Vec(0, 0), new Vec(10, 10), 2)).toMatchObject(new Vec(20, 20))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Rescale', () => {
|
||||
it('Rescales a vector by a scalar', () => {
|
||||
expect(Vec.Rescale(new Vec(5, 0), 1)).toMatchObject(new Vec(1, 0))
|
||||
expect(Vec.Rescale(new Vec(5, 0), 2)).toMatchObject(new Vec(2, 0))
|
||||
expect(Vec.Rescale(new Vec(0.5, 0.25), 2)).toEqual(
|
||||
new Vec(1.7888543819998317, 0.8944271909999159)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.IsClockwise', () => {
|
||||
it('Gets whether point A and point B wind clockwise around point C.', () => {
|
||||
expect(Vec.Clockwise(new Vec(0, 0), new Vec(5, 5), new Vec(0, 5))).toEqual(true)
|
||||
expect(Vec.Clockwise(new Vec(5, 5), new Vec(0, 0), new Vec(0, 5))).toEqual(false)
|
||||
expect(Vec.Clockwise(new Vec(0, 10), new Vec(0, 0), new Vec(0, 5))).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.ToFixed', () => {
|
||||
it('Rounds a vector to the a given precision.', () => {
|
||||
expect(Vec.ToFixed(new Vec(1.2345, 5.678), 1)).toMatchObject(new Vec(1.2, 5.7))
|
||||
expect(Vec.ToFixed(new Vec(1.2345, 5.678), 2)).toMatchObject(new Vec(1.23, 5.68))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Snap', () => {
|
||||
it('Snaps a vector to the nearest increment provided.', () => {
|
||||
expect(Vec.Snap(new Vec(10.5, 28), 10)).toMatchObject(new Vec(10, 30))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.NearestPointOnLineThroughPoint', () => {
|
||||
it('Gets the distance from a point to a line that passes through a given point.', () => {
|
||||
expect(
|
||||
Vec.NearestPointOnLineThroughPoint(new Vec(0, 0), new Vec(0, 1), new Vec(5, 5))
|
||||
).toMatchObject(new Vec(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.DistanceToLineThroughPoint', () => {
|
||||
it('Gets the distance from a point to a line that passes through a given point.', () => {
|
||||
expect(Vec.DistanceToLineThroughPoint(new Vec(0, 0), new Vec(0, 1), new Vec(5, 5))).toEqual(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.NearestPointOnLineSegment', () => {
|
||||
it('Gets the distance from a point to a line segment.', () => {
|
||||
expect(
|
||||
Vec.NearestPointOnLineSegment(new Vec(0, 0), new Vec(0, 10), new Vec(5, 5))
|
||||
).toMatchObject(new Vec(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.DistanceToLineSegment', () => {
|
||||
it('Gets the distance from a point to a line segment.', () => {
|
||||
expect(Vec.DistanceToLineSegment(new Vec(0, 0), new Vec(0, 10), new Vec(5, 5))).toEqual(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Nudge', () => {
|
||||
it('Pushes a point towards another point by a given distance.', () => {
|
||||
expect(Vec.Nudge(new Vec(0, 0), new Vec(0, 10), 5)).toMatchObject(new Vec(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
// describe('Vec.NudgeAtVector', () => {
|
||||
// it('Pushes a point in a given direction vector by a given distance.', () => {
|
||||
// expect(Vec.NudgeAtVector(new Vec(0, 0), new Vec(0.5, 0.75), 10)).toEqual(
|
||||
// new Vec(5, 7.5)
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('Vec.NudgeAtAngle', () => {
|
||||
// it('Pushes a point in a given angle by a given distance.', () => {
|
||||
// expect(Vec.NudgeAtAngle(new Vec(0, 0), Math.PI / 8, 10)).toEqual(
|
||||
// new Vec(9.238795325112868, 3.826834323650898)
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('Vec.PointsBetween', () => {
|
||||
// it('Interpolates points between two points.', () => {
|
||||
// expect(Vec.PointsBetween(new Vec(0, 0), [100, 100], 10)).toMatchObject(new Vec2)(
|
||||
// new Vec(0, 0, 1),
|
||||
// new Vec(11.11111111111111, 11.11111111111111, 0.8888888888888888),
|
||||
// new Vec(22.22222222222222, 22.22222222222222, 0.7777777777777778),
|
||||
// new Vec(33.33333333333333, 33.33333333333333, 0.6666666666666667),
|
||||
// new Vec(44.44444444444444, 44.44444444444444, 0.5555555555555556),
|
||||
// new Vec(55.55555555555556, 55.55555555555556, 0.5555555555555556),
|
||||
// new Vec(66.66666666666666, 66.66666666666666, 0.6666666666666666),
|
||||
// new Vec(77.77777777777779, 77.77777777777779, 0.7777777777777778),
|
||||
// new Vec(88.88888888888889, 88.88888888888889, 0.8888888888888888),
|
||||
// new Vec(100, 100, 1),
|
||||
// ])
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('Vec.Slope', () => {
|
||||
it('Gets a slope from a vector.', () => {
|
||||
expect(Vec.Slope(new Vec(0, 0), new Vec(100, 100))).toEqual(1)
|
||||
expect(Vec.Slope(new Vec(0, 0), new Vec(50, 100))).toEqual(2)
|
||||
expect(Vec.Slope(new Vec(0, 0), new Vec(-50, 100))).toEqual(-2)
|
||||
expect(Vec.Slope(new Vec(123, 456), new Vec(789, 24))).toEqual(-0.6486486486486487)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.ToAngle', () => {
|
||||
it('Gets an angle from a vector.', () => {
|
||||
expect(Vec.ToAngle(new Vec(1, 0.5))).toEqual(0.4636476090008061)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Max', () => {
|
||||
it('Gets the minimum of the given vectors', () => {
|
||||
expect(Vec.Max(new Vec(4, 1), new Vec(3, 2))).toMatchObject(new Vec(4, 2))
|
||||
expect(Vec.Max(new Vec(3, 2), new Vec(4, 1))).toMatchObject(new Vec(4, 2))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.Min', () => {
|
||||
it('Gets the minimum of the given vectors', () => {
|
||||
expect(Vec.Min(new Vec(4, 1), new Vec(3, 2))).toMatchObject(new Vec(3, 1))
|
||||
expect(Vec.Min(new Vec(3, 2), new Vec(4, 1))).toMatchObject(new Vec(3, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec.snapToGrid', () => {
|
||||
it('snaps to the nearest given increment, mutating the original vector and returning it', () => {
|
||||
expect(new Vec(25, 29).snapToGrid(8)).toMatchObject(new Vec(24, 32))
|
||||
expect(new Vec(25, 29).snapToGrid(8)).toMatchObject(new Vec(24, 32))
|
||||
expect(new Vec(25, 29).snapToGrid(3)).toMatchObject(new Vec(24, 30))
|
||||
expect(new Vec(25, 29).snapToGrid(10)).toMatchObject(new Vec(30, 30))
|
||||
expect(new Vec(12, 49).snapToGrid(10)).toMatchObject(new Vec(10, 50))
|
||||
|
||||
expect(Vec.SnapToGrid(new Vec(25, 29))).toMatchObject(new Vec(24, 32))
|
||||
expect(Vec.SnapToGrid(new Vec(25, 29), 8)).toMatchObject(new Vec(24, 32))
|
||||
expect(Vec.SnapToGrid(new Vec(25, 29), 3)).toMatchObject(new Vec(24, 30))
|
||||
expect(Vec.SnapToGrid(new Vec(25, 29), 10)).toMatchObject(new Vec(30, 30))
|
||||
expect(Vec.SnapToGrid(new Vec(12, 49), 10)).toMatchObject(new Vec(10, 50))
|
||||
})
|
||||
})
|
|
@ -1,11 +1,11 @@
|
|||
import { Vec2dModel } from '@tldraw/tlschema'
|
||||
import { VecModel } from '@tldraw/tlschema'
|
||||
import { EASINGS } from './easings'
|
||||
|
||||
/** @public */
|
||||
export type VecLike = Vec2d | Vec2dModel
|
||||
export type VecLike = Vec | VecModel
|
||||
|
||||
/** @public */
|
||||
export class Vec2d {
|
||||
export class Vec {
|
||||
constructor(public x = 0, public y = 0, public z = 1) {}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
|
@ -48,9 +48,9 @@ export class Vec2d {
|
|||
return this
|
||||
}
|
||||
|
||||
clone(): Vec2d {
|
||||
clone(): Vec {
|
||||
const { x, y, z } = this
|
||||
return new Vec2d(x, y, z)
|
||||
return new Vec(x, y, z)
|
||||
}
|
||||
|
||||
sub(V: VecLike) {
|
||||
|
@ -138,7 +138,7 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
nudge(B: VecLike, distance: number) {
|
||||
const tan = Vec2d.Tan(B, this)
|
||||
const tan = Vec.Tan(B, this)
|
||||
return this.add(tan.mul(distance))
|
||||
}
|
||||
|
||||
|
@ -157,23 +157,23 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
dpr(V: VecLike): number {
|
||||
return Vec2d.Dpr(this, V)
|
||||
return Vec.Dpr(this, V)
|
||||
}
|
||||
|
||||
cpr(V: VecLike) {
|
||||
return Vec2d.Cpr(this, V)
|
||||
return Vec.Cpr(this, V)
|
||||
}
|
||||
|
||||
len2(): number {
|
||||
return Vec2d.Len2(this)
|
||||
return Vec.Len2(this)
|
||||
}
|
||||
|
||||
len(): number {
|
||||
return Vec2d.Len(this)
|
||||
return Vec.Len(this)
|
||||
}
|
||||
|
||||
pry(V: VecLike): number {
|
||||
return Vec2d.Pry(this, V)
|
||||
return Vec.Pry(this, V)
|
||||
}
|
||||
|
||||
per() {
|
||||
|
@ -184,23 +184,23 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
uni() {
|
||||
return Vec2d.Uni(this)
|
||||
return Vec.Uni(this)
|
||||
}
|
||||
|
||||
tan(V: VecLike): Vec2d {
|
||||
return Vec2d.Tan(this, V)
|
||||
tan(V: VecLike): Vec {
|
||||
return Vec.Tan(this, V)
|
||||
}
|
||||
|
||||
dist(V: VecLike): number {
|
||||
return Vec2d.Dist(this, V)
|
||||
return Vec.Dist(this, V)
|
||||
}
|
||||
|
||||
distanceToLineSegment(A: VecLike, B: VecLike): number {
|
||||
return Vec2d.DistanceToLineSegment(A, B, this)
|
||||
return Vec.DistanceToLineSegment(A, B, this)
|
||||
}
|
||||
|
||||
slope(B: VecLike): number {
|
||||
return Vec2d.Slope(this, B)
|
||||
return Vec.Slope(this, B)
|
||||
}
|
||||
|
||||
snapToGrid(gridSize: number) {
|
||||
|
@ -210,25 +210,25 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
angle(B: VecLike): number {
|
||||
return Vec2d.Angle(this, B)
|
||||
return Vec.Angle(this, B)
|
||||
}
|
||||
|
||||
toAngle() {
|
||||
return Vec2d.ToAngle(this)
|
||||
return Vec.ToAngle(this)
|
||||
}
|
||||
|
||||
lrp(B: VecLike, t: number): Vec2d {
|
||||
lrp(B: VecLike, t: number): Vec {
|
||||
this.x = this.x + (B.x - this.x) * t
|
||||
this.y = this.y + (B.y - this.y) * t
|
||||
return this
|
||||
}
|
||||
|
||||
equals(B: VecLike) {
|
||||
return Vec2d.Equals(this, B)
|
||||
return Vec.Equals(this, B)
|
||||
}
|
||||
|
||||
equalsXY(x: number, y: number) {
|
||||
return Vec2d.EqualsXY(this, x, y)
|
||||
return Vec.EqualsXY(this, x, y)
|
||||
}
|
||||
|
||||
norm() {
|
||||
|
@ -239,75 +239,75 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
toFixed() {
|
||||
return Vec2d.ToFixed(this)
|
||||
return Vec.ToFixed(this)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return Vec2d.ToString(Vec2d.ToFixed(this))
|
||||
return Vec.ToString(Vec.ToFixed(this))
|
||||
}
|
||||
|
||||
toJson(): Vec2dModel {
|
||||
return Vec2d.ToJson(this)
|
||||
toJson(): VecModel {
|
||||
return Vec.ToJson(this)
|
||||
}
|
||||
|
||||
toArray(): number[] {
|
||||
return Vec2d.ToArray(this)
|
||||
return Vec.ToArray(this)
|
||||
}
|
||||
|
||||
static Add(A: VecLike, B: VecLike): Vec2d {
|
||||
return new Vec2d(A.x + B.x, A.y + B.y)
|
||||
static Add(A: VecLike, B: VecLike): Vec {
|
||||
return new Vec(A.x + B.x, A.y + B.y)
|
||||
}
|
||||
|
||||
static AddXY(A: VecLike, x: number, y: number): Vec2d {
|
||||
return new Vec2d(A.x + x, A.y + y)
|
||||
static AddXY(A: VecLike, x: number, y: number): Vec {
|
||||
return new Vec(A.x + x, A.y + y)
|
||||
}
|
||||
|
||||
static Sub(A: VecLike, B: VecLike): Vec2d {
|
||||
return new Vec2d(A.x - B.x, A.y - B.y)
|
||||
static Sub(A: VecLike, B: VecLike): Vec {
|
||||
return new Vec(A.x - B.x, A.y - B.y)
|
||||
}
|
||||
|
||||
static SubXY(A: VecLike, x: number, y: number): Vec2d {
|
||||
return new Vec2d(A.x - x, A.y - y)
|
||||
static SubXY(A: VecLike, x: number, y: number): Vec {
|
||||
return new Vec(A.x - x, A.y - y)
|
||||
}
|
||||
|
||||
static AddScalar(A: VecLike, n: number): Vec2d {
|
||||
return new Vec2d(A.x + n, A.y + n)
|
||||
static AddScalar(A: VecLike, n: number): Vec {
|
||||
return new Vec(A.x + n, A.y + n)
|
||||
}
|
||||
|
||||
static SubScalar(A: VecLike, n: number): Vec2d {
|
||||
return new Vec2d(A.x - n, A.y - n)
|
||||
static SubScalar(A: VecLike, n: number): Vec {
|
||||
return new Vec(A.x - n, A.y - n)
|
||||
}
|
||||
|
||||
static Div(A: VecLike, t: number): Vec2d {
|
||||
return new Vec2d(A.x / t, A.y / t)
|
||||
static Div(A: VecLike, t: number): Vec {
|
||||
return new Vec(A.x / t, A.y / t)
|
||||
}
|
||||
|
||||
static Mul(A: VecLike, t: number): Vec2d {
|
||||
return new Vec2d(A.x * t, A.y * t)
|
||||
static Mul(A: VecLike, t: number): Vec {
|
||||
return new Vec(A.x * t, A.y * t)
|
||||
}
|
||||
|
||||
static DivV(A: VecLike, B: VecLike): Vec2d {
|
||||
return new Vec2d(A.x / B.x, A.y / B.y)
|
||||
static DivV(A: VecLike, B: VecLike): Vec {
|
||||
return new Vec(A.x / B.x, A.y / B.y)
|
||||
}
|
||||
|
||||
static MulV(A: VecLike, B: VecLike): Vec2d {
|
||||
return new Vec2d(A.x * B.x, A.y * B.y)
|
||||
static MulV(A: VecLike, B: VecLike): Vec {
|
||||
return new Vec(A.x * B.x, A.y * B.y)
|
||||
}
|
||||
|
||||
static Neg(A: VecLike): Vec2d {
|
||||
return new Vec2d(-A.x, -A.y)
|
||||
static Neg(A: VecLike): Vec {
|
||||
return new Vec(-A.x, -A.y)
|
||||
}
|
||||
|
||||
static Per(A: VecLike): Vec2d {
|
||||
return new Vec2d(A.y, -A.x)
|
||||
static Per(A: VecLike): Vec {
|
||||
return new Vec(A.y, -A.x)
|
||||
}
|
||||
|
||||
static Dist2(A: VecLike, B: VecLike): number {
|
||||
return Vec2d.Sub(A, B).len2()
|
||||
return Vec.Sub(A, B).len2()
|
||||
}
|
||||
|
||||
static Abs(A: VecLike): Vec2d {
|
||||
return new Vec2d(Math.abs(A.x), Math.abs(A.y))
|
||||
static Abs(A: VecLike): Vec {
|
||||
return new Vec(Math.abs(A.x), Math.abs(A.y))
|
||||
}
|
||||
|
||||
static Dist(A: VecLike, B: VecLike): number {
|
||||
|
@ -319,7 +319,7 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
static Cross(A: VecLike, V: VecLike) {
|
||||
return new Vec2d(
|
||||
return new Vec(
|
||||
A.y * V.z! - A.z! * V.y,
|
||||
A.z! * V.x - A.x * V.z!
|
||||
// A.z = A.x * V.y - A.y * V.x
|
||||
|
@ -339,45 +339,45 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
static Pry(A: VecLike, B: VecLike): number {
|
||||
return Vec2d.Dpr(A, B) / Vec2d.Len(B)
|
||||
return Vec.Dpr(A, B) / Vec.Len(B)
|
||||
}
|
||||
|
||||
static Uni(A: VecLike) {
|
||||
return Vec2d.Div(A, Vec2d.Len(A))
|
||||
return Vec.Div(A, Vec.Len(A))
|
||||
}
|
||||
|
||||
static Tan(A: VecLike, B: VecLike): Vec2d {
|
||||
return Vec2d.Uni(Vec2d.Sub(A, B))
|
||||
static Tan(A: VecLike, B: VecLike): Vec {
|
||||
return Vec.Uni(Vec.Sub(A, B))
|
||||
}
|
||||
|
||||
static Min(A: VecLike, B: VecLike): Vec2d {
|
||||
return new Vec2d(Math.min(A.x, B.x), Math.min(A.y, B.y))
|
||||
static Min(A: VecLike, B: VecLike): Vec {
|
||||
return new Vec(Math.min(A.x, B.x), Math.min(A.y, B.y))
|
||||
}
|
||||
|
||||
static Max(A: VecLike, B: VecLike): Vec2d {
|
||||
return new Vec2d(Math.max(A.x, B.x), Math.max(A.y, B.y))
|
||||
static Max(A: VecLike, B: VecLike): Vec {
|
||||
return new Vec(Math.max(A.x, B.x), Math.max(A.y, B.y))
|
||||
}
|
||||
|
||||
static From({ x, y, z = 1 }: Vec2dModel) {
|
||||
return new Vec2d(x, y, z)
|
||||
static From({ x, y, z = 1 }: VecModel) {
|
||||
return new Vec(x, y, z)
|
||||
}
|
||||
|
||||
static FromArray(v: number[]): Vec2d {
|
||||
return new Vec2d(v[0], v[1])
|
||||
static FromArray(v: number[]): Vec {
|
||||
return new Vec(v[0], v[1])
|
||||
}
|
||||
|
||||
static Rot(A: VecLike, r = 0): Vec2d {
|
||||
static Rot(A: VecLike, r = 0): Vec {
|
||||
const s = Math.sin(r)
|
||||
const c = Math.cos(r)
|
||||
return new Vec2d(A.x * c - A.y * s, A.x * s + A.y * c)
|
||||
return new Vec(A.x * c - A.y * s, A.x * s + A.y * c)
|
||||
}
|
||||
|
||||
static RotWith(A: VecLike, C: VecLike, r: number): Vec2d {
|
||||
static RotWith(A: VecLike, C: VecLike, r: number): Vec {
|
||||
const x = A.x - C.x
|
||||
const y = A.y - C.y
|
||||
const s = Math.sin(r)
|
||||
const c = Math.cos(r)
|
||||
return new Vec2d(C.x + (x * c - y * s), C.y + (x * s + y * c))
|
||||
return new Vec(C.x + (x * c - y * s), C.y + (x * s + y * c))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -391,41 +391,41 @@ export class Vec2d {
|
|||
* @param u - The unit vector for the line.
|
||||
* @param P - A point not on the line to test.
|
||||
*/
|
||||
static NearestPointOnLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): Vec2d {
|
||||
return Vec2d.Mul(u, Vec2d.Sub(P, A).pry(u)).add(A)
|
||||
static NearestPointOnLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): Vec {
|
||||
return Vec.Mul(u, Vec.Sub(P, A).pry(u)).add(A)
|
||||
}
|
||||
|
||||
static NearestPointOnLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp = true): Vec2d {
|
||||
const u = Vec2d.Tan(B, A)
|
||||
const C = Vec2d.Add(A, Vec2d.Mul(u, Vec2d.Sub(P, A).pry(u)))
|
||||
static NearestPointOnLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp = true): Vec {
|
||||
const u = Vec.Tan(B, A)
|
||||
const C = Vec.Add(A, Vec.Mul(u, Vec.Sub(P, A).pry(u)))
|
||||
|
||||
// todo: fix error P is B or A, which leads to a NaN value
|
||||
|
||||
if (clamp) {
|
||||
if (C.x < Math.min(A.x, B.x)) return Vec2d.Cast(A.x < B.x ? A : B)
|
||||
if (C.x > Math.max(A.x, B.x)) return Vec2d.Cast(A.x > B.x ? A : B)
|
||||
if (C.y < Math.min(A.y, B.y)) return Vec2d.Cast(A.y < B.y ? A : B)
|
||||
if (C.y > Math.max(A.y, B.y)) return Vec2d.Cast(A.y > B.y ? A : B)
|
||||
if (C.x < Math.min(A.x, B.x)) return Vec.Cast(A.x < B.x ? A : B)
|
||||
if (C.x > Math.max(A.x, B.x)) return Vec.Cast(A.x > B.x ? A : B)
|
||||
if (C.y < Math.min(A.y, B.y)) return Vec.Cast(A.y < B.y ? A : B)
|
||||
if (C.y > Math.max(A.y, B.y)) return Vec.Cast(A.y > B.y ? A : B)
|
||||
}
|
||||
|
||||
return C
|
||||
}
|
||||
|
||||
static DistanceToLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): number {
|
||||
return Vec2d.Dist(P, Vec2d.NearestPointOnLineThroughPoint(A, u, P))
|
||||
return Vec.Dist(P, Vec.NearestPointOnLineThroughPoint(A, u, P))
|
||||
}
|
||||
|
||||
static DistanceToLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp = true): number {
|
||||
return Vec2d.Dist(P, Vec2d.NearestPointOnLineSegment(A, B, P, clamp))
|
||||
return Vec.Dist(P, Vec.NearestPointOnLineSegment(A, B, P, clamp))
|
||||
}
|
||||
|
||||
static Snap(A: VecLike, step = 1) {
|
||||
return new Vec2d(Math.round(A.x / step) * step, Math.round(A.y / step) * step)
|
||||
return new Vec(Math.round(A.x / step) * step, Math.round(A.y / step) * step)
|
||||
}
|
||||
|
||||
static Cast(A: VecLike): Vec2d {
|
||||
if (A instanceof Vec2d) return A
|
||||
return Vec2d.From(A)
|
||||
static Cast(A: VecLike): Vec {
|
||||
if (A instanceof Vec) return A
|
||||
return Vec.From(A)
|
||||
}
|
||||
|
||||
static Slope(A: VecLike, B: VecLike): number {
|
||||
|
@ -437,12 +437,12 @@ export class Vec2d {
|
|||
return Math.atan2(B.y - A.y, B.x - A.x)
|
||||
}
|
||||
|
||||
static Lrp(A: VecLike, B: VecLike, t: number): Vec2d {
|
||||
return Vec2d.Sub(B, A).mul(t).add(A)
|
||||
static Lrp(A: VecLike, B: VecLike, t: number): Vec {
|
||||
return Vec.Sub(B, A).mul(t).add(A)
|
||||
}
|
||||
|
||||
static Med(A: VecLike, B: VecLike): Vec2d {
|
||||
return new Vec2d((A.x + B.x) / 2, (A.y + B.y) / 2)
|
||||
static Med(A: VecLike, B: VecLike): Vec {
|
||||
return new Vec((A.x + B.x) / 2, (A.y + B.y) / 2)
|
||||
}
|
||||
|
||||
static Equals(A: VecLike, B: VecLike): boolean {
|
||||
|
@ -458,20 +458,20 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
static Rescale(A: VecLike, n: number) {
|
||||
const l = Vec2d.Len(A)
|
||||
return new Vec2d((n * A.x) / l, (n * A.y) / l)
|
||||
const l = Vec.Len(A)
|
||||
return new Vec((n * A.x) / l, (n * A.y) / l)
|
||||
}
|
||||
|
||||
static ScaleWithOrigin(A: VecLike, scale: number, origin: VecLike) {
|
||||
return Vec2d.Sub(A, origin).mul(scale).add(origin)
|
||||
return Vec.Sub(A, origin).mul(scale).add(origin)
|
||||
}
|
||||
|
||||
static ToFixed(A: VecLike, n = 2) {
|
||||
return new Vec2d(+A.x.toFixed(n), +A.y.toFixed(n), +A.z!.toFixed(n))
|
||||
return new Vec(+A.x.toFixed(n), +A.y.toFixed(n), +A.z!.toFixed(n))
|
||||
}
|
||||
|
||||
static Nudge(A: VecLike, B: VecLike, distance: number) {
|
||||
return Vec2d.Add(A, Vec2d.Tan(B, A).mul(distance))
|
||||
return Vec.Add(A, Vec.Tan(B, A).mul(distance))
|
||||
}
|
||||
|
||||
static ToString(A: VecLike) {
|
||||
|
@ -486,7 +486,7 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
static FromAngle(r: number, length = 1) {
|
||||
return new Vec2d(Math.cos(r) * length, Math.sin(r) * length)
|
||||
return new Vec(Math.cos(r) * length, Math.sin(r) * length)
|
||||
}
|
||||
|
||||
static ToArray(A: VecLike) {
|
||||
|
@ -500,19 +500,19 @@ export class Vec2d {
|
|||
|
||||
static Average(arr: VecLike[]) {
|
||||
const len = arr.length
|
||||
const avg = new Vec2d(0, 0)
|
||||
const avg = new Vec(0, 0)
|
||||
for (let i = 0; i < len; i++) {
|
||||
avg.add(arr[i])
|
||||
}
|
||||
return avg.div(len)
|
||||
}
|
||||
|
||||
static Clamp(A: Vec2d, min: number, max?: number) {
|
||||
static Clamp(A: Vec, min: number, max?: number) {
|
||||
if (max === undefined) {
|
||||
return new Vec2d(Math.min(Math.max(A.x, min)), Math.min(Math.max(A.y, min)))
|
||||
return new Vec(Math.min(Math.max(A.x, min)), Math.min(Math.max(A.y, min)))
|
||||
}
|
||||
|
||||
return new Vec2d(Math.min(Math.max(A.x, min), max), Math.min(Math.max(A.y, min), max))
|
||||
return new Vec(Math.min(Math.max(A.x, min), max), Math.min(Math.max(A.y, min), max))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -522,12 +522,12 @@ export class Vec2d {
|
|||
* @param B - The second point.
|
||||
* @param steps - The number of points to return.
|
||||
*/
|
||||
static PointsBetween(A: Vec2dModel, B: Vec2dModel, steps = 6): Vec2d[] {
|
||||
const results: Vec2d[] = []
|
||||
static PointsBetween(A: VecModel, B: VecModel, steps = 6): Vec[] {
|
||||
const results: Vec[] = []
|
||||
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const t = EASINGS.easeInQuad(i / (steps - 1))
|
||||
const point = Vec2d.Lrp(A, B, t)
|
||||
const point = Vec.Lrp(A, B, t)
|
||||
point.z = Math.min(1, 0.5 + Math.abs(0.5 - ease(t)) * 0.65)
|
||||
results.push(point)
|
||||
}
|
||||
|
@ -536,7 +536,7 @@ export class Vec2d {
|
|||
}
|
||||
|
||||
static SnapToGrid(A: VecLike, gridSize = 8) {
|
||||
return new Vec2d(Math.round(A.x / gridSize) * gridSize, Math.round(A.y / gridSize) * gridSize)
|
||||
return new Vec(Math.round(A.x / gridSize) * gridSize, Math.round(A.y / gridSize) * gridSize)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,379 +0,0 @@
|
|||
import { Vec2d } from './Vec2d'
|
||||
|
||||
describe('iteratable', () => {
|
||||
it('Constructs', () => {
|
||||
const v = new Vec2d(1, 2)
|
||||
const { x, y } = v
|
||||
expect(x).toBeCloseTo(1)
|
||||
expect(y).toBeCloseTo(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Clamp', () => {
|
||||
it('Clamps a vector between a range.', () => {
|
||||
expect(Vec2d.Clamp(new Vec2d(9, 5), 7, 10)).toMatchObject(new Vec2d(9, 7))
|
||||
expect(Vec2d.Clamp(new Vec2d(-9, 5), 0, 10)).toMatchObject(new Vec2d(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Clamp', () => {
|
||||
it('Clamps a vector between a range.', () => {
|
||||
expect(Vec2d.Clamp(new Vec2d(9, 5), 7, 10)).toMatchObject(new Vec2d(9, 7))
|
||||
expect(Vec2d.Clamp(new Vec2d(-9, 5), 0, 10)).toMatchObject(new Vec2d(0, 5))
|
||||
})
|
||||
it('Clamps a vector between a range.', () => {
|
||||
expect(Vec2d.Clamp(new Vec2d(9, 5), 10)).toMatchObject(new Vec2d(10, 10))
|
||||
expect(Vec2d.Clamp(new Vec2d(-9, 5), 10)).toMatchObject(new Vec2d(10, 10))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Neg', () => {
|
||||
it('Negates a vector.', () => {
|
||||
expect(Vec2d.Neg(new Vec2d(9, 5))).toMatchObject(new Vec2d(-9, -5))
|
||||
expect(Vec2d.Neg(new Vec2d(-9, 0))).toMatchObject(new Vec2d(9, -0))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Add', () => {
|
||||
it('Adds two vectors.', () => {
|
||||
expect(Vec2d.Add(new Vec2d(9, 5), new Vec2d(2, 1))).toMatchObject(new Vec2d(11, 6))
|
||||
expect(Vec2d.Add(new Vec2d(-9, 5), new Vec2d(2, -1))).toMatchObject(new Vec2d(-7, 4))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.AddScalar', () => {
|
||||
it('Adds a scalar to a vector.', () => {
|
||||
expect(Vec2d.AddScalar(new Vec2d(9, 5), 2)).toMatchObject(new Vec2d(11, 7))
|
||||
expect(Vec2d.AddScalar(new Vec2d(-9, 5), 2)).toMatchObject(new Vec2d(-7, 7))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Sub', () => {
|
||||
it('Subtracts two vectors.', () => {
|
||||
expect(Vec2d.Sub(new Vec2d(9, 5), new Vec2d(2, 1))).toMatchObject(new Vec2d(7, 4))
|
||||
expect(Vec2d.Sub(new Vec2d(-9, 5), new Vec2d(2, -1))).toMatchObject(new Vec2d(-11, 6))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.SubScalar', () => {
|
||||
it('Subtracts a scalar from a vector.', () => {
|
||||
expect(Vec2d.SubScalar(new Vec2d(9, 5), 2)).toMatchObject(new Vec2d(7, 3))
|
||||
expect(Vec2d.SubScalar(new Vec2d(-9, 5), 2)).toMatchObject(new Vec2d(-11, 3))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Mul', () => {
|
||||
it('Get a vector multiplied by a scalar.', () => {
|
||||
expect(Vec2d.Mul(new Vec2d(9, 9), 3)).toMatchObject(new Vec2d(27, 27))
|
||||
expect(Vec2d.Mul(new Vec2d(10, 10), 2)).toMatchObject(new Vec2d(20, 20))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.DivV', () => {
|
||||
it('Get a vector multiplied by a vector.', () => {
|
||||
expect(Vec2d.MulV(new Vec2d(16, 12), new Vec2d(2, 4))).toMatchObject(new Vec2d(32, 48))
|
||||
expect(Vec2d.MulV(new Vec2d(5, 15), new Vec2d(5, 3))).toMatchObject(new Vec2d(25, 45))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Div', () => {
|
||||
it('Get a vector divided by a scalar.', () => {
|
||||
expect(Vec2d.Div(new Vec2d(9, 9), 3)).toMatchObject(new Vec2d(3, 3))
|
||||
expect(Vec2d.Div(new Vec2d(10, 10), 2)).toMatchObject(new Vec2d(5, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.DivV', () => {
|
||||
it('Get a vector divided by a vector.', () => {
|
||||
expect(Vec2d.DivV(new Vec2d(16, 12), new Vec2d(2, 4))).toMatchObject(new Vec2d(8, 3))
|
||||
expect(Vec2d.DivV(new Vec2d(5, 15), new Vec2d(5, 3))).toMatchObject(new Vec2d(1, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Per', () => {
|
||||
it('Gets the perpendicular rotation of a vector.', () => {
|
||||
expect(Vec2d.Per(new Vec2d(1, -1))).toMatchObject(new Vec2d(-1, -1))
|
||||
expect(Vec2d.Per(new Vec2d(-1, 1))).toMatchObject(new Vec2d(1, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Dpr', () => {
|
||||
it('Gets the dot product of two vectors.', () => {
|
||||
expect(Vec2d.Dpr(new Vec2d(1, 0), new Vec2d(1, 0))).toEqual(1)
|
||||
expect(Vec2d.Dpr(new Vec2d(1, 0), new Vec2d(0, 0))).toEqual(0)
|
||||
expect(Vec2d.Dpr(new Vec2d(1, 0), new Vec2d(-1, 0))).toEqual(-1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Cpr', () => {
|
||||
it('Gets the cross product (outer product) of two vectors.', () => {
|
||||
expect(Vec2d.Cpr(new Vec2d(0, 1), new Vec2d(1, 1))).toEqual(-1)
|
||||
expect(Vec2d.Cpr(new Vec2d(1, 1), new Vec2d(1, 1))).toEqual(0)
|
||||
expect(Vec2d.Cpr(new Vec2d(1, 1), new Vec2d(0, 1))).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Len2', () => {
|
||||
it('Gets the length of a vector squared.', () => {
|
||||
expect(Vec2d.Len2(new Vec2d(0, 0))).toEqual(0)
|
||||
expect(Vec2d.Len2(new Vec2d(0, 1))).toEqual(1)
|
||||
expect(Vec2d.Len2(new Vec2d(1, 1))).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Len', () => {
|
||||
it('Gets the length of a vector.', () => {
|
||||
expect(Vec2d.Len(new Vec2d(0, 0))).toEqual(0)
|
||||
expect(Vec2d.Len(new Vec2d(0, 1))).toEqual(1)
|
||||
expect(Vec2d.Len(new Vec2d(1, 1))).toEqual(1.4142135623730951)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Pry', () => {
|
||||
it('Projects a vector A over vector B.', () => {
|
||||
expect(Vec2d.Pry(new Vec2d(0, 0), new Vec2d(0, 10))).toEqual(0)
|
||||
expect(Vec2d.Pry(new Vec2d(0, 0), new Vec2d(10, 10))).toEqual(0)
|
||||
expect(Vec2d.Pry(new Vec2d(10, 10), new Vec2d(0, 10))).toEqual(10)
|
||||
expect(Vec2d.Pry(new Vec2d(10, 10), new Vec2d(10, 10))).toEqual(14.14213562373095)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Uni', () => {
|
||||
it('Gets the normalized vector.', () => {
|
||||
expect(Vec2d.Uni(new Vec2d(0, 10))).toMatchObject(new Vec2d(0, 1))
|
||||
expect(Vec2d.Uni(new Vec2d(10, 10))).toMatchObject(
|
||||
new Vec2d(0.7071067811865475, 0.7071067811865475)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Tan', () => {
|
||||
it('Gets the tangent between two vectors.', () => {
|
||||
expect(Vec2d.Tan(new Vec2d(0, 0), new Vec2d(0, 10))).toMatchObject(new Vec2d(0, -1))
|
||||
expect(Vec2d.Tan(new Vec2d(0, 0), new Vec2d(10, 10))).toMatchObject(
|
||||
new Vec2d(-0.7071067811865475, -0.7071067811865475)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Dist2', () => {
|
||||
it('Finds the squared distance between two points.', () => {
|
||||
expect(Vec2d.Dist2(new Vec2d(0, 0), new Vec2d(0, 10))).toEqual(100)
|
||||
expect(Vec2d.Dist2(new Vec2d(0, 0), new Vec2d(10, 10))).toEqual(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Dist', () => {
|
||||
it('Finds the distance between two points.', () => {
|
||||
expect(Vec2d.Dist(new Vec2d(0, 0), new Vec2d(0, 10))).toEqual(10)
|
||||
expect(Vec2d.Dist(new Vec2d(0, 0), new Vec2d(10, 10))).toEqual(14.142135623730951)
|
||||
})
|
||||
})
|
||||
|
||||
// describe('Vec2d.Ang2', () => {
|
||||
// it('Finds the angle in radians between two vectors.', () => {
|
||||
// expect(Vec2d.Ang2(new Vec2d(1, 0), new Vec2d(0, 1))).toEqual(Math.PI / 2)
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('Vec2d.Ang3', () => {
|
||||
// it('Gets the angle of ∠ABC', () => {
|
||||
// expect(Vec2d.Ang3([5, 0], new Vec2d(0, 0), new Vec2d(0, 5))).toEqual(Math.PI / 2)
|
||||
// expect(Vec2d.Ang3(new Vec2d(1, 0), new Vec2d(0, 0), new Vec2d(0, 1))).toEqual(Math.PI / 2)
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('Vec2d.Angle', () => {
|
||||
it('Finds the angle in radians between two points.', () => {
|
||||
expect(Vec2d.Angle(new Vec2d(0, 0), new Vec2d(10, 10))).toEqual(Math.PI / 4)
|
||||
expect(Vec2d.Angle(new Vec2d(0, 0), new Vec2d(10, 0))).toEqual(0)
|
||||
expect(Vec2d.Angle(new Vec2d(0, 0), new Vec2d(0, 10))).toEqual(Math.PI / 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Med', () => {
|
||||
it('Finds the midpoint between two vectors.', () => {
|
||||
expect(Vec2d.Med(new Vec2d(0, 0), new Vec2d(10, 10))).toMatchObject(new Vec2d(5, 5))
|
||||
expect(Vec2d.Med(new Vec2d(0, 0), new Vec2d(10, 0))).toMatchObject(new Vec2d(5, 0))
|
||||
expect(Vec2d.Med(new Vec2d(0, 0), new Vec2d(0, 10))).toMatchObject(new Vec2d(0, 5))
|
||||
expect(Vec2d.Med(new Vec2d(-100, 0), new Vec2d(0, 100))).toMatchObject(new Vec2d(-50, 50))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Rot', () => {
|
||||
it('Rotates a vector by a rotation in radians.', () => {
|
||||
const { x, y } = Vec2d.Rot(new Vec2d(1, 0), Math.PI / 4)
|
||||
expect(x).toBeCloseTo(0.7, 1)
|
||||
expect(y).toBeCloseTo(0.7, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.RotWith', () => {
|
||||
it('Rotates a vector around a second vector by a rotation in radians.', () => {
|
||||
expect(Vec2d.RotWith(new Vec2d(1, 0), new Vec2d(0, 0), Math.PI / 4)).toMatchObject(
|
||||
new Vec2d(0.7071067811865476, 0.7071067811865475)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Equals', () => {
|
||||
it('Gets whether two vectors are identical.', () => {
|
||||
expect(Vec2d.Equals(new Vec2d(1, 2), new Vec2d(1, 2))).toEqual(true)
|
||||
expect(Vec2d.Equals(new Vec2d(1, 2), new Vec2d(1, 3))).toEqual(false)
|
||||
expect(Vec2d.Equals(new Vec2d(-0, 2), new Vec2d(0, 2))).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Int', () => {
|
||||
it('Interpolate from A to B', () => {
|
||||
expect(Vec2d.Lrp(new Vec2d(0, 0), new Vec2d(10, 10), 0.5)).toMatchObject(new Vec2d(5, 5))
|
||||
expect(Vec2d.Lrp(new Vec2d(0, 0), new Vec2d(10, 10), 2)).toMatchObject(new Vec2d(20, 20))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Rescale', () => {
|
||||
it('Rescales a vector by a scalar', () => {
|
||||
expect(Vec2d.Rescale(new Vec2d(5, 0), 1)).toMatchObject(new Vec2d(1, 0))
|
||||
expect(Vec2d.Rescale(new Vec2d(5, 0), 2)).toMatchObject(new Vec2d(2, 0))
|
||||
expect(Vec2d.Rescale(new Vec2d(0.5, 0.25), 2)).toEqual(
|
||||
new Vec2d(1.7888543819998317, 0.8944271909999159)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.IsClockwise', () => {
|
||||
it('Gets whether point A and point B wind clockwise around point C.', () => {
|
||||
expect(Vec2d.Clockwise(new Vec2d(0, 0), new Vec2d(5, 5), new Vec2d(0, 5))).toEqual(true)
|
||||
expect(Vec2d.Clockwise(new Vec2d(5, 5), new Vec2d(0, 0), new Vec2d(0, 5))).toEqual(false)
|
||||
expect(Vec2d.Clockwise(new Vec2d(0, 10), new Vec2d(0, 0), new Vec2d(0, 5))).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.ToFixed', () => {
|
||||
it('Rounds a vector to the a given precision.', () => {
|
||||
expect(Vec2d.ToFixed(new Vec2d(1.2345, 5.678), 1)).toMatchObject(new Vec2d(1.2, 5.7))
|
||||
expect(Vec2d.ToFixed(new Vec2d(1.2345, 5.678), 2)).toMatchObject(new Vec2d(1.23, 5.68))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Snap', () => {
|
||||
it('Snaps a vector to the nearest increment provided.', () => {
|
||||
expect(Vec2d.Snap(new Vec2d(10.5, 28), 10)).toMatchObject(new Vec2d(10, 30))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.NearestPointOnLineThroughPoint', () => {
|
||||
it('Gets the distance from a point to a line that passes through a given point.', () => {
|
||||
expect(
|
||||
Vec2d.NearestPointOnLineThroughPoint(new Vec2d(0, 0), new Vec2d(0, 1), new Vec2d(5, 5))
|
||||
).toMatchObject(new Vec2d(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.DistanceToLineThroughPoint', () => {
|
||||
it('Gets the distance from a point to a line that passes through a given point.', () => {
|
||||
expect(
|
||||
Vec2d.DistanceToLineThroughPoint(new Vec2d(0, 0), new Vec2d(0, 1), new Vec2d(5, 5))
|
||||
).toEqual(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.NearestPointOnLineSegment', () => {
|
||||
it('Gets the distance from a point to a line segment.', () => {
|
||||
expect(
|
||||
Vec2d.NearestPointOnLineSegment(new Vec2d(0, 0), new Vec2d(0, 10), new Vec2d(5, 5))
|
||||
).toMatchObject(new Vec2d(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.DistanceToLineSegment', () => {
|
||||
it('Gets the distance from a point to a line segment.', () => {
|
||||
expect(Vec2d.DistanceToLineSegment(new Vec2d(0, 0), new Vec2d(0, 10), new Vec2d(5, 5))).toEqual(
|
||||
5
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Nudge', () => {
|
||||
it('Pushes a point towards another point by a given distance.', () => {
|
||||
expect(Vec2d.Nudge(new Vec2d(0, 0), new Vec2d(0, 10), 5)).toMatchObject(new Vec2d(0, 5))
|
||||
})
|
||||
})
|
||||
|
||||
// describe('Vec2d.NudgeAtVector', () => {
|
||||
// it('Pushes a point in a given direction vector by a given distance.', () => {
|
||||
// expect(Vec2d.NudgeAtVector(new Vec2d(0, 0), new Vec2d(0.5, 0.75), 10)).toEqual(
|
||||
// new Vec2d(5, 7.5)
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('Vec2d.NudgeAtAngle', () => {
|
||||
// it('Pushes a point in a given angle by a given distance.', () => {
|
||||
// expect(Vec2d.NudgeAtAngle(new Vec2d(0, 0), Math.PI / 8, 10)).toEqual(
|
||||
// new Vec2d(9.238795325112868, 3.826834323650898)
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('Vec2d.PointsBetween', () => {
|
||||
// it('Interpolates points between two points.', () => {
|
||||
// expect(Vec2d.PointsBetween(new Vec2d(0, 0), [100, 100], 10)).toMatchObject(new Vec2)(
|
||||
// new Vec2d(0, 0, 1),
|
||||
// new Vec2d(11.11111111111111, 11.11111111111111, 0.8888888888888888),
|
||||
// new Vec2d(22.22222222222222, 22.22222222222222, 0.7777777777777778),
|
||||
// new Vec2d(33.33333333333333, 33.33333333333333, 0.6666666666666667),
|
||||
// new Vec2d(44.44444444444444, 44.44444444444444, 0.5555555555555556),
|
||||
// new Vec2d(55.55555555555556, 55.55555555555556, 0.5555555555555556),
|
||||
// new Vec2d(66.66666666666666, 66.66666666666666, 0.6666666666666666),
|
||||
// new Vec2d(77.77777777777779, 77.77777777777779, 0.7777777777777778),
|
||||
// new Vec2d(88.88888888888889, 88.88888888888889, 0.8888888888888888),
|
||||
// new Vec2d(100, 100, 1),
|
||||
// ])
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('Vec2d.Slope', () => {
|
||||
it('Gets a slope from a vector.', () => {
|
||||
expect(Vec2d.Slope(new Vec2d(0, 0), new Vec2d(100, 100))).toEqual(1)
|
||||
expect(Vec2d.Slope(new Vec2d(0, 0), new Vec2d(50, 100))).toEqual(2)
|
||||
expect(Vec2d.Slope(new Vec2d(0, 0), new Vec2d(-50, 100))).toEqual(-2)
|
||||
expect(Vec2d.Slope(new Vec2d(123, 456), new Vec2d(789, 24))).toEqual(-0.6486486486486487)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.ToAngle', () => {
|
||||
it('Gets an angle from a vector.', () => {
|
||||
expect(Vec2d.ToAngle(new Vec2d(1, 0.5))).toEqual(0.4636476090008061)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Max', () => {
|
||||
it('Gets the minimum of the given vectors', () => {
|
||||
expect(Vec2d.Max(new Vec2d(4, 1), new Vec2d(3, 2))).toMatchObject(new Vec2d(4, 2))
|
||||
expect(Vec2d.Max(new Vec2d(3, 2), new Vec2d(4, 1))).toMatchObject(new Vec2d(4, 2))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.Min', () => {
|
||||
it('Gets the minimum of the given vectors', () => {
|
||||
expect(Vec2d.Min(new Vec2d(4, 1), new Vec2d(3, 2))).toMatchObject(new Vec2d(3, 1))
|
||||
expect(Vec2d.Min(new Vec2d(3, 2), new Vec2d(4, 1))).toMatchObject(new Vec2d(3, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Vec2d.snapToGrid', () => {
|
||||
it('snaps to the nearest given increment, mutating the original vector and returning it', () => {
|
||||
expect(new Vec2d(25, 29).snapToGrid(8)).toMatchObject(new Vec2d(24, 32))
|
||||
expect(new Vec2d(25, 29).snapToGrid(8)).toMatchObject(new Vec2d(24, 32))
|
||||
expect(new Vec2d(25, 29).snapToGrid(3)).toMatchObject(new Vec2d(24, 30))
|
||||
expect(new Vec2d(25, 29).snapToGrid(10)).toMatchObject(new Vec2d(30, 30))
|
||||
expect(new Vec2d(12, 49).snapToGrid(10)).toMatchObject(new Vec2d(10, 50))
|
||||
|
||||
expect(Vec2d.SnapToGrid(new Vec2d(25, 29))).toMatchObject(new Vec2d(24, 32))
|
||||
expect(Vec2d.SnapToGrid(new Vec2d(25, 29), 8)).toMatchObject(new Vec2d(24, 32))
|
||||
expect(Vec2d.SnapToGrid(new Vec2d(25, 29), 3)).toMatchObject(new Vec2d(24, 30))
|
||||
expect(Vec2d.SnapToGrid(new Vec2d(25, 29), 10)).toMatchObject(new Vec2d(30, 30))
|
||||
expect(Vec2d.SnapToGrid(new Vec2d(12, 49), 10)).toMatchObject(new Vec2d(10, 50))
|
||||
})
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { intersectLineSegmentCircle } from '../intersect'
|
||||
import { PI, PI2, shortAngleDist } from '../utils'
|
||||
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
||||
|
@ -6,10 +6,10 @@ import { getVerticesCountForLength } from './geometry-constants'
|
|||
|
||||
/** @public */
|
||||
export class Arc2d extends Geometry2d {
|
||||
_center: Vec2d
|
||||
_center: Vec
|
||||
radius: number
|
||||
start: Vec2d
|
||||
end: Vec2d
|
||||
start: Vec
|
||||
end: Vec
|
||||
|
||||
measure: number
|
||||
length: number
|
||||
|
@ -18,10 +18,10 @@ export class Arc2d extends Geometry2d {
|
|||
|
||||
constructor(
|
||||
config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {
|
||||
center: Vec2d
|
||||
center: Vec
|
||||
radius: number
|
||||
start: Vec2d
|
||||
end: Vec2d
|
||||
start: Vec
|
||||
end: Vec
|
||||
sweepFlag: number
|
||||
largeArcFlag: number
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ export class Arc2d extends Geometry2d {
|
|||
if (start.equals(end)) throw Error(`Arc must have different start and end points.`)
|
||||
|
||||
// ensure that the start and end are clockwise
|
||||
this.angleStart = Vec2d.Angle(center, start)
|
||||
this.angleEnd = Vec2d.Angle(center, end)
|
||||
this.angleStart = Vec.Angle(center, start)
|
||||
this.angleEnd = Vec.Angle(center, end)
|
||||
this.measure = getArcMeasure(this.angleStart, this.angleEnd, sweepFlag, largeArcFlag)
|
||||
this.length = this.measure * radius
|
||||
|
||||
|
@ -43,7 +43,7 @@ export class Arc2d extends Geometry2d {
|
|||
this.radius = radius
|
||||
}
|
||||
|
||||
nearestPoint(point: Vec2d): Vec2d {
|
||||
nearestPoint(point: Vec): Vec {
|
||||
const { _center, measure, radius, angleEnd, angleStart, start: A, end: B } = this
|
||||
const t = getPointInArcT(measure, angleStart, angleEnd, _center.angle(point))
|
||||
if (t <= 0) return A
|
||||
|
@ -53,7 +53,7 @@ export class Arc2d extends Geometry2d {
|
|||
const P = _center.clone().add(point.clone().sub(_center).uni().mul(radius))
|
||||
|
||||
let distance = Infinity
|
||||
let nearest: Vec2d | undefined
|
||||
let nearest: Vec | undefined
|
||||
for (const pt of [A, B, P]) {
|
||||
if (point.dist(pt) < distance) {
|
||||
nearest = pt
|
||||
|
@ -65,7 +65,7 @@ export class Arc2d extends Geometry2d {
|
|||
return nearest
|
||||
}
|
||||
|
||||
hitTestLineSegment(A: Vec2d, B: Vec2d, _zoom: number): boolean {
|
||||
hitTestLineSegment(A: Vec, B: Vec, _zoom: number): boolean {
|
||||
const { _center, radius, measure, angleStart, angleEnd } = this
|
||||
const intersection = intersectLineSegmentCircle(A, B, _center, radius)
|
||||
if (intersection === null) return false
|
||||
|
@ -76,14 +76,14 @@ export class Arc2d extends Geometry2d {
|
|||
})
|
||||
}
|
||||
|
||||
getVertices(): Vec2d[] {
|
||||
getVertices(): Vec[] {
|
||||
const { _center, measure, length, radius, angleStart } = this
|
||||
const vertices: Vec2d[] = []
|
||||
const vertices: Vec[] = []
|
||||
|
||||
for (let i = 0, n = getVerticesCountForLength(Math.abs(length)); i < n + 1; i++) {
|
||||
const t = (i / n) * measure
|
||||
const angle = angleStart + t
|
||||
vertices.push(_center.clone().add(new Vec2d(Math.cos(angle), Math.sin(angle)).mul(radius)))
|
||||
vertices.push(_center.clone().add(new Vec(Math.cos(angle), Math.sin(angle)).mul(radius)))
|
||||
}
|
||||
|
||||
return vertices
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { Circle2d } from './Circle2d'
|
||||
|
||||
describe('Circle2d.bounds', () => {
|
||||
|
@ -53,7 +53,7 @@ describe('Circle2d.isPointInBounds', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).isPointInBounds(new Vec2d(-2, -2))
|
||||
}).isPointInBounds(new Vec(-2, -2))
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
|
@ -62,7 +62,7 @@ describe('Circle2d.isPointInBounds', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).isPointInBounds(new Vec2d(5, 5))
|
||||
}).isPointInBounds(new Vec(5, 5))
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
|
@ -71,7 +71,7 @@ describe('Circle2d.isPointInBounds', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).isPointInBounds(new Vec2d(-2, -2), 2)
|
||||
}).isPointInBounds(new Vec(-2, -2), 2)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
@ -82,7 +82,7 @@ describe('Circle2d.getDistanceToPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).distanceToPoint(new Vec2d(10, -2))
|
||||
}).distanceToPoint(new Vec(10, -2))
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
|
@ -91,7 +91,7 @@ describe('Circle2d.getDistanceToPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).distanceToPoint(new Vec2d(10, 2))
|
||||
}).distanceToPoint(new Vec(10, 2))
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
|
@ -100,7 +100,7 @@ describe('Circle2d.getDistanceToPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: true,
|
||||
}).distanceToPoint(new Vec2d(10, 2))
|
||||
}).distanceToPoint(new Vec(10, 2))
|
||||
).toBe(-2)
|
||||
})
|
||||
})
|
||||
|
@ -111,7 +111,7 @@ describe('Circle2d.hitTestPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).hitTestPoint(new Vec2d(10, -2), 0)
|
||||
}).hitTestPoint(new Vec(10, -2), 0)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
|
@ -120,7 +120,7 @@ describe('Circle2d.hitTestPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).hitTestPoint(new Vec2d(10, 2), 0)
|
||||
}).hitTestPoint(new Vec(10, 2), 0)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
|
@ -129,7 +129,7 @@ describe('Circle2d.hitTestPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: true,
|
||||
}).hitTestPoint(new Vec2d(10, 2), 0)
|
||||
}).hitTestPoint(new Vec(10, 2), 0)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
|
@ -140,7 +140,7 @@ describe('Circle2d.hitTestPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).hitTestPoint(new Vec2d(10, -2), 2)
|
||||
}).hitTestPoint(new Vec(10, -2), 2)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
|
@ -149,7 +149,7 @@ describe('Circle2d.hitTestPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).hitTestPoint(new Vec2d(10, 2), 2)
|
||||
}).hitTestPoint(new Vec(10, 2), 2)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
|
@ -158,7 +158,7 @@ describe('Circle2d.hitTestPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: true,
|
||||
}).hitTestPoint(new Vec2d(10, 2), 2)
|
||||
}).hitTestPoint(new Vec(10, 2), 2)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
@ -169,22 +169,22 @@ describe('Circle2d.nearestPoint', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).nearestPoint(new Vec2d(10, -2))
|
||||
).toMatchObject(new Vec2d(10, 0))
|
||||
}).nearestPoint(new Vec(10, -2))
|
||||
).toMatchObject(new Vec(10, 0))
|
||||
|
||||
expect(
|
||||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).nearestPoint(new Vec2d(10, 2))
|
||||
).toMatchObject(new Vec2d(10, 0))
|
||||
}).nearestPoint(new Vec(10, 2))
|
||||
).toMatchObject(new Vec(10, 0))
|
||||
|
||||
expect(
|
||||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).nearestPoint(new Vec2d(10, 30))
|
||||
).toMatchObject(new Vec2d(10, 20))
|
||||
}).nearestPoint(new Vec(10, 30))
|
||||
).toMatchObject(new Vec(10, 20))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -194,14 +194,14 @@ describe('Circle2d.hitTestLineSegment', () => {
|
|||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).hitTestLineSegment(new Vec2d(0, -2), new Vec2d(20, -2), 1)
|
||||
}).hitTestLineSegment(new Vec(0, -2), new Vec(20, -2), 1)
|
||||
).toBe(false)
|
||||
|
||||
expect(
|
||||
new Circle2d({
|
||||
radius: 10,
|
||||
isFilled: false,
|
||||
}).hitTestLineSegment(new Vec2d(0, 2), new Vec2d(20, 2), 1)
|
||||
}).hitTestLineSegment(new Vec(0, 2), new Vec(20, 2), 1)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Box2d } from '../Box2d'
|
||||
import { Vec2d } from '../Vec2d'
|
||||
import { Box } from '../Box'
|
||||
import { Vec } from '../Vec'
|
||||
import { intersectLineSegmentCircle } from '../intersect'
|
||||
import { PI2 } from '../utils'
|
||||
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
||||
|
@ -7,7 +7,7 @@ import { getVerticesCountForLength } from './geometry-constants'
|
|||
|
||||
/** @public */
|
||||
export class Circle2d extends Geometry2d {
|
||||
_center: Vec2d
|
||||
_center: Vec
|
||||
radius: number
|
||||
x: number
|
||||
y: number
|
||||
|
@ -24,32 +24,32 @@ export class Circle2d extends Geometry2d {
|
|||
const { x = 0, y = 0, radius } = config
|
||||
this.x = x
|
||||
this.y = y
|
||||
this._center = new Vec2d(radius + x, radius + y)
|
||||
this._center = new Vec(radius + x, radius + y)
|
||||
this.radius = radius
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
return new Box2d(this.x, this.y, this.radius * 2, this.radius * 2)
|
||||
return new Box(this.x, this.y, this.radius * 2, this.radius * 2)
|
||||
}
|
||||
|
||||
getVertices(): Vec2d[] {
|
||||
getVertices(): Vec[] {
|
||||
const { _center, radius } = this
|
||||
const perimeter = PI2 * radius
|
||||
const vertices: Vec2d[] = []
|
||||
const vertices: Vec[] = []
|
||||
for (let i = 0, n = getVerticesCountForLength(perimeter); i < n; i++) {
|
||||
const angle = (i / n) * PI2
|
||||
vertices.push(_center.clone().add(Vec2d.FromAngle(angle).mul(radius)))
|
||||
vertices.push(_center.clone().add(Vec.FromAngle(angle).mul(radius)))
|
||||
}
|
||||
return vertices
|
||||
}
|
||||
|
||||
nearestPoint(point: Vec2d): Vec2d {
|
||||
nearestPoint(point: Vec): Vec {
|
||||
const { _center, radius } = this
|
||||
if (_center.equals(point)) return Vec2d.AddXY(_center, radius, 0)
|
||||
if (_center.equals(point)) return Vec.AddXY(_center, radius, 0)
|
||||
return _center.clone().add(point.clone().sub(_center).uni().mul(radius))
|
||||
}
|
||||
|
||||
hitTestLineSegment(A: Vec2d, B: Vec2d, _zoom: number): boolean {
|
||||
hitTestLineSegment(A: Vec, B: Vec, _zoom: number): boolean {
|
||||
const { _center, radius } = this
|
||||
return intersectLineSegmentCircle(A, B, _center, radius) !== null
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { Geometry2dOptions } from './Geometry2d'
|
||||
import { Polyline2d } from './Polyline2d'
|
||||
|
||||
/** @public */
|
||||
export class CubicBezier2d extends Polyline2d {
|
||||
a: Vec2d
|
||||
b: Vec2d
|
||||
c: Vec2d
|
||||
d: Vec2d
|
||||
a: Vec
|
||||
b: Vec
|
||||
c: Vec
|
||||
d: Vec
|
||||
|
||||
constructor(
|
||||
config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {
|
||||
start: Vec2d
|
||||
cp1: Vec2d
|
||||
cp2: Vec2d
|
||||
end: Vec2d
|
||||
start: Vec
|
||||
cp1: Vec
|
||||
cp2: Vec
|
||||
end: Vec
|
||||
}
|
||||
) {
|
||||
const { start: a, cp1: b, cp2: c, end: d } = config
|
||||
|
@ -26,13 +26,13 @@ export class CubicBezier2d extends Polyline2d {
|
|||
}
|
||||
|
||||
override getVertices() {
|
||||
const vertices = [] as Vec2d[]
|
||||
const vertices = [] as Vec[]
|
||||
const { a, b, c, d } = this
|
||||
// we'll always use ten vertices for each bezier curve
|
||||
for (let i = 0, n = 10; i <= n; i++) {
|
||||
const t = i / n
|
||||
vertices.push(
|
||||
new Vec2d(
|
||||
new Vec(
|
||||
(1 - t) * (1 - t) * (1 - t) * a.x +
|
||||
3 * ((1 - t) * (1 - t)) * t * b.x +
|
||||
3 * (1 - t) * (t * t) * c.x +
|
||||
|
@ -51,8 +51,8 @@ export class CubicBezier2d extends Polyline2d {
|
|||
return getAtT(this, 0.5)
|
||||
}
|
||||
|
||||
nearestPoint(A: Vec2d): Vec2d {
|
||||
let nearest: Vec2d | undefined
|
||||
nearestPoint(A: Vec): Vec {
|
||||
let nearest: Vec | undefined
|
||||
let dist = Infinity
|
||||
for (const edge of this.segments) {
|
||||
const p = edge.nearestPoint(A)
|
||||
|
@ -70,7 +70,7 @@ export class CubicBezier2d extends Polyline2d {
|
|||
|
||||
function getAtT(segment: CubicBezier2d, t: number) {
|
||||
const { a, b, c, d } = segment
|
||||
return new Vec2d(
|
||||
return new Vec(
|
||||
(1 - t) * (1 - t) * (1 - t) * a.x +
|
||||
3 * ((1 - t) * (1 - t)) * t * b.x +
|
||||
3 * (1 - t) * (t * t) * c.x +
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { CubicBezier2d } from './CubicBezier2d'
|
||||
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
||||
|
||||
/** @public */
|
||||
export class CubicSpline2d extends Geometry2d {
|
||||
points: Vec2d[]
|
||||
points: Vec[]
|
||||
|
||||
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { points: Vec2d[] }) {
|
||||
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { points: Vec[] }) {
|
||||
super({ ...config, isClosed: false, isFilled: false })
|
||||
const { points } = config
|
||||
|
||||
|
@ -32,13 +32,11 @@ export class CubicSpline2d extends Geometry2d {
|
|||
const p3 = i === last ? p2 : points[i + 2]
|
||||
const start = p1,
|
||||
cp1 =
|
||||
i === 0
|
||||
? p0
|
||||
: new Vec2d(p1.x + ((p2.x - p0.x) / 6) * k, p1.y + ((p2.y - p0.y) / 6) * k),
|
||||
i === 0 ? p0 : new Vec(p1.x + ((p2.x - p0.x) / 6) * k, p1.y + ((p2.y - p0.y) / 6) * k),
|
||||
cp2 =
|
||||
i === last
|
||||
? p2
|
||||
: new Vec2d(p2.x - ((p3.x - p1.x) / 6) * k, p2.y - ((p3.y - p1.y) / 6) * k),
|
||||
: new Vec(p2.x - ((p3.x - p1.x) / 6) * k, p2.y - ((p3.y - p1.y) / 6) * k),
|
||||
end = p2
|
||||
|
||||
this._segments.push(new CubicBezier2d({ start, cp1, cp2, end }))
|
||||
|
@ -61,13 +59,13 @@ export class CubicSpline2d extends Geometry2d {
|
|||
getVertices() {
|
||||
const vertices = this.segments.reduce((acc, segment) => {
|
||||
return acc.concat(segment.vertices)
|
||||
}, [] as Vec2d[])
|
||||
}, [] as Vec[])
|
||||
vertices.push(this.points[this.points.length - 1])
|
||||
return vertices
|
||||
}
|
||||
|
||||
nearestPoint(A: Vec2d): Vec2d {
|
||||
let nearest: Vec2d | undefined
|
||||
nearestPoint(A: Vec): Vec {
|
||||
let nearest: Vec | undefined
|
||||
let dist = Infinity
|
||||
for (const segment of this.segments) {
|
||||
const p = segment.nearestPoint(A)
|
||||
|
@ -82,7 +80,7 @@ export class CubicSpline2d extends Geometry2d {
|
|||
return nearest
|
||||
}
|
||||
|
||||
hitTestLineSegment(A: Vec2d, B: Vec2d, zoom: number): boolean {
|
||||
hitTestLineSegment(A: Vec, B: Vec, zoom: number): boolean {
|
||||
return this.segments.some((segment) => segment.hitTestLineSegment(A, B, zoom))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { linesIntersect } from '../intersect'
|
||||
import { Geometry2d } from './Geometry2d'
|
||||
|
||||
/** @public */
|
||||
export class Edge2d extends Geometry2d {
|
||||
start: Vec2d
|
||||
end: Vec2d
|
||||
d: Vec2d
|
||||
u: Vec2d
|
||||
start: Vec
|
||||
end: Vec
|
||||
d: Vec
|
||||
u: Vec
|
||||
ul: number
|
||||
|
||||
constructor(config: { start: Vec2d; end: Vec2d; isSnappable?: boolean }) {
|
||||
constructor(config: { start: Vec; end: Vec; isSnappable?: boolean }) {
|
||||
super({ ...config, isClosed: false, isFilled: false })
|
||||
const { start, end } = config
|
||||
|
||||
|
@ -32,28 +32,28 @@ export class Edge2d extends Geometry2d {
|
|||
return this._length
|
||||
}
|
||||
|
||||
midPoint(): Vec2d {
|
||||
midPoint(): Vec {
|
||||
return this.start.lrp(this.end, 0.5)
|
||||
}
|
||||
|
||||
override getVertices(): Vec2d[] {
|
||||
override getVertices(): Vec[] {
|
||||
return [this.start, this.end]
|
||||
}
|
||||
|
||||
override nearestPoint(point: Vec2d): Vec2d {
|
||||
override nearestPoint(point: Vec): Vec {
|
||||
const { start, end, u, ul: l } = this
|
||||
if (l === 0) return start // no length in the unit vector
|
||||
const k = Vec2d.Sub(point, start).dpr(u) / l
|
||||
const k = Vec.Sub(point, start).dpr(u) / l
|
||||
const cx = start.x + u.x * k
|
||||
if (cx < Math.min(start.x, end.x)) return start.x < end.x ? start : end
|
||||
if (cx > Math.max(start.x, end.x)) return start.x > end.x ? start : end
|
||||
const cy = start.y + u.y * k
|
||||
if (cy < Math.min(start.y, end.y)) return start.y < end.y ? start : end
|
||||
if (cy > Math.max(start.y, end.y)) return start.y > end.y ? start : end
|
||||
return new Vec2d(cx, cy)
|
||||
return new Vec(cx, cy)
|
||||
}
|
||||
|
||||
override hitTestLineSegment(A: Vec2d, B: Vec2d, _zoom: number): boolean {
|
||||
override hitTestLineSegment(A: Vec, B: Vec, _zoom: number): boolean {
|
||||
return linesIntersect(A, B, this.start, this.end)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Box2d } from '../Box2d'
|
||||
import { Vec2d } from '../Vec2d'
|
||||
import { Box } from '../Box'
|
||||
import { Vec } from '../Vec'
|
||||
import { PI, PI2 } from '../utils'
|
||||
import { Edge2d } from './Edge2d'
|
||||
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
||||
|
@ -63,7 +63,7 @@ export class Ellipse2d extends Geometry2d {
|
|||
const vertices = Array(len)
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
vertices[i] = new Vec2d(cx + cx * cos, cy + cy * sin)
|
||||
vertices[i] = new Vec(cx + cx * cos, cy + cy * sin)
|
||||
ts = b * cos + a * sin
|
||||
tc = a * cos - b * sin
|
||||
sin = ts
|
||||
|
@ -73,8 +73,8 @@ export class Ellipse2d extends Geometry2d {
|
|||
return vertices
|
||||
}
|
||||
|
||||
nearestPoint(A: Vec2d): Vec2d {
|
||||
let nearest: Vec2d | undefined
|
||||
nearestPoint(A: Vec): Vec {
|
||||
let nearest: Vec | undefined
|
||||
let dist = Infinity
|
||||
for (const edge of this.edges) {
|
||||
const p = edge.nearestPoint(A)
|
||||
|
@ -89,11 +89,11 @@ export class Ellipse2d extends Geometry2d {
|
|||
return nearest
|
||||
}
|
||||
|
||||
hitTestLineSegment(A: Vec2d, B: Vec2d, zoom: number): boolean {
|
||||
hitTestLineSegment(A: Vec, B: Vec, zoom: number): boolean {
|
||||
return this.edges.some((edge) => edge.hitTestLineSegment(A, B, zoom))
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
return new Box2d(0, 0, this.w, this.h)
|
||||
return new Box(0, 0, this.w, this.h)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Box2d } from '../Box2d'
|
||||
import { Vec2d } from '../Vec2d'
|
||||
import { Box } from '../Box'
|
||||
import { Vec } from '../Vec'
|
||||
import { pointInPolygon } from '../utils'
|
||||
|
||||
export interface Geometry2dOptions {
|
||||
|
@ -23,16 +23,16 @@ export abstract class Geometry2d {
|
|||
this.isLabel = opts.isLabel ?? false
|
||||
}
|
||||
|
||||
abstract getVertices(): Vec2d[]
|
||||
abstract getVertices(): Vec[]
|
||||
|
||||
abstract nearestPoint(point: Vec2d): Vec2d
|
||||
abstract nearestPoint(point: Vec): Vec
|
||||
|
||||
hitTestPoint(point: Vec2d, margin = 0, hitInside = false) {
|
||||
hitTestPoint(point: Vec, margin = 0, hitInside = false) {
|
||||
// We've removed the broad phase here; that should be done outside of the call
|
||||
return this.distanceToPoint(point, hitInside) <= margin
|
||||
}
|
||||
|
||||
distanceToPoint(point: Vec2d, hitInside = false) {
|
||||
distanceToPoint(point: Vec, hitInside = false) {
|
||||
const dist = point.dist(this.nearestPoint(point))
|
||||
|
||||
if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) {
|
||||
|
@ -41,22 +41,22 @@ export abstract class Geometry2d {
|
|||
return dist
|
||||
}
|
||||
|
||||
distanceToLineSegment(A: Vec2d, B: Vec2d) {
|
||||
distanceToLineSegment(A: Vec, B: Vec) {
|
||||
const point = this.nearestPointOnLineSegment(A, B)
|
||||
const dist = Vec2d.DistanceToLineSegment(A, B, point) // repeated, bleh
|
||||
const dist = Vec.DistanceToLineSegment(A, B, point) // repeated, bleh
|
||||
return this.isClosed && this.isFilled && pointInPolygon(point, this.vertices) ? -dist : dist
|
||||
}
|
||||
|
||||
hitTestLineSegment(A: Vec2d, B: Vec2d, distance = 0): boolean {
|
||||
hitTestLineSegment(A: Vec, B: Vec, distance = 0): boolean {
|
||||
return this.distanceToLineSegment(A, B) <= distance
|
||||
}
|
||||
|
||||
nearestPointOnLineSegment(A: Vec2d, B: Vec2d): Vec2d {
|
||||
nearestPointOnLineSegment(A: Vec, B: Vec): Vec {
|
||||
let distance = Infinity
|
||||
let nearest: Vec2d | undefined
|
||||
let nearest: Vec | undefined
|
||||
for (let i = 0; i < this.vertices.length; i++) {
|
||||
const point = this.vertices[i]
|
||||
const d = Vec2d.DistanceToLineSegment(A, B, point)
|
||||
const d = Vec.DistanceToLineSegment(A, B, point)
|
||||
if (d < distance) {
|
||||
distance = d
|
||||
nearest = point
|
||||
|
@ -66,7 +66,7 @@ export abstract class Geometry2d {
|
|||
return nearest
|
||||
}
|
||||
|
||||
isPointInBounds(point: Vec2d, margin = 0) {
|
||||
isPointInBounds(point: Vec, margin = 0) {
|
||||
const { bounds } = this
|
||||
return !(
|
||||
point.x < bounds.minX - margin ||
|
||||
|
@ -76,10 +76,10 @@ export abstract class Geometry2d {
|
|||
)
|
||||
}
|
||||
|
||||
_vertices: Vec2d[] | undefined
|
||||
_vertices: Vec[] | undefined
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get vertices(): Vec2d[] {
|
||||
get vertices(): Vec[] {
|
||||
if (!this._vertices) {
|
||||
this._vertices = this.getVertices()
|
||||
}
|
||||
|
@ -88,20 +88,20 @@ export abstract class Geometry2d {
|
|||
}
|
||||
|
||||
getBounds() {
|
||||
return Box2d.FromPoints(this.vertices)
|
||||
return Box.FromPoints(this.vertices)
|
||||
}
|
||||
|
||||
_bounds: Box2d | undefined
|
||||
_bounds: Box | undefined
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get bounds(): Box2d {
|
||||
get bounds(): Box {
|
||||
if (!this._bounds) {
|
||||
this._bounds = this.getBounds()
|
||||
}
|
||||
return this._bounds
|
||||
}
|
||||
|
||||
_snapPoints: Vec2d[] | undefined
|
||||
_snapPoints: Vec[] | undefined
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
get snapPoints() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Box2d } from '../Box2d'
|
||||
import { Vec2d } from '../Vec2d'
|
||||
import { Box } from '../Box'
|
||||
import { Vec } from '../Vec'
|
||||
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
||||
|
||||
/** @public */
|
||||
|
@ -19,13 +19,13 @@ export class Group2d extends Geometry2d {
|
|||
this.children = children
|
||||
}
|
||||
|
||||
override getVertices(): Vec2d[] {
|
||||
override getVertices(): Vec[] {
|
||||
return this.children.filter((c) => !c.isLabel).flatMap((c) => c.vertices)
|
||||
}
|
||||
|
||||
override nearestPoint(point: Vec2d): Vec2d {
|
||||
override nearestPoint(point: Vec): Vec {
|
||||
let d = Infinity
|
||||
let p: Vec2d | undefined
|
||||
let p: Vec | undefined
|
||||
|
||||
const { children } = this
|
||||
|
||||
|
@ -45,17 +45,17 @@ export class Group2d extends Geometry2d {
|
|||
return p
|
||||
}
|
||||
|
||||
override distanceToPoint(point: Vec2d, hitInside = false) {
|
||||
override distanceToPoint(point: Vec, hitInside = false) {
|
||||
return Math.min(...this.children.map((c, i) => c.distanceToPoint(point, hitInside || i > 0)))
|
||||
}
|
||||
|
||||
override hitTestPoint(point: Vec2d, margin: number, hitInside: boolean): boolean {
|
||||
override hitTestPoint(point: Vec, margin: number, hitInside: boolean): boolean {
|
||||
return !!this.children
|
||||
.filter((c) => !c.isLabel)
|
||||
.find((c) => c.hitTestPoint(point, margin, hitInside))
|
||||
}
|
||||
|
||||
override hitTestLineSegment(A: Vec2d, B: Vec2d, zoom: number): boolean {
|
||||
override hitTestLineSegment(A: Vec, B: Vec, zoom: number): boolean {
|
||||
return !!this.children.filter((c) => !c.isLabel).find((c) => c.hitTestLineSegment(A, B, zoom))
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ export class Group2d extends Geometry2d {
|
|||
path += child.toSimpleSvgPath()
|
||||
}
|
||||
|
||||
const corners = Box2d.FromPoints(this.vertices).corners
|
||||
const corners = Box.FromPoints(this.vertices).corners
|
||||
// draw just a few pixels around each corner, e.g. an L shape for the bottom left
|
||||
|
||||
for (let i = 0, n = corners.length; i < n; i++) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
||||
|
||||
/** @public */
|
||||
export class Point2d extends Geometry2d {
|
||||
point: Vec2d
|
||||
point: Vec
|
||||
|
||||
constructor(
|
||||
config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { margin: number; point: Vec2d }
|
||||
config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { margin: number; point: Vec }
|
||||
) {
|
||||
super({ ...config, isClosed: true, isFilled: true })
|
||||
const { point } = config
|
||||
|
@ -18,11 +18,11 @@ export class Point2d extends Geometry2d {
|
|||
return [this.point]
|
||||
}
|
||||
|
||||
nearestPoint(): Vec2d {
|
||||
nearestPoint(): Vec {
|
||||
return this.point
|
||||
}
|
||||
|
||||
hitTestLineSegment(A: Vec2d, B: Vec2d, margin: number): boolean {
|
||||
return Vec2d.DistanceToLineSegment(A, B, this.point) < margin
|
||||
hitTestLineSegment(A: Vec, B: Vec, margin: number): boolean {
|
||||
return Vec.DistanceToLineSegment(A, B, this.point) < margin
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { Geometry2dOptions } from './Geometry2d'
|
||||
import { Polyline2d } from './Polyline2d'
|
||||
|
||||
/** @public */
|
||||
export class Polygon2d extends Polyline2d {
|
||||
constructor(config: Omit<Geometry2dOptions, 'isClosed'> & { points: Vec2d[] }) {
|
||||
constructor(config: Omit<Geometry2dOptions, 'isClosed'> & { points: Vec[] }) {
|
||||
super({ ...config })
|
||||
this.isClosed = true
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { Vec } from '../Vec'
|
||||
import { Edge2d } from './Edge2d'
|
||||
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
||||
|
||||
/** @public */
|
||||
export class Polyline2d extends Geometry2d {
|
||||
points: Vec2d[]
|
||||
points: Vec[]
|
||||
|
||||
constructor(config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & { points: Vec2d[] }) {
|
||||
constructor(config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & { points: Vec[] }) {
|
||||
super({ isClosed: false, isFilled: false, ...config })
|
||||
const { points } = config
|
||||
this.points = points
|
||||
|
@ -47,12 +47,12 @@ export class Polyline2d extends Geometry2d {
|
|||
return this.points
|
||||
}
|
||||
|
||||
nearestPoint(A: Vec2d): Vec2d {
|
||||
nearestPoint(A: Vec): Vec {
|
||||
const { segments } = this
|
||||
let nearest = this.points[0]
|
||||
let dist = Infinity
|
||||
|
||||
let p: Vec2d // current point on segment
|
||||
let p: Vec // current point on segment
|
||||
let d: number // distance from A to p
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
p = segments[i].nearestPoint(A)
|
||||
|
@ -66,7 +66,7 @@ export class Polyline2d extends Geometry2d {
|
|||
return nearest
|
||||
}
|
||||
|
||||
hitTestLineSegment(A: Vec2d, B: Vec2d, zoom: number): boolean {
|
||||
hitTestLineSegment(A: Vec, B: Vec, zoom: number): boolean {
|
||||
return this.segments.some((edge) => edge.hitTestLineSegment(A, B, zoom))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Box2d } from '../Box2d'
|
||||
import { Vec2d } from '../Vec2d'
|
||||
import { Box } from '../Box'
|
||||
import { Vec } from '../Vec'
|
||||
import { Geometry2dOptions } from './Geometry2d'
|
||||
import { Polygon2d } from './Polygon2d'
|
||||
|
||||
|
@ -22,10 +22,10 @@ export class Rectangle2d extends Polygon2d {
|
|||
super({
|
||||
...config,
|
||||
points: [
|
||||
new Vec2d(x, y),
|
||||
new Vec2d(x + width, y),
|
||||
new Vec2d(x + width, y + height),
|
||||
new Vec2d(x, y + height),
|
||||
new Vec(x, y),
|
||||
new Vec(x + width, y),
|
||||
new Vec(x + width, y + height),
|
||||
new Vec(x, y + height),
|
||||
],
|
||||
})
|
||||
this.x = x
|
||||
|
@ -35,6 +35,6 @@ export class Rectangle2d extends Polygon2d {
|
|||
}
|
||||
|
||||
getBounds() {
|
||||
return new Box2d(this.x, this.y, this.w, this.h)
|
||||
return new Box(this.x, this.y, this.w, this.h)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Vec2d } from '../Vec2d'
|
||||
import { PI, TAU } from '../utils'
|
||||
import { Vec } from '../Vec'
|
||||
import { HALF_PI, PI } from '../utils'
|
||||
import { Ellipse2d } from './Ellipse2d'
|
||||
import { Geometry2dOptions } from './Geometry2d'
|
||||
|
||||
|
@ -19,21 +19,21 @@ export class Stadium2d extends Ellipse2d {
|
|||
const cy = h / 2
|
||||
|
||||
const len = 10
|
||||
const points: Vec2d[] = Array(len * 2 - 2)
|
||||
const points: Vec[] = Array(len * 2 - 2)
|
||||
|
||||
if (h > w) {
|
||||
for (let i = 0; i < len - 1; i++) {
|
||||
const t1 = -PI + (PI * i) / (len - 2)
|
||||
const t2 = (PI * i) / (len - 2)
|
||||
points[i] = new Vec2d(cx + cx * Math.cos(t1), cx + cx * Math.sin(t1))
|
||||
points[i + (len - 1)] = new Vec2d(cx + cx * Math.cos(t2), h - cx + cx * Math.sin(t2))
|
||||
points[i] = new Vec(cx + cx * Math.cos(t1), cx + cx * Math.sin(t1))
|
||||
points[i + (len - 1)] = new Vec(cx + cx * Math.cos(t2), h - cx + cx * Math.sin(t2))
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < len - 1; i++) {
|
||||
const t1 = -TAU + (PI * i) / (len - 2)
|
||||
const t2 = TAU + (PI * -i) / (len - 2)
|
||||
points[i] = new Vec2d(w - cy + cy * Math.cos(t1), h - cy + cy * Math.sin(t1))
|
||||
points[i + (len - 1)] = new Vec2d(cy - cy * Math.cos(t2), h - cy + cy * Math.sin(t2))
|
||||
const t1 = -HALF_PI + (PI * i) / (len - 2)
|
||||
const t2 = HALF_PI + (PI * -i) / (len - 2)
|
||||
points[i] = new Vec(w - cy + cy * Math.cos(t1), h - cy + cy * Math.sin(t1))
|
||||
points[i + (len - 1)] = new Vec(cy - cy * Math.cos(t2), h - cy + cy * Math.sin(t2))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const SPACING = 20
|
||||
export const MIN_COUNT = 8
|
||||
const SPACING = 20
|
||||
const MIN_COUNT = 8
|
||||
|
||||
export function getVerticesCountForLength(length: number, spacing = SPACING) {
|
||||
return Math.max(MIN_COUNT, Math.ceil(length / spacing))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Box2d } from './Box2d'
|
||||
import { Box } from './Box'
|
||||
import { pointInPolygon } from './utils'
|
||||
import { Vec2d, VecLike } from './Vec2d'
|
||||
import { Vec, VecLike } from './Vec'
|
||||
|
||||
// need even more intersections? See https://gist.github.com/steveruizok/35c02d526c707003a5c79761bfb89a52
|
||||
|
||||
|
@ -37,7 +37,7 @@ export function intersectLineSegmentLineSegment(
|
|||
const ua = ua_t / u_b
|
||||
const ub = ub_t / u_b
|
||||
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
|
||||
return Vec2d.AddXY(a1, ua * AVx, ua * AVy)
|
||||
return Vec.AddXY(a1, ua * AVx, ua * AVy)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,8 +76,8 @@ export function intersectLineSegmentCircle(a1: VecLike, a2: VecLike, c: VecLike,
|
|||
|
||||
const result: VecLike[] = []
|
||||
|
||||
if (0 <= u1 && u1 <= 1) result.push(Vec2d.Lrp(a1, a2, u1))
|
||||
if (0 <= u2 && u2 <= 1) result.push(Vec2d.Lrp(a1, a2, u2))
|
||||
if (0 <= u1 && u1 <= 1) result.push(Vec.Lrp(a1, a2, u1))
|
||||
if (0 <= u2 && u2 <= 1) result.push(Vec.Lrp(a1, a2, u2))
|
||||
|
||||
if (result.length === 0) return null // no intersection
|
||||
|
||||
|
@ -151,8 +151,8 @@ export function intersectCircleCircle(c1: VecLike, r1: number, c2: VecLike, r2:
|
|||
dx /= d
|
||||
dy /= d
|
||||
return [
|
||||
new Vec2d(c1.x + dx * x - dy * y, c1.y + dy * x + dx * y),
|
||||
new Vec2d(c1.x + dx * x + dy * y, c1.y + dy * x - dx * y),
|
||||
new Vec(c1.x + dx * x - dy * y, c1.y + dy * x + dx * y),
|
||||
new Vec(c1.x + dx * x + dy * y, c1.y + dy * x - dx * y),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -209,7 +209,7 @@ export function intersectCirclePolyline(c: VecLike, r: number, points: VecLike[]
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export function intersectPolygonBounds(points: VecLike[], bounds: Box2d) {
|
||||
export function intersectPolygonBounds(points: VecLike[], bounds: Box) {
|
||||
const result: VecLike[] = []
|
||||
let segmentIntersection: VecLike[] | null
|
||||
|
||||
|
@ -285,8 +285,8 @@ export function intersectPolygonPolygon(
|
|||
}
|
||||
|
||||
function orderClockwise(points: VecLike[]): VecLike[] {
|
||||
const C = Vec2d.Average(points)
|
||||
return points.sort((A, B) => Vec2d.Angle(C, A) - Vec2d.Angle(C, B))
|
||||
const C = Vec.Average(points)
|
||||
return points.sort((A, B) => Vec.Angle(C, A) - Vec.Angle(C, B))
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Box2d } from './Box2d'
|
||||
import { Vec2d, VecLike } from './Vec2d'
|
||||
import { Vec, VecLike } from './Vec'
|
||||
|
||||
/** @public */
|
||||
export function precise(A: VecLike) {
|
||||
|
@ -14,12 +13,10 @@ export function average(A: VecLike, B: VecLike) {
|
|||
/** @public */
|
||||
export const PI = Math.PI
|
||||
/** @public */
|
||||
export const TAU = PI / 2
|
||||
export const HALF_PI = PI / 2
|
||||
/** @public */
|
||||
export const PI2 = PI * 2
|
||||
/** @public */
|
||||
export const EPSILON = Math.PI / 180
|
||||
/** @public */
|
||||
export const SIN = Math.sin
|
||||
|
||||
/**
|
||||
|
@ -148,52 +145,6 @@ export function shortAngleDist(a0: number, a1: number): number {
|
|||
return ((2 * da) % PI2) - da
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the long angle distance between two angles.
|
||||
*
|
||||
* @param a0 - The first angle.
|
||||
* @param a1 - The second angle.
|
||||
* @public
|
||||
*/
|
||||
export function longAngleDist(a0: number, a1: number): number {
|
||||
return PI2 - shortAngleDist(a0, a1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate an angle between two angles.
|
||||
*
|
||||
* @param a0 - The first angle.
|
||||
* @param a1 - The second angle.
|
||||
* @param t - The interpolation value.
|
||||
* @public
|
||||
*/
|
||||
export function lerpAngles(a0: number, a1: number, t: number): number {
|
||||
return a0 + shortAngleDist(a0, a1) * t
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the short distance between two angles.
|
||||
*
|
||||
* @param a0 - The first angle.
|
||||
* @param a1 - The second angle.
|
||||
* @public
|
||||
*/
|
||||
export function angleDelta(a0: number, a1: number): number {
|
||||
return shortAngleDist(a0, a1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "sweep" or short distance between two points on a circle's perimeter.
|
||||
*
|
||||
* @param C - The center of the circle.
|
||||
* @param A - The first point.
|
||||
* @param B - The second point.
|
||||
* @public
|
||||
*/
|
||||
export function getSweep(C: VecLike, A: VecLike, B: VecLike): number {
|
||||
return angleDelta(Vec2d.Angle(C, A), Vec2d.Angle(C, B))
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp radians within 0 and 2PI
|
||||
*
|
||||
|
@ -231,38 +182,6 @@ export function areAnglesCompatible(a: number, b: number) {
|
|||
return a === b || approximately((a % (Math.PI / 2)) - (b % (Math.PI / 2)), 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Is angle c between angles a and b?
|
||||
*
|
||||
* @param a - The first angle.
|
||||
* @param b - The second angle.
|
||||
* @param c - The third angle.
|
||||
* @public
|
||||
*/
|
||||
export function isAngleBetween(a: number, b: number, c: number): boolean {
|
||||
// Normalize the angles to ensure they're in the same domain
|
||||
a = canonicalizeRotation(a)
|
||||
b = canonicalizeRotation(b)
|
||||
c = canonicalizeRotation(c)
|
||||
|
||||
// Compute vectors corresponding to angles a and b
|
||||
const ax = Math.cos(a)
|
||||
const ay = Math.sin(a)
|
||||
const bx = Math.cos(b)
|
||||
const by = Math.sin(b)
|
||||
|
||||
// Compute the vector corresponding to angle c
|
||||
const cx = Math.cos(c)
|
||||
const cy = Math.sin(c)
|
||||
|
||||
// Calculate dot products
|
||||
const dotAc = ax * cx + ay * cy
|
||||
const dotBc = bx * cx + by * cy
|
||||
|
||||
// If angle c is between a and b, both dot products should be >= 0
|
||||
return dotAc >= 0 && dotBc >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert degrees to radians.
|
||||
*
|
||||
|
@ -283,20 +202,6 @@ export function radiansToDegrees(r: number): number {
|
|||
return (r * 180) / PI
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of an arc between two points on a circle's perimeter.
|
||||
*
|
||||
* @param C - The circle's center as [x, y].
|
||||
* @param r - The circle's radius.
|
||||
* @param A - The first point.
|
||||
* @param B - The second point.
|
||||
* @public
|
||||
*/
|
||||
export function getArcLength(C: VecLike, r: number, A: VecLike, B: VecLike): number {
|
||||
const sweep = getSweep(C, A, B)
|
||||
return r * PI2 * (sweep / PI2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a point on the perimeter of a circle.
|
||||
*
|
||||
|
@ -307,13 +212,13 @@ export function getArcLength(C: VecLike, r: number, A: VecLike, B: VecLike): num
|
|||
* @public
|
||||
*/
|
||||
export function getPointOnCircle(cx: number, cy: number, r: number, a: number) {
|
||||
return new Vec2d(cx + r * Math.cos(a), cy + r * Math.sin(a))
|
||||
return new Vec(cx + r * Math.cos(a), cy + r * Math.sin(a))
|
||||
}
|
||||
/** @public */
|
||||
export function getPolygonVertices(width: number, height: number, sides: number) {
|
||||
const cx = width / 2
|
||||
const cy = height / 2
|
||||
const pointsOnPerimeter: Vec2d[] = []
|
||||
const pointsOnPerimeter: Vec[] = []
|
||||
|
||||
let minX = Infinity
|
||||
let maxX = -Infinity
|
||||
|
@ -321,14 +226,14 @@ export function getPolygonVertices(width: number, height: number, sides: number)
|
|||
let maxY = -Infinity
|
||||
for (let i = 0; i < sides; i++) {
|
||||
const step = PI2 / sides
|
||||
const t = -TAU + i * step
|
||||
const t = -HALF_PI + i * step
|
||||
const x = cx + cx * Math.cos(t)
|
||||
const y = cy + cy * Math.sin(t)
|
||||
if (x < minX) minX = x
|
||||
if (y < minY) minY = y
|
||||
if (x > maxX) maxX = x
|
||||
if (y > maxY) maxY = y
|
||||
pointsOnPerimeter.push(new Vec2d(x, y))
|
||||
pointsOnPerimeter.push(new Vec(x, y))
|
||||
}
|
||||
|
||||
// Bounds of calculated points
|
||||
|
@ -387,91 +292,11 @@ export function rangeIntersection(
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width/height of a star given its input bounds.
|
||||
*
|
||||
* @param sides - Number of sides
|
||||
* @param w - T target width
|
||||
* @param h - Target height
|
||||
* @returns Box2d
|
||||
* @public
|
||||
*/
|
||||
export const getStarBounds = (sides: number, w: number, h: number): Box2d => {
|
||||
const step = PI2 / sides / 2
|
||||
const rightMostIndex = Math.floor(sides / 4) * 2
|
||||
const leftMostIndex = sides * 2 - rightMostIndex
|
||||
const topMostIndex = 0
|
||||
const bottomMostIndex = Math.floor(sides / 2) * 2
|
||||
const maxX = (Math.cos(-TAU + rightMostIndex * step) * w) / 2
|
||||
const minX = (Math.cos(-TAU + leftMostIndex * step) * w) / 2
|
||||
const minY = (Math.sin(-TAU + topMostIndex * step) * h) / 2
|
||||
const maxY = (Math.sin(-TAU + bottomMostIndex * step) * h) / 2
|
||||
return new Box2d(0, 0, maxX - minX, maxY - minY)
|
||||
}
|
||||
|
||||
/** Helper for point in polygon */
|
||||
function cross(x: VecLike, y: VecLike, z: VecLike): number {
|
||||
return (y.x - x.x) * (z.y - x.y) - (z.x - x.x) * (y.y - x.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils for working with points.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
/**
|
||||
* Get whether a point is inside of a circle.
|
||||
*
|
||||
* @param A - The point to check.
|
||||
* @param C - The circle's center point as [x, y].
|
||||
* @param r - The circle's radius.
|
||||
* @returns Boolean
|
||||
* @public
|
||||
*/
|
||||
export function pointInCircle(A: VecLike, C: VecLike, r: number): boolean {
|
||||
return Vec2d.Dist(A, C) <= r
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a point is inside of an ellipse.
|
||||
*
|
||||
* @param point - The point to check.
|
||||
* @param center - The ellipse's center point as [x, y].
|
||||
* @param rx - The ellipse's x radius.
|
||||
* @param ry - The ellipse's y radius.
|
||||
* @param rotation - The ellipse's rotation.
|
||||
* @returns Boolean
|
||||
* @public
|
||||
*/
|
||||
export function pointInEllipse(
|
||||
A: VecLike,
|
||||
C: VecLike,
|
||||
rx: number,
|
||||
ry: number,
|
||||
rotation = 0
|
||||
): boolean {
|
||||
rotation = rotation || 0
|
||||
const cos = Math.cos(rotation)
|
||||
const sin = Math.sin(rotation)
|
||||
const delta = Vec2d.Sub(A, C)
|
||||
const tdx = cos * delta.x + sin * delta.y
|
||||
const tdy = sin * delta.x - cos * delta.y
|
||||
|
||||
return (tdx * tdx) / (rx * rx) + (tdy * tdy) / (ry * ry) <= 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a point is inside of a rectangle.
|
||||
*
|
||||
* @param A - The point to check.
|
||||
* @param point - The rectangle's top left point as [x, y].
|
||||
* @param size - The rectangle's size as [width, height].
|
||||
* @public
|
||||
*/
|
||||
export function pointInRect(A: VecLike, point: VecLike, size: VecLike): boolean {
|
||||
return !(A.x < point.x || A.x > point.x + size.x || A.y < point.y || A.y > point.y + size.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a point is inside of a polygon.
|
||||
*
|
||||
|
@ -502,245 +327,6 @@ export function pointInPolygon(A: VecLike, points: VecLike[]): boolean {
|
|||
return windingNumber !== 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a point is inside of a bounds.
|
||||
*
|
||||
* @param A - The point to check.
|
||||
* @param b - The bounds to check.
|
||||
* @returns Boolean
|
||||
* @public
|
||||
*/
|
||||
export function pointInBounds(A: VecLike, b: Box2d): boolean {
|
||||
return !(A.x < b.minX || A.x > b.maxX || A.y < b.minY || A.y > b.maxY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hit test a point and a polyline using a minimum distance.
|
||||
*
|
||||
* @param A - The point to check.
|
||||
* @param points - The points that make up the polyline.
|
||||
* @param distance - The mininum distance that qualifies a hit.
|
||||
* @returns Boolean
|
||||
* @public
|
||||
*/
|
||||
export function pointInPolyline(A: VecLike, points: VecLike[], distance = 3): boolean {
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
if (Vec2d.DistanceToLineSegment(points[i - 1], points[i], A) < distance) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a point is within a certain distance from a polyline.
|
||||
*
|
||||
* @param A - The point to check.
|
||||
* @param points - The points that make up the polyline.
|
||||
* @param distance - The mininum distance that qualifies a hit.
|
||||
* @public
|
||||
*/
|
||||
export function pointNearToPolyline(A: VecLike, points: VecLike[], distance = 8) {
|
||||
const len = points.length
|
||||
for (let i = 1; i < len; i++) {
|
||||
const p1 = points[i - 1]
|
||||
const p2 = points[i]
|
||||
const d = Vec2d.DistanceToLineSegment(p1, p2, A)
|
||||
if (d < distance) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a point is within a certain distance from a line segment.
|
||||
*
|
||||
* @param A - The point to check.
|
||||
* @param p1 - The polyline's first point.
|
||||
* @param p2 - The polyline's second point.
|
||||
* @param distance - The mininum distance that qualifies a hit.
|
||||
* @public
|
||||
*/
|
||||
export function pointNearToLineSegment(A: VecLike, p1: VecLike, p2: VecLike, distance = 8) {
|
||||
const d = Vec2d.DistanceToLineSegment(p1, p2, A)
|
||||
if (d < distance) return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify a line (using Ramer-Douglas-Peucker algorithm).
|
||||
*
|
||||
* @param points - An array of points as [x, y, ...][]
|
||||
* @param tolerance - The minimum line distance (also called epsilon).
|
||||
* @returns Simplified array as [x, y, ...][]
|
||||
* @public
|
||||
*/
|
||||
export function simplify(points: VecLike[], tolerance = 1): VecLike[] {
|
||||
const len = points.length
|
||||
const a = points[0]
|
||||
const b = points[len - 1]
|
||||
const { x: x1, y: y1 } = a
|
||||
const { x: x2, y: y2 } = b
|
||||
if (len > 2) {
|
||||
let distance = 0
|
||||
let index = 0
|
||||
const max = new Vec2d(y2 - y1, x2 - x1).len2()
|
||||
for (let i = 1; i < len - 1; i++) {
|
||||
const { x: x0, y: y0 } = points[i]
|
||||
const d = Math.pow(x0 * (y2 - y1) + x1 * (y0 - y2) + x2 * (y1 - y0), 2) / max
|
||||
if (distance > d) continue
|
||||
distance = d
|
||||
index = i
|
||||
}
|
||||
if (distance > tolerance) {
|
||||
const l0 = simplify(points.slice(0, index + 1), tolerance)
|
||||
const l1 = simplify(points.slice(index + 1), tolerance)
|
||||
return l0.concat(l1.slice(1))
|
||||
}
|
||||
}
|
||||
return [a, b]
|
||||
}
|
||||
|
||||
function _getSqSegDist(p: VecLike, p1: VecLike, p2: VecLike) {
|
||||
let x = p1.x
|
||||
let y = p1.y
|
||||
let dx = p2.x - x
|
||||
let dy = p2.y - y
|
||||
if (dx !== 0 || dy !== 0) {
|
||||
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy)
|
||||
if (t > 1) {
|
||||
x = p2.x
|
||||
y = p2.y
|
||||
} else if (t > 0) {
|
||||
x += dx * t
|
||||
y += dy * t
|
||||
}
|
||||
}
|
||||
dx = p.x - x
|
||||
dy = p.y - y
|
||||
return dx * dx + dy * dy
|
||||
}
|
||||
|
||||
function _simplifyStep(
|
||||
points: VecLike[],
|
||||
first: number,
|
||||
last: number,
|
||||
sqTolerance: number,
|
||||
result: VecLike[]
|
||||
) {
|
||||
let maxSqDist = sqTolerance
|
||||
let index = -1
|
||||
for (let i = first + 1; i < last; i++) {
|
||||
const sqDist = _getSqSegDist(points[i], points[first], points[last])
|
||||
if (sqDist > maxSqDist) {
|
||||
index = i
|
||||
maxSqDist = sqDist
|
||||
}
|
||||
}
|
||||
if (index > -1 && maxSqDist > sqTolerance) {
|
||||
if (index - first > 1) _simplifyStep(points, first, index, sqTolerance, result)
|
||||
result.push(points[index])
|
||||
if (last - index > 1) _simplifyStep(points, index, last, sqTolerance, result)
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function simplify2(points: VecLike[], tolerance = 1) {
|
||||
if (points.length <= 2) return points
|
||||
const sqTolerance = tolerance * tolerance
|
||||
// Radial distance
|
||||
let A = points[0]
|
||||
let B = points[1]
|
||||
const newPoints = [A]
|
||||
for (let i = 1, len = points.length; i < len; i++) {
|
||||
B = points[i]
|
||||
if ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y) > sqTolerance) {
|
||||
newPoints.push(B)
|
||||
A = B
|
||||
}
|
||||
}
|
||||
if (A !== B) newPoints.push(B)
|
||||
// Ramer-Douglas-Peucker
|
||||
const last = newPoints.length - 1
|
||||
const result = [newPoints[0]]
|
||||
_simplifyStep(newPoints, 0, last, sqTolerance, result)
|
||||
result.push(newPoints[last], points[points.length - 1])
|
||||
return result
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getMinX(pts: VecLike[]) {
|
||||
let top = pts[0]
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
if (pts[i].x < top.x) {
|
||||
top = pts[i]
|
||||
}
|
||||
}
|
||||
return top.x
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getMinY(pts: VecLike[]) {
|
||||
let top = pts[0]
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
if (pts[i].y < top.y) {
|
||||
top = pts[i]
|
||||
}
|
||||
}
|
||||
return top.y
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getMaxX(pts: VecLike[]) {
|
||||
let top = pts[0]
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
if (pts[i].x > top.x) {
|
||||
top = pts[i]
|
||||
}
|
||||
}
|
||||
return top.x
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getMaxY(pts: VecLike[]) {
|
||||
let top = pts[0]
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
if (pts[i].y > top.y) {
|
||||
top = pts[i]
|
||||
}
|
||||
}
|
||||
return top.y
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getMidX(pts: VecLike[]) {
|
||||
const a = getMinX(pts)
|
||||
const b = getMaxX(pts)
|
||||
|
||||
return a + (b - a) / 2
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getMidY(pts: VecLike[]) {
|
||||
const a = getMinY(pts)
|
||||
const b = getMaxY(pts)
|
||||
|
||||
return a + (b - a) / 2
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getWidth(pts: VecLike[]) {
|
||||
const a = getMinX(pts)
|
||||
const b = getMaxX(pts)
|
||||
return b - a
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getHeight(pts: VecLike[]) {
|
||||
const a = getMinY(pts)
|
||||
const b = getMaxY(pts)
|
||||
return b - a
|
||||
}
|
||||
|
||||
/**
|
||||
* The DOM likes values to be fixed to 3 decimal places
|
||||
*
|
||||
|
|
|
@ -202,8 +202,7 @@ interface Defaults<T> {
|
|||
all: T
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface DebugFlagDef<T> {
|
||||
interface DebugFlagDef<T> {
|
||||
name: string
|
||||
defaults: Defaults<T>
|
||||
shouldStoreForSession: boolean
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Editor } from '../editor/Editor'
|
|||
* @param dimension - The component dimension on the axis.
|
||||
* @internal
|
||||
*/
|
||||
export function getEdgeProximityFactor(position: number, dimension: number) {
|
||||
function getEdgeProximityFactor(position: number, dimension: number) {
|
||||
if (position < 0) {
|
||||
return 1
|
||||
} else if (position > dimension) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { VecLike } from '../primitives/Vec2d'
|
||||
import { VecLike } from '../primitives/Vec'
|
||||
import { average, precise } from '../primitives/utils'
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,86 +1,6 @@
|
|||
import {
|
||||
decrementInteger,
|
||||
generateKeyBetween,
|
||||
generateNKeysBetween,
|
||||
incrementInteger,
|
||||
midpoint,
|
||||
} from './dgreensp'
|
||||
import { generateNKeysBetween } from './dgreensp'
|
||||
|
||||
describe('midpoint', () => {
|
||||
it('passes tests', () => {
|
||||
expect(midpoint('', undefined)).toBe('V')
|
||||
expect(midpoint('V', undefined)).toBe('l')
|
||||
expect(midpoint('l', undefined)).toBe('t')
|
||||
expect(midpoint('t', undefined)).toBe('x')
|
||||
expect(midpoint('x', undefined)).toBe('z')
|
||||
expect(midpoint('z', undefined)).toBe('zV')
|
||||
expect(midpoint('zV', undefined)).toBe('zl')
|
||||
expect(midpoint('zl', undefined)).toBe('zt')
|
||||
expect(midpoint('zt', undefined)).toBe('zx')
|
||||
expect(midpoint('zx', undefined)).toBe('zz')
|
||||
expect(midpoint('zz', undefined)).toBe('zzV')
|
||||
expect(midpoint('1', '2')).toBe('1V')
|
||||
expect(() => midpoint('2', '1')).toThrowError()
|
||||
expect(() => midpoint('', '')).toThrowError()
|
||||
expect(() => midpoint('0', '1')).toThrowError()
|
||||
expect(() => midpoint('1', '10')).toThrowError()
|
||||
expect(() => midpoint('11', '1')).toThrowError()
|
||||
expect(midpoint('001', '001002')).toBe('001001')
|
||||
expect(midpoint('001', '001001')).toBe('001000V')
|
||||
expect(midpoint('', 'V')).toBe('G')
|
||||
expect(midpoint('', 'G')).toBe('8')
|
||||
expect(midpoint('', '8')).toBe('4')
|
||||
expect(midpoint('', '4')).toBe('2')
|
||||
expect(midpoint('', '2')).toBe('1')
|
||||
expect(midpoint('', '1')).toBe('0V')
|
||||
expect(midpoint('0V', '1')).toBe('0l')
|
||||
expect(midpoint('', '0G')).toBe('08')
|
||||
expect(midpoint('', '08')).toBe('04')
|
||||
expect(midpoint('', '02')).toBe('01')
|
||||
expect(midpoint('', '01')).toBe('00V')
|
||||
expect(midpoint('4zz', '5')).toBe('4zzV')
|
||||
})
|
||||
})
|
||||
|
||||
describe('decrement integer', () => {
|
||||
it('passes tests', () => {
|
||||
expect(decrementInteger('a1')).toBe('a0')
|
||||
expect(decrementInteger('a2')).toBe('a1')
|
||||
expect(decrementInteger('b00')).toBe('az')
|
||||
expect(decrementInteger('b10')).toBe('b0z')
|
||||
expect(decrementInteger('b20')).toBe('b1z')
|
||||
expect(decrementInteger('c000')).toBe('bzz')
|
||||
expect(decrementInteger('Zz')).toBe('Zy')
|
||||
expect(decrementInteger('a0')).toBe('Zz')
|
||||
expect(decrementInteger('Yzz')).toBe('Yzy')
|
||||
expect(decrementInteger('Z0')).toBe('Yzz')
|
||||
expect(decrementInteger('Xz00')).toBe('Xyzz')
|
||||
expect(decrementInteger('Xz01')).toBe('Xz00')
|
||||
expect(decrementInteger('Y00')).toBe('Xzzz')
|
||||
expect(decrementInteger('dAC00')).toBe('dABzz')
|
||||
expect(decrementInteger('A00000000000000000000000000')).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe('increment integer', () => {
|
||||
it('produces the right integer string', () => {
|
||||
expect(incrementInteger('a0')).toBe('a1')
|
||||
expect(incrementInteger('a1')).toBe('a2')
|
||||
expect(incrementInteger('az')).toBe('b00')
|
||||
expect(incrementInteger('b0z')).toBe('b10')
|
||||
expect(incrementInteger('b1z')).toBe('b20')
|
||||
expect(incrementInteger('bzz')).toBe('c000')
|
||||
expect(incrementInteger('Zy')).toBe('Zz')
|
||||
expect(incrementInteger('Zz')).toBe('a0')
|
||||
expect(incrementInteger('Yzy')).toBe('Yzz')
|
||||
expect(incrementInteger('Yzz')).toBe('Z0')
|
||||
expect(incrementInteger('Xyzz')).toBe('Xz00')
|
||||
expect(incrementInteger('Xz00')).toBe('Xz01')
|
||||
expect(incrementInteger('Xzzz')).toBe('Y00')
|
||||
expect(incrementInteger('dABzz')).toBe('dAC00')
|
||||
expect(incrementInteger('zzzzzzzzzzzzzzzzzzzzzzzzzzz')).toBe(undefined)
|
||||
})
|
||||
})
|
||||
const generateKeyBetween = (a?: string, b?: string) => generateNKeysBetween(a, b, 1)[0]
|
||||
|
||||
describe('get order between', () => {
|
||||
it('passes tests', () => {
|
||||
|
|
|
@ -10,7 +10,7 @@ const SMALLEST_INTEGER = 'A00000000000000000000000000'
|
|||
*
|
||||
* @param head - The integer to use.
|
||||
*/
|
||||
export function getIntegerLength(head: string): number {
|
||||
function getIntegerLength(head: string): number {
|
||||
if (head >= 'a' && head <= 'z') {
|
||||
return head.charCodeAt(0) - 'a'.charCodeAt(0) + 2
|
||||
} else if (head >= 'A' && head <= 'Z') {
|
||||
|
@ -25,13 +25,13 @@ export function getIntegerLength(head: string): number {
|
|||
*
|
||||
* @param int - The integer to use.
|
||||
*/
|
||||
export function validateInteger(int: string): asserts int is string {
|
||||
function validateInteger(int: string): asserts int is string {
|
||||
if (int.length !== getIntegerLength(int.charAt(0))) {
|
||||
throw new Error('invalid integer part of index key: ' + int)
|
||||
}
|
||||
}
|
||||
|
||||
export function isNotUndefined(n: string | undefined): asserts n is string {
|
||||
function isNotUndefined(n: string | undefined): asserts n is string {
|
||||
if (n === undefined) throw Error('n is undefined')
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,10 @@ export function isNotUndefined(n: string | undefined): asserts n is string {
|
|||
* Increment an integer.
|
||||
*
|
||||
* @param x - The integer to increment
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function incrementInteger(x: string): string | undefined {
|
||||
function incrementInteger(x: string): string | undefined {
|
||||
validateInteger(x)
|
||||
const [head, ...digs] = x.split('')
|
||||
let carry = true
|
||||
|
@ -72,8 +74,10 @@ export function incrementInteger(x: string): string | undefined {
|
|||
* Decrement an integer.
|
||||
*
|
||||
* @param x - The integer to decrement
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function decrementInteger(x: string): string | undefined {
|
||||
function decrementInteger(x: string): string | undefined {
|
||||
validateInteger(x)
|
||||
const [head, ...digs] = x.split('')
|
||||
let borrow = true
|
||||
|
@ -106,8 +110,10 @@ export function decrementInteger(x: string): string | undefined {
|
|||
*
|
||||
* @param a - The start index.
|
||||
* @param b - The end index.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function midpoint(a: string, b: string | undefined): string {
|
||||
function midpoint(a: string, b: string | undefined): string {
|
||||
if (b !== undefined && a >= b) {
|
||||
throw new Error(a + ' >= ' + b)
|
||||
}
|
||||
|
@ -142,7 +148,7 @@ export function midpoint(a: string, b: string | undefined): string {
|
|||
*
|
||||
* @param index - The index to use.
|
||||
*/
|
||||
export function getIntegerPart(index: string): string {
|
||||
function getIntegerPart(index: string): string {
|
||||
const integerPartLength = getIntegerLength(index.charAt(0))
|
||||
if (integerPartLength > index.length) {
|
||||
throw new Error('invalid index: ' + index)
|
||||
|
@ -155,7 +161,7 @@ export function getIntegerPart(index: string): string {
|
|||
*
|
||||
* @param x - The index to validate.
|
||||
*/
|
||||
export function validateOrder(index: string): asserts index is string {
|
||||
function validateOrder(index: string): asserts index is string {
|
||||
if (index === SMALLEST_INTEGER) {
|
||||
throw new Error('invalid index: ' + index)
|
||||
}
|
||||
|
@ -173,7 +179,7 @@ export function validateOrder(index: string): asserts index is string {
|
|||
* A string made up of an integer part followed by a fraction part. The fraction point consists of
|
||||
* zero or more digits with no trailing zeros.
|
||||
*/
|
||||
export type OrderKey = string
|
||||
type OrderKey = string
|
||||
|
||||
/**
|
||||
* Generate an index key at the midpoint between a start and end.
|
||||
|
@ -181,7 +187,7 @@ export type OrderKey = string
|
|||
* @param a - The start index key string.
|
||||
* @param b - The end index key string, greater than A.
|
||||
*/
|
||||
export function generateKeyBetween(a: OrderKey | undefined, b: OrderKey | undefined): OrderKey {
|
||||
function generateKeyBetween(a: OrderKey | undefined, b: OrderKey | undefined): OrderKey {
|
||||
if (a !== undefined) validateOrder(a)
|
||||
if (b !== undefined) validateOrder(b)
|
||||
if (a !== undefined && b !== undefined && a >= b) {
|
||||
|
@ -259,21 +265,3 @@ export function generateNKeysBetween(
|
|||
const c = generateKeyBetween(a, b)
|
||||
return [...generateNKeysBetween(a, c, mid), c, ...generateNKeysBetween(c, b, n - mid - 1)]
|
||||
}
|
||||
|
||||
export function getCounter() {
|
||||
let index = 'a0'
|
||||
return () => {
|
||||
index = generateKeyBetween(index, undefined)
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
export function* iterableRange(n = 1) {
|
||||
let index = 'a0'
|
||||
let i = 0
|
||||
while (i < n) {
|
||||
i++
|
||||
index = generateKeyBetween(index, undefined)
|
||||
yield index
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
import { generateKeyBetween, generateNKeysBetween, getCounter } from './dgreensp'
|
||||
export { getCounter, generateKeyBetween, generateNKeysBetween }
|
|
@ -1,4 +1,4 @@
|
|||
import { generateNKeysBetween } from './dgreensp'
|
||||
import { generateNKeysBetween } from './dgreensp/dgreensp'
|
||||
|
||||
/**
|
||||
* Get a number of indices between two indices.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { isShapeId, TLShape, TLShapePartial } from '@tldraw/tlschema'
|
||||
import { structuredClone } from '@tldraw/utils'
|
||||
import { Editor } from '../editor/Editor'
|
||||
import { Matrix2d } from '../primitives/Matrix2d'
|
||||
import { Mat } from '../primitives/Mat'
|
||||
import { canonicalizeRotation } from '../primitives/utils'
|
||||
import { Vec2d } from '../primitives/Vec2d'
|
||||
import { Vec } from '../primitives/Vec'
|
||||
|
||||
/** @internal */
|
||||
export function getRotationSnapshot({ editor }: { editor: Editor }): TLRotationSnapshot | null {
|
||||
|
@ -49,12 +49,12 @@ export function getRotationSnapshot({ editor }: { editor: Editor }): TLRotationS
|
|||
* @public
|
||||
**/
|
||||
export type TLRotationSnapshot = {
|
||||
selectionPageCenter: Vec2d
|
||||
selectionPageCenter: Vec
|
||||
initialCursorAngle: number
|
||||
initialSelectionRotation: number
|
||||
shapeSnapshots: {
|
||||
shape: TLShape
|
||||
initialPagePoint: Vec2d
|
||||
initialPagePoint: Vec
|
||||
}[]
|
||||
}
|
||||
|
||||
|
@ -79,14 +79,14 @@ export function applyRotationToSnapshotShapes({
|
|||
|
||||
const parentTransform = isShapeId(shape.parentId)
|
||||
? editor.getShapePageTransform(shape.parentId)!
|
||||
: Matrix2d.Identity()
|
||||
: Mat.Identity()
|
||||
|
||||
const newPagePoint = Vec2d.RotWith(initialPagePoint, selectionPageCenter, delta)
|
||||
const newPagePoint = Vec.RotWith(initialPagePoint, selectionPageCenter, delta)
|
||||
|
||||
const newLocalPoint = Matrix2d.applyToPoint(
|
||||
const newLocalPoint = Mat.applyToPoint(
|
||||
// use the current parent transform in case it has moved/resized since the start
|
||||
// (e.g. if rotating a shape at the edge of a group)
|
||||
Matrix2d.Inverse(parentTransform),
|
||||
Mat.Inverse(parentTransform),
|
||||
newPagePoint
|
||||
)
|
||||
const newRotation = canonicalizeRotation(shape.rotation + delta)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import { ArrayOfValidator } from '@tldraw/editor';
|
||||
import { BaseBoxShapeTool } from '@tldraw/editor';
|
||||
import { BaseBoxShapeUtil } from '@tldraw/editor';
|
||||
import { Box2d } from '@tldraw/editor';
|
||||
import { Box } from '@tldraw/editor';
|
||||
import { Circle2d } from '@tldraw/editor';
|
||||
import { CubicSpline2d } from '@tldraw/editor';
|
||||
import { DictValidator } from '@tldraw/editor';
|
||||
|
@ -21,8 +21,8 @@ import { Geometry2d } from '@tldraw/editor';
|
|||
import { Group2d } from '@tldraw/editor';
|
||||
import { JsonObject } from '@tldraw/editor';
|
||||
import { LANGUAGES } from '@tldraw/editor';
|
||||
import { Matrix2d } from '@tldraw/editor';
|
||||
import { Matrix2dModel } from '@tldraw/editor';
|
||||
import { Mat } from '@tldraw/editor';
|
||||
import { MatModel } from '@tldraw/editor';
|
||||
import { MigrationFailureReason } from '@tldraw/editor';
|
||||
import { Migrations } from '@tldraw/editor';
|
||||
import { NamedExoticComponent } from 'react';
|
||||
|
@ -107,9 +107,9 @@ import { TLVideoShape } from '@tldraw/editor';
|
|||
import { UnionValidator } from '@tldraw/editor';
|
||||
import { UnknownRecord } from '@tldraw/editor';
|
||||
import { Validator } from '@tldraw/editor';
|
||||
import { Vec2d } from '@tldraw/editor';
|
||||
import { Vec2dModel } from '@tldraw/editor';
|
||||
import { Vec } from '@tldraw/editor';
|
||||
import { VecLike } from '@tldraw/editor';
|
||||
import { VecModel } from '@tldraw/editor';
|
||||
|
||||
// @public (undocumented)
|
||||
export class ArrowShapeTool extends StateNode {
|
||||
|
@ -177,7 +177,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
binding: ObjectValidator< {
|
||||
type: "binding";
|
||||
boundShapeId: TLShapeId;
|
||||
normalizedAnchor: Vec2dModel;
|
||||
normalizedAnchor: VecModel;
|
||||
isExact: boolean;
|
||||
isPrecise: boolean;
|
||||
}>;
|
||||
|
@ -191,7 +191,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
binding: ObjectValidator< {
|
||||
type: "binding";
|
||||
boundShapeId: TLShapeId;
|
||||
normalizedAnchor: Vec2dModel;
|
||||
normalizedAnchor: VecModel;
|
||||
isExact: boolean;
|
||||
isPrecise: boolean;
|
||||
}>;
|
||||
|
@ -365,7 +365,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
segments: ArrayOfValidator< {
|
||||
type: "free" | "straight";
|
||||
points: Vec2dModel[];
|
||||
points: VecModel[];
|
||||
}>;
|
||||
isComplete: Validator<boolean>;
|
||||
isClosed: Validator<boolean>;
|
||||
|
@ -774,7 +774,7 @@ export class HighlightShapeUtil extends ShapeUtil<TLHighlightShape> {
|
|||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
segments: ArrayOfValidator< {
|
||||
type: "free" | "straight";
|
||||
points: Vec2dModel[];
|
||||
points: VecModel[];
|
||||
}>;
|
||||
isComplete: Validator<boolean>;
|
||||
isPen: Validator<boolean>;
|
||||
|
@ -816,8 +816,8 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
|
|||
url: Validator<string>;
|
||||
assetId: Validator<TLAssetId | null>;
|
||||
crop: Validator< {
|
||||
topLeft: Vec2dModel;
|
||||
bottomRight: Vec2dModel;
|
||||
topLeft: VecModel;
|
||||
bottomRight: VecModel;
|
||||
} | null>;
|
||||
};
|
||||
// (undocumented)
|
||||
|
@ -889,7 +889,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
// (undocumented)
|
||||
getHandles(shape: TLLineShape): TLHandle[];
|
||||
// (undocumented)
|
||||
getOutlineSegments(shape: TLLineShape): Vec2d[][];
|
||||
getOutlineSegments(shape: TLLineShape): Vec[][];
|
||||
// (undocumented)
|
||||
hideResizeHandles: () => boolean;
|
||||
// (undocumented)
|
||||
|
|
|
@ -1321,8 +1321,8 @@
|
|||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Vec2dModel",
|
||||
"canonicalReference": "@tldraw/tlschema!Vec2dModel:interface"
|
||||
"text": "VecModel",
|
||||
"canonicalReference": "@tldraw/tlschema!VecModel:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -1366,8 +1366,8 @@
|
|||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Vec2dModel",
|
||||
"canonicalReference": "@tldraw/tlschema!Vec2dModel:interface"
|
||||
"text": "VecModel",
|
||||
"canonicalReference": "@tldraw/tlschema!VecModel:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -3601,8 +3601,8 @@
|
|||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Vec2dModel",
|
||||
"canonicalReference": "@tldraw/tlschema!Vec2dModel:interface"
|
||||
"text": "VecModel",
|
||||
"canonicalReference": "@tldraw/tlschema!VecModel:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -8826,8 +8826,8 @@
|
|||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Vec2dModel",
|
||||
"canonicalReference": "@tldraw/tlschema!Vec2dModel:interface"
|
||||
"text": "VecModel",
|
||||
"canonicalReference": "@tldraw/tlschema!VecModel:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -9480,8 +9480,8 @@
|
|||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Vec2dModel",
|
||||
"canonicalReference": "@tldraw/tlschema!Vec2dModel:interface"
|
||||
"text": "VecModel",
|
||||
"canonicalReference": "@tldraw/tlschema!VecModel:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -9489,8 +9489,8 @@
|
|||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Vec2dModel",
|
||||
"canonicalReference": "@tldraw/tlschema!Vec2dModel:interface"
|
||||
"text": "VecModel",
|
||||
"canonicalReference": "@tldraw/tlschema!VecModel:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -10320,8 +10320,8 @@
|
|||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Vec2d",
|
||||
"canonicalReference": "@tldraw/editor!Vec2d:class"
|
||||
"text": "Vec",
|
||||
"canonicalReference": "@tldraw/editor!Vec:class"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -15722,7 +15722,7 @@
|
|||
"text": "export interface TLUiContextMenuProps "
|
||||
}
|
||||
],
|
||||
"fileUrlPath": "packages/tldraw/.tsbuild-api/lib/ui/components/ContextMenu.d.ts",
|
||||
"fileUrlPath": "packages/tldraw/src/lib/ui/components/ContextMenu.tsx",
|
||||
"releaseTag": "Public",
|
||||
"name": "TLUiContextMenuProps",
|
||||
"preserveMemberOrder": false,
|
||||
|
@ -15745,7 +15745,6 @@
|
|||
"text": ";"
|
||||
}
|
||||
],
|
||||
"fileUrlPath": "packages/tldraw/src/lib/ui/components/ContextMenu.tsx",
|
||||
"isReadonly": false,
|
||||
"isOptional": false,
|
||||
"releaseTag": "Public",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
Box2d,
|
||||
Box,
|
||||
RotateCorner,
|
||||
TLEmbedShape,
|
||||
TLSelectionForegroundComponent,
|
||||
|
@ -19,7 +19,7 @@ import { TldrawCropHandles } from './TldrawCropHandles'
|
|||
|
||||
/** @public */
|
||||
export const TldrawSelectionForeground: TLSelectionForegroundComponent = track(
|
||||
function TldrawSelectionForeground({ bounds, rotation }: { bounds: Box2d; rotation: number }) {
|
||||
function TldrawSelectionForeground({ bounds, rotation }: { bounds: Box; rotation: number }) {
|
||||
const editor = useEditor()
|
||||
const rSvg = useRef<SVGSVGElement>(null)
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
TLShapePartial,
|
||||
TLTextShape,
|
||||
TLTextShapeProps,
|
||||
Vec2d,
|
||||
Vec,
|
||||
VecLike,
|
||||
compact,
|
||||
createShapeId,
|
||||
|
@ -226,7 +226,7 @@ export function registerDefaultExternalContentHandlers(
|
|||
point ??
|
||||
(editor.inputs.shiftKey ? editor.inputs.currentPagePoint : editor.getViewportPageCenter())
|
||||
|
||||
const pagePoint = new Vec2d(position.x, position.y)
|
||||
const pagePoint = new Vec(position.x, position.y)
|
||||
|
||||
const assets: TLAsset[] = []
|
||||
|
||||
|
@ -404,7 +404,7 @@ export async function createShapesForAssets(
|
|||
): Promise<TLShapeId[]> {
|
||||
if (!assets.length) return []
|
||||
|
||||
const currentPoint = Vec2d.From(position)
|
||||
const currentPoint = Vec.From(position)
|
||||
const partials: TLShapePartial[] = []
|
||||
|
||||
for (const asset of assets) {
|
||||
|
@ -489,7 +489,7 @@ function centerSelectionAroundPoint(editor: Editor, position: VecLike) {
|
|||
editor.updateShapes(
|
||||
editor.getSelectedShapes().map((shape) => {
|
||||
const localRotation = editor.getShapeParentTransform(shape).decompose().rotation
|
||||
const localDelta = Vec2d.Rot(offset, -localRotation)
|
||||
const localDelta = Vec.Rot(offset, -localRotation)
|
||||
return {
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TLArrowShape, Vec2d, createShapeId } from '@tldraw/editor'
|
||||
import { TLArrowShape, Vec, createShapeId } from '@tldraw/editor'
|
||||
import { TestEditor } from '../../../test/TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
@ -132,7 +132,7 @@ describe('When pointing a start shape', () => {
|
|||
expect(editor.getHintingShapeIds().length).toBe(1)
|
||||
|
||||
// Fake some velocity
|
||||
editor.inputs.pointerVelocity = new Vec2d(1, 1)
|
||||
editor.inputs.pointerVelocity = new Vec(1, 1)
|
||||
|
||||
editor.pointerMove(375, 500)
|
||||
|
||||
|
@ -172,7 +172,7 @@ describe('When pointing an end shape', () => {
|
|||
expect(editor.getHintingShapeIds().length).toBe(0)
|
||||
|
||||
// Fake some velocity
|
||||
editor.inputs.pointerVelocity = new Vec2d(1, 1)
|
||||
editor.inputs.pointerVelocity = new Vec(1, 1)
|
||||
|
||||
// Move onto shape
|
||||
editor.pointerMove(375, 375)
|
||||
|
@ -206,7 +206,7 @@ describe('When pointing an end shape', () => {
|
|||
it('unbinds and rebinds', () => {
|
||||
editor.setCurrentTool('arrow').pointerDown(0, 0)
|
||||
|
||||
editor.inputs.pointerVelocity = new Vec2d(1, 1)
|
||||
editor.inputs.pointerVelocity = new Vec(1, 1)
|
||||
|
||||
editor.pointerMove(375, 375)
|
||||
|
||||
|
@ -268,7 +268,7 @@ describe('When pointing an end shape', () => {
|
|||
})
|
||||
|
||||
// Build up some velocity
|
||||
editor.inputs.pointerVelocity = new Vec2d(1, 1)
|
||||
editor.inputs.pointerVelocity = new Vec(1, 1)
|
||||
editor.pointerMove(325, 325)
|
||||
expect(editor.getHintingShapeIds().length).toBe(1)
|
||||
|
||||
|
@ -318,7 +318,7 @@ describe('When pointing an end shape', () => {
|
|||
|
||||
it('begins imprecise when moving quickly', () => {
|
||||
editor.setCurrentTool('arrow').pointerDown(0, 0)
|
||||
editor.inputs.pointerVelocity = new Vec2d(1, 1)
|
||||
editor.inputs.pointerVelocity = new Vec(1, 1)
|
||||
editor.pointerMove(370, 370)
|
||||
|
||||
const arrow = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1]
|
||||
|
@ -361,7 +361,7 @@ describe('When pointing an end shape', () => {
|
|||
|
||||
expect(editor.getHintingShapeIds().length).toBe(0)
|
||||
|
||||
editor.inputs.pointerVelocity = new Vec2d(0.001, 0.001)
|
||||
editor.inputs.pointerVelocity = new Vec(0.001, 0.001)
|
||||
editor.pointerMove(375, 375)
|
||||
|
||||
arrow = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
assert,
|
||||
createShapeId,
|
||||
TAU,
|
||||
HALF_PI,
|
||||
TLArrowShape,
|
||||
TLArrowShapeTerminal,
|
||||
TLShapeId,
|
||||
|
@ -343,7 +343,7 @@ describe('When a shape it rotated', () => {
|
|||
},
|
||||
})
|
||||
|
||||
editor.updateShapes([{ id: ids.box2, type: 'geo', rotation: TAU }])
|
||||
editor.updateShapes([{ id: ids.box2, type: 'geo', rotation: HALF_PI }])
|
||||
|
||||
editor.pointerMove(225, 350)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
Arc2d,
|
||||
Box2d,
|
||||
Box,
|
||||
DefaultFontFamilies,
|
||||
Edge2d,
|
||||
Group2d,
|
||||
|
@ -21,17 +21,12 @@ import {
|
|||
TLShapePartial,
|
||||
TLShapeUtilCanvasSvgDef,
|
||||
TLShapeUtilFlag,
|
||||
Vec2d,
|
||||
Vec,
|
||||
arrowShapeMigrations,
|
||||
arrowShapeProps,
|
||||
deepCopy,
|
||||
getArrowTerminalsInArrowSpace,
|
||||
getArrowheadPathForType,
|
||||
getCurvedArrowHandlePath,
|
||||
getDefaultColorTheme,
|
||||
getSolidCurvedArrowPath,
|
||||
getSolidStraightArrowPath,
|
||||
getStraightArrowHandlePath,
|
||||
toDomPrecision,
|
||||
useIsEditing,
|
||||
} from '@tldraw/editor'
|
||||
|
@ -50,6 +45,13 @@ import {
|
|||
getFontDefForExport,
|
||||
} from '../shared/defaultStyleDefs'
|
||||
import { getPerfectDashProps } from '../shared/getPerfectDashProps'
|
||||
import { getArrowheadPathForType } from './arrowheads'
|
||||
import {
|
||||
getCurvedArrowHandlePath,
|
||||
getSolidCurvedArrowPath,
|
||||
getSolidStraightArrowPath,
|
||||
getStraightArrowHandlePath,
|
||||
} from './arrowpaths'
|
||||
import { ArrowTextLabel } from './components/ArrowTextLabel'
|
||||
|
||||
let globalRenderIndex = 0
|
||||
|
@ -92,14 +94,14 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
|
||||
const bodyGeom = info.isStraight
|
||||
? new Edge2d({
|
||||
start: Vec2d.From(info.start.point),
|
||||
end: Vec2d.From(info.end.point),
|
||||
start: Vec.From(info.start.point),
|
||||
end: Vec.From(info.end.point),
|
||||
})
|
||||
: new Arc2d({
|
||||
center: Vec2d.Cast(info.handleArc.center),
|
||||
center: Vec.Cast(info.handleArc.center),
|
||||
radius: info.handleArc.radius,
|
||||
start: Vec2d.Cast(info.start.point),
|
||||
end: Vec2d.Cast(info.end.point),
|
||||
start: Vec.Cast(info.start.point),
|
||||
end: Vec.Cast(info.end.point),
|
||||
sweepFlag: info.bodyArc.sweepFlag,
|
||||
largeArcFlag: info.bodyArc.largeArcFlag,
|
||||
})
|
||||
|
@ -209,16 +211,16 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
// Bending the arrow...
|
||||
const { start, end } = getArrowTerminalsInArrowSpace(this.editor, shape)
|
||||
|
||||
const delta = Vec2d.Sub(end, start)
|
||||
const v = Vec2d.Per(delta)
|
||||
const delta = Vec.Sub(end, start)
|
||||
const v = Vec.Per(delta)
|
||||
|
||||
const med = Vec2d.Med(end, start)
|
||||
const A = Vec2d.Sub(med, v)
|
||||
const B = Vec2d.Add(med, v)
|
||||
const med = Vec.Med(end, start)
|
||||
const A = Vec.Sub(med, v)
|
||||
const B = Vec.Add(med, v)
|
||||
|
||||
const point = Vec2d.NearestPointOnLineSegment(A, B, handle, false)
|
||||
let bend = Vec2d.Dist(point, med)
|
||||
if (Vec2d.Clockwise(point, end, med)) bend *= -1
|
||||
const point = Vec.NearestPointOnLineSegment(A, B, handle, false)
|
||||
let bend = Vec.Dist(point, med)
|
||||
if (Vec.Clockwise(point, end, med)) bend *= -1
|
||||
return { id: shape.id, type: shape.type, props: { bend } }
|
||||
}
|
||||
|
||||
|
@ -264,7 +266,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
// we've got a target! the handle is being dragged over a shape, bind to it
|
||||
|
||||
const targetGeometry = this.editor.getShapeGeometry(target)
|
||||
const targetBounds = Box2d.ZeroFix(targetGeometry.bounds)
|
||||
const targetBounds = Box.ZeroFix(targetGeometry.bounds)
|
||||
const pointInTargetSpace = this.editor.getPointInShapeSpace(target, pointInPageSpace)
|
||||
|
||||
let precise = isPrecise
|
||||
|
@ -307,7 +309,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
// Funky math but we want the snap distance to be 4 at the minimum and either
|
||||
// 16 or 15% of the smaller dimension of the target shape, whichever is smaller
|
||||
if (
|
||||
Vec2d.Dist(pointInTargetSpace, targetBounds.center) <
|
||||
Vec.Dist(pointInTargetSpace, targetBounds.center) <
|
||||
Math.max(4, Math.min(Math.min(targetBounds.width, targetBounds.height) * 0.15, 16)) /
|
||||
this.editor.getZoomLevel()
|
||||
) {
|
||||
|
@ -326,7 +328,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
|
||||
if (next.props.start.type === 'binding' && next.props.end.type === 'binding') {
|
||||
if (next.props.start.boundShapeId === next.props.end.boundShapeId) {
|
||||
if (Vec2d.Equals(next.props.start.normalizedAnchor, next.props.end.normalizedAnchor)) {
|
||||
if (Vec.Equals(next.props.start.normalizedAnchor, next.props.end.normalizedAnchor)) {
|
||||
next.props.end.normalizedAnchor.x += 0.05
|
||||
}
|
||||
}
|
||||
|
@ -501,7 +503,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
) && !this.editor.getInstanceState().isReadonly
|
||||
|
||||
const info = this.editor.getArrowInfo(shape)
|
||||
const bounds = Box2d.ZeroFix(this.editor.getShapeGeometry(shape).bounds)
|
||||
const bounds = Box.ZeroFix(this.editor.getShapeGeometry(shape).bounds)
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const changeIndex = React.useMemo<number>(() => {
|
||||
|
@ -524,7 +526,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
const sw = 2
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
info.isStraight
|
||||
? Vec2d.Dist(info.start.handle, info.end.handle)
|
||||
? Vec.Dist(info.start.handle, info.end.handle)
|
||||
: Math.abs(info.handleArc.length),
|
||||
sw,
|
||||
{
|
||||
|
@ -686,7 +688,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
const isEditing = useIsEditing(shape.id)
|
||||
|
||||
if (!info) return null
|
||||
if (Vec2d.Equals(start, end)) return null
|
||||
if (Vec.Equals(start, end)) return null
|
||||
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { Vec2d, VecLike } from '../../../../primitives/Vec2d'
|
||||
import { intersectCircleCircle } from '../../../../primitives/intersect'
|
||||
import { PI, TAU } from '../../../../primitives/utils'
|
||||
import { TLArrowInfo } from './arrow-types'
|
||||
import { HALF_PI, PI, TLArrowInfo, Vec, VecLike, intersectCircleCircle } from '@tldraw/editor'
|
||||
|
||||
type TLArrowPointsInfo = {
|
||||
point: VecLike
|
||||
|
@ -16,14 +13,14 @@ function getArrowPoints(
|
|||
const PT = side === 'end' ? info.end.point : info.start.point
|
||||
const PB = side === 'end' ? info.start.point : info.end.point
|
||||
|
||||
const compareLength = info.isStraight ? Vec2d.Dist(PB, PT) : Math.abs(info.bodyArc.length) // todo: arc length for curved arrows
|
||||
const compareLength = info.isStraight ? Vec.Dist(PB, PT) : Math.abs(info.bodyArc.length) // todo: arc length for curved arrows
|
||||
|
||||
const length = Math.max(Math.min(compareLength / 5, strokeWidth * 3), strokeWidth)
|
||||
|
||||
let P0: VecLike
|
||||
|
||||
if (info.isStraight) {
|
||||
P0 = Vec2d.Nudge(PT, PB, length)
|
||||
P0 = Vec.Nudge(PT, PB, length)
|
||||
} else {
|
||||
const ints = intersectCircleCircle(PT, length, info.handleArc.center, info.handleArc.radius)
|
||||
P0 =
|
||||
|
@ -42,72 +39,68 @@ function getArrowPoints(
|
|||
}
|
||||
}
|
||||
|
||||
export function getArrowhead({ point, int }: TLArrowPointsInfo) {
|
||||
const PL = Vec2d.RotWith(int, point, PI / 6)
|
||||
const PR = Vec2d.RotWith(int, point, -PI / 6)
|
||||
function getArrowhead({ point, int }: TLArrowPointsInfo) {
|
||||
const PL = Vec.RotWith(int, point, PI / 6)
|
||||
const PR = Vec.RotWith(int, point, -PI / 6)
|
||||
|
||||
return `M ${PL.x} ${PL.y} L ${point.x} ${point.y} L ${PR.x} ${PR.y}`
|
||||
}
|
||||
|
||||
export function getTriangleHead({ point, int }: TLArrowPointsInfo) {
|
||||
const PL = Vec2d.RotWith(int, point, PI / 6)
|
||||
const PR = Vec2d.RotWith(int, point, -PI / 6)
|
||||
function getTriangleHead({ point, int }: TLArrowPointsInfo) {
|
||||
const PL = Vec.RotWith(int, point, PI / 6)
|
||||
const PR = Vec.RotWith(int, point, -PI / 6)
|
||||
|
||||
return `M ${PL.x} ${PL.y} L ${point.x} ${point.y} L ${PR.x} ${PR.y} Z`
|
||||
}
|
||||
|
||||
export function getInvertedTriangleHead({ point, int }: TLArrowPointsInfo) {
|
||||
const d = Vec2d.Sub(int, point).div(2)
|
||||
const PL = Vec2d.Add(point, Vec2d.Rot(d, TAU))
|
||||
const PR = Vec2d.Sub(point, Vec2d.Rot(d, TAU))
|
||||
function getInvertedTriangleHead({ point, int }: TLArrowPointsInfo) {
|
||||
const d = Vec.Sub(int, point).div(2)
|
||||
const PL = Vec.Add(point, Vec.Rot(d, HALF_PI))
|
||||
const PR = Vec.Sub(point, Vec.Rot(d, HALF_PI))
|
||||
|
||||
return `M ${PL.x} ${PL.y} L ${int.x} ${int.y} L ${PR.x} ${PR.y} Z`
|
||||
}
|
||||
|
||||
export function getDotHead({ point, int }: TLArrowPointsInfo) {
|
||||
const A = Vec2d.Lrp(point, int, 0.45)
|
||||
const r = Vec2d.Dist(A, point)
|
||||
function getDotHead({ point, int }: TLArrowPointsInfo) {
|
||||
const A = Vec.Lrp(point, int, 0.45)
|
||||
const r = Vec.Dist(A, point)
|
||||
|
||||
return `M ${A.x - r},${A.y}
|
||||
a ${r},${r} 0 1,0 ${r * 2},0
|
||||
a ${r},${r} 0 1,0 -${r * 2},0 `
|
||||
}
|
||||
|
||||
export function getDiamondHead({ point, int }: TLArrowPointsInfo) {
|
||||
const PB = Vec2d.Lrp(point, int, 0.75)
|
||||
const PL = Vec2d.RotWith(PB, point, PI / 4)
|
||||
const PR = Vec2d.RotWith(PB, point, -PI / 4)
|
||||
function getDiamondHead({ point, int }: TLArrowPointsInfo) {
|
||||
const PB = Vec.Lrp(point, int, 0.75)
|
||||
const PL = Vec.RotWith(PB, point, PI / 4)
|
||||
const PR = Vec.RotWith(PB, point, -PI / 4)
|
||||
|
||||
const PQ = Vec2d.Lrp(PL, PR, 0.5)
|
||||
PQ.add(Vec2d.Sub(PQ, point))
|
||||
const PQ = Vec.Lrp(PL, PR, 0.5)
|
||||
PQ.add(Vec.Sub(PQ, point))
|
||||
|
||||
return `M ${PQ.x} ${PQ.y} L ${PL.x} ${PL.y} ${point.x} ${point.y} L ${PR.x} ${PR.y} Z`
|
||||
}
|
||||
|
||||
export function getSquareHead({ int, point }: TLArrowPointsInfo) {
|
||||
const PB = Vec2d.Lrp(point, int, 0.85)
|
||||
const d = Vec2d.Sub(PB, point).div(2)
|
||||
const PL1 = Vec2d.Add(point, Vec2d.Rot(d, TAU))
|
||||
const PR1 = Vec2d.Sub(point, Vec2d.Rot(d, TAU))
|
||||
const PL2 = Vec2d.Add(PB, Vec2d.Rot(d, TAU))
|
||||
const PR2 = Vec2d.Sub(PB, Vec2d.Rot(d, TAU))
|
||||
function getSquareHead({ int, point }: TLArrowPointsInfo) {
|
||||
const PB = Vec.Lrp(point, int, 0.85)
|
||||
const d = Vec.Sub(PB, point).div(2)
|
||||
const PL1 = Vec.Add(point, Vec.Rot(d, HALF_PI))
|
||||
const PR1 = Vec.Sub(point, Vec.Rot(d, HALF_PI))
|
||||
const PL2 = Vec.Add(PB, Vec.Rot(d, HALF_PI))
|
||||
const PR2 = Vec.Sub(PB, Vec.Rot(d, HALF_PI))
|
||||
|
||||
return `M ${PL1.x} ${PL1.y} L ${PL2.x} ${PL2.y} L ${PR2.x} ${PR2.y} L ${PR1.x} ${PR1.y} Z`
|
||||
}
|
||||
|
||||
export function getBarHead({ int, point }: TLArrowPointsInfo) {
|
||||
const d = Vec2d.Sub(int, point).div(2)
|
||||
function getBarHead({ int, point }: TLArrowPointsInfo) {
|
||||
const d = Vec.Sub(int, point).div(2)
|
||||
|
||||
const PL = Vec2d.Add(point, Vec2d.Rot(d, TAU))
|
||||
const PR = Vec2d.Sub(point, Vec2d.Rot(d, TAU))
|
||||
const PL = Vec.Add(point, Vec.Rot(d, HALF_PI))
|
||||
const PR = Vec.Sub(point, Vec.Rot(d, HALF_PI))
|
||||
|
||||
return `M ${PL.x} ${PL.y} L ${PR.x} ${PR.y}`
|
||||
}
|
||||
|
||||
export function getPipeHead() {
|
||||
return ''
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getArrowheadPathForType(
|
||||
info: TLArrowInfo,
|
49
packages/tldraw/src/lib/shapes/arrow/arrowpaths.ts
Normal file
49
packages/tldraw/src/lib/shapes/arrow/arrowpaths.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { TLArrowInfo, VecLike } from '@tldraw/editor'
|
||||
|
||||
/* --------------------- Curved --------------------- */
|
||||
|
||||
/**
|
||||
* Get a solid path for a curved arrow's handles.
|
||||
*
|
||||
* @param info - The arrow info.
|
||||
* @public
|
||||
*/
|
||||
export function getCurvedArrowHandlePath(info: TLArrowInfo & { isStraight: false }) {
|
||||
const {
|
||||
start,
|
||||
end,
|
||||
handleArc: { radius, largeArcFlag, sweepFlag },
|
||||
} = info
|
||||
return `M${start.handle.x},${start.handle.y} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.handle.x},${end.handle.y}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a solid path for a curved arrow's body.
|
||||
*
|
||||
* @param info - The arrow info.
|
||||
* @public
|
||||
*/
|
||||
export function getSolidCurvedArrowPath(info: TLArrowInfo & { isStraight: false }) {
|
||||
const {
|
||||
start,
|
||||
end,
|
||||
bodyArc: { radius, largeArcFlag, sweepFlag },
|
||||
} = info
|
||||
return `M${start.point.x},${start.point.y} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.point.x},${end.point.y}`
|
||||
}
|
||||
|
||||
/* -------------------- Straight -------------------- */
|
||||
|
||||
function getArrowPath(start: VecLike, end: VecLike) {
|
||||
return `M${start.x},${start.y}L${end.x},${end.y}`
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getStraightArrowHandlePath(info: TLArrowInfo & { isStraight: true }) {
|
||||
return getArrowPath(info.start.handle, info.end.handle)
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function getSolidStraightArrowPath(info: TLArrowInfo & { isStraight: true }) {
|
||||
return getArrowPath(info.start.point, info.end.point)
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import {
|
||||
Box2d,
|
||||
Box,
|
||||
Circle2d,
|
||||
Polygon2d,
|
||||
Polyline2d,
|
||||
|
@ -60,7 +60,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
|
||||
// A dot
|
||||
if (shape.props.segments.length === 1) {
|
||||
const box = Box2d.FromPoints(points)
|
||||
const box = Box.FromPoints(points)
|
||||
if (box.width < strokeWidth * 2 && box.height < strokeWidth * 2) {
|
||||
return new Circle2d({
|
||||
x: -strokeWidth,
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
TLDefaultDashStyle,
|
||||
TLDrawShape,
|
||||
TLDrawShapeSegment,
|
||||
Vec2d,
|
||||
Vec,
|
||||
} from '@tldraw/editor'
|
||||
import { StrokeOptions } from '../shared/freehand/types'
|
||||
|
||||
|
@ -81,17 +81,17 @@ export function getFreehandOptions(
|
|||
}
|
||||
|
||||
export function getPointsFromSegments(segments: TLDrawShapeSegment[]) {
|
||||
const points: Vec2d[] = []
|
||||
const points: Vec[] = []
|
||||
|
||||
for (const segment of segments) {
|
||||
if (segment.type === 'free' || segment.points.length < 2) {
|
||||
points.push(...segment.points.map(Vec2d.Cast))
|
||||
points.push(...segment.points.map(Vec.Cast))
|
||||
} else {
|
||||
const pointsToInterpolate = Math.max(
|
||||
4,
|
||||
Math.floor(Vec2d.Dist(segment.points[0], segment.points[1]) / 16)
|
||||
Math.floor(Vec.Dist(segment.points[0], segment.points[1]) / 16)
|
||||
)
|
||||
points.push(...Vec2d.PointsBetween(segment.points[0], segment.points[1], pointsToInterpolate))
|
||||
points.push(...Vec.PointsBetween(segment.points[0], segment.points[1], pointsToInterpolate))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
DRAG_DISTANCE,
|
||||
Matrix2d,
|
||||
Mat,
|
||||
StateNode,
|
||||
TLDefaultSizeStyle,
|
||||
TLDrawShape,
|
||||
|
@ -9,8 +9,8 @@ import {
|
|||
TLHighlightShape,
|
||||
TLPointerEventInfo,
|
||||
TLShapePartial,
|
||||
Vec2d,
|
||||
Vec2dModel,
|
||||
Vec,
|
||||
VecModel,
|
||||
createShapeId,
|
||||
last,
|
||||
snapAngle,
|
||||
|
@ -38,11 +38,11 @@ export class Drawing extends StateNode {
|
|||
|
||||
didJustShiftClickToExtendPreviousShapeLine = false
|
||||
|
||||
pagePointWhereCurrentSegmentChanged = {} as Vec2d
|
||||
pagePointWhereCurrentSegmentChanged = {} as Vec
|
||||
|
||||
pagePointWhereNextSegmentChanged = null as Vec2d | null
|
||||
pagePointWhereNextSegmentChanged = null as Vec | null
|
||||
|
||||
lastRecordedPoint = {} as Vec2d
|
||||
lastRecordedPoint = {} as Vec
|
||||
mergeNextPoint = false
|
||||
currentLineLength = 0
|
||||
|
||||
|
@ -86,7 +86,7 @@ export class Drawing extends StateNode {
|
|||
// Don't update the shape if we haven't moved far enough from the last time we recorded a point
|
||||
if (inputs.isPen) {
|
||||
if (
|
||||
Vec2d.Dist(inputs.currentPagePoint, this.lastRecordedPoint) >=
|
||||
Vec.Dist(inputs.currentPagePoint, this.lastRecordedPoint) >=
|
||||
1 / this.editor.getZoomLevel()
|
||||
) {
|
||||
this.lastRecordedPoint = inputs.currentPagePoint.clone()
|
||||
|
@ -161,7 +161,7 @@ export class Drawing extends StateNode {
|
|||
return (
|
||||
firstPoint !== lastPoint &&
|
||||
this.currentLineLength > strokeWidth * 4 &&
|
||||
Vec2d.Dist(firstPoint, lastPoint) < strokeWidth * 2
|
||||
Vec.Dist(firstPoint, lastPoint) < strokeWidth * 2
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ export class Drawing extends StateNode {
|
|||
}
|
||||
|
||||
// Convert prevPoint to page space
|
||||
const prevPointPageSpace = Matrix2d.applyToPoint(
|
||||
const prevPointPageSpace = Mat.applyToPoint(
|
||||
this.editor.getShapePageTransform(shape.id)!,
|
||||
prevPoint
|
||||
)
|
||||
|
@ -310,7 +310,7 @@ export class Drawing extends StateNode {
|
|||
}
|
||||
|
||||
const hasMovedFarEnough =
|
||||
Vec2d.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
|
||||
Vec.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
|
||||
|
||||
// Find the distance from where the pointer was when shift was released and
|
||||
// where it is now; if it's far enough away, then update the page point where
|
||||
|
@ -337,7 +337,7 @@ export class Drawing extends StateNode {
|
|||
.toJson()
|
||||
|
||||
if (prevSegment.type === 'straight') {
|
||||
this.currentLineLength += Vec2d.Dist(prevLastPoint, newLastPoint)
|
||||
this.currentLineLength += Vec.Dist(prevLastPoint, newLastPoint)
|
||||
|
||||
newSegment = {
|
||||
type: 'straight',
|
||||
|
@ -346,10 +346,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
const transform = this.editor.getShapePageTransform(shape)!
|
||||
|
||||
this.pagePointWhereCurrentSegmentChanged = Matrix2d.applyToPoint(
|
||||
transform,
|
||||
prevLastPoint
|
||||
)
|
||||
this.pagePointWhereCurrentSegmentChanged = Mat.applyToPoint(transform, prevLastPoint)
|
||||
} else {
|
||||
newSegment = {
|
||||
type: 'straight',
|
||||
|
@ -386,7 +383,7 @@ export class Drawing extends StateNode {
|
|||
}
|
||||
|
||||
const hasMovedFarEnough =
|
||||
Vec2d.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
|
||||
Vec.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
|
||||
|
||||
// Find the distance from where the pointer was when shift was released and
|
||||
// where it is now; if it's far enough away, then update the page point where
|
||||
|
@ -411,9 +408,7 @@ export class Drawing extends StateNode {
|
|||
// ended and where the pointer is now
|
||||
const newFreeSegment: TLDrawShapeSegment = {
|
||||
type: 'free',
|
||||
points: [
|
||||
...Vec2d.PointsBetween(prevPoint, newPoint, 6).map((p) => p.toFixed().toJson()),
|
||||
],
|
||||
points: [...Vec.PointsBetween(prevPoint, newPoint, 6).map((p) => p.toFixed().toJson())],
|
||||
}
|
||||
|
||||
const finalSegments = [...newSegments, newFreeSegment]
|
||||
|
@ -449,7 +444,7 @@ export class Drawing extends StateNode {
|
|||
if (!pagePointWhereCurrentSegmentChanged)
|
||||
throw Error('We should have a point where the segment changed')
|
||||
|
||||
let pagePoint: Vec2dModel
|
||||
let pagePoint: VecModel
|
||||
let shouldSnapToAngle = false
|
||||
|
||||
if (this.didJustShiftClickToExtendPreviousShapeLine) {
|
||||
|
@ -473,7 +468,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
if (shouldSnap) {
|
||||
if (newSegments.length > 2) {
|
||||
let nearestPoint: Vec2dModel | undefined = undefined
|
||||
let nearestPoint: VecModel | undefined = undefined
|
||||
let minDistance = 8 / this.editor.getZoomLevel()
|
||||
|
||||
// Don't try to snap to the last two segments
|
||||
|
@ -487,12 +482,12 @@ export class Drawing extends StateNode {
|
|||
if (!(first && lastPoint)) continue
|
||||
|
||||
// Snap to the nearest point on the segment, if it's closer than the previous snapped point
|
||||
const nearestPointOnSegment = Vec2d.NearestPointOnLineSegment(
|
||||
const nearestPointOnSegment = Vec.NearestPointOnLineSegment(
|
||||
first,
|
||||
lastPoint,
|
||||
newPoint
|
||||
)
|
||||
const distance = Vec2d.Dist(nearestPointOnSegment, newPoint)
|
||||
const distance = Vec.Dist(nearestPointOnSegment, newPoint)
|
||||
|
||||
if (distance < minDistance) {
|
||||
nearestPoint = nearestPointOnSegment.toFixed().toJson()
|
||||
|
@ -515,11 +510,11 @@ export class Drawing extends StateNode {
|
|||
const lastPoint = last(snapSegment.points)
|
||||
if (!lastPoint) throw Error('Expected a last point!')
|
||||
|
||||
const A = Matrix2d.applyToPoint(transform, first)
|
||||
const A = Mat.applyToPoint(transform, first)
|
||||
|
||||
const B = Matrix2d.applyToPoint(transform, lastPoint)
|
||||
const B = Mat.applyToPoint(transform, lastPoint)
|
||||
|
||||
const snappedPoint = Matrix2d.applyToPoint(transform, newPoint)
|
||||
const snappedPoint = Mat.applyToPoint(transform, newPoint)
|
||||
|
||||
this.editor.snaps.setLines([
|
||||
{
|
||||
|
@ -533,11 +528,11 @@ export class Drawing extends StateNode {
|
|||
|
||||
if (shouldSnapToAngle) {
|
||||
// Snap line angle to nearest 15 degrees
|
||||
const currentAngle = Vec2d.Angle(pagePointWhereCurrentSegmentChanged, currentPagePoint)
|
||||
const currentAngle = Vec.Angle(pagePointWhereCurrentSegmentChanged, currentPagePoint)
|
||||
const snappedAngle = snapAngle(currentAngle, 24)
|
||||
const angleDiff = snappedAngle - currentAngle
|
||||
|
||||
pagePoint = Vec2d.RotWith(
|
||||
pagePoint = Vec.RotWith(
|
||||
currentPagePoint,
|
||||
pagePointWhereCurrentSegmentChanged,
|
||||
angleDiff
|
||||
|
@ -553,7 +548,7 @@ export class Drawing extends StateNode {
|
|||
// then the user just did a click-and-immediately-press-shift to create a new straight line
|
||||
// without continuing the previous line. In this case, we want to remove the previous segment.
|
||||
|
||||
this.currentLineLength += Vec2d.Dist(newSegment.points[0], newPoint)
|
||||
this.currentLineLength += Vec.Dist(newSegment.points[0], newPoint)
|
||||
|
||||
newSegments[newSegments.length - 1] = {
|
||||
...newSegment,
|
||||
|
@ -595,7 +590,7 @@ export class Drawing extends StateNode {
|
|||
// Note: we could recompute the line length here, but it's not really necessary
|
||||
// this.currentLineLength = this.getLineLength(newSegments)
|
||||
} else {
|
||||
this.currentLineLength += Vec2d.Dist(newPoints[newPoints.length - 1], newPoint)
|
||||
this.currentLineLength += Vec.Dist(newPoints[newPoints.length - 1], newPoint)
|
||||
newPoints.push(newPoint)
|
||||
}
|
||||
|
||||
|
@ -667,7 +662,7 @@ export class Drawing extends StateNode {
|
|||
for (let i = 0; i < segment.points.length - 1; i++) {
|
||||
const A = segment.points[i]
|
||||
const B = segment.points[i + 1]
|
||||
length += Vec2d.Sub(B, A).len2()
|
||||
length += Vec.Sub(B, A).len2()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
Ellipse2d,
|
||||
Geometry2d,
|
||||
Group2d,
|
||||
HALF_PI,
|
||||
HTMLContainer,
|
||||
PI2,
|
||||
Polygon2d,
|
||||
|
@ -13,13 +14,12 @@ import {
|
|||
SVGContainer,
|
||||
Stadium2d,
|
||||
SvgExportContext,
|
||||
TAU,
|
||||
TLDefaultDashStyle,
|
||||
TLGeoShape,
|
||||
TLOnEditEndHandler,
|
||||
TLOnResizeHandler,
|
||||
TLShapeUtilCanvasSvgDef,
|
||||
Vec2d,
|
||||
Vec,
|
||||
VecLike,
|
||||
geoShapeMigrations,
|
||||
geoShapeProps,
|
||||
|
@ -110,14 +110,14 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
}
|
||||
case 'triangle': {
|
||||
body = new Polygon2d({
|
||||
points: [new Vec2d(cx, 0), new Vec2d(w, h), new Vec2d(0, h)],
|
||||
points: [new Vec(cx, 0), new Vec(w, h), new Vec(0, h)],
|
||||
isFilled,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'diamond': {
|
||||
body = new Polygon2d({
|
||||
points: [new Vec2d(cx, 0), new Vec2d(w, cy), new Vec2d(cx, h), new Vec2d(0, cy)],
|
||||
points: [new Vec(cx, 0), new Vec(w, cy), new Vec(cx, h), new Vec(0, cy)],
|
||||
isFilled,
|
||||
})
|
||||
break
|
||||
|
@ -170,11 +170,11 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
const leftMostIndex = sides * 2 - rightMostIndex
|
||||
const topMostIndex = 0
|
||||
const bottomMostIndex = Math.floor(sides / 2) * 2
|
||||
const maxX = (Math.cos(-TAU + rightMostIndex * step) * w) / 2
|
||||
const minX = (Math.cos(-TAU + leftMostIndex * step) * w) / 2
|
||||
const maxX = (Math.cos(-HALF_PI + rightMostIndex * step) * w) / 2
|
||||
const minX = (Math.cos(-HALF_PI + leftMostIndex * step) * w) / 2
|
||||
|
||||
const minY = (Math.sin(-TAU + topMostIndex * step) * h) / 2
|
||||
const maxY = (Math.sin(-TAU + bottomMostIndex * step) * h) / 2
|
||||
const minY = (Math.sin(-HALF_PI + topMostIndex * step) * h) / 2
|
||||
const maxY = (Math.sin(-HALF_PI + bottomMostIndex * step) * h) / 2
|
||||
const diffX = w - Math.abs(maxX - minX)
|
||||
const diffY = h - Math.abs(maxY - minY)
|
||||
const offsetX = w / 2 + minX - (w / 2 - maxX)
|
||||
|
@ -190,8 +190,8 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
|
||||
body = new Polygon2d({
|
||||
points: Array.from(Array(sides * 2)).map((_, i) => {
|
||||
const theta = -TAU + i * step
|
||||
return new Vec2d(
|
||||
const theta = -HALF_PI + i * step
|
||||
return new Vec(
|
||||
cx + (i % 2 ? ix : ox) * Math.cos(theta),
|
||||
cy + (i % 2 ? iy : oy) * Math.sin(theta)
|
||||
)
|
||||
|
@ -203,12 +203,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
case 'rhombus': {
|
||||
const offset = Math.min(w * 0.38, h * 0.38)
|
||||
body = new Polygon2d({
|
||||
points: [
|
||||
new Vec2d(offset, 0),
|
||||
new Vec2d(w, 0),
|
||||
new Vec2d(w - offset, h),
|
||||
new Vec2d(0, h),
|
||||
],
|
||||
points: [new Vec(offset, 0), new Vec(w, 0), new Vec(w - offset, h), new Vec(0, h)],
|
||||
isFilled,
|
||||
})
|
||||
break
|
||||
|
@ -216,12 +211,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
case 'rhombus-2': {
|
||||
const offset = Math.min(w * 0.38, h * 0.38)
|
||||
body = new Polygon2d({
|
||||
points: [
|
||||
new Vec2d(0, 0),
|
||||
new Vec2d(w - offset, 0),
|
||||
new Vec2d(w, h),
|
||||
new Vec2d(offset, h),
|
||||
],
|
||||
points: [new Vec(0, 0), new Vec(w - offset, 0), new Vec(w, h), new Vec(offset, h)],
|
||||
isFilled,
|
||||
})
|
||||
break
|
||||
|
@ -229,12 +219,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
case 'trapezoid': {
|
||||
const offset = Math.min(w * 0.38, h * 0.38)
|
||||
body = new Polygon2d({
|
||||
points: [
|
||||
new Vec2d(offset, 0),
|
||||
new Vec2d(w - offset, 0),
|
||||
new Vec2d(w, h),
|
||||
new Vec2d(0, h),
|
||||
],
|
||||
points: [new Vec(offset, 0), new Vec(w - offset, 0), new Vec(w, h), new Vec(0, h)],
|
||||
isFilled,
|
||||
})
|
||||
break
|
||||
|
@ -244,13 +229,13 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
const oy = h * 0.16
|
||||
body = new Polygon2d({
|
||||
points: [
|
||||
new Vec2d(0, oy),
|
||||
new Vec2d(w - ox, oy),
|
||||
new Vec2d(w - ox, 0),
|
||||
new Vec2d(w, h / 2),
|
||||
new Vec2d(w - ox, h),
|
||||
new Vec2d(w - ox, h - oy),
|
||||
new Vec2d(0, h - oy),
|
||||
new Vec(0, oy),
|
||||
new Vec(w - ox, oy),
|
||||
new Vec(w - ox, 0),
|
||||
new Vec(w, h / 2),
|
||||
new Vec(w - ox, h),
|
||||
new Vec(w - ox, h - oy),
|
||||
new Vec(0, h - oy),
|
||||
],
|
||||
isFilled,
|
||||
})
|
||||
|
@ -261,13 +246,13 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
const oy = h * 0.16
|
||||
body = new Polygon2d({
|
||||
points: [
|
||||
new Vec2d(ox, 0),
|
||||
new Vec2d(ox, oy),
|
||||
new Vec2d(w, oy),
|
||||
new Vec2d(w, h - oy),
|
||||
new Vec2d(ox, h - oy),
|
||||
new Vec2d(ox, h),
|
||||
new Vec2d(0, h / 2),
|
||||
new Vec(ox, 0),
|
||||
new Vec(ox, oy),
|
||||
new Vec(w, oy),
|
||||
new Vec(w, h - oy),
|
||||
new Vec(ox, h - oy),
|
||||
new Vec(ox, h),
|
||||
new Vec(0, h / 2),
|
||||
],
|
||||
isFilled,
|
||||
})
|
||||
|
@ -278,13 +263,13 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
const oy = Math.min(w, h) * 0.38
|
||||
body = new Polygon2d({
|
||||
points: [
|
||||
new Vec2d(w / 2, 0),
|
||||
new Vec2d(w, oy),
|
||||
new Vec2d(w - ox, oy),
|
||||
new Vec2d(w - ox, h),
|
||||
new Vec2d(ox, h),
|
||||
new Vec2d(ox, oy),
|
||||
new Vec2d(0, oy),
|
||||
new Vec(w / 2, 0),
|
||||
new Vec(w, oy),
|
||||
new Vec(w - ox, oy),
|
||||
new Vec(w - ox, h),
|
||||
new Vec(ox, h),
|
||||
new Vec(ox, oy),
|
||||
new Vec(0, oy),
|
||||
],
|
||||
isFilled,
|
||||
})
|
||||
|
@ -295,13 +280,13 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
const oy = Math.min(w, h) * 0.38
|
||||
body = new Polygon2d({
|
||||
points: [
|
||||
new Vec2d(ox, 0),
|
||||
new Vec2d(w - ox, 0),
|
||||
new Vec2d(w - ox, h - oy),
|
||||
new Vec2d(w, h - oy),
|
||||
new Vec2d(w / 2, h),
|
||||
new Vec2d(0, h - oy),
|
||||
new Vec2d(ox, h - oy),
|
||||
new Vec(ox, 0),
|
||||
new Vec(w - ox, 0),
|
||||
new Vec(w - ox, h - oy),
|
||||
new Vec(w, h - oy),
|
||||
new Vec(w / 2, h),
|
||||
new Vec(0, h - oy),
|
||||
new Vec(ox, h - oy),
|
||||
],
|
||||
isFilled,
|
||||
})
|
||||
|
@ -862,7 +847,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
h = nextH
|
||||
}
|
||||
|
||||
const offset = new Vec2d(0, 0)
|
||||
const offset = new Vec(0, 0)
|
||||
|
||||
// x offsets
|
||||
|
||||
|
@ -1113,10 +1098,10 @@ function getXBoxLines(w: number, h: number, sw: number, dash: TLDefaultDashStyle
|
|||
|
||||
if (dash === 'dashed') {
|
||||
return [
|
||||
[new Vec2d(0, 0), new Vec2d(w / 2, h / 2)],
|
||||
[new Vec2d(w, h), new Vec2d(w / 2, h / 2)],
|
||||
[new Vec2d(0, h), new Vec2d(w / 2, h / 2)],
|
||||
[new Vec2d(w, 0), new Vec2d(w / 2, h / 2)],
|
||||
[new Vec(0, 0), new Vec(w / 2, h / 2)],
|
||||
[new Vec(w, h), new Vec(w / 2, h / 2)],
|
||||
[new Vec(0, h), new Vec(w / 2, h / 2)],
|
||||
[new Vec(w, 0), new Vec(w / 2, h / 2)],
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -1125,12 +1110,12 @@ function getXBoxLines(w: number, h: number, sw: number, dash: TLDefaultDashStyle
|
|||
|
||||
return [
|
||||
[
|
||||
new Vec2d(clampX(sw * inset), clampY(sw * inset)),
|
||||
new Vec2d(clampX(w - sw * inset), clampY(h - sw * inset)),
|
||||
new Vec(clampX(sw * inset), clampY(sw * inset)),
|
||||
new Vec(clampX(w - sw * inset), clampY(h - sw * inset)),
|
||||
],
|
||||
[
|
||||
new Vec2d(clampX(sw * inset), clampY(h - sw * inset)),
|
||||
new Vec2d(clampX(w - sw * inset), clampY(sw * inset)),
|
||||
new Vec(clampX(sw * inset), clampY(h - sw * inset)),
|
||||
new Vec(clampX(w - sw * inset), clampY(sw * inset)),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
@ -1145,12 +1130,12 @@ function getCheckBoxLines(w: number, h: number) {
|
|||
|
||||
return [
|
||||
[
|
||||
new Vec2d(clampX(ox + size * 0.25), clampY(oy + size * 0.52)),
|
||||
new Vec2d(clampX(ox + size * 0.45), clampY(oy + size * 0.82)),
|
||||
new Vec(clampX(ox + size * 0.25), clampY(oy + size * 0.52)),
|
||||
new Vec(clampX(ox + size * 0.45), clampY(oy + size * 0.82)),
|
||||
],
|
||||
[
|
||||
new Vec2d(clampX(ox + size * 0.45), clampY(oy + size * 0.82)),
|
||||
new Vec2d(clampX(ox + size * 0.82), clampY(oy + size * 0.22)),
|
||||
new Vec(clampX(ox + size * 0.45), clampY(oy + size * 0.82)),
|
||||
new Vec(clampX(ox + size * 0.82), clampY(oy + size * 0.22)),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
@ -1168,5 +1153,5 @@ export function getCentroidOfRegularPolygon(points: VecLike[]) {
|
|||
x += points[i].x
|
||||
y += points[i].y
|
||||
}
|
||||
return new Vec2d(x / len, y / len)
|
||||
return new Vec(x / len, y / len)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
PI,
|
||||
TLDefaultSizeStyle,
|
||||
Vec2d,
|
||||
Vec2dModel,
|
||||
Vec,
|
||||
VecModel,
|
||||
clockwiseAngleDist,
|
||||
getPointOnCircle,
|
||||
rng,
|
||||
|
@ -19,12 +19,12 @@ function getPillCircumference(width: number, height: number) {
|
|||
type PillSection =
|
||||
| {
|
||||
type: 'straight'
|
||||
start: Vec2dModel
|
||||
delta: Vec2dModel
|
||||
start: VecModel
|
||||
delta: VecModel
|
||||
}
|
||||
| {
|
||||
type: 'arc'
|
||||
center: Vec2dModel
|
||||
center: VecModel
|
||||
startAngle: number
|
||||
}
|
||||
|
||||
|
@ -41,55 +41,55 @@ export function getPillPoints(width: number, height: number, numPoints: number)
|
|||
? [
|
||||
{
|
||||
type: 'straight',
|
||||
start: new Vec2d(radius, 0),
|
||||
delta: new Vec2d(1, 0),
|
||||
start: new Vec(radius, 0),
|
||||
delta: new Vec(1, 0),
|
||||
},
|
||||
{
|
||||
type: 'arc',
|
||||
center: new Vec2d(width - radius, radius),
|
||||
center: new Vec(width - radius, radius),
|
||||
startAngle: -PI / 2,
|
||||
},
|
||||
{
|
||||
type: 'straight',
|
||||
start: new Vec2d(width - radius, height),
|
||||
delta: new Vec2d(-1, 0),
|
||||
start: new Vec(width - radius, height),
|
||||
delta: new Vec(-1, 0),
|
||||
},
|
||||
{
|
||||
type: 'arc',
|
||||
center: new Vec2d(radius, radius),
|
||||
center: new Vec(radius, radius),
|
||||
startAngle: PI / 2,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
type: 'straight',
|
||||
start: new Vec2d(width, radius),
|
||||
delta: new Vec2d(0, 1),
|
||||
start: new Vec(width, radius),
|
||||
delta: new Vec(0, 1),
|
||||
},
|
||||
{
|
||||
type: 'arc',
|
||||
center: new Vec2d(radius, height - radius),
|
||||
center: new Vec(radius, height - radius),
|
||||
startAngle: 0,
|
||||
},
|
||||
{
|
||||
type: 'straight',
|
||||
start: new Vec2d(0, height - radius),
|
||||
delta: new Vec2d(0, -1),
|
||||
start: new Vec(0, height - radius),
|
||||
delta: new Vec(0, -1),
|
||||
},
|
||||
{
|
||||
type: 'arc',
|
||||
center: new Vec2d(radius, radius),
|
||||
center: new Vec(radius, radius),
|
||||
startAngle: PI,
|
||||
},
|
||||
]
|
||||
|
||||
let sectionOffset = 0
|
||||
|
||||
const points: Vec2d[] = []
|
||||
const points: Vec[] = []
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
const section = sections[0]
|
||||
if (section.type === 'straight') {
|
||||
points.push(Vec2d.Add(section.start, Vec2d.Mul(section.delta, sectionOffset)))
|
||||
points.push(Vec.Add(section.start, Vec.Mul(section.delta, sectionOffset)))
|
||||
} else {
|
||||
points.push(
|
||||
getPointOnCircle(
|
||||
|
@ -159,12 +159,12 @@ export function getCloudArcs(
|
|||
// in at the bottom-right and the top-left looks relatively stable
|
||||
const wiggledPoints = bumpPoints.slice(0)
|
||||
for (let i = 0; i < Math.floor(numBumps / 2); i++) {
|
||||
wiggledPoints[i] = Vec2d.AddXY(
|
||||
wiggledPoints[i] = Vec.AddXY(
|
||||
wiggledPoints[i],
|
||||
getRandom() * maxWiggleX,
|
||||
getRandom() * maxWiggleY
|
||||
)
|
||||
wiggledPoints[numBumps - i - 1] = Vec2d.AddXY(
|
||||
wiggledPoints[numBumps - i - 1] = Vec.AddXY(
|
||||
wiggledPoints[numBumps - i - 1],
|
||||
getRandom() * maxWiggleX,
|
||||
getRandom() * maxWiggleY
|
||||
|
@ -180,17 +180,17 @@ export function getCloudArcs(
|
|||
const leftPoint = bumpPoints[i]
|
||||
const rightPoint = bumpPoints[j]
|
||||
|
||||
const midPoint = Vec2d.Average([leftPoint, rightPoint])
|
||||
const offsetAngle = Vec2d.Angle(leftPoint, rightPoint) - Math.PI / 2
|
||||
const midPoint = Vec.Average([leftPoint, rightPoint])
|
||||
const offsetAngle = Vec.Angle(leftPoint, rightPoint) - Math.PI / 2
|
||||
// when the points are on the curvy part of a pill, there is a natural arc that we need to extends past
|
||||
// otherwise it looks like the bumps get less bumpy on the curvy parts
|
||||
const distanceBetweenOriginalPoints = Vec2d.Dist(leftPoint, rightPoint)
|
||||
const distanceBetweenOriginalPoints = Vec.Dist(leftPoint, rightPoint)
|
||||
const curvatureOffset = distanceBetweenPointsOnPerimeter - distanceBetweenOriginalPoints
|
||||
const distanceBetweenWigglePoints = Vec2d.Dist(leftWigglePoint, rightWigglePoint)
|
||||
const distanceBetweenWigglePoints = Vec.Dist(leftWigglePoint, rightWigglePoint)
|
||||
const relativeSize = distanceBetweenWigglePoints / distanceBetweenOriginalPoints
|
||||
const finalDistance = (Math.max(paddingX, paddingY) + curvatureOffset) * relativeSize
|
||||
|
||||
const arcPoint = Vec2d.Add(midPoint, Vec2d.FromAngle(offsetAngle, finalDistance))
|
||||
const arcPoint = Vec.Add(midPoint, Vec.FromAngle(offsetAngle, finalDistance))
|
||||
if (arcPoint.x < 0) {
|
||||
arcPoint.x = 0
|
||||
} else if (arcPoint.x > width) {
|
||||
|
@ -203,8 +203,8 @@ export function getCloudArcs(
|
|||
}
|
||||
|
||||
const center = getCenterOfCircleGivenThreePoints(leftWigglePoint, rightWigglePoint, arcPoint)
|
||||
const radius = Vec2d.Dist(
|
||||
center ? center : Vec2d.Average([leftWigglePoint, rightWigglePoint]),
|
||||
const radius = Vec.Dist(
|
||||
center ? center : Vec.Average([leftWigglePoint, rightWigglePoint]),
|
||||
leftWigglePoint
|
||||
)
|
||||
|
||||
|
@ -221,14 +221,14 @@ export function getCloudArcs(
|
|||
}
|
||||
|
||||
type Arc = {
|
||||
leftPoint: Vec2d
|
||||
rightPoint: Vec2d
|
||||
arcPoint: Vec2d
|
||||
center: Vec2d | null
|
||||
leftPoint: Vec
|
||||
rightPoint: Vec
|
||||
arcPoint: Vec
|
||||
center: Vec | null
|
||||
radius: number
|
||||
}
|
||||
|
||||
function getCenterOfCircleGivenThreePoints(a: Vec2d, b: Vec2d, c: Vec2d) {
|
||||
function getCenterOfCircleGivenThreePoints(a: Vec, b: Vec, c: Vec) {
|
||||
const A = a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y
|
||||
const B =
|
||||
(a.x * a.x + a.y * a.y) * (c.y - b.y) +
|
||||
|
@ -247,7 +247,7 @@ function getCenterOfCircleGivenThreePoints(a: Vec2d, b: Vec2d, c: Vec2d) {
|
|||
return null
|
||||
}
|
||||
|
||||
return new Vec2d(x, y)
|
||||
return new Vec(x, y)
|
||||
}
|
||||
|
||||
export function cloudOutline(
|
||||
|
@ -256,7 +256,7 @@ export function cloudOutline(
|
|||
seed: string,
|
||||
size: TLDefaultSizeStyle
|
||||
) {
|
||||
const path: Vec2d[] = []
|
||||
const path: Vec[] = []
|
||||
|
||||
const arcs = getCloudArcs(width, height, seed, size)
|
||||
|
||||
|
@ -316,12 +316,10 @@ export function inkyCloudSvgPath(
|
|||
}
|
||||
const arcs = getCloudArcs(width, height, seed, size)
|
||||
const avgArcLength =
|
||||
arcs.reduce((sum, arc) => sum + Vec2d.Dist(arc.leftPoint, arc.rightPoint), 0) / arcs.length
|
||||
arcs.reduce((sum, arc) => sum + Vec.Dist(arc.leftPoint, arc.rightPoint), 0) / arcs.length
|
||||
const shouldMutatePoints = avgArcLength > mutMultiplier * 15
|
||||
|
||||
const mutPoint = shouldMutatePoints
|
||||
? (p: Vec2d) => new Vec2d(mut(p.x), mut(p.y))
|
||||
: (p: Vec2d) => p
|
||||
const mutPoint = shouldMutatePoints ? (p: Vec) => new Vec(mut(p.x), mut(p.y)) : (p: Vec) => p
|
||||
let pathA = `M${toDomPrecision(arcs[0].leftPoint.x)},${toDomPrecision(arcs[0].leftPoint.y)}`
|
||||
let leftMutPoint = mutPoint(arcs[0].leftPoint)
|
||||
let pathB = `M${toDomPrecision(leftMutPoint.x)},${toDomPrecision(leftMutPoint.y)}`
|
||||
|
@ -348,7 +346,7 @@ export function inkyCloudSvgPath(
|
|||
leftMutPoint = rightMutPoint
|
||||
continue
|
||||
}
|
||||
const mutRadius = Math.abs(Vec2d.Dist(mutCenter, leftMutPoint))
|
||||
const mutRadius = Math.abs(Vec.Dist(mutCenter, leftMutPoint))
|
||||
|
||||
pathB += ` A${toDomPrecision(mutRadius)},${toDomPrecision(
|
||||
mutRadius
|
||||
|
@ -360,19 +358,19 @@ export function inkyCloudSvgPath(
|
|||
}
|
||||
|
||||
export function pointsOnArc(
|
||||
startPoint: Vec2dModel,
|
||||
endPoint: Vec2dModel,
|
||||
center: Vec2dModel | null,
|
||||
startPoint: VecModel,
|
||||
endPoint: VecModel,
|
||||
center: VecModel | null,
|
||||
radius: number,
|
||||
numPoints: number
|
||||
): Vec2d[] {
|
||||
): Vec[] {
|
||||
if (center === null) {
|
||||
return [Vec2d.From(startPoint), Vec2d.From(endPoint)]
|
||||
return [Vec.From(startPoint), Vec.From(endPoint)]
|
||||
}
|
||||
const results: Vec2d[] = []
|
||||
const results: Vec[] = []
|
||||
|
||||
const startAngle = Vec2d.Angle(center, startPoint)
|
||||
const endAngle = Vec2d.Angle(center, endPoint)
|
||||
const startAngle = Vec.Angle(center, startPoint)
|
||||
const endAngle = Vec.Angle(center, endPoint)
|
||||
|
||||
const l = clockwiseAngleDist(startAngle, endAngle)
|
||||
|
||||
|
@ -386,6 +384,6 @@ export function pointsOnArc(
|
|||
return results
|
||||
}
|
||||
|
||||
function isLeft(a: Vec2d, b: Vec2d, c: Vec2d) {
|
||||
function isLeft(a: Vec, b: Vec, c: Vec) {
|
||||
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) > 0
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
TLDefaultColorTheme,
|
||||
TLGeoShape,
|
||||
TLShapeId,
|
||||
Vec2d,
|
||||
Vec,
|
||||
canonicalizeRotation,
|
||||
} from '@tldraw/editor'
|
||||
import * as React from 'react'
|
||||
|
@ -40,10 +40,10 @@ export const DashStyleCloud = React.memo(function DashStylePolygon({
|
|||
const arcLength = center
|
||||
? radius *
|
||||
canonicalizeRotation(
|
||||
canonicalizeRotation(Vec2d.Angle(center, rightPoint)) -
|
||||
canonicalizeRotation(Vec2d.Angle(center, leftPoint))
|
||||
canonicalizeRotation(Vec.Angle(center, rightPoint)) -
|
||||
canonicalizeRotation(Vec.Angle(center, leftPoint))
|
||||
)
|
||||
: Vec2d.Dist(leftPoint, rightPoint)
|
||||
: Vec.Dist(leftPoint, rightPoint)
|
||||
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
arcLength,
|
||||
|
@ -100,10 +100,10 @@ export function DashStyleCloudSvg({
|
|||
const arcLength = center
|
||||
? radius *
|
||||
canonicalizeRotation(
|
||||
canonicalizeRotation(Vec2d.Angle(center, rightPoint)) -
|
||||
canonicalizeRotation(Vec2d.Angle(center, leftPoint))
|
||||
canonicalizeRotation(Vec.Angle(center, rightPoint)) -
|
||||
canonicalizeRotation(Vec.Angle(center, leftPoint))
|
||||
)
|
||||
: Vec2d.Dist(leftPoint, rightPoint)
|
||||
: Vec.Dist(leftPoint, rightPoint)
|
||||
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(arcLength, strokeWidth, {
|
||||
style: dash,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TLDefaultColorTheme, TLGeoShape, Vec2d, VecLike } from '@tldraw/editor'
|
||||
import { TLDefaultColorTheme, TLGeoShape, Vec, VecLike } from '@tldraw/editor'
|
||||
import * as React from 'react'
|
||||
import {
|
||||
ShapeFill,
|
||||
|
@ -31,7 +31,7 @@ export const DashStylePolygon = React.memo(function DashStylePolygon({
|
|||
const A = outline[i]
|
||||
const B = outline[(i + 1) % outline.length]
|
||||
|
||||
const dist = Vec2d.Dist(A, B)
|
||||
const dist = Vec.Dist(A, B)
|
||||
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(dist, strokeWidth, {
|
||||
style: dash,
|
||||
|
@ -53,7 +53,7 @@ export const DashStylePolygon = React.memo(function DashStylePolygon({
|
|||
})}
|
||||
{lines &&
|
||||
lines.map(([A, B], i) => {
|
||||
const dist = Vec2d.Dist(A, B)
|
||||
const dist = Vec.Dist(A, B)
|
||||
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(dist, strokeWidth, {
|
||||
style: dash,
|
||||
|
@ -102,7 +102,7 @@ export function DashStylePolygonSvg({
|
|||
const A = outline[i]
|
||||
const B = outline[(i + 1) % outline.length]
|
||||
|
||||
const dist = Vec2d.Dist(A, B)
|
||||
const dist = Vec.Dist(A, B)
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(dist, strokeWidth, {
|
||||
style: dash,
|
||||
})
|
||||
|
@ -120,7 +120,7 @@ export function DashStylePolygonSvg({
|
|||
|
||||
if (lines) {
|
||||
for (const [A, B] of lines) {
|
||||
const dist = Vec2d.Dist(A, B)
|
||||
const dist = Vec.Dist(A, B)
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(dist, strokeWidth, {
|
||||
style: dash,
|
||||
start: 'skip',
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
EASINGS,
|
||||
HALF_PI,
|
||||
PI2,
|
||||
TAU,
|
||||
TLDefaultColorTheme,
|
||||
TLGeoShape,
|
||||
TLShapeId,
|
||||
Vec2d,
|
||||
Vec,
|
||||
getSvgPathFromPoints,
|
||||
perimeterOfEllipse,
|
||||
rng,
|
||||
|
@ -98,10 +98,10 @@ export function getEllipseStrokePoints(
|
|||
const ry = height / 2
|
||||
const perimeter = perimeterOfEllipse(rx, ry)
|
||||
|
||||
const points: Vec2d[] = []
|
||||
const points: Vec[] = []
|
||||
|
||||
const start = PI2 * getRandom()
|
||||
const length = PI2 + TAU / 2 + Math.abs(getRandom()) * TAU
|
||||
const length = PI2 + HALF_PI / 2 + Math.abs(getRandom()) * HALF_PI
|
||||
const count = Math.max(16, perimeter / 10)
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
|
@ -110,7 +110,7 @@ export function getEllipseStrokePoints(
|
|||
const c = Math.cos(r)
|
||||
const s = Math.sin(r)
|
||||
points.push(
|
||||
new Vec2d(
|
||||
new Vec(
|
||||
rx * c + width * 0.5 + 0.05 * getRandom(),
|
||||
ry * s + height / 2 + 0.05 * getRandom(),
|
||||
Math.min(
|
||||
|
|
|
@ -88,14 +88,14 @@ export function DrawStylePolygonSvg({
|
|||
}
|
||||
|
||||
// function getPolygonDrawPoints(id: string, outline: VecLike[], strokeWidth: number) {
|
||||
// const points: Vec2d[] = []
|
||||
// const points: Vec[] = []
|
||||
|
||||
// const getRandom = rng(id)
|
||||
|
||||
// const start = Math.round(Math.abs(getRandom()) * outline.length)
|
||||
|
||||
// const corners = outline.map((p) =>
|
||||
// Vec2d.AddXY(p, (getRandom() * strokeWidth) / 4, (getRandom() * strokeWidth) / 4)
|
||||
// Vec.AddXY(p, (getRandom() * strokeWidth) / 4, (getRandom() * strokeWidth) / 4)
|
||||
// )
|
||||
|
||||
// const len = corners.length
|
||||
|
@ -104,20 +104,20 @@ export function DrawStylePolygonSvg({
|
|||
// const At = corners[(start + i) % len]
|
||||
// const Bt = corners[(start + i + 1) % len]
|
||||
|
||||
// const dist = Math.min(Vec2d.Dist(At, Bt) / 2, strokeWidth / 2)
|
||||
// const A = Vec2d.Nudge(At, Bt, dist)
|
||||
// const dist = Math.min(Vec.Dist(At, Bt) / 2, strokeWidth / 2)
|
||||
// const A = Vec.Nudge(At, Bt, dist)
|
||||
|
||||
// const D = Vec2d.Med(At, Bt)
|
||||
// const D = Vec.Med(At, Bt)
|
||||
|
||||
// if (i === 0) {
|
||||
// Bt.z = 0.7
|
||||
// points.push(new Vec2d(D.x, D.y, 0.7), Bt)
|
||||
// points.push(new Vec(D.x, D.y, 0.7), Bt)
|
||||
// } else if (i === outline.length) {
|
||||
// const lastSegPoints = Vec2d.PointsBetween(A, D, 4)
|
||||
// const lastSegPoints = Vec.PointsBetween(A, D, 4)
|
||||
// lastSegPoints.forEach((p) => (p.z = 0.7))
|
||||
// points.push(...lastSegPoints)
|
||||
// } else {
|
||||
// points.push(...Vec2d.PointsBetween(A, Bt, 6))
|
||||
// points.push(...Vec.PointsBetween(A, Bt, 6))
|
||||
// }
|
||||
// }
|
||||
|
||||
|
@ -199,8 +199,8 @@ export function DrawStylePolygonSvg({
|
|||
|
||||
// ox = random() * offset
|
||||
// oy = random() * offset
|
||||
// const c1 = Vec2d.Lrp(p0, p1, 0.25)
|
||||
// const c2 = Vec2d.Lrp(p0, p1, 0.75)
|
||||
// const c1 = Vec.Lrp(p0, p1, 0.25)
|
||||
// const c2 = Vec.Lrp(p0, p1, 0.75)
|
||||
|
||||
// polylineB += `C${c1.x + ox},${c1.y + oy} ${c2.x - ox},${c2.y - oy} ${p1.x},${p1.y}`
|
||||
// }
|
||||
|
@ -224,9 +224,9 @@ export function DrawStylePolygonSvg({
|
|||
// for (let i = 0, n = len * 2; i < n; i++) {
|
||||
// p0 = outline[i % len]
|
||||
// p1 = outline[(i + 1) % len]
|
||||
// const dist = Vec2d.Dist(p0, p1)
|
||||
// const dist = Vec.Dist(p0, p1)
|
||||
|
||||
// const c1 = Vec2d.Lrp(p0, p1, 0.5 + random() / 2)
|
||||
// const c1 = Vec.Lrp(p0, p1, 0.5 + random() / 2)
|
||||
// polylineA += `${c1.x + random() * Math.min(dist / 10, offset)},${
|
||||
// c1.y + random() * Math.min(dist / 10, offset)
|
||||
// } ${p1.x + (random() * offset) / 2},${p1.y + (random() * offset) / 2} `
|
||||
|
@ -249,9 +249,9 @@ export function DrawStylePolygonSvg({
|
|||
// for (let i = 0, n = len * 2; i < n; i++) {
|
||||
// p0 = outline[i % len]
|
||||
// p1 = outline[(i + 1) % len]
|
||||
// const dist = Vec2d.Dist(p0, p1)
|
||||
// const dist = Vec.Dist(p0, p1)
|
||||
|
||||
// const c1 = Vec2d.Lrp(p0, p1, 0.5 + random() / 2)
|
||||
// const c1 = Vec.Lrp(p0, p1, 0.5 + random() / 2)
|
||||
// polylineA += `${c1.x + random() * Math.min(dist / 10, offset)},${
|
||||
// c1.y + random() * Math.min(dist / 10, offset)
|
||||
// } ${p1.x + (random() * offset) / 2},${p1.y + (random() * offset) / 2} `
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import {
|
||||
Box2d,
|
||||
Box,
|
||||
GeoShapeGeoStyle,
|
||||
StateNode,
|
||||
TLEventHandlers,
|
||||
TLGeoShape,
|
||||
createShapeId,
|
||||
getStarBounds,
|
||||
} from '@tldraw/editor'
|
||||
|
||||
export class Pointing extends StateNode {
|
||||
|
@ -93,10 +92,10 @@ export class Pointing extends StateNode {
|
|||
|
||||
const bounds =
|
||||
shape.props.geo === 'star'
|
||||
? getStarBounds(5, 200, 200)
|
||||
? new Box(0, 0, 200, 190)
|
||||
: shape.props.geo === 'cloud'
|
||||
? new Box2d(0, 0, 300, 180)
|
||||
: new Box2d(0, 0, 200, 200)
|
||||
? new Box(0, 0, 300, 180)
|
||||
: new Box(0, 0, 200, 200)
|
||||
|
||||
const delta = bounds.center
|
||||
const parentTransform = this.editor.getShapeParentTransform(shape)
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
TLImageShape,
|
||||
TLOnDoubleClickHandler,
|
||||
TLShapePartial,
|
||||
Vec2d,
|
||||
Vec,
|
||||
deepCopy,
|
||||
imageShapeMigrations,
|
||||
imageShapeProps,
|
||||
|
@ -190,10 +190,10 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
|
|||
const croppedHeight = (crop.bottomRight.y - crop.topLeft.y) * height
|
||||
|
||||
const points = [
|
||||
new Vec2d(0, 0),
|
||||
new Vec2d(croppedWidth, 0),
|
||||
new Vec2d(croppedWidth, croppedHeight),
|
||||
new Vec2d(0, croppedHeight),
|
||||
new Vec(0, 0),
|
||||
new Vec(croppedWidth, 0),
|
||||
new Vec(croppedWidth, croppedHeight),
|
||||
new Vec(0, croppedHeight),
|
||||
]
|
||||
|
||||
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')
|
||||
|
@ -261,7 +261,7 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
|
|||
const w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w
|
||||
const h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h
|
||||
|
||||
const pointDelta = new Vec2d(crop.topLeft.x * w, crop.topLeft.y * h).rot(shape.rotation)
|
||||
const pointDelta = new Vec(crop.topLeft.x * w, crop.topLeft.y * h).rot(shape.rotation)
|
||||
|
||||
const partial: TLShapePartial<TLImageShape> = {
|
||||
id: shape.id,
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
TLLineShape,
|
||||
TLOnHandleChangeHandler,
|
||||
TLOnResizeHandler,
|
||||
Vec2d,
|
||||
Vec,
|
||||
WeakMapCache,
|
||||
deepCopy,
|
||||
getDefaultColorTheme,
|
||||
|
@ -408,7 +408,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
/** @public */
|
||||
export function getGeometryForLineShape(shape: TLLineShape): CubicSpline2d | Polyline2d {
|
||||
const { spline, handles } = shape.props
|
||||
const handlePoints = Object.values(handles).sort(sortByIndex).map(Vec2d.From)
|
||||
const handlePoints = Object.values(handles).sort(sortByIndex).map(Vec.From)
|
||||
|
||||
switch (spline) {
|
||||
case 'cubic': {
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
CubicSpline2d,
|
||||
Edge2d,
|
||||
Polyline2d,
|
||||
Vec2d,
|
||||
Vec,
|
||||
toDomPrecision,
|
||||
} from '@tldraw/editor'
|
||||
|
||||
|
@ -20,7 +20,7 @@ export function getSvgPathForEdge(edge: Edge2d, first: boolean) {
|
|||
export function getSvgPathForBezierCurve(curve: CubicBezier2d, first: boolean) {
|
||||
const { a, b, c, d } = curve
|
||||
|
||||
if (Vec2d.Equals(a, d)) return ''
|
||||
if (Vec.Equals(a, d)) return ''
|
||||
|
||||
return `${first ? `M${toDomPrecision(a.x)},${toDomPrecision(a.y)}` : ``}C${toDomPrecision(
|
||||
b.x
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
Matrix2d,
|
||||
Mat,
|
||||
StateNode,
|
||||
TLEventHandlers,
|
||||
TLHandle,
|
||||
TLInterruptEvent,
|
||||
TLLineShape,
|
||||
TLShapeId,
|
||||
Vec2d,
|
||||
Vec,
|
||||
createShapeId,
|
||||
getIndexAbove,
|
||||
last,
|
||||
|
@ -45,18 +45,18 @@ export class Pointing extends StateNode {
|
|||
const endHandle = vertexHandles[vertexHandles.length - 1]
|
||||
const prevEndHandle = vertexHandles[vertexHandles.length - 2]
|
||||
|
||||
const shapePagePoint = Matrix2d.applyToPoint(
|
||||
const shapePagePoint = Mat.applyToPoint(
|
||||
this.editor.getShapeParentTransform(this.shape)!,
|
||||
new Vec2d(this.shape.x, this.shape.y)
|
||||
new Vec(this.shape.x, this.shape.y)
|
||||
)
|
||||
|
||||
let nextEndHandleIndex: string, nextEndHandleId: string, nextEndHandle: TLHandle
|
||||
|
||||
const nextPoint = Vec2d.Sub(currentPagePoint, shapePagePoint)
|
||||
const nextPoint = Vec.Sub(currentPagePoint, shapePagePoint)
|
||||
|
||||
if (
|
||||
Vec2d.Dist(endHandle, prevEndHandle) < MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES ||
|
||||
Vec2d.Dist(nextPoint, endHandle) < MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES
|
||||
Vec.Dist(endHandle, prevEndHandle) < MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES ||
|
||||
Vec.Dist(nextPoint, endHandle) < MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES
|
||||
) {
|
||||
// If the end handle is too close to the previous end handle, we'll just extend the previous end handle
|
||||
nextEndHandleIndex = endHandle.index
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue