1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-31 07:29:49 +00:00

Tile mode fixes (Heavy testing required) (#723)

* Hide/Show options based on current tile option

* Changed a condition for tile mode

* Simplified tile detection code

* Priortize main tile as nearest if mouse is in it

* make tile mode draw behind canvas

* Changed a condition

* Tiles on top gets detected first...

...in case of overlap

* Only display relavant options

according to current mode

* Update preview according to..

..current mode

* removed print()

* Added tile masking

* Added tile masking

* Compare mask with project size

only masks with same size as project are accepted

* Formatting

* Added the suggestions

* Some minor modifications

* detect if a mask is loaded or not

* Added tile_mask to project

* Cosmetics

* added path detection

* added option to emit signal instead of loading...

... image directly through OpenSave

* Added a way to load mask for HTML5

* formatting

* formatting

* Formatting

* set proper way for saving tile_mask

* Formatting

* removed whitespace
This commit is contained in:
Variable 2022-08-02 20:57:06 +05:00 committed by GitHub
parent b7e6a183a6
commit 8803acd105
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 259 additions and 63 deletions

View file

@ -3,6 +3,7 @@ extends Node
# Thanks to Pukkah from GitHub for providing the original code
signal in_focus
signal image_loaded # emits a signal for returning loaded image info
func _ready() -> void:
@ -70,7 +71,9 @@ func _define_js() -> void:
)
func load_image() -> void:
# If (load_directly = false) then image info (image and it's name)
# will not be directly farwarded it to OpenSave
func load_image(load_directly := true):
if OS.get_name() != "HTML5" or !OS.has_feature("JavaScript"):
return
@ -97,6 +100,7 @@ func load_image() -> void:
var image = Image.new()
var image_error
var image_info: Dictionary = {}
match image_type:
"image/png":
image_error = image.load_png_from_buffer(image_data)
@ -111,7 +115,10 @@ func load_image() -> void:
print("An error occurred while trying to display the image.")
return
else:
OpenSave.handle_loading_image(image_name, image)
image_info = {"image": image, "name": image_name}
if load_directly:
OpenSave.handle_loading_image(image_name, image)
emit_signal("image_loaded", image_info)
func load_shader() -> void:

View file

