从 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 本身并不困难。我的默认方法是使用这个“状态机”来读取一行:
- 向前跳至当前字符为白色space。
- 向前跳至当前字符非白色space.
- 读到
'/'
个字符。这是顶点索引。
- 返回步骤 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 加载库并丢弃法线和纹理数据。
我正在尝试提取面,然后从 .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 本身并不困难。我的默认方法是使用这个“状态机”来读取一行:
- 向前跳至当前字符为白色space。
- 向前跳至当前字符非白色space.
- 读到
'/'
个字符。这是顶点索引。 - 返回步骤 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 加载库并丢弃法线和纹理数据。