从 Wavefront .obj 文件中提取面顶点索引

Extracting face vertex indices from a Wavefront .obj file

我正在尝试提取面,然后从 .obj 文件中获取相应的顶点索引,如下所示。我现在可以使用 strtok() 函数单独打印顶点索引,但我似乎无法找到正确的解析方法。

这是我的代码:

#include <stdio.h>
#include <string.h>

//lazy wavefront obj file parser

int main(){
    FILE *fp = fopen("head.obj", "r");

    //find the number of lines in the file
    int no_lines = 0;
    char ch;
    while(ch != EOF){
        ch = getc(fp);
        if(ch == '\n')
            no_lines++;
    }
    printf("number of lines: %d\n", no_lines);

    //set seek point to start of the file
    fseek(fp, 0, SEEK_SET);

    //get the faces and parse them
    char line[100];
    while(fgets(line, sizeof(line), fp) != NULL){
        if(line[0] == 'f'){

            //split the line at spaces
            char *s = strtok(line, " ");
            
            while(s != NULL){
                if(*s != 'f'){
                    /*this will print faces as follows
                        58/35/58
                        59/36/59
                        60/37/60

                        we need to get the first number from each line, i.e., 58, 59, 60
                    */
                    printf("%s\n", s);
                }
                s = strtok(NULL, " ");
            }
        }
    }
    
    fclose(fp);

    return 0;
}

输出看起来像这样。

1202/1248/1202
1220/1281/1220
1200/1246/1200

1200/1246/1200
1220/1281/1220
1198/1247/1198

1201/1249/1201
1200/1246/1200
1199/1245/1199

1201/1249/1201
1202/1248/1202
1200/1246/1200

我想从上面的输出中提取数字如下,我只需要每一行的第一个数字。

对于以下几行

1202/1248/1202
1220/1281/1220
1200/1246/1200

输出应该是 1202, 1220, 1200.

简要概述

下面的代码绝不是一个完整的Wavefront OBJ解析器,但它满足了问题中的要求。首先,它检查该行的第一个字符是否是 'f',如果是 而不是 的情况,那么我们可以跳过这一行。否则,行解析开始。
我们首先跳过 'f',然后使用交替的分隔符重复两次调用 strtok。在第一个中,我们读取到第一个顶点索引 '/' 。在第二个中,我们一直读到下一个 space 字符(并忽略结果)。指针现在位于第二个顶点索引的开头。重复此过程,直到行尾。

一些技术细节

根据 this source blank space can freely be added to the OBJ file, although this source 的说法,“在斜线之前或之后不允许使用 space”。跳过 whitespace 本身并不困难。我的默认方法是使用这个“状态机”来读取一行:

  1. 向前跳至当前字符为白色space。
  2. 向前跳至当前字符白色space.
  3. 读到 '/' 个字符。这是顶点索引。
  4. 返回步骤 1。

手动推进指针并使用 sscanf 读取顶点索引是一种有效的方法。在这里尝试使用 strtok 很诱人,但是,这会使解析变得更加困难,因为 strtok

  • 有不同的初始调用,
  • 将当前位置保持为内部状态,
  • 具有破坏性,
  • 总是寻找 下一个 字符,即无法处理长度为 0 的结果。

由于问题表明实现应该是“简单的”,并且通常使用单个 space 作为白色 space 分隔符,因此可以实现以下实现:

代码

示例输入行:f 2/1/1 4/2/1 1/3/1.
示例输出行:2 4 1 .

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LINE_SIZE_MAX 128U

static size_t fileToLines(FILE *fp);
static bool parseFacesFromFile(FILE *fp);

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <obj file name>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    FILE *fp = fopen(argv[1], "r");
    if (fp == NULL)
    {
        fprintf(stderr, "Error opening file\n");
        exit(EXIT_FAILURE);
    }

    printf("Number of lines: %zu\n", fileToLines(fp));

    printf("Parsing %s\n", parseFacesFromFile(fp) ? "succeeded" : "failed");

    fclose(fp);
}

static size_t fileToLines(FILE *fp)
{
    size_t numberOfLines = 0U;
    int ch;
    while ((ch = getc(fp)) != EOF)
        if (ch == '\n')
            ++numberOfLines;

    fseek(fp, 0L, SEEK_SET);
    
    return numberOfLines;
}

static bool parseFacesFromFile(FILE *fp)
{
    char line[LINE_SIZE_MAX];
    while (fgets(line, sizeof(line), fp) != NULL)
    {
        if (line[0] != 'f')
            continue;

        char *tokenPtr;
        strtok(line, " "); // Read past the initial "f"
        while ((tokenPtr = strtok(NULL, "/")) != NULL)
        {
            printf("%s ", tokenPtr);
            strtok(NULL, " "); // Return value checking omitted, assume correct input
        }
        putchar('\n');
    }

    return true;
}

附加评论:

  • 不使用任何参数的 main 的典型签名是 int main(void)
  • 在问题的代码中,char ch 在首次使用时未初始化。
  • 函数 getc returns 一个 int 应该 转换为 char。在后一种情况下,无法可靠地检测到 EOF
  • 如果您只是追求最终结果,请考虑使用 OBJ 加载库并丢弃法线和纹理数据。