Skip to content

Commit

Permalink
Cleaner plot3d rotation around arbitrary axis
Browse files Browse the repository at this point in the history
  • Loading branch information
robertwb committed Sep 13, 2007
1 parent ff2f4de commit 11a5225
Showing 1 changed file with 128 additions and 28 deletions.
156 changes: 128 additions & 28 deletions src/sage/plot/plot3d/transform.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from math import atan2, sin, cos, atan, sqrt, acos
cdef extern from *:
double sin(double)
double cos(double)
double sqrt(double)

#from math import atan2, sin, cos, atan, sqrt, acos

include "point_c.pxi"

Expand Down Expand Up @@ -34,23 +39,7 @@ cdef class Transformation:
if rot is not None:
# rotate about v by theta
vx, vy, vz, theta = rot
if vx == vy == 0:
t = RDF(pi/2)
else:
t = atan2(vy,vz) + pi/2
m = self.rotX(t) * m
new_y = vy*cos(t) - vz*sin(t)
# v = [vx, new_y, 0]
if vx == new_y == 0:
s = RDF(pi/2)
else:
s = atan2(vx,new_y) + pi/2
m = self.rotZ(s) * m
# v = [new_x, 0, 0]
m = self.rotX(theta) * m
# now put back to our former reference frame
m = self.rotZ(-s) * m
m = self.rotX(-t) * m
m *= rotate_arbitrary((vx, vy, vz), theta)

self.matrix = m.augment(matrix(RDF, 3, 1, list(trans))) \
.stack(matrix(RDF, 1, 4, [0,0,0,1]))
Expand All @@ -61,6 +50,8 @@ cdef class Transformation:
for i from 0 <= i < 12:
self._matrix_data[i] = m_data[i]

def get_matrix(self):
return self.matrix.copy()

def is_skew(self, eps=1e-5):
dx, dy, dz = self.matrix.submatrix(0,0,3,3).columns()
Expand All @@ -77,16 +68,6 @@ cdef class Transformation:
len_a = a.dot_product(a)
return max([abs(len_a - b.dot_product(b)) for b in basis]) < eps

def rotX(self, theta):
return matrix(RDF, 3, 3, [1, 0, 0,
0, cos(theta), -sin(theta),
0, sin(theta), cos(theta)])

def rotZ(self, theta):
return matrix(RDF, 3, 3, [cos(theta), -sin(theta), 0,
sin(theta), cos(theta), 0,
0, 0, 1])

def transform_point(self, x):
Tx = self.matrix * vector(RDF, [x[0], x[1], x[2], 1])
return (Tx[0], Tx[1], Tx[2])
Expand All @@ -112,3 +93,122 @@ cdef class Transformation:
if self._svd is None:
self._svd = self.matrix.submatrix(0,0,3,3).SVD()
return self._svd[1][0,0]


