Improve rectangle rendering (#163)
This commit is contained in:
parent
db62005251
commit
01c824bbbe
3 changed files with 68 additions and 48 deletions
|
@ -1585,7 +1585,7 @@ left past the initial left edge) then swap points on that axis.
|
||||||
* @param arr
|
* @param arr
|
||||||
* @param offset
|
* @param offset
|
||||||
*/
|
*/
|
||||||
static shuffleArr<T>(arr: T[], offset: number): T[] {
|
static rotateArray<T>(arr: T[], offset: number): T[] {
|
||||||
return arr.map((_, i) => arr[(i + offset) % arr.length])
|
return arr.map((_, i) => arr[(i + offset) % arr.length])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Utils, SVGContainer, ShapeUtil } from '@tldraw/core'
|
import { Utils, SVGContainer, ShapeUtil } from '@tldraw/core'
|
||||||
import { Vec } from '@tldraw/vec'
|
import { Vec } from '@tldraw/vec'
|
||||||
import getStroke, { getStrokePoints } from 'perfect-freehand'
|
import getStroke, { getStrokeOutlinePoints, getStrokePoints } from 'perfect-freehand'
|
||||||
import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles'
|
import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles'
|
||||||
import { RectangleShape, DashStyle, TLDrawShapeType, TLDrawMeta } from '~types'
|
import { RectangleShape, DashStyle, TLDrawShapeType, TLDrawMeta } from '~types'
|
||||||
import { getBoundsRectangle, transformRectangle, transformSingleRectangle } from '../shared'
|
import { getBoundsRectangle, transformRectangle, transformSingleRectangle } from '../shared'
|
||||||
|
@ -173,70 +173,93 @@ function getRectangleDrawPoints(shape: RectangleShape) {
|
||||||
|
|
||||||
const getRandom = Utils.rng(shape.id)
|
const getRandom = Utils.rng(shape.id)
|
||||||
|
|
||||||
const strokeWidth = +styles.strokeWidth
|
const sw = styles.strokeWidth
|
||||||
|
|
||||||
const baseOffset = strokeWidth / 2
|
// Dimensions
|
||||||
|
const w = Math.max(0, shape.size[0])
|
||||||
|
const h = Math.max(0, shape.size[1])
|
||||||
|
|
||||||
const offsets = Array.from(Array(4)).map(() => [
|
// Random corner offsets
|
||||||
getRandom() * baseOffset,
|
const offsets = Array.from(Array(4)).map(() => {
|
||||||
getRandom() * baseOffset,
|
return [getRandom() * sw * 0.75, getRandom() * sw * 0.75]
|
||||||
])
|
})
|
||||||
|
|
||||||
const sw = strokeWidth
|
|
||||||
|
|
||||||
const w = Math.max(0, shape.size[0] - sw / 2)
|
|
||||||
const h = Math.max(0, shape.size[1] - sw / 2)
|
|
||||||
|
|
||||||
|
// Corners
|
||||||
const tl = Vec.add([sw / 2, sw / 2], offsets[0])
|
const tl = Vec.add([sw / 2, sw / 2], offsets[0])
|
||||||
const tr = Vec.add([w, sw / 2], offsets[1])
|
const tr = Vec.add([w - sw / 2, sw / 2], offsets[1])
|
||||||
const br = Vec.add([w, h], offsets[2])
|
const br = Vec.add([w - sw / 2, h - sw / 2], offsets[2])
|
||||||
const bl = Vec.add([sw / 2, h], offsets[3])
|
const bl = Vec.add([sw / 2, h - sw / 2], offsets[3])
|
||||||
|
|
||||||
const px = Math.max(8, Math.floor(w / 20))
|
// Which side to start drawing first
|
||||||
const py = Math.max(8, Math.floor(h / 20))
|
const rm = Math.round(Math.abs(getRandom() * 2 * 4))
|
||||||
const rm = Math.floor(5 + getRandom() * 4)
|
|
||||||
|
|
||||||
const lines = Utils.shuffleArr(
|
// Corner radii
|
||||||
|
const rx = Math.min(w / 2, sw * 2)
|
||||||
|
const ry = Math.min(h / 2, sw * 2)
|
||||||
|
|
||||||
|
// Number of points per side
|
||||||
|
const px = Math.max(8, Math.floor(w / 16))
|
||||||
|
const py = Math.max(8, Math.floor(h / 16))
|
||||||
|
|
||||||
|
// Inset each line by the corner radii and let the freehand algo
|
||||||
|
// interpolate points for the corners.
|
||||||
|
const lines = Utils.rotateArray(
|
||||||
[
|
[
|
||||||
Vec.pointsBetween(tr, br, py, EASINGS.linear),
|
Vec.pointsBetween(Vec.add(tl, [rx, 0]), Vec.sub(tr, [rx, 0]), px),
|
||||||
Vec.pointsBetween(br, bl, px, EASINGS.linear),
|
Vec.pointsBetween(Vec.add(tr, [0, ry]), Vec.sub(br, [0, ry]), py),
|
||||||
Vec.pointsBetween(bl, tl, py, EASINGS.linear),
|
Vec.pointsBetween(Vec.sub(br, [rx, 0]), Vec.add(bl, [rx, 0]), px),
|
||||||
Vec.pointsBetween(tl, tr, px, EASINGS.linear),
|
Vec.pointsBetween(Vec.sub(bl, [0, ry]), Vec.add(tl, [0, ry]), py),
|
||||||
],
|
],
|
||||||
rm
|
rm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// For the final points, include the first half of the first line again,
|
||||||
|
// so that the line wraps around and avoids ending on a sharp corner.
|
||||||
|
// This has a bit of finesse and magic—if you change the points between
|
||||||
|
// function, then you'll likely need to change this one too.
|
||||||
|
|
||||||
|
const points = [...lines.flat(), ...lines[0]].slice(
|
||||||
|
5,
|
||||||
|
Math.floor((rm % 2 === 0 ? px : py) / -2) + 3
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
points: [...lines.flat(), ...lines[0], ...lines[1]].slice(
|
points,
|
||||||
4,
|
|
||||||
Math.floor((rm % 2 === 0 ? px : py) / -2) + 2
|
|
||||||
),
|
|
||||||
edgeDistance: rm % 2 === 0 ? px : py,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRectanglePath(shape: RectangleShape) {
|
function getDrawStrokeInfo(shape: RectangleShape) {
|
||||||
const { points, edgeDistance } = getRectangleDrawPoints(shape)
|
const { points } = getRectangleDrawPoints(shape)
|
||||||
|
|
||||||
const { strokeWidth } = getShapeStyle(shape.style)
|
const { strokeWidth } = getShapeStyle(shape.style)
|
||||||
|
|
||||||
const stroke = getStroke(points, {
|
const options = {
|
||||||
size: 1 + strokeWidth,
|
size: strokeWidth,
|
||||||
thinning: 0.6,
|
thinning: 0.65,
|
||||||
end: { taper: edgeDistance },
|
streamline: 0.3,
|
||||||
start: { taper: edgeDistance },
|
smoothing: 1,
|
||||||
simulatePressure: false,
|
simulatePressure: false,
|
||||||
last: true,
|
last: true,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return { points, options }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRectanglePath(shape: RectangleShape) {
|
||||||
|
const { points, options } = getDrawStrokeInfo(shape)
|
||||||
|
|
||||||
|
const stroke = getStroke(points, options)
|
||||||
|
|
||||||
return Utils.getSvgPathFromStroke(stroke)
|
return Utils.getSvgPathFromStroke(stroke)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRectangleIndicatorPathData(shape: RectangleShape) {
|
function getRectangleIndicatorPathData(shape: RectangleShape) {
|
||||||
const { points } = getRectangleDrawPoints(shape)
|
const { points, options } = getDrawStrokeInfo(shape)
|
||||||
|
|
||||||
|
const strokePoints = getStrokePoints(points, options)
|
||||||
|
|
||||||
return Utils.getSvgPathFromStroke(
|
return Utils.getSvgPathFromStroke(
|
||||||
getStrokePoints(points).map((pt) => pt.point.slice(0, 2)),
|
strokePoints.map((pt) => pt.point.slice(0, 2)),
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -512,15 +512,12 @@ export class Vec {
|
||||||
* @param steps The number of points to return.
|
* @param steps The number of points to return.
|
||||||
* @param ease An easing function to apply to the simulated pressure.
|
* @param ease An easing function to apply to the simulated pressure.
|
||||||
*/
|
*/
|
||||||
static pointsBetween = (
|
static pointsBetween = (A: number[], B: number[], steps = 6): number[][] => {
|
||||||
A: number[],
|
return Array.from(Array(steps)).map((_, i) => {
|
||||||
B: number[],
|
const t = i / (steps - 1)
|
||||||
steps = 6,
|
const k = Math.min(1, 0.5 + Math.abs(0.5 - t))
|
||||||
ease = (t: number) => t * t * t * t
|
return [...Vec.lrp(A, B, t), k]
|
||||||
): number[][] => {
|
})
|
||||||
return Array.from(Array(steps)).map((_, i) =>
|
|
||||||
Vec.round([...Vec.lrp(A, B, i / steps), (1 - ease(i / steps)) / 2])
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue