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