cocos2d源码分析(五):加载obj文件

obj文件是一种常用的3D模型文件,本文分析cocos2d解析文件的过程。obj文件也是普通的文本文件,以cocos2d的demo中的boss1.obj文件为例,看看它长啥样:

# "#"后面是注释
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 15.05.2014 08:13:14

#材质库
mtllib boo1.mtl

#
# object Object
#

#顶点坐标,一行表示一个顶点(x, y, z)
v  0.5491 -0.2313 7.4010
v  0.5491 -0.3996 7.0495
v  0.5491 -0.3669 7.0495
#省略...
# 445 vertices

#顶点法线,一行表示一个顶点法线(x, y, z)
vn 0.6502 0.2638 0.7125
vn 1.0000 0.0000 -0.0000
vn 0.7327 0.6756 -0.0817
#省略...
# 331 vertex normals

#纹理坐标,一行表示一个纹理坐标(s,t),第三个数恒为0
vt 0.3471 0.0763 0.0000
vt 0.4009 0.0969 0.0000
vt 0.4005 0.0921 0.0000
#..
# 409 texture coords

#g是组名称
g Object

#usemtl是材质名称
usemtl _bossdefault

#s是光滑组
s 1

#f是面的索引,每一行代表一个面,如a/b/c,a是顶点索引,b是法线索引,c是纹理坐标索引
f 1/1/1 2/2/2 3/3/2 4/4/3 
f 1/1/1 4/4/3 5/5/4 
f 6/3/5 7/2/5 8/1/6 9/4/7 
#...
# 307 polygons - 102 triangles

如在Sprite3D中要加载一个obj文件,代码如下:

bool Sprite3D::loadFromFile(const std::string& path, NodeDatas* nodedatas, MeshDatas* meshdatas,  MaterialDatas* materialdatas)
{
    std::string fullPath = FileUtils::getInstance()->fullPathForFilename(path);
    
    std::string ext = FileUtils::getInstance()->getFileExtension(path);
    if (ext == ".obj")
    {
        return Bundle3D::loadObj(*meshdatas, *materialdatas, *nodedatas, fullPath);
    }
    else if (ext == ".c3b" || ext == ".c3t")
    {
        //load from .c3b or .c3t
        auto bundle = Bundle3D::createBundle();
        if (!bundle->load(fullPath))
        {
            Bundle3D::destroyBundle(bundle);
            return false;
        }
        
        auto ret = bundle->loadMeshDatas(*meshdatas)
            && bundle->loadMaterials(*materialdatas) && bundle->loadNodes(*nodedatas);
        Bundle3D::destroyBundle(bundle);
        
        return ret;
    }
    return false;
}

由代码可见,最终的obj文件的信息会被加载到NodeDatas,MeshDatas和MaterialDatas三个结构体中,通过调用Bundle3D的静态函数loadObj加载,具体调用堆栈及数据流如下:


5994163-c23e502f882c4e21.png
obj加载.png

解释下这个图,数据流从下往上,首先解析obj文件,把顶点数据(v开头),法线坐标(vn开头),纹理坐标(vt开头),面索引(f开头),还有其他信息(本次分析忽略了materials材质加载)存储到数组v,数组vn,数组vt和数组faceGroup中,然后再把这些数据整理后存到数组shapes和materials,最后再整理数据到meshdatas,materialdatas和nodedatas。

