Previously I reported on creating a Vec2D class for py5 and using it to create sketches. Now I’ve added a (work in progress) Vec3D class. I have found a neat way to use py5.vertices
with both vector types, by implementing a tuple()
method for the vector types. Here is Andres Colubri trefoil sketch converted to run on py5:-
import py5
import math
from surface.surface import *
PI = math.pi
W = 1024
H = 768
def settings():
py5.size(1024, 768, py5.P3D)
def setup():
global pg, trefoil # PGraphics, PShape
sketch_title('Trefoil')
py5.texture_mode(py5.NORMAL)
py5.no_stroke()
# Creating offscreen surface for 3D rendering.
pg = py5.create_graphics(32, 512, py5.P3D)
with pg.begin_draw():
pg.background(0, 0)
pg.no_stroke()
pg.fill(255, 0, 0, 200)
# Saving trefoil surface into a PShape3D object
trefoil = create_trefoil(350, 60, 15, pg)
def draw():
py5.background(0)
with pg.begin_draw():
pg.ellipse(py5.random(pg.width), py5.random(pg.height), 4, 4)
py5.ambient(250, 250, 250)
py5.point_light(255, 255, 255, 0, 0, 200)
with py5.push_matrix():
py5.translate(W / 2, H / 2, -200)
py5.rotate_x(py5.frame_count * PI / 500)
py5.rotate_y(py5.frame_count * PI / 500)
py5.shape(trefoil)
def sketch_title(name):
py5.get_surface().set_title(name)
py5.run_sketch()
And the surface module:-
"""
Code to draw a trefoil knot surface, normals and texture coordinates.
Adapted from the parametric equations example by Philip Rideout:
http://iphone-3d-programming.labs.oreilly.com/ch03.html
"""
import py5
from vector.vec3d import Vec3D
import math
TWO_PI = math.tau
def create_trefoil(s, ny, nx, tex):
"""
This function draws a trefoil knot surface as a triangle mesh derived
from its parametric equation.
"""
obj = py5.create_shape()
with obj.begin_shape(py5.TRIANGLES):
obj.texture(tex)
for j in range(nx):
u0 = float(j) / nx
u1 = float(j + 1) / nx
for i in range(ny):
v0 = float(i) / ny
v1 = float(i + 1) / ny
p0 = eval_point(u0, v0)
n0 = eval_normal(u0, v0)
p1 = eval_point(u0, v1)
n1 = eval_normal(u0, v1)
p2 = eval_point(u1, v1)
n2 = eval_normal(u1, v1)
# Triangle p0-p1-p2
obj.normal(n0.x, n0.y, n0.z)
obj.vertex(s * p0.x, s * p0.y, s * p0.z, u0, v0)
obj.normal(n1.x, n1.y, n1.z)
obj.vertex(s * p1.x, s * p1.y, s * p1.z, u0, v1)
obj.normal(n2.x, n2.y, n2.z)
obj.vertex(s * p2.x, s * p2.y, s * p2.z, u1, v1)
p1 = eval_point(u1, v0)
n1 = eval_normal(u1, v0)
# Triangle p0-p2-p1
obj.normal(n0.x, n0.y, n0.z)
obj.vertex(s * p0.x, s * p0.y, s * p0.z, u0, v0)
obj.normal(n2.x, n2.y, n2.z)
obj.vertex(s * p2.x, s * p2.y, s * p2.z, u1, v1)
obj.normal(n1.x, n1.y, n1.z)
obj.vertex(s * p1.x, s * p1.y, s * p1.z, u1, v0)
return obj
def eval_normal(u, v):
"""
Evaluates the surface normal corresponding to normalized parameters (u, v)
"""
# Compute the tangents and their cross product.
p = eval_point(u, v)
tang_u = eval_point(u + 0.01, v)
tang_v = eval_point(u, v + 0.01)
tang_u -= p
tang_v -= p
norm_uv = tang_v.cross(tang_u)
norm_uv.normalize()
return norm_uv
def eval_point(u, v):
"""
Evaluates the surface point corresponding to normalized parameters (u, v)
"""
a, b, c, d = 0.5, 0.3, 0.5, 0.1
s = TWO_PI * u
t = (TWO_PI * (1 - v)) * 2
r = a + b * math.cos(1.5 * t)
x = r * math.cos(t)
y = r * math.sin(t)
z = c * math.sin(1.5 * t)
dv = Vec3D()
dv.x = (-1.5 * b * math.sin(1.5 * t) * math.cos(t) -
(a + b * math.cos(1.5 * t)) * math.sin(t))
dv.y = (-1.5 * b * math.sin(1.5 * t) * math.sin(t) +
(a + b * math.cos(1.5 * t)) * math.cos(t))
dv.z = 1.5 * c * math.cos(1.5 * t)
q = dv
q.normalize()
qvn = Vec3D(q.y, -q.x, 0)
qvn.normalize()
ww = q.cross(qvn)
pt = Vec3D()
pt.x = x + d * (qvn.x * math.cos(s) + ww.x * math.sin(s))
pt.y = y + d * (qvn.y * math.cos(s) + ww.y * math.sin(s))
pt.z = z + d * ww.z * math.sin(s)
return pt
For more worked examples and the Vec2D and Vec3D classes see this github repo