redef fun load
do
+ if loaded then return
+
var ext = path.file_extension
if ext == "obj" then
load_obj_file
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
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
# 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
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
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
# 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
# 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
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
# 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
# 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
# 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")
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