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
44 var ext
= path
.file_extension
48 print_error
"Model failed to load: Extension '{ext or else "null"}' unrecognized"
51 if leaves
.is_empty
then
52 # Nothing was loaded, use a cube with the default material
53 var leaf
= placeholder_model
58 private fun load_obj_file
60 # Read .obj description from assets
61 var text_asset
= new TextAsset(path
)
62 var content
= text_asset
.to_s
63 if content
.is_empty
then
64 print_error
"Model failed to load: Asset empty at '{self.path}'"
65 leaves
.add
new LeafModel(new Cube, new SmoothMaterial.default
)
69 # Parse .obj description
70 var parser
= new ObjFileParser(content
)
71 var obj_def
= parser
.parse
72 if obj_def
== null then
73 print_error
"Model failed to load: .obj format error on '{self.path}'"
74 leaves
.add
new LeafModel(new Cube, new SmoothMaterial.default
)
79 if debug_gamnit
then assert obj_def
.is_coherent
82 var converter
= new ModelFromObj(path
, obj_def
)
83 converter
.models leaves
86 redef var leaves
= new Array[LeafModel]
89 # Short-lived service to convert an `ObjDef` to `models`
91 # Limitations: This service only support faces with 3 or 4 vertices.
92 # Faces with more vertices should be triangulated by the modeling tool.
93 private class ModelFromObj
95 # Path to the .obj file in the assets folder, used to find .mtl files
98 # Parsed .obj definition
101 fun models
(array
: Array[LeafModel])
103 # Sort faces by material
104 var mtl_to_faces
= new MultiHashMap[String, ObjFace]
105 for face
in obj_def
.faces
do
106 var mtl_lib_name
= face
.material_lib
107 var mtl_name
= face
.material_name
110 if mtl_lib_name
!= null and mtl_name
!= null then full_name
= mtl_lib_name
/ mtl_name
112 mtl_to_faces
[full_name
].add face
116 # TODO do not load each libs more than once
117 var mtl_libs
= new Map[String, Map[String, MtlDef]]
118 var lib_names
= obj_def
.material_libs
119 for name
in lib_names
do
120 var lib_path
= self.path
.dirname
/ name
121 var lib_asset
= new TextAsset(lib_path
)
124 var error
= lib_asset
.error
125 if error
!= null then
126 print_error error
.to_s
130 var mtl_parser
= new MtlFileParser(lib_asset
.to_s
)
131 var mtl_lib
= mtl_parser
.parse
132 mtl_libs
[name
] = mtl_lib
135 # Create 1 mesh per material, and prepare materials
136 var mesh_to_mtl
= new Map[Mesh, nullable MtlDef]
137 var texture_names
= new Set[String]
138 for full_name
, faces
in mtl_to_faces
do
142 mesh
.vertices
= vertices
(faces
)
143 mesh
.normals
= normals
(faces
)
144 mesh
.texture_coords
= texture_coords
(faces
)
149 var mtl_lib_name
= faces
.first
.material_lib
150 var mtl_name
= faces
.first
.material_name
151 if mtl_lib_name
!= null and mtl_name
!= null then
152 var mtl_lib
= mtl_libs
[mtl_lib_name
]
153 var mtl
= mtl_lib
.get_or_null
(mtl_name
)
158 texture_names
.add
self.path
.dirname
/ e
161 print_error
"mtl '{mtl_name}' not found in '{mtl_lib_name}'"
165 mesh_to_mtl
[mesh
] = mtl_def
168 # Load textures need for these materials
169 for name
in texture_names
do
170 if not asset_textures_by_name
.keys
.has
(name
) then
171 var tex
= new TextureAsset(name
)
172 asset_textures_by_name
[name
] = tex
176 # Create final `Materials` from defs and textures
177 var materials
= new Map[MtlDef, Material]
178 for mtl
in mesh_to_mtl
.values
do
179 if mtl
== null then continue
181 var ambient
= mtl
.ambient
.to_a
184 var diffuse
= mtl
.diffuse
.to_a
187 var specular
= mtl
.specular
.to_a
190 var material
= new TexturedMaterial(ambient
, diffuse
, specular
)
191 materials
[mtl
] = material
193 var tex_name
= mtl
.map_ambient
194 if tex_name
!= null then
195 tex_name
= self.path
.dirname
/ tex_name
196 material
.ambient_texture
= asset_textures_by_name
[tex_name
]
199 tex_name
= mtl
.map_diffuse
200 if tex_name
!= null then
201 tex_name
= self.path
.dirname
/ tex_name
202 material
.diffuse_texture
= asset_textures_by_name
[tex_name
]
205 tex_name
= mtl
.map_specular
206 if tex_name
!= null then
207 tex_name
= self.path
.dirname
/ tex_name
208 material
.specular_texture
= asset_textures_by_name
[tex_name
]
212 # Create models and store them
213 for mesh
, mtl_def
in mesh_to_mtl
do
214 var material
= materials
.get_or_null
(mtl_def
)
215 if material
== null then material
= new SmoothMaterial.default
217 var model
= new LeafModel(mesh
, material
)
222 # Compute the vertices coordinates of `faces` in a flat `Array[Float]`
223 fun vertices
(faces
: Array[ObjFace]): Array[Float] do
224 var obj_def
= obj_def
226 var vertices
= new Array[Float]
231 for e
in face
.vertices
do
232 var i
= e
.vertex_point_index
- 1
233 var v
= obj_def
.vertex_points
[i
]
239 if count
== 2 then break
243 # If square, 2nd triangle
245 # This may not support all vertices ordering.
246 if face
.vertices
.length
> 3 then
247 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
248 var i
= e
.vertex_point_index
- 1
249 var v
= obj_def
.vertex_points
[i
]
257 # TODO use polygon triangulation to support larger polygons
262 # Compute the normals of `faces` in a flat `Array[Float]`
263 fun normals
(faces
: Array[ObjFace]): Array[Float] do
264 var obj_def
= obj_def
266 var normals
= new Array[Float]
270 for e
in face
.vertices
do
271 var i
= e
.normal_index
273 compute_and_append_normal
(normals
, face
)
275 var v
= obj_def
.normals
[i-1
]
281 if count
== 2 then break
285 # If square, 2nd triangle
287 # This may not support all vertices ordering.
288 if face
.vertices
.length
> 3 then
289 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
290 var i
= e
.normal_index
292 compute_and_append_normal
(normals
, face
)
294 var v
= obj_def
.normals
[i-1
]
305 # Compute the normal of `face` and append it as 3 floats to `seq`
307 # Resulting normals are not normalized.
308 fun compute_and_append_normal
(seq
: Sequence[Float], face
: ObjFace)
310 var i1
= face
.vertices
[0].vertex_point_index
311 var i2
= face
.vertices
[1].vertex_point_index
312 var i3
= face
.vertices
[2].vertex_point_index
314 var v1
= obj_def
.vertex_points
[i1-1
]
315 var v2
= obj_def
.vertex_points
[i2-1
]
316 var v3
= obj_def
.vertex_points
[i3-1
]
325 var nx
= (vy
*wz
) - (vz
*wy
)
326 var ny
= (vz
*wx
) - (vx
*wz
)
327 var nz
= (vx
*wy
) - (vy
*wx
)
335 # Compute the texture coordinates of `faces` in a flat `Array[Float]`
336 fun texture_coords
(faces
: Array[ObjFace]): Array[Float] do
337 var obj_def
= obj_def
339 var coords
= new Array[Float]
344 for e
in face
.vertices
do
345 var i
= e
.texture_coord_index
350 var tc
= obj_def
.texture_coords
[i-1
]
355 if count
== 2 then break
359 # If square, 2nd triangle
361 # This may not support all vertices ordering.
362 if face
.vertices
.length
> 3 then
363 for e
in [face
.vertices
[0], face
.vertices
[2], face
.vertices
[3]] do
364 var i
= e
.texture_coord_index
369 var tc
= obj_def
.texture_coords
[i-1
]
381 # Textures loaded from .mtl files for models
382 var asset_textures_by_name
= new Map[String, TextureAsset]
384 # All instantiated asset models
385 var models
= new Set[ModelAsset]
387 # Blue cube of 1 unit on each side, acting as placeholder for models failing to load
389 # This model can be freely used by any `Actor` as placeholder or for debugging.
390 var placeholder_model
= new LeafModel(new Cube, new SmoothMaterial.default
) is lazy