parent
c5e9e5aab1
commit
1d5c91a6de
12 changed files with 347 additions and 206 deletions
@ -0,0 +1,49 @@ |
|||||||
|
#if TOOLS |
||||||
|
[Tool] |
||||||
|
public partial class ReceiveInputInEditorPlugin |
||||||
|
: EditorPlugin |
||||||
|
{ |
||||||
|
public override bool _Handles(GodotObject obj) |
||||||
|
=> true; |
||||||
|
|
||||||
|
public override int _Forward3DGuiInput(Camera3D camera, InputEvent ev) |
||||||
|
{ |
||||||
|
var root = (Node3D)EditorInterface.Singleton.GetEditedSceneRoot(); |
||||||
|
|
||||||
|
var viewport = root.GetViewport(); |
||||||
|
// Don't know about any negative effect of changing these and leaving them like that. |
||||||
|
viewport.GuiDisableInput = false; // Re-enable input, required for `PushInput` to work at all. |
||||||
|
viewport.HandleInputLocally = true; // Let sub-viewport handle its own input, makes `SetInputAsHandled` work as expected? |
||||||
|
|
||||||
|
// This will propagate input events into the edited scene, |
||||||
|
// including regular, GUI, shortcut, unhandled key and unhandled. |
||||||
|
viewport.PushInput(ev); |
||||||
|
|
||||||
|
// Enabling `PhysicsObjectPicking` causes `IsInputHandled()` to always |
||||||
|
// return true, and object picking isn't immediate anyway, so let's just |
||||||
|
// do it ourselves. This doesn't support touch input, though. |
||||||
|
if (!viewport.IsInputHandled() && (ev is InputEventMouse { Position: var mouse })) { |
||||||
|
// Ray is cast from the editor camera's view. |
||||||
|
var from = camera.ProjectRayOrigin(mouse); |
||||||
|
var to = from + camera.ProjectRayNormal(mouse) * camera.Far; |
||||||
|
|
||||||
|
// Actual collision is done in the edited scene though. |
||||||
|
var space = root.GetWorld3D().DirectSpaceState; |
||||||
|
var query = PhysicsRayQueryParameters3D.Create(from, to); |
||||||
|
|
||||||
|
var result = space.IntersectRay(query); |
||||||
|
// The collider object isn't necessarily a `CollisionObject3D`. |
||||||
|
var collider = (GodotObject)result.GetValueOrDefault("collider"); |
||||||
|
if (collider is CollisionObject3D { Visible: true } obj) { |
||||||
|
var pos = (Vector3)result["position"]; |
||||||
|
var normal = (Vector3)result["normal"]; |
||||||
|
var shape = (int)result["shape"]; |
||||||
|
obj._InputEvent(camera, ev, pos, normal, shape); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// If any node calls `SetInputAsHandled()`, the event is not passed on to other editor gizmos / plugins. |
||||||
|
return viewport.IsInputHandled() ? (int)AfterGuiInput.Stop : (int)AfterGuiInput.Pass; |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,38 @@ |
|||||||
|
@tool |
||||||
|
extends EditorPlugin |
||||||
|
|
||||||
|
func _handles(obj: Object) -> bool: |
||||||
|
return true |
||||||
|
|
||||||
|
func _forward_3d_gui_input(camera: Camera3D, event: InputEvent) -> int: |
||||||
|
var root := EditorInterface.get_edited_scene_root() as Node3D |
||||||
|
|
||||||
|
var viewport := root.get_viewport() |
||||||
|
# Don't know about any negative effect of changing these and leaving them like that. |
||||||
|
viewport.gui_disable_input = false # Re-enable input, required for `push_input` to work at all. |
||||||
|
viewport.handle_input_locally = true # Let sub-viewport handle its own input, makes `set_input_as_handled` work as expected? |
||||||
|
|
||||||
|
# This will propagate input events into the edited scene, |
||||||
|
# including regular, GUI, shortcut, unhandled key and unhandled. |
||||||
|
viewport.push_input(event) |
||||||
|
|
||||||
|
# Enabling `physics_object_picking` causes `is_input_handled()` to always |
||||||
|
# return true, and object picking isn't immediate anyway, so let's just |
||||||
|
# do it ourselves. This doesn't support touch input, though. |
||||||
|
if !viewport.is_input_handled() and event is InputEventMouse: |
||||||
|
# Ray is cast from the editor camera's view. |
||||||
|
var from := camera.project_ray_origin(event.position) |
||||||
|
var to := from + camera.project_ray_normal(event.position) * camera.far |
||||||
|
|
||||||
|
# Actual collision is done in the edited scene though. |
||||||
|
var space := root.get_world_3d().direct_space_state |
||||||
|
var query := PhysicsRayQueryParameters3D.create(from, to) |
||||||
|
|
||||||
|
var result := space.intersect_ray(query) |
||||||
|
# The collider object isn't necessarily a `CollisionObject3D`. |
||||||
|
var obj := result.get("collider") as CollisionObject3D |
||||||
|
if obj and obj.visible: |
||||||
|
obj._input_event(camera, event, result["position"], result["normal"], result["shape"]) |
||||||
|
|
||||||
|
# If any node calls `set_input_as_handled()`, the event is not passed on to other editor gizmos / plugins. |
||||||
|
return AfterGUIInput.AFTER_GUI_INPUT_STOP if viewport.is_input_handled() else AfterGUIInput.AFTER_GUI_INPUT_PASS |
@ -0,0 +1,7 @@ |
|||||||
|
[plugin] |
||||||
|
|
||||||
|
name="Receive Input in Editor" |
||||||
|
description="" |
||||||
|
author="copygirl" |
||||||
|
version="" |
||||||
|
script="ReceiveInputInEditorPlugin.cs" |
@ -1,65 +0,0 @@ |
|||||||
#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 |
|
@ -1,7 +0,0 @@ |
|||||||
[plugin] |
|
||||||
|
|
||||||
name="Terrain Plugin" |
|
||||||
description="" |
|
||||||
author="copygirl" |
|
||||||
version="" |
|
||||||
script="TerrainPlugin.cs" |
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,133 @@ |
|||||||
|
[Tool] |
||||||
|
public partial class Terrain |
||||||
|
{ |
||||||
|
bool _unhandledMouseMotion = false; // Used to detect when mouse moves off the terrain. |
||||||
|
Vector2I? _tileHover = null; // Position of currently hovered tile. |
||||||
|
bool _isSelecting = false; // Whether left mouse is held down to select tiles. |
||||||
|
(Vector2I Start, Vector2I End)? _selection = null; |
||||||
|
|
||||||
|
public override void _Input(InputEvent ev) |
||||||
|
{ |
||||||
|
if (!IsEditing()) return; |
||||||
|
|
||||||
|
if (_isSelecting && ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: false }) { |
||||||
|
_isSelecting = false; |
||||||
|
GetViewport().SetInputAsHandled(); |
||||||
|
} |
||||||
|
|
||||||
|
if ((ev is InputEventMouseButton { ButtonIndex: var wheel, ShiftPressed: true }) |
||||||
|
&& (wheel is MouseButton.WheelUp or MouseButton.WheelDown)) |
||||||
|
{ |
||||||
|
const float AdjustHeight = 0.5f; |
||||||
|
var value = (wheel == MouseButton.WheelUp) ? 1.0f : -1.0f; |
||||||
|
foreach (var tile in GetSelectedTiles()) |
||||||
|
AdjustTileHeight(tile, value * AdjustHeight); |
||||||
|
if (_selection != null) |
||||||
|
UpdateMeshAndShape(); |
||||||
|
NotifyPropertyListChanged(); |
||||||
|
GetViewport().SetInputAsHandled(); |
||||||
|
} |
||||||
|
|
||||||
|
if (ev is InputEventMouseMotion) |
||||||
|
_unhandledMouseMotion = true; |
||||||
|
} |
||||||
|
|
||||||
|
public override void _InputEvent(Camera3D camera, InputEvent ev, Vector3 position, Vector3 normal, int shapeIdx) |
||||||
|
{ |
||||||
|
if (!IsEditing()) return; |
||||||
|
|
||||||
|
var localPos = ToLocal(position); |
||||||
|
var tilePos = ToTilePos(localPos); |
||||||
|
|
||||||
|
if (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) { |
||||||
|
_selection = (tilePos, tilePos); |
||||||
|
_isSelecting = true; |
||||||
|
GetViewport().SetInputAsHandled(); |
||||||
|
} |
||||||
|
|
||||||
|
if (ev is InputEventMouseMotion) { |
||||||
|
_unhandledMouseMotion = false; |
||||||
|
_tileHover = tilePos; |
||||||
|
if (_isSelecting) _selection = _selection.Value with { End = tilePos }; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override void _Process(double delta) |
||||||
|
{ |
||||||
|
if (!IsEditing()) { |
||||||
|
_tileHover = null; |
||||||
|
_selection = null; |
||||||
|
_isSelecting = false; |
||||||
|
} |
||||||
|
|
||||||
|
if (_unhandledMouseMotion) |
||||||
|
_tileHover = null; |
||||||
|
|
||||||
|
if ((_tileHover != null) || (_selection != null)) { |
||||||
|
var mesh = GetOrCreateMesh("EditToolMesh"); |
||||||
|
mesh.ClearSurfaces(); |
||||||
|
mesh.SurfaceBegin(Mesh.PrimitiveType.Lines); |
||||||
|
|
||||||
|
|
||||||
|
void AddLine(Vector3 start, Vector3 end) { |
||||||
|
mesh.SurfaceAddVertex(start); |
||||||
|
mesh.SurfaceAddVertex(end); |
||||||
|
} |
||||||
|
|
||||||
|
void AddQuad(Vector3 topLeft, Vector3 topRight, |
||||||
|
Vector3 bottomLeft, Vector3 bottomRight) { |
||||||
|
AddLine(topLeft , topRight ); |
||||||
|
AddLine(topRight , bottomRight); |
||||||
|
AddLine(bottomRight, bottomLeft ); |
||||||
|
AddLine(bottomLeft , topLeft ); |
||||||
|
} |
||||||
|
|
||||||
|
if (_tileHover is Vector2I hover) { |
||||||
|
var corners = GetGridCorners(hover); |
||||||
|
var margin = 0.1f; |
||||||
|
mesh.SurfaceSetColor(Colors.Black); |
||||||
|
AddQuad( |
||||||
|
corners.TopLeft + new Vector3(-margin, 0, -margin), |
||||||
|
corners.TopRight + new Vector3(+margin, 0, -margin), |
||||||
|
corners.BottomLeft + new Vector3(-margin, 0, +margin), |
||||||
|
corners.BottomRight + new Vector3(+margin, 0, +margin) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
mesh.SurfaceSetColor(Colors.Blue); |
||||||
|
foreach (var tilePos in GetSelectedTiles()) { |
||||||
|
var corners = GetGridCorners(tilePos); |
||||||
|
AddQuad(corners.TopLeft, corners.TopRight, corners.BottomLeft, corners.BottomRight); |
||||||
|
} |
||||||
|
|
||||||
|
mesh.SurfaceEnd(); |
||||||
|
mesh.SurfaceSetMaterial(0, _editToolMaterial); |
||||||
|
} else { |
||||||
|
var meshInstance = (MeshInstance3D)GetNodeOrNull("EditToolMesh"); |
||||||
|
var mesh = (ImmediateMesh)meshInstance?.Mesh; |
||||||
|
mesh?.ClearSurfaces(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool IsEditing() |
||||||
|
{ |
||||||
|
if (Engine.IsEditorHint()) { |
||||||
|
var selection = EditorInterface.Singleton.GetSelection(); |
||||||
|
return selection.GetSelectedNodes().Contains(this); |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
IEnumerable<Vector2I> GetSelectedTiles() |
||||||
|
{ |
||||||
|
if (_selection is not (Vector2I start, Vector2I end)) yield break; |
||||||
|
// Ensure start.X/Y is smaller than end.X/Y. |
||||||
|
(start, end) = (new(Min(start.X, end.X), Min(start.Y, end.Y)), |
||||||
|
new(Max(start.X, end.X), Max(start.Y, end.Y))); |
||||||
|
// Go over all tiles in the range and yield each one. |
||||||
|
for (var x = start.X; x <= end.X; x++) |
||||||
|
for (var y = start.Y; y <= end.Y; y++) |
||||||
|
yield return new(x, y); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue