mse / RSOD (public) (License: CC0 and other licenses) (since 2025-03-01) (hash sha1)
Free software FPS engine

/src/obj2anib.cpp (06a8ef303450e6c8292e7cd5225469baaa2c11aa) (6063 bytes) (mode 100644) (type blob)

// obj2anib: a tool that turns Wavefront .obj into animated vertex/index binary files.
//
// Not endian-proof! Assume little endian because that's what OpenGL uses.
//
// 64-bit file header
// ANIBnnnn (where nnnn is int32_t number of frames)
//
// 64-bit frame header
// Where x is (sizeof(AnibVertex) * num_verts) and y is (sizeof(Index) * num_inds)
// Layout: [4 bytes num_verts][4 bytes num_inds][x bytes verts][y bytes inds]
//
// Compile with something like:
// g++ -std=c++14 -Wall -Wextra -Wfatal-errors -O3 -o obj2bin obj2bin.cpp

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

#include <cmath>

#include <map>
#include <string>
#include <utility>
#include <vector>

struct AnibVertex {
	float Position[3];
	float Normal[3];
	float UV[2];
};

typedef uint32_t Index;

void NormalizeVertices( std::vector<AnibVertex> &verts ){
	for( size_t i = 0; i < verts.size(); i++ ){
		float* n = verts[i].Normal;
		float l = std::sqrt( n[0] * n[0] + n[1] * n[1] + n[2] * n[2] );
		if( l != 0.0f && l != 1.0f ){
			verts[i].Normal[0] /= l;
			verts[i].Normal[1] /= l;
			verts[i].Normal[2] /= l;
		}
	}
}

// This function takes a Wavefront OBJ as input and appends a binary block to outVec.
// The OBJ must be triangulated before input. This function cannot deal with quads.
void AppendOBJ( const std::string &inFile, std::vector<unsigned char> &outVec ){
	std::vector<AnibVertex> verts;
	std::vector<Index> inds;

	FILE *file = fopen( inFile.c_str(), "rb" );
	if( !file ){
		fprintf( stderr, "Failed to open %s\n", inFile.c_str() );
		return;
	}

	std::string text = "";
	char buf[4096];
	while( size_t len = fread( buf, 1, sizeof( buf ), file ) ){
		text += std::string( buf, len );
	}
	fclose( file );

	size_t line = 0;

	struct float3 { float x, y, z; };
	struct float2 { float u, v; };
	struct obdex { int v1, vt1, vn1,  v2, vt2, vn2,  v3, vt3, vn3; };

	std::vector<float3> obj_v, obj_vn;
	std::vector<float2> obj_vt;
	std::vector<obdex> obj_f;

	size_t head = 0, tail = 0;

	do{
		line++;
		tail = text.find_first_of( "\n", head );
		if( tail == std::string::npos ) tail = text.size();
		if( text[head] != '#' ){
			std::string ln = text.substr( head, tail - head );

			std::vector<std::string> s;

			size_t i1 = 0, i2 = 0;

			do{
				i2 = ln.find_first_of( " \r/", i1 );
				if( i2 == std::string::npos ) i2 = ln.size();
				std::string value = ln.substr( i1, i2 - i1 );
				if( value.length() > 0 ) s.push_back( value );
				i1 = i2 + 1;
			}while( i1 < ln.size() );

			if( s.size() > 0 ){
				if( s[0] == "v" && s.size() >= 4 ){
					obj_v.push_back( { std::stof(s[1]), std::stof(s[2]), std::stof(s[3]) } );
				}else if( s[0] == "vt" && s.size() >= 3 ){
					// Flip the vertical texture coordinate.
					obj_vt.push_back( { std::stof(s[1]), 1.0f - std::stof(s[2]) } );
				}else if( s[0] == "vn" && s.size() >= 4 ){
					obj_vn.push_back( { std::stof(s[1]), std::stof(s[2]), std::stof(s[3]) } );
				}else if( s[0] == "f" && s.size() >= 10 ){
					obj_f.push_back( {
						std::stoi(s[1]),
						std::stoi(s[2]),
						std::stoi(s[3]),
						std::stoi(s[4]),
						std::stoi(s[5]),
						std::stoi(s[6]),
						std::stoi(s[7]),
						std::stoi(s[8]),
						std::stoi(s[9])
					} );
				}
			}
		}
		head = tail + 1;
	}while( head < text.size() );

	// Reduce the mesh to an efficient vertex/index representation.
	// https://community.khronos.org/t/obj-texture-coordinates-arent-mapped-correctly/69926
	std::map<std::string,std::pair<AnibVertex,Index>> vertmap;
	Index idx = 0, next_idx = 0;
	for( obdex f : obj_f ){
		const int lookup_v[] = { f.v1, f.v2, f.v3 };
		const int lookup_vt[] = { f.vt1, f.vt2, f.vt3 };
		const int lookup_vn[] = { f.vn1, f.vn2, f.vn3 };
		for( int i = 0; i < 3; i++ ){
			int v = lookup_v[i] - 1;
			int vt = lookup_vt[i] - 1;
			int vn = lookup_vn[i] - 1;
			int key[] = { v, vt, vn };
			std::string skey( (const char*)key, sizeof(int) * 3 );
			if( vertmap.find( skey ) == vertmap.end() ){
				// create vertex from position/normal/texcoord
				// store new vertex in vertices with key and next_vertex
				vertmap[skey] = { {
					{ obj_v[v].x, obj_v[v].y, obj_v[v].z },
					{ obj_vn[vn].x, obj_vn[vn].y, obj_vn[vn].z },
					{ obj_vt[vt].u, obj_vt[vt].v }
				}, next_idx };
				idx = next_idx;
				next_idx++;
			}else{
				idx = vertmap[skey].second;
			}
			// Save idx in index buffer.
			inds.push_back( idx );
		}
	}

	// Order verts by index number.
	verts.resize( vertmap.size() );
	for( const auto &el : vertmap ){
		verts[el.second.second] = el.second.first;
	}

	NormalizeVertices( verts );

	// Write verts and inds to a buffer.
	size_t memsize = sizeof(int32_t) * 2 + sizeof(AnibVertex) * verts.size() + sizeof(Index) * inds.size();
	unsigned char *memory = (unsigned char*)malloc( memsize );
	((int32_t*)memory)[0] = (int32_t)verts.size();
	((int32_t*)memory)[1] = (int32_t)inds.size();
	memcpy( memory + sizeof(int32_t) * 2, verts.data(), sizeof(AnibVertex) * verts.size() );
	memcpy( memory + sizeof(int32_t) * 2 + sizeof(AnibVertex) * verts.size(), inds.data(), sizeof(Index) * inds.size() );
	// Write the buffer to outVec.
	size_t vecEnd = outVec.size();
	outVec.resize(vecEnd + memsize);
	memcpy((void*)&(outVec.data()[vecEnd]), memory, memsize);
	free( memory );
}

