#!/usr/bin/python3
# ====================================================================
# intersection of a line and plane (code found on the web)
# ====================================================================
import numpy as np
import graphics as gr
import coordinate_conversion as cc
import sys
# --------------------------------------------------------------------
# calculate the intersection
# --------------------------------------------------------------------
def intersect_line_plane(line_point,line_direction,
plane_point,plane_normal):
line_point = np.array(line_point)
line_direction = np.array(line_direction)
plane_point = np.array(plane_point)
plane_normal = np.array(plane_normal)
dot_product_direction_normal = np.dot(line_direction,plane_normal)
if dot_product_direction_normal == 0:
# Line is parallel to the plane
if np.dot(line_point-plane_point,plane_normal) == 0:
return "Line lies in the plane (infinite intersections)"
else:
return "Line is parallel to the plane and does not intersect"
else:
# Unique intersection point
t = -np.dot(line_point-plane_point,plane_normal)/ \
dot_product_direction_normal
intersection_point = line_point + (t * line_direction)
return intersection_point
# --------------------------------------------------------------------
# ---- create a graphics window (window coordinates)
# --------------------------------------------------------------------
def create_a_graphics_window(width:int,
height:int,
d3axes:bool=False,
planeoutline:bool=False,
origin_offset:tuple=(0,0),
title:str=None) -> gr.GraphWin:
'''
Create graphics window and maybe 3D Cartesian axes.
All calculations are window coordinates and based on
the graphics window's height and width.
Window Coordinates Cartesian Coordinates
(0,0) +Y
*---------- +X |
| |
| |
| |(0,0,0)
| *---------- +X
+Y /
/
+Z
Note: The viewer is assumed to be on the Z axis at +infinity.
(There is no perspective size change due to an object's
distance from the viewer.)
'''
# ---- create a graphics window
win = gr.GraphWin(title,width,height)
win.setBackground("white")
# ---- draw 3D axes?
if d3axes:
# ---- center of graphics window (origin - 0,0)
# ---- (in window coordinates)
xorigin = round(width/2.0)
yorigin = round(height/2.0)
# ---- length of X,Y,Z axis lines
# ---- (in window coordinates)
x_axis_length = round(xorigin/2.0)
y_axis_length = round(yorigin/2.0)
z_axis_length = round((x_axis_length+y_axis_length)/4.0)
# ---- origin's offset from the center of window
# ---- (in window coordinates)
xoffset = origin_offset[0]
yoffset = origin_offset[1]
xo = xorigin + xoffset # orgin X + offset
yo = yorigin + yoffset # orgin Y + offset
# ---- draw X axis (line)
##xl = gr.Line(gr.Point(xo,yo),
## gr.Point(xo-1+x_axis_length,yo))
xl = gr.Line(gr.Point(xo-1-x_axis_length,yo),
gr.Point(xo-1+x_axis_length,yo))
xl.setWidth(2)
xl.setFill("black")
xl.draw(win)
t = gr.Text(gr.Point(xo-1+x_axis_length+10,yo),' +X')
t.setFace("courier")
t.setSize(12)
t.setTextColor("black")
t.draw(win)
# ---- draw Y axis (line)
##yl = gr.Line(gr.Point(xo,yo),
## gr.Point(xo,yo+1-y_axis_length))
yl = gr.Line(gr.Point(xo,yo+1+y_axis_length),
gr.Point(xo,yo+1-y_axis_length))
yl.setWidth(2)
yl.setFill("black")
yl.draw(win)
t = gr.Text(gr.Point(xo,yo+1-y_axis_length-10),'+Y')
t.setTextColor("black")
t.setFace("courier")
t.setSize(12)
t.draw(win)
# ---- draw Z axis (line)
zl = gr.Line(gr.Point(xo,yo),
gr.Point(1-z_axis_length+xo+5,
z_axis_length+yo-1+5))
##zl = gr.Line(gr.Point(1-z_axis_length+xo,yo,
## z_axis_length-yo-1),
## gr.Point(1-z_axis_length+xo,
## z_axis_length+yo-1))
zl.setWidth(2)
zl.setFill("black")
zl.draw(win)
t = gr.Text(gr.Point(1-z_axis_length+xo-5,
z_axis_length+yo+15),'+Z')
t.setTextColor("black")
t.setFace("courier")
t.setSize(12)
t.draw(win)
# ---- draw an outline of the plane
if planeoutline:
ulx = 1 + xo - z_axis_length # upper left X
uly = -1 + yo - z_axis_length # upper left Y
lrx = 1 + xo + z_axis_length # lower left X
lry = -1 + yo + z_axis_length # lower left Y
print()
print('plane coordinates')
print(f'upper left X,Y = {ulx:.3f},{uly:.3f}')
print(f'lower right X,y = {lrx:.3f},{lry:.3f}')
c = gr.Circle(gr.Point(ulx,uly),4)
c.setFill('blue')
c.draw(win)
##ol = gr.Line(gr.Point(ulx,uly),gr.Point(ulx,lry))
##ol.setWidth(1)
##ol.setFill("black")
##ol.draw(win)
return (win,xoffset,yoffset)
# --------------------------------------------------------------
# ---- draw a 3D point (on 2D plane)
# ---- Note: origin offset X,Y are window coordinates
# --------------------------------------------------------------
def draw_a_point(win:gr.GraphWin,
pt:tuple,
radius=4,
point_color:str='black',
origin_offset:tuple=(0,0)) -> None:
'''
draw a point
Note: a 3D point is a tuple (x,y,z) but window coordinates
X,Y (Z=0) are used to draw a point (circle)
'''
x = pt[0] # Cartesian coord
y = pt[1] # Cartesian coord
# ---- convert point Cartesian coords to window coords
wx1,wy1 = cc.center_to_win_coords(x,y,
win.width,win.height)
# ---- draw a point (small circle)
wxo1 = wx1 + origin_offset[0]
wyo1 = wy1 + origin_offset[1]
c = gr.Circle(gr.Point(wxo1,wyo1),radius)
c.setFill(point_color)
if radius > 10:
c.setOutline("black")
c.setWidth(2)
c.draw(win)
return
# --------------------------------------------------------------
# ---- draw a line between two 3D points (on a 2D plane)
# ---- Note: origin offsets are window coordinates
# --------------------------------------------------------------
def draw_a_line(win:gr.GraphWin,
pt1:tuple,
pt2:tuple,
line_color:str='black',
origin_offset:tuple=(0,0)) -> None:
'''
draw a line between two points
Note: a 3D point is a tuple (x,y,z) but window coordinates
X,Y (Z=0) are used to draw a line
'''
# ---- convert point #1 Cartesian coords to window coords
x = pt1[0] # Cartesian coord
y = pt1[1] # Cartesian coord
wx1,wy1 = cc.center_to_win_coords(x,y,
win.width,win.height)
# ---- convert point #2 Cartesian coords to window coords
x = pt2[0] # Cartesian coord
y = pt2[1] # Cartesian coord
wx2,wy2 = cc.center_to_win_coords(x,y,
win.width,win.height)
# ---- draw a line
l = gr.Line(gr.Point(wx1,wy1),gr.Point(wx2,wy2))
l.setWidth(3)
l.setFill(line_color)
l.draw(win)
return
# --------------------------------------------------------------------
# ---- main
# --------------------------------------------------------------------
if __name__ == '__main__':
# ---- example 1
#line_pt = [200, 0, 200]
#line_dir = [0, 100, 0]
#plane_pt = [0, 0, 0]
#plane_norm = [0, 100, 0]
# ---- example 2
#line_pt = [0, 100, 0]
#line_dir = [100, 100, 100]
#plane_pt = [ 0, 0, 0]
#plane_norm = [ 0, 100, 0]
# ---- example 3
line_pt = [200, 200, 200]
line_dir = [200, 200, 0]
plane_pt = [ 0, 0, 100]
plane_norm = [100, 100, 0]
# line is (200,200,100),(400,400,200)
# ---- create graphics window and draw axes
win,x_offset,y_offset = create_a_graphics_window(801,801,
d3axes=True,
planeoutline=True,
title='Line/Plane Intersection Test')
print()
print('Window X,Y offset')
print(f'x offset = {x_offset:.3f}')
print(f'y offset = {y_offset:.3f}')
# ---- calculate line/plane intersection coordinates
intersection = intersect_line_plane(line_pt,line_dir,
plane_pt,plane_norm)
print()
print('intersection in Cartesian coordinates')
print(f'x = {intersection[0]:.3f}')
print(f'y = {intersection[1]:.3f}')
print(f'z = {intersection[2]:.3f}')
# ---- draw line/plane intersection point
draw_a_point(win,(intersection[0],intersection[1]),
point_color='red')
# ---- draw line (segment)
draw_a_line(win,(200,200,100),(400,400,200),
line_color='green')
# ----------------------------------------------------------------
# ---- wait for a mouse click in the window, then exit
print()
click = win.getMouse()
win.close()
sys.exit()