Services to parse .obj geometry files

Introduced classes

class ObjDef

gamnit :: ObjDef

Geometry from a .obj file
class ObjFace

gamnit :: ObjFace

Flat surface of an ObjDef
class ObjFileParser

gamnit :: ObjFileParser

Parser of .obj files in ASCII format
class ObjObj

gamnit :: ObjObj

Sub-object within an ObjDef
class ObjVertex

gamnit :: ObjVertex

Vertex composing a ObjFace

All class definitions

class ObjDef

gamnit $ ObjDef

Geometry from a .obj file
class ObjFace

gamnit $ ObjFace

Flat surface of an ObjDef
class ObjFileParser

gamnit $ ObjFileParser

Parser of .obj files in ASCII format
class ObjObj

gamnit $ ObjObj

Sub-object within an ObjDef
class ObjVertex

gamnit $ ObjVertex

Vertex composing a ObjFace
package_diagram gamnit::obj obj gamnit::model_parser_base model_parser_base gamnit::obj->gamnit::model_parser_base parser_base parser_base gamnit::model_parser_base->parser_base ...parser_base ... ...parser_base->parser_base gamnit::more_models more_models gamnit::more_models->gamnit::obj gamnit::depth depth gamnit::depth->gamnit::more_models gamnit::depth... ... gamnit::depth...->gamnit::depth

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module parser_base

parser_base :: parser_base

Simple base for hand-made parsers of all kinds
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module model_parser_base

gamnit :: model_parser_base

Services to parse models from a text description

Children

module more_models

gamnit :: more_models

Services to load models from the assets folder

Descendants

module a_star-m

a_star-m

module cardboard

gamnit :: cardboard

Update the orientation of world_camera at each frame using the head position given by android::cardboard
module depth

gamnit :: depth

Framework for 3D games in Nit
module stereoscopic_view

gamnit :: stereoscopic_view

Refine EulerCamera and App::frame_core_draw to get a stereoscopic view
module vr

gamnit :: vr

VR support for gamnit depth, for Android only
# Services to parse .obj geometry files
module obj

import model_parser_base

# 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

# Geometry from a .obj file
class ObjDef
	# Vertex coordinates
	var vertex_points = new Array[Vec4]

	# Texture coordinates
	var texture_coords = new Array[Vec3]

	# Normals
	var normals = new Array[Vec3]

	# Surface parameters
	var params = new Array[Vec3]

	# Sub-objects
	var objects = new Array[ObjObj]

	# Relative paths to referenced material libraries
	fun material_libs: Set[String] do
		var libs = new Set[String]
		for obj in objects do
			for face in obj.faces do
				var lib = face.material_lib
				if lib != null then libs.add lib
			end
		end
		return libs
	end

	# Check the coherence of the model
	#
	# Returns `false` on error and prints details to stderr.
	#
	# This service can be useful for debugging, however it should not
	# be executed at each execution of a game.
	fun is_coherent: Bool
	do
		for obj in objects do
			for f in obj.faces do
				if f.vertices.length < 3 then return error("Face with less than 3 vertices")
			end

			for f in obj.faces do for v in f.vertices do
				var i = v.vertex_point_index
				if i < 1 then return error("Vertex point index < 1")
				if i > vertex_points.length then return error("Vertex point index > than length")

				var j = v.texture_coord_index
				if j != null then
					if j < 1 then return error("Texture coord index < 1")
					if j > texture_coords.length then return error("Texture coord index > than length")
				end

				j = v.normal_index
				if j != null then
					if j < 1 then return error("Normal index < 1")
					if j > normals.length then return error("Normal index > than length")
				end
			end
		end
		return true
	end

	# Service to print errors for `is_coherent`
	private fun error(msg: Text): Bool
	do
		print_error "ObjDef Error: {msg}"
		return false
	end
end

# Sub-object within an `ObjDef`
class ObjObj

	# Sub-object name as declared in the source file
	var name: String

	# Sub-object faces
	var faces = new Array[ObjFace]
end

# Flat surface of an `ObjDef`
class ObjFace
	# Vertex composing this surface, there should be 3 or more
	var vertices = new Array[ObjVertex]

	# Relative path to the .mtl material lib
	var material_lib: nullable String

	# Name of the material in `material_lib`
	var material_name: nullable String
end

# Vertex composing a `ObjFace`
class ObjVertex
	# Vertex coordinates index in `ObjDef::vertex_points`, starting at 1
	var vertex_point_index = 0

	# Texture coordinates index in `ObjDef::texture_coords`, starting at 1
	var texture_coord_index: nullable Int = null

	# Normal index in `ObjDef::normals`, starting at 1
	var normal_index: nullable Int = null
end
lib/gamnit/model_parsers/obj.nit:15,1--252,3