先看tinyobj::LoadObj的代码:

    std::string LoadObj(std::vector<shape_t> &shapes,
                        std::vector<material_t> &materials, // [output]
                        const char *filename, const char *mtl_basepath) {
        
        shapes.clear();
        
        std::stringstream err;
        
        std::istringstream ifs(cocos2d::FileUtils::getInstance()->getStringFromFile(filename));
        if (!ifs) {
            err << "Cannot open file [" << filename << "]" << std::endl;
            return err.str();
        }
        
        std::string basePath;
        if (mtl_basepath) {
            basePath = mtl_basepath;
        }
        MaterialFileReader matFileReader(basePath);
        
        return LoadObj(shapes, materials, ifs, matFileReader);
    }
    
    std::string LoadObj(std::vector<shape_t> &shapes,
                        std::vector<material_t> &materials, // [output]
                        std::istream &inStream, MaterialReader &readMatFn) {
        std::stringstream err;
        
        std::vector<float> v;   //存储顶点
        std::vector<float> vn;  //存储顶点法线
        std::vector<float> vt;  //存储纹理坐标
        std::vector<std::vector<vertex_index> > faceGroup; //存储每个面的索引
        std::string name;
        
        // material
        std::map<std::string, int> material_map;
        std::map<vertex_index, unsigned int> vertexCache;
        int material = -1;
        
        shape_t shape;
        
        int maxchars = 8192;             // Alloc enough size.
        std::vector<char> buf(maxchars); // Alloc enough size.
        while (inStream.peek() != -1) {
            inStream.getline(&buf[0], maxchars);
            
            std::string linebuf(&buf[0]);
            
            // Trim newline '\r\n' or '\n'
            if (linebuf.size() > 0) {
                if (linebuf[linebuf.size() - 1] == '\n')
                    linebuf.erase(linebuf.size() - 1);
            }
            if (linebuf.size() > 0) {
                if (linebuf[linebuf.size() - 1] == '\r')
                    linebuf.erase(linebuf.size() - 1);
            }
            
            // Skip if empty line.
            if (linebuf.empty()) {
                continue;
            }
            
            // Skip leading space.
            const char *token = linebuf.c_str();
            token += strspn(token, " \t");
            
            assert(token);
            if (token[0] == '\0')
                continue; // empty line
            
            if (token[0] == '#')
                continue; // comment line
            
            // vertex 顶点坐标,v开头
            if (token[0] == 'v' && isSpace((token[1]))) {
                token += 2;
                float x, y, z;
                parseFloat3(x, y, z, token);
                v.push_back(x);
                v.push_back(y);
                v.push_back(z);
                continue;
            }
            
            // normal 法线,vn开头
            if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) {
                token += 3;
                float x, y, z;
                parseFloat3(x, y, z, token);
                vn.push_back(x);
                vn.push_back(y);
                vn.push_back(z);
                continue;
            }
            
            // texcoord 纹理坐标,vt开头
            if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) {
                token += 3;
                float x, y;
                parseFloat2(x, y, token);
                vt.push_back(x);
                vt.push_back(y);
                continue;
            }
            
            // face  面索引,f开头
            if (token[0] == 'f' && isSpace((token[1]))) {
                token += 2;
                token += strspn(token, " \t");
                
                std::vector<vertex_index> face;
                auto first = static_cast<int>(v.size() / 3);
                auto second = static_cast<int>(vn.size() / 3);
                auto third = static_cast<int>(vt.size() / 2);
                while (!isNewLine(token[0])) {
                    vertex_index vi =
                    parseTriple(token, first, second, third);
                    face.push_back(vi);
                    size_t n = strspn(token, " \t\r");
                    token += n;
                }
                
                faceGroup.push_back(face);
                
                continue;
            }
            
            // use mtl
            if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) {
                
                char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
                token += 7;
#ifdef _MSC_VER
                sscanf_s(token, "%s", namebuf, _countof(namebuf));
#else
                sscanf(token, "%s", namebuf);
#endif
                
                // Create face group per material.
                bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt,
                                                  faceGroup, material, name, true);
                if (ret) {
                    shapes.push_back(shape);
                }
                shape = shape_t();
                faceGroup.clear();
                
                if (material_map.find(namebuf) != material_map.end()) {
                    material = material_map[namebuf];
                } else {
                    // { error!! material not found }
                    material = -1;
                }
                
                continue;
            }
            
            // load mtl
            if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) {
                char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
                token += 7;
#ifdef _MSC_VER
                sscanf_s(token, "%s", namebuf, _countof(namebuf));
#else
                sscanf(token, "%s", namebuf);
#endif
                
                std::string err_mtl = readMatFn(namebuf, materials, material_map);
                if (!err_mtl.empty()) {
                    faceGroup.clear(); // for safety
                    return err_mtl;
                }
                
                continue;
            }
            
            // group name
            if (token[0] == 'g' && isSpace((token[1]))) {
                
                // flush previous face group.
                bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt,
                                                  faceGroup, material, name, true);
                if (ret) {
                    shapes.push_back(shape);
                }
                
                shape = shape_t();
                
                // material = -1;
                faceGroup.clear();
                
                std::vector<std::string> names;
                while (!isNewLine(token[0])) {
                    std::string str = parseString(token);
                    names.push_back(str);
                    token += strspn(token, " \t\r"); // skip tag
                }
                
                assert(names.size() > 0);
                
                // names[0] must be 'g', so skip the 0th element.
                if (names.size() > 1) {
                    name = names[1];
                } else {
                    name = "";
                }
                
                continue;
            }
            
            // object name
            if (token[0] == 'o' && isSpace((token[1]))) {
                
                // flush previous face group.
                bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt,
                                                  faceGroup, material, name, true);
                if (ret) {
                    shapes.push_back(shape);
                }
                
                // material = -1;
                faceGroup.clear();
                shape = shape_t();
                
                // @todo { multiple object name? }
                char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
                token += 2;
