From 57c4c1ad791acb2fd307c6784657fda43c208919 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alexis=20Laferri=C3=A8re?= Date: Mon, 31 Jul 2017 23:22:57 -0400 Subject: [PATCH] gamnit: keep the named sub-object composing an obj object MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Alexis Laferrière --- lib/gamnit/depth/depth_core.nit | 3 + lib/gamnit/depth/more_models.nit | 152 ++++++++++++++++++++++++-------------- lib/gamnit/model_parsers/obj.nit | 38 +++++++--- 3 files changed, 127 insertions(+), 66 deletions(-) diff --git a/lib/gamnit/depth/depth_core.nit b/lib/gamnit/depth/depth_core.nit index 56e0575..588b321 100644 --- a/lib/gamnit/depth/depth_core.nit +++ b/lib/gamnit/depth/depth_core.nit @@ -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` diff --git a/lib/gamnit/depth/more_models.nit b/lib/gamnit/depth/more_models.nit index eb148c9..9ec6d2e 100644 --- a/lib/gamnit/depth/more_models.nit +++ b/lib/gamnit/depth/more_models.nit @@ -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 diff --git a/lib/gamnit/model_parsers/obj.nit b/lib/gamnit/model_parsers/obj.nit index e9f8499..069c653 100644 --- a/lib/gamnit/model_parsers/obj.nit +++ b/lib/gamnit/model_parsers/obj.nit @@ -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 -- 1.7.9.5