Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/plot3d/transform.pyx
4036 views
1
#*****************************************************************************
2
# Copyright (C) 2007 Robert Bradshaw <[email protected]>
3
#
4
# Distributed under the terms of the GNU General Public License version 2 (GPLv2)
5
#
6
# The full text of the GPLv2 is available at:
7
#
8
# http://www.gnu.org/licenses/
9
#*****************************************************************************
10
cdef extern from *:
11
double sin(double)
12
double cos(double)
13
double sqrt(double)
14
15
#from math import atan2, sin, cos, atan, sqrt, acos
16
17
include "point_c.pxi"
18
19
from sage.rings.real_double import RDF
20
# from sage.misc.functional import sqrt, atan, acos
21
22
from sage.matrix.constructor import matrix
23
from sage.modules.free_module_element import vector
24
25
pi = RDF.pi()
26
27
28
29
cdef class Transformation:
30
def __init__(self, scale=(1,1,1),
31
rot=None,
32
trans=[0,0,0],
33
m=None):
34
35
if scale is None:
36
scale = (1,1,1)
37
if trans is None:
38
trans = [0,0,0]
39
40
# TODO: determine for sure if x3d does scale or rotation first
41
if m is not None:
42
self.matrix = m
43
44
else:
45
m = matrix(RDF, 3, 3,
46
[scale[0], 0, 0, 0, scale[1], 0, 0, 0, scale[2]])
47
48
if rot is not None:
49
# rotate about v by theta
50
vx, vy, vz, theta = rot
51
m *= rotate_arbitrary((vx, vy, vz), theta)
52
53
self.matrix = m.augment(matrix(RDF, 3, 1, list(trans))) \
54
.stack(matrix(RDF, 1, 4, [0,0,0,1]))
55
56
# this raw data is used for optimized transformations
57
m_data = self.matrix.list()
58
cdef int i
59
for i from 0 <= i < 12:
60
self._matrix_data[i] = m_data[i]
61
62
def get_matrix(self):
63
return self.matrix.__copy__()
64
65
def is_skew(self, eps=1e-5):
66
dx, dy, dz = self.matrix[0:3, 0:3].columns()
67
return abs(dx.dot_product(dy)) + abs(dx.dot_product(dz)) + abs(dy.dot_product(dz)) > eps
68
69
def is_uniform(self, eps=1e-5):
70
cols = self.matrix[0:3, 0:3].columns()
71
lens = [col.dot_product(col) for col in cols]
72
return abs(lens[0] - lens[1]) + abs(lens[0] - lens[2]) < eps
73
74
def is_uniform_on(self, basis, eps=1e-5):
75
basis = [vector(RDF, self.transform_vector(b)) for b in basis]
76
a = basis.pop()
77
len_a = a.dot_product(a)
78
return max([abs(len_a - b.dot_product(b)) for b in basis]) < eps
79
80
cpdef transform_point(self, x):
81
cdef point_c res, P
82
P.x, P.y, P.z = x
83
point_c_transform(&res, self._matrix_data, P)
84
return res.x, res.y, res.z
85
86
cpdef transform_vector(self, v):
87
cdef point_c res, P
88
P.x, P.y, P.z = v
89
point_c_stretch(&res, self._matrix_data, P)
90
return res.x, res.y, res.z
91
92
cpdef transform_bounding_box(self, box):
93
cdef point_c lower, upper, res, temp
94
cdef point_c bounds[2]
95
bounds[0].x, bounds[0].y, bounds[0].z = box[0]
96
bounds[1].x, bounds[1].y, bounds[1].z = box[1]
97
point_c_transform(&lower, self._matrix_data, bounds[0])
98
point_c_transform(&upper, self._matrix_data, bounds[0])
99
cdef int i
100
for i from 1 <= i < 8:
101
temp.x = bounds[ i & 1 ].x
102
temp.y = bounds[(i & 2) >> 1].y
103
temp.z = bounds[(i & 4) >> 2].z
104
point_c_transform(&res, self._matrix_data, temp)
105
point_c_lower_bound(&lower, lower, res)
106
point_c_upper_bound(&upper, upper, res)
107
return (lower.x, lower.y, lower.z), (upper.x, upper.y, upper.z)
108
109
110
cdef void transform_point_c(self, point_c* res, point_c P):
111
point_c_transform(res, self._matrix_data, P)
112
113
cdef void transform_vector_c(self, point_c* res, point_c P):
114
point_c_stretch(res, self._matrix_data, P)
115
116
def __mul__(Transformation self, Transformation other):
117
return Transformation(m = self.matrix * other.matrix)
118
119
def __invert__(Transformation self):
120
return Transformation(m=~self.matrix)
121
122
def __call__(self, p):
123
return self.transform_point(p)
124
125
def max_scale(self):
126
if self._svd is None:
127
self._svd = self.matrix[0:3, 0:3].SVD()
128
return self._svd[1][0,0]
129
130
def avg_scale(self):
131
if self._svd is None:
132
self._svd = self.matrix[0:3, 0:3].SVD()
133
return (self._svd[1][0,0] * self._svd[1][1,1] * self._svd[1][2,2]) ** (1/3.0)
134
135
136
def rotate_arbitrary(v, double theta):
137
"""
138
Return a matrix that rotates the coordinate space about
139
the axis v by the angle theta.
140
141
INPUT:
142
143
- ``theta`` - real number, the angle
144
145
EXAMPLES::
146
147
sage: from sage.plot.plot3d.transform import rotate_arbitrary
148
149
Try rotating about the axes::
150
151
sage: rotate_arbitrary((1,0,0), 1)
152
[ 1.0 0.0 0.0]
153
[ 0.0 0.540302305868 0.841470984808]
154
[ 0.0 -0.841470984808 0.540302305868]
155
sage: rotate_arbitrary((0,1,0), 1)
156
[ 0.540302305868 0.0 -0.841470984808]
157
[ 0.0 1.0 0.0]
158
[ 0.841470984808 0.0 0.540302305868]
159
sage: rotate_arbitrary((0,0,1), 1)
160
[ 0.540302305868 0.841470984808 0.0]
161
[-0.841470984808 0.540302305868 0.0]
162
[ 0.0 0.0 1.0]
163
164
These next two should be the same (up to machine epsilon)::
165
166
sage: rotate_arbitrary((1,1,1), 1)
167
[ 0.693534870579 0.639056064305 -0.332590934883]
168
[-0.332590934883 0.693534870579 0.639056064305]
169
[ 0.639056064305 -0.332590934883 0.693534870579]
170
sage: rotate_arbitrary((1,1,1), -1)^(-1)
171
[ 0.693534870579 0.639056064305 -0.332590934883]
172
[-0.332590934883 0.693534870579 0.639056064305]
173
[ 0.639056064305 -0.332590934883 0.693534870579]
174
175
Make sure it does the right thing...::
176
177
sage: rotate_arbitrary((1,2,3), -1).det()
178
1.0
179
sage: rotate_arbitrary((1,1,1), 2*pi/3) * vector(RDF, (1,2,3))
180
(2.0, 3.0, 1.0)
181
sage: rotate_arbitrary((1,2,3), 5) * vector(RDF, (1,2,3))
182
(1.0, 2.0, 3.0)
183
sage: rotate_arbitrary((1,1,1), pi/7)^7
184
[-0.333333333333 0.666666666667 0.666666666667]
185
[ 0.666666666667 -0.333333333333 0.666666666667]
186
[ 0.666666666667 0.666666666667 -0.333333333333]
187
188
AUTHORS:
189
190
- Robert Bradshaw
191
192
ALGORITHM:
193
194
There is a formula. Where did it come from? Lets take
195
a quick jaunt into Sage's calculus package...
196
197
Setup some variables::
198
199
sage: vx,vy,vz,theta = var('x y z theta')
200
201
Symbolic rotation matrices about X and Y axis::
202
203
sage: def rotX(theta): return matrix(SR, 3, 3, [1, 0, 0, 0, cos(theta), -sin(theta), 0, sin(theta), cos(theta)])
204
sage: def rotZ(theta): return matrix(SR, 3, 3, [cos(theta), -sin(theta), 0, sin(theta), cos(theta), 0, 0, 0, 1])
205
206
Normalizing $y$ so that $|v|=1$. Perhaps there is a better
207
way to tell Maxima that $x^2+y^2+z^2=1$ which would make for
208
a much cleaner calculation::
209
210
sage: vy = sqrt(1-vx^2-vz^2)
211
212
Now we rotate about the $x$-axis so $v$ is in the $xy$-plane::
213
214
sage: t = arctan(vy/vz)+pi/2
215
sage: m = rotX(t)
216
sage: new_y = vy*cos(t) - vz*sin(t)
217
218
And rotate about the $z$ axis so $v$ lies on the $x$ axis::
219
220
sage: s = arctan(vx/new_y) + pi/2
221
sage: m = rotZ(s) * m
222
223
Rotating about $v$ in our old system is the same as rotating
224
about the $x$-axis in the new::
225
226
sage: m = rotX(theta) * m
227
228
Do some simplifying here to avoid blow-up::
229
230
sage: m = m.simplify_rational()
231
232
Now go back to the original coordinate system::
233
234
sage: m = rotZ(-s) * m
235
sage: m = rotX(-t) * m
236
237
And simplify every single entry (which is more effective that simplify
238
the whole matrix like above)::
239
240
sage: m = m.parent()([x.simplify_full() for x in m._list()])
241
sage: m # random output - remove this in trac #9880
242
[ -(cos(theta) - 1)*x^2 + cos(theta) -(cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*x + sin(theta)*abs(z) -((cos(theta) - 1)*x*z^2 + sqrt(-x^2 - z^2 + 1)*sin(theta)*abs(z))/z]
243
[ -(cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*x - sin(theta)*abs(z) (cos(theta) - 1)*x^2 + (cos(theta) - 1)*z^2 + 1 -((cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*z*abs(z) - x*z*sin(theta))/abs(z)]
244
[ -((cos(theta) - 1)*x*z^2 - sqrt(-x^2 - z^2 + 1)*sin(theta)*abs(z))/z -((cos(theta) - 1)*sqrt(-x^2 - z^2 + 1)*z*abs(z) + x*z*sin(theta))/abs(z) -(cos(theta) - 1)*z^2 + cos(theta)]
245
246
Re-expressing some entries in terms of y and resolving the absolute
247
values introduced by eliminating y, we get the desired result.
248
"""
249
cdef double x,y,z, len_v
250
x,y,z = v
251
len_v = sqrt(x*x+y*y+z*z)
252
# normalize for an easier formula
253
x /= len_v
254
y /= len_v
255
z /= len_v
256
cdef double cos_t = cos(theta), sin_t = sin(theta)
257
258
entries = [
259
(1 - cos_t)*x*x + cos_t,
260
sin_t*z - (cos_t - 1)*x*y,
261
-sin_t*y + (1 - cos_t)*x*z,
262
263
-sin_t*z + (1 - cos_t)*x*y,
264
(1 - cos_t)*y*y + cos_t,
265
sin_t*x - (cos_t - 1)*z*y,
266
267
sin_t*y - (cos_t - 1)*x*z,
268
-(cos_t - 1)*z*y - sin_t*x,
269
(1 - cos_t)*z*z + cos_t ]
270
271
return matrix(RDF, 3, 3, entries)
272
273
274