# 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 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 `fill_leaves`
var errors = new Array[Error]
# Fill `leaves` with objects described in `obj_def`
fun fill_leaves(target_model: ModelAsset)
do
var leaves = target_model.leaves_cache
# 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
var mtl_libs = sys.mtl_libs
var lib_names = obj_def.material_libs
for name in lib_names do
var asset_path = self.path.dirname / name
var lib_asset = new TextAsset(asset_path)
lib_asset.load
var error = lib_asset.error
if error != null then
errors.add error
continue
end
var mtl_parser = new MtlFileParser(lib_asset.to_s)
var mtl_lib = mtl_parser.parse
mtl_libs[asset_path] = mtl_lib
end
# 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 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
end
mesh_to_mtl[mesh] = mtl_def
mesh_to_name[mesh] = obj.name
end
end
# Load textures need for these materials
for name in texture_names do
if not asset_textures_by_name.keys.has(name) then
var tex = new TextureAsset(name)
asset_textures_by_name[name] = tex
tex.load
var error = tex.error
if error != null then errors.add error
end
end
# Create final `Materials` from defs and textures
var materials = new Map[MtlDef, Material]
for mtl in mesh_to_mtl.values do
if mtl == null then continue
var ambient = mtl.ambient.to_a
ambient.add 1.0
var diffuse = mtl.diffuse.to_a
diffuse.add 1.0
var specular = mtl.specular.to_a
specular.add 1.0
var material = new TexturedMaterial(ambient, diffuse, specular)
materials[mtl] = material
var tex_name = mtl.map_ambient
if tex_name != null then
tex_name = self.path.dirname / tex_name
material.ambient_texture = asset_textures_by_name[tex_name]
end
tex_name = mtl.map_diffuse
if tex_name != null then
tex_name = self.path.dirname / tex_name
material.diffuse_texture = asset_textures_by_name[tex_name]
end
tex_name = mtl.map_specular
if tex_name != null then
tex_name = self.path.dirname / tex_name
material.specular_texture = asset_textures_by_name[tex_name]
end
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
# Compute the vertices coordinates of `faces` in a flat `Array[Float]`
fun vertices(faces: Array[ObjFace]): Array[Float] do
var obj_def = obj_def
var vertices = new Array[Float]
for face in faces do
# 1st triangle
var count = 0
for e in face.vertices do
var i = e.vertex_point_index - 1
var v = obj_def.vertex_points[i]
vertices.add v.x
vertices.add v.y
vertices.add v.z
if count == 2 then break
count += 1
end
# If square, 2nd triangle
#
# This may not support all vertices ordering.
if face.vertices.length > 3 then
for e in [face.vertices[0], face.vertices[2], face.vertices[3]] do
var i = e.vertex_point_index - 1
var v = obj_def.vertex_points[i]
vertices.add v.x
vertices.add v.y
vertices.add v.z
end
end
# TODO use polygon triangulation to support larger polygons
end
return vertices
end
# Compute the normals of `faces` in a flat `Array[Float]`
fun normals(faces: Array[ObjFace]): Array[Float] do
var obj_def = obj_def
var normals = new Array[Float]
for face in faces do
# 1st triangle
var count = 0
for e in face.vertices do
var i = e.normal_index
if i == null then
compute_and_append_normal(normals, face)
else
var v = obj_def.normals[i-1]
normals.add v.x
normals.add v.y
normals.add v.z
end
if count == 2 then break
count += 1
end
# If square, 2nd triangle
#
# This may not support all vertices ordering.
if face.vertices.length > 3 then
for e in [face.vertices[0], face.vertices[2], face.vertices[3]] do
var i = e.normal_index
if i == null then
compute_and_append_normal(normals, face)
else
var v = obj_def.normals[i-1]
normals.add v.x
normals.add v.y
normals.add v.z
end
end
end
end
return normals
end
# Compute the normal of `face` and append it as 3 floats to `seq`
#
# Resulting normals are not normalized.
fun compute_and_append_normal(seq: Sequence[Float], face: ObjFace)
do
var i1 = face.vertices[0].vertex_point_index
var i2 = face.vertices[1].vertex_point_index
var i3 = face.vertices[2].vertex_point_index
var v1 = obj_def.vertex_points[i1-1]
var v2 = obj_def.vertex_points[i2-1]
var v3 = obj_def.vertex_points[i3-1]
var vx = v2.x - v1.x
var vy = v2.y - v1.y
var vz = v2.z - v1.z
var wx = v3.x - v1.x
var wy = v3.y - v1.y
var wz = v3.z - v1.z
var nx = (vy*wz) - (vz*wy)
var ny = (vz*wx) - (vx*wz)
var nz = (vx*wy) - (vy*wx)
# Append to `seq`
seq.add nx
seq.add ny
seq.add nz
end
# Compute the texture coordinates of `faces` in a flat `Array[Float]`
fun texture_coords(faces: Array[ObjFace]): Array[Float] do
var obj_def = obj_def
var coords = new Array[Float]
for face in faces do
# 1st triangle
var count = 0
for e in face.vertices do
var i = e.texture_coord_index
if i == null then
coords.add 0.0
coords.add 0.0
else
var tc = obj_def.texture_coords[i-1]
coords.add tc.u
coords.add tc.v
end
if count == 2 then break
count += 1
end
# If square, 2nd triangle
#
# This may not support all vertices ordering.
if face.vertices.length > 3 then
for e in [face.vertices[0], face.vertices[2], face.vertices[3]] do
var i = e.texture_coord_index
if i == null then
coords.add 0.0
coords.add 0.0
else
var tc = obj_def.texture_coords[i-1]
coords.add tc.u
coords.add tc.v
end
end
end
end
return coords
end
end
lib/gamnit/depth/more_models.nit:126,1--449,3