gamnit: keep the named sub-object composing an obj object
authorAlexis Laferrière <alexis.laf@xymus.net>
Tue, 1 Aug 2017 03:22:57 +0000 (23:22 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Mon, 25 Sep 2017 15:11:11 +0000 (11:11 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_models.nit
lib/gamnit/model_parsers/obj.nit

index 56e0575..588b321 100644 (file)
@@ -113,6 +113,9 @@ abstract class Model
        # Usually, there is one `LeafModel` per material.
        # At each frame, each material is asked to draw all the live `LeafModel` instaces.
        fun leaves: Array[LeafModel] is abstract
+
+       # Sub-models with names, usually declared in the asset file
+       var named_parts = new Map[Text, Model]
 end
 
 # Model composed of one or many other `LeafModel`
index eb148c9..9ec6d2e 100644 (file)
@@ -43,6 +43,8 @@ class ModelAsset
 
        redef fun load
        do
+               if loaded then return
+
                var ext = path.file_extension
                if ext == "obj" then
                        load_obj_file
@@ -59,6 +61,22 @@ class ModelAsset
                loaded = true
        end
 
+       private fun lazy_load
+       do
+               if loaded then return
+
+               # Lazy load
+               load
+
+               # Print errors when lazy loading only
+               if errors.length == 1 then
+                       print_error errors.first
+               else if errors.length > 1 then
+                       print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
+                       print_error errors.join("\n* ")
+               end
+       end
+
        private fun load_obj_file
        do
                # Read .obj description from assets
@@ -83,37 +101,33 @@ class ModelAsset
                if debug_gamnit then assert obj_def.is_coherent
 
                # Build models
-               var converter = new ModelFromObj(path, obj_def)
-               converter.models leaves_cache
+               var converter = new BuildModelFromObj(path, obj_def)
+               converter.fill_leaves self
                errors.add_all converter.errors
        end
 
        redef fun leaves
        do
-               if not loaded then
-                       # Lazy load
-                       load
-
-                       # Print errors when lazy loading only
-                       if errors.length == 1 then
-                               print_error errors.first
-                       else if errors.length > 1 then
-                               print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
-                               print_error errors.join("\n* ")
-                       end
-               end
-
+               lazy_load
                return leaves_cache
        end
 
        private var leaves_cache = new Array[LeafModel]
+
+       redef fun named_parts
+       do
+               lazy_load
+               return named_leaves_cache
+       end
+
+       private var named_leaves_cache = new Map[String, Model]
 end
 
-# Short-lived service to convert an `ObjDef` to `models`
+# Short-lived service to convert an `ObjDef` to `fill_leaves`
 #
 # Limitations: This service only support faces with 3 or 4 vertices.
 # Faces with more vertices should be triangulated by the modeling tool.
-private class ModelFromObj
+private class BuildModelFromObj
 
        # Path to the .obj file in the assets folder, used to find .mtl files
        var path: String
@@ -121,22 +135,28 @@ private class ModelFromObj
        # Parsed .obj definition
        var obj_def: ObjDef
 
-       # Errors raised by calls to `models`
+       # Errors raised by calls to `fill_leaves`
        var errors = new Array[Error]
 
-       # Fill `leaves` with models described in `obj_def`
-       fun models(leaves: Array[LeafModel])
+       # Fill `leaves` with objects described in `obj_def`
+       fun fill_leaves(target_model: ModelAsset)
        do
-               # Sort faces by material
-               var mtl_to_faces = new MultiHashMap[String, ObjFace]
-               for face in obj_def.faces do
-                       var mtl_lib_name = face.material_lib
-                       var mtl_name = face.material_name
-
-                       var full_name = ""
-                       if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+               var leaves = target_model.leaves_cache
 
-                       mtl_to_faces[full_name].add face
+               # Sort faces by material
+               var obj_mtl_to_faces = new Map[ObjObj, MultiHashMap[String, ObjFace]]
+               for obj in obj_def.objects do
+                       var mtl_to_faces = new MultiHashMap[String, ObjFace]
+                       obj_mtl_to_faces[obj] = mtl_to_faces
+                       for face in obj.faces do
+                               var mtl_lib_name = face.material_lib
+                               var mtl_name = face.material_name
+
+                               var full_name = ""
+                               if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+
+                               mtl_to_faces[full_name].add face
+                       end
                end
 
                # Load material libs
@@ -158,38 +178,43 @@ private class ModelFromObj
                        mtl_libs[asset_path] = mtl_lib
                end
 
-               # Create 1 mesh per material, and prepare materials
+               # Create 1 mesh per material per object, and prepare materials
                var mesh_to_mtl = new Map[Mesh, nullable MtlDef]
+               var mesh_to_name = new Map[Mesh, String]
                var texture_names = new Set[String]
-               for full_name, faces in mtl_to_faces do
-
-                       # Create mesh
-                       var mesh = new Mesh
-                       mesh.vertices = vertices(faces)
-                       mesh.normals = normals(faces)
-                       mesh.texture_coords = texture_coords(faces)
-
-                       # Material
-                       var mtl_def = null
-
-                       var mtl_lib_name = faces.first.material_lib
-                       var mtl_name = faces.first.material_name
-                       if mtl_lib_name != null and mtl_name != null then
-                               var asset_path = self.path.dirname / mtl_lib_name
-                               var mtl_lib = mtl_libs[asset_path]
-                               var mtl = mtl_lib.get_or_null(mtl_name)
-                               if mtl != null then
-                                       mtl_def = mtl
-
-                                       for e in mtl.maps do
-                                               texture_names.add self.path.dirname / e
+               for obj in obj_def.objects do
+                       var mtl_to_faces = obj_mtl_to_faces[obj]
+                       for mtl_path, faces in mtl_to_faces do
+
+                               # Create mesh
+                               var mesh = new Mesh
+                               mesh.vertices = vertices(faces)
+                               mesh.normals = normals(faces)
+                               mesh.texture_coords = texture_coords(faces)
+
+                               # Material
+                               var mtl_def = null
+
+                               var mtl_lib_name = faces.first.material_lib
+                               var mtl_name = faces.first.material_name
+                               if mtl_lib_name != null and mtl_name != null then
+                                       var asset_path = self.path.dirname / mtl_lib_name
+                                       var mtl_lib = mtl_libs[asset_path]
+                                       var mtl = mtl_lib.get_or_null(mtl_name)
+                                       if mtl != null then
+                                               mtl_def = mtl
+
+                                               for e in mtl.maps do
+                                                       texture_names.add self.path.dirname / e
+                                               end
+                                       else
+                                               errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
                                        end
-                               else
-                                       errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
                                end
-                       end
 
-                       mesh_to_mtl[mesh] = mtl_def
+                               mesh_to_mtl[mesh] = mtl_def
+                               mesh_to_name[mesh] = obj.name
+                       end
                end
 
                # Load textures need for these materials
@@ -241,12 +266,27 @@ private class ModelFromObj
                end
 
                # Create models and store them
+               var name_to_leaves = new MultiHashMap[String, LeafModel]
                for mesh, mtl_def in mesh_to_mtl do
+
                        var material = materials.get_or_null(mtl_def)
                        if material == null then material = new Material
 
                        var model = new LeafModel(mesh, material)
                        leaves.add model
+
+                       name_to_leaves[mesh_to_name[mesh]].add model
+               end
+
+               # Collect objects with a name
+               for name, models in name_to_leaves do
+                       if models.length == 1 then
+                               target_model.named_leaves_cache[name] = models.first
+                       else
+                               var named_model = new CompositeModel
+                               named_model.leaves.add_all models
+                               target_model.named_leaves_cache[name] = named_model
+                       end
                end
        end
 
index e9f8499..069c653 100644 (file)
@@ -47,6 +47,7 @@ import model_parser_base
 # var parser = new ObjFileParser(obj_src)
 # var parsed_obj = parser.parse
 # assert parsed_obj.is_coherent
+# assert parsed_obj.objects.first.name == "Cube"
 # ~~~
 class ObjFileParser
        super StringProcessor
@@ -60,6 +61,7 @@ class ObjFileParser
        # Execute parsing of `src` to extract an `ObjDef`
        fun parse: nullable ObjDef
        do
+               var obj_obj = null
                while not eof do
                        var token = read_token
                        if token.is_empty or token == "#" then
@@ -78,7 +80,7 @@ class ObjFileParser
                                geometry.params.add vec
                        else if token == "f" then # Faces
                                var face = read_face
-                               geometry.faces.add face
+                               if obj_obj != null then obj_obj.faces.add face
                        else if token == "mtllib" then
                                current_material_lib = read_until_eol_or_comment
                        else if token == "usemtl" then
@@ -87,6 +89,8 @@ class ObjFileParser
                        # TODO other line type headers
                        else if token == "s" then
                        else if token == "o" then
+                               obj_obj = new ObjObj(read_until_eol_or_comment)
+                               geometry.objects.add obj_obj
                        else if token == "g" then
                        end
                        skip_eol
@@ -152,15 +156,17 @@ class ObjDef
        # Surface parameters
        var params = new Array[Vec3]
 
-       # Faces
-       var faces = new Array[ObjFace]
+       # Sub-objects
+       var objects = new Array[ObjObj]
 
        # Relative paths to referenced material libraries
        fun material_libs: Set[String] do
                var libs = new Set[String]
-               for face in faces do
-                       var lib = face.material_lib
-                       if lib != null then libs.add lib
+               for obj in objects do
+                       for face in obj.faces do
+                               var lib = face.material_lib
+                               if lib != null then libs.add lib
+                       end
                end
                return libs
        end
@@ -173,10 +179,12 @@ class ObjDef
        # be executed at each execution of a game.
        fun is_coherent: Bool
        do
-               for f in faces do
-                       if f.vertices.length < 3 then return error("ObjFace with less than 3 vertices")
+               for obj in objects do
+                       for f in obj.faces do
+                               if f.vertices.length < 3 then return error("Face with less than 3 vertices")
+                       end
 
-                       for v in f.vertices do
+                       for f in obj.faces do for v in f.vertices do
                                var i = v.vertex_point_index
                                if i < 1 then return error("Vertex point index < 1")
                                if i > vertex_points.length then return error("Vertex point index > than length")
@@ -205,9 +213,19 @@ class ObjDef
        end
 end
 
+# Sub-object within an `ObjDef`
+class ObjObj
+
+       # Sub-object name as declared in the source file
+       var name: String
+
+       # Sub-object faces
+       var faces = new Array[ObjFace]
+end
+
 # Flat surface of an `ObjDef`
 class ObjFace
-       # Vertex composing this surface, thene should be 3 or more
+       # Vertex composing this surface, there should be 3 or more
        var vertices = new Array[ObjVertex]
 
        # Relative path to the .mtl material lib