418 lines
17 KiB
GDScript
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
|