# HG changeset patch
# User Emily Kirkman <>
# Date 1242874073 25200
# Node ID ef8c792c47412e9cd9e3c328790bfebf94d0c4c5
# Parent  ca1f31d6f6bf7b1f8da0cb8973c838ab19921c29
3d bezier path implementation

diff -r ca1f31d6f6bf -r ef8c792c4741 sage/plot/bezier_path.py
--- a/sage/plot/bezier_path.py	Thu Jul 09 15:14:36 2009 -0700
+++ b/sage/plot/bezier_path.py	Wed May 20 19:47:53 2009 -0700
@@ -15,11 +15,11 @@
 #
 #                  http://www.gnu.org/licenses/
 #*****************************************************************************
-from sage.plot.primitive import GraphicPrimitive
+from sage.plot.primitive import GraphicPrimitive_xydata
 from sage.plot.misc import options, rename_keyword
 from sage.plot.colors import to_mpl_color
 
-class BezierPath(GraphicPrimitive):
+class BezierPath(GraphicPrimitive_xydata):
     """
     Path of Bezier Curves graphics primitive.
     """
@@ -41,7 +41,7 @@
             codes += (len(curve))*[len(curve)+1]
         self.codes = codes
         self.vertices = np.array(vertices, np.float)
-        GraphicPrimitive.__init__(self, options)
+        GraphicPrimitive_xydata.__init__(self, options)
         
     def _allowed_options(self):
         """
@@ -63,8 +63,58 @@
                 'thickness':'How thick the border of the polygon is.',
                 'rgbcolor':'The color as an rgb tuple.',
                 'zorder':'The layer level in which to draw',
-                                'linestyle':"The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."}
-                
+                'linestyle':"The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."}
+    
+    def _plot3d_options(self, options=None):
+        """
+        Updates BezierPath options to those allowed by 3d implementation.
+        
+        EXAMPLES:
+        
+            sage: from sage.plot.bezier_path import BezierPath
+            sage: B = BezierPath([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],{'linestyle':'dashed'})
+            sage: B._plot3d_options()
+            Traceback (most recent call last):
+            ...
+            NotImplementedError: Invalid 3d line style: dashed
+            sage: B = BezierPath([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],{'fill':False, 'thickness':2})
+            sage: B._plot3d_options()
+            {'thickness': 2}
+        """
+        if options == None:
+            options = dict(self.options())
+        options_3d = {}
+        if 'thickness' in options:
+            options_3d['thickness'] = options['thickness']
+            del options['thickness']
+        if 'fill' in options:
+            if options['fill']:
+                raise NotImplementedError, "Invalid 3d fill style.  Must set fill to False."
+            del options['fill']
+        if 'linestyle' in options:
+            if options['linestyle'] != 'solid':
+                raise NotImplementedError, "Invalid 3d line style: %s" % options['linestyle']
+            del options['linestyle']
+        options_3d.update(GraphicPrimitive_xydata._plot3d_options(self, options))
+        return options_3d
+
+    def plot3d(self, **kwds):
+        """
+        Returns a 3d plot (Jmol) of the bezier path.  Since a BezierPath primitive contains
+        only x,y coordinates, the path will be drawn in the z=0 plane.  To create a bezier path
+        with nonzero z coordinates in the path and control points, use the constructor bezier3d
+        instead of bezier_path.
+        
+        EXAMPLES:
+            sage: b = bezier_path([[(0,0),(0,1),(1,0)]])
+            sage: b.plot3d()
+            sage: bezier3d([[(0,0,0),(1,0,0),(0,1,0),(0,1,1)]])
+        """
+        from sage.plot.plot3d.shapes2 import bezier3d
+        options = self._plot3d_options()
+        options.update(kwds)
+        return bezier3d([[(x,y,0) for x,y in self.path[i]] for i in range(len(self.path))], **options)
+                                        
     def _repr_(self):
         return "Bezier path from %s to %s"%(self.path[0][0],self.path[-1][-1])
         
diff -r ca1f31d6f6bf -r ef8c792c4741 sage/plot/plot3d/all.py
--- a/sage/plot/plot3d/all.py	Thu Jul 09 15:14:36 2009 -0700
+++ b/sage/plot/plot3d/all.py	Wed May 20 19:47:53 2009 -0700
@@ -6,7 +6,7 @@
 
 from platonic          import tetrahedron, cube, octahedron, dodecahedron, icosahedron
 
-from shapes2           import sphere, line3d, polygon3d, point3d, text3d
+from shapes2           import sphere, line3d, polygon3d, point3d, text3d, bezier3d
 
 from shapes            import arrow3d
 
diff -r ca1f31d6f6bf -r ef8c792c4741 sage/plot/plot3d/shapes2.py
--- a/sage/plot/plot3d/shapes2.py	Thu Jul 09 15:14:36 2009 -0700
+++ b/sage/plot/plot3d/shapes2.py	Wed May 20 19:47:53 2009 -0700
@@ -104,6 +104,89 @@
         w._set_extra_kwds(kwds)
         return w
 
+@options(opacity=1, color="red", aspect_ratio=[1,1,1], thickness=2)
+def bezier3d(path, **options):
+    """
+    Draws a 3-dimensional bezier path.  Input is similar to bezier_path, but each
+    point in the path and each control point is required to have 3 coordinates.
+    
+    INPUT:
+    
+    -  ``path`` - a list of curves, which each is a list of points. See further 
+        detail below.
+    
+    -  ``thickness`` - (default: 2)
+    
+    -  ``color`` - a word that describes a color
+
+    -  ``opacity`` - (default: 1) if less than 1 then is
+       transparent
+       
+    -  ``aspect_ratio`` - (default:[1,1,1])
+    
+    The path is a list of curves, and each curve is a list of points.  
+    Each point is a tuple (x,y,z).
+    
+    The first curve contains the endpoints as the first and last point 
+    in the list.  All other curves assume a starting point given by the
+    last entry in the preceding list, and take the last point in the list
+    as their opposite endpoint.  A curve can have 0, 1 or 2 control points
+    listed between the endpoints.  In the input example for path below, 
+    the first and second curves have 2 control points, the third has one,
+    and the fourth has no control points:    
+    
+    path = [[p1, c1, c2, p2], [c3, c4, p3], [c5, p4], [p5], ...]
+    
+    In the case of no control points, a striaght line will be drawn
+    between the two endpoints.  If one control point is supplied, then 
+    the curve at each of the endpoints will be tangent to the line from
+    that endpoint to the control point.  Similarly, in the case of two 
+    control points, at each endpoint the curve will be tangent to the line
+    connecting that endpoint with the control point immediately after or
+    immediately preceding it in the list.
+    
+    So in our example above, the curve between p1 and p2 is tangent to the
+    line through p1 and c1 at p1, and tangent to the line through p2 and c2
+    at p2.  Similarly, the curve between p2 and p3 is tangent to line(p2,c3)
+    at p2 and tangent to line(p3,c4) at p3.  Curve(p3,p4) is tangent to 
+    line(p3,c5) at p3 and tangent to line(p4,c5) at p4.  Curve(p4,p5) is a
+    straight line.
+
+    EXAMPLES:
+        sage: path = [[(0,0,0),(.5,.1,.2),(.75,3,-1),(1,1,0)],[(.5,1,.2),(1,.5,0)],[(.7,.2,.5)]]
+        sage: b = bezier3d(path, color='green')
+        sage: b
+
+    To construct a simple curve, create a list containing a single list:
+        
+        sage: path = [[(0,0,0),(1,0,0),(0,1,0),(0,1,1)]]
+        sage: curve = bezier3d(path, thickness=5, color='blue')
+        sage: curve
+    """
+    import parametric_plot3d as P3D
+    from sage.modules.free_module_element import vector
+    from sage.calculus.calculus import var
+    
+    p0 = vector(path[0][-1])
+    t = var('t')
+    if len(path[0]) > 2:
+        B = (1-t)**3*vector(path[0][0])+3*t*(1-t)**2*vector(path[0][1])+3*t**2*(1-t)*vector(path[0][-2])+t**3*p0
+        G = P3D.parametric_plot3d(list(B), (0, 1), color=options['color'], aspect_ratio=options['aspect_ratio'], thickness=options['thickness'], opacity=options['opacity'])
+    else:
+        G = line3d([path[0][0], p0], color=options['color'], thickness=options['thickness'], opacity=options['opacity'])
+    
+    for curve in path[1:]:
+        if len(curve) > 1:
+            p1 = vector(curve[0])
+            p2 = vector(curve[-2])
+            p3 = vector(curve[-1])
+            B = (1-t)**3*p0+3*t*(1-t)**2*p1+3*t**2*(1-t)*p2+t**3*p3
+            G += P3D.parametric_plot3d(list(B), (0, 1), color=options['color'], aspect_ratio=options['aspect_ratio'], thickness=options['thickness'], opacity=options['opacity'])
+        else:
+            G += line3d([p0,curve[0]], color=options['color'], thickness=options['thickness'], opacity=options['opacity'])
+        p0 = curve[-1]
+    return G
+    
 @rename_keyword(alpha='opacity')
 @options(opacity=1, color=(0,0,1))
 def polygon3d(points, **options):
diff -r ca1f31d6f6bf -r ef8c792c4741 sage/plot/primitive.py
--- a/sage/plot/primitive.py	Thu Jul 09 15:14:36 2009 -0700
+++ b/sage/plot/primitive.py	Wed May 20 19:47:53 2009 -0700
@@ -106,6 +106,8 @@
         if 'alpha' in options:
             options_3d['opacity'] = options['alpha']
             del options['alpha']
+        if 'zorder' in options:
+            del options['zorder']
         if len(options) != 0:
             raise NotImplementedError, "Unknown plot3d equivalent for %s" % ", ".join(options.keys())
         return options_3d
