Skip to content

Commit

Permalink
Merge pull request #10 from steveruizok/vector
Browse files Browse the repository at this point in the history
1.4.0
  • Loading branch information
steveruizok authored Apr 30, 2021
2 parents ec18541 + 1a6241f commit af0cfbf
Show file tree
Hide file tree
Showing 15 changed files with 685 additions and 169 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
node_modules
dist
example/yarn.lock

.vercel
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.4.0

- Adds `last` option.
- Adds `start` and `end` option, each with `taper` and `easing`.
- Improves cap handling.

## 0.3.5

- Improves caps.
Expand Down
26 changes: 26 additions & 0 deletions example/components/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,32 @@ export default function Controls() {
options={['linear', 'easeIn', 'easeOut', 'easeInOut']}
onChange={v => state.send('CHANGED_OPTIONS', { easing: v })}
/>
<NumberInput
value={options.taperStart}
onChange={v => state.send('CHANGED_OPTIONS', { taperStart: v })}
label="Taper Start"
min={0}
max={100}
/>
<EnumInput
label="Taper Start Easing"
value={options.taperStartEasing}
options={['linear', 'easeIn', 'easeOut', 'easeInOut']}
onChange={v => state.send('CHANGED_OPTIONS', { taperStartEasing: v })}
/>
<NumberInput
value={options.taperEnd}
onChange={v => state.send('CHANGED_OPTIONS', { taperEnd: v })}
label="Taper End"
min={0}
max={100}
/>
<EnumInput
label="Taper End Easing"
value={options.taperEndEasing}
options={['linear', 'easeIn', 'easeOut', 'easeInOut']}
onChange={v => state.send('CHANGED_OPTIONS', { taperEndEasing: v })}
/>
<ButtonGroup>
<button onClick={() => state.send('RESET_OPTIONS')}>Reset</button>
<button onClick={() => state.send('TOGGLED_CONTROLS')}>Close</button>
Expand Down
2 changes: 1 addition & 1 deletion example/hooks/useEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function useEvents() {
dx = x - pointer.x,
dy = y - pointer.y,
type = e.pointerType as 'pen' | 'mouse' | 'touch',
p = type === 'pen' ? e.pressure : 0.5
p = e.pressure

if (dx === 0 && dy === 0) return false

Expand Down
6 changes: 3 additions & 3 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
"start": "next start"
},
"dependencies": {
"@state-designer/react": "^1.4.5",
"@state-designer/react": "^1.7.1",
"next": "10.0.7",
"perfect-freehand": "^0.3.5",
"polygon-clipping": "^0.15.2",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-feather": "^2.0.9",
"styled-components": "^5.2.1",
"typescript": "^4.1.5"
"typescript": "^4.1.5",
"perfect-freehand": "latest"
},
"devDependencies": {
"@types/node": "^14.14.31",
Expand Down
4 changes: 2 additions & 2 deletions example/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ export default function Home() {
<path
key={i}
d={mark.path}
strokeWidth={2}
strokeWidth={0}
stroke={darkMode ? '#fff' : '#000'}
fill={showTrace ? 'transparent' : darkMode ? '#fff' : '#000'}
/>
))}
{currentMark && (
<path
d={currentMark.path}
strokeWidth={2}
strokeWidth={0}
stroke={darkMode ? '#fff' : '#000'}
fill={showTrace ? 'transparent' : darkMode ? '#fff' : '#000'}
/>
Expand Down
56 changes: 35 additions & 21 deletions example/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ import { Mark, ClipboardMessage } from './types'
import getStroke, { StrokeOptions } from 'perfect-freehand'
import polygonClipping from 'polygon-clipping'
import { copyToClipboard } from './utils'
import * as svg from 'svg'
import * as vec from 'vec'

function getSvgPathFromStroke(stroke: number[][]) {
if (stroke.length === 0) return ''

const d = []

let [p0, p1] = stroke
if (stroke.length < 3) {
return ''
}

let p0 = stroke[stroke.length - 3]
let p1 = stroke[stroke.length - 2]

d.push('M', p0[0], p0[1], 'Q')

for (let i = 1; i < stroke.length; i++) {
for (let i = 0; i < stroke.length; i++) {
d.push(p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2)
p0 = p1
p1 = stroke[i]
Expand Down Expand Up @@ -49,12 +54,21 @@ const easings = {
function getStrokePath(
points: Mark['points'],
options: AppOptions,
type: string
last: boolean
) {
const stroke = getStroke(points, {
...options,
easing: easings[options.easing],
simulatePressure: type !== 'pen',
simulatePressure: points[0].pressure === 0,
start: {
taper: options.taperStart,
easing: easings[options.taperStartEasing],
},
end: {
taper: options.taperEnd,
easing: easings[options.taperEndEasing],
},
last,
})

return options.clip
Expand All @@ -70,6 +84,10 @@ interface AppOptions {
thinning: number
smoothing: number
simulatePressure: boolean
taperStart: number
taperEnd: number
taperStartEasing: string
taperEndEasing: string
}

const defaultOptions: AppOptions = {
Expand All @@ -80,6 +98,10 @@ const defaultOptions: AppOptions = {
simulatePressure: true,
clip: false,
easing: 'linear',
taperStart: 0,
taperEnd: 0,
taperStartEasing: 'linear',
taperEndEasing: 'linear',
}

const defaultSettings = {
Expand Down Expand Up @@ -219,7 +241,7 @@ const state = createState({

data.marks = marks.map(mark => ({
...mark,
path: getStrokePath(mark.points, alg, mark.type),
path: getStrokePath(mark.points, alg, true),
}))

data.settings = {
Expand All @@ -246,7 +268,7 @@ const state = createState({
data.currentMark = {
type,
points: [point],
path: getStrokePath([point], alg, type),
path: getStrokePath([point], alg, false),
}
},
addPointToMark(data) {
Expand All @@ -261,11 +283,7 @@ const state = createState({
pressure: p,
})

currentMark!.path = getStrokePath(
currentMark!.points,
alg,
currentMark!.type
)
currentMark!.path = getStrokePath(currentMark!.points, alg, false)
},
completeMark(data) {
const { currentMark, alg } = data
Expand All @@ -274,7 +292,7 @@ const state = createState({

data.marks.push({
...currentMark,
path: getStrokePath(currentMark.points, alg, currentMark!.type),
path: getStrokePath(currentMark.points, alg, true),
})

data.currentMark = null
Expand All @@ -287,7 +305,7 @@ const state = createState({
const { alg } = data
data.marks = payload.marks.map(mark => ({
...mark,
path: getStrokePath(mark.points, alg, mark.type),
path: getStrokePath(mark.points, alg, true),
}))
},
undoMark(data) {
Expand Down Expand Up @@ -323,15 +341,11 @@ const state = createState({
updatePaths(data) {
const { currentMark, alg, marks } = data
for (let mark of marks) {
mark.path = getStrokePath(mark.points, alg, mark.type)
mark.path = getStrokePath(mark.points, alg, true)
}

if (currentMark) {
currentMark.path = getStrokePath(
currentMark.points,
alg,
currentMark.type
)
currentMark.path = getStrokePath(currentMark.points, alg, false)
}
},
// Clipboard message
Expand Down
93 changes: 93 additions & 0 deletions example/svg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Some helpers for drawing SVGs.

import * as vec from 'vec'

// General

export function ellipse(A: number[], r: number) {
return `M ${A[0] - r},${A[1]}
a ${r},${r} 0 1,0 ${r * 2},0
a ${r},${r} 0 1,0 -${r * 2},0 `
}

export function dot(v: number[]) {
return ellipse(v, 3)
}

export function moveTo(v: number[]) {
return `M ${v[0]},${v[1]} `
}

export function lineTo(v: number[]) {
return `L ${v[0]},${v[1]} `
}

export function line(a: number[], ...pts: number[][]) {
return moveTo(a) + pts.map(p => lineTo(p)).join()
}

export function hLineTo(v: number[]) {
return `H ${v[0]},${v[1]} `
}

export function vLineTo(v: number[]) {
return `V ${v[0]},${v[1]} `
}

// /**
// * Return the path for a rectangle between two points.
// * @param a
// * @param b
// * @returns
// */
// export function rectFromBounds(bounds: IBounds) {
// const { minX, maxX, minY, maxY } = bounds
// return [
// moveTo([minX, maxX]),
// lineTo([maxX, minY]),
// lineTo([maxX, maxY]),
// lineTo([minX, minY]),
// closePath(),
// ].join(' ')
// }

// /**
// * Return the path for a rectangle with a given point and size.
// * @param point
// * @param size
// * @returns
// */
// export function rect(point: number[], size: number[]) {
// return rectFromBounds(getBoundsBetweenPoints(point, vec.add(point, size)))
// }

export function bezierTo(A: number[], B: number[], C: number[]) {
return `C ${A[0]},${A[1]} ${B[0]},${B[1]} ${C[0]},${C[1]} `
}

// export function arcTo(C: number[], r: number, A: number[], B: number[]) {
// return [
// // moveTo(A),
// 'A',
// r,
// r,
// 0,
// getSweep(C, A, B) > 0 ? '1' : '0',
// 0,
// B[0],
// B[1],
// ].join(' ')
// }

export function closePath() {
return 'Z'
}

export function rectTo(A: number[]) {
return ['R', A[0], A[1]].join(' ')
}

export function getPointAtLength(path: SVGPathElement, length: number) {
const point = path.getPointAtLength(length)
return [point.x, point.y]
}
Loading

0 comments on commit af0cfbf

Please sign in to comment.