113 lines
3.0 KiB
GDScript
113 lines
3.0 KiB
GDScript
@tool
|
||
extends TextureRect
|
||
|
||
signal shaven(progress: float)
|
||
|
||
# points inside radius 9 circle(必须>5)
|
||
var brush_points := generate_brush_points(9)
|
||
var area_size := Vector2.ZERO
|
||
var image: Image
|
||
# bit_mask is the bottom
|
||
var bit_mask := BitMap.new()
|
||
var total_pixels := 0.0
|
||
|
||
|
||
func _ready() -> void:
|
||
if not texture:
|
||
printerr("texture not found")
|
||
return
|
||
area_size = texture.get_size()
|
||
image = texture.get_image()
|
||
texture = ImageTexture.create_from_image(image)
|
||
bit_mask.create_from_image_alpha(image)
|
||
total_pixels = bit_mask.get_true_bit_count()
|
||
|
||
|
||
# a solid circle brush, center = (0, 0)
|
||
func generate_brush_points(radius: int) -> PackedVector2Array:
|
||
var points := PackedVector2Array()
|
||
# Top points
|
||
for x in range(-radius, radius + 1):
|
||
for y in range(-radius, radius + 1):
|
||
# If point is within circle radius
|
||
if x * x + y * y <= radius * radius:
|
||
points.append(Vector2(x, y))
|
||
# mid points
|
||
radius = int(radius / 3.0)
|
||
for x in range(-radius , radius + 1):
|
||
for y in range(-radius, radius + 1):
|
||
# If point is within circle radius
|
||
if x * x + y * y <= radius * radius:
|
||
points.append(Vector2(x, y))
|
||
# bottom points
|
||
points.append(Vector2(0, 0))
|
||
points.append(Vector2(1, 0))
|
||
points.append(Vector2(0, 1))
|
||
points.append(Vector2(1, 1))
|
||
return points
|
||
|
||
|
||
var last_pos := Vector2(-1, -1)
|
||
|
||
|
||
func _shave() -> void:
|
||
if not mouse_pressing:
|
||
return
|
||
var mouse_pos := get_local_mouse_position()
|
||
if last_pos == mouse_pos:
|
||
return
|
||
last_pos = mouse_pos
|
||
var updated = false
|
||
for point in brush_points:
|
||
var pos = mouse_pos + point
|
||
if pos.x < 0 or pos.x >= area_size.x or pos.y < 0 or pos.y >= area_size.y:
|
||
continue
|
||
if bit_mask.get_bitv(pos):
|
||
updated = true
|
||
var color = image.get_pixelv(pos) as Color
|
||
color.a = clampf(color.a - 0.15, 0.0, 1.0)
|
||
if color.a == 0:
|
||
bit_mask.set_bitv(pos, false)
|
||
image.set_pixelv(pos, color)
|
||
if updated:
|
||
(texture as ImageTexture).update(image)
|
||
_send_signal()
|
||
|
||
|
||
func _send_signal() -> void:
|
||
var true_bit_count = bit_mask.get_true_bit_count()
|
||
var progress = 1.0 - float(true_bit_count) / total_pixels
|
||
shaven.emit(progress)
|
||
|
||
|
||
var mouse_pressing = false
|
||
|
||
|
||
# 可以刮出区域
|
||
func _gui_input(event: InputEvent) -> void:
|
||
if not is_visible_in_tree():
|
||
return
|
||
# 通过鼠标点击拖动,刮开区域,set_bit 一个 brush_size 的圆形区域为 true
|
||
# 每次更新后,bit_mask 会更新到 texture 上
|
||
# 通过 shaven 信号,通知外部,刮开了多少区域
|
||
if event is InputEventMouseButton:
|
||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||
# 先设置 mouse_pressing 为 true
|
||
mouse_pressing = true
|
||
# 刮开区域
|
||
_shave()
|
||
elif event.button_index == MOUSE_BUTTON_LEFT and not event.pressed:
|
||
mouse_pressing = false
|
||
accept_event()
|
||
elif event is InputEventMouseMotion and mouse_pressing:
|
||
_shave()
|
||
accept_event()
|
||
# elif event is InputEventScreenTouch:
|
||
# if event.pressed:
|
||
# mouse_pressing = true
|
||
# _shave(event.position)
|
||
# accept_event()
|
||
# elif not event.pressed:
|
||
# mouse_pressing = false
|
||
# accept_event()
|