Robotics & Hardware  

Point, Line, Safety: How Triangle Modeling Keeps Warehouse Robots from Crashing Using Python

Table of Contents

  • Introduction

  • What Is a Point-Based Triangle Model?

  • Real-World Scenario: Collision Avoidance in Autonomous Forklifts

  • Building the Triangle Model in Python

  • Complete Implementation with Validation

  • Best Practices for Geometric Modeling

  • Conclusion

Introduction

In computational geometry, a triangle is more than just three numbers—it’s a spatial relationship defined by points in space. Modeling triangles using a point structure (rather than just side lengths) unlocks powerful capabilities: rotation, translation, collision detection, and real-time spatial reasoning.

This approach is essential in robotics, gaming, GIS, and computer vision. In this article, we’ll explore how to model a triangle using 2D points in Python, grounded in a compelling real-world use case from warehouse automation, and ensure our implementation is robust, intuitive, and production-ready.

What Is a Point-Based Triangle Model?

A point-based triangle represents a triangle as three coordinate pairs—typically (x, y) in 2D space. Unlike side-length models, this preserves orientation, position, and shape, enabling:

  • Accurate spatial queries (e.g., “Is this robot inside the triangle?”)

  • Dynamic transformations (e.g., moving or rotating the triangle)

  • Integration with sensor data (e.g., LiDAR or camera coordinates)

This model treats geometry as it exists in the real world: anchored in space, not abstracted into lengths.

Real-World Scenario: Collision Avoidance in Autonomous Forklifts

In a smart warehouse, autonomous forklifts navigate narrow aisles while avoiding workers, shelves, and other robots. Safety zones around humans are often modeled as triangular regions—for example, a “personal space” cone extending from a worker’s position.

Each safety triangle is defined by:

  • The worker’s current GPS or indoor positioning coordinates (Point A)

  • Two boundary points (Points B and C) forming a 60° forward-facing zone

The forklift’s control system continuously:

  1. Receives real-time (x, y) positions of workers

  2. Constructs dynamic safety triangles

  3. Checks if the forklift’s path intersects any triangle

PlantUML Diagram

If the triangle weren’t modeled as points, the system couldn’t adapt to movement or orientation—rendering collision avoidance useless. This isn’t theoretical: companies like Amazon Robotics and Geek+ deploy similar logic daily.

Building the Triangle Model in Python

We’ll create a clean, object-oriented Point and Triangle structure with built-in validation and utility methods.

from typing import Tuple
import math

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def distance_to(self, other: 'Point') -> float:
        return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

    def __eq__(self, other):
        if not isinstance(other, Point):
            return False
        return abs(self.x - other.x) < 1e-9 and abs(self.y - other.y) < 1e-9


class Triangle:
    def __init__(self, p1: Point, p2: Point, p3: Point):
        self.p1, self.p2, self.p3 = p1, p2, p3
        if not self._is_valid():
            raise ValueError("Points are collinear or duplicate—cannot form a triangle.")

    def _is_valid(self) -> bool:
        # Compute area using cross product; area = 0 means collinear
        area = abs(
            (self.p2.x - self.p1.x) * (self.p3.y - self.p1.y) -
            (self.p3.x - self.p1.x) * (self.p2.y - self.p1.y)
        ) / 2.0
        return area > 1e-9

    def side_lengths(self) -> Tuple[float, float, float]:
        a = self.p1.distance_to(self.p2)
        b = self.p2.distance_to(self.p3)
        c = self.p1.distance_to(self.p3)
        return a, b, c

    def perimeter(self) -> float:
        a, b, c = self.side_lengths()
        return a + b + c

    def area(self) -> float:
        # Using cross product (same as in _is_valid)
        return abs(
            (self.p2.x - self.p1.x) * (self.p3.y - self.p1.y) -
            (self.p3.x - self.p1.x) * (self.p2.y - self.p1.y)
        ) / 2.0

    def is_isosceles(self, tol: float = 1e-9) -> bool:
        a, b, c = self.side_lengths()
        return (abs(a - b) <= tol) or (abs(b - c) <= tol) or (abs(a - c) <= tol)

    def contains_point(self, pt: Point) -> bool:
        # Barycentric coordinate method
        denom = (self.p2.y - self.p3.y) * (self.p1.x - self.p3.x) + \
                (self.p3.x - self.p2.x) * (self.p1.y - self.p3.y)
        if abs(denom) < 1e-9:
            return False

        a = ((self.p2.y - self.p3.y) * (pt.x - self.p3.x) + 
             (self.p3.x - self.p2.x) * (pt.y - self.p3.y)) / denom
        b = ((self.p3.y - self.p1.y) * (pt.x - self.p3.x) + 
             (self.p1.x - self.p3.x) * (pt.y - self.p3.y)) / denom
        c = 1 - a - b

        return (a >= -1e-9) and (b >= -1e-9) and (c >= -1e-9)

This design ensures spatial fidelity, numerical stability, and extensibility.

Complete Implementation with Validation

from typing import Tuple
import math
import unittest

# --- Geometric Classes ---

