美文网首页
SceneKit框架中SCNScene场景手动导出STL格式模型

SceneKit框架中SCNScene场景手动导出STL格式模型

作者: GDXL2012 | 来源:发表于2022-09-06 17:08 被阅读0次

    一、SCNScene系统API导出STL格式模型会出现细节丢失问题

    - (BOOL)writeToURL:(NSURL *)url options:(nullable NSDictionary<NSString *, id> *)options delegate:(nullable id <SCNSceneExportDelegate>)delegate progressHandler:(nullable SCNSceneExportProgressHandler)progressHandler;
    

    二、STL文件格式:二进制格式

    起始有80个字节文件头用于存储文件名,接着4个字节表示三角面片的数量【数量一定要正确,否则导出的模型会显示异常】,每个三角面片占用固定的50个字节:法向矢量---3个4字节浮点数、三角面的三个顶点:每个顶点为3个4字节浮点数,接着是2个字节描述三角形基本属性【暂时没有用到】

    三、实现

    /// 保存多个模型到一个文件:异步操作
    /// @param nodes 模型节点列表
    /// @param filePath <#filePath description#>
    /// @param complete <#complete description#>
    +(void)xlSaveSCNNodes:(NSArray *)nodes
                   toFile:(NSString *)filePath
                 complete:(void(^)(NSError *error))complete{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // 写文件句柄
            NSFileHandle *fileHandler = [self xlFileHandleForWritingAtPath:filePath];
            
            // 写入文件头
            NSError *error = nil;
            NSString *name = filePath.lastPathComponent;
            name = [name stringByDeletingPathExtension];
            NSData *data = [name dataUsingEncoding:NSUTF8StringEncoding];
            
            if (data.length > 80) {
                data = [data subdataWithRange:NSMakeRange(0, 80)];
                [self xlFileHandler:fileHandler writeData:data error:&error];
            } else {
                [self xlFileHandler:fileHandler writeData:data error:&error];
                NSInteger datCcount = 80 - data.length;
                NSString *space = @"";
                for (int index = 0; index < datCcount; index ++) {
                    space = [space stringByAppendingString:@" "];
                }
                data = [space dataUsingEncoding:NSUTF8StringEncoding];
                [self xlFileHandler:fileHandler writeData:data error:&error];
            }
            
            // 写入面的个数
            NSInteger allCount = [self xlNormalsWithNodes:nodes];
            data = [NSData dataWithBytes:&allCount length:sizeof(int)];
            [self xlFileHandler:fileHandler writeData:data error:&error];
            
            // 保存法线与顶点信息
            for (SCNNode *node in nodes) {
                SCNGeometry *geometry = node.geometry;
                // 法线信息
                NSArray<SCNGeometrySource *> *normals = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticNormal];
                SCNGeometrySource *normal = normals[0];
                
                // 顶点信息
                NSArray<SCNGeometrySource *> *vertices = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];
                SCNGeometrySource *vertice = vertices[0];
                
                NSInteger normalCount = (normal.data.length - normal.dataOffset) / normal.dataStride / 3;
                NSMutableData *mData = [NSMutableData data];
                char a[2];
                NSData *vectorData = nil;
                SCNMatrix4 matrix4 = node.worldTransform;
                GLKMatrix4 glkMatrix4 = SCNMatrix4ToGLKMatrix4(matrix4);
                for (int i = 0; i < normalCount; i ++) {
                    NSData *data = [normal.data subdataWithRange:NSMakeRange(normal.dataOffset + i * normal.dataStride * 3, normal.dataStride)];
                    SCNVector3 nVector3 = [self xlVector3FromData:data];
                    vectorData = [NSData dataWithBytes:&(nVector3.x) length:4];
                    [mData appendData:vectorData];
                    vectorData = [NSData dataWithBytes:&(nVector3.y) length:4];
                    [mData appendData:vectorData];
                    vectorData = [NSData dataWithBytes:&(nVector3.z) length:4];
                    [mData appendData:vectorData];
                    for (int j = 0; j <= 2; j ++) {
                        data = [vertice.data subdataWithRange:NSMakeRange(i * normal.dataStride * 3 + j*normal.dataStride, normal.dataStride)];
                        nVector3 = [self xlVector3FromData:data];
                        GLKVector3 glkVector3 = SCNVector3ToGLKVector3(nVector3);
                        GLKVector3 nGLKVector3 = GLKMatrix4MultiplyAndProjectVector3(glkMatrix4, glkVector3);
    
                        nVector3 = SCNVector3FromGLKVector3(nGLKVector3);
                        vectorData = [NSData dataWithBytes:&(nVector3.x) length:4];
                        [mData appendData:vectorData];
                        vectorData = [NSData dataWithBytes:&(nVector3.y) length:4];
                        [mData appendData:vectorData];
                        vectorData = [NSData dataWithBytes:&(nVector3.z) length:4];
                        [mData appendData:vectorData];
                    }
                    [mData appendBytes:(char*)(a) length:sizeof(char) * 2];
                }
                [self xlFileHandler:fileHandler writeData:mData error:&error];
            }
            
            if (@available(iOS 13.0, *)) {
                NSError *closeError = nil;
                [fileHandler closeAndReturnError:&closeError];
                if (complete) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        complete(nil);
                    });
                }
            } else {
                [fileHandler closeFile];
                if (complete) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        complete(nil);
                    });
                }
            }
        });
    }
    
    // 计算面个数
    +(NSInteger)xlNormalsWithNodes:(NSArray *)nodes{
        NSInteger allCount = 0;
        for (SCNNode *node in nodes) {
            SCNGeometry *geometry = node.geometry;
            // 获取法线信息
            NSArray<SCNGeometrySource *> *normals = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticNormal];
            // 获取法线信息
            SCNGeometrySource *normal = normals[0];
            allCount = allCount + (normal.data.length - normal.dataOffset) / normal.dataStride / 3;
        }
        return allCount;
    }
    
    // 文件句柄
    +(NSFileHandle *)xlFileHandleForWritingAtPath:(NSString *)filePath{
        NSFileHandle *fileHandler = [NSFileHandle fileHandleForWritingAtPath:filePath];
        if(fileHandler == nil) {
             [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
            fileHandler = [NSFileHandle fileHandleForWritingAtPath:filePath];
        }
        return fileHandler;
    }
    
    // NSData转坐标
    +(SCNVector3)xlVector3FromData:(NSData *)data{
        float tmpValue[1];
        float v0, v1, v2;
        NSData *tmpData = [data subdataWithRange:NSMakeRange(0, 4)];
        [tmpData getBytes:tmpValue length:4];
        v0 = tmpValue[0];
        tmpData = [data subdataWithRange:NSMakeRange(4, 4)];
        [tmpData getBytes:tmpValue length:4];
        v1 = tmpValue[0];
        tmpData = [data subdataWithRange:NSMakeRange(4 * 2, 4)];
        [tmpData getBytes:tmpValue length:4];
        v2 = tmpValue[0];
        return SCNVector3Make(v0, v1, v2);
    }
    
    // SCN坐标系转OpenGL坐标系
    +(SCNVector3)xlSCNMatrix4:(SCNMatrix4)scnMatrix4 multiplyAndProjectVector3:(SCNVector3)scnVector3{
        GLKMatrix4 glkMatrix4 = SCNMatrix4ToGLKMatrix4(scnMatrix4);
        GLKVector3 glkVector3 = SCNVector3ToGLKVector3(scnVector3);
        GLKVector3 nGLKVector3 = GLKMatrix4MultiplyAndProjectVector3(glkMatrix4, glkVector3);
    
        return SCNVector3FromGLKVector3(nGLKVector3);
    }
    
    // 写文件
    +(void)xlFileHandler:(NSFileHandle *)fileHandler writeData:(NSData *)data error:(NSError **)error{
        if (@available(iOS 13.0, *)) {
            [fileHandler writeData:data error:error];
        } else {
            // Fallback on earlier versions
            [fileHandler writeData:data];
        }
    }
    

    四、其他

    ASCII编码格式的STL文件导出未实现,可以参考STL文件解析来处理ASCII编码格式的STL文件导出。

    函数中计算法向量数使用的normal.dataOffset,为数据的起始偏移值,不同的加载方式该值可能不同【暂时遇到过0、12】,normal.dataStride为单个顶点数据的字节长度。

    刚接触3D渲染框架,难免有错漏,欢迎评论留言指出。

    转载请注明出处。

    相关文章

      网友评论

          本文标题:SceneKit框架中SCNScene场景手动导出STL格式模型

          本文链接:https://www.haomeiwen.com/subject/wibgnrtx.html