342 lines
9.1 KiB
GDScript
342 lines
9.1 KiB
GDScript
class_name OBJLoader
|
|
extends RefCounted
|
|
|
|
#Non si vedevano i materiali e le texture dei vagoni
|
|
#Il problema era l'inversione della coordinata V nelle UV, perché:
|
|
#OBJ/Blender: V=0 è in alto
|
|
#Godot/OpenGL: V=0 è in basso
|
|
|
|
static func load_obj(path: String) -> ArrayMesh:
|
|
var file = FileAccess.open(path, FileAccess.READ)
|
|
if not file:
|
|
push_error("Impossibile aprire il file: " + path)
|
|
return null
|
|
|
|
# Directory base per i file relativi (mtl, texture)
|
|
var base_dir = path.get_base_dir()
|
|
|
|
# Dati geometrici globali
|
|
var vertices: Array[Vector3] = []
|
|
var normals: Array[Vector3] = []
|
|
var uvs: Array[Vector2] = []
|
|
|
|
# Materiali caricati dal file .mtl
|
|
var materials: Dictionary = {}
|
|
|
|
# Mesh per ogni materiale: { "nome_materiale": { "v": [], "n": [], "uv": [] } }
|
|
var surfaces: Dictionary = {}
|
|
var current_material: String = "__default__"
|
|
surfaces[current_material] = {"v": [], "n": [], "uv": []}
|
|
|
|
while not file.eof_reached():
|
|
var line = file.get_line().strip_edges()
|
|
|
|
if line.begins_with("#") or line.is_empty():
|
|
continue
|
|
|
|
var parts = line.split(" ", false)
|
|
if parts.is_empty():
|
|
continue
|
|
|
|
match parts[0]:
|
|
"mtllib": # File materiale
|
|
var mtl_path = base_dir.path_join(parts[1])
|
|
materials = _load_mtl(mtl_path)
|
|
|
|
"usemtl": # Usa materiale
|
|
current_material = parts[1] if parts.size() > 1 else "__default__"
|
|
if not surfaces.has(current_material):
|
|
surfaces[current_material] = {"v": [], "n": [], "uv": []}
|
|
|
|
"v": # Vertice
|
|
vertices.append(Vector3(
|
|
float(parts[1]),
|
|
float(parts[2]),
|
|
float(parts[3])
|
|
))
|
|
|
|
"vn": # Normale
|
|
normals.append(Vector3(
|
|
float(parts[1]),
|
|
float(parts[2]),
|
|
float(parts[3])
|
|
))
|
|
|
|
"vt": # UV
|
|
uvs.append(Vector2(
|
|
float(parts[1]),
|
|
float(parts[2]) if parts.size() > 2 else 0.0
|
|
))
|
|
|
|
"f": # Faccia
|
|
var face_vertices: Array = []
|
|
for i in range(1, parts.size()):
|
|
face_vertices.append(_parse_face_vertex(parts[i], vertices, normals, uvs))
|
|
|
|
# Triangola e aggiungi alla surface corrente
|
|
var surf = surfaces[current_material]
|
|
for i in range(1, face_vertices.size() - 1):
|
|
_add_vertex(face_vertices[0], surf["v"], surf["n"], surf["uv"])
|
|
_add_vertex(face_vertices[i], surf["v"], surf["n"], surf["uv"])
|
|
_add_vertex(face_vertices[i + 1], surf["v"], surf["n"], surf["uv"])
|
|
|
|
file.close()
|
|
return _create_mesh(surfaces, materials)
|
|
|
|
|
|
static func _load_mtl(path: String) -> Dictionary:
|
|
var materials: Dictionary = {}
|
|
var file = FileAccess.open(path, FileAccess.READ)
|
|
|
|
if not file:
|
|
push_warning("File MTL non trovato: " + path)
|
|
return materials
|
|
|
|
var current_mat: String = ""
|
|
var base_dir = path.get_base_dir()
|
|
|
|
while not file.eof_reached():
|
|
var line = file.get_line().strip_edges()
|
|
|
|
if line.begins_with("#") or line.is_empty():
|
|
continue
|
|
|
|
var parts = line.split(" ", false)
|
|
if parts.is_empty():
|
|
continue
|
|
|
|
match parts[0]:
|
|
"newmtl": # Nuovo materiale
|
|
current_mat = parts[1]
|
|
materials[current_mat] = {
|
|
"albedo": Color.WHITE,
|
|
"metallic": 0.0,
|
|
"roughness": 1.0,
|
|
"emission": Color.BLACK,
|
|
"alpha": 1.0,
|
|
"texture": "",
|
|
"normal_map": "",
|
|
"roughness_map": "",
|
|
"metallic_map": ""
|
|
}
|
|
|
|
"Kd": # Colore diffuso (albedo)
|
|
if current_mat != "":
|
|
materials[current_mat]["albedo"] = Color(
|
|
float(parts[1]),
|
|
float(parts[2]),
|
|
float(parts[3])
|
|
)
|
|
|
|
"Ks": # Colore speculare → approssima metallic
|
|
if current_mat != "":
|
|
var spec = (float(parts[1]) + float(parts[2]) + float(parts[3])) / 3.0
|
|
materials[current_mat]["metallic"] = spec
|
|
|
|
"Ns": # Esponente speculare → approssima roughness
|
|
if current_mat != "":
|
|
# Ns va da 0 a 1000 tipicamente, invertiamo per roughness
|
|
var ns = float(parts[1])
|
|
materials[current_mat]["roughness"] = 1.0 - clampf(ns / 1000.0, 0.0, 1.0)
|
|
|
|
"Ke": # Emissione
|
|
if current_mat != "":
|
|
materials[current_mat]["emission"] = Color(
|
|
float(parts[1]),
|
|
float(parts[2]),
|
|
float(parts[3])
|
|
)
|
|
|
|
"d": # Opacità
|
|
if current_mat != "":
|
|
materials[current_mat]["alpha"] = float(parts[1])
|
|
|
|
"Tr": # Trasparenza (inverso di d)
|
|
if current_mat != "":
|
|
materials[current_mat]["alpha"] = 1.0 - float(parts[1])
|
|
|
|
"map_Kd": # Texture diffusa
|
|
if current_mat != "":
|
|
var tex_path = base_dir.path_join(parts[1])
|
|
materials[current_mat]["texture"] = tex_path
|
|
|
|
"map_Bump", "bump", "map_bump": # Normal map
|
|
if current_mat != "":
|
|
# Gestisce anche "-bm 1.0 nomefile.png"
|
|
var tex_path = parts[parts.size() - 1]
|
|
materials[current_mat]["normal_map"] = base_dir.path_join(tex_path)
|
|
|
|
"map_Pr", "map_Ns": # Roughness map
|
|
if current_mat != "":
|
|
materials[current_mat]["roughness_map"] = base_dir.path_join(parts[1])
|
|
|
|
"map_Pm": # Metallic map
|
|
if current_mat != "":
|
|
materials[current_mat]["metallic_map"] = base_dir.path_join(parts[1])
|
|
|
|
file.close()
|
|
return materials
|
|
|
|
|
|
static func _parse_face_vertex(data: String, vertices: Array, normals: Array, uvs: Array) -> Dictionary:
|
|
var parts = data.split("/")
|
|
var result = {"v": null, "vt": null, "vn": null}
|
|
|
|
# Indice vertice (obbligatorio)
|
|
var v_idx = int(parts[0])
|
|
if v_idx < 0:
|
|
v_idx = vertices.size() + v_idx
|
|
else:
|
|
v_idx -= 1
|
|
result["v"] = vertices[v_idx]
|
|
|
|
# Indice UV (opzionale)
|
|
if parts.size() > 1 and not parts[1].is_empty():
|
|
var vt_idx = int(parts[1])
|
|
if vt_idx < 0:
|
|
vt_idx = uvs.size() + vt_idx
|
|
else:
|
|
vt_idx -= 1
|
|
var uv = uvs[vt_idx]
|
|
# INVERTI LA COORDINATA V
|
|
result["vt"] = Vector2(uv.x, 1.0 - uv.y)
|
|
|
|
# Indice normale (opzionale)
|
|
if parts.size() > 2 and not parts[2].is_empty():
|
|
var vn_idx = int(parts[2])
|
|
if vn_idx < 0:
|
|
vn_idx = normals.size() + vn_idx
|
|
else:
|
|
vn_idx -= 1
|
|
result["vn"] = normals[vn_idx]
|
|
|
|
return result
|
|
|
|
|
|
static func _add_vertex(data: Dictionary, mesh_v: Array, mesh_n: Array, mesh_uv: Array):
|
|
mesh_v.append(data["v"])
|
|
if data["vn"]:
|
|
mesh_n.append(data["vn"])
|
|
if data["vt"]:
|
|
mesh_uv.append(data["vt"])
|
|
|
|
|
|
static func _create_mesh(surfaces: Dictionary, materials: Dictionary) -> ArrayMesh:
|
|
var mesh = ArrayMesh.new()
|
|
|
|
for mat_name in surfaces.keys():
|
|
var surf = surfaces[mat_name]
|
|
var verts: Array = surf["v"]
|
|
var norms: Array = surf["n"]
|
|
var uv: Array = surf["uv"]
|
|
|
|
if verts.is_empty():
|
|
continue
|
|
|
|
var arrays = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
|
|
arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array(verts)
|
|
|
|
if not norms.is_empty() and norms.size() == verts.size():
|
|
arrays[Mesh.ARRAY_NORMAL] = PackedVector3Array(norms)
|
|
|
|
if not uv.is_empty() and uv.size() == verts.size():
|
|
arrays[Mesh.ARRAY_TEX_UV] = PackedVector2Array(uv)
|
|
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
|
|
var material = _create_material(mat_name, materials)
|
|
mesh.surface_set_material(mesh.get_surface_count() - 1, material)
|
|
|
|
return mesh
|
|
|
|
|
|
static func _create_material(mat_name: String, materials: Dictionary) -> StandardMaterial3D:
|
|
var mat = StandardMaterial3D.new()
|
|
|
|
# DEBUG: Disabilita culling e rendi visibile senza luce
|
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED # Ignora illuminazione
|
|
|
|
if not materials.has(mat_name):
|
|
mat.albedo_color = Color(0.8, 0.8, 0.8)
|
|
return mat
|
|
|
|
var data: Dictionary = materials[mat_name]
|
|
|
|
mat.albedo_color = data["albedo"]
|
|
mat.metallic = data["metallic"]
|
|
mat.roughness = data["roughness"]
|
|
|
|
if data["emission"] != Color.BLACK:
|
|
mat.emission_enabled = true
|
|
mat.emission = data["emission"]
|
|
|
|
if data["alpha"] < 1.0:
|
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
mat.albedo_color.a = data["alpha"]
|
|
|
|
if data["texture"] != "":
|
|
var tex = _load_texture(data["texture"])
|
|
if tex:
|
|
mat.albedo_texture = tex
|
|
|
|
return mat
|
|
|
|
|
|
static func _load_texture(path: String) -> Texture2D:
|
|
|
|
# Metodo 1: Risorsa importata da Godot (per res://)
|
|
if ResourceLoader.exists(path):
|
|
var res = ResourceLoader.load(path)
|
|
if res is Texture2D:
|
|
return res
|
|
elif res is Image:
|
|
return ImageTexture.create_from_image(res)
|
|
|
|
# Metodo 2: Prova il percorso senza spazi
|
|
var clean_path = path.replace(" ", "%20")
|
|
if clean_path != path and ResourceLoader.exists(clean_path):
|
|
var res = ResourceLoader.load(clean_path)
|
|
if res is Texture2D:
|
|
return res
|
|
|
|
# Metodo 3: File raw (per user:// o path assoluti)
|
|
if FileAccess.file_exists(path):
|
|
var file = FileAccess.open(path, FileAccess.READ)
|
|
if not file:
|
|
push_warning("Impossibile aprire: " + path)
|
|
return null
|
|
|
|
var buffer = file.get_buffer(file.get_length())
|
|
file.close()
|
|
|
|
var image = Image.new()
|
|
var error: Error
|
|
|
|
var ext = path.get_extension().to_lower()
|
|
match ext:
|
|
"png":
|
|
error = image.load_png_from_buffer(buffer)
|
|
"jpg", "jpeg":
|
|
error = image.load_jpg_from_buffer(buffer)
|
|
"webp":
|
|
error = image.load_webp_from_buffer(buffer)
|
|
"bmp":
|
|
error = image.load_bmp_from_buffer(buffer)
|
|
"tga":
|
|
error = image.load_tga_from_buffer(buffer)
|
|
_:
|
|
push_warning("Formato non supportato: " + ext)
|
|
return null
|
|
|
|
if error != OK:
|
|
push_warning("Errore caricamento: " + str(error))
|
|
return null
|
|
|
|
return ImageTexture.create_from_image(image)
|
|
|
|
push_warning("Texture NON trovata: " + path)
|
|
return null
|