Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mark: Mark auto offsetting #314

Merged
merged 11 commits into from
Nov 10, 2023
119 changes: 96 additions & 23 deletions src/bezier.typ
Original file line number Diff line number Diff line change
Expand Up @@ -236,55 +236,128 @@
(right.at(0), right.at(3), right.at(1), right.at(2)))
}

/// Shorten curve by length d. A negative length shortens from the end.
/// Approximate cubic curve length
/// - s (vector): Curve start
/// - e (vector): Curve end
/// - c1 (vector): Control point 1
/// - c2 (vector): Control point 2
/// -> float Arc length
#let cubic-arclen(s, e, c1, c2, samples: 10) = {
let d = 0
let last = none
for t in range(0, samples + 1) {
let pt = cubic-point(s, e, c1, c2, t / samples)
if last != none {
d += vector.dist(last, pt)
}
last = pt
}
return d
}

/// Shorten the curve by offsetting s and c1 or e and c2
/// by distance d. If d is positive the curve gets shortened
/// by moving s and c1 closer to e, if d is negative, e and c2
/// get moved closer to s.
///
/// - s (vector): Curve start
/// - e (vector): Curve end
/// - c1 (vector): Control point 1
/// - c2 (vector): Control point 2
/// - d (float): Distance to shorten by
/// -> (s, e, c1, c2) Shortened curve
#let shorten(s, e, c1, c2, d) = {
#let cubic-shorten-linear(s, e, c1, c2, d) = {
if d == 0 { return (s, e, c1, c2) }

let t = if d < 0 { 1 } else { 0 }
let sign = if d < 0 { -1 } else { 1 }

let a = cubic-point(s, e, c1, c2, t)
let b = cubic-point(s, e, c1, c2, t + sign * 0.01)
let offset = vector.scale(vector.norm(vector.sub(b, a)),
calc.abs(d))
if d > 0 {
s = vector.add(s, offset)
c1 = vector.add(c1, offset)
} else {
e = vector.add(e, offset)
c2 = vector.add(c2, offset)
}
return (s, e, c1, c2)
}

/// Approximate bezier interval t for a given distance d.
/// If d is positive, the functions starts from the curves
/// start s, if d is negative, it starts form the curves end
/// e.
/// -> float Bezier t value from [0,1]
#let cubic-t-for-distance(s, e, c1, c2, d, samples: 10) = {
if d == 0 {
return (s, e, c1, c2)
return 0
}

let num-samples = 6
let split-t = 0
if d > 0 {
let travel = 0
let last = cubic-point(s, e, c1, c2, 0)

for t in range(0, num-samples + 1) {
let t = t / num-samples
let travel = 0 // Distance traveled along the curve
let last = s
for t in range(1, samples + 1) {
let t = t / samples
let curr = cubic-point(s, e, c1, c2, t)
let dist = calc.abs(vector.dist(last, curr))
let dist = vector.dist(last, curr)
travel += dist
if travel >= d {
split-t = t - (travel - d) / num-samples
break
return t - 1/samples + d / (travel * samples)
}
last = curr
}
return 1
} else {
let travel = 0
let last = cubic-point(s, e, c1, c2, 1)
return 1 - cubic-t-for-distance(e, s, c2, c1, -d, samples: samples)
}
}

/// Shorten curve by distance d. This keeps the curvature of the
/// curve by finding new values along the original curve.
/// If d is positive the curve gets shortened by moving s
/// closer to e, if d is negative, e is moved closer to s.
/// The points s and e are moved along the curve, keeping the
/// kurves curvature the same (the control points get recalculated).
///
/// - s (vector): Curve start
/// - e (vector): Curve end
/// - c1 (vector): Control point 1
/// - c2 (vector): Control point 2
/// - d (float): Distance to shorten by
/// - samples (int): Maximum of samples/steps to use
/// -> (s, e, c1, c2) Shortened curve
#let cubic-shorten(s, e, c1, c2, d, samples: 15) = {
if d == 0 {
return (s, e, c1, c2)
}

