166 lines
7.6 KiB
GDScript
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)
|