#ifdef _MSC_VER
                sscanf_s(token, "%s", namebuf, _countof(namebuf));
#else
                sscanf(token, "%s", namebuf);
#endif
                name = std::string(namebuf);
                
                continue;
            }
            
            // Ignore unknown command.
        }
        
        //将v, vn, vt, faceGroup的数据转存到shape中
        bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup,
                                          material, name, true);
        if (ret) {
            shapes.push_back(shape);
        }
        faceGroup.clear(); // for safety
        
        return err.str();
    }

LoadObj主要是解析obj文件,把数据存到v, vn, vt, faceGroup中。然后,调用exportFaceGroupToShape把v, vn, vt, faceGroup的数据转存到shape中,代码如下

    static bool exportFaceGroupToShape(
                                       shape_t &shape, std::map<vertex_index, unsigned int> vertexCache,
                                       const std::vector<float> &in_positions,
                                       const std::vector<float> &in_normals,
                                       const std::vector<float> &in_texcoords,
                                       const std::vector<std::vector<vertex_index> > &faceGroup,
                                       const int material_id, const std::string &name, bool clearCache) {
        if (faceGroup.empty()) {
            return false;
        }
        
        // Flatten vertices and indices
        for (size_t i = 0, size = faceGroup.size(); i < size; ++i) {
            const std::vector<vertex_index> &face = faceGroup[i];
            
            vertex_index i0 = face[0];
            vertex_index i1(-1);
            vertex_index i2 = face[1];
            
            size_t npolys = face.size();
            
            // Polygon -> triangle fan conversion
            for (size_t k = 2; k < npolys; k++) {  //一次循环一个三角形
                i1 = i2;
                i2 = face[k];
               
                unsigned int v0 = updateVertex(
                                               vertexCache, shape.mesh.positions, shape.mesh.normals,
                                               shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); //第一个循环:face[0];第二个循环:face[0]
                unsigned int v1 = updateVertex(
                                               vertexCache, shape.mesh.positions, shape.mesh.normals,
                                               shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); //第一个循环:face[1];第二个循环:face[2]
                unsigned int v2 = updateVertex(
                                               vertexCache, shape.mesh.positions, shape.mesh.normals,
                                               shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); //第一个循环:face[2];第二个循环:face[3]
                
                shape.mesh.indices.push_back(v0);
                shape.mesh.indices.push_back(v1);
                shape.mesh.indices.push_back(v2);
                
                shape.mesh.material_ids.push_back(material_id);
            }
        }
        
        shape.name = name;
        
        if (clearCache)
            vertexCache.clear();
        
        return true;
    }

整个过程的内存图大致如下:


5994163-7849d7f2aac57f9d.png

得到shapes和materials后,就对meshdatas,materialdatas和nodedatas赋值。这三个结构体的类图大概如下:


5994163-0eb40faf1f192836.png

具体处理在Bundle3D::loadObj函数中,代码如下:

