Хорошо, так что это будет длинный ответ. Вот что я сделал:
Я запрограммировал 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
Как это работает:
Убедитесь, что вы создали файл .bezier, соответствующий структуре, которую я показал выше, и поместили его в комплект вашего приложения.
Создание нового экземпляра AEBezierPath с помощью:
- (id) initFromFile: (NSString *) файл;
Это прочитает все данные из файла .bezier с именем * file и создаст из него UIBezierPath, а также сохранит необходимую информацию о длине в AEBezierPath.
Запросите AEBezierPath для позиции x / y в форме CGPoint, отправив ему значение расстояния для перемещения от начала пути, используя метод:
- (CGPoint) positionFromDistance: (с плавающей точкой) fromDistance;
Этот метод сначала определит, к какому сегменту Безье относится это расстояние, используя длины каждого сегмента Безье, ранее извлеченные из файла .bezier. После этого метод будет использовать функцию bezierInterpolation, упомянутую в предыдущих статьях в этом вопросе SO, чтобы вычислить позицию x / y на пути Безье на этом расстоянии и вернуть ее в виде CGPoint.
Это не идеально, все еще есть некоторые заметные различия в пройденном расстоянии по длинным кривым Безье по сравнению с короткими затянутыми углами, но это, безусловно, гораздо менее заметно, чем вообще не использовать эту систему и вместо этого полагаться на процентное значение для перемещения по кривая Безье.
Я знаю, что код, безусловно, можно оптимизировать, это всего лишь первый прогон, чтобы все заработало, но я думаю, что его достаточно, чтобы опубликовать ответ на данный момент.