Line joints and caps

Every Line2D joint, cap and gradient side by side.

▶ Run in browser

Tags: 2d

A Line2D is more than a run of segments. Where it bends it leaves a notch, and joint_mode decides what fills it; where it ends it stops flat, and begin_cap_mode / end_cap_mode decide whether it rounds off; and gradient colours it along its own length instead of flat.

How it works

Four rows, each the same zigzag drawn with a different setting so the difference is visible rather than described:

  1. joint_mode is "sharp" (a miter), "bevel" (a flat corner) or "round" (an arc). A miter that would shoot a spike off a near-reversal falls back to a bevel on its own.

  2. begin_cap_mode and end_cap_mode are "none" or "round", and each end is set independently.

  3. gradient is a list of (t, colour) stops. t runs from 0 at the first point to 1 at the last, measured along the line, and the gradient replaces colour rather than tinting it.

  4. A width of 1.0 or less is a hairline: it rides the one-pixel line pipeline whatever the camera is doing, so it has no width for a joint or a cap to fill and neither is drawn.

The alpha on the top row is deliberately low: a joint fills the outside of a bend only, so a translucent line does not darken along its own corners.

Source

  1"""Line joints and caps: Every Line2D joint, cap and gradient side by side.
  2
  3A `Line2D` is more than a run of segments. Where it bends it leaves a notch,
  4and `joint_mode` decides what fills it; where it ends it stops flat, and
  5`begin_cap_mode` / `end_cap_mode` decide whether it rounds off; and `gradient`
  6colours it along its own length instead of flat.
  7
  8## How it works
  9
 10Four rows, each the same zigzag drawn with a different setting so the
 11difference is visible rather than described:
 12
 131. `joint_mode` is `"sharp"` (a miter), `"bevel"` (a flat corner) or `"round"`
 14   (an arc). A miter that would shoot a spike off a near-reversal falls back to
 15   a bevel on its own.
 162. `begin_cap_mode` and `end_cap_mode` are `"none"` or `"round"`, and each end
 17   is set independently.
 183. `gradient` is a list of `(t, colour)` stops. `t` runs from 0 at the first
 19   point to 1 at the last, measured along the line, and the gradient replaces
 20   `colour` rather than tinting it.
 214. A `width` of 1.0 or less is a hairline: it rides the one-pixel line pipeline
 22   whatever the camera is doing, so it has no width for a joint or a cap to
 23   fill and neither is drawn.
 24
 25The alpha on the top row is deliberately low: a joint fills the outside of a
 26bend only, so a translucent line does not darken along its own corners.
 27"""
 28
 29from simvx.core import Line2D, Node2D
 30from simvx.graphics import App
 31
 32WIDTH, HEIGHT = 900, 620
 33
 34#: A right angle, an acute bend and a near-reversal, so every joint mode has
 35#: something to show.
 36ZIGZAG = [(0, 60), (60, 60), (60, 0), (140, 70), (200, 10), (150, 14)]
 37
 38ROWS = (
 39    (60.0, "joint_mode: sharp, bevel, round"),
 40    (210.0, "begin_cap_mode / end_cap_mode: none, both, end only"),
 41    (360.0, "gradient: a ramp, a hard stop at the midpoint, four stops"),
 42    (510.0, "width 1.0 or less is a hairline: no joint, no cap"),
 43)
 44
 45
 46def _at(row: int, column: int) -> tuple[float, float]:
 47    return 60.0 + column * 280.0, ROWS[row][0]
 48
 49
 50class LineGallery(Node2D):
 51    def on_ready(self):
 52        for column, mode in enumerate(("sharp", "bevel", "round")):
 53            self.add_child(
 54                Line2D(
 55                    name=f"Joint{mode.title()}",
 56                    position=_at(0, column),
 57                    points=ZIGZAG,
 58                    width=14.0,
 59                    colour=(0.3, 0.85, 1.0, 0.45),
 60                    joint_mode=mode,
 61                )
 62            )
 63
 64        caps = (("none", "none"), ("round", "round"), ("none", "round"))
 65        for column, (begin, end) in enumerate(caps):
 66            self.add_child(
 67                Line2D(
 68                    name=f"Cap{begin.title()}{end.title()}",
 69                    position=_at(1, column),
 70                    points=ZIGZAG,
 71                    width=14.0,
 72                    colour=(1.0, 0.72, 0.25, 1.0),
 73                    joint_mode="round",
 74                    begin_cap_mode=begin,
 75                    end_cap_mode=end,
 76                )
 77            )
 78
 79        gradients = (
 80            [(0.0, (1.0, 0.2, 0.3, 1.0)), (1.0, (0.25, 0.4, 1.0, 1.0))],
 81            [
 82                (0.0, (1.0, 0.85, 0.2, 1.0)),
 83                (0.5, (1.0, 0.85, 0.2, 1.0)),
 84                (0.5, (0.2, 0.9, 0.5, 1.0)),
 85                (1.0, (0.2, 0.9, 0.5, 1.0)),
 86            ],
 87            [
 88                (0.0, (1.0, 0.3, 0.3, 1.0)),
 89                (0.33, (1.0, 0.9, 0.3, 1.0)),
 90                (0.66, (0.3, 1.0, 0.6, 1.0)),
 91                (1.0, (0.4, 0.5, 1.0, 1.0)),
 92            ],
 93        )
 94        for column, gradient in enumerate(gradients):
 95            self.add_child(
 96                Line2D(
 97                    name=f"Gradient{column}",
 98                    position=_at(2, column),
 99                    points=ZIGZAG,
100                    width=14.0,
101                    joint_mode="round",
102                    gradient=gradient,
103                )
104            )
105
106        for column, width in enumerate((1.0, 3.0, 14.0)):
107            self.add_child(
108                Line2D(
109                    name=f"Weight{column}",
110                    position=_at(3, column),
111                    points=ZIGZAG,
112                    width=width,
113                    colour=(0.9, 0.9, 0.95, 1.0),
114                    joint_mode="round",
115                    begin_cap_mode="round",
116                    end_cap_mode="round",
117                )
118            )
119
120    def on_draw(self, renderer):
121        for y, title in ROWS:
122            renderer.draw_text(title, (40, y - 34), scale=1, colour=(0.75, 0.78, 0.85, 1.0))
123
124
125if __name__ == "__main__":
126    App(title="Line joints and caps", width=WIDTH, height=HEIGHT).run(LineGallery())