2012-01-16 60 views
2

我最近完成了爲Maya輸出一個包含Bezier路徑(一系列xy點和xy控制點)數據的文件的腳本。查詢沿着UIBezierCurve的動畫的特定點?

這個bezier路徑是爲了表示我的角色在應用程序內部以恆定速度沿着的3D「軌道」或路徑。

我明白如何構建一個UIBezierCurve,但是我無法找到任何有關如果可能/如何獲得曲線上一個點的x/y位置的任何可靠信息,給定沿曲線行進的距離。

我發現蘋果的這份名單:

http://lists.apple.com/archives/cocoa-dev/2002/Feb/msg01806.html

但我不太明白什麼函數返回,我怎麼會用它來完成什麼林瞄準。

任何幫助/建議將不勝感激,

感謝, - 亞當Eisfeld

回答

1

好吧,所以這會是一個漫長的答案。這裏是香港專業教育學院做了什麼:

  1. 伊夫編程的MEL腳本,可以讓你在Maya中繪製貝塞爾曲線,然後 - 選擇該曲線 - 運行我的腳本,它會經過曲線分析曲線的每個貝塞爾節計算每個部分的長度和曲線點/控制點的位置。一旦它計算了所有這些數據,它就會將所有數據導出到一個。其構造是這樣貝塞爾文件:

    線路1:包含在整個貝塞爾路徑個人貝塞爾曲線數量 第2行:第一貝塞爾曲線 的長度... 行X:最後貝塞爾曲線的長度

    X中的第一曲線點 第一曲線點的第一控制點的第一曲線點 Z位置的第一控制點的Y位置的第一控制點的位置

    X中的第一曲線的位置點號 Y位置的f第一曲線點的開始步驟曲線點 Z位置

    X中的第一曲線點 的第二控制點的第一曲線點 Z位置的第二控制點的Y位置的第二控制點的位置第一曲線點

    ...

    最後一個曲線點 最後一個曲線點的第一個控制點的Y位置的第一個控制點的X位置 最後的一個控制點的Z位置曲線點

    X的最後一個曲線點 最後曲線點的最後一個曲線點 Z位置的Y位置的位置

    X的最後一個曲線點的第二控制點的位置 第二個控制點的Y位置最後一個曲線點

所以對於這組類的第二個控制點的最後一個曲線點 Z位置的工作,你就需要構建一個這樣的文件。

這裏有三類伊夫然後編程處理.bezier文件:

AEBezierPath:

.h文件中:

#import <Foundation/Foundation.h> 
#import "AEBezierVertex.h" 
#import "AEBezierLine.h" 

@interface AEBezierPath : NSObject 
{ 
    NSMutableArray *vertices; 
    NSMutableArray *lines; 
    UIBezierPath *path; 
} 

@property (strong) NSMutableArray *vertices; 
@property (strong) NSMutableArray *lines; 
@property (strong) UIBezierPath *path; 

-(id) initFromFile: (NSString*) file; 
-(CGPoint) positionFromDistance: (float) fromDistance; 

@end 

.m文件:

#import "AEBezierPath.h" 

CGFloat bezierInterpolation(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) { 
    // see also below for another way to do this, that follows the 'coefficients' 
    // idea, and is a little clearer 
    CGFloat t2 = t * t; 
    CGFloat t3 = t2 * t; 
    return a + (-a * 3 + t * (3 * a - a * t)) * t 
    + (3 * b + t * (-6 * b + b * 3 * t)) * t 
    + (c * 3 - c * 3 * t) * t2 
    + d * t3; 
} 

@implementation AEBezierPath 
@synthesize vertices; 
@synthesize lines; 
@synthesize path; 

