예제 #1
0
파일: bezier.py 프로젝트: bernt/pymt
class BezierPath(object):
    '''Create a line based on bezier equation, or a shape using GLU tess.

    :Parameters:
        `filled` : boolean, default to False
            Create a filled bezier shape
        `path` : list, default to None
            Create a path directly from points. See up description for more
            information about the format used in path.
    '''
    def __init__(self, **kwargs):
        kwargs.setdefault('filled', False)
        kwargs.setdefault('path', None)

        self._tess                  = None
        self._filled_path           = None
        self._dl                    = GlDisplayList()
        self._path                  = []
        self._bezier_coefficients   = []
        self._bezier_points         = 10
        self._contructing           = False

        super(BezierPath, self).__init__()

        self.filled                 = kwargs.get('filled')
        self.x, self.y              = 0, 0

        if kwargs.get('path'):
            self.calculate_from_bezier_path(kwargs.get('path'))

    def path_begin(self, x, y):
        '''Start a new bezier path'''
        self._path = [x, y]
        self.x, self.y = x, y

    def path_end(self):
        '''End the current bezier path'''
        self._path += self._path[0:2]
        self.reset()

    def path_curve_to(self, x1, y1, x2, y2, x, y):
        '''Add a control point into bezier path'''
        if not self._bezier_coefficients:
            for i in xrange(self._bezier_points + 1):
                t = float(i) / self._bezier_points
                t0 = (1 - t) ** 3
                t1 = 3 * t * (1 - t) ** 2
                t2 = 3 * t ** 2 * (1 - t)
                t3 = t ** 3
                self._bezier_coefficients.append([t0, t1, t2, t3])
        for i, t in enumerate(self._bezier_coefficients):
            px = t[0] * self.x + t[1] * x1 + t[2] * x2 + t[3] * x
            py = t[0] * self.y + t[1] * y1 + t[2] * y2 + t[3] * y
            self._path += px, py
        self.x, self.y = px, py
        self.reset()

    def calculate_from_bezier_path(self, points):
        '''Create a new path from a list of control points'''
        self.path_begin(points[0], points[1])
        for i in xrange(2, len(points), 6):
            x1, y1, x2, y2, x, y = points[i:i+6]
            self.path_curve_to(x1, y1, x2, y2, x, y)
        self.path_end()
        self.reset()

    def draw_filled_path(self):
        for style, points in self.filled_path:
            with gx_begin(style):
                for x, y in zip(points[::2], points[1::2]):
                    glVertex2f(x, y)

    def draw(self):
        '''Draw the path on screen (filled or line)'''
        if not self._dl.is_compiled():
            with self._dl:
                if self.filled:
                    self.draw_filled_path()
                else:
                    drawLine(self.path)
        self._dl.draw()

    def reset(self):
        '''Reset the display list cache'''
        self._dl.clear()

    def _get_path(self):
        return self._path
    path = property(_get_path, doc='''Return the calculated path in format (x,y,x,y...)''')

    def _get_filled_path(self):
        if self._filled_path:
            return self._filled_path

        self._tess = gluNewTess()
        gluTessNormal(self._tess, 0, 0, 1)
        gluTessProperty(self._tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO)

        tess_list = []

        def tess_vertex(vertex):
            self._tess_shape += list(vertex[0:2])

        def tess_begin(which):
            self._tess_style = which
            self._tess_shape = []

        def tess_end():
            tess_list.append((self._tess_style, self._tess_shape))

        def tess_error(code):
            err = gluErrorString(code)
            pymt.pymt_logger.warning('BezierPath: GLU Tesselation Error: %s' % str(err))

        gluTessCallback(self._tess, GLU_TESS_VERTEX, tess_vertex)
        gluTessCallback(self._tess, GLU_TESS_BEGIN, tess_begin)
        gluTessCallback(self._tess, GLU_TESS_END, tess_end)
        gluTessCallback(self._tess, GLU_TESS_ERROR, tess_error)

        gluTessBeginPolygon(self._tess, None)
        gluTessBeginContour(self._tess)
        for x, y in zip(self._path[::2], self._path[1::2]):
            v_data = (x, y, 0)
            gluTessVertex(self._tess, v_data, v_data)
        gluTessEndContour(self._tess)
        gluTessEndPolygon(self._tess)

        self._filled_path = tess_list
        return tess_list
    filled_path = property(_get_filled_path,
                    doc='''Return the filled shape in format ((gl style, (x,y,x,y...)),...)''')