2

最近、ベジエ パス (一連の xy ポイントと xy コントロール ポイント) のデータを含むファイルをエクスポートする Maya 用のスクリプトを書き終えました。

このベジェ パスは、アプリ内でキャラクターが一定の速度で移動する 3D の「レール」またはパスを表すためのものです。

UIBezierCurve の作成方法は理解していますが、曲線に沿って移動する距離が与えられた場合に、曲線上の点の x / y 位置を取得する方法が可能かどうかについての確実な情報を見つけることができないようです。

Appleでこのリストを見つけました:

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

しかし、その関数が何を返しているのか、それを使用して目的を達成する方法をよく理解していません。

ヘルプ/アドバイスをいただければ幸いです。

ありがとう - アダム・アイスフェルド

4

2 に答える 2

1

わかりましたので、これは長い答えになります。これが私がやったことです:

  1. Maya 内でベジェ曲線を描画できるようにする MEL スクリプトをプログラムし、その曲線を選択して、曲線を通過するスクリプトを実行し、曲線の各ベジェ セクションを分析して、各セクションの長さと曲線の位置を計算します。ポイント/コントロールポイント。このデータがすべて計算されると、次のような構造の .bezier ファイルにすべてがエクスポートされます。

    行 1: ベジェ パス全体に含まれる個々のベジェ曲線の数 行 2: 最初のベジェ曲線の長さ ... 行 X: 最後のベジェ曲線の長さ

    X 最初のカーブ ポイントの最初のコントロール ポイントの位置 Y 最初のカーブ ポイントの最初のコントロール ポイントの位置 Z 最初のカーブ ポイントの最初のコントロール ポイントの位置

    X 最初のカーブ ポイントの位置 Y 最初のカーブ ポイントの位置 Z 最初のカーブ ポイントの位置

    X 最初の曲線点の 2 番目の制御点の位置 Y 最初の曲線点の 2 番目の制御点の位置 Z 最初の曲線点の 2 番目の制御点の位置

    ...

    X 最後のカーブ ポイントの最初のコントロール ポイントの位置 Y 最後のカーブ ポイントの最初のコントロール ポイントの位置 Z 最後のカーブ ポイントの最初のコントロール ポイントの位置

    X 最後のカーブ ポイントの位置 Y 最後のカーブ ポイントの位置 Z 最後のカーブ ポイントの位置

    X 最後のカーブ ポイントの 2 番目のコントロール ポイントの位置 Y 最後のカーブ ポイントの 2 番目のコントロール ポイントの位置 Z 最後のカーブ ポイントの 2 番目のコントロール ポイントの位置

したがって、この一連のクラスを機能させるには、そのような構造のファイルが必要です。

次に、.bezier ファイルを処理するようにプログラムした 3 つのクラスを示します。

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

AEBezier頂点:

.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*) ファイル;

これにより、*file という名前の .bezier ファイルからすべてのデータが読み取られ、そこから UIBezierPath が構築され、必要な長さ情報が AEBezierPath に格納されます。

  1. 次のメソッドを使用して、パスの始点から移動する距離値を送信することにより、AEBezierPath に CGPoint の形式で x/y 位置を照会します。

    -(CGPoint) positionFromDistance: (float) fromDistance;

このメソッドは、まず、.bezier ファイルから以前に取得した各ベジェ セグメントの長さを使用して、距離が存在するベジェ セグメントを特定します。この後、メソッドは、この SO Question の以前の投稿で説明した bezierInterpolation 関数を使用して、この距離でのベジエ パス上の x/y 位置を計算し、CGPoint として返します。

完璧ではありません。長いベジェ曲線と短いタイトなコーナーを移動する距離にはまだいくつかの顕著な違いがありますが、このシステムをまったく使用せず、代わりにパーセンテージ値に依存してベジェ曲線に沿って移動するよりもはるかに目立たないことは確かです.

コードを確実に最適化できることはわかっています。これはすべてを機能させるための最初の実行にすぎませんが、今のところ回答として投稿するのに十分だと思います。

  • アダム・アイスフェルド
于 2012-01-18T22:10:04.850 に答える
0

あなたが引用したリンクが暗示しているのは、ベジエ曲線の各セグメントがパス (x(t), y(t)) をトレースすることです。ここで、t は 0 から 1 になります。

私は に慣れていませんが、そこからUIBezierCurveを取得でき、NSBezierPathそこからセグメントを手動で反復処理できると思います。各セグメントは、moveTo、lineTo、curveTo、または close (最後の moveTo 位置の lineTo に相当) のいずれかです。唯一の重要なパス タイプは、curveTo です。詳細については、次を参照してください。

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

各セグメントに一定の時間を与えて曲線に沿って移動するアニメーションを作成したいだけであれば、それは簡単です。セグメントを反復処理し、各セグメント内で t を 0 から 1 まで徐々に実行し、方程式に差し込むことができます。

トリッキーな部分は、一定の速度で移動することです。そのためには、各セグメントの長さを実際に測定し、その長さを各フレームのパーツに分割する必要があります。この質問でそれについてもっと読むことができます:

ベジエ曲線上の等距離点

私はしばらく Cocoa を扱っていませんが、おそらくかなり簡単に移植できる Java のコードがいくつかあります (それはすべて単なる数学であり、どの言語でも同じです)

サンプルプログラムの出力

于 2012-01-16T23:34:50.910 に答える