Skip to content

Commit

Permalink
use catmull-rom algorithm for drawing gesture
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmtimbo committed Nov 7, 2024
1 parent 92300ab commit 90f5a3f
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 31 deletions.
8 changes: 1 addition & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@
"fuzzy": "0.1.3",
"glob": "7.1.6",
"http-errors": "1.8.0",
"jsdom": "24.0.0",
"perfect-freehand": "1.0.16"
"jsdom": "24.0.0"
},
"devDependencies": {
"@types/chrome": "0.0.178",
Expand Down
40 changes: 19 additions & 21 deletions src/client/render/attachGesture.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import { getStroke } from 'perfect-freehand'
import { System } from '../../system'
import { Unlisten } from '../../types/Unlisten'
import { namespaceURI } from '../component/namespaceURI'
import { _addEventListener } from '../event'
import { UnitPointerEvent } from '../event/pointer'
import { catmullRomSplineSegment } from '../util/geometry'
import { Point } from '../util/geometry/types'

function getSvgPathFromStroke(stroke): string {
function pathFromSpline(stroke: number[][]): string {
if (!stroke.length) {
return ''
}

const d = stroke.reduce(
(acc, [x0, y0], i, arr) => {
const [x1, y1] = arr[(i + 1) % arr.length]
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2)
return acc
},
['M', ...stroke[0], 'Q']
)
let d = `M ${stroke[0][0]} ${stroke[0][1]}`

d.push('Z')
for (let i = 1; i < stroke.length; i++) {
d = d + ` L ${stroke[i][0]} ${stroke[i][1]}`
}

return d.join(' ')
return d
}

const STROKE_OPT = {
Expand Down Expand Up @@ -60,26 +55,27 @@ export function attachGesture(system: System): void {
} = {},
callback: (event: PointerEvent, track: Point[]) => void
): Unlisten => {
const { pointerId, screenX, screenY, pageX, pageY } = event

// svg.setPointerCapture(pointerId)
const { pointerId, pageX, pageY } = event

const path = createElementNS(namespaceURI, 'path')

svg.appendChild(path)

const { lineWidth = 2, strokeStyle = '#d1d1d1' } = opt
const { lineWidth = 10, strokeStyle = '#d1d1d1' } = opt

const color = strokeStyle

path.style.stroke = color
path.style.strokeWidth = `${lineWidth}px`
path.style.fill = color
path.style.fill = 'none'
path.style.strokeLinecap = 'round'

let active = true

const track: Point[] = [{ x: pageX, y: pageY }]

let d = ''

const pointerMoveListener = (_event: PointerEvent) => {
// console.log('attachGesture', 'pointerMoveListener')

Expand All @@ -88,13 +84,15 @@ export function attachGesture(system: System): void {
if (_pointerId === pointerId) {
const { pageX, pageY } = _event

const outline = getStroke(track, STROKE_OPT)
track.push({ x: pageX, y: pageY })

const d = getSvgPathFromStroke(outline)
if (track.length > 3) {
const segment = catmullRomSplineSegment(track.slice(-4))

path.setAttribute('d', d)
d += pathFromSpline(segment)
}

track.push({ x: pageX, y: pageY })
path.setAttribute('d', d)
}
}

Expand Down
81 changes: 81 additions & 0 deletions src/client/util/geometry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,3 +773,84 @@ export const roundPoint = (position: Position): Position => {
y: Math.round(position.y),
}
}

export function catmullRom(
p0: number,
p1: number,
p2: number,
p3: number,
t: number
): number {
const tensionFactor = 0.5
const startingPointWeight = 2
const linearTerm1 = -1
const linearTerm2 = 1
const quadraticTerm1 = 2
const quadraticTerm2 = -5
const quadraticTerm3 = 4
const quadraticTerm4 = -1
const cubicTerm1 = -1
const cubicTerm2 = 3
const cubicTerm3 = -3
const cubicTerm4 = 1

const t2 = t * t
const t3 = t2 * t

return (
tensionFactor *
(startingPointWeight * p1 +
(linearTerm1 * p0 + linearTerm2 * p2) * t +
(quadraticTerm1 * p0 +
quadraticTerm2 * p1 +
quadraticTerm3 * p2 +
quadraticTerm4 * p3) *
t2 +
(cubicTerm1 * p0 + cubicTerm2 * p1 + cubicTerm3 * p2 + cubicTerm4 * p3) *
t3)
)
}

export function catmullRomSpline(points: Point[]): number[][] {
if (points.length < 4) return

const spline: number[][] = []

spline.push([points[0].x, points[0].y])

for (let i = 0; i < points.length - 3; i++) {
const fourPoints = points.slice(i, i + 4)

const segment = catmullRomSplineSegment(fourPoints)

for (let i = 0; i < segment.length; i++) {
spline.push(segment[i])
}
}

return spline
}

export function catmullRomSplineSegment(
lastFourPoints: Point[],
step: number = 0.1
): number[][] {
if (lastFourPoints.length < 4) {
return []
}

const spline: number[][] = []

spline.push([lastFourPoints[0].x, lastFourPoints[0].y])

const [p0, p1, p2, p3] = lastFourPoints

for (let t = 0; t <= 1; t += step) {
const x = catmullRom(p0.x, p1.x, p2.x, p3.x, t)
const y = catmullRom(p0.y, p1.y, p2.y, p3.y, t)

spline.push([x, y])
}

return spline
}
2 changes: 1 addition & 1 deletion src/system/platform/component/app/Editor/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52614,7 +52614,7 @@ export class Editor_ extends Element<HTMLDivElement, Props_> {

const color = this._get_color()
const strokeStyle = getThemeModeColor($theme, this._mode, color)
const lineWidth = 2
const lineWidth = 4

const unlisten = captureGesture(
event,
Expand Down

0 comments on commit 90f5a3f

Please sign in to comment.