From 9c520a65e9afce81e9a382d506f7f82e192aa55c Mon Sep 17 00:00:00 2001 From: kleonc <9283098+kleonc@users.noreply.github.com> Date: Mon, 18 Jan 2021 21:59:26 +0100 Subject: [PATCH] Refactored isometric grid (#430) * Move get_tile_mode_rect() method to the Project class * Refactored isometric grid Now it should be drawn properly in the target rect. Settings (in pixels): - cell's AABB size, - offset from the canvas origin. --- Translations/Translations.pot | 22 ++++- src/Autoload/Global.gd | 7 +- src/Classes/Project.gd | 14 +++- src/Preferences/PreferencesDialog.gd | 8 +- src/Preferences/PreferencesDialog.tscn | 76 +++++++++++++++-- src/Tools/Base.gd | 12 --- src/Tools/Draw.gd | 12 +-- src/UI/Canvas/Grid.gd | 108 ++++++++++++++++--------- src/UI/Canvas/PixelGrid.gd | 17 +--- src/UI/TopMenuContainer.gd | 1 + 10 files changed, 192 insertions(+), 85 deletions(-) diff --git a/Translations/Translations.pot b/Translations/Translations.pot index 62a8a03..a226847 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -807,10 +807,28 @@ msgstr "" msgid "Sets how far apart are horizontal lines of the grid" msgstr "" -msgid "Isometric cell size:" +msgid "Isometric cell bounds width:" msgstr "" -msgid "Sets the size of the cells in an isometric grid" +msgid "Sets the width of the isometric cell's axis aligned bounding box" +msgstr "" + +msgid "Isometric cell bounds height:" +msgstr "" + +msgid "Sets the height of the isometric cell's axis aligned bounding box" +msgstr "" + +msgid "Isometric grid offset x:" +msgstr "" + +msgid "Sets isometric grid's x offset from the canvas origin (top left corner of the image)" +msgstr "" + +msgid "Isometric grid offset y:" +msgstr "" + +msgid "Sets isometric grid's y offset from the canvas origin (top left corner of the image)" msgstr "" msgid "Grid color:" diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 0f98263..af47171 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -54,7 +54,10 @@ var default_fill_color := Color(0, 0, 0, 0) var grid_type = Grid_Types.CARTESIAN var grid_width := 2 var grid_height := 2 -var grid_isometric_cell_size := 2 +var grid_isometric_cell_bounds_width := 16 +var grid_isometric_cell_bounds_height := 8 +var grid_isometric_offset_x := 0 +var grid_isometric_offset_y := 0 var grid_color := Color.black var pixel_grid_show_at_zoom := 1500.0 # percentage var pixel_grid_color := Color("91212121") @@ -343,7 +346,6 @@ func undo(_frame_index := -1, _layer_index := -1, project : Project = current_pr if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.isometric_polylines.clear() Global.canvas.grid.update() Global.canvas.pixel_grid.update() Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] @@ -375,7 +377,6 @@ func redo(_frame_index := -1, _layer_index := -1, project : Project = current_pr if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.isometric_polylines.clear() Global.canvas.grid.update() Global.canvas.pixel_grid.update() Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 6ee2e09..cb6e6d6 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -174,7 +174,6 @@ func change_project() -> void: i += 1 Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %" Global.canvas.update() - Global.canvas.grid.isometric_polylines.clear() Global.canvas.grid.update() Global.canvas.pixel_grid.update() Global.transparent_checker._ready() @@ -566,3 +565,16 @@ func has_changed_changed(value : bool) -> void: Global.tabs.set_tab_title(Global.tabs.current_tab, name + "(*)") else: Global.tabs.set_tab_title(Global.tabs.current_tab, name) + + +func get_tile_mode_rect() -> Rect2: + match Global.current_project.tile_mode: + Global.Tile_Mode.NONE: + return Rect2(Vector2.ZERO, size) + Global.Tile_Mode.XAXIS: + return Rect2(Vector2(-1, 0) * size, Vector2(3, 1) * size) + Global.Tile_Mode.YAXIS: + return Rect2(Vector2(0, -1) * size, Vector2(1, 3) * size) + Global.Tile_Mode.BOTH: + return Rect2(Vector2(-1, -1) * size, Vector2(3, 3) * size) + return Rect2() diff --git a/src/Preferences/PreferencesDialog.gd b/src/Preferences/PreferencesDialog.gd index ec15aee..22f07ac 100644 --- a/src/Preferences/PreferencesDialog.gd +++ b/src/Preferences/PreferencesDialog.gd @@ -20,7 +20,10 @@ var preferences = [ ["grid_type", "Canvas/GridOptions/GridType", "selected", Global.grid_type], ["grid_width", "Canvas/GridOptions/GridWidthValue", "value", Global.grid_width], ["grid_height", "Canvas/GridOptions/GridHeightValue", "value", Global.grid_height], - ["grid_isometric_cell_size", "Canvas/GridOptions/IsometricCellSizeValue", "value", Global.grid_isometric_cell_size], + ["grid_isometric_cell_bounds_width", "Canvas/GridOptions/IsometricCellBoundsWidthValue", "value", Global.grid_isometric_cell_bounds_width], + ["grid_isometric_cell_bounds_height", "Canvas/GridOptions/IsometricCellBoundsHeightValue", "value", Global.grid_isometric_cell_bounds_height], + ["grid_isometric_offset_x", "Canvas/GridOptions/IsometricGridOffsetXValue", "value", Global.grid_isometric_offset_x], + ["grid_isometric_offset_y", "Canvas/GridOptions/IsometricGridOffsetYValue", "value", Global.grid_isometric_offset_y], ["grid_color", "Canvas/GridOptions/GridColor", "color", Global.grid_color], ["pixel_grid_show_at_zoom", "Canvas/PixelGridOptions/ShowAtZoom", "value", Global.pixel_grid_show_at_zoom], ["pixel_grid_color", "Canvas/PixelGridOptions/GridColor", "color", Global.pixel_grid_color], @@ -128,8 +131,7 @@ func preference_update(prop : String) -> void: else: autosave_interval.mouse_default_cursor_shape = Control.CURSOR_FORBIDDEN - if prop in ["grid_type", "grid_width", "grid_height", "grid_isometric_cell_size", "grid_color"]: - Global.canvas.grid.isometric_polylines.clear() + if prop in ["grid_type", "grid_width", "grid_height", "grid_isometric_cell_bounds_width", "grid_isometric_cell_bounds_height", "grid_isometric_offset_x", "grid_isometric_offset_y", "grid_color"]: Global.canvas.grid.update() if prop in ["pixel_grid_show_at_zoom", "pixel_grid_color"]: diff --git a/src/Preferences/PreferencesDialog.tscn b/src/Preferences/PreferencesDialog.tscn index 3fa30a8..5cd9386 100644 --- a/src/Preferences/PreferencesDialog.tscn +++ b/src/Preferences/PreferencesDialog.tscn @@ -342,24 +342,88 @@ rounded = true align = 2 suffix = "px" -[node name="IsometricCellSizeLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +[node name="IsometricCellBoundsWidthLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] margin_top = 57.0 margin_right = 123.0 margin_bottom = 71.0 -hint_tooltip = "Sets the size of the cells in an isometric grid" +hint_tooltip = "Sets the width of the isometric cell's axis aligned bounding box" mouse_filter = 0 -text = "Isometric cell size:" +text = "Isometric cell bounds width:" -[node name="IsometricCellSizeValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +[node name="IsometricCellBoundsWidthValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] margin_left = 127.0 margin_top = 52.0 margin_right = 230.0 margin_bottom = 76.0 -hint_tooltip = "Sets the size of the cells in an isometric grid" +hint_tooltip = "Sets the width of the isometric cell's axis aligned bounding box" mouse_default_cursor_shape = 2 min_value = 2.0 max_value = 16384.0 -value = 2.0 +value = 16.0 +rounded = true +align = 2 +suffix = "px" + +[node name="IsometricCellBoundsHeightLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +margin_top = 57.0 +margin_right = 123.0 +margin_bottom = 71.0 +hint_tooltip = "Sets the height of the isometric cell's axis aligned bounding box" +mouse_filter = 0 +text = "Isometric cell bounds height:" + +[node name="IsometricCellBoundsHeightValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +margin_left = 127.0 +margin_top = 52.0 +margin_right = 230.0 +margin_bottom = 76.0 +hint_tooltip = "Sets the height of the isometric cell's axis aligned bounding box" +mouse_default_cursor_shape = 2 +min_value = 2.0 +max_value = 16384.0 +value = 8.0 +rounded = true +align = 2 +suffix = "px" + +[node name="IsometricGridOffsetXLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +margin_top = 57.0 +margin_right = 123.0 +margin_bottom = 71.0 +hint_tooltip = "Sets isometric grid's x offset from the canvas origin (top left corner of the image)" +mouse_filter = 0 +text = "Isometric grid offset x:" + +[node name="IsometricGridOffsetXValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +margin_left = 127.0 +margin_top = 52.0 +margin_right = 230.0 +margin_bottom = 76.0 +hint_tooltip = "Sets isometric grid's x offset from the canvas origin (top left corner of the image)" +mouse_default_cursor_shape = 2 +min_value = -16384.0 +max_value = 16384.0 +rounded = true +align = 2 +suffix = "px" + +[node name="IsometricGridOffsetYLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +margin_top = 57.0 +margin_right = 123.0 +margin_bottom = 71.0 +hint_tooltip = "Sets isometric grid's y offset from the canvas origin (top left corner of the image)" +mouse_filter = 0 +text = "Isometric grid offset y:" + +[node name="IsometricGridOffsetYValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"] +margin_left = 127.0 +margin_top = 52.0 +margin_right = 230.0 +margin_bottom = 76.0 +hint_tooltip = "Sets isometric grid's y offset from the canvas origin (top left corner of the image)" +mouse_default_cursor_shape = 2 +min_value = -16384.0 +max_value = 16384.0 rounded = true align = 2 suffix = "px" diff --git a/src/Tools/Base.gd b/src/Tools/Base.gd index 4363df4..b6364d3 100644 --- a/src/Tools/Base.gd +++ b/src/Tools/Base.gd @@ -81,18 +81,6 @@ func _get_draw_rect() -> Rect2: return Rect2(selected_pixels[0].x, selected_pixels[0].y, selected_pixels[-1].x - selected_pixels[0].x + 1, selected_pixels[-1].y - selected_pixels[0].y + 1) -func _get_tile_mode_rect() -> Rect2: - match Global.current_project.tile_mode: - Global.Tile_Mode.XAXIS: - return Rect2(Vector2(-Global.current_project.size.x,0), Vector2(Global.current_project.size.x * 3,Global.current_project.size.y)) - Global.Tile_Mode.YAXIS: - return Rect2(Vector2(0,-Global.current_project.size.y), Vector2(Global.current_project.size.x,Global.current_project.size.y * 3)) - Global.Tile_Mode.BOTH: - return Rect2(-Global.current_project.size, Global.current_project.size * 3) - return Rect2(Vector2(0,0),Global.current_project.size) - - - func _get_draw_image() -> Image: var project : Project = Global.current_project return project.frames[project.current_frame].cels[project.current_layer].image diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index 3dcf39a..8a9fd82 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -257,11 +257,12 @@ func draw_tool_circle(position : Vector2, fill := false) -> void: func draw_tool_brush(position : Vector2) -> void: + var project : Project = Global.current_project if Global.mirror_view: - position.x = Global.current_project.size.x - position.x + position.x = project.size.x - position.x - if Global.current_project.tile_mode and _get_tile_mode_rect().has_point(position): - position = position.posmodv(Global.current_project.size) + if project.tile_mode and project.get_tile_mode_rect().has_point(position): + position = position.posmodv(project.size) var size := _brush_image.get_size() var dst := position - (size / 2).floor() @@ -273,7 +274,6 @@ func draw_tool_brush(position : Vector2) -> void: var src_rect := Rect2(dst_rect.position - dst, dst_rect.size) dst = dst_rect.position - var project : Project = Global.current_project _draw_brush_image(_brush_image, src_rect, dst) # Handle Mirroring @@ -305,7 +305,7 @@ func draw_tool_brush(position : Vector2) -> void: func draw_indicator() -> void: draw_indicator_at(_cursor, Vector2.ZERO, Color.blue) - if Global.current_project.tile_mode and _get_tile_mode_rect().has_point(_cursor): + if Global.current_project.tile_mode and Global.current_project.get_tile_mode_rect().has_point(_cursor): var tile := _line_start if _draw_line else _cursor if not tile in Global.current_project.selected_pixels: var offset := tile - tile.posmodv(Global.current_project.size) @@ -336,7 +336,7 @@ func _set_pixel(position : Vector2) -> void: var project : Project = Global.current_project if Global.mirror_view: position.x = project.size.x - position.x - 1 - if Global.current_project.tile_mode and _get_tile_mode_rect().has_point(position): + if project.tile_mode and project.get_tile_mode_rect().has_point(position): position = position.posmodv(project.size) var entire_image_selected : bool = project.selected_pixels.size() == project.size.x * project.size.y diff --git a/src/UI/Canvas/Grid.gd b/src/UI/Canvas/Grid.gd index 848d7c0..f4cccff 100644 --- a/src/UI/Canvas/Grid.gd +++ b/src/UI/Canvas/Grid.gd @@ -1,50 +1,86 @@ extends Node2D -var isometric_polylines := [] # An array of PoolVector2Arrays - - func _draw() -> void: - if Global.draw_grid: - draw_grid(Global.grid_type) + if not Global.draw_grid: + return + var rect := Rect2(Vector2.ZERO, Global.current_project.size) + if rect.has_no_area(): + return -func draw_grid(grid_type : int) -> void: - var size : Vector2 = Global.current_project.size + var grid_type : int = Global.grid_type if grid_type == Global.Grid_Types.CARTESIAN || grid_type == Global.Grid_Types.ALL: - for x in range(0, size.x + 1, Global.grid_width): - draw_line(Vector2(x, 0), Vector2(x, size.y), Global.grid_color) + var start_x : float = wrapf(0, rect.position.x, rect.position.x + Global.grid_width) + for x in range(start_x, rect.end.x + 1, Global.grid_width): + draw_line(Vector2(x, rect.position.y), Vector2(x, rect.end.y), Global.grid_color) - for y in range(0, size.y + 1, Global.grid_height): - draw_line(Vector2(0, y), Vector2(size.x, y), Global.grid_color) + var start_y : float = wrapf(0, rect.position.y, rect.position.y + Global.grid_height) + for y in range(start_y, rect.end.y + 1, Global.grid_height): + draw_line(Vector2(rect.position.x, y), Vector2(rect.end.x, y), Global.grid_color) if grid_type == Global.Grid_Types.ISOMETRIC || grid_type == Global.Grid_Types.ALL: - var i := 0 - for x in range(Global.grid_isometric_cell_size, size.x + 2, Global.grid_isometric_cell_size * 2): - for y in range(0, size.y + 1, Global.grid_isometric_cell_size): - draw_isometric_tile(i, Vector2(x, y)) - i += 1 + _draw_isometric_grid(rect) -func draw_isometric_tile(i : int, origin := Vector2.RIGHT, cell_size : int = Global.grid_isometric_cell_size) -> void: - # A random value I found by trial and error, I have no idea why it "works" - var diff = 1.11754 - var approx_30_degrees = deg2rad(26.565) +func _draw_isometric_grid(target_rect : Rect2) -> void: + # Using Array instead of PoolVector2Array to avoid kinda + # random "resize: Can't resize PoolVector if locked" errors. + # See: https://github.com/Orama-Interactive/Pixelorama/issues/331 + # It will be converted to PoolVector2Array before being sent to be rendered. + var grid_multiline_points := [] - var pool := PoolVector2Array() - if i < isometric_polylines.size(): - pool = isometric_polylines[i] - else: - var a = origin - Vector2(0, 0.5) - var b = a + Vector2(cos(approx_30_degrees), sin(approx_30_degrees)) * cell_size * diff - var c = a + Vector2.DOWN * cell_size - var d = c - Vector2(cos(approx_30_degrees), sin(approx_30_degrees)) * cell_size * diff - pool.append(a) - pool.append(b) - pool.append(c) - pool.append(d) - pool.append(a) - isometric_polylines.append(pool) + var cell_size := Vector2(Global.grid_isometric_cell_bounds_width, Global.grid_isometric_cell_bounds_height) + var max_cell_count : Vector2 = target_rect.size / cell_size - if pool.size() > 2: - draw_polyline(pool, Global.grid_color) + var origin := Vector2(Global.grid_isometric_offset_x, Global.grid_isometric_offset_y) + var origin_offset : Vector2 = (origin - target_rect.position).posmodv(cell_size) + + # lines ↗↗↗ (from bottom-left to top-right) + var per_cell_offset := cell_size * Vector2(1, -1) + + # lines ↗↗↗ starting from the rect's left side (top to bottom): + var y : float = fposmod(origin_offset.y + cell_size.y * (0.5 + origin_offset.x / cell_size.x), cell_size.y) + while y < target_rect.size.y: + var start : Vector2 = target_rect.position + Vector2(0, y) + var cells_to_rect_bounds : float = min(max_cell_count.x, y / cell_size.y) + var end : Vector2 = start + cells_to_rect_bounds * per_cell_offset + grid_multiline_points.push_back(start) + grid_multiline_points.push_back(end) + y += cell_size.y + + # lines ↗↗↗ starting from the rect's bottom side (left to right): + var x : float = (y - target_rect.size.y) / cell_size.y * cell_size.x + while x < target_rect.size.x: + var start : Vector2 = target_rect.position + Vector2(x, target_rect.size.y) + var cells_to_rect_bounds : float = min(max_cell_count.y, max_cell_count.x - x / cell_size.x) + var end : Vector2 = start + cells_to_rect_bounds * per_cell_offset + grid_multiline_points.push_back(start) + grid_multiline_points.push_back(end) + x += cell_size.x + + # lines ↘↘↘ (from top-left to bottom-right) + per_cell_offset = cell_size + + # lines ↘↘↘ starting from the rect's left side (top to bottom): + y = fposmod(origin_offset.y - cell_size.y * (0.5 + origin_offset.x / cell_size.x), cell_size.y) + while y < target_rect.size.y: + var start : Vector2 = target_rect.position + Vector2(0, y) + var cells_to_rect_bounds : float = min(max_cell_count.x, max_cell_count.y - y / cell_size.y) + var end : Vector2 = start + cells_to_rect_bounds * per_cell_offset + grid_multiline_points.push_back(start) + grid_multiline_points.push_back(end) + y += cell_size.y + + # lines ↘↘↘ starting from the rect's top side (left to right): + x = fposmod(origin_offset.x - cell_size.x * (0.5 + origin_offset.y / cell_size.y), cell_size.x) + while x < target_rect.size.x: + var start : Vector2 = target_rect.position + Vector2(x, 0) + var cells_to_rect_bounds : float = min(max_cell_count.y, max_cell_count.x - x / cell_size.x) + var end : Vector2 = start + cells_to_rect_bounds * per_cell_offset + grid_multiline_points.push_back(start) + grid_multiline_points.push_back(end) + x += cell_size.x + + if not grid_multiline_points.empty(): + draw_multiline(grid_multiline_points, Global.grid_color) diff --git a/src/UI/Canvas/PixelGrid.gd b/src/UI/Canvas/PixelGrid.gd index 6a5dc0a..4059b38 100644 --- a/src/UI/Canvas/PixelGrid.gd +++ b/src/UI/Canvas/PixelGrid.gd @@ -9,7 +9,7 @@ func _draw() -> void: if zoom_percentage < Global.pixel_grid_show_at_zoom: return - var rect : Rect2 = get_rect_to_draw() + var rect : Rect2 = Global.current_project.get_tile_mode_rect() if rect.has_no_area(): return @@ -17,18 +17,3 @@ func _draw() -> void: draw_line(Vector2(x, rect.position.y), Vector2(x, rect.end.y), Global.pixel_grid_color) for y in range(ceil(rect.position.y), floor(rect.end.y) + 1): draw_line(Vector2(rect.position.x, y), Vector2(rect.end.x, y), Global.pixel_grid_color) - - -func get_rect_to_draw() -> Rect2: - var size := Global.current_project.size - var tiling_rect : Rect2 - match Global.current_project.tile_mode: - Global.Tile_Mode.NONE: - tiling_rect = Rect2(Vector2.ZERO, size) - Global.Tile_Mode.XAXIS: - tiling_rect = Rect2(Vector2(-1, 0) * size, Vector2(3, 1) * size) - Global.Tile_Mode.YAXIS: - tiling_rect = Rect2(Vector2(0, -1) * size, Vector2(1, 3) * size) - Global.Tile_Mode.BOTH: - tiling_rect = Rect2(Vector2(-1, -1) * size, Vector2(3, 3) * size) - return tiling_rect diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 45cf3b2..81ca558 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -288,6 +288,7 @@ func tile_mode_submenu_id_pressed(id : int) -> void: Global.tile_mode_submenu.set_item_checked(i, i == id) Global.canvas.tile_mode.update() Global.canvas.pixel_grid.update() + Global.canvas.grid.update() func toggle_mirror_view() -> void: