I'm animating the coffee mug torus homeomorphism.
I want my animation to match this gif as closely as possible. The gif above, along with the POV-ray code used to create it, can be found here:https://en.wikipedia.org/wiki/File:Mug_and_Torus_morph.gif.
I'm working with a relatively low poly mesh for now and here is what I have created:
The first part of the Wikipedia animation where the base of the cup rises is easy to make, so I'm excluding that part of the animation for this post. I just need help on the main transformation part.
To create this, I wrote a python script that sends the points of the cup to the closest point on the torus. The pseudocode of the python code is roughly as follows:
# for each vertex of the mug,
# keyframe the vertex coordinate at initial frame
# find the angle of the vertex relative to the center of the torus
# find the central point of the torus of which this angle belongs to (this new central point is a point along the major radius ring)
# solve for where the line through the vertex and central point intersects the torus (there are two points of intersection)
# set vertex coordinate to value of intersection that's closest to the vertex
# keyframe vertex coordinate at final frame
Here is the actual code:
import bpy
from math import *
from mathutils import *
mug = bpy.context.scene.objects["MorphingMug"]
verts = mug.data.vertices
#initial handle torus and target torus have a major radius of:
bigR = .5
#target torus has radius of:
r = .25
#center of both initial handle torus and target torus has x value of:
originX = .8
#for each vertex of the mug
for v in verts:
#keyframe vertex at framei
v.keyframe_insert(data_path = "co", frame = 1)
#get x, y, and z values of vertex
x = v.co.x
y = v.co.y
z = v.co.z
#difference vector between vertex and (center of torus = [originX, 0, 0])
d = Vector([x - originX, y, z])
#find the angle in the xz-plane of this difference vector
if v.co == Vector([originX, 0, 0]):
theta = pi
elif d.x == 0 and d.z > 0:
theta = pi/2
elif d.x == 0 and d.z < 0:
theta = -pi/2
elif d.x < 0:
theta = pi + atan(d.z/d.x)
else:
theta = atan(d.z/d.x)
#find the central point of the target torus of which this angle belongs to
c_1 = bigR * cos(theta) + originX
c_3 = bigR * sin(theta)
#line parameterization values of where the line through vertex and central point intersect target torus
t1 = -r/sqrt(c_1**2 - 2*c_1*x + c_3**2 - 2*c_3*z + x**2 + y**2 + z**2) + 1
t2 = r/sqrt(c_1**2 - 2*c_1*x + c_3**2 - 2*c_3*z + x**2 + y**2 + z**2) + 1
#first value of intersection
v1 = x + t1*(c_1 - x)
v2 = y + t1*(c_2 - y)
v3 = z + t1*(c_3 - z)
vec1 = [v1, v2, v3]
#second value of intersection
v1 = x + t2*(c_1 - x)
v2 = y + t2*(-y)
v3 = z + t2*(c_3 - z)
vec2 = [v1, v2, v3]
#difference of these intersection values and vertex
diff1 = Vector(vec1) - v.co
diff2 = Vector(vec2) - v.co
#choose the value that's closest to vertex
if diff1.magnitude > diff2.magnitude:
vec = vec2
else:
vec = vec1
#set vertex equal to closest intersection
v.co = vec
#keyframe at final position
v.keyframe_insert(data_path = "co", frame = 120)
There are clearly some issues with my animation. I don't think I'm seeing the math to this projection clearly. I do see that the top face up the cup should be sent to the uppermost point of the torus and same for the bottom face and the lowermost point. I also see the rightmost points of the non-handle part of the cup that are also at y = 0 are mapped upwards and downwards, which is incorrect, except for the point [.8, 0, 0], which I manually mapped to the correct position.
Maybe I need to pick different projective origin points based on the vertex position? Maybe I should split the mesh into vertex groups to make it more of a piecewise transformation? Maybe I need to reposition the non-handle part of the mug slightly more in the +x direction? Any help would be greatly appreciated. Thanks.
my file can be downloaded here: