diff --git a/manual.pdf b/manual.pdf index 857e5f86..ccdcbf14 100644 Binary files a/manual.pdf and b/manual.pdf differ diff --git a/manual.typ b/manual.typ index e8b099d9..351f1964 100644 --- a/manual.typ +++ b/manual.typ @@ -311,6 +311,10 @@ $ M_"world" = M_"world" dot M_"local" $ #doc-style.parse-show-module("/src/draw/projection.typ") +== Bodies + +#doc-style.parse-show-module("/src/draw/bodies.typ") + = Coordinate Systems A _coordinate_ is a position on the canvas on which the picture is drawn. They take the form of dictionaries and the following sub-sections define the key value pairs for each system. Some systems have a more implicit form as an array of values and `CeTZ` attempts to infer the system based on the element types. diff --git a/src/draw.typ b/src/draw.typ index 9baf614b..ff98fde7 100644 --- a/src/draw.typ +++ b/src/draw.typ @@ -2,5 +2,6 @@ #import "draw/transformations.typ": set-transform, 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, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path +#import "draw/bodies.typ": prism #import "draw/projection.typ": ortho, on-xy, on-xz, on-yz #import "draw/util.typ": assert-version diff --git a/src/draw/bodies.typ b/src/draw/bodies.typ new file mode 100644 index 00000000..998debbb --- /dev/null +++ b/src/draw/bodies.typ @@ -0,0 +1,101 @@ +#import "/src/coordinate.typ" +#import "/src/drawable.typ" +#import "/src/styles.typ" +#import "/src/path-util.typ" +#import "/src/util.typ" +#import "/src/vector.typ" +#import "/src/matrix.typ" +#import "/src/process.typ" +#import "/src/polygon.typ" + +/// Draw a prism by extending a single element +/// into a direction. +/// +/// Curved shapes get sampled into linear ones. +/// +/// = parameters +/// +/// = Styling +/// *Root:* `prism` +/// == Keys +/// #show-parameter-block("front-stroke", ("stroke", "none"), [Front-face stroke], default: auto) +/// #show-parameter-block("front-fill", ("fill", "none"), [Front-face fill], default: auto) +/// #show-parameter-block("back-stroke", ("stroke", "none"), [Back-face stroke], default: auto) +/// #show-parameter-block("back-fill", ("fill", "none"), [Back-face fill], default: auto) +/// #show-parameter-block("side-stroke", ("stroke", "none"), [Side stroke], default: auto) +/// #show-parameter-block("side-fill", ("fill", "none"), [Side fill], default: auto) +/// +/// ```example +/// ortho({ +/// // Draw a cube with and edge length of 2 +/// prism({ +/// rect((-1, -1), (rel: (2, 2))) +/// }, 2) +/// }) +/// ``` +/// +/// - front-face (elements): A single element to use as front-face +/// - dir (number,vector): Z-distance or direction vector to extend +/// the front-face along +/// - samples (int): Number of samples to use for sampling curves +#let prism(front-face, dir, samples: 10, ..style) = { + assert.eq(style.pos(), (), + message: "Prism takes no positional arguments") + + let style = style.named() + (ctx => { + let transform = ctx.transform + ctx.transform = matrix.ident() + let (ctx, drawables, bounds) = process.many(ctx, util.resolve-body(ctx, front-face)) + ctx.transform = transform + + assert.eq(drawables.len(), 1, + message: "Prism shape must be a single drawable.") + + let points = polygon.from-segments(drawables.first().segments, samples: samples) + + let style = styles.resolve(ctx.style, merge: style, root: "prism") + + // Normal to extend the front face along + let n = if type(dir) == array { + dir.map(util.resolve-number.with(ctx)) + } else { + (0, 0, util.resolve-number(ctx, dir)) + } + + let stroke = (:) + let fill = (:) + for face in ("front", "back", "side") { + stroke.insert(face, style.at("stroke-" + face, default: style.stroke)) + fill.insert(face, style.at("fill-" + face, default: style.fill)) + } + + let drawables = () + let back-points = util.apply-transform(matrix.transform-translate(..n), ..points) + + // Back + let back = drawable.path(path-util.line-segment(back-points.rev()), + close: true, stroke: stroke.back, fill: fill.back) + drawables.push(back) + + // Sides + for i in range(0, points.len()) { + let k = calc.rem(i + 1, points.len()) + + let quad = (points.at(i), back-points.at(i), back-points.at(k), points.at(k)) + let side = drawable.path(path-util.line-segment(quad), + close: true, stroke: stroke.side, fill: fill.side) + drawables.push(side) + } + + // Front + let front = drawable.path(path-util.line-segment(points), + close: true, stroke: stroke.front, fill: fill.front) + drawables.push(front) + + return ( + ctx: ctx, + drawables: drawable.apply-transform(ctx.transform, drawables), + ) + },) +} diff --git a/src/styles.typ b/src/styles.typ index 72db88e4..043447b8 100644 --- a/src/styles.typ +++ b/src/styles.typ @@ -129,6 +129,10 @@ fill: auto, stroke: auto, ), + prism: ( + stroke: auto, + fill: auto, + ), ) /// You can use this to combine the style in `ctx`, the style given by a user for a single element and an element's default style. diff --git a/tests/bodies/ref/1.png b/tests/bodies/ref/1.png new file mode 100644 index 00000000..27cb924e Binary files /dev/null and b/tests/bodies/ref/1.png differ diff --git a/tests/bodies/test.typ b/tests/bodies/test.typ new file mode 100644 index 00000000..d1997dac --- /dev/null +++ b/tests/bodies/test.typ @@ -0,0 +1,62 @@ +#set page(width: auto, height: auto) +#import "/src/lib.typ": * +#import "/tests/helper.typ": * + +#let h-test-case = test-case +#let test-case(body) = h-test-case({ + draw.set-style(stroke: (join: "round")) + body +}) + +#test-case({ + import draw: * + prism({ rect((-1, -1), (1, 1)) }, 1) +}) + +#test-case({ + import draw: * + prism({ + circle((0,0)) + }, samples: 3, 1) +}) + +#test-case({ + import draw: * + set-style(prism: ( + fill-back: blue, + fill-front: red, + fill-side: yellow, + )) + ortho(cull-face: "cw", { + prism({ + line((-1, -1), (1, -1), (0, 1), close: true) + }, -2) + translate((3, 0, 0)) + rotate(y: 160deg) + prism({ + line((-1, -1), (1, -1), (0, 0), close: true) + }, -2) + }) +}) + +#test-case({ + import draw: * + ortho(x: 10deg, y: 30deg, { + prism({ + translate((1,0,0)) + rect((-1, -1), (1, 1)) + }, 2) + prism({ + translate((-2,0,0)) + rect((-1, -1), (1, 1)) + }, 2) + prism({ + translate((1,0,-3)) + rect((-1, -1), (1, 1)) + }, 2) + prism({ + translate((-2,0,-3)) + rect((-1, -1), (1, 1)) + }, 2) + }) +})