#if TOOLS using System.Security.Cryptography.X509Certificates; [Tool] public partial class TerrainPlugin : EditorPlugin { public override bool _Handles(GodotObject @object) => @object is Terrain; public override void _EnterTree() { // Initialization of the plugin goes here. } public override void _ExitTree() { // Clean-up of the plugin goes here. } Terrain _previousTerrain = null; public override int _Forward3DGuiInput(Camera3D viewportCamera, InputEvent @event) { if (@event is not InputEventMouse) return (int)AfterGuiInput.Pass; if (!IsInstanceValid(_previousTerrain)) _previousTerrain = null; if (RayCastTerrain(viewportCamera) is var (terrain, pos, normal, shape)) { if (_previousTerrain != terrain) { _previousTerrain?._MouseExit(); terrain._MouseEnter(); _previousTerrain = terrain; } terrain._InputEvent(viewportCamera, @event, pos, normal, shape); return viewportCamera.GetViewport().IsInputHandled() ? (int)AfterGuiInput.Stop : (int)AfterGuiInput.Pass; } else { _previousTerrain?._MouseExit(); _previousTerrain = null; return (int)AfterGuiInput.Pass; } } (Terrain Terrain, Vector3 Position, Vector3 Normal, int Shape)? RayCastTerrain(Camera3D camera) { var viewport = camera.GetViewport(); var mouse = viewport.GetMousePosition(); if (!viewport.GetVisibleRect().HasPoint(mouse)) return null; var from = camera.ProjectRayOrigin(mouse); var to = from + camera.ProjectRayNormal(mouse) * camera.Far; var space = camera.GetWorld3D().DirectSpaceState; var query = new PhysicsRayQueryParameters3D { From = from, To = to }; var result = space.IntersectRay(query); if (result.Count == 0) return null; if ((GodotObject)result["collider"] is not Terrain terrain) return null; return (terrain, (Vector3)result["position"], (Vector3)result["normal"], (int)result["shape"]); } } #endif