2018-12-24 12:37:51 -08:00
|
|
|
from functools import reduce
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
import numpy as np
|
2018-03-31 15:11:35 -07:00
|
|
|
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.constants import OUT
|
|
|
|
from manimlib.constants import PI
|
|
|
|
from manimlib.constants import RIGHT
|
|
|
|
from manimlib.constants import TAU
|
|
|
|
from manimlib.utils.iterables import adjacent_pairs
|
2019-02-07 21:38:03 -08:00
|
|
|
from manimlib.utils.simple_functions import fdiv
|
2018-03-30 18:19:23 -07:00
|
|
|
|
2018-08-22 14:48:42 -07:00
|
|
|
|
2018-08-15 17:30:24 -07:00
|
|
|
def get_norm(vect):
|
|
|
|
return sum([x**2 for x in vect])**0.5
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-09-10 11:38:01 -07:00
|
|
|
# Quaternions
|
|
|
|
# TODO, implement quaternion type
|
|
|
|
|
|
|
|
|
|
|
|
def quaternion_mult(q1, q2):
|
|
|
|
w1, x1, y1, z1 = q1
|
|
|
|
w2, x2, y2, z2 = q2
|
|
|
|
return np.array([
|
|
|
|
w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
|
|
|
|
w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
|
|
|
|
w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2,
|
|
|
|
w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2,
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
def quaternion_from_angle_axis(angle, axis):
|
|
|
|
return np.append(
|
|
|
|
np.cos(angle / 2),
|
|
|
|
np.sin(angle / 2) * normalize(axis)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-09-27 17:35:40 -07:00
|
|
|
def angle_axis_from_quaternion(quaternion):
|
|
|
|
axis = normalize(
|
|
|
|
quaternion[1:],
|
|
|
|
fall_back=np.array([1, 0, 0])
|
|
|
|
)
|
|
|
|
angle = 2 * np.arccos(quaternion[0])
|
|
|
|
if angle > TAU / 2:
|
|
|
|
angle = TAU - angle
|
|
|
|
return angle, axis
|
|
|
|
|
|
|
|
|
2018-09-10 11:38:01 -07:00
|
|
|
def quaternion_conjugate(quaternion):
|
|
|
|
result = np.array(quaternion)
|
|
|
|
result[1:] *= -1
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def rotate_vector(vector, angle, axis=OUT):
|
2019-01-04 12:48:05 -08:00
|
|
|
if len(vector) == 2:
|
|
|
|
# Use complex numbers...because why not
|
|
|
|
z = complex(*vector) * np.exp(complex(0, angle))
|
|
|
|
return np.array([z.real, z.imag])
|
|
|
|
elif len(vector) == 3:
|
|
|
|
# Use quaternions...because why not
|
|
|
|
quat = quaternion_from_angle_axis(angle, axis)
|
|
|
|
quat_inv = quaternion_conjugate(quat)
|
|
|
|
product = reduce(
|
|
|
|
quaternion_mult,
|
|
|
|
[quat, np.append(0, vector), quat_inv]
|
|
|
|
)
|
|
|
|
return product[1:]
|
|
|
|
else:
|
|
|
|
raise Exception("vector must be of dimension 2 or 3")
|
2018-09-10 11:38:01 -07:00
|
|
|
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def thick_diagonal(dim, thickness=2):
|
2018-03-30 18:19:23 -07:00
|
|
|
row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
|
|
|
|
col_indices = np.transpose(row_indices)
|
2018-04-06 13:58:59 -07:00
|
|
|
return (np.abs(row_indices - col_indices) < thickness).astype('uint8')
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
|
|
|
|
def rotation_matrix(angle, axis):
|
|
|
|
"""
|
|
|
|
Rotation in R^3 about a specified axis of rotation.
|
|
|
|
"""
|
|
|
|
about_z = rotation_about_z(angle)
|
|
|
|
z_to_axis = z_to_vector(axis)
|
|
|
|
axis_to_z = np.linalg.inv(z_to_axis)
|
|
|
|
return reduce(np.dot, [z_to_axis, about_z, axis_to_z])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def rotation_about_z(angle):
|
|
|
|
return [
|
|
|
|
[np.cos(angle), -np.sin(angle), 0],
|
2018-04-06 13:58:59 -07:00
|
|
|
[np.sin(angle), np.cos(angle), 0],
|
2018-03-30 18:19:23 -07:00
|
|
|
[0, 0, 1]
|
|
|
|
]
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def z_to_vector(vector):
|
|
|
|
"""
|
2018-04-06 13:58:59 -07:00
|
|
|
Returns some matrix in SO(3) which takes the z-axis to the
|
2018-03-30 18:19:23 -07:00
|
|
|
(normalized) vector provided as an argument
|
|
|
|
"""
|
2018-08-15 17:30:24 -07:00
|
|
|
norm = get_norm(vector)
|
2018-03-30 18:19:23 -07:00
|
|
|
if norm == 0:
|
|
|
|
return np.identity(3)
|
|
|
|
v = np.array(vector) / norm
|
|
|
|
phi = np.arccos(v[2])
|
|
|
|
if any(v[:2]):
|
2018-04-06 13:58:59 -07:00
|
|
|
# projection of vector to unit circle
|
2018-08-15 17:30:24 -07:00
|
|
|
axis_proj = v[:2] / get_norm(v[:2])
|
2018-03-30 18:19:23 -07:00
|
|
|
theta = np.arccos(axis_proj[0])
|
|
|
|
if axis_proj[1] < 0:
|
|
|
|
theta = -theta
|
|
|
|
else:
|
|
|
|
theta = 0
|
|
|
|
phi_down = np.array([
|
|
|
|
[np.cos(phi), 0, np.sin(phi)],
|
|
|
|
[0, 1, 0],
|
|
|
|
[-np.sin(phi), 0, np.cos(phi)]
|
|
|
|
])
|
|
|
|
return np.dot(rotation_about_z(theta), phi_down)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def angle_between(v1, v2):
|
|
|
|
return np.arccos(np.dot(
|
2018-08-15 17:30:24 -07:00
|
|
|
v1 / get_norm(v1),
|
|
|
|
v2 / get_norm(v2)
|
2018-03-30 18:19:23 -07:00
|
|
|
))
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def angle_of_vector(vector):
|
|
|
|
"""
|
|
|
|
Returns polar coordinate theta when vector is project on xy plane
|
|
|
|
"""
|
|
|
|
z = complex(*vector[:2])
|
|
|
|
if z == 0:
|
|
|
|
return 0
|
|
|
|
return np.angle(complex(*vector[:2]))
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def angle_between_vectors(v1, v2):
|
|
|
|
"""
|
|
|
|
Returns the angle between two 3D vectors.
|
2019-02-07 21:38:03 -08:00
|
|
|
This angle will always be btw 0 and pi
|
2018-03-30 18:19:23 -07:00
|
|
|
"""
|
2019-02-07 21:38:03 -08:00
|
|
|
return np.arccos(fdiv(
|
|
|
|
np.dot(v1, v2),
|
|
|
|
get_norm(v1) * get_norm(v2)
|
|
|
|
))
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
|
|
|
|
def project_along_vector(point, vector):
|
|
|
|
matrix = np.identity(3) - np.outer(vector, vector)
|
|
|
|
return np.dot(point, matrix.T)
|
|
|
|
|
2018-08-15 16:23:29 -07:00
|
|
|
|
2018-08-29 00:09:57 -07:00
|
|
|
def normalize(vect, fall_back=None):
|
2018-08-15 16:23:29 -07:00
|
|
|
norm = get_norm(vect)
|
|
|
|
if norm > 0:
|
2018-08-30 14:24:57 -07:00
|
|
|
return np.array(vect) / norm
|
2018-08-15 16:23:29 -07:00
|
|
|
else:
|
2018-08-29 00:09:57 -07:00
|
|
|
if fall_back is not None:
|
|
|
|
return fall_back
|
|
|
|
else:
|
|
|
|
return np.zeros(len(vect))
|
2018-08-15 16:23:29 -07:00
|
|
|
|
|
|
|
|
|
|
|
def cross(v1, v2):
|
|
|
|
return np.array([
|
|
|
|
v1[1] * v2[2] - v1[2] * v2[1],
|
|
|
|
v1[2] * v2[0] - v1[0] * v2[2],
|
|
|
|
v1[0] * v2[1] - v1[1] * v2[0]
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
def get_unit_normal(v1, v2):
|
|
|
|
return normalize(cross(v1, v2))
|
|
|
|
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
###
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def compass_directions(n=4, start_vect=RIGHT):
|
2018-10-05 17:19:48 -07:00
|
|
|
angle = TAU / n
|
2018-03-30 18:19:23 -07:00
|
|
|
return np.array([
|
2018-04-06 13:58:59 -07:00
|
|
|
rotate_vector(start_vect, k * angle)
|
2018-03-30 18:19:23 -07:00
|
|
|
for k in range(n)
|
|
|
|
])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def complex_to_R3(complex_num):
|
|
|
|
return np.array((complex_num.real, complex_num.imag, 0))
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def R3_to_complex(point):
|
|
|
|
return complex(*point[:2])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-05-30 12:02:25 -07:00
|
|
|
def complex_func_to_R3_func(complex_func):
|
|
|
|
return lambda p: complex_to_R3(complex_func(R3_to_complex(p)))
|
|
|
|
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def center_of_mass(points):
|
|
|
|
points = [np.array(point).astype("float") for point in points]
|
|
|
|
return sum(points) / len(points)
|
2018-07-14 10:32:13 -07:00
|
|
|
|
|
|
|
|
2019-05-27 19:48:33 -07:00
|
|
|
def midpoint(point1, point2):
|
|
|
|
return center_of_mass([point1, point2])
|
|
|
|
|
|
|
|
|
2018-07-14 10:32:13 -07:00
|
|
|
def line_intersection(line1, line2):
|
|
|
|
"""
|
|
|
|
return intersection point of two lines,
|
|
|
|
each defined with a pair of vectors determining
|
|
|
|
the end points
|
|
|
|
"""
|
|
|
|
x_diff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
|
|
|
|
y_diff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
|
|
|
|
|
|
|
|
def det(a, b):
|
|
|
|
return a[0] * b[1] - a[1] * b[0]
|
|
|
|
|
|
|
|
div = det(x_diff, y_diff)
|
|
|
|
if div == 0:
|
|
|
|
raise Exception("Lines do not intersect")
|
|
|
|
d = (det(*line1), det(*line2))
|
|
|
|
x = det(d, x_diff) / div
|
|
|
|
y = det(d, y_diff) / div
|
|
|
|
return np.array([x, y, 0])
|
2018-08-28 09:45:12 -07:00
|
|
|
|
|
|
|
|
|
|
|
def get_winding_number(points):
|
|
|
|
total_angle = 0
|
|
|
|
for p1, p2 in adjacent_pairs(points):
|
|
|
|
d_angle = angle_of_vector(p2) - angle_of_vector(p1)
|
|
|
|
d_angle = ((d_angle + PI) % TAU) - PI
|
|
|
|
total_angle += d_angle
|
|
|
|
return total_angle / TAU
|