Skip to content

Commit

Permalink
angle: Add Right Angle Function (#415)
Browse files Browse the repository at this point in the history
Adds a `right-angle` function to the angle library that behaves tikz
like.
Fixes #414.
  • Loading branch information
johannes-wolf authored Dec 20, 2023
1 parent 0826cb3 commit 53152f8
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 7 deletions.
95 changes: 88 additions & 7 deletions src/lib/angle.typ
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#import "../drawable.typ"
#import "../styles.typ"
#import "../vector.typ"
#import "../util.typ"
#import "../coordinate.typ"
#import "../anchor.typ" as anchor_
#import "/src/drawable.typ"
#import "/src/styles.typ"
#import "/src/vector.typ"
#import "/src/util.typ"
#import "/src/coordinate.typ"
#import "/src/anchor.typ" as anchor_
#import "/src/draw.typ"

// Angle default-style
Expand Down Expand Up @@ -63,7 +63,8 @@
..style
) = draw.group(name: name, ctx => {
let style = styles.resolve(ctx.style, merge: style.named(), base: default-style, root: "angle")
let (ctx, origin, a, b) = coordinate.resolve(ctx, origin, a, b)
let (ctx, origin) = coordinate.resolve(ctx, origin)
let (ctx, a, b) = coordinate.resolve(ctx, a, b, update: false)

assert(origin.at(2) == a.at(2) and a.at(2) == b.at(2),
message: "Angle z coordinates of all three points must be equal")
Expand Down Expand Up @@ -132,3 +133,83 @@
draw.content(label-pt, label)
}
})

/// Draw a right angle between `a` and `b` through origin `origin`
///
/// #example(```
/// line((0,0), (1,1.5), name: "a")
/// line((0,0), (2,-1), name: "b")
///
/// // Draw an angle between the two lines
/// cetz.angle.right-angle("a.start", "a.end", "b.end",
/// radius: 1.5)
/// ```)
///
/// *Style Root:* `angle`
///
/// *Style Keys:*
/// #show-parameter-block("radius", ("number"), [
/// The radius of the angles arc. If of type `ratio`, it is relative to the smaller distance of either origin to a or origin to b.], default: .5)
/// #show-parameter-block("label-radius", ("number", "ratio"), [
/// The radius of the angles label origin. If of type `ratio`, it is relative to the distance between `origin` and the angle corner.], default: 50%)
///
/// *Anchors*
/// / `"a"`: Point a
/// / `"b"`: Point b
/// / `"origin"`: Origin
/// / `"corner"`: Angle corner
/// / `"label"`: Label center
///
/// - origin (coordinate): Angle origin
/// - a (coordinate): Coordinate of side `a`, containing an angle between `origin` and `b`.
/// - b (coordinate): Coordinate of side `b`, containing an angle between `origin` and `a`.
/// - label (none,content): Draw a label at the angles "label" anchor.
/// - name (none,string): Element name, used for querying anchors.
/// - ..style (style): Style key-value pairs.
#let right-angle(
origin,
a,
b,
label: "•",
name: none,
..style
) = draw.group(name: name, ctx => {
let style = styles.resolve(ctx.style, merge: style.named(), base: default-style, root: "angle")
let (ctx, origin) = coordinate.resolve(ctx, origin)
let (ctx, a, b) = coordinate.resolve(ctx, a, b, update: false)
let vo = origin; let va = a; let vb = b

// Radius can be relative to the min-distance between origin-a and origin-b
if type(style.radius) == ratio {
style.radius = style.radius * calc.min(vector.dist(vo, va), vector.dist(vo, vb)) / 100%
}
let (r, _) = util.resolve-radius(style.radius).map(util.resolve-number.with(ctx))

let va = vector.add(vo, vector.scale(vector.norm(vector.sub(va, vo)), r))
let vb = vector.add(vo, vector.scale(vector.norm(vector.sub(vb, vo)), r))
let angle-b = vector.angle2(vo, vb)
let vm = vector.add(va, (calc.cos(angle-b) * r, calc.sin(angle-b) * r, 0))

// Label radius can be relative to the distance between origin and the
// angle corner
if type(style.label-radius) == ratio {
style.label-radius = style.label-radius * vector.dist(vm, vo) / 100%
}
let (ra, _) = util.resolve-radius(style.label-radius).map(util.resolve-number.with(ctx))

if style.fill != none {
draw.line(vo, va, vm, vb, close: true, stroke: none, fill: style.fill)
}
draw.line(va, vm, vb, ..style, fill: none)

let label-pt = vector.scale(vector.norm(vector.sub(vm, vo)), ra)
if label != none {
draw.content(label-pt, label)
}

draw.anchor("a", a)
draw.anchor("b", b)
draw.anchor("origin", origin)
draw.anchor("corner", vm)
draw.anchor("label", label-pt)
})
Binary file added tests/angle/right-angle/ref.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions tests/angle/right-angle/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#import "/tests/helper.typ": *

#test-case({
import draw: *
import angle: right-angle

for a in range(0, 360, step: 36) {
a *= 1deg
translate((1.5, 0, 0))
group({
let (o, a, b) = ((0,0), (calc.cos(a), calc.sin(a)), (calc.cos(a+90deg), calc.sin(a+90deg)))
line(a, o, b)
right-angle(o, a, b)
})
}
})

#test-case({
import draw: *
import angle: right-angle

for a in range(0, 360, step: 36) {
a *= 1deg
translate((1.5, 0, 0))
group({
let (o, a, b) = ((0,0), (calc.cos(a), calc.sin(a)), (calc.cos(a+45deg), calc.sin(a+45deg)))
line(a, o, b)
right-angle(o, a, b)
})
}
})

#test-case({
import draw: *
import angle: right-angle

for a in range(0, 360, step: 36) {
a *= 1deg
translate((1.5, 0, 0))
group({
let (o, a, b) = ((0,0), (calc.cos(a), calc.sin(a)), (calc.cos(a+120deg), calc.sin(a+120deg)))
line(a, o, b)
right-angle(o, a, b)
})
}
})

#test-case({
import draw: *
import angle: right-angle, angle

scale(3)
let (o, a, b) = ((0,0), (0,1), (1,0))
line(a, o, b)
right-angle(o, a, b, name: "angle")
for-each-anchor("angle", n => {
if n in ("a", "b", "origin", "corner", "label") {
circle("angle." + n, stroke: blue, radius: .1)
content("angle." + n, [#n])
}
})
})

0 comments on commit 53152f8

Please sign in to comment.