@ -125,6 +125,17 @@ func open_pxo_file(path: String, untitled_backup: bool = false, replace_empty: b
new_project.brushes.append(image)
Brushes.add_project_brush(image)
if dict.result.has("tile_mask") and dict.result.has("has_mask"):
if dict.result.has_mask:
var t_width = dict.result.tile_mask.size_x
var t_height = dict.result.tile_mask.size_y
var buffer := file.get_buffer(t_width * t_height * 4)
var image := Image.new()
image.create_from_data(t_width, t_height, false, Image.FORMAT_RGBA8, buffer)
new_project.tiles.tile_mask = image
else:
new_project.tiles.reset_mask()
file.close()
if !empty_project:
Global.projects.append(new_project)
@ -351,6 +362,9 @@ func save_pxo_file(
for brush in project.brushes:
file.store_buffer(brush.get_data())
if project.tiles.has_mask:
file.store_buffer(project.tiles.tile_mask.get_data())
file.close()
if OS.get_name() == "HTML5" and OS.has_feature("JavaScript") and !autosave:

View file

@ -347,6 +347,10 @@ func serialize() -> Dictionary:
for brush in brushes:
brush_data.append({"size_x": brush.get_size().x, "size_y": brush.get_size().y})
var tile_mask_data := {
"size_x": tiles.tile_mask.get_size().x, "size_y": tiles.tile_mask.get_size().y
}
var metadata := _serialize_metadata(self)
var project_data := {
@ -354,6 +358,8 @@ func serialize() -> Dictionary:
"name": name,
"size_x": size.x,
"size_y": size.y,
"has_mask": tiles.has_mask,
"tile_mask": tile_mask_data,
"tile_mode_x_basis_x": tiles.x_basis.x,
"tile_mode_x_basis_y": tiles.x_basis.y,
"tile_mode_y_basis_x": tiles.y_basis.x,
@ -383,6 +389,8 @@ func deserialize(dict: Dictionary) -> void:
size.y = dict.size_y
tiles.tile_size = size
selection_bitmap = resize_bitmap(selection_bitmap, size)
if dict.has("has_mask"):
tiles.has_mask = dict.has_mask
if dict.has("tile_mode_x_basis_x") and dict.has("tile_mode_x_basis_y"):
tiles.x_basis.x = dict.tile_mode_x_basis_x
tiles.x_basis.y = dict.tile_mode_x_basis_y
@ -494,6 +502,7 @@ func _size_changed(value: Vector2) -> void:
else:
tiles.y_basis = Vector2(0, value.y)
tiles.tile_size = value
tiles.reset_mask()
size = value
@ -760,7 +769,7 @@ func can_pixel_get_drawn(
if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y:
return false
if tiles.mode != Tiles.MODE.NONE and tiles.get_nearest_tile(pixel).position != Vector2.ZERO:
if tiles.mode != Tiles.MODE.NONE and !tiles.has_point(pixel):
return false
if has_selection:

View file

@ -7,12 +7,16 @@ var mode: int = MODE.NONE
var x_basis: Vector2
var y_basis: Vector2
var tile_size: Vector2
var tile_mask := Image.new()
var has_mask := false
func _init(size: Vector2):
x_basis = Vector2(size.x, 0)
y_basis = Vector2(0, size.y)
tile_size = size
tile_mask.create(tile_size.x, tile_size.y, false, Image.FORMAT_RGBA8)
tile_mask.fill(Color.white)
func get_bounding_rect() -> Rect2:
@ -41,29 +45,36 @@ func get_bounding_rect() -> Rect2:
func get_nearest_tile(point: Vector2) -> Rect2:
var tile_to_screen_space := Transform2D(x_basis, y_basis, Vector2.ZERO)
# Transform2D.basis_xform_inv() is broken so compute the inverse explicitly:
# https://github.com/godotengine/godot/issues/58556
var screen_to_tile_space := tile_to_screen_space.affine_inverse()
var p := point - tile_size / 2.0 + Vector2(0.5, 0.5) # p relative to center of tiles
var p_tile_space := screen_to_tile_space.basis_xform(p)
var tl_tile := tile_to_screen_space.basis_xform(p_tile_space.floor())
var tr_tile := tl_tile + x_basis
var bl_tile := tl_tile + y_basis
var br_tile := tl_tile + x_basis + y_basis
var tl_tile_dist := (p - tl_tile).length_squared()
var tr_tile_dist := (p - tr_tile).length_squared()
var bl_tile_dist := (p - bl_tile).length_squared()
var br_tile_dist := (p - br_tile).length_squared()
match [tl_tile_dist, tr_tile_dist, bl_tile_dist, br_tile_dist].min():
tl_tile_dist:
return Rect2(tl_tile, tile_size)
tr_tile_dist:
return Rect2(tr_tile, tile_size)
bl_tile_dist:
return Rect2(bl_tile, tile_size)
_:
return Rect2(br_tile, tile_size)
var positions = Global.canvas.tile_mode.get_tile_positions()
positions.append(Vector2.ZERO)
var candidates := []
for pos in positions:
var test_rect = Rect2(pos, tile_size)
if test_rect.has_point(point):
candidates.append(test_rect)
if candidates.empty():
return Rect2(Vector2.ZERO, tile_size)
var final := []
tile_mask.lock()
for candidate in candidates:
var rel_pos = point - candidate.position
if tile_mask.get_pixelv(rel_pos).a == 1.0:
final.append(candidate)
tile_mask.unlock()
if final.empty():
return Rect2(Vector2.ZERO, tile_size)
final.sort_custom(self, "sort_by_height")
return final[0]
func sort_by_height(a: Rect2, b: Rect2):
if a.position.y > b.position.y:
return false
else:
return true
func get_canon_position(position: Vector2) -> Vector2:
@ -76,15 +87,19 @@ func get_canon_position(position: Vector2) -> Vector2:
func has_point(point: Vector2) -> bool:
var screen_to_tile_space := Transform2D(x_basis, y_basis, Vector2.ZERO).affine_inverse()
var nearest_tile := get_nearest_tile(point)
var nearest_tile_tile_space := screen_to_tile_space.basis_xform(nearest_tile.position).round()
match mode:
MODE.BOTH:
return abs(nearest_tile_tile_space.x) <= 1 and abs(nearest_tile_tile_space.y) <= 1
MODE.X_AXIS:
return abs(nearest_tile_tile_space.x) <= 1 and abs(nearest_tile_tile_space.y) == 0
MODE.Y_AXIS:
return abs(nearest_tile_tile_space.x) == 0 and abs(nearest_tile_tile_space.y) <= 1
_:
return nearest_tile_tile_space == Vector2.ZERO
var positions = Global.canvas.tile_mode.get_tile_positions()
positions.append(Vector2.ZERO) # The central tile is included manually
tile_mask.lock()
for tile_pos in positions:
var test_rect = Rect2(tile_pos, tile_size)
var rel_pos = point - tile_pos
if test_rect.has_point(point) and tile_mask.get_pixelv(rel_pos).a == 1.0:
return true
tile_mask.unlock()
return false
func reset_mask():
tile_mask.create(tile_size.x, tile_size.y, false, Image.FORMAT_RGBA8)
tile_mask.fill(Color.white)
has_mask = false

View file

@ -145,7 +145,7 @@ func draw_start(position: Vector2) -> void:
Global.canvas.selection.transform_content_confirm()
if (
!Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn()
or Global.current_project.tiles.get_nearest_tile(position).position != Vector2.ZERO
or !Rect2(Vector2.ZERO, Global.current_project.size).has_point(position)
):
return
if (

View file

@ -43,6 +43,7 @@ render_target_update_mode = 3
script = ExtResource( 5 )
[node name="TileMode" type="Node2D" parent="."]
show_behind_parent = true
material = SubResource( 1 )
script = ExtResource( 4 )

View file

@ -1,26 +1,62 @@
extends ConfirmationDialog
onready var x_basis_x_spinbox: SpinBox = $VBoxContainer/OptionsContainer/XBasisX
onready var x_basis_y_spinbox: SpinBox = $VBoxContainer/OptionsContainer/XBasisY
onready var y_basis_x_spinbox: SpinBox = $VBoxContainer/OptionsContainer/YBasisX
onready var y_basis_y_spinbox: SpinBox = $VBoxContainer/OptionsContainer/YBasisY
onready var x_basis_x_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/XBasisX
onready var x_basis_y_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/XBasisY
onready var y_basis_x_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/YBasisX
onready var y_basis_y_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/YBasisY
onready var preview_rect: Control = $VBoxContainer/AspectRatioContainer/Preview
onready var tile_mode: Node2D = $VBoxContainer/AspectRatioContainer/Preview/TileMode
onready var load_button: Button = $VBoxContainer/HBoxContainer/Mask/LoadMask
onready var mask_hint: TextureRect = $VBoxContainer/HBoxContainer/Mask/MaskHint
func _on_TileModeOffsetsDialog_about_to_show() -> void:
tile_mode.draw_center = true
tile_mode.tiles = Tiles.new(Global.current_project.size)
tile_mode.tiles.mode = Tiles.MODE.BOTH
if Global.current_project.tiles.mode != Tiles.MODE.NONE:
tile_mode.tiles.mode = Global.current_project.tiles.mode
if Global.current_project.tiles.mode != Tiles.MODE.NONE:
tile_mode.tiles.mode = Global.current_project.tiles.mode
tile_mode.tiles.x_basis = Global.current_project.tiles.x_basis
tile_mode.tiles.y_basis = Global.current_project.tiles.y_basis
x_basis_x_spinbox.value = tile_mode.tiles.x_basis.x
x_basis_y_spinbox.value = tile_mode.tiles.x_basis.y
y_basis_x_spinbox.value = tile_mode.tiles.y_basis.x
y_basis_y_spinbox.value = tile_mode.tiles.y_basis.y
_show_options()
if Global.current_project.tiles.mode == Tiles.MODE.X_AXIS:
y_basis_x_spinbox.visible = false
y_basis_y_spinbox.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisXLabel.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisYLabel.visible = false
elif Global.current_project.tiles.mode == Tiles.MODE.Y_AXIS:
x_basis_x_spinbox.visible = false
x_basis_y_spinbox.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisXLabel.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisYLabel.visible = false
load_button.text = "Load Mask"
if Global.current_project.tiles.has_mask:
load_button.text = "Loaded"
var tex := ImageTexture.new()
tex.create_from_image(Global.current_project.tiles.tile_mask)
mask_hint.texture = tex
update_preview()
func _show_options():
x_basis_x_spinbox.visible = true
x_basis_y_spinbox.visible = true
y_basis_x_spinbox.visible = true
y_basis_y_spinbox.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisXLabel.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisYLabel.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisXLabel.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisYLabel.visible = true
func _on_TileModeOffsetsDialog_confirmed() -> void:
Global.current_project.tiles.x_basis = tile_mode.tiles.x_basis
Global.current_project.tiles.y_basis = tile_mode.tiles.y_basis
@ -81,3 +117,47 @@ func _on_Reset_pressed():
y_basis_x_spinbox.value = 0
y_basis_y_spinbox.value = size.y
update_preview()
func _on_LoadMask_pressed() -> void:
if OS.get_name() == "HTML5":
Html5FileExchange.connect("image_loaded", self, "_on_html_image_loaded")
Html5FileExchange.load_image(false)
else:
$FileDialog.current_dir = Global.current_project.directory_path
$FileDialog.popup_centered()
func _on_html_image_loaded(image_info: Dictionary):
if image_info.has("image"):
load_mask(image_info.image)
Html5FileExchange.disconnect("image_loaded", self, "_on_html_image_loaded")
func _on_FileDialog_file_selected(path: String) -> void:
var image := Image.new()
var err := image.load(path)
if err != OK: # An error occured
var file_name: String = path.get_file()
Global.error_dialog.set_text(
tr("Can't load file '%s'.\nError code: %s") % [file_name, str(err)]
)
Global.error_dialog.popup_centered()
Global.dialog_open(true)
return
if image.get_size() != Global.current_project.size:
Global.error_dialog.set_text(tr("The mask must have the same size as the project"))
Global.error_dialog.popup_centered()
Global.dialog_open(true)
return
load_mask(image)
func load_mask(image: Image):
load_button.text = "Loaded"
Global.current_project.tiles.tile_mask = image
Global.current_project.tiles.has_mask = true
var tex := ImageTexture.new()
tex.create_from_image(Global.current_project.tiles.tile_mask)
mask_hint.texture = tex

View file

@ -18,29 +18,33 @@ script = ExtResource( 3 )
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 208.0
margin_right = 293.0
margin_bottom = 362.0
[node name="TileModeOffsets" type="Label" parent="VBoxContainer"]
margin_right = 200.0
margin_right = 285.0
margin_bottom = 14.0
text = "Tile Mode Offsets"
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"]
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 18.0
margin_right = 200.0
margin_right = 285.0
margin_bottom = 150.0
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer/HBoxContainer"]
margin_right = 137.0
margin_bottom = 132.0
custom_constants/vseparation = 4
custom_constants/hseparation = 2
columns = 2
[node name="XBasisXLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
[node name="XBasisXLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_top = 5.0
margin_right = 61.0
margin_bottom = 19.0
text = "X-basis x:"
[node name="XBasisX" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
[node name="XBasisX" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_left = 63.0
margin_right = 137.0
margin_bottom = 24.0
@ -49,13 +53,13 @@ min_value = -16384.0
max_value = 16384.0
suffix = "px"
[node name="XBasisYLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
[node name="XBasisYLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_top = 33.0
margin_right = 61.0
margin_bottom = 47.0
text = "X-basis y:"
[node name="XBasisY" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
[node name="XBasisY" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_left = 63.0
margin_top = 28.0
margin_right = 137.0
@ -65,13 +69,13 @@ min_value = -16384.0
max_value = 16384.0
suffix = "px"
[node name="YBasisXLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
[node name="YBasisXLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_top = 61.0
margin_right = 61.0
margin_bottom = 75.0
text = "Y-basis x:"
[node name="YBasisX" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
[node name="YBasisX" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_left = 63.0
margin_top = 56.0
margin_right = 137.0
@ -81,13 +85,13 @@ min_value = -16384.0
max_value = 16384.0
suffix = "px"
[node name="YBasisYLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
[node name="YBasisYLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_top = 89.0
margin_right = 61.0
margin_bottom = 103.0
text = "Y-basis y:"
[node name="YBasisY" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
[node name="YBasisY" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_left = 63.0
margin_top = 84.0
margin_right = 137.0
@ -97,20 +101,67 @@ min_value = -16384.0
max_value = 16384.0
suffix = "px"
[node name="Reset" type="Button" parent="VBoxContainer/OptionsContainer"]
[node name="Reset" type="Button" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
margin_top = 112.0
margin_right = 61.0
margin_bottom = 132.0
text = "Reset"
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/HBoxContainer"]
margin_left = 141.0
margin_right = 145.0
margin_bottom = 132.0
[node name="Mask" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
margin_left = 149.0
margin_right = 285.0
margin_bottom = 132.0
size_flags_horizontal = 3
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/Mask"]
margin_right = 136.0
margin_bottom = 14.0
text = "Tile Mask"
align = 1
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/HBoxContainer/Mask"]
margin_top = 18.0
margin_right = 136.0
margin_bottom = 22.0
[node name="MaskHint" type="TextureRect" parent="VBoxContainer/HBoxContainer/Mask"]
margin_top = 26.0
margin_right = 136.0
margin_bottom = 100.0
rect_min_size = Vector2( 136, 74 )
size_flags_vertical = 3
expand = true
stretch_mode = 6
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer/HBoxContainer/Mask"]
margin_top = 104.0
margin_right = 136.0
margin_bottom = 108.0
[node name="LoadMask" type="Button" parent="VBoxContainer/HBoxContainer/Mask"]
margin_top = 112.0
margin_right = 136.0
margin_bottom = 132.0
rect_min_size = Vector2( 100, 0 )
hint_tooltip = "Mask will only allow the drawing to remain within it's bounds
(Used for custom tiles)"
text = "Load Mask"
clip_text = true
[node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"]
margin_top = 154.0
margin_right = 200.0
margin_right = 285.0
margin_bottom = 354.0
size_flags_vertical = 3
[node name="Preview" type="Control" parent="VBoxContainer/AspectRatioContainer"]
margin_right = 200.0
margin_left = 42.5
margin_right = 242.5
margin_bottom = 200.0
rect_min_size = Vector2( 200, 200 )
@ -125,12 +176,31 @@ anchor_bottom = 1.0
margin_right = 0.0
margin_bottom = 0.0
[node name="FileDialog" type="FileDialog" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 293.0
margin_bottom = 362.0
rect_min_size = Vector2( 172, 60.2 )
window_title = "Open a File"
resizable = true
mode = 0
access = 2
filters = PoolStringArray( "*.png ; PNG Image" )
current_dir = "/home/variable/Documents/Godot/Godot projects/Pixelorama-Tile-mode-Fixes"
current_path = "/home/variable/Documents/Godot/Godot projects/Pixelorama-Tile-mode-Fixes/"
__meta__ = {
"_editor_description_": ""
}
[connection signal="about_to_show" from="." to="." method="_on_TileModeOffsetsDialog_about_to_show"]
[connection signal="confirmed" from="." to="." method="_on_TileModeOffsetsDialog_confirmed"]
[connection signal="item_rect_changed" from="." to="." method="_on_TileModeOffsetsDialog_item_rect_changed"]
[connection signal="popup_hide" from="." to="." method="_on_TileModeOffsetsDialog_popup_hide"]
[connection signal="value_changed" from="VBoxContainer/OptionsContainer/XBasisX" to="." method="_on_XBasisX_value_changed"]
[connection signal="value_changed" from="VBoxContainer/OptionsContainer/XBasisY" to="." method="_on_XBasisY_value_changed"]
[connection signal="value_changed" from="VBoxContainer/OptionsContainer/YBasisX" to="." method="_on_YBasisX_value_changed"]
[connection signal="value_changed" from="VBoxContainer/OptionsContainer/YBasisY" to="." method="_on_YBasisY_value_changed"]
[connection signal="pressed" from="VBoxContainer/OptionsContainer/Reset" to="." method="_on_Reset_pressed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/XBasisX" to="." method="_on_XBasisX_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/XBasisY" to="." method="_on_XBasisY_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/YBasisX" to="." method="_on_YBasisX_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/YBasisY" to="." method="_on_YBasisY_value_changed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/OptionsContainer/Reset" to="." method="_on_Reset_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Mask/LoadMask" to="." method="_on_LoadMask_pressed"]
[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]