aboutsummaryrefslogtreecommitdiff
path: root/src/addons/rmsmartshape/shapes/shape_anchor.gd
blob: f10fee67728dc04189b00447fda6b450b26b32d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
tool
extends Node2D

const DEBUG_DRAW_LINE_LENGTH = 128.0

class_name SS2D_Shape_Anchor, "../assets/Anchor.svg"

export (NodePath) var shape_path: NodePath setget set_shape_path
export (int) var shape_point_index: int = 0 setget set_shape_point_index
export (float, 0.0, 1.0) var shape_point_offset: float = 0.0 setget set_shape_point_offset
export (float, 0, 3.14) var child_rotation: float = 3.14 setget set_child_rotation
export (bool) var use_shape_scale: bool = false setget set_use_shape_scale

export (bool) var debug_draw: bool = false setget set_debug_draw

var cached_shape_transform:Transform2D = Transform2D.IDENTITY
var shape = null


###########
# SETTERS #
###########
func set_shape_path(value: NodePath):
	# Assign path value
	shape_path = value
	set_shape()

	property_list_changed_notify()
	refresh()

func set_shape():
	# 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 = get_node(shape_path)
		if not new_node is SS2D_Shape_Base:
			push_error("Shape Path isn't a valid subtype of SS2D_Shape_Base! 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_Base, idx:int)->int:
	var point_count = 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 = point_count - 2
	if idx < 0:
		idx = final_idx
	idx = idx % (final_idx + 1)
	return idx

func set_shape_point_index(value: int):
	if value == shape_point_index:
		return

	if shape == null:
		shape_point_index = value
		return

	shape_point_index = get_shape_index_range(shape, value)
	#property_list_changed_notify()
	refresh()


func set_shape_point_offset(value: float):
	shape_point_offset = value
	#property_list_changed_notify()
	refresh()


func set_use_shape_scale(value: bool):
	use_shape_scale = value
	#property_list_changed_notify()
	refresh()


func set_child_rotation(value: float):
	child_rotation = value
	#property_list_changed_notify()
	refresh()


func set_debug_draw(v: bool):
	debug_draw = v
	#property_list_changed_notify()
	refresh()


##########
# EVENTS #
##########
func _process(delta):
	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():
	set_shape_path("")


func _handle_point_change():
	refresh()


#########
# LOGIC #
#########
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2:
	var q0 = p0.linear_interpolate(p1, t)
	var q1 = p1.linear_interpolate(p2, t)
	var q2 = p2.linear_interpolate(p3, t)

	var r0 = q0.linear_interpolate(q1, t)
	var r1 = q1.linear_interpolate(q2, t)

	var s = r0.linear_interpolate(r1, t)
	return s


func disconnect_shape(s: SS2D_Shape_Base):
	s.disconnect("points_modified", self, "_handle_point_change")
	s.disconnect("tree_exiting", self, "_monitored_node_leaving")


func connect_shape(s: SS2D_Shape_Base):
	s.connect("points_modified", self, "_handle_point_change")
	s.connect("tree_exiting", self, "_monitored_node_leaving")


func refresh():
	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 = shape.get_point_count() - 1

	var pt_a_index = shape_point_index
	var pt_b_index = shape_point_index + 1
	var pt_a_key = shape.get_point_key_at_index(pt_a_index)
	var pt_b_key = shape.get_point_key_at_index(pt_b_index)

	var pt_a: Vector2 = shape.global_transform.xform(shape.get_point_position(pt_a_key))
	var pt_b: Vector2 = shape.global_transform.xform(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.xform(
		shape.get_point_position(pt_a_key) + shape.get_point_out(pt_a_key)
	)
	pt_b_handle = shape.global_transform.xform(
		shape.get_point_position(pt_b_key) + shape.get_point_in(pt_b_key)
	)

	n_pt = _cubic_bezier(pt_a, pt_a_handle, pt_b_handle, pt_b, shape_point_offset)
	n_pt_a = _cubic_bezier(
		pt_a, pt_a_handle, pt_b_handle, pt_b, clamp(shape_point_offset - 0.1, 0.0, 1.0)
	)
	n_pt_b = _cubic_bezier(
		pt_a, pt_a_handle, pt_b_handle, pt_b, clamp(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

	update()


func _draw():
	if Engine.editor_hint and debug_draw:
		draw_line(Vector2.ZERO, Vector2(0, -DEBUG_DRAW_LINE_LENGTH), self.modulate)