I'm working on spawning a unit on the terrain where the user clicks their mouse. How would I return the location of a click on the surface of a mesh?
Get position of mouse click in 3d space
- Edited
Lousifr Create a concave collider from the mesh using Mesh::create_trimesh_shape() and check if a mouse ray intersects it. There's a recipe in the last section on this page in the docs.
xyz Haven't been able to work on this until now. Reading the docs, it seems I should be creating a StaticBody3D instead of manually casting a ray from the camera, but does that return the location of the click, or just the object clicked on? Despite this, I'm working to implement the method you've set forth. Now that I think about it, I'm going to want to have StaticBody3D's for each chunk anyway. Is this how I should be storing the collision shapes?
var chunks: Array[MeshInstance3D]
var collision_shapes: Array[CollisionShape3D]
func build_mesh(i: int):
collision_shapes.append(CollisionShape3D.new())
collision_shapes[i].shape = chunks[i].mesh.create_convex_shape()
- Edited
xyz How would I handle the input event (this) of when the mesh is clicked on? I'm looking at the signals for the Node3D, but since I'm creating the static body using code, I'm not seeing the signal for the StaticBody3D, just the Node3D. The code in _physics_process() is working, returning the proper location of the ray intersection. Is there a better way to structure my project, instead of how I've got currently, with all the code taking place primarily in GameMap? In the code you provided to get the game map working, you had the code written as a script attached to a MeshInstance3D. Would it be better I reorganize the code such that GameMap is controlling a scene with each chunk individually? Units and buildings will be added as children of the chunks, so as to keep the units positioned right despite camera and game map position.
class_name GameMap
extends Node3D
var num_chunks: int = 6
var chunks: Array[MeshInstance3D]
var static_bodies: Array[StaticBody3D]
var collision_shapes: Array[CollisionShape3D]
@export var uv_translate = Vector2(0.0, 0.0)
@export var uv_scale = Vector2(1.0, 1.0)
@export var chunk_size = Vector2i(180, 540)
var md: MeshDataTool = MeshDataTool.new()
var st: SurfaceTool = SurfaceTool.new()
var plane: PlaneMesh = PlaneMesh.new()
@export var plain_noise: FastNoiseLite = FastNoiseLite.new()
@export var mountain_noise: FastNoiseLite = FastNoiseLite.new()
@export var plain_scale: float = 30
@export var mountain_scale: float = 10
@export var camera_rig: Node3D = Node3D.new()
@onready var camera = camera_rig.get_child(0).get_child(0)
const RAY_LENGTH = 1000
var unit_scene = preload("res://Scenes/unit.tscn")
func _ready() -> void:
plain_noise.seed = randi() % 9223372036854775807
mountain_noise.seed = randi() % 9223372036854775807
for i in range(0, num_chunks):
chunks.append(MeshInstance3D.new())
static_bodies.append(StaticBody3D.new())
collision_shapes.append(CollisionShape3D.new())
build_mesh(i)
add_child(chunks[i])
static_bodies[i].add_child(collision_shapes[i])
chunks[i].add_child(static_bodies[i])
chunks[i].translate(Vector3(chunk_size.x * i - (chunk_size.x / 2), 0, 0))
func _process(delta) -> void:
#for i in range(0, num_chunks):
#build_mesh(i)
map_to_camera()
func _physics_process(delta) -> void:
var space_state = get_world_3d().direct_space_state
var mousepos = get_viewport().get_mouse_position()
var origin = camera.project_ray_origin(mousepos)
var end = origin + camera.project_ray_normal(mousepos) * RAY_LENGTH
var query = PhysicsRayQueryParameters3D.create(origin, end)
var result = space_state.intersect_ray(query)
print(result)
func build_mesh(i: int) -> void:
chunks[i].mesh = ArrayMesh.new()
# create plane array mesh with specified subdivs
plane.size = chunk_size
plane.subdivide_depth = chunk_size.y
plane.subdivide_width = chunk_size.x
chunks[i].mesh.clear_surfaces()
chunks[i].mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, plane.get_mesh_arrays())
# displace vertices
md.create_from_surface(chunks[i].mesh, 0)
md.set_material(preload("res://Shaders/terrain_shader_material.tres"))
for v in md.get_vertex_count():
var cyl_coord = cylinder_uv_to_xyz(md.get_vertex_uv(v) * uv_scale * Vector2(1.0 / num_chunks, 1.0) + Vector2(i * (1.0 / num_chunks), 0.0))
var plains = plain_noise.get_noise_3dv(cyl_coord) * (plain_scale)
var mountains = mountain_noise.get_noise_3dv(cyl_coord) * (mountain_scale)
var elevation = plains + (mountains - plains) * (mountains * 0.7)
if elevation < 0.0:
elevation *= 1.25
md.set_vertex(v, md.get_vertex(v) + Vector3(0.0, elevation, 0.0))
# rebuild normals
chunks[i].mesh.clear_surfaces()
md.commit_to_surface(chunks[i].mesh)
st.create_from(chunks[i].mesh, 0)
st.generate_normals()
# commit to mesh
chunks[i].mesh = st.commit()
collision_shapes[i].shape = chunks[i].mesh.create_trimesh_shape()
func cylinder_uv_to_xyz(uv: Vector2) -> Vector3:
return Vector3(1.0, uv.y * PI * 2, 0.0).rotated(Vector3.UP, uv.x * PI * 2)
func map_to_camera() -> void:
var map_width = chunk_size.x * num_chunks
for m in chunks:
var widths_from_camera = (m.global_position.x - camera_rig.global_position.x) / map_width
if (abs(widths_from_camera) <= 0.5):
continue
if (widths_from_camera > 0):
widths_from_camera += 0.5
else:
widths_from_camera -= 0.5
var widths_to_fix: int = widths_from_camera
m.global_position.x -= widths_to_fix * map_width
- Edited
Lousifr Didnt read too long, but if you need to get mouse click coords then just use th eexample from the docs.
https://docs.godotengine.org/en/stable/tutorials/physics/ray-casting.html
func get_ray_results(rid_wall):
var space_state = get_world_3d().direct_space_state
var cam = cameraNode
var mousepos = get_viewport().get_mouse_position()
var origin = cam.project_ray_origin(mousepos)
var RAY_LENGTH = 1000.0
var end = origin + cam.project_ray_normal(mousepos) * RAY_LENGTH
var query = PhysicsRayQueryParameters3D.create(origin, end)
query.collide_with_areas = true
if rid_wall:
query.exclude = [self, rid_wall]
else:
query.exclude = [self]
return space_state.intersect_ray(query)
result: this is for 2D, but in 3d vec2 is vec3
{
position: Vector2 # point in world space for collision
normal: Vector2 # normal in world space for collision
collider: Object # Object collided or null (if unassociated)
collider_id: ObjectID # Object it collided against
rid: RID # RID it collided against
shape: int # shape index of collider
metadata: Variant() # metadata of collider
}