MagicNStuff/source/addons/proto_shape/proto_ramp/proto_ramp_gizmos.gd
SchimmelSpreu83 2a60380258 Added Plugins
- ProtoShape
- StairsCharacter (GDExtension)
- TODO Manager
- 3D Cursor
2025-09-21 23:42:15 +02:00

418 lines
17 KiB
GDScript

# Implementing Gizmo
const ProtoGizmoWrapper = preload("res://addons/proto_shape/proto_gizmo_wrapper/proto_gizmo_wrapper.gd")
const ProtoGizmoUtils = preload("res://addons/proto_shape/proto_gizmo/proto_gizmo_utils.gd")
const ProtoRamp = preload("res://addons/proto_shape/proto_ramp/proto_ramp.gd")
const ProtoGizmoPlugin = preload("res://addons/proto_shape/proto_gizmo/proto_gizmo.gd")
var width_gizmo_id: int
var depth_gizmo_id: int
var height_gizmo_id: int
var fill_gizmo_id1: int
var fill_gizmo_id2: int
var gizmo_utils := ProtoGizmoUtils.new()
var ramp: ProtoRamp = null
var undo_redo: EditorUndoRedoManager
var is_editing := false
func attach_ramp(node: ProtoRamp) -> void:
ramp = node
if ramp.get_parent() is ProtoGizmoWrapper:
var parent: ProtoGizmoWrapper = ramp.get_parent()
parent.redraw_gizmos_for_child_signal.connect(redraw_gizmos)
parent.set_handle_for_child_signal.connect(set_handle)
parent.commit_handle.connect(commit_handle)
func remove_ramp() -> void:
if ramp.get_parent() is ProtoGizmoWrapper:
var parent: ProtoGizmoWrapper = ramp.get_parent()
parent.redraw_gizmos_for_child_signal.disconnect(redraw_gizmos)
parent.set_handle_for_child_signal.disconnect(set_handle)
parent.commit_handle.disconnect(commit_handle)
# Disconnecting any leftover connections
if plugin != null:
if current_fine_snap_callable != null:
if plugin.fine_snapping_changed.is_connected(current_fine_snap_callable):
plugin.fine_snapping_changed.disconnect(current_fine_snap_callable)
if current_snap_callable != null:
if plugin.snapping_changed.is_connected(current_snap_callable):
plugin.snapping_changed.disconnect(current_snap_callable)
plugin = null
ramp = null
# Snapping to grid
var snapping_enabled: bool = false
var snap_unit: float = 1.0
var fine_snapping_enabled: bool = false
var fine_snap_unit: float = 0.1
# Captured arguments required for signal connections
# ramp: ProtoRamp - on closing a scene, the ramp is freed, so we need to check for null
# Unfortunately, disconnecting does not work (bug?), the lambda is still called afterwards
# plugin: ProtoGizmoPlugin - captured plugin argument will not be null (even after setting the field to null in remove_ramp)
func node_snap_listener(ramp: ProtoRamp, plugin: ProtoGizmoPlugin) -> Callable:
return func (enabled: bool) -> void:
snapping_enabled = enabled
if ramp != null:
ramp.update_gizmos()
else:
plugin.snapping_changed.disconnect(current_snap_callable)
func node_fine_snap_listener(ramp: ProtoRamp, plugin: ProtoGizmoPlugin) -> Callable:
return func (enabled: bool) -> void:
fine_snapping_enabled = enabled
if ramp != null:
ramp.update_gizmos()
else:
plugin.fine_snapping_changed.disconnect(current_fine_snap_callable)
var current_snap_callable: Callable
var current_fine_snap_callable: Callable
var plugin: ProtoGizmoPlugin
func init_gizmo(plugin: ProtoGizmoPlugin) -> void:
# Generate a "random" id for each gizmo
width_gizmo_id = Time.get_ticks_usec()
depth_gizmo_id = width_gizmo_id + 1
height_gizmo_id = width_gizmo_id + 2
fill_gizmo_id1 = width_gizmo_id + 3
fill_gizmo_id2 = width_gizmo_id + 4
undo_redo = plugin.undo_redo
plugin = plugin
current_snap_callable = node_snap_listener(ramp, plugin)
current_fine_snap_callable = node_fine_snap_listener(ramp, plugin)
plugin.fine_snapping_changed.connect(current_fine_snap_callable)
plugin.snapping_changed.connect(current_snap_callable)
snapping_enabled = plugin.snapping
fine_snapping_enabled = plugin.fine_snapping
# Debug purposes
var screen_pos: Vector2
var debug_gizmo_handler_id: int
var camera_position: Vector3
## As gizmos can only be used in the Editor, we can cast the [gizmo] to [EditorNode3DGizmo] and [plugin] to [EditorNode3DGizmoPlugin].
func redraw_gizmos(gizmo: EditorNode3DGizmo, plugin: ProtoGizmoPlugin) -> void:
if gizmo.get_node_3d() != ramp:
return
if width_gizmo_id == 0 or depth_gizmo_id == 0 or height_gizmo_id == 0 or fill_gizmo_id1 == 0 or fill_gizmo_id2 == 0:
init_gizmo(plugin)
gizmo.clear()
var true_depth: float = ramp.get_true_depth()
var true_height: float = ramp.get_true_height()
var anchor_offset: Vector3 = ramp.get_anchor_offset(ramp.anchor)
var fill: float = ramp.get_fill()
var depth_gizmo_position := Vector3(0, true_height / 2, true_depth) + anchor_offset
var width_gizmo_position := Vector3(ramp.width / 2, true_height / 2, true_depth / 2) + anchor_offset
var height_gizmo_position := Vector3(0, true_height, true_depth / 2) + anchor_offset
# Calculate perpendicular line points for hypotenuse and the offset
var fill_gizmo_hypotenuse_projection := _get_fill_max_offset() * (1 - fill)
var fill_gizmo_position1 := Vector3(-ramp.width / 2, fill_gizmo_hypotenuse_projection.y, true_depth - fill_gizmo_hypotenuse_projection.z) + anchor_offset
var fill_gizmo_position2 := Vector3(ramp.width / 2, fill_gizmo_hypotenuse_projection.y, true_depth - fill_gizmo_hypotenuse_projection.z) + anchor_offset
# When on the left, width gizmo is on the right
# When in the back (top, base), depth gizmo is on the front
# When on the top, height gizmo is on the bottom
# Don't offset fill gizmo positions
match ramp.anchor:
ProtoRamp.Anchor.BOTTOM_LEFT:
width_gizmo_position.x = -ramp.width
ProtoRamp.Anchor.TOP_LEFT:
width_gizmo_position.x = -ramp.width
depth_gizmo_position.z = -true_depth
height_gizmo_position.y = -true_height
ProtoRamp.Anchor.BASE_LEFT:
width_gizmo_position.x = -ramp.width
depth_gizmo_position.z = -true_depth
ProtoRamp.Anchor.BASE_CENTER:
depth_gizmo_position.z = -true_depth
ProtoRamp.Anchor.BASE_RIGHT:
depth_gizmo_position.z = -true_depth
ProtoRamp.Anchor.TOP_RIGHT:
depth_gizmo_position.z = -true_depth
height_gizmo_position.y = -true_height
ProtoRamp.Anchor.TOP_CENTER:
depth_gizmo_position.z = -true_depth
height_gizmo_position.y = -true_height
var handles = PackedVector3Array()
handles.push_back(depth_gizmo_position)
handles.push_back(width_gizmo_position)
handles.push_back(height_gizmo_position)
handles.push_back(fill_gizmo_position1)
handles.push_back(fill_gizmo_position2)
gizmo.add_handles(handles, plugin.get_material("proto_handler", gizmo), [depth_gizmo_id, width_gizmo_id, height_gizmo_id, fill_gizmo_id1, fill_gizmo_id2])
if ramp.shape_polygon != null and ramp.shape_polygon.get_meshes().size() > 1:
var offset := ramp.get_anchor_offset(ramp.anchor)
var polygon_offset := offset - Vector3(ramp.width / 2, 0, 0)
var mesh: Mesh = ramp.shape_polygon.get_meshes()[1]
var mdt := MeshDataTool.new()
mdt.create_from_surface(mesh, 0)
for i in range(mdt.get_vertex_count()):
var vertex := mdt.get_vertex(i)
vertex = vertex.rotated(Vector3.UP, -PI / 2.0)
vertex += polygon_offset
mdt.set_vertex(i, vertex)
var newMesh: Mesh = ArrayMesh.new()
newMesh.clear_surfaces()
mdt.commit_to_surface(newMesh)
mdt.clear()
gizmo.add_collision_triangles(newMesh.generate_triangle_mesh())
gizmo.add_mesh(newMesh.create_outline(0.001), plugin.get_material("selected", gizmo))
# Adding debug lines for gizmo if we have cursor screen position set
if screen_pos:
var grid_size_modifier = 1.0
# Grid size is always the max of the two other dimensions
match debug_gizmo_handler_id:
depth_gizmo_id:
# Setting depth
grid_size_modifier = max(ramp.get_true_height(), ramp.get_width())
var local_offset_axis = Vector3(0, 0, 1)
gizmo_utils.debug_draw_handle_grid(camera_position, screen_pos, depth_gizmo_position, local_offset_axis, ramp, gizmo, plugin, grid_size_modifier)
width_gizmo_id:
# Setting width
grid_size_modifier = max(ramp.get_true_height(), ramp.get_true_depth())
var local_offset_axis = Vector3(1, 0, 0)
gizmo_utils.debug_draw_handle_grid(camera_position, screen_pos, width_gizmo_position, local_offset_axis, ramp, gizmo, plugin, grid_size_modifier)
height_gizmo_id:
# Setting height
grid_size_modifier = max(ramp.get_width(), ramp.get_true_depth())
var local_offset_axis = Vector3(0, 1, 0)
gizmo_utils.debug_draw_handle_grid(camera_position, screen_pos, height_gizmo_position, local_offset_axis, ramp, gizmo, plugin, grid_size_modifier)
fill_gizmo_id1:
# Setting fill 1
grid_size_modifier = max(ramp.get_true_height(), ramp.get_true_depth())
var local_plane_normal = Vector3(1, 0, 0)
var local_offset_axis = _get_fill_max_offset().normalized()
local_offset_axis.z = -local_offset_axis.z
gizmo_utils.debug_draw_handle_grid_on_plane(fill_gizmo_position1, local_offset_axis, local_plane_normal, ramp, gizmo, plugin, grid_size_modifier)
fill_gizmo_id2:
# Setting fill 2
grid_size_modifier = max(ramp.get_true_height(), ramp.get_true_depth())
var local_plane_normal = Vector3(1, 0, 0)
var local_offset_axis = _get_fill_max_offset().normalized()
local_offset_axis.z = -local_offset_axis.z
gizmo_utils.debug_draw_handle_grid_on_plane(fill_gizmo_position2, local_offset_axis, local_plane_normal, ramp, gizmo, plugin, grid_size_modifier)
var start_offset := 0.0
var end_offset := 0.0
func set_handle(
gizmo: EditorNode3DGizmo,
plugin: ProtoGizmoPlugin,
handle_id: int,
secondary: bool,
camera: Camera3D,
screen_pos: Vector2) -> void:
# Set debug parameters for redraw
var child := gizmo.get_node_3d()
if child != ramp:
return
self.screen_pos = screen_pos
self.camera_position = camera.position
match handle_id:
depth_gizmo_id:
end_offset = _get_depth_handle_offset(camera, screen_pos)
if snapping_enabled and not fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, snap_unit)
elif fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, fine_snap_unit)
ramp.depth = _get_ramp_depth(end_offset)
width_gizmo_id:
end_offset = _get_width_handle_offset(camera, screen_pos)
if snapping_enabled and not fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, snap_unit)
elif fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, fine_snap_unit)
ramp.width = _get_ramp_width(end_offset)
height_gizmo_id:
end_offset = _get_height_handle_offset(camera, screen_pos)
if snapping_enabled and not fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, snap_unit)
elif fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, fine_snap_unit)
ramp.height = _get_ramp_height(end_offset)
fill_gizmo_id1:
end_offset = _get_fill_handle_offset(camera, screen_pos, Vector3(-ramp.width / 2, 0, 0))
if snapping_enabled and not fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, snap_unit)
elif fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, fine_snap_unit)
ramp.fill = end_offset
fill_gizmo_id2:
end_offset = _get_fill_handle_offset(camera, screen_pos, Vector3(ramp.width / 2, 0, 0))
if snapping_enabled and not fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, snap_unit)
elif fine_snapping_enabled:
end_offset = gizmo_utils.snap_to_grid(end_offset, fine_snap_unit)
ramp.fill = end_offset
if !is_editing:
match handle_id:
depth_gizmo_id:
debug_gizmo_handler_id = depth_gizmo_id
start_offset = _get_depth_handle_offset(camera, screen_pos)
width_gizmo_id:
debug_gizmo_handler_id = width_gizmo_id
start_offset = _get_width_handle_offset(camera, screen_pos)
height_gizmo_id:
debug_gizmo_handler_id = height_gizmo_id
start_offset = _get_height_handle_offset(camera, screen_pos)
fill_gizmo_id1:
debug_gizmo_handler_id = fill_gizmo_id1
start_offset = _get_fill_handle_offset(camera, screen_pos, Vector3(-ramp.width / 2, 0, 0))
fill_gizmo_id2:
debug_gizmo_handler_id = fill_gizmo_id2
start_offset = _get_fill_handle_offset(camera, screen_pos, Vector3(ramp.width / 2, 0, 0))
is_editing = true
ramp.update_gizmos()
func _get_depth_handle_offset(
camera: Camera3D,
screen_pos: Vector2) -> float:
var local_offset_axis = Vector3(0, 0, 1)
var gizmo_position = Vector3(0, ramp.get_true_height() / 2, ramp.get_true_depth()) + ramp.get_anchor_offset(ramp.anchor)
var handle_offset = gizmo_utils.get_handle_offset(camera, screen_pos, gizmo_position, local_offset_axis, ramp)
return handle_offset.z
func _get_width_handle_offset(
camera: Camera3D,
screen_pos: Vector2) -> float:
var local_offset_axis = Vector3(1, 0, 0)
var gizmo_position = Vector3(ramp.width / 2, ramp.get_true_height() / 2, ramp.get_true_depth() / 2) + ramp.get_anchor_offset(ramp.anchor)
var handle_offset = gizmo_utils.get_handle_offset(camera, screen_pos, gizmo_position, local_offset_axis, ramp)
return handle_offset.x
func _get_height_handle_offset(
camera: Camera3D,
screen_pos: Vector2) -> float:
var local_offset_axis = Vector3(0, 1, 0)
var gizmo_position = Vector3(0, ramp.get_true_height(), ramp.get_true_depth() / 2) + ramp.get_anchor_offset(ramp.anchor)
var handle_offset = gizmo_utils.get_handle_offset(camera, screen_pos, gizmo_position, local_offset_axis, ramp)
return handle_offset.y
func _get_fill_handle_offset(
camera: Camera3D,
screen_pos: Vector2,
gizmo_position_offset: Vector3) -> float:
var fill_gizmo_axis := _get_fill_max_offset()
fill_gizmo_axis.z = ramp.get_true_depth() - fill_gizmo_axis.z
var fill_gizmo_offset := fill_gizmo_axis * (1 - ramp.fill)
var gizmo_position := Vector3(0, fill_gizmo_offset.y, fill_gizmo_offset.z) + ramp.get_anchor_offset(ramp.anchor) + gizmo_position_offset
var local_plane_normal := Vector3(1, 0, 0)
var handle_offset = gizmo_utils.get_handle_offset_by_plane(camera, screen_pos, gizmo_position, local_plane_normal, ramp)
var gizmo_base_position := Vector3(0, 0, ramp.get_true_depth())
handle_offset -= gizmo_base_position
var gizmo_max_position := fill_gizmo_axis - gizmo_base_position
gizmo_max_position.x = 0
handle_offset -= ramp.get_anchor_offset(ramp.anchor)
handle_offset.x = 0
if (handle_offset.dot(gizmo_max_position) < 0):
return 1
return min(1.0, max(0.0, 1 - handle_offset.project(gizmo_max_position).length() / gizmo_max_position.length()))
func _get_ramp_width(offset: float) -> float:
# If anchor is on the left, offset is negative
# If anchor is not centered, offset is divided by 2
match ramp.anchor:
ProtoRamp.Anchor.BOTTOM_LEFT:
offset = -offset / 2
ProtoRamp.Anchor.TOP_LEFT:
offset = -offset / 2
ProtoRamp.Anchor.BASE_LEFT:
offset = -offset / 2
ProtoRamp.Anchor.BOTTOM_RIGHT:
offset = offset / 2
ProtoRamp.Anchor.TOP_RIGHT:
offset = offset / 2
ProtoRamp.Anchor.BASE_RIGHT:
offset = offset / 2
return offset * 2
func _get_ramp_depth(offset: float) -> float:
if ramp.calculation == ProtoRamp.Calculation.STEP_DIMENSIONS and ramp.type == ProtoRamp.Type.STAIRCASE:
offset = offset / ramp.steps
# If anchor is on the back, offset is negative
match ramp.anchor:
ProtoRamp.Anchor.BASE_CENTER:
offset = -offset
ProtoRamp.Anchor.BASE_RIGHT:
offset = -offset
ProtoRamp.Anchor.BASE_LEFT:
offset = -offset
ProtoRamp.Anchor.TOP_CENTER:
offset = -offset
ProtoRamp.Anchor.TOP_RIGHT:
offset = -offset
ProtoRamp.Anchor.TOP_LEFT:
offset = -offset
return offset
func _get_ramp_height(offset: float) -> float:
# If anchor is TOP, offset is negative
if ramp.calculation == ProtoRamp.Calculation.STEP_DIMENSIONS and ramp.type == ProtoRamp.Type.STAIRCASE:
offset = offset / ramp.steps
match ramp.anchor:
ProtoRamp.Anchor.TOP_LEFT:
offset = -offset
ProtoRamp.Anchor.TOP_CENTER:
offset = -offset
ProtoRamp.Anchor.TOP_RIGHT:
offset = -offset
return offset
func _get_fill_max_offset() -> Vector3:
var A := Vector2(0, ramp.get_true_height())
var B := Vector2(ramp.get_true_depth(), 0)
var fill_gizmo_base_position := Vector2(0, 0)
var dir := (B - A).normalized()
var t := (fill_gizmo_base_position - A).dot(dir)
var fill_gizmo_hypotenuse_projection := A + dir * t
var projection_vector := fill_gizmo_hypotenuse_projection - fill_gizmo_base_position
return Vector3(0, projection_vector.y, projection_vector.x)
func commit_handle(
gizmo: EditorNode3DGizmo,
handle_id: int,
secondary: bool,
restore: Variant,
cancel: bool) -> void:
if gizmo.get_node_3d() != ramp:
return
match handle_id:
depth_gizmo_id:
restore = _get_ramp_depth(start_offset)
undo_redo.create_action("Edit ramp depth", 0, ramp, true)
undo_redo.add_do_property(ramp, "depth", _get_ramp_depth(end_offset))
undo_redo.add_undo_property(ramp, "depth", restore)
width_gizmo_id:
restore = _get_ramp_width(start_offset)
undo_redo.create_action("Edit ramp width", 0, ramp, true)
undo_redo.add_do_property(ramp, "width", _get_ramp_width(end_offset))
undo_redo.add_undo_property(ramp, "width", restore)
height_gizmo_id:
restore = _get_ramp_height(start_offset)
undo_redo.create_action("Edit ramp height", 0, ramp, true)
undo_redo.add_do_property(ramp, "height", _get_ramp_height(end_offset))
undo_redo.add_undo_property(ramp, "height", restore)
fill_gizmo_id1, fill_gizmo_id2:
restore = start_offset
undo_redo.create_action("Edit ramp fill", 0, ramp, true)
undo_redo.add_do_property(ramp, "fill", end_offset)
undo_redo.add_undo_property(ramp, "fill", restore)
undo_redo.commit_action()
is_editing = false