Files
Trinittu/scripts/objloader.gd
JMP Games e1a95d7417 Init
2026-01-22 08:34:02 +01:00

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