Introduction
While working on my 'Searching, generating, validating and rendering Euclidean tilings' project, I wanted to draw some annotations for representing geometric reflection transforms (!!! Fig 404 !!!). I thought "I want the reflection line to extend across the whole render area" - and that sent me on a little journey to figure out how. Writing this article is my way of remembering what I learned.
Terminology
Before we get to the maths, let's learn some concepts. Here's a quick glossary of some things we'll be using:
- Line segment: When we say 'a line' this actually refers to a line that goes on forever in both directions. A line segment is a line that has a start and an end point.
- slope: The slope of a line is the ratio of the vertical change (
) to the horizontal change (y2 - y1
). It's the gradient of the line and we'll refer to this below with the letterx2 - x1
. There's 2 things to note about the slope. When a line is completely horizontal the slope ism
, and when a line is completely vertical the slope is0
.Infinity
- y-intercept: This is the point where a line crosses the y-axis or in other words it's the value of
wheny
and we'll refer to it with the letterx == 0
.b
Line intersection
In this section, we're going to look at finding the intersection of 2 lines first. The information we'll be working with is the endpoints of 2 line segments.
y = m * x + b
This equation is a useful one to remember. Given an
value, the slope and y-intercept we can find any x
value on a line. We can also rearrange this equation to find the y
value given a x
value (!!! Fig 404 !!!).y
It's useful to also take the time to understand what this equation is doing.
represents the rate of change, it's simply a scalar value that for every 1 unit of m
changes, how many units of x
change. However, this assumes that the line is flat. The y
part of the equation is the offset of the line from the origin.b
To find the intersection of 2 lines we need to find the
and x
values where the lines cross. We can do this by setting the equations of the lines equal to each other and solving for y
. We can then use this x
value to find the x
value.y
Let's say we have 2 line segments. The first line segment is defined by the points
and (x1, y1)
. The second line segment is defined by the points (x2, y2)
and (x3, y3)
(!!! Fig 404 !!!).(x4, y4)
Even if the slopes of the lines are so gradual that they are effectively parallel they will eventually intersect at some point, however precisely parallel lines never will so we need to handle this case. Luckily, this is easy to check for by comparing the slopes of the lines !!! Fig 404 !!!.
Bounds intersection
Back to the main problem of this article which is working with a line segment that exists within a bounded area (a rectangle). We're not going to think of this area as a rectangle but as 4 separate line segments. With this in mind, it's now easy to use what we've learned above to find the intersection of this line with the lines of the bounding area.
Something that you may have realised is that a continuous line drawn within the bounds of 4 other continuous lines will always intersect with all 4 lines (unless it's parallel to one of them).
This is a fairly trivial problem to solve, by finding the intersection points of all 4 lines and then checking which intersection points are within the bounds of the rectangle (!!! Fig 404 !!!).
In fact, because of this property and because the lines we'll be working with are perfectly horizontal or vertical, we can simplify the problem further. All we need to do is plug in the parallel axis value to the line equation to find the intersection point. For example (!!! Fig 404 !!!).
With the above code, we can now find the intersection points on a bounds line segments. For example when
is intercepts_min_y
, then we know our true
value is ix1
and x_for_min_y
value is iy1
.min_y
A rotational bug
In the code above, we used the
and y1
as the starting point of the line segment. This is fine and produces the same result as using the x1
and y2
but the now inferred direction of the line has some implications.x2
To not introduce any graphical and animation bugs down the line by accidentally flipping the line segment, we need to make sure that the extended line segment points returned are in the given direction.
In fact, as well as annotating a reflective transform, I needed to annotate rotational transforms too (!!! Fig 404 !!!). The problem with this is that I only needed to extend the line segment in one direction and therefor needed to control which point was the point that extended.
I figured two ways to solve this problem, and I think it's matter of preference which one you choose.
Method 1: Using distance alone by determining whichever points is closest. A point that extended away in the direction from another point will always be closer than the point at the end of the line segment. This is because the point at the end of the line segment is the same distance away plus the length of the line segment.
Method 2: Using angles by determining the angle between the center of the line segment and the points, we can then know which bounding line segment they are pointing at.
I went with Method 2, because I had already implemented this before I thought of Method 1. I plan to benchmark the two methods to see which is faster, and maybe I'll come back and swap these. But they essentially do the same thing.
-π
, and anticlockwise increases from 0 to a bounded value of π
. At 9 o'clock it jumps from -π
to π
.Those assigning conditions need to be done for each of the 4 bounding lines. The code above is just a snippet, but essentially we need to just check that the direction of the point and the line segment match up.
Summary
That's it! The full implementation can be found here, and you can play around with the example on this page that uses that Rust implementation through Wasm.