scopedvr-map-editor/addons/modular_builder_en/modular_mesh_baker.gd

166 lines
7.6 KiB
GDScript

@tool
extends RefCounted
func bake(building: ModularBuilderPiece) -> ArrayMesh:
var mesh_instances: Array[MeshInstance3D] = []
_collect_mesh_instances(building, mesh_instances)
var groups: Array[Dictionary] = []
var projected_materials: Dictionary = {}
for mesh_instance in mesh_instances:
if not is_instance_valid(mesh_instance) or not mesh_instance.is_visible_in_tree() or _is_export_excluded(mesh_instance, building):
continue
var source_mesh := mesh_instance.mesh
if source_mesh == null:
continue
var relative_transform := building.global_transform.affine_inverse() * mesh_instance.global_transform
if absf(relative_transform.basis.determinant()) < 0.000001:
continue
var normal_transform := relative_transform.basis.inverse().transposed()
var mirrored := relative_transform.basis.determinant() < 0.0
for surface_index in range(source_mesh.get_surface_count()):
var primitive_type := Mesh.PRIMITIVE_TRIANGLES
var surface_arrays: Array
if source_mesh is PrimitiveMesh:
# PrimitiveMesh provides mesh arrays directly; surface_get_* methods
# are used for other meshes.
if surface_index > 0:
continue
surface_arrays = source_mesh.get_mesh_arrays()
else:
primitive_type = source_mesh.surface_get_primitive_type(surface_index)
surface_arrays = source_mesh.surface_get_arrays(surface_index)
if primitive_type != Mesh.PRIMITIVE_TRIANGLES:
continue
if surface_arrays.is_empty() or not surface_arrays[Mesh.ARRAY_VERTEX] is PackedVector3Array:
continue
var source_vertices: PackedVector3Array = surface_arrays[Mesh.ARRAY_VERTEX]
if source_vertices.size() < 3:
continue
var source_normals := PackedVector3Array()
var source_uvs := PackedVector2Array()
var source_colors := PackedColorArray()
if surface_arrays[Mesh.ARRAY_NORMAL] is PackedVector3Array:
source_normals = surface_arrays[Mesh.ARRAY_NORMAL]
if surface_arrays[Mesh.ARRAY_TEX_UV] is PackedVector2Array:
source_uvs = surface_arrays[Mesh.ARRAY_TEX_UV]
if surface_arrays[Mesh.ARRAY_COLOR] is PackedColorArray:
source_colors = surface_arrays[Mesh.ARRAY_COLOR]
var source_indices := PackedInt32Array()
if surface_arrays[Mesh.ARRAY_INDEX] is PackedInt32Array:
source_indices = surface_arrays[Mesh.ARRAY_INDEX]
var material := mesh_instance.get_active_material(surface_index)
var force_projected_uvs := false
var bake_material := material
if material is StandardMaterial3D and material.uv1_triplanar:
force_projected_uvs = true
var material_id := material.get_instance_id()
if not projected_materials.has(material_id):
var material_copy := material.duplicate() as StandardMaterial3D
material_copy.uv1_triplanar = false
material_copy.uv1_world_triplanar = false
projected_materials[material_id] = material_copy
bake_material = projected_materials[material_id] as Material
var group_index := _find_or_create_group(groups, bake_material)
var group: Dictionary = groups[group_index]
var group_vertices: Array = group["vertices"]
var group_normals: Array = group["normals"]
var group_uvs: Array = group["uvs"]
var group_colors: Array = group["colors"]
var has_indices := not source_indices.is_empty()
var triangle_count := source_indices.size() if has_indices else source_vertices.size()
for triangle_start in range(0, triangle_count - 2, 3):
var index_a := source_indices[triangle_start] if has_indices else triangle_start
var index_b := source_indices[triangle_start + 1] if has_indices else triangle_start + 1
var index_c := source_indices[triangle_start + 2] if has_indices else triangle_start + 2
if index_a >= source_vertices.size() or index_b >= source_vertices.size() or index_c >= source_vertices.size():
continue
if mirrored:
var swap_index := index_b
index_b = index_c
index_c = swap_index
var local_a: Vector3 = source_vertices[index_a]
var local_b: Vector3 = source_vertices[index_b]
var local_c: Vector3 = source_vertices[index_c]
var baked_a: Vector3 = relative_transform * local_a
var baked_b: Vector3 = relative_transform * local_b
var baked_c: Vector3 = relative_transform * local_c
var face_normal := (baked_b - baked_a).cross(baked_c - baked_a).normalized()
if face_normal.length_squared() < 0.5:
continue
var baked_points := [baked_a, baked_b, baked_c]
var source_point_indices := [index_a, index_b, index_c]
var source_face_normal := (local_b - local_a).cross(local_c - local_a).normalized()
for corner in range(3):
var source_index: int = source_point_indices[corner]
var baked_point: Vector3 = baked_points[corner]
var baked_normal := face_normal
if source_normals.size() == source_vertices.size():
baked_normal = (normal_transform * source_normals[source_index]).normalized()
if baked_normal.length_squared() < 0.5:
baked_normal = face_normal
var uv: Vector2
if not force_projected_uvs and source_uvs.size() == source_vertices.size():
uv = source_uvs[source_index]
else:
uv = _project_uv(source_vertices[source_index], source_face_normal)
var color := source_colors[source_index] if source_colors.size() == source_vertices.size() else Color.WHITE
group_vertices.append(baked_point)
group_normals.append(baked_normal)
group_uvs.append(uv)
group_colors.append(color)
groups[group_index] = group
if groups.is_empty():
return null
var combined := ArrayMesh.new()
combined.resource_name = "%s_Mesh" % building.name
for group in groups:
var vertices := PackedVector3Array(group["vertices"])
if vertices.is_empty():
continue
var arrays: Array = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_NORMAL] = PackedVector3Array(group["normals"])
arrays[Mesh.ARRAY_TEX_UV] = PackedVector2Array(group["uvs"])
arrays[Mesh.ARRAY_COLOR] = PackedColorArray(group["colors"])
var surface_tool := SurfaceTool.new()
surface_tool.create_from_arrays(arrays, Mesh.PRIMITIVE_TRIANGLES)
if group["material"] is Material:
surface_tool.set_material(group["material"])
surface_tool.generate_tangents()
var temp_mesh := surface_tool.commit()
if temp_mesh == null or temp_mesh.get_surface_count() == 0:
continue
combined.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, temp_mesh.surface_get_arrays(0))
if group["material"] is Material:
combined.surface_set_material(combined.get_surface_count() - 1, group["material"])
return combined if combined.get_surface_count() > 0 else null
func _find_or_create_group(groups: Array[Dictionary], material: Material) -> int:
for i in range(groups.size()):
if groups[i]["material"] == material:
return i
groups.append({"material": material, "vertices": [], "normals": [], "uvs": [], "colors": []})
return groups.size() - 1
func _project_uv(point: Vector3, normal: Vector3) -> Vector2:
var absolute_normal := normal.abs()
if absolute_normal.y >= absolute_normal.x and absolute_normal.y >= absolute_normal.z:
return Vector2(point.x, point.z)
if absolute_normal.x >= absolute_normal.z:
return Vector2(point.z, point.y)
return Vector2(point.x, point.y)
func _is_export_excluded(mesh_instance: MeshInstance3D, building: ModularBuilderPiece) -> bool:
var current: Node = mesh_instance
while current != null and current != building:
if current.has_meta("modular_builder_preview") or current.name == "ModularBuilderMarkers":
return true
current = current.get_parent()
return false
func _collect_mesh_instances(node: Node, result: Array[MeshInstance3D]) -> void:
if node is MeshInstance3D:
result.append(node)
for child in node.get_children():
_collect_mesh_instances(child, result)