broken_seals_2ds/game/addons/Godoxel/PaintCanvas.gd

490 lines
13 KiB
GDScript3
Raw Normal View History

2022-01-30 23:20:50 +01:00
tool
extends Control
var image = Image.new()
var last_pixel = []
onready var canvas_image_node = get_node("CanvasImage")
export var grid_size = 16
export var canvas_size = Vector2(48, 28)
export var region_size = 10
export var can_draw = true
var mouse_in_region
var mouse_on_top
#terms
#global cell - a cell that has a global grid position on the canvas
#local cell - a cell that has a local grid position in a chunk region on the canvas
#chunk region - a set of cells contained in an even number
#TODO: Maybe each chunk region can hold an image resource so that way the engine wouldn't lag at all when updating the canvas
var layers = {}
var active_layer
var preview_layer = "preview"
var preview_enabled = false
func _enter_tree():
#----------------------
# init Layer
#----------------------
layers[preview_layer] = {
"layer": null,
"data": [],
"chunks": null,
}
canvas_size = Vector2(int(rect_size.x / grid_size), int(rect_size.y / grid_size))
#print("canvas_size: ", canvas_size)
func _ready():
active_layer = add_existing_layer(get_tree().get_nodes_in_group("layer")[0])
#print("active Layer: ", active_layer)
func get_layer_data(layer_name):
return layers[layer_name]
func get_active_layer():
return layers[active_layer]
func get_preview_layer():
return layers[preview_layer]
func clear_active_layer():
for pixel in layers[active_layer].data:
set_global_cell_in_chunk(pixel[0], pixel[1], Color(0,0,0,0))
func clear_layer(layer_name: String):
for pixel in layers[layer_name].data:
set_global_cell_in_chunk(pixel[0], pixel[1], Color(0,0,0,0))
func clear_preview_layer():
for pixel in layers["preview"].data:
set_global_cell_in_chunk(pixel[0], pixel[1], Color(0,0,0,0))
func remove_layer(layer_name):
get_node("ChunkNodes").remove_child(layers[layer_name].chunks)
layers[layer_name].chunks.queue_free()
layers.erase(layer_name)
if active_layer == layer_name:
for layer in layers:
if layer == preview_layer:
continue
active_layer = layer
break
return active_layer
# only needed for init
func add_existing_layer(layer):
layers[layer.name] = {
"layer": layer,
"data": [],
"chunks": null,
}
generate_chunks()
return layer.name
func add_new_layer(layer_name):
layers[layer_name] = {
"layer": null,
"data": [],
"chunks": null,
}
generate_chunks()
return layer_name
func duplicate_layer(layer: String, neu_layer_name: String):
var _preview = preview_enabled
preview_enabled = false
var _temp = active_layer
active_layer = neu_layer_name
layers[neu_layer_name] = {
"layer": null,
"data": layers[layer].data.duplicate(true),
"chunks": null,
}
generate_chunks()
# get_node("ChunkNodes").remove_child(layers[neu_layer_name].chunks)
# get_node("ChunkNodes").add_child_below_node(layers[layer].chunks, layers[neu_layer_name].chunks, true)
for pixel in layers[neu_layer_name].data:
set_pixel_cell(pixel[0], pixel[1], pixel[2])
active_layer = _temp
preview_enabled = _preview
return neu_layer_name
func toggle_layer_visibility(layer_name):
layers[layer_name].chunks.visible = not layers[layer_name].chunks.visible
#print("Layer: ", layer_name, " is now: ", layers[layer_name].chunks.visible)
var util = preload("res://addons/Godoxel/Util.gd")
func _on_mouse_entered():
mouse_on_top = true
func _on_mouse_exited():
mouse_on_top = false
func _process(delta):
var mouse_position = get_local_mouse_position()
var rect = Rect2(Vector2(0, 0), rect_size)
mouse_in_region = rect.has_point(mouse_position)
update()
#if not Engine.editor_hint:
# print(mouse_on_canvas, " | ", has_focus())
#draw_canvas_out just updates the image constantly
#if can_draw:
# draw_canvas_out()
func generate_chunks():
var maxium_chunk_size = get_maxium_filled_chunks()
#TODO: We probably don't need to check for x and y anymore
for key in layers:
if layers[key].chunks != null:
continue
var chunk_node = Control.new()
get_node("ChunkNodes").add_child(chunk_node)
chunk_node.owner = self
layers[key].chunks = chunk_node
for x in maxium_chunk_size.x:
for y in maxium_chunk_size.y:
var paint_canvas_chunk = load("res://addons/Godoxel/PaintCanvasChunk.tscn").instance()
paint_canvas_chunk.setup(region_size)
paint_canvas_chunk.name = "C-%s-%s" % [x, y]
paint_canvas_chunk.rect_position = Vector2(x * (grid_size * region_size), y * (grid_size * region_size))
layers[key].chunks.add_child(paint_canvas_chunk)
func get_maxium_filled_chunks():
return Vector2(canvas_size.x / region_size, canvas_size.y / region_size).ceil()
#TODO: Remake these functions with godot's setget features
#so that we don't have to call these functions
func resize_grid(grid):
#print(grid)
if grid <= 0:
return
grid_size = grid
canvas_image_node.rect_scale = Vector2(grid, grid)
func resize_canvas(x, y):
image.unlock()
image.create(x, y, true, Image.FORMAT_RGBA8)
canvas_size = Vector2(x, y)
#setup_all_chunks()
image.lock()
#func draw_canvas_out(a = ""):
# if canvas_image_node == null:
# return
# var image_texture = ImageTexture.new()
# image_texture.create_from_image(image)
# image_texture.set_flags(0)
# canvas_image_node.texture = image_texture
func get_wrapped_region_cell(x, y):
return Vector2(wrapi(x, 0, region_size), wrapi(y, 0, region_size))
func get_region_from_cell(x, y):
return Vector2(floor(x / region_size), floor(y / region_size))
func set_local_cell_in_chunk(chunk_x, chunk_y, local_cell_x, local_cell_y, color):
var chunk_node
if preview_enabled:
chunk_node = layers.preview.chunks.get_node_or_null("C-%s-%s" % [chunk_x, chunk_y])
else:
chunk_node = layers[active_layer].chunks.get_node_or_null("C-%s-%s" % [chunk_x, chunk_y])
if chunk_node == null:
#print("Can't find chunk node!")
return
chunk_node.set_cell(local_cell_x, local_cell_y, color)
func set_global_cell_in_chunk(cell_x, cell_y, color):
var chunk = get_region_from_cell(cell_x, cell_y)
var wrapped_cell = get_wrapped_region_cell(cell_x, cell_y)
set_local_cell_in_chunk(chunk.x, chunk.y, wrapped_cell.x, wrapped_cell.y, color)
#func update_chunk_region_from_cell(x, y):
# var region_to_update = get_region_from_cell(x, y)
# update_chunk_region(region_to_update.x, region_to_update.y)
func get_pixel_cell_color(x, y):
if not cell_in_canvas_region(x, y):
return null
var pixel_cell = get_pixel_cell(x, y)
if pixel_cell == null:
#We already checked that the cell can't be out of the canvas region so we can assume the pixel cell is completely transparent if it's null
return Color(0, 0, 0, 0)
else:
return util.color_from_array(pixel_cell[2])
func get_pixel_cell_color_v(vec2):
return get_pixel_cell_color(vec2.x, vec2.y)
func get_pixel_cell(x, y):
if active_layer == null:
return
if not cell_in_canvas_region(x, y):
return null
for pixel in get_active_layer().data:
if pixel[0] == x and pixel[1] == y:
return pixel
return null
func get_pixel_cell_v(vec2):
return get_pixel_cell(vec2.x, vec2.y)
#func remove_pixel_cell(x, y):
# if can_draw == false:
# return false
# if not cell_in_canvas_region(x, y):
# return false
# var layer_data = get_layer_data("Layer 1")
# for pixel in range(0, layer_data.size()):
# if layer_data[pixel][0] == x and layer_data[pixel][1] == y:
# layer_data.remove(pixel)
# #update_chunk_region_from_cell(x, y)
# #TOOD: If pixel exists in temp_pool_pixels then remove it
# image.set_pixel(x, y, Color(0, 0, 0, 0))
# return true
# return false
#func remove_pixel_cell_v(vec2):
# return remove_pixel_cell(vec2.x, vec2.y)
func set_pixel_cell(x, y, color):
if can_draw == false:
return false
if not cell_in_canvas_region(x, y):
return false
var layer
if preview_enabled:
layer = get_preview_layer()
else:
layer = get_active_layer()
var index = 0
for pixel in layer.data:
#TODO: Make a better way of accessing the array because the more pixels we have, the longer it takes to
#set the pixel
if pixel[0] == x and pixel[1] == y:
#No reason to set the pixel again if the colors are the same
#If the color we are setting is 0, 0, 0, 0 then there is no reason to keep the information about the pixel
#so we remove it from the layer data
if color == Color(0, 0, 0, 0):
layer.data.remove(index)
else:
pixel[2] = color
#TODO: The new system is going to allow chunks to each have their own TextureRect and Image
#nodes so what we are doing in here is that we are setting the local cell in the region of that image
set_global_cell_in_chunk(x, y, color)
last_pixel = [x, y, color]
return true
index += 1
#don't append any data if the color is 0, 0, 0, 0
if color != Color(0, 0, 0, 0):
#if the pixel data doesn't exist then we add it in
layer.data.append([x, y, color])
set_global_cell_in_chunk(x, y, color)
last_pixel = [x, y, color]
return true
func set_pixel_cell_v(vec2, color):
return set_pixel_cell(vec2.x, vec2.y, color)
func set_pixels_from_line(vec2_1, vec2_2, color):
var points = get_pixels_from_line(vec2_1, vec2_2)
for i in points:
set_pixel_cell_v(i, color)
func set_random_pixels_from_line(vec2_1, vec2_2):
var points = get_pixels_from_line(vec2_1, vec2_2)
for i in points:
set_pixel_cell_v(i, util.random_color_alt())
func get_pixels_from_line(vec2_1, vec2_2):
var points = PoolVector2Array()
var dx = abs(vec2_2.x - vec2_1.x)
var dy = abs(vec2_2.y - vec2_1.y)
var x = vec2_1.x
var y = vec2_1.y
var sx = 0
if vec2_1.x > vec2_2.x:
sx = -1
else:
sx = 1
var sy = 0
if vec2_1.y > vec2_2.y:
sy = -1
else:
sy = 1
if dx > dy:
var err = dx / 2
while(true):
if x == vec2_2.x:
break
points.push_back(Vector2(x, y))
err -= dy
if err < 0:
y += sy
err += dx
x += sx
else:
var err = dy / 2
while (true):
if y == vec2_2.y:
break
points.push_back(Vector2(x, y))
err -= dx
if err < 0:
x += sx
err += dy
y += sy
points.push_back(Vector2(x, y))
return points
#even though the function checks for it, we can't afford adding more functions to the call stack
#because godot has a limit until it crashes
var flood_fill_queue = 0
func flood_fill(x, y, target_color, replacement_color):
#yield(get_tree().create_timer(1), "timeout")
flood_fill_queue += 1
if not cell_in_canvas_region(x, y):
flood_fill_queue -= 1
return
if target_color == replacement_color:
flood_fill_queue -= 1
return
elif not get_pixel_cell_color(x, y) == target_color:
flood_fill_queue -= 1
return
else:
set_pixel_cell(x, y, replacement_color)
if flood_fill_queue >= 500:
#print(flood_fill_queue)
yield(get_tree().create_timer(0.01), "timeout")
#up
if get_pixel_cell_color(x, y - 1) == target_color:
flood_fill(x, y - 1, target_color, replacement_color)
#down
if get_pixel_cell_color(x, y + 1) == target_color:
flood_fill(x, y + 1, target_color, replacement_color)
#left
if get_pixel_cell_color(x - 1, y) == target_color:
flood_fill(x - 1, y, target_color, replacement_color)
#right
if get_pixel_cell_color(x + 1, y) == target_color:
flood_fill(x + 1, y, target_color, replacement_color)
flood_fill_queue -= 1
return
#func flood_fill_erase(x, y, target_color):
# yield(get_tree().create_timer(0.001), "timeout")
# if not cell_in_canvas_region(x, y):
# print("cell not in canvas")
# return
# #if target_color == replacement_color:
# # return
# elif not get_pixel_cell_color(x, y) == target_color:
# print("cell doesn't match pixel color")
# return
# elif not get_pixel_cell(x, y):
# print("cell already erased")
# return
# else:
# print("removed pixel")
# remove_pixel_cell(x, y)
# print("x: ", x, " y: ", y, " color: ", target_color)
# #up
# flood_fill_erase(x, y - 1, target_color)
# #down
# flood_fill_erase(x, y + 1, target_color)
# #left
# flood_fill_erase(x - 1, y, target_color)
# #right
# flood_fill_erase(x + 1, y, target_color)
# return
func cell_in_canvas_region(x, y):
if x > canvas_size.x - 1 or x < 0 or y > canvas_size.y - 1 or y < 0:
#out of bounds, return false
return false
else:
return true
#Both of these functions right now just return the starting position of the canvas and the last position of the canvas
func get_all_used_regions_in_canvas():
var first_used_region = get_first_used_region_in_canvas()
var last_used_region = get_last_used_region_in_canvas()
var chunk_pool = PoolVector2Array()
for chunk_x in range(first_used_region.x, last_used_region.x):
for chunk_y in range(first_used_region.y, last_used_region.y):
chunk_pool.append(Vector2(chunk_x, chunk_y))
return chunk_pool
func get_first_used_region_in_canvas():
return get_region_from_cell(0, 0)
func get_last_used_region_in_canvas():
return get_region_from_cell(canvas_size.x - 1, canvas_size.y - 1)
func get_cells_in_region(x, y):
var start_cell = Vector2(x * region_size, y * region_size)
var end_cell = Vector2((x * region_size) + region_size, (y * region_size) + region_size)
var cell_array = []
for cx in range(start_cell.x, end_cell.x):
for cy in range(start_cell.y, end_cell.y):
var pixel_cell = get_pixel_cell(cx, cy)
if pixel_cell == null:
pixel_cell = [cx, cy, Color(0, 0, 0, 0)]
cell_array.append(pixel_cell)
return cell_array