Ray Casting#
Adapted from https://p5js.org/examples/3d-ray-casting.html
Original example by Jonathan Watson. Detecting the position of the mouse in 3D space with ray casting.
from proceso import Sketch
p5 = Sketch()
p5.describe("A pink sphere is drawn on different surfaces as a person mouses over them.")
objects = []
eye_z: float
def setup():
p5.create_canvas(710, 400, p5.WEBGL)
global eye_z
eye_z = (
p5.height / 2 / p5.tan((30 * p5.PI) / 180)
) # The default distance the camera is away from the origin.
objects.append(IntersectPlane(1, 0, 0, -100, 0, 0)) # Left wall
objects.append(IntersectPlane(1, 0, 0, 100, 0, 0)) # Right wall
objects.append(IntersectPlane(0, 1, 0, 0, -100, 0)) # Bottom wall
objects.append(IntersectPlane(0, 1, 0, 0, 100, 0)) # Top wall
objects.append(IntersectPlane(0, 0, 1, 0, 0, 0)) # Back wall
p5.no_stroke()
# p5.ambient_material(250)
def draw():
p5.background(0)
# Lights
p5.point_light(255, 255, 255, 0, 0, 400)
p5.ambient_light(244, 122, 158)
# Left wall
p5.push()
p5.translate(-100, 0, 200)
p5.rotate_y((90 * p5.PI) / 180)
p5.plane(400, 200)
p5.pop()
# Right wall
p5.push()
p5.translate(100, 0, 200)
p5.rotate_y((90 * p5.PI) / 180)
p5.plane(400, 200)
p5.pop()
# Bottom wall
p5.push()
p5.translate(0, 100, 200)
p5.rotate_x((90 * p5.PI) / 180)
p5.plane(200, 400)
p5.pop()
# Top wall
p5.push()
p5.translate(0, -100, 200)
p5.rotate_x((90 * p5.PI) / 180)
p5.plane(200, 400)
p5.pop()
p5.plane(200, 200) # Back wall
x = p5.mouse_x - p5.width / 2
y = p5.mouse_y - p5.height / 2
Q = p5.Vector(
0, 0, eye_z
) # A point on the ray and the default position of the camera.
v = p5.Vector(x, y, -eye_z) # The direction vector of the ray.
intersect: p5.Vector # The point of intersection between the ray and a plane.
closest_lambda = eye_z * 10 # The draw distance.
for o in objects:
lam = o.get_lambda(Q, v)
# The value of lambda where the ray intersects the object
if (lam < closest_lambda) and (lam > 0):
# Find the position of the intersection of the ray and the object.
intersect = Q + v * lam
closest_lambda = lam
# Cursor
p5.push()
p5.translate(intersect.x, intersect.y, intersect.z)
p5.fill(237, 34, 93)
p5.sphere(10)
p5.pop()
# Class for a plane that extends to infinity.
class IntersectPlane:
def __init__(
self, n1: float, n2: float, n3: float, p1: float, p2: float, p3: float
):
self.normal = p5.Vector(n1, n2, n3)
# The normal vector of the plane
self.point = p5.Vector(p1, p2, p3)
# A point on the plane
self.d = self.point.dot(self.normal)
def get_lambda(self, Q, v):
lam = 0
try:
lam = (-self.d - self.normal.dot(Q)) / self.normal.dot(v)
except:
pass
finally:
return lam
p5.run_sketch(setup=setup, draw=draw)