-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathQuaternion.py
176 lines (155 loc) · 5.37 KB
/
Quaternion.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
'''
A Quaternion class, which includes basic Quaternion math operations
:author: micou(Zezhou Sun)
:version: 2021.1.1
'''
import time
import math
import numpy as np
from Point import Point
class Quaternion:
"""
Defines Quaternion object here, which includes several basic quaternion operations
"""
# scalar component of this quaternion
s = 0.0
# vector components of this quaternion
v = None
def __init__(self, s: float = 1, v0: float = 0, v1: float = 0, v2: float = 0):
self.v = [0, 0, 0]
self.set(s, v0, v1, v2)
def isNum(self, var):
"""
Type checking if a variable is number
:return: True if is number, Otherwise False
:rtype: bool
"""
return isinstance(var, int) or isinstance(var, float)
def set(self, s, v0, v1, v2):
"""
Set Quaternion Value for this one. Will apply type checking before the set
:return: None
"""
if (not self.isNum(s)) or (not self.isNum(v0)) or (not self.isNum(v1)) or (not self.isNum(v2)):
raise TypeError("Incorrect type set for quaternion")
self.s = s
self.v[0] = v0
self.v[1] = v1
self.v[2] = v2
def multiply(self, q) -> "Quaternion":
"""
multiply with another Quaternion and return a new quaternion
:return: a new Quaternion
:rtype: Quaternion
"""
if not isinstance(q, Quaternion):
raise TypeError("Quaternion can only multiply Quaternion")
# s = s1*s2 - v1.v2
new_s = self.s * q.s - self.v[0] * q.v[0] - self.v[1] * q.v[1] - self.v[2] * q.v[2]
# v = s1 v2 + s2 v1 + v1 x v2
new_v0 = (self.s * q.v[0]) + (q.s * self.v[0]) + (self.v[1] * q.v[2] - self.v[2] * q.v[1])
new_v1 = (self.s * q.v[1]) + (q.s * self.v[1]) + (self.v[2] * q.v[0] - self.v[0] * q.v[2])
new_v2 = (self.s * q.v[2]) + (q.s * self.v[2]) + (self.v[0] * q.v[1] - self.v[1] * q.v[0])
return Quaternion(new_s, new_v0, new_v1, new_v2)
def multiplyPoint(self, point: Point) -> Point:
"""
multiply a vector (Point) with this quaternion,
and return a new Point object (ignore the scalar part of this quaternion)
:param point: Point
:return: a new Point
"""
d = point.coords
q2 = Quaternion(0, d[0], d[1], d[2])
q = self.multiply(q2)
q = q.multiply(self.conjugate())
return Point(q.v[0], q.v[1], q.v[2])
def conjugate(self) -> "Quaternion":
"""
conjugate of this quaternion
:return: a new Quaternion
:rtype: Quaternion
"""
return Quaternion(self.s, -self.v[0], -self.v[1], -self.v[2])
def norm(self):
"""
Norm of this quaternion
:return: norm of this quaternion
:rtype: float
"""
return math.sqrt(self.s * self.s + self.v[0] * self.v[0] + self.v[1] * self.v[1] + self.v[2] * self.v[2])
def normalize(self):
"""
Normalize this quaternion if this quaternion's norm if greater than 0
:return: this quaternion
:rtype: Quaternion
"""
mag = self.norm()
# Set a threshold for mag, to avoid divided by 0
if mag > 1e-6:
self.s /= mag
self.v[0] /= mag
self.v[1] /= mag
self.v[2] /= mag
return self
def reset(self):
"""
Reset this Quaternion. This is fast then rebuild a new Quaternion
:return: None
"""
self.s = 1
self.v[0] = 0
self.v[1] = 0
self.v[2] = 0
def toMatrix(self):
"""
turn Quaternion to Matrix form(with numpy)
:return: a (4, 4) matrix comes from current quaternion
:rtype: numpy.ndarray
"""
q_matrix = np.zeros((4, 4), dtype=np.float64)
s = self.s
a = self.v[0]
b = self.v[1]
c = self.v[2]
q_matrix[0, 0] = 1 - 2 * b * b - 2 * c * c
q_matrix[1, 0] = 2 * a * b + 2 * s * c
q_matrix[2, 0] = 2 * a * c - 2 * s * b
q_matrix[0, 1] = 2 * a * b - 2 * s * c
q_matrix[1, 1] = 1 - 2 * a * a - 2 * c * c
q_matrix[2, 1] = 2 * b * c + 2 * s * a
q_matrix[0, 2] = 2 * a * c + 2 * s * b
q_matrix[1, 2] = 2 * b * c - 2 * s * a
q_matrix[2, 2] = 1 - 2 * a * a - 2 * b * b
q_matrix[3, 3] = 1
return q_matrix
def __repr__(self):
return f"Quaternion({self.s}, ({self.v[0]}, {self.v[1]}, {self.v[2]}))"
@staticmethod
def axisAngleToQuaternion(axis: Point, angle: float) -> "Quaternion":
"""
turn axis and angle to Quaternion
:param axis: Point
:param angle: float
:return: a new Quaternion
:rtype: Quaternion
"""
if not isinstance(axis, Point):
raise TypeError("axis must be a Point")
axis = axis.normalize().coords
angle = angle / 2
sin_val = math.sin(angle)
return Quaternion(math.cos(angle), axis[0] * sin_val, axis[1] * sin_val, axis[2] * sin_val)
if __name__ == "__main__":
t1 = time.time()
for _ in range(1000000):
a = Quaternion()
t2 = time.time()
a = Quaternion(1, 1, 0, 0)
b = Quaternion(1, 0, 1, 0)
a.normalize()
b.normalize()
print(a.multiply(b))
print(a.toMatrix())
c = a.multiply(b).normalize()
print(c.toMatrix())
print("Cost time: ", t2 - t1)