scopedvr-map-editor/addons/modular_builder/modular_mesh_picker.gd

55 lines
2.4 KiB
GDScript

@tool
extends RefCounted
static func raycast(root: Node3D, camera: Camera3D, screen_position: Vector2) -> Variant:
var ray_origin := camera.project_ray_origin(screen_position)
var ray_direction := camera.project_ray_normal(screen_position).normalized()
var nearest_point: Variant = null
var nearest_distance := INF
var mesh_nodes: Array[MeshInstance3D] = []
_collect_mesh_instances(root, mesh_nodes)
for mesh_node in mesh_nodes:
var source_mesh := mesh_node.mesh
if source_mesh == null or not mesh_node.is_visible_in_tree():
continue
for surface_index in range(source_mesh.get_surface_count()):
var arrays: Array
if source_mesh is PrimitiveMesh:
if surface_index > 0:
continue
arrays = source_mesh.get_mesh_arrays()
else:
if source_mesh.surface_get_primitive_type(surface_index) != Mesh.PRIMITIVE_TRIANGLES:
continue
arrays = source_mesh.surface_get_arrays(surface_index)
if arrays.is_empty() or not arrays[Mesh.ARRAY_VERTEX] is PackedVector3Array:
continue
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
var indices := PackedInt32Array()
if arrays[Mesh.ARRAY_INDEX] is PackedInt32Array:
indices = arrays[Mesh.ARRAY_INDEX]
var has_indices := not indices.is_empty()
var triangle_count := indices.size() if has_indices else vertices.size()
for triangle_start in range(0, triangle_count - 2, 3):
var index_a := indices[triangle_start] if has_indices else triangle_start
var index_b := indices[triangle_start + 1] if has_indices else triangle_start + 1
var index_c := indices[triangle_start + 2] if has_indices else triangle_start + 2
if index_a < 0 or index_b < 0 or index_c < 0 or index_a >= vertices.size() or index_b >= vertices.size() or index_c >= vertices.size():
continue
var vertex_a := mesh_node.to_global(vertices[index_a])
var vertex_b := mesh_node.to_global(vertices[index_b])
var vertex_c := mesh_node.to_global(vertices[index_c])
var hit: Variant = Geometry3D.ray_intersects_triangle(ray_origin, ray_direction, vertex_a, vertex_b, vertex_c)
if hit == null:
continue
var distance := ray_origin.distance_to(hit)
if distance < nearest_distance:
nearest_distance = distance
nearest_point = hit
return nearest_point
static 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)