gamnit: move `new SmoothMaterial::default` to `new Material`
[nit.git] / lib / gamnit / depth / more_models.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Services to load models from the assets folder
16 module more_models
17
18 intrude import depth_core
19
20 import gamnit::obj
21 import gamnit::mtl
22
23 import more_materials
24 import more_meshes
25
26 redef class Model
27 # Prepare to load a model from the assets folder
28 new(path: Text) do return new ModelAsset(path.to_s)
29 end
30
31 # Model loaded from a file in the asset folder
32 #
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`.
36 class ModelAsset
37 super Model
38 super Asset
39
40 init do models.add self
41
42 redef fun load
43 do
44 var ext = path.file_extension
45 if ext == "obj" then
46 load_obj_file
47 else
48 print_error "Model failed to load: Extension '{ext or else "null"}' unrecognized"
49 end
50
51 if leaves.is_empty then
52 # Nothing was loaded, use a cube with the default material
53 var leaf = placeholder_model
54 leaves.add leaf
55 end
56 end
57
58 private fun load_obj_file
59 do
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 Material)
66 return
67 end
68
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 Material)
75 return
76 end
77
78 # Check for errors
79 if debug_gamnit then assert obj_def.is_coherent
80
81 # Build models
82 var converter = new ModelFromObj(path, obj_def)
83 converter.models leaves
84 end
85
86 redef var leaves = new Array[LeafModel]
87 end
88
89 # Short-lived service to convert an `ObjDef` to `models`
90 #
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
94
95 # Path to the .obj file in the assets folder, used to find .mtl files
96 var path: String
97
98 # Parsed .obj definition
99 var obj_def: ObjDef
100
101 fun models(array: Array[LeafModel])
102 do
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
108
109 var full_name = ""
110 if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
111
112 mtl_to_faces[full_name].add face
113 end
114
115 # Load material libs
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)
122 lib_asset.load
123
124 var error = lib_asset.error
125 if error != null then
126 print_error error.to_s
127 continue
128 end
129
130 var mtl_parser = new MtlFileParser(lib_asset.to_s)
131 var mtl_lib = mtl_parser.parse
132 mtl_libs[name] = mtl_lib
133 end
134
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
139
140 # Create mesh
141 var mesh = new Mesh
142 mesh.vertices = vertices(faces)
143 mesh.normals = normals(faces)
144 mesh.texture_coords = texture_coords(faces)
145
146 # Material
147 var mtl_def = null
148
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)
154 if mtl != null then
155 mtl_def = mtl
156
157 for e in mtl.maps do
158 texture_names.add self.path.dirname / e
159 end
160 else
161 print_error "mtl '{mtl_name}' not found in '{mtl_lib_name}'"
162 end
163 end
164
165 mesh_to_mtl[mesh] = mtl_def
166 end
167
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
173 end
174 end
175
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
180
181 var ambient = mtl.ambient.to_a
182 ambient.add 1.0
183
184 var diffuse = mtl.diffuse.to_a
185 diffuse.add 1.0
186
187 var specular = mtl.specular.to_a
188 specular.add 1.0
189
190 var material = new TexturedMaterial(ambient, diffuse, specular)
191 materials[mtl] = material
192
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]
197 end
198
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]
203 end
204
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]
209 end
210 end
211
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 Material
216
217 var model = new LeafModel(mesh, material)
218 array.add model
219 end
220 end
221
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
225
226 var vertices = new Array[Float]
227 for face in faces do
228
229 # 1st triangle
230 var count = 0
231 for e in face.vertices do
232 var i = e.vertex_point_index - 1
233 var v = obj_def.vertex_points[i]
234
235 vertices.add v.x
236 vertices.add v.y
237 vertices.add v.z
238
239 if count == 2 then break
240 count += 1
241 end
242
243 # If square, 2nd triangle
244 #
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]
250
251 vertices.add v.x
252 vertices.add v.y
253 vertices.add v.z
254 end
255 end
256
257 # TODO use polygon triangulation to support larger polygons
258 end
259 return vertices
260 end
261
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
265
266 var normals = new Array[Float]
267 for face in faces do
268 # 1st triangle
269 var count = 0
270 for e in face.vertices do
271 var i = e.normal_index
272 if i == null then
273 compute_and_append_normal(normals, face)
274 else
275 var v = obj_def.normals[i-1]
276 normals.add v.x
277 normals.add v.y
278 normals.add v.z
279 end
280
281 if count == 2 then break
282 count += 1
283 end
284
285 # If square, 2nd triangle
286 #
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
291 if i == null then
292 compute_and_append_normal(normals, face)
293 else
294 var v = obj_def.normals[i-1]
295 normals.add v.x
296 normals.add v.y
297 normals.add v.z
298 end
299 end
300 end
301 end
302 return normals
303 end
304
305 # Compute the normal of `face` and append it as 3 floats to `seq`
306 #
307 # Resulting normals are not normalized.
308 fun compute_and_append_normal(seq: Sequence[Float], face: ObjFace)
309 do
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
313
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]
317
318 var vx = v2.x - v1.x
319 var vy = v2.y - v1.y
320 var vz = v2.z - v1.z
321 var wx = v3.x - v1.x
322 var wy = v3.y - v1.y
323 var wz = v3.z - v1.z
324
325 var nx = (vy*wz) - (vz*wy)
326 var ny = (vz*wx) - (vx*wz)
327 var nz = (vx*wy) - (vy*wx)
328
329 # Append to `seq`
330 seq.add nx
331 seq.add ny
332 seq.add nz
333 end
334
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
338
339 var coords = new Array[Float]
340 for face in faces do
341
342 # 1st triangle
343 var count = 0
344 for e in face.vertices do
345 var i = e.texture_coord_index
346 if i == null then
347 coords.add 0.0
348 coords.add 0.0
349 else
350 var tc = obj_def.texture_coords[i-1]
351 coords.add tc.u
352 coords.add tc.v
353 end
354
355 if count == 2 then break
356 count += 1
357 end
358
359 # If square, 2nd triangle
360 #
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
365 if i == null then
366 coords.add 0.0
367 coords.add 0.0
368 else
369 var tc = obj_def.texture_coords[i-1]
370 coords.add tc.u
371 coords.add tc.v
372 end
373 end
374 end
375 end
376 return coords
377 end
378 end
379
380 redef class Sys
381 # Textures loaded from .mtl files for models
382 var asset_textures_by_name = new Map[String, TextureAsset]
383
384 # All instantiated asset models
385 var models = new Set[ModelAsset]
386
387 # Blue cube of 1 unit on each side, acting as placeholder for models failing to load
388 #
389 # This model can be freely used by any `Actor` as placeholder or for debugging.
390 var placeholder_model = new LeafModel(new Cube, new Material) is lazy
391 end