Init
This commit is contained in:
341
scripts/objloader.gd
Normal file
341
scripts/objloader.gd
Normal file
@@ -0,0 +1,341 @@
|
||||
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
|
||||
Reference in New Issue
Block a user