int main( int argc, char* argv[] ){
	if( argc > 1 ){
		std::vector<unsigned char> outVec;
		outVec.resize(8);
		// Write header.
		char header[8] = "ANIB";
		((int32_t*)header)[1] = argc - 1;
		memcpy(outVec.data(), header, 8);
		// Write frames.
		for( int i = 1; i < argc; i++ ){
			std::string inFile = argv[i];
			AppendOBJ(inFile, outVec);
		}
		// Write data to a file.
		std::string outFile = "out.anib";
		FILE *file = fopen(outFile.c_str(), "wb");
		if(!file){
			fprintf(stderr, "Failed to open %s for writing\n", outFile.c_str());
			return 2;
		}
		fwrite(outVec.data(), outVec.size(), 1, file);
		fclose(file);
		printf("%s\n", outFile.c_str());
		return 0;
	}else{
		fprintf( stderr, "Expects 1 or more input files.\n" );
		if( argc == 1 ) printf( "Example:\n%s *.obj\n", argv[0] );
		return 1;
	}
}


Mode Type Size Ref File
100644 blob 170 42b08f467f099371d96fc6f35757c4510bfe7987 .gitignore
100644 blob 13711 25dbc408cf6dce13f6aac062d31879240d7f4f5e Makefile
100644 blob 2396 fc392e2bdb9b9ac4abb5fded56a32df18102c407 README.md
040000 tree - 56f2c438287fb1f800a0493d6b0d6907dc648241 base
100644 blob 487269 29cfd3578eb40b1f039e271bcaa81af49d1b7f3c gamecontrollerdb.txt
040000 tree - 99c807d76953d139e1c10f9f5c053455b9a79d94 include
100755 blob 257 5da83586fddf2984c55ed40e03f43c504552e8aa makeanib
100755 blob 677 e3ce6d4069311dcbb89f1d1166a00a2f9d2934b5 package-steam
100644 blob 879 1aa6cc46749b8ad10c792dd501a23d62886ae951 package-steam-build-demo.vdf
100644 blob 887 accdaa60338652380422b5c2caa0651cc8b0ea7e package-steam-build-playtest.vdf
100644 blob 879 0b12a202f0ef30f781c82678a68c9a5093365e7e package-steam-build.vdf
100644 blob 1182 81610ae8ed0d39b7b1412ef22eff86289fe2adb2 rsod.cbp
040000 tree - 43b2388b0f36e437807a966f6cf9a0dfae5fef7a src
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/mse/RSOD

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/mse/RSOD

Clone this repository using git:
git clone git://git.rocketgit.com/user/mse/RSOD

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main