mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-19 09:39:48 +00:00
The bucket tool now works with draw tiles mode
This commit is contained in:
parent
8efbf0bf83
commit
4d5eebc670
|
@ -125,6 +125,29 @@ func get_cell_position(coords: Vector2i) -> int:
|
||||||
return x + y
|
return x + y
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the position of a cell in the tilemap
|
||||||
|
## at tilemap coordinates [param coords] in the cel's image.
|
||||||
|
func get_cell_position_in_tilemap_space(coords: Vector2i) -> int:
|
||||||
|
var x := coords.x
|
||||||
|
x = clampi(x, 0, horizontal_cells - 1)
|
||||||
|
var y := coords.y
|
||||||
|
y = clampi(y, 0, vertical_cells - 1)
|
||||||
|
y *= horizontal_cells
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the index of a cell in the tilemap
|
||||||
|
## at pixel coordinates [param coords] in the cel's image.
|
||||||
|
func get_cell_index_at_coords(coords: Vector2i) -> int:
|
||||||
|
return cells[get_cell_position(coords)].index
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the index of a cell in the tilemap
|
||||||
|
## at tilemap coordinates [param coords] in the cel's image.
|
||||||
|
func get_cell_index_at_coords_in_tilemap_space(coords: Vector2i) -> int:
|
||||||
|
return cells[get_cell_position_in_tilemap_space(coords)].index
|
||||||
|
|
||||||
|
|
||||||
## Returns [code]true[/code] if the tile at cell position [param cell_position]
|
## Returns [code]true[/code] if the tile at cell position [param cell_position]
|
||||||
## with image [param image_portion] is equal to [param tile_image].
|
## with image [param image_portion] is equal to [param tile_image].
|
||||||
func tiles_equal(cell_position: int, image_portion: Image, tile_image: Image) -> bool:
|
func tiles_equal(cell_position: int, image_portion: Image, tile_image: Image) -> bool:
|
||||||
|
|
|
@ -374,9 +374,8 @@ func _pick_color(pos: Vector2i) -> void:
|
||||||
if pos.x < 0 or pos.y < 0:
|
if pos.x < 0 or pos.y < 0:
|
||||||
return
|
return
|
||||||
if is_placing_tiles():
|
if is_placing_tiles():
|
||||||
var tile_position := get_cell_position(pos)
|
|
||||||
var cel := Global.current_project.get_current_cel() as CelTileMap
|
var cel := Global.current_project.get_current_cel() as CelTileMap
|
||||||
Tools.selected_tile_index_changed.emit(cel.cells[tile_position].index)
|
Tools.selected_tile_index_changed.emit(cel.get_cell_index_at_coords(pos))
|
||||||
return
|
return
|
||||||
var image := Image.new()
|
var image := Image.new()
|
||||||
image.copy_from(_get_draw_image())
|
image.copy_from(_get_draw_image())
|
||||||
|
|
|
@ -186,6 +186,11 @@ func draw_end(pos: Vector2i) -> void:
|
||||||
commit_undo()
|
commit_undo()
|
||||||
|
|
||||||
|
|
||||||
|
func draw_tile(pos: Vector2i, cel: CelTileMap) -> void:
|
||||||
|
var tile_position := get_cell_position(pos)
|
||||||
|
cel.set_index(tile_position, TileSetPanel.selected_tile_index)
|
||||||
|
|
||||||
|
|
||||||
func fill(pos: Vector2i) -> void:
|
func fill(pos: Vector2i) -> void:
|
||||||
match _fill_area:
|
match _fill_area:
|
||||||
FillArea.AREA:
|
FillArea.AREA:
|
||||||
|
@ -199,6 +204,17 @@ func fill(pos: Vector2i) -> void:
|
||||||
|
|
||||||
func fill_in_color(pos: Vector2i) -> void:
|
func fill_in_color(pos: Vector2i) -> void:
|
||||||
var project := Global.current_project
|
var project := Global.current_project
|
||||||
|
if is_placing_tiles():
|
||||||
|
for cel in _get_selected_draw_cels():
|
||||||
|
if cel is not CelTileMap:
|
||||||
|
continue
|
||||||
|
var tilemap_cel := cel as CelTileMap
|
||||||
|
var tile_index := tilemap_cel.get_cell_index_at_coords(pos)
|
||||||
|
for i in tilemap_cel.cells.size():
|
||||||
|
var cell := tilemap_cel.cells[i]
|
||||||
|
if cell.index == tile_index:
|
||||||
|
tilemap_cel.set_index(i, TileSetPanel.selected_tile_index)
|
||||||
|
return
|
||||||
var color := project.get_current_cel().get_image().get_pixelv(pos)
|
var color := project.get_current_cel().get_image().get_pixelv(pos)
|
||||||
var images := _get_selected_draw_images()
|
var images := _get_selected_draw_images()
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -311,6 +327,74 @@ func fill_in_selection() -> void:
|
||||||
gen.generate_image(image, PATTERN_FILL_SHADER, params, project.size)
|
gen.generate_image(image, PATTERN_FILL_SHADER, params, project.size)
|
||||||
|
|
||||||
|
|
||||||
|
func _flood_fill(pos: Vector2i) -> void:
|
||||||
|
# implements the floodfill routine by Shawn Hargreaves
|
||||||
|
# from https://www1.udel.edu/CIS/software/dist/allegro-4.2.1/src/flood.c
|
||||||
|
var project := Global.current_project
|
||||||
|
if is_placing_tiles():
|
||||||
|
for cel in _get_selected_draw_cels():
|
||||||
|
if cel is not CelTileMap:
|
||||||
|
continue
|
||||||
|
var tile_index := (cel as CelTileMap).get_cell_index_at_coords(pos)
|
||||||
|
# init flood data structures
|
||||||
|
_allegro_flood_segments = []
|
||||||
|
_allegro_image_segments = []
|
||||||
|
_compute_segments_for_tilemap(pos, cel, tile_index)
|
||||||
|
_color_segments_tilemap(cel)
|
||||||
|
return
|
||||||
|
|
||||||
|
var images := _get_selected_draw_images()
|
||||||
|
for image in images:
|
||||||
|
if Tools.check_alpha_lock(image, pos):
|
||||||
|
continue
|
||||||
|
var color: Color = image.get_pixelv(pos)
|
||||||
|
if _fill_with == FillWith.COLOR or _pattern == null:
|
||||||
|
# end early if we are filling with the same color
|
||||||
|
if tool_slot.color.is_equal_approx(color):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# end early if we are filling with an empty pattern
|
||||||
|
var pattern_size := _pattern.image.get_size()
|
||||||
|
if pattern_size.x == 0 or pattern_size.y == 0:
|
||||||
|
return
|
||||||
|
# init flood data structures
|
||||||
|
_allegro_flood_segments = []
|
||||||
|
_allegro_image_segments = []
|
||||||
|
_compute_segments_for_image(pos, project, image, color)
|
||||||
|
# now actually color the image: since we have already checked a few things for the points
|
||||||
|
# we'll process here, we're going to skip a bunch of safety checks to speed things up.
|
||||||
|
_color_segments(image)
|
||||||
|
|
||||||
|
|
||||||
|
func _compute_segments_for_image(
|
||||||
|
pos: Vector2i, project: Project, image: Image, src_color: Color
|
||||||
|
) -> void:
|
||||||
|
# initially allocate at least 1 segment per line of image
|
||||||
|
for j in image.get_height():
|
||||||
|
_add_new_segment(j)
|
||||||
|
# start flood algorithm
|
||||||
|
_flood_line_around_point(pos, project, image, src_color)
|
||||||
|
# test all segments while also discovering more
|
||||||
|
var done := false
|
||||||
|
while not done:
|
||||||
|
done = true
|
||||||
|
var max_index := _allegro_flood_segments.size()
|
||||||
|
for c in max_index:
|
||||||
|
var p := _allegro_flood_segments[c]
|
||||||
|
if p.todo_below: # check below the segment?
|
||||||
|
p.todo_below = false
|
||||||
|
if _check_flooded_segment(
|
||||||
|
p.y + 1, p.left_position, p.right_position, project, image, src_color
|
||||||
|
):
|
||||||
|
done = false
|
||||||
|
if p.todo_above: # check above the segment?
|
||||||
|
p.todo_above = false
|
||||||
|
if _check_flooded_segment(
|
||||||
|
p.y - 1, p.left_position, p.right_position, project, image, src_color
|
||||||
|
):
|
||||||
|
done = false
|
||||||
|
|
||||||
|
|
||||||
## Add a new segment to the array
|
## Add a new segment to the array
|
||||||
func _add_new_segment(y := 0) -> void:
|
func _add_new_segment(y := 0) -> void:
|
||||||
_allegro_flood_segments.append(Segment.new(y))
|
_allegro_flood_segments.append(Segment.new(y))
|
||||||
|
@ -407,62 +491,6 @@ func _check_flooded_segment(
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
func _flood_fill(pos: Vector2i) -> void:
|
|
||||||
# implements the floodfill routine by Shawn Hargreaves
|
|
||||||
# from https://www1.udel.edu/CIS/software/dist/allegro-4.2.1/src/flood.c
|
|
||||||
var project := Global.current_project
|
|
||||||
var images := _get_selected_draw_images()
|
|
||||||
for image in images:
|
|
||||||
if Tools.check_alpha_lock(image, pos):
|
|
||||||
continue
|
|
||||||
var color: Color = image.get_pixelv(pos)
|
|
||||||
if _fill_with == FillWith.COLOR or _pattern == null:
|
|
||||||
# end early if we are filling with the same color
|
|
||||||
if tool_slot.color.is_equal_approx(color):
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
# end early if we are filling with an empty pattern
|
|
||||||
var pattern_size := _pattern.image.get_size()
|
|
||||||
if pattern_size.x == 0 or pattern_size.y == 0:
|
|
||||||
return
|
|
||||||
# init flood data structures
|
|
||||||
_allegro_flood_segments = []
|
|
||||||
_allegro_image_segments = []
|
|
||||||
_compute_segments_for_image(pos, project, image, color)
|
|
||||||
# now actually color the image: since we have already checked a few things for the points
|
|
||||||
# we'll process here, we're going to skip a bunch of safety checks to speed things up.
|
|
||||||
_color_segments(image)
|
|
||||||
|
|
||||||
|
|
||||||
func _compute_segments_for_image(
|
|
||||||
pos: Vector2i, project: Project, image: Image, src_color: Color
|
|
||||||
) -> void:
|
|
||||||
# initially allocate at least 1 segment per line of image
|
|
||||||
for j in image.get_height():
|
|
||||||
_add_new_segment(j)
|
|
||||||
# start flood algorithm
|
|
||||||
_flood_line_around_point(pos, project, image, src_color)
|
|
||||||
# test all segments while also discovering more
|
|
||||||
var done := false
|
|
||||||
while not done:
|
|
||||||
done = true
|
|
||||||
var max_index := _allegro_flood_segments.size()
|
|
||||||
for c in max_index:
|
|
||||||
var p := _allegro_flood_segments[c]
|
|
||||||
if p.todo_below: # check below the segment?
|
|
||||||
p.todo_below = false
|
|
||||||
if _check_flooded_segment(
|
|
||||||
p.y + 1, p.left_position, p.right_position, project, image, src_color
|
|
||||||
):
|
|
||||||
done = false
|
|
||||||
if p.todo_above: # check above the segment?
|
|
||||||
p.todo_above = false
|
|
||||||
if _check_flooded_segment(
|
|
||||||
p.y - 1, p.left_position, p.right_position, project, image, src_color
|
|
||||||
):
|
|
||||||
done = false
|
|
||||||
|
|
||||||
|
|
||||||
func _color_segments(image: ImageExtended) -> void:
|
func _color_segments(image: ImageExtended) -> void:
|
||||||
if _fill_with == FillWith.COLOR or _pattern == null:
|
if _fill_with == FillWith.COLOR or _pattern == null:
|
||||||
# This is needed to ensure that the color used to fill is not wrong, due to float
|
# This is needed to ensure that the color used to fill is not wrong, due to float
|
||||||
|
@ -493,6 +521,115 @@ func _set_pixel_pattern(image: ImageExtended, x: int, y: int, pattern_size: Vect
|
||||||
image.set_pixel_custom(x, y, pc)
|
image.set_pixel_custom(x, y, pc)
|
||||||
|
|
||||||
|
|
||||||
|
func _compute_segments_for_tilemap(pos: Vector2i, cel: CelTileMap, src_index: int) -> void:
|
||||||
|
# initially allocate at least 1 segment per line of the tilemap
|
||||||
|
for j in cel.vertical_cells:
|
||||||
|
_add_new_segment(j)
|
||||||
|
pos /= cel.tileset.tile_size
|
||||||
|
# start flood algorithm
|
||||||
|
_flood_line_around_point_tilemap(pos, cel, src_index)
|
||||||
|
# test all segments while also discovering more
|
||||||
|
var done := false
|
||||||
|
while not done:
|
||||||
|
done = true
|
||||||
|
var max_index := _allegro_flood_segments.size()
|
||||||
|
for c in max_index:
|
||||||
|
var p := _allegro_flood_segments[c]
|
||||||
|
if p.todo_below: # check below the segment?
|
||||||
|
p.todo_below = false
|
||||||
|
if _check_flooded_segment_tilemap(
|
||||||
|
p.y + 1, p.left_position, p.right_position, cel, src_index
|
||||||
|
):
|
||||||
|
done = false
|
||||||
|
if p.todo_above: # check above the segment?
|
||||||
|
p.todo_above = false
|
||||||
|
if _check_flooded_segment_tilemap(
|
||||||
|
p.y - 1, p.left_position, p.right_position, cel, src_index
|
||||||
|
):
|
||||||
|
done = false
|
||||||
|
|
||||||
|
|
||||||
|
## Fill an horizontal segment around the specified position, and adds it to the
|
||||||
|
## list of segments filled. Returns the first x coordinate after the part of the
|
||||||
|
## line that has been filled.
|
||||||
|
## Τhis method is called by [method _flood_fill] after the required data structures
|
||||||
|
## have been initialized.
|
||||||
|
func _flood_line_around_point_tilemap(pos: Vector2i, cel: CelTileMap, src_index: int) -> int:
|
||||||
|
if cel.get_cell_index_at_coords_in_tilemap_space(pos) != src_index:
|
||||||
|
return pos.x + 1
|
||||||
|
var west := pos
|
||||||
|
var east := pos
|
||||||
|
while west.x >= 0 && cel.get_cell_index_at_coords_in_tilemap_space(west) == src_index:
|
||||||
|
west += Vector2i.LEFT
|
||||||
|
while (
|
||||||
|
east.x < cel.horizontal_cells
|
||||||
|
&& cel.get_cell_index_at_coords_in_tilemap_space(east) == src_index
|
||||||
|
):
|
||||||
|
east += Vector2i.RIGHT
|
||||||
|
# Make a note of the stuff we processed
|
||||||
|
var c := pos.y
|
||||||
|
var segment := _allegro_flood_segments[c]
|
||||||
|
# we may have already processed some segments on this y coordinate
|
||||||
|
if segment.flooding:
|
||||||
|
while segment.next > 0:
|
||||||
|
c = segment.next # index of next segment in this line of image
|
||||||
|
segment = _allegro_flood_segments[c]
|
||||||
|
# found last current segment on this line
|
||||||
|
c = _allegro_flood_segments.size()
|
||||||
|
segment.next = c
|
||||||
|
_add_new_segment(pos.y)
|
||||||
|
segment = _allegro_flood_segments[c]
|
||||||
|
# set the values for the current segment
|
||||||
|
segment.flooding = true
|
||||||
|
segment.left_position = west.x + 1
|
||||||
|
segment.right_position = east.x - 1
|
||||||
|
segment.y = pos.y
|
||||||
|
segment.next = 0
|
||||||
|
# Should we process segments above or below this one?
|
||||||
|
# when there is a selected area, the pixels above and below the one we started creating this
|
||||||
|
# segment from may be outside it. It's easier to assume we should be checking for segments
|
||||||
|
# above and below this one than to specifically check every single pixel in it, because that
|
||||||
|
# test will be performed later anyway.
|
||||||
|
# On the other hand, this test we described is the same `project.can_pixel_get_drawn` does if
|
||||||
|
# there is no selection, so we don't need branching here.
|
||||||
|
segment.todo_above = pos.y > 0
|
||||||
|
segment.todo_below = pos.y < cel.vertical_cells - 1
|
||||||
|
# this is an actual segment we should be coloring, so we add it to the results for the
|
||||||
|
# current image
|
||||||
|
if segment.right_position >= segment.left_position:
|
||||||
|
_allegro_image_segments.append(segment)
|
||||||
|
# we know the point just east of the segment is not part of a segment that should be
|
||||||
|
# processed, else it would be part of this segment
|
||||||
|
return east.x + 1
|
||||||
|
|
||||||
|
|
||||||
|
func _check_flooded_segment_tilemap(
|
||||||
|
y: int, left: int, right: int, cel: CelTileMap, src_index: int
|
||||||
|
) -> bool:
|
||||||
|
var ret := false
|
||||||
|
var c := 0
|
||||||
|
while left <= right:
|
||||||
|
c = y
|
||||||
|
while true:
|
||||||
|
var segment := _allegro_flood_segments[c]
|
||||||
|
if left >= segment.left_position and left <= segment.right_position:
|
||||||
|
left = segment.right_position + 2
|
||||||
|
break
|
||||||
|
c = segment.next
|
||||||
|
if c == 0: # couldn't find a valid segment, so we draw a new one
|
||||||
|
left = _flood_line_around_point_tilemap(Vector2i(left, y), cel, src_index)
|
||||||
|
ret = true
|
||||||
|
break
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
func _color_segments_tilemap(cel: CelTileMap) -> void:
|
||||||
|
for c in _allegro_image_segments.size():
|
||||||
|
var p := _allegro_image_segments[c]
|
||||||
|
for px in range(p.left_position, p.right_position + 1):
|
||||||
|
draw_tile(Vector2i(px, p.y) * cel.tileset.tile_size, cel)
|
||||||
|
|
||||||
|
|
||||||
func commit_undo() -> void:
|
func commit_undo() -> void:
|
||||||
var project := Global.current_project
|
var project := Global.current_project
|
||||||
project.update_tilemaps(_undo_data)
|
project.update_tilemaps(_undo_data)
|
||||||
|
|
|
@ -66,9 +66,8 @@ func _pick_color(pos: Vector2i) -> void:
|
||||||
if pos.x < 0 or pos.y < 0:
|
if pos.x < 0 or pos.y < 0:
|
||||||
return
|
return
|
||||||
if is_placing_tiles():
|
if is_placing_tiles():
|
||||||
var tile_position := get_cell_position(pos)
|
|
||||||
var cel := Global.current_project.get_current_cel() as CelTileMap
|
var cel := Global.current_project.get_current_cel() as CelTileMap
|
||||||
Tools.selected_tile_index_changed.emit(cel.cells[tile_position].index)
|
Tools.selected_tile_index_changed.emit(cel.get_cell_index_at_coords(pos))
|
||||||
return
|
return
|
||||||
var image := Image.new()
|
var image := Image.new()
|
||||||
image.copy_from(_get_draw_image())
|
image.copy_from(_get_draw_image())
|
||||||
|
|
Loading…
Reference in a new issue