Skip to content

Commit

Permalink
feat: Add functions for cubic bezier
Browse files Browse the repository at this point in the history
- Add `getCrossSegAndBezier3WithT` to get extra information of intersections
- Add `divideBezier3` to divide cubic bezier
  • Loading branch information
miyanokomiya committed Feb 24, 2024
1 parent 1517361 commit 7d64142
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [3.1.3] - 2024-02-24
### Added
- Add `getCrossSegAndBezier3WithT` to get extra information of intersections
- Add `divideBezier3` to divide cubic bezier

## [3.1.2] - 2024-02-22
### Added
- Add `getCrossSegAndBezier3` to get intersections between a segment and a cubic bezier
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "okageo",
"version": "3.1.2",
"version": "3.1.3",
"description": "parse SVG to polygons",
"main": "./dist/okageo.js",
"module": "./dist/okageo.mjs",
Expand Down
45 changes: 37 additions & 8 deletions src/geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ export function getBezier2LerpFn(
* @return calced point
*/
export function getPointOnBezier3(
pointList: Readonly<[IVec2, IVec2, IVec2, IVec2]>,
pointList: Readonly<Bezier3>,
rate: number
): IVec2 {
const t = rate
Expand All @@ -828,7 +828,7 @@ export function getPointOnBezier3(
}

export function getBezier3LerpFn(
pointList: Readonly<[IVec2, IVec2, IVec2, IVec2]>
pointList: Readonly<Bezier3>
): (t: number) => IVec2 {
return (t) => getPointOnBezier3(pointList, t)
}
Expand All @@ -844,7 +844,7 @@ export function getBezier3LerpFn(
* @return calced point
*/
export function getYOnBezier3AtX(
pointList: Readonly<[IVec2, IVec2, IVec2, IVec2]>,
pointList: Readonly<Bezier3>,
x: number
): number {
const [p0, p1, p2, p3] = pointList
Expand Down Expand Up @@ -1694,13 +1694,22 @@ function solveBezierInterpolationEquations(points: IVec2[]): IVec2[] {
return ret
}

type Bezier3 = [c0: IVec2, c1: IVec2, c2: IVec2, c3: IVec2]

/**
* The order of returned items is srbitrary.
*/
export function getCrossSegAndBezier3(
seg: Readonly<[IVec2, IVec2]>,
bezier: Readonly<[c0: IVec2, c1: IVec2, c2: IVec2, c3: IVec2]>
bezier: Readonly<Bezier3>
): IVec2[] {
return getCrossSegAndBezier3WithT(seg, bezier).map(([p]) => p)
}

export function getCrossSegAndBezier3WithT(
seg: Readonly<[IVec2, IVec2]>,
bezier: Readonly<Bezier3>
): [IVec2, t: number][] {
const ax = 3 * (bezier[1].x - bezier[2].x) + bezier[3].x - bezier[0].x
const ay = 3 * (bezier[1].y - bezier[2].y) + bezier[3].y - bezier[0].y

Expand All @@ -1727,10 +1736,30 @@ export function getCrossSegAndBezier3(

return roots
.filter((t) => 0 <= t && t <= 1)
.map((t) => ({
x: ((ax * t + bx) * t + cx) * t + dx,
y: ((ay * t + by) * t + cy) * t + dy,
}))
.map((t) => [
{
x: ((ax * t + bx) * t + cx) * t + dx,
y: ((ay * t + by) * t + cy) * t + dy,
},
t,
])
}

export function divideBezier3(
bezier: Readonly<Bezier3>,
t: number
): [Bezier3, Bezier3] {
const [a, b, c, d] = bezier
const e = lerpPoint(a, b, t)
const f = lerpPoint(b, c, t)
const g = lerpPoint(c, d, t)
const h = lerpPoint(e, f, t)
const j = lerpPoint(f, g, t)
const k = lerpPoint(h, j, t)
return [
[a, e, h, k],
[k, j, g, d],
]
}

export function getClosestPointOnBezier3(
Expand Down
20 changes: 20 additions & 0 deletions test/geo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2075,6 +2075,26 @@ describe('getCrossSegAndBezier3', () => {
})
})

describe('divideBezier3', () => {
it('should return divided beziers', () => {
const bezier = [
{ x: 0, y: 0 },
{ x: 5, y: -10 },
{ x: 5, y: 10 },
{ x: 10, y: 0 },
] as const
const lerp = geo.getBezier3LerpFn(bezier)

const res0 = geo.divideBezier3(bezier, 0.5)
const lerp00 = geo.getBezier3LerpFn(res0[0])
const lerp01 = geo.getBezier3LerpFn(res0[1])
expect(lerp00(0.2).x).toBeCloseTo(lerp(0.1).x)
expect(lerp00(0.2).y).toBeCloseTo(lerp(0.1).y)
expect(lerp01(0.2).x).toBeCloseTo(lerp(0.6).x)
expect(lerp01(0.2).y).toBeCloseTo(lerp(0.6).y)
})
})

describe('getClosestPointOnBezier3', () => {
it('should return closest point on a bezier', () => {
const bezier = [
Expand Down

0 comments on commit 7d64142

Please sign in to comment.