Skip to content

Commit

Permalink
Uses resample() generator for subdividing arc
Browse files Browse the repository at this point in the history
  • Loading branch information
baku89 committed Feb 24, 2024
1 parent 5f13b92 commit f892a3d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 49 deletions.
59 changes: 11 additions & 48 deletions src/Path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {Line} from './Line'
import {PathLocation, SegmentLocation} from './Location'
import {Rect} from './Rect'
import {Segment} from './Segment'
import {iterateRange, memoize, toFixedSimple} from './utils'
import {iterateRange, memoize, resample, toFixedSimple} from './utils'

paper.setup(document.createElement('canvas'))

Expand Down Expand Up @@ -503,69 +503,32 @@ export namespace Path {
radius: number,
startAngle: number,
endAngle: number,
{step = 180, align = 'start', count}: ArcOptions = {}
options: ArcOptions = {}
): Path {
const maxAngleStep = 360 - 1e-4
// Clamp the angle step not to be zero or negative
let step = options.step ?? 180
step = scalar.clamp(Math.abs(step), 0.1, maxAngleStep)

const diffAngle = endAngle - startAngle
const radii: vec2 = [radius, radius]
const sweepFlag = endAngle > startAngle

const vertices = beginVertex(vec2.add(center, vec2.dir(startAngle, radius)))

// Calculate the signed angle step and number of segments
if (count) {
if (Math.abs(diffAngle) / count > maxAngleStep) {
count = Math.ceil(Math.abs(diffAngle) / maxAngleStep)
} else {
count = Math.floor(Math.max(1, count))
}
step = diffAngle / count
} else if (align === 'uniform') {
count = Math.ceil(Math.abs(diffAngle) / step)
step = diffAngle / count
} else {
// align === 'start' | 'end'
count = Math.floor(Math.abs(diffAngle) / step)
step *= Math.sign(diffAngle)
}
// Add intermediate vertices
const angles = resample(startAngle, endAngle, {step, ...options})

const radii: vec2 = [radius, radius]
const sweepFlag = endAngle > startAngle
const largeArc = Math.abs(step) > 180

// Add the additional start vertex if necessary
if (align === 'end') {
const throughAngle = endAngle - step * count
if (throughAngle !== endAngle) {
vertices.push({
point: vec2.add(center, vec2.dir(throughAngle, radius)),
command: 'A',
args: [radii, 0, largeArc, sweepFlag],
})
}
startAngle = throughAngle
}
let prevAngle = startAngle

// Add intermediate vertices
for (let i = 1; i <= count; i++) {
const throughAngle = startAngle + step * i
for (const throughAngle of angles) {
const largeArc = Math.abs(throughAngle - prevAngle) >= 180 - 1e-4
vertices.push({
point: vec2.add(center, vec2.dir(throughAngle, radius)),
command: 'A',
args: [radii, 0, largeArc, sweepFlag],
})
}

// Add the end vertex if necessary
if (align === 'start') {
if (startAngle + step * count !== endAngle) {
vertices.push({
point: vec2.add(center, vec2.dir(endAngle, radius)),
command: 'A',
args: [radii, 0, largeArc, sweepFlag],
})
}
prevAngle = throughAngle
}

return {curves: [{vertices, closed: false}]}
Expand Down
14 changes: 13 additions & 1 deletion src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {toFixedSimple} from './utils'
import {resample, toFixedSimple} from './utils'

describe('toFixedSimple', () => {
it('should work', () => {
Expand All @@ -13,3 +13,15 @@ describe('toFixedSimple', () => {
expect(toFixedSimple(0.019, 2)).toEqual('.02')
})
})

describe('resample', () => {
it('yield `to` when `from` and `to` are the same', () => {
const result = [...resample(0, 0)]
expect(result).toEqual([0])
})

it('yield `to` when `from` and `to` are the same', () => {
const result = [...resample(0, -0)]
expect(result).toEqual([0])
})
})
52 changes: 52 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {scalar} from 'linearly'

export function toFixedSimple(a: number, fractionDigits = 2): string {
return a
.toFixed(fractionDigits)
Expand Down Expand Up @@ -54,3 +56,53 @@ export function* iterateRange(
yield from + i * step
}
}

interface ResampleOptions {
step?: number
align?: 'uniform' | 'start' | 'center' | 'end'
count?: number
}

export function* resample(
from: number,
to: number,
{step, align = 'start', count}: ResampleOptions = {}
) {
const diff = to - from

if (step === undefined) {
step = diff
}

let fromOffset = from

if (count !== undefined) {
count = Math.max(1, Math.floor(count))
step = diff / count
} else if (align === 'start' || align === 'center' || align === 'end') {
count = Math.floor(Math.abs(diff / step))
step *= Math.sign(diff)

if (align === 'end') {
fromOffset = to - count * step
} else if (align === 'center') {
fromOffset = scalar.lerp(from, to - count * step, 0.5)
}
} else {
// align === 'uniform'
count = Math.ceil(Math.abs(diff / step))
step = diff / count
}

if (from !== fromOffset) {
yield fromOffset
}

for (let i = 1; i <= count; i++) {
yield fromOffset + i * step
}

if (from === to || fromOffset + count * step + midOffset !== to) {

Check failure on line 105 in src/utils.ts

View workflow job for this annotation

GitHub Actions / deploy

Cannot find name 'midOffset'.
yield to
}
}

0 comments on commit f892a3d

Please sign in to comment.