-(id) initFromFile: (NSString*) file 
{ 
    self = [super init]; 
    if (self) { 

     //Init file objects for reading 
     NSError *fileError; 
     NSStringEncoding *encoding; 

     vertices = [[NSMutableArray alloc] init]; 
     lines = [[NSMutableArray alloc] init]; 
     path = [[UIBezierPath alloc] init]; 

     //Load the specified file's contents into an NSString 
     NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"testcurve" ofType:@"bezier"] usedEncoding:&encoding error:&fileError]; 
     NSScanner *scanner = [[NSScanner alloc] initWithString:fileData]; 

     if(fileData == nil) 
     { 
      NSLog(@"Error reading bezier path file"); 
     } 
     else 
     { 
      float x; 
      float y; 
      float cx; 
      float cy; 
      float cx2; 
      float cy2; 
      float temp; 

      CGPoint readPoint; 
      CGPoint readControlIn; 
      CGPoint readControlOut; 

      int curRead = 0; 
      int totalSegments = 0; 
      float length; 

      [scanner scanInt:&totalSegments]; 

      for (int s = 0; s < totalSegments; s++) { 
       [scanner scanFloat:&length]; 
       AEBezierLine *newLine = [[AEBezierLine alloc] initWithLength:length]; 

       [lines addObject:newLine]; 
      } 

      AEBezierVertex *vertex; 

      while ([scanner isAtEnd] == 0) { 

       if (curRead == 0) { 
        [scanner scanFloat:&x]; 
        [scanner scanFloat:&temp]; 
        [scanner scanFloat:&y]; 


        [scanner scanFloat:&cx2]; 
        [scanner scanFloat:&temp]; 
        [scanner scanFloat:&cy2]; 

        cx = x; 
        cy = y; 
       } 

       else{ 

        [scanner scanFloat:&cx]; 
        [scanner scanFloat:&temp]; 
        [scanner scanFloat:&cy]; 

        [scanner scanFloat:&x]; 
        [scanner scanFloat:&temp]; 
        [scanner scanFloat:&y]; 

        if ([scanner isAtEnd] == 0) { 
         [scanner scanFloat:&cx2]; 
         [scanner scanFloat:&temp]; 
         [scanner scanFloat:&cy2]; 
        }else 
        { 
         cx = x; 
         cy = y; 
        } 
       } 

       readPoint = CGPointMake(x, y); 
       readControlIn = CGPointMake(cx, cy); 
       readControlOut = CGPointMake(cx2, cy2); 

       vertex = [[AEBezierVertex alloc] initWithControl:readPoint In:readControlIn Out:readControlOut]; 

       [vertices addObject:vertex]; 

       curRead ++; 

      } 

      for (int c = 0; c < [vertices count]-1; c++) { 

       //Init CGPoints for single bezier curve segment 
       CGPoint p1, p2, p3, p4; 

       //Store starting bezier point and control point 
       AEBezierVertex *b1 = [vertices objectAtIndex:c]; 
       p1 = b1.control; 
       p2 = b1.controlOut;  

       //Store ending bezier point and control point 
       AEBezierVertex *b2 = [vertices objectAtIndex:c+1]; 
       p3 = b2.controlIn; 
       p4 = b2.control; 

       if (c == 0) { 
        [path moveToPoint:p1]; 
       } 
       else 
       { 
        [path addCurveToPoint:p4 controlPoint1:p2 controlPoint2:p3]; 
       } 
      } 
     } 
    } 
    return self; 
} 

-(CGPoint) positionFromDistance: (float) fromDistance 
{ 
    CGPoint position; 


    AEBezierLine *line; 
    float runningLength; 
    int seg = 0; 

    for (int c = 0; c < [lines count]; c++) { 
     seg = c; 
     line = [lines objectAtIndex:c]; 
     runningLength += line.length; 
     if (runningLength > fromDistance) { 
      break; 
     } 
    } 

    CGPoint p1, p2, p3, p4; 

    AEBezierVertex *vert1 = [vertices objectAtIndex:seg]; 
    p1 = vert1.control; 
    p2 = vert1.controlOut;  

    //Store ending bezier point and control point 
    AEBezierVertex *vert2 = [vertices objectAtIndex:seg+1]; 
    p3 = vert2.controlIn; 
    p4 = vert2.control; 

    float travelDist; 
    travelDist = fromDistance; 

    travelDist = runningLength - travelDist; 
    travelDist = line.length - travelDist; 

    float t = travelDist/line.length; 

    //Create a new point to represent this position 
    position = CGPointMake(bezierInterpolation(t, p1.x, p2.x, p3.x, p4.x), 
           bezierInterpolation(t, p1.y, p2.y, p3.y, p4.y));  

    return position; 
} 

@end 

AEBe zierVertex:

h文件:

#import <Foundation/Foundation.h> 

@interface AEBezierVertex : NSObject 
{ 
    CGPoint controlIn; 
    CGPoint controlOut; 
    CGPoint control; 
} 
@property CGPoint controlIn; 
@property CGPoint controlOut; 
@property CGPoint control; 

