Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Python Line Intersection for Pygame

0.00/5 (No votes)
13 Jan 2015 1  
An algorithm to compute the intersection point (if it exists) of a pair of 2D line segments

Introduction

This is sort of an exercise in applied algebra (specifically, application of the equation y = mx + b, the so-called slope-intercept form of a linear equation).

Background

I have a "toy" 3D viewing app written in Python, using Pygame for the UI. It has wireframe display, and a primitive "hidden surface removal" option (namely, sort the polygons by their z coordinates, then "paint" in back-to-front order so the closer polygons hide the further polygons). I wanted to add a hidden-line-removal option, because Pygame doesn't seem to offer an "outlined polygon" drawing method. I thought this code might be a step in that direction. Later, I discovered that MeshLab offers the features I wanted... maybe I'll work on implementing hidden-line-removal someday... maybe...

Using the Code

To use the code, you should install Python (obviously) as well as Pygame (although the intersection algorithm doesn't depend on Pygame, the test app does). Both the 2.x and 3.x flavors of Python should work.

Alternatively, you can just extract four functions (slope(), y_intercept(), intersect() & segment_intersect()) and use them as you see fit.

The function intersect( line1, line2 ) tries to compute the intersection of line1 and line2; it includes a primitive sort of guard against numeric overflow, and returns a point (a tuple (x,y)).

C++
#
# the line 'data structure' looks like this:

line1 = [(100.,100.),(700.,700.)]

# (defines a line segment between the points (100,100) and (700,700))

The line data structure used ensures that line endpoints are compatible with Pygame's line-drawing routines, e.g.:

C++
# draw line1 
# ("anti-aliased" and using the color stored in the fgcolor variable, 
# which must be defined previously)

pygame.draw.aaline(screen, fgcolor, line1[0], line1[1])

These three functions implement the basic line-intersection-point computation:

C++
def slope(p1, p2) :
   return (p2[1] - p1[1]) * 1. / (p2[0] - p1[0])
   
def y_intercept(slope, p1) :
   return p1[1] - 1. * slope * p1[0]
   
def intersect(line1, line2) :
   min_allowed = 1e-5   # guard against overflow
   big_value = 1e10     # use instead (if overflow would have occurred)
   m1 = slope(line1[0], line1[1])
   print( 'm1: %d' % m1 )
   b1 = y_intercept(m1, line1[0])
   print( 'b1: %d' % b1 )
   m2 = slope(line2[0], line2[1])
   print( 'm2: %d' % m2 )
   b2 = y_intercept(m2, line2[0])
   print( 'b2: %d' % b2 )
   if abs(m1 - m2) < min_allowed :
      x = big_value
   else :
      x = (b2 - b1) / (m1 - m2)
   y = m1 * x + b1
   y2 = m2 * x + b2
   print( '(x,y,y2) = %d,%d,%d' % (x, y, y2))
   return (int(x),int(y))

The function segment_intersect( line1, line2 ) tries to deal with the fact that line1 and line2 define line segments, and there may be no point of intersection between them even when they aren't parallel (i.e., when the intersection point between the infinite lines isn't part of (at least one of) the finite line segments). It returns either the intersection point returned by intersect(), or None (Python's equivalent of Java's null, C's NULL, etc.).

C++
def segment_intersect(line1, line2) :
   intersection_pt = intersect(line1, line2)
   
   print( line1[0][0], line1[1][0], line2[0][0], line2[1][0], intersection_pt[0] )
   print( line1[0][1], line1[1][1], line2[0][1], line2[1][1], intersection_pt[1] )
   
   if (line1[0][0] < line1[1][0]) :
      if intersection_pt[0] < line1[0][0] or intersection_pt[0] > line1[1][0] :
         print( 'exit 1' )
         return None
   else :
      if intersection_pt[0] > line1[0][0] or intersection_pt[0] < line1[1][0] :
         print( 'exit 2' )
         return None
         
   if (line2[0][0] < line2[1][0]) :
      if intersection_pt[0] < line2[0][0] or intersection_pt[0] > line2[1][0] :
         print( 'exit 3' )
         return None
   else :
      if intersection_pt[0] > line2[0][0] or intersection_pt[0] < line2[1][0] :
         print( 'exit 4' )
         return None

   return intersection_pt

To run the test application:

C++
python linex.py

The test app's UI is very simple. Two line segments are drawn, and their intersection (if any) has a small circle drawn around it. One of the four line segment endpoints is considered the "active" endpoint, indicated by a small red circle. To change which endpoint is "active," press 0,1,2, or 3. To change the location of the "active" endpoint, click in the Pygame window's drawing area with the mouse. To quit, press 'q' (or use one of your OS's shortcuts).

Points of Interest

To guard against numeric overflow, the intersect() function uses a pair of constants to prevent division by zero (or division by any number that might be small enough to cause overflow). Primitive, but it works for this application.

The code is liberally interspersed with print() statements for debugging.

History

  • 12 Jan 2015: Version 1 posted

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here