File: line.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 (123 lines) | stat: -rw-r--r-- 4,128 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
import math
import sys
from typing import List, Tuple

from beziers.point import Point
from beziers.segment import Segment
from beziers.utils import isclose


class Line(Segment):
    """Represents a line segment within a Bezier path."""

    def __init__(self, start: Point, end: Point):
        """Create a new line segment.

        Args:
            start (Point): The starting point of the line.
            end (Point): The ending point of the line.
        """
        self.points = [start, end]
        self._orig = None

    def __repr__(self):
        return "L<%s--%s>" % (self.points[0], self.points[1])

    @classmethod
    def fromRepr(klass, text: str) -> "Line":
        """Create a new line segment from a string representation."""
        import re

        p = re.compile("^L<(<.*?>)--(<.*?>)>$")
        m = p.match(text)
        return klass(Point.fromRepr(m.group(1)), Point.fromRepr(m.group(2)))

    def pointAtTime(self, t: float) -> Point:
        """Returns the point at time t (0->1) along the line."""
        return self.start.lerp(self.end, t)

    # XXX One of these is wrong
    def tangentAtTime(self, t: float) -> Point:
        """Returns the tangent at time t (0->1) along the line."""
        return Point.fromAngle(
            math.atan2(self.end.y - self.start.y, self.end.x - self.start.x)
        )

    def normalAtTime(self, t: float) -> Point:
        """Returns the normal at time t (0->1) along the line."""
        return self.tangentAtTime(t).rotated(Point(0, 0), math.pi / 2)

    def curvatureAtTime(self, _t: float) -> float:
        """Returns the C curvature at time `t`."""
        return sys.float_info.epsilon  # Avoid divide-by-zero

    def splitAtTime(self, t: float) -> Tuple["Line", "Line"]:
        """Returns two segments, dividing the given segment at a point t (0->1) along the line."""
        return (
            Line(self.start, self.pointAtTime(t)),
            Line(self.pointAtTime(t), self.end),
        )

    def _findRoots(self, dimension: str) -> List[float]:
        if dimension == "x":
            t = (
                self[0].x / (self[0].x - self[1].x)
                if not isclose(self[0].x, self[1].x)
                else 100.0
            )
        elif dimension == "y":
            t = (
                self[0].y / (self[0].y - self[1].y)
                if not isclose(self[0].y, self[1].y)
                else 100.0
            )
        else:
            raise SyntaxError("Meh")
        if 0.0 <= t <= 1.0:
            return [t]
        return []

    def tOfPoint(self, point: Point, its_on_the_line_i_swear=False) -> float:
        """Returns the t (0->1) value of the given point, assuming it lies on the line, or -1 if it does not."""
        # Just find one and hope the other fits
        # point = self.start * (1-t) + self.end * t
        if not isclose(self.end.x, self.start.x):
            t = (point.x - self.start.x) / (self.end.x - self.start.x)
        elif not isclose(self.end.y, self.start.y):
            t = (point.y - self.start.y) / (self.end.y - self.start.y)
        else:
            print("! Line %s is actually a point..." % self)
            return -1
        if its_on_the_line_i_swear or self.pointAtTime(t).distanceFrom(point) < 2e-7:
            return t
        return -1

    def flatten(self, _degree=8) -> List["Line"]:
        return [self]

    @property
    def slope(self) -> float:
        """Returns the slope of the line."""
        v = self[1] - self[0]
        if v.x == 0:
            return 0
        return v.y / v.x

    @property
    def intercept(self) -> float:
        """Returns the y-intercept of the line."""
        return self[1].y - self.slope * self[1].x

    @property
    def length(self) -> float:
        """Returns the length of the line."""
        return self[0].distanceFrom(self[1])

    def findExtremes(self) -> List[Point]:
        """Returns the extrema of the line."""
        return []

    @property
    def area(self) -> float:
        """Returns the signed area of the line."""
        return 0.5 * (self[1].x - self[0].x) * (self[0].y + self[1].y)