209 lines
5.1 KiB
GDScript
209 lines
5.1 KiB
GDScript
@tool
|
|
@icon("../assets/Anchor.svg")
|
|
extends Node2D
|
|
class_name SS2D_Shape_Anchor
|
|
|
|
const DEBUG_DRAW_LINE_LENGTH := 128.0
|
|
|
|
@export var shape_path: NodePath : set = set_shape_path
|
|
@export var shape_point_index: int = 0 : set = set_shape_point_index
|
|
@export_range (0.0, 1.0) var shape_point_offset: float = 0.0 : set = set_shape_point_offset
|
|
@export_range (0, 3.14) var child_rotation: float = 3.14 : set = set_child_rotation
|
|
@export var use_shape_scale: bool = false : set = set_use_shape_scale
|
|
|
|
@export var debug_draw: bool = false : set = set_debug_draw
|
|
|
|
var cached_shape_transform: Transform2D = Transform2D.IDENTITY
|
|
var shape: SS2D_Shape = null
|
|
|
|
|
|
###########
|
|
#-SETTERS-#
|
|
###########
|
|
|
|
func set_shape_path(value: NodePath) -> void:
|
|
# Assign path value
|
|
shape_path = value
|
|
set_shape()
|
|
|
|
notify_property_list_changed()
|
|
refresh()
|
|
|
|
|
|
func set_shape() -> void:
|
|
# Disconnect old shape
|
|
if shape != null:
|
|
disconnect_shape(shape)
|
|
|
|
# Set shape if path is valid and connect
|
|
shape = null
|
|
if has_node(shape_path):
|
|
var new_node: Node = get_node(shape_path)
|
|
if not new_node is SS2D_Shape:
|
|
push_error("Shape Path isn't a valid subtype of SS2D_Shape! Aborting...")
|
|
return
|
|
shape = new_node
|
|
connect_shape(shape)
|
|
shape_point_index = get_shape_index_range(shape, shape_point_index)
|
|
|
|
|
|
func get_shape_index_range(s: SS2D_Shape, idx: int) -> int:
|
|
var point_count: int = s.get_point_count()
|
|
# Subtract 2;
|
|
# 'point_count' is out of bounds; subtract 1
|
|
# cannot use final idx as starting point_index; subtract another 1
|
|
var final_idx: int = point_count - 2
|
|
if idx < 0:
|
|
idx = final_idx
|
|
idx = idx % (final_idx + 1)
|
|
return idx
|
|
|
|
|
|
func set_shape_point_index(value: int) -> void:
|
|
if value == shape_point_index:
|
|
return
|
|
|
|
if shape == null:
|
|
shape_point_index = value
|
|
return
|
|
|
|
shape_point_index = get_shape_index_range(shape, value)
|
|
#notify_property_list_changed()
|
|
refresh()
|
|
|
|
|
|
func set_shape_point_offset(value: float) -> void:
|
|
shape_point_offset = value
|
|
#notify_property_list_changed()
|
|
refresh()
|
|
|
|
|
|
func set_use_shape_scale(value: bool) -> void:
|
|
use_shape_scale = value
|
|
#notify_property_list_changed()
|
|
refresh()
|
|
|
|
|
|
func set_child_rotation(value: float) -> void:
|
|
child_rotation = value
|
|
#notify_property_list_changed()
|
|
refresh()
|
|
|
|
|
|
func set_debug_draw(v: bool) -> void:
|
|
debug_draw = v
|
|
#notify_property_list_changed()
|
|
refresh()
|
|
|
|
|
|
##########
|
|
#-EVENTS-#
|
|
##########
|
|
func _process(_delta: float) -> void:
|
|
if shape == null:
|
|
set_shape()
|
|
return
|
|
if shape.is_queued_for_deletion():
|
|
return
|
|
if shape.get_global_transform() != cached_shape_transform:
|
|
cached_shape_transform = shape.get_global_transform()
|
|
refresh()
|
|
|
|
|
|
func _monitored_node_leaving() -> void:
|
|
pass
|
|
|
|
|
|
func _handle_point_change() -> void:
|
|
refresh()
|
|
|
|
|
|
#########
|
|
#LOGIC-#
|
|
#########
|
|
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2:
|
|
var q0 := p0.lerp(p1, t)
|
|
var q1 := p1.lerp(p2, t)
|
|
var q2 := p2.lerp(p3, t)
|
|
|
|
var r0 := q0.lerp(q1, t)
|
|
var r1 := q1.lerp(q2, t)
|
|
|
|
var s := r0.lerp(r1, t)
|
|
return s
|
|
|
|
|
|
func disconnect_shape(s: SS2D_Shape) -> void:
|
|
s.disconnect("points_modified", self._handle_point_change)
|
|
s.disconnect("tree_exiting", self._monitored_node_leaving)
|
|
|
|
|
|
func connect_shape(s: SS2D_Shape) -> void:
|
|
s.connect("points_modified", self._handle_point_change)
|
|
s.connect("tree_exiting", self._monitored_node_leaving)
|
|
|
|
|
|
func refresh() -> void:
|
|
if shape == null:
|
|
return
|
|
if not is_instance_valid(shape):
|
|
return
|
|
if shape.is_queued_for_deletion():
|
|
disconnect_shape(shape)
|
|
shape = null
|
|
return
|
|
|
|
# Subtract one, cannot use final point as starting index
|
|
# var point_count: int = shape.get_point_count() - 1
|
|
|
|
var pt_a_index: int = shape_point_index
|
|
var pt_b_index: int = shape_point_index + 1
|
|
var pt_a_key: int = shape.get_point_key_at_index(pt_a_index)
|
|
var pt_b_key: int = shape.get_point_key_at_index(pt_b_index)
|
|
|
|
var pt_a: Vector2 = shape.global_transform * shape.get_point_position(pt_a_key)
|
|
var pt_b: Vector2 = shape.global_transform * shape.get_point_position(pt_b_key)
|
|
|
|
var pt_a_handle: Vector2
|
|
var pt_b_handle: Vector2
|
|
|
|
var n_pt: Vector2
|
|
var n_pt_a: Vector2
|
|
var n_pt_b: Vector2
|
|
|
|
var angle := 0.0
|
|
|
|
pt_a_handle = shape.global_transform * (
|
|
shape.get_point_position(pt_a_key) + shape.get_point_out(pt_a_key)
|
|
)
|
|
pt_b_handle = shape.global_transform * (
|
|
shape.get_point_position(pt_b_key) + shape.get_point_in(pt_b_key)
|
|
)
|
|
|
|
# If this segment uses no bezier curve, use linear interpolation instead
|
|
if pt_a_handle != pt_a or pt_b_handle != pt_b:
|
|
n_pt = _cubic_bezier(pt_a, pt_a_handle, pt_b_handle, pt_b, shape_point_offset)
|
|
else:
|
|
n_pt = pt_a.lerp(pt_b, shape_point_offset)
|
|
|
|
n_pt_a = _cubic_bezier(
|
|
pt_a, pt_a_handle, pt_b_handle, pt_b, clampf(shape_point_offset - 0.1, 0.0, 1.0)
|
|
)
|
|
n_pt_b = _cubic_bezier(
|
|
pt_a, pt_a_handle, pt_b_handle, pt_b, clampf(shape_point_offset + 0.1, 0.0, 1.0)
|
|
)
|
|
|
|
angle = atan2(n_pt_a.y - n_pt_b.y, n_pt_a.x - n_pt_b.x)
|
|
|
|
self.global_transform = Transform2D(angle + child_rotation, n_pt)
|
|
|
|
if use_shape_scale:
|
|
self.scale = shape.scale
|
|
|
|
queue_redraw()
|
|
|
|
|
|
func _draw() -> void:
|
|
if Engine.is_editor_hint() and debug_draw:
|
|
draw_line(Vector2.ZERO, Vector2(0, -DEBUG_DRAW_LINE_LENGTH), self.modulate)
|