-(id) initWithControl: (CGPoint) setControl In: (CGPoint) setIn Out: (CGPoint) setOut; 

@end 

.m文件:

#import "AEBezierVertex.h" 

@implementation AEBezierVertex 
@synthesize controlIn; 
@synthesize controlOut; 
@synthesize control; 

-(id) initWithControl: (CGPoint) setControl In: (CGPoint) setIn Out: (CGPoint) setOut 
{ 
    self = [super init]; 
    if (self) { 
     //Init 
     control = setControl; 
     controlIn = setIn; 
     controlOut = setOut; 
    } 
    return self; 
} 

@end 

AEBezierLine:

h文件:

#import <Foundation/Foundation.h> 

@interface AEBezierLine : NSObject 
{ 
    float length; 
} 
@property float length; 

-(id) initWithLength: (float) setLength; 

@end 

。M檔:

#import "AEBezierLine.h" 

@implementation AEBezierLine 
@synthesize length; 

-(id) initWithLength: (float) setLength 
{ 
    self = [super init]; 
    if (self) { 
     //Init 
     length = setLength; 
    } 
    return self; 
} 

@end 

如何使用:

  1. 確保你已經創建了一個.bezier文件相適應我上面所示的結構,它在你的應用程序的包。

  2. 通過實例化一個新的AEBezierPath實例:

    - (ID)initFromFile:(的NSString *)文件;

這將從名爲.bezier文件*文件中讀取的所有數據,並從中構造UIBezierPath,以及儲存必要的長度信息到AEBezierPath。

  1. 查詢的AEBezierPath用於在CGPoint形式的X/Y位置,通過發送它的距離值,以從所述路徑的開始行進,使用方法:

    - (CGPoint) positionFromDistance:(float)fromDistance;

此方法將首先確定該距離通過使用先前從.bezier文件中檢索每一個貝塞爾曲線段的長度位於其貝塞爾曲線段。在此之後,該方法將使用此SO問題的前一篇文章中提到的bezierInterpolation函數計算此距離處貝塞爾路徑上的x/y位置,並將其作爲CGPoint返回。

雖然它並不完美,但長距離貝塞爾曲線與較短的緊角之間的距離仍存在一些明顯的差異,但它肯定比根本不使用該系統更明顯,而是依靠百分比值來旅行沿着貝塞爾曲線。

我知道代碼當然可以優化,這只是第一次運行,以便讓所有的工作都能正常工作,但我認爲它足夠好,可以作爲現在的答案發布。

  • 亞當Eisfeld
0

你舉被影射鏈接是什麼,一個貝塞爾曲線的每個段描繪出的路徑(X( t),y(t)),其中t從0變爲1.

我不熟悉UIBezierCurve,但我敢打賭你可以從它得到NSBezierPath,從那裏你可以遍歷細分手動。每個片段可以是moveTo,lineTo,curveTo或close(相當於lineTo最後一個moveTo位置)。唯一的非平凡的路徑類型是curveTo,你可以在此處詳細瞭解:

http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves

如果你只是想動畫沿着曲線給每個的固定時間內移動,這將是簡單;你可以遍歷片段,並在每個片段中,逐漸從0運行到1並插入等式。

棘手的位將在恆定的速度移動。爲此,您需要實際測量每個段的長度,並將該長度分成每個幀的部分。您可以在這個問題上閱讀更多有關:

Equidistant points across Bezier curves

我已經有一段時間沒有與可可的工作,但我有一些代碼here在Java中,你也許可以端口很容易(這一切都只是數學,這是任何語言相同):

Output of sample program

+0

NSBezierPath是了AppKit的一部分,因此在可可觸摸不可用。你的意思是CGPath? – 2012-01-17 03:32:26

+0

是的,它看起來像在UIKit中,你將不得不使用'CGPath',唯一的方法是通過回調和'CGPathApply'來遍歷它。對於那個很抱歉;我的可可經驗來自多年前在桌面上。 – 2012-01-17 12:40:04

+0

謝謝。經過大量的實驗和閱讀後,我想我終於想出了一個MEL腳本,用於將一個完整的貝塞爾曲線導出到文本文件中的MEL腳本和一個可以在該文件中讀取並返回x/y的Objective C++自定義類基於距離在貝塞爾路徑上的位置。我把所有東西都包起來,並在今晚或明天發佈我的發現。 – 2012-01-18 05:51:26