Skip to content

Commit

Permalink
shapes: Add hobby curve implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-wolf committed Nov 9, 2023
1 parent 891aba1 commit 784e61f
Show file tree
Hide file tree
Showing 13 changed files with 513 additions and 55 deletions.
6 changes: 6 additions & 0 deletions manual.typ
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ catmull((0,0), (1,1), (2,-1), (3,0), tension: .4, stroke: blue)
catmull((0,0), (1,1), (2,-1), (3,0), tension: .5, stroke: red)
```

#show-module-fn(draw-module, "hobby")
```example
hobby((0,0), (1,1), (2,-1), (3,0), omega: 0, stroke: blue)
hobby((0,0), (1,1), (2,-1), (3,0), omega: 1, stroke: red)
```

#show-module-fn(draw-module, "grid")
```example
grid((0,0), (3,2), help-lines: true)
Expand Down
44 changes: 44 additions & 0 deletions src/complex.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Real and imaginary part
#let re(V) = V.at(0)
#let im(V) = V.at(1)

// Complex multiplication
#let mul(V,W) = (re(V)*re(W) - im(V)*im(W),im(V)*re(W) + re(V)*im(W))

// Complex conjugate
#let conj(V) = (re(V),-im(V))

// Dot product of V and W as vectors in R^2
#let dot(V,W) = re(mul(V,conj(W)))

// Norm and norm-squared
#let normsq(V) = dot(V,V)
#let norm(V) = calc.sqrt(normsq(V))

// V*t
#let scale(V,t) = mul(V,(t,0))

// Unit vector in the direction of V
#let unit(V) = scale(V, 1/norm(V))

// V^(-1) as a complex number
#let inv(V) = scale(conj(V), 1/normsq(V))

// V / W
#let div(V,W) = mul(V,inv(W))

// V + W and V - W
#let add(V,W) = (re(V) + re(W),im(V) + im(W))
#let sub(V,W) = (re(V) - re(W),im(V) - im(W))

// Argument
#let arg(V) = calc.atan2(..V) / 1rad

// Signed angle from V to W
#let ang(V,W) = arg(div(W,V))

// exp(i*a)
#let expi(a) = (calc.cos(a),calc.sin(a))

// Rotate by angle a
#let rot(v,a) = mul(v,expi(a))
2 changes: 1 addition & 1 deletion src/draw.typ
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#import "draw/grouping.typ": intersections, group, anchor, copy-anchors, place-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, place-marks
#import "draw/transformations.typ": rotate, translate, scale, set-origin, move-to, set-viewport
#import "draw/styling.typ": set-style, fill, stroke
#import "draw/shapes.typ": circle, circle-through, arc, mark, line, grid, content, rect, bezier, bezier-through, catmull, merge-path, shadow
#import "draw/shapes.typ": circle, circle-through, arc, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, shadow
83 changes: 75 additions & 8 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "/src/matrix.typ"
#import "/src/process.typ"
#import "/src/bezier.typ" as bezier_
#import "/src/hobby.typ" as hobby_
#import "/src/anchor.typ" as anchor_
#import "/src/mark.typ" as mark_

Expand Down Expand Up @@ -828,24 +829,90 @@
}

let style = styles.resolve(ctx.style, style, root: "catmull")
let curves = bezier_.catmull-to-cubic(
pts,
style.tension,
close: close)

let (marks, pts) = if style.mark != none {
mark_.place-marks-along-catmull(ctx, pts, style, style.mark, close: close)
let (marks, curves) = if style.mark != none {
mark_.place-marks-along-beziers(ctx, curves, style, style.mark)
} else {
(none, pts)
(none, curves)
}

let drawables = (
drawable.path(
bezier_.catmull-to-cubic(
pts,
style.tension,
close: close
).map(c => path-util.cubic-segment(..c)),
curves.map(c => path-util.cubic-segment(..c)),
fill: style.fill,
stroke: style.stroke,
close: close),)
if marks != none {
drawables += marks
}

return (
ctx: ctx,
name: name,
anchors: anchors,
drawables: drawable.apply-transform(
transform,
drawables
)
)
},)
}


#let hobby(..pts-style, ta: auto, tb: auto, close: false, name: none) = {
let (pts, style) = (pts-style.pos(), pts-style.named())

assert(pts.len() >= 2, message: "Hobby curve requires at least two points. Got " + repr(pts.len()) + "instead.")

pts.map(coordinate.resolve-system)

return (ctx => {
let (ctx, ..pts) = coordinate.resolve(ctx, ..pts)

let (transform, anchors) = {
let a = (
start: pts.first(),
end: pts.last(),
)
for (i, pt) in pts.enumerate() {
a.insert("pt-" + str(i), pt)
}
anchor_.setup(
anchor => {
a.at(anchor)
},
a.keys(),
name: name,
default: "start",
transform: ctx.transform
)
}

let style = styles.resolve(ctx.style, style, root: "hobby")
let curves = hobby_.hobby-to-cubic(
pts,
ta: ta,
tb: tb,
omega: style.omega,
rho: style.rho,
close: close)

let (marks, curves) = if style.mark != none {
mark_.place-marks-along-beziers(ctx, curves, style, style.mark)
} else {
(none, curves)
}

let drawables = (
drawable.path(
curves.map(c => path-util.cubic-segment(..c)),
fill: style.fill,
stroke: style.stroke,
close: close),)
if marks != none {
drawables += marks
}
Expand Down
Loading

0 comments on commit 784e61f

Please sign in to comment.