class Point:
    """Represents a point in a 2D plane."""
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def distance_to(self, other: 'Point') -> float:
        """Calculates the Euclidean distance to another point."""
        return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)

    def __repr__(self):
        """String representation of the Point."""
        return f"Point({self.x}, {self.y})"

    def __eq__(self, other):
        """Checks for equality with tolerance."""
        if not isinstance(other, Point):
            return False
        # Use a small tolerance for float comparison
        return abs(self.x - other.x) < 1e-9 and abs(self.y - other.y) < 1e-9


class Triangle:
    """Represents a triangle defined by three Point objects."""
    def __init__(self, p1: Point, p2: Point, p3: Point):
        self.p1, self.p2, self.p3 = p1, p2, p3
        if not self._is_valid():
            raise ValueError("Points are collinear or duplicate—cannot form a triangle.")

    def _is_valid(self) -> bool:
        """Checks if the three points form a non-degenerate triangle (i.e., not collinear)."""
        # Compute area using cross product; area = 0 means collinear
        # Area = 0.5 * |(x2 - x1)*(y3 - y1) - (x3 - x1)*(y2 - y1)|
        cross_product = (self.p2.x - self.p1.x) * (self.p3.y - self.p1.y) - \
                        (self.p3.x - self.p1.x) * (self.p2.y - self.p1.y)
        # Check if the absolute value of the cross product (twice the area) is greater than a small epsilon
        return abs(cross_product) > 2 * 1e-9 # Area > 1e-9

    def side_lengths(self) -> Tuple[float, float, float]:
        """Returns the lengths of the three sides (a=p1-p2, b=p2-p3, c=p1-p3)."""
        a = self.p1.distance_to(self.p2)
        b = self.p2.distance_to(self.p3)
        c = self.p1.distance_to(self.p3)
        return a, b, c

    def perimeter(self) -> float:
        """Calculates the perimeter of the triangle."""
        a, b, c = self.side_lengths()
        return a + b + c

    def area(self) -> float:
        """Calculates the area of the triangle using the determinant/cross-product method."""
        # Area = 0.5 * |(x2 - x1)*(y3 - y1) - (x3 - x1)*(y2 - y1)|
        return abs(
            (self.p2.x - self.p1.x) * (self.p3.y - self.p1.y) -
            (self.p3.x - self.p1.x) * (self.p2.y - self.p1.y)
        ) / 2.0

    def is_isosceles(self, tol: float = 1e-9) -> bool:
        """Checks if the triangle is isosceles (at least two sides are equal)."""
        a, b, c = self.side_lengths()
        # Check if any two side lengths are equal within tolerance
        return (abs(a - b) <= tol) or (abs(b - c) <= tol) or (abs(a - c) <= tol)

    def contains_point(self, pt: Point) -> bool:
        """Checks if a point is inside or on the boundary of the triangle using the Barycentric coordinate method."""
        # This is a robust method; point (x,y) is inside if barycentric coordinates (a, b, c) are all >= 0.
        
        # Denominator is twice the triangle's area.
        denom = (self.p2.y - self.p3.y) * (self.p1.x - self.p3.x) + \
                (self.p3.x - self.p2.x) * (self.p1.y - self.p3.y)
        
        # If area is too small, something is wrong, though _is_valid should handle this.
        if abs(denom) < 2 * 1e-9: # 2*Area < 2*epsilon
            return False

        # Calculate coordinates 'a' (weight for p1) and 'b' (weight for p2)
        a = ((self.p2.y - self.p3.y) * (pt.x - self.p3.x) + 
             (self.p3.x - self.p2.x) * (pt.y - self.p3.y)) / denom
        b = ((self.p3.y - self.p1.y) * (pt.x - self.p3.x) + 
             (self.p1.x - self.p3.x) * (pt.y - self.p3.y)) / denom
        c = 1 - a - b # Coordinate 'c' (weight for p3)

        # Point is inside if all coordinates are non-negative (within tolerance)
        return (a >= -1e-9) and (b >= -1e-9) and (c >= -1e-9)

# --- Unit Tests ---

