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 load models from the assets folder
18 intrude import depth_core
27 # Prepare to load a model from the assets folder
28 new(path
: Text) do return new ModelAsset(path
.to_s
)
31 # Model loaded from a file in the asset folder
33 # In case of error, `error` is set accordingly.
34 # If the error is on the mesh, `mesh` is set to a default `new Cube`.
35 # If the material is missing or it failed to load, `material` is set to the blueish `new Material`.
40 init do models
.add
self
42 private var loaded
= false
48 var ext
= path
.file_extension
52 errors
.add
new Error("Model at '{path}' failed to load: Extension '{ext or else "null"}' unrecognized")
55 if leaves_cache
.is_empty
then
56 # Nothing was loaded, use a cube with the default material
57 var leaf
= placeholder_model
71 # Print errors when lazy loading only
72 if errors
.length
== 1 then
73 print_error errors
.first
74 else if errors
.length
> 1 then
75 print_error
"Loading model at '{path}' raised {errors.length} errors:\n* "
76 print_error errors
.join
("\n* ")
80 private fun load_obj_file
82 # Read .obj description from assets
83 var text_asset
= new TextAsset(path
)
84 var content
= text_asset
.to_s
85 if content
.is_empty
then
86 errors
.add
new Error("Model failed to load: Asset empty at '{self.path}'")
87 leaves_cache
.add
new LeafModel(new Cube, new Material)
91 # Parse .obj description
92 var parser
= new ObjFileParser(content
)
93 var obj_def
= parser
.parse
94 if obj_def
== null then
95 errors
.add
new Error("Model failed to load: .obj format error on '{self.path}'")
96 leaves_cache
.add
new LeafModel(new Cube, new Material)
101 if debug_gamnit
then assert obj_def
.is_coherent
104 var converter
= new BuildModelFromObj(path
, obj_def
)
105 converter
.fill_leaves
self
106 errors
.add_all converter
.errors
115 private var leaves_cache
= new Array[LeafModel]
117 redef fun named_parts
120 return named_leaves_cache
123 private var named_leaves_cache
= new Map[String, Model]
126 # Short-lived service to convert an `ObjDef` to `fill_leaves`
128 # Limitations: This service only support faces with 3 or 4 vertices.
129 # Faces with more vertices should be triangulated by the modeling tool.
130 private class BuildModelFromObj
132 # Path to the .obj file in the assets folder, used to find .mtl files
135 # Parsed .obj definition
138 # Errors raised by calls to `fill_leaves`
139 var errors
= new Array[Error]
141 # Fill `leaves` with objects described in `obj_def`
142 fun fill_leaves
(target_model
: ModelAsset)
144 var leaves
= target_model
.leaves_cache
146 # Sort faces by material
147 var obj_mtl_to_faces
= new Map[ObjObj, MultiHashMap[String, ObjFace]]
148 for obj
in obj_def
.objects
do
149 var mtl_to_faces
= new MultiHashMap[String, ObjFace]
150 obj_mtl_to_faces
[obj
] = mtl_to_faces
151 for face
in obj
.faces
do
152 var mtl_lib_name
= face
.material_lib
153 var mtl_name
= face
.material_name
156 if mtl_lib_name
!= null and mtl_name
!= null then full_name
= mtl_lib_name
/ mtl_name
158 mtl_to_faces
[full_name
].add face
163 var mtl_libs
= sys
.mtl_libs
164 var lib_names
= obj_def
.material_libs
165 for name
in lib_names
do
166 var asset_path
= self.path
.dirname
/ name
167 var lib_asset
= new TextAsset(asset_path
)
170 var error
= lib_asset
.error
171 if error
!= null then
176 var mtl_parser
= new MtlFileParser(lib_asset
.to_s
)
177 var mtl_lib
= mtl_parser
.parse
178 mtl_libs
[asset_path
] = mtl_lib
181 # Create 1 mesh per material per object, and prepare materials
182 var mesh_to_mtl
= new Map[Mesh, nullable MtlDef]
183 var mesh_to_name
= new Map[Mesh, String]
184 var texture_names
= new Set[String]
185 for obj
in obj_def
.objects
do
186 var mtl_to_faces
= obj_mtl_to_faces
[obj
]
187 for mtl_path
, faces
in mtl_to_faces
do
191 mesh
.vertices
= vertices
(faces
)
192 mesh
.normals
= normals
(faces
)
193 mesh
.texture_coords
= texture_coords
(faces
)
198 var mtl_lib_name
= faces
.first
.material_lib
199 var mtl_name
= faces
.first
.material_name
200 if mtl_lib_name
!= null and mtl_name
!= null then
201 var asset_path
= self.path
.dirname
/ mtl_lib_name
202 var mtl_lib
= mtl_libs
[asset_path
]
203 var mtl
= mtl_lib
.get_or_null
(mtl_name
)
208 texture_names
.add
self.path
.dirname
/ e
211 errors
.add
new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
215 mesh_to_mtl
[mesh
] = mtl_def
216 mesh_to_name
[mesh
] = obj
.name
220 # Load textures need for these materials
221 for name
in texture_names
do
222 if not asset_textures_by_name
.keys
.has
(name
) then
223 var tex
= new TextureAsset(name
)
224 asset_textures_by_name
[name
] = tex
227 var error
= tex
.error
228 if error
!= null then errors
.add error
232 # Create final `Materials` from defs and textures
233 var materials
= new Map[MtlDef, Material]
234 for mtl
in mesh_to_mtl
.values
do
235 if mtl
== null then continue
237 var ambient
= mtl
.ambient
.to_a
240 var diffuse
= mtl
.diffuse
.to_a
243 var specular
= mtl
.specular
.to_a
246 var material
= new TexturedMaterial(ambient
, diffuse
, specular
)
247 materials
[mtl
] = material
249 var tex_name
= mtl
.map_ambient
250 if tex_name
!= null then
251 tex_name
= self.path
.dirname
/ tex_name
252 material
.ambient_texture
= asset_textures_by_name
[tex_name
]
255 tex_name
= mtl
.map_diffuse
256 if tex_name
!= null then
257 tex_name
= self.path
.dirname
/ tex_name
258 material
.diffuse_texture
= asset_textures_by_name
[tex_name
]
261 tex_name
= mtl
.map_specular
262 if tex_name
!= null then
263 tex_name
= self.path
.dirname
/ tex_name
264 material
.specular_texture
= asset_textures_by_name
[tex_name
]
268 # Create models and store them
269 var name_to_leaves
= new MultiHashMap[String, LeafModel]
270 for mesh
, mtl_def
in mesh_to_mtl
do
272 var material
= materials
.get_or_null
(mtl_def
)
273 if material
== null then material
= new Material
275 var model
= new LeafModel(mesh
, material
)
278 name_to_leaves
[mesh_to_name
[mesh
]].add model
281 # Collect objects with a name
282 for name
, models
in name_to_leaves
do
283 if models
.length
== 1 then
284 target_model
.named_leaves_cache
[name
] = models
.first
286 var named_model
= new CompositeModel
287 named_model
.leaves
.add_all models
288 target_model
.named_leaves_cache
[name
] = named_model
293 # Compute the vertices coordinates of `faces` in a flat `Array[Float]`
294 fun vertices
(faces
: Array[ObjFace]): Array[Float] do
295 var obj_def
= obj_def
297 var vertices
= new Array[Float]
302 for e
in face
.vertices
do
303 var i
= e
.vertex_point_index
- 1
304 var v
= obj_def
.vertex_points
[i
]
310 if count
== 2 then break
314 # If square, 2nd triangle
316 # This may not support all vertices ordering.
317 if face
.vertices
.length
> 3 then
318 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
319 var i
= e
.vertex_point_index
- 1
320 var v
= obj_def
.vertex_points
[i
]
328 # TODO use polygon triangulation to support larger polygons
333 # Compute the normals of `faces` in a flat `Array[Float]`
334 fun normals
(faces
: Array[ObjFace]): Array[Float] do
335 var obj_def
= obj_def
337 var normals
= new Array[Float]
341 for e
in face
.vertices
do
342 var i
= e
.normal_index
344 compute_and_append_normal
(normals
, face
)
346 var v
= obj_def
.normals
[i-1
]
352 if count
== 2 then break
356 # If square, 2nd triangle
358 # This may not support all vertices ordering.
359 if face
.vertices
.length
> 3 then
360 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
361 var i
= e
.normal_index
363 compute_and_append_normal
(normals
, face
)
365 var v
= obj_def
.normals
[i-1
]
376 # Compute the normal of `face` and append it as 3 floats to `seq`
378 # Resulting normals are not normalized.
379 fun compute_and_append_normal
(seq
: Sequence[Float], face
: ObjFace)
381 var i1
= face
.vertices
[0].vertex_point_index
382 var i2
= face
.vertices
[1].vertex_point_index
383 var i3
= face
.vertices
[2].vertex_point_index
385 var v1
= obj_def
.vertex_points
[i1-1
]
386 var v2
= obj_def
.vertex_points
[i2-1
]
387 var v3
= obj_def
.vertex_points
[i3-1
]
396 var nx
= (vy
*wz
) - (vz
*wy
)
397 var ny
= (vz
*wx
) - (vx
*wz
)
398 var nz
= (vx
*wy
) - (vy
*wx
)
406 # Compute the texture coordinates of `faces` in a flat `Array[Float]`
407 fun texture_coords
(faces
: Array[ObjFace]): Array[Float] do
408 var obj_def
= obj_def
410 var coords
= new Array[Float]
415 for e
in face
.vertices
do
416 var i
= e
.texture_coord_index
421 var tc
= obj_def
.texture_coords
[i-1
]
426 if count
== 2 then break
430 # If square, 2nd triangle
432 # This may not support all vertices ordering.
433 if face
.vertices
.length
> 3 then
434 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
435 var i
= e
.texture_coord_index
440 var tc
= obj_def
.texture_coords
[i-1
]
452 # Textures loaded from .mtl files for models
453 var asset_textures_by_name
= new Map[String, TextureAsset]
455 # Loaded .mtl material definitions, sorted by path in assets and material name
456 private var mtl_libs
= new Map[String, Map[String, MtlDef]]
458 # All instantiated asset models
459 var models
= new Set[ModelAsset]
461 # Blue cube of 1 unit on each side, acting as placeholder for models failing to load
463 # This model can be freely used by any `Actor` as placeholder or for debugging.
464 var placeholder_model
= new LeafModel(new Cube, new Material) is lazy