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