2010-07-08 10 views
8

मेरे पास बिंदु एस, सी 1, सी 2, ई, और चौड़ाई का प्रतिनिधित्व करने वाला एक सकारात्मक संख्या डब्ल्यू के साथ एक बेजियर वक्र बी है। क्या दो बेजियर घटता बी 1, बी 2 के नियंत्रण बिंदुओं की त्वरित गणना करने का कोई तरीका है कि बी 1 और बी 2 के बीच की सामग्री बी द्वारा प्रतिनिधित्व किया गया विस्तृत मार्ग है?बेजियर पथ चौड़ाई

अधिक औपचारिक रूप से: गणना B1, B2, के लिए अच्छा बेज़ियर अनुमानों के नियंत्रण अंक जहां बी 1 = (/ 2 डब्ल्यू) {(x, y) + N (एक्स, वाई) | (एक्स, वाई) सी में
बी 2 = {(एक्स, वाई) - एन (एक्स, वाई)
(डब्ल्यू/2) | (एक्स, वाई) सी में,
जहां एन (एक्स, वाई) सामान्य सी (x, y) पर सामान्य है।

मैं अच्छा अनुमान लगाता हूं क्योंकि बी 1, बी 2 बहुपद घटता नहीं हो सकता है (मुझे यकीन नहीं है कि वे हैं)।

+0

आप सही हैं कि बी 1 और बी 2 वास्तव में बहुपद घटता नहीं हैं, और दुर्भाग्य से बेजियर वक्र के रूप में व्यक्त नहीं किया जा सकता है। मुझे निम्नलिखित संसाधन मूल्यवान मिला: http://pomax.github.io/bezierinfo/#offsetting –

+1

यह प्रश्न संबंधित प्रतीत होता है: http://stackoverflow.com/questions/4148831/how-to-offset-a-cubic-bezier -curve –

उत्तर

18

बेजियर वक्र का सटीक समानांतर गणितीय दृष्टिकोण से काफी बदसूरत है (इसे 10 वीं डिग्री बहुपदों की आवश्यकता होती है)।

क्या करना आसान है बेजियर के पॉलीगोनल सन्निकटन से एक चौड़ाई की गणना करना (जो आप बेजियर से लाइन सेगमेंट की गणना करते हैं और फिर वक्र के दोनों किनारों पर मानकों के साथ बिंदुओं को स्थानांतरित करते हैं)।

यह अच्छा परिणाम देता है अगर आपके मोटाई वक्रता की तुलना में बहुत बड़ा नहीं है ... एक "अब तक समानांतर" के बजाय अपने दम पर एक राक्षस है (और यह क्या एक समानांतर है की एक परिभाषा आसानी से मिल भी नहीं है एक खुले वक्र की जो हर किसी को खुश करेगी)।

एक बार जब आप दोनों पक्षों के लिए दो पोललाइन हैं तो आप उन पथों के लिए सबसे अच्छा अनुमानित बेजियर ढूंढ रहे हैं यदि आपको उस प्रतिनिधित्व की आवश्यकता है। एक बार फिर मुझे लगता है कि "सामान्य मामलों" (जो उचित रूप से पतली रेखाएं हैं) के लिए भी दोनों पक्षों में से प्रत्येक के लिए केवल एक ही बेजियर आर्क सटीक होना चाहिए (त्रुटि लाइन की मोटाई से बहुत छोटी होनी चाहिए)।

EDIT: वास्तव में एक बेजियर आर्क का उपयोग करने से मुझे काफी सामान्य मामलों के लिए भी अपेक्षा की जाती है। मैंने प्रत्येक पक्ष के लिए दो बेजियर आर्क का उपयोग करने की कोशिश की और नतीजा बेहतर है लेकिन अभी भी सही नहीं है। त्रुटि निश्चित रूप से रेखा की मोटाई की तुलना में बहुत छोटी है, जब तक कि रेखाएं बहुत मोटी न हों, यह एक उचित विकल्प हो सकता है। निम्नलिखित तस्वीर में यह एक मोटा बेजियर (प्रति-बिंदु मोटाई के साथ) दिखाया गया है, प्रत्येक पक्ष के लिए एक बेजियर आर्क का उपयोग करके अनुमान लगाया जाता है और प्रत्येक पक्ष के लिए दो बेजियर आर्क का उपयोग करके अनुमान लगाया जाता है।

enter image description here

संपादित 2: अनुरोध के रूप में मैं कोड मैं चित्रों प्राप्त करने के लिए इस्तेमाल किया जोड़ने; यह अजगर में है और केवल क्यूटी की आवश्यकता है। यह कोड दूसरों द्वारा पढ़ने के लिए नहीं था इसलिए मैंने कुछ चालें उपयोग कीं जो शायद मैं वास्तविक उत्पादन कोड में उपयोग नहीं करूँगा। एल्गोरिदम भी बहुत अक्षम है लेकिन मुझे गति की परवाह नहीं है (यह देखने के लिए एक शॉट प्रोग्राम होना था कि विचार काम करता है या नहीं)।

# 
# This code has been written during an ego-pumping session on 
# www.stackoverflow.com, while trying to reply to an interesting 
# question. Do whatever you want with it but don't blame me if 
# doesn't do what *you* think it should do or even if doesn't do 
# what *I* say it should do. 
# 
# Comments of course are welcome... 
# 
# Andrea "6502" Griffini 
# 
# Requirements: Qt and PyQt 
# 
import sys 
from PyQt4.Qt import * 

QW = QWidget 

bezlevels = 5 

def avg(a, b): 
    """Average of two (x, y) points""" 
    xa, ya = a 
    xb, yb = b 
    return ((xa + xb)*0.5, (ya + yb)*0.5) 

def bez3split(p0, p1, p2,p3): 
    """ 
    Given the control points of a bezier cubic arc computes the 
    control points of first and second half 
    """ 
    p01 = avg(p0, p1) 
    p12 = avg(p1, p2) 
    p23 = avg(p2, p3) 
    p012 = avg(p01, p12) 
    p123 = avg(p12, p23) 
    p= avg(p012, p123) 
    return [(p0, p01, p012, p0123), 
      (p0123, p123, p23, p3)] 

def bez3(p0, p1, p2, p3, levels=bezlevels): 
    """ 
    Builds a bezier cubic arc approximation using a fixed 
    number of half subdivisions. 
    """ 
    if levels <= 0: 
     return [p0, p3] 
    else: 
     (a0, a1, a2, a3), (b0, b1, b2, b3) = bez3split(p0, p1, p2, p3) 
     return (bez3(a0, a1, a2, a3, levels-1) + 
       bez3(b0, b1, b2, b3, levels-1)[1:]) 

def thickPath(pts, d): 
    """ 
    Given a polyline and a distance computes an approximation 
    of the two one-sided offset curves and returns it as two 
    polylines with the same number of vertices as input. 

    NOTE: Quick and dirty approach, just uses a "normal" for every 
      vertex computed as the perpendicular to the segment joining 
      the previous and next vertex. 
      No checks for self-intersections (those happens when the 
      distance is too big for the local curvature), and no check 
      for degenerate input (e.g. multiple points). 
    """ 
    l1 = [] 
    l2 = [] 
    for i in xrange(len(pts)): 
     i0 = max(0, i - 1)    # previous index 
     i1 = min(len(pts) - 1, i + 1) # next index 
     x, y = pts[i] 
     x0, y0 = pts[i0] 
     x1, y1 = pts[i1] 
     dx = x1 - x0 
     dy = y1 - y0 
     L = (dx**2 + dy**2) ** 0.5 
     nx = - d*dy/L 
     ny = d*dx/L 
     l1.append((x - nx, y - ny)) 
     l2.append((x + nx, y + ny)) 
    return l1, l2 

def dist2(x0, y0, x1, y1): 
    "Squared distance between two points" 
    return (x1 - x0)**2 + (y1 - y0)**2 

def dist(x0, y0, x1, y1): 
    "Distance between two points" 
    return ((x1 - x0)**2 + (y1 - y0)**2) ** 0.5 

def ibez(pts, levels=bezlevels): 
    """ 
    Inverse-bezier computation. 
    Given a list of points computes the control points of a 
    cubic bezier arc that approximates them. 
    """ 
    # 
    # NOTE: 
    # 
    # This is a very specific routine that only works 
    # if the input has been obtained from the computation 
    # of a bezier arc with "levels" levels of subdivisions 
    # because computes the distance as the maximum of the 
    # distances of *corresponding points*. 
    # Note that for "big" changes in the input from the 
    # original bezier I dont't think is even true that the 
    # best parameters for a curve-curve match would also 
    # minimize the maximum distance between corresponding 
    # points. For a more general input a more general 
    # path-path error estimation is needed. 
    # 
    # The minimizing algorithm is a step descent on the two 
    # middle control points starting with a step of about 
    # 1/10 of the lenght of the input to about 1/1000. 
    # It's slow and ugly but required no dependencies and 
    # is just a bunch of lines of code, so I used that. 
    # 
    # Note that there is a closed form solution for finding 
    # the best bezier approximation given starting and 
    # ending points and a list of intermediate parameter 
    # values and points, and this formula also could be 
    # used to implement a much faster and accurate 
    # inverse-bezier in the general case. 
    # If you care about the problem of inverse-bezier then 
    # I'm pretty sure there are way smarter methods around. 
    # 
    # The minimization used here is very specific, slow 
    # and not so accurate. It's not production-quality code. 
    # You have been warned. 
    # 

    # Start with a straight line bezier arc (surely not 
    # the best choice but this is just a toy). 
    x0, y0 = pts[0] 
    x3, y3 = pts[-1] 
    x1, y1 = (x0*3 + x3)/4.0, (y0*3 + y3)/4.0 
    x2, y2 = (x0 + x3*3)/4.0, (y0 + y3*3)/4.0 
    L = sum(dist(*(pts[i] + pts[i-1])) for i in xrange(len(pts) - 1)) 
    step = L/10 
    limit = step/100 

    # Function to minimize = max((a[i] - b[i])**2) 
    def err(x0, y0, x1, y1, x2, y2, x3, y3): 
     return max(dist2(*(x+p)) for x, p in zip(pts, bez3((x0, y0), (x1, y1), 
                  (x2, y2), (x3, y3), 
                  levels))) 
    while step > limit: 
     best = None 
     for dx1 in (-step, 0, step): 
      for dy1 in (-step, 0, step): 
       for dx2 in (-step, 0, step): 
        for dy2 in (-step, 0, step): 
         e = err(x0, y0, 
           x1+dx1, y1+dy1, 
           x2+dx2, y2+dy2, 
           x3, y3) 
         if best is None or e < best[0] * 0.9999: 
          best = e, dx1, dy1, dx2, dy2 
     e, dx1, dy1, dx2, dy2 = best 
     if (dx1, dy1, dx2, dy2) == (0, 0, 0, 0): 
      # We got to a minimum for this step => refine 
      step *= 0.5 
     else: 
      # We're still moving 
      x1 += dx1 
      y1 += dy1 
      x2 += dx2 
      y2 += dy2 

    return [(x0, y0), (x1, y1), (x2, y2), (x3, y3)] 

def poly(pts): 
    "Converts a list of (x, y) points to a QPolygonF)" 
    return QPolygonF(map(lambda p: QPointF(*p), pts)) 

class Viewer(QW): 
    def __init__(self, parent): 
     QW.__init__(self, parent) 
     self.pts = [(100, 100), (200, 100), (200, 200), (100, 200)] 
     self.tracking = None # Mouse dragging callback 
     self.ibez = 0   # Thickening algorithm selector 

    def sizeHint(self): 
     return QSize(900, 700) 

    def wheelEvent(self, e): 
     # Moving the wheel changes between 
     # - original polygonal thickening 
     # - single-arc thickening 
     # - double-arc thickening 
     self.ibez = (self.ibez + 1) % 3 
     self.update() 

    def paintEvent(self, e): 
     dc = QPainter(self) 
     dc.setRenderHints(QPainter.Antialiasing) 

     # First build the curve and the polygonal thickening 
     pts = bez3(*self.pts) 
     l1, l2 = thickPath(pts, 15) 

     # Apply inverse bezier computation if requested 
     if self.ibez == 1: 
      # Single arc 
      l1 = bez3(*ibez(l1)) 
      l2 = bez3(*ibez(l2)) 
     elif self.ibez == 2: 
      # Double arc 
      l1 = (bez3(*ibez(l1[:len(l1)/2+1], bezlevels-1)) + 
        bez3(*ibez(l1[len(l1)/2:], bezlevels-1))[1:]) 
      l2 = (bez3(*ibez(l2[:len(l2)/2+1], bezlevels-1)) + 
        bez3(*ibez(l2[len(l2)/2:], bezlevels-1))[1:]) 

     # Draw results 
     dc.setBrush(QBrush(QColor(0, 255, 0))) 
     dc.drawPolygon(poly(l1 + l2[::-1])) 
     dc.drawPolyline(poly(pts)) 
     dc.drawPolyline(poly(self.pts)) 

     # Draw control points 
     dc.setBrush(QBrush(QColor(255, 0, 0))) 
     dc.setPen(QPen(Qt.NoPen)) 
     for x, y in self.pts: 
      dc.drawEllipse(QRectF(x-3, y-3, 6, 6)) 

     # Display the algorithm that has been used 
     dc.setPen(QPen(QColor(0, 0, 0))) 
     dc.drawText(20, 20, 
        ["Polygonal", "Single-arc", "Double-arc"][self.ibez]) 

    def mousePressEvent(self, e): 
     # Find closest control point 
     i = min(range(len(self.pts)), 
       key=lambda i: (e.x() - self.pts[i][0])**2 + 
           (e.y() - self.pts[i][1])**2) 

     # Setup a callback for mouse dragging 
     self.tracking = lambda p: self.pts.__setitem__(i, p) 

    def mouseMoveEvent(self, e): 
     if self.tracking: 
      self.tracking((e.x(), e.y())) 
      self.update() 

    def mouseReleaseEvent(self, e): 
     self.tracking = None 

# Qt boilerplate 
class MyDialog(QDialog): 
    def __init__(self, parent): 
     QDialog.__init__(self, parent) 
     self.ws = Viewer(self) 
     L = QVBoxLayout(self) 
     L.addWidget(self.ws) 
     self.setModal(True) 
     self.show() 

app = QApplication([]) 
aa = MyDialog(None) 
aa.exec_() 
aa = None 
+0

कोई भी मौका है कि आप इस के लिए कोड साझा करते हैं? ऐसा लगता है कि ज़िप में केवल स्क्रीनशॉट हैं। – Quasimondo

+0

@Quasimondo आपका स्वागत है (लेकिन कोड के बारे में सावधान रहें ... विचार को देखने के लिए यह एक बार हैक था कुल बकवास नहीं था)। – 6502

+0

आह महान - धन्यवाद! – Quasimondo

संबंधित मुद्दे