Запрашиваете конкретную точку вдоль UIBezierCurve для анимации? - PullRequest
2 голосов
/ 17 января 2012

Я недавно закончил написание сценария для Maya, который экспортирует файл, содержащий данные для пути Безье (ряд точек xy и контрольных точек xy).

Этот путь Безье предназначен для представления 3D-рельса или пути, по которому мой персонаж будет двигаться с постоянной скоростью внутри приложения.

Я понимаю, как построить UIBezierCurve, но я не могу найти какую-либо достоверную информацию о том, возможно ли это / как получить положение x / y точки на кривой, учитывая расстояние, которое нужно пройти вдоль кривой.

Я нашел этот список на яблоке:

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

Но я не совсем понимаю, что возвращает эта функция и как я буду использовать ее для достижения цели, к которой я стремлюсь.

Любая помощь / совет будет принята с благодарностью,

Спасибо, - Адам Эйсфельд

Ответы [ 2 ]

1 голос
/ 19 января 2012

Хорошо, так что это будет длинный ответ. Вот что я сделал:

  1. Я запрограммировал MEL-скрипт, который позволяет рисовать кривую Безье в Maya, а затем - выбирая эту кривую - запускаю мой скрипт, который будет проходить через кривую, анализируя каждый безье-сегмент кривой, вычисляя длину каждого раздела. и положения точек кривой / контрольных точек. Как только все эти данные рассчитаны, он экспортирует все в файл .bezier, который имеет такую ​​структуру:

    Строка 1: количество отдельных кривых Безье, содержащихся во всем пути Безье. Строка 2: длина первой кривой Безье ... Линия X: длина последней кривой Безье

    X Положение первой контрольной точки первой точки кривой Y Положение первой контрольной точки первой точки кривой Z Положение первой контрольной точки первой точки кривой

    X Положение первой точки кривой Y Положение первой точки кривой Z Положение первой точки кривой

    X Положение второй контрольной точки первой точки кривой Y Положение второй контрольной точки первой точки кривой Z Положение второй контрольной точки первой точки кривой

    ...

    X Положение первой контрольной точки последней точки кривой Y Положение первой контрольной точки последней точки кривой Z Положение первой контрольной точки последней точки кривой

    X Положение последней точки кривой Y позиция последней точки кривой Z Положение последней точки кривой

    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

AEBezierVertex:

.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 с именем * file и создаст из него UIBezierPath, а также сохранит необходимую информацию о длине в AEBezierPath.

  1. Запросите AEBezierPath для позиции x / y в форме CGPoint, отправив ему значение расстояния для перемещения от начала пути, используя метод:

    - (CGPoint) positionFromDistance: (с плавающей точкой) fromDistance;

Этот метод сначала определит, к какому сегменту Безье относится это расстояние, используя длины каждого сегмента Безье, ранее извлеченные из файла .bezier. После этого метод будет использовать функцию bezierInterpolation, упомянутую в предыдущих статьях в этом вопросе SO, чтобы вычислить позицию x / y на пути Безье на этом расстоянии и вернуть ее в виде CGPoint.

Это не идеально, все еще есть некоторые заметные различия в пройденном расстоянии по длинным кривым Безье по сравнению с короткими затянутыми углами, но это, безусловно, гораздо менее заметно, чем вообще не использовать эту систему и вместо этого полагаться на процентное значение для перемещения по кривая Безье.

Я знаю, что код, безусловно, можно оптимизировать, это всего лишь первый прогон, чтобы все заработало, но я думаю, что его достаточно, чтобы опубликовать ответ на данный момент.

  • Адам Эйсфельд
0 голосов
/ 17 января 2012

Ссылка, на которую вы ссылаетесь, ссылается на то, что каждый сегмент кривой Безье отслеживает путь (x (t), y (t)), где t идет от 0 до 1.

IЯ не знаком с UIBezierCurve, но держу пари, что вы можете получить от него NSBezierPath, а оттуда вы можете перебирать сегменты вручную.Каждый сегмент является либо moveTo, lineTo, curveTo, либо close (эквивалентно lineTo, который является последним местоположением moveTo).Единственный нетривиальный тип пути - это CurveTo, о котором вы можете узнать подробнее здесь:

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

Если вы просто хотите анимировать движение по кривой, давая каждый сегмент фиксированное количество времени, это было бы просто;Вы можете просто пройтись по сегментам и внутри каждого, запустить t постепенно от 0 до 1 и подключиться к уравнению.

Хитрый бит будет двигаться с постоянной скоростью .Для этого вам нужно измерить длину каждого сегмента и разделить эту длину на части для каждого кадра.Вы можете узнать больше об этом в этом вопросе:

Эквидистантные точки на кривых Безье

Я некоторое время не работал с Какао, но у меня есть некоторый код здесь в Java, который вы, вероятно, можете портировать довольно легко (все это просто математика, которая одинакова на любом языке):

Output of sample program

...