bool Bundle3D::loadObj(MeshDatas& meshdatas, MaterialDatas& materialdatas, NodeDatas& nodedatas, const std::string& fullPath, const char* mtl_basepath)
{
    meshdatas.resetData();
    materialdatas.resetData();
    nodedatas.resetData();

    std::string mtlPath = "";
    if (mtl_basepath)
        mtlPath = mtl_basepath;
    else
        mtlPath = fullPath.substr(0, fullPath.find_last_of("\\/") + 1);
    
    std::vector<tinyobj::shape_t> shapes;
    std::vector<tinyobj::material_t> materials;
    auto ret = tinyobj::LoadObj(shapes, materials, fullPath.c_str(), mtlPath.c_str());
    if (ret.empty())
    {
        //fill data
        //convert material
        int i = 0;
        char str[20];
        std::string dir = "";
        auto last = fullPath.rfind("/");
        if (last != std::string::npos)
            dir = fullPath.substr(0, last + 1);
        for (auto& material : materials) {
            NMaterialData materialdata;
            
            NTextureData tex;
            tex.filename = material.diffuse_texname.empty() ? material.diffuse_texname : dir + material.diffuse_texname;
            tex.type = NTextureData::Usage::Diffuse;
            tex.wrapS = GL_CLAMP_TO_EDGE;
            tex.wrapT = GL_CLAMP_TO_EDGE;
            
            sprintf(str, "%d", ++i);
            materialdata.textures.push_back(tex);
            materialdata.id = str;
            material.name = str;
            materialdatas.materials.push_back(materialdata);
        }
        
        //convert mesh
        i = 0;
        for (auto& shape : shapes) {
            auto mesh = shape.mesh;
            MeshData* meshdata = new (std::nothrow) MeshData();
            MeshVertexAttrib attrib;
            attrib.size = 3;
            attrib.type = GL_FLOAT;
            
            if (mesh.positions.size())
            {
                attrib.vertexAttrib = GLProgram::VERTEX_ATTRIB_POSITION;
                attrib.attribSizeBytes = attrib.size * sizeof(float);
                meshdata->attribs.push_back(attrib);
                
            }
            bool hasnormal = false, hastex = false;
            if (mesh.normals.size())
            {
                hasnormal = true;
                attrib.vertexAttrib = GLProgram::VERTEX_ATTRIB_NORMAL;
                attrib.attribSizeBytes = attrib.size * sizeof(float);
                meshdata->attribs.push_back(attrib);
            }
            if (mesh.texcoords.size())
            {
                hastex = true;
                attrib.size = 2;
                attrib.vertexAttrib = GLProgram::VERTEX_ATTRIB_TEX_COORD;
                attrib.attribSizeBytes = attrib.size * sizeof(float);
                meshdata->attribs.push_back(attrib);
            }
            
            auto vertexNum = mesh.positions.size() / 3;
            for(unsigned int k = 0; k < vertexNum; ++k)
            {
                meshdata->vertex.push_back(mesh.positions[k * 3]);
                meshdata->vertex.push_back(mesh.positions[k * 3 + 1]);
                meshdata->vertex.push_back(mesh.positions[k * 3 + 2]);
                
                if (hasnormal)
                {
                    meshdata->vertex.push_back(mesh.normals[k * 3]);
                    meshdata->vertex.push_back(mesh.normals[k * 3 + 1]);
                    meshdata->vertex.push_back(mesh.normals[k * 3 + 2]);
                }
                
                if (hastex)
                {
                    meshdata->vertex.push_back(mesh.texcoords[k * 2]);
                    meshdata->vertex.push_back(mesh.texcoords[k * 2 + 1]);
                }
            }
            
            //split into submesh according to material
            std::map<int, std::vector<unsigned short> > subMeshMap;
            for (size_t k = 0, size = mesh.material_ids.size(); k < size; ++k) {
                int id = mesh.material_ids[k];
                size_t idx = k * 3;
                subMeshMap[id].push_back(mesh.indices[idx]);
                subMeshMap[id].push_back(mesh.indices[idx + 1]);
                subMeshMap[id].push_back(mesh.indices[idx + 2]);
            }
            
            auto node = new (std::nothrow) NodeData();
            node->id = shape.name;
            for (auto& submesh : subMeshMap) {
                meshdata->subMeshIndices.push_back(submesh.second);
                meshdata->subMeshAABB.push_back(calculateAABB(meshdata->vertex, meshdata->getPerVertexSize(), submesh.second));
                sprintf(str, "%d", ++i);
                meshdata->subMeshIds.push_back(str);
                
                auto modelnode = new (std::nothrow) ModelData();
                modelnode->materialId = submesh.first == -1 ? "" : materials[submesh.first].name;
                modelnode->subMeshId = str;
                node->modelNodeDatas.push_back(modelnode);
            }
            nodedatas.nodes.push_back(node);
            meshdatas.meshDatas.push_back(meshdata);
        }
        
        return true;
    }
    CCLOG("warning: load %s file error: %s", fullPath.c_str(), ret.c_str());
    return false;
}

代码比较简单就不多说了,再简单画一下MeshDatas的内存图


5994163-063e041ffab8e9cf.png

猜你喜欢

转载自blog.csdn.net/weixin_33973609/article/details/87470421