Commits

Manfred Moitzi committed 8bae6d2

polybezier - find_position works

Comments (0)

Files changed (3)

geoalg/polybezier.py

 
 from .nurbs import Bezier4P
 from .vector2d import Vec2, NULLVEC
-from .util import almost_equal_vectors, memodict
+from .util import almost_equal_vectors
+from ray import Ray2D
 import math
 
 class PolyBezier(object):
 
         :param int segments: segment count for each bezier part.
         """
-        points = []
+        points = [self.breakpoints[0]]
         for bezier in self.iter_bezier_parts():
-            points.extend(bezier.approximate(segments))
+            pts = bezier.approximate(segments)
+            next(pts) # skip first point, because equals last point of prev part
+            points.extend(pts)
         return points
 
-    def find_position(self, p, epsilon=0.0001):
+    def find_position(self, p, segments=100):
         """ Find curve position near point p.
 
         :param 2-tuple p: curve position
         """
-        # this algorithm works only with 'flat' curves, no loops or
-        # nearly self touching curves.
+        def interpolate(start, end):
+            if start > end:
+                start, end = end, start
+            ps = Vec2(self.get_point(start))
+            pe = Vec2(self.get_point(end))
+            ray1 = Ray2D(ps, pe)
+            ip =  ray1.intersect(ray1.normal_through(p))
+            a = ps.distance(ip)
+            b = pe.distance(ip)
+            return start + (end - start) * (a / (a + b))
+
+        def curve_distances():
+            delta = self.length / segments
+            for i in range(segments+1):
+                position = i * delta
+                curve_point = Vec2(self.get_point(position))
+                yield p.distance(curve_point), position
 
         p = Vec2(p)
-
-        # caching of get_point() is save, because no changing of breakpoints
-        # or tangents within a search.
-        curve_point = memodict(self.get_point)
-
-        def is_close_enough(p2):
-            return p.distance(p2) <= epsilon
-
-        def search(s, e):
-            m = (s + e) / 2.0
-            pm = curve_point(m)
-            if is_close_enough(pm):
-                return m
-
-            if abs(e - s) < epsilon:
-                # requested point is beside the curve
-                return m
-
-            # recursive binary search
-            if p.distance(curve_point(s)) < p.distance(curve_point(e)):
-                return search(s, m)
-            else:
-                return search(m, e)
-
-        if is_close_enough(curve_point(0.)):
-            return 0.
-        elif is_close_enough(curve_point(self.length)):
-            return self.length
-        else:
-            return search(0., self.length)
+        start, end = sorted(curve_distances())[:2]
+        return interpolate(start[1], end[1])
 
     def find_part(self, position):
         """ Find bezier curve part associated to curve position.
         bezier, t = self.find_part(position)
         # 0 <= t <= 1.
         return bezier.get_tangent(t)
-            
+
 class Tangent(object):
     def __init__(self, left, right, align=True):
         self._left = Vec2(left)

polybezier_examples.py

             dwg.add(dxf.line(p, p+tangent.right, color=4))
 
     def add_curvepoint(p1):
-        cpos = bezier.find_position(p1)
+        cpos = bezier.find_position(p1, 300)
         p2 = bezier.get_point(cpos)
         for p in (p1, p2):
             dwg.add(dxf.circle(radius=.2, center=p, color=5))
     add_defpoints()
     add_tangents()
     add_curvepoint((22, 16))
+    add_curvepoint((7, 13))
+    add_curvepoint((35, 12))
     dwg.save()
 
+def print_bezier_data(filename, bezier):
+    import csv
+    points = bezier.approximate(10)
+    rows = []
+    point_gen = iter(points)
+    prev_point = Vec2(next(point_gen))
+    dist_abs = 0.
+    num = 0
+    for p in point_gen:
+        p = Vec2(p)
+        distance = prev_point.distance(p)
+        dist_abs += distance
+        num += 1
+        rows.append((num, p.x, p.y, distance, dist_abs))
+        prev_point = p
+
+    with open(filename, 'wt') as fp:
+        writer = csv.writer(fp, dialect='excel')
+        writer.writerow(('num', 'x', 'y', 'dist-rel', 'dist-abs'))
+        for row in rows:
+            writer.writerow(row)
 def main():
-    draw("polybezier.svg", PolyBezier(DEFPOINTS, START_ANGLE, END_ANGLE))
-    draw("polybezier_reversed.svg", PolyBezier(reversed(DEFPOINTS), END_ANGLE, START_ANGLE))
-    draw("bezier_circle.svg", bezier_circle())
+    #draw("polybezier.svg", PolyBezier(DEFPOINTS, START_ANGLE, END_ANGLE))
+    #draw("polybezier_reversed.svg", PolyBezier(reversed(DEFPOINTS), END_ANGLE, START_ANGLE))
+    #draw("bezier_circle.svg", bezier_circle())
     dxf_curve('polybezier.dxf', PolyBezier(DEFPOINTS, START_ANGLE, END_ANGLE))
+    #print_bezier_data('polybezier.csv', PolyBezier(DEFPOINTS, START_ANGLE, END_ANGLE))
 
 if __name__ == '__main__':
     main()

polybezier_sdl.py

             pygame.draw.circle(surface, CURVE_COLOR, point2int(p), 2)
 
     def marker_points(self):
-        for dist in range(int(self.bezier.length/30)):
-            yield self.bezier.get_point(dist*30)
+        t = self.bezier.length/40.
+        for i in range(41):
+            yield self.bezier.get_point(t*i)
 
     def draw_points(self, surface, points):
         for p in  points: