@@ -22,37 +22,61 @@ func set_position(new_position: PackedFloat64Array) -> void:
2222 var pathdata : AttributePathdata = element .get_attribute (pathdata_name )
2323
2424 # Constrain to match the angle of the neighboring segment if Ctrl is pressed.
25+ # Quadratic beziers can have two neighboring segments, align to the closest.
2526 if Input .is_key_pressed (KEY_CTRL ):
2627 var cmd := pathdata .get_command (command_index )
28+ var cmd_char := cmd .command_char .to_lower ()
29+ var subpath := pathdata .get_subpath (command_index )
2730
28- var opposite : PackedFloat64Array
29- var offset : PackedFloat64Array
30- if command_index != 0 and pathdata .get_command (command_index - 1 ).command_char in "CcSsQqLlHhVv" and \
31- cmd .command_char in "CcQqLl" and not (cmd .command_char .to_lower () in "CcQq" and (x_param != "x1" or y_param != "y1" )):
32- # Using previous command.
33- var other := pathdata .get_command (command_index - 1 )
34- offset = [cmd .start_x , cmd .start_y ]
35-
36- match other .command_char :
37- "C" , "c" , "S" , "s" : opposite = [other .x2 , other .y2 ]
38- "Q" , "q" : opposite = [other .x1 , other .y1 ]
39- "L" , "l" , "H" , "h" , "V" , "v" : opposite = [other .start_x , other .start_y ]
40- elif command_index < pathdata .get_command_count () - 1 and pathdata .get_command (command_index + 1 ).command_char in "CcQqLlHhVv" and \
41- cmd .command_char in "CcSsQq" and not (cmd .command_char in "CcSs" and \
42- (x_param != "x2" or y_param != "y2" ) or cmd .command_char in "Qq" and (x_param != "x1" or y_param != "y1" )):
43- # Using next command.
44- var other := pathdata .get_command (command_index + 1 )
45- offset = [other .start_x , other .start_y ]
46-
47- match other .command_char :
48- "C" , "c" , "Q" , "q" : opposite = [other .x1 , other .y1 ]
49- "L" , "l" : opposite = [other .x , other .y ]
50- "H" , "h" : opposite = [other .x , other .start_y ]
51- "V" , "v" : opposite = [other .start_x , other .y ]
31+ # Batches of 4 coordinates representing lines: [x1, y1, x2, y2].
32+ var alignment_lines : Array [PackedFloat64Array ] = []
5233
53- opposite = [opposite [0 ] - offset [0 ], opposite [1 ] - offset [1 ]]
54- new_position = Utils64Bit .vector_project ([new_position [0 ] - offset [0 ], new_position [1 ] - offset [1 ]], opposite )
55- new_position = [new_position [0 ] + offset [0 ], new_position [1 ] + offset [1 ]]
34+ if cmd_char in "l" or (cmd_char in "cq" and x_param == "x1" and y_param == "y1" ):
35+ var prev_cmd_index := command_index
36+ while true :
37+ prev_cmd_index -= 1
38+ if prev_cmd_index < subpath .x and pathdata .get_command (subpath .y ) is PathCommand .CloseCommand :
39+ prev_cmd_index = subpath .y
40+ if not pathdata .is_command_zero_length (prev_cmd_index ) or prev_cmd_index == command_index :
41+ break
42+ if prev_cmd_index != command_index :
43+ var prev_cmd := pathdata .get_command (prev_cmd_index )
44+ var new_line := PackedFloat64Array ([cmd .start_x , cmd .start_y ])
45+ match prev_cmd .command_char .to_lower ():
46+ "c" , "s" : alignment_lines .append (new_line + PackedFloat64Array ([prev_cmd .x2 , prev_cmd .y2 ]))
47+ "q" : alignment_lines .append (new_line + PackedFloat64Array ([prev_cmd .x1 , prev_cmd .y1 ]))
48+ "l" , "h" , "v" : alignment_lines .append (new_line + PackedFloat64Array ([prev_cmd .start_x , prev_cmd .start_y ]))
49+
50+ if (cmd_char in "cs" and x_param == "x2" and y_param == "y2" ) or (cmd_char in "q" and x_param == "x1" and y_param == "y1" ):
51+ var next_cmd_index := command_index
52+ while true :
53+ next_cmd_index += 1
54+ if next_cmd_index > subpath .y and pathdata .get_command (subpath .y ) is PathCommand .CloseCommand :
55+ next_cmd_index = subpath .x
56+ if not pathdata .is_command_zero_length (next_cmd_index ) or next_cmd_index == command_index :
57+ break
58+ if next_cmd_index != command_index :
59+ var next_cmd := pathdata .get_command (next_cmd_index )
60+ var new_line := PackedFloat64Array ([cmd .x , cmd .y ])
61+ match next_cmd .command_char .to_lower ():
62+ "c" , "q" : alignment_lines .append (new_line + PackedFloat64Array ([next_cmd .x1 , next_cmd .y1 ]))
63+ "l" : alignment_lines .append (new_line + PackedFloat64Array ([next_cmd .x , next_cmd .y ]))
64+ "h" : alignment_lines .append (new_line + PackedFloat64Array ([next_cmd .x , next_cmd .start_y ]))
65+ "v" : alignment_lines .append (new_line + PackedFloat64Array ([next_cmd .start_x , next_cmd .y ]))
66+ "z" :
67+ var start_command := pathdata .get_command (subpath .x )
68+ alignment_lines .append (new_line + PackedFloat64Array ([start_command .x , start_command .y ]))
69+
70+ var closest_distance := INF
71+ for line in alignment_lines :
72+ var direction := [line [0 ] - line [2 ], line [1 ] - line [3 ]]
73+ var relative_position := [new_position [0 ] - line [2 ], new_position [1 ] - line [3 ]]
74+ var projected := Utils64Bit .vector_project (relative_position , direction )
75+ var projected_position := [projected [0 ] + line [2 ], projected [1 ] + line [3 ]]
76+ var distance := Utils64Bit .distance_squared_to (projected_position , new_position )
77+ if distance < closest_distance :
78+ closest_distance = distance
79+ new_position = projected_position
5680
5781 pathdata .set_command_property (command_index , x_param , new_position [0 ])
5882 pathdata .set_command_property (command_index , y_param , new_position [1 ])
0 commit comments