def rotate_arbitrary(v, double theta):
"""
Return a matrix that rotates the coordinate space about
the axis v by the angle theta.
EXAMPLES:
sage: from sage.plot.plot3d.transform import rotate_arbitrary
Try rotating about the axes:
sage: rotate_arbitrary((1,0,0), 1)
[ 1.0 0.0 0.0]
[ 0.0 0.540302305868 0.841470984808]
[ 0.0 -0.841470984808 0.540302305868]
sage: rotate_arbitrary((0,1,0), 1)
[ 0.540302305868 0.0 -0.841470984808]
[ 0.0 1.0 0.0]
[ 0.841470984808 0.0 0.540302305868]
sage: rotate_arbitrary((0,0,1), 1)
[ 0.540302305868 0.841470984808 0.0]
[-0.841470984808 0.540302305868 0.0]
[ 0.0 0.0 1.0]
These next two should be the same (up to machine epsilon)
sage: rotate_arbitrary((1,1,1), 1)
[ 0.693534870579 0.639056064305 -0.332590934883]
[-0.332590934883 0.693534870579 0.639056064305]
[ 0.639056064305 -0.332590934883 0.693534870579]
sage: rotate_arbitrary((1,1,1), -1)^(-1)
[ 0.693534870579 0.639056064305 -0.332590934883]
[-0.332590934883 0.693534870579 0.639056064305]
[ 0.639056064305 -0.332590934883 0.693534870579]
Make sure it does the right thing...
sage: rotate_arbitrary((1,2,3), -1).det()
1.0
sage: rotate_arbitrary((1,1,1), 2*pi/3) * vector(RDF, (1,2,3))
(2.0, 3.0, 1.0)
sage: rotate_arbitrary((1,2,3), 5) * vector(RDF, (1,2,3))
(1.0, 2.0, 3.0)
sage: rotate_arbitrary((1,1,1), pi/7)^7
[-0.333333333333 0.666666666667 0.666666666667]
[ 0.666666666667 -0.333333333333 0.666666666667]
[ 0.666666666667 0.666666666667 -0.333333333333]
AUTHORS:
-- Robert Bradshaw
ALGORITHM:
There is a formula. Where did it come from? Lets take
a quick jaunt into SAGE's calculus package...
Setup some variables
sage: vx,vy,vz,theta = var('x y z theta')
Symbolic rotation matrices about X and Y axis:
sage: def rotX(theta): return matrix(SR, 3, 3, [1, 0, 0, 0, cos(theta), -sin(theta), 0, sin(theta), cos(theta)])
sage: def rotZ(theta): return matrix(SR, 3, 3, [cos(theta), -sin(theta), 0, sin(theta), cos(theta), 0, 0, 0, 1])
Normalizing $y$ so that $|v|=1$. Perhaps there is a better
way to tell maxima that $x^2+y^2+z^2=1$ which would make for
a much cleaner calculation.
sage: vy = sqrt(1-vx^2-vz^2)
Now we rotate about the $x$-axis so $v$ is in the $xy$-plane.
sage: t = atan(vy/vz)+pi/2
sage: m = rotX(t)
sage: new_y = vy*cos(t) - vz*sin(t)
And rotate about the $z$ axis so $v$ lies on the $x$ axis.
sage: s = atan(vx/new_y) + pi/2
sage: m = rotZ(s) * m
Rotating about $v$ in our old system is the same as rotating
about the $x$-axis in the new.
sage: m = rotX(theta) * m
Do some simplfying here to avoid blow-up.
sage: ix = [(i,j) for i in range(3) for j in range(3)]
sage: for ij in ix: m[ij] = m[ij].simplify_rational()
Now go back to the original coordinate system.
sage: m = rotZ(-s) * m
sage: m = rotX(-t) * m
sage: for ij in ix: m[ij] = m[ij].simplify_rational()
sage: m # or show(m)
[ (1 - cos(theta))*x^2 + cos(theta) -(sin(theta)*abs(z)^3 + (cos(theta) - 1)*x*z^2*sqrt(-z^2 - x^2 + 1))/z^2 (sin(theta)*sqrt(-z^2 - x^2 + 1)*abs(z)^3 + (1 - cos(theta))*x*z^4)/z^3]
[ sin(theta)*abs(z) + (1 - cos(theta))*x*sqrt(-z^2 - x^2 + 1) (cos(theta) - 1)*z^2 + (cos(theta) - 1)*x^2 + 1 -(sin(theta)*x*abs(z) + (cos(theta) - 1)*z^2*sqrt(-z^2 - x^2 + 1))/z]
[ -(sin(theta)*sqrt(-z^2 - x^2 + 1)*abs(z) + (cos(theta) - 1)*x*z^2)/z -((cos(theta) - 1)*z^2*sqrt(-z^2 - x^2 + 1) - sin(theta)*x*abs(z))/z (1 - cos(theta))*z^2 + cos(theta)]
Re-expressing some entries in terms of y and resolving the absolute
values introduced by eliminating y, we get the desired result.
"""
cdef double x,y,z, len_v
x,y,z = v
len_v = sqrt(x*x+y*y+z*z)
# normalize for an easier formula
x /= len_v
y /= len_v
z /= len_v
cdef double cos_t = cos(theta), sin_t = sin(theta)

entries = [
(1 - cos_t)*x*x + cos_t,
sin_t*z - (cos_t - 1)*x*y,
-sin_t*y + (1 - cos_t)*x*z,

-sin_t*z + (1 - cos_t)*x*y,
(1 - cos_t)*y*y + cos_t,
sin_t*x - (cos_t - 1)*z*y,

sin_t*y - (cos_t - 1)*x*z,
-(cos_t - 1)*z*y - sin_t*x,
(1 - cos_t)*z*z + cos_t ]

return matrix(RDF, 3, 3, entries)

0 comments on commit 11a5225

Please sign in to comment.