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?

  • xyz replied to this.

    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()
    • xyz replied to this.

      Lousifr Is this how I should be storing the collision shapes?

      You can store them however is convenient. You'll need to parent each to its collision object (either static body or area) anyway.

        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

          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
          }