Godot Version
Godot 4.5 stable mono
Question
I’ve been trying to do 3D GUIs. I’ve got a model with the ViewportTexture set to the interface and a StaticBody to detect raycasting. The logic in my script works fine, but InputEventMouseMotions don’t get passed in _GuiInput to Control nodes, which results in FoldableContainers and ScrollBars to not change their appearance when hovered, Buttons work fine because they listen to MouseEntered and MouseExited instead. This is extremely infuriating, and I was wondering if anyone knew of a way around this, or if this should be listed as an issue on Godot’s GitHub.
Here’s the full script, which includes the raycasting logic and event passing. Again, the events do work and the aforementioned controls are usable, they just don’t change appearance when hovered unless they are also being clicked.
public partial class MenuComputer : MeshInstance3D
{
[Export] public SubViewport Interface;
private PhysicsDirectSpaceState3D space;
private Vector2 mousePrevious;
public override void _Ready()
{
space = GetWorld3D().DirectSpaceState;
SetProcessUnhandledInput(true);
}
public Dictionary RaycastFromMouse(Camera3D camera, float distance = 2f)
{
var space = GetWorld3D().DirectSpaceState;
Vector2 mouse = GetViewport().GetMousePosition();
Vector3 from = camera.ProjectRayOrigin(mouse);
Vector3 dir = camera.ProjectRayNormal(mouse);
var query = new PhysicsRayQueryParameters3D()
{
From = from,
To = from + dir * distance,
CollideWithAreas = false,
CollideWithBodies = true,
};
return space.IntersectRay(query);
}
public (Vector3[] vertices, Vector2[] uv, Vector2[] uv2, int[] indices)
GetTriangleData(ArrayMesh arrayMesh, int surface)
{
var surfaceTool = new SurfaceTool();
surfaceTool.CreateFrom(arrayMesh, surface);
var meshData = surfaceTool.CommitToArrays();
var vertices = meshData[(int)Mesh.ArrayType.Vertex].AsVector3Array();
var uv = meshData[(int)Mesh.ArrayType.TexUV].AsVector2Array();
var uv2 = meshData[(int)Mesh.ArrayType.TexUV2].AsVector2Array();
var indices = meshData[(int)Mesh.ArrayType.Index].AsInt32Array();
return (vertices, uv, uv2, indices);
}
public Vector2 GetInterpolatedUV2(
Vector3 pLocal,
int faceIndex,
Vector3[] vertices,
Vector2[] uv2,
int[] indices)
{
int i0 = indices[faceIndex * 3 + 0];
int i1 = indices[faceIndex * 3 + 1];
int i2 = indices[faceIndex * 3 + 2];
Vector3 v0 = vertices[i0];
Vector3 v1 = vertices[i1];
Vector3 v2 = vertices[i2];
Vector2 uv0 = uv2[i0];
Vector2 uv1 = uv2[i1];
Vector2 uv2v = uv2[i2];
// Barycentric math
Vector3 v0v1 = v1 - v0;
Vector3 v0v2 = v2 - v0;
Vector3 v0p = pLocal - v0;
float d00 = v0v1.Dot(v0v1);
float d01 = v0v1.Dot(v0v2);
float d11 = v0v2.Dot(v0v2);
float d20 = v0p.Dot(v0v1);
float d21 = v0p.Dot(v0v2);
float denom = d00 * d11 - d01 * d01;
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
return uv0 * u + uv1 * v + uv2v * w;
}
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouse)
{
var camera = GetViewport().GetCamera3D();
var result = RaycastFromMouse(camera);
var mouse = (InputEventMouse)@event.Duplicate();
mouse.Position = -10 * Vector2.One;
if (result.Count > 0)
{
Vector3 hitPos = result["position"].AsVector3();
int faceIndex = result["face_index"].AsInt32();
var hitLocal = ToLocal(hitPos);
var mesh = Mesh as ArrayMesh;
var (verts, _, uv2, indices) = GetTriangleData(mesh, surface: 1);
var hitUV2 = GetInterpolatedUV2(hitLocal, faceIndex, verts, uv2, indices);
mouse.Position = hitUV2 * Interface.Size;
}
mouse.GlobalPosition = mouse.Position;
if (mouse is InputEventMouseMotion mouseMotion)
{
mouseMotion.Relative *= Vector2.One / GetWindow().Size * Interface.Size;
mouseMotion.Velocity *= Vector2.One / GetWindow().Size * Interface.Size;
}
Interface.PushInput(mouse, inLocalCoords: true);
GetViewport().SetInputAsHandled();
return;
}
Interface.PushInput(@event);
GetViewport().SetInputAsHandled();
}
}
Thank you!
Here’s a minimal example: