How can I update my code to account for Orthogonal Projection?

Godot Version

4.4.1

Question

I’m using the code shown below to detect collisions. Currently it works when the Camera3D is in Perspective Projection, but not in Orthogonal Projection, where it simply puts the collision point seemingly in the center of the screen regardless of the mouse position.

In the first image, the little red dot is the collision_point (a small MeshInstance3D sphere) and the white structure (Minecraft character) is what it collides with. Works fine in Perspective, but always hits the middle in Orthogonal, regardless of mouse position.

How would I update the code to account for the Orthogonal Projection?

The node setup is shown here:

Click to Toggle

Main Scene


Wide Scene

I don’t believe it’s relevant (since it’s disabled), but the thin scene is structured the same as wide with the exception of some differences in sizes for the arms.

The script in the Camera3D is shown here:

Click to Toggle
extends Camera3D

@onready var ray_cast_3d: RayCast3D = $RayCast3D
@onready var collision_point: MeshInstance3D = $"../Collision Point"

var mouse_position: Vector2

func _process(delta: float) -> void:
	if Input.is_action_just_pressed("Click"):
		mouse_position = get_viewport().get_mouse_position()
		ray_cast_3d.target_position = project_local_ray_normal(mouse_position) * 100.0
		ray_cast_3d.force_raycast_update()
		
		collision_point.visible = ray_cast_3d.is_colliding()
		if collision_point.visible:
			print(mouse_position, ray_cast_3d.get_collision_point())
			collision_point.position = ray_cast_3d.get_collision_point()

That looks to me like it should work for ortho as well. Assuming the Camera3D is set to an ortho projection, the docs for project_local_ray_normal() say they should take the camera projection into account (which makes sense, it wouldn’t be much use otherwise…).

It’s possible it’s a Godot bug? I bet ortho cam doesn’t see a lot of use…

If you need to work around this, doing the math by hand isn’t hard; the mouse position is just an offset from the camera in a plane for which the camera facing is the normal. The mouse ray’s emission direction is the camera facing normal. So, get the camera’s transform, apply that to a forward normal (I think? It depends on what direction a the camera faces “at rest”…) that has been translated by the mouse coords (probably after adjusting the origin to the middle of the screen) and then hand that to your raycast.

1 Like

Managed to get help on my other post on the Godot Discord. Here is the final script which worked as I wanted.

extends Camera3D

@onready var ray_cast_3d: RayCast3D = $RayCast3D
@onready var collision_point: MeshInstance3D = $"../Collision Point"

@export var distance := 100.0

var mouse_position: Vector2

func _process(delta: float) -> void:
	if Input.is_action_pressed("Click"):
		mouse_position = get_viewport().get_mouse_position()
		var normal := project_local_ray_normal(mouse_position)
		ray_cast_3d.global_position = project_ray_origin(mouse_position)
		ray_cast_3d.target_position = normal * distance
		ray_cast_3d.force_raycast_update()
		
		if ray_cast_3d.is_colliding():
			print(mouse_position, ray_cast_3d.get_collision_point())
			collision_point.position = ray_cast_3d.get_collision_point()
			collision_point.visible = true
		else:
			collision_point.visible = false
2 Likes