由于项目需要,因此需要在项目中集成STL文件3D显示效果,最早解决方案是STL文件转DAE文件,使用SceneKit直接加载并显示出来,但是多了个步骤总是感觉很麻烦,后来想到使用OpenGL用来显示STL文件,而OpenGL那晦涩难懂的语法实在是浪费太多时间,所以写了个简单的项目(基于ScentKit)用来集成STL的文件显示。
首先对于STL文件的内容请参考STL文件解析,STL文件分为两种编码格式,一种为ASCII,一种为二进制,具体区别请自行查看。
这里以SceneKit为例,首先创建一个类继承自SCNView:
#import
#import
@interface JassonSTLView : SCNView
- (instancetype)initWithSTLPath:(NSString *)path;
@end
实现方法:
首先实现初始化方法:
- (instancetype)initWithSTLPath:(NSString *)path
{
if (self = [super init])
{
//调用显示STL文件的方法
[self initSceneWithSTLPath:path];
}
return self;
}
使用传递的路径初始化场景:
这里场景的光照原来一直是自己打的,后来发现自己打的光怎么样都不好看,所以使用了默认的光源。
- (void)initSceneWithSTLPath:(NSString *)path
{
//整个场景的背景颜色
self.backgroundColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:1];
//使用场景的默认光照
self.autoenablesDefaultLighting = YES;
//允许手动旋转
self.allowsCameraControl = YES;
//创建一个空的场景
SCNScene *scene = [SCNScene scene];
self.scene = scene;
//载入节点
SCNNode *node = [self loadSTLWithPath:path];
//修改节点的默认位置为X(0)、Y(0)、Z(0)
node.position = SCNVector3Make(0, 0, 0);
//添加节点到根节点
[scene.rootNode addChildNode:node];
}
在这里加载STL文件(顺便区分STL文件的编码方式)
- (SCNNode *)loadSTLWithPath:(NSString *)path
{
SCNNode *node = nil;
NSData *data = [NSData dataWithContentsOfFile:path];
if (data.length > 80)
{
//为什么取前80个字节请查看前面的STL文件解析
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 80)];
NSString *headerStr = [[NSString alloc] initWithData:headerData encoding:NSASCIIStringEncoding];
if ([headerStr containsString:@"solid"])
{
//ASCII编码的STL文件
node = [self loadASCIISTLWithData:data];
}
else
{
//载入二进制的STL文件
node = [self loadBinarySTLWithData:[data subdataWithRange:NSMakeRange(84, data.length - 84)]];
}
}
return node;
}
加载二进制的STL文件:
- (SCNNode *)loadBinarySTLWithData:(NSData *)data
{
//顶点信息
NSMutableData *vertices = [NSMutableData data];
//法线信息
NSMutableData *normals = [NSMutableData data];
//可以暂时理解为编号信息
NSMutableData *elements = [NSMutableData data];
if (data.length % 50 != 0)
{
NSLog(@"STL(二进制)文件错误");
return nil;
}
NSInteger allCount = data.length/50;
//为什么要这样解析,还是请查看文章开始提到的STL文件解析
for (int i = 0; i < allCount; i ++)
{
for (int j = 1; j <= 3; j ++)
{
[normals appendData:[data subdataWithRange:NSMakeRange(i * 50, 12)]];
[vertices appendData:[data subdataWithRange:NSMakeRange(i * 50 + j*12, 12)]];
}
int element[3] = {(int)i * 3,(int)i*3 + 1,(int)i*3 + 2};
[elements appendBytes:&element[0] length:sizeof(element)];
}
SCNGeometrySource *verticesSource = [SCNGeometrySource geometrySourceWithData:vertices semantic:SCNGeometrySourceSemanticVertex vectorCount:allCount*3 floatComponents:YES componentsPerVector:3 bytesPerComponent:sizeof(float) dataOffset:0 dataStride:sizeof(SCNVector3)];
SCNGeometrySource *normalsSource = [SCNGeometrySource geometrySourceWithData:normals semantic:SCNGeometrySourceSemanticNormal vectorCount:allCount*3 floatComponents:YES componentsPerVector:3 bytesPerComponent:sizeof(float) dataOffset:0 dataStride:sizeof(SCNVector3)];
SCNGeometryElement *geoMetryElement = [SCNGeometryElement geometryElementWithData:elements primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:allCount bytesPerIndex:sizeof(int)];
SCNGeometry *geometry = [SCNGeometry geometryWithSources:@[verticesSource,normalsSource] elements:@[geoMetryElement]];
//纹理的颜色,也就是3D模型的颜色
geometry.firstMaterial.diffuse.contents = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1];
//创建节点模型
SCNNode *node = [SCNNode nodeWithGeometry:geometry];
return node;
}
加载ASCII编码的STL文件:
- (SCNNode *)loadASCIISTLWithData:(NSData *)data
{
//顶点信息
NSMutableData *vertices = [NSMutableData data];
//法线信息
NSMutableData *normals = [NSMutableData data];
//编号信息
NSMutableData *elements = [NSMutableData data];
NSString *asciiStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSArray *asciiStrArr = [asciiStr componentsSeparatedByString:@"\n"];
int elementCount = 0;
for (int i = 0; i < asciiStrArr.count; i ++)
{
NSString *currentStr = asciiStrArr[i];
if ([currentStr containsString:@"facet"])
{
if ([currentStr containsString:@"normal"])
{
for (int j = 1; j <= 3; j++)
{
NSArray *subNormal = [currentStr componentsSeparatedByString:@" "];
SCNVector3 normal = SCNVector3Make([subNormal[subNormal.count - 3] floatValue], [subNormal[subNormal.count - 2] floatValue], [subNormal[subNormal.count - 1] floatValue]);
[normals appendBytes:&normal length:sizeof(normal)];
NSArray *subVertice = [asciiStrArr[i+j+1] componentsSeparatedByString:@" "];
SCNVector3 vertice = SCNVector3Make([subVertice[subVertice.count - 3] floatValue], [subVertice[subVertice.count - 2] floatValue], [subVertice[subVertice.count - 1] floatValue]);
[vertices appendBytes:&vertice length:sizeof(vertice)];
}
int element[3] = {elementCount * 3,elementCount * 3 + 1,elementCount * 3 + 2};
elementCount++;
[elements appendBytes:&element length:sizeof(element)];
i = i+6;
}
}
}
SCNGeometrySource *verticesSource = [SCNGeometrySource geometrySourceWithData:vertices semantic:SCNGeometrySourceSemanticVertex vectorCount:(elementCount-1)*3 floatComponents:YES componentsPerVector:3 bytesPerComponent:sizeof(float) dataOffset:0 dataStride:sizeof(SCNVector3)];
SCNGeometrySource *normalsSource = [SCNGeometrySource geometrySourceWithData:normals semantic:SCNGeometrySourceSemanticNormal vectorCount:(elementCount-1)*3 floatComponents:YES componentsPerVector:3 bytesPerComponent:sizeof(float) dataOffset:0 dataStride:sizeof(SCNVector3)];
SCNGeometryElement *geoMetryElement = [SCNGeometryElement geometryElementWithData:elements primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:elementCount - 1 bytesPerIndex:sizeof(int)];
SCNGeometry *geometry = [SCNGeometry geometryWithSources:@[verticesSource,normalsSource] elements:@[geoMetryElement]];
//在节点处添加模型
SCNNode *node = [SCNNode nodeWithGeometry:geometry];
return node;
}
好了,那么既然解析方法和显示方法都已经创建完毕,那么接下来显示STL文件就简单很多了:
JassonSTLView *stlView = [[JassonSTLView alloc] initWithSTLPath:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"stl"]];
//设置场景的大小
stlView.frame = CGRectMake(0, 50, self.view.bounds.size.width, 350);
[self.view addSubview:stlView];
最终效果如图所示: