1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Services to parse .obj geometry files
18 import model_parser_base
20 # Parser of .obj files in ASCII format
22 # Instantiate from a `String` and use `parse` to extract the `ObjDef`.
27 # mtllib material_file.mtl
29 # v 1.000000 0.000000 0.000000
30 # v 1.000000 0.000000 1.000000
31 # v 0.000000 0.000000 1.000000
32 # v 0.000000 0.000000 0.000000
33 # v 1.000000 1.000000 0.999999
34 # v 0.999999 1.000000 1.000001
35 # v 0.000000 1.000000 1.000000
36 # v 0.000000 1.000000 0.000000
37 # usemtl GreenMaterial
47 # var parser = new ObjFileParser(obj_src)
48 # var parsed_obj = parser.parse
49 # assert parsed_obj.is_coherent
50 # assert parsed_obj.objects.first.name == "Cube"
55 private var geometry
= new ObjDef is lazy
57 private var current_material_lib
: nullable String = null
59 private var current_material_name
: nullable String = null
61 # Execute parsing of `src` to extract an `ObjDef`
62 fun parse
: nullable ObjDef
66 var token
= read_token
67 if token
.is_empty
or token
== "#" then
68 # Ignore empty lines and comments
69 else if token
== "v" then # Vertex points
71 geometry
.vertex_points
.add vec
72 else if token
== "vt" then # Texture coords
74 geometry
.texture_coords
.add vec
75 else if token
== "vn" then # Normals
76 var vec
= read_vec3
# This one should not accept `w` values
77 geometry
.normals
.add vec
78 else if token
== "vp" then # Parameter space vertices
80 geometry
.params
.add vec
81 else if token
== "f" then # Faces
83 if obj_obj
== null then
84 obj_obj
= new ObjObj("")
85 geometry
.objects
.add obj_obj
87 obj_obj
.faces
.add face
88 else if token
== "mtllib" then
89 current_material_lib
= read_until_eol_or_comment
90 else if token
== "usemtl" then
91 current_material_name
= read_until_eol_or_comment
93 # TODO other line type headers
94 else if token
== "s" then
95 else if token
== "o" then
96 obj_obj
= new ObjObj(read_until_eol_or_comment
)
97 geometry
.objects
.add obj_obj
98 else if token
== "g" then
105 private fun read_face
: ObjFace
107 var face
= new ObjFace(current_material_lib
, current_material_name
)
110 var r
= read_face_index_set
(face
)
117 private fun read_face_index_set
(face
: ObjFace): Bool
119 var token
= read_token
121 var parts
= token
.split
('/')
122 if parts
.is_empty
or parts
.first
.is_empty
then return false
124 var v
= new ObjVertex
125 for i
in parts
.length
.times
, part
in parts
do
129 if not part
.is_empty
and part
.is_numeric
then n
= part
.to_i
132 n
= n
or else 0 # Error if n == null
133 if n
< 0 then n
= geometry
.vertex_points
.length
+ n
134 v
.vertex_point_index
= n
136 if n
!= null and n
< 0 then n
= geometry
.texture_coords
.length
+ n
137 v
.texture_coord_index
= n
139 if n
!= null and n
< 0 then n
= geometry
.normals
.length
+ n
149 # Geometry from a .obj file
152 var vertex_points
= new Array[Vec4]
154 # Texture coordinates
155 var texture_coords
= new Array[Vec3]
158 var normals
= new Array[Vec3]
161 var params
= new Array[Vec3]
164 var objects
= new Array[ObjObj]
166 # Relative paths to referenced material libraries
167 fun material_libs
: Set[String] do
168 var libs
= new Set[String]
169 for obj
in objects
do
170 for face
in obj
.faces
do
171 var lib
= face
.material_lib
172 if lib
!= null then libs
.add lib
178 # Check the coherence of the model
180 # Returns `false` on error and prints details to stderr.
182 # This service can be useful for debugging, however it should not
183 # be executed at each execution of a game.
184 fun is_coherent
: Bool
186 for obj
in objects
do
187 for f
in obj
.faces
do
188 if f
.vertices
.length
< 3 then return error
("Face with less than 3 vertices")
191 for f
in obj
.faces
do for v
in f
.vertices
do
192 var i
= v
.vertex_point_index
193 if i
< 1 then return error
("Vertex point index < 1")
194 if i
> vertex_points
.length
then return error
("Vertex point index > than length")
196 var j
= v
.texture_coord_index
198 if j
< 1 then return error
("Texture coord index < 1")
199 if j
> texture_coords
.length
then return error
("Texture coord index > than length")
204 if j
< 1 then return error
("Normal index < 1")
205 if j
> normals
.length
then return error
("Normal index > than length")
212 # Service to print errors for `is_coherent`
213 private fun error
(msg
: Text): Bool
215 print_error
"ObjDef Error: {msg}"
220 # Sub-object within an `ObjDef`
223 # Sub-object name as declared in the source file
227 var faces
= new Array[ObjFace]
230 # Flat surface of an `ObjDef`
232 # Vertex composing this surface, there should be 3 or more
233 var vertices
= new Array[ObjVertex]
235 # Relative path to the .mtl material lib
236 var material_lib
: nullable String
238 # Name of the material in `material_lib`
239 var material_name
: nullable String
242 # Vertex composing a `ObjFace`
244 # Vertex coordinates index in `ObjDef::vertex_points`, starting at 1
245 var vertex_point_index
= 0
247 # Texture coordinates index in `ObjDef::texture_coords`, starting at 1
248 var texture_coord_index
: nullable Int = null
250 # Normal index in `ObjDef::normals`, starting at 1
251 var normal_index
: nullable Int = null