home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Multimedia / k3d-setup-0.7.11.0.exe / lib / site-packages / cgkit / beziercurvegeom.py < prev    next >
Encoding:
Python Source  |  2007-01-11  |  18.4 KB  |  612 lines

  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Python Computer Graphics Kit.
  15. #
  16. # The Initial Developer of the Original Code is Matthias Baas.
  17. # Portions created by the Initial Developer are Copyright (C) 2004
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. #
  22. # Alternatively, the contents of this file may be used under the terms of
  23. # either the GNU General Public License Version 2 or later (the "GPL"), or
  24. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25. # in which case the provisions of the GPL or the LGPL are applicable instead
  26. # of those above. If you wish to allow use of your version of this file only
  27. # under the terms of either the GPL or the LGPL, and not to allow others to
  28. # use your version of this file under the terms of the MPL, indicate your
  29. # decision by deleting the provisions above and replace them with the notice
  30. # and other provisions required by the GPL or the LGPL. If you do not delete
  31. # the provisions above, a recipient may use your version of this file under
  32. # the terms of any one of the MPL, the GPL or the LGPL.
  33. #
  34. # ***** END LICENSE BLOCK *****
  35. # $Id: beziercurvegeom.py,v 1.2 2005/09/02 12:58:02 mbaas Exp $
  36.  
  37. from _OpenGL.GL import *
  38. import bisect
  39. from geomobject import *
  40. from slots import *
  41. from cgtypes import *
  42. from boundingbox import BoundingBox
  43. import protocols
  44. from ribexport import IGeometry
  45. from ri import *
  46.  
  47. # BezierPoint
  48. class BezierPoint:
  49.     """A single Bezier point with tangents.
  50.  
  51.     This object is used during construction of a BezierCurveGeom object.
  52.     """
  53.     def __init__(self, pos, intangent=vec3(0), outtangent=vec3(0)):
  54.         self.pos = vec3(pos)
  55.         self.intangent = vec3(intangent)
  56.         self.outtangent = vec3(outtangent)
  57.  
  58. _uniform_size_constraint = UserSizeConstraint(1)
  59. _null_size_constraint = UserSizeConstraint(0)
  60.  
  61. # BezierCurveGeom
  62. class BezierCurveGeom(GeomObject):
  63.     """Cubic Bezier curve.
  64.     """
  65.  
  66.     protocols.advise(instancesProvide=[IGeometry])
  67.     
  68.     def __init__(self,
  69.                  pnts = None,
  70.                  closed = False,
  71.                  epsilon = 0.01,
  72.                  subdiv = 4,
  73.                  show_tangents = False):
  74.         """Constructor.
  75.  
  76.         pnts is a list of BezierPoint objects.
  77.         """
  78.         GeomObject.__init__(self)
  79.  
  80.         # Epsilon value for segLength()
  81.         self.epsilon = epsilon
  82.         # Number of subdivisions for drawGL()
  83.         self.subdiv = subdiv
  84.         # Show tangents or not?
  85.         self.show_tangents = show_tangents
  86.  
  87.         # Create the slots...
  88.         
  89.         # The end points of the Bezier segments, i.e. the curve passes
  90.         # through these points.
  91.         self.pnts_slot = Vec3ArraySlot()
  92.         self._var_sizeconstraint = LinearSizeConstraint(self.pnts_slot,1,0)
  93.         if closed:
  94.             b = 0
  95.         else:
  96.             b = -2
  97.         self._vtx_sizeconstraint = LinearSizeConstraint(self.pnts_slot,3,b)
  98.         # The incoming tangent for point i
  99.         self.intangents_slot = Vec3ArraySlot(constraint=self._var_sizeconstraint)
  100.         # The outgoing tangent for point i
  101.         self.outtangents_slot = Vec3ArraySlot(constraint=self._var_sizeconstraint)
  102.         
  103.         # Create aliases for the slot "value" attribute
  104.         self.pnts = self.pnts_slot
  105.         self.intangents = self.intangents_slot
  106.         self.outtangents = self.outtangents_slot
  107.  
  108.         # Add the slots to the component
  109.         self.addSlot("pnts", self.pnts_slot)
  110.         self.addSlot("intangents", self.intangents_slot)
  111.         self.addSlot("outtangents", self.outtangents_slot)
  112.  
  113.         self._closed = closed
  114.  
  115.         if pnts!=None:
  116.             # Initialize the Bezier points...
  117.             self.pnts.resize(len(pnts))
  118.             for i,bp in enumerate(pnts):
  119.                 self.pnts[i] = bp.pos
  120.                 self.intangents[i] = bp.intangent
  121.                 self.outtangents[i] = bp.outtangent
  122.  
  123.         # A list of lengths (the lengths are accumulated, so it's
  124.         # the length of the entire curve until segment i)
  125.         self._seg_lengths = []
  126.         # Flag that indicates if all internal precomputed attributes have
  127.         # to be recomputed or if they are still valid.
  128.         self._internal_data_invalid = True
  129.  
  130.         # Have the slots call the onCurveChanged() method whenever
  131.         # a point or a tangent is modified.
  132.         self._nf = NotificationForwarder(self.onCurveChanged, self.onCurveResized)
  133.         self.pnts_slot.addDependent(self._nf)
  134.         self.intangents_slot.addDependent(self._nf)
  135.         self.outtangents_slot.addDependent(self._nf)
  136.  
  137.  
  138.     def _updateInternalData(self):
  139.         """Precalculate the lengths of the Bezier segments.
  140.         """
  141.         self._seg_lengths = []
  142.         sl = 0.0
  143.         if self._closed:
  144.             k = 0
  145.         else:
  146.             k = 1
  147.         for i in range(self.pnts.size()-k):
  148.             sl += self.segLength(self.segCtrlPoints(i), self.epsilon)
  149.             self._seg_lengths.append(sl)
  150.  
  151.         self._internal_data_invalid = False
  152.         
  153.  
  154. #    def uniformCount(self):
  155. #        return 1
  156.  
  157. #    def varyingCount(self):
  158. #        # One value per segment boundary, i.e.
  159. #        # self.numsegs   for periodic curves and
  160. #        # self.numsegs+1 for non-periodic curves
  161. #        # which can be reduced to self.pnts.size() for both cases
  162. #        # (as the curve points just mark segment boundaries)
  163. #        return self.pnts.size()
  164.  
  165. #    def vertexCount(self):
  166. #        # One value per control points,
  167. #        n = 3*self.numsegs
  168. #        if not self._closed:
  169. #            n += 1
  170. #        return n
  171.  
  172.     # slotSizeConstraint
  173.     def slotSizeConstraint(self, storage):
  174.         global _uniform_size_constraint
  175.         
  176.         if storage==UNIFORM:
  177.             return _uniform_size_constraint
  178.         elif storage==VARYING:
  179.             return self._var_sizeconstraint
  180.         elif storage==VERTEX:
  181.             return self._vtx_sizeconstraint
  182.         else:
  183.             return _null_size_constraint
  184.  
  185.     # boundingBox
  186.     def boundingBox(self):
  187.         """Return the bounding box of the control polygon.
  188.         """
  189.         bb = BoundingBox()
  190.         for i,p in enumerate(self.pnts):
  191.             bb.addPoint(p)
  192.             bb.addPoint(p+self.intangents[i])
  193.             bb.addPoint(p+self.outtangents[i])
  194.         return bb
  195.  
  196.     # drawGL
  197.     def drawGL(self):
  198.         if self.pnts.size()==0:
  199.             return
  200.  
  201.         glPushAttrib(GL_LIGHTING_BIT)
  202.         glDisable(GL_LIGHTING)
  203.         glColor3f(1,1,1)
  204.  
  205.         # Draw the curve...
  206.         p0 = self.pnts[0]
  207.         in0 = self.intangents[0]
  208.         out0 = self.outtangents[0]
  209.         for i in range(1, self.pnts.size()):
  210.             p1 = self.pnts[i]
  211.             in1 = self.intangents[i]
  212.             out1 = self.outtangents[i]
  213.             self.segDrawGL([p0, p0+out0, p1+in1, p1], self.subdiv)
  214.             p0 = p1
  215.             in0 = in1
  216.             out0 = out1
  217.  
  218.         # Draw an additional segment if the curve is closed...
  219.         if self._closed:
  220.             p0 = self.pnts[-1]
  221.             out0 = self.outtangents[-1]
  222.             p1 = self.pnts[0]
  223.             in1 = self.intangents[0]
  224.             self.segDrawGL([p0, p0+out0, p1+in1, p1], self.subdiv)
  225.             
  226.  
  227.         # Draw the in and out tangents...
  228.         if self.show_tangents:
  229.             for i in range(self.pnts.size()):
  230.                 p = self.pnts[i]
  231.                 pin = self.intangents[i]
  232.                 pout = self.outtangents[i]
  233.                 glBegin(GL_LINE_STRIP)
  234.                 glColor3f(0,1,0)
  235.                 v = p+pin
  236.                 glVertex3d(v.x, v.y, v.z)
  237.                 glVertex3d(p.x, p.y, p.z)
  238.                 glColor3f(0,0,1)
  239.                 glVertex3d(p.x, p.y, p.z)
  240.                 v = p+pout
  241.                 glVertex3d(v.x, v.y, v.z)
  242.                 glEnd()            
  243.  
  244.         glPopAttrib()
  245.  
  246.     # render
  247.     def render(self, matid):
  248.         if matid==0:
  249.             # Set Bezier basis
  250.             RiBasis(RiBezierBasis, RI_BEZIERSTEP, RiBezierBasis, RI_BEZIERSTEP)
  251.  
  252.             # Create the list of curve vertices...
  253.             pnts = []
  254.             for i in range(self.numsegs):
  255.                 ps = self.segCtrlPoints(i)
  256.                 pnts += ps[0:3]
  257.  
  258.             if self._closed:
  259.                 wrap = RI_PERIODIC
  260.             else:
  261.                 wrap = RI_NONPERIODIC
  262.                 pnts.append(self.pnts[-1])
  263.  
  264.             # Create parameter list...
  265.             params = {"P":pnts}
  266.             clss = ["constant", "uniform", "varying", "vertex",
  267.                     "facevarying", "facevertex", "user"]
  268.             typs = ["integer", "float", "string", "color", "point",
  269.                     "vector", "normal", "matrix", "hpoint"]
  270.             for name, storage, type, multiplicity in self.iterVariables():
  271.                 cls = clss[abs(storage)]
  272.                 typ = typs[abs(type)]
  273.                 if cls=="user":
  274.                     continue
  275.                 s = self.slot(name)
  276.                 params[name] = list(s)
  277.                 if multiplicity==1:
  278.                     decl = "%s %s"%(cls, typ)
  279.                 else:
  280.                     decl = "%s %s[%d]"%(cls, typ, multiplicity)
  281.                 RiDeclare(name, decl)
  282.  
  283.             # Output a curve primitive
  284.             RiCurves(RI_CUBIC, [len(pnts)], wrap, params)
  285.  
  286.     # eval
  287.     def eval(self, t):
  288.         """Evaluate the curve at parameter t and return the curve point.
  289.         """
  290.         # Segment number
  291.         seg = int(t)
  292.         # Segment parameter
  293.         t = t%1.0
  294. #        if seg>=self.pnts.size()-1:
  295. #            seg = self.pnts.size()-2
  296. #            t = 1.0
  297.         return self.segEval(t, self.segCtrlPoints(seg))        
  298.  
  299.     # evalFrame
  300.     def evalFrame(self, t):
  301.         """Evaluate the curve at parameter t and return a coordinate frame.
  302.         """
  303.         # Segment number
  304.         seg = int(t)
  305.         # Segment parameter
  306.         t = t%1.0
  307. #        if seg>=self.pnts.size()-1:
  308. #            seg = self.pnts.size()-2
  309. #            t = 1.0
  310.         return self.segEvalFrame(t, self.segCtrlPoints(seg))
  311.  
  312.     # deriv
  313.     def deriv(self, t):
  314.         """Return the derivative at parameter t.
  315.         """
  316.         # Segment number
  317.         seg = int(t)
  318.         # Segment parameter
  319.         t = t%1.0
  320. #        if seg>=self.pnts.size()-1:
  321. #            seg = self.pnts.size()-2
  322. #            t = 1.0
  323.         return self.segDeriv(t, self.segCtrlPoints(seg))
  324.  
  325.     # arcLen
  326.     def arcLen(self, t):
  327.         """Return the arc length up to t.
  328.         """
  329.         if self._internal_data_invalid:
  330.             self._updateInternalData()
  331.             
  332.         seg = int(t)
  333.         t = t%1.0
  334.         if seg>=len(self._seg_lengths):
  335.             return self._seg_lengths[-1]
  336.  
  337.         res = 0.0
  338.         if seg>0:
  339.             res = self._seg_lengths[seg-1]
  340.         b,c = self.segSplit(self.segCtrlPoints(seg), t)
  341.         return res + self.segLength(b, self.epsilon)
  342.  
  343.     # length
  344.     def length(self):
  345.         """Return the length of the entire curve.
  346.         """
  347.         if self._internal_data_invalid:
  348.             self._updateInternalData()
  349.         if len(self._seg_lengths)==0:
  350.             return 0.0
  351.         else:
  352.             return self._seg_lengths[-1]
  353.  
  354.  
  355.     def eval0(self, t):
  356.         if self._internal_data_invalid:
  357.             self._updateInternalData()
  358.  
  359.         # Determine the Bezier segment index we're currently in
  360.         seg = bisect.bisect_right(self._seg_lengths, t)
  361.         if seg==self.pnts.size()-1:
  362.             return self.pnts[-1], vec3(), vec3()
  363.  
  364.         if seg>0:
  365.             t -= self._seg_lengths[seg-1]
  366.  
  367.         p,t = self.segEval0(t, self.segCtrlPoints(seg))
  368.         if p!=None:
  369.             return p, vec3(), vec3()
  370.         else:
  371.             return self.pnts[-1], vec3(), vec3()
  372.  
  373.     # onCurveChanged
  374.     def onCurveChanged(self, start, end):
  375.         """This method is called whenever a point or tangent is modified.
  376.         """
  377.         self._internal_data_invalid = True
  378.  
  379.     # onCurveResized
  380.     def onCurveResized(self, size):
  381.         """This method is called whenever the number of points is modified.
  382.         """
  383.         self._internal_data_invalid = True
  384.         
  385.     ## protected:
  386.  
  387.     def segSmooth(self, seg):
  388.         b0,b1,b2,b3 = self.segCtrlPoints(seg-1)
  389.         d1 = b2-b1
  390.         d2 = b3-b2
  391.         self.outtangents[seg] = d2
  392.         self.intangents[seg+1] = self.pnts[seg]+3*d2-d1 - self.pnts[seg+1]
  393.  
  394.     def segCtrlPoints(self, i):
  395.         """Return the Bezier control points of segment i.
  396.  
  397.         i may be an arbitrary segment number (the numbering is cyclic).
  398.         """
  399.         s = self.pnts.size()
  400.         i = i%s
  401.         j = (i+1)%s
  402.         b0 = self.pnts[i]
  403.         b3 = self.pnts[j]
  404.         b1 = b0+self.outtangents[i]
  405.         b2 = b3+self.intangents[j]
  406.         return [b0, b1, b2, b3]
  407.  
  408.     # segEval0
  409.     def segEval0(self, t, ctrlpnts, eps=0.001):
  410.         """
  411.  
  412.         Return value: (pnt, t2)
  413.         pnt is None if t>length. t2 is t-length.
  414.         """
  415.         l1 = self.segHullLength(ctrlpnts)
  416.         b,c = self.segSplit(ctrlpnts)
  417.         bl = self.segHullLength(b)
  418.         cl = self.segHullLength(c)
  419.         l2 = bl+cl
  420.         # Is the result accurate enough? then return the current result,
  421.         # otherwise subdivide further
  422.         if l1-l2<=eps:
  423.             p,t = self.segHullEval0(t, b)
  424.             if p!=None:
  425.                 return p,t
  426.             p,t = self.segHullEval0(t, c)
  427.             if p!=None:
  428.                 return p,t
  429.             return None, t
  430.         else:
  431.             p,t = self.segEval0(t, b, eps=eps)
  432.             if p!=None:
  433.                 return p,t
  434.             p,t = self.segEval0(t, c, eps=eps)
  435.             if p!=None:
  436.                 return p,t
  437.             return None, t
  438.  
  439.     def segHullEval0(self, t, ctrlpnts):
  440.         b0,b1,b2,b3 = ctrlpnts
  441.         # 1st segment
  442.         d = (b1-b0).length()
  443.         if t<d:
  444.             a = t/d
  445.             return (1-a)*b0 + a*b1, t
  446.         t-=d
  447.         # 2nd segment
  448.         d = (b2-b1).length()
  449.         if t<d:
  450.             a = t/d
  451.             return (1-a)*b1 + a*b2, t
  452.         t-=d
  453.         # 3rd segment
  454.         d = (b3-b2).length()
  455.         if t<d:
  456.             a = t/d
  457.             return (1-a)*b2 + a*b3, t
  458.         t-=d
  459.         return None, t
  460.         
  461.     # segEval
  462.     def segEval(self, t, ctrlpnts):
  463.         """Evaluate the curve at parameter t using the de Casteljau scheme.
  464.  
  465.         t ranges from 0 to 1.
  466.         """
  467.         _t = 1.0-t
  468.         b0,b1,b2,b3 = ctrlpnts
  469.         c0 = _t*b0 + t*b1
  470.         c1 = _t*b1 + t*b2
  471.         c2 = _t*b2 + t*b3
  472.         d0 = _t*c0 + t*c1
  473.         d1 = _t*c1 + t*c2
  474.         return _t*d0+t*d1
  475.  
  476.     # segEvalFrame
  477.     def segEvalFrame(self, t, ctrlpnts):
  478.         """Evaluate the curve at parameter t using the de Casteljau scheme.
  479.  
  480.         t ranges from 0 to 1.
  481.         """
  482.         _t = 1.0-t
  483.         b0,b1,b2,b3 = ctrlpnts
  484.         c0 = _t*b0 + t*b1
  485.         c1 = _t*b1 + t*b2
  486.         c2 = _t*b2 + t*b3
  487.         d0 = _t*c0 + t*c1
  488.         d1 = _t*c1 + t*c2
  489.         e0 = _t*d0+t*d1
  490.         return e0, 3*(d1-d0), 6*(c2-2*c1+c0)
  491.  
  492.     # segDeriv
  493.     def segDeriv(self, t, ctrlpnts):
  494.         """Return the derivation at t.
  495.  
  496.         t ranges from 0 to 1.
  497.         """
  498.         _t = 1.0-t
  499.         b0,b1,b2,b3 = ctrlpnts
  500.         c0 = _t*b0 + t*b1
  501.         c1 = _t*b1 + t*b2
  502.         c2 = _t*b2 + t*b3
  503.         d0 = _t*c0 + t*c1
  504.         d1 = _t*c1 + t*c2
  505.         return 3*(d1-d0)
  506.  
  507.  
  508.     # segDrawGL
  509.     def segDrawGL(self, ctrlpnts, subdiv=4):
  510.         """Draw one Bezier segment.
  511.  
  512.         Actually the control polygon is drawn after a number of
  513.         subdivisions.
  514.         """
  515.         if subdiv==0:
  516.             b0,b1,b2,b3 = ctrlpnts
  517.             glBegin(GL_LINE_STRIP)
  518.             glVertex3d(b0.x, b0.y, b0.z)
  519.             glVertex3d(b1.x, b1.y, b1.z)
  520.             glVertex3d(b2.x, b2.y, b2.z)
  521.             glVertex3d(b3.x, b3.y, b3.z)
  522.             glEnd()
  523.         else:
  524.             b,c = self.segSplit(ctrlpnts)
  525.             self.segDrawGL(b, subdiv=subdiv-1)
  526.             self.segDrawGL(c, subdiv=subdiv-1)
  527.             
  528.     # segLength
  529.     def segLength(self, ctrlpnts, eps=0.001):
  530.         """Return the length of one Bezier segment.
  531.  
  532.         ctrlpnts is a list of four vec3s.
  533.         """
  534.         l1 = self.segHullLength(ctrlpnts)
  535.         b,c = self.segSplit(ctrlpnts)
  536.         bl = self.segHullLength(b)
  537.         cl = self.segHullLength(c)
  538.         l2 = bl+cl
  539.         # Is the result accurate enough? then return the current result,
  540.         # otherwise subdivide further
  541.         if l1-l2<=eps:
  542.             return l2
  543.         else:
  544.             return self.segLength(b,eps)+self.segLength(c,eps)
  545.  
  546.     # segHullLength
  547.     def segHullLength(self, ctrlpnts):
  548.         """Return the length of the control "polygon".
  549.         """
  550.         b0,b1,b2,b3 = ctrlpnts
  551.         return (b1-b0).length() + (b2-b1).length() + (b3-b2).length()
  552.  
  553.     # segSplit
  554.     def segSplit(self, ctrlpnts, t=0.5):
  555.         """Split a segment into two segments.
  556.         """
  557.         b0,b1,b2,b3 = ctrlpnts
  558.         _t = 1.0-t
  559.         c0 = _t*b0 + t*b1
  560.         c1 = _t*b1 + t*b2
  561.         c2 = _t*b2 + t*b3
  562.         d0 = _t*c0 + t*c1
  563.         d1 = _t*c1 + t*c2
  564.         e0 = _t*d0 + t*d1
  565.         return [b0,c0,d0,e0], [e0,d1,c2,b3]
  566.  
  567.     # "paraminterval" property...
  568.     
  569.     def _getParamInterval(self):
  570.         """Return the current parameter interval.
  571.  
  572.         This method is used for retrieving the \a paraminterval property.
  573.  
  574.         \return (t_min, t_max)
  575.         """
  576.         if self._closed:
  577.             return 0, self.pnts.size()
  578.         else:
  579.             return 0, self.pnts.size()-1
  580.  
  581.     paraminterval = property(_getParamInterval, None, None, "Parameter range")
  582.  
  583.     # "closed" property...
  584.     
  585.     def _getClosed(self):
  586.         return self._closed
  587.  
  588.     def _setClosed(self, c):
  589.         if c==self._closed:
  590.             return
  591.         self._closed = c
  592.         self._internal_data_invalid = True
  593.         # Update the size constraint for vertex variables
  594.         if self._closed:
  595.             self._vtx_sizeconstraint.setCoeffs(3,0)
  596.         else:
  597.             self._vtx_sizeconstraint.setCoeffs(3,-2)
  598.  
  599.     closed = property(_getClosed, _setClosed, None, "Closed")
  600.  
  601.     # "numsegs" property...
  602.     
  603.     def _getNumSegs(self):
  604.         if self._closed:
  605.             return self.pnts.size()
  606.         else:
  607.             return self.pnts.size()-1
  608.  
  609.     numsegs = property(_getNumSegs, None, None, "Number of Bezier segments")
  610.  
  611.  
  612.