lib/mittsu/extras/geometries/torus_knot_buffer_geometry.rb
module Mittsu
class TorusKnotBufferGeometry < BufferGeometry
def initialize(radius = 100.0, tube = 40.0, radial_segments = 64, tubular_segments = 8, p_val = 2, q_val = 3)
super()
@type = 'TorusKnotBufferGeometry'
@parameters = {
radius: radius,
tube: tube,
radial_segments: radial_segments,
tubular_segments: tubular_segments,
p_val: p_val,
q_val: q_val
}
# buffers
indices = []
vertices = []
normals = []
uvs = []
# helper variables
vertex = Vector3.new
normal = Vector3.new
p1 = Vector3.new
p2 = Vector3.new
b = Vector3.new
t = Vector3.new
n = Vector3.new
# generate vertices, normals and uvs
for i in 0..tubular_segments do
# the radian "u" is used to calculate the position on the torus curve of the current tubular segement
u = i.to_f / tubular_segments.to_f * p_val.to_f * ::Math::PI * 2.0
# now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead.
# these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions
calculate_position_on_curve(u, p_val, q_val, radius, p1)
calculate_position_on_curve(u + 0.01, p_val, q_val, radius, p2)
# calculate orthonormal basis
t.sub_vectors(p2, p1)
n.add_vectors(p2, p1)
b.cross_vectors(t, n)
n.cross_vectors(b, t)
# normalize B, N. T can be ignored, we don't use it
b.normalize
n.normalize
for j in 0..radial_segments do
# now calculate the vertices. they are nothing more than an extrusion of the torus curve.
# because we extrude a shape in the xy-plane, there is no need to calculate a z-value.
v = j.to_f / radial_segments.to_f * ::Math::PI * 2.0
cx = -tube * ::Math.cos(v)
cy = tube * ::Math.sin(v)
# now calculate the final vertex position.
# first we orient the extrusion with our basis vectos, then we add it to the current position on the curve
vertex.x = p1.x + (cx * n.x + cy * b.x)
vertex.y = p1.y + (cx * n.y + cy * b.y)
vertex.z = p1.z + (cx * n.z + cy * b.z)
vertices += vertex.elements
# normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal)
normal.sub_vectors(vertex, p1).normalize
normals += normal.elements
# uv
uvs << i.to_f / tubular_segments.to_f
uvs << j.to_f / radial_segments.to_f
end
end
# generate indices
for j in 1..tubular_segments do
for i in 1..radial_segments do
# indices
a = (radial_segments + 1) * (j - 1) + (i - 1)
b = (radial_segments + 1) * j + (i - 1)
c = (radial_segments + 1) * j + i
d = (radial_segments + 1) * (j - 1) + i
# faces
indices += [a, b, d]
indices += [b, c, d]
end
end
# build geometry
self[:index] = BufferAttribute.new(indices, 1)
self[:position] = BufferAttribute.new(vertices, 3)
self[:normal] = BufferAttribute.new(normals, 3)
self[:uv] = BufferAttribute.new(uvs, 2)
end
private
def calculate_position_on_curve(u, p_val, q_val, radius, position)
cu = ::Math.cos(u)
su = ::Math.sin(u)
qu_over_p = q_val.to_f / p_val.to_f * u
cs = ::Math.cos(qu_over_p)
position.x = radius * (2.0 + cs) * 0.5 * cu
position.y = radius * (2.0 + cs) * su * 0.5
position.z = radius * ::Math.sin(qu_over_p) * 0.5
end
end
end