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 Mesh.cube`.
35 # If the material is missing or it failed to load, `material` is set to a `new SimpleMaterial.default`.
40 init do models
.add
self
42 private var loaded
= false
46 var ext
= path
.file_extension
50 errors
.add
new Error("Model at '{path}' failed to load: Extension '{ext or else "null"}' unrecognized")
53 if leaves_cache
.is_empty
then
54 # Nothing was loaded, use a cube with the default material
55 var leaf
= placeholder_model
62 private fun load_obj_file
64 # Read .obj description from assets
65 var text_asset
= new TextAsset(path
)
66 var content
= text_asset
.to_s
67 if content
.is_empty
then
68 errors
.add
new Error("Model failed to load: Asset empty at '{self.path}'")
69 leaves_cache
.add
new LeafModel(new Cube, new Material)
73 # Parse .obj description
74 var parser
= new ObjFileParser(content
)
75 var obj_def
= parser
.parse
76 if obj_def
== null then
77 errors
.add
new Error("Model failed to load: .obj format error on '{self.path}'")
78 leaves_cache
.add
new LeafModel(new Cube, new Material)
83 if debug_gamnit
then assert obj_def
.is_coherent
86 var converter
= new ModelFromObj(path
, obj_def
)
87 converter
.models leaves_cache
88 errors
.add_all converter
.errors
97 # Print errors when lazy loading only
98 if errors
.length
== 1 then
99 print_error errors
.first
100 else if errors
.length
> 1 then
101 print_error
"Loading model at '{path}' raised {errors.length} errors:\n* "
102 print_error errors
.join
("\n* ")
109 private var leaves_cache
= new Array[LeafModel]
112 # Short-lived service to convert an `ObjDef` to `models`
114 # Limitations: This service only support faces with 3 or 4 vertices.
115 # Faces with more vertices should be triangulated by the modeling tool.
116 private class ModelFromObj
118 # Path to the .obj file in the assets folder, used to find .mtl files
121 # Parsed .obj definition
124 # Errors raised by calls to `models`
125 var errors
= new Array[Error]
127 # Fill `leaves` with models described in `obj_def`
128 fun models
(leaves
: Array[LeafModel])
130 # Sort faces by material
131 var mtl_to_faces
= new MultiHashMap[String, ObjFace]
132 for face
in obj_def
.faces
do
133 var mtl_lib_name
= face
.material_lib
134 var mtl_name
= face
.material_name
137 if mtl_lib_name
!= null and mtl_name
!= null then full_name
= mtl_lib_name
/ mtl_name
139 mtl_to_faces
[full_name
].add face
143 var mtl_libs
= sys
.mtl_libs
144 var lib_names
= obj_def
.material_libs
145 for name
in lib_names
do
146 var asset_path
= self.path
.dirname
/ name
147 var lib_asset
= new TextAsset(asset_path
)
150 var error
= lib_asset
.error
151 if error
!= null then
156 var mtl_parser
= new MtlFileParser(lib_asset
.to_s
)
157 var mtl_lib
= mtl_parser
.parse
158 mtl_libs
[asset_path
] = mtl_lib
161 # Create 1 mesh per material, and prepare materials
162 var mesh_to_mtl
= new Map[Mesh, nullable MtlDef]
163 var texture_names
= new Set[String]
164 for full_name
, faces
in mtl_to_faces
do
168 mesh
.vertices
= vertices
(faces
)
169 mesh
.normals
= normals
(faces
)
170 mesh
.texture_coords
= texture_coords
(faces
)
175 var mtl_lib_name
= faces
.first
.material_lib
176 var mtl_name
= faces
.first
.material_name
177 if mtl_lib_name
!= null and mtl_name
!= null then
178 var asset_path
= self.path
.dirname
/ mtl_lib_name
179 var mtl_lib
= mtl_libs
[asset_path
]
180 var mtl
= mtl_lib
.get_or_null
(mtl_name
)
185 texture_names
.add
self.path
.dirname
/ e
188 errors
.add
new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
192 mesh_to_mtl
[mesh
] = mtl_def
195 # Load textures need for these materials
196 for name
in texture_names
do
197 if not asset_textures_by_name
.keys
.has
(name
) then
198 var tex
= new TextureAsset(name
)
199 asset_textures_by_name
[name
] = tex
202 var error
= tex
.error
203 if error
!= null then errors
.add error
207 # Create final `Materials` from defs and textures
208 var materials
= new Map[MtlDef, Material]
209 for mtl
in mesh_to_mtl
.values
do
210 if mtl
== null then continue
212 var ambient
= mtl
.ambient
.to_a
215 var diffuse
= mtl
.diffuse
.to_a
218 var specular
= mtl
.specular
.to_a
221 var material
= new TexturedMaterial(ambient
, diffuse
, specular
)
222 materials
[mtl
] = material
224 var tex_name
= mtl
.map_ambient
225 if tex_name
!= null then
226 tex_name
= self.path
.dirname
/ tex_name
227 material
.ambient_texture
= asset_textures_by_name
[tex_name
]
230 tex_name
= mtl
.map_diffuse
231 if tex_name
!= null then
232 tex_name
= self.path
.dirname
/ tex_name
233 material
.diffuse_texture
= asset_textures_by_name
[tex_name
]
236 tex_name
= mtl
.map_specular
237 if tex_name
!= null then
238 tex_name
= self.path
.dirname
/ tex_name
239 material
.specular_texture
= asset_textures_by_name
[tex_name
]
243 # Create models and store them
244 for mesh
, mtl_def
in mesh_to_mtl
do
245 var material
= materials
.get_or_null
(mtl_def
)
246 if material
== null then material
= new Material
248 var model
= new LeafModel(mesh
, material
)
253 # Compute the vertices coordinates of `faces` in a flat `Array[Float]`
254 fun vertices
(faces
: Array[ObjFace]): Array[Float] do
255 var obj_def
= obj_def
257 var vertices
= new Array[Float]
262 for e
in face
.vertices
do
263 var i
= e
.vertex_point_index
- 1
264 var v
= obj_def
.vertex_points
[i
]
270 if count
== 2 then break
274 # If square, 2nd triangle
276 # This may not support all vertices ordering.
277 if face
.vertices
.length
> 3 then
278 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
279 var i
= e
.vertex_point_index
- 1
280 var v
= obj_def
.vertex_points
[i
]
288 # TODO use polygon triangulation to support larger polygons
293 # Compute the normals of `faces` in a flat `Array[Float]`
294 fun normals
(faces
: Array[ObjFace]): Array[Float] do
295 var obj_def
= obj_def
297 var normals
= new Array[Float]
301 for e
in face
.vertices
do
302 var i
= e
.normal_index
304 compute_and_append_normal
(normals
, face
)
306 var v
= obj_def
.normals
[i-1
]
312 if count
== 2 then break
316 # If square, 2nd triangle
318 # This may not support all vertices ordering.
319 if face
.vertices
.length
> 3 then
320 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
321 var i
= e
.normal_index
323 compute_and_append_normal
(normals
, face
)
325 var v
= obj_def
.normals
[i-1
]
336 # Compute the normal of `face` and append it as 3 floats to `seq`
338 # Resulting normals are not normalized.
339 fun compute_and_append_normal
(seq
: Sequence[Float], face
: ObjFace)
341 var i1
= face
.vertices
[0].vertex_point_index
342 var i2
= face
.vertices
[1].vertex_point_index
343 var i3
= face
.vertices
[2].vertex_point_index
345 var v1
= obj_def
.vertex_points
[i1-1
]
346 var v2
= obj_def
.vertex_points
[i2-1
]
347 var v3
= obj_def
.vertex_points
[i3-1
]
356 var nx
= (vy
*wz
) - (vz
*wy
)
357 var ny
= (vz
*wx
) - (vx
*wz
)
358 var nz
= (vx
*wy
) - (vy
*wx
)
366 # Compute the texture coordinates of `faces` in a flat `Array[Float]`
367 fun texture_coords
(faces
: Array[ObjFace]): Array[Float] do
368 var obj_def
= obj_def
370 var coords
= new Array[Float]
375 for e
in face
.vertices
do
376 var i
= e
.texture_coord_index
381 var tc
= obj_def
.texture_coords
[i-1
]
386 if count
== 2 then break
390 # If square, 2nd triangle
392 # This may not support all vertices ordering.
393 if face
.vertices
.length
> 3 then
394 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
395 var i
= e
.texture_coord_index
400 var tc
= obj_def
.texture_coords
[i-1
]
412 # Textures loaded from .mtl files for models
413 var asset_textures_by_name
= new Map[String, TextureAsset]
415 # Loaded .mtl material definitions, sorted by path in assets and material name
416 private var mtl_libs
= new Map[String, Map[String, MtlDef]]
418 # All instantiated asset models
419 var models
= new Set[ModelAsset]
421 # Blue cube of 1 unit on each side, acting as placeholder for models failing to load
423 # This model can be freely used by any `Actor` as placeholder or for debugging.
424 var placeholder_model
= new LeafModel(new Cube, new Material) is lazy