File: quadraticbezier.py

package info (click to toggle)
python-beziers 0.6.0%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 672 kB
  • sloc: python: 3,160; makefile: 20
file content (140 lines) | stat: -rw-r--r-- 4,418 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from beziers.line import Line
from beziers.point import Point
from beziers.segment import Segment
from beziers.utils import quadraticRoots
from beziers.utils.arclengthmixin import ArcLengthMixin

my_epsilon = 2e-7


class QuadraticBezier(ArcLengthMixin, Segment):
    def __init__(self, start, c1, end):
        self.points = [start, c1, end]
        self._range = [0, 1]

    def __repr__(self):
        return "B<%s-%s-%s>" % (self[0], self[1], self[2])

    @classmethod
    def fromRepr(klass, text):
        import re

        p = re.compile("^B<(<.*?>)-(<.*?>)-(<.*?>)>$")
        m = p.match(text)
        points = [Point.fromRepr(m.group(t)) for t in range(1, 4)]
        return klass(*points)

    def pointAtTime(self, t):
        """Returns the point at time t (0->1) along the curve."""
        x = (
            (1 - t) * (1 - t) * self[0].x
            + 2 * (1 - t) * t * self[1].x
            + t * t * self[2].x
        )
        y = (
            (1 - t) * (1 - t) * self[0].y
            + 2 * (1 - t) * t * self[1].y
            + t * t * self[2].y
        )
        return Point(x, y)

    def tOfPoint(self, p):
        """Returns the time t (0->1) of a point on the curve."""
        xroots = quadraticRoots(
            self[0].x - 2 * self[1].x + self[2].x,
            2 * (self[1].x - self[0].x),
            self[0].x - p.x,
        )
        yroots = quadraticRoots(
            self[0].y - 2 * self[1].y + self[2].y,
            2 * (self[1].y - self[0].y),
            self[0].y - p.y,
        )
        if not len(xroots) or not len(yroots):
            return -1
        for x in xroots:
            for y in yroots:
                if -my_epsilon < x - y < my_epsilon:
                    return x
        return -1

    def splitAtTime(self, t):
        """Returns two segments, dividing the given segment at a point t (0->1) along the curve."""
        p4 = self[0].lerp(self[1], t)
        p5 = self[1].lerp(self[2], t)
        p7 = p4.lerp(p5, t)
        return (QuadraticBezier(self[0], p4, p7), QuadraticBezier(p7, p5, self[2]))

    def derivative(self):
        """Returns a `Line` representing the derivative of this curve."""
        return Line((self[1] - self[0]) * 2, (self[2] - self[1]) * 2)

    def flatten(self, degree=8):
        ss = []
        if self.length < degree:
            return [Line(self[0], self[2])]
        samples = self.sample(self.length / degree)
        for i in range(1, len(samples)):
            l = Line(samples[i - 1], samples[i])
            l._orig = self
            ss.append(l)
        return ss

    def _findRoots(self, dimension):
        if dimension == "x":
            return quadraticRoots(
                self[0].x - 2 * self[1].x + self[2].x,
                2 * (self[1].x - self[0].x),
                self[0].x,
            )
        elif dimension == "y":
            return quadraticRoots(
                self[0].y - 2 * self[1].y + self[2].y,
                2 * (self[1].y - self[0].y),
                self[0].y,
            )
        else:
            raise "Meh"

    def _findDRoots(self):
        d1 = self[0].x - 2 * self[1].x + self[2].x
        d2 = self[0].y - 2 * self[1].y + self[2].y
        roots = []
        if d1 != 0:
            r1 = (self[0].x - self[1].x) / d1
            roots.append(r1)
        if d2 != 0:
            r2 = (self[0].y - self[1].y) / d2
            roots.append(r2)
        return [r for r in roots if r >= 0.01 and r <= 0.99]

    def findExtremes(self):
        """Returns a list of time `t` values for extremes of the curve."""
        return self._findDRoots()

    @property
    def area(self):
        """Returns the signed area between the curve and the y-axis"""
        return (
            2
            * (
                self[1].x * self[0].y
                - self[0].x * self[1].y
                - self[1].x * self[2].y
                + self[2].x * self[1].y
            )
            + 3 * (self[2].x * self[2].y - self[0].x * self[0].y)
            + self[2].x * self[0].y
            - self[0].x * self[2].y
        ) / 6.0

    def toCubicBezier(self):
        """Converts the quadratic bezier to a CubicBezier"""
        from beziers.cubicbezier import CubicBezier

        return CubicBezier(
            self[0],
            self[0] * (1 / 3.0) + self[1] * (2 / 3.0),
            self[1] * (2 / 3.0) + self[2] * (1 / 3.0),
            self[2],
        )