tochie-kit/addons/Godoxel/PaintCanvas.gd

501 lines
13 KiB
GDScript3
Raw Permalink Normal View History

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: float) -> void:
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
#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