From c1a89332d48f1d656624c90f87a715ba2a2f2a94 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alexis=20Laferri=C3=A8re?= Date: Mon, 11 Jan 2016 22:52:31 -0500 Subject: [PATCH] lib/gamnit: intro parsers for .obj and .mtl files MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Alexis Laferrière --- lib/gamnit/model_parsers/mtl.nit | 131 ++++++++++++++++++++++ lib/gamnit/model_parsers/obj.nit | 230 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 lib/gamnit/model_parsers/mtl.nit create mode 100644 lib/gamnit/model_parsers/obj.nit diff --git a/lib/gamnit/model_parsers/mtl.nit b/lib/gamnit/model_parsers/mtl.nit new file mode 100644 index 0000000..b962fe1 --- /dev/null +++ b/lib/gamnit/model_parsers/mtl.nit @@ -0,0 +1,131 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Services to parse .mtl material files +module mtl + +import model_parser_base + +# Parser of `.mtl` files +# +# ~~~ +# var mtl_src = """ +# # Green material with low reflections +# newmtl GreenMaterial +# Ns 96.078431 +# Ka 0.000000 0.000000 0.000000 +# Kd 0.027186 0.180434 0.012088 +# Ks 0.500000 0.500000 0.500000 +# Ni 1.000000 +# d 1.000000 +# illum 2 +# """ +# +# var parser = new MtlFileParser(mtl_src) +# var name_to_mtls = parser.parse +# assert name_to_mtls.keys.join == "GreenMaterial" +# ~~~ +class MtlFileParser + super StringProcessor + + # Read and parse source and return all materials organized by their names + fun parse: Map[String, MtlDef] + do + var mat_lib = new Map[String, MtlDef] + var material: nullable MtlDef = null + + while not eof do + var token = read_token + if token == "newmtl" then + var name = read_until_eol_or_comment + material = new MtlDef(name) + mat_lib[name] = material + else if material != null then + if token == "Ka" then + material.ambient = read_vec3 + else if token == "Kd" then + material.diffuse = read_vec3 + else if token == "Ks" then + material.specular = read_vec3 + else if token == "d" then + material.dissolved = read_number + else if token == "Tr" then + material.dissolved = 1.0 - read_number + else if token == "illum" then + material.illumination_model = read_number.to_i + else if token == "map_Kd" then + material.map_diffuse = read_until_eol_or_comment + else if token == "map_Bump" then + material.map_bump = read_until_eol_or_comment + else if token == "map_Ks" then + material.map_specular = read_until_eol_or_comment + else if token == "map_Ns" then + material.map_exponent = read_until_eol_or_comment + + # TODO other line type headers + else if token == "Ns" then + else if token == "Ni" then + else if token == "sharpness" then + else if token == "bump" then + end + end + skip_eol + end + + return mat_lib + end +end + +# Material defined in a `.mtl` file +class MtlDef + + # Name of this material + var name: String + + # Ambient color + var ambient = new Vec3 is lazy + + # Diffuse color + var diffuse = new Vec3 is lazy + + # Specular color + var specular = new Vec3 is lazy + + # Dissolved level, where 1.0 is fully visible + var dissolved = 1.0 + + # Transparency level, where 1.0 is fully invisible + fun transparency: Float do return 1.0 - dissolved + + # Illumination model + var illumination_model = 0 + + # Diffuse map + var map_diffuse: nullable String = null + + # Bump map or normals texture + var map_bump: nullable String = null + + # Specular reflectivity map + var map_specular: nullable String = null + + # Specular exponent map + var map_exponent: nullable String = null + + # Collect non-null maps from `map_diffuse, map_bump, map_specular, map_exponent` + fun maps: Array[String] + do + return [for m in [map_diffuse, map_bump, map_specular, map_exponent] do if m != null then m] + end +end diff --git a/lib/gamnit/model_parsers/obj.nit b/lib/gamnit/model_parsers/obj.nit new file mode 100644 index 0000000..abd573d --- /dev/null +++ b/lib/gamnit/model_parsers/obj.nit @@ -0,0 +1,230 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# 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 +# ~~~ +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 + 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 + geometry.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 + 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] + + # Faces + var faces = new Array[ObjFace] + + # Referenced material libraries + fun material_libs: Set[String] do + var libs = new Set[String] + for face in faces do + var lib = face.material_lib + if lib != null then libs.add lib + 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 f in faces do + if f.vertices.length < 3 then return error("ObjFace with less than 3 vertices") + + 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 + +# Flat surface of an `ObjDef` +class ObjFace + # Vertex composing this surface, thene 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 -- 1.7.9.5