Conversation
In order to fix over-softened corners and end artifacts when shift-clicking to draw straight line segments, this PR adds an easing parameter to `Vec.PointsBetween` and reworks the straight segment interpolation in `getPointsFromDrawSegment`. <img width="1348" height="990" alt="image" src="https://github.com/user-attachments/assets/f16522d7-92e9-432f-ae07-d1233ae5cb55" /> Closes #7867 ### Change type - [x] `bugfix` ### Test plan 1. Select the draw tool 2. Click to start, then hold shift and click at several points to create straight line segments 3. Verify corners between segments are sharper and endpoints don't have rendering artifacts 4. Verify freehand drawing still works normally ### Release notes - Fix over-softened corners when shift-clicking to draw straight line segments ### API changes - Changed `Vec.PointsBetween()` to accept an optional `ease` parameter for custom easing curves <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes stroke interpolation math for straight draw segments and updates a shared `Vec.PointsBetween` API, which could subtly affect rendering/pressure profiles across call sites. > > **Overview** > Improves rendering of shift-click straight draw segments by changing how points are interpolated in `getPointsFromDrawSegment`: straight segments now add small endpoint nudges and interpolate between inner points with `EASINGS.easeInOutCubic` to reduce softened corners and end artifacts. > > Extends `Vec.PointsBetween` to accept an optional `ease` function (defaulting to `EASINGS.easeInQuad`) and updates its internals to use the provided easing for position interpolation while keeping pressure (`z`) simulation based on `easeInOutQuad`; the API report is updated accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d527883. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Max Drake <maxdrake46@gmail.com>
In order to improve pointer event throughput during hit testing, this PR reduces allocations, unnecessary square roots, and function call overhead across the geometry and intersection primitives. These are all hot paths called 2-4x per shape on every pointer move via `Editor.getShapeAtPoint()`. ### Change type - [x] `improvement` ### Benchmark results (original → optimized) | Function | Before (M ops/s) | After (M ops/s) | Speedup | |---|---|---|---| | `Vec.NearestPointOnLineSegment` | 26.6 | 44.1 | **1.7x** | | `Vec.DistanceToLineSegment` | 25.5 | 43.9 | **1.7x** | | `Edge2d.nearestPoint` | 34.3 | 42.7 | **1.2x** | | `Edge2d.distanceToPoint` | 25.6 | 48.6 | **1.9x** | | `Circle2d.distanceToPoint` | 2.6 | 49.1 | **19x** | | `Circle2d.hitTestPoint` | 2.8 | 48.7 | **17x** | | `Arc2d.nearestPoint` | 13.2 | 20.5 | **1.6x** | | `Polyline2d.nearestPoint` | 2.6 | 17.6 | **6.8x** | | `Polyline2d.distanceToPoint` | 2.6 | 13.1 | **5x** | | `Rect.hitTestPoint (outside)` | 5.1 | 23.6 | **4.6x** | | `Rect.hitTestPoint (inside)` | 9.6 | 23.3 | **2.4x** | | `Rect.distanceToPoint` | 4.8-5.2 | 21.6-22.2 | **4.3x** | | `intersectLineSegmentLineSegment (hit)` | 13.3 | 37.7 | **2.8x** | | `intersectLineSegmentLineSegment (miss)` | 21.7 | 43.5 | **2.0x** | | `intersectLineSegmentCircle (hit)` | 19.5 | 22.0 | **1.13x** | ### What changed **Vec.ts** — Rewrite `NearestPointOnLineSegment` and `DistanceToLineSegment` with parametric t-projection (no intermediate Vec allocations). Inline `Lrp`, `NearestPointOnLineThroughPoint`, `DistanceToLineThroughPoint`, and `AngleBetween`. **Edge2d** — Precompute `dx`, `dy`, `len2` in constructor. Rewrite `nearestPoint` with parametric clamping. Add `distanceToPoint` override (0 allocs). **Circle2d** — Inline `nearestPoint` (1 alloc, 1 sqrt). Add `distanceToPoint` override (0 allocs). Add `hitTestPoint` override (0 allocs, 0 sqrts). **Arc2d** — Remove unnecessary A/B/P distance loop when t is in range. **Polyline2d** — Inline edge math in `nearestPoint` (N allocs → 1). Add `distanceToPoint` and `hitTestPoint` overrides using scalar fast path. **CubicBezier2d / CubicSpline2d / Ellipse2d / Stadium2d** — Add `distanceToPoint` overrides using Edge2d's scalar fast path. **pointInPolygon** — Remove `Vec.Dist(A,a) + Vec.Dist(A,b) === Vec.Dist(a,b)` edge check (3 sqrts/vertex, exact float equality never fires). **intersect.ts** — Inline `approximately`/`approximatelyLte` in `intersectLineSegmentLineSegment` (7+ function calls → inline comparisons). Simplify `intersectLineSegmentCircle` with precomputed dx/dy. ### API changes - Added `Circle2d.distanceToPoint()`, `Circle2d.hitTestPoint()` - Added `Polyline2d.distanceToPoint()`, `Polyline2d.hitTestPoint()` - Added `CubicBezier2d.distanceToPoint()`, `CubicSpline2d.distanceToPoint()` - Added `Ellipse2d.distanceToPoint()`, `Stadium2d.distanceToPoint()` ### Code changes | Section | LOC change | |---|---| | Core code | +272 / -95 | | Tests | +179 / -0 | | Automated files | +18 / -0 | ### Test plan 1. Draw shapes of all types, verify hit testing works correctly 2. Test selection, hovering, brush selection with various shape types 3. Verify arrow binding snaps to shapes properly - [x] Unit tests (764 editor + 131 tldraw pass) - [x] Vitest benchmarks ### Release notes - Improve hit testing performance across all geometry types (up to 19x faster for circles, 5-7x for polylines/rectangles, 2-3x for line segment intersections) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches core geometry hit-testing math (distance/nearest-point/intersections) across multiple primitives, so subtle numerical/edge-case regressions are possible despite being performance-oriented. > > **Overview** > **Optimizes geometry and intersection hot paths used during hit testing** by rewriting vector/segment math to use scalar projections and fewer allocations/square roots. > > Adds fast-path `distanceToPoint`/`hitTestPoint` overrides for key shapes (`Circle2d`, `Polyline2d`, `Edge2d`, plus `CubicBezier2d`, `CubicSpline2d`, `Ellipse2d`, `Stadium2d`) and simplifies `Arc2d.nearestPoint` and segment/circle & segment/segment intersection computations. > > Cleans up `pointInPolygon` to remove an expensive edge-distance equality check, updates the public API report accordingly, and introduces a new `vitest` benchmark suite (`geometry.bench.ts`) to track these performance-critical operations. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1f53610. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ditor-controller package (#7952) In order to make the imperative editor-driving API available for REPL, agent/MCP, scripting, and automation use cases (not just tests), this PR extracts the framework-agnostic imperative surface from `TestEditor` into a new `@tldraw/editor-controller` package. Closes #7950. The `EditorController` class wraps an `Editor` instance (composition, not inheritance) and provides: - **Event dispatch** — `pointerMove/Down/Up`, `click`, `rightClick`, `doubleClick`, `keyPress/Down/Up/Repeat`, `wheel`, `pan`, `pinchStart/To/End`, `forceTick` - **Selection transforms** — `rotateSelection`, `translateSelection`, `resizeSelection` - **Clipboard** — `copy`, `cut`, `paste` (internal, not browser) - **Shape queries** — `getViewportPageCenter`, `getSelectionPageCenter`, `getPageCenter`, `getPageRotation(ById)`, `getArrowsBoundTo` - **ID helpers** — `testShapeID`, `testPageID` All methods use only public Editor APIs and return `this` for fluent chaining. `TestEditor` now delegates all extracted methods to a composed `EditorController` instance while keeping test-specific concerns (vitest mocks, assertions, DOM setup, `createShapesFromJsx`) local. A **Scripter example** is included to demonstrate the package: a Scripter-style scripting environment with a resizable script list, code editor, run button (⌘+Enter), and console output panel. Ships with starter scripts for creating shapes, translating selections, randomizing colors, and more. ### Change type - [x] `feature` ### Test plan - All 2020+ existing tldraw tests pass unchanged (the delegation is transparent) - Run the Scripter example at `localhost:5420` and execute starter scripts against the canvas - [x] Unit tests ### Release notes - Add `@tldraw/editor-controller` package: an imperative API for driving the tldraw editor programmatically, useful for scripting, automation, and agent workflows. - Add Scripter example demonstrating `EditorController` usage with an interactive script editor. ### API changes - Added `@tldraw/editor-controller` package with `EditorController` class, `PointerEventInit` type, and `EventModifiers` type <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new public package and rewires `TestEditor` input/clipboard helpers to delegate to it, which could subtly affect test interactions and event simulation behavior. The change is mostly additive but touches core test harness utilities used broadly across the suite. > > **Overview** > Introduces a new `@tldraw/driver` package that exposes a fluent, imperative API for driving an `Editor` (pointer/keyboard/wheel/pinch events, camera pan, clipboard ops, selection transforms, and shape/selection queries), including API extractor config/report and docs. > > Updates `tldraw` and `examples` to depend on and reference the new package, and refactors `TestEditor` to compose a `Driver` instance and delegate the previously inlined event/clipboard/selection helper methods to it. As follow-ups, a large set of tests are adjusted to stop using `test-jsx` shape creation helpers (switching to `editor.createShapes`) and to access clipboard via `getClipboard()` instead of a direct `clipboard` field, plus a few test helper imports are moved to `lib/utils/test-helpers`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 60b2432. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )