iOS开发Marked Articles

iOS显示STL文件(3D效果)

2017-07-24  本文已影响32人  Felix_

由于项目需要,因此需要在项目中集成STL文件3D显示效果,最早解决方案是STL文件转DAE文件,使用SceneKit直接加载并显示出来,但是多了个步骤总是感觉很麻烦,后来想到使用OpenGL用来显示STL文件,而OpenGL那晦涩难懂的语法实在是浪费太多时间,所以写了个简单的项目(基于ScentKit)用来集成STL的文件显示。
首先对于STL文件的内容请参考STL文件解析,STL文件分为两种编码格式,一种为ASCII,一种为二进制,具体区别请自行查看。
这里以SceneKit为例,首先创建一个类继承自SCNView:

#import <Foundation/Foundation.h>
#import <SceneKit/SceneKit.h>
@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];

最终效果如图所示:

WX20170724-091648.png
上一篇下一篇

猜你喜欢

热点阅读