Triangle Circumcircle in py5

In this sketch I make use of the cross product of two 2D vectors, to determine whether 3 points are collinear (NB: you cannot do this with PVector, because it returns the cross product of two 3D vectors, so is flawed). Here is the sketch:-

import py5
from vector.vec2d import Vec2D
from circle.t_points import TrianglePoints
from circle.circumcircle import Circumcircle

def settings():
    py5.size(800, 600, py5.P2D)

def setup():
    global points
    sketch_title('Circumcircle')
    py5.ellipse_mode(py5.RADIUS)
    py5.scale(1, -1)
    py5.translate(0, -py5.height)
    points = TrianglePoints()


def draw():
    global points
    py5.background(0)
    py5.no_stroke()
    pts = points.positions()
    for pt in pts:
        py5.fill(255, 255, 0)
        py5.ellipse(pt.x, pt.y, 5, 5)
        py5.fill(250)
        py5.text(str(pt), pt.x - 30, pt.y - 20)
    py5.no_fill()
    py5.stroke(200, 0, 0)
    if points.full():
        py5.triangle(pts[0].x, pts[0].y, pts[1].x, pts[1].y, pts[2].x, pts[2].y)
    draw_circle()

def mouse_pressed():
    global points
    points.append(Vec2D(py5.mouse_x, py5.mouse_y))

def draw_circle():
    global points
    circumcircle = Circumcircle(points.positions())
    if points.full():
        circumcircle.calculate()
        center_point = circumcircle.center
        radius = circumcircle.radius
        py5.no_fill()
        py5.stroke(255)
        if points.collinear():
            return
        py5.ellipse(center_point.x, center_point.y, radius, radius)

def sketch_title(name):
    py5.get_surface().set_title(name)

py5.run_sketch()

For full code see github repo here. Here is snippet using matrix from numpy to calculate center and radius of circumcircle:-

import numpy as np
from vector.vec2d import Vec2D
# Circumcircle from 3 points
class Circumcircle:
    def __init__(self, points):
        self.points = points

    def calculate(self):
        vec = self.points[2]
        self.center = Vec2D(-(self.bx() / self.am()), -(self.by() / self.am()))
        self.radius = self.center.dist(vec) # points[2] = c

    # Matrix math see matrix_math.md and in detail
    # http://mathworld.wolfram.com/Circumcircle.html

    def am(self):
         pts = self.points
         a = [pts[0].x, pts[0].y, 1.0]
         b = [pts[1].x, pts[1].y, 1.0]
         c = [pts[2].x, pts[2].y, 1.0]
         matrix = np.array([a, b, c])
         return 2 * np.linalg.det(matrix)

    def bx(self):
        pts = self.points
        a = [pts[0].x**2 + pts[0].y**2, pts[0].y, 1.0]
        b = [pts[1].x**2 + pts[1].y**2, pts[1].y, 1.0]
        c = [pts[2].x**2 + pts[2].y**2, pts[2].y, 1.0]
        matrix = np.array([a, b, c])
        return -np.linalg.det(matrix)

    def by(self):
        pts = self.points
        a = [pts[0].x**2 + pts[0].y**2, pts[0].x, 1.0]
        b = [pts[1].x**2 + pts[1].y**2, pts[1].x, 1.0]
        c = [pts[2].x**2 + pts[2].y**2, pts[2].x, 1.0]
        matrix = np.array([a, b, c])
        return np.linalg.det(matrix)
1 Like

Nice work.

Thanks for including the code. Today I started working on a Py5Vector class and read through your code.

Can you elaborate on this? I believe you mentioned this to me a few times before. I noticed that Processing’s PVector class is really a 3D vector class that assumes the z value is zero for 2D vectors. I guess that is fine if the user’s code ignores the z value and treats the object as a 2D vector but calculating the cross product with another 2D or 3D vector is suspect.

I’d like for this class to be easy to use and suitable for students while also avoiding flawed math. Are there any other design pitfalls I should be aware of?

If foo is a 2D vector and bar is a 3D vector, do you see any downsizes to foo + bar evaluating to a 3D vector that makes the assumption that foo.z is zero? It’s either that or throw an error. It seems like upgrading to a 3D vector is more useful and more in line with what users would want.

Strictly speaking there is no cross product of two dimensional vectors, so PVector is wrong in that it provides a result. However the analogous computation with two dimensional vectors is useful, whatever it should be called (possibly wedge product) and for convenience it is often referred to as cross-product. But that’s not the only issue caused by conflating two dimensional and three dimensional vectors. For py5 particularly it inhibits use of p5.vertices for 2 dimensional sketches. If you look at libraries such as toxicilibs there is a case for a four dimensional vector.

Right, the cross product is only defined for 3D vectors. I did find this discussion about what to do for 2D vectors, which relates to what you are talking about:

Thanks for pointing out toxiclibs’s vector classes. These provide useful ideas as I work on this.

Right now I have (I believe) a working prototype with separate classes for 2D and 3D vectors, with both inheriting from the same class. I’ll consider 4D vectors also. Looking at toxiclibs I see the value of it.

I believe my prototype addresses this. The following code works correctly for 2D or 3D sketches when list_of_vectors is a list of 2 or 3 dimensional vectors, respectively.

with begin_shape():
    vertices(list_of_vectors)

Plus this is retaining the ability to do math on 2D and 3D vectors. For example, adding a 2D and 3D vector together should be allowed, and create a 3D vector.

Also, I added Vector4D class. :slight_smile: