1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-20 12:33:14 +00:00

Move perspective lines with mouse (plus code improvements) (#821)

* Code Improvements to Perspective editor

* formatting

* fixed a bug

* Some drastic changes

* Delete src/UI/Tools directory

* Delete src/UI/UI/PerspectiveEditor directory

* Some drastic changes

* i messed up so here's the fix

* Added easy movement and rotation

* formatting

* useless script

* fix some things and add tracker disabler

* formatting

* fix incorrect length on redo

* remove useless code
This commit is contained in:
Variable 2023-02-18 05:33:46 +05:00 committed by GitHub
parent c60675b4e7
commit 0da30c015c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 304 additions and 198 deletions

View file

@ -193,7 +193,7 @@ func change_project() -> void:
Global.horizontal_ruler.update()
Global.vertical_ruler.update()
Global.references_panel.project_changed()
Global.perspective_editor.update()
Global.perspective_editor.update_points()
Global.cursor_position_label.text = "[%s×%s]" % [size.x, size.y]
Global.window_title = "%s - Pixelorama %s" % [name, Global.current_version]

View file

@ -162,15 +162,18 @@ func snap_position(position: Vector2) -> Vector2:
if Global.snap_to_perspective_guides:
for point in Global.current_project.vanishing_points:
for i in point.angles.size():
var angle: float = -deg2rad(point.angles[i])
var length: float = point.lengths[i]
var s1 := Vector2(point.position_x, point.position_y)
var s2 := s1 + Vector2(length * cos(angle), length * sin(angle))
var snap := _snap_to_guide(snap_to, position, snap_distance, s1, s2)
if snap == Vector2.INF:
continue
snap_to = snap
if point.has("pos_x") and point.has("pos_y"): # Sanity check
for i in point.lines.size():
if point.lines[i].has("angle") and point.lines[i].has("length"): # Sanity check
var angle: float = deg2rad(point.lines[i].angle)
var length: float = point.lines[i].length
var start = Vector2(point.pos_x, point.pos_y)
var s1: Vector2 = start
var s2 := s1 + Vector2(length * cos(angle), length * sin(angle))
var snap := _snap_to_guide(snap_to, position, snap_distance, s1, s2)
if snap == Vector2.INF:
continue
snap_to = snap
if snap_to != Vector2.INF:
position = snap_to.floor()

View file

@ -1,9 +0,0 @@
extends Button
onready var length_slider = $"%LengthSlider"
func _ready():
var p_size = Global.current_project.size
var suitable_length = sqrt(pow(p_size.x, 2) + pow(p_size.y, 2))
length_slider.max_value = suitable_length

View file

@ -35,6 +35,7 @@ size_flags_horizontal = 3
margin_right = 51.0
max_value = 359.999
step = 0.001
allow_greater = true
prefix = "Angle:"
suffix = "°"
@ -43,6 +44,7 @@ unique_name_in_owner = true
margin_top = 28.0
margin_right = 51.0
margin_bottom = 52.0
min_value = 1.0
max_value = 19999.0
value = 19999.0
allow_greater = true

View file

@ -4,12 +4,12 @@ var axes: Node2D
var do_pool = [] # A pool that stores data of points removed by undo
var delete_pool = [] # A pool that containg deleted data and their index
var vanishing_point_res := preload("res://src/UI/PerspectiveEditor/VanishingPoint.tscn")
var tracker_disabled := false
onready var vanishing_point_container = $"%VanishingPointContainer"
func _on_AddPoint_pressed() -> void:
do_pool.clear() # Reset
do_pool.clear() # Reset (clears Redo history of vanishing points)
var project = Global.current_project
project.undos += 1
project.undo_redo.create_action("Add Vanishing Point")
@ -18,20 +18,16 @@ func _on_AddPoint_pressed() -> void:
project.undo_redo.commit_action()
func update():
for c in vanishing_point_container.get_children():
c.queue_free()
for idx in Global.current_project.vanishing_points.size():
var point_data = Global.current_project.vanishing_points[idx]
var vanishing_point := vanishing_point_res.instance()
vanishing_point_container.add_child(vanishing_point)
vanishing_point.initiate(point_data, idx)
func _on_TrackerLines_toggled(button_pressed):
for point in vanishing_point_container.get_children():
tracker_disabled = !button_pressed
func add_vanishing_point(is_redo := false):
var vanishing_point := vanishing_point_res.instance()
vanishing_point_container.add_child(vanishing_point)
if is_redo and !do_pool.empty():
# if it's a redo then initialize it with the redo data
vanishing_point.initiate(do_pool.pop_back())
vanishing_point.update_data_to_project()
else:
@ -41,7 +37,7 @@ func add_vanishing_point(is_redo := false):
func undo_add_vanishing_point():
var point = vanishing_point_container.get_child(vanishing_point_container.get_child_count() - 1)
point.queue_free()
do_pool.append(point.data.duplicate())
do_pool.append(point.serialize())
point.update_data_to_project(true)
@ -56,7 +52,7 @@ func delete_point(idx):
func do_delete_point(idx):
var point = vanishing_point_container.get_child(idx)
delete_pool.append(point.data)
delete_pool.append(point.serialize())
point.queue_free()
point.update_data_to_project(true)
@ -64,4 +60,18 @@ func do_delete_point(idx):
func undo_delete_point(idx):
var point = delete_pool.pop_back()
Global.current_project.vanishing_points.insert(idx, point)
update()
update_points()
func update_points():
# Delete old vanishing points
for c in vanishing_point_container.get_children():
c.queue_free()
# Add the "updated" vanising points from the current_project
for idx in Global.current_project.vanishing_points.size():
# Create the point
var vanishing_point := vanishing_point_res.instance()
vanishing_point_container.add_child(vanishing_point)
# Initialize it
var point_data = Global.current_project.vanishing_points[idx]
vanishing_point.initiate(point_data, idx)

View file

@ -45,18 +45,25 @@ rect_min_size = Vector2( 30, 30 )
text = "+"
clip_text = true
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
[node name="TrackerLines" type="CheckButton" parent="VBoxContainer"]
margin_top = 34.0
margin_right = 262.0
margin_bottom = 38.0
margin_bottom = 74.0
pressed = true
text = "Tracker Lines"
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
margin_top = 78.0
margin_right = 262.0
margin_bottom = 82.0
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
margin_top = 42.0
margin_top = 86.0
margin_right = 262.0
margin_bottom = 46.0
margin_bottom = 90.0
[node name="Content" type="ScrollContainer" parent="VBoxContainer"]
margin_top = 50.0
margin_top = 94.0
margin_right = 262.0
margin_bottom = 210.0
size_flags_vertical = 3
@ -64,9 +71,10 @@ size_flags_vertical = 3
[node name="VanishingPointContainer" type="VBoxContainer" parent="VBoxContainer/Content"]
unique_name_in_owner = true
margin_right = 262.0
margin_bottom = 160.0
margin_bottom = 116.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_constants/separation = 5
[connection signal="pressed" from="VBoxContainer/Header/AddPoint" to="." method="_on_AddPoint_pressed"]
[connection signal="toggled" from="VBoxContainer/TrackerLines" to="." method="_on_TrackerLines_toggled"]

View file

@ -1,101 +1,156 @@
class_name PerspectiveLine
extends Line2D
const INPUT_WIDTH := 4
const LINE_WIDTH := 2
const CIRCLE_RAD := 4
var angle := 0
var length := 19999
var hidden = false
var has_focus := false
var track_mouse := false
var _data = {"start": Vector2.ZERO, "angle": 0, "length": 19999, "color": Color.black}
var change_length = false
var line_button: Node
var _vanishing_point: Node
func initiate(data: Dictionary):
width = Global.camera.zoom.x * 2
func serialize() -> Dictionary:
var data = {"angle": angle, "length": length}
return data
func deserialize(data: Dictionary):
if data.has("angle"):
angle = data.angle
if data.has("length"):
length = data.length
func initiate(data: Dictionary, vanishing_point: Node):
_vanishing_point = vanishing_point
width = Global.camera.zoom.x * LINE_WIDTH
Global.canvas.add_child(self)
refresh(data)
deserialize(data)
refresh()
func refresh(data: Dictionary):
_data = data
default_color = data.color
func refresh():
default_color = _vanishing_point.color
draw_perspective_line()
func draw_perspective_line():
var angle = -_data.angle
points[0] = _data.start
var start = Vector2(_vanishing_point.pos_x.value, _vanishing_point.pos_y.value)
points[0] = start
if hidden:
points[1] = _data.start
points[1] = start
else:
points[1] = (
_data.start
+ Vector2(_data.length * cos(deg2rad(angle)), _data.length * sin(deg2rad(angle)))
)
points[1] = (start + Vector2(length * cos(deg2rad(angle)), length * sin(deg2rad(angle))))
func hide_perspective_line():
points[1] = _data.start
var start = Vector2(_vanishing_point.pos_x.value, _vanishing_point.pos_y.value)
points[1] = start
hidden = true
func _input(event: InputEvent) -> void:
if event is InputEventMouse:
var mouse_point = Global.canvas.current_pixel
var project_size = Global.current_project.size
if track_mouse:
if !Global.can_draw or !Global.has_focus:
if !Global.can_draw or !Global.has_focus or Global.perspective_editor.tracker_disabled:
hide_perspective_line()
return
default_color.a = 0.5
var tmp_transform = get_canvas_transform().affine_inverse()
var tmp_position = Global.main_viewport.get_local_mouse_position()
var mouse_point = tmp_transform.basis_xform(tmp_position) + tmp_transform.origin
var project_size = Global.current_project.size
if Rect2(Vector2.ZERO, project_size).has_point(mouse_point):
var start = Vector2(_vanishing_point.pos_x.value, _vanishing_point.pos_y.value)
hidden = false
draw_perspective_line()
var rel_vector = mouse_point - _data.start
var test_vector = Vector2(_data.start.x, 0)
if sign(test_vector.x) == 0:
test_vector.x += 0.5
_data.angle = rad2deg(test_vector.angle_to(rel_vector))
if sign(test_vector.x) == -1:
_data.angle += 180
angle = rad2deg(mouse_point.angle_to_point(points[0]))
if angle < 0:
angle += 360
points[1] = (
_data.start
+ Vector2(
_data.length * cos(deg2rad(_data.angle)),
_data.length * sin(deg2rad(_data.angle))
)
start
+ Vector2(length * cos(deg2rad(angle)), length * sin(deg2rad(angle)))
)
else:
hide_perspective_line()
else:
try_rotate_scale()
update()
func try_rotate_scale():
var mouse_point = Global.canvas.current_pixel
var project_size = Global.current_project.size
var test_line := (points[1] - points[0]).rotated(deg2rad(90)).normalized()
var from_a = mouse_point - test_line * Global.camera.zoom.x * LINE_WIDTH * 2
var from_b = mouse_point + test_line * Global.camera.zoom.x * LINE_WIDTH * 2
if Input.is_action_just_pressed("left_mouse") and Global.can_draw and Global.has_focus:
if (
Geometry.segment_intersects_segment_2d(from_a, from_b, points[0], points[1])
or mouse_point.distance_to(points[1]) < Global.camera.zoom.x * CIRCLE_RAD * 2
):
if (
!Rect2(Vector2.ZERO, project_size).has_point(mouse_point)
or Global.move_guides_on_canvas
):
if mouse_point.distance_to(points[1]) < Global.camera.zoom.x * CIRCLE_RAD * 2:
change_length = true
has_focus = true
Global.has_focus = false
update()
if has_focus:
if Input.is_action_pressed("left_mouse"):
# rotation code here
if line_button:
var new_angle = rad2deg(mouse_point.angle_to_point(points[0]))
if new_angle < 0:
new_angle += 360
_vanishing_point.angle_changed(new_angle, line_button)
if change_length:
var new_length = mouse_point.distance_to(points[0])
_vanishing_point.length_changed(new_length, line_button)
elif Input.is_action_just_released("left_mouse"):
Global.has_focus = true
has_focus = false
change_length = false
update()
func _draw() -> void:
draw_circle(_data.start, Global.camera.zoom.x * 5, default_color)
width = Global.camera.zoom.x * 2
var mouse_point = Global.canvas.current_pixel
var arc_points = []
draw_circle(points[0], Global.camera.zoom.x * CIRCLE_RAD, default_color) # Starting circle
if !track_mouse and mouse_point.distance_to(points[0]) < Global.camera.zoom.x * CIRCLE_RAD * 2:
if (
!Rect2(Vector2.ZERO, Global.current_project.size).has_point(mouse_point)
or Global.move_guides_on_canvas
or has_focus
):
arc_points.append(points[0])
if (
mouse_point.distance_to(points[1]) < Global.camera.zoom.x * CIRCLE_RAD * 2
or (has_focus and Input.is_action_pressed("left_mouse"))
):
if (
!Rect2(Vector2.ZERO, Global.current_project.size).has_point(mouse_point)
or Global.move_guides_on_canvas
or has_focus
):
if !arc_points.has(points[0]):
arc_points.append(points[0])
arc_points.append(points[1])
for point in arc_points:
draw_arc(point, Global.camera.zoom.x * CIRCLE_RAD * 2, 0, 360, 360, default_color, 0.5)
width = Global.camera.zoom.x * LINE_WIDTH
if hidden: # Hidden line
return
var viewport_size: Vector2 = Global.main_viewport.rect_size
var zoom: Vector2 = Global.camera.zoom
# viewport_poly is an array of the points that make up the corners of the viewport
var viewport_poly := [
Vector2.ZERO, Vector2(viewport_size.x, 0), viewport_size, Vector2(0, viewport_size.y)
]
# Adjusting viewport_poly to take into account the camera offset, zoom, and rotation
for p in range(viewport_poly.size()):
viewport_poly[p] = (
viewport_poly[p].rotated(Global.camera.rotation) * zoom
+ Vector2(
(
Global.camera.offset.x
- (viewport_size.rotated(Global.camera.rotation).x / 2) * zoom.x
),
(
Global.camera.offset.y
- (viewport_size.rotated(Global.camera.rotation).y / 2) * zoom.y
)
)
)
# If there's no intersection with a viewport edge, show string in top left corner
draw_set_transform(viewport_poly[0], Global.camera.rotation, zoom * 2)

View file

@ -1,53 +1,66 @@
extends VBoxContainer
var has_focus := false
var perspective_lines = []
var color := Color(randf(), randf(), randf(), 1)
var tracker_line: PerspectiveLine
var data = {
"position_x": 0,
"position_y": 0,
"angles": [],
"lengths": [],
"color": Color(randf(), randf(), randf(), 1).to_html(),
}
onready var color_picker_button = $"%ColorPickerButton"
onready var title = $"%PointCollapseContainer"
onready var pos_x = $"%X"
onready var pos_y = $"%Y"
onready var line_buttons_container = $"%LinesContainer"
onready var boundary_l = $Content/BoundaryL
onready var boundary_r = $Content/BoundaryR
onready var boundary_b = $Content/VBoxContainer/BoundaryB
onready var title := $"%PointCollapseContainer"
onready var pos_x := $"%X"
onready var pos_y := $"%Y"
onready var line_buttons_container := $"%LinesContainer"
onready var boundary_l := $Content/BoundaryL
onready var boundary_r := $Content/BoundaryR
onready var boundary_b := $Content/VBoxContainer/BoundaryB
func initiate(start_data = null, idx = -1) -> void:
if start_data:
data = start_data.duplicate()
add_line(null, true)
for i in data.angles.size():
var loaded_line_data = {
"start": Vector2(data.position_x, data.position_y),
"angle": data.angles[i],
"length": data.lengths[i],
"color": Color(data.color)
}
add_line(loaded_line_data)
if idx != -1:
func serialize() -> Dictionary:
var lines_data := []
for line in perspective_lines:
lines_data.append(line.serialize())
var data = {
"pos_x": pos_x.value,
"pos_y": pos_y.value,
"lines": lines_data,
"color": color.to_html(),
}
return data
func deserialize(start_data: Dictionary):
if start_data: # Data is not {} means the project knows about this point
if start_data.has("pos_x") and start_data.has("pos_y"):
pos_x.value = start_data.pos_x
pos_y.value = start_data.pos_y
if start_data.has("color"):
color = Color(start_data.color)
# Add lines if their data is provided
if start_data.has("lines"):
for line_data in start_data["lines"]:
add_line(line_data)
else: # If the project doesn't know about this point
update_data_to_project()
add_line({}, true) # This is a tracker line (Always follows mouse)
color_picker_button.color = color
update_boundary_color()
func initiate(start_data: Dictionary = {}, idx = -1) -> void:
deserialize(start_data)
# Title of Vanishing point button
if idx != -1: # If the initialization is part of a Redo
title.point_text = str("Point: ", idx + 1)
else:
title.point_text = str("Point: ", get_parent().get_child_count())
pos_x.value = data.position_x
pos_y.value = data.position_y
color_picker_button.color = Color(data.color)
update_boundary_color(color_picker_button.color)
# connect signals
color_picker_button.connect("color_changed", self, "_on_color_changed")
pos_x.connect("value_changed", self, "_on_X_value_changed")
pos_y.connect("value_changed", self, "_on_Y_value_changed")
if !start_data:
update_data_to_project()
pos_x.connect("value_changed", self, "_on_pos_value_changed")
pos_y.connect("value_changed", self, "_on_pos_value_changed")
func update_boundary_color(color: Color):
func update_boundary_color():
var luminance = (0.2126 * color.r) + (0.7152 * color.g) + (0.0722 * color.b)
color.a = 0.9 - luminance * 0.4 # Interpolates between 0.5 to 0.9
boundary_l.color = color
@ -55,6 +68,33 @@ func update_boundary_color(color: Color):
boundary_b.color = color
func _input(_event):
var mouse_point = Global.canvas.current_pixel
var project_size = Global.current_project.size
var start = Vector2(pos_x.value, pos_y.value)
if (
Input.is_action_just_pressed("left_mouse")
and Global.can_draw
and Global.has_focus
and mouse_point.distance_to(start) < Global.camera.zoom.x * 8
):
if (
!Rect2(Vector2.ZERO, project_size).has_point(Global.canvas.current_pixel)
or Global.move_guides_on_canvas
):
has_focus = true
Global.has_focus = false
if has_focus:
if Input.is_action_pressed("left_mouse"):
# rotation code here
pos_x.value = mouse_point.x
pos_y.value = mouse_point.y
elif Input.is_action_just_released("left_mouse"):
Global.has_focus = true
has_focus = false
# Signals
func _on_AddLine_pressed() -> void:
add_line()
@ -66,35 +106,38 @@ func _on_Delete_pressed() -> void:
func _on_color_changed(_color: Color):
update_boundary_color(_color)
data.color = _color.to_html()
update_boundary_color()
color = _color
refresh(-1)
update_data_to_project()
func _on_X_value_changed(value: float) -> void:
data.position_x = value
func _on_pos_value_changed(_value: float) -> void:
refresh(-1)
update_data_to_project()
func _on_Y_value_changed(value):
data.position_y = value
refresh(-1)
func angle_changed(value: float, line_button):
# check if the properties are changing the line or is the line changing properties
var angle_slider = line_button.find_node("AngleSlider")
if angle_slider.value != value: # the line is changing the properties
angle_slider.value = value
else:
var line_index = line_button.get_index()
perspective_lines[line_index].angle = value
refresh(line_index)
update_data_to_project()
func _angle_changed(value: float, line_button):
var line_index = line_button.get_index()
data.angles[line_index] = value
refresh(line_index)
update_data_to_project()
func _length_changed(value: float, line_button):
var line_index = line_button.get_index()
data.lengths[line_index] = value
refresh(line_index)
func length_changed(value: float, line_button):
# check if the properties are changing the line or is the line changing properties
var length_slider = line_button.find_node("LengthSlider")
if length_slider.value != value: # the line is changing the properties
length_slider.value = value
else:
var line_index = line_button.get_index()
perspective_lines[line_index].length = value
refresh(line_index)
update_data_to_project()
@ -106,34 +149,39 @@ func _remove_line_pressed(line_button):
# Methods
func add_line(loaded_line_data = null, is_tracker := false):
var p_size = Global.current_project.size
var default_line_data = {
"start": Vector2(data.position_x, data.position_y),
"angle": 0,
"length": 19999,
"color": Color(data.color)
}
if default_line_data.start.x > p_size.x / 2:
# If new line is created ahed of half project distance then
# reverse it's angle (for beautification)
default_line_data.angle = 180
func generate_line_data(initial_data: Dictionary = {}) -> Dictionary:
# The default data
var line_data = {"angle": 0, "length": 19999}
# If any data needs to be changed by initial_data from project (or possibly by redo data)
if initial_data.has("angle"):
line_data.angle = initial_data["angle"]
if initial_data.has("length"):
line_data.length = initial_data["length"]
return line_data
# Check if we have loading data instead of creating line
# Then THAT should be our data
if loaded_line_data:
default_line_data = loaded_line_data
func add_line(loaded_line_data := {}, is_tracker := false):
var p_size = Global.current_project.size # for use later in function
# Note: line_data will automatically get default values if loaded_line_data = {}
var line_data = generate_line_data(loaded_line_data)
# This code in if block is purely for beautification
if pos_x.value > p_size.x / 2 and !loaded_line_data:
# If new line is created ahed of half project distance then
# reverse it's angle
line_data.angle = 180
if is_tracker: # if we are creating tracker line then length adjustment is not required
if tracker_line != null: # Also if the tracker line already exists then cancel creation
return
else: # If we are not creating a perspective line then adjust it's length
var suitable_length = sqrt(pow(p_size.x, 2) + pow(p_size.y, 2))
default_line_data.length = suitable_length
if !loaded_line_data:
line_data.length = p_size.x
# Create the visual line
var line = preload("res://src/UI/PerspectiveEditor/PerspectiveLine.tscn").instance()
line.initiate(default_line_data)
line.initiate(line_data, self)
# Set it's mode accordingly
if is_tracker: # Settings for Tracker mode
@ -147,7 +195,7 @@ func add_line(loaded_line_data = null, is_tracker := false):
line_button.get_parent().move_child(line_button, index)
var line_name = str(
"Line", line_button.get_index() + 1, " (", int(default_line_data.angle), "°)"
"Line", line_button.get_index() + 1, " (", int(abs(line_data.angle)), "°)"
)
line_button.text = line_name
@ -155,14 +203,12 @@ func add_line(loaded_line_data = null, is_tracker := false):
var angle_slider = line_button.find_node("AngleSlider")
var length_slider = line_button.find_node("LengthSlider")
angle_slider.value = default_line_data.angle
length_slider.value = default_line_data.length
if !loaded_line_data:
data.angles.append(angle_slider.value)
data.lengths.append(length_slider.value)
angle_slider.value = abs(line_data.angle)
length_slider.value = line_data.length
angle_slider.connect("value_changed", self, "_angle_changed", [line_button])
length_slider.connect("value_changed", self, "_length_changed", [line_button])
line.line_button = line_button # In case we need to change properties from line
angle_slider.connect("value_changed", self, "angle_changed", [line_button])
length_slider.connect("value_changed", self, "length_changed", [line_button])
remove_button.connect("pressed", self, "_remove_line_pressed", [line_button])
perspective_lines.append(line)
@ -170,54 +216,45 @@ func add_line(loaded_line_data = null, is_tracker := false):
func remove_line(line_index):
var line_to_remove = perspective_lines[line_index]
perspective_lines.remove(line_index)
data.angles.remove(line_index)
data.lengths.remove(line_index)
line_to_remove.queue_free()
func update_data_to_project(removal := false):
var project = Global.current_project
var idx = get_index()
# If deletion is requested
if removal:
project.vanishing_points.remove(idx)
return
# If project knows about this vanishing point then update it
var data = serialize()
if idx < project.vanishing_points.size():
project.vanishing_points[idx] = data
# If project doesn't know about this vanishing point then NOW it knows
else:
project.vanishing_points.append(data)
Global.current_project.has_changed = true
func refresh(index: int):
if index == -1: # means all lines should be refreshed
if index == -1: # means all lines should be refreshed (including the tracker line)
refresh_tracker()
for i in data.angles.size():
for i in perspective_lines.size():
refresh_line(i)
else:
refresh_line(index)
func refresh_line(index: int):
var line_data = {
"start": Vector2(data.position_x, data.position_y),
"angle": data.angles[index],
"length": data.lengths[index],
"color": Color(data.color)
}
var line_button = line_buttons_container.get_child(index)
var line_name = str("Line", line_button.get_index() + 1, " (", int(line_data.angle), "°)")
var line_data = perspective_lines[index].serialize()
var line_name = str("Line", line_button.get_index() + 1, " (", int(abs(line_data.angle)), "°)")
line_button.text = line_name
perspective_lines[index].refresh(line_data)
perspective_lines[index].refresh()
func refresh_tracker():
var line_data = {
"start": Vector2(data.position_x, data.position_y),
"angle": tracker_line._data.angle,
"length": tracker_line._data.length,
"color": Color(data.color)
}
tracker_line.refresh(line_data)
tracker_line.refresh()
func _exit_tree() -> void: