gizmo.gd
· 4.3 KiB · GDScript3
Raw
@tool
extends EditorNode3DGizmoPlugin
const ONE_VEC = Vector3(1.0, 1.0, 1.0)
const MAX_DEFLECTION = 4096.0
var undo_redo: EditorUndoRedoManager
var handles: PackedVector3Array = [
Vector3.UP,
-Vector3.FORWARD,
Vector3.RIGHT,
-Vector3.UP,
Vector3.FORWARD,
-Vector3.RIGHT
]
var directions: Array[String] = [
"+Y", "+Z", "+X", "-Y", "-Z", "-X"
]
var visible = true
var first_pos: Variant
var first_scale: Vector3
var first_position: Vector3
var first_global: Vector3
func _has_gizmo(node):
return node is Node3D
func _get_gizmo_name() -> String:
return "Rodot Resize"
func _init(undo_redo: EditorUndoRedoManager):
self.undo_redo = undo_redo
create_handle_material("handles")
func _redraw(gizmo: EditorNode3DGizmo):
gizmo.clear()
if visible:
gizmo.add_handles(handles, get_material("handles", gizmo), [])
func _get_handle_name(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> String:
return "Resize " + directions[handle_id]
func _get_handle_value(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> Variant:
return handles[handle_id]
func _begin_handle_action(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> void:
var node = gizmo.get_node_3d()
first_pos = null
first_scale = node.scale
first_position = node.position
first_global = node.global_position
func _commit_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, restore: Variant, cancel: bool) -> void:
var node = gizmo.get_node_3d()
if cancel:
node.scale = first_scale
node.position = first_position
return
if node.scale.is_equal_approx(first_scale) and node.position.is_equal_approx(first_position):
return
undo_redo.create_action("Resize Node3D")
undo_redo.add_do_property(node, "position", node.position)
undo_redo.add_undo_property(node, "position", first_position)
undo_redo.add_do_property(node, "scale", node.scale)
undo_redo.add_undo_property(node, "scale", first_scale)
undo_redo.commit_action()
func _update_node(node: Node3D, change: Vector3, axis: Vector3, minimum_deflection = 1.0) -> void:
# Reset to default
node.position = first_position
node.scale = first_scale
if change.length() == 0:
# No size change
return
var axis_direction = sign(change.dot(axis))
var direction = axis_direction * sign(axis.dot(axis.abs()))
var amount_to_add = change.length() * direction
var current_scale = (first_scale * axis.abs()).length()
# If user attempts to go negative or zero, force them back to the minimum amount
var deflection = current_scale + amount_to_add
if deflection < minimum_deflection:
amount_to_add = amount_to_add - deflection + minimum_deflection
var abs_axis: Vector3 = axis.abs()
var scale_vec: Vector3 = ONE_VEC - abs_axis
var factor: float = (current_scale + amount_to_add) / current_scale
var axis_factor: Vector3 = abs_axis * factor
var apply_scale: Vector3 = scale_vec + axis_factor
var pos_factor = ((apply_scale - ONE_VEC) / 2) * axis
node.translate_object_local(pos_factor)
node.scale_object_local(apply_scale)
func _set_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, camera: Camera3D, screen_pos: Vector2) -> void:
var node := gizmo.get_node_3d()
var pos = _get_projected_pos(gizmo, handle_id, camera, screen_pos)
if not pos is Vector3:
return
if not first_pos:
first_pos = pos
return
var o_pos := first_pos as Vector3
var m_pos := pos as Vector3
var axis := handles[handle_id]
var offset := (m_pos - o_pos) * axis
var scale_snap: float = EditorInterface.get_node_3d_scale_snap() / 100.0
var handle_diff := offset.length()
if is_nan(handle_diff) or is_inf(handle_diff) or handle_diff >= MAX_DEFLECTION:
return
var snap_diff = handle_diff
if EditorInterface.is_node_3d_snap_enabled():
snap_diff = roundf(handle_diff / scale_snap) * scale_snap
var handle_dot := offset.normalized().dot(axis)
var handle_dir = sign(handle_dot)
var handle_diff_vec = axis * snap_diff * handle_dir
_update_node(node, handle_diff_vec, axis, scale_snap)
node.update_gizmos()
func _get_projected_pos(gizmo: EditorNode3DGizmo, handle_id: int, camera: Camera3D, screen_pos: Vector2) -> Variant:
var node := gizmo.get_node_3d()
var from := camera.project_ray_origin(screen_pos)
var plane = Plane(from, first_global)
var pos = plane.intersects_ray(from, camera.project_ray_normal(screen_pos) * MAX_DEFLECTION)
return pos
| 1 | @tool |
| 2 | extends EditorNode3DGizmoPlugin |
| 3 | |
| 4 | const ONE_VEC = Vector3(1.0, 1.0, 1.0) |
| 5 | const MAX_DEFLECTION = 4096.0 |
| 6 | |
| 7 | var undo_redo: EditorUndoRedoManager |
| 8 | |
| 9 | var handles: PackedVector3Array = [ |
| 10 | Vector3.UP, |
| 11 | -Vector3.FORWARD, |
| 12 | Vector3.RIGHT, |
| 13 | |
| 14 | -Vector3.UP, |
| 15 | Vector3.FORWARD, |
| 16 | -Vector3.RIGHT |
| 17 | ] |
| 18 | |
| 19 | var directions: Array[String] = [ |
| 20 | "+Y", "+Z", "+X", "-Y", "-Z", "-X" |
| 21 | ] |
| 22 | |
| 23 | var visible = true |
| 24 | |
| 25 | var first_pos: Variant |
| 26 | var first_scale: Vector3 |
| 27 | var first_position: Vector3 |
| 28 | var first_global: Vector3 |
| 29 | |
| 30 | func _has_gizmo(node): |
| 31 | return node is Node3D |
| 32 | |
| 33 | func _get_gizmo_name() -> String: |
| 34 | return "Rodot Resize" |
| 35 | |
| 36 | func _init(undo_redo: EditorUndoRedoManager): |
| 37 | self.undo_redo = undo_redo |
| 38 | create_handle_material("handles") |
| 39 | |
| 40 | func _redraw(gizmo: EditorNode3DGizmo): |
| 41 | gizmo.clear() |
| 42 | if visible: |
| 43 | gizmo.add_handles(handles, get_material("handles", gizmo), []) |
| 44 | |
| 45 | func _get_handle_name(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> String: |
| 46 | return "Resize " + directions[handle_id] |
| 47 | |
| 48 | func _get_handle_value(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> Variant: |
| 49 | return handles[handle_id] |
| 50 | |
| 51 | func _begin_handle_action(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool) -> void: |
| 52 | var node = gizmo.get_node_3d() |
| 53 | first_pos = null |
| 54 | first_scale = node.scale |
| 55 | first_position = node.position |
| 56 | first_global = node.global_position |
| 57 | |
| 58 | func _commit_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, restore: Variant, cancel: bool) -> void: |
| 59 | var node = gizmo.get_node_3d() |
| 60 | if cancel: |
| 61 | node.scale = first_scale |
| 62 | node.position = first_position |
| 63 | return |
| 64 | |
| 65 | if node.scale.is_equal_approx(first_scale) and node.position.is_equal_approx(first_position): |
| 66 | return |
| 67 | |
| 68 | undo_redo.create_action("Resize Node3D") |
| 69 | undo_redo.add_do_property(node, "position", node.position) |
| 70 | undo_redo.add_undo_property(node, "position", first_position) |
| 71 | undo_redo.add_do_property(node, "scale", node.scale) |
| 72 | undo_redo.add_undo_property(node, "scale", first_scale) |
| 73 | undo_redo.commit_action() |
| 74 | |
| 75 | func _update_node(node: Node3D, change: Vector3, axis: Vector3, minimum_deflection = 1.0) -> void: |
| 76 | # Reset to default |
| 77 | node.position = first_position |
| 78 | node.scale = first_scale |
| 79 | |
| 80 | if change.length() == 0: |
| 81 | # No size change |
| 82 | return |
| 83 | |
| 84 | var axis_direction = sign(change.dot(axis)) |
| 85 | var direction = axis_direction * sign(axis.dot(axis.abs())) |
| 86 | var amount_to_add = change.length() * direction |
| 87 | var current_scale = (first_scale * axis.abs()).length() |
| 88 | |
| 89 | # If user attempts to go negative or zero, force them back to the minimum amount |
| 90 | var deflection = current_scale + amount_to_add |
| 91 | if deflection < minimum_deflection: |
| 92 | amount_to_add = amount_to_add - deflection + minimum_deflection |
| 93 | |
| 94 | var abs_axis: Vector3 = axis.abs() |
| 95 | var scale_vec: Vector3 = ONE_VEC - abs_axis |
| 96 | var factor: float = (current_scale + amount_to_add) / current_scale |
| 97 | var axis_factor: Vector3 = abs_axis * factor |
| 98 | var apply_scale: Vector3 = scale_vec + axis_factor |
| 99 | |
| 100 | var pos_factor = ((apply_scale - ONE_VEC) / 2) * axis |
| 101 | |
| 102 | node.translate_object_local(pos_factor) |
| 103 | node.scale_object_local(apply_scale) |
| 104 | |
| 105 | func _set_handle(gizmo: EditorNode3DGizmo, handle_id: int, secondary: bool, camera: Camera3D, screen_pos: Vector2) -> void: |
| 106 | var node := gizmo.get_node_3d() |
| 107 | var pos = _get_projected_pos(gizmo, handle_id, camera, screen_pos) |
| 108 | |
| 109 | if not pos is Vector3: |
| 110 | return |
| 111 | |
| 112 | if not first_pos: |
| 113 | first_pos = pos |
| 114 | return |
| 115 | |
| 116 | var o_pos := first_pos as Vector3 |
| 117 | var m_pos := pos as Vector3 |
| 118 | var axis := handles[handle_id] |
| 119 | |
| 120 | var offset := (m_pos - o_pos) * axis |
| 121 | |
| 122 | var scale_snap: float = EditorInterface.get_node_3d_scale_snap() / 100.0 |
| 123 | |
| 124 | var handle_diff := offset.length() |
| 125 | if is_nan(handle_diff) or is_inf(handle_diff) or handle_diff >= MAX_DEFLECTION: |
| 126 | return |
| 127 | |
| 128 | var snap_diff = handle_diff |
| 129 | |
| 130 | if EditorInterface.is_node_3d_snap_enabled(): |
| 131 | snap_diff = roundf(handle_diff / scale_snap) * scale_snap |
| 132 | |
| 133 | var handle_dot := offset.normalized().dot(axis) |
| 134 | var handle_dir = sign(handle_dot) |
| 135 | |
| 136 | var handle_diff_vec = axis * snap_diff * handle_dir |
| 137 | |
| 138 | _update_node(node, handle_diff_vec, axis, scale_snap) |
| 139 | |
| 140 | node.update_gizmos() |
| 141 | |
| 142 | func _get_projected_pos(gizmo: EditorNode3DGizmo, handle_id: int, camera: Camera3D, screen_pos: Vector2) -> Variant: |
| 143 | var node := gizmo.get_node_3d() |
| 144 | var from := camera.project_ray_origin(screen_pos) |
| 145 | |
| 146 | var plane = Plane(from, first_global) |
| 147 | var pos = plane.intersects_ray(from, camera.project_ray_normal(screen_pos) * MAX_DEFLECTION) |
| 148 | |
| 149 | return pos |
| 150 |