let split-t = 0
if d > 0 {
let travel = 0 // Distance traveled along the curve

for t in range(num-samples, -1, step: -1) {
let t = t / num-samples
let last = s
for t in range(1, samples + 1) {
let t = t / samples
let curr = cubic-point(s, e, c1, c2, t)
let dist = calc.abs(vector.dist(last, curr))
travel -= dist
if travel <= d {
split-t = t - (travel - d) / num-samples
let dist = vector.dist(last, curr)
travel += dist
if travel >= d {
split-t = t - 1/samples + d / (travel * samples)
break
}
last = curr
}
} else {
// Run the algorithm from end to start by swapping the curve.
let (e, s, c2, c1) = cubic-shorten(e, s, c2, c1, -d, samples: samples)
fenjalien marked this conversation as resolved.
Show resolved Hide resolved
return (s, e, c1, c2)
}

let (left, right) = split(s, e, c1, c2, split-t)
return if d > 0 { right } else { left }
let (_, right) = split(s, e, c1, c2, split-t)
return right
}

/// Align curve points pts to the line start-end
Expand Down
4 changes: 1 addition & 3 deletions src/draw/grouping.typ
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,7 @@
vector.add(pos, dir),
pos,
mark.mark,
style.size,
fill: style.fill,
stroke: style.stroke
style,
)
)
if "name" in mark {
Expand Down
130 changes: 65 additions & 65 deletions src/draw/shapes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "/src/process.typ"
#import "/src/bezier.typ" as bezier_
#import "/src/anchor.typ" as anchor_
#import "/src/mark.typ" as mark_

#import "transformations.typ": *
#import "styling.typ": *
Expand Down Expand Up @@ -174,25 +175,38 @@
let stop-angle = if stop == auto { start + delta } else { stop }
// Border angles can break if the angle is 0.
assert.ne(start-angle, stop-angle, message: "Angle must be greater than 0deg")

return (ctx => {
let style = styles.resolve(ctx.style, style, root: "arc")
assert(style.mode in ("OPEN", "PIE", "CLOSE"))

let (ctx, arc-start) = coordinate.resolve(ctx, position)
let (x, y, z) = arc-start
let (rx, ry) = util.resolve-radius(style.radius).map(util.resolve-number.with(ctx))

// Calculate marks and optimized angles
let (marks, draw-arc-start, draw-start-angle, draw-stop-angle) = if style.mark != none {
mark_.place-marks-along-arc(ctx, start-angle, stop-angle,
arc-start, rx, ry, style, style.mark)
} else {
(none, arc-start, start-angle, stop-angle)
}

let (x, y, z) = arc-start
let path = (drawable.arc(
x, y, z,
start-angle,
stop-angle,
..draw-arc-start,
draw-start-angle,
draw-stop-angle,
rx,
ry,
stroke: style.stroke,
fill: style.fill,
mode: style.mode,
),)

if marks != none {
path += marks
}

let sector-center = (
x - rx * calc.cos(start-angle),
y - ry * calc.sin(start-angle),
Expand Down Expand Up @@ -305,9 +319,7 @@
return (ctx: ctx, drawables: drawable.mark(
..pts,
style.symbol,
style.size,
fill: style.fill,
stroke: style.stroke,
style
))
},)
}
Expand Down Expand Up @@ -339,33 +351,23 @@
name: name,
transform: ctx.transform,
)


// Place marks and adjust points
let (marks, pts) = if style.mark != none {
mark_.place-marks-along-line(ctx, pts, style.mark)
} else {
(none, pts)
}

let drawables = (drawable.path(
(path-util.line-segment(pts),),
fill: style.fill,
stroke: style.stroke,
close: close,
),)

if style.mark.start != none {
drawables.push(drawable.mark(
pts.at(1),
pts.at(0),
style.mark.start,
style.mark.size,
fill: style.mark.fill,
stroke: style.mark.stroke,
))
}
if style.mark.end != none {
drawables.push(drawable.mark(
pts.at(-2),
pts.at(-1),
style.mark.end,
style.mark.size,
fill: style.mark.fill,
stroke: style.mark.stroke,
))

if marks != none {
drawables += marks
}

return (
Expand Down Expand Up @@ -726,7 +728,7 @@

// Coordinates check
let t = coordinates.map(coordinate.resolve-system)

return (
ctx => {
let (ctx, start, ..ctrl, end) = coordinate.resolve(ctx, ..coordinates)
Expand All @@ -752,35 +754,21 @@

let style = styles.resolve(ctx.style, style, root: "bezier")

let curve = (start, end, ..ctrl)
let (marks, curve) = if style.mark != none {
mark_.place-marks-along-bezier(ctx, curve, style, style.mark)
} else {
(none, curve)
}

let drawables = (drawable.path(
path-util.cubic-segment(start, end, ctrl.at(0), ctrl.at(1)),
path-util.cubic-segment(..curve),
fill: style.fill,
stroke: style.stroke,
),)

if style.mark != none {
style = style.mark
let offset = 0.001
if style.start != none {
drawables.push(drawable.mark(
start,
bezier_.cubic-point(start, end, ..ctrl, offset),
style.start,
style.size,
fill: style.fill,
stroke: style.stroke
))
}
if style.start != none {
drawables.push(drawable.mark(
bezier_.cubic-point(start, end, ..ctrl, 1 - offset),
end,
style.end,
style.size,
fill: style.fill,
stroke: style.stroke
))
}
if marks != none {
drawables += marks
}

return (
Expand Down Expand Up @@ -810,7 +798,7 @@
},)
}

#let catmull(..pts-style, tension: .5, close: false, name: none) = {
#let catmull(..pts-style, close: false, name: none) = {
let (pts, style) = (pts-style.pos(), pts-style.named())

assert(pts.len() >= 2, message: "Catmull-rom curve requires at least two points. Got " + repr(pts.len()) + "instead.")
Expand Down Expand Up @@ -841,22 +829,34 @@

let style = styles.resolve(ctx.style, style, root: "catmull")

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

let drawables = (
drawable.path(
bezier_.catmull-to-cubic(
pts,
style.tension,
close: close
).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,
drawable.path(
bezier_.catmull-to-cubic(
pts,
tension,
close: close
).map(c => path-util.cubic-segment(..c)),
fill: style.fill,
stroke: style.stroke,
close: close
)
drawables
)
)
},)
Expand Down
Loading
Loading