mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-19 01:29:49 +00:00
296 lines
8 KiB
GDScript3
296 lines
8 KiB
GDScript3
|
class_name ObjParse
|
||
|
|
||
|
const DEBUG := false
|
||
|
|
||
|
# Obj parser made by Ezcha, updated by Deakcor
|
||
|
# Created on 7/11/2018
|
||
|
# https://ezcha.net
|
||
|
# https://github.com/Ezcha/gd-obj
|
||
|
# MIT License
|
||
|
# https://github.com/Ezcha/gd-obj/blob/master/LICENSE
|
||
|
|
||
|
# Returns an array of materials from a MTL file
|
||
|
|
||
|
# Public methods
|
||
|
|
||
|
|
||
|
# Create mesh from obj and mtl paths
|
||
|
static func load_obj(obj_path: String, mtl_path: String = "") -> Mesh:
|
||
|
if mtl_path == "":
|
||
|
mtl_path = search_mtl_path(obj_path)
|
||
|
var obj := get_data(obj_path)
|
||
|
var mats := {}
|
||
|
if mtl_path != "":
|
||
|
mats = _create_mtl(get_data(mtl_path), get_mtl_tex(mtl_path))
|
||
|
return _create_obj(obj, mats) if obj and mats else null
|
||
|
|
||
|
|
||
|
# Create mesh from obj, materials. Materials should be {"matname":data}
|
||
|
static func load_obj_from_buffer(obj_data: String, materials: Dictionary) -> Mesh:
|
||
|
return _create_obj(obj_data, materials)
|
||
|
|
||
|
|
||
|
# Create materials
|
||
|
static func load_mtl_from_buffer(mtl_data: String, textures: Dictionary) -> Dictionary:
|
||
|
return _create_mtl(mtl_data, textures)
|
||
|
|
||
|
|
||
|
# Get data from file path
|
||
|
static func get_data(path: String) -> String:
|
||
|
if path != "":
|
||
|
var file := File.new()
|
||
|
var err := file.open(path, File.READ)
|
||
|
if err == OK:
|
||
|
var res := file.get_as_text()
|
||
|
file.close()
|
||
|
return res
|
||
|
return ""
|
||
|
|
||
|
|
||
|
# Get textures from mtl path (return {"tex_path":data})
|
||
|
static func get_mtl_tex(mtl_path: String) -> Dictionary:
|
||
|
var file_paths := get_mtl_tex_paths(mtl_path)
|
||
|
var textures := {}
|
||
|
for k in file_paths:
|
||
|
textures[k] = _get_image(mtl_path, k).save_png_to_buffer()
|
||
|
return textures
|
||
|
|
||
|
|
||
|
# Get textures paths from mtl path
|
||
|
static func get_mtl_tex_paths(mtl_path: String) -> Array:
|
||
|
var file := File.new()
|
||
|
var err := file.open(mtl_path, File.READ)
|
||
|
var paths := []
|
||
|
if err == OK:
|
||
|
var lines := file.get_as_text().split("\n", false)
|
||
|
file.close()
|
||
|
for line in lines:
|
||
|
var parts = line.split(" ", false, 1)
|
||
|
if parts[0] in ["map_Kd", "map_Ks", "map_Ka"]:
|
||
|
if !parts[1] in paths:
|
||
|
paths.push_back(parts[1])
|
||
|
return paths
|
||
|
|
||
|
|
||
|
# Try to find mtl path from obj path
|
||
|
static func search_mtl_path(obj_path: String) -> String:
|
||
|
var mtl_path := obj_path.get_base_dir().plus_file(
|
||
|
obj_path.get_file().rsplit(".", false, 1)[0] + ".mtl"
|
||
|
)
|
||
|
var dir: Directory = Directory.new()
|
||
|
if !dir.file_exists(mtl_path):
|
||
|
mtl_path = obj_path.get_base_dir().plus_file(obj_path.get_file() + ".mtl")
|
||
|
if !dir.file_exists(mtl_path):
|
||
|
return ""
|
||
|
return mtl_path
|
||
|
|
||
|
|
||
|
# Private methods
|
||
|
|
||
|
|
||
|
static func _create_mtl(obj: String, textures: Dictionary) -> Dictionary:
|
||
|
var mats := {}
|
||
|
var current_mat: SpatialMaterial = null
|
||
|
|
||
|
var lines := obj.split("\n", false)
|
||
|
for line in lines:
|
||
|
var parts = line.split(" ", false)
|
||
|
match parts[0]:
|
||
|
"#":
|
||
|
# Comment
|
||
|
#print("Comment: "+line)
|
||
|
pass
|
||
|
"newmtl":
|
||
|
# Create a new material
|
||
|
if DEBUG:
|
||
|
print("Adding new material " + parts[1])
|
||
|
current_mat = SpatialMaterial.new()
|
||
|
mats[parts[1]] = current_mat
|
||
|
"Ka":
|
||
|
# Ambient color
|
||
|
#current_mat.albedo_color = Color(float(parts[1]), float(parts[2]), float(parts[3]))
|
||
|
pass
|
||
|
"Kd":
|
||
|
# Diffuse color
|
||
|
current_mat.albedo_color = Color(float(parts[1]), float(parts[2]), float(parts[3]))
|
||
|
if DEBUG:
|
||
|
print("Setting material color " + str(current_mat.albedo_color))
|
||
|
_:
|
||
|
if parts[0] in ["map_Kd", "map_Ks", "map_Ka"]:
|
||
|
var path = line.split(" ", false, 1)[1]
|
||
|
if textures.has(path):
|
||
|
current_mat.albedo_texture = _create_texture(textures[path])
|
||
|
return mats
|
||
|
|
||
|
|
||
|
static func _get_image(mtl_filepath: String, tex_filename: String) -> Image:
|
||
|
if DEBUG:
|
||
|
print(" Debug: Mapping texture file " + tex_filename)
|
||
|
var texfilepath := tex_filename
|
||
|
if tex_filename.is_rel_path():
|
||
|
texfilepath = mtl_filepath.get_base_dir().plus_file(tex_filename)
|
||
|
var filetype := texfilepath.get_extension()
|
||
|
if DEBUG:
|
||
|
print(" Debug: texture file path: " + texfilepath + " of type " + filetype)
|
||
|
|
||
|
var img: Image = Image.new()
|
||
|
img.load(texfilepath)
|
||
|
return img
|
||
|
|
||
|
|
||
|
static func _create_texture(data: PoolByteArray) -> ImageTexture:
|
||
|
var img: Image = Image.new()
|
||
|
var tex: ImageTexture = ImageTexture.new()
|
||
|
img.load_png_from_buffer(data)
|
||
|
tex.create_from_image(img)
|
||
|
return tex
|
||
|
|
||
|
|
||
|
static func _create_obj(obj: String, mats: Dictionary) -> Mesh:
|
||
|
# Setup
|
||
|
var mesh := ArrayMesh.new()
|
||
|
var vertices := PoolVector3Array()
|
||
|
var normals := PoolVector3Array()
|
||
|
var uvs := PoolVector2Array()
|
||
|
var faces := {}
|
||
|
|
||
|
var mat_name := "default"
|
||
|
var count_mtl := 0
|
||
|
|
||
|
# Parse
|
||
|
var lines := obj.split("\n", false)
|
||
|
for line in lines:
|
||
|
var parts = line.split(" ", false)
|
||
|
match parts[0]:
|
||
|
"#":
|
||
|
# Comment
|
||
|
#print("Comment: "+line)
|
||
|
pass
|
||
|
"v":
|
||
|
# Vertex
|
||
|
var n_v = Vector3(float(parts[1]), float(parts[2]), float(parts[3]))
|
||
|
vertices.append(n_v)
|
||
|
"vn":
|
||
|
# Normal
|
||
|
var n_vn = Vector3(float(parts[1]), float(parts[2]), float(parts[3]))
|
||
|
normals.append(n_vn)
|
||
|
"vt":
|
||
|
# UV
|
||
|
var n_uv = Vector2(float(parts[1]), 1 - float(parts[2]))
|
||
|
uvs.append(n_uv)
|
||
|
"usemtl":
|
||
|
# Material group
|
||
|
count_mtl += 1
|
||
|
mat_name = parts[1]
|
||
|
if not faces.has(mat_name):
|
||
|
var mats_keys := mats.keys()
|
||
|
if !mats.has(mat_name):
|
||
|
if mats_keys.size() > count_mtl:
|
||
|
mat_name = mats_keys[count_mtl]
|
||
|
faces[mat_name] = []
|
||
|
"f":
|
||
|
if not faces.has(mat_name):
|
||
|
var mats_keys := mats.keys()
|
||
|
if mats_keys.size() > count_mtl:
|
||
|
mat_name = mats_keys[count_mtl]
|
||
|
faces[mat_name] = []
|
||
|
# Face
|
||
|
if parts.size() == 4:
|
||
|
# Tri
|
||
|
var face = {"v": [], "vt": [], "vn": []}
|
||
|
for map in parts:
|
||
|
var vertices_index = map.split("/")
|
||
|
if str(vertices_index[0]) != "f":
|
||
|
face["v"].append(int(vertices_index[0]) - 1)
|
||
|
face["vt"].append(int(vertices_index[1]) - 1)
|
||
|
if vertices_index.size() > 2:
|
||
|
face["vn"].append(int(vertices_index[2]) - 1)
|
||
|
if faces.has(mat_name):
|
||
|
faces[mat_name].append(face)
|
||
|
elif parts.size() > 4:
|
||
|
# Triangulate
|
||
|
var points = []
|
||
|
for map in parts:
|
||
|
var vertices_index = map.split("/")
|
||
|
if str(vertices_index[0]) != "f":
|
||
|
var point = []
|
||
|
point.append(int(vertices_index[0]) - 1)
|
||
|
point.append(int(vertices_index[1]) - 1)
|
||
|
if vertices_index.size() > 2:
|
||
|
point.append(int(vertices_index[2]) - 1)
|
||
|
points.append(point)
|
||
|
for i in points.size():
|
||
|
if i != 0:
|
||
|
var face = {"v": [], "vt": [], "vn": []}
|
||
|
var point0 = points[0]
|
||
|
var point1 = points[i]
|
||
|
var point2 = points[i - 1]
|
||
|
face["v"].append(point0[0])
|
||
|
face["v"].append(point2[0])
|
||
|
face["v"].append(point1[0])
|
||
|
face["vt"].append(point0[1])
|
||
|
face["vt"].append(point2[1])
|
||
|
face["vt"].append(point1[1])
|
||
|
if point0.size() > 2:
|
||
|
face["vn"].append(point0[2])
|
||
|
if point2.size() > 2:
|
||
|
face["vn"].append(point2[2])
|
||
|
if point1.size() > 2:
|
||
|
face["vn"].append(point1[2])
|
||
|
faces[mat_name].append(face)
|
||
|
|
||
|
# Make tri
|
||
|
for matgroup in faces.keys():
|
||
|
if DEBUG:
|
||
|
print(
|
||
|
(
|
||
|
"Creating surface for matgroup "
|
||
|
+ matgroup
|
||
|
+ " with "
|
||
|
+ str(faces[matgroup].size())
|
||
|
+ " faces"
|
||
|
)
|
||
|
)
|
||
|
|
||
|
# Mesh Assembler
|
||
|
var st = SurfaceTool.new()
|
||
|
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||
|
if !mats.has(matgroup):
|
||
|
mats[matgroup] = SpatialMaterial.new()
|
||
|
st.set_material(mats[matgroup])
|
||
|
for face in faces[matgroup]:
|
||
|
if face["v"].size() == 3:
|
||
|
# Vertices
|
||
|
var fan_v = PoolVector3Array()
|
||
|
fan_v.append(vertices[face["v"][0]])
|
||
|
fan_v.append(vertices[face["v"][2]])
|
||
|
fan_v.append(vertices[face["v"][1]])
|
||
|
|
||
|
# Normals
|
||
|
var fan_vn = PoolVector3Array()
|
||
|
if face["vn"].size() > 0:
|
||
|
fan_vn.append(normals[face["vn"][0]])
|
||
|
fan_vn.append(normals[face["vn"][2]])
|
||
|
fan_vn.append(normals[face["vn"][1]])
|
||
|
|
||
|
# Textures
|
||
|
var fan_vt = PoolVector2Array()
|
||
|
if face["vt"].size() > 0:
|
||
|
for k in [0, 2, 1]:
|
||
|
var f = face["vt"][k]
|
||
|
if f > -1:
|
||
|
var uv = uvs[f]
|
||
|
fan_vt.append(uv)
|
||
|
|
||
|
st.add_triangle_fan(fan_v, fan_vt, PoolColorArray(), PoolVector2Array(), fan_vn, [])
|
||
|
mesh = st.commit(mesh)
|
||
|
for k in mesh.get_surface_count():
|
||
|
var mat = mesh.surface_get_material(k)
|
||
|
mat_name = ""
|
||
|
for m in mats:
|
||
|
if mats[m] == mat:
|
||
|
mat_name = m
|
||
|
mesh.surface_set_name(k, mat_name)
|
||
|
# Finish
|
||
|
return mesh
|