class TestTriangleModel(unittest.TestCase):
    """Tests for the Point and Triangle classes."""
    
    def test_point_distance(self):
        p1 = Point(0, 0)
        p2 = Point(3, 4)
        self.assertAlmostEqual(p1.distance_to(p2), 5.0)

    def test_valid_triangle(self):
        t = Triangle(Point(0,0), Point(1,0), Point(0,1))
        self.assertAlmostEqual(t.area(), 0.5)
        self.assertTrue(t.is_isosceles(), "Should be isosceles (right triangle)")

    def test_collinear_points(self):
        with self.assertRaises(ValueError):
            Triangle(Point(0,0), Point(1,1), Point(2,2))

    def test_duplicate_points(self):
        with self.assertRaises(ValueError):
            Triangle(Point(0,0), Point(0,0), Point(1,1))

    def test_contains_point(self):
        t = Triangle(Point(0,0), Point(2,0), Point(1,2))
        
        # Inside
        self.assertTrue(t.contains_point(Point(1, 0.5)), "Inside point")
        
        # Outside
        self.assertFalse(t.contains_point(Point(3, 3)), "Outside point")
        
        # On edge/vertex
        self.assertTrue(t.contains_point(Point(1, 0)), "On edge point")
        self.assertTrue(t.contains_point(Point(0, 0)), "On vertex point")

    def test_perimeter_and_sides(self):
        # A 3-4-5 right triangle
        t = Triangle(Point(0,0), Point(3,0), Point(0,4))
        a, b, c = t.side_lengths()
        
        # p1-p2=3, p2-p3=5, p1-p3=4
        self.assertAlmostEqual(a, 3.0)
        self.assertAlmostEqual(b, 5.0)
        self.assertAlmostEqual(c, 4.0)
        self.assertAlmostEqual(t.perimeter(), 12.0)
        self.assertAlmostEqual(t.area(), 6.0)
        self.assertFalse(t.is_isosceles(), "Should not be isosceles")
        
    def test_isosceles(self):
        # Isosceles, non-right triangle
        t_iso = Triangle(Point(0, 0), Point(5, 0), Point(2.5, 4))
        self.assertTrue(t_iso.is_isosceles())
        
        # Equilateral triangle (also isosceles)
        t_equ = Triangle(Point(0, 0), Point(1, 0), Point(0.5, math.sqrt(3)/2))
        self.assertTrue(t_equ.is_isosceles())

# --- Interactive Section ---

def get_point_input(prompt: str) -> Point:
    """Helper function to get point coordinates from user."""
    while True:
        try:
            coords = input(f"Enter coordinates for {prompt} (x, y): ").split(',')
            if len(coords) != 2:
                raise ValueError("Requires exactly two numbers.")
            x = float(coords[0].strip())
            y = float(coords[1].strip())
            return Point(x, y)
        except ValueError as e:
            print(f"Invalid input: {e}. Please try again.")

def interactive_mode():
    """Runs the interactive demonstration."""
    print("\n" + "="*50)
    print("Welcome to the Triangle Model Interactive Demo! ")
    print("="*50 + "\n")

    # 1. Run Unit Tests first
    print("--- Running Unit Tests ---")
    
    # Run tests without exiting the program
    runner = unittest.TextTestRunner(verbosity=2)
    suite = unittest.TestLoader().loadTestsFromTestCase(TestTriangleModel)
    test_result = runner.run(suite)
    
    if test_result.wasSuccessful():
        print("\n All unit tests passed successfully!")
    else:
        print("\n Some unit tests failed. Please review the code.")
        
    print("\n" + "-"*50)
    
    # 2. Interactive Triangle Creation
    print("--- Create Your Own Triangle ---")
    
    try:
        p1 = get_point_input("Point 1 (p1)")
        p2 = get_point_input("Point 2 (p2)")
        p3 = get_point_input("Point 3 (p3)")
        
        my_triangle = Triangle(p1, p2, p3)
        print("\n Triangle created successfully!")
        
        print("\n--- Triangle Properties ---")
        print(f"Vertices: {my_triangle.p1}, {my_triangle.p2}, {my_triangle.p3}")
        
        a, b, c = my_triangle.side_lengths()
        print(f"Side Lengths: a={a:.4f}, b={b:.4f}, c={c:.4f}")
        print(f"Perimeter: {my_triangle.perimeter():.4f}")
        print(f"Area: {my_triangle.area():.4f}")
        
        if my_triangle.is_isosceles():
            print("Type: Isosceles (at least two sides are equal).")
        else:
            print("Type: Scalene (all sides are different lengths).")

        # 3. Interactive Point Containment Check
        print("\n--- Test Point Containment ---")
        test_pt = get_point_input("Test Point (pt)")
        
        if my_triangle.contains_point(test_pt):
            print(f"\n Point {test_pt} is **INSIDE** or **ON THE BOUNDARY** of the triangle.")
        else:
            print(f"\n Point {test_pt} is **OUTSIDE** the triangle.")

    except ValueError as e:
        print(f"\n Error creating triangle: {e}")
    except Exception as e:
        print(f"\nAn unexpected error occurred: {e}")
    
    print("\n" + "="*50)
    print("Interactive Demo Complete. Goodbye!")
    print("="*50)


if __name__ == "__main__":
    interactive_mode()

3

All tests pass, including edge cases like degenerate triangles and point-in-triangle checks.

Best Practices for Geometric Modeling

  • Always validate geometry—collinear or duplicate points break assumptions.

  • Use cross products for area and collinearity—more stable than angle or slope checks.

  • Prefer immutable or well-encapsulated point objects—prevents accidental mutation.

  • Include spatial queries like contains_point—they’re often needed in real applications.

  • Design for 2D/3D extensibility—even if you start in 2D, plan for future dimensions.

Conclusion

Modeling a triangle as three points isn’t just academically elegant—it’s operationally essential in real-time systems like autonomous vehicles, drones, and industrial robots. By anchoring geometry in actual coordinates, we enable dynamic, responsive, and safe interactions with the physical world.

Whether you’re building a warehouse robot or a game engine, remember: geometry lives in space, not in numbers alone. Model it that way, and your code will be as smart as the systems it powers.