[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Demonstrate Morley's trisector theorem in Python

[A Python program demonstrating Morley's trisector theorem]

Morley's trisector theorem says the following. Start with any triangle and find its angles' trisectors. Then find the intersections of the adjacent trisectors. Those three intersections form an equilateral triangle.

This example doesn't prove that theorem, it demonstrates it. Click to pick three points and the program draws the angle trisectors and Morley's equilateral triangle.

The program performs two main tasks: letting the user pick three points and drawing Morley's triangle, although finding and drawing the triangle takes a few extra steps. Here's a quick list of the program's main methods and what they do.

  • find_trisector_intersections—Finds the three points of intersection between adjacent angle trisectors. This function calls find_triangle_trisector_points and find_line_intersection.
  • find_triangle_trisector_points—Finds points that lie on the triangle's trisector line segments. This function calls find_angle_trisector_points to find the trisector points for each of the triangle's three vertices.
  • find_angle_trisector_points—Finds points that lie on a single angle's trisector line segments.
  • find_line_intersection—Finds the point where two lines intersect. For details, see my post Determine where two lines intersect in Python.

Picking Points

When the program creates its widgets, it uses the following code to watch for mouse down events on its Canvas widget.

# Watch for mouse clicks. self.points = [] self.canvas.bind("", self.mouse_down)

This code creates an empty points list and then binds left button presses to the mouse_down event handler.

When the user presses the mouse button down on the canvas, the following code executes.

def mouse_down(self, event): '''Save the point.''' # If we already have two points, start anew. if len(self.points) == 3: self.points = [] # Save this point. self.points.append((event.x, event.y)) # Update the display. self.update_display()

If we already have three points, that means we have previously displays a Morley triangle and we're starting a new one. In that case, the program clears the points list.

Next, the code adds the new point to the points list. It then calls the update_display method to draw the points and the Morley triangle if as have enough information.

Morley's Triangle

The following update_display method draws the currently selected points. If we have three points, it also draws the angle trisectors and Morley's triangle.

def update_display(self): '''Draw the points and arcs if defined.''' # Clear the canvas. self.canvas.delete(tk.ALL) # Drawing parameters. r = 5 # If we have three points, find the trisectors, etc. if len(self.points) == 3: # Find the trisectors. pois = find_trisector_intersections(self.points) # Draw Morley's triangle. self.canvas.create_polygon(pois, outline='green', fill='light green') # Draw the trisectors. for i, point in enumerate(self.points): i1 = (i - 1) % len(self.points) self.canvas.create_line( point[0], point[1], pois[i][0], pois[i][1], fill='blue') self.canvas.create_line( point[0], point[1], pois[i1][0], pois[i1][1], fill='blue') # Draw the trisector points. for poi in pois: self.canvas.create_oval( poi[0] - r, poi[1] - r, poi[0] + r, poi[1] + r, fill='light green') # Draw the triangle. self.canvas.create_polygon(self.points, outline='red', fill='') # Draw the points. for point in self.points: self.canvas.create_oval( point[0] - r, point[1] - r, point[0] + r, point[1] + r, fill='pink')

The code first clears the Canvas widget. If we have three points, it performs four tasks.

First, the code calls find_trisector_intersections (shown shortly) to find the three trisector points of intersection that define Morley's triangle. It then draws the triangle defined by those three points of intersection.

Fourth, the code draws the angle trisectors. To draw the trisectors, the program connects each triangle vertex to the corresponding POI and the POI before that one. In other words, point number i connects to POI numbers i and i - 1.

Third, after it draws the trisectors, the code draws the POIs so you see circles at the corners of Morley's triangle.

Fourth, the code calls the Canvas widget's create_polygon method to draw the user's selected triangle.

If we have three points, the program just drew the user's triangle, Morley's triangle, the trisectors, and the POIs. Whether we have three points or not, the code then loops through the user's selected points and draws them.

The only remaining detail is the find_trisector_intersections method.

find_trisector_intersections

The following code shows the find_trisector_intersections function.

def find_trisector_intersections(points): '''Find the points where the triangle's trisectors intersect.''' trisector_points = find_triangle_trisector_points(points) pois = [] for i1 in range(len(points)): p11 = points[i1] p12 = trisector_points[i1][1] i2 = (i1 + 1) % len(points) p21 = points[i2] p22 = trisector_points[i2][0] lines_intersect, segments_intersect, intersection, \ close_p1, close_p2 = find_line_intersection(p11, p12, p21, p22) pois.append(intersection) # Convert tuples into Vector2 objects and return. return pois

This function first calls the find_triangle_trisector_points function (we'll get to that, I promise) to find points along each angle's trisectors. Each entry in the returned list is a tuple holding two (X, Y) tuples representing points on the angle's two trisectors.

The code then loops through the points selected by the user. For point i, it finds the intersection between the second trisector of point i and the first trisector of point i + 1. That gives the intersection between two adjacent trisectors, and that gives the location of one of the corners of Morley's triangle.

The program uses the find_line_intersection function to find the intersection between the two trisectors. For information about that function, see my post Determine where two lines intersect in Python.

The code appends the new intersection to the pois list.

After if finds all of the intersections, the program returns the pois list.

find_triangle_trisector_points

The following code shows the find_triangle_trisector_points function.

def find_triangle_trisector_points(points): '''Return a list of tuples holding points on each point's trisectors.''' # Convert the points from (X, Y) tuples into Vector2 objects. points = [pygame.Vector2(point[0], point[1]) for point in points] # Make the list of trisector points. trisector_points = [] for i2 in range(len(points)): # Find this point's trisectors. i1 = (i2 - 1) % len(points) i3 = (i2 + 1) % len(points) trisector_points.append( find_angle_trisector_points( points[i1], points[i2], points[i3])) # Convert the results back into (X, Y) tuples. return trisector_points

This function finds points on the triangle's angle trisectors. The find_angle_trisector_points function that it uses takes advantage of some methods provided by Pygame's Vector2 class, so this function first coverts the points from (X, Y) tuples into Vector2 objects.

Next, the code loops through the converted points. It finds the triangle's other two points and passes the three points to the find_angle_trisector_points function (shown next) to find the trisectors for this angle. That function returns two points on this angle's trisectors and this code appends those points to the trisector_points list.

After it has found all of the angles' trisector points, the function returns the trisector_points list.

find_angle_trisector_points

The following find_angle_trisector_points function (I promise this is the last branch down this rabbit hole) finds points on a single angle's trisectors.

def find_angle_trisector_points(p1, p2, p3): '''Find points that lie along the trisectors of the angle p1-p2-p3.''' # Get the angle. v12 = p1 - p2 v32 = p3 - p2 angle = v12.angle_to(v32) % 360 # Find the trisector angles. angle1 = angle / 3 angle2 = 2 * angle / 3 # Rotate to find trisector vectors. tri1 = v12.rotate(angle1) tri2 = v12.rotate(angle2) # Find points along the new vectors. out_p1 = p2 + tri1 out_p2 = p2 + tri2 # Return the trisector points. return (out_p1, out_p2)

The point p2 is the one at the angle in question and points p1 and p3 are the triangle's other two vertices.

This function uses vector subtraction to find the vectors from p1 to p2, and from p3 to p2. If then uses the first vector's angle_to method to find the angle from the first vector to the second. (This is one of the places where the Vetcor2 class is handy.) It takes the angle modulo 360 so the result lies between 0° and 359°.

The code divides the angle by three to get the trisected angles. It then uses the first vector's rotate method to find new vectors rotated by the trisected angles. (Another fine use of the Vector2 class.)

Finally, the function adds the vertex p2 to the new vectors to get points that lie along the trisection vectors. (Our last use of Vector2 class capabilities in this example.) The function then returns the two trisection points.

Conclusion

[If you pick points clockwise, the equilateral triangle lies outside of your triangle.] It was a bit of a long walk to get to Morley's trisector theorem, but the pieces aren't too bad if you take them one at a time. The find_angle_trisector_points function finds the trisectors for a single angle, find_triangle_trisector_points uses it to find trisectors for all of a triangle's points, and find_trisector_intersections intersects those trisectors to find the points that make up Morley's triangle.

One thing I haven't addressed is how the triangle depends on the orientation of the points you select. If you pick the points in counterclockwise order, you get the picture shown at the top of the post. If you pick the points in clockwise order, you still get an equilateral triangle but it lies outside of your triangle as shown in the picture on the right.

Download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.