added license header
[PyX/mjg.git] / pyx / trafo.py
blobc65af47e5c0485008589ea04a3ea40b44b35dbb8
1 #!/usr/bin/env python
4 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 import unit, canvas
25 # TODO:
26 # - switch to affine space description (i.e. represent transformation by
27 # 3x3 matrix (cf. PLRM Sect. 4.3.3)? Cooler!
30 # some helper routines
32 def _rmatrix(angle):
33 from math import pi, cos, sin
34 phi = 2*pi*angle/360
36 return (( cos(phi), sin(phi)),
37 (-sin(phi), cos(phi)))
39 def _mmatrix(angle):
40 from math import pi, cos, sin
41 phi = 2*pi*angle/360
43 return ( (cos(phi)*cos(phi)-sin(phi)*sin(phi), -2*sin(phi)*cos(phi) ),
44 (-2*sin(phi)*cos(phi), sin(phi)*sin(phi)-cos(phi)*cos(phi) ) )
46 def _det(matrix):
47 return matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]
49 # Exception
51 class UndefinedResultError(ArithmeticError):
52 pass
54 # transformation: affine transformations
56 class transformation:
57 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
58 if _det(matrix)==0:
59 raise UndefinedResultError, "transformation matrix must not be singular"
60 else:
61 self.matrix=matrix
62 self.vector=tuple(map(lambda x: unit.length(x), vector))
64 def __mul__(self, other):
65 if isinstance(other, transformation):
66 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] + self.matrix[0][1]*other.matrix[1][0],
67 self.matrix[0][0]*other.matrix[0][1] + self.matrix[0][1]*other.matrix[1][1] ),
68 ( self.matrix[1][0]*other.matrix[0][0] + self.matrix[1][1]*other.matrix[1][0],
69 self.matrix[1][0]*other.matrix[0][1] + self.matrix[1][1]*other.matrix[1][1] )
72 vector = ( self.matrix[0][0]*other.vector[0] + self.matrix[1][0]*other.vector[1] + self.vector[0],
73 self.matrix[0][1]*other.vector[0] + self.matrix[1][1]*other.vector[1] + self.vector[1] )
75 # print " ( %s * %s => %s ) "% (self, other, transformation(angle=angle, vector=vector))
77 return transformation(matrix=matrix, vector=vector)
78 else:
79 raise NotImplementedError, "can only multiply two transformations"
80 def __rmul__(self, other): # TODO: not needed!?
81 return other.__mul__(self)
83 def apply(self, point):
84 return (self.matrix[0][0]*point[0] + self.matrix[1][0]*point[1]+self.vector[0],
85 self.matrix[0][1]*point[0] + self.matrix[1][1]*point[1]+self.vector[1])
87 def matrix():
88 return self.matrix
90 def vector(self):
91 return self.vector
93 def angle(self):
94 return self.angle
96 def translate(self,x,y):
97 return transformation(vector=(x,y))*self
99 def rotate(self,angle):
100 return transformation(matrix=_rmatrix(angle))*self
102 def mirror(self,angle):
103 return transformation(matrix=_mmatrix(angle))*self
105 def scale(self, x, y=None):
106 return transformation(matrix=((x, 0), (0,y or x)))*self
108 def inverse(self):
109 det = _det(self.matrix) # shouldn't be zero, but
110 try:
111 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
112 (-self.matrix[1][0]/det, self.matrix[0][0]/det)
114 except ZeroDivisionError:
115 raise UndefinedResultError, "transformation matrix must not be singular"
116 return transformation(matrix=matrix) * transformation(vector=(-self.vector[0],-self.vector[1]))
118 def bbox(self, acanvas):
119 # assert 0, "this shouldn't be called!"
120 return canvas.bbox()
122 def write(self, canvas, file):
123 file.write("[%f %f %f %f %f %f] concat\n" % \
124 ( self.matrix[0][0], self.matrix[0][1],
125 self.matrix[1][0], self.matrix[1][1],
126 canvas.unit.pt(self.vector[0]), canvas.unit.pt(self.vector[1])))
129 class translate(transformation):
130 def __init__(self,x,y):
131 transformation.__init__(self, vector=(x,y))
133 class rotate(transformation):
134 def __init__(self,angle):
135 transformation.__init__(self, matrix=_rmatrix(angle))
137 class mirror(transformation):
138 def __init__(self,angle=0):
139 transformation.__init__(self, matrix=_mmatrix(angle))
141 class scale(transformation):
142 def __init__(self,x,y=None):
143 transformation.__init__(self, matrix=((x,0),(0,y or x)))
146 if __name__=="__main__":
147 # test for some invariants:
149 def checkforidentity(trafo):
150 u=unit.unit()
151 m = max(map(abs,[trafo.matrix[0][0]-1,
152 trafo.matrix[1][0],
153 trafo.matrix[0][1],
154 trafo.matrix[1][1]-1,
155 u.pt(trafo.vector[0]),
156 u.pt(trafo.vector[1])]))
158 assert m<1e-7, "tests for invariants failed"
161 # transformation(angle=angle, vector=(x,y)) == translate(x,y) * rotate(angle)
162 checkforidentity( translate(1,3) * rotate(15) * transformation(matrix=_rmatrix(15),vector=(1,3)).inverse())
164 # t*t.inverse() == 1
165 t = translate(-1,-1)*rotate(72)*translate(1,1)
166 checkforidentity(t*t.inverse())
168 # -mirroring two times should yield identiy
169 # -mirror(phi)=mirror(phi+180)
170 checkforidentity( mirror(20)*mirror(20))
171 checkforidentity( mirror(20).mirror(20))
172 checkforidentity( mirror(20)*mirror(180+20))
174 # equivalent notations
175 checkforidentity( translate(1,2).rotate(72).translate(-3,-1)*(translate(-3,-1)*rotate(72)*translate(1,2)).inverse() )
177 checkforidentity( rotate(40).rotate(120).rotate(90).rotate(110) )
179 checkforidentity( scale(2,3).scale(1/2.0, 1/3.0) )
181 def applyonbasis(t):
182 u=unit.unit()
183 ex = (1,0)
184 ey = (0,1)
185 print "%s:" % t
186 t=eval(t)
187 esx=t.apply(ex)
188 esy=t.apply(ey)
189 print " (1,0) => (%f, %f)" % (u.m(esx[0])*100, u.m(esx[1])*100)
190 print " (0,1) => (%f, %f)" % (u.m(esy[0])*100, u.m(esy[1])*100)
192 applyonbasis("translate(1,0)")
193 applyonbasis("translate(0,1)")
194 applyonbasis("rotate(90)")
195 applyonbasis("scale(0.5)")
196 applyonbasis("translate(1,0)*rotate(90)")
197 applyonbasis("translate(1,0)*scale(0.5)")
198 applyonbasis("rotate(90)*translate(1,0)")
199 applyonbasis("scale(0.5)*translate(1,0)")
200 applyonbasis("translate(1,0)*rotate(90)*scale(0.5)")
201 applyonbasis("translate(1,0)*scale(0.5)*rotate(90)")
202 applyonbasis("rotate(90)*scale(0.5)*translate(1,0)")
203 applyonbasis("scale(0.5)*rotate(90)*translate(1,0)")