free_transform.gd 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. extends Sprite2D
  2. signal value_change(index: int, position: Vector2, rotation: float, scale: Vector2, skew: float)
  3. @onready var target: Sprite2D
  4. @export var zoom = 1.0
  5. var target_index = -1
  6. var is_dragging = false
  7. var cursor_sprite_frames: SpriteFrames
  8. var local_direction = 0
  9. var cursor_direction = 0
  10. var action = ""
  11. var transform_start_local_direction = 0
  12. var transform_start_cursor_direction = 0
  13. var transform_start_global_position: Vector2
  14. var transform_start_position: Vector2
  15. var transform_start_rotation: float
  16. var transform_start_scale: Vector2
  17. var _corners = [
  18. Vector2(1.0, 0.5), Vector2(1.0, 0.0), Vector2(0.5, 0.0), Vector2(0.0, 0.0),
  19. Vector2(0.0, 0.5), Vector2(0.0, 1.0), Vector2(0.5, 1.0), Vector2(1.0, 1.0)
  20. ]
  21. var editable = true
  22. func _ready() -> void:
  23. set_target(null, -1)
  24. cursor_sprite_frames = load("res://Resources/cursor_sprite_frames.tres")
  25. func set_target(_sprite: Sprite2D, _index: int, _editable: bool = false):
  26. target = _sprite
  27. target_index = _index
  28. editable = _editable
  29. if target == null:
  30. visible = false
  31. return
  32. visible = true
  33. var image = Image.create(target.texture.get_width() + 512, target.texture.get_height() + 512, false, Image.FORMAT_RGBA8)
  34. texture = ImageTexture.create_from_image(image)
  35. position = target.position
  36. rotation = target.rotation
  37. scale = target.scale
  38. skew = target.skew
  39. func grow_rect(rect: Rect2, amount: float, _scale: Vector2) -> Rect2:
  40. var amount_x = clampf(amount / abs(_scale.x), 1.0, 9999.9) if _scale.x != 0.0 else 1.0
  41. var amount_y = clampf(amount / abs(_scale.y), 1.0, 9999.9) if _scale.y != 0.0 else 1.0
  42. return rect.grow_individual(amount_x, amount_y, amount_x, amount_y)
  43. func _process(_delta: float) -> void:
  44. if target == null:
  45. return
  46. update_shader_params()
  47. func _input(event):
  48. if target == null or editable == false:
  49. return
  50. if event is InputEventMouseButton:
  51. if event.button_index == MOUSE_BUTTON_LEFT:
  52. if event.pressed and action != "":
  53. transform_start_global_position = get_global_mouse_position()
  54. transform_start_local_direction = local_direction
  55. transform_start_cursor_direction = cursor_direction
  56. transform_start_position = target.position
  57. transform_start_rotation = target.rotation
  58. transform_start_scale = target.scale
  59. is_dragging = true
  60. get_viewport().set_input_as_handled()
  61. else:
  62. is_dragging = false
  63. elif event is InputEventKey:
  64. if event.pressed and not event.echo:
  65. if Input.is_key_pressed(KEY_ENTER) or Input.is_key_pressed(KEY_KP_ENTER):
  66. is_dragging = false
  67. set_target(null, -1)
  68. Input.set_custom_mouse_cursor(null)
  69. return
  70. var _offset = Vector2.ZERO;
  71. if Input.is_key_pressed(KEY_LEFT): _offset.x = -1
  72. if Input.is_key_pressed(KEY_RIGHT): _offset.x = 1
  73. if Input.is_key_pressed(KEY_UP): _offset.y = -1
  74. if Input.is_key_pressed(KEY_DOWN): _offset.y = 1
  75. if _offset == Vector2.ZERO: return
  76. if event.ctrl_pressed:
  77. _offset *= 10;
  78. target.position += _offset;
  79. position = target.position
  80. value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew)
  81. get_viewport().set_input_as_handled()
  82. elif event is InputEventMouseMotion and is_dragging:
  83. if action == "Move":
  84. var _offset = get_global_mouse_position() - transform_start_global_position;
  85. if event.ctrl_pressed:
  86. if abs(_offset.x) < abs(_offset.y):
  87. _offset.x = 0
  88. else:
  89. _offset.y = 0
  90. target.position = transform_start_position + _offset;
  91. position = target.position
  92. value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew)
  93. get_viewport().set_input_as_handled()
  94. elif action == "Rotation":
  95. var vector_OA = (transform_start_global_position - transform_start_position).normalized()
  96. var vector_OB = (get_global_mouse_position() - transform_start_position).normalized()
  97. var angle_rad = -vector_OA.angle_to(vector_OB)
  98. if angle_rad < 0: angle_rad += 2 * PI
  99. if event.ctrl_pressed:
  100. angle_rad = deg_to_rad(round(rad_to_deg(angle_rad) / 15.0) * 15.0)
  101. target.rotation = transform_start_rotation - angle_rad
  102. rotation = target.rotation
  103. value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew)
  104. get_viewport().set_input_as_handled()
  105. elif action == "Scale":
  106. # 将起始点和当前点转换到Sprite的局部坐标系
  107. var start_local = target.to_local(transform_start_global_position)
  108. var current_local = target.to_local(get_global_mouse_position())
  109. # 在局部坐标系中计算缩放
  110. var scale_delta = Vector2.ONE
  111. var start_vec = start_local - target.offset # 从中心点到起始点的向量
  112. var current_vec = current_local - target.offset # 从中心点到当前点的向量
  113. # 避免除零错误
  114. if start_vec.x != 0: scale_delta.x = current_vec.x / start_vec.x
  115. if start_vec.y != 0: scale_delta.y = current_vec.y / start_vec.y
  116. # 根据拖拽方向锁定轴
  117. if transform_start_local_direction == 0 or transform_start_local_direction == 4: # 左右
  118. scale_delta.y = 1.0
  119. elif transform_start_local_direction == 2 or transform_start_local_direction == 6: # 上下
  120. scale_delta.x = 1.0
  121. # 等比缩放
  122. if event.ctrl_pressed:
  123. var uniform_scale = (scale_delta.x + scale_delta.y) / 2.0
  124. scale_delta = Vector2(uniform_scale, uniform_scale)
  125. # 应用缩放
  126. target.scale = transform_start_scale * scale_delta
  127. scale = target.scale
  128. value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew)
  129. get_viewport().set_input_as_handled()
  130. #func _draw() -> void:
  131. #var _scale = target.scale * zoom
  132. #var r0 = target.get_rect()
  133. #var r1 = grow_rect(r0, -15, _scale) # move
  134. #var r2 = grow_rect(r0, 30, _scale) # scale
  135. #var r3 = grow_rect(r0, 80, _scale) # rotation
  136. #draw_rect(r0, Color.WHITE, false)
  137. #draw_rect(r1, Color.WHITE, false)
  138. #draw_rect(r2, Color.WHITE, false)
  139. #draw_rect(r3, Color.WHITE, false)
  140. #
  141. #var texture_size = target.texture.get_size()
  142. #var s = Vector2(1.0, texture_size.y / texture_size.x) if texture_size.x < texture_size.y else Vector2(texture_size.x / texture_size.y, 1.0)
  143. #var t = Transform2D(0.0, s, 0.0, Vector2.ZERO)
  144. #var scale_local_mouse_pos = t.affine_inverse() * get_local_mouse_position()
  145. #
  146. #var PI16 = PI / 16;
  147. #for i in range(32):
  148. #var p = (Vector2.RIGHT * 3000.0).rotated(i * PI16)
  149. #var point = t * p
  150. #draw_line(Vector2.ZERO, point, Color.WHITE, 2.0)
  151. func update_free_transform(update_cursor: bool=true):
  152. if update_cursor == false or editable == false or target == null:
  153. action = ""
  154. local_direction = -1
  155. Input.set_custom_mouse_cursor(null)
  156. return
  157. var local_mouse_position = get_local_mouse_position()
  158. var PI8 = PI / 8;
  159. var texture_size = target.texture.get_size()
  160. var scale_factor = Vector2(1.0, texture_size.y / texture_size.x) if texture_size.x < texture_size.y else Vector2(texture_size.x / texture_size.y, 1.0)
  161. var scale_local_mouse_pos = local_mouse_position / scale_factor
  162. var angle = fposmod(PI / 16 - scale_local_mouse_pos.angle(), TAU)
  163. local_direction = -1
  164. for i in range(9):
  165. if (i * 2) * PI8 <= angle and angle < (1 + i * 2) * PI8:
  166. local_direction = i % 8
  167. break
  168. #print(rad_to_deg(angle), " ", local_direction, " ")
  169. var target_rect = target.get_rect()
  170. if local_direction != -1:
  171. var rect = Rect2(-target_rect.size / 2, target_rect.size)
  172. var trans = target.transform
  173. trans.origin = Vector2.ZERO
  174. cursor_direction = find_point_mapping(rect, trans, local_direction)
  175. if is_dragging == false:
  176. action = ""
  177. var _scale = target.scale * zoom
  178. var local_mouse_pos = local_mouse_position
  179. if grow_rect(target_rect, -15, _scale).has_point(local_mouse_pos):
  180. action = "Move"
  181. elif local_direction != -1:
  182. if grow_rect(target_rect, 30, _scale).has_point(local_mouse_pos):
  183. action = "Scale"
  184. elif grow_rect(target_rect, 80, _scale).has_point(local_mouse_pos):
  185. action = "Rotation"
  186. if action == "":
  187. Input.set_custom_mouse_cursor(null)
  188. elif action == "Move":
  189. var tex = cursor_sprite_frames.get_frame_texture(action, 0)
  190. Input.set_custom_mouse_cursor(tex, Input.CursorShape.CURSOR_ARROW, Vector2(tex.get_width() / 2.0, tex.get_height() / 2.0))
  191. elif action == "Rotation":
  192. var curson_name = "%s_%d" % [action, cursor_direction]
  193. var tex = cursor_sprite_frames.get_frame_texture(curson_name, 0)
  194. Input.set_custom_mouse_cursor(tex, Input.CursorShape.CURSOR_ARROW, Vector2(tex.get_width() / 2.0, tex.get_height() / 2.0))
  195. elif action == "Scale":
  196. if is_dragging and transform_start_local_direction in [0, 2, 4, 6]:
  197. return
  198. var curson_name = "%s_%d" % [action, cursor_direction]
  199. var tex = cursor_sprite_frames.get_frame_texture(curson_name, 0)
  200. Input.set_custom_mouse_cursor(tex, Input.CursorShape.CURSOR_ARROW, Vector2(tex.get_width() / 2.0, tex.get_height() / 2.0))
  201. else:
  202. Input.set_custom_mouse_cursor(null)
  203. action = ""
  204. update_shader_params()
  205. func update_shader_params():
  206. var rect = target.get_rect()
  207. var points_array = [
  208. rect.position, # 左上角
  209. rect.position + Vector2(rect.size.x, 0), # 右上角
  210. rect.position + rect.size, # 右下角
  211. rect.position + Vector2(0, rect.size.y) # 左下角
  212. ]
  213. for i in range(4):
  214. points_array[i] = target.transform * points_array[i]
  215. material.set_shader_parameter("points", points_array)
  216. material.set_shader_parameter("editable", editable)
  217. func find_point_mapping(rect: Rect2, trans: Transform2D, direction: int) -> int:
  218. var points = []
  219. for corner in _corners:
  220. points.append(rect.position + rect.size * corner)
  221. var min_index = -1
  222. var min_angle = TAU
  223. for i in points.size():
  224. var angle = abs((trans * points[i]).angle())
  225. if angle < min_angle:
  226. min_angle = angle
  227. min_index = i
  228. if(trans.get_scale().y < 0):
  229. return (min_index - direction + 8) % 8
  230. else:
  231. return (direction - min_index + 8) % 8