好吧,这将是一个很长的答案。这是我所做的:
我编写了一个 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 文件而编写的三个类:
AEBezier路径:
.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
AEBezier线:
.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*) 文件;
这将从名为 *file 的 .bezier 文件中读取所有数据,并从中构造一个 UIBezierPath,并将必要的长度信息存储到 AEBezierPath 中。
查询 AEBezierPath 以获取 CGPoint 形式的 x/y 位置,方法是向其发送从路径起点行进的距离值,使用以下方法:
-(CGPoint) positionFromDistance: (float) fromDistance;
此方法将首先通过使用先前从 .bezier 文件中检索到的每个贝塞尔线段的长度来确定该距离所在的贝塞尔线段。在此之后,该方法将使用此 SO Question 的前几篇文章中提到的 bezierInterpolation 函数来计算此距离处贝塞尔路径上的 x/y 位置,并将其作为 CGPoint 返回。
它并不完美,在长贝塞尔曲线和短弯角上行驶的距离仍然存在一些明显的差异,但与完全不使用这个系统而是依靠百分比值沿着贝塞尔曲线行驶相比,它肯定远没有那么明显.
我知道代码当然可以优化,这只是让一切正常工作的第一次运行,但我认为它足以作为现在的答案发布。