root/trunk/RBRapier/RBRapier/Tools/Geometry/Curves.py

Revision 732, 10.2 kB (checked in by sholloway, 5 years ago)

*** empty log message ***

Line 
1 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 #~ Imports
3 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 import math
6 import Numeric
7
8 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 #~ Constants / Variables / Etc.
10 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11
12 _2pi = 2 * Numeric.pi
13
14 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15 #~ Definitions
16 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
18 class CurveBase(object):
19     _steps = 16
20     NumericType = Numeric.Float
21
22     def __init__(self, steps=None):
23         if steps is not None:
24             self.SetSteps(steps)
25
26     def GetSteps(self):
27         return self._steps
28     def SetSteps(self, steps):
29         if steps != self._steps:
30             self._steps = steps
31             self._Invalidate()
32
33     def asCurve(self):
34         raise NotImplementedError
35
36     def __iter__(self):
37         return iter(self.asCurve())
38
39     def _Invalidate(self):
40         pass
41
42 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43 #~ Bezier Based Curves
44 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45
46 class BezierBase(CurveBase):
47     def __init__(self, *points, **kw):
48         if points:
49             self.SetControlPoints(*points)
50         CurveBase.__init__(self, **kw)
51
52     def _CreateUVector(self):
53         return self._BuildUVector(Numeric.arrayrange(0., 1., 1./(self.GetSteps()-1), self.NumericType), True)
54
55     def _BuildUVector(self, u, addend=False):
56         result = [u, Numeric.ones(len(u), self.NumericType)]
57         count = self.GetPolyDimension()+1
58         for i in xrange(2, count):
59             result.insert(0, u*result[0])
60         if addend:
61             result = Numeric.concatenate((result, Numeric.ones((count, 1), self.NumericType)), 1)
62         return Numeric.transpose(result)
63
64     def GetUVector(self):
65         result = getattr(self, '_uvector', ())
66         if len(result) != self.GetSteps():
67             result = self._uvector = self._CreateUVector()
68         return result
69
70     def SetControlPoints(self, *points):
71         assert len(points) == self.GetPolyDimension()+1
72         self.points = Numeric.asarray(points)
73     def GetControlPoints(self):
74         return self.points
75
76     def CurveFromUVector(self, uvector):
77         Ubezier = Numeric.dot(uvector, self.GetBezierMatrix())
78         return Numeric.dot(Ubezier, self.GetControlPoints())
79
80     def CurveAtPoints(self, *points):
81         uvector = self._BuildUVector(Numeric.asarray(points, self.NumericType))
82         return self.CurveFromUVector(uvector)
83
84     def CurveRange(self, start=0., stop=1., stepsize=None):
85         stepsize = stepsize or 1./(self.GetSteps()-1)
86         uvector = self._BuildUVector(Numeric.arrayrange(start, stop, stepsize, self.NumericType))
87         return self.CurveFromUVector(uvector)
88
89     def asCurve(self):
90         return self.CurveFromUVector(self.GetUVector())
91
92     def GetPolyDimension(self):
93         return len(self.GetBezierMatrix())-1
94     def GetBezierMatrix(self):
95         raise NotImplementedError
96
97     def _Invalidate(self):
98         try: delattr(self, '_uvector')
99         except AttributeError: pass
100
101 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102
103 class LinearBezier(BezierBase):
104     _matrix = Numeric.asarray([
105         [-1.,  1.],
106         [ 1.,  0.]],
107         BezierBase.NumericType)
108
109     def GetBezierMatrix(self):
110         return self._matrix
111
112     def GetPolyDimension(self):
113         return 1
114
115 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
116
117 class QuadraticBezier(BezierBase):
118     _matrix = Numeric.asarray([
119         [ 1., -2.,  1.],
120         [-2.,  2.,  0.],
121         [ 1.,  0.,  0.]],
122         BezierBase.NumericType)
123
124     def GetBezierMatrix(self):
125         return self._matrix
126
127     def GetPolyDimension(self):
128         return 2
129
130 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131
132 class CubicBezier(BezierBase):
133     _matrix = Numeric.asarray([
134         [-1.,  3., -3.,  1.],
135         [ 3., -6.,  3.,  0.],
136         [-3.,  3.,  0.,  0.],
137         [ 1.,  0.,  0.,  0.]],
138         BezierBase.NumericType)
139
140     def GetBezierMatrix(self):
141         return self._matrix
142
143     def GetPolyDimension(self):
144         return 3
145
146 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147 #~ Ellipse Based Curves
148 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
149
150 class Ellipse(CurveBase):
151     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
152     #~ Constants / Variables / Etc.
153     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
154
155     center = (0., 0.)
156     rx = 1.
157     ry = 1.
158     sweep = (0., _2pi)
159     xrotation = 0
160
161     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162     #~ Public Methods
163     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
164
165     def __init__(self, center=(0., 0.), rx=1., ry=1., startangle=0, endangle=360, xrotation=0, inDegrees=True, **kw):
166         CurveBase.__init__(self, **kw)
167         self.SetCenter(center)
168         self.SetRadii(rx, ry)
169         if inDegrees:
170             self.SetSweepAngles(startangle, endangle)
171             self.SetXRotationAngle(xrotation)
172         else:
173             self.SetSweep(startangle, endangle)
174             self.SetXRotation(xrotation)
175
176
177     def fromArc(klass, fromxy, toxy, rx=1., ry=1., xrotation=0, largeArcFlag=False, sweepFlag=False, inDegrees=True, **kw):
178         """
179         Derived from SVG Specification:
180             http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
181         """
182         if inDegrees:
183             xrotation = math.radians(xrotation)
184         fromxy = Numeric.asarray(fromxy, klass.NumericType)
185         toxy = Numeric.asarray(toxy, klass.NumericType)
186         if xrotation:
187             x, y = Numeric.dot(klass._RotationMatrix(-xrotation), 0.5*(fromxy-toxy))
188         else:
189             x, y = 0.5*(fromxy-toxy)
190         x2,y2 = x**2, y**2
191
192         rx2, ry2 = rx**2, ry**2
193         radiscale = x2/rx2 + y2/ry2
194         if radiscale > 1:
195             # ellipse is not big enough... scale it
196             radiscale = Numeric.sqrt(radiscale)
197             rx, ry = radiscale*rx, radiscale*ry
198             rx2, ry2 = rx**2, ry**2
199
200         tmp = rx2*y2 + ry2*x2
201         radicand = (rx2*ry2-tmp)/tmp
202         if radicand > 0:
203             sign = (largeArcFlag != sweepFlag) and 1 or -1
204             scale = sign * Numeric.sqrt(radicand)
205             cxP, cyP = scale*y*rx/ry, scale*x*-ry/rx
206         else:
207             cxP, cyP = 0., 0.
208
209         if xrotation:
210             rot = klass._RotTranMatrix(xrotation, (0.5*(fromxy+toxy)))
211             center = Numeric.dot(rot, Numeric.asarray([cxP, cyP, 1.], klass.NumericType))
212         else:
213             center = Numeric.asarray([cxP, cyP]) + (0.5*(fromxy+toxy))
214         center = center[:2]
215        
216         startpt = ((x-cxP)/rx, (y-cyP)/ry)
217         endpt = ((-x-cxP)/rx, (-y-cyP)/ry)
218         startradians = klass._FindAngle((1., 0.), startpt)
219         sweepradians = klass._FindAngle(startpt, endpt) % _2pi
220         if sweepFlag:
221             if sweepradians < 0:
222                 sweepradians += _2pi
223         else:
224             if sweepradians > 0:
225                 sweepradians -= _2pi
226
227         endradians = startradians + sweepradians
228         return klass(center, rx, ry, startradians, endradians, xrotation, inDegrees=False, **kw)
229     fromArc = classmethod(fromArc)
230
231     def SetCenter(self, center):
232         self.center = center
233     def GetCenter(self):
234         return self.center
235
236     def SetRadius(self, radius):
237         self.SetRadii(radius, radius)
238     def SetRadii(self, rx, ry):
239         self.rx = rx
240         self.ry = ry
241     def GetRadii(self):
242         return self.rx, self.ry
243
244     def SetSweepAngles(self, startangle, endangle):
245         self.SetSweep(math.radians(startangle), math.radians(endangle))
246     def SetSweep(self, startradians, endradians):
247         self.sweep = [startradians, endradians]
248     def GetSweep(self):
249         return self.sweep
250
251     def SetXRotationAngle(self, xrotationAngle):
252         self.SetXRotation(math.radians(xrotationAngle))
253     def SetXRotation(self, xrotationRadians):
254         self.xrotation = xrotationRadians
255     def GetXRotation(self):
256         return self.xrotation
257
258     def asCurve(self):
259         sweep = self._GetSweepVector()
260         rx, ry = self.GetRadii()
261         xrotation = self.GetXRotation()
262         if xrotation:
263             ellipse = Numeric.asarray([rx*Numeric.cos(sweep), ry*Numeric.sin(sweep), Numeric.ones(len(sweep), self.NumericType)], self.NumericType)
264             rot = self._RotTranMatrix(xrotation, self.GetCenter())
265             ellipse = Numeric.dot(rot, ellipse)
266             return Numeric.transpose(ellipse[:-1,:])
267         else:
268             cx, cy = self.GetCenter()
269             ellipse = Numeric.asarray([rx*Numeric.cos(sweep)+cx, ry*Numeric.sin(sweep)+cy], self.NumericType)
270             return Numeric.transpose(ellipse)
271
272     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
273
274     def _GetSweepVector(self):
275         sweep = getattr(self, '_sweepvector', ())
276         if len(sweep) != self.GetSteps():
277             sweep = self._sweepvector = self._CreateSweepVector()
278         return sweep
279
280     def _CreateSweepVector(self):
281         steps = self.GetSteps()-1 # we append the last answer so it is always end-correct... even if the last step is a little off
282         startrad, endrad = self.GetSweep()
283         sweep = Numeric.arrayrange(startrad, endrad, (endrad-startrad)/steps, self.NumericType)
284         sweep = Numeric.concatenate((sweep, [endrad]), 0)
285         return sweep
286
287     def _RotationMatrix(klass, radians):
288         """Count clockwise rotation"""
289         cosr = Numeric.cos(radians)
290         sinr = Numeric.sin(radians)
291         return Numeric.asarray([[cosr, -sinr],[sinr, cosr]], klass.NumericType)
292     _RotationMatrix = classmethod(_RotationMatrix)
293
294     def _RotTranMatrix(klass, radians, (tx, ty)=(0.,0.)):
295         """Count clockwise rotation"""
296         cosr = Numeric.cos(radians)
297         sinr = Numeric.sin(radians)
298         return Numeric.asarray([[cosr, -sinr, tx],[sinr, cosr, ty], [0., 0., 1.]], klass.NumericType)
299     _RotTranMatrix = classmethod(_RotTranMatrix)
300
301     def _FindAngle(klass, u, v):
302         cosangle = Numeric.dot(u,v)/Numeric.sqrt(Numeric.dot(u,u)*Numeric.dot(v,v))
303         sign = Numeric.sign(u[0]*v[1]-u[1]*v[0]) or 1. # 0 is neither poistive or negative... but just make it positive for argument's sake...
304         return sign*Numeric.arccos(cosangle)
305     _FindAngle = classmethod(_FindAngle)
306
307     def _Invalidate(self):
308         try: delattr(self, '_sweepvector')
309         except AttributeError: pass
310
Note: See TracBrowser for help on using the browser.