Property definitions

gamnit $ ObjFileParser :: defaultinit
# Parser of .obj files in ASCII format
#
# Instantiate from a `String` and use `parse` to extract the `ObjDef`.
#
# ~~~
# var obj_src = """
# # Model of a cube
# mtllib material_file.mtl
# o Cube
# v 1.000000 0.000000 0.000000
# v 1.000000 0.000000 1.000000
# v 0.000000 0.000000 1.000000
# v 0.000000 0.000000 0.000000
# v 1.000000 1.000000 0.999999
# v 0.999999 1.000000 1.000001
# v 0.000000 1.000000 1.000000
# v 0.000000 1.000000 0.000000
# usemtl GreenMaterial
# s off
# f 1 2 3 4
# f 5 6 7 8
# f 1 5 8 2
# f 2 8 7 3
# f 3 7 6 4
# f 5 1 4 6
# """
#
# var parser = new ObjFileParser(obj_src)
# var parsed_obj = parser.parse
# assert parsed_obj.is_coherent
# assert parsed_obj.objects.first.name == "Cube"
# ~~~
class ObjFileParser
	super StringProcessor

	private var geometry = new ObjDef is lazy

	private var current_material_lib: nullable String = null

	private var current_material_name: nullable String = null

	# Execute parsing of `src` to extract an `ObjDef`
	fun parse: nullable ObjDef
	do
		var obj_obj = null
		while not eof do
			var token = read_token
			if token.is_empty or token == "#" then
				# Ignore empty lines and comments
			else if token == "v" then # Vertex points
				var vec = read_vec4
				geometry.vertex_points.add vec
			else if token == "vt" then # Texture coords
				var vec = read_vec3
				geometry.texture_coords.add vec
			else if token == "vn" then # Normals
				var vec = read_vec3 # This one should not accept `w` values
				geometry.normals.add vec
			else if token == "vp" then # Parameter space vertices
				var vec = read_vec3
				geometry.params.add vec
			else if token == "f" then # Faces
				var face = read_face
				if obj_obj == null then
					obj_obj = new ObjObj("")
					geometry.objects.add obj_obj
				end
				obj_obj.faces.add face
			else if token == "mtllib" then
				current_material_lib = read_until_eol_or_comment
			else if token == "usemtl" then
				current_material_name = read_until_eol_or_comment

			# TODO other line type headers
			else if token == "s" then
			else if token == "o" then
				obj_obj = new ObjObj(read_until_eol_or_comment)
				geometry.objects.add obj_obj
			else if token == "g" then
			end
			skip_eol
		end
		return geometry
	end

	private fun read_face: ObjFace
	do
		var face = new ObjFace(current_material_lib, current_material_name)

		loop
			var r = read_face_index_set(face)
			if not r then break
		end

		return face
	end

	private fun read_face_index_set(face: ObjFace): Bool
	do
		var token = read_token

		var parts = token.split('/')
		if parts.is_empty or parts.first.is_empty then return false

		var v = new ObjVertex
		for i in parts.length.times, part in parts do
			part = part.trim

			var n = null
			if not part.is_empty and part.is_numeric then n = part.to_i

			if i == 0 then
				n = n or else 0 # Error if n == null
				if n < 0 then n = geometry.vertex_points.length + n
				v.vertex_point_index = n
			else if i == 1 then
				if n != null and n < 0 then n = geometry.texture_coords.length + n
				v.texture_coord_index = n
			else if i == 2 then
				if n != null and n < 0 then n = geometry.normals.length + n
				v.normal_index = n
			else abort
		end
		face.vertices.add v

		return true
	end
end
lib/gamnit/model_parsers/obj.nit:20,1--147,3