Boost::Spirit::QiによるWavefront Objファイルのパーサ

3Dメッシュフォーマットの一つ、Wavefront Objのパーサを書いてみた。Boost::Spiritの勉強を兼ねている。

Boost::Spirit::Qiはパーサコンビネータ。気や霊魂をモチーフにしたネーミングが中二病くさいことで散々ネタにされているけれど、実際結構すごいやつ。

けれども、それに輪を掛けてすごいのが、Boost::Phoenix。C++11が出たらBoost::Lambdaなんていらないと思っていたけれど、それは間違いだった。拡張性が半端ない。boost::spirit::qiでは、返値のための変数_valを文脈に応じて独自に定義するために使用している感じか、たぶん。

開発環境:gcc4.6 boost1.48.0 動作テスト:してない

3Dメッシュフォーマットの一つ、Wavefront Objのパーサを書いてみた。Boost::Spiritの勉強を兼ねている。

Boost::Spirit::Qiはパーサコンビネータ。気や霊魂をモチーフにしたネーミングが中二病くさいことで散々ネタにされているけれど、実際結構すごいやつ。

けれども、それに輪を掛けてすごいのが、Boost::Phoenix。C++11が出たらBoost::Lambdaなんていらないと思っていたけれど、それは間違いだった。拡張性が半端ない。boost::spirit::qiでは、返値のための変数_valを文脈に応じて独自に定義するために使用している感じか、たぶん。

開発環境:gcc4.6 boost1.48.0 動作テスト:してない

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/home/phoenix.hpp>
#include <fstream>

#include "Vector2d.h"
#include "Vector3d.h"

BOOST_FUSION_ADAPT_STRUCT(
	float3d,
	(float, x)
	(float, y)
	(float, z)
);

BOOST_FUSION_ADAPT_STRUCT(
	float2d,
	(float, x)
	(float, y)
);

class ObjFile {
public:
	class Group {
	public:
		std::string name;
		std::string material;
		std::vector<float3d> vertexList;
		std::vector<float3d> normalList;
		std::vector<float2d> texCoordList;
	};

	class Index {
	public:
		u32 vertex, texcoord, normal;
	};

	class Face {
	public:
		Index i1, i2, i3;
	};

	std::string materialFile;
	std::vector<Group> groups;
};

BOOST_FUSION_ADAPT_STRUCT(
	ObjFile,
	(std::string, materialFile)
	(std::vector<ObjFile::Group>, groups)
);

BOOST_FUSION_ADAPT_STRUCT(
	ObjFile::Group,
	(std::string, name)
	(std::string, material)
	(std::vector<float3d>, vertexList)
	(std::vector<float3d>, normalList)
	(std::vector<float2d>, texCoordList)
	(std::vector<ObjFile::Face>, faceList)
);

BOOST_FUSION_ADAPT_STRUCT(
	ObjFile::Index,
	(u32, vertex)
	(u32, texcoord)
	(u32, normal)
);

BOOST_FUSION_ADAPT_STRUCT(
	ObjFile::Face,
	(ObjFile::Index, i1)
	(ObjFile::Index, i2)
	(ObjFile::Index, i3)
);


using namespace boost::spirit;

template <typename Iterator>
class ObjParser : public qi::grammar<Iterator, ObjFile()>
{
	qi::rule<Iterator, float3d()> float3d_;
	qi::rule<Iterator, float2d()> float2d_;
	qi::rule<Iterator, ObjFile::Index()> index;

	qi::rule<Iterator, float3d()> vert_line;
	qi::rule<Iterator, float3d()> normal_line;
	qi::rule<Iterator, float2d()> texcoord_line;
	qi::rule<Iterator, std::string()> group_line;
	qi::rule<Iterator, std::string()> usemtl_line;
	qi::rule<Iterator, std::string()> mtllib_line;
	qi::rule<Iterator, ObjFile::Face()> face_line;
	qi::rule<Iterator> surface_line;
	qi::rule<Iterator> comment_line;

	qi::rule<Iterator, ObjFile::Group()> group;

	qi::rule<Iterator, ObjFile()> start;

public:
	ObjParser()
		: ObjParser::base_type(start)
	{
		using namespace qi;
		namespace phx = boost::phoenix;

		float3d_ %= float_ >> ' ' >> float_ >> ' ' >> float_;
		float2d_ %= float_ >> ' ' >> float_;
		index = eps[_val = phx::construct<ObjFile::Index>()]
				>> uint_[phx::at_c<0>(_val) = qi::_1]
				>> !('/' >> uint_[phx::at_c<1>(_val) = qi::_1]
					>> !('/' >> uint_[phx::at_c<2>(_val) = qi::_1])
				);

		vert_line %= "v " >> float3d_ >> eol;
		normal_line %= "vn " >> float3d_ >> eol;
		texcoord_line %= "vt " >> float2d_ >> eol;

		group_line %= "g " >> +(char_ - eol) >> eol;

		face_line %= "f " >> index >> ' ' >> index >> ' ' >> index >> eol;
		surface_line = "s " >> *(char_ - eol) >> eol;

		usemtl_line %= "usemtl " >> +(char_ - eol) >> eol;
		mtllib_line %= "mtllib " >> +(char_ - eol) >> eol;

		comment_line = '#' >> *(char_ - eol) >> eol | eol;

		group = eps[_val = phx::construct<ObjFile::Group>()]
				>> group_line[phx::at_c<0>(_val) = qi::_1]
				>> *(
						usemtl_line[phx::at_c<1>(_val) = qi::_1]
						| vert_line[phx::push_back(phx::at_c<2>(_val), qi::_1)]
						| normal_line[phx::push_back(phx::at_c<3>(_val), qi::_1)]
						| texcoord_line[phx::push_back(phx::at_c<4>(_val), qi::_1)]
						| comment_line
				);

		start = eps[_val = phx::construct<ObjFile>()]
				>> *(
					group[phx::push_back(phx::at_c<1>(_val), qi::_1)]
					| mtllib_line[phx::at_c<0>(_val) = qi::_1]
					| comment_line
				);
	}
};

int main(int argc, const char** argv) {
	if (argc == 0) {
		std::cout << "require one argument : .obj filepath" << std::endl;
		return -1;
	}

	std::ifstream file(argv[0]);
	std::string source;
	for(char c; !file.eof() && file.get(c); ) {
		source.push_back(c);
	}
	file.close();

	ObjFile value;
	ObjParser<std::string::iterator> parser;
	qi::parse(source.begin(), source.end(), parser, value);

	return 0;
}