diff --git a/addons/rmsmartshape/actions/action.gd b/addons/rmsmartshape/actions/action.gd new file mode 100644 index 0000000..5125edc --- /dev/null +++ b/addons/rmsmartshape/actions/action.gd @@ -0,0 +1,23 @@ +class_name SS2D_Action +extends RefCounted + +## Base class for all plugin actions. +## +## UndoRedo system will call [method do] and [method undo]. + +# @virtual +## Returns string to be used as a name in editor History tab. +func get_name() -> String: + return "UntitledAction" + + +# @virtual +## Do action here. +func do() -> void: + pass + + +# @virtual +## Undo action here. +func undo() -> void: + pass diff --git a/addons/rmsmartshape/actions/action_add_collision_nodes.gd b/addons/rmsmartshape/actions/action_add_collision_nodes.gd new file mode 100644 index 0000000..648f67d --- /dev/null +++ b/addons/rmsmartshape/actions/action_add_collision_nodes.gd @@ -0,0 +1,56 @@ +extends SS2D_Action + +## ActionAddCollisionNodes + +var _shape: SS2D_Shape + +var _saved_index: int +var _saved_pos: Vector2 + + +func _init(shape: SS2D_Shape) -> void: + _shape = shape + + +func get_name() -> String: + return "Add Collision Nodes" + + +func do() -> void: + _saved_index = _shape.get_index() + _saved_pos = _shape.position + + var owner := _shape.owner + var static_body := StaticBody2D.new() + static_body.position = _shape.position + _shape.position = Vector2.ZERO + + _shape.get_parent().add_child(static_body, true) + static_body.owner = owner + + _shape.get_parent().remove_child(_shape) + static_body.add_child(_shape, true) + _shape.owner = owner + + var poly: CollisionPolygon2D = CollisionPolygon2D.new() + static_body.add_child(poly, true) + poly.owner = owner + # TODO: Make this a option at some point + poly.modulate.a = 0.3 + poly.visible = false + _shape.collision_polygon_node_path = _shape.get_path_to(poly) + + +func undo() -> void: + var owner := _shape.owner + var parent := _shape.get_parent() + var grandparent := _shape.get_parent().get_parent() + parent.remove_child(_shape) + grandparent.remove_child(parent) + parent.free() + + grandparent.add_child(_shape) + _shape.owner = owner + grandparent.move_child(_shape, _saved_index) + _shape.position = _saved_pos + diff --git a/addons/rmsmartshape/actions/action_add_point.gd b/addons/rmsmartshape/actions/action_add_point.gd new file mode 100644 index 0000000..017eadc --- /dev/null +++ b/addons/rmsmartshape/actions/action_add_point.gd @@ -0,0 +1,44 @@ +extends SS2D_Action + +## ActionAddPoint + +const ActionInvertOrientation := preload("res://addons/rmsmartshape/actions/action_invert_orientation.gd") +var _invert_orientation: ActionInvertOrientation + +var _commit_update: bool +var _shape: SS2D_Shape +var _key: int +var _position: Vector2 +var _idx: int + + +func _init(shape: SS2D_Shape, position: Vector2, idx: int = -1, commit_update: bool = true) -> void: + _shape = shape + _position = position + _commit_update = commit_update + _idx = _shape.adjust_add_point_index(idx) + _key = _shape.reserve_key() + _invert_orientation = ActionInvertOrientation.new(shape) + + +func get_name() -> String: + return "Add Point at (%d, %d)" % [_position.x, _position.y] + + +func do() -> void: + _shape.begin_update() + _key = _shape.add_point(_position, _idx, _key) + _invert_orientation.do() + if _commit_update: + _shape.end_update() + + +func undo() -> void: + _shape.begin_update() + _invert_orientation.undo() + _shape.remove_point(_key) + _shape.end_update() + + +func get_key() -> int: + return _key diff --git a/addons/rmsmartshape/actions/action_close_shape.gd b/addons/rmsmartshape/actions/action_close_shape.gd new file mode 100644 index 0000000..d935209 --- /dev/null +++ b/addons/rmsmartshape/actions/action_close_shape.gd @@ -0,0 +1,40 @@ +extends SS2D_Action + +## ActionCloseShape + +const ActionInvertOrientation := preload("res://addons/rmsmartshape/actions/action_invert_orientation.gd") +var _invert_orientation: ActionInvertOrientation + +var _shape: SS2D_Shape +var _key: int +var _performed: bool + + +func _init(shape: SS2D_Shape) -> void: + _shape = shape + _invert_orientation = ActionInvertOrientation.new(shape) + + +func get_name() -> String: + return "Close Shape" + + +func do() -> void: + _performed = _shape.can_close() + if _performed: + _shape.begin_update() + _key = _shape.close_shape(_key) + _invert_orientation.do() + _shape.end_update() + + +func undo() -> void: + if _performed: + _shape.begin_update() + _invert_orientation.undo() + _shape.remove_point(_key) + _shape.end_update() + + +func get_key() -> int: + return _key diff --git a/addons/rmsmartshape/actions/action_cut_edge.gd b/addons/rmsmartshape/actions/action_cut_edge.gd new file mode 100644 index 0000000..5fc3f65 --- /dev/null +++ b/addons/rmsmartshape/actions/action_cut_edge.gd @@ -0,0 +1,41 @@ +extends SS2D_Action + +## ActionCutEdge +## +## A delegate action that selects an action to perform based on the edge +## location and shape state. + +var ActionOpenShape := preload("res://addons/rmsmartshape/actions/action_open_shape.gd") +var ActionDeletePoint := preload("res://addons/rmsmartshape/actions/action_delete_point.gd") +var ActionSplitShape := preload("res://addons/rmsmartshape/actions/action_split_shape.gd") + +var _shape: SS2D_Shape +var _action: SS2D_Action + + +func _init(shape: SS2D_Shape, key_edge_start: int, key_edge_end: int) -> void: + _shape = shape + + var key_first: int = shape.get_point_key_at_index(0) + var key_last: int = shape.get_point_key_at_index(shape.get_point_count()-1) + if _shape.is_shape_closed(): + _action = ActionOpenShape.new(shape, key_edge_start) + elif key_edge_start == key_first: + _action = ActionDeletePoint.new(shape, key_edge_start) + elif key_edge_end == key_last: + _action = ActionDeletePoint.new(shape, key_edge_end) + else: + _action = ActionSplitShape.new(shape, key_edge_start) + + +func get_name() -> String: + return _action.get_name() + + +func do() -> void: + _action.do() + + +func undo() -> void: + _action.undo() + diff --git a/addons/rmsmartshape/actions/action_delete_control_point.gd b/addons/rmsmartshape/actions/action_delete_control_point.gd new file mode 100644 index 0000000..56a2bdc --- /dev/null +++ b/addons/rmsmartshape/actions/action_delete_control_point.gd @@ -0,0 +1,45 @@ +extends SS2D_Action + +## ActionDeleteControlPoint + +enum PointType {POINT_IN, POINT_OUT} + +const ActionInvertOrientation := preload("res://addons/rmsmartshape/actions/action_invert_orientation.gd") +var _invert_orientation: ActionInvertOrientation + +var _shape: SS2D_Shape +var _key: int +var _point_type: PointType +var _old_value: Vector2 + + +func _init(shape: SS2D_Shape, key: int, point_type: PointType) -> void: + _shape = shape + _key = key + _point_type = point_type + _old_value = shape.get_point_in(key) if _point_type == PointType.POINT_IN else shape.get_point_out(key) + _invert_orientation = ActionInvertOrientation.new(shape) + + +func get_name() -> String: + return "Delete Control Point " + ("In" if _point_type == PointType.POINT_IN else "Out") + + +func do() -> void: + _shape.begin_update() + if _point_type == PointType.POINT_IN: + _shape.set_point_in(_key, Vector2.ZERO) + else: + _shape.set_point_out(_key, Vector2.ZERO) + _invert_orientation.do() + _shape.end_update() + + +func undo() -> void: + _shape.begin_update() + _invert_orientation.undo() + if _point_type == PointType.POINT_IN: + _shape.set_point_in(_key, _old_value) + else: + _shape.set_point_out(_key, _old_value) + _shape.end_update() diff --git a/addons/rmsmartshape/actions/action_delete_point.gd b/addons/rmsmartshape/actions/action_delete_point.gd new file mode 100644 index 0000000..ad63e85 --- /dev/null +++ b/addons/rmsmartshape/actions/action_delete_point.gd @@ -0,0 +1,13 @@ +extends "res://addons/rmsmartshape/actions/action_delete_points.gd" + +## ActionDeletePoint + + +func _init(shape: SS2D_Shape, key: int, commit_update: bool = true) -> void: + var keys: PackedInt32Array = [key] + super(shape, keys, commit_update) + + +func get_name() -> String: + var pos := _shape.get_point_position(_keys[0]) + return "Delete Point at (%d, %d)" % [pos.x, pos.y] diff --git a/addons/rmsmartshape/actions/action_delete_points.gd b/addons/rmsmartshape/actions/action_delete_points.gd new file mode 100644 index 0000000..60ec5ba --- /dev/null +++ b/addons/rmsmartshape/actions/action_delete_points.gd @@ -0,0 +1,88 @@ +extends SS2D_Action + +## ActionDeletePoints + +const TUP := preload("res://addons/rmsmartshape/lib/tuple.gd") + +const ActionInvertOrientation := preload("res://addons/rmsmartshape/actions/action_invert_orientation.gd") +var _invert_orientation: ActionInvertOrientation + +const ActionCloseShape := preload("res://addons/rmsmartshape/actions/action_close_shape.gd") +var _close_shape: ActionCloseShape + +var _shape: SS2D_Shape +var _keys: PackedInt32Array +var _indicies: PackedInt32Array +var _positions: PackedVector2Array +var _points_in: PackedVector2Array +var _points_out: PackedVector2Array +var _properties: Array[SS2D_VertexProperties] +var _constraints: Array[Dictionary] +var _was_closed: bool +var _commit_update: bool + + +func _init(shape: SS2D_Shape, keys: PackedInt32Array, commit_update: bool = true) -> void: + _shape = shape + _invert_orientation = ActionInvertOrientation.new(shape) + _close_shape = ActionCloseShape.new(shape) + _commit_update = commit_update + + for k in keys: + add_point_to_delete(k) + + +func get_name() -> String: + return "Delete Points %s" % [_keys] + + +func do() -> void: + _shape.begin_update() + _was_closed = _shape.is_shape_closed() + var first_run := _positions.size() == 0 + for k in _keys: + if first_run: + _indicies.append(_shape.get_point_index(k)) + _positions.append(_shape.get_point_position(k)) + _points_in.append(_shape.get_point_in(k)) + _points_out.append(_shape.get_point_out(k)) + _properties.append(_shape.get_point_properties(k)) + _shape.remove_point(k) + if _was_closed: + _close_shape.do() + _invert_orientation.do() + if _commit_update: + _shape.end_update() + + +func undo() -> void: + _shape.begin_update() + _invert_orientation.undo() + if _was_closed: + _close_shape.undo() + for i in range(_keys.size()-1, -1, -1): + _shape.add_point(_positions[i], _indicies[i], _keys[i]) + _shape.set_point_in(_keys[i], _points_in[i]) + _shape.set_point_out(_keys[i], _points_out[i]) + _shape.set_point_properties(_keys[i], _properties[i]) + # Restore point constraints. + for i in range(_keys.size()-1, -1, -1): + for tuple: Vector2i in _constraints[i]: + _shape.set_constraint(tuple[0], tuple[1], _constraints[i][tuple]) + _shape.end_update() + + +func add_point_to_delete(key: int) -> void: + _keys.push_back(key) + var constraints := _shape.get_point_array().get_point_constraints(key) + # Save point constraints. + _constraints.append(constraints) + + for tuple: Vector2i in constraints: + var constraint: SS2D_Point_Array.CONSTRAINT = constraints[tuple] + if constraint == SS2D_Point_Array.CONSTRAINT.NONE: + continue + var key_other := SS2D_IndexTuple.get_other_value(tuple, key) + if constraint & SS2D_Point_Array.CONSTRAINT.ALL: + if not _keys.has(key_other): + add_point_to_delete(key_other) diff --git a/addons/rmsmartshape/actions/action_invert_orientation.gd b/addons/rmsmartshape/actions/action_invert_orientation.gd new file mode 100644 index 0000000..0f41504 --- /dev/null +++ b/addons/rmsmartshape/actions/action_invert_orientation.gd @@ -0,0 +1,33 @@ +extends SS2D_Action + +## ActionInvertOrientation + +var _shape: SS2D_Shape +var _performed: bool + + +func _init(shape: SS2D_Shape) -> void: + _shape = shape + + +func get_name() -> String: + return "Invert Orientation" + + +func do() -> void: + _performed = should_invert_orientation(_shape) + if _performed: + _shape.invert_point_order() + + +func undo() -> void: + if _performed: + _shape.invert_point_order() + + +func should_invert_orientation(s: SS2D_Shape) -> bool: + if s == null: + return false + if not s.is_shape_closed(): + return false + return not s.are_points_clockwise() and s.get_point_count() >= 3 diff --git a/addons/rmsmartshape/actions/action_make_shape_unique.gd b/addons/rmsmartshape/actions/action_make_shape_unique.gd new file mode 100644 index 0000000..28a064a --- /dev/null +++ b/addons/rmsmartshape/actions/action_make_shape_unique.gd @@ -0,0 +1,26 @@ +extends SS2D_Action + +## ActionMakeShapeUnique + +var _shape: SS2D_Shape +var _old_array: SS2D_Point_Array +var _new_array: SS2D_Point_Array + + +func _init(shape: SS2D_Shape) -> void: + _shape = shape + _old_array = shape.get_point_array() + _new_array = _shape.get_point_array().clone(true) + + +func get_name() -> String: + return "Make Shape Unique" + + +func do() -> void: + _shape.set_point_array(_new_array) + + +func undo() -> void: + _shape.set_point_array(_old_array) + diff --git a/addons/rmsmartshape/actions/action_move_control_points.gd b/addons/rmsmartshape/actions/action_move_control_points.gd new file mode 100644 index 0000000..5af9e67 --- /dev/null +++ b/addons/rmsmartshape/actions/action_move_control_points.gd @@ -0,0 +1,43 @@ +extends SS2D_Action + +## ActionMoveControlPoints + +var _shape: SS2D_Shape +var _keys: PackedInt32Array +var _old_points_in: PackedVector2Array +var _old_points_out: PackedVector2Array +var _new_points_in: PackedVector2Array +var _new_points_out: PackedVector2Array + + +func _init(s: SS2D_Shape, keys: PackedInt32Array, + old_points_in: PackedVector2Array, old_points_out: PackedVector2Array) -> void: + _shape = s + _keys = keys + _old_points_in = old_points_in + _old_points_out = old_points_out + for key in _keys: + _new_points_in.append(_shape.get_point_in(key)) + _new_points_out.append(_shape.get_point_out(key)) + + +func get_name() -> String: + return "Move Control Point" + + +func do() -> void: + _assign_points_in_out(_keys, _new_points_in, _new_points_out) + + +func undo() -> void: + _assign_points_in_out(_keys, _old_points_in, _old_points_out) + + +func _assign_points_in_out(keys: PackedInt32Array, in_positions: PackedVector2Array, out_positions: PackedVector2Array) -> void: + _shape.begin_update() + for i in keys.size(): + if _shape.get_point_in(keys[i]) != in_positions[i]: + _shape.set_point_in(keys[i], in_positions[i]) + if _shape.get_point_out(keys[i]) != out_positions[i]: + _shape.set_point_out(keys[i], out_positions[i]) + _shape.end_update() diff --git a/addons/rmsmartshape/actions/action_move_verticies.gd b/addons/rmsmartshape/actions/action_move_verticies.gd new file mode 100644 index 0000000..f8d4a47 --- /dev/null +++ b/addons/rmsmartshape/actions/action_move_verticies.gd @@ -0,0 +1,44 @@ +extends SS2D_Action + +## ActionMoveVerticies + +const ActionInvertOrientation := preload("res://addons/rmsmartshape/actions/action_invert_orientation.gd") +var _invert_orientation: ActionInvertOrientation + +var _shape: SS2D_Shape +var _keys: PackedInt32Array +var _old_positions: PackedVector2Array +var _new_positions: PackedVector2Array + + +func _init(s: SS2D_Shape, keys: PackedInt32Array, old_positions: PackedVector2Array) -> void: + _shape = s + _keys = keys.duplicate() + _old_positions = old_positions.duplicate() + _new_positions = PackedVector2Array() + for k in _keys: + _new_positions.append(_shape.get_point_position(k)) + _invert_orientation = ActionInvertOrientation.new(_shape) + + +func get_name() -> String: + if _keys.size() == 1: + return "Move Vertex to (%d, %d)" % [_new_positions[0].x, _new_positions[0].y] + else: + return "Move Verticies" + + +func do() -> void: + _shape.begin_update() + for i in _keys.size(): + _shape.set_point_position(_keys[i], _new_positions[i]) + _invert_orientation.do() + _shape.end_update() + + +func undo() -> void: + _shape.begin_update() + _invert_orientation.undo() + for i in range(_keys.size() - 1, -1, -1): + _shape.set_point_position(_keys[i], _old_positions[i]) + _shape.end_update() diff --git a/addons/rmsmartshape/actions/action_open_shape.gd b/addons/rmsmartshape/actions/action_open_shape.gd new file mode 100644 index 0000000..adbbb1f --- /dev/null +++ b/addons/rmsmartshape/actions/action_open_shape.gd @@ -0,0 +1,31 @@ +extends SS2D_Action + +## ActionOpenShape + +var _shape: SS2D_Shape +var _cut_idx: int +var _closing_key: int + + +func _init(shape: SS2D_Shape, edge_start_key: int) -> void: + _shape = shape + _cut_idx = shape.get_point_index(edge_start_key) + + +func get_name() -> String: + return "Open Shape" + + +func do() -> void: + _shape.begin_update() + var last_idx: int = _shape.get_point_count() - 1 + _closing_key = _shape.get_point_key_at_index(last_idx) + _shape.open_shape_at_edge(_cut_idx) + _shape.end_update() + + +func undo() -> void: + _shape.begin_update() + _shape.undo_open_shape_at_edge(_cut_idx, _closing_key) + _shape.end_update() + diff --git a/addons/rmsmartshape/actions/action_set_pivot.gd b/addons/rmsmartshape/actions/action_set_pivot.gd new file mode 100644 index 0000000..6157337 --- /dev/null +++ b/addons/rmsmartshape/actions/action_set_pivot.gd @@ -0,0 +1,44 @@ +extends SS2D_Action + +## ActionSetPivot + +var _shape: SS2D_Shape + +var _new_pos: Vector2 +var _old_pos: Vector2 + + +func _init(s: SS2D_Shape, pos: Vector2) -> void: + _shape = s + _new_pos = pos + _old_pos = _shape.global_position + + +func get_name() -> String: + return "Set Pivot" + + +func do() -> void: + _set_pivot(_new_pos) + + +func undo() -> void: + _set_pivot(_old_pos) + + +func _set_pivot(shape_position: Vector2) -> void: + var shape_gt: Transform2D = _shape.get_global_transform() + + _shape.global_position = shape_position + + _shape.begin_update() + _shape.disable_constraints() + + for i in _shape.get_point_count(): + var key: int = _shape.get_point_key_at_index(i) + var point: Vector2 = _shape.get_point_position(key) + _shape.set_point_position(key, _shape.to_local(shape_gt * point)) + + _shape.enable_constraints() + _shape.end_update() + diff --git a/addons/rmsmartshape/actions/action_split_curve.gd b/addons/rmsmartshape/actions/action_split_curve.gd new file mode 100644 index 0000000..a4ca489 --- /dev/null +++ b/addons/rmsmartshape/actions/action_split_curve.gd @@ -0,0 +1,10 @@ +extends "res://addons/rmsmartshape/actions/action_add_point.gd" + +## ActionSplitCurve + +func _init(shape: SS2D_Shape, idx: int, gpoint: Vector2, xform: Transform2D, commit_update: bool = true) -> void: + super._init(shape, xform.affine_inverse() * gpoint, idx, commit_update) + + +func get_name() -> String: + return "Split Curve at (%d, %d)" % [_position.x, _position.y] diff --git a/addons/rmsmartshape/actions/action_split_shape.gd b/addons/rmsmartshape/actions/action_split_shape.gd new file mode 100644 index 0000000..8fb2a28 --- /dev/null +++ b/addons/rmsmartshape/actions/action_split_shape.gd @@ -0,0 +1,74 @@ +extends SS2D_Action + +## ActionSplitShape +## +## How it's done: +## 1. First, the shape is copied and added to the scene tree. +## 2. Then, points of the splitted shape are deleted from first point to split point. +## 3. Finally, points of the original shape are deleted from the point after split point to last point. + +const ActionDeletePoints := preload("res://addons/rmsmartshape/actions/action_delete_points.gd") +var _delete_points_from_original: ActionDeletePoints + +var _shape: SS2D_Shape +var _splitted: SS2D_Shape +var _splitted_collision: CollisionPolygon2D +var _split_idx: int + + +func _init(shape: SS2D_Shape, split_point_key: int) -> void: + assert(shape.is_shape_closed() == false) + _shape = shape + _split_idx = shape.get_point_index(split_point_key) + _splitted = null + _splitted_collision = null + _delete_points_from_original = null + + +func get_name() -> String: + return "Split Shape" + + +func do() -> void: + if not is_instance_valid(_splitted): + _splitted = _shape.clone() + _splitted.begin_update() + for i in range(0, _split_idx + 1): + _splitted.remove_point_at_index(0) + _splitted.end_update() + _shape.get_parent().add_child(_splitted, true) + _splitted.set_owner(_shape.get_tree().get_edited_scene_root()) + # Add a collision shape node if the original shape has one. + if (not _shape.collision_polygon_node_path.is_empty() and _shape.has_node(_shape.collision_polygon_node_path)): + var collision_polygon_original := _shape.get_node(_shape.collision_polygon_node_path) as CollisionPolygon2D + if not is_instance_valid(_splitted_collision): + _splitted_collision = CollisionPolygon2D.new() + _splitted_collision.visible = collision_polygon_original.visible + _splitted_collision.modulate = collision_polygon_original.modulate + collision_polygon_original.get_parent().add_child(_splitted_collision, true) + _splitted_collision.set_owner(collision_polygon_original.get_tree().get_edited_scene_root()) + _splitted.collision_polygon_node_path = _splitted.get_path_to(_splitted_collision) + + if _delete_points_from_original == null: + var delete_keys := PackedInt32Array() + for i in range(_shape.get_point_count() - 1, _split_idx, -1): + delete_keys.append(_shape.get_point_key_at_index(i)) + _delete_points_from_original = ActionDeletePoints.new(_shape, delete_keys) + _delete_points_from_original.do() + + +func undo() -> void: + _splitted.set_owner(null) + _splitted.get_parent().remove_child(_splitted) + if is_instance_valid(_splitted_collision): + _splitted_collision.set_owner(null) + _splitted_collision.get_parent().remove_child(_splitted_collision) + _delete_points_from_original.undo() + + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + if is_instance_valid(_splitted) and _splitted.get_parent() == null: + _splitted.queue_free() + if is_instance_valid(_splitted_collision) and _splitted_collision.get_parent() == null: + _splitted_collision.queue_free() diff --git a/addons/rmsmartshape/asset_library_icon-new.png b/addons/rmsmartshape/asset_library_icon-new.png new file mode 100644 index 0000000..44db166 Binary files /dev/null and b/addons/rmsmartshape/asset_library_icon-new.png differ diff --git a/addons/rmsmartshape/asset_library_icon-new.png.import b/addons/rmsmartshape/asset_library_icon-new.png.import new file mode 100644 index 0000000..554070c --- /dev/null +++ b/addons/rmsmartshape/asset_library_icon-new.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5wllxq74p8kq" +path="res://.godot/imported/asset_library_icon-new.png-60877ecd9837f02c48423d4ec08e9284.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/asset_library_icon-new.png" +dest_files=["res://.godot/imported/asset_library_icon-new.png-60877ecd9837f02c48423d4ec08e9284.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/asset_library_icon.png b/addons/rmsmartshape/asset_library_icon.png new file mode 100644 index 0000000..1ff18e6 Binary files /dev/null and b/addons/rmsmartshape/asset_library_icon.png differ diff --git a/addons/rmsmartshape/asset_library_icon.png.import b/addons/rmsmartshape/asset_library_icon.png.import new file mode 100644 index 0000000..6d146eb --- /dev/null +++ b/addons/rmsmartshape/asset_library_icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbde02owb6iuu" +path="res://.godot/imported/asset_library_icon.png-57d4f9e357cb3293fe7f718c0ef3633c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/asset_library_icon.png" +dest_files=["res://.godot/imported/asset_library_icon.png-57d4f9e357cb3293fe7f718c0ef3633c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/assets/Anchor.svg b/addons/rmsmartshape/assets/Anchor.svg new file mode 100644 index 0000000..a9caae0 --- /dev/null +++ b/addons/rmsmartshape/assets/Anchor.svg @@ -0,0 +1,12 @@ + + + + + image/svg+xml + + + + + + + diff --git a/addons/rmsmartshape/assets/Anchor.svg.import b/addons/rmsmartshape/assets/Anchor.svg.import new file mode 100644 index 0000000..57a43f2 --- /dev/null +++ b/addons/rmsmartshape/assets/Anchor.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2h8c634u1120" +path="res://.godot/imported/Anchor.svg-1c23ca67e353b480fc5d8186bb0064b9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/Anchor.svg" +dest_files=["res://.godot/imported/Anchor.svg-1c23ca67e353b480fc5d8186bb0064b9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/CenterView.svg b/addons/rmsmartshape/assets/CenterView.svg new file mode 100644 index 0000000..dc4052f --- /dev/null +++ b/addons/rmsmartshape/assets/CenterView.svg @@ -0,0 +1 @@ + diff --git a/addons/rmsmartshape/assets/CenterView.svg.import b/addons/rmsmartshape/assets/CenterView.svg.import new file mode 100644 index 0000000..41a9077 --- /dev/null +++ b/addons/rmsmartshape/assets/CenterView.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://byx10wt6pl1vo" +path="res://.godot/imported/CenterView.svg-f5e9c7178a59ab586ba9d8fac61799bc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/CenterView.svg" +dest_files=["res://.godot/imported/CenterView.svg-f5e9c7178a59ab586ba9d8fac61799bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/InterpLinear.svg b/addons/rmsmartshape/assets/InterpLinear.svg new file mode 100644 index 0000000..241a82f --- /dev/null +++ b/addons/rmsmartshape/assets/InterpLinear.svg @@ -0,0 +1 @@ + diff --git a/addons/rmsmartshape/assets/InterpLinear.svg.import b/addons/rmsmartshape/assets/InterpLinear.svg.import new file mode 100644 index 0000000..c2dc335 --- /dev/null +++ b/addons/rmsmartshape/assets/InterpLinear.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://umo7sgb6w70s" +path="res://.godot/imported/InterpLinear.svg-c9d61ab817f2809471482a31f06cda10.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/InterpLinear.svg" +dest_files=["res://.godot/imported/InterpLinear.svg-c9d61ab817f2809471482a31f06cda10.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/SourceCodeVariable-Roman.ttf b/addons/rmsmartshape/assets/SourceCodeVariable-Roman.ttf new file mode 100644 index 0000000..42e619f Binary files /dev/null and b/addons/rmsmartshape/assets/SourceCodeVariable-Roman.ttf differ diff --git a/addons/rmsmartshape/assets/SourceCodeVariable-Roman.ttf.import b/addons/rmsmartshape/assets/SourceCodeVariable-Roman.ttf.import new file mode 100644 index 0000000..0b511de --- /dev/null +++ b/addons/rmsmartshape/assets/SourceCodeVariable-Roman.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://cjgo8gf0cp441" +path="res://.godot/imported/SourceCodeVariable-Roman.ttf-cb1e4f55679e973e90075716efcdf601.fontdata" + +[deps] + +source_file="res://addons/rmsmartshape/assets/SourceCodeVariable-Roman.ttf" +dest_files=["res://.godot/imported/SourceCodeVariable-Roman.ttf-cb1e4f55679e973e90075716efcdf601.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/rmsmartshape/assets/closed_shape.png b/addons/rmsmartshape/assets/closed_shape.png new file mode 100644 index 0000000..e2834e3 Binary files /dev/null and b/addons/rmsmartshape/assets/closed_shape.png differ diff --git a/addons/rmsmartshape/assets/closed_shape.png.import b/addons/rmsmartshape/assets/closed_shape.png.import new file mode 100644 index 0000000..dbc0499 --- /dev/null +++ b/addons/rmsmartshape/assets/closed_shape.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8r4xp6j0ro3y" +path="res://.godot/imported/closed_shape.png-c1d349f5c2caecc40ec65e52f86a8145.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/closed_shape.png" +dest_files=["res://.godot/imported/closed_shape.png-c1d349f5c2caecc40ec65e52f86a8145.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/assets/freehand.png b/addons/rmsmartshape/assets/freehand.png new file mode 100644 index 0000000..ee1125a Binary files /dev/null and b/addons/rmsmartshape/assets/freehand.png differ diff --git a/addons/rmsmartshape/assets/freehand.png.import b/addons/rmsmartshape/assets/freehand.png.import new file mode 100644 index 0000000..85093e0 --- /dev/null +++ b/addons/rmsmartshape/assets/freehand.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjbq000i2n7qd" +path="res://.godot/imported/freehand.png-de4c7fa877a42fa4776ee9cd166cdabd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/freehand.png" +dest_files=["res://.godot/imported/freehand.png-de4c7fa877a42fa4776ee9cd166cdabd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/assets/gui_theme.res b/addons/rmsmartshape/assets/gui_theme.res new file mode 100644 index 0000000..55d0270 Binary files /dev/null and b/addons/rmsmartshape/assets/gui_theme.res differ diff --git a/addons/rmsmartshape/assets/icon.png b/addons/rmsmartshape/assets/icon.png new file mode 100644 index 0000000..a9d3337 Binary files /dev/null and b/addons/rmsmartshape/assets/icon.png differ diff --git a/addons/rmsmartshape/assets/icon.png.import b/addons/rmsmartshape/assets/icon.png.import new file mode 100644 index 0000000..2f57b16 --- /dev/null +++ b/addons/rmsmartshape/assets/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://h0jpajdrldwm" +path="res://.godot/imported/icon.png-cf4ff9c0f4595c1f390bb463008250b4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon.png" +dest_files=["res://.godot/imported/icon.png-cf4ff9c0f4595c1f390bb463008250b4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/assets/icon_collision_polygon_2d.svg b/addons/rmsmartshape/assets/icon_collision_polygon_2d.svg new file mode 100644 index 0000000..9d90a66 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_collision_polygon_2d.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/addons/rmsmartshape/assets/icon_collision_polygon_2d.svg.import b/addons/rmsmartshape/assets/icon_collision_polygon_2d.svg.import new file mode 100644 index 0000000..25f570c --- /dev/null +++ b/addons/rmsmartshape/assets/icon_collision_polygon_2d.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3n1j5ybtgjit" +path="res://.godot/imported/icon_collision_polygon_2d.svg-d52c533e615c0baed724848cec175fe6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_collision_polygon_2d.svg" +dest_files=["res://.godot/imported/icon_collision_polygon_2d.svg-d52c533e615c0baed724848cec175fe6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_curve_create.svg b/addons/rmsmartshape/assets/icon_curve_create.svg new file mode 100644 index 0000000..1181111 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_curve_create.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/rmsmartshape/assets/icon_curve_create.svg.import b/addons/rmsmartshape/assets/icon_curve_create.svg.import new file mode 100644 index 0000000..eb6db2b --- /dev/null +++ b/addons/rmsmartshape/assets/icon_curve_create.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qf0hmmvirgbn" +path="res://.godot/imported/icon_curve_create.svg-24c8317cfb8ced81d4c832ca7b3310d5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_curve_create.svg" +dest_files=["res://.godot/imported/icon_curve_create.svg-24c8317cfb8ced81d4c832ca7b3310d5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_curve_delete.svg b/addons/rmsmartshape/assets/icon_curve_delete.svg new file mode 100644 index 0000000..901a08e --- /dev/null +++ b/addons/rmsmartshape/assets/icon_curve_delete.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/rmsmartshape/assets/icon_curve_delete.svg.import b/addons/rmsmartshape/assets/icon_curve_delete.svg.import new file mode 100644 index 0000000..9577201 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_curve_delete.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ce2cbowctirhc" +path="res://.godot/imported/icon_curve_delete.svg-9121c11be9a22040b2d567743d44ead0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_curve_delete.svg" +dest_files=["res://.godot/imported/icon_curve_delete.svg-9121c11be9a22040b2d567743d44ead0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_curve_edit.svg b/addons/rmsmartshape/assets/icon_curve_edit.svg new file mode 100644 index 0000000..8f09ca6 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_curve_edit.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/rmsmartshape/assets/icon_curve_edit.svg.import b/addons/rmsmartshape/assets/icon_curve_edit.svg.import new file mode 100644 index 0000000..639cb49 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_curve_edit.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crjuwuq4vp15w" +path="res://.godot/imported/icon_curve_edit.svg-bd7d5e1af7c72f4525dff1d79ad0af94.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_curve_edit.svg" +dest_files=["res://.godot/imported/icon_curve_edit.svg-bd7d5e1af7c72f4525dff1d79ad0af94.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_freehand.svg b/addons/rmsmartshape/assets/icon_editor_freehand.svg new file mode 100644 index 0000000..d354946 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_freehand.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/addons/rmsmartshape/assets/icon_editor_freehand.svg.import b/addons/rmsmartshape/assets/icon_editor_freehand.svg.import new file mode 100644 index 0000000..eda11a4 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_freehand.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://da64iqyjipw4f" +path="res://.godot/imported/icon_editor_freehand.svg-4eb5cac550480a5ffb50b9c62fba3af1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_freehand.svg" +dest_files=["res://.godot/imported/icon_editor_freehand.svg-4eb5cac550480a5ffb50b9c62fba3af1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_handle.svg b/addons/rmsmartshape/assets/icon_editor_handle.svg new file mode 100644 index 0000000..328dc04 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle.svg @@ -0,0 +1 @@ + diff --git a/addons/rmsmartshape/assets/icon_editor_handle.svg.import b/addons/rmsmartshape/assets/icon_editor_handle.svg.import new file mode 100644 index 0000000..5b6c811 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ccnyogs0qbq8b" +path="res://.godot/imported/icon_editor_handle.svg-ace1b9f89612a442855e8ad0888fdf3e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_handle.svg" +dest_files=["res://.godot/imported/icon_editor_handle.svg-ace1b9f89612a442855e8ad0888fdf3e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_handle_add.svg b/addons/rmsmartshape/assets/icon_editor_handle_add.svg new file mode 100644 index 0000000..a8bc1fd --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_add.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/addons/rmsmartshape/assets/icon_editor_handle_add.svg.import b/addons/rmsmartshape/assets/icon_editor_handle_add.svg.import new file mode 100644 index 0000000..d8998c0 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_add.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ci73r1ncmyrrw" +path="res://.godot/imported/icon_editor_handle_add.svg-7258971e5edcfe4396b7b9c27c0ba1cc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_handle_add.svg" +dest_files=["res://.godot/imported/icon_editor_handle_add.svg-7258971e5edcfe4396b7b9c27c0ba1cc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_handle_bezier.svg b/addons/rmsmartshape/assets/icon_editor_handle_bezier.svg new file mode 100644 index 0000000..b498345 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_bezier.svg @@ -0,0 +1 @@ + diff --git a/addons/rmsmartshape/assets/icon_editor_handle_bezier.svg.import b/addons/rmsmartshape/assets/icon_editor_handle_bezier.svg.import new file mode 100644 index 0000000..c6cf95a --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_bezier.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cycko7tpd5xk5" +path="res://.godot/imported/icon_editor_handle_bezier.svg-b0150f05772e8974a66bc735765e0aeb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_handle_bezier.svg" +dest_files=["res://.godot/imported/icon_editor_handle_bezier.svg-b0150f05772e8974a66bc735765e0aeb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_handle_control.svg b/addons/rmsmartshape/assets/icon_editor_handle_control.svg new file mode 100644 index 0000000..ea69f4e --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_control.svg @@ -0,0 +1 @@ + diff --git a/addons/rmsmartshape/assets/icon_editor_handle_control.svg.import b/addons/rmsmartshape/assets/icon_editor_handle_control.svg.import new file mode 100644 index 0000000..9208cb7 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_control.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgcshah05p0bm" +path="res://.godot/imported/icon_editor_handle_control.svg-8383942d01a7ee5e839992ed99e53aaf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_handle_control.svg" +dest_files=["res://.godot/imported/icon_editor_handle_control.svg-8383942d01a7ee5e839992ed99e53aaf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_handle_selected.svg b/addons/rmsmartshape/assets/icon_editor_handle_selected.svg new file mode 100644 index 0000000..ac96ca6 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_selected.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/rmsmartshape/assets/icon_editor_handle_selected.svg.import b/addons/rmsmartshape/assets/icon_editor_handle_selected.svg.import new file mode 100644 index 0000000..4b670dd --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_handle_selected.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dh3vdfbsl5o65" +path="res://.godot/imported/icon_editor_handle_selected.svg-356ef806610ee2a60b5838a989f58598.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_handle_selected.svg" +dest_files=["res://.godot/imported/icon_editor_handle_selected.svg-356ef806610ee2a60b5838a989f58598.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.25 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_position.svg b/addons/rmsmartshape/assets/icon_editor_position.svg new file mode 100644 index 0000000..7657eb5 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_position.svg @@ -0,0 +1,4 @@ + + + + diff --git a/addons/rmsmartshape/assets/icon_editor_position.svg.import b/addons/rmsmartshape/assets/icon_editor_position.svg.import new file mode 100644 index 0000000..fb6f20f --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_position.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c18veogqnx5ht" +path="res://.godot/imported/icon_editor_position.svg-1f6e1977293add549ed436510f8834c1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_position.svg" +dest_files=["res://.godot/imported/icon_editor_position.svg-1f6e1977293add549ed436510f8834c1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_editor_snap.svg b/addons/rmsmartshape/assets/icon_editor_snap.svg new file mode 100644 index 0000000..a4a1f33 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_snap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/rmsmartshape/assets/icon_editor_snap.svg.import b/addons/rmsmartshape/assets/icon_editor_snap.svg.import new file mode 100644 index 0000000..e2de758 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_editor_snap.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bojrgd0r131xh" +path="res://.godot/imported/icon_editor_snap.svg-f0374ecd8cb313d78a2acb46b093a7f1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_editor_snap.svg" +dest_files=["res://.godot/imported/icon_editor_snap.svg-f0374ecd8cb313d78a2acb46b093a7f1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/rmsmartshape/assets/icon_width_handle.svg.import b/addons/rmsmartshape/assets/icon_width_handle.svg.import new file mode 100644 index 0000000..a1e995e --- /dev/null +++ b/addons/rmsmartshape/assets/icon_width_handle.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_width_handle.svg-55d6fdc3b3c08e53dc0bed806d2f0a8f.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_width_handle.svg" +dest_files=[ "res://.import/icon_width_handle.svg-55d6fdc3b3c08e53dc0bed806d2f0a8f.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/rmsmartshape/assets/icon_width_handle0.svg.import b/addons/rmsmartshape/assets/icon_width_handle0.svg.import new file mode 100644 index 0000000..3d70ef9 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_width_handle0.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_width_handle0.svg-9a486a252970ecb72608cabecc20a9f6.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_width_handle0.svg" +dest_files=[ "res://.import/icon_width_handle0.svg-9a486a252970ecb72608cabecc20a9f6.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/addons/rmsmartshape/assets/icon_width_handle1.svg.import b/addons/rmsmartshape/assets/icon_width_handle1.svg.import new file mode 100644 index 0000000..c4edbdc --- /dev/null +++ b/addons/rmsmartshape/assets/icon_width_handle1.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_width_handle1.svg-a1da7c369cb74b89d2283c3679312805.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_width_handle1.svg" +dest_files=[ "res://.import/icon_width_handle1.svg-a1da7c369cb74b89d2283c3679312805.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/addons/rmsmartshape/assets/icon_width_handle2.svg.import b/addons/rmsmartshape/assets/icon_width_handle2.svg.import new file mode 100644 index 0000000..fd84d56 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_width_handle2.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_width_handle2.svg-b02ee2da69342d687c1fd9680123e834.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_width_handle2.svg" +dest_files=[ "res://.import/icon_width_handle2.svg-b02ee2da69342d687c1fd9680123e834.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/addons/rmsmartshape/assets/icon_width_handle3.svg.import b/addons/rmsmartshape/assets/icon_width_handle3.svg.import new file mode 100644 index 0000000..751beb8 --- /dev/null +++ b/addons/rmsmartshape/assets/icon_width_handle3.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_width_handle3.svg-3dcf9d2b715a3978009539d7a6640342.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_width_handle3.svg" +dest_files=[ "res://.import/icon_width_handle3.svg-3dcf9d2b715a3978009539d7a6640342.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/addons/rmsmartshape/assets/icon_width_handle4.svg.import b/addons/rmsmartshape/assets/icon_width_handle4.svg.import new file mode 100644 index 0000000..957a8ee --- /dev/null +++ b/addons/rmsmartshape/assets/icon_width_handle4.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_width_handle4.svg-47fd86c3a89419c03da7bc2def15fbfa.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/icon_width_handle4.svg" +dest_files=[ "res://.import/icon_width_handle4.svg-47fd86c3a89419c03da7bc2def15fbfa.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/rmsmartshape/assets/light1-1.png b/addons/rmsmartshape/assets/light1-1.png new file mode 100644 index 0000000..4523497 Binary files /dev/null and b/addons/rmsmartshape/assets/light1-1.png differ diff --git a/addons/rmsmartshape/assets/light1-1.png.import b/addons/rmsmartshape/assets/light1-1.png.import new file mode 100644 index 0000000..30774a7 --- /dev/null +++ b/addons/rmsmartshape/assets/light1-1.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b350jwt2tgchl" +path="res://.godot/imported/light1-1.png-433fe7882cc83fa28e02c4948a40f886.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/light1-1.png" +dest_files=["res://.godot/imported/light1-1.png-433fe7882cc83fa28e02c4948a40f886.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/assets/meta_shape.png b/addons/rmsmartshape/assets/meta_shape.png new file mode 100644 index 0000000..2e8259d Binary files /dev/null and b/addons/rmsmartshape/assets/meta_shape.png differ diff --git a/addons/rmsmartshape/assets/meta_shape.png.import b/addons/rmsmartshape/assets/meta_shape.png.import new file mode 100644 index 0000000..424753a --- /dev/null +++ b/addons/rmsmartshape/assets/meta_shape.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://olmio6gxe5d6" +path="res://.godot/imported/meta_shape.png-462cb0c943fa311edd3a47aeb27824d9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/meta_shape.png" +dest_files=["res://.godot/imported/meta_shape.png-462cb0c943fa311edd3a47aeb27824d9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/assets/open_shape.png b/addons/rmsmartshape/assets/open_shape.png new file mode 100644 index 0000000..8e1978d Binary files /dev/null and b/addons/rmsmartshape/assets/open_shape.png differ diff --git a/addons/rmsmartshape/assets/open_shape.png.import b/addons/rmsmartshape/assets/open_shape.png.import new file mode 100644 index 0000000..9ac56fa --- /dev/null +++ b/addons/rmsmartshape/assets/open_shape.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cpg65u2v4mthq" +path="res://.godot/imported/open_shape.png-f18727d78226e221aef881edda953bbd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/assets/open_shape.png" +dest_files=["res://.godot/imported/open_shape.png-f18727d78226e221aef881edda953bbd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/assets/shape.png.import b/addons/rmsmartshape/assets/shape.png.import new file mode 100644 index 0000000..7af62f8 --- /dev/null +++ b/addons/rmsmartshape/assets/shape.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/shape.png-00d53041e0aeffe00cc49849dfbcc65f.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/shape.png" +dest_files=[ "res://.import/shape.png-00d53041e0aeffe00cc49849dfbcc65f.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=false +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/rmsmartshape/assets/shape_anchor.png.import b/addons/rmsmartshape/assets/shape_anchor.png.import new file mode 100644 index 0000000..86c89d7 --- /dev/null +++ b/addons/rmsmartshape/assets/shape_anchor.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/shape_anchor.png-c4527dad8f7093abc2088dd0157f0c72.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/shape_anchor.png" +dest_files=[ "res://.import/shape_anchor.png-c4527dad8f7093abc2088dd0157f0c72.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/rmsmartshape/common_functions.gd b/addons/rmsmartshape/common_functions.gd new file mode 100644 index 0000000..3fa40af --- /dev/null +++ b/addons/rmsmartshape/common_functions.gd @@ -0,0 +1,33 @@ +@tool +extends Node +class_name SS2D_Common_Functions + + +static func sort_z(a, b) -> bool: + if a.z_index < b.z_index: + return true + return false + + +static func sort_int_ascending(a: int, b: int) -> bool: + if a < b: + return true + return false + + +static func sort_int_descending(a: int, b: int) -> bool: + if a < b: + return false + return true + + +static func to_vector3(vector: Vector2) -> Vector3: + return Vector3(vector.x, vector.y, 0) + + +static func merge_arrays(arrays: Array) -> Array: + var new_array := [] + for array: Array in arrays: + for v: Variant in array: + new_array.push_back(v) + return new_array diff --git a/addons/rmsmartshape/documentation/.gdignore b/addons/rmsmartshape/documentation/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/addons/rmsmartshape/documentation/Controls.md b/addons/rmsmartshape/documentation/Controls.md new file mode 100644 index 0000000..424b2d8 --- /dev/null +++ b/addons/rmsmartshape/documentation/Controls.md @@ -0,0 +1,47 @@ +# SmartShape2D - Controls and Hotkeys + + + +## Controls - Point Create + +- Add Point + - Left Click Anywhere in the viewport + +- Leave Point Create Mode + - ESCAPE + +## Controls - Point Edit + +- Add Point + - Either: + - Hold ALT and Left Click Anywhere in the viewport + - Click on an edge between two points + +- Grab closest point + - Hold CTRL + +- Cycle through texture indices of a point + - Mouseover a point and MOUSEWHEEL up or down to increment / decrement the texture index + +- Flip texture + - Mouseover a point and press SPACE + +- Change texture width property + - Mouseover a point, hold SHIFT, then MOUSEWHEEL up or down to increment / decrement the texture width + +- Add Bezier curve + - Mouseover a point, hold SHIFT, then click and drag to create control points on the point + +- Create New Shape + - Hold SHFT + ALT and click + - The location of the click will be the the first point of a newly created Shape Node + +### Overlap + +When multiple points and edges overlap, it can be ambiguous what clicking will do. +SmartShape adheres the following rules: +- If a control point overlaps a vertex, the control point takes priority +- If a control point or vertex overlaps an edge: + - Clicking will move the control point or vert + - Clicking while holding ALT will create new point on the edge + diff --git a/addons/rmsmartshape/documentation/FAQ.md b/addons/rmsmartshape/documentation/FAQ.md new file mode 100644 index 0000000..ca83607 --- /dev/null +++ b/addons/rmsmartshape/documentation/FAQ.md @@ -0,0 +1,47 @@ +# SmartShape2D - FAQ + + + +## Why aren't my textures repeating? + +If your textures aren't repeating and look something like this: + +![Non-Repeating-Texture-IMG](./imgs/faq-texture-repeat.png) + +The issue is most likely that you need to set the texture's import options in Godot: + +![Import-Texture-Settings](./imgs/faq-texture-repeat-import.png) + +## Why isn't my shape updaing when I change the Light Mask? + +Each shape is currently rendered by multiple-subnodes (Children Node2Ds). +Their owner isn't set, making them invisible in the editor (unless you have debug mode on). + +Unfortunately, there is no accessible signal when changing the Light Mask setting in editor. +That means changing the Light Mask setting in editor won't trigger the children nodes to change THIER Light Mask + +The Shape nodes CAN update their children's light mask, they just need to be triggered to do it manually. +If you update the shape in any way, the shape will update its children's light mask. + +When playing the game, the render Node children are regenerated, ensuring that the light mask will be set +correctly. + +The actual shape has set\_light\_mask overloaded, so changing the light\_mask in code should work without issue + +If you need to manually tell the shape to update its rendering, call the set\_as\_dirty() method + +If anyone has any insights on this issue, please feel free to open an issue on this subject +and let us know how we might be able to fix it + +## Why does changing the width look so ugly? + +Changing the width of the quads generally looks best with welding turned off. + +If welding is on, you can still change the width of the quads, but you may need to play with it a bit. +It's best that you change the width gradually in small increments instead of sharply. +Sharply changing the width will result in odd looking shapes. + +[Non-perspective rendering to a non-parallelogram is kinda tough](http://reedbeta.com/blog/quadrilateral-interpolation-part-1/) + +If anyone has any insights on this issue, please feel free to open an issue on this subject +and let us know how we might be able to fix it diff --git a/addons/rmsmartshape/documentation/Godot4.md b/addons/rmsmartshape/documentation/Godot4.md new file mode 100644 index 0000000..93423a3 --- /dev/null +++ b/addons/rmsmartshape/documentation/Godot4.md @@ -0,0 +1,40 @@ +# Using SmartShape2D with Godot 4 + +Godot 4 moved `repeat` for textures as an import option to a per-node option. On how to make textures repeat and +set normal maps see [section below.](#repeating-textures-and-normal-textures-with-canvastexture) + +By default, **shape resources are shared** when the shape is copy-pasted. Editing points will edit every copy of that shape. +To make point geometry unique, press **"Make Unique"** property button in Geometry property group in the inspector: + +![Making Shape Geometry Unique](imgs/godot4-make-points-unique.png) + +## Repeating Textures and Normal Textures with CanvasTexture + +CanvasItem, the base class of Node2D has a Texture section with `repeat`. If you aren't using a normal, you can set it here. +By default, this setting is inherited by children nodes so you could set it on a parent node (assuming all the children need +repeating textures or it's easier to disable for a few specific nodes than turn it on for most). + +![Creating CanvasTexture](imgs/canvas-item-repeat.png) + +Normal textures are no longer set in a material resource. + +To set normal textures, you can create a `CanvasTexture` resource in the inspector on any property, that allows setting a `Texture2D`. + +![Creating CanvasTexture](imgs/godot4-create-texture-res.png) + +`CanvasTexture` allows you to assign diffuse texture and normal map texture, as well as set textures to `repeat`: + +![Assigning Texture, Normal Map and Setting Repeat flag](img/../imgs/godot4-assign-normal-tex.png) + +## Converting Projects from Godot 3.x + +Scene files with shapes saved in Godot 3.x should load in Godot 4 project. However, you may encounter some issues. Here is a list of expected problems: +1. Textures are looking weird, not repeated. +2. Normal textures are not used. + +Please read the section on [how to set repeat and use normal textures in Godot 4](#repeating-textures-and-normal-textures-with-canvastexture). + +## Removed Features + +- The Godot 4 version of this addon does not support 1.x RMSmartShape2D nodes anymore. +- SS2D_Shape_Meta node was removed, since its functionality is available copy-pasted shapes by default. diff --git a/addons/rmsmartshape/documentation/Install.md b/addons/rmsmartshape/documentation/Install.md new file mode 100644 index 0000000..3a3d0ca --- /dev/null +++ b/addons/rmsmartshape/documentation/Install.md @@ -0,0 +1,20 @@ +# SmartShape2D - Install + +## Asset Library + +- After installing the plugin, you may encounter an error, this is normal. +- You need to restart Godot before using the plugin. + +## Manual Install + +- Clone the repository at https://github.com/SirRamEsq/SmartShape2D. +- Move the "addons/rmsmartshape" folder to your project's "addons" folder. +- Open your project in Godot to have the addon install. +- After installing the plugin, you may encounter an error, this is normal. +- Restart Godot. + +## Activate Plugin + +- After Installing the plugin, activate the plugin by navigating to `Project -> Project Settings... -> Plugins` + +![Activate Plugin](./imgs/PluginActivate.png) diff --git a/addons/rmsmartshape/documentation/Normals.md b/addons/rmsmartshape/documentation/Normals.md new file mode 100644 index 0000000..91c3e7b --- /dev/null +++ b/addons/rmsmartshape/documentation/Normals.md @@ -0,0 +1,60 @@ +# Normals + +## Default Normals + +Even if you assign normal textures to your Edge Material, the normals will look wrong. + +For example, consider the following image: + +![Normals Wrong](./imgs/NormalWrong.png) + +The normals in the image clearly don't line up with where the light is actually coming from. + +## Encoding Normal data in the canvas_item Vertex Shader Color Parameter + +As SmartShape renders the edges, the textures and their normal textures are also rotated. +This will result in incorrect normals. +To solve this, we can use a shader to correctly calculate the normals. + +The tricky part lies in how few ways there are to pass data to a canvasItem shader on a per-vertex basis. +See here for the full list: +https://docs.godotengine.org/en/stable/tutorials/shading/shading_reference/canvas_item_shader.html#doc-canvas-item-shader + +Fortunately, the COLOR parameter can be used for this purpose. +The COLOR ENCODING paramter of an Edge Material can be used to choose what data is encoded in the +Vertex Shader's Color Parameter. + +If we set the COLOR ENCODING value to "Normals", we get this: + +![Normals wrong with weird diffuse colors](./imgs/NormalColors.png) + +Ok, so now the normals still look wrong, but the colors look wrong too. Great. + +There is one final step before our normals will be correct. We need to decode the normal data in a shader. + + +## Writing a Shader + +You'll want to assign a new shader to the Edge Material's material property. +This shader will determine how each edge is rendered. + +Here's a sample shader to decode our normal data and set our actual color to that of our diffuse texture: + +```glsl +shader_type canvas_item; + +varying mat2 NORMAL_MATRIX; + +void vertex() { + NORMAL_MATRIX = mat2(COLOR.rg, COLOR.ba)*2.0 - mat2(vec2(1.0), vec2(1.0)); +} + +void fragment() { + NORMAL.xy = NORMAL_MATRIX*NORMAL.xy; + COLOR = texture(TEXTURE, UV); +} +``` + +After assigning this shader the the Edge Material's material property, our normals finally look right: + +![Normals Correct](./imgs/NormalCorrect.png) diff --git a/addons/rmsmartshape/documentation/Quickstart.md b/addons/rmsmartshape/documentation/Quickstart.md new file mode 100644 index 0000000..14554b0 --- /dev/null +++ b/addons/rmsmartshape/documentation/Quickstart.md @@ -0,0 +1,136 @@ +# SmartShape2D - QuickStart +--- +![Finished Image](./imgs/Inpsector-EdgeMaterialsNormalRange.png) + +If you feel like your workflow with SmartShape2D is a little bit slow, try reading [Controls and Hotkeys](./Controls.md). The hotkeys may help you work with the tool more effectively. + +## Basic understanding +SmartShapes work similarly to [tilesets](https://docs.godotengine.org/en/latest/tutorials/2d/using_tilesets.html) but are not bound to a grid. They can be used to create polygons and even organic shapes. This allows for level design akin to [Rayman Legends](https://youtu.be/WFu1utKAZ18?si=_33TaErpHSh-r732&t=916) (based on the UbiArt Framework). + +Each SmartShape is made up of multiple textures that are responsible for rendering different aspects like corners or edges: + +![Texture Breakdown](./imgs/smartshape_textures_breakdown.png) + +## Creating a Shape + + + +- First, instance a node of either: + - SS2D_Shape_Open + - **SS2D_Shape_Closed** + - **We'll use a closed shape for this Quickstart demo** +- SS2D_Shape_Base cannot be instanced directly +- SS2D_Shape_Anchor is a node that attaches to a shape +- The following Nodes are legacy nodes and are deprecated: + - RMSmartShape2D + - RMSmartShape2DAnchor + +![Nodes](./imgs/NewNode-SS2D_Nodes.png) + +## Editing the Shape + +- After creating the shape node, make sure it's selected and the toolbar appears and is in Point Edit mode + - ![Toolbar Default State](./imgs/Toolbar-PointEdit.png) +- Hold ALT and Left Click on the viewport to add points + - If this is a closed shape, the polygon will close after adding the 3rd point +- You should now have a shape consisting of a few points and lines: + +![Toolbar Default State](./imgs/ShapeClosed-Untextured.png) + +## Setting the Fill Texture of the Shape (Closed Shape only) + +- To give it some life, we'll want to edit the "Shape Material" in the Inspector +- Under "Shape Material" Expand "Fill Textures" and you'll see an empty array +- Set the Array's size to '1' +- Assign a texture to the newly created slot in the array +- After assigning the shape should now have a valid texture + - If nothing happens after setting the texture, try to force the shape to update by adjusting one of the points +- **Note that "Fill Textures" does not affect open shapes at all** +- If you want to add a normal_texture, you would add it using the "Fill Texture Normals" property + +![Fill Texture Set](./imgs/ShapeClosed-FillTextured.png) + +## Texturing the Edges + +- This where the rubber hits the road, the real meat of the tool +- Under "Shape Material" add an element to the "Edge Meta Materials" property + - Shape Material -> Edge Meta Materials +- Set the resource of the newly created element to "SS2D_Material_Edge_Metadata" + - Unfortunately, due to Godot limitations, every avaiable resource will offered to you instead of the one you want + - The options are alphabetized though, which helps in finding the resource you want +- Expand the first element of the "Edge Meta Materials" that you just set + - Shape Material -> Edge Meta Materials -> element 1 +- Set the value of the "Edge Material" property to a new resource of type "SS2D_Material_Edge" + - Shape Material -> Edge Meta Materials -> element 1 -> Edge Material +- Expand "Edge Material" that you just set +- Add an element to "Textures" and assign the texture to one that you want to use as an edge +- The shape's edges should now update using the texture you set + - If nothing happens after setting the texture, try to force the shape to update by adjusting one of the points +- If you want to add a normal_texture, you would add it using the "Texture Normals" property +- Godot should now look something like this: + +![Inspector](./imgs/Inpsector-EdgeMaterial.png) + +### Corners + +- If your shape has sharp 90-degree corners, the texture can look a bit warped in those places +- You can specify a unique texture to use for inner and outer corners for each Edge Material +- The following Edge Material properties are used for corners + - Textures Corner Inner + - Texture Normals Corner Inner + - Textures Corner Outer + - Texture Normals Corner Outer +- See how the addition of outer corner textures improves the square created earlier + +![Inspector](./imgs/Inpsector-EdgeMaterialCornerOuter.png) + +### Multiple Edge Materials in One Edge + +- You can add as many Edge Meta Materials as you want to a Shape Material, each with their own Edge Material +- For instance, you can add an additional egde with a rock texture (and its own set of corner textures) and have it render behind the grass + - To have it render behind the grass, Set the Z index of the meta material + +![Inspector](./imgs/Inpsector-EdgeMaterials2.png) + +### Normal Range + +- Each Meta material has a Normal Range +- The Normal Range indicates when a texture should be rendered + - If the normal range is 0 - 360 or 0 - 0, then any angle is considered in range and the edge will always render + - Angle "0" is Facing directly Right + - Angle "90" is Facing directly Up + - Angle "180" is Facing directly Left + - Angle "270" is Facing directly Down + +- If you wanted to, for example: + - Have rocks display on the bottom part of the shape only + - Have grass display on the sides and top of the shape only +- You could: + - Set the grass Normal Range to 0 - 180 + - Set the rock Normal Range to 181 - 359 + +![Inspector](./imgs/Inpsector-EdgeMaterialsNormalRange.png) + +### Material Overrides + +- Using Material Overrides will allow you to change how specific edges are rendered +- For Example, to prevent the left edge from rendering, we'll do the following: + - Select the edge edit button from the toolbar ![](./imgs/icon_edge.png) + - Right Click the left edge of the shape + - Press the "Material Override" Button + - Uncheck the "Render" Checkbox + +![Edge NoRender](./imgs/EdgeEdit-NoRender.png) + +- You can use material overrrides to also specify a specific material for a single edge +- For example, Checking Render and choosing the "Rock" Edge Material will cause the edge to render as rocks, even though the NormalRange configuration would otherwise have it render as grass + +### Multiple Textures + +- If more than one texture is specified for an Edge Material, you can specify which texture should be used +- Enter Point Edit mode, mouseover a point, and scroll up or down to change the texture index + +## Anchoring Nodes to the Shape + +- To anchor nodes directly to the SmartShape2D node, use SmartSahpeAnchor2D +- You can then make nodes children to the anchor diff --git a/addons/rmsmartshape/documentation/Resources.md b/addons/rmsmartshape/documentation/Resources.md new file mode 100644 index 0000000..92c5678 --- /dev/null +++ b/addons/rmsmartshape/documentation/Resources.md @@ -0,0 +1,115 @@ +# SmartShape2D - Resources + + + +## Shape Materials + +Shape materials provide all the texture and collision information needed by the SmartShape nodes. +Once a shape material is defined, it can be easily reused by any number of SmartShape2D nodes. + +- Edge Meta Materials + - An array of resources of type SS2D_Material_Edge_Metadata +- Fill Textures + - Used as the texture for the inside of the polygon for Closed Shapes + - Currently, only the first texture can be used, multiple textures may be supported at a later date +- Normal Textures + - In Godot 4, you can set normal textures with `CanvasTexture` resource. +- Fill Texture Z Index + - Each Edge Meta Material has a ZIndex indicating which edges are drawn first + - This sets the ZIndex for the fill texture + - This allows the user to draw the fill texture in front of some edges or behind others +- Fill Mesh Offset + - The Offset of the Fill Mesh + - Can be used to grow / shrink the fill mesh +- Render Offset + - Every edge in the shape will be offset by this amount +- Weld + - Whether or not to weld the last quad of an edge with the first quad of the next edge + +## Edge Meta Material + +An Edge Meta Material doesn't contain the actual textures used to render an edge like **Edge Material** does. +Instead, this resource contains a single **Edge Material** and describes how and when to render the edge. + +- Normal Range + - The angles at which an edge is allowed to be rendered +- Weld + - Whether or not to weld the quads in this edge +- Taper Sharp Corners + - Edge vertices sharper than 90° that aren't rendered as corners, will be tapered and not welded + - Will not work properly on shapes with curves +- Render + - Whether or not this edge is visible +- Z Index + - Dictates the order in which edges are drawn +- Offset + - Offset of the edge + - Can use a positive or negative value to draw the edges closer or further from the shape + +## Normal Range + +The Normal Range indicates when a texture should be rendered. +Each shape will compare the Surface Normal of an edge to the Normal Range in each Edge Meta Material. +If the edge's Normal is inside a Meta Material's Normal Range, the Meta Material's Edge Material is rendered. + +![NormalRangeVisual](./imgs/AngleExplaination.png) + +- A Normal Range is specified in Degrees + - If the normal range is 0 - 360 or 0 - 0, any angle is considered in range and the edge will always render + - Angle "0" is Facing directly Right + - Angle "90" is Facing directly Up + - Angle "180" is Facing directly Left + - Angle "270" is Facing directly Down + +## Edge Material + +The actual textures used to define an edge + +### Textures + +- The primary textures used for the edge +- At least one texture must be defined +- Example: ![Grass](./imgs/grass.png) + +### Taper Textures + +These textures will be used as the first or last quad in an edge. +They're named "Taper Textures" because the purpose is to show the edge "tapering off" +- Textures_Taper_Left is the first quad in an edge + - Example: ![Grass Taper Left](./imgs/grass-taper-left.png) +- Textures_Taper_Right is the final quad in an edge + - Example: ![Grass Taper Right](./imgs/grass-taper-right.png) + +### Corner Textures + +These textures will be used when the edge forms a sharp corner (80 degrees - 100 degrees) +These are used because corners can look warped when using only regular textures +- Texture_Corner_Inner is used when the corner forms an inner corner + - Example: ![Grass Corner Inner](./imgs/grass-corner-inner.png) +- Texture_Corner_Outer is used when the corner forms an outer angle + - Example: ![Grass Corner Outer](./imgs/grass-corner-outer.png) + +### Normal Texture and Repeat + +To use normal textures, you can create a `CanvasTexture` resource in the inspector on any property, +that allows to set a texture. There you can assign your texture and your normal texture, as well as set +those to `repeat`. + +### Repeat Textures + +See previous section. + +### Fit Mode + +Most likely, the textures you use will not *perfectly* fit the polygon. +This setting allows you to determine how SmartShape will rectify this. + +Differt options may look better or worse depending on the art-style. + +- Sqush and Stretch + - Texture will be mutated + - Either slightly squished or stretched to fit the polygon +- Crop + - Texture will not be mutated + - Texture will simply be cropped when changing from one texture to the next + diff --git a/addons/rmsmartshape/documentation/Shapes.md b/addons/rmsmartshape/documentation/Shapes.md new file mode 100644 index 0000000..70afa84 --- /dev/null +++ b/addons/rmsmartshape/documentation/Shapes.md @@ -0,0 +1,88 @@ +# SmartShape2D - Shapes + +Each shape consists of a set of points. You can directly edit either the points or the edges between the points in the viewport. + +Shapes are configured to use a [Shape Material](./Resources.md#ShapeMaterial) which determines how the shape is rendered. + +A shape can be open or closed. Each new shape starts open. To close a shape, simply add a point on top of the first one. + + +## Properties + +> 🛈 Most properties now have documentation comments. + + + +### Editor Debug + +- Will show the bounding box for each quad in the mesh of edges. +- Can be helpful to illustrate why a shape doesn't look the way you expect. + +### Flip Edges + +- Will flip the edges of the shape (invert y). + +### Render Edges + +- Whether or not the edges of the shape should be rendered. + +### Collision Size + +- Size of the collision shape. + +### Collision Offset + +- Offset of where the collision shape starts and ends. +- A **positive** value offsets the collision shape **outwards**. +- A **negative** value offsets the collision shape **inwards**. + +### Tessellation Stages + +- Number of stages in the curve tessellation process (Uses Curve2D Internally). +- First Param in Curve2D.tessellate. +- See [Curve2D Documentation](https://docs.godotengine.org/en/3.2/classes/class_curve2d.html#class-curve2d-method-tessellate). + +### Tessellation Tolerence + +- Tolerence Degrees in the curve tessellation process (Uses Curve2D Internally). +- Second Param in Curve2D.tessellate. +- See [Curve2D Documentation](https://docs.godotengine.org/en/3.2/classes/class_curve2d.html#class-curve2d-method-tessellate). + +### Collision Generation Method + +- Controls which method should be used to generate the collision shape. +- See also in-engine documentation. + +### Collision Update Mode + +- Controls when to update collisions. +- See also in-engine documentation. + +### Curve Bake Interval + +- Bake interval value for Curve2D. +- See [Curve2D Documentation](https://docs.godotengine.org/en/3.2/classes/class_curve2d.html#class-curve2d-property-bake-interval). + +### Collision Polygon Node Path + +- The path to the CollisionShape that the SmartShape will use for collision. +- Is Autoset when pressing the generate collision button. + +### Shape Material + +- The material that this shape will use to render itself. +- For backwards compatibility `fill_texture_z_index` defaults to `-10`. Set this to `0` and enable `fill_texture_show_behind_parent` in order to preserve Godot's normal z-sorting when layering with other nodes. + +### Points + +- **There is no need to edit this property by hand, but you can if you'd like.** +- Contains all of the points and meta-data for the points contained in this shape. +- This data structure is updated as you manipulate the shape. + +### Material Overrides + +- **There is no need to edit this property by hand, but you can if you'd like.** +- When an edge is given a "Material Override" the data for that edge is stored here. +- This data structure is updated as you manipulate the shape. +![EdgeData Popup](./imgs/EdgeEdit-MaterialOverride.png) + diff --git a/addons/rmsmartshape/documentation/Toolbar.md b/addons/rmsmartshape/documentation/Toolbar.md new file mode 100644 index 0000000..ffd31b8 --- /dev/null +++ b/addons/rmsmartshape/documentation/Toolbar.md @@ -0,0 +1,53 @@ +# SmartShape2D - Toolbar + +![Toolbar Default State](./imgs/Toolbar-PointEdit.png) + + + +## Create Mode + +- In this mode you can start creating a new shape. +- Left-Click anywhere to add a new point. +- Press ESCAPE to exit create mode. +- Hold down ALT and Left-Click to create a point between the two points closest to your mouse. + +## Point Mode + +- In this mode you can add, delete, and move all of the points that make up a shape +- To **Add** a new point to the shape: + - Hold down ALT and Left-Click anywhere on the viewport to add a point between the two points closest to your mouse. + - Left-Click on an edge between two points. +- To **Move** a point, Left-Click on any point and drag +- To **Delete** a point, Right-Click on any point +- To set the **Control Points** of a point (for curves), hold **Shift**, Left Click on any point and drag + - After the Control Points have been set, you can edit them individually by Left-Clicking and dragging + - You can delete control points by right clicking them + - To make an empty clone SmartShape2D, hold down ALT + SHIFT and Left-Click anywhere in the viewport. + +## Edge Mode + +- In this mode you can Move Eges and choose how specific edges are rendered +- To **Move** an Edge, Left Click and Drag the Edge +- To **Change an Edges Rendering**, right click the edge and press "**Material Override**" +![EdgeData Popup](./imgs/EdgeEdit-MaterialOverride.png) + +- This popup allows you to **change how edges are rendered** + - **Render** will toggle whether or not this edge will be drawn with Edge Materials + - **Set Material** allows you to choose a specific Edge Material to use to render this edge + +## Origin Set + +- This tool allows you to set the origin of any SmartShape +- To **Set the Origin** Left Click anywhere on the viewport + +## Generate Collision + +- If you want your shape to have collision, press this button to autogenerate the collision nodes +- The shape will be made a child of a newly created **StaticBody2D** +- A sibling node, **CollisionPolygon2D** will also be created and added to the SceneTree + - The "Collision Polygon" parameter of the Shape will be set to this sibling **CollisionPolygon2D** + +## Snapping + +When Moving / Adding points, snapping will cause the positions of the points to snap to the grid. This works the same as Godot's built-in snapping. +You can have snapping either use Global Space, or space relative to the shape's origin. diff --git a/addons/rmsmartshape/documentation/VersionHistory.md b/addons/rmsmartshape/documentation/VersionHistory.md new file mode 100644 index 0000000..e924a95 --- /dev/null +++ b/addons/rmsmartshape/documentation/VersionHistory.md @@ -0,0 +1,117 @@ +# Version History + +## 2.x +### 2.2 +January 4th 2021 +### Fix +- Fix for crash that would occur when points were aligned *just* right +- See issue 66 + + https://github.com/SirRamEsq/SmartShape2D/issues/66 +### Features +- Each Edge Material can now have a Material (Shader) +- Each Edge Material Meta can have a z-index and z-as-relative set +- See issue 64 + + https://github.com/SirRamEsq/SmartShape2D/issues/64 + +### 2.1 +December 14th 2020 +#### Significant Changes from 2.0 +- Improved Width handling +- Improved Welding +- Rendering is now achieved by having multiple child-nodes each render a piece of the shape + + Previously, all the rendering was done by the shape node + + Improves performance + + Fixes lighting bugs +- Point Creation mode reimplemented + + Mode active by default + + Can be exited by pressing ESC +- Several usability additions + + Hotkey for grabbing closest point + + Hotkey for creating new shape at point + + Width Grabber for closest point + + Preview for adding points +- Several Bug fixes and issues closed +#### New Features +- Meta Shapes Introduced +- "Fit mode" added to edge material + + Can either squash and stretch the texture or crop it +#### Minor Changes +- Changes to GUI Theme + + More in line with standard Godot +- Add windows scripts for running unit tests +- Changed default snap settings to 8x8 pixels + +### 2.0 +September 7th 2020 +#### Significant Changes from 1.0 +- Edge Textures are no longer determined by a cardinal direction (UP, DOWN, LEFT, RIGHT) + - Instead, a starting and ending normal angle is specified for each edge +- Textures are now defined per-edge instead of per-shape +#### New Features +- Taper textures + - Instead of simply ending, the user can have an edge "taper-off" +- Editing by Edges +- Material Overrides +#### Internal Changes +- Completely overhauled everything +- A rudimentary constraint system is in place + - Closed shapes will add a point when closing, then constrain the added point's position to the first point +- Points are no longer refered to by index, they are refered to by keys + - This enables points to have relationships that aren't affected when: + - Adding/Removing a point + - Changing orientation of the poly +- Many Unit and Integration tests + - Refactored original working code to better support testing +- Kept original scripts and classes from version 1.0 to ease importing + +## 1.x +### Changes in 1.3 +This update primarily fixes bugs and improves existing features to be more usable. +#### Changes +- Merged top/left/right/bottom offset into one variable. render offset +#### Fixes +- Input bugs +- Edge Flipping +- Polygon orientation bugs +- Quad Welding +- Corer quad generation and welding +- Collision variables in the RMSmartShapeMaterial working as intended + +### Changes in 1.2 +#### Tweaks +- Refactoring +- Toolbar takes less space +- Minor bug fixes + +#### New Features +- Bezier Curves! + - Hold shift on a control point to create a curve +- Corner Quads! + - Both inner and outer corner quads are now generated + - Textures can be speciied for each direction of both inner and outer quads +- Edge Moving! + - Can move an edge (two points) by pressing SHIFT in move mode and dragging the edge + +### Changes in 1.1 +- Refactoring +- Fixed Errors Occuring when Texture Arrays are size '0' but not null +- Fixed sync between texture, flip, and width indicies + - Would sometimes share a single array between the 3 vars + - Are all unique now + +- Snapping +- More informative toolbar + +### Changes in 1.0 +- Fixed many debug errors reported related to indexing beyond array sizes +- Fixed control point wrapping of RMSmartShapeAnchor2D nodes anchored to RMSmartShape2D nodes. +- Tested on newly released 3.2 Godot. + +### Changes in 0.91 +- Edges are calculated in relationship to object space instead of screen space +- Added option to allow user to let the object recalculate edges based on screen space. +- Fixed uv calculations for flipped textures. +- Fixed uv bug for edge sections less than half the size of texture width +- Added option to allow for a RMSmartShapeAnchor to mimic scale of monitored node +- Removed sections of code related to clockwise versus clockwise checks, very specifically regarding the direction of texture edges. +- Corrected normal texture bug for fill and edge rendering diff --git a/addons/rmsmartshape/documentation/imgs/AngleExplaination.png b/addons/rmsmartshape/documentation/imgs/AngleExplaination.png new file mode 100644 index 0000000..5b74564 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/AngleExplaination.png differ diff --git a/addons/rmsmartshape/documentation/imgs/EdgeEdit-MaterialOverride.png b/addons/rmsmartshape/documentation/imgs/EdgeEdit-MaterialOverride.png new file mode 100644 index 0000000..56c0070 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/EdgeEdit-MaterialOverride.png differ diff --git a/addons/rmsmartshape/documentation/imgs/EdgeEdit-NoRender.png b/addons/rmsmartshape/documentation/imgs/EdgeEdit-NoRender.png new file mode 100644 index 0000000..789ac59 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/EdgeEdit-NoRender.png differ diff --git a/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterial.png b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterial.png new file mode 100644 index 0000000..e031a52 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterial.png differ diff --git a/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterialCornerOuter.png b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterialCornerOuter.png new file mode 100644 index 0000000..42bb12b Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterialCornerOuter.png differ diff --git a/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterials2.png b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterials2.png new file mode 100644 index 0000000..f03a474 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterials2.png differ diff --git a/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterialsNormalRange.png b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterialsNormalRange.png new file mode 100644 index 0000000..cfc3a68 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/Inpsector-EdgeMaterialsNormalRange.png differ diff --git a/addons/rmsmartshape/documentation/imgs/NewNode-SS2D_Nodes.png b/addons/rmsmartshape/documentation/imgs/NewNode-SS2D_Nodes.png new file mode 100644 index 0000000..df063e4 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/NewNode-SS2D_Nodes.png differ diff --git a/addons/rmsmartshape/documentation/imgs/NormalColors.png b/addons/rmsmartshape/documentation/imgs/NormalColors.png new file mode 100644 index 0000000..904f83a Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/NormalColors.png differ diff --git a/addons/rmsmartshape/documentation/imgs/NormalCorrect.png b/addons/rmsmartshape/documentation/imgs/NormalCorrect.png new file mode 100644 index 0000000..2c899e2 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/NormalCorrect.png differ diff --git a/addons/rmsmartshape/documentation/imgs/NormalWrong.png b/addons/rmsmartshape/documentation/imgs/NormalWrong.png new file mode 100644 index 0000000..79c188e Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/NormalWrong.png differ diff --git a/addons/rmsmartshape/documentation/imgs/PluginActivate.png b/addons/rmsmartshape/documentation/imgs/PluginActivate.png new file mode 100644 index 0000000..1a96705 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/PluginActivate.png differ diff --git a/addons/rmsmartshape/documentation/imgs/ShapeClosed-FillTextured.png b/addons/rmsmartshape/documentation/imgs/ShapeClosed-FillTextured.png new file mode 100644 index 0000000..d768f8e Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/ShapeClosed-FillTextured.png differ diff --git a/addons/rmsmartshape/documentation/imgs/ShapeClosed-Untextured.png b/addons/rmsmartshape/documentation/imgs/ShapeClosed-Untextured.png new file mode 100644 index 0000000..275b25c Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/ShapeClosed-Untextured.png differ diff --git a/addons/rmsmartshape/documentation/imgs/Toolbar-PointEdit.png b/addons/rmsmartshape/documentation/imgs/Toolbar-PointEdit.png new file mode 100644 index 0000000..8bc8b56 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/Toolbar-PointEdit.png differ diff --git a/addons/rmsmartshape/documentation/imgs/canvas-item-repeat.png b/addons/rmsmartshape/documentation/imgs/canvas-item-repeat.png new file mode 100644 index 0000000..1045c52 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/canvas-item-repeat.png differ diff --git a/addons/rmsmartshape/documentation/imgs/faq-texture-repeat-import.png b/addons/rmsmartshape/documentation/imgs/faq-texture-repeat-import.png new file mode 100644 index 0000000..f5963d7 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/faq-texture-repeat-import.png differ diff --git a/addons/rmsmartshape/documentation/imgs/faq-texture-repeat.png b/addons/rmsmartshape/documentation/imgs/faq-texture-repeat.png new file mode 100644 index 0000000..1ceedbf Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/faq-texture-repeat.png differ diff --git a/addons/rmsmartshape/documentation/imgs/godot4-assign-normal-tex.png b/addons/rmsmartshape/documentation/imgs/godot4-assign-normal-tex.png new file mode 100644 index 0000000..cc64e74 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/godot4-assign-normal-tex.png differ diff --git a/addons/rmsmartshape/documentation/imgs/godot4-create-texture-res.png b/addons/rmsmartshape/documentation/imgs/godot4-create-texture-res.png new file mode 100644 index 0000000..4a0e417 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/godot4-create-texture-res.png differ diff --git a/addons/rmsmartshape/documentation/imgs/godot4-make-points-unique.png b/addons/rmsmartshape/documentation/imgs/godot4-make-points-unique.png new file mode 100644 index 0000000..9b1e49d Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/godot4-make-points-unique.png differ diff --git a/addons/rmsmartshape/documentation/imgs/grass-corner-inner.png b/addons/rmsmartshape/documentation/imgs/grass-corner-inner.png new file mode 100644 index 0000000..59c4705 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/grass-corner-inner.png differ diff --git a/addons/rmsmartshape/documentation/imgs/grass-corner-outer.png b/addons/rmsmartshape/documentation/imgs/grass-corner-outer.png new file mode 100644 index 0000000..5aa5b59 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/grass-corner-outer.png differ diff --git a/addons/rmsmartshape/documentation/imgs/grass-taper-left.png b/addons/rmsmartshape/documentation/imgs/grass-taper-left.png new file mode 100644 index 0000000..c28f026 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/grass-taper-left.png differ diff --git a/addons/rmsmartshape/documentation/imgs/grass-taper-right.png b/addons/rmsmartshape/documentation/imgs/grass-taper-right.png new file mode 100644 index 0000000..c3803fc Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/grass-taper-right.png differ diff --git a/addons/rmsmartshape/documentation/imgs/grass.png b/addons/rmsmartshape/documentation/imgs/grass.png new file mode 100644 index 0000000..caa0420 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/grass.png differ diff --git a/addons/rmsmartshape/documentation/imgs/icon_edge.png b/addons/rmsmartshape/documentation/imgs/icon_edge.png new file mode 100644 index 0000000..e2e08d6 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/icon_edge.png differ diff --git a/addons/rmsmartshape/documentation/imgs/sample.gif b/addons/rmsmartshape/documentation/imgs/sample.gif new file mode 100644 index 0000000..0e9064e Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/sample.gif differ diff --git a/addons/rmsmartshape/documentation/imgs/sample.png b/addons/rmsmartshape/documentation/imgs/sample.png new file mode 100644 index 0000000..bf47a3e Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/sample.png differ diff --git a/addons/rmsmartshape/documentation/imgs/smartshape_textures_breakdown.png b/addons/rmsmartshape/documentation/imgs/smartshape_textures_breakdown.png new file mode 100644 index 0000000..36a7886 Binary files /dev/null and b/addons/rmsmartshape/documentation/imgs/smartshape_textures_breakdown.png differ diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.gd b/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.gd new file mode 100644 index 0000000..89ef973 --- /dev/null +++ b/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.gd @@ -0,0 +1,64 @@ +@tool +extends VBoxContainer +class_name SS2D_NormalRangeEditor + +signal value_changed + +var start: float: get = _get_start, set = _set_start +var end: float: get = _get_end, set = _set_end +var zero_equals_full_circle := true + +var _progress_bar: TextureProgressBar: + get: + if _progress_bar == null: + _progress_bar = get_node_or_null("%TextureProgressBar") + return _progress_bar + + +func _ready() -> void: + _set_initial_angles() + + +func _enter_tree() -> void: + _set_initial_angles() + + +func _set_initial_angles() -> void: + _set_start(_progress_bar.radial_initial_angle) + _set_end(_progress_bar.radial_fill_degrees) + + +func _on_startSlider_value_changed(value: float) -> void: + _set_start(value) + + +func _on_endSlider_value_changed(value: float) -> void: + _set_end(value) + + +func _set_start(value: float) -> void: + var fill: float = _progress_bar.radial_fill_degrees + var init_angle: float = 360.0 - fill - value + 90.0 + _progress_bar.radial_initial_angle = _mutate_angle_deg(init_angle) + + +func _get_start() -> float: + return _progress_bar.radial_initial_angle + + +func _set_end(value: float) -> void: + _progress_bar.radial_fill_degrees = _mutate_angle_deg(value) + + +func _get_end() -> float: + return _progress_bar.radial_fill_degrees + + +func _on_SS2D_StartEditorSpinSlider_value_changed(value: float) -> void: + _set_start(value) + + +func _mutate_angle_deg(v: float) -> float: + if zero_equals_full_circle and v == 0.0: + return 360.0 + return v diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.tscn b/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.tscn new file mode 100644 index 0000000..37f4d4b --- /dev/null +++ b/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.tscn @@ -0,0 +1,32 @@ +[gd_scene load_steps=5 format=3 uid="uid://cg1vy768oxwxr"] + +[ext_resource type="Texture2D" uid="uid://dajqljvjm2d2q" path="res://addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png" id="1"] +[ext_resource type="Script" path="res://addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.gd" id="2"] +[ext_resource type="Texture2D" uid="uid://dgd4sbhsw1no3" path="res://addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png" id="3"] +[ext_resource type="Texture2D" uid="uid://cax163h05b60j" path="res://addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png" id="4"] + +[node name="NormalRangeEditor" type="VBoxContainer"] +anchors_preset = -1 +anchor_right = 0.087 +anchor_bottom = 0.027 +offset_right = 38.912 +offset_bottom = 203.8 +size_flags_horizontal = 3 +size_flags_vertical = 9 +script = ExtResource("2") + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 2 + +[node name="TextureProgressBar" type="TextureProgressBar" parent="CenterContainer"] +unique_name_in_owner = true +layout_mode = 2 +max_value = 1.0 +step = 0.0 +value = 1.0 +fill_mode = 4 +texture_under = ExtResource("4") +texture_over = ExtResource("3") +texture_progress = ExtResource("1") +radial_initial_angle = 79.3 +radial_fill_degrees = 93.8 diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditorProperty.gd b/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditorProperty.gd new file mode 100644 index 0000000..6e28eac --- /dev/null +++ b/addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditorProperty.gd @@ -0,0 +1,26 @@ +extends EditorProperty +class_name SS2D_NormalRangeEditorProperty + +var control: SS2D_NormalRangeEditor = preload( + "res://addons/rmsmartshape/editors/NormalRangeEditor/NormalRangeEditor.tscn" + ).instantiate() + + +func _init() -> void: + add_child(control) + add_focusable(control) + + +func _enter_tree() -> void: + control.connect("value_changed", self._value_changed) + _value_changed() + + +func _exit_tree() -> void: + control.disconnect("value_changed", self._value_changed) + + +func _value_changed() -> void: + var obj: SS2D_NormalRange = get_edited_object() + control.end = obj.distance + control.start = obj.begin diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png b/addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png new file mode 100644 index 0000000..7b47da6 Binary files /dev/null and b/addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png differ diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png.import b/addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png.import new file mode 100644 index 0000000..b8afe45 --- /dev/null +++ b/addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgd4sbhsw1no3" +path="res://.godot/imported/over_texture.png-8aa46a605c51c9642f0609a4a8bbd43d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/editors/NormalRangeEditor/over_texture.png" +dest_files=["res://.godot/imported/over_texture.png-8aa46a605c51c9642f0609a4a8bbd43d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png b/addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png new file mode 100644 index 0000000..1911c99 Binary files /dev/null and b/addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png differ diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png.import b/addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png.import new file mode 100644 index 0000000..6407be1 --- /dev/null +++ b/addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dajqljvjm2d2q" +path="res://.godot/imported/progress_texture.png-5c3ce00c522dd3c8b7a6f676bd7a3382.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/editors/NormalRangeEditor/progress_texture.png" +dest_files=["res://.godot/imported/progress_texture.png-5c3ce00c522dd3c8b7a6f676bd7a3382.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png b/addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png new file mode 100644 index 0000000..b838714 Binary files /dev/null and b/addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png differ diff --git a/addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png.import b/addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png.import new file mode 100644 index 0000000..9914330 --- /dev/null +++ b/addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cax163h05b60j" +path="res://.godot/imported/under_texture.png-87a243e17179ba2b162ef41ea65a8e51.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rmsmartshape/editors/NormalRangeEditor/under_texture.png" +dest_files=["res://.godot/imported/under_texture.png-87a243e17179ba2b162ef41ea65a8e51.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/rmsmartshape/editors/action_property_inspector_plugin.gd b/addons/rmsmartshape/editors/action_property_inspector_plugin.gd new file mode 100644 index 0000000..cbb97b3 --- /dev/null +++ b/addons/rmsmartshape/editors/action_property_inspector_plugin.gd @@ -0,0 +1,60 @@ +extends EditorInspectorPlugin + +## This inspector plugin will show an Execute button for action properties in +## SS2D_Shape. +## +## To add an action property export it with: +## +## @export_placeholder("ActionProperty") var ... +## +## Then, when an action is executed by user (by pressing an Execute button in inspector), +## setter will be called with non-empty string: +## +## func _action_property_setter(value: String) -> void: +## if value.size() > 0: +## ## Action is executed +## + +class ActionPropertyEditor: + extends EditorProperty + + signal action_pressed + + var button: Button + + func _init() -> void: + button = Button.new() + button.text = "Execute" + add_child(button) + button.connect("pressed", func() -> void: emit_signal("action_pressed")) + + func _ready() -> void: + button.icon = get_theme_icon("TextEditorPlay", "EditorIcons") + + +func _can_handle(object: Object) -> bool: + if object is SS2D_Shape: + return true + return false + + +func _parse_property( + object: Object, + _type: Variant.Type, + name: String, + _hint_type: PropertyHint, + hint_string: String, + _usage_flags, + _wide: bool +) -> bool: + if hint_string == "ActionProperty": + var prop_editor := ActionPropertyEditor.new() + add_property_editor(name, prop_editor) + prop_editor.connect("action_pressed", self._on_action_pressed.bind(object, name)) + return true + return false + + +func _on_action_pressed(object: Object, prop_name: String) -> void: + prints("Action executed:", prop_name.capitalize()) + object.set(prop_name, "executed") diff --git a/addons/rmsmartshape/editors/normal_range_inspector_plugin.gd b/addons/rmsmartshape/editors/normal_range_inspector_plugin.gd new file mode 100644 index 0000000..8678296 --- /dev/null +++ b/addons/rmsmartshape/editors/normal_range_inspector_plugin.gd @@ -0,0 +1,45 @@ +extends EditorInspectorPlugin + +var control: SS2D_NormalRangeEditorProperty = null + + +func _can_handle(object: Object) -> bool: + #if object is SS2D_NormalRange: + # return true + if object is SS2D_NormalRange: + #Connect + if not object.is_connected("changed", self._changed): + object.connect("changed", self._changed.bind(object)) + return true + else: + #Disconnect + if control != null: + control = null + + if object.has_signal("changed"): + if object.is_connected("changed", self._changed): + object.disconnect("changed", self._changed) + pass + + return false + + +func _changed(_object: Object) -> void: + control._value_changed() + + +func _parse_property( + _object: Object, + _type: Variant.Type, + name: String, + _hint_type: PropertyHint, + _hint_string: String, + _usage_flags: int, + _wide: bool +) -> bool: + if name == "edgeRendering": + control = SS2D_NormalRangeEditorProperty.new() + add_property_editor(" ", control) + return true + return false + diff --git a/addons/rmsmartshape/lib/tesselation_vertex_mapping.gd b/addons/rmsmartshape/lib/tesselation_vertex_mapping.gd new file mode 100644 index 0000000..8336b02 --- /dev/null +++ b/addons/rmsmartshape/lib/tesselation_vertex_mapping.gd @@ -0,0 +1,35 @@ +extends RefCounted +class_name SS2D_TesselationVertexMapping + +## Provides mappings from tesselated point indices to their corresponding vertex indices and vice-versa. + +var _t_point_idx_to_point_idx := PackedInt32Array() +var _point_idx_to_t_points_idx: Array[PackedInt32Array] = [] + + +## Rebuild the mapping using the given tesselated points and vertices. +func build(tesselated_points: PackedVector2Array, vertices: PackedVector2Array) -> void: + _t_point_idx_to_point_idx.clear() + _point_idx_to_t_points_idx.clear() + + var point_idx := -1 + + for t_point_idx in tesselated_points.size(): + var next_point_idx := SS2D_PluginFunctionality.get_next_point_index_wrap_around(point_idx, vertices) + + if tesselated_points[t_point_idx] == vertices[next_point_idx]: + point_idx = next_point_idx + _point_idx_to_t_points_idx.push_back(PackedInt32Array()) + + _t_point_idx_to_point_idx.push_back(point_idx) + _point_idx_to_t_points_idx[point_idx].push_back(t_point_idx) + + +## Returns the vertex index corresponding to the given tesselated point index +func tess_to_vertex_index(tesselated_idx: int) -> int: + return _t_point_idx_to_point_idx[tesselated_idx] + + +## Returns a list of tesselated point indices corresponding to the given vertex index +func vertex_to_tess_indices(vertex_idx: int) -> PackedInt32Array: + return _point_idx_to_t_points_idx[vertex_idx] diff --git a/addons/rmsmartshape/lib/tuple.gd b/addons/rmsmartshape/lib/tuple.gd new file mode 100644 index 0000000..aea5e3c --- /dev/null +++ b/addons/rmsmartshape/lib/tuple.gd @@ -0,0 +1,142 @@ +@tool +extends RefCounted +class_name SS2D_IndexTuple + +## Provides utility functions for handling and storing indices of two related points using Vector2i. +## +## Index tuples are considered equal if their elements are equal, regardless of their order: +## T(X, Y) <=> T(Y, X). +## +## For effectively working with containers, helper functions for arrays and dictionaries are +## provided that implement the above behavior. + +## Returns the second tuple element that does not equal the given value. +## Returns -1 if neither element matches. +static func get_other_value(t: Vector2i, value: int) -> int: + if t.x == value: + return t.y + elif t.y == value: + return t.x + return -1 + + +## Returns whether two tuples are equal. Two tuples are considered equal when both contain the same values regardless of order. +static func are_equal(t1: Vector2i, t2: Vector2i) -> bool: + return t1 == t2 or t1 == flip_elements(t2) + + +## Returns true when the tuple contains the given value. +static func has(t: Vector2i, value: int) -> bool: + return t.x == value or t.y == value + + +## Searches for an equal tuple in the given array and returns the index or -1 if not found. +## Incorporates the equality behavior. +static func array_find(tuple_array: Array[Vector2i], t: Vector2i) -> int: + for i in tuple_array.size(): + if are_equal(tuple_array[i], t): + return i + return -1 + + +## Returns whether the given tuple exists in the given array. +## Incorporates the equality behavior. +static func array_has(tuple_array: Array[Vector2i], t: Vector2i) -> bool: + return array_find(tuple_array, t) != -1 + + +## Returns a list indices to tuples that contain the given value. +static func array_find_partial(tuple_array: Array[Vector2i], value: int) -> PackedInt32Array: + var out := PackedInt32Array() + + for i in tuple_array.size(): + if tuple_array[i].x == value or tuple_array[i].y == value: + out.push_back(i) + + return out + + +## Transform the tuple into a normalized representation (elements in ascending order). +## Same as sort_ascending() at the moment. +## Useful in more optimized use-cases where certain assumptions can be made if all tuples share a +## normalized representation. +static func normalize_tuple(tuple: Vector2i) -> Vector2i: + return sort_ascending(tuple) + + +## Returns a tuple with elements in ascending order. +static func sort_ascending(tuple: Vector2i) -> Vector2i: + if tuple.x <= tuple.y: + return tuple + return flip_elements(tuple) + + + +## Returns a tuple with x and y components switched. +static func flip_elements(tuple: Vector2i) -> Vector2i: + return Vector2i(tuple.y, tuple.x) + + +## Validates the keys of a dictionary to be correct tuple values and converts all Arrays to +## corresponding Vector2i values. +## Optionally also validates that values are of the given type. +## Exists mostly for backwards compatibility to allow a seamless transition from Array to Vector2i tuples. +static func dict_validate(dict: Dictionary, value_type: Variant = null) -> void: + # TODO: Maybe don't use asserts but push_warning and return true if successful + for key: Variant in dict.keys(): + var value: Variant = dict[key] + + if value_type != null: + assert(is_instance_of(value, value_type), "Incorrect value type in dictionary: " + var_to_str(value)) + + if key is Array or key is PackedInt32Array or key is PackedInt64Array: + var converted := Vector2i(int(key[0]), int(key[1])) + dict.erase(key) + dict[converted] = value + else: + assert(key is Vector2i, "Invalid tuple representation: %s. Should be Vector2i." % var_to_str(key)) + + +## Get the value in a dictionary with the given tuple as key or a default value if it does not exist. +## Incorporates the equality behavior. +static func dict_get(dict: Dictionary, tuple: Vector2i, default: Variant = null) -> Variant: + if dict.has(tuple): + return dict[tuple] + return dict.get(flip_elements(tuple), default) + + +static func dict_has(dict: Dictionary, tuple: Vector2i) -> bool: + return dict.has(tuple) or dict.has(flip_elements(tuple)) + + +static func dict_set(dict: Dictionary, tuple: Vector2i, value: Variant) -> void: + dict[dict_get_key(dict, tuple)] = value + + +## Removes the given entry from the dictionary. Returns true if a corresponding key existed, otherwise false. +static func dict_erase(dict: Dictionary, tuple: Vector2i) -> bool: + return dict.erase(dict_get_key(dict, tuple)) + + +## Checks if there is an existing key for the given tuple or its flipped variant and returns it. +## If a key does not exist, returns the tuple as it is. +## Usually this function does not need to be invoked manually, as helpers for dictionary and array access exist. +static func dict_get_key(dict: Dictionary, tuple: Vector2i) -> Vector2i: + if not dict.has(tuple): + var flipped := flip_elements(tuple) + + if dict.has(flipped): + return flipped + + return tuple + + +## Returns a list of all dictionary keys (tuples) that contain the given value. +static func dict_find_partial(dict: Dictionary, value: int) -> Array[Vector2i]: + var out: Array[Vector2i] = [] + + for t: Vector2i in dict.keys(): + if t.x == value or t.y == value: + out.push_back(t) + + return out diff --git a/addons/rmsmartshape/materials/edge_material.gd b/addons/rmsmartshape/materials/edge_material.gd new file mode 100644 index 0000000..81f5fc7 --- /dev/null +++ b/addons/rmsmartshape/materials/edge_material.gd @@ -0,0 +1,156 @@ +@tool +extends Resource +class_name SS2D_Material_Edge + +## This material represents the set of textures used for a single edge. +## +## This consists of: [br] +## - textures [br] +## - corner_textures [br] +## - taper_textures [br] + +## All variations of the main edge texture.[br] +## _textures[0] is considered the "main" texture for the EdgeMaterial.[br][br] +## [b]Note:[/b] Will be used to generate an icon representing an edge texture.[br] +@export var textures: Array[Texture2D] = [] : set = _set_textures + +# Textures for the final left and right quad of the edge when the angle is steep +@export var textures_corner_outer: Array[Texture2D] = [] : set = _set_textures_corner_outer +@export var textures_corner_inner: Array[Texture2D] = [] : set = _set_textures_corner_inner + +# Textures for the final left and right quad of the edge when the angle is shallow +# Named as such because the desired look is that the texture "tapers-off" +@export var textures_taper_left: Array[Texture2D] = [] : set = _set_textures_taper_left +@export var textures_taper_right: Array[Texture2D] = [] : set = _set_textures_taper_right + +## Textures that will be used for the sharp_corner_tapering feature +@export var textures_taper_corner_left: Array[Texture2D] = [] : set = _set_textures_taper_corner_left +@export var textures_taper_corner_right: Array[Texture2D] = [] : set = _set_textures_taper_corner_right + +## If the texture choice should be randomized instead of the choice by point setup +@export var randomize_texture: bool = false : set = _set_randomize_texture +## If corner textures should be used +@export var use_corner_texture: bool = true : set = _set_use_corner +## If taper textures should be used +@export var use_taper_texture: bool = true : set = _set_use_taper + +## Whether squishing can occur when texture doesn't fit nicely into total length. +enum FITMODE {SQUISH_AND_STRETCH, CROP} +@export var fit_mode: FITMODE = FITMODE.SQUISH_AND_STRETCH : set = _set_fit_texture + +@export var material: Material = null : set = _set_material + + +########### +# SETTERS # +########### +func _set_textures(ta: Array[Texture2D]) -> void: + textures = ta + emit_changed() + + +func _set_textures_corner_outer(a: Array[Texture2D]) -> void: + textures_corner_outer = a + emit_changed() + + +func _set_textures_corner_inner(a: Array[Texture2D]) -> void: + textures_corner_inner = a + emit_changed() + + +func _set_textures_taper_left(a: Array[Texture2D]) -> void: + textures_taper_left = a + emit_changed() + + +func _set_textures_taper_right(a: Array[Texture2D]) -> void: + textures_taper_right = a + emit_changed() + +func _set_textures_taper_corner_left(a: Array[Texture2D]) -> void: + textures_taper_corner_left = a + emit_changed() + +func _set_textures_taper_corner_right(a: Array[Texture2D]) -> void: + textures_taper_corner_right = a + emit_changed() + +func _set_randomize_texture(b: bool) -> void: + randomize_texture = b + emit_changed() + + +func _set_use_corner(b: bool) -> void: + use_corner_texture = b + emit_changed() + + +func _set_use_taper(b: bool) -> void: + use_taper_texture = b + emit_changed() + + +func _set_fit_texture(fitmode: FITMODE) -> void: + fit_mode = fitmode + emit_changed() + + +func _set_material(m: Material) -> void: + material = m + emit_changed() + + +########### +# GETTERS # +########### +func get_texture(idx: int) -> Texture2D: + return _get_element(idx, textures) + + +func get_texture_corner_inner(idx: int) -> Texture2D: + return _get_element(idx, textures_corner_inner) + + +func get_texture_corner_outer(idx: int) -> Texture2D: + return _get_element(idx, textures_corner_outer) + + +func get_texture_taper_left(idx: int) -> Texture2D: + return _get_element(idx, textures_taper_left) + + +func get_texture_taper_right(idx: int) -> Texture2D: + return _get_element(idx, textures_taper_right) + + +func get_texture_taper_corner_left(idx: int) -> Texture2D: + return _get_element(idx, textures_taper_corner_left) + + +func get_texture_taper_corner_right(idx: int) -> Texture2D: + return _get_element(idx, textures_taper_corner_right) + + +######### +# USAGE # +######### + +## Returns main texture used to visually identify this edge material +func get_icon_texture() -> Texture2D: + if not textures.is_empty(): + return textures[0] + return null + + +############ +# INTERNAL # +############ +func _get_element(idx: int, a: Array) -> Variant: + if a.is_empty(): + return null + return a[_adjust_idx(idx, a)] + + +func _adjust_idx(idx: int, a: Array) -> int: + return idx % a.size() diff --git a/addons/rmsmartshape/materials/edge_material_metadata.gd b/addons/rmsmartshape/materials/edge_material_metadata.gd new file mode 100644 index 0000000..2573188 --- /dev/null +++ b/addons/rmsmartshape/materials/edge_material_metadata.gd @@ -0,0 +1,81 @@ +@tool +extends Resource +class_name SS2D_Material_Edge_Metadata + +## Represents the metadata for an edge material. +## +## Used by Shape Material. + +@export var edge_material: SS2D_Material_Edge = null : set = set_edge_material +## What range of normals can this edge be used on. +@export var normal_range := SS2D_NormalRange.new(0, 360) : set = set_normal_range +## If edge should be welded to the edges surrounding it. +@export var weld: bool = true : set = set_weld +## Whether or not the edges should use the taper corner textures and not be welded, if they are +## too sharp to be welded without significant distortion. NOTE this will not work properly +## in curved shapes +@export var taper_sharp_corners: bool = false : set = set_taper_sharp_corners +## If this edge should be visible. +@export var render: bool = true : set = set_render +## z index for an edge. +@export var z_index: int = 0 : set = set_z_index +## z index for an edge. +@export var z_as_relative: bool = true : set = set_z_as_relative +## Distance from center. +@export_range (-1.5, 1.5, 0.1) var offset: float = 0.0 : set = set_offset + + +func _to_string() -> String: + return "%s | %s" % [str(edge_material), normal_range] + + +func set_render(b: bool) -> void: + render = b + emit_changed() + + +func set_edge_material(m: SS2D_Material_Edge) -> void: + if edge_material != null: + if edge_material.is_connected("changed", self._on_edge_changed): + edge_material.disconnect("changed", self._on_edge_changed) + edge_material = m + if edge_material != null: + edge_material.connect("changed", self._on_edge_changed) + emit_changed() + + +func set_normal_range(nr: SS2D_NormalRange) -> void: + if nr == null: + return + if normal_range.is_connected("changed", self._on_edge_changed): + normal_range.disconnect("changed", self._on_edge_changed) + normal_range = nr + normal_range.connect("changed", self._on_edge_changed) + emit_changed() + + +func set_weld(b: bool) -> void: + weld = b + emit_changed() + +func set_taper_sharp_corners(val: bool) -> void: + taper_sharp_corners = val + emit_changed() + +func set_z_index(z: int) -> void: + z_index = z + emit_changed() + + +func set_z_as_relative(b: bool) -> void: + z_as_relative = b + emit_changed() + + +func set_offset(f: float) -> void: + offset = f + emit_changed() + + +func _on_edge_changed() -> void: + emit_changed() diff --git a/addons/rmsmartshape/materials/shape_material.gd b/addons/rmsmartshape/materials/shape_material.gd new file mode 100644 index 0000000..e77955c --- /dev/null +++ b/addons/rmsmartshape/materials/shape_material.gd @@ -0,0 +1,139 @@ +@tool +extends Resource +class_name SS2D_Material_Shape + +## This material represents the set of edge materials used for a smart shape. +## +## Each edge represents a set of textures used to render an edge. + +## List of materials this shape can use. +@export var _edge_meta_materials: Array[SS2D_Material_Edge_Metadata] = [] : set = set_edge_meta_materials +@export var fill_textures: Array[Texture2D] = [] : set = set_fill_textures +@export var fill_texture_z_index: int = -10 : set = set_fill_texture_z_index +@export var fill_texture_show_behind_parent: bool = false : set = set_fill_texture_show_behind_parent + +## Scale the fill texture +@export_range(0.1, 4, 0.01, "or_greater") var fill_texture_scale: float = 1.0 : set = set_fill_texture_scale + +## Whether the fill texture should start at the global 0/0 instead of the node's 0/0 +@export var fill_texture_absolute_position: bool = false : set = set_fill_texture_absolute_position + +## Whether the fill texture should ignore the node's rotation +@export var fill_texture_absolute_rotation: bool = false : set = set_fill_texture_absolute_rotation + +## How many pixels the fill texture should be shifted in x and y direction +@export var fill_texture_offset: Vector2 = Vector2.ZERO : set = set_fill_texture_offset + +## Added rotation of the texture in degrees +@export_range(-180, 180, 0.1) var fill_texture_angle_offset: float = 0.0 : set = set_fill_texture_angle_offset + +@export var fill_mesh_offset: float = 0.0 : set = set_fill_mesh_offset +@export var fill_mesh_material: Material = null : set = set_fill_mesh_material + +## How much to offset all edges +@export_range (-1.5, 1.5, 0.1) var render_offset: float = 0.0 : set = set_render_offset + + +func set_fill_mesh_material(m: Material) -> void: + fill_mesh_material = m + emit_changed() + + +func set_fill_mesh_offset(f: float) -> void: + fill_mesh_offset = f + emit_changed() + + +func set_render_offset(f: float) -> void: + render_offset = f + emit_changed() + + +## Get all valid edge materials for this normal. +func get_edge_meta_materials(normal: Vector2) -> Array[SS2D_Material_Edge_Metadata]: + var materials: Array[SS2D_Material_Edge_Metadata] = [] + for e in _edge_meta_materials: + if e == null: + continue + if e.normal_range.is_in_range(normal): + materials.push_back(e) + return materials + + +func get_all_edge_meta_materials() -> Array[SS2D_Material_Edge_Metadata]: + return _edge_meta_materials + + +func get_all_edge_materials() -> Array[SS2D_Material_Edge]: + var materials: Array[SS2D_Material_Edge] = [] + for meta in _edge_meta_materials: + if meta.edge_material != null: + materials.push_back(meta.edge_material) + return materials + + +func add_edge_material(e: SS2D_Material_Edge_Metadata) -> void: + var new_array := _edge_meta_materials.duplicate() + new_array.push_back(e) + set_edge_meta_materials(new_array) + + +func _on_edge_material_changed() -> void: + emit_changed() + + +func set_fill_textures(a: Array[Texture2D]) -> void: + fill_textures = a + emit_changed() + + +func set_fill_texture_z_index(i: int) -> void: + fill_texture_z_index = i + emit_changed() + + +func set_fill_texture_show_behind_parent(value: bool) -> void: + fill_texture_show_behind_parent = value + emit_changed() + + +func set_edge_meta_materials(a: Array[SS2D_Material_Edge_Metadata]) -> void: + for e in _edge_meta_materials: + if e == null: + continue + if not a.has(e): + e.disconnect("changed", self._on_edge_material_changed) + + for e in a: + if e == null: + continue + if not e.is_connected("changed", self._on_edge_material_changed): + e.connect("changed", self._on_edge_material_changed) + + _edge_meta_materials = a + emit_changed() + + +func set_fill_texture_offset(value: Vector2) -> void: + fill_texture_offset = value + emit_changed() + + +func set_fill_texture_scale(value:float) -> void: + fill_texture_scale = value + emit_changed() + + +func set_fill_texture_absolute_rotation(value: bool) -> void: + fill_texture_absolute_rotation = value + emit_changed() + + +func set_fill_texture_angle_offset(value: float) -> void: + fill_texture_angle_offset = value + emit_changed() + + +func set_fill_texture_absolute_position(value: bool) -> void: + fill_texture_absolute_position = value + emit_changed() \ No newline at end of file diff --git a/addons/rmsmartshape/normal_range.gd b/addons/rmsmartshape/normal_range.gd new file mode 100644 index 0000000..195d2f3 --- /dev/null +++ b/addons/rmsmartshape/normal_range.gd @@ -0,0 +1,99 @@ +@tool +extends Resource +class_name SS2D_NormalRange + +## This class will determine if the normal of a vector falls within the specifed angle ranges. +## +## - if begin and end are equal, any angle is considered to be within range [br] +## - 360.0 and 0.0 degrees are considered equivilent [br] + +@export_range (0, 360, 1) var begin: float = 0.0 : set = set_begin +@export_range (0, 360, 1) var distance: float = 0.0 : set = set_distance + +# This is a hack to support the custom editor, needed a property +# to exist to lock the TextureProgressBar to. Makes it flow better +# in the Inspector. +@export var edgeRendering: Vector2 + + +func set_distance(f: float) -> void: + distance = f + emit_changed() + + +func set_begin(f: float) -> void: + begin = f + emit_changed() + + +func _to_string() -> String: + return "NormalRange: %s - %s" % [begin, begin + distance] + + +static func get_angle_from_vector(vec: Vector2) -> float: + var normal: Vector2 = vec.normalized() + # With respect to the X-axis + # This is how Vector2.angle() is calculated, best to keep it consistent + var comparison_vector := Vector2(1, 0) + + var ab: Vector2 = normal + var bc: Vector2 = comparison_vector + var dot_prod: float = ab.dot(bc) + var determinant: float = (ab.x * bc.y) - (ab.y * bc.x) + var angle: float = atan2(determinant, dot_prod) + + # This angle has a range of 360 degrees + # Is between 180 and - 180 + var deg: float = rad_to_deg(angle) + + # Get range between 0.0 and 360.0 + if deg < 0: + deg = 360.0 + deg + return deg + + +# Get in range between 0.0 and 360.0. +static func _get_positive_angle_deg(degrees: float) -> float: + while degrees < 0: + degrees += 360 + return fmod(degrees, 360.0) + +# Get in range between -360.0 and 360.0 +static func _get_signed_angle_deg(degrees: float) -> float: + var new_degrees: float = degrees + while absf(new_degrees) > 360.0: + new_degrees += (360.0 * signf(degrees) * -1.0) + return new_degrees + + +# Saving a scene with this resource requires a parameter-less init method +func _init(_begin: float = 0.0, _distance: float = 0.0) -> void: + _begin = SS2D_NormalRange._get_signed_angle_deg(_begin) + _distance = SS2D_NormalRange._get_signed_angle_deg(_distance) + + begin = _begin + distance = _distance + + +func is_in_range(vec: Vector2) -> bool: + # A Distance of 0 or 360 is the entire circle + if distance == 0 or SS2D_NormalRange._get_positive_angle_deg(distance) == 360.0: + return true + + var begin_positive: float = SS2D_NormalRange._get_positive_angle_deg(begin) + var end_positive: float = SS2D_NormalRange._get_positive_angle_deg(begin + distance) + # If positive, counter clockwise direction + # If negative, clockwise direction + var direction: float = signf(distance) + var angle: float = SS2D_NormalRange.get_angle_from_vector(vec) + + # Swap begin and end if direction is negative + if direction == -1: + var t: float = begin_positive + begin_positive = end_positive + end_positive = t + + if begin_positive < end_positive: + return ((angle >= begin_positive) and (angle <= end_positive)) + else: + return ((angle >= begin_positive) or (angle <= end_positive)) diff --git a/addons/rmsmartshape/plugin.cfg b/addons/rmsmartshape/plugin.cfg new file mode 100644 index 0000000..92088d1 --- /dev/null +++ b/addons/rmsmartshape/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="SmartShape2D" +description="Tool to design nicely textured 2D polygons" +author="Ryan Lloyd, Robert Morse, Guy Unger, Serhii Snitsaruk, Marvin Ewald" +version="3.2.0" +script="plugin.gd" diff --git a/addons/rmsmartshape/plugin.gd b/addons/rmsmartshape/plugin.gd new file mode 100644 index 0000000..69208e1 --- /dev/null +++ b/addons/rmsmartshape/plugin.gd @@ -0,0 +1,1639 @@ +@tool +extends EditorPlugin + + +## Common Abbreviations +## et = editor transform (viewport's canvas transform) +## +## - Snapping using the build in functionality isn't going to happen +## - https://github.com/godotengine/godot/issues/11180 +## - https://godotengine.org/qa/18051/tool-script-in-3-0 + +# Icons +# TODO: Change to const and preload when this is resolved: +# https://github.com/godotengine/godot/issues/17483 +var ICON_HANDLE: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_handle.svg") +var ICON_HANDLE_SELECTED: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_handle_selected.svg") +var ICON_HANDLE_BEZIER: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_handle_bezier.svg") +var ICON_HANDLE_CONTROL: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_handle_control.svg") +var ICON_FREEHAND_MODE: Texture2D = load("res://addons/rmsmartshape/assets/freehand.png") +var ICON_CIRCLE_ERASE: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_snap.svg") +var ICON_ADD_HANDLE: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_handle_add.svg") +var ICON_CURVE_EDIT: Texture2D = load("res://addons/rmsmartshape/assets/icon_curve_edit.svg") +var ICON_CURVE_CREATE: Texture2D = load("res://addons/rmsmartshape/assets/icon_curve_create.svg") +var ICON_CURVE_DELETE: Texture2D = load("res://addons/rmsmartshape/assets/icon_curve_delete.svg") +var ICON_PIVOT_POINT: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_position.svg") +var ICON_CENTER_PIVOT: Texture2D = load("res://addons/rmsmartshape/assets/CenterView.svg") +var ICON_COLLISION: Texture2D = load("res://addons/rmsmartshape/assets/icon_collision_polygon_2d.svg") +var ICON_INTERP_LINEAR: Texture2D = load("res://addons/rmsmartshape/assets/InterpLinear.svg") +var ICON_SNAP: Texture2D = load("res://addons/rmsmartshape/assets/icon_editor_snap.svg") +var ICON_IMPORT_CLOSED: Texture2D = load("res://addons/rmsmartshape/assets/closed_shape.png") +var ICON_IMPORT_OPEN: Texture2D = load("res://addons/rmsmartshape/assets/open_shape.png") + +const FUNC = preload("plugin_functionality.gd") +const ActionAddCollisionNodes := preload("res://addons/rmsmartshape/actions/action_add_collision_nodes.gd") +const ActionMoveVerticies := preload("res://addons/rmsmartshape/actions/action_move_verticies.gd") +const ActionSetPivot := preload("res://addons/rmsmartshape/actions/action_set_pivot.gd") +const ActionMoveControlPoints := preload("res://addons/rmsmartshape/actions/action_move_control_points.gd") +const ActionDeleteControlPoint := preload("res://addons/rmsmartshape/actions/action_delete_control_point.gd") +const ActionDeletePoint := preload("res://addons/rmsmartshape/actions/action_delete_point.gd") +const ActionAddPoint := preload("res://addons/rmsmartshape/actions/action_add_point.gd") +const ActionSplitCurve := preload("res://addons/rmsmartshape/actions/action_split_curve.gd") +const ActionMakeShapeUnique := preload("res://addons/rmsmartshape/actions/action_make_shape_unique.gd") +const ActionCutEdge := preload("res://addons/rmsmartshape/actions/action_cut_edge.gd") +const ActionCloseShape := preload("res://addons/rmsmartshape/actions/action_close_shape.gd") +const ActionSplitShape := preload("res://addons/rmsmartshape/actions/action_split_shape.gd") + +enum MODE { EDIT_VERT, EDIT_EDGE, CUT_EDGE, SET_PIVOT, CREATE_VERT, FREEHAND } + +enum SNAP_MENU { ID_USE_GRID_SNAP, ID_SNAP_RELATIVE, ID_CONFIGURE_SNAP } +enum OPTIONS_MENU { ID_DEFER_MESH_UPDATES } + +enum ACTION_VERT { + NONE = 0, + MOVE_VERT = 1, + MOVE_CONTROL = 2, + MOVE_CONTROL_IN = 3, + MOVE_CONTROL_OUT = 4, + MOVE_WIDTH_HANDLE = 5 +} + + +# Data related to an action being taken on points +class ActionDataVert: + #Type of Action from the ACTION_VERT enum + var type: ACTION_VERT = ACTION_VERT.NONE + # The affected Verticies and their initial positions + var keys: PackedInt32Array + var starting_width: PackedFloat32Array + var starting_positions: PackedVector2Array = [] + var starting_positions_control_in: PackedVector2Array = [] + var starting_positions_control_out: PackedVector2Array = [] + + func _init( + _keys: PackedInt32Array, + positions: PackedVector2Array, + positions_in: PackedVector2Array, + positions_out: PackedVector2Array, + width: PackedFloat32Array, + t: ACTION_VERT + ) -> void: + type = t + keys = _keys + starting_positions = positions + starting_positions_control_in = positions_in + starting_positions_control_out = positions_out + starting_width = width + + func are_verts_selected() -> bool: + return keys.size() > 0 + + func _to_string() -> String: + return "%s: %s = %s" % [type, keys, starting_positions] + + func is_single_vert_selected() -> bool: + return keys.size() == 1 + + func current_point_key() -> int: + if not is_single_vert_selected(): + return -1 + return keys[0] + + func current_point_index(s: SS2D_Shape) -> int: + if not is_single_vert_selected(): + return -1 + return s.get_point_index(keys[0]) + + +# PRELOADS +var GUI_SNAP_POPUP := preload("scenes/SnapPopup.tscn") +var GUI_POINT_INFO_PANEL := preload("scenes/GUI_InfoPanel.tscn") +var GUI_EDGE_INFO_PANEL := preload("scenes/GUI_Edge_InfoPanel.tscn") +var gui_point_info_panel: SS2D_PointInfoPanel = GUI_POINT_INFO_PANEL.instantiate() +var gui_edge_info_panel: SS2D_EdgeInfoPanel = GUI_EDGE_INFO_PANEL.instantiate() +var gui_snap_settings: SS2D_SnapPopup = GUI_SNAP_POPUP.instantiate() + +const GUI_POINT_INFO_PANEL_OFFSET := Vector2(256, 130) + +# This is the shape node being edited +var shape: SS2D_Shape = null + +# Toolbar Stuff +var tb_hb: HBoxContainer = null +var tb_vert_create: Button = null +var tb_vert_edit: Button = null +var tb_edge_edit: Button = null +var tb_edge_cut: Button = null +var tb_pivot: Button = null +var tb_center_pivot: Button = null +var tb_collision: Button = null +var tb_freehand: Button = null +var tb_button_group: ButtonGroup = null + +var tb_snap: MenuButton = null +# The PopupMenu that belongs to tb_snap +var tb_snap_popup: PopupMenu = null + +var tb_options: MenuButton = null +var tb_options_popup: PopupMenu = null + +var make_unique_dialog: AcceptDialog + +# Edge Stuff +var on_edge: bool = false +var edge_point: Vector2 +var edge_data: SS2D_Edge = null + +# Width Handle Stuff +var on_width_handle: bool = false +const WIDTH_HANDLE_OFFSET: float = 60.0 +var closest_key: int +var closest_edge_keys := Vector2i(-1, -1) +var width_scaling: float + +# Vertex paint mode stuff +var last_point_position: Vector2 +var _mouse_lmb_pressed := false +var _mouse_rmb_pressed := false +var freehand_paint_size := 20.0 +var freehand_erase_size := 40.0 + +# Track our mode of operation +var current_mode: int = MODE.CREATE_VERT +var previous_mode: int = MODE.CREATE_VERT + +var current_action := ActionDataVert.new([], [], [], [], [], ACTION_VERT.NONE) +var cached_shape_global_transform: Transform2D + +# Action Move Variables +var _mouse_motion_delta_starting_pos := Vector2(0, 0) + +# Defining the viewport to get the current zoom/scale +var target_viewport: Viewport +var current_zoom_level : float = 1.0 + +# Track the property plugin +var plugin: EditorInspectorPlugin + +var is_2d_screen_active := false + +var _defer_mesh_updates := false + +####### +# GUI # +####### + + +func gui_display_snap_settings() -> void: + var pos := tb_snap.get_screen_position() + tb_snap.size + pos.x -= (gui_snap_settings.size.x + tb_snap.size.x) / 2.0 + gui_snap_settings.position = pos + gui_snap_settings.popup() + + +func _snapping_item_selected(id: int) -> void: + if id == SNAP_MENU.ID_USE_GRID_SNAP: + tb_snap_popup.set_item_checked(id, not tb_snap_popup.is_item_checked(id)) + if id == SNAP_MENU.ID_SNAP_RELATIVE: + tb_snap_popup.set_item_checked(id, not tb_snap_popup.is_item_checked(id)) + elif id == SNAP_MENU.ID_CONFIGURE_SNAP: + gui_display_snap_settings() + + +func _options_item_selected(id: int) -> void: + if id == OPTIONS_MENU.ID_DEFER_MESH_UPDATES: + tb_options_popup.set_item_checked(id, not tb_options_popup.is_item_checked(id)) + _defer_mesh_updates = tb_options_popup.is_item_checked(id) + + +func _gui_build_toolbar() -> void: + tb_hb = HBoxContainer.new() + add_control_to_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, tb_hb) + + var sep := VSeparator.new() + tb_hb.add_child(sep) + + tb_button_group = ButtonGroup.new() + + tb_vert_create = create_tool_button(ICON_CURVE_CREATE, SS2D_Strings.EN_TOOLTIP_CREATE_VERT) + tb_vert_create.connect(&"pressed", self._enter_mode.bind(MODE.CREATE_VERT)) + tb_vert_create.button_pressed = true + + tb_vert_edit = create_tool_button(ICON_CURVE_EDIT, SS2D_Strings.EN_TOOLTIP_EDIT_VERT) + tb_vert_edit.connect(&"pressed", self._enter_mode.bind(MODE.EDIT_VERT)) + + tb_edge_edit = create_tool_button(ICON_INTERP_LINEAR, SS2D_Strings.EN_TOOLTIP_EDIT_EDGE) + tb_edge_edit.connect(&"pressed", self._enter_mode.bind(MODE.EDIT_EDGE)) + + var edge_cut_icon: Texture2D = EditorInterface.get_base_control().get_theme_icon(&"ActionCut", &"EditorIcons") + tb_edge_cut = create_tool_button(edge_cut_icon, SS2D_Strings.EN_TOOLTIP_CUT_EDGE) + tb_edge_cut.connect(&"pressed", _enter_mode.bind(MODE.CUT_EDGE)) + + tb_pivot = create_tool_button(ICON_PIVOT_POINT, SS2D_Strings.EN_TOOLTIP_PIVOT) + tb_pivot.connect(&"pressed", self._enter_mode.bind(MODE.SET_PIVOT)) + + tb_center_pivot = create_tool_button(ICON_CENTER_PIVOT, SS2D_Strings.EN_TOOLTIP_CENTER_PIVOT, false) + tb_center_pivot.connect(&"pressed", self._center_pivot) + + tb_freehand = create_tool_button(ICON_FREEHAND_MODE, SS2D_Strings.EN_TOOLTIP_FREEHAND) + tb_freehand.connect(&"pressed", self._enter_mode.bind(MODE.FREEHAND)) + + tb_collision = create_tool_button(ICON_COLLISION, SS2D_Strings.EN_TOOLTIP_COLLISION, false) + tb_collision.connect(&"pressed", self._add_collision) + + tb_snap = MenuButton.new() + tb_snap.tooltip_text = SS2D_Strings.EN_TOOLTIP_SNAP + tb_snap_popup = tb_snap.get_popup() + tb_snap.icon = ICON_SNAP + tb_snap_popup.add_check_item("Use Grid Snap", SNAP_MENU.ID_USE_GRID_SNAP) + tb_snap_popup.add_check_item("Snap Relative", SNAP_MENU.ID_SNAP_RELATIVE) + tb_snap_popup.add_separator() + tb_snap_popup.add_item("Configure Snap...", SNAP_MENU.ID_CONFIGURE_SNAP) + tb_snap_popup.hide_on_checkable_item_selection = false + tb_hb.add_child(tb_snap) + tb_snap_popup.connect("id_pressed", self._snapping_item_selected) + + tb_options = MenuButton.new() + tb_options.tooltip_text = SS2D_Strings.EN_TOOLTIP_MORE_OPTIONS + tb_options.icon = EditorInterface.get_base_control().get_theme_icon("GuiTabMenuHl", "EditorIcons") + tb_options_popup = tb_options.get_popup() + tb_options_popup.add_check_item(SS2D_Strings.EN_OPTIONS_DEFER_MESH_UPDATES, OPTIONS_MENU.ID_DEFER_MESH_UPDATES) + tb_options_popup.hide_on_checkable_item_selection = false + tb_hb.add_child(tb_options) + tb_options_popup.connect("id_pressed", self._options_item_selected) + + +func create_tool_button(icon: Texture2D, tooltip: String, toggle: bool = true) -> Button: + var tb := Button.new() + tb.toggle_mode = toggle + tb.button_group = tb_button_group + tb.theme_type_variation = "FlatButton" + tb.focus_mode = Control.FocusMode.FOCUS_NONE + tb.icon = icon + tb.tooltip_text = tooltip + tb_hb.add_child(tb) + return tb + + +func _gui_update_vert_info_panel() -> void: + var idx: int = current_action.current_point_index(shape) + var key: int = current_action.current_point_key() + if not is_key_valid(key): + gui_point_info_panel.visible = false + return + gui_point_info_panel.visible = true + # Shrink panel + gui_point_info_panel.size = Vector2(1, 1) + + var properties := shape.get_point_properties(key) + gui_point_info_panel.set_idx(idx) + gui_point_info_panel.set_texture_idx(properties.texture_idx) + gui_point_info_panel.set_width(properties.width) + gui_point_info_panel.set_flip(properties.flip) + + +func _load_config() -> void: + var conf := ConfigFile.new() + conf.load(EditorInterface.get_editor_paths().get_project_settings_dir().path_join("ss2d.cfg")) + _defer_mesh_updates = conf.get_value("options", "defer_mesh_updates", false) + tb_options_popup.set_item_checked(OPTIONS_MENU.ID_DEFER_MESH_UPDATES, _defer_mesh_updates) + tb_snap_popup.set_item_checked(SNAP_MENU.ID_USE_GRID_SNAP, conf.get_value("options", "use_grid_snap", false)) + tb_snap_popup.set_item_checked(SNAP_MENU.ID_SNAP_RELATIVE, conf.get_value("options", "snap_relative", false)) + + +func _save_config() -> void: + var conf := ConfigFile.new() + conf.set_value("options", "defer_mesh_updates", _defer_mesh_updates) + conf.set_value("options", "use_grid_snap", tb_snap_popup.is_item_checked(SNAP_MENU.ID_USE_GRID_SNAP)) + conf.set_value("options", "snap_relative", tb_snap_popup.is_item_checked(SNAP_MENU.ID_SNAP_RELATIVE)) + conf.save(EditorInterface.get_editor_paths().get_project_settings_dir().path_join("ss2d.cfg")) + + +func _process(_delta: float) -> void: + if current_mode == MODE.FREEHAND: + current_zoom_level = get_canvas_scale() + + +func get_canvas_scale() -> float: + get_current_viewport() + if target_viewport: + return target_viewport.global_canvas_transform.x.x + else: + return 1.0 + + +func get_current_viewport() -> void: + if !get_tree().get_edited_scene_root(): + return + var editor_viewport: Node = get_tree().get_edited_scene_root().get_parent() + + if editor_viewport is SubViewport: + target_viewport = editor_viewport + elif editor_viewport is SubViewportContainer: + target_viewport = get_tree().get_edited_scene_root() + else: + target_viewport = editor_viewport.get_parent() + + +func _gui_update_edge_info_panel() -> void: + # Don't update if already visible + if gui_edge_info_panel.visible: + return + var indicies := Vector2i(-1, -1) + var override: SS2D_Material_Edge_Metadata = null + if on_edge: + var t: Transform2D = get_et() * shape.get_global_transform() + var offset: float = shape.get_closest_offset_straight_edge(t.affine_inverse() * edge_point) + var keys: Vector2i = _get_edge_point_keys_from_offset(offset, true) + indicies = Vector2i(shape.get_point_index(keys.x), shape.get_point_index(keys.y)) + if shape.get_point_array().has_material_override(keys): + override = shape.get_point_array().get_material_override(keys) + gui_edge_info_panel.set_indicies(indicies) + if override != null: + gui_edge_info_panel.set_material_override(true) + gui_edge_info_panel.load_values_from_meta_material(override) + else: + gui_edge_info_panel.set_material_override(false) + + # Shrink panel to minimum size + gui_edge_info_panel.size = Vector2(1, 1) + + +func _gui_update_info_panels() -> void: + if not is_2d_screen_active: + _gui_hide_info_panels() + return + match current_mode: + MODE.EDIT_VERT: + _gui_update_vert_info_panel() + gui_edge_info_panel.visible = false + MODE.EDIT_EDGE: + _gui_update_edge_info_panel() + gui_point_info_panel.visible = false + _: + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + + +func _gui_hide_info_panels() -> void: + gui_edge_info_panel.visible = false + gui_point_info_panel.visible = false + +######### +# GODOT # +######### + + +# Called when saving +# https://docs.godotengine.org/en/3.2/classes/class_editorplugin.html?highlight=switch%20scene%20tab +func _apply_changes() -> void: + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + + +func _init() -> void: + pass + + +func _ready() -> void: + # Support the undo-redo actions + _gui_build_toolbar() + _load_config() + add_child(gui_point_info_panel) + gui_point_info_panel.visible = false + add_child(gui_edge_info_panel) + gui_edge_info_panel.visible = false + gui_edge_info_panel.connect("material_override_toggled", self._on_edge_material_override_toggled) + gui_edge_info_panel.connect("render_toggled", self._on_edge_material_override_render_toggled) + gui_edge_info_panel.connect("weld_toggled", self._on_edge_material_override_weld_toggled) + gui_edge_info_panel.connect("z_index_changed", self._on_edge_material_override_z_index_changed) + gui_edge_info_panel.connect("edge_material_changed", self._on_edge_material_changed) + add_child(gui_snap_settings) + gui_snap_settings.hide() + + make_unique_dialog = AcceptDialog.new() + make_unique_dialog.title = "Make Shape Unique" + make_unique_dialog.get_label().text = "Make shape point geometry unique (not materials). Proceed?" + make_unique_dialog.get_ok_button().text = "Proceed" + make_unique_dialog.add_cancel_button("Cancel") + make_unique_dialog.theme = EditorInterface.get_base_control().theme + make_unique_dialog.connect("confirmed", self._shape_make_unique) + add_child(make_unique_dialog) + + connect("main_screen_changed", self._on_main_screen_changed) + + +func _enter_tree() -> void: + @warning_ignore("unsafe_method_access") + plugin = load("res://addons/rmsmartshape/editors/normal_range_inspector_plugin.gd").new() + if plugin != null: + add_inspector_plugin(plugin) + + @warning_ignore("unsafe_method_access") + var action_plugin: EditorInspectorPlugin = load("res://addons/rmsmartshape/editors/action_property_inspector_plugin.gd").new() + if action_plugin != null: + add_inspector_plugin(action_plugin) + + +func _exit_tree() -> void: + if (plugin != null): + remove_inspector_plugin(plugin) + + _save_config() + + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + remove_control_from_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, tb_hb) + tb_hb.queue_free() + + +func _forward_canvas_gui_input(event: InputEvent) -> bool: + if not is_shape_valid(): + return false + + # Force update if global transforma has been changed + if cached_shape_global_transform != shape.get_global_transform(): + shape.set_as_dirty() + cached_shape_global_transform = shape.get_global_transform() + + var et: Transform2D = get_et() + var grab_threshold: float = EditorInterface.get_editor_settings().get( + "editors/polygon_editor/point_grab_radius" + ) + + var key_return_value := false + if event is InputEventKey: + key_return_value = _input_handle_keyboard_event(event) + + var mb_return_value := false + if event is InputEventMouseButton: + mb_return_value = _input_handle_mouse_button_event(event, et, grab_threshold) + + var mm_return_value := false + if event is InputEventMouseMotion: + mb_return_value = _input_handle_mouse_motion_event(event, et, grab_threshold) + + var return_value := key_return_value == true or mb_return_value == true or mm_return_value == true + _gui_update_info_panels() + return return_value + + +func _handles(object: Object) -> bool: + var hideToolbar: bool = true + + update_overlays() + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + + var selection: EditorSelection = EditorInterface.get_selection() + if selection != null: + if selection.get_selected_nodes().size() == 1: + if selection.get_selected_nodes()[0] is SS2D_Shape: + hideToolbar = false + + if hideToolbar == true: + tb_hb.hide() + + if object is Resource: + return false + + return object is SS2D_Shape + + +func _edit(object: Object) -> void: + on_edge = false + deselect_verts() + if is_shape_valid(): + disconnect_shape(shape) + + shape = object + + if not is_shape_valid(): + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + shape = null + else: + connect_shape(shape) + + if shape.get_point_array().get_point_count() == 0: + _enter_mode(MODE.CREATE_VERT) + elif current_mode == MODE.CREATE_VERT: + _enter_mode(MODE.EDIT_VERT) + + update_overlays() + + +func _make_visible(visible: bool) -> void: + if visible: + tb_hb.show() + else: + tb_hb.hide() + + + +func _on_main_screen_changed(screen_name: String) -> void: + is_2d_screen_active = screen_name == "2D" + if not is_2d_screen_active: + _gui_hide_info_panels() + + +############ +# SNAPPING # +############ +func use_global_snap() -> bool: + return not tb_snap_popup.is_item_checked(SNAP_MENU.ID_SNAP_RELATIVE) + + +func use_snap() -> bool: + return tb_snap_popup.is_item_checked(SNAP_MENU.ID_USE_GRID_SNAP) + + +func get_snap_offset() -> Vector2: + return gui_snap_settings.get_snap_offset() + + +func get_snap_step() -> Vector2: + return gui_snap_settings.get_snap_step() + + +func snap(v: Vector2, force: bool = false) -> Vector2: + if not use_snap() and not force: + return v + var step: Vector2 = get_snap_step() + var offset: Vector2 = get_snap_offset() + var t := Transform2D.IDENTITY + if use_global_snap(): + t = shape.get_global_transform() + return SS2D_PluginFunctionality.snap_position(v, offset, step, t) + + +########## +# PLUGIN # +########## + +func disconnect_shape(s: SS2D_Shape) -> void: + if s.is_connected("make_unique_pressed", self._on_shape_make_unique): + s.disconnect("make_unique_pressed", self._on_shape_make_unique) + + +func connect_shape(s: SS2D_Shape) -> void: + if not s.is_connected("make_unique_pressed", self._on_shape_make_unique): + s.connect("make_unique_pressed", self._on_shape_make_unique) + + +func get_material_override_from_indicies() -> SS2D_Material_Edge_Metadata: + var keys := shape.get_point_array().get_edge_keys_for_indices(gui_edge_info_panel.indicies) + return shape.get_point_array().get_material_override(keys) + + +func _on_edge_material_override_render_toggled(enabled: bool) -> void: + var override := get_material_override_from_indicies() + if override != null: + override.render = enabled + + +func _on_edge_material_override_weld_toggled(enabled: bool) -> void: + var override := get_material_override_from_indicies() + if override != null: + override.weld = enabled + + +func _on_edge_material_override_z_index_changed(z: int) -> void: + var override := get_material_override_from_indicies() + if override != null: + override.z_index = z + + +func _on_edge_material_changed(m: SS2D_Material_Edge) -> void: + var override := get_material_override_from_indicies() + if override != null: + override.edge_material = m + + +func _on_edge_material_override_toggled(enabled: bool) -> void: + var indices := gui_edge_info_panel.indicies + + if SS2D_IndexTuple.has(indices, -1): + return + + var keys := shape.get_point_array().get_edge_keys_for_indices(indices) + + # Get the relevant Override data if any exists + var override: SS2D_Material_Edge_Metadata = shape.get_point_array().get_material_override(keys) + + if enabled: + if override == null: + override = SS2D_Material_Edge_Metadata.new() + override.edge_material = null + shape.get_point_array().set_material_override(keys, override) + + # Load override data into the info panel + gui_edge_info_panel.load_values_from_meta_material(override) + else: + if override != null: + shape.get_point_array().remove_material_override(keys) + + +func is_shape_valid() -> bool: + if shape == null: + return false + if not is_instance_valid(shape): + return false + if not shape.is_inside_tree(): + return false + return true + + +func _on_shape_make_unique(_shape: SS2D_Shape) -> void: + make_unique_dialog.popup_centered() + + +func _shape_make_unique() -> void: + perform_action(ActionMakeShapeUnique.new(shape)) + + +func get_et() -> Transform2D: + return EditorInterface.get_edited_scene_root().get_viewport().global_canvas_transform + + +func is_key_valid(key: int) -> bool: + if not is_shape_valid(): + return false + return shape.has_point(key) + + +func _enter_mode(mode: int) -> void: + if current_mode == mode: + return + + for tb: Button in [tb_vert_edit, tb_edge_edit, tb_pivot, tb_center_pivot, tb_vert_create, tb_freehand]: + tb.button_pressed = false + + previous_mode = current_mode + current_mode = mode + match mode: + MODE.CREATE_VERT: + tb_vert_create.button_pressed = true + MODE.EDIT_VERT: + tb_vert_edit.button_pressed = true + MODE.EDIT_EDGE: + tb_edge_edit.button_pressed = true + MODE.CUT_EDGE: + tb_edge_cut.button_pressed = true + MODE.SET_PIVOT: + tb_pivot.button_pressed = true + MODE.FREEHAND: + tb_freehand.button_pressed = true + _: + tb_vert_edit.button_pressed = true + + update_overlays() + + +func _add_collision() -> void: + call_deferred("_add_deferred_collision") + + +func _add_deferred_collision() -> void: + if shape and not shape.get_parent() is PhysicsBody2D: + perform_action(ActionAddCollisionNodes.new(shape)) + + +func _center_pivot() -> void: + if shape and shape.is_shape_closed(): + # Calculate centroid + var points: PackedVector2Array = shape.get_tessellated_points() + var point_count: int = points.size() + var total_area: float = 0.0 + var center: Vector2 = Vector2.ZERO + for i in range(point_count): + var pt1: Vector2 = points[i] + var pt2: Vector2 + if i == point_count - 1: + pt2 = points[0] + else: + pt2 = points[i + 1] + + var triangle_area: float = pt1.cross(pt2) + total_area += triangle_area + center += (pt1 + pt2) * triangle_area + + if total_area != 0.0: + center /= 3 * total_area + + perform_action(ActionSetPivot.new(shape, shape.to_global(center))) + +############# +# RENDERING # +############# + +func _forward_canvas_draw_over_viewport(overlay: Control) -> void: + # Something might force a draw which we had no control over, + # in this case do some updating to be sure + if not is_shape_valid() or not is_inside_tree(): + return + + match current_mode: + MODE.CREATE_VERT: + draw_mode_edit_vert(overlay) + if Input.is_key_pressed(KEY_ALT) and Input.is_key_pressed(KEY_SHIFT): + draw_new_shape_preview(overlay) + elif Input.is_key_pressed(KEY_ALT): + draw_new_point_close_preview(overlay) + else: + draw_new_point_preview(overlay) + MODE.EDIT_VERT: + draw_mode_edit_vert(overlay) + if Input.is_key_pressed(KEY_ALT): + if Input.is_key_pressed(KEY_SHIFT): + draw_new_shape_preview(overlay) + elif not on_edge: + draw_new_point_close_preview(overlay) + MODE.EDIT_EDGE: + draw_mode_edit_edge(overlay, Color.WHITE, Color.YELLOW) + MODE.CUT_EDGE: + draw_mode_cut_edge(overlay) + MODE.FREEHAND: + if not _mouse_lmb_pressed: + draw_new_point_close_preview(overlay) + draw_freehand_circle(overlay) + draw_mode_edit_vert(overlay, false) + + +func draw_freehand_circle(overlay: Control) -> void: + var mouse: Vector2 = overlay.get_local_mouse_position() + var size: float = freehand_paint_size + var color := Color.WHITE + if Input.is_key_pressed(KEY_CTRL): + color = Color.RED + size = freehand_erase_size + color.a = 0.5 + overlay.draw_arc(mouse, size * 2 * current_zoom_level, 0, TAU, 64, color, 1, true) + color.a = 0.05 + overlay.draw_circle(mouse, size * 2 * current_zoom_level, color) + + +func draw_mode_edit_edge(overlay: Control, color_normal: Color, color_highlight: Color) -> void: + var t: Transform2D = get_et() * shape.get_global_transform() + var verts: PackedVector2Array = shape.get_vertices() + + draw_shape_outline(overlay, t, verts, color_normal) + draw_vert_handles(overlay, t, verts, false) + + if current_action.type == ACTION_VERT.MOVE_VERT: + var edge_point_keys := current_action.keys + var p1: Vector2 = shape.get_point_position(edge_point_keys[0]) + var p2: Vector2 = shape.get_point_position(edge_point_keys[1]) + overlay.draw_line(t * p1, t * p2, Color.BLACK, 8.0, true) + overlay.draw_line(t * p1, t * p2, color_highlight, 4.0, true) + elif on_edge: + var offset: float = shape.get_closest_offset_straight_edge(t.affine_inverse() * edge_point) + var edge_point_keys := _get_edge_point_keys_from_offset(offset, true) + var p1: Vector2 = shape.get_point_position(edge_point_keys.x) + var p2: Vector2 = shape.get_point_position(edge_point_keys.y) + overlay.draw_line(t * p1, t * p2, Color.BLACK, 8.0, true) + overlay.draw_line(t * p1, t * p2, color_highlight, 4.0, true) + + +func draw_mode_cut_edge(overlay: Control) -> void: + draw_mode_edit_edge(overlay, Color(1.0, 0.25, 0.25, 0.8), Color(1.0, 0.75, 0.75, 1.0)) + + if on_edge: + # Draw "X" marks along the edge that is selected + var t: Transform2D = get_et() * shape.get_global_transform() + var offset: float = shape.get_closest_offset_straight_edge(t.affine_inverse() * edge_point) + var edge_point_keys := _get_edge_point_keys_from_offset(offset, true) + var from: Vector2 = t * shape.get_point_position(edge_point_keys.x) + var to: Vector2 = t * shape.get_point_position(edge_point_keys.y) + var dir: Vector2 = (to - from).normalized() + var angle: float = dir.angle() + var length: float = (to - from).length() + var num_crosses := remap(length, 0.0, 2000.0, 0.0, 10.0) + @warning_ignore("narrowing_conversion") + num_crosses = snappedi(num_crosses, 2.0) + 1 + var fraction := 1.0 / (num_crosses + 1) + for i in num_crosses: + var pos: Vector2 = from + dir * length * fraction * (i + 1) + overlay.draw_line(Vector2(8.0, 8.0).rotated(angle) + pos, + Vector2(-8.0, -8.0).rotated(angle) + pos, Color.RED, 3.0, true) + overlay.draw_line(Vector2(-8.0, 8.0).rotated(angle) + pos, + Vector2(8.0, -8.0).rotated(angle) + pos, Color.RED, 3.0, true) + + +func draw_mode_edit_vert(overlay: Control, show_vert_handles: bool = true) -> void: + var t: Transform2D = get_et() * shape.get_global_transform() + var verts: PackedVector2Array = shape.get_vertices() + var points: PackedVector2Array = shape.get_tessellated_points() + draw_shape_outline(overlay, t, points, shape.modulate) + if show_vert_handles: + draw_vert_handles(overlay, t, verts, true) + if on_edge: + overlay.draw_texture(ICON_ADD_HANDLE, edge_point - ICON_ADD_HANDLE.get_size() * 0.5) + + # Draw Highlighted Handle + if current_action.is_single_vert_selected(): + var tex: Texture2D = ICON_HANDLE_SELECTED + overlay.draw_texture( + tex, t * verts[current_action.current_point_index(shape)] - tex.get_size() * 0.5 + ) + + +func draw_shape_outline( + overlay: Control, t: Transform2D, points: PackedVector2Array, color: Color, width: float = 2.0 +) -> void: + if points.size() >= 2: + overlay.draw_polyline(t * points, Color.BLACK, width * 1.5, true) + overlay.draw_polyline(t * points, color, width, true) + + +func draw_vert_handles( + overlay: Control, t: Transform2D, verts: PackedVector2Array, control_points: bool +) -> void: + var transformed_verts := t * verts + for i in verts.size(): + # Draw Vert handles + var hp: Vector2 = transformed_verts[i] + var icon: Texture2D = ICON_HANDLE_BEZIER if (Input.is_key_pressed(KEY_SHIFT) and not current_mode == MODE.FREEHAND) else ICON_HANDLE + overlay.draw_texture(icon, hp - icon.get_size() * 0.5) + + # Draw Width handle + var offset: float = WIDTH_HANDLE_OFFSET + var width_handle_key: int = closest_key + if ( + Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) + and current_action.type == ACTION_VERT.MOVE_WIDTH_HANDLE + ): + offset *= width_scaling + width_handle_key = current_action.keys[0] + + var point_index: int = shape.get_point_index(width_handle_key) + if point_index == -1: + return + + var width_handle_normal: Vector2 = _get_vert_normal(t, verts, point_index) + var vertex_position: Vector2 = t * shape.get_point_position(width_handle_key) + var icon_position: Vector2 = vertex_position + width_handle_normal * offset + var size: Vector2 = Vector2.ONE * 10.0 + var width_handle_color := Color("f53351") + overlay.draw_line(vertex_position, icon_position, width_handle_color, 1.0) + overlay.draw_set_transform(icon_position, width_handle_normal.angle(), Vector2.ONE) + overlay.draw_rect(Rect2(-size / 2.0, size), width_handle_color, true) + overlay.draw_set_transform(Vector2.ZERO, 0, Vector2.ONE) + + # Draw Control point handles + if control_points: + for i in verts.size(): + var key: int = shape.get_point_key_at_index(i) + var hp: Vector2 = transformed_verts[i] + + # Drawing the point-out for the last point makes no sense, as there's no point ahead of it + if i < verts.size() - 1: + var pointout: Vector2 = t * (verts[i] + shape.get_point_out(key)) + if hp != pointout: + _draw_control_point_line(overlay, hp, pointout, ICON_HANDLE_CONTROL) + # Drawing the point-in for point 0 makes no sense, as there's no point behind it + if i > 0: + var pointin: Vector2 = t * (verts[i] + shape.get_point_in(key)) + if hp != pointin: + _draw_control_point_line(overlay, hp, pointin, ICON_HANDLE_CONTROL) + + +func _draw_control_point_line(c: Control, vert: Vector2, cp: Vector2, tex: Texture2D) -> void: + # Draw the line with a dark and light color to be visible on all backgrounds + var color_dark := Color(0, 0, 0, 0.3) + var color_light := Color(1, 1, 1, .5) + var width := 2.0 + var normal := (cp - vert).normalized() + c.draw_line(vert + normal * 4 + Vector2.DOWN, cp + Vector2.DOWN, color_dark, width) + c.draw_line(vert + normal * 4, cp, color_light, width) + c.draw_texture(tex, cp - tex.get_size() * 0.5) + + +func draw_new_point_preview(overlay: Control) -> void: + # Draw lines to where a new point will be added + var verts: PackedVector2Array = shape.get_vertices() + var t: Transform2D = get_et() * shape.get_global_transform() + var color := Color(1, 1, 1, .5) + var width := 2.0 + var mouse: Vector2 = overlay.get_local_mouse_position() + + if verts.size() > 0: + var a: Vector2 + if shape.is_shape_closed() and verts.size() > 1: + a = t * verts[verts.size() - 2] + overlay.draw_line(mouse, t * verts[0], color,width * .5) + else: + a = t * verts[verts.size() - 1] + overlay.draw_line(mouse, a, color, width) + + overlay.draw_texture(ICON_ADD_HANDLE, mouse - ICON_ADD_HANDLE.get_size() * 0.5) + + +func draw_new_point_close_preview(overlay: Control) -> void: + # Draw lines to where a new point will be added + var t: Transform2D = get_et() * shape.get_global_transform() + var color := Color(1, 1, 1, .5) + var width := 2.0 + + var mouse: Vector2 = overlay.get_local_mouse_position() + var a: Vector2 = t * shape.get_point_position(closest_edge_keys[0]) + var b: Vector2 = t * shape.get_point_position(closest_edge_keys[1]) + overlay.draw_line(mouse, a, color, width) + color.a = 0.1 + overlay.draw_line(mouse, b, color, width) + overlay.draw_texture(ICON_ADD_HANDLE, mouse - ICON_ADD_HANDLE.get_size() * 0.5) + + +func draw_new_shape_preview(overlay: Control) -> void: + # Draw a plus where a new shape will be added + var mouse: Vector2 = overlay.get_local_mouse_position() + overlay.draw_texture(ICON_ADD_HANDLE, mouse - ICON_ADD_HANDLE.get_size() * 0.5) + + +########## +# PLUGIN # +########## +func deselect_verts() -> void: + current_action = ActionDataVert.new([], [], [], [], [], ACTION_VERT.NONE) + + +func select_verticies(keys: PackedInt32Array, action: ACTION_VERT) -> ActionDataVert: + var from_positions := PackedVector2Array() + var from_positions_c_in := PackedVector2Array() + var from_positions_c_out := PackedVector2Array() + var from_widths := PackedFloat32Array() + for key in keys: + from_positions.push_back(shape.get_point_position(key)) + from_positions_c_in.push_back(shape.get_point_in(key)) + from_positions_c_out.push_back(shape.get_point_out(key)) + from_widths.push_back(shape.get_point_width(key)) + return ActionDataVert.new( + keys, from_positions, from_positions_c_in, from_positions_c_out, from_widths, action + ) + + +func select_vertices_to_move(keys: PackedInt32Array, _mouse_starting_pos_viewport: Vector2) -> void: + _mouse_motion_delta_starting_pos = _mouse_starting_pos_viewport + current_action = select_verticies(keys, ACTION_VERT.MOVE_VERT) + + +func select_control_points_to_move( + keys: PackedInt32Array, _mouse_starting_pos_viewport: Vector2, action: ACTION_VERT = ACTION_VERT.MOVE_CONTROL +) -> void: + current_action = select_verticies(keys, action) + _mouse_motion_delta_starting_pos = _mouse_starting_pos_viewport + + +func select_width_handle_to_move(keys: PackedInt32Array, _mouse_starting_pos_viewport: Vector2) -> void: + _mouse_motion_delta_starting_pos = _mouse_starting_pos_viewport + current_action = select_verticies(keys, ACTION_VERT.MOVE_WIDTH_HANDLE) + + +func perform_action(action: SS2D_Action) -> void: + var undo := get_undo_redo() + undo.create_action(action.get_name(), UndoRedo.MERGE_DISABLE, shape.get_point_array()) + undo.add_do_method(action, "do") + undo.add_do_method(self, "update_overlays") + undo.add_undo_method(action, "undo") + undo.add_undo_method(self, "update_overlays") + undo.commit_action() + + +######### +# INPUT # +######### +func _input_handle_right_click_press(mb_position: Vector2, grab_threshold: float) -> bool: + if not shape.can_edit: + return false + if current_mode == MODE.EDIT_VERT or current_mode == MODE.CREATE_VERT: + # Mouse over a single vertex? + if current_action.is_single_vert_selected(): + perform_action(ActionDeletePoint.new(shape, current_action.keys[0])) + deselect_verts() + return true + else: + # Mouse over a control point? + var et: Transform2D = get_et() + var points_in: Array = FUNC.get_intersecting_control_point_in( + shape, et, mb_position, grab_threshold + ) + var points_out: Array = FUNC.get_intersecting_control_point_out( + shape, et, mb_position, grab_threshold + ) + if not points_in.is_empty(): + perform_action(ActionDeleteControlPoint.new(shape, points_in[0], + ActionDeleteControlPoint.PointType.POINT_IN)) + return true + elif not points_out.is_empty(): + perform_action(ActionDeleteControlPoint.new(shape, points_out[0], + ActionDeleteControlPoint.PointType.POINT_OUT)) + return true + elif current_mode == MODE.EDIT_EDGE: + if on_edge: + gui_edge_info_panel.visible = not gui_edge_info_panel.visible + gui_edge_info_panel.position = get_window().get_mouse_position() + return true + return false + + +func _input_handle_left_click( + mb: InputEventMouseButton, + vp_m_pos: Vector2, + t: Transform2D, + et: Transform2D, + grab_threshold: float +) -> bool: + # Set Pivot? + if current_mode == MODE.SET_PIVOT: + var local_position: Vector2 = et.affine_inverse() * mb.position + if use_snap(): + local_position = snap(local_position) + perform_action(ActionSetPivot.new(shape, local_position)) + return true + + if current_mode == MODE.EDIT_VERT or current_mode == MODE.CREATE_VERT: + gui_edge_info_panel.visible = false + var can_add_point: bool = Input.is_key_pressed(KEY_ALT) or current_mode == MODE.CREATE_VERT + var is_first_selected: bool = current_action.is_single_vert_selected() and current_action.current_point_key() == shape.get_point_key_at_index(0) + + if _defer_mesh_updates: + shape.begin_update() + + # Close the shape if the first point is clicked + if can_add_point and is_first_selected and shape.can_close(): + var close_action := ActionCloseShape.new(shape) + perform_action(close_action) + if Input.is_key_pressed(KEY_SHIFT): + select_control_points_to_move([close_action.get_key()], vp_m_pos) + else: + select_vertices_to_move([close_action.get_key()], vp_m_pos) + return true + + # Any nearby control points to move? + if not Input.is_key_pressed(KEY_ALT): + if _input_move_control_points(mb, vp_m_pos, grab_threshold): + return true + + # Highlighting a vert to move or add control points to + if current_action.is_single_vert_selected(): + if on_width_handle: + select_width_handle_to_move([current_action.current_point_key()], vp_m_pos) + elif Input.is_key_pressed(KEY_SHIFT): + select_control_points_to_move([current_action.current_point_key()], vp_m_pos) + return true + else: + select_vertices_to_move([current_action.current_point_key()], vp_m_pos) + return true + + # Split the Edge? + if _input_split_edge(mb, vp_m_pos, t): + return true + + if not on_edge and can_add_point: + # Create new point + var local_position: Vector2 = t.affine_inverse() * mb.position + if use_snap(): + local_position = snap(local_position) + + var idx: int = -1 + if Input.is_key_pressed(KEY_SHIFT) and Input.is_key_pressed(KEY_ALT): + # Copy shape with a new single point + var copy: SS2D_Shape = copy_shape(shape) + copy.set_point_array(SS2D_Point_Array.new()) + _enter_mode(MODE.CREATE_VERT) + var selection := EditorInterface.get_selection() + selection.clear() + selection.add_node(copy) + shape = copy + elif Input.is_key_pressed(KEY_ALT): + # Add point between start and end points of the closest edge + idx = shape.get_point_index(closest_edge_keys[1]) + var add_point := ActionAddPoint.new(shape, local_position, idx, not _defer_mesh_updates) + perform_action(add_point) + if Input.is_key_pressed(KEY_SHIFT) and not Input.is_key_pressed(KEY_ALT): + select_control_points_to_move([add_point.get_key()], vp_m_pos) + else: + select_vertices_to_move([add_point.get_key()], vp_m_pos) + return true + elif current_mode == MODE.EDIT_EDGE: + if gui_edge_info_panel.visible: + gui_edge_info_panel.visible = false + return true + if on_edge: + # Grab Edge (2 points) + var offset: float = shape.get_closest_offset_straight_edge( + t.affine_inverse() * edge_point + ) + var edge_point_keys := _get_edge_point_keys_from_offset(offset, true) + select_vertices_to_move([edge_point_keys.x, edge_point_keys.y], vp_m_pos) + if _defer_mesh_updates: + shape.begin_update() + return true + elif current_mode == MODE.CUT_EDGE: + if not on_edge: + return true + var offset: float = shape.get_closest_offset_straight_edge(t.affine_inverse() * edge_point) + var edge_keys := _get_edge_point_keys_from_offset(offset, true) + perform_action(ActionCutEdge.new(shape, edge_keys.x, edge_keys.y)) + on_edge = false + return true + elif current_mode == MODE.FREEHAND: + return true + return false + + +func _input_handle_mouse_wheel(btn: int) -> bool: + if current_mode == MODE.FREEHAND: + if Input.is_key_pressed(KEY_CTRL) and Input.is_key_pressed(KEY_SHIFT): + var step_multiplier := 1.2 if btn == MOUSE_BUTTON_WHEEL_UP else 0.8 + freehand_erase_size = roundf(clampf(freehand_erase_size * step_multiplier, 5, 400)) + update_overlays() + return true + elif Input.is_key_pressed(KEY_SHIFT): + var step_multiplier := 1.2 if btn == MOUSE_BUTTON_WHEEL_UP else 0.8 + freehand_paint_size = roundf(clampf(freehand_paint_size * step_multiplier, 5, 400)) + update_overlays() + return true + elif current_action.is_single_vert_selected(): + if not shape.can_edit: + return false + var key: int = current_action.current_point_key() + if Input.is_key_pressed(KEY_SHIFT): + var width: float = shape.get_point_width(key) + var width_step := 0.1 + if btn == MOUSE_BUTTON_WHEEL_DOWN: + width_step *= -1 + var new_width: float = width + width_step + shape.set_point_width(key, new_width) + + else: + var texture_idx_step := 1 + if btn == MOUSE_BUTTON_WHEEL_DOWN: + texture_idx_step *= -1 + + var tex_idx: int = shape.get_point_texture_index(key) + texture_idx_step + shape.set_point_texture_index(key, tex_idx) + + update_overlays() + _gui_update_info_panels() + return true + + return false + + +func _input_handle_keyboard_event(event: InputEventKey) -> bool: + if not shape.can_edit: + return false + var kb: InputEventKey = event + if _is_valid_keyboard_scancode(kb): + if current_action.is_single_vert_selected(): + if kb.pressed and kb.keycode == KEY_SPACE: + var key: int = current_action.current_point_key() + shape.set_point_texture_flip(key, not shape.get_point_texture_flip(key)) + _gui_update_info_panels() + + if kb.pressed and kb.keycode == KEY_ESCAPE: + # Hide edge_info_panel + if gui_edge_info_panel.visible: + gui_edge_info_panel.visible = false + + if current_mode == MODE.CREATE_VERT: + _enter_mode(MODE.EDIT_VERT) + + if kb.keycode == KEY_CTRL: + if kb.pressed and not kb.echo: + on_edge = false + if closest_key != -1: + current_action = select_verticies([closest_key], ACTION_VERT.NONE) + else: + deselect_verts() + update_overlays() + + if kb.keycode == KEY_ALT: + update_overlays() + + return true + return false + + +func _is_valid_keyboard_scancode(kb: InputEventKey) -> bool: + match kb.keycode: + KEY_ESCAPE: + return true + KEY_ENTER: + return true + KEY_SPACE: + return true + KEY_SHIFT: + return true + KEY_ALT: + return true + KEY_CTRL: + return true + return false + + +func _input_handle_mouse_button_event( + event: InputEventMouseButton, et: Transform2D, grab_threshold: float +) -> bool: + if not shape.can_edit: + return false + var t: Transform2D = et * shape.get_global_transform() + var mb: InputEventMouseButton = event + var viewport_mouse_position: Vector2 = et.affine_inverse() * mb.position + var mouse_wheel_spun: bool = ( + mb.pressed + and (mb.button_index == MOUSE_BUTTON_WHEEL_DOWN or mb.button_index == MOUSE_BUTTON_WHEEL_UP) + ) + + ####################################### + # Left Mouse Button released + if not mb.pressed and mb.button_index == MOUSE_BUTTON_LEFT: + _mouse_lmb_pressed = false + var rslt: bool = false + var type: ACTION_VERT = current_action.type + var _in := type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_IN + var _out := type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_OUT + if type == ACTION_VERT.MOVE_VERT: + perform_action(ActionMoveVerticies.new(shape, current_action.keys, + current_action.starting_positions)) + rslt = true + elif _in or _out: + perform_action(ActionMoveControlPoints.new( + shape, + current_action.keys, + current_action.starting_positions_control_in, + current_action.starting_positions_control_out + )) + rslt = true + elif current_mode == MODE.FREEHAND: + if _defer_mesh_updates: + shape.end_update() + deselect_verts() + return rslt + + ####################################### + # Right Mouse Button released + if not mb.pressed and mb.button_index == MOUSE_BUTTON_RIGHT: + _mouse_rmb_pressed = false + + ######################################### + # Mouse Wheel on valid point + elif mouse_wheel_spun: + return _input_handle_mouse_wheel(mb.button_index) + + ######################################### + # Mouse left click + elif mb.pressed and mb.button_index == MOUSE_BUTTON_LEFT: + _mouse_lmb_pressed = true + return _input_handle_left_click(mb, viewport_mouse_position, t, et, grab_threshold) + + ######################################### + # Mouse right click + elif mb.pressed and mb.button_index == MOUSE_BUTTON_RIGHT: + _mouse_rmb_pressed = true + return _input_handle_right_click_press(mb.position, grab_threshold) + + return false + + +func _input_split_edge(mb: InputEventMouseButton, vp_m_pos: Vector2, t: Transform2D) -> bool: + if not on_edge: + return false + var gpoint: Vector2 = mb.position + var insertion_point: int = -1 + var mb_offset: float = shape.get_closest_offset(t.affine_inverse() * gpoint) + + insertion_point = shape.get_point_index(_get_edge_point_keys_from_offset(mb_offset)[1]) + + if insertion_point == -1: + insertion_point = shape.get_point_count() - 1 + + var split_curve := ActionSplitCurve.new(shape, insertion_point, gpoint, t, not _defer_mesh_updates) + perform_action(split_curve) + select_vertices_to_move([split_curve.get_key()], vp_m_pos) + on_edge = false + + if _defer_mesh_updates: + shape.begin_update() + + return true + + +func _input_move_control_points(mb: InputEventMouseButton, vp_m_pos: Vector2, grab_threshold: float) -> bool: + var points_in := SS2D_PluginFunctionality.get_intersecting_control_point_in( + shape, get_et(), mb.position, grab_threshold + ) + var points_out := SS2D_PluginFunctionality.get_intersecting_control_point_out( + shape, get_et(), mb.position, grab_threshold + ) + if not points_in.is_empty(): + select_control_points_to_move([points_in[0]], vp_m_pos, ACTION_VERT.MOVE_CONTROL_IN) + return true + elif not points_out.is_empty(): + select_control_points_to_move([points_out[0]], vp_m_pos, ACTION_VERT.MOVE_CONTROL_OUT) + return true + return false + + +func _get_edge_point_keys_from_offset( + offset: float, straight: bool = false, _position := Vector2(0, 0) +) -> Vector2i: + for i in range(0, shape.get_point_count() - 1, 1): + var key: int = shape.get_point_key_at_index(i) + var key_next: int = shape.get_point_key_at_index(i + 1) + var this_offset := 0.0 + var next_offset := 0.0 + if straight: + this_offset = shape.get_closest_offset_straight_edge(shape.get_point_position(key)) + next_offset = shape.get_closest_offset_straight_edge(shape.get_point_position(key_next)) + else: + this_offset = shape.get_closest_offset(shape.get_point_position(key)) + next_offset = shape.get_closest_offset(shape.get_point_position(key_next)) + + if offset >= this_offset and offset <= next_offset: + return Vector2i(key, key_next) + # for when the shape is closed and the final point has an offset of 0 + if next_offset == 0 and offset >= this_offset: + return Vector2i(key, key_next) + return Vector2i(-1, -1) + + +func _input_motion_is_on_edge(mm: InputEventMouseMotion, grab_threshold: float) -> bool: + var xform: Transform2D = get_et() * shape.get_global_transform() + if shape.get_point_count() < 2: + return false + + # Find edge + var closest_point: Vector2 + if current_mode == MODE.EDIT_EDGE or current_mode == MODE.CUT_EDGE: + closest_point = shape.get_closest_point_straight_edge( + xform.affine_inverse() * mm.position + ) + else: + closest_point = shape.get_closest_point(xform.affine_inverse() * mm.position) + edge_point = xform * closest_point + if edge_point.distance_to(mm.position) <= grab_threshold: + return true + return false + + +func _input_find_closest_edge_keys(mm: InputEventMouseMotion) -> void: + if shape.get_point_count() < 2: + return + + # Find edge + var xform: Transform2D = get_et() * shape.get_global_transform() + var closest_point: Vector2 = shape.get_closest_point_straight_edge(xform.affine_inverse() * mm.position) + var edge_p: Vector2 = xform * closest_point + var offset: float = shape.get_closest_offset_straight_edge(xform.affine_inverse() * edge_p) + closest_edge_keys = _get_edge_point_keys_from_offset(offset, true, xform.affine_inverse() * mm.position) + + +func get_mouse_over_vert_key(mm: InputEventMouseMotion, grab_threshold: float) -> int: + var xform: Transform2D = get_et() * shape.get_global_transform() + # However, if near a control point or one of its handles then we are not on the edge + for k in shape.get_all_point_keys(): + var pp: Vector2 = shape.get_point_position(k) + var p: Vector2 = xform * pp + if p.distance_to(mm.position) <= grab_threshold: + return k + return -1 + + +func get_mouse_over_width_handle(mm: InputEventMouseMotion, grab_threshold: float) -> int: + var xform: Transform2D = get_et() * shape.get_global_transform() + for k in shape.get_all_point_keys(): + var pp: Vector2 = shape.get_point_position(k) + var normal: Vector2 = _get_vert_normal( + xform, shape.get_vertices(), shape.get_point_index(k) + ) + var p: Vector2 = xform * pp + normal * WIDTH_HANDLE_OFFSET + if p.distance_to(mm.position) <= grab_threshold: + return k + return -1 + + +func _input_motion_move_control_points(delta: Vector2, _in: bool, _out: bool) -> bool: + var rslt := false + for i in range(0, current_action.keys.size(), 1): + var key: int = current_action.keys[i] + var out_multiplier := 1 + # Invert the delta for position_out if moving both at once + if _out and _in: + out_multiplier = -1 + + var from_in: Vector2 = shape.to_global(current_action.starting_positions_control_in[i]) + var new_position_in: Vector2 = shape.global_transform.affine_inverse() * ( + delta + from_in + ) + + var from_out: Vector2 = shape.to_global(current_action.starting_positions_control_out[i]) + var new_position_out: Vector2 = shape.global_transform.affine_inverse() * ( + (delta * out_multiplier) + + from_out + ) + + if use_snap(): + new_position_in = snap(new_position_in) + new_position_out = snap(new_position_out) + if _in: + shape.set_point_in(key, new_position_in) + rslt = true + if _out: + shape.set_point_out(key, new_position_out) + rslt = true + update_overlays() + + return rslt + + +func _input_motion_move_verts(delta: Vector2) -> bool: + for i in range(0, current_action.keys.size(), 1): + var key: int = current_action.keys[i] + var from: Vector2 = shape.to_global(current_action.starting_positions[i]) + var new_position: Vector2 = shape.global_transform.affine_inverse() * (from + delta) + if use_snap(): + new_position = snap(new_position) + shape.set_point_position(key, new_position) + update_overlays() + return true + + +func _input_motion_move_width_handle(mouse_position: Vector2, scale: Vector2) -> bool: + for i in range(0, current_action.keys.size(), 1): + var key: int = current_action.keys[i] + var from_width: float = current_action.starting_width[i] + var from_position: Vector2 = current_action.starting_positions[i] + width_scaling = from_position.distance_to(mouse_position) / WIDTH_HANDLE_OFFSET * scale.x + shape.set_point_width(key, roundf(from_width * width_scaling * 10.0) / 10.0) + update_overlays() + return true + + +## Will return index of closest vert to point. +func get_closest_vert_to_point(s: SS2D_Shape, p: Vector2) -> int: + var gt: Transform2D = shape.get_global_transform() + var verts: PackedVector2Array = s.get_vertices() + var transformed_point: Vector2 = gt.affine_inverse() * p + var idx: int = -1 + var closest_distance: float = -1.0 + for i in verts.size(): + var distance: float = verts[i].distance_to(transformed_point) + if distance < closest_distance or closest_distance == -1.0: + idx = s.get_point_key_at_index(i) + closest_distance = distance + return idx + + +func _input_handle_mouse_motion_event( + event: InputEventMouseMotion, et: Transform2D, grab_threshold: float +) -> bool: + var t: Transform2D = et * shape.get_global_transform() + var mm: InputEventMouseMotion = event + var delta_current_pos: Vector2 = et.affine_inverse() * mm.position + gui_point_info_panel.position = mm.position + GUI_POINT_INFO_PANEL_OFFSET + var delta: Vector2 = delta_current_pos - _mouse_motion_delta_starting_pos + + closest_key = get_closest_vert_to_point(shape, delta_current_pos) + + if current_mode == MODE.EDIT_VERT or current_mode == MODE.CREATE_VERT: + var type: ACTION_VERT = current_action.type + var _in := type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_IN + var _out := type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_OUT + + if type == ACTION_VERT.MOVE_VERT: + return _input_motion_move_verts(delta) + elif _in or _out: + return _input_motion_move_control_points(delta, _in, _out) + elif type == ACTION_VERT.MOVE_WIDTH_HANDLE: + return _input_motion_move_width_handle( + et.affine_inverse() * mm.position, et.get_scale() + ) + var mouse_over_key: int = get_mouse_over_vert_key(event, grab_threshold) + var mouse_over_width_handle: int = get_mouse_over_width_handle(event, grab_threshold) + + # Make the closest key grabable while holding down Control + if ( + Input.is_key_pressed(KEY_CTRL) + and not Input.is_key_pressed(KEY_ALT) + and mouse_over_width_handle == -1 + and mouse_over_key == -1 + ): + mouse_over_key = closest_key + + on_width_handle = false + if mouse_over_key != -1: + on_edge = false + current_action = select_verticies([mouse_over_key], ACTION_VERT.NONE) + elif mouse_over_width_handle != -1: + on_edge = false + on_width_handle = true + current_action = select_verticies([mouse_over_width_handle], ACTION_VERT.NONE) + elif Input.is_key_pressed(KEY_ALT): + _input_find_closest_edge_keys(mm) + else: + deselect_verts() + on_edge = _input_motion_is_on_edge(mm, grab_threshold) + + elif current_mode == MODE.EDIT_EDGE or current_mode == MODE.CUT_EDGE: + # Don't update if edge panel is visible + if gui_edge_info_panel.visible: + return false + var type: ACTION_VERT = current_action.type + if type == ACTION_VERT.MOVE_VERT: + return _input_motion_move_verts(delta) + else: + deselect_verts() + on_edge = _input_motion_is_on_edge(mm, grab_threshold) + + elif current_mode == MODE.FREEHAND: + if _mouse_lmb_pressed: + if not Input.is_key_pressed(KEY_CTRL): + var local_position: Vector2 = t.affine_inverse() * mm.position + if last_point_position.distance_to(local_position) >= freehand_paint_size * 2: + last_point_position = local_position + if use_snap(): + local_position = snap(local_position) + var idx: int = shape.get_point_index(closest_edge_keys[1]) if shape.is_shape_closed() else -1 + perform_action(ActionAddPoint.new(shape, local_position, idx, not _defer_mesh_updates)) + update_overlays() + return true + else: + var xform: Transform2D = get_et() * shape.get_global_transform() + var closest_ss2d_point: SS2D_Point = (shape as SS2D_Shape).get_point(closest_key) + if closest_ss2d_point != null: + var closest_point: Vector2 = closest_ss2d_point.position + closest_point = xform * closest_point + if closest_point.distance_to(mm.position) / current_zoom_level <= freehand_erase_size * 2: + var delete_point: int = get_mouse_over_vert_key(event, grab_threshold) + delete_point = closest_key + on_width_handle = false + if delete_point != -1: + perform_action(ActionDeletePoint.new(shape, delete_point, not _defer_mesh_updates)) + last_point_position = Vector2.ZERO + update_overlays() + return true + else: + _input_find_closest_edge_keys(mm) + + update_overlays() + return false + + +func _get_vert_normal(t: Transform2D, verts: PackedVector2Array, i: int) -> Vector2: + var point: Vector2 = t * verts[i] + var prev_point: Vector2 = t * (verts[(i - 1) % verts.size()]) + var next_point: Vector2 = t * (verts[(i + 1) % verts.size()]) + return ((prev_point - point).normalized().rotated(PI / 2) + (point - next_point).normalized().rotated(PI / 2)).normalized() + + +func copy_shape(s: SS2D_Shape) -> SS2D_Shape: + var copy: SS2D_Shape = s.clone(false) + + var undo := get_undo_redo() + undo.create_action("Add Shape Node") + + undo.add_do_method(s.get_parent(), "add_child", copy, true) + undo.add_do_method(copy, "set_owner", get_tree().get_edited_scene_root()) + undo.add_do_reference(copy) + undo.add_undo_method(copy, "set_owner", null) + undo.add_undo_method(s.get_parent(), "remove_child", copy) + + var collision_polygon_original := s.get_collision_polygon_node() + + if collision_polygon_original: + var collision_polygon_new := CollisionPolygon2D.new() + collision_polygon_new.visible = collision_polygon_original.visible + + undo.add_do_method(collision_polygon_original.get_parent(), "add_child", collision_polygon_new, true) + undo.add_do_method(collision_polygon_new, "set_owner", get_tree().get_edited_scene_root()) + undo.add_do_reference(collision_polygon_new) + + undo.add_undo_method(collision_polygon_original.get_parent(), "remove_child", collision_polygon_new) + + undo.commit_action() + + copy.collision_polygon_node_path = copy.get_path_to(collision_polygon_new) + else: + undo.commit_action() + + return copy + + +######### +# DEBUG # +######### +func _debug_mouse_positions(mm: InputEventMouseMotion, t: Transform2D) -> void: + print("========================================") + print("MouseDelta:%s" % str(_mouse_motion_delta_starting_pos)) + print("= MousePositions =") + print("Position: %s" % str(mm.position)) + print("Relative: %s" % str(mm.relative)) + print("= Transforms =") + print("Transform3D: %s" % str(t)) + print("Inverse: %s" % str(t.affine_inverse())) + print("= Transformed Mouse positions =") + print("Position: %s" % str(t.affine_inverse() * mm.position)) + print("Relative: %s" % str(t.affine_inverse() * mm.relative)) + print("MouseDelta:%s" % str(t.affine_inverse() * _mouse_motion_delta_starting_pos)) diff --git a/addons/rmsmartshape/plugin_functionality.gd b/addons/rmsmartshape/plugin_functionality.gd new file mode 100644 index 0000000..3d12e53 --- /dev/null +++ b/addons/rmsmartshape/plugin_functionality.gd @@ -0,0 +1,132 @@ +@tool +class_name SS2D_PluginFunctionality +extends RefCounted + +## - Everything in this script should be static +## - There is one reason to have code in this script +## 1. To separate out code from the main plugin script to ease testing +## +## Common Abbreviations +## et = editor transform (viewport's canvas transform) + +# --- VERTS + +static func get_intersecting_control_point_in( + s: SS2D_Shape, et: Transform2D, mouse_pos: Vector2, grab_threshold: float +) -> PackedInt32Array: + return _get_intersecting_control_point(s, et, mouse_pos, grab_threshold, true) + + +static func get_intersecting_control_point_out( + s: SS2D_Shape, et: Transform2D, mouse_pos: Vector2, grab_threshold: float +) -> PackedInt32Array: + return _get_intersecting_control_point(s, et, mouse_pos, grab_threshold, false) + + +static func _get_intersecting_control_point( + s: SS2D_Shape, et: Transform2D, mouse_pos: Vector2, grab_threshold: float, _in: bool +) -> PackedInt32Array: + var points := PackedInt32Array() + var xform: Transform2D = et * s.get_global_transform() + for i in s.get_point_count(): + var key: int = s.get_point_key_at_index(i) + var vec_pos: Vector2 = s.get_point_position(key) + var c_pos := Vector2.ZERO + if _in: + c_pos = s.get_point_in(key) + else: + c_pos = s.get_point_out(key) + if c_pos == Vector2.ZERO: + continue + var final_pos := vec_pos + c_pos + final_pos = xform * final_pos + if final_pos.distance_to(mouse_pos) <= grab_threshold: + points.push_back(key) + + return points + + +static func get_next_point_index(idx: int, points: PackedVector2Array, wrap_around: bool = false) -> int: + if wrap_around: + return get_next_point_index_wrap_around(idx, points) + return get_next_point_index_no_wrap_around(idx, points) + + +static func get_previous_point_index(idx: int, points: PackedVector2Array, wrap_around: bool = false) -> int: + if wrap_around: + return get_previous_point_index_wrap_around(idx, points) + return get_previous_point_index_no_wrap_around(idx, points) + + +static func get_next_point_index_no_wrap_around(idx: int, points: PackedVector2Array) -> int: + return mini(idx + 1, points.size() - 1) + + +static func get_previous_point_index_no_wrap_around(idx: int, _points_: PackedVector2Array) -> int: + return maxi(idx - 1, 0) + + +static func get_next_point_index_wrap_around(idx: int, points: PackedVector2Array) -> int: + return (idx + 1) % points.size() + + +static func get_previous_point_index_wrap_around(idx: int, points: PackedVector2Array) -> int: + return posmod(idx - 1, points.size()) + + +## Get the next point that doesn't share the same position with the current point.[br] +## In other words, get the next point in the array with a unique position.[br] +static func get_next_unique_point_idx(idx: int, pts: PackedVector2Array, wrap_around: bool) -> int: + var next_idx: int = get_next_point_index(idx, pts, wrap_around) + if next_idx == idx: + return idx + var pt1: Vector2 = pts[idx] + var pt2: Vector2 = pts[next_idx] + if pt1 == pt2: + return get_next_unique_point_idx(next_idx, pts, wrap_around) + return next_idx + + +static func get_previous_unique_point_idx(idx: int, pts: PackedVector2Array, wrap_around: bool) -> int: + var previous_idx: int = get_previous_point_index(idx, pts, wrap_around) + if previous_idx == idx: + return idx + var pt1: Vector2 = pts[idx] + var pt2: Vector2 = pts[previous_idx] + if pt1 == pt2: + return get_previous_unique_point_idx(previous_idx, pts, wrap_around) + return previous_idx + + +static func snap_position( + pos_global: Vector2, snap_offset: Vector2, snap_step: Vector2, local_t: Transform2D +) -> Vector2: + # Move global position to local position to snap in local space + var pos_local: Vector2 = local_t * pos_global + + # Snap in local space + var x: float = pos_local.x + if snap_step.x != 0: + var delta: float = fmod(pos_local.x, snap_step.x) + # Round up + if delta >= (snap_step.x / 2.0): + x = pos_local.x + (snap_step.x - delta) + # Round down + else: + x = pos_local.x - delta + var y: float = pos_local.y + if snap_step.y != 0: + var delta: float = fmod(pos_local.y, snap_step.y) + # Round up + if delta >= (snap_step.y / 2.0): + y = pos_local.y + (snap_step.y - delta) + # Round down + else: + y = pos_local.y - delta + + # Transform3D local position to global position + var pos_global_snapped := (local_t.affine_inverse() * Vector2(x, y)) + snap_offset + #print ("%s | %s | %s | %s" % [pos_global, pos_local, Vector2(x,y), pos_global_snapped]) + return pos_global_snapped + + diff --git a/addons/rmsmartshape/scenes/GUI_Edge_InfoPanel.gd b/addons/rmsmartshape/scenes/GUI_Edge_InfoPanel.gd new file mode 100644 index 0000000..f748341 --- /dev/null +++ b/addons/rmsmartshape/scenes/GUI_Edge_InfoPanel.gd @@ -0,0 +1,146 @@ +@tool +extends PanelContainer +class_name SS2D_EdgeInfoPanel + +signal material_override_toggled(enabled: bool) +signal render_toggled(enabled: bool) +signal weld_toggled(enabled: bool) +signal z_index_changed(value: int) +signal edge_material_changed(value: SS2D_Material_Edge) + +var indicies := Vector2i(-1, -1) : set = set_indicies +var edge_material: SS2D_Material_Edge = null +var edge_material_selector := FileDialog.new() + +@onready var idx_label: Label = %IDX +@onready var material_override_button: Button = %MaterialOverride +@onready var override_container: Container = %OverrideContainer +@onready var render_checkbox: CheckBox = %Render +@onready var weld_checkbox: CheckBox = %Weld +@onready var z_index_spinbox: SpinBox = %ZIndex +@onready var set_material_button: Button = %SetMaterial +@onready var clear_material_button: Button = %ClearMaterial +@onready var material_status: Label = %MaterialStatus + + +func _ready() -> void: + material_override_button.connect("toggled", self._on_toggle_material_override) + render_checkbox.connect("toggled", self._on_toggle_render) + weld_checkbox.connect("toggled", self._on_toggle_weld) + z_index_spinbox.connect("value_changed", self._on_set_z_index) + set_material_button.connect("pressed", self._on_set_edge_material_pressed) + clear_material_button.connect("pressed", self._on_set_edge_material_clear_pressed) + + override_container.hide() + clear_material_button.hide() + + edge_material_selector.file_mode = FileDialog.FILE_MODE_OPEN_FILE + edge_material_selector.dialog_hide_on_ok = true + edge_material_selector.show_hidden_files = true + edge_material_selector.mode_overrides_title = false + edge_material_selector.title = "Select Edge Material" + edge_material_selector.filters = PackedStringArray(["*.tres"]) + edge_material_selector.connect("file_selected", self._on_set_edge_material_file_selected) + add_child(edge_material_selector) + + +func _on_set_edge_material_clear_pressed() -> void: + set_edge_material(null) + + +func _on_set_edge_material_pressed() -> void: + # Update file list + edge_material_selector.invalidate() + edge_material_selector.popup_centered_ratio(0.8) + + +func _on_set_edge_material_file_selected(f: String) -> void: + var rsc := load(f) + if not rsc is SS2D_Material_Edge: + push_error("Selected resource is not an Edge Material! (SS2D_Material_Edge)") + return + set_edge_material(rsc) + + +func set_indicies(t: Vector2i) -> void: + indicies = t + idx_label.text = "IDX: %s" % indicies + + +func set_material_override(enabled: bool) -> void: + material_override_button.button_pressed = enabled + _on_toggle_material_override(enabled) + + +func set_render(enabled: bool, emit: bool = true) -> void: + render_checkbox.button_pressed = enabled + if emit: + _on_toggle_render(enabled) + + +func set_weld(enabled: bool, emit: bool = true) -> void: + weld_checkbox.button_pressed = enabled + if emit: + _on_toggle_weld(enabled) + + +func set_edge_material(v: SS2D_Material_Edge, emit: bool = true) -> void: + edge_material = v + if v == null: + material_status.text = "[No Material]" + clear_material_button.visible = false + else: + # Call string function 'get_file()' to get the filepath + material_status.text = "[%s]" % (v.resource_path).get_file() + clear_material_button.visible = true + if emit: + emit_signal("edge_material_changed", v) + + +func set_edge_z_index(v: int, emit: bool = true) -> void: + z_index_spinbox.value = float(v) + if emit: + _on_set_z_index(float(v)) + + +func get_render() -> bool: + return render_checkbox.button_pressed + + +func get_weld() -> bool: + return weld_checkbox.button_pressed + + +func get_edge_z_index() -> int: + return int(z_index_spinbox.value) + + +func _on_toggle_material_override(pressed: bool) -> void: + override_container.visible = pressed + emit_signal("material_override_toggled", pressed) + + +func _on_toggle_render(pressed: bool) -> void: + emit_signal("render_toggled", pressed) + + +func _on_toggle_weld(pressed: bool) -> void: + emit_signal("weld_toggled", pressed) + + +func _on_set_z_index(v: float) -> void: + emit_signal("z_index_changed", int(v)) + + +func load_values_from_meta_material(meta_mat: SS2D_Material_Edge_Metadata) -> void: + set_render(meta_mat.render) + set_weld(meta_mat.weld) + set_z_index(meta_mat.z_index) + set_edge_material(meta_mat.edge_material) + + +func save_values_to_meta_material(meta_mat: SS2D_Material_Edge_Metadata) -> void: + meta_mat.render = get_render() + meta_mat.weld = get_weld() + meta_mat.z_index = get_z_index() + meta_mat.edge_material = edge_material diff --git a/addons/rmsmartshape/scenes/GUI_Edge_InfoPanel.tscn b/addons/rmsmartshape/scenes/GUI_Edge_InfoPanel.tscn new file mode 100644 index 0000000..6ddae7e --- /dev/null +++ b/addons/rmsmartshape/scenes/GUI_Edge_InfoPanel.tscn @@ -0,0 +1,100 @@ +[gd_scene load_steps=3 format=3 uid="uid://cy1l6tyadc4s3"] + +[ext_resource type="Theme" uid="uid://dud4fe6fsicvm" path="res://addons/rmsmartshape/assets/gui_theme.res" id="1_pwu3l"] +[ext_resource type="Script" path="res://addons/rmsmartshape/scenes/GUI_Edge_InfoPanel.gd" id="2"] + +[node name="GUI_Edge_InfoPanel" type="PanelContainer"] +anchors_preset = -1 +anchor_right = 0.203125 +anchor_bottom = 0.345679 +offset_bottom = -62.0 +size_flags_horizontal = 4 +size_flags_vertical = 4 +mouse_filter = 2 +theme = ExtResource("1_pwu3l") +script = ExtResource("2") +metadata/_edit_use_anchors_ = true + +[node name="Panel" type="Panel" parent="."] +layout_mode = 2 +mouse_filter = 2 + +[node name="Container" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Properties" type="VBoxContainer" parent="Container"] +layout_mode = 2 +mouse_filter = 2 + +[node name="IDX" type="Label" parent="Container/Properties"] +unique_name_in_owner = true +layout_mode = 2 +text = "IDX: [1,2]" + +[node name="Functions" type="VBoxContainer" parent="Container"] +layout_mode = 2 +mouse_filter = 2 + +[node name="Make Inner Curve" type="Button" parent="Container/Functions"] +visible = false +layout_mode = 2 +text = "Make Inner Curve" + +[node name="Make Outer Curve" type="Button" parent="Container/Functions"] +visible = false +layout_mode = 2 +text = "Make Outer Curve" + +[node name="MaterialOverride" type="Button" parent="Container/Functions"] +unique_name_in_owner = true +layout_mode = 2 +toggle_mode = true +text = "Material Override" + +[node name="OverrideContainer" type="VBoxContainer" parent="Container"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="HSeparator" type="HSeparator" parent="Container/OverrideContainer"] +layout_mode = 2 + +[node name="Render" type="CheckBox" parent="Container/OverrideContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Render" + +[node name="Weld" type="CheckBox" parent="Container/OverrideContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +text = "Weld" + +[node name="ZIndexSection" type="HBoxContainer" parent="Container/OverrideContainer"] +visible = false +layout_mode = 2 + +[node name="lbl" type="Label" parent="Container/OverrideContainer/ZIndexSection"] +layout_mode = 2 +text = "Z:" + +[node name="ZIndex" type="SpinBox" parent="Container/OverrideContainer/ZIndexSection"] +unique_name_in_owner = true +layout_mode = 2 +min_value = -100.0 + +[node name="SetMaterial" type="Button" parent="Container/OverrideContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Set Material" + +[node name="ClearMaterial" type="Button" parent="Container/OverrideContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +text = "Clear Material" + +[node name="MaterialStatus" type="Label" parent="Container/OverrideContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "[No Material]" diff --git a/addons/rmsmartshape/scenes/GUI_InfoPanel.gd b/addons/rmsmartshape/scenes/GUI_InfoPanel.gd new file mode 100644 index 0000000..1ff15f2 --- /dev/null +++ b/addons/rmsmartshape/scenes/GUI_InfoPanel.gd @@ -0,0 +1,25 @@ +@tool +extends PanelContainer +class_name SS2D_PointInfoPanel + + +@onready var idx_label: Label = %IDX +@onready var tex_label: Label = %Tex +@onready var width_label: Label = %Width +@onready var flip_label: Label = %Flip + + +func set_idx(i: int) -> void: + idx_label.text = "IDX: %s" % i + + +func set_texture_idx(i: int) -> void: + tex_label.text = "Texture2D: %s" % i + + +func set_width(f: float) -> void: + width_label.text = "Width: %s" % f + + +func set_flip(b: bool) -> void: + flip_label.text = "Flip: %s" % b diff --git a/addons/rmsmartshape/scenes/GUI_InfoPanel.tscn b/addons/rmsmartshape/scenes/GUI_InfoPanel.tscn new file mode 100644 index 0000000..9bb1c70 --- /dev/null +++ b/addons/rmsmartshape/scenes/GUI_InfoPanel.tscn @@ -0,0 +1,44 @@ +[gd_scene load_steps=3 format=3 uid="uid://cxu6258urdtf1"] + +[ext_resource type="Theme" uid="uid://dud4fe6fsicvm" path="res://addons/rmsmartshape/assets/gui_theme.res" id="1_byls6"] +[ext_resource type="Script" path="res://addons/rmsmartshape/scenes/GUI_InfoPanel.gd" id="2"] + +[node name="GUI_InfoPanel" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -918.0 +offset_bottom = -486.0 +size_flags_horizontal = 4 +size_flags_vertical = 4 +mouse_filter = 2 +theme = ExtResource("1_byls6") +script = ExtResource("2") + +[node name="Panel" type="Panel" parent="."] +layout_mode = 2 +mouse_filter = 2 + +[node name="Properties" type="VBoxContainer" parent="."] +layout_mode = 2 +mouse_filter = 2 + +[node name="IDX" type="Label" parent="Properties"] +unique_name_in_owner = true +layout_mode = 2 +text = "IDX: 13" + +[node name="Tex" type="Label" parent="Properties"] +unique_name_in_owner = true +layout_mode = 2 +text = "Tex: 1" + +[node name="Width" type="Label" parent="Properties"] +unique_name_in_owner = true +layout_mode = 2 +text = "Width: 1.0" + +[node name="Flip" type="Label" parent="Properties"] +unique_name_in_owner = true +layout_mode = 2 +text = "Flip: N" diff --git a/addons/rmsmartshape/scenes/SnapPopup.gd b/addons/rmsmartshape/scenes/SnapPopup.gd new file mode 100644 index 0000000..cb87ea0 --- /dev/null +++ b/addons/rmsmartshape/scenes/SnapPopup.gd @@ -0,0 +1,16 @@ +@tool +extends Popup +class_name SS2D_SnapPopup + +@onready var snap_offset_x: SpinBox = %SnapOffsetX +@onready var snap_offset_y: SpinBox = %SnapOffsetY +@onready var snap_step_x: SpinBox = %SnapStepX +@onready var snap_step_y: SpinBox = %SnapStepY + + +func get_snap_offset() -> Vector2: + return Vector2(snap_offset_x.value, snap_offset_y.value) + + +func get_snap_step() -> Vector2: + return Vector2(snap_step_x.value, snap_step_y.value) diff --git a/addons/rmsmartshape/scenes/SnapPopup.tscn b/addons/rmsmartshape/scenes/SnapPopup.tscn new file mode 100644 index 0000000..12af8f3 --- /dev/null +++ b/addons/rmsmartshape/scenes/SnapPopup.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=3 format=3 uid="uid://d1acvw8yuubxo"] + +[ext_resource type="Script" path="res://addons/rmsmartshape/scenes/SnapPopup.gd" id="1"] +[ext_resource type="Theme" uid="uid://dud4fe6fsicvm" path="res://addons/rmsmartshape/assets/gui_theme.res" id="1_6ft7o"] + +[node name="SnapPopup" type="PopupPanel"] +size = Vector2i(301, 128) +visible = true +theme = ExtResource("1_6ft7o") +script = ExtResource("1") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +offset_left = 12.0 +offset_top = 12.0 +offset_right = 289.0 +offset_bottom = 116.0 + +[node name="Label" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "Configure Snap" + +[node name="SnapOffset" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +alignment = 2 + +[node name="Label" type="Label" parent="VBoxContainer/SnapOffset"] +layout_mode = 2 +text = "Grid Offset:" + +[node name="SnapOffsetX" type="SpinBox" parent="VBoxContainer/SnapOffset"] +unique_name_in_owner = true +layout_mode = 2 +suffix = "px" + +[node name="SnapOffsetY" type="SpinBox" parent="VBoxContainer/SnapOffset"] +unique_name_in_owner = true +layout_mode = 2 +suffix = "px" + +[node name="SnapStep" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +alignment = 2 + +[node name="Label" type="Label" parent="VBoxContainer/SnapStep"] +layout_mode = 2 +text = "Grid Step: " + +[node name="SnapStepX" type="SpinBox" parent="VBoxContainer/SnapStep"] +unique_name_in_owner = true +layout_mode = 2 +value = 8.0 +suffix = "px" + +[node name="SnapStepY" type="SpinBox" parent="VBoxContainer/SnapStep"] +unique_name_in_owner = true +layout_mode = 2 +value = 8.0 +suffix = "px" diff --git a/addons/rmsmartshape/shapes/edge.gd b/addons/rmsmartshape/shapes/edge.gd new file mode 100644 index 0000000..35ee62b --- /dev/null +++ b/addons/rmsmartshape/shapes/edge.gd @@ -0,0 +1,337 @@ +@tool +extends RefCounted +class_name SS2D_Edge + +## An SS2D_Edge represents an edge that will be rendered. +## +## It contains: [br] +## - A list of quads that should be rendered [br] +## - A [Material] that dictates how the edge should be rendered [br] + +## What to encode in the color data (for use by shaders). +enum COLOR_ENCODING { + COLOR, ## Encode a diffuse value to offset the quads color by (currently only ever white). + NORMALS, ## Encode normal data in the colors to be unpacked by a shader later. +} + +var quads: Array[SS2D_Quad] = [] +var first_point_key: int = -1 +var last_point_key: int = -1 +var z_index: int = 0 +var z_as_relative: bool = false +## If final point is connected to first point. +var wrap_around: bool = false +var material: Material = null + + +## Will return true if the 2 quads must be drawn in two calls. +static func different_render(q1: SS2D_Quad, q2: SS2D_Quad) -> bool: + if q1.matches_quad(q2): + return false + return true + + +static func get_consecutive_quads_for_mesh(_quads: Array[SS2D_Quad]) -> Array[Array]: + var quad_ranges: Array[Array] = [] + + if _quads.is_empty(): + return quad_ranges + + var quad_range: Array[SS2D_Quad] = [] + quad_range.push_back(_quads[0]) + for i in range(1, _quads.size(), 1): + var quad_prev: SS2D_Quad = _quads[i - 1] + var quad: SS2D_Quad = _quads[i] + if different_render(quad, quad_prev): + quad_ranges.push_back(quad_range) + quad_range = [quad] + else: + quad_range.push_back(quad) + + quad_ranges.push_back(quad_range) + return quad_ranges + + +## Will generate normals for a given quad +## and interpolate with previous and next quads. +static func generate_normals_for_quad_interpolated(qp: SS2D_Quad, q: SS2D_Quad, qn: SS2D_Quad) -> Array[Color]: + # Interpolation and normalization + #First, consider everything to be a non corner + var tg_a: Vector2 = (q.tg_a + qp.tg_d) + var bn_a: Vector2 = (q.bn_a + qp.bn_d) + + var tg_b: Vector2 = (q.tg_b + qp.tg_c) + var bn_b: Vector2 = (q.bn_b + qp.bn_c) + + var tg_c: Vector2 = (q.tg_c + qn.tg_b) + var bn_c: Vector2 = (q.bn_c + qn.bn_b) + + var tg_d: Vector2 = (q.tg_d + qn.tg_a) + var bn_d: Vector2 = (q.bn_d + qn.bn_a) + + #then, fix values for corner cases (and edge ends) + if q.corner == q.CORNER.NONE: + if qp.corner == q.CORNER.NONE: + #check validity + if (not q.pt_a.is_equal_approx(qp.pt_d)) or (not q.pt_b.is_equal_approx(qp.pt_c)): + tg_a = q.tg_a + tg_b = q.tg_b + bn_a = q.bn_a + bn_b = q.bn_b + elif qp.corner == q.CORNER.INNER: + tg_a = (-qp.bn_d) + bn_a = (-qp.tg_d) + tg_b = (q.tg_b - qp.bn_a) + bn_b = (q.bn_b - qp.tg_a) + #check validity + if (not q.pt_a.is_equal_approx(qp.pt_d)) or (not q.pt_b.is_equal_approx(qp.pt_a)): + tg_a = q.tg_a + tg_b = q.tg_b + bn_a = q.bn_a + bn_b = q.bn_b + elif qp.corner == q.CORNER.OUTER: + tg_a = (q.tg_a + qp.bn_c) + bn_a = (q.bn_a - qp.tg_c) + tg_b = (qp.bn_b) + bn_b = (-qp.tg_b) + #check validity + if (not q.pt_a.is_equal_approx(qp.pt_c)) or (not q.pt_b.is_equal_approx(qp.pt_b)): + tg_a = q.tg_a + tg_b = q.tg_b + bn_a = q.bn_a + bn_b = q.bn_b + if qn.corner == q.CORNER.NONE: + #check validity + if (not q.pt_c.is_equal_approx(qn.pt_b)) or (not q.pt_d.is_equal_approx(qn.pt_a)): + tg_c = q.tg_c + tg_d = q.tg_d + bn_c = q.bn_c + bn_d = q.bn_d + elif qn.corner == q.CORNER.INNER: + tg_d = (-qn.tg_d) + bn_d = (qn.bn_d) + tg_c = (q.tg_c - qn.tg_c) + bn_c = (q.bn_c + qn.bn_c) + #check validity + if (not q.pt_c.is_equal_approx(qn.pt_c)) or (not q.pt_d.is_equal_approx(qn.pt_d)): + tg_c = q.tg_c + tg_d = q.tg_d + bn_c = q.bn_c + bn_d = q.bn_d + elif qn.corner == q.CORNER.OUTER: + tg_c = (qn.tg_b) + bn_c = (qn.bn_b) + #check validity + if (not q.pt_c.is_equal_approx(qn.pt_b)) or (not q.pt_d.is_equal_approx(qn.pt_a)): + tg_c = q.tg_c + tg_d = q.tg_d + bn_c = q.bn_c + bn_d = q.bn_d + + elif q.corner == q.CORNER.INNER: + #common + tg_d = q.tg_d + bn_d = q.bn_d + tg_b = (q.tg_b) + bn_b = (q.bn_b) + #previous + tg_c = (q.tg_c - qp.tg_c) + bn_c = (q.bn_c + qp.bn_c) + #next + tg_a = (q.tg_a - qn.bn_b) + bn_a = (q.bn_a - qn.tg_b) + #check validity + if qp.corner != qp.CORNER.NONE or (not q.pt_c.is_equal_approx(qp.pt_c)) or (not q.pt_d.is_equal_approx(qp.pt_d)): + tg_c = q.tg_c + bn_c = q.bn_c + if qn.corner != qp.CORNER.NONE or (not q.pt_a.is_equal_approx(qn.pt_b)) or (not q.pt_d.is_equal_approx(qn.pt_a)): + tg_a = q.tg_a + bn_a = q.bn_a + + elif q.corner == q.CORNER.OUTER: + tg_d = q.tg_d + bn_d = q.bn_d + tg_b = (q.tg_b) + bn_b = (q.bn_b) + #previous + tg_a = (q.tg_a + qp.tg_d) + bn_a = (q.bn_a + qp.bn_d) + #qn + tg_c = (q.tg_c - qn.bn_a) + bn_c = (q.bn_c + qn.tg_a) + #check validity + if qp.corner != qp.CORNER.NONE or (not q.pt_a.is_equal_approx(qp.pt_d)) or (not q.pt_b.is_equal_approx(qp.pt_c)): + tg_a = q.tg_a + bn_a = q.bn_a + if qn.corner != qp.CORNER.NONE or (not q.pt_b.is_equal_approx(qn.pt_b)) or (not q.pt_c.is_equal_approx(qn.pt_a)): + tg_c = q.tg_c + bn_c = q.bn_c + + if q.flip_texture: + bn_a = -bn_a; + bn_b = -bn_b; + bn_c = -bn_c; + bn_d = -bn_d; + + #Normalize the values + var half_vector: Vector2 = Vector2.ONE * 0.5 + tg_a = tg_a.normalized()*0.5 + half_vector + tg_b = tg_b.normalized()*0.5 + half_vector + tg_c = tg_c.normalized()*0.5 + half_vector + tg_d = tg_d.normalized()*0.5 + half_vector + + bn_a = bn_a.normalized()*0.5 + half_vector + bn_b = bn_b.normalized()*0.5 + half_vector + bn_c = bn_c.normalized()*0.5 + half_vector + bn_d = bn_d.normalized()*0.5 + half_vector + + var normal_pt_a := Color(tg_a.x, tg_a.y, bn_a.x, bn_a.y) + var normal_pt_b := Color(tg_b.x, tg_b.y, bn_b.x, bn_b.y) + var normal_pt_c := Color(tg_c.x, tg_c.y, bn_c.x, bn_c.y) + var normal_pt_d := Color(tg_d.x, tg_d.y, bn_d.x, bn_d.y) + + return [normal_pt_a, normal_pt_b, normal_pt_c, normal_pt_d] + + +## Assumes each quad in the sequence is of the same render type (same textures, values, etc...).[br] +## [param _quads] should have been generated by [method get_consecutive_quads_for_mesh]. +static func generate_array_mesh_from_quad_sequence(_quads: Array[SS2D_Quad], _wrap_around: bool, color_encoding: int) -> ArrayMesh: + # FIXME: _wrap_around is unused. + if _quads.is_empty(): + return ArrayMesh.new() + + var total_length: float = 0.0 + for q in _quads: + total_length += q.get_length_average() + if total_length == 0.0: + return ArrayMesh.new() + + var first_quad: SS2D_Quad = _quads[0] + var tex: Texture2D = first_quad.texture + # The change in length required to apply to each quad + # to make the textures begin and end at the start and end of each texture + var change_in_length: float = -1.0 + if tex != null: + # How many times the texture is repeated + var texture_reps: float = roundf(total_length / tex.get_size().x) + # Length required to display all the reps with the texture's full width + var texture_full_length: float = texture_reps * tex.get_size().x + # How much each quad's texture must be offset to make up the difference in full length vs total length + change_in_length = (texture_full_length / total_length) + + if first_quad.fit_texture == SS2D_Material_Edge.FITMODE.CROP: + change_in_length = 1.0 + + var length_elapsed: float = 0.0 + var st := SurfaceTool.new() + st.begin(Mesh.PRIMITIVE_TRIANGLES) + for q in _quads: + q.update_tangents() + for i in _quads.size(): + var q: SS2D_Quad = _quads[i] + var section_length: float = q.get_length_average() * change_in_length +# var highest_value: float = max(q.get_height_left(), q.get_height_right()) + # When welding and using different widths, quads can look a little weird + # This is because they are no longer parallelograms + # This is a tough problem to solve + # See http://reedbeta.com/blog/quadrilateral-interpolation-part-1/ + var uv_a := Vector2(0, 0) + var uv_b := Vector2(0, 1) + var uv_c := Vector2(1, 1) + var uv_d := Vector2(1, 0) + # If we have a valid texture and this quad isn't a corner + if tex != null and q.corner == q.CORNER.NONE: + var x_left: float = (length_elapsed) / tex.get_size().x + var x_right: float = (length_elapsed + section_length) / tex.get_size().x + uv_a.x = x_left + uv_b.x = x_left + uv_c.x = x_right + uv_d.x = x_right + if q.flip_texture: + var t: Vector2 = uv_a + uv_a = uv_b + uv_b = t + t = uv_c + uv_c = uv_d + uv_d = t + + var color_a := q.color + var color_b := q.color + var color_c := q.color + var color_d := q.color + + if color_encoding == COLOR_ENCODING.NORMALS: + var next := _quads[wrapi(i + 1, 0, _quads.size())] + var prev := _quads[wrapi(i - 1, 0, _quads.size())] + + var normals: Array[Color] = generate_normals_for_quad_interpolated(next, q, prev) + color_a = normals[0] + color_b = normals[1] + color_c = normals[2] + color_d = normals[3] + + # A + _add_uv_to_surface_tool(st, uv_a) + st.set_color(color_a) + st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_a)) + + # B + _add_uv_to_surface_tool(st, uv_b) + st.set_color(color_b) + st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_b)) + + # C + _add_uv_to_surface_tool(st, uv_c) + st.set_color(color_c) + st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_c)) + + # A + _add_uv_to_surface_tool(st, uv_a) + st.set_color(color_a) + st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_a)) + + # C + _add_uv_to_surface_tool(st, uv_c) + st.set_color(color_c) + st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_c)) + + # D + _add_uv_to_surface_tool(st, uv_d) + st.set_color(color_d) + st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_d)) + + length_elapsed += section_length + + st.index() + st.generate_normals() + return st.commit() + + +func get_meshes(color_encoding: SS2D_Edge.COLOR_ENCODING) -> Array[SS2D_Mesh]: + # Get Arrays of consecutive quads with the same mesh data. + # For each array, generate Mesh Data from the quad. + + var consecutive_quad_arrays := SS2D_Edge.get_consecutive_quads_for_mesh(quads) + #print("Arrays: %s" % consecutive_quad_arrays.size()) + var meshes: Array[SS2D_Mesh] = [] + for consecutive_quads in consecutive_quad_arrays: + if consecutive_quads.is_empty(): + continue + var array_mesh: ArrayMesh = SS2D_Edge.generate_array_mesh_from_quad_sequence( + consecutive_quads, wrap_around, color_encoding + ) + var quad: SS2D_Quad = consecutive_quads[0] + var tex: Texture2D = quad.texture + var flip: bool = quad.flip_texture + var mesh_data := SS2D_Mesh.new(tex, flip, Transform2D(), [array_mesh], material) + mesh_data.force_no_tiling = quad.is_tapered or quad.corner != SS2D_Quad.CORNER.NONE + mesh_data.z_index = z_index + mesh_data.z_as_relative = z_as_relative + meshes.push_back(mesh_data) + + return meshes + + +static func _add_uv_to_surface_tool(surface_tool: SurfaceTool, uv: Vector2) -> void: + surface_tool.set_uv(uv) + surface_tool.set_uv2(uv) diff --git a/addons/rmsmartshape/shapes/index_map.gd b/addons/rmsmartshape/shapes/index_map.gd new file mode 100644 index 0000000..31f7eda --- /dev/null +++ b/addons/rmsmartshape/shapes/index_map.gd @@ -0,0 +1,264 @@ +@tool +extends RefCounted +class_name SS2D_IndexMap + +## Maps a set of indicies to an object. + +var object: Variant = null +var indicies: PackedInt32Array + + +## Parameter [param subresources] has no effect, no subresources to duplicate. +func duplicate(_subresources: bool = false) -> SS2D_IndexMap: + return SS2D_IndexMap.new(indicies.duplicate(), object) + + +func _init(i: PackedInt32Array, o: Variant) -> void: + indicies = i + object = o + + +func _to_string() -> String: + return "[M_2_IDX] (%s) | %s" % [str(object), indicies] + + +static func is_index_array_valid(idx_array: PackedInt32Array) -> bool: + return idx_array.size() >= 2 + + +func is_valid() -> bool: + return SS2D_IndexMap.is_index_array_valid(indicies) + + +# FIXME: Unused. Remove eventually +# func get_contiguous_segments() -> Array: +# if is_contiguous(): +# return [indicies.duplicate()] +# var segments: Array = [] +# var break_idx: int = find_break() +# var remainder: Array[int] = indicies.duplicate() +# while break_idx != -1: +# var new_slice: Array[int] = [] +# for i in range(0, break_idx): +# new_slice.push_back(remainder[i]) +# segments.push_back(new_slice) +# remainder = remainder.slice(break_idx, remainder.size()) +# break_idx = SS2D_IndexMap.find_break_in_array(remainder) +# if not remainder.is_empty(): +# segments.push_back(remainder) +# return segments + + +## Will join together segments that share the same idx, +## ex. [1,2], [4,5], and [2,3,4] become [1,2,3,4,5] +static func join_segments(segments: Array[PackedInt32Array]) -> Array[PackedInt32Array]: + var final_segments: Array[PackedInt32Array] = [] + final_segments.assign(segments.duplicate()) + + var to_join_tuple: Vector2i + var join_performed := true + while join_performed: + join_performed = false + for i in range(0, final_segments.size()): + if join_performed: + break + for ii in range(i + 1, final_segments.size()): + var a := final_segments[i] + var b := final_segments[ii] + if a[-1] == b[0]: + to_join_tuple = Vector2i(i, ii) + join_performed = true + if b[-1] == a[0]: + to_join_tuple = Vector2i(ii, i) + join_performed = true + if join_performed: + break + if join_performed: + var idx_lowest: int = to_join_tuple[0] + var idx_highest: int = to_join_tuple[1] + var lowest: PackedInt32Array = final_segments[idx_lowest] + var highest: PackedInt32Array = final_segments[idx_highest] + final_segments.erase(lowest) + final_segments.erase(highest) + # pop the shared idx from lowest + lowest.remove_at(lowest.size() - 1) + var new_segment := lowest + highest + final_segments.push_back(new_segment) + + return final_segments + + +## Does each index increment by 1 without any breaks. +func is_contiguous() -> bool: + return SS2D_IndexMap.is_array_contiguous(indicies) + + +static func is_array_contiguous(a: PackedInt32Array) -> bool: + return find_break_in_array(a) == -1 + + +## Find a break in the indexes where they aren't contiguous.[br] +## Will return -1 if there's no break.[br] +func find_break() -> int: + return SS2D_IndexMap.find_break_in_array(indicies) + + +static func find_break_in_array(a: PackedInt32Array, offset: int = 0) -> int: + for i in range(offset, a.size() - 1, 1): + if is_break_at_index_in_array(a, i): + return i + 1 + return -1 + + +## Whether there is a break at the given index.[br] +## Will return -1 if there's no break.[br] +func is_break_at_index(i: int) -> bool: + return SS2D_IndexMap.is_break_at_index_in_array(indicies, i) + + +static func is_break_at_index_in_array(a: PackedInt32Array, i: int) -> bool: + var difference: int = absi((a[i]) - (a[i + 1])) + return difference != 1 + + +func has_index(idx: int) -> bool: + return indicies.has(idx) + + +# FIXME: Unused, remove eventually. +# func lowest_index() -> int: +# return indicies.min() +# +# +# func highest_index() -> int: +# return indicies.max() + + +# FIXME: Unused, remove eventually +func _split_indicies_into_multiple_mappings(new_indicies: PackedInt32Array) -> Array[SS2D_IndexMap]: + var maps: Array[SS2D_IndexMap] = [] + var break_idx := SS2D_IndexMap.find_break_in_array(new_indicies) + var offset := 0 + var sub_indicies: PackedInt32Array + + while break_idx != -1: + sub_indicies = new_indicies.slice(offset, break_idx) + + if SS2D_IndexMap.is_index_array_valid(sub_indicies): + maps.push_back(SS2D_IndexMap.new(sub_indicies, object)) + + offset = break_idx + break_idx = SS2D_IndexMap.find_break_in_array(new_indicies, offset) + + sub_indicies = new_indicies.slice(offset) + + if SS2D_IndexMap.is_index_array_valid(sub_indicies): + maps.push_back(SS2D_IndexMap.new(sub_indicies, object)) + + return maps + + +## FIXME: Unused, remove eventually +## Will create a new set of SS2D_IndexMaps. [br][br] +## +## The new set will contain all of the indicies of the current set, +## minus the ones specified in the indicies parameter. [br][br] +## +## Example: [br] +## indicies = [0,1,2,3,4,5,6] [br] +## to_remove = [3,4] [br] +## new_sets = [0,1,2] [5,6] [br][br] +## +## This may split the IndexMap or make it invalid entirely. +## As a result, the returned array could have 0 or several IndexMaps. +func remove_indicies(to_remove: PackedInt32Array) -> Array[SS2D_IndexMap]: + var out: Array[SS2D_IndexMap] = [] + var new_indicies := indicies.duplicate() + + for r in to_remove: + var idx := new_indicies.find(r) + if idx >= 0: + new_indicies.remove_at(idx) + + if not SS2D_IndexMap.is_index_array_valid(new_indicies): + return out + + if SS2D_IndexMap.is_array_contiguous(new_indicies): + out.push_back(SS2D_IndexMap.new(new_indicies, object)) + return out + + return _split_indicies_into_multiple_mappings(new_indicies) + + +## Will create a new set of SS2D_IndexMaps. [br][br] +## +## The new set will contain all of the edges of the current set, +## minus the ones specified in the indicies parameter. [br][br] +## +## Example: [br] +## indicies = [0,1,2,3,4,5,6] [br] +## to_remove = [4,5] [br] +## new_sets = [0,1,2,3,4] [4,5,6] [br][br] +## +## This may split the IndexMap or make it invalid entirely. +## As a result, the returned array could have 0 or several IndexMaps. +func remove_edges(to_remove: PackedInt32Array) -> Array[SS2D_IndexMap]: + # Corner case + if to_remove.size() == 2: + var idx: int = indicies.find(to_remove[0]) + if idx != indicies.size()-1: + if indicies[idx+1] == to_remove[1]: + # Need one split + var set_1 := indicies.slice(0, idx+1) + var set_2 := indicies.slice(idx+1, indicies.size()) + var new_maps: Array[SS2D_IndexMap] = [] + if SS2D_IndexMap.is_index_array_valid(set_1): + new_maps.push_back(SS2D_IndexMap.new(set_1, object)) + if SS2D_IndexMap.is_index_array_valid(set_2): + new_maps.push_back(SS2D_IndexMap.new(set_2, object)) + return new_maps + return [SS2D_IndexMap.new(indicies, object)] + + # General case + var new_edges := SS2D_IndexMap.indicies_to_edges(indicies) + for i in range(0, to_remove.size() - 1, 1): + var idx1: int = to_remove[i] + var idx2: int = to_remove[i + 1] + var edges_to_remove := PackedInt32Array() + for ii in new_edges.size(): + var edge := new_edges[ii] + if (edge[0] == idx1 or edge[0] == idx2) and (edge[1] == idx1 or edge[1] == idx2): + edges_to_remove.push_back(ii) + # Reverse iterate + for ii in range(edges_to_remove.size()-1, -1, -1): + new_edges.remove_at(edges_to_remove[ii]) + + new_edges = SS2D_IndexMap.join_segments(new_edges) + var new_index_mappings: Array[SS2D_IndexMap] = [] + for e in new_edges: + new_index_mappings.push_back(SS2D_IndexMap.new(e, object)) + return new_index_mappings + + +# NOTE: Even though it makes more sense to return an Array[Vector2i], we return PackedInt32Arrays +# instead because it makes things easier in the context where this function output is needed. +static func indicies_to_edges(p_indicies: PackedInt32Array) -> Array[PackedInt32Array]: + var edges: Array[PackedInt32Array] = [] + for i in p_indicies.size() - 1: + var edge := PackedInt32Array([ i, i+1 ]) + if absi(edge[0] - edge[1]) == 1: + edges.push_back(edge) + return edges + + +## Returns a Dict[Variant, Array[SS2D_IndexMap]] +static func index_map_array_sort_by_object(imaps: Array) -> Dictionary: + var dict := {} + for imap: SS2D_IndexMap in imaps: + if not dict.has(imap.object): + var arr: Array[SS2D_IndexMap] = [ imap ] + dict[imap.object] = arr + else: + var arr: Array[SS2D_IndexMap] = dict[imap.object] + arr.push_back(imap) + return dict diff --git a/addons/rmsmartshape/shapes/mesh.gd b/addons/rmsmartshape/shapes/mesh.gd new file mode 100644 index 0000000..0c26f9b --- /dev/null +++ b/addons/rmsmartshape/shapes/mesh.gd @@ -0,0 +1,80 @@ +@tool +extends RefCounted +class_name SS2D_Mesh + +## Used to organize all requested meshes to be rendered by their textures. + +var texture: Texture2D = null +var flip_texture: bool = false +var meshes: Array[ArrayMesh] = [] +var mesh_transform: Transform2D = Transform2D() +var material: Material = null +var z_index: int = 0 +var z_as_relative: bool = true +var show_behind_parent: bool = false +var force_no_tiling: bool = false + + +func _init( + t: Texture2D = null, + f: bool = false, + xform: Transform2D = Transform2D(), + m: Array[ArrayMesh] = [], + mat: Material = null +) -> void: + texture = t + flip_texture = f + meshes = m + mesh_transform = xform + material = mat + + +# Note: Not an override. +func duplicate(subresources: bool = false) -> SS2D_Mesh: + var copy := SS2D_Mesh.new() + copy.texture = texture + copy.flip_texture = flip_texture + copy.mesh_transform = mesh_transform + copy.material = material + copy.z_index = z_index + copy.z_as_relative = z_as_relative + copy.show_behind_parent = show_behind_parent + copy.force_no_tiling = force_no_tiling + copy.meshes = [] + if subresources: + for m in meshes: + copy.meshes.push_back(m.duplicate(true)) + return copy + + +func matches(tex: Texture2D, f: bool, t: Transform2D, m: Material, zi: int, zb: bool) -> bool: + return ( + tex == texture + and f == flip_texture + and t == mesh_transform + and m == material + and zi == z_index + and zb == z_as_relative + ) + + +func mesh_matches(m: SS2D_Mesh) -> bool: + return matches( + m.texture, + m.flip_texture, + m.mesh_transform, + m.material, + m.z_index, + m.z_as_relative + ) + + +func debug_print_array_mesh(am: ArrayMesh) -> String: + var s := "Faces:%s | Surfs:%s | " % [am.get_faces(), am.get_surface_count()] + return s + + +func render(ci: CanvasItem) -> void: + #print("mesh count %s" % meshes.size()) + for mesh in meshes: + ci.draw_mesh(mesh, texture) diff --git a/addons/rmsmartshape/shapes/point.gd b/addons/rmsmartshape/shapes/point.gd new file mode 100644 index 0000000..a7b58e4 --- /dev/null +++ b/addons/rmsmartshape/shapes/point.gd @@ -0,0 +1,72 @@ +@tool +extends Resource +class_name SS2D_Point + +@export var position: Vector2 : set = _set_position +@export var point_in: Vector2 : set = _set_point_in +@export var point_out: Vector2 : set = _set_point_out +@export var properties: SS2D_VertexProperties : set = _set_properties + +# If class members are written to, the 'changed' signal may not be emitted +# Signal is only emitted when data is actually changed +# If assigned data is the same as the existing data, no signal is emitted + + +func _init(pos: Vector2 = Vector2(0, 0)) -> void: + position = pos + point_in = Vector2(0, 0) + point_out = Vector2(0, 0) + properties = SS2D_VertexProperties.new() + + +func equals(other: SS2D_Point) -> bool: + if position != other.position: + return false + if point_in != other.point_in: + return false + if point_out != other.point_out: + return false + print ("E! %s" % properties.equals(other.properties)) + if not properties.equals(other.properties): + return false + return true + + +func _set_position(v: Vector2) -> void: + if position != v: + position = v + emit_changed() + + +func _set_point_in(v: Vector2) -> void: + if point_in != v: + point_in = v + emit_changed() + + +func _set_point_out(v: Vector2) -> void: + if point_out != v: + point_out = v + emit_changed() + + +func _set_properties(other: SS2D_VertexProperties) -> void: + # FIXME: What if other is null? + if properties == null or not properties.equals(other): + if properties: + properties.changed.disconnect(_on_properties_changed) + + properties = other + + if properties: + properties.changed.connect(_on_properties_changed) + + emit_changed() + + +func _to_string() -> String: + return "" % [position] + + +func _on_properties_changed() -> void: + emit_changed() diff --git a/addons/rmsmartshape/shapes/point_array.gd b/addons/rmsmartshape/shapes/point_array.gd new file mode 100644 index 0000000..0d74011 --- /dev/null +++ b/addons/rmsmartshape/shapes/point_array.gd @@ -0,0 +1,675 @@ +@tool +extends Resource +class_name SS2D_Point_Array + +enum CONSTRAINT { NONE = 0, AXIS_X = 1, AXIS_Y = 2, CONTROL_POINTS = 4, PROPERTIES = 8, ALL = 15 } + +# Maps a key to each point: Dict[int, SS2D_Point] +@export var _points: Dictionary = {} : set = _set_points +# Contains all keys; the order of the keys determines the order of the points +@export var _point_order := PackedInt32Array() : set = set_point_order + +## Dict[Vector2i, CONSTRAINT] +## Key is tuple of point_keys; Value is the CONSTRAINT enum. +@export var _constraints: Dictionary = {} : set = _set_constraints +# Next key value to generate +@export var _next_key: int = 0 : set = set_next_key + +## Dict[Vector2i, SS2D_Material_Edge_Metadata] +## Dictionary of specific materials to use for specific tuples of points. +## Key is tuple of two point keys, value is material. +@export var _material_overrides: Dictionary = {} : set = set_material_overrides + +## Controls how many subdivisions a curve segment may face before it is considered +## approximate enough. +@export_range(0, 8, 1) +var tessellation_stages: int = 3 : set = set_tessellation_stages + +## Controls how many degrees the midpoint of a segment may deviate from the real +## curve, before the segment has to be subdivided. +@export_range(0.1, 16.0, 0.1, "or_greater", "or_lesser") +var tessellation_tolerance: float = 6.0 : set = set_tessellation_tolerance + +@export_range(1, 512) var curve_bake_interval: float = 20.0 : set = set_curve_bake_interval + +var _constraints_enabled: bool = true +var _updating_constraints := false +var _keys_to_update_constraints := PackedInt32Array() + +var _changed_during_update := false +var _updating := false + +# Point caches +var _point_cache_dirty := true +var _vertex_cache := PackedVector2Array() +var _curve := Curve2D.new() +var _curve_no_control_points := Curve2D.new() +var _tesselation_cache := PackedVector2Array() +var _tess_vertex_mapping := SS2D_TesselationVertexMapping.new() + +## Gets called when points were modified. +## In comparison to the "changed" signal, "update_finished" will only be called once after +## begin/end_update() blocks, while "changed" will be called for every singular change. +## Hence, this signal is usually better suited to react to point updates. +signal update_finished() + +signal constraint_removed(key1: int, key2: int) +signal material_override_changed(tuple: Vector2i) + +################### +# HANDLING POINTS # +################### + + +func _init() -> void: + # Required by Godot to correctly make unique instances of this resource + _points = {} + _constraints = {} + _next_key = 0 + # Assigning an empty dict to _material_overrides this way + # instead of assigning in the declaration appears to bypass + # a weird Godot bug where _material_overrides of one shape + # interfere with another + if _material_overrides == null: + _material_overrides = {} + + +func clone(deep: bool = false) -> SS2D_Point_Array: + var copy := SS2D_Point_Array.new() + copy._next_key = _next_key + copy.tessellation_stages = tessellation_stages + copy.tessellation_tolerance = tessellation_tolerance + copy.curve_bake_interval = curve_bake_interval + + if deep: + var new_point_dict := {} + for k: int in _points: + new_point_dict[k] = get_point(k).duplicate(true) + copy._points = new_point_dict + copy._point_order = _point_order.duplicate() + copy._constraints = _constraints.duplicate() + copy._material_overrides = _material_overrides.duplicate() + else: + copy._points = _points + copy._point_order = _point_order + copy._constraints = _constraints + copy._material_overrides = _material_overrides + + return copy + + +## Called by Godot when loading from a saved scene +func _set_points(ps: Dictionary) -> void: + _points = ps + for k: int in _points: + _hook_point(k) + _changed() + + +func set_point_order(po: PackedInt32Array) -> void: + _point_order = po + _changed() + + +func _set_constraints(cs: Dictionary) -> void: + _constraints = cs + + # For backwards compatibility (Array to Vector2i transition) + # FIXME: Maybe remove during the next breaking release + SS2D_IndexTuple.dict_validate(_constraints, TYPE_INT) + + +func set_next_key(i: int) -> void: + _next_key = i + + +func __generate_key(next: int) -> int: + if not is_key_valid(next): + return __generate_key(maxi(next + 1, 0)) + return next + + +## Reserve a key. It will not be generated again. +func reserve_key() -> int: + var next: int = __generate_key(_next_key) + _next_key = next + 1 + return next + + +## Returns next key that would be generated when adding a new point, e.g. when [method add_point] is called. +func get_next_key() -> int: + return __generate_key(_next_key) + + +func is_key_valid(k: int) -> bool: + return k >= 0 and not _points.has(k) + + +## Add a point and insert it at the given index or at the end by default. +## Returns the key of the added point. +func add_point(point: Vector2, idx: int = -1, use_key: int = -1) -> int: +# print("Add Point :: ", point, " | idx: ", idx, " | key: ", use_key, " |") + if use_key == -1 or not is_key_valid(use_key): + use_key = reserve_key() + if use_key == _next_key: + _next_key += 1 + _points[use_key] = SS2D_Point.new(point) + _hook_point(use_key) + _point_order.push_back(use_key) + if idx != -1: + set_point_index(use_key, idx) + _changed() + return use_key + + +## Deprecated. There is no reason to use this function, points can be modified directly. +## @deprecated +func set_point(key: int, value: SS2D_Point) -> void: + if has_point(key): + # FIXME: Should there be a call to remove_constraints() like in remove_point()? Because + # we're technically deleting a point and replacing it with another. + _unhook_point(get_point(key)) + _points[key] = value + _hook_point(key) + _changed() + + +## Connects the changed signal of the given point. Requires that the point exists in _points. +func _hook_point(key: int) -> void: + var p := get_point(key) + if not p.changed.is_connected(_on_point_changed): + p.changed.connect(_on_point_changed.bind(key)) + + +## Disconnects the changed signal of the given point. See also _hook_point(). +func _unhook_point(p: SS2D_Point) -> void: + if not p.changed.is_connected(_on_point_changed): + p.changed.disconnect(_on_point_changed) + + +func is_index_in_range(idx: int) -> bool: + return idx >= 0 and idx < _point_order.size() + + +func get_point_key_at_index(idx: int) -> int: + return _point_order[idx] + + +func get_edge_keys_for_indices(indices: Vector2i) -> Vector2i: + return Vector2i( + get_point_key_at_index(indices.x), + get_point_key_at_index(indices.y) + ) + + +func get_point_at_index(idx: int) -> SS2D_Point: + return _points[_point_order[idx]] + + +## Returns the point with the given key as reference or null if it does not exist. +func get_point(key: int) -> SS2D_Point: + return _points.get(key) + + +func get_point_count() -> int: + return _point_order.size() + + +func get_point_index(key: int) -> int: + if has_point(key): + var idx := 0 + for k in _point_order: + if key == k: + return idx + idx += 1 + return -1 + + +## Reverse order of points in point array.[br] +## I.e. [1, 2, 3, 4] will become [4, 3, 2, 1].[br] +func invert_point_order() -> void: + # Postpone `changed` and disable constraints. + var was_updating: bool = _updating + _updating = true + disable_constraints() + + _point_order.reverse() + # Swap Bezier points. + for p: SS2D_Point in _points.values(): + if p.point_out != p.point_in: + var tmp: Vector2 = p.point_out + p.point_out = p.point_in + p.point_in = tmp + + # Re-enable contraits and emit `changed`. + enable_constraints() + _updating = was_updating + _changed() + + +func set_point_index(key: int, idx: int) -> void: + if not has_point(key): + return + var old_idx: int = get_point_index(key) + if idx < 0 or idx >= _points.size(): + idx = _points.size() - 1 + if idx == old_idx: + return + _point_order.remove_at(old_idx) + _point_order.insert(idx, key) + _changed() + + +func has_point(key: int) -> bool: + return _points.has(key) + + +func get_all_point_keys() -> PackedInt32Array: + # _point_order should contain every single point ONLY ONCE + return _point_order + + +func remove_point(key: int) -> bool: + if has_point(key): +# print("Remove Point :: ", get_point_position(key), " | idx: ", get_point_index(key), " | key: ", key, " |") + remove_constraints(key) + _unhook_point(get_point(key)) + _point_order.remove_at(get_point_index(key)) + _points.erase(key) + _changed() + return true + return false + + +func remove_point_at_index(idx: int) -> void: + remove_point(get_point_key_at_index(idx)) + + +## Remove all points from point array. +func clear() -> void: + _points.clear() + _point_order.clear() + _constraints.clear() + _next_key = 0 + _changed() + + +## point_in controls the edge leading from the previous vertex to this one +func set_point_in(key: int, value: Vector2) -> void: + if has_point(key): + _points[key].point_in = value + _changed() + + +func get_point_in(key: int) -> Vector2: + if has_point(key): + return _points[key].point_in + return Vector2(0, 0) + + +## point_out controls the edge leading from this vertex to the next +func set_point_out(key: int, value: Vector2) -> void: + if has_point(key): + _points[key].point_out = value + _changed() + + +func get_point_out(key: int) -> Vector2: + if has_point(key): + return _points[key].point_out + return Vector2(0, 0) + + +func set_point_position(key: int, value: Vector2) -> void: + if has_point(key): + _points[key].position = value + _changed() + + +func get_point_position(key: int) -> Vector2: + if has_point(key): + return _points[key].position + return Vector2(0, 0) + + +func set_point_properties(key: int, value: SS2D_VertexProperties) -> void: + if has_point(key): + _points[key].properties = value + _changed() + + +func get_point_properties(key: int) -> SS2D_VertexProperties: + var p := get_point(key) + return p.properties if p else null + + +## Returns the corresponding key for a given point or -1 if it does not exist. +func get_key_from_point(p: SS2D_Point) -> int: + for k: int in _points: + if p == _points[k]: + return k + return -1 + + +func _on_point_changed(key: int) -> void: + if _updating_constraints: + _keys_to_update_constraints.push_back(key) + else: + update_constraints(key) + + +## Begin updating the shape.[br] +## Shape mesh and curve will only be updated after [method end_update] is called. +func begin_update() -> void: + _updating = true + + +## End updating the shape.[br] +## Mesh and curve will be updated, if changes were made to points array after +## [method begin_update] was called. +func end_update() -> bool: + var was_dirty := _changed_during_update + _updating = false + _changed_during_update = false + if was_dirty: + update_finished.emit() + return was_dirty + + +## Is shape in the middle of being updated. +## Returns [code]true[/code] after [method begin_update] and before [method end_update]. +func is_updating() -> bool: + return _updating + + +func _changed() -> void: + _point_cache_dirty = true + + emit_changed() + + if _updating: + _changed_during_update = true + else: + update_finished.emit() + +############### +# CONSTRAINTS # +############### + + +func disable_constraints() -> void: + _constraints_enabled = false + + +func enable_constraints() -> void: + _constraints_enabled = true + + +func _update_constraints(src: int) -> void: + if not _constraints_enabled: + return + + var constraints := get_point_constraints_tuples(src) + + for tuple in constraints: + var constraint: CONSTRAINT = SS2D_IndexTuple.dict_get(_constraints, tuple) + + if constraint == CONSTRAINT.NONE: + continue + + var dst: int = SS2D_IndexTuple.get_other_value(tuple, src) + + if constraint & CONSTRAINT.AXIS_X: + set_point_position(dst, Vector2(get_point_position(src).x, get_point_position(dst).y)) + if constraint & CONSTRAINT.AXIS_Y: + set_point_position(dst, Vector2(get_point_position(dst).x, get_point_position(src).y)) + if constraint & CONSTRAINT.CONTROL_POINTS: + set_point_in(dst, get_point_in(src)) + set_point_out(dst, get_point_out(src)) + if constraint & CONSTRAINT.PROPERTIES: + set_point_properties(dst, get_point_properties(src)) + + +## Will mutate points based on constraints.[br] +## Values from Passed key will be used to update constrained points.[br] +func update_constraints(src: int) -> void: + if not has_point(src) or _updating_constraints: + return + + _updating_constraints = true + # Initial pass of updating constraints + _update_constraints(src) + + # Subsequent required passes of updating constraints + while not _keys_to_update_constraints.is_empty(): + var key_set := _keys_to_update_constraints + _keys_to_update_constraints = PackedInt32Array() + for k in key_set: + _update_constraints(k) + + _updating_constraints = false + _changed() + + +## Returns all point constraint that include the given point key. +## Returns a Dictionary[Vector2i, CONSTRAINT]. +func get_point_constraints(key1: int) -> Dictionary: + var constraints := {} + var tuples := get_point_constraints_tuples(key1) + + for t in tuples: + constraints[t] = get_point_constraint(t.x, t.y) + + return constraints + + +## Returns all point constraint tuples that include the given point key. +func get_point_constraints_tuples(key1: int) -> Array[Vector2i]: + return SS2D_IndexTuple.dict_find_partial(_constraints, key1) + + +## Returns the constraint for a pair of keys or CONSTRAINT.NONE if no constraint exists. +func get_point_constraint(key1: int, key2: int) -> CONSTRAINT: + return SS2D_IndexTuple.dict_get(_constraints, Vector2i(key1, key2), CONSTRAINT.NONE) + + +## Set a constraint between two points. If the constraint is NONE, remove_constraint() is called instead. +func set_constraint(key1: int, key2: int, constraint: CONSTRAINT) -> void: + var t := Vector2i(key1, key2) + + if constraint == CONSTRAINT.NONE: + remove_constraint(t) + return + + SS2D_IndexTuple.dict_set(_constraints, t, constraint) + update_constraints(key1) + _changed() + + +## Remove all constraints involving the given point key. +func remove_constraints(key1: int) -> void: + for tuple in get_point_constraints_tuples(key1): + remove_constraint(tuple) + + +## Remove the constraint between the two point indices of the given tuple. +func remove_constraint(point_index_tuple: Vector2i) -> void: + if SS2D_IndexTuple.dict_erase(_constraints, point_index_tuple): + emit_signal("constraint_removed", point_index_tuple.x, point_index_tuple.y) + + +######## +# MISC # +######## +func debug_print() -> void: + for k in get_all_point_keys(): + var pos: Vector2 = get_point_position(k) + var _in: Vector2 = get_point_in(k) + var out: Vector2 = get_point_out(k) + print("%s = P:%s | I:%s | O:%s" % [k, pos, _in, out]) + + +###################### +# MATERIAL OVERRIDES # +###################### +## dict: Dict[Vector2i, SS2D_Material_Edge_Metadata] +func set_material_overrides(dict: Dictionary) -> void: + # For backwards compatibility (Array to Vector2i transition) + # FIXME: Maybe remove during the next breaking release + SS2D_IndexTuple.dict_validate(dict, SS2D_Material_Edge_Metadata) + + if _material_overrides != null: + for old: SS2D_Material_Edge_Metadata in _material_overrides.values(): + _unhook_mat(old) + + _material_overrides = dict + + for tuple: Vector2i in _material_overrides: + _hook_mat(tuple, _material_overrides[tuple]) + + +func has_material_override(tuple: Vector2i) -> bool: + return SS2D_IndexTuple.dict_has(_material_overrides, tuple) + + +func remove_material_override(tuple: Vector2i) -> void: + var old := get_material_override(tuple) + + if old != null: + _unhook_mat(old) + SS2D_IndexTuple.dict_erase(_material_overrides, tuple) + _on_material_override_changed(tuple) + + +func set_material_override(tuple: Vector2i, mat: SS2D_Material_Edge_Metadata) -> void: + var old := get_material_override(tuple) + + if old != null: + if old == mat: + return + else: + _unhook_mat(old) + + _hook_mat(tuple, mat) + SS2D_IndexTuple.dict_set(_material_overrides, tuple, mat) + _on_material_override_changed(tuple) + + +## Returns the material override for the edge defined by the given point index tuple, or null if +## there is no override. +func get_material_override(tuple: Vector2i) -> SS2D_Material_Edge_Metadata: + return SS2D_IndexTuple.dict_get(_material_overrides, tuple) + + +func _hook_mat(tuple: Vector2i, mat: SS2D_Material_Edge_Metadata) -> void: + if not mat.changed.is_connected(_on_material_override_changed): + mat.changed.connect(_on_material_override_changed.bind(tuple)) + + +func _unhook_mat(mat: SS2D_Material_Edge_Metadata) -> void: + if mat.changed.is_connected(_on_material_override_changed): + mat.changed.disconnect(_on_material_override_changed) + + +## Returns a list of index tuples for wich material overrides exist. +func get_material_overrides() -> Array[Vector2i]: + var keys: Array[Vector2i] = [] + keys.assign(_material_overrides.keys()) + return keys + + +func clear_all_material_overrides() -> void: + _material_overrides = {} + + + +## Returns a PackedVector2Array with all points of the shape. +func get_vertices() -> PackedVector2Array: + _update_cache() + return _vertex_cache + + +## Returns a Curve2D representing the shape including bezier handles. +func get_curve() -> Curve2D: + _update_cache() + return _curve + + +## Returns a Curve2D representing the shape, disregarding bezier handles. +func get_curve_no_control_points() -> Curve2D: + _update_cache() + return _curve_no_control_points + + +## Returns a PackedVector2Array with all points +func get_tessellated_points() -> PackedVector2Array: + _update_cache() + return _tesselation_cache + + +func set_tessellation_stages(value: int) -> void: + tessellation_stages = value + _changed() + + +func set_tessellation_tolerance(value: float) -> void: + tessellation_tolerance = value + _changed() + + +func set_curve_bake_interval(f: float) -> void: + curve_bake_interval = f + _curve.bake_interval = f + _changed() + + +func get_tesselation_vertex_mapping() -> SS2D_TesselationVertexMapping: + _update_cache() + return _tess_vertex_mapping + + +func _update_cache() -> void: + # NOTE: Theoretically one could differentiate between vertex list dirty, curve dirty and + # tesselation dirty to never waste any computation time. + # However, 99% of the time, the cache will be dirty due to vertex updates, so we don't bother. + + if not _point_cache_dirty: + return + + var keys := get_all_point_keys() + + _vertex_cache.resize(keys.size()) + _curve.clear_points() + _curve_no_control_points.clear_points() + + for i in keys.size(): + var key := keys[i] + var pos := get_point_position(keys[i]) + + # Vertex cache + _vertex_cache[i] = pos + + # Curves + _curve.add_point(pos, get_point_in(key), get_point_out(key)) + _curve_no_control_points.add_point(pos) + + # Tesselation + # Point 0 will be the same on both the curve points and the vertices + # Point size - 1 will be the same on both the curve points and the vertices + _tesselation_cache = _curve.tessellate(tessellation_stages, tessellation_tolerance) + + if _tesselation_cache.size() >= 2: + _tesselation_cache[0] = _curve.get_point_position(0) + _tesselation_cache[-1] = _curve.get_point_position(_curve.get_point_count() - 1) + + _tess_vertex_mapping.build(_tesselation_cache, _vertex_cache) + + _point_cache_dirty = false + + +func _to_string() -> String: + return "" % [_points.keys(), _point_order] + + +func _on_material_override_changed(tuple: Vector2i) -> void: + material_override_changed.emit(tuple) diff --git a/addons/rmsmartshape/shapes/quad.gd b/addons/rmsmartshape/shapes/quad.gd new file mode 100644 index 0000000..a5d3375 --- /dev/null +++ b/addons/rmsmartshape/shapes/quad.gd @@ -0,0 +1,210 @@ +@tool +extends RefCounted +class_name SS2D_Quad + +enum ORIENTATION { COLINEAR = 0, CCW, CW } +enum CORNER { NONE = 0, OUTER, INNER } + +var pt_a: Vector2 +var pt_b: Vector2 +var pt_c: Vector2 +var pt_d: Vector2 + +var tg_a : Vector2 +var tg_b : Vector2 +var tg_c : Vector2 +var tg_d : Vector2 + +var bn_a : Vector2 +var bn_b : Vector2 +var bn_c : Vector2 +var bn_d : Vector2 + +var texture: Texture2D = null +var color: Color = Color(1.0, 1.0, 1.0, 1.0) + +var is_tapered: bool = false +var ignore_weld_next: bool = false +var flip_texture: bool = false +# Deprecated, should remove control_point_index +var control_point_index: int +var fit_texture := SS2D_Material_Edge.FITMODE.SQUISH_AND_STRETCH + +# Contains value from CORNER enum +var corner: int = 0 + + +# Will return two quads split down the middle of this one +func bisect() -> Array[SS2D_Quad]: + var delta: Vector2 = pt_d - pt_a + var delta_normal := delta.normalized() + var quad_left: SS2D_Quad = duplicate() + var quad_right: SS2D_Quad = duplicate() + var mid_point := Vector2(get_length_average(), 0.0) * delta_normal + quad_left.pt_d = pt_a + mid_point + quad_left.pt_c = pt_b + mid_point + quad_right.pt_a = pt_d - mid_point + quad_right.pt_b = pt_c - mid_point + return [quad_left, quad_right] + + +func _to_string() -> String: + return "[Quad] A:%s B:%s C:%s D:%s | Corner: %s" % [pt_a, pt_b, pt_c, pt_d, corner] + + +func matches_quad(q: SS2D_Quad) -> bool: + return ( + texture == q.texture + and color == q.color + and flip_texture == q.flip_texture + and fit_texture == q.fit_texture + ) + + +func duplicate() -> SS2D_Quad: + var q := SS2D_Quad.new() + q.pt_a = pt_a + q.pt_b = pt_b + q.pt_c = pt_c + q.pt_d = pt_d + + q.texture = texture + q.color = color + + q.flip_texture = flip_texture + q.control_point_index = control_point_index + + q.corner = corner + return q + + +func update_tangents() -> void: + tg_a = (pt_d-pt_a).normalized() + tg_b = (pt_c-pt_b).normalized() + tg_c = tg_b + tg_d = tg_a + + bn_a = (pt_b - pt_a).normalized() + bn_b = bn_a + bn_c = (pt_c - pt_d).normalized() + bn_d = bn_c + + +func _init( + a: Vector2 = Vector2.ZERO, + b: Vector2 = Vector2.ZERO, + c: Vector2 = Vector2.ZERO, + d: Vector2 = Vector2.ZERO, + t: Texture2D = null, + f: bool = false +) -> void: + pt_a = a + pt_b = b + pt_c = c + pt_d = d + + texture = t + flip_texture = f + + +func get_rotation() -> float: + return SS2D_NormalRange.get_angle_from_vector(pt_c - pt_a) + + +## Given three colinear points p, q, r, the function checks if +## point q lies on line segment 'pr'. +func on_segment(p: Vector2, q: Vector2, r: Vector2) -> bool: + return ( + (q.x <= maxf(p.x, r.x)) + and (q.x >= minf(p.x, r.x)) + and (q.y <= maxf(p.y, r.y)) + and (q.y >= minf(p.y, r.y)) + ) + + +## Returns CCW, CW, or colinear.[br] +## see https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ +func get_orientation(a: Vector2, b: Vector2, c: Vector2) -> ORIENTATION: + var val := (float(b.y - a.y) * (c.x - b.x)) - (float(b.x - a.x) * (c.y - b.y)) + if val > 0: + return ORIENTATION.CW + elif val < 0: + return ORIENTATION.CCW + return ORIENTATION.COLINEAR + + +## Return true if line segments p1q1 and p2q2 intersect. +func edges_intersect(p1: Vector2, q1: Vector2, p2: Vector2, q2: Vector2) -> bool: + var o1 := get_orientation(p1, q1, p2) + var o2 := get_orientation(p1, q1, q2) + var o3 := get_orientation(p2, q2, p1) + var o4 := get_orientation(p2, q2, q1) + # General case + if (o1 != o2) and (o3 != o4): + return true + + # Special Cases + # p1 , q1 and p2 are colinear and p2 lies on segment p1q1 + if (o1 == 0) and on_segment(p1, p2, q1): + return true + + # p1 , q1 and q2 are colinear and q2 lies on segment p1q1 + if (o2 == 0) and on_segment(p1, q2, q1): + return true + + # p2 , q2 and p1 are colinear and p1 lies on segment p2q2 + if (o3 == 0) and on_segment(p2, p1, q2): + return true + + # p2 , q2 and q1 are colinear and q1 lies on segment p2q2 + if (o4 == 0) and on_segment(p2, q1, q2): + return true + + return false + + +func self_intersects() -> bool: + return edges_intersect(pt_a, pt_d, pt_b, pt_c) or edges_intersect(pt_a, pt_b, pt_d, pt_c) + + +func render_lines(ci: CanvasItem) -> void: + ci.draw_line(pt_a, pt_b, color) + ci.draw_line(pt_b, pt_c, color) + ci.draw_line(pt_c, pt_d, color) + ci.draw_line(pt_d, pt_a, color) + + +func render_points(rad: float, intensity: float, ci: CanvasItem) -> void: + ci.draw_circle(pt_a, rad, Color(intensity, 0, 0)) + ci.draw_circle(pt_b, rad, Color(0, 0, intensity)) + ci.draw_circle(pt_c, rad, Color(0, intensity, 0)) + ci.draw_circle(pt_d, rad, Color(intensity, 0, intensity)) + + +func get_height_average() -> float: + return (get_height_left() + get_height_right()) / 2.0 + + +func get_height_left() -> float: + return pt_a.distance_to(pt_b) + + +func get_height_right() -> float: + return pt_d.distance_to(pt_c) + + +## Returns the difference in height between the left and right sides. +func get_height_difference() -> float: + return get_height_left() - get_height_right() + + +func get_length_average() -> float: + return (get_length_top() + get_length_bottom()) / 2.0 + + +func get_length_top() -> float: + return pt_d.distance_to(pt_a) + + +func get_length_bottom() -> float: + return pt_c.distance_to(pt_b) diff --git a/addons/rmsmartshape/shapes/shape.gd b/addons/rmsmartshape/shapes/shape.gd new file mode 100644 index 0000000..8dacecf --- /dev/null +++ b/addons/rmsmartshape/shapes/shape.gd @@ -0,0 +1,1894 @@ +@tool +@icon("../assets/closed_shape.png") +extends Node2D +class_name SS2D_Shape + +## Represents the base functionality for all smart shapes. + +# Functions consist of the following categories:[br] +# - Setters / Getters +# - Curve +# - Curve Wrapper +# - Godot +# - Misc +# +# To use search to jump between categories, use the regex: # .+ # + +################ +#-DECLARATIONS-# +################ + +var _dirty: bool = false +var _edges: Array[SS2D_Edge] = [] +var _meshes: Array[SS2D_Mesh] = [] +var _collision_polygon_node: CollisionPolygon2D +# Whether or not the plugin should allow editing this shape +var can_edit: bool = true + +signal points_modified +signal on_dirty_update +signal make_unique_pressed(shape: SS2D_Shape) + +enum ORIENTATION { COLINEAR, CLOCKWISE, C_CLOCKWISE } + +enum CollisionGenerationMethod { + ## Uses the shape curve to generate a collision polygon. Usually this method is accurate enough. + ## For open shapes, a precise method will be used instead, as the fast method is not suitable. + Fast, + ## Uses the edge generation algorithm to create an accurate collision representation that + ## exactly matches the shape's visuals. + ## Depending on the shape's complexity, this method is very expensive. + Precise, +} + +enum CollisionUpdateMode { + ## Only update collisions in editor. If the corresponding CollisionPolygon2D is part of the same + ## scene, it will be saved automatically by Godot, hence no additional regeneration at runtime + ## is necessary, which reduces the loading times. + ## Does not work if the CollisionPolygon2D is part of an instanced scene, as only the scene root + ## node will be saved by Godot. + Editor, + ## Only update collisions during runtime. Improves the shape-editing performance in editor but + ## increases loading times as collision generation is deferred to runtime. + Runtime, + ## Update collisions both in editor and during runtime. This is the default behavior in older + ## SS2D versions. + EditorAndRuntime, +} + +########### +#-EXPORTS-# +########### + +# Execute to refresh shape rendered geometry and textures. +@warning_ignore("unused_private_class_variable") +@export_placeholder("ActionProperty") var _refresh: String = "" : set = _refresh_action +# ActionProperty will add a button to inspector to execute this action. +# When non-empty string is passed into setter, action is considerd executed. + +## Visualize generated quads and edges. +@export var editor_debug: bool = false : set = _set_editor_debug + +## @deprecated +@export_range(1, 512) var curve_bake_interval: float = 20.0 : + set(value): _points.curve_bake_interval = value + get: return _points.curve_bake_interval + +## How to treat color data. See [enum SS2D_Edge.COLOR_ENCODING]. +@export var color_encoding: SS2D_Edge.COLOR_ENCODING = SS2D_Edge.COLOR_ENCODING.COLOR : set = set_color_encoding + +@export_group("Geometry") + +# Execute to make shape point geometry unique (not materials). +@warning_ignore("unused_private_class_variable") +@export_placeholder("ActionProperty") var _make_unique: String = "" : set = _make_unique_action +# ActionProperty will add a button to inspector to execute this action. +# When non-empty string is passed into setter, action is considerd executed. + +## Resource that holds shape point geometry (aka point array). +@export var _points: SS2D_Point_Array : set = set_point_array + +@export_group("Edges") + +@export var flip_edges: bool = false : set = set_flip_edges + +## Enable/disable rendering of the edges. +@export var render_edges: bool = true : set = set_render_edges + +@export_group("Materials") + +## Contains textures and data on how to visualize the shape. +@export var shape_material := SS2D_Material_Shape.new() : set = _set_material + +## Dictionary of (Array of 2 keys) to (SS2D_Material_Edge_Metadata) +## Deprecated, exists for Support of older versions +## @deprecated +@export var material_overrides: Dictionary = {} : set = set_material_overrides + +@export_group("Tesselation") + +## Controls how many subdivisions a curve segment may face before it is considered +## approximate enough. +## @deprecated +@export_range(0, 8, 1) +var tessellation_stages: int = 3 : + set(value): _points.tessellation_stages = value + get: return _points.tessellation_stages + +## Controls how many degrees the midpoint of a segment may deviate from the real +## curve, before the segment has to be subdivided. +## @deprecated +@export_range(0.1, 16.0, 0.1, "or_greater", "or_lesser") +var tessellation_tolerence: float = 6.0 : + set(value): _points.tessellation_tolerance = value + get: return _points.tessellation_tolerance + +@export_group("Collision") + +## Controls which method should be used to generate the collision shape. +@export var collision_generation_method := CollisionGenerationMethod.Fast : set = set_collision_generation_method + +## Controls when to update collisions. +@export var collision_update_mode := CollisionUpdateMode.Editor : set = set_collision_update_mode + +## Controls size of generated polygon for CollisionPolygon2D. +@export_range(0.0, 64.0, 1.0, "or_greater") +var collision_size: float = 32 : set = set_collision_size + +## Controls offset of generated polygon for CollisionPolygon2D. +@export_range(-64.0, 64.0, 1.0, "or_greater", "or_lesser") +var collision_offset: float = 0.0 : set = set_collision_offset + +## NodePath to CollisionPolygon2D node for which polygon data will be generated. +@export_node_path("CollisionPolygon2D") var collision_polygon_node_path: NodePath : set = set_collision_polygon_node_path + +##################### +#-SETTERS / GETTERS-# +##################### + +func set_collision_polygon_node_path(value: NodePath) -> void: + collision_polygon_node_path = value + set_as_dirty() + + if not is_inside_tree(): + return + + if collision_polygon_node_path.is_empty(): + _collision_polygon_node = null + return + + _collision_polygon_node = get_node(collision_polygon_node_path) as CollisionPolygon2D + + if not _collision_polygon_node: + push_error("collision_polygon_node_path should point to proper CollisionPolygon2D node.") + + +func get_collision_polygon_node() -> CollisionPolygon2D: + return _collision_polygon_node + + +func get_point_array() -> SS2D_Point_Array: + return _points + + +func set_point_array(a: SS2D_Point_Array) -> void: + if _points != null: + if _points.is_connected("update_finished", self._points_modified): + _points.disconnect("update_finished", self._points_modified) + if _points.material_override_changed.is_connected(_handle_material_override_change): + _points.material_override_changed.disconnect(_handle_material_override_change) + if a == null: + a = SS2D_Point_Array.new() + _points = a + _points.connect("update_finished", self._points_modified) + _points.material_override_changed.connect(_handle_material_override_change) + clear_cached_data() + set_as_dirty() + notify_property_list_changed() + + +func _refresh_action(value: String) -> void: + if value.length() > 0: + _points_modified() + + +func _make_unique_action(value: String) -> void: + if value.length() > 0: + emit_signal("make_unique_pressed", self) + + +func set_flip_edges(b: bool) -> void: + flip_edges = b + set_as_dirty() + notify_property_list_changed() + + +func set_render_edges(b: bool) -> void: + render_edges = b + set_as_dirty() + notify_property_list_changed() + + +func set_collision_generation_method(value: CollisionGenerationMethod) -> void: + collision_generation_method = value + set_as_dirty() + + +func set_collision_update_mode(value: CollisionUpdateMode) -> void: + collision_update_mode = value + set_as_dirty() + + +func set_collision_size(s: float) -> void: + collision_size = s + set_as_dirty() + notify_property_list_changed() + + +func set_collision_offset(s: float) -> void: + collision_offset = s + set_as_dirty() + notify_property_list_changed() + + +# FIXME: Only used by unit test. +func set_curve(curve: Curve2D) -> void: + _points.begin_update() + _points.clear() + + for i in curve.get_point_count(): + _points.add_point(curve.get_point_position(i)) + + _points.end_update() + + +## Deprecated. Use get_point_array().get_curve() instead. +## @deprecated +func get_curve() -> Curve2D: + return _points.get_curve() + + +func _set_editor_debug(value: bool) -> void: + editor_debug = value + set_as_dirty() + notify_property_list_changed() + + +func set_render_node_light_masks(value: int) -> void: + # TODO: This method should be called when user changes mask in the inspector. + var render_parent: SS2D_Shape_Render = _get_rendering_nodes_parent() + for c: CanvasItem in render_parent.get_children(): + c.light_mask = value + render_parent.light_mask = value + + +func set_render_node_owners(v: bool) -> void: + if Engine.is_editor_hint(): + # Force scene tree update + var render_parent: SS2D_Shape_Render = _get_rendering_nodes_parent() + var new_owner: Node = null + if v: + new_owner = get_tree().edited_scene_root + render_parent.set_owner(new_owner) + + # Set owner recurisvely + for c in render_parent.get_children(): + c.set_owner(new_owner) + + # Force update + var dummy_name := "__DUMMY__" + if has_node(dummy_name): + var n: Node = get_node(dummy_name) + remove_child(n) + n.queue_free() + + var dummy := Node2D.new() + dummy.name = dummy_name + add_child(dummy) + dummy.set_owner(new_owner) + + +func update_render_nodes() -> void: +# set_render_node_owners(editor_debug) + set_render_node_light_masks(light_mask) + + +## Deprecated. Use get_point_array().tessellation_stages instead. +## @deprecated +func set_tessellation_stages(value: int) -> void: + _points.tessellation_stages = value + + +## Deprecated. Use get_point_array().tessellation_tolerance instead. +## @deprecated +func set_tessellation_tolerence(value: float) -> void: + _points.tessellation_tolerance = value + + +## Deprecated. Use get_point_array().curve_bake_interval instead. +## @deprecated +func set_curve_bake_interval(f: float) -> void: + _points.curve_bake_interval = f + + +func set_color_encoding(i: SS2D_Edge.COLOR_ENCODING) -> void: + color_encoding = i + notify_property_list_changed() + set_as_dirty() + + +func _set_material(value: SS2D_Material_Shape) -> void: + if ( + shape_material != null + and shape_material.is_connected("changed", self._handle_material_change) + ): + shape_material.disconnect("changed", self._handle_material_change) + + shape_material = value + if shape_material != null: + shape_material.connect("changed", self._handle_material_change) + set_as_dirty() + notify_property_list_changed() + + +func set_material_overrides(dict: Dictionary) -> void: + material_overrides = {} + if dict == null: + return + _points.set_material_overrides(dict) + + +######### +#-CURVE-# +######### + +## Deprecated. Use get_point_array().get_vertices() instead. +## @deprecated +func get_vertices() -> PackedVector2Array: + return _points.get_vertices() + + +## Deprecated. Use get_point_array().get_tessellated_points() instead. +## @deprecated +func get_tessellated_points() -> PackedVector2Array: + return _points.get_tessellated_points() + + +## Deprecated. Use get_point_array().invert_point_order() instead. +## @deprecated +func invert_point_order() -> void: + _points.invert_point_order() + + +## Deprecated. Use get_point_array().clear() instead. +## @deprecated +func clear_points() -> void: + _points.clear() + + +func adjust_add_point_index(index: int) -> int: + # Don't allow a point to be added after the last point of the closed shape or before the first + if _has_closing_point(): + if index < 0 or (index > get_point_count() - 1): + index = maxi(get_point_count() - 1, 0) + if index < 1: + index = 1 + return index + + +# FIXME: Only unit tests use this. +func add_points(verts: PackedVector2Array, starting_index: int = -1, key: int = -1) -> PackedInt32Array: + starting_index = adjust_add_point_index(starting_index) + var keys := PackedInt32Array() + _points.begin_update() + for i in range(0, verts.size(), 1): + var v: Vector2 = verts[i] + if starting_index != -1: + keys.push_back(_points.add_point(v, starting_index + i, key)) + else: + keys.push_back(_points.add_point(v, starting_index, key)) + _points.end_update() + return keys + + +## Deprecated. Use get_point_array().add_point() instead. +## @deprecated +func add_point(pos: Vector2, index: int = -1, key: int = -1) -> int: + return _points.add_point(pos, adjust_add_point_index(index), key) + + +## Is this shape closed, i.e. last point is constrained to the first point. +func is_shape_closed() -> bool: + if _points.get_point_count() < 4: + return false + return _has_closing_point() + + +## Is this shape not yet closed but should be.[br] +## Returns [code]false[/code] for open shapes.[br] +func can_close() -> bool: + return _points.get_point_count() > 2 and _has_closing_point() == false + + +## Will mutate the _points to ensure this is a closed_shape.[br] +## Last point will be constrained to first point.[br] +## Returns key of a point used to close the shape.[br] +## [param key] suggests which key to use instead of auto-generated.[br] +func close_shape(key: int = -1) -> int: + if not can_close(): + return -1 + + var key_first: int = _points.get_point_key_at_index(0) + var key_last: int = _points.get_point_key_at_index(_points.get_point_count() - 1) + + if get_point_position(key_first) != get_point_position(key_last): + key_last = _points.add_point(_points.get_point_position(key_first), -1, key) + _points.set_constraint(key_first, key_last, SS2D_Point_Array.CONSTRAINT.ALL) + + return key_last + + +## Open shape by removing edge that starts at specified point index. +func open_shape_at_edge(edge_start_idx: int) -> void: + var last_idx: int = get_point_count() - 1 + if is_shape_closed(): + remove_point(get_point_key_at_index(last_idx)) + if edge_start_idx < last_idx: + for i in range(edge_start_idx + 1): + _points.set_point_index(_points.get_point_key_at_index(0), last_idx) + else: + push_warning("Can't open a shape that is not a closed shape.") + + +## Undo shape opening done by [method open_shape_at_edge]. +func undo_open_shape_at_edge(edge_start_idx: int, closing_index: int) -> void: + var last_idx := get_point_count() - 1 + if edge_start_idx < last_idx: + for i in range(edge_start_idx + 1): + _points.set_point_index(_points.get_point_key_at_index(last_idx), 0) + if can_close(): + close_shape(closing_index) + + +func _has_closing_point() -> bool: + if _points.get_point_count() < 2: + return false + var key1: int = _points.get_point_key_at_index(0) + var key2: int = _points.get_point_key_at_index(_points.get_point_count() - 1) + return _points.get_point_constraint(key1, key2) == SS2D_Point_Array.CONSTRAINT.ALL + + +## Deprecated. Use get_point_array().begin_update() instead. +## @deprecated +func begin_update() -> void: + _points.begin_update() + + +## Deprecated. Use get_point_array().end_update() instead. +## @deprecated +func end_update() -> void: + _points.end_update() + + +## Deprecated. Use get_point_array().is_updating() instead. +## @deprecated +func is_updating() -> bool: + return _points.is_updating() + + +## Deprecated. Use get_point_array().get_next_key() instead. +## @deprecated +func get_next_key() -> int: + return _points.get_next_key() + + +## Deprecated. Use get_point_array().reserve_key() instead. +## @deprecated +func reserve_key() -> int: + return _points.reserve_key() + + +func _points_modified() -> void: + set_as_dirty() + points_modified.emit() + + +func _is_array_index_in_range(a: Array, i: int) -> bool: + return a.size() > i and i >= 0; + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func is_index_in_range(idx: int) -> bool: + return _points.is_index_in_range(idx) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func set_point_position(key: int, pos: Vector2) -> void: + _points.set_point_position(key, pos) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func remove_point(key: int) -> void: + _points.remove_point(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func remove_point_at_index(idx: int) -> void: + _points.remove_point_at_index(idx) + + +func clone(clone_point_array: bool = true) -> SS2D_Shape: + var copy := SS2D_Shape.new() + copy.transform = transform + copy.modulate = modulate + copy.shape_material = shape_material + copy.editor_debug = editor_debug + copy.flip_edges = flip_edges + copy.editor_debug = editor_debug + copy.collision_size = collision_size + copy.collision_offset = collision_offset + #copy.material_overrides = s.material_overrides + copy.name = get_name().rstrip("0123456789") + if clone_point_array: + copy.set_point_array(_points.clone(true)) + return copy + + +####################### +#-POINT ARRAY WRAPPER-# +####################### + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func has_point(key: int) -> bool: + return _points.has_point(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_all_point_keys() -> PackedInt32Array: + return _points.get_all_point_keys() + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_key_at_index(idx: int) -> int: + return _points.get_point_key_at_index(idx) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_at_index(idx: int) -> SS2D_Point: + return _points.get_point_at_index(idx) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_index(key: int) -> int: + return _points.get_point_index(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func set_point_in(key: int, v: Vector2) -> void: + _points.set_point_in(key, v) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func set_point_out(key: int, v: Vector2) -> void: + _points.set_point_out(key, v) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_in(key: int) -> Vector2: + return _points.get_point_in(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_out(key: int) -> Vector2: + return _points.get_point_out(key) + + +func get_closest_point(to_point: Vector2) -> Vector2: + return _points.get_curve().get_closest_point(to_point) + + +func get_closest_point_straight_edge(to_point: Vector2) -> Vector2: + return _points.get_curve_no_control_points().get_closest_point(to_point) + + +func get_closest_offset_straight_edge(to_point: Vector2) -> float: + return _points.get_curve_no_control_points().get_closest_offset(to_point) + + +func get_closest_offset(to_point: Vector2) -> float: + return _points.get_curve().get_closest_offset(to_point) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func disable_constraints() -> void: + _points.disable_constraints() + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func enable_constraints() -> void: + _points.enable_constraints() + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_count() -> int: + return _points.get_point_count() + + +func get_edges() -> Array[SS2D_Edge]: + return _edges + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_position(key: int) -> Vector2: + return _points.get_point_position(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point(key: int) -> SS2D_Point: + return _points.get_point(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_constraints(key: int) -> Dictionary: + return _points.get_point_constraints(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_constraint(key1: int, key2: int) -> SS2D_Point_Array.CONSTRAINT: + return _points.get_point_constraint(key1, key2) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func set_constraint(key1: int, key2: int, c: SS2D_Point_Array.CONSTRAINT) -> void: + _points.set_constraint(key1, key2, c) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func set_point(key: int, value: SS2D_Point) -> void: + _points.set_point(key, value) + + +## Deprecated. Use respective property in get_point_array().get_point_properties() instead. +## @deprecated +func set_point_width(key: int, w: float) -> void: + _points.get_point_properties(key).width = w + + +## Deprecated. Use respective property in get_point_array().get_point_properties() instead. +## @deprecated +func get_point_width(key: int) -> float: + return _points.get_point_properties(key).width + + +## Deprecated. Use respective property in get_point_array().get_point_properties() instead. +## @deprecated +func set_point_texture_index(key: int, tex_idx: int) -> void: + _points.get_point_properties(key).texture_idx = tex_idx + + +## Deprecated. Use respective property in get_point_array().get_point_properties() instead. +## @deprecated +func get_point_texture_index(key: int) -> int: + return _points.get_point_properties(key).texture_idx + + +## Deprecated. Use respective property in get_point_array().get_point_properties() instead. +## @deprecated +func set_point_texture_flip(key: int, flip: bool) -> void: + _points.get_point_properties(key).flip = flip + + +## Deprecated. Use respective property in get_point_array().get_point_properties() instead. +## @deprecated +func get_point_texture_flip(key: int) -> bool: + return _points.get_point_properties(key).flip + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func get_point_properties(key: int) -> SS2D_VertexProperties: + return _points.get_point_properties(key) + + +## Deprecated. Use respective function in get_point_array() instead. +## @deprecated +func set_point_properties(key: int, properties: SS2D_VertexProperties) -> void: + _points.set_point_properties(key, properties) + + +######### +#-GODOT-# +######### + +func _init() -> void: + set_point_array(SS2D_Point_Array.new()) + + +func _enter_tree() -> void: + # Call this again because get_node() only works when the node is inside the tree + set_collision_polygon_node_path(collision_polygon_node_path) + + # Handle material changes if scene is (re-)entered (e.g. after switching to another) + if shape_material != null: + if not shape_material.is_connected("changed", self._handle_material_change): + shape_material.connect("changed", self._handle_material_change) + + +func _get_rendering_nodes_parent() -> SS2D_Shape_Render: + var render_parent_name := "_SS2D_RENDER" + var render_parent: SS2D_Shape_Render = null + if not has_node(render_parent_name): + render_parent = SS2D_Shape_Render.new() + render_parent.name = render_parent_name + render_parent.light_mask = light_mask + add_child(render_parent) + if editor_debug and Engine.is_editor_hint(): + render_parent.set_owner(get_tree().edited_scene_root) + else: + render_parent = get_node(render_parent_name) + return render_parent + + +# Returns true if the children have changed. +func _create_rendering_nodes(size: int) -> bool: + var render_parent: SS2D_Shape_Render = _get_rendering_nodes_parent() + var child_count := render_parent.get_child_count() + var delta := size - child_count + #print ("%s | %s | %s" % [child_count, size, delta]) + # Size and child_count match + if delta == 0: + return false + + # More children than needed + elif delta < 0: + var children := render_parent.get_children() + for i in range(0, abs(delta), 1): + var child: SS2D_Shape_Render = children[child_count - 1 - i] + render_parent.remove_child(child) + child.set_mesh(null) + child.queue_free() + + # Fewer children than needed + elif delta > 0: + for i in range(0, delta, 1): + var child := SS2D_Shape_Render.new() + child.light_mask = light_mask + render_parent.add_child(child) + if editor_debug and Engine.is_editor_hint(): + child.set_owner(get_tree().edited_scene_root) + return true + + +# Takes an array of SS2D_Meshes and returns a flat array of SS2D_Meshes. +# If a SS2D_Mesh has n meshes, will return an array contain n SS2D_Mesh. +# The returned array will consist of SS2D_Meshes each with a SS2D_Mesh::meshes array of size 1. +func _draw_flatten_meshes_array(meshes: Array[SS2D_Mesh]) -> Array[SS2D_Mesh]: + var flat_meshes: Array[SS2D_Mesh] = [] + for ss2d_mesh in meshes: + for godot_mesh in ss2d_mesh.meshes: + var new_mesh: SS2D_Mesh = ss2d_mesh.duplicate(false) + var arr: Array[ArrayMesh] = [godot_mesh] + new_mesh.meshes = arr + flat_meshes.push_back(new_mesh) + return flat_meshes + + +func _draw() -> void: + var flat_meshes: Array[SS2D_Mesh] = _draw_flatten_meshes_array(_meshes) + _create_rendering_nodes(flat_meshes.size()) + var render_parent: SS2D_Shape_Render = _get_rendering_nodes_parent() + var render_nodes := render_parent.get_children() + #print ("RENDER | %s" % [render_nodes]) + #print ("MESHES | %s" % [flat_meshes]) + for i in range(0, flat_meshes.size(), 1): + var m: SS2D_Mesh = flat_meshes[i] + var render_node: SS2D_Shape_Render = render_nodes[i] + render_node.set_mesh(m) + + if editor_debug and Engine.is_editor_hint(): + _draw_debug(SS2D_Shape.sort_by_z_index(_edges)) + + +func _draw_debug(edges: Array[SS2D_Edge]) -> void: + for e in edges: + for q in e.quads: + q.render_lines(self) + + var _range := range(0, e.quads.size(), 1) + for i: int in _range: + var q := e.quads[i] + if not (i % 3 == 0): + continue + q.render_points(3, 0.5, self) + + for i: int in _range: + var q := e.quads[i] + if not ((i + 1) % 3 == 0): + continue + q.render_points(2, 0.75, self) + + for i: int in _range: + var q := e.quads[i] + if not ((i + 2) % 3 == 0): + continue + q.render_points(1, 1.0, self) + + +func _exit_tree() -> void: + if shape_material != null: + if shape_material.is_connected("changed", self._handle_material_change): + shape_material.disconnect("changed", self._handle_material_change) + + +############ +#-GEOMETRY-# +############ + + +func should_flip_edges() -> bool: + if is_shape_closed(): + return (are_points_clockwise() == flip_edges) + else: + return flip_edges + + +func _generate_collision_points_precise() -> PackedVector2Array: + var points := PackedVector2Array() + var num_points: int = _points.get_point_count() + if num_points < 2: + return points + + var csize: float = 1.0 if is_shape_closed() else collision_size + var indices := PackedInt32Array(range(num_points)) + var edge_data := SS2D_IndexMap.new(indices, null) + var edge: SS2D_Edge = _build_edge_with_material(edge_data, collision_offset - 1.0, csize) + _weld_quad_array(edge.quads, false) + + if is_shape_closed(): + var first_quad: SS2D_Quad = edge.quads[0] + var last_quad: SS2D_Quad = edge.quads.back() + SS2D_Shape.weld_quads(last_quad, first_quad) + + if not edge.quads.is_empty(): + # Top edge (typically point A unless corner quad) + for quad in edge.quads: + if quad.corner == SS2D_Quad.CORNER.NONE: + points.push_back(quad.pt_a) + elif quad.corner == SS2D_Quad.CORNER.OUTER: + points.push_back(quad.pt_d) + elif quad.corner == SS2D_Quad.CORNER.INNER: + pass + + if not is_shape_closed(): + # Right Edge (point d, the first or final quad will never be a corner) + points.push_back(edge.quads[edge.quads.size() - 1].pt_d) + + # Bottom Edge (typically point c) + for quad_index in edge.quads.size(): + var quad: SS2D_Quad = edge.quads[edge.quads.size() - 1 - quad_index] + if quad.corner == SS2D_Quad.CORNER.NONE: + points.push_back(quad.pt_c) + elif quad.corner == SS2D_Quad.CORNER.OUTER: + pass + elif quad.corner == SS2D_Quad.CORNER.INNER: + points.push_back(quad.pt_b) + + # Left Edge (point b) + points.push_back(edge.quads[0].pt_b) + return points + + +func _generate_collision_points_fast() -> PackedVector2Array: + return _points.get_tessellated_points() + + +func bake_collision() -> void: + if not _collision_polygon_node: + return + + if collision_update_mode == CollisionUpdateMode.Editor and not Engine.is_editor_hint() \ + or collision_update_mode == CollisionUpdateMode.Runtime and Engine.is_editor_hint(): + return + + var generated_points: PackedVector2Array + + if collision_generation_method == CollisionGenerationMethod.Fast and is_shape_closed(): + generated_points = _generate_collision_points_fast() + else: + generated_points = _generate_collision_points_precise() + + var xform := _collision_polygon_node.get_global_transform().affine_inverse() * get_global_transform() + _collision_polygon_node.polygon = xform * generated_points + + +func cache_edges() -> void: + if shape_material != null and render_edges: + _edges = _build_edges(shape_material, _points.get_vertices()) + else: + _edges = [] + + +func cache_meshes() -> void: + if shape_material != null: + _meshes = _build_meshes(SS2D_Shape.sort_by_z_index(_edges)) + + +func _build_meshes(edges: Array[SS2D_Edge]) -> Array[SS2D_Mesh]: + var meshes: Array[SS2D_Mesh] = [] + if _points == null or _points.get_point_count() < 2: + return meshes + + var produced_fill_mesh := false + for e in edges: + if not produced_fill_mesh and is_shape_closed(): + if e.z_index > shape_material.fill_texture_z_index: + # Produce Fill Meshes + for m in _build_fill_mesh(_points.get_tessellated_points(), shape_material): + meshes.push_back(m) + produced_fill_mesh = true + + # Produce edge Meshes + for m in e.get_meshes(color_encoding): + meshes.push_back(m) + if not produced_fill_mesh and is_shape_closed(): + for m in _build_fill_mesh(_points.get_tessellated_points(), shape_material): + meshes.push_back(m) + produced_fill_mesh = true + return meshes + + +func _build_fill_mesh(points: PackedVector2Array, s_mat: SS2D_Material_Shape) -> Array[SS2D_Mesh]: + var meshes: Array[SS2D_Mesh] = [] + if s_mat == null: + return meshes + if s_mat.fill_textures.is_empty(): + return meshes + if points.size() < 3: + return meshes + + var tex: Texture2D = null + if s_mat.fill_textures.is_empty(): + return meshes + tex = s_mat.fill_textures[0] + var tex_size: Vector2 = tex.get_size() + + # Points to produce the fill mesh + var fill_points: PackedVector2Array = PackedVector2Array() + var polygons: Array[PackedVector2Array] = Geometry2D.offset_polygon( + PackedVector2Array(points), tex_size.x * s_mat.fill_mesh_offset + ) + points = polygons[0] + fill_points.resize(points.size()) + for i in range(points.size()): + fill_points[i] = points[i] + + # Produce the fill mesh + var fill_tris: PackedInt32Array = Geometry2D.triangulate_polygon(fill_points) + if fill_tris.is_empty(): + push_error("'%s': Couldn't Triangulate shape" % name) + return [] + + var st: SurfaceTool + st = SurfaceTool.new() + st.begin(Mesh.PRIMITIVE_TRIANGLES) + + var uv_points := _get_uv_points(points, s_mat, tex_size) + + for i in range(0, fill_tris.size() - 1, 3): + st.set_color(Color.WHITE) + _add_uv_to_surface_tool(st, uv_points[fill_tris[i]]) + st.add_vertex(Vector3(points[fill_tris[i]].x, points[fill_tris[i]].y, 0)) + st.set_color(Color.WHITE) + _add_uv_to_surface_tool(st, uv_points[fill_tris[i + 1]]) + st.add_vertex(Vector3(points[fill_tris[i + 1]].x, points[fill_tris[i + 1]].y, 0)) + st.set_color(Color.WHITE) + _add_uv_to_surface_tool(st, uv_points[fill_tris[i + 2]]) + st.add_vertex(Vector3(points[fill_tris[i + 2]].x, points[fill_tris[i + 2]].y, 0)) + st.index() + st.generate_normals() + st.generate_tangents() + var array_mesh := st.commit() + var flip := false + var trans := Transform2D() + var mesh_data := SS2D_Mesh.new(tex, flip, trans, [array_mesh]) + mesh_data.material = s_mat.fill_mesh_material + mesh_data.z_index = s_mat.fill_texture_z_index + mesh_data.z_as_relative = true + mesh_data.show_behind_parent = s_mat.fill_texture_show_behind_parent + meshes.push_back(mesh_data) + + return meshes + + +func _get_uv_points( + points: PackedVector2Array, + s_material: SS2D_Material_Shape, + tex_size: Vector2 +) -> PackedVector2Array: + var transformation: Transform2D = global_transform + + # If relative position ... undo translation from global_transform + if not s_material.fill_texture_absolute_position: + transformation = transformation.translated(-global_position) + + # Scale + var tex_scale := 1.0 / s_material.fill_texture_scale + transformation = transformation.scaled(Vector2(tex_scale, tex_scale)) + + # If relative rotation ... undo rotation from global_transform + if not s_material.fill_texture_absolute_rotation: + transformation = transformation.rotated(-global_rotation) + + # Rotate the desired extra amount + transformation = transformation.rotated(-deg_to_rad(s_material.fill_texture_angle_offset)) + + # Shift the desired amount (adjusted so it's scale independent) + transformation = transformation.translated(-s_material.fill_texture_offset / s_material.fill_texture_scale) + + # Convert local space to UV + transformation = transformation.scaled(Vector2(1 / tex_size.x, 1 / tex_size.y)) + + return transformation * points + + +## Given three colinear points p, q, r, the function checks if point q lies on line segment 'pr'.[br] +## See: https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ +static func on_segment(p: Vector2, q: Vector2, r: Vector2) -> bool: + return ( + q.x <= maxf(p.x, r.x) + and q.x >= minf(p.x, r.x) + and q.y <= maxf(p.y, r.y) + and q.y >= minf(p.y, r.y) + ) + + +static func get_points_orientation(points: PackedVector2Array) -> ORIENTATION: + var point_count: int = points.size() + if point_count < 3: + return ORIENTATION.COLINEAR + + var sum := 0.0 + for i in point_count: + var pt := points[i] + var pt2 := points[(i + 1) % point_count] + sum += pt.cross(pt2) + + # Colinear + if sum == 0.0: + return ORIENTATION.COLINEAR + + # Clockwise + if sum > 0.0: + return ORIENTATION.CLOCKWISE + return ORIENTATION.C_CLOCKWISE + + +func are_points_clockwise() -> bool: + var points: PackedVector2Array = _points.get_tessellated_points() + var orient: ORIENTATION = SS2D_Shape.get_points_orientation(points) + return orient == ORIENTATION.CLOCKWISE + + +func _add_uv_to_surface_tool(surface_tool: SurfaceTool, uv: Vector2) -> void: + surface_tool.set_uv(uv) + surface_tool.set_uv2(uv) + + +static func build_quad_from_two_points( + pt: Vector2, + pt_next: Vector2, + tex: Texture2D, + width: float, + flip_x: bool, + flip_y: bool, + first_point: bool, + last_point: bool, + custom_offset: float, + custom_extends: float, + fit_texture: SS2D_Material_Edge.FITMODE +) -> SS2D_Quad: + # Create new quad + var quad := SS2D_Quad.new() + quad.texture = tex + quad.color = Color(1.0, 1.0, 1.0, 1.0) + quad.flip_texture = flip_x + quad.fit_texture = fit_texture + + # Calculate the normal + var delta: Vector2 = pt_next - pt + var delta_normal := delta.normalized() + var normal_direction := Vector2(delta.y, -delta.x).normalized() + var normal_length: float = width + var normal_with_magnitude: Vector2 = normal_direction * (normal_length * 0.5) + if flip_y: + normal_with_magnitude *= -1 + var offset: Vector2 = normal_with_magnitude * custom_offset + + # If is first or last point, extend past the normal boundary by 'custom_extends' pixels + if first_point: + pt -= (delta_normal * custom_extends) + if last_point: + pt_next += (delta_normal * custom_extends) + + ############################################## + # QUAD POINT ILLUSTRATION # # + ############################################## + # LENGTH # + # <--------------> # + # pt_a -> O--------O <- pt_d ▲ # + # | | | # + # | pt | | WIDTH # + # | | | # + # pt_b -> O--------O <- pt_c ▼ # + ############################################## + ############################################## + + quad.pt_a = pt + normal_with_magnitude + offset + quad.pt_b = pt - normal_with_magnitude + offset + quad.pt_c = pt_next - normal_with_magnitude + offset + quad.pt_d = pt_next + normal_with_magnitude + offset + + return quad + + +## Builds a corner quad. [br] +## - [param pt] is the center of this corner quad. [br] +## - [param width] will scale the quad in line with the next point (one dimension). [br] +## - [param prev_width] will scale the quad in line with the prev point (hte other dimension). [br] +## - [param custom_scale] will scale the quad in both dimensions. [br] +static func build_quad_corner( + pt_next: Vector2, + pt: Vector2, + pt_prev: Vector2, + pt_width: float, + pt_prev_width: float, + flip_edges_: bool, + corner_status: int, + texture: Texture2D, + size: Vector2, + custom_scale: float, + custom_offset: float +) -> SS2D_Quad: + var new_quad := SS2D_Quad.new() + + # :BUILD PLAN: + # OUTER CORNER INNER CORNER + # + # 0------A-----D 0-----0 + # | 1 : 2 | | 3 : + # 0......B.....C | : + # : | 0-------D-----A + # : 3 | | 1 | 2 : + # 0-----0 0.......C.....B + # + # 1-previous, 2-current, 3-next (points) + + var quad_size: Vector2 = size * 0.5 + var dir_12: Vector2 = (pt - pt_prev).normalized() + var dir_23: Vector2 = (pt_next - pt).normalized() + var offset_12: Vector2 = dir_12 * custom_scale * pt_width * quad_size + var offset_23: Vector2 = dir_23 * custom_scale * pt_prev_width * quad_size + var custom_offset_13: Vector2 = (dir_12 - dir_23) * custom_offset * quad_size + + if flip_edges_: + offset_12 *= -1 + offset_23 *= -1 + custom_offset_13 *= -1 + + # Should we mirror internal ABCD vertices relative to quad center. + # - Historically, quad internal vertices are flipped for inner corner quads (see illustration). + # - Value: 1.0 for outer, -1.0 for inner (mirrored). + var mirror: float = -1.0 if corner_status == SS2D_Quad.CORNER.INNER else 1.0 + + new_quad.pt_a = pt + (-offset_12 - offset_23 + custom_offset_13) * mirror + new_quad.pt_b = pt + (-offset_12 + offset_23 + custom_offset_13) * mirror + new_quad.pt_c = pt + (offset_12 + offset_23 + custom_offset_13) * mirror + new_quad.pt_d = pt + (offset_12 - offset_23 + custom_offset_13) * mirror + + new_quad.corner = corner_status + new_quad.texture = texture + + return new_quad + + +func _get_width_for_tessellated_point( + points: PackedVector2Array, + t_idx: int +) -> float: + var v_idx := _points.get_tesselation_vertex_mapping().tess_to_vertex_index(t_idx) + var v_idx_next := SS2D_PluginFunctionality.get_next_point_index(v_idx, points) + var w1: float = _points.get_point_properties(_points.get_point_key_at_index(v_idx)).width + var w2: float = _points.get_point_properties(_points.get_point_key_at_index(v_idx_next)).width + var ratio: float = get_ratio_from_tessellated_point_to_vertex(t_idx) + return lerp(w1, w2, ratio) + + +## Mutates two quads to be welded.[br] +## Returns the midpoint of the weld.[br] +static func weld_quads(a: SS2D_Quad, b: SS2D_Quad, custom_scale: float = 1.0) -> Vector2: + var midpoint := Vector2(0, 0) + # If both quads are not a corner + if a.corner == SS2D_Quad.CORNER.NONE and b.corner == SS2D_Quad.CORNER.NONE: + var needed_height: float = (a.get_height_average() + b.get_height_average()) / 2.0 + + var pt1: Vector2 = (a.pt_d + b.pt_a) * 0.5 + var pt2: Vector2 = (a.pt_c + b.pt_b) * 0.5 + + midpoint = Vector2(pt1 + pt2) / 2.0 + var half_line: Vector2 = (pt2 - midpoint).normalized() * needed_height * custom_scale / 2.0 + + if half_line != Vector2.ZERO: + pt2 = midpoint + half_line + pt1 = midpoint - half_line + + a.pt_d = pt1 + a.pt_c = pt2 + b.pt_a = pt1 + b.pt_b = pt2 + + # If either quad is a corner + else: + if a.corner == SS2D_Quad.CORNER.OUTER: + b.pt_a = a.pt_c + b.pt_b = a.pt_b + midpoint = (b.pt_a + b.pt_b) / 2.0 + + elif a.corner == SS2D_Quad.CORNER.INNER: + b.pt_a = a.pt_d + b.pt_b = a.pt_a + midpoint = (b.pt_a + b.pt_b) / 2.0 + + elif b.corner == SS2D_Quad.CORNER.OUTER: + a.pt_d = b.pt_a + a.pt_c = b.pt_b + midpoint = (a.pt_d + a.pt_c) / 2.0 + + elif b.corner == SS2D_Quad.CORNER.INNER: + a.pt_d = b.pt_d + a.pt_c = b.pt_c + midpoint = (a.pt_d + a.pt_c) / 2.0 + + return midpoint + + +func _weld_quad_array( + quads: Array[SS2D_Quad], weld_first_and_last: bool, start_idx: int = 0 +) -> void: + if quads.is_empty(): + return + + for index in range(start_idx, quads.size() - 1, 1): + var this_quad: SS2D_Quad = quads[index] + var next_quad: SS2D_Quad = quads[index + 1] + if not this_quad.ignore_weld_next: + SS2D_Shape.weld_quads(this_quad, next_quad) + # If this quad self_intersects after welding, it's likely very small and can be removed + # Usually happens when welding a very large and very small quad together + # Generally looks better when simply being removed + # + # When welding and using different widths, quads can look a little weird + # This is because they are no longer parallelograms + # This is a tough problem to solve + # See http://reedbeta.com/blog/quadrilateral-interpolation-part-1/ + if this_quad.self_intersects(): + quads.remove_at(index) + if index < quads.size(): + var new_index: int = maxi(index - 1, 0) + _weld_quad_array(quads, weld_first_and_last, new_index) + return + + if weld_first_and_last: + if not quads[-1].ignore_weld_next: + SS2D_Shape.weld_quads(quads[-1], quads[0]) + + +func _merge_index_maps(imaps: Array[SS2D_IndexMap], verts: PackedVector2Array) -> Array[SS2D_IndexMap]: + if not is_shape_closed(): + return imaps + # See if any edges have both the first (0) and last idx (size) + # Merge them into one if so + var final_edges: Array[SS2D_IndexMap] = imaps.duplicate() + var edges_by_material: Dictionary = SS2D_IndexMap.index_map_array_sort_by_object(final_edges) + # Erase any with null material + edges_by_material.erase(null) + for mat: Variant in edges_by_material: + var edge_first_idx: SS2D_IndexMap = null + var edge_last_idx: SS2D_IndexMap = null + for e: SS2D_IndexMap in edges_by_material[mat]: + if e.indicies.has(0): + edge_first_idx = e + if e.indicies.has(verts.size()-1): + edge_last_idx = e + if edge_first_idx != null and edge_last_idx != null: + break + if edge_first_idx != null and edge_last_idx != null: + if edge_first_idx == edge_last_idx: + pass + else: + final_edges.erase(edge_last_idx) + final_edges.erase(edge_first_idx) + var indicies := edge_last_idx.indicies + edge_first_idx.indicies + var merged_edge := SS2D_IndexMap.new(indicies, mat) + final_edges.push_back(merged_edge) + return final_edges + + +func _build_edges(s_mat: SS2D_Material_Shape, verts: PackedVector2Array) -> Array[SS2D_Edge]: + var edges: Array[SS2D_Edge] = [] + if s_mat == null: + return edges + + var index_maps: Array[SS2D_IndexMap] = _get_meta_material_index_mapping(s_mat, verts) + var overrides: Array[SS2D_IndexMap] = SS2D_Shape.get_meta_material_index_mapping_for_overrides(s_mat, _points) + + # Remove the override indicies from the default index_maps + for override in overrides: + var old_to_new_imaps := {} + for index_map in index_maps: + var new_imaps: Array[SS2D_IndexMap] = index_map.remove_edges(override.indicies) + old_to_new_imaps[index_map] = new_imaps + for k: SS2D_IndexMap in old_to_new_imaps: + index_maps.erase(k) + for new_imap: SS2D_IndexMap in old_to_new_imaps[k]: + index_maps.push_back(new_imap) + + # Merge index maps + index_maps = _merge_index_maps(index_maps, verts) + + # Add the overrides to the mappings to be rendered + for override in overrides: + index_maps.push_back(override) + + # Edge case for web so it doesn't use thread + if OS.get_name() != "Web": + var threads: Array[Thread] = [] + for index_map in index_maps: + var thread := Thread.new() + var args := [index_map, s_mat.render_offset, 0.0] + var priority := 2 + thread.start(self._build_edge_with_material_thread_wrapper.bind(args), priority) + threads.push_back(thread) + for thread in threads: + var new_edge: SS2D_Edge = thread.wait_to_finish() + edges.push_back(new_edge) + + else: + # Process index_maps sequentially for web exports (probably slower than thread) + for index_map in index_maps: + var args = [index_map, s_mat.render_offset, 0.0] + var new_edge: SS2D_Edge = _build_edge_with_material_thread_wrapper(args) + edges.push_back(new_edge) + + return edges + + +## Will return an array of SS2D_IndexMaps.[br] +## Each index map will map a set of indicies to a meta_material.[br] +static func get_meta_material_index_mapping_for_overrides( + _s_material: SS2D_Material_Shape, pa: SS2D_Point_Array +) -> Array[SS2D_IndexMap]: + var mappings: Array[SS2D_IndexMap] = [] + for key_tuple in pa.get_material_overrides(): + var indices := SS2D_IndexTuple.sort_ascending(Vector2i(pa.get_point_index(key_tuple.x), pa.get_point_index(key_tuple.y))) + var m: SS2D_Material_Edge_Metadata = pa.get_material_override(key_tuple) + var new_mapping := SS2D_IndexMap.new(PackedInt32Array([ indices.x, indices.y ]), m) + mappings.push_back(new_mapping) + + return mappings + + +## Will return a dictionary containing array of SS2D_IndexMap.[br] +## Each element in the array is a contiguous sequence of indicies that fit inside +## the meta_material's normalrange.[br] +func _get_meta_material_index_mapping( + s_material: SS2D_Material_Shape, verts: PackedVector2Array +) -> Array[SS2D_IndexMap]: + return SS2D_Shape.get_meta_material_index_mapping(s_material, verts, is_shape_closed()) + + +static func get_meta_material_index_mapping( + s_material: SS2D_Material_Shape, verts: PackedVector2Array, wrap_around: bool +) -> Array[SS2D_IndexMap]: + var final_edges: Array[SS2D_IndexMap] = [] + var edge_building: Dictionary = {} # Dict[SS2D_Material_Edge_Metadata, SS2D_IndexMap] + for idx in range(0, verts.size() - 1, 1): + var idx_next: int = SS2D_PluginFunctionality.get_next_point_index(idx, verts, wrap_around) + var pt: Vector2 = verts[idx] + var pt_next: Vector2 = verts[idx_next] + var delta: Vector2 = pt_next - pt + var normal := Vector2(delta.y, -delta.x).normalized() + + # Get all valid edge_meta_materials for this normal value + var edge_meta_materials := s_material.get_edge_meta_materials(normal) + + # Append to existing edges being built. Add new ones if needed + for e in edge_meta_materials: + var imap: SS2D_IndexMap = edge_building.get(e) + + # Is exsiting, append + if imap: + if not idx_next in imap.indicies: + imap.indicies.push_back(idx_next) + # Isn't existing, make a new mapping + else: + edge_building[e] = SS2D_IndexMap.new([idx, idx_next], e) + + # Closeout and stop building edges that are no longer viable + for e: SS2D_Material_Edge_Metadata in edge_building.keys(): + if not edge_meta_materials.has(e): + final_edges.push_back(edge_building[e]) + edge_building.erase(e) + + # Closeout all edge building + for e: SS2D_Material_Edge_Metadata in edge_building.keys(): + final_edges.push_back(edge_building[e]) + + return final_edges + +######## +#-MISC-# +######## +func _handle_material_change() -> void: + set_as_dirty() + + +func _handle_material_override_change(_tuple: Vector2i) -> void: + set_as_dirty() + + +func set_as_dirty() -> void: + if not _dirty: + call_deferred("_on_dirty_update") + _dirty = true + + +static func sort_by_z_index(a: Array) -> Array: + a.sort_custom(Callable(SS2D_Common_Functions, "sort_z")) + return a + + +static func sort_by_int_ascending(a: Array) -> Array: + a.sort_custom(Callable(SS2D_Common_Functions, "sort_int_ascending")) + return a + + +func clear_cached_data() -> void: + _edges = [] + _meshes = [] + + +func _on_dirty_update() -> void: + if _dirty: + force_update() + + +func force_update() -> void: + update_render_nodes() + clear_cached_data() + + bake_collision() + if get_point_count() >= 2: + cache_edges() + cache_meshes() + queue_redraw() + _dirty = false + + +## Returns a float between 0.0 and 1.0.[br] +## 0.0 means that this tessellated point is at the same position as the vertex.[br] +## 0.5 means that this tessellated point is half-way between this vertex and the next.[br] +## 0.999 means that this tessellated point is basically at the next vertex.[br] +## 1.0 isn't going to happen; If a tess point is at the same position as a vert, it gets a ratio of 0.0.[br] +func get_ratio_from_tessellated_point_to_vertex(t_point_idx: int) -> float: + # Index of the starting vertex + var point_idx := _points.get_tesselation_vertex_mapping().tess_to_vertex_index(t_point_idx) + # Index of the first tesselated point with the same vertex + var tess_point_first_idx: int = _points.get_tesselation_vertex_mapping().vertex_to_tess_indices(point_idx)[0] + # The total tessellated points with the same vertex + var tess_point_count := _points.get_tesselation_vertex_mapping().vertex_to_tess_indices(point_idx).size() + # The index of the passed t_point_idx relative to the starting vert + var tess_index_count := t_point_idx - tess_point_first_idx + return tess_index_count / float(tess_point_count) + + +func debug_print_points() -> void: + _points.debug_print() + + +################### +#-EDGE GENERATION-# +################### + +## Get Number of TessPoints from the start and end indicies of the index_map parameter. +func _edge_data_get_tess_point_count(index_map: SS2D_IndexMap) -> int: + ## TODO Test this function + var count: int = 0 + for i in range(index_map.indicies.size() - 1): + var this_idx := index_map.indicies[i] + var next_idx := index_map.indicies[i + 1] + if this_idx > next_idx: + count += 1 + continue + var this_t_idx: int = _points.get_tesselation_vertex_mapping().vertex_to_tess_indices(this_idx)[0] + var next_t_idx: int = _points.get_tesselation_vertex_mapping().vertex_to_tess_indices(next_idx)[0] + var delta: int = next_t_idx - this_t_idx + count += delta + return count + + +## This function determines if a corner quad should be generated.[br] +## if so, OUTER or INNER? [br] +## - The conditions deg < 0 and flip_edges are used to determine this.[br] +## - These conditions works correctly so long as the points are in Clockwise order.[br] +static func edge_should_generate_corner(pt_prev: Vector2, pt: Vector2, pt_next: Vector2, flip_edges_: bool) -> SS2D_Quad.CORNER: + var generate_corner := SS2D_Quad.CORNER.NONE + var ab: Vector2 = pt - pt_prev + var bc: Vector2 = pt_next - pt + var dot_prod: float = ab.dot(bc) + var determinant: float = (ab.x * bc.y) - (ab.y * bc.x) + var angle := atan2(determinant, dot_prod) + # This angle has a range of 360 degrees + # Is between 180 and - 180 + var deg := rad_to_deg(angle) + var corner_range := 10.0 + var corner_angle := 90.0 + if absf(deg) >= corner_angle - corner_range and absf(deg) <= corner_angle + corner_range: + var inner := false + if deg < 0: + inner = true + if flip_edges_: + inner = not inner + if inner: + generate_corner = SS2D_Quad.CORNER.INNER + else: + generate_corner = SS2D_Quad.CORNER.OUTER + return generate_corner + + +func _edge_generate_corner( + pt_prev: Vector2, + pt: Vector2, + pt_next: Vector2, + width_prev: float, + width: float, + size: float, + edge_material: SS2D_Material_Edge, + texture_idx: int, + c_scale: float, + c_offset: float +) -> SS2D_Quad: + var generate_corner := SS2D_Shape.edge_should_generate_corner(pt_prev, pt, pt_next, flip_edges) + if generate_corner == SS2D_Quad.CORNER.NONE: + return null + var corner_texture: Texture2D = null + if edge_material != null: + if generate_corner == SS2D_Quad.CORNER.OUTER: + corner_texture = edge_material.get_texture_corner_outer(texture_idx) + elif generate_corner == SS2D_Quad.CORNER.INNER: + corner_texture = edge_material.get_texture_corner_inner(texture_idx) + var corner_quad: SS2D_Quad = SS2D_Shape.build_quad_corner( + pt_next, + pt, + pt_prev, + width, + width_prev, + flip_edges, + generate_corner, + corner_texture, + Vector2(size, size), + c_scale, + c_offset + ) + return corner_quad + + +func _imap_contains_all_points(imap: SS2D_IndexMap, verts: PackedVector2Array) -> bool: + return imap.indicies[0] == 0 and imap.indicies[-1] == verts.size()-1 + + +func _is_edge_contiguous(imap: SS2D_IndexMap, verts: PackedVector2Array) -> bool: + if not is_shape_closed(): + return false + return _imap_contains_all_points(imap, verts) + + +# Will construct an SS2D_Edge from the passed parameters. +# index_map must be a SS2D_IndexMap with a SS2D_Material_Edge_Metadata for an object +# the indicies used by index_map should match up with the get_verticies() indicies +# +# default_quad_width is the quad width used if a texture isn't available +# +# c_offset is the magnitude to offset all of the points +# the direction of the offset is the surface_normal +func _build_edge_with_material( + index_map: SS2D_IndexMap, c_offset: float, default_quad_width: float +) -> SS2D_Edge: + var verts_t: PackedVector2Array = _points.get_tessellated_points() + var verts: PackedVector2Array = _points.get_vertices() + var edge := SS2D_Edge.new() + var is_edge_contiguous: bool = _is_edge_contiguous(index_map, verts) + edge.wrap_around = is_edge_contiguous + if not index_map.is_valid(): + return edge + var c_scale := 1.0 + var c_extends := 0.0 + + var edge_material_meta: SS2D_Material_Edge_Metadata = null + var edge_material: SS2D_Material_Edge = null + if index_map.object != null: + edge_material_meta = index_map.object + if edge_material_meta == null: + return edge + if not edge_material_meta.render: + return edge + edge_material = edge_material_meta.edge_material + if edge_material == null: + return edge + c_offset += edge_material_meta.offset + + edge.z_index = edge_material_meta.z_index + edge.z_as_relative = edge_material_meta.z_as_relative + edge.material = edge_material_meta.edge_material.material + + var first_idx: int = index_map.indicies[0] + var last_idx: int = index_map.indicies[-1] + var first_idx_t: int = _points.get_tesselation_vertex_mapping().vertex_to_tess_indices(first_idx)[0] + var last_idx_t: int = _points.get_tesselation_vertex_mapping().vertex_to_tess_indices(last_idx)[-1] + edge.first_point_key = _points.get_point_key_at_index(first_idx) + edge.last_point_key = _points.get_point_key_at_index(last_idx) + + var should_flip := should_flip_edges() + + # How many tessellated points are contained within this index map? + var tess_point_count: int = _edge_data_get_tess_point_count(index_map) + + var i := 0 + var texture_idx := 0 + var sharp_taper_next: SS2D_Quad = null + var is_not_corner: bool = true + var taper_sharp: bool = edge_material_meta != null and edge_material_meta.taper_sharp_corners + while i < tess_point_count: + var tess_idx: int = (first_idx_t + i) % verts_t.size() + var tess_idx_next: int = SS2D_PluginFunctionality.get_next_unique_point_idx(tess_idx, verts_t, true) + var tess_idx_prev: int = SS2D_PluginFunctionality.get_previous_unique_point_idx(tess_idx, verts_t, true) + + # set next_point_delta + # next_point_delta is the number of tess_pts from + # the current tess_pt to the next unique tess_pt + # unique meaning it has a different position from the current tess_pt + var next_point_delta := 0 + for j in range(verts_t.size()): + if ((tess_idx + j) % verts_t.size()) == tess_idx_next: + next_point_delta = j + break + + var vert_idx: int = _points.get_tesselation_vertex_mapping().tess_to_vertex_index(tess_idx) + var vert_key: int = get_point_key_at_index(vert_idx) + var pt: Vector2 = verts_t[tess_idx] + var pt_next: Vector2 = verts_t[tess_idx_next] + var pt_prev: Vector2 = verts_t[tess_idx_prev] + var flip_x: bool = get_point_texture_flip(vert_key) + + var width_scale: float = _get_width_for_tessellated_point(verts, tess_idx) + var is_first_point: bool = (vert_idx == first_idx) and not is_edge_contiguous + var is_last_point: bool = (vert_idx == last_idx - 1) and not is_edge_contiguous + var is_first_tess_point: bool = (tess_idx == first_idx_t) and not is_edge_contiguous + var is_last_tess_point: bool = (tess_idx == last_idx_t - 1) and not is_edge_contiguous + + var tex: Texture2D = null + var tex_size := Vector2(default_quad_width, default_quad_width) + var fitmode := SS2D_Material_Edge.FITMODE.SQUISH_AND_STRETCH + if edge_material != null: + if edge_material.randomize_texture: + texture_idx = randi() % edge_material.textures.size() + else : + texture_idx = get_point_texture_index(vert_key) + tex = edge_material.get_texture(texture_idx) + tex_size = tex.get_size() + fitmode = edge_material.fit_mode + # Exit if we have an edge material defined but no texture to render + if tex == null: + i += next_point_delta + continue + + var new_quad: SS2D_Quad = SS2D_Shape.build_quad_from_two_points( + pt, + pt_next, + tex, + width_scale * c_scale * tex_size.y, + flip_x, + should_flip, + is_first_point, + is_last_point, + c_offset, + c_extends, + fitmode + ) + var new_quads: Array[SS2D_Quad] = [] + new_quads.push_back(new_quad) + + # Corner Quad + if edge_material != null and edge_material.use_corner_texture: + if tess_idx != first_idx_t or is_edge_contiguous: + var prev_width: float = _get_width_for_tessellated_point(verts, tess_idx_prev) + var q: SS2D_Quad = _edge_generate_corner( + pt_prev, + pt, + pt_next, + prev_width, + width_scale, + tex_size.y, + edge_material, + texture_idx, + c_scale, + c_offset + ) + if q != null: + new_quads.push_front(q) + is_not_corner = false + else: + is_not_corner = true + + # Taper Quad + # Bear in mind, a point can be both first AND last + # Consider an edge that consists of two points (one edge) + # This first point is used to generate the quad; it is both first and last + var did_taper_left: bool = false + var did_taper_right: bool = false + if is_first_tess_point and edge_material != null and edge_material.use_taper_texture: + did_taper_left = true + var taper_quad := _taper_quad(new_quad, edge_material, texture_idx, false, false) + if taper_quad != null: + new_quads.push_front(taper_quad) + if is_last_tess_point and edge_material != null and edge_material.use_taper_texture: + did_taper_right = true + var taper_quad := _taper_quad(new_quad, edge_material, texture_idx, true, false) + if taper_quad != null: + new_quads.push_back(taper_quad) + + # Taper sharp corners + if taper_sharp: + var ang_threshold := PI * 0.5 + if sharp_taper_next != null and is_not_corner: + var taper := _taper_quad(sharp_taper_next, edge_material, texture_idx, true, true) + if taper != null: + taper.ignore_weld_next = true + edge.quads.push_back(taper) + else: + sharp_taper_next.ignore_weld_next = true + sharp_taper_next = null + var vert := verts[vert_idx] + var prev_vert := verts[wrapi(vert_idx - 1, 0, verts.size() - 1)] + var next_vert := verts[wrapi(vert_idx + 1, 0, verts.size() - 1)] + if not did_taper_left and is_not_corner: + var ang_from := prev_vert.angle_to_point(vert) + var ang_to := vert.angle_to_point(next_vert) + var ang_dif := angle_difference(ang_from, ang_to) + if absf(ang_dif) > ang_threshold: + var taper := _taper_quad(new_quad, edge_material, texture_idx, false, true) + if taper != null: + new_quads.push_front(taper) + if not did_taper_right: + var next_next_vert := verts[wrapi(vert_idx + 2, 0, verts.size() - 1)] + var ang_from := vert.angle_to_point(next_vert) + var ang_to := next_vert.angle_to_point(next_next_vert) + var ang_dif := angle_difference(ang_from, ang_to) + if absf(ang_dif) > ang_threshold: + sharp_taper_next = new_quad + + # Final point for closed shapes fix + # Corner quads aren't always correctly when the corner is between final and first pt + if is_last_point and is_edge_contiguous: + var idx_mid: int = verts_t.size() - 1 + var idx_next: int = SS2D_PluginFunctionality.get_next_unique_point_idx(idx_mid, verts_t, true) + var idx_prev: int = SS2D_PluginFunctionality.get_previous_unique_point_idx(idx_mid, verts_t, true) + var p_p: Vector2 = verts_t[idx_prev] + var p_m: Vector2 = verts_t[idx_mid] + var p_n: Vector2 = verts_t[idx_next] + var w_p: float = _get_width_for_tessellated_point(verts, idx_prev) + var w_m: float = _get_width_for_tessellated_point(verts, idx_mid) + var q: SS2D_Quad = _edge_generate_corner( + p_p, p_m, p_n, w_p, w_m, tex_size.y, edge_material, texture_idx, c_scale, c_offset + ) + if q != null: + new_quads.push_back(q) + + # Add new quads to edge + for q in new_quads: + edge.quads.push_back(q) + i += next_point_delta + + # leftover final taper for the last sharp corner if required + if taper_sharp: + if sharp_taper_next != null and edge.quads[0].corner == SS2D_Quad.CORNER.NONE: + var taper := _taper_quad(sharp_taper_next, edge_material, texture_idx, true, true) + if taper != null: + taper.ignore_weld_next = true + edge.quads.push_back(taper) + else: + sharp_taper_next.ignore_weld_next = true + sharp_taper_next = null + + if edge_material_meta != null: + if edge_material_meta.weld: + _weld_quad_array(edge.quads, edge.wrap_around) + + return edge + + +# get the appropriate tapering texture based on direction and whether the current taper is a sharp +# corner taper or normal material edge taper +func get_taper_tex(edge_mat: SS2D_Material_Edge, tex_idx: int, facing_right: bool, corner_taper: bool) -> Texture2D: + if facing_right: + if corner_taper: + return edge_mat.get_texture_taper_corner_right(tex_idx) + else: + return edge_mat.get_texture_taper_right(tex_idx) + else: + if corner_taper: + return edge_mat.get_texture_taper_corner_left(tex_idx) + else: + return edge_mat.get_texture_taper_left(tex_idx) + + +func _taper_quad( + quad: SS2D_Quad, + edge_mat: SS2D_Material_Edge, + tex_idx: int, + facing_right: bool, + corner_taper: bool +) -> SS2D_Quad: + var taper_texture: Texture2D = get_taper_tex(edge_mat, tex_idx, facing_right, corner_taper) + if taper_texture != null: + var taper_size: Vector2 = taper_texture.get_size() + var fit: bool = absf(taper_size.x) <= quad.get_length_average() + if fit: + var taper_quad := quad.duplicate() + taper_quad.corner = SS2D_Quad.CORNER.NONE + taper_quad.texture = taper_texture + var delta_normal: Vector2 = (taper_quad.pt_d - taper_quad.pt_a).normalized() + var offset: Vector2 = delta_normal * taper_size + if facing_right: + taper_quad.pt_a = taper_quad.pt_d - offset + taper_quad.pt_b = taper_quad.pt_c - offset + quad.pt_d = taper_quad.pt_a + quad.pt_c = taper_quad.pt_b + else: + taper_quad.pt_d = taper_quad.pt_a + offset + taper_quad.pt_c = taper_quad.pt_b + offset + quad.pt_a = taper_quad.pt_d + quad.pt_b = taper_quad.pt_c + + taper_quad.is_tapered = true + return taper_quad + # If a new taper quad doesn't fit, re-texture the new_quad + else: + quad.is_tapered = true + quad.texture = taper_texture + return null + + +func _build_edge_with_material_thread_wrapper(args: Array) -> SS2D_Edge: + return _build_edge_with_material(args[0], args[1], args[2]) diff --git a/addons/rmsmartshape/shapes/shape_anchor.gd b/addons/rmsmartshape/shapes/shape_anchor.gd new file mode 100644 index 0000000..f6492b8 --- /dev/null +++ b/addons/rmsmartshape/shapes/shape_anchor.gd @@ -0,0 +1,208 @@ +@tool +@icon("../assets/Anchor.svg") +extends Node2D +class_name SS2D_Shape_Anchor + +const DEBUG_DRAW_LINE_LENGTH := 128.0 + +@export var shape_path: NodePath : set = set_shape_path +@export var shape_point_index: int = 0 : set = set_shape_point_index +@export_range (0.0, 1.0) var shape_point_offset: float = 0.0 : set = set_shape_point_offset +@export_range (0, 3.14) var child_rotation: float = 3.14 : set = set_child_rotation +@export var use_shape_scale: bool = false : set = set_use_shape_scale + +@export var debug_draw: bool = false : set = set_debug_draw + +var cached_shape_transform: Transform2D = Transform2D.IDENTITY +var shape: SS2D_Shape = null + + +########### +#-SETTERS-# +########### + +func set_shape_path(value: NodePath) -> void: + # Assign path value + shape_path = value + set_shape() + + notify_property_list_changed() + refresh() + + +func set_shape() -> void: + # Disconnect old shape + if shape != null: + disconnect_shape(shape) + + # Set shape if path is valid and connect + shape = null + if has_node(shape_path): + var new_node: Node = get_node(shape_path) + if not new_node is SS2D_Shape: + push_error("Shape Path isn't a valid subtype of SS2D_Shape! Aborting...") + return + shape = new_node + connect_shape(shape) + shape_point_index = get_shape_index_range(shape, shape_point_index) + + +func get_shape_index_range(s: SS2D_Shape, idx: int) -> int: + var point_count: int = s.get_point_count() + # Subtract 2; + # 'point_count' is out of bounds; subtract 1 + # cannot use final idx as starting point_index; subtract another 1 + var final_idx: int = point_count - 2 + if idx < 0: + idx = final_idx + idx = idx % (final_idx + 1) + return idx + + +func set_shape_point_index(value: int) -> void: + if value == shape_point_index: + return + + if shape == null: + shape_point_index = value + return + + shape_point_index = get_shape_index_range(shape, value) + #notify_property_list_changed() + refresh() + + +func set_shape_point_offset(value: float) -> void: + shape_point_offset = value + #notify_property_list_changed() + refresh() + + +func set_use_shape_scale(value: bool) -> void: + use_shape_scale = value + #notify_property_list_changed() + refresh() + + +func set_child_rotation(value: float) -> void: + child_rotation = value + #notify_property_list_changed() + refresh() + + +func set_debug_draw(v: bool) -> void: + debug_draw = v + #notify_property_list_changed() + refresh() + + +########## +#-EVENTS-# +########## +func _process(_delta: float) -> void: + if shape == null: + set_shape() + return + if shape.is_queued_for_deletion(): + return + if shape.get_global_transform() != cached_shape_transform: + cached_shape_transform = shape.get_global_transform() + refresh() + + +func _monitored_node_leaving() -> void: + pass + + +func _handle_point_change() -> void: + refresh() + + +######### +#LOGIC-# +######### +func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2: + var q0 := p0.lerp(p1, t) + var q1 := p1.lerp(p2, t) + var q2 := p2.lerp(p3, t) + + var r0 := q0.lerp(q1, t) + var r1 := q1.lerp(q2, t) + + var s := r0.lerp(r1, t) + return s + + +func disconnect_shape(s: SS2D_Shape) -> void: + s.disconnect("points_modified", self._handle_point_change) + s.disconnect("tree_exiting", self._monitored_node_leaving) + + +func connect_shape(s: SS2D_Shape) -> void: + s.connect("points_modified", self._handle_point_change) + s.connect("tree_exiting", self._monitored_node_leaving) + + +func refresh() -> void: + if shape == null: + return + if not is_instance_valid(shape): + return + if shape.is_queued_for_deletion(): + disconnect_shape(shape) + shape = null + return + + # Subtract one, cannot use final point as starting index +# var point_count: int = shape.get_point_count() - 1 + + var pt_a_index: int = shape_point_index + var pt_b_index: int = shape_point_index + 1 + var pt_a_key: int = shape.get_point_key_at_index(pt_a_index) + var pt_b_key: int = shape.get_point_key_at_index(pt_b_index) + + var pt_a: Vector2 = shape.global_transform * shape.get_point_position(pt_a_key) + var pt_b: Vector2 = shape.global_transform * shape.get_point_position(pt_b_key) + + var pt_a_handle: Vector2 + var pt_b_handle: Vector2 + + var n_pt: Vector2 + var n_pt_a: Vector2 + var n_pt_b: Vector2 + + var angle := 0.0 + + pt_a_handle = shape.global_transform * ( + shape.get_point_position(pt_a_key) + shape.get_point_out(pt_a_key) + ) + pt_b_handle = shape.global_transform * ( + shape.get_point_position(pt_b_key) + shape.get_point_in(pt_b_key) + ) + + # If this segment uses no bezier curve, use linear interpolation instead + if pt_a_handle != pt_a or pt_b_handle != pt_b: + n_pt = _cubic_bezier(pt_a, pt_a_handle, pt_b_handle, pt_b, shape_point_offset) + else: + n_pt = pt_a.lerp(pt_b, shape_point_offset) + + n_pt_a = _cubic_bezier( + pt_a, pt_a_handle, pt_b_handle, pt_b, clampf(shape_point_offset - 0.1, 0.0, 1.0) + ) + n_pt_b = _cubic_bezier( + pt_a, pt_a_handle, pt_b_handle, pt_b, clampf(shape_point_offset + 0.1, 0.0, 1.0) + ) + + angle = atan2(n_pt_a.y - n_pt_b.y, n_pt_a.x - n_pt_b.x) + + self.global_transform = Transform2D(angle + child_rotation, n_pt) + + if use_shape_scale: + self.scale = shape.scale + + queue_redraw() + + +func _draw() -> void: + if Engine.is_editor_hint() and debug_draw: + draw_line(Vector2.ZERO, Vector2(0, -DEBUG_DRAW_LINE_LENGTH), self.modulate) diff --git a/addons/rmsmartshape/shapes/shape_closed.gd b/addons/rmsmartshape/shapes/shape_closed.gd new file mode 100644 index 0000000..1fa3ae7 --- /dev/null +++ b/addons/rmsmartshape/shapes/shape_closed.gd @@ -0,0 +1,58 @@ +@tool +@icon("../assets/closed_shape.png") +extends SS2D_Shape +class_name SS2D_Shape_Closed +## DEPRECATED: Use [SS2D_Shape] instead. +## @deprecated + +# UNUSED FUNCTIONS: + +## Returns true if line segment 'a1a2' and 'b1b2' intersect.[br] +## Find the four orientations needed for general and special cases.[br] +#func do_edges_intersect(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2) -> bool: +# var o1: int = get_points_orientation([a1, a2, b1]) +# var o2: int = get_points_orientation([a1, a2, b2]) +# var o3: int = get_points_orientation([b1, b2, a1]) +# var o4: int = get_points_orientation([b1, b2, a2]) +# +# # General case +# if o1 != o2 and o3 != o4: +# return true +# +# # Special Cases +# # a1, a2 and b1 are colinear and b1 lies on segment p1q1 +# if o1 == ORIENTATION.COLINEAR and on_segment(a1, b1, a2): +# return true +# +# # a1, a2 and b2 are colinear and b2 lies on segment p1q1 +# if o2 == ORIENTATION.COLINEAR and on_segment(a1, b2, a2): +# return true +# +# # b1, b2 and a1 are colinear and a1 lies on segment p2q2 +# if o3 == ORIENTATION.COLINEAR and on_segment(b1, a1, b2): +# return true +# +# # b1, b2 and a2 are colinear and a2 lies on segment p2q2 +# if o4 == ORIENTATION.COLINEAR and on_segment(b1, a2, b2): +# return true +# +# # Doesn't fall in any of the above cases +# return false + + +#static func get_edge_intersection(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2) -> Variant: +# var den: float = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y) +# +# # Check if lines are parallel or coincident +# if den == 0: +# return null +# +# var ua: float = ((b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x)) / den +# var ub: float = ((a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x)) / den +# +# if ua < 0 or ub < 0 or ua > 1 or ub > 1: +# return null +# +# return Vector2(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)) + + diff --git a/addons/rmsmartshape/shapes/shape_open.gd b/addons/rmsmartshape/shapes/shape_open.gd new file mode 100644 index 0000000..76d11d3 --- /dev/null +++ b/addons/rmsmartshape/shapes/shape_open.gd @@ -0,0 +1,7 @@ +@tool +@icon("../assets/open_shape.png") +extends SS2D_Shape +class_name SS2D_Shape_Open + +## DEPRECATED: Use [SS2D_Shape] instead. +## @deprecated diff --git a/addons/rmsmartshape/shapes/shape_render.gd b/addons/rmsmartshape/shapes/shape_render.gd new file mode 100644 index 0000000..dfa43b7 --- /dev/null +++ b/addons/rmsmartshape/shapes/shape_render.gd @@ -0,0 +1,31 @@ +@tool +extends Node2D +class_name SS2D_Shape_Render + +## Node is used to render shape geometry. + +var mesh: SS2D_Mesh = null : set = set_mesh + + +func set_mesh(m: SS2D_Mesh) -> void: + mesh = m + if m != null: + if m.force_no_tiling: + texture_repeat = CanvasItem.TEXTURE_REPEAT_DISABLED + else: + texture_repeat = CanvasItem.TEXTURE_REPEAT_PARENT_NODE + material = mesh.material + z_index = mesh.z_index + z_as_relative = mesh.z_as_relative + show_behind_parent = mesh.show_behind_parent + else: + material = null + z_index = 0 + z_as_relative = true + show_behind_parent = false + queue_redraw() + + +func _draw() -> void: + if mesh != null: + mesh.render(self) diff --git a/addons/rmsmartshape/strings.gd b/addons/rmsmartshape/strings.gd new file mode 100644 index 0000000..9195b79 --- /dev/null +++ b/addons/rmsmartshape/strings.gd @@ -0,0 +1,16 @@ +@tool +extends Resource +class_name SS2D_Strings + +const EN_TOOLTIP_CREATE_VERT := "Create Vertices Tool\nLMB: Add vertex\nShift+LMB: Create Bezier curve using Control Points\nControl+LMB: Set Pivot Point\nLMB+Drag: Move Point\nLMB: Click on edge to split\nRMB: Delete Point" +const EN_TOOLTIP_EDIT_VERT := "Edit Vertices Tool\nShift+LMB: Create Bezier curve using Control Points\nAlt+LMB: Add vertex\nControl+LMB: Set Pivot Point\nLMB+Drag: Move Point\nLMB: Click on edge to split\nRMB: Delete Point" +const EN_TOOLTIP_EDIT_EDGE := "Edit Edge Tool\nSelect each edge's properties" +const EN_TOOLTIP_CUT_EDGE := "Cut Edge Tool\nRemoves the edge between vertices, opening the shape.\nCan be used to split the shape into two separate shapes." +const EN_TOOLTIP_FREEHAND := "Freehand Tool\nHold LMB: Add vertices along the drag line.\nControl+LMB: remove vertices inside the circle while dragging.\nShift+Mousewheel: Change circle size for drawing.\nShift+Control+Mousewheel: Change circle size for eraser." +const EN_TOOLTIP_PIVOT := "Set Pivot Tool\nSets the origin of the shape" +const EN_TOOLTIP_CENTER_PIVOT := "Center Pivot\nSets the origin to the centroid of the shape" +const EN_TOOLTIP_COLLISION := "Collision Tool\nAdds a static body parent and collision polygon sibling\nUse this to auto-generate collision nodes" +const EN_TOOLTIP_SNAP := "Snapping Options" +const EN_TOOLTIP_MORE_OPTIONS := "More Options" + +const EN_OPTIONS_DEFER_MESH_UPDATES := "Defer Mesh Updates" diff --git a/addons/rmsmartshape/vertex_properties.gd b/addons/rmsmartshape/vertex_properties.gd new file mode 100644 index 0000000..ab53688 --- /dev/null +++ b/addons/rmsmartshape/vertex_properties.gd @@ -0,0 +1,38 @@ +@tool +extends Resource +class_name SS2D_VertexProperties + +@export var texture_idx: int : set = set_texture_idx +@export var flip: bool : set = set_flip +@export var width: float : set = set_width + + +func set_texture_idx(i: int) -> void: + texture_idx = i + emit_changed() + + +func set_flip(b: bool) -> void: + flip = b + emit_changed() + + +func set_width(w: float) -> void: + width = w + emit_changed() + + +func _init() -> void: + texture_idx = 0 + flip = false + width = 1.0 + + +func equals(other: SS2D_VertexProperties) -> bool: + if other.flip != flip: + return false + if other.texture_idx != texture_idx: + return false + if other.width != width: + return false + return true diff --git a/examples/edge_shader/edge_shader.gdshader b/examples/edge_shader/edge_shader.gdshader new file mode 100644 index 0000000..a2def85 --- /dev/null +++ b/examples/edge_shader/edge_shader.gdshader @@ -0,0 +1,16 @@ +shader_type canvas_item; +/** + * This shader fixes normal textures looking wrong when used with SmartShape2D. + * See: https://github.com/SirRamEsq/SmartShape2D/blob/master/addons/rmsmartshape/documentation/Normals.md + */ + +varying mat2 NORMAL_MATRIX; + +void vertex() { + NORMAL_MATRIX = mat2(COLOR.rg, COLOR.ba) * 2.0 - mat2(vec2(1.0), vec2(1.0)); +} + +void fragment() { + NORMAL.xy = NORMAL_MATRIX * NORMAL.xy; + COLOR = texture(TEXTURE, UV); +} diff --git a/examples/edge_shader/material_edge_shader.tres b/examples/edge_shader/material_edge_shader.tres new file mode 100644 index 0000000..0bda5c4 --- /dev/null +++ b/examples/edge_shader/material_edge_shader.tres @@ -0,0 +1,6 @@ +[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://dpec5hjpflmri"] + +[ext_resource type="Shader" path="res://examples/edge_shader/edge_shader.gdshader" id="1_akl0v"] + +[resource] +shader = ExtResource("1_akl0v") diff --git a/examples/sharp_corner_tapering/assets/mat_edge_metal.tres b/examples/sharp_corner_tapering/assets/mat_edge_metal.tres new file mode 100644 index 0000000..aacc59b --- /dev/null +++ b/examples/sharp_corner_tapering/assets/mat_edge_metal.tres @@ -0,0 +1,22 @@ +[gd_resource type="Resource" script_class="SS2D_Material_Edge" load_steps=7 format=3 uid="uid://c8q2nrxnbopbb"] + +[ext_resource type="Script" path="res://addons/rmsmartshape/materials/edge_material.gd" id="1_lbfns"] +[ext_resource type="Texture2D" uid="uid://b3aqa3bj1osvp" path="res://examples/sharp_corner_tapering/assets/tex_metal_edge.png" id="2_bmjh3"] +[ext_resource type="Texture2D" uid="uid://bobwi3r6aiiqg" path="res://examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png" id="3_n1i1s"] +[ext_resource type="Texture2D" uid="uid://bc34cuc50as8f" path="res://examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png" id="4_ml2u7"] +[ext_resource type="Texture2D" uid="uid://jc3g5qsmnpdd" path="res://examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png" id="5_244mn"] +[ext_resource type="Texture2D" uid="uid://cdnfaf3bslk38" path="res://examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png" id="6_1c2na"] + +[resource] +script = ExtResource("1_lbfns") +textures = Array[Texture2D]([ExtResource("2_bmjh3")]) +textures_corner_outer = Array[Texture2D]([ExtResource("4_ml2u7")]) +textures_corner_inner = Array[Texture2D]([ExtResource("3_n1i1s")]) +textures_taper_left = Array[Texture2D]([]) +textures_taper_right = Array[Texture2D]([]) +textures_taper_corner_left = Array[Texture2D]([ExtResource("5_244mn")]) +textures_taper_corner_right = Array[Texture2D]([ExtResource("6_1c2na")]) +randomize_texture = false +use_corner_texture = true +use_taper_texture = false +fit_mode = 0 diff --git a/examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png b/examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png new file mode 100644 index 0000000..6adcf60 Binary files /dev/null and b/examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png differ diff --git a/examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png.import b/examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png.import new file mode 100644 index 0000000..835818b --- /dev/null +++ b/examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bobwi3r6aiiqg" +path="res://.godot/imported/tex_metal_corner_inner.png-cdfd7b81c523bc5bb5c7863d7307007e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png" +dest_files=["res://.godot/imported/tex_metal_corner_inner.png-cdfd7b81c523bc5bb5c7863d7307007e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png b/examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png new file mode 100644 index 0000000..1dcc88a Binary files /dev/null and b/examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png differ diff --git a/examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png.import b/examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png.import new file mode 100644 index 0000000..10587db --- /dev/null +++ b/examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bc34cuc50as8f" +path="res://.godot/imported/tex_metal_corner_outer.png-6ea6aecab21ef2cdfa9c20ae8389ee06.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png" +dest_files=["res://.godot/imported/tex_metal_corner_outer.png-6ea6aecab21ef2cdfa9c20ae8389ee06.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/sharp_corner_tapering/assets/tex_metal_edge.png b/examples/sharp_corner_tapering/assets/tex_metal_edge.png new file mode 100644 index 0000000..cab72a4 Binary files /dev/null and b/examples/sharp_corner_tapering/assets/tex_metal_edge.png differ diff --git a/examples/sharp_corner_tapering/assets/tex_metal_edge.png.import b/examples/sharp_corner_tapering/assets/tex_metal_edge.png.import new file mode 100644 index 0000000..4e4b107 --- /dev/null +++ b/examples/sharp_corner_tapering/assets/tex_metal_edge.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3aqa3bj1osvp" +path="res://.godot/imported/tex_metal_edge.png-5aaf6e458197a953a7afeee0e270fea4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/sharp_corner_tapering/assets/tex_metal_edge.png" +dest_files=["res://.godot/imported/tex_metal_edge.png-5aaf6e458197a953a7afeee0e270fea4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/sharp_corner_tapering/assets/tex_metal_fill.png b/examples/sharp_corner_tapering/assets/tex_metal_fill.png new file mode 100644 index 0000000..d8a5619 Binary files /dev/null and b/examples/sharp_corner_tapering/assets/tex_metal_fill.png differ diff --git a/examples/sharp_corner_tapering/assets/tex_metal_fill.png.import b/examples/sharp_corner_tapering/assets/tex_metal_fill.png.import new file mode 100644 index 0000000..2b1eb9c --- /dev/null +++ b/examples/sharp_corner_tapering/assets/tex_metal_fill.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bj658oli0klj3" +path="res://.godot/imported/tex_metal_fill.png-307d5ebcedc9e8c4f154a2c09687cb41.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/sharp_corner_tapering/assets/tex_metal_fill.png" +dest_files=["res://.godot/imported/tex_metal_fill.png-307d5ebcedc9e8c4f154a2c09687cb41.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png new file mode 100644 index 0000000..17ab69d Binary files /dev/null and b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png differ diff --git a/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png.import b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png.import new file mode 100644 index 0000000..5315f40 --- /dev/null +++ b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jc3g5qsmnpdd" +path="res://.godot/imported/tex_metal_taper_corner_left.png-735fbf01b6fc0725b32a91fb24aae5d5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/sharp_corner_tapering/assets/tex_metal_taper_corner_left.png" +dest_files=["res://.godot/imported/tex_metal_taper_corner_left.png-735fbf01b6fc0725b32a91fb24aae5d5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png new file mode 100644 index 0000000..3d06df9 Binary files /dev/null and b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png differ diff --git a/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png.import b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png.import new file mode 100644 index 0000000..7adf36e --- /dev/null +++ b/examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdnfaf3bslk38" +path="res://.godot/imported/tex_metal_taper_corner_right.png-75d13fe99bb8e540489390ee64c63ea5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/sharp_corner_tapering/assets/tex_metal_taper_corner_right.png" +dest_files=["res://.godot/imported/tex_metal_taper_corner_right.png-75d13fe99bb8e540489390ee64c63ea5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/sharp_corner_tapering/sharp_corner_tapering.tscn b/examples/sharp_corner_tapering/sharp_corner_tapering.tscn new file mode 100644 index 0000000..ecbfeb0 --- /dev/null +++ b/examples/sharp_corner_tapering/sharp_corner_tapering.tscn @@ -0,0 +1,315 @@ +[gd_scene load_steps=46 format=3 uid="uid://cl71flny1scun"] + +[ext_resource type="Script" path="res://addons/rmsmartshape/shapes/shape.gd" id="1_5hlge"] +[ext_resource type="Script" path="res://addons/rmsmartshape/vertex_properties.gd" id="2_yp1d2"] +[ext_resource type="Script" path="res://addons/rmsmartshape/shapes/point.gd" id="3_edpr6"] +[ext_resource type="Script" path="res://addons/rmsmartshape/shapes/point_array.gd" id="4_udfgv"] +[ext_resource type="Texture2D" uid="uid://bj658oli0klj3" path="res://examples/sharp_corner_tapering/assets/tex_metal_fill.png" id="6_ry0x0"] +[ext_resource type="Script" path="res://addons/rmsmartshape/materials/shape_material.gd" id="7_ncdby"] +[ext_resource type="Resource" uid="uid://c8q2nrxnbopbb" path="res://examples/sharp_corner_tapering/assets/mat_edge_metal.tres" id="8_uwntg"] +[ext_resource type="Script" path="res://addons/rmsmartshape/normal_range.gd" id="9_tqu3h"] +[ext_resource type="Script" path="res://addons/rmsmartshape/materials/edge_material.gd" id="10_55l7o"] +[ext_resource type="Script" path="res://addons/rmsmartshape/materials/edge_material_metadata.gd" id="10_umoay"] +[ext_resource type="Texture2D" uid="uid://b3aqa3bj1osvp" path="res://examples/sharp_corner_tapering/assets/tex_metal_edge.png" id="11_jc4po"] +[ext_resource type="Texture2D" uid="uid://bobwi3r6aiiqg" path="res://examples/sharp_corner_tapering/assets/tex_metal_corner_inner.png" id="12_va43u"] +[ext_resource type="Texture2D" uid="uid://bc34cuc50as8f" path="res://examples/sharp_corner_tapering/assets/tex_metal_corner_outer.png" id="13_f7yxr"] + +[sub_resource type="Resource" id="Resource_2r7x3"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_vpbfx"] +script = ExtResource("3_edpr6") +position = Vector2(83.365, 45.3257) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_2r7x3") + +[sub_resource type="Resource" id="Resource_a0g7n"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_w5cbt"] +script = ExtResource("3_edpr6") +position = Vector2(263.601, 615.758) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_a0g7n") + +[sub_resource type="Resource" id="Resource_h14mo"] +script = ExtResource("2_yp1d2") +texture_idx = -4 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_j5l1j"] +script = ExtResource("3_edpr6") +position = Vector2(1026.77, 577.75) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_h14mo") + +[sub_resource type="Resource" id="Resource_ukuig"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_324cb"] +script = ExtResource("3_edpr6") +position = Vector2(731.167, 13.1754) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_ukuig") + +[sub_resource type="Resource" id="Resource_wasbm"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_p3ja8"] +script = ExtResource("3_edpr6") +position = Vector2(83.365, 45.3257) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_wasbm") + +[sub_resource type="Resource" id="Resource_1fh62"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_qjb65"] +script = ExtResource("3_edpr6") +position = Vector2(742.381, 447.381) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_1fh62") + +[sub_resource type="Resource" id="Resource_o8bfq"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_w1ohc"] +script = ExtResource("3_edpr6") +position = Vector2(641.411, 322.256) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_o8bfq") + +[sub_resource type="Resource" id="Resource_tp3up"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_tlf23"] +script = ExtResource("3_edpr6") +position = Vector2(1133.09, 291.15) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_tp3up") + +[sub_resource type="Resource" id="Resource_6u83i"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_86kx1"] +script = ExtResource("3_edpr6") +position = Vector2(546.956, 94.6283) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_6u83i") + +[sub_resource type="Resource" id="Resource_kysxg"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_eqyck"] +script = ExtResource("3_edpr6") +position = Vector2(293.278, 51.6277) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_kysxg") + +[sub_resource type="Resource" id="Resource_egpc8"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_1wyqg"] +script = ExtResource("3_edpr6") +position = Vector2(871.022, 351.231) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_egpc8") + +[sub_resource type="Resource" id="Resource_baip5"] +script = ExtResource("2_yp1d2") +texture_idx = 0 +flip = false +width = 1.0 + +[sub_resource type="Resource" id="Resource_2owaw"] +script = ExtResource("3_edpr6") +position = Vector2(239.115, 334.66) +point_in = Vector2(0, 0) +point_out = Vector2(0, 0) +properties = SubResource("Resource_baip5") + +[sub_resource type="Resource" id="Resource_o2w6y"] +script = ExtResource("4_udfgv") +_points = { +0: SubResource("Resource_vpbfx"), +1: SubResource("Resource_w5cbt"), +2: SubResource("Resource_j5l1j"), +3: SubResource("Resource_324cb"), +4: SubResource("Resource_p3ja8"), +6: SubResource("Resource_qjb65"), +8: SubResource("Resource_w1ohc"), +9: SubResource("Resource_tlf23"), +10: SubResource("Resource_86kx1"), +11: SubResource("Resource_eqyck"), +12: SubResource("Resource_1wyqg"), +13: SubResource("Resource_2owaw") +} +_point_order = PackedInt32Array(4, 11, 8, 10, 3, 12, 9, 2, 6, 1, 13, 0) +_constraints = { +Vector2i(0, 4): 15 +} +_next_key = 14 +_material_overrides = {} +tessellation_stages = 5 +tessellation_tolerance = 4.0 +curve_bake_interval = 20.0 + +[sub_resource type="Resource" id="Resource_42qh7"] +script = ExtResource("9_tqu3h") +begin = 0.0 +distance = 360.0 +edgeRendering = Vector2(0, 0) + +[sub_resource type="Resource" id="Resource_t7ed3"] +script = ExtResource("10_umoay") +edge_material = ExtResource("8_uwntg") +normal_range = SubResource("Resource_42qh7") +weld = true +taper_sharp_corners = true +render = true +z_index = 0 +z_as_relative = true +offset = 0.0 + +[sub_resource type="Resource" id="Resource_07ymf"] +script = ExtResource("7_ncdby") +_edge_meta_materials = Array[ExtResource("10_umoay")]([SubResource("Resource_t7ed3")]) +fill_textures = Array[Texture2D]([ExtResource("6_ry0x0")]) +fill_texture_z_index = -10 +fill_texture_show_behind_parent = false +fill_texture_scale = 1.0 +fill_texture_absolute_position = false +fill_texture_absolute_rotation = false +fill_texture_offset = Vector2(0, 0) +fill_texture_angle_offset = 0.0 +fill_mesh_offset = 0.0 +render_offset = 0.0 + +[sub_resource type="Resource" id="Resource_ma2v2"] +script = ExtResource("10_55l7o") +textures = Array[Texture2D]([ExtResource("11_jc4po")]) +textures_corner_outer = Array[Texture2D]([ExtResource("13_f7yxr")]) +textures_corner_inner = Array[Texture2D]([ExtResource("12_va43u")]) +textures_taper_left = Array[Texture2D]([]) +textures_taper_right = Array[Texture2D]([]) +textures_taper_corner_left = Array[Texture2D]([]) +textures_taper_corner_right = Array[Texture2D]([]) +randomize_texture = false +use_corner_texture = true +use_taper_texture = true +fit_mode = 0 + +[sub_resource type="Resource" id="Resource_atqmo"] +script = ExtResource("9_tqu3h") +begin = 0.0 +distance = 360.0 +edgeRendering = Vector2(0, 0) + +[sub_resource type="Resource" id="Resource_vboqn"] +script = ExtResource("10_umoay") +edge_material = SubResource("Resource_ma2v2") +normal_range = SubResource("Resource_atqmo") +weld = true +taper_sharp_corners = false +render = true +z_index = 0 +z_as_relative = true +offset = 0.0 + +[sub_resource type="Resource" id="Resource_xsfev"] +script = ExtResource("7_ncdby") +_edge_meta_materials = Array[ExtResource("10_umoay")]([SubResource("Resource_vboqn")]) +fill_textures = Array[Texture2D]([ExtResource("6_ry0x0")]) +fill_texture_z_index = -10 +fill_texture_show_behind_parent = false +fill_texture_scale = 1.0 +fill_texture_absolute_position = false +fill_texture_absolute_rotation = false +fill_texture_offset = Vector2(0, 0) +fill_texture_angle_offset = 0.0 +fill_mesh_offset = 0.0 +render_offset = 0.0 + +[node name="Node2D" type="Node2D"] + +[node name="WithTapering" type="Node2D" parent="."] +texture_repeat = 2 +position = Vector2(-30, 228) +scale = Vector2(0.65, 0.65) +script = ExtResource("1_5hlge") +_points = SubResource("Resource_o2w6y") +shape_material = SubResource("Resource_07ymf") + +[node name="WithoutTapering" type="Node2D" parent="."] +texture_repeat = 2 +position = Vector2(400, 10) +scale = Vector2(0.65, 0.65) +script = ExtResource("1_5hlge") +_points = SubResource("Resource_o2w6y") +shape_material = SubResource("Resource_xsfev") + +[node name="Message" type="Label" parent="."] +offset_left = 33.0 +offset_top = 82.0 +offset_right = 434.0 +offset_bottom = 190.0 +theme_override_font_sizes/font_size = 24 +text = "NOTE: Sharp corner tapering can +cause issues when used on shapes +with curved geometry" + +[node name="With" type="Label" parent="."] +offset_left = 275.0 +offset_top = 454.0 +offset_right = 503.0 +offset_bottom = 477.0 +text = "Sharp Corner Tapering" + +[node name="Without" type="Label" parent="."] +offset_left = 666.0 +offset_top = 237.0 +offset_right = 907.0 +offset_bottom = 260.0 +text = "No Sharp Corner Tapering" diff --git a/objects/big_coin.tscn b/objects/big_coin.tscn new file mode 100644 index 0000000..4cff7f9 --- /dev/null +++ b/objects/big_coin.tscn @@ -0,0 +1,35 @@ +[gd_scene load_steps=6 format=3 uid="uid://bargnp4twtmxg"] + +[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_aya2w"] +[ext_resource type="Script" path="res://scripts/components/collectable.gd" id="2_7cph7"] +[ext_resource type="Resource" uid="uid://bsnr5v2b2mfsl" path="res://resources/collectables/big_coin.tres" id="3_lk3av"] +[ext_resource type="Script" path="res://scripts/components/fade_away.gd" id="4_wkrj0"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_3ask2"] +radius = 9.0 + +[node name="Big Coin" type="Area2D"] +scale = Vector2(2, 2) +collision_layer = 2 +collision_mask = 4 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_3ask2") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_aya2w") +hframes = 12 +vframes = 12 +frame = 51 + +[node name="Collectable" type="Node" parent="." node_paths=PackedStringArray("area2d") groups=["coins"]] +script = ExtResource("2_7cph7") +area2d = NodePath("..") +collectable_data = ExtResource("3_lk3av") + +[node name="FadeAwayComponent" type="Node" parent="." node_paths=PackedStringArray("sprite2d", "root", "area2d")] +script = ExtResource("4_wkrj0") +sprite2d = NodePath("../Sprite2D") +fade_duration = 0.4 +root = NodePath("..") +area2d = NodePath("..") diff --git a/objects/big_treasure.tscn b/objects/big_treasure.tscn new file mode 100644 index 0000000..5346e97 --- /dev/null +++ b/objects/big_treasure.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=6 format=3 uid="uid://d08dfqmirnd66"] + +[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_1co1x"] +[ext_resource type="Script" path="res://scripts/components/collectable.gd" id="2_cujcq"] +[ext_resource type="Resource" uid="uid://b6xqotmke54x" path="res://resources/collectables/big_treasure.tres" id="3_k64cr"] +[ext_resource type="Script" path="res://scripts/components/fade_away.gd" id="4_nw7tw"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_3ask2"] +radius = 9.0 + +[node name="Big Treasure" type="Area2D"] +collision_layer = 2 +collision_mask = 4 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_3ask2") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_1co1x") +hframes = 12 +vframes = 12 +frame = 64 + +[node name="Collectable" type="Node" parent="." node_paths=PackedStringArray("area2d") groups=["coins"]] +script = ExtResource("2_cujcq") +area2d = NodePath("..") +collectable_data = ExtResource("3_k64cr") + +[node name="FadeAwayComponent" type="Node" parent="." node_paths=PackedStringArray("sprite2d", "root", "area2d")] +script = ExtResource("4_nw7tw") +sprite2d = NodePath("../Sprite2D") +fade_duration = 0.4 +root = NodePath("..") +area2d = NodePath("..") diff --git a/objects/brick_player.tscn b/objects/brick_player.tscn index 7f9fedb..25ad50e 100644 --- a/objects/brick_player.tscn +++ b/objects/brick_player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=19 format=3 uid="uid://bqi5s710xb1ju"] +[gd_scene load_steps=21 format=3 uid="uid://bqi5s710xb1ju"] [ext_resource type="Script" path="res://scripts/player.gd" id="1_8j4h4"] [ext_resource type="Texture2D" uid="uid://b7gp0gqvkv8j4" path="res://sprites/MrBrick_base.png" id="2_bc55y"] @@ -15,6 +15,8 @@ [ext_resource type="Script" path="res://scripts/components/flashing_component.gd" id="13_hrtyn"] [ext_resource type="Script" path="res://scripts/components/invulnerability_component.gd" id="14_jopig"] [ext_resource type="Script" path="res://scripts/components/magnetic_skill.gd" id="15_4df3h"] +[ext_resource type="Script" path="res://scripts/components/can_be_launched_component.gd" id="16_kemlv"] +[ext_resource type="Script" path="res://scripts/components/trigger_lever_component.gd" id="17_hglfj"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_hdsg1"] size = Vector2(16, 31) @@ -27,7 +29,7 @@ radius = 48.0 [node name="Brick Player" type="CharacterBody2D"] collision_layer = 4 -collision_mask = 11 +collision_mask = 43 script = ExtResource("1_8j4h4") [node name="Root" type="Node2D" parent="."] @@ -132,5 +134,11 @@ collision_mask = 2 [node name="CollisionShape2D" type="CollisionShape2D" parent="MagneticArea"] shape = SubResource("CircleShape2D_ps31c") +[node name="CanBeLaunchedComponent" type="Node" parent="."] +script = ExtResource("16_kemlv") + +[node name="TriggerLeverComponent" type="Node" parent="."] +script = ExtResource("17_hglfj") + [connection signal="on_death" from="HealthComponent" to="PlayerDeathComponent" method="_on_health_component_on_death"] [connection signal="on_health_change" from="HealthComponent" to="KnockbackComponent" method="_on_health_component_on_health_change"] diff --git a/objects/cage.tscn b/objects/cage.tscn new file mode 100644 index 0000000..93845f0 --- /dev/null +++ b/objects/cage.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=5 format=3 uid="uid://to2xnqev0pu1"] + +[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_5poh3"] +[ext_resource type="Script" path="res://scripts/components/cage_component.gd" id="2_unomj"] + +[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_aivtb"] +texture = ExtResource("1_5poh3") +2:9/size_in_atlas = Vector2i(2, 2) +2:9/0 = 0 +2:9/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16) + +[sub_resource type="TileSet" id="TileSet_67qt2"] +tile_size = Vector2i(32, 32) +physics_layer_0/collision_layer = 1 +sources/0 = SubResource("TileSetAtlasSource_aivtb") + +[node name="Cage" type="Node2D"] +scale = Vector2(2, 2) + +[node name="TileMapLayer" type="TileMapLayer" parent="."] +position = Vector2(20, 4) +tile_map_data = PackedByteArray(0, 0, 255, 255, 255, 255, 0, 0, 2, 0, 9, 0, 0, 0) +tile_set = SubResource("TileSet_67qt2") + +[node name="CageComponent" type="Node" parent="." node_paths=PackedStringArray("root")] +script = ExtResource("2_unomj") +root = NodePath("..") +should_free = false diff --git a/objects/enemy.tscn b/objects/enemy.tscn index 281b452..fa8efc2 100644 --- a/objects/enemy.tscn +++ b/objects/enemy.tscn @@ -75,9 +75,11 @@ debug_color = Color(0.913521, 0.265052, 0.323172, 0.42) [node name="Left Ray" type="RayCast2D" parent="."] position = Vector2(-16, 13) +target_position = Vector2(0, 8) [node name="Right Ray" type="RayCast2D" parent="."] position = Vector2(16, 13) +target_position = Vector2(0, 8) [node name="FlashingComponent" type="Node" parent="." node_paths=PackedStringArray("sprite", "health_component")] process_mode = 3 diff --git a/objects/jump_pad.tscn b/objects/jump_pad.tscn new file mode 100644 index 0000000..2a9bab8 --- /dev/null +++ b/objects/jump_pad.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=4 format=3 uid="uid://cm3rixnnev1pg"] + +[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_ctugi"] +[ext_resource type="Script" path="res://scripts/components/jump_pad_component.gd" id="2_2ypfs"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_ci3ij"] +size = Vector2(16, 6) + +[node name="Jump pad" type="Area2D"] +collision_layer = 0 +collision_mask = 4 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(0, 5) +shape = SubResource("RectangleShape2D_ci3ij") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_ctugi") +hframes = 12 +vframes = 12 +frame = 120 + +[node name="JumpPadComponent" type="Node" parent="." node_paths=PackedStringArray("area2d", "sprite2d")] +script = ExtResource("2_2ypfs") +jump_force = 1110.0 +area2d = NodePath("..") +sprite2d = NodePath("../Sprite2D") +start_animation_index = 120 diff --git a/objects/lever.tscn b/objects/lever.tscn new file mode 100644 index 0000000..bb6abae --- /dev/null +++ b/objects/lever.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=4 format=3 uid="uid://bd51frym6mm7v"] + +[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_psg62"] +[ext_resource type="Script" path="res://scripts/components/lever_component.gd" id="2_0p0wb"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_ke5tv"] +size = Vector2(12, 13) + +[node name="Lever" type="Area2D" groups=["levers"]] +collision_layer = 0 +collision_mask = 4 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(0, 1.5) +shape = SubResource("RectangleShape2D_ke5tv") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_psg62") +hframes = 12 +vframes = 12 +frame = 75 + +[node name="LeverComponent" type="Node" parent="." node_paths=PackedStringArray("area2d", "sprite2d")] +script = ExtResource("2_0p0wb") +area2d = NodePath("..") +sprite2d = NodePath("../Sprite2D") +start_animation_index = 75 diff --git a/objects/treasure.tscn b/objects/treasure.tscn new file mode 100644 index 0000000..7a3575d --- /dev/null +++ b/objects/treasure.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=6 format=3 uid="uid://073ts5cxtwbl"] + +[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_uh3ex"] +[ext_resource type="Script" path="res://scripts/components/collectable.gd" id="2_i1ssp"] +[ext_resource type="Resource" uid="uid://bws2xldndlre1" path="res://resources/collectables/treasure.tres" id="3_uknsr"] +[ext_resource type="Script" path="res://scripts/components/fade_away.gd" id="4_ccbcr"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_3ask2"] +radius = 9.0 + +[node name="Treasure" type="Area2D"] +collision_layer = 2 +collision_mask = 4 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_3ask2") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_uh3ex") +hframes = 12 +vframes = 12 +frame = 66 + +[node name="Collectable" type="Node" parent="." node_paths=PackedStringArray("area2d") groups=["coins"]] +script = ExtResource("2_i1ssp") +area2d = NodePath("..") +collectable_data = ExtResource("3_uknsr") + +[node name="FadeAwayComponent" type="Node" parent="." node_paths=PackedStringArray("sprite2d", "root", "area2d")] +script = ExtResource("4_ccbcr") +sprite2d = NodePath("../Sprite2D") +fade_duration = 0.4 +root = NodePath("..") +area2d = NodePath("..") diff --git a/project.godot b/project.godot index 829bbe7..8895a83 100644 --- a/project.godot +++ b/project.godot @@ -46,11 +46,12 @@ project/assembly_name="Mr. Brick Adventures" [editor_plugins] -enabled=PackedStringArray("res://addons/phantom_camera/plugin.cfg") +enabled=PackedStringArray("res://addons/phantom_camera/plugin.cfg", "res://addons/rmsmartshape/plugin.cfg") [global_group] coins="" +levers="" [input] @@ -88,6 +89,7 @@ attack={ 2d_physics/layer_3="Player" 2d_physics/layer_4="Enemy" 2d_physics/layer_5="player projectiles" +2d_physics/layer_6="Environment" [physics] diff --git a/resources/collectables/big_coin.tres b/resources/collectables/big_coin.tres new file mode 100644 index 0000000..e7549c4 --- /dev/null +++ b/resources/collectables/big_coin.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="CollectableResource" load_steps=2 format=3 uid="uid://bsnr5v2b2mfsl"] + +[ext_resource type="Script" path="res://scripts/resources/collectable_resource.gd" id="1_fudbo"] + +[resource] +script = ExtResource("1_fudbo") +amount = 5 diff --git a/resources/collectables/big_treasure.tres b/resources/collectables/big_treasure.tres new file mode 100644 index 0000000..a6f9604 --- /dev/null +++ b/resources/collectables/big_treasure.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="CollectableResource" load_steps=2 format=3 uid="uid://b6xqotmke54x"] + +[ext_resource type="Script" path="res://scripts/resources/collectable_resource.gd" id="1_037vi"] + +[resource] +script = ExtResource("1_037vi") +amount = 100 diff --git a/resources/collectables/treasure.tres b/resources/collectables/treasure.tres new file mode 100644 index 0000000..c423854 --- /dev/null +++ b/resources/collectables/treasure.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="CollectableResource" load_steps=2 format=3 uid="uid://bws2xldndlre1"] + +[ext_resource type="Script" path="res://scripts/resources/collectable_resource.gd" id="1_w50p5"] + +[resource] +script = ExtResource("1_w50p5") +amount = 50 diff --git a/scenes/test.tscn b/scenes/test.tscn index a8e7607..486741d 100644 --- a/scenes/test.tscn +++ b/scenes/test.tscn @@ -1,16 +1,22 @@ -[gd_scene load_steps=27 format=4 uid="uid://h60obxmju6mo"] +[gd_scene load_steps=33 format=4 uid="uid://h60obxmju6mo"] [ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_5lb42"] [ext_resource type="TileSet" uid="uid://cl4bn8lofqvky" path="res://tileset/village/tileset_village.tres" id="1_d680t"] [ext_resource type="Texture2D" uid="uid://dxvevrm15uus1" path="res://sprites/flowers_tileset.png" id="3_235u2"] [ext_resource type="PackedScene" uid="uid://54w4wisfj8v8" path="res://objects/coin.tscn" id="3_ygehw"] [ext_resource type="PackedScene" uid="uid://bqi5s710xb1ju" path="res://objects/brick_player.tscn" id="4_hetw8"] +[ext_resource type="PackedScene" uid="uid://bargnp4twtmxg" path="res://objects/big_coin.tscn" id="5_bpga1"] [ext_resource type="PackedScene" uid="uid://ct8fim6mduyl3" path="res://objects/collapsing_bridge.tscn" id="6_84ckv"] +[ext_resource type="PackedScene" uid="uid://d08dfqmirnd66" path="res://objects/big_treasure.tscn" id="6_a3vrq"] +[ext_resource type="PackedScene" uid="uid://073ts5cxtwbl" path="res://objects/treasure.tscn" id="7_4mhb5"] [ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="7_jgh7o"] [ext_resource type="PackedScene" uid="uid://bwdlmualj6xbw" path="res://objects/enemy.tscn" id="7_qgddg"] [ext_resource type="PackedScene" uid="uid://bhc7y4xugu4q7" path="res://objects/bullet.tscn" id="8_c68mx"] +[ext_resource type="PackedScene" uid="uid://cm3rixnnev1pg" path="res://objects/jump_pad.tscn" id="8_dt3jb"] +[ext_resource type="PackedScene" uid="uid://to2xnqev0pu1" path="res://objects/cage.tscn" id="9_oiafb"] [ext_resource type="PackedScene" uid="uid://dfwpha0d18dmn" path="res://objects/cannon_ray_down.tscn" id="9_ysd8p"] [ext_resource type="PackedScene" uid="uid://d3lt4rhxduv44" path="res://objects/cannon_ray_left.tscn" id="10_7v2ff"] +[ext_resource type="PackedScene" uid="uid://bd51frym6mm7v" path="res://objects/lever.tscn" id="10_gxrpi"] [ext_resource type="PackedScene" uid="uid://dstko446qydsc" path="res://objects/cannon.tscn" id="11_qv64j"] [ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="13_rsy5s"] [ext_resource type="Script" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="14_mjvn7"] @@ -260,6 +266,12 @@ texture = ExtResource("1_5lb42") [sub_resource type="TileSetScenesCollectionSource" id="TileSetScenesCollectionSource_v3eon"] scenes/1/scene = ExtResource("3_ygehw") scenes/2/scene = ExtResource("6_84ckv") +scenes/3/scene = ExtResource("5_bpga1") +scenes/4/scene = ExtResource("6_a3vrq") +scenes/5/scene = ExtResource("7_4mhb5") +scenes/6/scene = ExtResource("8_dt3jb") +scenes/7/scene = ExtResource("9_oiafb") +scenes/8/scene = ExtResource("10_gxrpi") [sub_resource type="TileSet" id="TileSet_yf4x4"] physics_layer_0/collision_layer = 1 @@ -404,19 +416,19 @@ scale = Vector2(4.05664, 753) texture = SubResource("GradientTexture1D_uhg81") [node name="Background Layer" type="TileMapLayer" parent="."] -tile_map_data = PackedByteArray("AAAKAP7/AAAAAAYAAAAKAP//AAAAAAcAAAALAP7/AAABAAYAAAALAP//AAABAAcAAAALAAAAAAABAAgAAAALAAEAAAABAAkAAAAMAP7/AAACAAYAAAAMAP//AAACAAcAAAAMAAAAAAACAAgAAAATAAAAAAAAAAYAAAATAAEAAAAAAAcAAAAUAAAAAAABAAYAAAAUAAEAAAABAAcAAAAUAAIAAAABAAgAAAAUAAMAAAABAAkAAAAVAAAAAAACAAYAAAAVAAEAAAACAAcAAAAVAAIAAAACAAgAAAAbAPb/AAAAAAYAAAAbAPf/AAAAAAcAAAAcAPb/AAABAAYAAAAcAPf/AAABAAcAAAAcAPj/AAABAAgAAAAcAPn/AAABAAkAAAAdAPb/AAACAAYAAAAdAPf/AAACAAcAAAAdAPj/AAACAAgAAAAPAPb/AAAAAAYAAAAPAPf/AAAAAAcAAAAQAPb/AAABAAYAAAAQAPf/AAABAAcAAAAQAPj/AAABAAgAAAAQAPn/AAABAAkAAAARAPb/AAACAAYAAAARAPf/AAACAAcAAAARAPj/AAACAAgAAAD3//z/AAALAAUAAAA=") +tile_map_data = PackedByteArray("AAAKAP7/AAAAAAYAAAAKAP//AAAAAAcAAAALAP7/AAABAAYAAAALAP//AAABAAcAAAALAAAAAAABAAgAAAALAAEAAAABAAkAAAAMAP7/AAACAAYAAAAMAP//AAACAAcAAAAMAAAAAAACAAgAAAATAAAAAAAAAAYAAAATAAEAAAAAAAcAAAAUAAAAAAABAAYAAAAUAAEAAAABAAcAAAAUAAIAAAABAAgAAAAUAAMAAAABAAkAAAAVAAAAAAACAAYAAAAVAAEAAAACAAcAAAAVAAIAAAACAAgAAAAbAPb/AAAAAAYAAAAbAPf/AAAAAAcAAAAcAPb/AAABAAYAAAAcAPf/AAABAAcAAAAcAPj/AAABAAgAAAAcAPn/AAABAAkAAAAdAPb/AAACAAYAAAAdAPf/AAACAAcAAAAdAPj/AAACAAgAAAAPAPb/AAAAAAYAAAAPAPf/AAAAAAcAAAAQAPb/AAABAAYAAAAQAPf/AAABAAcAAAAQAPj/AAABAAgAAAAQAPn/AAABAAkAAAARAPb/AAACAAYAAAARAPf/AAACAAcAAAARAPj/AAACAAgAAAD3//z/AAALAAUAAABjAAUAAAAKAAUAAABkAAUAAAAKAAUAAABlAAUAAAAKAAUAAABmAAUAAAAKAAUAAABnAAUAAAAKAAUAAABoAAUAAAAKAAUAAABpAAUAAAAKAAUAAABqAAUAAAAKAAUAAABrAAUAAAAKAAUAAABsAAUAAAAKAAUAAABjAAQAAAAJAAUAAABkAAQAAAAJAAUAAABlAAQAAAAJAAUAAABmAAQAAAAJAAUAAABnAAQAAAAJAAUAAABoAAQAAAAJAAUAAABpAAQAAAAJAAUAAABqAAQAAAAJAAUAAABrAAQAAAAJAAUAAABsAAQAAAAJAAUAAABtAAQAAAAJAAUAAABuAAQAAAAJAAUAAABvAAQAAAAJAAUAAABwAAQAAAAJAAUAAABxAAQAAAAJAAUAAAByAAQAAAAJAAUAAABzAAQAAAAJAAUAAAB0AAQAAAAJAAUAAAB1AAQAAAAJAAUAAAB2AAQAAAAJAAUAAAB3AAQAAAAJAAUAAAB4AAQAAAAJAAUAAAB5AAQAAAAJAAUAAAB6AAQAAAAJAAUAAAB7AAQAAAAJAAUAAAB8AAQAAAAJAAUAAAB8AAUAAAAKAAUAAAB7AAUAAAAKAAUAAAB6AAUAAAAKAAUAAAB5AAUAAAAKAAUAAAB4AAUAAAAKAAUAAAB3AAUAAAAKAAUAAAB2AAUAAAAKAAUAAAB1AAUAAAAKAAUAAAB0AAUAAAAKAAUAAABzAAUAAAAKAAUAAAByAAUAAAAKAAUAAABxAAUAAAAKAAUAAABwAAUAAAAKAAUAAABvAAUAAAAKAAUAAABuAAUAAAAKAAUAAABtAAUAAAAKAAUAAABjAAYAAAAKAAUAAABjAAcAAAAKAAUAAABkAAcAAAAKAAUAAABlAAcAAAAKAAUAAABmAAcAAAAKAAUAAABnAAcAAAAKAAUAAABoAAcAAAAKAAUAAABpAAcAAAAKAAUAAABqAAcAAAAKAAUAAABrAAcAAAAKAAUAAABsAAcAAAAKAAUAAABtAAcAAAAKAAUAAABuAAcAAAAKAAUAAABvAAcAAAAKAAUAAABwAAcAAAAKAAUAAABxAAcAAAAKAAUAAAByAAcAAAAKAAUAAABzAAcAAAAKAAUAAAB0AAcAAAAKAAUAAAB1AAcAAAAKAAUAAAB2AAcAAAAKAAUAAAB3AAcAAAAKAAUAAAB4AAcAAAAKAAUAAAB5AAcAAAAKAAUAAAB6AAcAAAAKAAUAAAB7AAcAAAAKAAUAAAB7AAYAAAAKAAUAAAB8AAYAAAAKAAUAAAB8AAcAAAAKAAUAAAB6AAYAAAAKAAUAAAB5AAYAAAAKAAUAAAB4AAYAAAAKAAUAAAB3AAYAAAAKAAUAAAB2AAYAAAAKAAUAAAB1AAYAAAAKAAUAAAB0AAYAAAAKAAUAAABzAAYAAAAKAAUAAAByAAYAAAAKAAUAAABxAAYAAAAKAAUAAABwAAYAAAAKAAUAAABvAAYAAAAKAAUAAABuAAYAAAAKAAUAAABtAAYAAAAKAAUAAABsAAYAAAAKAAUAAABrAAYAAAAKAAUAAABqAAYAAAAKAAUAAABpAAYAAAAKAAUAAABoAAYAAAAKAAUAAABnAAYAAAAKAAUAAABmAAYAAAAKAAUAAABlAAYAAAAKAAUAAABkAAYAAAAKAAUAAAA=") tile_set = SubResource("TileSet_7ri0q") [node name="Terrain Layer" type="TileMapLayer" parent="."] -tile_map_data = PackedByteArray("AAD8////AAABAAAAAAD9////AAABAAAAAAD+////AAABAAAAAAD/////AAABAAAAAAAAAP//AAABAAAAAAABAP//AAABAAAAAAABAAAAAAABAAEAAAABAAEAAAABAAEAAAAAAAEAAAABAAEAAAD//wEAAAABAAEAAAD+/wEAAAABAAEAAAD9/wEAAAABAAEAAAD8/wEAAAABAAEAAAD8/wAAAAABAAEAAAD9/wAAAAABAAEAAAD+/wAAAAABAAEAAAD//wAAAAABAAEAAAAAAAAAAAABAAEAAAACAP//AAABAAAAAAACAAAAAAABAAEAAAACAAEAAAABAAEAAAADAAEAAAABAAEAAAAEAAEAAAABAAEAAAAEAAAAAAABAAEAAAAEAP//AAABAAEAAAADAP//AAAGAAIAAAADAAAAAAABAAEAAAAFAAAAAAABAAEAAAAFAP//AAABAAEAAAAFAAEAAAABAAEAAAADAP7/AAAAAAEAAAADAP3/AAAAAAEAAAADAPz/AAAAAAEAAAADAPv/AAAAAAAAAAAEAPv/AAABAAAAAAAFAPv/AAACAAAAAAAFAPz/AAAFAAIAAAAFAP3/AAABAAEAAAAFAP7/AAABAAEAAAAEAP3/AAABAAEAAAAGAP3/AAABAAEAAAAGAPz/AAABAAAAAAAHAPz/AAACAAAAAAAHAP3/AAAFAAIAAAAIAP3/AAACAAAAAAAIAP7/AAACAAEAAAAIAP//AAACAAEAAAAIAAAAAAAFAAIAAAAJAAAAAAACAAAAAAAJAAEAAAACAAEAAAAJAAIAAAAFAAIAAAAKAAIAAAABAAAAAAALAAIAAAABAAAAAAAMAAIAAAABAAAAAAANAAIAAAAGAAIAAAANAAEAAAAAAAAAAAAOAAEAAAABAAAAAAAPAAEAAAABAAAAAAAQAAEAAAABAAAAAAARAAEAAAABAAAAAAASAAEAAAACAAAAAAASAAIAAAACAAEAAAASAAMAAAACAAEAAAARAAMAAAABAAEAAAAQAAMAAAABAAEAAAAPAAMAAAABAAEAAAAOAAMAAAABAAEAAAANAAMAAAABAAEAAAAMAAMAAAABAAEAAAALAAMAAAABAAEAAAAKAAMAAAABAAEAAAAJAAMAAAABAAEAAAAIAAMAAAABAAEAAAAHAAMAAAABAAEAAAAGAAMAAAABAAEAAAAGAAIAAAABAAEAAAAFAAIAAAABAAEAAAAEAAIAAAABAAEAAAADAAIAAAABAAEAAAACAAIAAAABAAEAAAABAAIAAAABAAEAAAAAAAIAAAABAAEAAAD//wIAAAABAAEAAAD+/wIAAAABAAEAAAD9/wIAAAABAAEAAAD8/wIAAAABAAEAAAD8/wMAAAABAAEAAAD9/wMAAAABAAEAAAD+/wMAAAABAAEAAAD+/wQAAAABAAEAAAD//wQAAAABAAEAAAAAAAQAAAABAAEAAAABAAQAAAABAAEAAAACAAQAAAABAAEAAAADAAQAAAABAAEAAAAEAAQAAAABAAEAAAAFAAQAAAABAAEAAAAGAAQAAAABAAEAAAAHAAQAAAABAAEAAAAIAAQAAAABAAEAAAAJAAQAAAABAAEAAAAKAAQAAAABAAEAAAALAAQAAAABAAEAAAAMAAQAAAABAAEAAAANAAQAAAABAAEAAAAOAAQAAAABAAEAAAAPAAQAAAABAAEAAAARAAIAAAABAAEAAAAQAAIAAAABAAEAAAAPAAIAAAABAAEAAAAOAAIAAAABAAEAAAAQAAQAAAABAAEAAAARAAQAAAABAAEAAAASAAQAAAAFAAIAAAAIAAIAAAABAAEAAAAHAAIAAAABAAEAAAAFAAMAAAABAAEAAAAEAAMAAAABAAEAAAADAAMAAAABAAEAAAACAAMAAAABAAEAAAABAAMAAAABAAEAAAAAAAMAAAABAAEAAAD//wMAAAABAAEAAAD9/wQAAAABAAEAAAD8/wQAAAABAAEAAAAIAAEAAAABAAEAAAAHAAEAAAABAAEAAAAHAAAAAAABAAEAAAAHAP//AAABAAEAAAAHAP7/AAABAAEAAAAGAAEAAAABAAEAAAAGAAAAAAABAAEAAAAGAP//AAABAAEAAAAGAP7/AAABAAEAAAAEAP7/AAABAAEAAAAEAPz/AAABAAEAAAAVAPz/AAABAAIAAAAUAPz/AAABAAIAAAATAP3/AAACAAIAAAASAP3/AAABAAIAAAARAP3/AAABAAIAAAAQAP3/AAABAAIAAAAPAP7/AAACAAIAAAAOAP7/AAAAAAIAAAAdAAQAAAABAAEAAAAcAAMAAAAAAAEAAAAbAAIAAAAAAAIAAAAaAAEAAAAAAAIAAAAZAAAAAAAAAAIAAAAZAP//AAAAAAEAAAAYAP7/AAAAAAIAAAAXAP3/AAAAAAIAAAAWAPz/AAABAAIAAAAcAAQAAAAGAAIAAAAbAAQAAAABAAAAAAAaAAQAAAABAAAAAAAZAAQAAAABAAAAAAAYAAQAAAABAAAAAAAXAAQAAAABAAAAAAAWAAQAAAABAAAAAAAVAAQAAAABAAAAAAAUAAQAAAABAAAAAAATAAQAAAABAAAAAAAdAAMAAAABAAEAAAAdAAIAAAABAAEAAAAcAAIAAAAGAAEAAAAcAAEAAAABAAEAAAAbAAEAAAAGAAEAAAAdAAEAAAABAAEAAAAdAAAAAAABAAEAAAAcAAAAAAABAAEAAAAbAAAAAAABAAEAAAAaAAAAAAAGAAEAAAAaAP//AAABAAEAAAAaAP7/AAABAAEAAAAZAP7/AAAGAAEAAAAZAP3/AAABAAEAAAAYAP3/AAAGAAEAAAAYAPz/AAABAAEAAAAXAPz/AAAGAAEAAAAZAPz/AAABAAEAAAAaAPz/AAABAAEAAAAaAP3/AAABAAEAAAAbAP3/AAABAAEAAAAbAP7/AAABAAEAAAAbAP//AAABAAEAAAAcAP//AAABAAEAAAAdAP//AAABAAEAAAAdAP7/AAABAAEAAAAcAP3/AAABAAEAAAAcAPz/AAABAAEAAAAbAPz/AAABAAEAAAAcAP7/AAABAAEAAAAdAP3/AAABAAEAAAAdAPz/AAABAAEAAAAdAPv/AAABAAEAAAAcAPv/AAABAAEAAAAbAPv/AAABAAEAAAAaAPv/AAABAAEAAAAZAPv/AAABAAEAAAAYAPv/AAABAAEAAAAXAPv/AAABAAEAAAAWAPv/AAABAAEAAAAWAPr/AAABAAAAAAAVAPr/AAABAAAAAAAUAPr/AAABAAAAAAAUAPv/AAABAAEAAAAVAPv/AAABAAEAAAAXAPr/AAABAAAAAAAYAPr/AAABAAAAAAAZAPr/AAABAAAAAAAaAPr/AAABAAAAAAAbAPr/AAABAAAAAAAcAPr/AAABAAAAAAAdAPr/AAABAAAAAAATAPz/AAAFAAEAAAASAPz/AAABAAEAAAARAPz/AAABAAEAAAARAPv/AAABAAEAAAASAPv/AAABAAEAAAASAPr/AAABAAAAAAATAPr/AAABAAAAAAATAPv/AAABAAEAAAARAPr/AAABAAAAAAAQAPr/AAABAAAAAAAQAPv/AAABAAEAAAAQAPz/AAABAAEAAAAPAPz/AAABAAEAAAAPAP3/AAAFAAEAAAAOAP3/AAAAAAEAAAAOAPz/AAAAAAEAAAAOAPv/AAAAAAEAAAAPAPv/AAABAAEAAAAPAPr/AAABAAAAAAAOAPr/AAAAAAAAAAAdAAUAAAABAAIAAAAcAAUAAAABAAIAAAAbAAUAAAABAAIAAAAaAAUAAAABAAIAAAAZAAUAAAABAAIAAAAYAAUAAAABAAIAAAASAAUAAAABAAIAAAATAAUAAAABAAIAAAAUAAUAAAABAAIAAAAVAAUAAAABAAIAAAAWAAUAAAABAAIAAAAXAAUAAAABAAIAAAARAAUAAAABAAIAAAAQAAUAAAABAAIAAAAPAAUAAAABAAIAAAAOAAUAAAABAAIAAAANAAUAAAABAAIAAAAMAAUAAAABAAIAAAALAAUAAAABAAIAAAAKAAUAAAABAAIAAAAJAAUAAAABAAIAAAAIAAUAAAABAAIAAAAHAAUAAAABAAIAAAAGAAUAAAABAAIAAAAFAAUAAAABAAIAAAAEAAUAAAABAAIAAAADAAUAAAABAAIAAAACAAUAAAABAAIAAAABAAUAAAABAAIAAAAAAAUAAAABAAIAAAD//wUAAAABAAIAAAD+/wUAAAABAAIAAAD9/wUAAAABAAIAAAD8/wUAAAABAAIAAAAnAAUAAAACAAIAAAAnAAQAAAACAAEAAAAnAAMAAAACAAEAAAAnAAIAAAACAAEAAAAnAAEAAAACAAEAAAAnAAAAAAACAAEAAAAnAP//AAACAAEAAAAnAP7/AAACAAEAAAAnAP3/AAACAAEAAAAnAPz/AAACAAEAAAAnAPv/AAACAAEAAAAnAPr/AAACAAEAAAAmAAUAAAABAAIAAAAmAAQAAAABAAEAAAAmAAMAAAABAAEAAAAmAAIAAAABAAEAAAAmAAEAAAABAAEAAAAmAAAAAAABAAEAAAAmAP//AAABAAEAAAAmAP7/AAABAAEAAAAmAP3/AAABAAEAAAAmAPz/AAABAAEAAAAmAPv/AAABAAEAAAAmAPr/AAABAAEAAAAlAAUAAAABAAIAAAAlAAQAAAABAAEAAAAlAAMAAAABAAEAAAAlAAIAAAABAAEAAAAlAAEAAAABAAEAAAAlAAAAAAABAAEAAAAlAP//AAABAAEAAAAlAP7/AAABAAEAAAAlAP3/AAABAAEAAAAlAPz/AAABAAEAAAAlAPv/AAABAAEAAAAlAPr/AAABAAEAAAAkAAUAAAABAAIAAAAkAAQAAAABAAEAAAAkAAMAAAABAAEAAAAkAAIAAAABAAEAAAAkAAEAAAABAAEAAAAkAAAAAAABAAEAAAAkAP//AAABAAEAAAAkAP7/AAABAAEAAAAkAP3/AAABAAEAAAAkAPz/AAABAAEAAAAkAPv/AAABAAEAAAAkAPr/AAABAAEAAAAjAAUAAAABAAIAAAAjAAQAAAABAAEAAAAjAAMAAAABAAEAAAAjAAIAAAABAAEAAAAjAAEAAAABAAEAAAAjAAAAAAABAAEAAAAjAP//AAABAAEAAAAjAP7/AAABAAEAAAAjAP3/AAABAAEAAAAjAPz/AAABAAEAAAAjAPv/AAABAAEAAAAjAPr/AAABAAEAAAAiAAUAAAABAAIAAAAiAAQAAAABAAEAAAAiAAMAAAABAAEAAAAiAAIAAAABAAEAAAAiAAEAAAABAAEAAAAiAAAAAAABAAEAAAAiAP//AAABAAEAAAAiAP7/AAABAAEAAAAiAP3/AAABAAEAAAAiAPz/AAABAAEAAAAiAPv/AAABAAEAAAAiAPr/AAABAAEAAAAhAAUAAAABAAIAAAAhAAQAAAABAAEAAAAhAAMAAAABAAEAAAAhAAIAAAABAAEAAAAhAAEAAAABAAEAAAAhAAAAAAABAAEAAAAhAP//AAABAAEAAAAhAP7/AAABAAEAAAAhAP3/AAABAAEAAAAhAPz/AAABAAEAAAAhAPv/AAABAAEAAAAhAPr/AAABAAEAAAAgAAUAAAABAAIAAAAgAAQAAAABAAEAAAAgAAMAAAABAAEAAAAgAAIAAAABAAEAAAAgAAEAAAABAAEAAAAgAAAAAAABAAEAAAAgAP//AAABAAEAAAAgAP7/AAABAAEAAAAgAP3/AAABAAEAAAAgAPz/AAABAAEAAAAgAPv/AAABAAEAAAAgAPr/AAAGAAIAAAAfAAUAAAABAAIAAAAfAAQAAAABAAEAAAAfAAMAAAABAAEAAAAfAAIAAAABAAEAAAAfAAEAAAABAAEAAAAfAAAAAAABAAEAAAAfAP//AAABAAEAAAAfAP7/AAABAAEAAAAfAP3/AAABAAEAAAAfAPz/AAABAAEAAAAfAPv/AAABAAEAAAAfAPr/AAABAAAAAAAeAAUAAAABAAIAAAAeAAQAAAABAAEAAAAeAAMAAAABAAEAAAAeAAIAAAABAAEAAAAeAAEAAAABAAEAAAAeAAAAAAABAAEAAAAeAP//AAABAAEAAAAeAP7/AAABAAEAAAAeAP3/AAABAAEAAAAeAPz/AAABAAEAAAAeAPv/AAABAAEAAAAeAPr/AAABAAAAAAAnAPn/AAACAAEAAAAnAPj/AAACAAEAAAAnAPf/AAACAAEAAAAnAPb/AAACAAEAAAAnAPX/AAACAAEAAAAnAPT/AAAFAAEAAAAnAPP/AAABAAEAAAAnAPL/AAABAAEAAAAnAPH/AAABAAEAAAAnAPD/AAABAAEAAAAmAPn/AAABAAEAAAAmAPj/AAABAAEAAAAmAPf/AAABAAEAAAAmAPb/AAABAAEAAAAmAPX/AAABAAEAAAAmAPT/AAABAAEAAAAmAPP/AAABAAEAAAAmAPL/AAABAAEAAAAmAPH/AAABAAEAAAAmAPD/AAABAAEAAAAlAPn/AAABAAEAAAAlAPj/AAABAAEAAAAlAPf/AAABAAEAAAAlAPb/AAABAAEAAAAlAPX/AAABAAEAAAAlAPT/AAABAAEAAAAlAPP/AAABAAEAAAAlAPL/AAABAAEAAAAlAPH/AAABAAEAAAAlAPD/AAABAAEAAAAkAPn/AAABAAEAAAAkAPj/AAABAAEAAAAkAPf/AAABAAEAAAAkAPb/AAABAAEAAAAkAPX/AAABAAEAAAAkAPT/AAABAAEAAAAkAPP/AAABAAEAAAAkAPL/AAABAAEAAAAkAPH/AAABAAEAAAAkAPD/AAABAAEAAAAjAPn/AAABAAEAAAAjAPj/AAABAAEAAAAjAPf/AAABAAEAAAAjAPb/AAABAAEAAAAjAPX/AAABAAEAAAAjAPT/AAABAAEAAAAjAPP/AAABAAEAAAAjAPL/AAABAAEAAAAjAPH/AAABAAEAAAAjAPD/AAABAAEAAAAiAPn/AAABAAEAAAAiAPj/AAABAAEAAAAiAPf/AAABAAEAAAAiAPb/AAABAAEAAAAiAPX/AAABAAEAAAAiAPT/AAABAAEAAAAiAPP/AAABAAEAAAAiAPL/AAABAAEAAAAiAPH/AAABAAEAAAAiAPD/AAABAAEAAAAhAPn/AAABAAEAAAAhAPj/AAABAAEAAAAhAPf/AAABAAEAAAAhAPb/AAABAAEAAAAhAPX/AAABAAEAAAAhAPT/AAABAAEAAAAhAPP/AAABAAEAAAAhAPL/AAABAAEAAAAhAPH/AAABAAEAAAAhAPD/AAABAAEAAAAgAPn/AAAAAAEAAAAgAPj/AAAAAAEAAAAgAPf/AAAAAAEAAAAgAPb/AAAAAAEAAAAgAPX/AAAAAAEAAAAgAPT/AAAAAAEAAAAgAPP/AAAAAAEAAAAgAPL/AAAAAAEAAAAgAPH/AAAAAAEAAAAgAPD/AAAAAAEAAAApAO//AAACAAEAAAApAO7/AAACAAEAAAApAO3/AAACAAEAAAApAOz/AAACAAEAAAApAOv/AAACAAEAAAApAOr/AAACAAAAAAAoAO//AAABAAEAAAAoAO7/AAABAAEAAAAoAO3/AAABAAEAAAAoAOz/AAABAAEAAAAoAOv/AAABAAEAAAAoAOr/AAABAAAAAAAnAO//AAABAAEAAAAnAO7/AAABAAEAAAAnAO3/AAABAAEAAAAnAOz/AAABAAEAAAAnAOv/AAABAAEAAAAnAOr/AAABAAAAAAAmAO//AAABAAEAAAAmAO7/AAABAAEAAAAmAO3/AAABAAEAAAAmAOz/AAABAAEAAAAmAOv/AAABAAEAAAAmAOr/AAABAAAAAAAlAO//AAABAAEAAAAlAO7/AAABAAEAAAAlAO3/AAABAAEAAAAlAOz/AAABAAEAAAAlAOv/AAABAAEAAAAlAOr/AAABAAAAAAAkAO//AAABAAEAAAAkAO7/AAABAAEAAAAkAO3/AAABAAEAAAAkAOz/AAABAAEAAAAkAOv/AAABAAEAAAAkAOr/AAABAAAAAAAjAO//AAABAAEAAAAjAO7/AAABAAEAAAAjAO3/AAABAAEAAAAjAOz/AAABAAEAAAAjAOv/AAABAAEAAAAjAOr/AAABAAAAAAAiAO//AAABAAEAAAAiAO7/AAABAAEAAAAiAO3/AAABAAEAAAAiAOz/AAABAAEAAAAiAOv/AAABAAEAAAAiAOr/AAABAAAAAAAhAO//AAABAAEAAAAhAO7/AAABAAEAAAAhAO3/AAABAAEAAAAhAOz/AAABAAEAAAAhAOv/AAABAAEAAAAhAOr/AAABAAAAAAAgAO//AAAGAAEAAAAgAO7/AAABAAEAAAAgAO3/AAABAAEAAAAgAOz/AAABAAEAAAAgAOv/AAABAAEAAAAgAOr/AAABAAAAAAApAPT/AAACAAIAAAApAPP/AAACAAEAAAApAPL/AAACAAEAAAApAPH/AAACAAEAAAApAPD/AAACAAEAAAAoAPT/AAABAAIAAAAoAPP/AAABAAEAAAAoAPL/AAABAAEAAAAoAPH/AAABAAEAAAAoAPD/AAABAAEAAAAfAO//AAABAAIAAAAfAO7/AAABAAEAAAAfAO3/AAABAAEAAAAfAOz/AAABAAEAAAAfAOv/AAABAAEAAAAfAOr/AAABAAAAAAAeAO//AAABAAIAAAAeAO7/AAABAAEAAAAeAO3/AAABAAEAAAAeAOz/AAABAAEAAAAeAOv/AAABAAEAAAAeAOr/AAABAAAAAAAdAO//AAABAAIAAAAdAO7/AAABAAEAAAAdAO3/AAABAAEAAAAdAOz/AAABAAEAAAAdAOv/AAABAAEAAAAdAOr/AAABAAAAAAAcAO//AAABAAIAAAAcAO7/AAABAAEAAAAcAO3/AAABAAEAAAAcAOz/AAABAAEAAAAcAOv/AAABAAEAAAAcAOr/AAABAAAAAAAbAO//AAABAAIAAAAbAO7/AAABAAEAAAAbAO3/AAABAAEAAAAbAOz/AAABAAEAAAAbAOv/AAABAAEAAAAbAOr/AAABAAAAAAAaAO//AAABAAIAAAAaAO7/AAABAAEAAAAaAO3/AAABAAEAAAAaAOz/AAABAAEAAAAaAOv/AAABAAEAAAAaAOr/AAABAAAAAAAZAO//AAABAAIAAAAZAO7/AAABAAEAAAAZAO3/AAABAAEAAAAZAOz/AAABAAEAAAAZAOv/AAABAAEAAAAZAOr/AAABAAAAAAAYAO//AAABAAIAAAAYAO7/AAABAAEAAAAYAO3/AAABAAEAAAAYAOz/AAABAAEAAAAYAOv/AAABAAEAAAAYAOr/AAABAAAAAAAXAO//AAABAAIAAAAXAO7/AAABAAEAAAAXAO3/AAABAAEAAAAXAOz/AAABAAEAAAAXAOv/AAABAAEAAAAXAOr/AAABAAAAAAAWAO//AAABAAIAAAAWAO7/AAABAAEAAAAWAO3/AAABAAEAAAAWAOz/AAABAAEAAAAWAOv/AAABAAEAAAAWAOr/AAABAAAAAAAVAO//AAABAAIAAAAVAO7/AAABAAEAAAAVAO3/AAABAAEAAAAVAOz/AAABAAEAAAAVAOv/AAABAAEAAAAVAOr/AAABAAAAAAAUAO//AAABAAIAAAAUAO7/AAABAAEAAAAUAO3/AAABAAEAAAAUAOz/AAABAAEAAAAUAOv/AAABAAEAAAAUAOr/AAABAAAAAAATAO//AAABAAIAAAATAO7/AAABAAEAAAATAO3/AAABAAEAAAATAOz/AAABAAEAAAATAOv/AAABAAEAAAATAOr/AAABAAAAAAASAO//AAABAAIAAAASAO7/AAABAAEAAAASAO3/AAABAAEAAAASAOz/AAABAAEAAAASAOv/AAABAAEAAAASAOr/AAABAAAAAAARAO//AAABAAIAAAARAO7/AAABAAEAAAARAO3/AAABAAEAAAARAOz/AAABAAEAAAARAOv/AAABAAEAAAARAOr/AAABAAAAAAAQAO//AAABAAIAAAAQAO7/AAABAAEAAAAQAO3/AAABAAEAAAAQAOz/AAABAAEAAAAQAOv/AAABAAEAAAAQAOr/AAABAAAAAAAPAO//AAABAAIAAAAPAO7/AAABAAEAAAAPAO3/AAABAAEAAAAPAOz/AAABAAEAAAAPAOv/AAABAAEAAAAPAOr/AAABAAAAAAAOAO//AAABAAIAAAAOAO7/AAABAAEAAAAOAO3/AAABAAEAAAAOAOz/AAABAAEAAAAOAOv/AAABAAEAAAAOAOr/AAABAAAAAAANAO//AAABAAIAAAANAO7/AAABAAEAAAANAO3/AAABAAEAAAANAOz/AAABAAEAAAANAOv/AAABAAEAAAANAOr/AAABAAAAAAAMAO//AAABAAIAAAAMAO7/AAABAAEAAAAMAO3/AAABAAEAAAAMAOz/AAABAAEAAAAMAOv/AAABAAEAAAAMAOr/AAABAAAAAAALAO//AAABAAIAAAALAO7/AAABAAEAAAALAO3/AAABAAEAAAALAOz/AAABAAEAAAALAOv/AAABAAEAAAALAOr/AAABAAAAAAAKAO//AAABAAIAAAAKAO7/AAABAAEAAAAKAO3/AAABAAEAAAAKAOz/AAABAAEAAAAKAOv/AAABAAEAAAAKAOr/AAABAAAAAAAJAO//AAABAAIAAAAJAO7/AAABAAEAAAAJAO3/AAABAAEAAAAJAOz/AAABAAEAAAAJAOv/AAABAAEAAAAJAOr/AAABAAAAAAAIAO//AAABAAIAAAAIAO7/AAABAAEAAAAIAO3/AAABAAEAAAAIAOz/AAABAAEAAAAIAOv/AAABAAEAAAAIAOr/AAABAAAAAAAHAO//AAABAAIAAAAHAO7/AAABAAEAAAAHAO3/AAABAAEAAAAHAOz/AAABAAEAAAAHAOv/AAABAAEAAAAHAOr/AAABAAAAAAAGAO//AAABAAIAAAAGAO7/AAABAAEAAAAGAO3/AAABAAEAAAAGAOz/AAABAAEAAAAGAOv/AAABAAEAAAAGAOr/AAABAAAAAAAFAO//AAABAAIAAAAFAO7/AAABAAEAAAAFAO3/AAABAAEAAAAFAOz/AAABAAEAAAAFAOv/AAABAAEAAAAFAOr/AAABAAAAAAAEAO//AAABAAIAAAAEAO7/AAABAAEAAAAEAO3/AAABAAEAAAAEAOz/AAABAAEAAAAEAOv/AAABAAEAAAAEAOr/AAABAAAAAAADAO//AAABAAIAAAADAO7/AAABAAEAAAADAO3/AAABAAEAAAADAOz/AAABAAEAAAADAOv/AAABAAEAAAADAOr/AAABAAAAAAACAO//AAABAAIAAAACAO7/AAABAAEAAAACAO3/AAABAAEAAAACAOz/AAABAAEAAAACAOv/AAABAAEAAAACAOr/AAABAAAAAAABAO//AAABAAIAAAABAO7/AAABAAEAAAABAO3/AAABAAEAAAABAOz/AAABAAEAAAABAOv/AAABAAEAAAABAOr/AAABAAAAAAAAAO//AAABAAIAAAAAAO7/AAABAAEAAAAAAO3/AAABAAEAAAAAAOz/AAABAAEAAAAAAOv/AAABAAEAAAAAAOr/AAABAAAAAAD//+//AAABAAIAAAD//+7/AAABAAEAAAD//+3/AAABAAEAAAD//+z/AAABAAEAAAD//+v/AAABAAEAAAD//+r/AAABAAAAAAD+/+//AAABAAIAAAD+/+7/AAABAAEAAAD+/+3/AAABAAEAAAD+/+z/AAABAAEAAAD+/+v/AAABAAEAAAD+/+r/AAABAAAAAAD9/+//AAABAAIAAAD9/+7/AAABAAEAAAD9/+3/AAABAAEAAAD9/+z/AAABAAEAAAD9/+v/AAABAAEAAAD9/+r/AAAFAAIAAAD9/+n/AAACAAAAAAD8/+//AAABAAIAAAD8/+7/AAABAAEAAAD8/+3/AAABAAEAAAD8/+z/AAABAAEAAAD8/+v/AAABAAEAAAD8/+r/AAABAAEAAAD8/+n/AAABAAAAAAD7/+//AAAFAAEAAAD7/+7/AAABAAEAAAD7/+3/AAABAAEAAAD7/+z/AAABAAEAAAD7/+v/AAABAAEAAAD7/+r/AAABAAEAAAD7/+n/AAABAAAAAAD6/+//AAABAAEAAAD6/+7/AAABAAEAAAD6/+3/AAABAAEAAAD6/+z/AAABAAEAAAD6/+v/AAABAAEAAAD6/+r/AAABAAEAAAD6/+n/AAABAAAAAAD5/+//AAABAAEAAAD5/+7/AAABAAEAAAD5/+3/AAABAAEAAAD5/+z/AAABAAEAAAD5/+v/AAABAAEAAAD5/+r/AAABAAEAAAD5/+n/AAABAAAAAAD4/+//AAABAAEAAAD4/+7/AAABAAEAAAD4/+3/AAABAAEAAAD4/+z/AAABAAEAAAD4/+v/AAABAAEAAAD4/+r/AAABAAEAAAD4/+n/AAABAAAAAAD3/+//AAABAAEAAAD3/+7/AAABAAEAAAD3/+3/AAABAAEAAAD3/+z/AAABAAEAAAD3/+v/AAABAAEAAAD3/+r/AAABAAEAAAD3/+n/AAABAAAAAAD2/+//AAABAAEAAAD2/+7/AAABAAEAAAD2/+3/AAABAAEAAAD2/+z/AAABAAEAAAD2/+v/AAABAAEAAAD2/+r/AAABAAEAAAD2/+n/AAABAAAAAAD1/+//AAAAAAEAAAD1/+7/AAAAAAEAAAD1/+3/AAAAAAEAAAD1/+z/AAAAAAEAAAD1/+v/AAAAAAEAAAD1/+r/AAAAAAEAAAD1/+n/AAAAAAAAAAD7/wUAAAABAAIAAAD7/wQAAAABAAEAAAD7/wMAAAABAAEAAAD7/wIAAAABAAEAAAD7/wEAAAABAAEAAAD7/wAAAAABAAEAAAD7////AAAFAAIAAAD7//7/AAACAAAAAAD7//j/AAACAAIAAAD7//f/AAACAAEAAAD7//b/AAACAAEAAAD7//X/AAACAAEAAAD7//T/AAACAAEAAAD7//P/AAACAAEAAAD7//L/AAACAAEAAAD7//H/AAACAAEAAAD7//D/AAACAAEAAAD6/wUAAAABAAIAAAD6/wQAAAABAAEAAAD6/wMAAAABAAEAAAD6/wIAAAABAAEAAAD6/wEAAAABAAEAAAD6/wAAAAABAAEAAAD6////AAABAAEAAAD6//7/AAABAAAAAAD6//j/AAABAAIAAAD6//f/AAABAAEAAAD6//b/AAABAAEAAAD6//X/AAABAAEAAAD6//T/AAABAAEAAAD6//P/AAABAAEAAAD6//L/AAABAAEAAAD6//H/AAABAAEAAAD6//D/AAABAAEAAAD5/wUAAAABAAIAAAD5/wQAAAABAAEAAAD5/wMAAAABAAEAAAD5/wIAAAABAAEAAAD5/wEAAAABAAEAAAD5/wAAAAABAAEAAAD5////AAABAAEAAAD5//7/AAABAAAAAAD5//j/AAABAAIAAAD5//f/AAABAAEAAAD5//b/AAABAAEAAAD5//X/AAABAAEAAAD5//T/AAABAAEAAAD5//P/AAABAAEAAAD5//L/AAABAAEAAAD5//H/AAABAAEAAAD5//D/AAABAAEAAAD4/wUAAAABAAIAAAD4/wQAAAABAAEAAAD4/wMAAAABAAEAAAD4/wIAAAABAAEAAAD4/wEAAAABAAEAAAD4/wAAAAABAAEAAAD4////AAABAAEAAAD4//7/AAAFAAIAAAD4//j/AAABAAIAAAD4//f/AAABAAEAAAD4//b/AAABAAEAAAD4//X/AAABAAEAAAD4//T/AAABAAEAAAD4//P/AAABAAEAAAD4//L/AAABAAEAAAD4//H/AAABAAEAAAD4//D/AAABAAEAAAD3/wUAAAABAAIAAAD3/wQAAAABAAEAAAD3/wMAAAABAAEAAAD3/wIAAAABAAEAAAD3/wEAAAABAAEAAAD3/wAAAAABAAEAAAD3////AAABAAEAAAD3//7/AAABAAEAAAD3//j/AAABAAIAAAD3//f/AAABAAEAAAD3//b/AAABAAEAAAD3//X/AAABAAEAAAD3//T/AAABAAEAAAD3//P/AAABAAEAAAD3//L/AAABAAEAAAD3//H/AAABAAEAAAD3//D/AAABAAEAAAD2/wUAAAABAAIAAAD2/wQAAAABAAEAAAD2/wMAAAABAAEAAAD2/wIAAAABAAEAAAD2/wEAAAABAAEAAAD2/wAAAAABAAEAAAD2////AAABAAEAAAD2//7/AAABAAEAAAD2//j/AAAFAAEAAAD2//f/AAABAAEAAAD2//b/AAABAAEAAAD2//X/AAABAAEAAAD2//T/AAABAAEAAAD2//P/AAABAAEAAAD2//L/AAABAAEAAAD2//H/AAABAAEAAAD2//D/AAABAAEAAAD1/wUAAAAAAAIAAAD1/wQAAAAAAAEAAAD1/wMAAAAAAAEAAAD1/wIAAAAAAAEAAAD1/wEAAAAAAAEAAAD1/wAAAAAAAAEAAAD1////AAAAAAEAAAD1//7/AAAAAAEAAAD1//j/AAAAAAEAAAD1//f/AAAAAAEAAAD1//b/AAAAAAEAAAD1//X/AAAAAAEAAAD1//T/AAAAAAEAAAD1//P/AAAAAAEAAAD1//L/AAAAAAEAAAD1//H/AAAAAAEAAAD1//D/AAAAAAEAAAD4//3/AAACAAAAAAD3//3/AAABAAAAAAD2//3/AAAFAAIAAAD1//3/AAAAAAEAAAD1//z/AAAAAAEAAAD1//v/AAAAAAEAAAD1//r/AAAAAAEAAAD1//n/AAAAAAEAAAD2//n/AAACAAEAAAD2//r/AAACAAEAAAD2//v/AAACAAEAAAD2//z/AAACAAEAAAA=") +tile_map_data = PackedByteArray("AAD8////AAABAAAAAAD9////AAABAAAAAAD+////AAABAAAAAAD/////AAABAAAAAAAAAP//AAABAAAAAAABAP//AAABAAAAAAABAAAAAAABAAEAAAABAAEAAAABAAEAAAAAAAEAAAABAAEAAAD//wEAAAABAAEAAAD+/wEAAAABAAEAAAD9/wEAAAABAAEAAAD8/wEAAAABAAEAAAD8/wAAAAABAAEAAAD9/wAAAAABAAEAAAD+/wAAAAABAAEAAAD//wAAAAABAAEAAAAAAAAAAAABAAEAAAACAP//AAABAAAAAAACAAAAAAABAAEAAAACAAEAAAABAAEAAAADAAEAAAABAAEAAAAEAAEAAAABAAEAAAAEAAAAAAABAAEAAAAEAP//AAABAAEAAAADAP//AAAGAAIAAAADAAAAAAABAAEAAAAFAAAAAAABAAEAAAAFAP//AAABAAEAAAAFAAEAAAABAAEAAAADAP7/AAAAAAEAAAADAP3/AAAAAAEAAAADAPz/AAAAAAEAAAADAPv/AAAAAAAAAAAEAPv/AAABAAAAAAAFAPv/AAACAAAAAAAFAPz/AAAFAAIAAAAFAP3/AAABAAEAAAAFAP7/AAABAAEAAAAEAP3/AAABAAEAAAAGAP3/AAABAAEAAAAGAPz/AAABAAAAAAAHAPz/AAACAAAAAAAHAP3/AAAFAAIAAAAIAP3/AAACAAAAAAAIAP7/AAACAAEAAAAIAP//AAACAAEAAAAIAAAAAAAFAAIAAAAJAAAAAAACAAAAAAAJAAEAAAACAAEAAAAJAAIAAAAFAAIAAAAKAAIAAAABAAAAAAALAAIAAAABAAAAAAAMAAIAAAABAAAAAAANAAIAAAAGAAIAAAANAAEAAAAAAAAAAAAOAAEAAAABAAAAAAAPAAEAAAABAAAAAAAQAAEAAAABAAAAAAARAAEAAAABAAAAAAASAAEAAAACAAAAAAASAAIAAAACAAEAAAASAAMAAAACAAEAAAARAAMAAAABAAEAAAAQAAMAAAABAAEAAAAPAAMAAAABAAEAAAAOAAMAAAABAAEAAAANAAMAAAABAAEAAAAMAAMAAAABAAEAAAALAAMAAAABAAEAAAAKAAMAAAABAAEAAAAJAAMAAAABAAEAAAAIAAMAAAABAAEAAAAHAAMAAAABAAEAAAAGAAMAAAABAAEAAAAGAAIAAAABAAEAAAAFAAIAAAABAAEAAAAEAAIAAAABAAEAAAADAAIAAAABAAEAAAACAAIAAAABAAEAAAABAAIAAAABAAEAAAAAAAIAAAABAAEAAAD//wIAAAABAAEAAAD+/wIAAAABAAEAAAD9/wIAAAABAAEAAAD8/wIAAAABAAEAAAD8/wMAAAABAAEAAAD9/wMAAAABAAEAAAD+/wMAAAABAAEAAAD+/wQAAAABAAEAAAD//wQAAAABAAEAAAAAAAQAAAABAAEAAAABAAQAAAABAAEAAAACAAQAAAABAAEAAAADAAQAAAABAAEAAAAEAAQAAAABAAEAAAAFAAQAAAABAAEAAAAGAAQAAAABAAEAAAAHAAQAAAABAAEAAAAIAAQAAAABAAEAAAAJAAQAAAABAAEAAAAKAAQAAAABAAEAAAALAAQAAAABAAEAAAAMAAQAAAABAAEAAAANAAQAAAABAAEAAAAOAAQAAAABAAEAAAAPAAQAAAABAAEAAAARAAIAAAABAAEAAAAQAAIAAAABAAEAAAAPAAIAAAABAAEAAAAOAAIAAAABAAEAAAAQAAQAAAABAAEAAAARAAQAAAABAAEAAAASAAQAAAAFAAIAAAAIAAIAAAABAAEAAAAHAAIAAAABAAEAAAAFAAMAAAABAAEAAAAEAAMAAAABAAEAAAADAAMAAAABAAEAAAACAAMAAAABAAEAAAABAAMAAAABAAEAAAAAAAMAAAABAAEAAAD//wMAAAABAAEAAAD9/wQAAAABAAEAAAD8/wQAAAABAAEAAAAIAAEAAAABAAEAAAAHAAEAAAABAAEAAAAHAAAAAAABAAEAAAAHAP//AAABAAEAAAAHAP7/AAABAAEAAAAGAAEAAAABAAEAAAAGAAAAAAABAAEAAAAGAP//AAABAAEAAAAGAP7/AAABAAEAAAAEAP7/AAABAAEAAAAEAPz/AAABAAEAAAAVAPz/AAABAAIAAAAUAPz/AAABAAIAAAATAP3/AAACAAIAAAASAP3/AAABAAIAAAARAP3/AAABAAIAAAAQAP3/AAABAAIAAAAPAP7/AAACAAIAAAAOAP7/AAAAAAIAAAAdAAQAAAABAAEAAAAcAAMAAAAAAAEAAAAbAAIAAAAAAAIAAAAaAAEAAAAAAAIAAAAZAAAAAAAAAAIAAAAZAP//AAAAAAEAAAAYAP7/AAAAAAIAAAAXAP3/AAAAAAIAAAAWAPz/AAABAAIAAAAcAAQAAAAGAAIAAAAbAAQAAAABAAAAAAAaAAQAAAABAAAAAAAZAAQAAAABAAAAAAAYAAQAAAABAAAAAAAXAAQAAAABAAAAAAAWAAQAAAABAAAAAAAVAAQAAAABAAAAAAAUAAQAAAABAAAAAAATAAQAAAABAAAAAAAdAAMAAAABAAEAAAAdAAIAAAABAAEAAAAcAAIAAAAGAAEAAAAcAAEAAAABAAEAAAAbAAEAAAAGAAEAAAAdAAEAAAABAAEAAAAdAAAAAAABAAEAAAAcAAAAAAABAAEAAAAbAAAAAAABAAEAAAAaAAAAAAAGAAEAAAAaAP//AAABAAEAAAAaAP7/AAABAAEAAAAZAP7/AAAGAAEAAAAZAP3/AAABAAEAAAAYAP3/AAAGAAEAAAAYAPz/AAABAAEAAAAXAPz/AAAGAAEAAAAZAPz/AAABAAEAAAAaAPz/AAABAAEAAAAaAP3/AAABAAEAAAAbAP3/AAABAAEAAAAbAP7/AAABAAEAAAAbAP//AAABAAEAAAAcAP//AAABAAEAAAAdAP//AAABAAEAAAAdAP7/AAABAAEAAAAcAP3/AAABAAEAAAAcAPz/AAABAAEAAAAbAPz/AAABAAEAAAAcAP7/AAABAAEAAAAdAP3/AAABAAEAAAAdAPz/AAABAAEAAAAdAPv/AAABAAEAAAAcAPv/AAABAAEAAAAbAPv/AAABAAEAAAAaAPv/AAABAAEAAAAZAPv/AAABAAEAAAAYAPv/AAABAAEAAAAXAPv/AAABAAEAAAAWAPv/AAABAAEAAAAWAPr/AAABAAAAAAAVAPr/AAABAAAAAAAUAPr/AAABAAAAAAAUAPv/AAABAAEAAAAVAPv/AAABAAEAAAAXAPr/AAABAAAAAAAYAPr/AAABAAAAAAAZAPr/AAABAAAAAAAaAPr/AAABAAAAAAAbAPr/AAABAAAAAAAcAPr/AAABAAAAAAAdAPr/AAABAAAAAAATAPz/AAAFAAEAAAASAPz/AAABAAEAAAARAPz/AAABAAEAAAARAPv/AAABAAEAAAASAPv/AAABAAEAAAASAPr/AAABAAAAAAATAPr/AAABAAAAAAATAPv/AAABAAEAAAARAPr/AAABAAAAAAAQAPr/AAABAAAAAAAQAPv/AAABAAEAAAAQAPz/AAABAAEAAAAPAPz/AAABAAEAAAAPAP3/AAAFAAEAAAAOAP3/AAAAAAEAAAAOAPz/AAAAAAEAAAAOAPv/AAAAAAEAAAAPAPv/AAABAAEAAAAPAPr/AAABAAAAAAAOAPr/AAAAAAAAAAAdAAUAAAABAAIAAAAcAAUAAAABAAIAAAAbAAUAAAABAAIAAAAaAAUAAAABAAIAAAAZAAUAAAABAAIAAAAYAAUAAAABAAIAAAASAAUAAAABAAIAAAATAAUAAAABAAIAAAAUAAUAAAABAAIAAAAVAAUAAAABAAIAAAAWAAUAAAABAAIAAAAXAAUAAAABAAIAAAARAAUAAAABAAIAAAAQAAUAAAABAAIAAAAPAAUAAAABAAIAAAAOAAUAAAABAAIAAAANAAUAAAABAAIAAAAMAAUAAAABAAIAAAALAAUAAAABAAIAAAAKAAUAAAABAAIAAAAJAAUAAAABAAIAAAAIAAUAAAABAAIAAAAHAAUAAAABAAIAAAAGAAUAAAABAAIAAAAFAAUAAAABAAIAAAAEAAUAAAABAAIAAAADAAUAAAABAAIAAAACAAUAAAABAAIAAAABAAUAAAABAAIAAAAAAAUAAAABAAIAAAD//wUAAAABAAIAAAD+/wUAAAABAAIAAAD9/wUAAAABAAIAAAD8/wUAAAABAAIAAAAnAAUAAAACAAIAAAAnAAQAAAACAAEAAAAnAAMAAAACAAEAAAAnAAIAAAACAAEAAAAnAAEAAAACAAEAAAAnAAAAAAACAAEAAAAnAP//AAACAAEAAAAnAP7/AAACAAEAAAAnAP3/AAACAAEAAAAnAPz/AAACAAEAAAAnAPv/AAACAAEAAAAnAPr/AAACAAEAAAAmAAUAAAABAAIAAAAmAAQAAAABAAEAAAAmAAMAAAABAAEAAAAmAAIAAAABAAEAAAAmAAEAAAABAAEAAAAmAAAAAAABAAEAAAAmAP//AAABAAEAAAAmAP7/AAABAAEAAAAmAP3/AAABAAEAAAAmAPz/AAABAAEAAAAmAPv/AAABAAEAAAAmAPr/AAABAAEAAAAlAAUAAAABAAIAAAAlAAQAAAABAAEAAAAlAAMAAAABAAEAAAAlAAIAAAABAAEAAAAlAAEAAAABAAEAAAAlAAAAAAABAAEAAAAlAP//AAABAAEAAAAlAP7/AAABAAEAAAAlAP3/AAABAAEAAAAlAPz/AAABAAEAAAAlAPv/AAABAAEAAAAlAPr/AAABAAEAAAAkAAUAAAABAAIAAAAkAAQAAAABAAEAAAAkAAMAAAABAAEAAAAkAAIAAAABAAEAAAAkAAEAAAABAAEAAAAkAAAAAAABAAEAAAAkAP//AAABAAEAAAAkAP7/AAABAAEAAAAkAP3/AAABAAEAAAAkAPz/AAABAAEAAAAkAPv/AAABAAEAAAAkAPr/AAABAAEAAAAjAAUAAAABAAIAAAAjAAQAAAABAAEAAAAjAAMAAAABAAEAAAAjAAIAAAABAAEAAAAjAAEAAAABAAEAAAAjAAAAAAABAAEAAAAjAP//AAABAAEAAAAjAP7/AAABAAEAAAAjAP3/AAABAAEAAAAjAPz/AAABAAEAAAAjAPv/AAABAAEAAAAjAPr/AAABAAEAAAAiAAUAAAABAAIAAAAiAAQAAAABAAEAAAAiAAMAAAABAAEAAAAiAAIAAAABAAEAAAAiAAEAAAABAAEAAAAiAAAAAAABAAEAAAAiAP//AAABAAEAAAAiAP7/AAABAAEAAAAiAP3/AAABAAEAAAAiAPz/AAABAAEAAAAiAPv/AAABAAEAAAAiAPr/AAABAAEAAAAhAAUAAAABAAIAAAAhAAQAAAABAAEAAAAhAAMAAAABAAEAAAAhAAIAAAABAAEAAAAhAAEAAAABAAEAAAAhAAAAAAABAAEAAAAhAP//AAABAAEAAAAhAP7/AAABAAEAAAAhAP3/AAABAAEAAAAhAPz/AAABAAEAAAAhAPv/AAABAAEAAAAhAPr/AAABAAEAAAAgAAUAAAABAAIAAAAgAAQAAAABAAEAAAAgAAMAAAABAAEAAAAgAAIAAAABAAEAAAAgAAEAAAABAAEAAAAgAAAAAAABAAEAAAAgAP//AAABAAEAAAAgAP7/AAABAAEAAAAgAP3/AAABAAEAAAAgAPz/AAABAAEAAAAgAPv/AAABAAEAAAAgAPr/AAAGAAIAAAAfAAUAAAABAAIAAAAfAAQAAAABAAEAAAAfAAMAAAABAAEAAAAfAAIAAAABAAEAAAAfAAEAAAABAAEAAAAfAAAAAAABAAEAAAAfAP//AAABAAEAAAAfAP7/AAABAAEAAAAfAP3/AAABAAEAAAAfAPz/AAABAAEAAAAfAPv/AAABAAEAAAAfAPr/AAABAAAAAAAeAAUAAAABAAIAAAAeAAQAAAABAAEAAAAeAAMAAAABAAEAAAAeAAIAAAABAAEAAAAeAAEAAAABAAEAAAAeAAAAAAABAAEAAAAeAP//AAABAAEAAAAeAP7/AAABAAEAAAAeAP3/AAABAAEAAAAeAPz/AAABAAEAAAAeAPv/AAABAAEAAAAeAPr/AAABAAAAAAAnAPn/AAACAAEAAAAnAPj/AAACAAEAAAAnAPf/AAACAAEAAAAnAPb/AAACAAEAAAAnAPX/AAACAAEAAAAnAPT/AAAFAAEAAAAnAPP/AAABAAEAAAAnAPL/AAABAAEAAAAnAPH/AAABAAEAAAAnAPD/AAABAAEAAAAmAPn/AAABAAEAAAAmAPj/AAABAAEAAAAmAPf/AAABAAEAAAAmAPb/AAABAAEAAAAmAPX/AAABAAEAAAAmAPT/AAABAAEAAAAmAPP/AAABAAEAAAAmAPL/AAABAAEAAAAmAPH/AAABAAEAAAAmAPD/AAABAAEAAAAlAPn/AAABAAEAAAAlAPj/AAABAAEAAAAlAPf/AAABAAEAAAAlAPb/AAABAAEAAAAlAPX/AAABAAEAAAAlAPT/AAABAAEAAAAlAPP/AAABAAEAAAAlAPL/AAABAAEAAAAlAPH/AAABAAEAAAAlAPD/AAABAAEAAAAkAPn/AAABAAEAAAAkAPj/AAABAAEAAAAkAPf/AAABAAEAAAAkAPb/AAABAAEAAAAkAPX/AAABAAEAAAAkAPT/AAABAAEAAAAkAPP/AAABAAEAAAAkAPL/AAABAAEAAAAkAPH/AAABAAEAAAAkAPD/AAABAAEAAAAjAPn/AAABAAEAAAAjAPj/AAABAAEAAAAjAPf/AAABAAEAAAAjAPb/AAABAAEAAAAjAPX/AAABAAEAAAAjAPT/AAABAAEAAAAjAPP/AAABAAEAAAAjAPL/AAABAAEAAAAjAPH/AAABAAEAAAAjAPD/AAABAAEAAAAiAPn/AAABAAEAAAAiAPj/AAABAAEAAAAiAPf/AAABAAEAAAAiAPb/AAABAAEAAAAiAPX/AAABAAEAAAAiAPT/AAABAAEAAAAiAPP/AAABAAEAAAAiAPL/AAABAAEAAAAiAPH/AAABAAEAAAAiAPD/AAABAAEAAAAhAPn/AAABAAEAAAAhAPj/AAABAAEAAAAhAPf/AAABAAEAAAAhAPb/AAABAAEAAAAhAPX/AAABAAEAAAAhAPT/AAABAAEAAAAhAPP/AAABAAEAAAAhAPL/AAABAAEAAAAhAPH/AAABAAEAAAAhAPD/AAABAAEAAAAgAPn/AAAAAAEAAAAgAPj/AAAAAAEAAAAgAPf/AAAAAAEAAAAgAPb/AAAAAAEAAAAgAPX/AAAAAAEAAAAgAPT/AAAAAAEAAAAgAPP/AAAAAAEAAAAgAPL/AAAAAAEAAAAgAPH/AAAAAAEAAAAgAPD/AAAAAAEAAAApAO//AAACAAEAAAApAO7/AAACAAEAAAApAO3/AAACAAEAAAApAOz/AAACAAEAAAApAOv/AAACAAEAAAApAOr/AAACAAAAAAAoAO//AAABAAEAAAAoAO7/AAABAAEAAAAoAO3/AAABAAEAAAAoAOz/AAABAAEAAAAoAOv/AAABAAEAAAAoAOr/AAABAAAAAAAnAO//AAABAAEAAAAnAO7/AAABAAEAAAAnAO3/AAABAAEAAAAnAOz/AAABAAEAAAAnAOv/AAABAAEAAAAnAOr/AAABAAAAAAAmAO//AAABAAEAAAAmAO7/AAABAAEAAAAmAO3/AAABAAEAAAAmAOz/AAABAAEAAAAmAOv/AAABAAEAAAAmAOr/AAABAAAAAAAlAO//AAABAAEAAAAlAO7/AAABAAEAAAAlAO3/AAABAAEAAAAlAOz/AAABAAEAAAAlAOv/AAABAAEAAAAlAOr/AAABAAAAAAAkAO//AAABAAEAAAAkAO7/AAABAAEAAAAkAO3/AAABAAEAAAAkAOz/AAABAAEAAAAkAOv/AAABAAEAAAAkAOr/AAABAAAAAAAjAO//AAABAAEAAAAjAO7/AAABAAEAAAAjAO3/AAABAAEAAAAjAOz/AAABAAEAAAAjAOv/AAABAAEAAAAjAOr/AAABAAAAAAAiAO//AAABAAEAAAAiAO7/AAABAAEAAAAiAO3/AAABAAEAAAAiAOz/AAABAAEAAAAiAOv/AAABAAEAAAAiAOr/AAABAAAAAAAhAO//AAABAAEAAAAhAO7/AAABAAEAAAAhAO3/AAABAAEAAAAhAOz/AAABAAEAAAAhAOv/AAABAAEAAAAhAOr/AAABAAAAAAAgAO//AAAGAAEAAAAgAO7/AAABAAEAAAAgAO3/AAABAAEAAAAgAOz/AAABAAEAAAAgAOv/AAABAAEAAAAgAOr/AAABAAAAAAApAPT/AAACAAIAAAApAPP/AAACAAEAAAApAPL/AAACAAEAAAApAPH/AAACAAEAAAApAPD/AAACAAEAAAAoAPT/AAABAAIAAAAoAPP/AAABAAEAAAAoAPL/AAABAAEAAAAoAPH/AAABAAEAAAAoAPD/AAABAAEAAAAfAO//AAABAAIAAAAfAO7/AAABAAEAAAAfAO3/AAABAAEAAAAfAOz/AAABAAEAAAAfAOv/AAABAAEAAAAfAOr/AAABAAAAAAAeAO//AAABAAIAAAAeAO7/AAABAAEAAAAeAO3/AAABAAEAAAAeAOz/AAABAAEAAAAeAOv/AAABAAEAAAAeAOr/AAABAAAAAAAdAO//AAABAAIAAAAdAO7/AAABAAEAAAAdAO3/AAABAAEAAAAdAOz/AAABAAEAAAAdAOv/AAABAAEAAAAdAOr/AAABAAAAAAAcAO//AAABAAIAAAAcAO7/AAABAAEAAAAcAO3/AAABAAEAAAAcAOz/AAABAAEAAAAcAOv/AAABAAEAAAAcAOr/AAABAAAAAAAbAO//AAABAAIAAAAbAO7/AAABAAEAAAAbAO3/AAABAAEAAAAbAOz/AAABAAEAAAAbAOv/AAABAAEAAAAbAOr/AAABAAAAAAAaAO//AAABAAIAAAAaAO7/AAABAAEAAAAaAO3/AAABAAEAAAAaAOz/AAABAAEAAAAaAOv/AAABAAEAAAAaAOr/AAABAAAAAAAZAO//AAABAAIAAAAZAO7/AAABAAEAAAAZAO3/AAABAAEAAAAZAOz/AAABAAEAAAAZAOv/AAABAAEAAAAZAOr/AAABAAAAAAAYAO//AAABAAIAAAAYAO7/AAABAAEAAAAYAO3/AAABAAEAAAAYAOz/AAABAAEAAAAYAOv/AAABAAEAAAAYAOr/AAABAAAAAAAXAO//AAABAAIAAAAXAO7/AAABAAEAAAAXAO3/AAABAAEAAAAXAOz/AAABAAEAAAAXAOv/AAABAAEAAAAXAOr/AAABAAAAAAAWAO//AAABAAIAAAAWAO7/AAABAAEAAAAWAO3/AAABAAEAAAAWAOz/AAABAAEAAAAWAOv/AAABAAEAAAAWAOr/AAABAAAAAAAVAO//AAABAAIAAAAVAO7/AAABAAEAAAAVAO3/AAABAAEAAAAVAOz/AAABAAEAAAAVAOv/AAABAAEAAAAVAOr/AAABAAAAAAAUAO//AAABAAIAAAAUAO7/AAABAAEAAAAUAO3/AAABAAEAAAAUAOz/AAABAAEAAAAUAOv/AAABAAEAAAAUAOr/AAABAAAAAAATAO//AAABAAIAAAATAO7/AAABAAEAAAATAO3/AAABAAEAAAATAOz/AAABAAEAAAATAOv/AAABAAEAAAATAOr/AAABAAAAAAASAO//AAABAAIAAAASAO7/AAABAAEAAAASAO3/AAABAAEAAAASAOz/AAABAAEAAAASAOv/AAABAAEAAAASAOr/AAABAAAAAAARAO//AAABAAIAAAARAO7/AAABAAEAAAARAO3/AAABAAEAAAARAOz/AAABAAEAAAARAOv/AAABAAEAAAARAOr/AAABAAAAAAAQAO//AAABAAIAAAAQAO7/AAABAAEAAAAQAO3/AAABAAEAAAAQAOz/AAABAAEAAAAQAOv/AAABAAEAAAAQAOr/AAABAAAAAAAPAO//AAABAAIAAAAPAO7/AAABAAEAAAAPAO3/AAABAAEAAAAPAOz/AAABAAEAAAAPAOv/AAABAAEAAAAPAOr/AAABAAAAAAAOAO//AAABAAIAAAAOAO7/AAABAAEAAAAOAO3/AAABAAEAAAAOAOz/AAABAAEAAAAOAOv/AAABAAEAAAAOAOr/AAABAAAAAAANAO//AAABAAIAAAANAO7/AAABAAEAAAANAO3/AAABAAEAAAANAOz/AAABAAEAAAANAOv/AAABAAEAAAANAOr/AAABAAAAAAAMAO//AAABAAIAAAAMAO7/AAABAAEAAAAMAO3/AAABAAEAAAAMAOz/AAABAAEAAAAMAOv/AAABAAEAAAAMAOr/AAABAAAAAAALAO//AAABAAIAAAALAO7/AAABAAEAAAALAO3/AAABAAEAAAALAOz/AAABAAEAAAALAOv/AAABAAEAAAALAOr/AAABAAAAAAAKAO//AAABAAIAAAAKAO7/AAABAAEAAAAKAO3/AAABAAEAAAAKAOz/AAABAAEAAAAKAOv/AAABAAEAAAAKAOr/AAABAAAAAAAJAO//AAABAAIAAAAJAO7/AAABAAEAAAAJAO3/AAABAAEAAAAJAOz/AAABAAEAAAAJAOv/AAABAAEAAAAJAOr/AAABAAAAAAAIAO//AAABAAIAAAAIAO7/AAABAAEAAAAIAO3/AAABAAEAAAAIAOz/AAABAAEAAAAIAOv/AAABAAEAAAAIAOr/AAABAAAAAAAHAO//AAABAAIAAAAHAO7/AAABAAEAAAAHAO3/AAABAAEAAAAHAOz/AAABAAEAAAAHAOv/AAABAAEAAAAHAOr/AAABAAAAAAAGAO//AAABAAIAAAAGAO7/AAABAAEAAAAGAO3/AAABAAEAAAAGAOz/AAABAAEAAAAGAOv/AAABAAEAAAAGAOr/AAABAAAAAAAFAO//AAABAAIAAAAFAO7/AAABAAEAAAAFAO3/AAABAAEAAAAFAOz/AAABAAEAAAAFAOv/AAABAAEAAAAFAOr/AAABAAAAAAAEAO//AAABAAIAAAAEAO7/AAABAAEAAAAEAO3/AAABAAEAAAAEAOz/AAABAAEAAAAEAOv/AAABAAEAAAAEAOr/AAABAAAAAAADAO//AAABAAIAAAADAO7/AAABAAEAAAADAO3/AAABAAEAAAADAOz/AAABAAEAAAADAOv/AAABAAEAAAADAOr/AAABAAAAAAACAO//AAABAAIAAAACAO7/AAABAAEAAAACAO3/AAABAAEAAAACAOz/AAABAAEAAAACAOv/AAABAAEAAAACAOr/AAABAAAAAAABAO//AAABAAIAAAABAO7/AAABAAEAAAABAO3/AAABAAEAAAABAOz/AAABAAEAAAABAOv/AAABAAEAAAABAOr/AAABAAAAAAAAAO//AAABAAIAAAAAAO7/AAABAAEAAAAAAO3/AAABAAEAAAAAAOz/AAABAAEAAAAAAOv/AAABAAEAAAAAAOr/AAABAAAAAAD//+//AAABAAIAAAD//+7/AAABAAEAAAD//+3/AAABAAEAAAD//+z/AAABAAEAAAD//+v/AAABAAEAAAD//+r/AAABAAAAAAD+/+//AAABAAIAAAD+/+7/AAABAAEAAAD+/+3/AAABAAEAAAD+/+z/AAABAAEAAAD+/+v/AAABAAEAAAD+/+r/AAABAAAAAAD9/+//AAABAAIAAAD9/+7/AAABAAEAAAD9/+3/AAABAAEAAAD9/+z/AAABAAEAAAD9/+v/AAABAAEAAAD9/+r/AAAFAAIAAAD9/+n/AAACAAAAAAD8/+//AAABAAIAAAD8/+7/AAABAAEAAAD8/+3/AAABAAEAAAD8/+z/AAABAAEAAAD8/+v/AAABAAEAAAD8/+r/AAABAAEAAAD8/+n/AAABAAAAAAD7/+//AAAFAAEAAAD7/+7/AAABAAEAAAD7/+3/AAABAAEAAAD7/+z/AAABAAEAAAD7/+v/AAABAAEAAAD7/+r/AAABAAEAAAD7/+n/AAABAAAAAAD6/+//AAABAAEAAAD6/+7/AAABAAEAAAD6/+3/AAABAAEAAAD6/+z/AAABAAEAAAD6/+v/AAABAAEAAAD6/+r/AAABAAEAAAD6/+n/AAABAAAAAAD5/+//AAABAAEAAAD5/+7/AAABAAEAAAD5/+3/AAABAAEAAAD5/+z/AAABAAEAAAD5/+v/AAABAAEAAAD5/+r/AAABAAEAAAD5/+n/AAABAAAAAAD4/+//AAABAAEAAAD4/+7/AAABAAEAAAD4/+3/AAABAAEAAAD4/+z/AAABAAEAAAD4/+v/AAABAAEAAAD4/+r/AAABAAEAAAD4/+n/AAABAAAAAAD3/+//AAABAAEAAAD3/+7/AAABAAEAAAD3/+3/AAABAAEAAAD3/+z/AAABAAEAAAD3/+v/AAABAAEAAAD3/+r/AAABAAEAAAD3/+n/AAABAAAAAAD2/+//AAABAAEAAAD2/+7/AAABAAEAAAD2/+3/AAABAAEAAAD2/+z/AAABAAEAAAD2/+v/AAABAAEAAAD2/+r/AAABAAEAAAD2/+n/AAABAAAAAAD1/+//AAAAAAEAAAD1/+7/AAAAAAEAAAD1/+3/AAAAAAEAAAD1/+z/AAAAAAEAAAD1/+v/AAAAAAEAAAD1/+r/AAAAAAEAAAD1/+n/AAAAAAAAAAD7/wUAAAABAAIAAAD7/wQAAAABAAEAAAD7/wMAAAABAAEAAAD7/wIAAAABAAEAAAD7/wEAAAABAAEAAAD7/wAAAAABAAEAAAD7////AAAFAAIAAAD7//7/AAACAAAAAAD7//j/AAACAAIAAAD7//f/AAACAAEAAAD7//b/AAACAAEAAAD7//X/AAACAAEAAAD7//T/AAACAAEAAAD7//P/AAACAAEAAAD7//L/AAACAAEAAAD7//H/AAACAAEAAAD7//D/AAACAAEAAAD6/wUAAAABAAIAAAD6/wQAAAABAAEAAAD6/wMAAAABAAEAAAD6/wIAAAABAAEAAAD6/wEAAAABAAEAAAD6/wAAAAABAAEAAAD6////AAABAAEAAAD6//7/AAABAAAAAAD6//j/AAABAAIAAAD6//f/AAABAAEAAAD6//b/AAABAAEAAAD6//X/AAABAAEAAAD6//T/AAABAAEAAAD6//P/AAABAAEAAAD6//L/AAABAAEAAAD6//H/AAABAAEAAAD6//D/AAABAAEAAAD5/wUAAAABAAIAAAD5/wQAAAABAAEAAAD5/wMAAAABAAEAAAD5/wIAAAABAAEAAAD5/wEAAAABAAEAAAD5/wAAAAABAAEAAAD5////AAABAAEAAAD5//7/AAABAAAAAAD5//j/AAABAAIAAAD5//f/AAABAAEAAAD5//b/AAABAAEAAAD5//X/AAABAAEAAAD5//T/AAABAAEAAAD5//P/AAABAAEAAAD5//L/AAABAAEAAAD5//H/AAABAAEAAAD5//D/AAABAAEAAAD4/wUAAAABAAIAAAD4/wQAAAABAAEAAAD4/wMAAAABAAEAAAD4/wIAAAABAAEAAAD4/wEAAAABAAEAAAD4/wAAAAABAAEAAAD4////AAABAAEAAAD4//7/AAAFAAIAAAD4//j/AAABAAIAAAD4//f/AAABAAEAAAD4//b/AAABAAEAAAD4//X/AAABAAEAAAD4//T/AAABAAEAAAD4//P/AAABAAEAAAD4//L/AAABAAEAAAD4//H/AAABAAEAAAD4//D/AAABAAEAAAD3/wUAAAABAAIAAAD3/wQAAAABAAEAAAD3/wMAAAABAAEAAAD3/wIAAAABAAEAAAD3/wEAAAABAAEAAAD3/wAAAAABAAEAAAD3////AAABAAEAAAD3//7/AAABAAEAAAD3//j/AAABAAIAAAD3//f/AAABAAEAAAD3//b/AAABAAEAAAD3//X/AAABAAEAAAD3//T/AAABAAEAAAD3//P/AAABAAEAAAD3//L/AAABAAEAAAD3//H/AAABAAEAAAD3//D/AAABAAEAAAD2/wUAAAABAAIAAAD2/wQAAAABAAEAAAD2/wMAAAABAAEAAAD2/wIAAAABAAEAAAD2/wEAAAABAAEAAAD2/wAAAAABAAEAAAD2////AAABAAEAAAD2//7/AAABAAEAAAD2//j/AAAFAAEAAAD2//f/AAABAAEAAAD2//b/AAABAAEAAAD2//X/AAABAAEAAAD2//T/AAABAAEAAAD2//P/AAABAAEAAAD2//L/AAABAAEAAAD2//H/AAABAAEAAAD2//D/AAABAAEAAAD1/wUAAAAAAAIAAAD1/wQAAAAAAAEAAAD1/wMAAAAAAAEAAAD1/wIAAAAAAAEAAAD1/wEAAAAAAAEAAAD1/wAAAAAAAAEAAAD1////AAAAAAEAAAD1//7/AAAAAAEAAAD1//j/AAAAAAEAAAD1//f/AAAAAAEAAAD1//b/AAAAAAEAAAD1//X/AAAAAAEAAAD1//T/AAAAAAEAAAD1//P/AAAAAAEAAAD1//L/AAAAAAEAAAD1//H/AAAAAAEAAAD1//D/AAAAAAEAAAD4//3/AAACAAAAAAD3//3/AAABAAAAAAD2//3/AAAFAAIAAAD1//3/AAAAAAEAAAD1//z/AAAAAAEAAAD1//v/AAAAAAEAAAD1//r/AAAAAAEAAAD1//n/AAAAAAEAAAD2//n/AAACAAEAAAD2//r/AAACAAEAAAD2//v/AAACAAEAAAD2//z/AAACAAEAAABiAAcAAAACAAIAAABiAAYAAAACAAEAAABiAAUAAAACAAEAAABiAAQAAAACAAEAAABiAAMAAAACAAEAAABiAAIAAAACAAEAAABiAAEAAAACAAEAAABiAAAAAAACAAEAAABhAAcAAAABAAIAAABhAAYAAAABAAEAAABhAAUAAAABAAEAAABhAAQAAAABAAEAAABhAAMAAAABAAEAAABhAAIAAAABAAEAAABhAAEAAAABAAEAAABhAAAAAAABAAEAAABgAAcAAAABAAIAAABgAAYAAAABAAEAAABgAAUAAAABAAEAAABgAAQAAAABAAEAAABgAAMAAAABAAEAAABgAAIAAAABAAEAAABgAAEAAAABAAEAAABgAAAAAAABAAEAAABfAAcAAAABAAIAAABfAAYAAAABAAEAAABfAAUAAAABAAEAAABfAAQAAAABAAEAAABfAAMAAAABAAEAAABfAAIAAAABAAEAAABfAAEAAAABAAEAAABfAAAAAAABAAEAAABeAAcAAAABAAIAAABeAAYAAAABAAEAAABeAAUAAAABAAEAAABeAAQAAAABAAEAAABeAAMAAAABAAEAAABeAAIAAAABAAEAAABeAAEAAAABAAEAAABeAAAAAAABAAEAAABdAAcAAAABAAIAAABdAAYAAAABAAEAAABdAAUAAAABAAEAAABdAAQAAAABAAEAAABdAAMAAAABAAEAAABdAAIAAAABAAEAAABdAAEAAAABAAEAAABdAAAAAAABAAEAAABcAAcAAAABAAIAAABcAAYAAAABAAEAAABcAAUAAAABAAEAAABcAAQAAAABAAEAAABcAAMAAAABAAEAAABcAAIAAAABAAEAAABcAAEAAAABAAEAAABcAAAAAAABAAEAAABbAAcAAAABAAIAAABbAAYAAAABAAEAAABbAAUAAAABAAEAAABbAAQAAAABAAEAAABbAAMAAAABAAEAAABbAAIAAAABAAEAAABbAAEAAAABAAEAAABbAAAAAAABAAEAAABaAAcAAAABAAIAAABaAAYAAAABAAEAAABaAAUAAAABAAEAAABaAAQAAAABAAEAAABaAAMAAAABAAEAAABaAAIAAAABAAEAAABaAAEAAAABAAEAAABaAAAAAAABAAEAAABZAAcAAAABAAIAAABZAAYAAAABAAEAAABZAAUAAAABAAEAAABZAAQAAAABAAEAAABZAAMAAAABAAEAAABZAAIAAAABAAEAAABZAAEAAAABAAEAAABZAAAAAAABAAEAAABYAAcAAAABAAIAAABYAAYAAAABAAEAAABYAAUAAAABAAEAAABYAAQAAAABAAEAAABYAAMAAAABAAEAAABYAAIAAAABAAEAAABYAAEAAAABAAEAAABYAAAAAAABAAEAAABXAAcAAAABAAIAAABXAAYAAAABAAEAAABXAAUAAAABAAEAAABXAAQAAAABAAEAAABXAAMAAAABAAEAAABXAAIAAAABAAEAAABXAAEAAAABAAEAAABXAAAAAAABAAEAAABWAAcAAAABAAIAAABWAAYAAAABAAEAAABWAAUAAAABAAEAAABWAAQAAAABAAEAAABWAAMAAAABAAEAAABWAAIAAAABAAEAAABWAAEAAAABAAEAAABWAAAAAAABAAEAAABVAAcAAAABAAIAAABVAAYAAAABAAEAAABVAAUAAAABAAEAAABVAAQAAAABAAEAAABVAAMAAAABAAEAAABVAAIAAAABAAEAAABVAAEAAAABAAEAAABVAAAAAAABAAEAAABUAAcAAAABAAIAAABUAAYAAAABAAEAAABUAAUAAAABAAEAAABUAAQAAAABAAEAAABUAAMAAAABAAEAAABUAAIAAAABAAEAAABUAAEAAAABAAEAAABUAAAAAAABAAEAAABTAAcAAAABAAIAAABTAAYAAAABAAEAAABTAAUAAAABAAEAAABTAAQAAAABAAEAAABTAAMAAAABAAEAAABTAAIAAAABAAEAAABTAAEAAAABAAEAAABTAAAAAAABAAEAAABSAAcAAAABAAIAAABSAAYAAAABAAEAAABSAAUAAAABAAEAAABSAAQAAAABAAEAAABSAAMAAAABAAEAAABSAAIAAAABAAEAAABSAAEAAAABAAEAAABSAAAAAAABAAEAAABRAAcAAAABAAIAAABRAAYAAAABAAEAAABRAAUAAAABAAEAAABRAAQAAAABAAEAAABRAAMAAAABAAEAAABRAAIAAAABAAEAAABRAAEAAAABAAEAAABRAAAAAAABAAEAAABQAAcAAAABAAIAAABQAAYAAAABAAEAAABQAAUAAAABAAEAAABQAAQAAAABAAEAAABQAAMAAAABAAEAAABQAAIAAAABAAEAAABQAAEAAAABAAEAAABQAAAAAAABAAEAAABPAAcAAAABAAIAAABPAAYAAAABAAEAAABPAAUAAAABAAEAAABPAAQAAAABAAEAAABPAAMAAAABAAEAAABPAAIAAAABAAEAAABPAAEAAAABAAEAAABPAAAAAAABAAEAAABOAAcAAAABAAIAAABOAAYAAAABAAEAAABOAAUAAAABAAEAAABOAAQAAAABAAEAAABOAAMAAAABAAEAAABOAAIAAAABAAEAAABOAAEAAAABAAEAAABOAAAAAAABAAEAAABNAAcAAAABAAIAAABNAAYAAAABAAEAAABNAAUAAAABAAEAAABNAAQAAAABAAEAAABNAAMAAAABAAEAAABNAAIAAAABAAEAAABNAAEAAAABAAEAAABNAAAAAAABAAEAAABMAAcAAAABAAIAAABMAAYAAAABAAEAAABMAAUAAAABAAEAAABMAAQAAAABAAEAAABMAAMAAAABAAEAAABMAAIAAAABAAEAAABMAAEAAAABAAEAAABMAAAAAAABAAEAAABLAAcAAAABAAIAAABLAAYAAAABAAEAAABLAAUAAAABAAEAAABLAAQAAAABAAEAAABLAAMAAAABAAEAAABLAAIAAAABAAEAAABLAAEAAAABAAEAAABLAAAAAAAGAAIAAABKAAcAAAABAAIAAABKAAYAAAABAAEAAABKAAUAAAABAAEAAABKAAQAAAABAAEAAABKAAMAAAABAAEAAABKAAIAAAABAAEAAABKAAEAAAABAAEAAABKAAAAAAABAAAAAABJAAcAAAABAAIAAABJAAYAAAABAAEAAABJAAUAAAABAAEAAABJAAQAAAABAAEAAABJAAMAAAABAAEAAABJAAIAAAABAAEAAABJAAEAAAABAAEAAABJAAAAAAABAAAAAABIAAcAAAABAAIAAABIAAYAAAABAAEAAABIAAUAAAABAAEAAABIAAQAAAABAAEAAABIAAMAAAABAAEAAABIAAIAAAABAAEAAABIAAEAAAABAAEAAABIAAAAAAABAAAAAABHAAcAAAABAAIAAABHAAYAAAABAAEAAABHAAUAAAABAAEAAABHAAQAAAABAAEAAABHAAMAAAABAAEAAABHAAIAAAABAAEAAABHAAEAAAABAAEAAABHAAAAAAABAAAAAABGAAcAAAAAAAIAAABGAAYAAAAAAAEAAABGAAUAAAAAAAEAAABGAAQAAAAAAAEAAABGAAMAAAAAAAEAAABGAAIAAAAAAAEAAABGAAEAAAAAAAEAAABGAAAAAAAAAAAAAAA/AAcAAAACAAIAAAA/AAYAAAACAAEAAAA/AAUAAAACAAEAAAA/AAQAAAACAAEAAAA/AAMAAAACAAEAAAA/AAIAAAACAAEAAAA/AAEAAAACAAEAAAA/AAAAAAACAAEAAAA+AAcAAAABAAIAAAA+AAYAAAABAAEAAAA+AAUAAAABAAEAAAA+AAQAAAABAAEAAAA+AAMAAAABAAEAAAA+AAIAAAABAAEAAAA+AAEAAAABAAEAAAA+AAAAAAABAAEAAAA9AAcAAAABAAIAAAA9AAYAAAABAAEAAAA9AAUAAAABAAEAAAA9AAQAAAABAAEAAAA9AAMAAAABAAEAAAA9AAIAAAABAAEAAAA9AAEAAAABAAEAAAA9AAAAAAABAAEAAAA8AAcAAAABAAIAAAA8AAYAAAABAAEAAAA8AAUAAAABAAEAAAA8AAQAAAABAAEAAAA8AAMAAAABAAEAAAA8AAIAAAABAAEAAAA8AAEAAAABAAEAAAA8AAAAAAABAAEAAAA7AAcAAAABAAIAAAA7AAYAAAABAAEAAAA7AAUAAAABAAEAAAA7AAQAAAABAAEAAAA7AAMAAAABAAEAAAA7AAIAAAABAAEAAAA7AAEAAAABAAEAAAA7AAAAAAABAAEAAAA6AAcAAAABAAIAAAA6AAYAAAABAAEAAAA6AAUAAAABAAEAAAA6AAQAAAABAAEAAAA6AAMAAAABAAEAAAA6AAIAAAABAAEAAAA6AAEAAAABAAEAAAA6AAAAAAABAAEAAAA5AAcAAAABAAIAAAA5AAYAAAABAAEAAAA5AAUAAAABAAEAAAA5AAQAAAABAAEAAAA5AAMAAAABAAEAAAA5AAIAAAABAAEAAAA5AAEAAAABAAEAAAA5AAAAAAABAAEAAAA4AAcAAAABAAIAAAA4AAYAAAABAAEAAAA4AAUAAAABAAEAAAA4AAQAAAABAAEAAAA4AAMAAAABAAEAAAA4AAIAAAABAAEAAAA4AAEAAAABAAEAAAA4AAAAAAABAAEAAAA3AAcAAAAAAAIAAAA3AAYAAAAAAAEAAAA3AAUAAAAAAAEAAAA3AAQAAAAAAAEAAAA3AAMAAAAAAAEAAAA3AAIAAAAAAAEAAAA3AAEAAAAAAAEAAAA3AAAAAAAAAAEAAAA/AP//AAACAAEAAAA/AP7/AAACAAEAAAA/AP3/AAACAAEAAAA/APz/AAACAAEAAAA/APv/AAACAAEAAAA/APr/AAACAAAAAAA+AP//AAABAAEAAAA+AP7/AAABAAEAAAA+AP3/AAABAAEAAAA+APz/AAABAAEAAAA+APv/AAABAAEAAAA+APr/AAABAAAAAAA9AP//AAABAAEAAAA9AP7/AAABAAEAAAA9AP3/AAABAAEAAAA9APz/AAABAAEAAAA9APv/AAABAAEAAAA9APr/AAABAAAAAAA8AP//AAABAAEAAAA8AP7/AAABAAEAAAA8AP3/AAABAAEAAAA8APz/AAABAAEAAAA8APv/AAABAAEAAAA8APr/AAABAAAAAAA7AP//AAABAAEAAAA7AP7/AAABAAEAAAA7AP3/AAABAAEAAAA7APz/AAABAAEAAAA7APv/AAABAAEAAAA7APr/AAABAAAAAAA6AP//AAABAAEAAAA6AP7/AAABAAEAAAA6AP3/AAABAAEAAAA6APz/AAABAAEAAAA6APv/AAABAAEAAAA6APr/AAABAAAAAAA5AP//AAABAAEAAAA5AP7/AAABAAEAAAA5AP3/AAABAAEAAAA5APz/AAABAAEAAAA5APv/AAABAAEAAAA5APr/AAABAAAAAAA4AP//AAABAAEAAAA4AP7/AAABAAEAAAA4AP3/AAABAAEAAAA4APz/AAABAAEAAAA4APv/AAABAAEAAAA4APr/AAABAAAAAAA3AP//AAAAAAEAAAA3AP7/AAAAAAEAAAA3AP3/AAAAAAEAAAA3APz/AAAAAAEAAAA3APv/AAAAAAEAAAA3APr/AAAAAAAAAABiAP//AAACAAEAAABiAP7/AAACAAAAAABhAP//AAABAAEAAABhAP7/AAABAAAAAABgAP//AAABAAEAAABgAP7/AAABAAAAAABfAP//AAABAAEAAABfAP7/AAABAAAAAABeAP//AAABAAEAAABeAP7/AAABAAAAAABdAP//AAABAAEAAABdAP7/AAABAAAAAABcAP//AAABAAEAAABcAP7/AAABAAAAAABbAP//AAABAAEAAABbAP7/AAABAAAAAABaAP//AAABAAEAAABaAP7/AAABAAAAAABZAP//AAABAAEAAABZAP7/AAABAAAAAABYAP//AAABAAEAAABYAP7/AAABAAAAAABXAP//AAABAAEAAABXAP7/AAABAAAAAABWAP//AAABAAEAAABWAP7/AAABAAAAAABVAP//AAABAAEAAABVAP7/AAABAAAAAABUAP//AAABAAEAAABUAP7/AAABAAAAAABTAP//AAABAAEAAABTAP7/AAABAAAAAABSAP//AAABAAEAAABSAP7/AAABAAAAAABRAP//AAABAAEAAABRAP7/AAABAAAAAABQAP//AAABAAEAAABQAP7/AAABAAAAAABPAP//AAABAAEAAABPAP7/AAABAAAAAABOAP//AAABAAEAAABOAP7/AAABAAAAAABNAP//AAABAAEAAABNAP7/AAABAAAAAABMAP//AAABAAEAAABMAP7/AAABAAAAAABLAP//AAAAAAEAAABLAP7/AAAAAAAAAABeAPr/AAABAAIAAABeAPn/AAABAAAAAABdAPr/AAABAAIAAABdAPn/AAABAAAAAABcAPr/AAABAAIAAABcAPn/AAABAAAAAABbAPr/AAABAAIAAABbAPn/AAABAAAAAABaAPr/AAABAAIAAABaAPn/AAABAAAAAABZAPr/AAABAAIAAABZAPn/AAABAAAAAABYAPr/AAABAAIAAABYAPn/AAABAAAAAABXAPr/AAABAAIAAABXAPn/AAABAAAAAABWAPr/AAABAAIAAABWAPn/AAABAAAAAABVAPr/AAABAAIAAABVAPn/AAABAAAAAABUAPr/AAABAAIAAABUAPn/AAABAAAAAABTAPr/AAABAAIAAABTAPn/AAABAAAAAABSAPr/AAABAAIAAABSAPn/AAABAAAAAABRAPr/AAABAAIAAABRAPn/AAABAAAAAABQAPr/AAABAAIAAABQAPn/AAABAAAAAABPAPr/AAABAAIAAABPAPn/AAABAAAAAABOAPr/AAABAAIAAABOAPn/AAABAAAAAABNAPr/AAABAAIAAABNAPn/AAABAAAAAABMAPr/AAAAAAIAAABMAPn/AAAAAAAAAABiAPr/AAACAAIAAABiAPn/AAACAAAAAABhAPr/AAABAAIAAABhAPn/AAABAAAAAABgAPr/AAABAAIAAABgAPn/AAABAAAAAABfAPr/AAABAAIAAABfAPn/AAABAAAAAACMAAcAAAABAAIAAACMAAYAAAABAAEAAACMAAUAAAABAAEAAACMAAQAAAABAAEAAACMAAMAAAABAAEAAACMAAIAAAABAAEAAACMAAEAAAABAAEAAACMAAAAAAABAAEAAACMAP//AAABAAEAAACMAP7/AAABAAEAAACMAP3/AAABAAAAAACLAAcAAAABAAIAAACLAAYAAAABAAEAAACLAAUAAAABAAEAAACLAAQAAAABAAEAAACLAAMAAAABAAEAAACLAAIAAAABAAEAAACLAAEAAAABAAEAAACLAAAAAAABAAEAAACLAP//AAABAAEAAACLAP7/AAABAAEAAACLAP3/AAABAAAAAACKAAcAAAABAAIAAACKAAYAAAABAAEAAACKAAUAAAABAAEAAACKAAQAAAABAAEAAACKAAMAAAABAAEAAACKAAIAAAABAAEAAACKAAEAAAABAAEAAACKAAAAAAABAAEAAACKAP//AAABAAEAAACKAP7/AAABAAEAAACKAP3/AAABAAAAAACJAAcAAAABAAIAAACJAAYAAAABAAEAAACJAAUAAAABAAEAAACJAAQAAAABAAEAAACJAAMAAAABAAEAAACJAAIAAAABAAEAAACJAAEAAAABAAEAAACJAAAAAAABAAEAAACJAP//AAABAAEAAACJAP7/AAABAAEAAACJAP3/AAABAAAAAACIAAcAAAABAAIAAACIAAYAAAABAAEAAACIAAUAAAABAAEAAACIAAQAAAABAAEAAACIAAMAAAABAAEAAACIAAIAAAABAAEAAACIAAEAAAABAAEAAACIAAAAAAABAAEAAACIAP//AAABAAEAAACIAP7/AAABAAEAAACIAP3/AAABAAAAAACHAAcAAAABAAIAAACHAAYAAAABAAEAAACHAAUAAAABAAEAAACHAAQAAAABAAEAAACHAAMAAAABAAEAAACHAAIAAAABAAEAAACHAAEAAAABAAEAAACHAAAAAAABAAEAAACHAP//AAABAAEAAACHAP7/AAABAAEAAACHAP3/AAABAAAAAACGAAcAAAABAAIAAACGAAYAAAABAAEAAACGAAUAAAABAAEAAACGAAQAAAABAAEAAACGAAMAAAABAAEAAACGAAIAAAABAAEAAACGAAEAAAABAAEAAACGAAAAAAABAAEAAACGAP//AAABAAEAAACGAP7/AAABAAEAAACGAP3/AAABAAAAAACFAAcAAAABAAIAAACFAAYAAAABAAEAAACFAAUAAAABAAEAAACFAAQAAAABAAEAAACFAAMAAAABAAEAAACFAAIAAAABAAEAAACFAAEAAAABAAEAAACFAAAAAAABAAEAAACFAP//AAABAAEAAACFAP7/AAABAAEAAACFAP3/AAABAAAAAACEAAcAAAABAAIAAACEAAYAAAABAAEAAACEAAUAAAABAAEAAACEAAQAAAABAAEAAACEAAMAAAABAAEAAACEAAIAAAABAAEAAACEAAEAAAABAAEAAACEAAAAAAABAAEAAACEAP//AAABAAEAAACEAP7/AAABAAEAAACEAP3/AAABAAAAAACDAAcAAAABAAIAAACDAAYAAAABAAEAAACDAAUAAAABAAEAAACDAAQAAAABAAEAAACDAAMAAAABAAEAAACDAAIAAAABAAEAAACDAAEAAAABAAEAAACDAAAAAAABAAEAAACDAP//AAABAAEAAACDAP7/AAABAAEAAACDAP3/AAABAAAAAACCAAcAAAABAAIAAACCAAYAAAABAAEAAACCAAUAAAABAAEAAACCAAQAAAABAAEAAACCAAMAAAABAAEAAACCAAIAAAABAAEAAACCAAEAAAABAAEAAACCAAAAAAABAAEAAACCAP//AAABAAEAAACCAP7/AAABAAEAAACCAP3/AAABAAAAAACBAAcAAAABAAIAAACBAAYAAAABAAEAAACBAAUAAAABAAEAAACBAAQAAAABAAEAAACBAAMAAAABAAEAAACBAAIAAAABAAEAAACBAAEAAAABAAEAAACBAAAAAAABAAEAAACBAP//AAABAAEAAACBAP7/AAABAAEAAACBAP3/AAABAAAAAACAAAcAAAABAAIAAACAAAYAAAABAAEAAACAAAUAAAABAAEAAACAAAQAAAABAAEAAACAAAMAAAABAAEAAACAAAIAAAABAAEAAACAAAEAAAABAAEAAACAAAAAAAABAAEAAACAAP//AAABAAEAAACAAP7/AAABAAEAAACAAP3/AAABAAAAAAB/AAcAAAABAAIAAAB/AAYAAAABAAEAAAB/AAUAAAABAAEAAAB/AAQAAAABAAEAAAB/AAMAAAABAAEAAAB/AAIAAAABAAEAAAB/AAEAAAABAAEAAAB/AAAAAAABAAEAAAB/AP//AAABAAEAAAB/AP7/AAABAAEAAAB/AP3/AAABAAAAAAB+AAcAAAABAAIAAAB+AAYAAAABAAEAAAB+AAUAAAABAAEAAAB+AAQAAAABAAEAAAB+AAMAAAABAAEAAAB+AAIAAAABAAEAAAB+AAEAAAABAAEAAAB+AAAAAAABAAEAAAB+AP//AAABAAEAAAB+AP7/AAABAAEAAAB+AP3/AAABAAAAAAB9AAcAAAAAAAIAAAB9AAYAAAAAAAEAAAB9AAUAAAAAAAEAAAB9AAQAAAAAAAEAAAB9AAMAAAAAAAEAAAB9AAIAAAAAAAEAAAB9AAEAAAAAAAEAAAB9AAAAAAAAAAEAAAB9AP//AAAAAAEAAAB9AP7/AAAAAAEAAAB9AP3/AAAAAAAAAACaAAcAAAACAAIAAACaAAYAAAACAAEAAACaAAUAAAACAAEAAACaAAQAAAACAAEAAACaAAMAAAACAAEAAACaAAIAAAACAAEAAACaAAEAAAACAAEAAACaAAAAAAACAAEAAACaAP//AAACAAEAAACaAP7/AAACAAEAAACaAP3/AAACAAAAAACZAAcAAAABAAIAAACZAAYAAAABAAEAAACZAAUAAAABAAEAAACZAAQAAAABAAEAAACZAAMAAAABAAEAAACZAAIAAAABAAEAAACZAAEAAAABAAEAAACZAAAAAAABAAEAAACZAP//AAABAAEAAACZAP7/AAABAAEAAACZAP3/AAABAAAAAACYAAcAAAABAAIAAACYAAYAAAABAAEAAACYAAUAAAABAAEAAACYAAQAAAABAAEAAACYAAMAAAABAAEAAACYAAIAAAABAAEAAACYAAEAAAABAAEAAACYAAAAAAABAAEAAACYAP//AAABAAEAAACYAP7/AAABAAEAAACYAP3/AAABAAAAAACXAAcAAAABAAIAAACXAAYAAAABAAEAAACXAAUAAAABAAEAAACXAAQAAAABAAEAAACXAAMAAAABAAEAAACXAAIAAAABAAEAAACXAAEAAAABAAEAAACXAAAAAAABAAEAAACXAP//AAABAAEAAACXAP7/AAABAAEAAACXAP3/AAABAAAAAACWAAcAAAABAAIAAACWAAYAAAABAAEAAACWAAUAAAABAAEAAACWAAQAAAABAAEAAACWAAMAAAABAAEAAACWAAIAAAABAAEAAACWAAEAAAABAAEAAACWAAAAAAABAAEAAACWAP//AAABAAEAAACWAP7/AAABAAEAAACWAP3/AAABAAAAAACVAAcAAAABAAIAAACVAAYAAAABAAEAAACVAAUAAAABAAEAAACVAAQAAAABAAEAAACVAAMAAAABAAEAAACVAAIAAAABAAEAAACVAAEAAAABAAEAAACVAAAAAAABAAEAAACVAP//AAABAAEAAACVAP7/AAABAAEAAACVAP3/AAABAAAAAACUAAcAAAABAAIAAACUAAYAAAABAAEAAACUAAUAAAABAAEAAACUAAQAAAABAAEAAACUAAMAAAABAAEAAACUAAIAAAABAAEAAACUAAEAAAABAAEAAACUAAAAAAABAAEAAACUAP//AAABAAEAAACUAP7/AAABAAEAAACUAP3/AAABAAAAAACTAAcAAAABAAIAAACTAAYAAAABAAEAAACTAAUAAAABAAEAAACTAAQAAAABAAEAAACTAAMAAAABAAEAAACTAAIAAAABAAEAAACTAAEAAAABAAEAAACTAAAAAAABAAEAAACTAP//AAABAAEAAACTAP7/AAABAAEAAACTAP3/AAABAAAAAACSAAcAAAABAAIAAACSAAYAAAABAAEAAACSAAUAAAABAAEAAACSAAQAAAABAAEAAACSAAMAAAABAAEAAACSAAIAAAABAAEAAACSAAEAAAABAAEAAACSAAAAAAABAAEAAACSAP//AAABAAEAAACSAP7/AAABAAEAAACSAP3/AAABAAAAAACRAAcAAAABAAIAAACRAAYAAAABAAEAAACRAAUAAAABAAEAAACRAAQAAAABAAEAAACRAAMAAAABAAEAAACRAAIAAAABAAEAAACRAAEAAAABAAEAAACRAAAAAAABAAEAAACRAP//AAABAAEAAACRAP7/AAABAAEAAACRAP3/AAABAAAAAACQAAcAAAABAAIAAACQAAYAAAABAAEAAACQAAUAAAABAAEAAACQAAQAAAABAAEAAACQAAMAAAABAAEAAACQAAIAAAABAAEAAACQAAEAAAABAAEAAACQAAAAAAABAAEAAACQAP//AAABAAEAAACQAP7/AAABAAEAAACQAP3/AAABAAAAAACPAAcAAAABAAIAAACPAAYAAAABAAEAAACPAAUAAAABAAEAAACPAAQAAAABAAEAAACPAAMAAAABAAEAAACPAAIAAAABAAEAAACPAAEAAAABAAEAAACPAAAAAAABAAEAAACPAP//AAABAAEAAACPAP7/AAABAAEAAACPAP3/AAABAAAAAACOAAcAAAABAAIAAACOAAYAAAABAAEAAACOAAUAAAABAAEAAACOAAQAAAABAAEAAACOAAMAAAABAAEAAACOAAIAAAABAAEAAACOAAEAAAABAAEAAACOAAAAAAABAAEAAACOAP//AAABAAEAAACOAP7/AAABAAEAAACOAP3/AAABAAAAAACNAAcAAAABAAIAAACNAAYAAAABAAEAAACNAAUAAAABAAEAAACNAAQAAAABAAEAAACNAAMAAAABAAEAAACNAAIAAAABAAEAAACNAAEAAAABAAEAAACNAAAAAAABAAEAAACNAP//AAABAAEAAACNAP7/AAABAAEAAACNAP3/AAABAAAAAACgAAcAAAACAAIAAACgAAYAAAACAAEAAACgAAUAAAACAAEAAACgAAQAAAACAAEAAACgAAMAAAACAAEAAACgAAIAAAACAAAAAACfAAcAAAABAAIAAACfAAYAAAABAAEAAACfAAUAAAABAAEAAACfAAQAAAABAAEAAACfAAMAAAABAAEAAACfAAIAAAABAAAAAACeAAcAAAABAAIAAACeAAYAAAABAAEAAACeAAUAAAABAAEAAACeAAQAAAABAAEAAACeAAMAAAABAAEAAACeAAIAAAABAAAAAACdAAcAAAAAAAIAAACdAAYAAAAAAAEAAACdAAUAAAAAAAEAAACdAAQAAAAAAAEAAACdAAMAAAAAAAEAAACdAAIAAAAAAAAAAACnAAcAAAABAAIAAACnAAYAAAABAAEAAACnAAUAAAABAAEAAACnAAQAAAABAAEAAACnAAMAAAABAAEAAACnAAIAAAABAAEAAACmAAcAAAABAAIAAACmAAYAAAABAAEAAACmAAUAAAABAAEAAACmAAQAAAABAAEAAACmAAMAAAABAAEAAACmAAIAAAABAAEAAAClAAcAAAABAAIAAAClAAYAAAABAAEAAAClAAUAAAABAAEAAAClAAQAAAABAAEAAAClAAMAAAABAAEAAAClAAIAAAABAAEAAACnAAEAAAAFAAIAAACnAAAAAAACAAAAAACmAAEAAAABAAEAAACmAAAAAAABAAAAAAClAAEAAAAGAAIAAAClAAAAAAAAAAAAAACoAAcAAAACAAIAAACoAAYAAAACAAEAAACoAAUAAAACAAEAAACoAAQAAAACAAEAAACoAAMAAAACAAEAAACoAAIAAAACAAEAAACoAAEAAAACAAAAAACkAAcAAAAAAAIAAACkAAYAAAAAAAEAAACkAAUAAAAAAAEAAACkAAQAAAAAAAEAAACkAAMAAAAAAAEAAACkAAIAAAAAAAEAAACkAAEAAAAAAAAAAACxAAcAAAACAAIAAACxAAYAAAACAAEAAACxAAUAAAACAAEAAACxAAQAAAACAAEAAACxAAMAAAACAAEAAACxAAIAAAACAAAAAACwAAcAAAABAAIAAACwAAYAAAABAAEAAACwAAUAAAABAAEAAACwAAQAAAABAAEAAACwAAMAAAABAAEAAACwAAIAAAABAAAAAACvAAcAAAABAAIAAACvAAYAAAABAAEAAACvAAUAAAABAAEAAACvAAQAAAAGAAIAAACvAAMAAAAAAAEAAACvAAIAAAAAAAAAAACuAAcAAAABAAIAAACuAAYAAAABAAEAAACuAAUAAAABAAEAAACuAAQAAAABAAAAAACtAAcAAAABAAIAAACtAAYAAAABAAEAAACtAAUAAAAGAAIAAACtAAQAAAAAAAAAAACsAAcAAAAAAAIAAACsAAYAAAAAAAEAAACsAAUAAAAAAAAAAAC2ADEAAAACAAIAAAC2ADAAAAACAAEAAAC2AC8AAAACAAEAAAC2AC4AAAACAAEAAAC2AC0AAAAHAAIAAAC2ACwAAAADAAEAAAC2ACsAAAADAAEAAAC2ACoAAAADAAEAAAC2ACkAAAADAAEAAAC2ACgAAAADAAEAAAC2ACcAAAADAAEAAAC2ACYAAAADAAEAAAC2ACUAAAADAAEAAAC1ADEAAAABAAIAAAC1ADAAAAABAAEAAAC1AC8AAAABAAEAAAC1AC4AAAABAAEAAAC1AC0AAAABAAAAAAC0ADEAAAABAAIAAAC0ADAAAAABAAEAAAC0AC8AAAABAAEAAAC0AC4AAAABAAEAAAC0AC0AAAABAAAAAACzADEAAAABAAIAAACzADAAAAABAAEAAACzAC8AAAABAAEAAACzAC4AAAABAAEAAACzAC0AAAABAAAAAACyADEAAAABAAIAAACyADAAAAABAAEAAACxADEAAAABAAIAAACxADAAAAABAAEAAACwADEAAAABAAIAAACwADAAAAABAAEAAACvADEAAAABAAIAAACvADAAAAABAAEAAACuADEAAAABAAIAAACuADAAAAABAAEAAACtADEAAAABAAIAAACtADAAAAABAAEAAACsADEAAAABAAIAAACsADAAAAABAAEAAACrADEAAAABAAIAAACrADAAAAABAAEAAACqADEAAAABAAIAAACqADAAAAABAAEAAACpADEAAAABAAIAAACpADAAAAABAAEAAACoADEAAAABAAIAAACoADAAAAABAAEAAACnADEAAAABAAIAAACnADAAAAABAAEAAACmADEAAAABAAIAAACmADAAAAABAAEAAAClADEAAAABAAIAAAClADAAAAABAAEAAAClAC8AAAABAAEAAAClAC4AAAABAAEAAAClAC0AAAABAAAAAACkADEAAAABAAIAAACkADAAAAABAAEAAACkAC8AAAABAAEAAACkAC4AAAABAAEAAACkAC0AAAABAAAAAACjADEAAAABAAIAAACjADAAAAABAAEAAACjAC8AAAABAAEAAACjAC4AAAABAAEAAACjAC0AAAABAAAAAACiADEAAAABAAIAAACiADAAAAABAAEAAACiAC8AAAABAAEAAACiAC4AAAABAAEAAACiAC0AAAABAAAAAAChADEAAAABAAIAAAChADAAAAABAAEAAAChAC8AAAABAAEAAAChAC4AAAABAAEAAAChAC0AAAABAAAAAACgADEAAAABAAIAAACgADAAAAABAAEAAACgAC8AAAABAAEAAACgAC4AAAABAAEAAACgAC0AAAABAAAAAACfADEAAAABAAIAAACfADAAAAABAAEAAACfAC8AAAABAAEAAACfAC4AAAABAAEAAACfAC0AAAABAAAAAACeADEAAAABAAIAAACeADAAAAABAAEAAACeAC8AAAABAAEAAACeAC4AAAABAAEAAACeAC0AAAABAAAAAACdADEAAAABAAIAAACdADAAAAABAAEAAACdAC8AAAABAAEAAACdAC4AAAABAAEAAACdAC0AAAABAAAAAACcADEAAAABAAIAAACcADAAAAABAAEAAACcAC8AAAABAAEAAACcAC4AAAABAAEAAACcAC0AAAABAAAAAACbADEAAAABAAIAAACbADAAAAABAAEAAACbAC8AAAABAAEAAACbAC4AAAABAAEAAACbAC0AAAABAAAAAACaADEAAAABAAIAAACaADAAAAABAAEAAACaAC8AAAABAAEAAACaAC4AAAABAAEAAACaAC0AAAABAAAAAACZADEAAAAAAAIAAACZADAAAAAAAAEAAACZAC8AAAAAAAEAAACZAC4AAAAAAAEAAACZAC0AAAAEAAIAAACZACwAAAADAAEAAACZACsAAAADAAEAAACZACoAAAADAAEAAACZACkAAAADAAEAAACZACgAAAADAAEAAACZACcAAAADAAEAAACZACYAAAADAAEAAACZACUAAAADAAEAAACyAC8AAAABAAEAAACyAC4AAAABAAEAAACyAC0AAAABAAAAAACxAC8AAAABAAEAAACxAC4AAAABAAEAAACxAC0AAAABAAAAAACwAC8AAAABAAEAAACwAC4AAAABAAEAAACwAC0AAAABAAAAAACvAC8AAAABAAEAAACvAC4AAAABAAEAAACvAC0AAAABAAAAAACuAC8AAAABAAEAAACuAC4AAAABAAEAAACuAC0AAAABAAAAAACtAC8AAAABAAEAAACtAC4AAAABAAEAAACtAC0AAAABAAAAAACsAC8AAAABAAEAAACsAC4AAAABAAEAAACsAC0AAAABAAAAAACrAC8AAAABAAEAAACrAC4AAAABAAEAAACrAC0AAAABAAAAAACqAC8AAAABAAEAAACqAC4AAAABAAEAAACqAC0AAAABAAAAAACpAC8AAAABAAEAAACpAC4AAAABAAEAAACpAC0AAAABAAAAAACoAC8AAAABAAEAAACoAC4AAAABAAEAAACoAC0AAAABAAAAAACnAC8AAAABAAEAAACnAC4AAAABAAEAAACnAC0AAAABAAAAAACmAC8AAAABAAEAAACmAC4AAAABAAEAAACmAC0AAAABAAAAAACeACQAAAABAAMAAACdACQAAAABAAMAAACcACQAAAABAAMAAACbACQAAAABAAMAAACaACQAAAABAAMAAACZACQAAAAEAAAAAAC2ACQAAAAHAAAAAAC1ACQAAAABAAMAAAC0ACQAAAABAAMAAACzACQAAAABAAMAAACyACQAAAABAAMAAACxACQAAAABAAMAAACwACQAAAAAAAMAAACgACQAAAACAAMAAACfACQAAAABAAMAAADcAAcAAAABAAIAAADcAAYAAAABAAEAAADcAAUAAAABAAEAAADcAAQAAAABAAEAAADcAAMAAAABAAEAAADcAAIAAAABAAEAAADcAAEAAAAFAAIAAADcAAAAAAACAAEAAADcAP//AAACAAAAAADbAAcAAAABAAIAAADbAAYAAAABAAEAAADbAAUAAAABAAEAAADbAAQAAAABAAEAAADbAAMAAAABAAEAAADbAAIAAAABAAEAAADbAAEAAAABAAEAAADbAAAAAAABAAEAAADbAP//AAABAAAAAADaAAcAAAABAAIAAADaAAYAAAABAAEAAADaAAUAAAABAAEAAADaAAQAAAABAAEAAADaAAMAAAABAAEAAADaAAIAAAABAAEAAADaAAEAAAABAAEAAADaAAAAAAABAAEAAADaAP//AAABAAAAAADZAAcAAAABAAIAAADZAAYAAAABAAEAAADZAAUAAAABAAEAAADZAAQAAAABAAEAAADZAAMAAAABAAEAAADZAAIAAAABAAEAAADZAAEAAAABAAEAAADZAAAAAAABAAEAAADZAP//AAABAAAAAADYAAcAAAABAAIAAADYAAYAAAABAAEAAADYAAUAAAABAAEAAADYAAQAAAABAAEAAADYAAMAAAABAAEAAADYAAIAAAABAAEAAADYAAEAAAABAAEAAADYAAAAAAABAAEAAADYAP//AAABAAAAAADXAAcAAAABAAIAAADXAAYAAAABAAEAAADXAAUAAAABAAEAAADXAAQAAAABAAEAAADXAAMAAAABAAEAAADXAAIAAAABAAEAAADXAAEAAAABAAEAAADXAAAAAAABAAEAAADXAP//AAABAAAAAADWAAcAAAABAAIAAADWAAYAAAABAAEAAADWAAUAAAABAAEAAADWAAQAAAABAAEAAADWAAMAAAABAAEAAADWAAIAAAABAAEAAADWAAEAAAABAAEAAADWAAAAAAABAAEAAADWAP//AAABAAAAAADVAAcAAAABAAIAAADVAAYAAAABAAEAAADVAAUAAAABAAEAAADVAAQAAAABAAEAAADVAAMAAAABAAEAAADVAAIAAAABAAEAAADVAAEAAAABAAEAAADVAAAAAAABAAEAAADVAP//AAABAAAAAADUAAcAAAABAAIAAADUAAYAAAABAAEAAADUAAUAAAABAAEAAADUAAQAAAABAAEAAADUAAMAAAABAAEAAADUAAIAAAABAAEAAADUAAEAAAABAAEAAADUAAAAAAABAAEAAADUAP//AAABAAAAAADTAAcAAAABAAIAAADTAAYAAAABAAEAAADTAAUAAAABAAEAAADTAAQAAAABAAEAAADTAAMAAAABAAEAAADTAAIAAAABAAEAAADTAAEAAAABAAEAAADTAAAAAAABAAEAAADTAP//AAABAAAAAADSAAcAAAABAAIAAADSAAYAAAABAAEAAADSAAUAAAABAAEAAADSAAQAAAABAAEAAADSAAMAAAABAAEAAADSAAIAAAABAAEAAADSAAEAAAABAAEAAADSAAAAAAABAAEAAADSAP//AAABAAAAAADRAAcAAAABAAIAAADRAAYAAAABAAEAAADRAAUAAAABAAEAAADRAAQAAAABAAEAAADRAAMAAAABAAEAAADRAAIAAAABAAEAAADRAAEAAAABAAEAAADRAAAAAAABAAEAAADRAP//AAABAAAAAADQAAcAAAABAAIAAADQAAYAAAABAAEAAADQAAUAAAABAAEAAADQAAQAAAABAAEAAADQAAMAAAABAAEAAADQAAIAAAABAAEAAADQAAEAAAABAAEAAADQAAAAAAABAAEAAADQAP//AAABAAAAAADPAAcAAAABAAIAAADPAAYAAAABAAEAAADPAAUAAAABAAEAAADPAAQAAAABAAEAAADPAAMAAAABAAEAAADPAAIAAAABAAEAAADPAAEAAAABAAEAAADPAAAAAAABAAEAAADPAP//AAABAAAAAADOAAcAAAABAAIAAADOAAYAAAABAAEAAADOAAUAAAABAAEAAADOAAQAAAABAAEAAADOAAMAAAABAAEAAADOAAIAAAABAAEAAADOAAEAAAABAAEAAADOAAAAAAABAAEAAADOAP//AAABAAAAAADNAAcAAAABAAIAAADNAAYAAAABAAEAAADNAAUAAAABAAEAAADNAAQAAAABAAEAAADNAAMAAAABAAEAAADNAAIAAAABAAEAAADNAAEAAAABAAEAAADNAAAAAAABAAEAAADNAP//AAABAAAAAADMAAcAAAABAAIAAADMAAYAAAABAAEAAADMAAUAAAABAAEAAADMAAQAAAABAAEAAADMAAMAAAABAAEAAADMAAIAAAABAAEAAADMAAEAAAABAAEAAADMAAAAAAABAAEAAADMAP//AAABAAAAAADLAAcAAAABAAIAAADLAAYAAAABAAEAAADLAAUAAAABAAEAAADLAAQAAAABAAEAAADLAAMAAAABAAEAAADLAAIAAAABAAEAAADLAAEAAAABAAEAAADLAAAAAAABAAEAAADLAP//AAABAAAAAADKAAcAAAABAAIAAADKAAYAAAABAAEAAADKAAUAAAABAAEAAADKAAQAAAABAAEAAADKAAMAAAABAAEAAADKAAIAAAABAAEAAADKAAEAAAABAAEAAADKAAAAAAABAAEAAADKAP//AAABAAAAAADJAAcAAAABAAIAAADJAAYAAAABAAEAAADJAAUAAAABAAEAAADJAAQAAAABAAEAAADJAAMAAAABAAEAAADJAAIAAAABAAEAAADJAAEAAAABAAEAAADJAAAAAAABAAEAAADJAP//AAABAAAAAADIAAcAAAABAAIAAADIAAYAAAABAAEAAADIAAUAAAABAAEAAADIAAQAAAABAAEAAADIAAMAAAABAAEAAADIAAIAAAABAAEAAADIAAEAAAABAAEAAADIAAAAAAABAAEAAADIAP//AAABAAAAAADHAAcAAAABAAIAAADHAAYAAAABAAEAAADHAAUAAAABAAEAAADHAAQAAAABAAEAAADHAAMAAAABAAEAAADHAAIAAAABAAEAAADHAAEAAAABAAEAAADHAAAAAAABAAEAAADHAP//AAABAAAAAADGAAcAAAABAAIAAADGAAYAAAABAAEAAADGAAUAAAABAAEAAADGAAQAAAABAAEAAADGAAMAAAABAAEAAADGAAIAAAABAAEAAADGAAEAAAABAAEAAADGAAAAAAABAAEAAADGAP//AAABAAAAAADFAAcAAAABAAIAAADFAAYAAAABAAEAAADFAAUAAAABAAEAAADFAAQAAAABAAEAAADFAAMAAAABAAEAAADFAAIAAAABAAEAAADFAAEAAAABAAEAAADFAAAAAAABAAEAAADFAP//AAABAAAAAADEAAcAAAABAAIAAADEAAYAAAABAAEAAADEAAUAAAABAAEAAADEAAQAAAABAAEAAADEAAMAAAABAAEAAADEAAIAAAABAAEAAADEAAEAAAABAAEAAADEAAAAAAABAAEAAADEAP//AAABAAAAAADDAAcAAAABAAIAAADDAAYAAAABAAEAAADDAAUAAAABAAEAAADDAAQAAAABAAEAAADDAAMAAAABAAEAAADDAAIAAAABAAEAAADDAAEAAAABAAEAAADDAAAAAAABAAEAAADDAP//AAABAAAAAADCAAcAAAABAAIAAADCAAYAAAABAAEAAADCAAUAAAABAAEAAADCAAQAAAABAAEAAADCAAMAAAABAAEAAADCAAIAAAABAAEAAADCAAEAAAABAAEAAADCAAAAAAABAAEAAADCAP//AAABAAAAAADBAAcAAAABAAIAAADBAAYAAAABAAEAAADBAAUAAAABAAEAAADBAAQAAAABAAEAAADBAAMAAAABAAEAAADBAAIAAAABAAEAAADBAAEAAAABAAEAAADBAAAAAAABAAEAAADBAP//AAABAAAAAADAAAcAAAABAAIAAADAAAYAAAABAAEAAADAAAUAAAABAAEAAADAAAQAAAABAAEAAADAAAMAAAABAAEAAADAAAIAAAABAAEAAADAAAEAAAABAAEAAADAAAAAAAABAAEAAADAAP//AAABAAAAAAC/AAcAAAABAAIAAAC/AAYAAAABAAEAAAC/AAUAAAABAAEAAAC/AAQAAAABAAEAAAC/AAMAAAABAAEAAAC/AAIAAAABAAEAAAC/AAEAAAABAAEAAAC/AAAAAAABAAEAAAC/AP//AAABAAAAAAC+AAcAAAABAAIAAAC+AAYAAAABAAEAAAC+AAUAAAABAAEAAAC+AAQAAAABAAEAAAC+AAMAAAABAAEAAAC+AAIAAAABAAEAAAC+AAEAAAABAAEAAAC+AAAAAAABAAEAAAC+AP//AAABAAAAAAC9AAcAAAABAAIAAAC9AAYAAAABAAEAAAC9AAUAAAABAAEAAAC9AAQAAAABAAEAAAC9AAMAAAABAAEAAAC9AAIAAAABAAEAAAC9AAEAAAABAAEAAAC9AAAAAAABAAEAAAC9AP//AAABAAAAAAC8AAcAAAABAAIAAAC8AAYAAAABAAEAAAC8AAUAAAABAAEAAAC8AAQAAAABAAEAAAC8AAMAAAABAAEAAAC8AAIAAAABAAEAAAC8AAEAAAABAAEAAAC8AAAAAAABAAEAAAC8AP//AAABAAAAAAC7AAcAAAABAAIAAAC7AAYAAAABAAEAAAC7AAUAAAABAAEAAAC7AAQAAAABAAEAAAC7AAMAAAABAAEAAAC7AAIAAAABAAEAAAC7AAEAAAABAAEAAAC7AAAAAAABAAEAAAC7AP//AAABAAAAAAC6AAcAAAABAAIAAAC6AAYAAAABAAEAAAC6AAUAAAABAAEAAAC6AAQAAAABAAEAAAC6AAMAAAABAAEAAAC6AAIAAAABAAEAAAC6AAEAAAABAAEAAAC6AAAAAAABAAEAAAC6AP//AAABAAAAAAC5AAcAAAABAAIAAAC5AAYAAAABAAEAAAC5AAUAAAABAAEAAAC5AAQAAAABAAEAAAC5AAMAAAABAAEAAAC5AAIAAAABAAEAAAC5AAEAAAABAAEAAAC5AAAAAAABAAEAAAC5AP//AAABAAAAAAC4AAcAAAAAAAIAAAC4AAYAAAAAAAEAAAC4AAUAAAAAAAEAAAC4AAQAAAAAAAEAAAC4AAMAAAAAAAEAAAC4AAIAAAAAAAEAAAC4AAEAAAAAAAEAAAC4AAAAAAAAAAEAAAC4AP//AAAAAAAAAADyAAcAAAABAAIAAADyAAYAAAABAAEAAADyAAUAAAABAAEAAADyAAQAAAABAAEAAADyAAMAAAAFAAIAAADyAAIAAAACAAEAAADyAAEAAAACAAAAAADxAAcAAAABAAIAAADxAAYAAAABAAEAAADxAAUAAAABAAEAAADxAAQAAAABAAEAAADxAAMAAAABAAEAAADxAAIAAAABAAEAAADxAAEAAAABAAAAAADwAAcAAAABAAIAAADwAAYAAAABAAEAAADwAAUAAAABAAEAAADwAAQAAAABAAEAAADwAAMAAAABAAEAAADwAAIAAAABAAEAAADwAAEAAAABAAAAAADvAAcAAAABAAIAAADvAAYAAAABAAEAAADvAAUAAAABAAEAAADvAAQAAAABAAEAAADvAAMAAAABAAEAAADvAAIAAAABAAEAAADvAAEAAAABAAAAAADuAAcAAAABAAIAAADuAAYAAAABAAEAAADuAAUAAAABAAEAAADuAAQAAAABAAEAAADuAAMAAAABAAEAAADuAAIAAAABAAEAAADuAAEAAAABAAAAAADtAAcAAAABAAIAAADtAAYAAAABAAEAAADtAAUAAAABAAEAAADtAAQAAAABAAEAAADtAAMAAAABAAEAAADtAAIAAAABAAEAAADtAAEAAAABAAAAAADsAAcAAAABAAIAAADsAAYAAAABAAEAAADsAAUAAAABAAEAAADsAAQAAAABAAEAAADsAAMAAAABAAEAAADsAAIAAAABAAEAAADsAAEAAAABAAAAAADrAAcAAAABAAIAAADrAAYAAAABAAEAAADrAAUAAAABAAEAAADrAAQAAAABAAEAAADrAAMAAAABAAEAAADrAAIAAAABAAEAAADrAAEAAAABAAAAAADqAAcAAAABAAIAAADqAAYAAAABAAEAAADqAAUAAAABAAEAAADqAAQAAAABAAEAAADqAAMAAAABAAEAAADqAAIAAAABAAEAAADqAAEAAAABAAAAAADpAAcAAAABAAIAAADpAAYAAAABAAEAAADpAAUAAAABAAEAAADpAAQAAAABAAEAAADpAAMAAAABAAEAAADpAAIAAAABAAEAAADpAAEAAAABAAAAAADoAAcAAAABAAIAAADoAAYAAAABAAEAAADoAAUAAAABAAEAAADoAAQAAAABAAEAAADoAAMAAAABAAEAAADoAAIAAAABAAEAAADoAAEAAAABAAAAAADnAAcAAAABAAIAAADnAAYAAAABAAEAAADnAAUAAAABAAEAAADnAAQAAAABAAEAAADnAAMAAAABAAEAAADnAAIAAAABAAEAAADnAAEAAAABAAAAAADmAAcAAAABAAIAAADmAAYAAAABAAEAAADmAAUAAAABAAEAAADmAAQAAAABAAEAAADmAAMAAAABAAEAAADmAAIAAAABAAEAAADmAAEAAAABAAAAAADlAAcAAAABAAIAAADlAAYAAAABAAEAAADlAAUAAAABAAEAAADlAAQAAAABAAEAAADlAAMAAAABAAEAAADlAAIAAAABAAEAAADlAAEAAAABAAAAAADkAAcAAAABAAIAAADkAAYAAAABAAEAAADkAAUAAAABAAEAAADkAAQAAAABAAEAAADkAAMAAAABAAEAAADkAAIAAAABAAEAAADkAAEAAAABAAAAAADjAAcAAAABAAIAAADjAAYAAAABAAEAAADjAAUAAAABAAEAAADjAAQAAAABAAEAAADjAAMAAAABAAEAAADjAAIAAAABAAEAAADjAAEAAAABAAAAAADiAAcAAAABAAIAAADiAAYAAAABAAEAAADiAAUAAAABAAEAAADiAAQAAAABAAEAAADiAAMAAAABAAEAAADiAAIAAAABAAEAAADiAAEAAAABAAAAAADhAAcAAAABAAIAAADhAAYAAAABAAEAAADhAAUAAAABAAEAAADhAAQAAAABAAEAAADhAAMAAAABAAEAAADhAAIAAAABAAEAAADhAAEAAAABAAAAAADgAAcAAAABAAIAAADgAAYAAAABAAEAAADgAAUAAAABAAEAAADgAAQAAAABAAEAAADgAAMAAAABAAEAAADgAAIAAAABAAEAAADgAAEAAAABAAAAAADfAAcAAAABAAIAAADfAAYAAAABAAEAAADfAAUAAAABAAEAAADfAAQAAAABAAEAAADfAAMAAAABAAEAAADfAAIAAAABAAEAAADfAAEAAAABAAAAAADeAAcAAAABAAIAAADeAAYAAAABAAEAAADeAAUAAAABAAEAAADeAAQAAAABAAEAAADeAAMAAAABAAEAAADeAAIAAAABAAEAAADeAAEAAAABAAAAAADdAAcAAAABAAIAAADdAAYAAAABAAEAAADdAAUAAAABAAEAAADdAAQAAAABAAEAAADdAAMAAAABAAEAAADdAAIAAAABAAEAAADdAAEAAAABAAAAAAABAQcAAAACAAIAAAABAQYAAAACAAEAAAABAQUAAAACAAEAAAABAQQAAAACAAEAAAABAQMAAAACAAAAAAAAAQcAAAABAAIAAAAAAQYAAAABAAEAAAAAAQUAAAABAAEAAAAAAQQAAAABAAEAAAAAAQMAAAABAAAAAAD/AAcAAAABAAIAAAD/AAYAAAABAAEAAAD/AAUAAAABAAEAAAD/AAQAAAABAAEAAAD/AAMAAAABAAAAAAD+AAcAAAABAAIAAAD+AAYAAAABAAEAAAD+AAUAAAABAAEAAAD+AAQAAAABAAEAAAD+AAMAAAABAAAAAAD9AAcAAAABAAIAAAD9AAYAAAABAAEAAAD9AAUAAAABAAEAAAD9AAQAAAABAAEAAAD9AAMAAAABAAAAAAD8AAcAAAABAAIAAAD8AAYAAAABAAEAAAD8AAUAAAABAAEAAAD8AAQAAAABAAEAAAD8AAMAAAABAAAAAAD7AAcAAAABAAIAAAD7AAYAAAABAAEAAAD7AAUAAAABAAEAAAD7AAQAAAABAAEAAAD7AAMAAAABAAAAAAD6AAcAAAABAAIAAAD6AAYAAAABAAEAAAD6AAUAAAABAAEAAAD6AAQAAAABAAEAAAD6AAMAAAABAAAAAAD5AAcAAAABAAIAAAD5AAYAAAABAAEAAAD5AAUAAAABAAEAAAD5AAQAAAABAAEAAAD5AAMAAAABAAAAAAD4AAcAAAABAAIAAAD4AAYAAAABAAEAAAD4AAUAAAABAAEAAAD4AAQAAAABAAEAAAD4AAMAAAABAAAAAAD3AAcAAAABAAIAAAD3AAYAAAABAAEAAAD3AAUAAAABAAEAAAD3AAQAAAABAAEAAAD3AAMAAAABAAAAAAD2AAcAAAABAAIAAAD2AAYAAAABAAEAAAD2AAUAAAABAAEAAAD2AAQAAAABAAEAAAD2AAMAAAABAAAAAAD1AAcAAAABAAIAAAD1AAYAAAABAAEAAAD1AAUAAAABAAEAAAD1AAQAAAABAAEAAAD1AAMAAAABAAAAAAD0AAcAAAABAAIAAAD0AAYAAAABAAEAAAD0AAUAAAABAAEAAAD0AAQAAAABAAEAAAD0AAMAAAABAAAAAADzAAcAAAABAAIAAADzAAYAAAABAAEAAADzAAUAAAABAAEAAADzAAQAAAABAAEAAADzAAMAAAABAAAAAABAAOv/AAACAAIAAABAAOr/AAACAAEAAABAAOn/AAACAAEAAABAAOj/AAACAAEAAABAAOf/AAACAAEAAABAAOb/AAACAAEAAABAAOX/AAACAAEAAABAAOT/AAACAAEAAABAAOP/AAACAAEAAABAAOL/AAACAAEAAABAAOH/AAACAAEAAABAAOD/AAACAAEAAABAAN//AAACAAEAAABAAN7/AAACAAEAAABAAN3/AAACAAAAAAA/AOv/AAABAAIAAAA/AOr/AAABAAEAAAA/AOn/AAABAAEAAAA/AOj/AAABAAEAAAA/AOf/AAABAAEAAAA/AOb/AAABAAEAAAA/AOX/AAABAAEAAAA/AOT/AAABAAEAAAA/AOP/AAABAAEAAAA/AOL/AAABAAEAAAA/AOH/AAABAAEAAAA/AOD/AAABAAEAAAA/AN//AAABAAEAAAA/AN7/AAABAAEAAAA/AN3/AAABAAAAAAA+AOv/AAABAAIAAAA+AOr/AAABAAEAAAA+AOn/AAABAAEAAAA+AOj/AAABAAEAAAA+AOf/AAABAAEAAAA+AOb/AAABAAEAAAA+AOX/AAABAAEAAAA+AOT/AAABAAEAAAA+AOP/AAABAAEAAAA+AOL/AAABAAEAAAA+AOH/AAABAAEAAAA+AOD/AAABAAEAAAA+AN//AAABAAEAAAA+AN7/AAABAAEAAAA+AN3/AAABAAAAAAA9AOv/AAABAAIAAAA9AOr/AAABAAEAAAA9AOn/AAABAAEAAAA9AOj/AAABAAEAAAA9AOf/AAABAAEAAAA9AOb/AAABAAEAAAA9AOX/AAABAAEAAAA9AOT/AAABAAEAAAA9AOP/AAABAAEAAAA9AOL/AAABAAEAAAA9AOH/AAABAAEAAAA9AOD/AAABAAEAAAA9AN//AAABAAEAAAA9AN7/AAABAAEAAAA9AN3/AAABAAAAAAA8AOv/AAABAAIAAAA8AOr/AAABAAEAAAA8AOn/AAABAAEAAAA8AOj/AAABAAEAAAA8AOf/AAABAAEAAAA8AOb/AAABAAEAAAA8AOX/AAABAAEAAAA8AOT/AAABAAEAAAA8AOP/AAABAAEAAAA8AOL/AAABAAEAAAA8AOH/AAABAAEAAAA8AOD/AAABAAEAAAA8AN//AAABAAEAAAA8AN7/AAABAAEAAAA8AN3/AAABAAAAAAA7AOv/AAABAAIAAAA7AOr/AAABAAEAAAA7AOn/AAABAAEAAAA7AOj/AAABAAEAAAA7AOf/AAABAAEAAAA7AOb/AAABAAEAAAA7AOX/AAABAAEAAAA7AOT/AAABAAEAAAA7AOP/AAABAAEAAAA7AOL/AAABAAEAAAA7AOH/AAABAAEAAAA7AOD/AAABAAEAAAA7AN//AAABAAEAAAA7AN7/AAABAAEAAAA7AN3/AAABAAAAAAA6AOv/AAABAAIAAAA6AOr/AAABAAEAAAA6AOn/AAABAAEAAAA6AOj/AAABAAEAAAA6AOf/AAABAAEAAAA6AOb/AAABAAEAAAA6AOX/AAABAAEAAAA6AOT/AAABAAEAAAA6AOP/AAABAAEAAAA6AOL/AAABAAEAAAA6AOH/AAABAAEAAAA6AOD/AAABAAEAAAA6AN//AAABAAEAAAA6AN7/AAABAAEAAAA6AN3/AAABAAAAAAA5AOv/AAABAAIAAAA5AOr/AAABAAEAAAA5AOn/AAABAAEAAAA5AOj/AAABAAEAAAA5AOf/AAABAAEAAAA5AOb/AAABAAEAAAA5AOX/AAABAAEAAAA5AOT/AAABAAEAAAA5AOP/AAABAAEAAAA5AOL/AAABAAEAAAA5AOH/AAABAAEAAAA5AOD/AAABAAEAAAA5AN//AAABAAEAAAA5AN7/AAABAAEAAAA5AN3/AAABAAAAAAA4AOv/AAABAAIAAAA4AOr/AAABAAEAAAA4AOn/AAABAAEAAAA4AOj/AAABAAEAAAA4AOf/AAABAAEAAAA4AOb/AAABAAEAAAA4AOX/AAABAAEAAAA4AOT/AAABAAEAAAA4AOP/AAABAAEAAAA4AOL/AAABAAEAAAA4AOH/AAABAAEAAAA4AOD/AAABAAEAAAA4AN//AAABAAEAAAA4AN7/AAABAAEAAAA4AN3/AAABAAAAAAA3AOv/AAABAAIAAAA3AOr/AAABAAEAAAA3AOn/AAABAAEAAAA3AOj/AAABAAEAAAA3AOf/AAABAAEAAAA3AOb/AAABAAEAAAA3AOX/AAABAAEAAAA3AOT/AAABAAEAAAA3AOP/AAABAAEAAAA3AOL/AAABAAEAAAA3AOH/AAABAAEAAAA3AOD/AAABAAEAAAA3AN//AAABAAEAAAA3AN7/AAABAAEAAAA3AN3/AAABAAAAAAA2AOv/AAABAAIAAAA2AOr/AAABAAEAAAA2AOn/AAABAAEAAAA2AOj/AAABAAEAAAA2AOf/AAABAAEAAAA2AOb/AAABAAEAAAA2AOX/AAABAAEAAAA2AOT/AAABAAEAAAA2AOP/AAABAAEAAAA2AOL/AAABAAEAAAA2AOH/AAABAAEAAAA2AOD/AAABAAEAAAA2AN//AAABAAEAAAA2AN7/AAABAAEAAAA2AN3/AAABAAAAAAA1AOv/AAABAAIAAAA1AOr/AAABAAEAAAA1AOn/AAABAAEAAAA1AOj/AAABAAEAAAA1AOf/AAABAAEAAAA1AOb/AAABAAEAAAA1AOX/AAABAAEAAAA1AOT/AAABAAEAAAA1AOP/AAABAAEAAAA1AOL/AAABAAEAAAA1AOH/AAABAAEAAAA1AOD/AAABAAEAAAA1AN//AAABAAEAAAA1AN7/AAABAAEAAAA1AN3/AAABAAAAAAA0AOv/AAAAAAIAAAA0AOr/AAAAAAEAAAA0AOn/AAAAAAEAAAA0AOj/AAAAAAEAAAA0AOf/AAAAAAEAAAA0AOb/AAAAAAEAAAA0AOX/AAAAAAEAAAA0AOT/AAAAAAEAAAA0AOP/AAAAAAEAAAA0AOL/AAAAAAEAAAA0AOH/AAAAAAEAAAA0AOD/AAAAAAEAAAA0AN//AAAAAAEAAAA0AN7/AAAAAAEAAAA0AN3/AAAAAAAAAABzAMj/AAACAAMAAAByAMj/AAABAAMAAABxAMj/AAABAAMAAABwAMj/AAABAAMAAABvAMj/AAABAAMAAABuAMj/AAABAAMAAABtAMj/AAABAAMAAABsAMj/AAABAAMAAABrAMj/AAABAAMAAABqAMj/AAABAAMAAABpAMj/AAABAAMAAABoAMj/AAABAAMAAABnAMj/AAABAAMAAABmAMj/AAABAAMAAABlAMj/AAABAAMAAABkAMj/AAABAAMAAABjAMj/AAABAAMAAABiAMj/AAABAAMAAABhAMj/AAABAAMAAABgAMj/AAABAAMAAABfAMj/AAABAAMAAABeAMj/AAABAAMAAABdAMj/AAABAAMAAABcAMj/AAABAAMAAABbAMj/AAABAAMAAABaAMj/AAABAAMAAABZAMj/AAABAAMAAABYAMj/AAABAAMAAABXAMj/AAABAAMAAABWAMj/AAABAAMAAABVAMj/AAABAAMAAABUAMj/AAABAAMAAABTAMj/AAABAAMAAABSAMj/AAABAAMAAABRAMj/AAABAAMAAABQAMj/AAABAAMAAABPAMj/AAABAAMAAABOAMj/AAABAAMAAABNAMj/AAABAAMAAABMAMj/AAABAAMAAABLAMj/AAABAAMAAABKAMj/AAABAAMAAABJAMj/AAABAAMAAABIAMj/AAABAAMAAABHAMj/AAABAAMAAABGAMj/AAABAAMAAABFAMj/AAABAAMAAABEAMj/AAABAAMAAABDAMj/AAABAAMAAABCAMj/AAABAAMAAABBAMj/AAABAAMAAABAAMj/AAABAAMAAAA/AMj/AAABAAMAAAA+AMj/AAABAAMAAAA9AMj/AAABAAMAAAA8AMj/AAABAAMAAAA7AMj/AAABAAMAAAA6AMj/AAABAAMAAAA5AMj/AAABAAMAAAA4AMj/AAABAAMAAAA3AMj/AAABAAMAAAA2AMj/AAABAAMAAAA1AMj/AAABAAMAAAA0AMj/AAABAAMAAAAzAMj/AAABAAMAAAAyAMj/AAABAAMAAAAAAMj/AAAAAAMAAAABAMj/AAABAAMAAAACAMj/AAABAAMAAAADAMj/AAABAAMAAAAEAMj/AAABAAMAAAAFAMj/AAABAAMAAAAGAMj/AAABAAMAAAAHAMj/AAABAAMAAAAIAMj/AAABAAMAAAAJAMj/AAABAAMAAAAKAMj/AAABAAMAAAALAMj/AAABAAMAAAAMAMj/AAABAAMAAAANAMj/AAABAAMAAAAOAMj/AAABAAMAAAAPAMj/AAABAAMAAAAQAMj/AAABAAMAAAARAMj/AAABAAMAAAASAMj/AAABAAMAAAATAMj/AAABAAMAAAAUAMj/AAABAAMAAAAVAMj/AAABAAMAAAAWAMj/AAABAAMAAAAXAMj/AAABAAMAAAAYAMj/AAABAAMAAAAZAMj/AAABAAMAAAAaAMj/AAABAAMAAAAbAMj/AAABAAMAAAAcAMj/AAABAAMAAAAdAMj/AAABAAMAAAAeAMj/AAABAAMAAAAfAMj/AAABAAMAAAAgAMj/AAABAAMAAAAhAMj/AAABAAMAAAAiAMj/AAABAAMAAAAjAMj/AAABAAMAAAAkAMj/AAABAAMAAAAlAMj/AAABAAMAAAAmAMj/AAABAAMAAAAnAMj/AAABAAMAAAAoAMj/AAABAAMAAAApAMj/AAABAAMAAAAqAMj/AAABAAMAAAArAMj/AAABAAMAAAAsAMj/AAABAAMAAAAtAMj/AAABAAMAAAAuAMj/AAABAAMAAAAvAMj/AAABAAMAAAAwAMj/AAABAAMAAAAxAMj/AAABAAMAAAA=") tile_set = ExtResource("1_d680t") [node name="Entities layer" type="TileMapLayer" parent="."] -tile_map_data = PackedByteArray("AAAJAPz/AAAHAAMAAAAJAP3/AAAHAAQAAAAKAPz/AAAIAAMAAAALAPz/AAAIAAMAAAAMAPz/AAAJAAMAAAANAPz/AAAKAAMAAAANAP3/AAAKAAQAAAAUAPz/AAAGAAMAADAVAPz/AAAGAAMAADAWAPz/AAAGAAMAADAcAAMAAAAGAAMAAGAPAPn/AAADAAYAAAAaAAMAAAAGAAQAAAAWAPn/AQAAAAAAAQAXAPn/AQAAAAAAAQAYAPn/AQAAAAAAAQAZAPn/AQAAAAAAAQAaAPn/AQAAAAAAAQAZAPj/AQAAAAAAAQAYAPj/AQAAAAAAAQAXAPj/AQAAAAAAAQAYAPf/AQAAAAAAAQAKAP3/AQAAAAAAAgALAP3/AQAAAAAAAgAMAP3/AQAAAAAAAgA=") +tile_map_data = PackedByteArray("AAAJAPz/AAAHAAMAAAAJAP3/AAAHAAQAAAAKAPz/AAAIAAMAAAALAPz/AAAIAAMAAAAMAPz/AAAJAAMAAAANAPz/AAAKAAMAAAANAP3/AAAKAAQAAAAUAPz/AAAGAAMAADAVAPz/AAAGAAMAADAWAPz/AAAGAAMAADAcAAMAAAAGAAMAAGAPAPn/AAADAAYAAAAaAAMAAAAGAAQAAAAWAPn/AQAAAAAAAQAXAPn/AQAAAAAAAQAYAPn/AQAAAAAAAQAZAPn/AQAAAAAAAQAaAPn/AQAAAAAAAQAZAPj/AQAAAAAAAQAYAPj/AQAAAAAAAQAXAPj/AQAAAAAAAQAYAPf/AQAAAAAAAQAKAP3/AQAAAAAAAgALAP3/AQAAAAAAAgAMAP3/AQAAAAAAAgBAAAAAAAAJAAYAAABFAAAAAAALAAYAAABEAAAAAAAKAAYAAABDAAAAAAAKAAYAAABCAAAAAAAKAAYAAABBAAAAAAAKAAYAAABAAPr/AAAJAAYAAABBAPr/AAAKAAYAAABCAPr/AAAKAAYAAABDAPr/AAAKAAYAAABEAPr/AAAKAAYAAABFAPr/AAAKAAYAAABGAPr/AAAKAAYAAABHAPr/AAAKAAYAAABIAPr/AAAKAAYAAABJAPr/AAAKAAYAAABKAPr/AAAKAAYAAABLAPr/AAALAAYAAABjAP7/AAAHAAQAAAB8AP7/AAAKAAQAAABkAP7/AQAAAAAAAgBlAP7/AQAAAAAAAgBmAP7/AQAAAAAAAgBnAP7/AQAAAAAAAgBoAP7/AQAAAAAAAgBpAP7/AQAAAAAAAgBsAP7/AQAAAAAAAgBtAP7/AQAAAAAAAgBrAP7/AQAAAAAAAgBqAP7/AQAAAAAAAgBuAP7/AQAAAAAAAgBvAP7/AQAAAAAAAgBwAP7/AQAAAAAAAgBxAP7/AQAAAAAAAgByAP7/AQAAAAAAAgBzAP7/AQAAAAAAAgB0AP7/AQAAAAAAAgB1AP7/AQAAAAAAAgB2AP7/AQAAAAAAAgB3AP7/AQAAAAAAAgB4AP7/AQAAAAAAAgB5AP7/AQAAAAAAAgB6AP7/AQAAAAAAAgB7AP7/AQAAAAAAAgCJAPr/AAAJAAYAAACKAPr/AAAKAAYAAACLAPr/AAALAAYAAADuAP7/AAAJAAYAAADvAP7/AAAKAAYAAADwAP7/AAALAAYAAADpAPz/AAAJAAYAAADqAPz/AAAKAAYAAADrAPz/AAALAAYAAADkAPv/AAAJAAYAAADlAPv/AAAKAAYAAADmAPv/AAALAAYAAADsAPn/AAAJAAYAAADtAPn/AAAKAAYAAADuAPn/AAALAAYAAADyAPf/AAAJAAYAAADzAPf/AAAKAAYAAAD0APf/AAALAAYAAAD3APb/AAAJAAYAAAD4APb/AAAKAAYAAAD5APb/AAALAAYAAAABAQIAAAAGAAQAAABSAPP/AAAKAAYAAABTAPP/AAAKAAYAAABUAPP/AAAKAAYAAABVAPP/AAAKAAYAAABWAPP/AAAKAAYAAABXAPP/AAAKAAYAAABYAPP/AAAKAAYAAABZAPP/AAAKAAYAAABaAPP/AAAKAAYAAABbAPP/AAAKAAYAAABcAPP/AAAKAAYAAABdAPP/AAAKAAYAAABeAPP/AAAKAAYAAABfAPP/AAAKAAYAAABgAPP/AAAKAAYAAABRAPP/AAAJAAYAAABhAPP/AAALAAYAAABkAO7/AAAJAAYAAABlAO7/AAAKAAYAAABmAO7/AAALAAYAAABMAO3/AAAJAAYAAABOAO3/AAALAAYAAABNAO3/AAAKAAYAAABBAOj/AAAJAAYAAABCAOj/AAAKAAYAAABDAOj/AAALAAYAAABGAOT/AAAJAAYAAABHAOT/AAAKAAYAAABIAOT/AAALAAYAAABBAN//AAAJAAYAAABCAN//AAAKAAYAAABDAN//AAALAAYAAABqAOz/AAAJAAYAAABrAOz/AAAKAAYAAABsAOz/AAALAAYAAAB+AOz/AAAJAAYAAAB/AOz/AAAKAAYAAACAAOz/AAALAAYAAACRAOz/AAAJAAYAAACSAOz/AAAKAAYAAACTAOz/AAALAAYAAABOAOz/AQAAAAAAAQBNAOz/AQAAAAAAAQBMAOz/AQAAAAAAAQBNAOv/AQAAAAAAAQBDAOf/AQAAAAAAAQBCAOf/AQAAAAAAAQBBAOf/AQAAAAAAAQBCAOb/AQAAAAAAAQBIAOP/AQAAAAAAAQBHAOP/AQAAAAAAAQBGAOP/AQAAAAAAAQBHAOL/AQAAAAAAAQBBAN7/AQAAAAAAAQBCAN7/AQAAAAAAAQBDAN7/AQAAAAAAAQBCAN3/AQAAAAAAAQBDAOb/AQAAAAAAAQBGAOL/AQAAAAAAAQBDAN3/AQAAAAAAAQBMAOv/AQAAAAAAAQCRAOv/AQAAAAAAAQCRAOr/AQAAAAAAAQCRAOn/AQAAAAAAAQCSAOn/AQAAAAAAAQCTAOn/AQAAAAAAAQCTAOr/AQAAAAAAAQCTAOv/AQAAAAAAAQCSAOv/AQAAAAAAAQCSAOr/AQAAAAAAAQCAAOv/AQAAAAAAAQB/AOv/AQAAAAAAAQB+AOv/AQAAAAAAAQB+AOr/AQAAAAAAAQB/AOr/AQAAAAAAAQBAAP//AQAAAAAAAQBBAP//AQAAAAAAAQBCAP//AQAAAAAAAQBDAP//AQAAAAAAAQBEAP//AQAAAAAAAQClAP//AQAAAAAAAQCmAP//AQAAAAAAAQCnAP//AQAAAAAAAQCnAP7/AQAAAAAAAQClAP7/AQAAAAAAAQD4APT/AQAAAAAAAwC5AP3/AQAAAAAAAwCnACwAAQAAAAAABQChACwAAQAAAAAABgCvACwAAQAAAAAABgA2ANz/AQAAAAAABwD5APX/AQAAAAAACAA=") tile_set = SubResource("TileSet_yf4x4") [node name="Foreground layer" type="TileMapLayer" parent="."] -tile_map_data = PackedByteArray("AAD9/wAAAAAEAAMAAAD+/wIAAAAEAAMAAAD+/wQAAAAEAAMAAAD//wAAAAAEAAMAAAD//wMAAAAEAAMAAAAAAAAAAAAEAAMAAAAAAAEAAAAEAAMAAAADAAAAAAAEAAMAAAAFAAAAAAAEAAMAAAAFAAEAAAAEAAMAAAAFAAIAAAAEAAMAAAAEAP3/AAAEAAMAAAALAAQAAAAEAAMAAAAcAP//AAAEAAMAAAAYAPv/AAAEAAMAAAAYAPz/AAAEAAMAAAAaAPv/AAAEAAMAAAAcAPv/AAAEAAMAAAAbAP3/AAAEAAMAAAAcAP7/AAAEAAMAAAAQAPz/AAAEAAMAAAARAPv/AAAEAAMAAAARAPz/AAAEAAMAAAAQAAIAAAAEAAMAAAAQAAQAAAAEAAMAAAAAAP7/AAAEAAQAAAAVAPn/AAAFAAQAAAD2/+v/AAAEAAMAAAD2/+z/AAAEAAMAAAD3/+r/AAAEAAMAAAD3/+v/AAAEAAMAAAD3/+7/AAAEAAMAAAD5/+r/AAAEAAMAAAD5/+3/AAAEAAMAAAD6/+z/AAAEAAMAAAD7/+r/AAAEAAMAAAD8/+r/AAAEAAMAAAD8/+v/AAAEAAMAAAD9/+v/AAAEAAMAAAD9/+z/AAAEAAMAAAD9/+3/AAAEAAMAAAD//+3/AAAEAAMAAAAAAOz/AAAEAAMAAAAAAO3/AAAEAAMAAAABAOv/AAAEAAMAAAACAOv/AAAEAAMAAAACAOz/AAAEAAMAAAACAO7/AAAEAAMAAAADAOz/AAAEAAMAAAAEAOz/AAAEAAMAAAAGAOz/AAAEAAMAAAAGAO7/AAAEAAMAAAAHAOv/AAAEAAMAAAAHAOz/AAAEAAMAAAAHAO7/AAAEAAMAAAAIAOz/AAAEAAMAAAAIAO3/AAAEAAMAAAAJAOz/AAAEAAMAAAAJAO3/AAAEAAMAAAAKAOv/AAAEAAMAAAAKAOz/AAAEAAMAAAAKAO3/AAAEAAMAAAAKAO7/AAAEAAMAAAALAO3/AAAEAAMAAAAMAO3/AAAEAAMAAAANAOv/AAAEAAMAAAANAO7/AAAEAAMAAAAPAOv/AAAEAAMAAAAQAOv/AAAEAAMAAAASAOz/AAAEAAMAAAASAO7/AAAEAAMAAAAUAOv/AAAEAAMAAAAUAO3/AAAEAAMAAAAVAO7/AAAEAAMAAAAWAOv/AAAEAAMAAAAWAO3/AAAEAAMAAAAXAOz/AAAEAAMAAAAXAO7/AAAEAAMAAAAYAO7/AAAEAAMAAAAZAOz/AAAEAAMAAAAdAO7/AAAEAAMAAAAeAO3/AAAEAAMAAAAfAOv/AAAEAAMAAAAfAOz/AAAEAAMAAAAgAO7/AAAEAAMAAAAjAOv/AAAEAAMAAAAjAO3/AAAEAAMAAAAkAOv/AAAEAAMAAAAmAOz/AAAEAAMAAAAmAO3/AAAEAAMAAAAnAO7/AAAEAAMAAAAoAOv/AAAEAAMAAAAhAO//AAAEAAMAAAAhAPD/AAAEAAMAAAAhAPL/AAAEAAMAAAAhAPT/AAAEAAMAAAAhAPX/AAAEAAMAAAAhAPf/AAAEAAMAAAAhAPn/AAAEAAMAAAAhAP3/AAAEAAMAAAAhAAAAAAAEAAMAAAAhAAEAAAAEAAMAAAAiAPD/AAAEAAMAAAAiAPH/AAAEAAMAAAAiAPP/AAAEAAMAAAAiAPf/AAAEAAMAAAAiAPj/AAAEAAMAAAAiAP//AAAEAAMAAAAiAAIAAAAEAAMAAAAiAAMAAAAEAAMAAAAjAPH/AAAEAAMAAAAjAPL/AAAEAAMAAAAjAPX/AAAEAAMAAAAjAPb/AAAEAAMAAAAjAPj/AAAEAAMAAAAjAP7/AAAEAAMAAAAkAO//AAAEAAMAAAAkAPH/AAAEAAMAAAAkAPX/AAAEAAMAAAAkAPb/AAAEAAMAAAAkAPf/AAAEAAMAAAAkAPj/AAAEAAMAAAAkAPn/AAAEAAMAAAAkAP//AAAEAAMAAAAlAPH/AAAEAAMAAAAlAPb/AAAEAAMAAAAlAPr/AAAEAAMAAAAlAPv/AAAEAAMAAAAlAP3/AAAEAAMAAAAlAAEAAAAEAAMAAAAlAAQAAAAEAAMAAAAmAPD/AAAEAAMAAAAmAPP/AAAEAAMAAAAmAPr/AAAEAAMAAAAmAPz/AAAEAAMAAAAmAP//AAAEAAMAAAAmAAIAAAAEAAMAAAD2/+//AAAEAAMAAAD2//H/AAAEAAMAAAD2//X/AAAEAAMAAAD2//b/AAAEAAMAAAD2/wEAAAAEAAMAAAD2/wMAAAAEAAMAAAD3/+//AAAEAAMAAAD3//H/AAAEAAMAAAD3//P/AAAEAAMAAAD3//7/AAAEAAMAAAD3/wAAAAAEAAMAAAD3/wIAAAAEAAMAAAD3/wMAAAAEAAMAAAD4/+//AAAEAAMAAAD4//L/AAAEAAMAAAD4//X/AAAEAAMAAAD4////AAAEAAMAAAD5//D/AAAEAAMAAAD5//P/AAAEAAMAAAD5//f/AAAEAAMAAAD5////AAAEAAMAAAD5/wAAAAAEAAMAAAD6//X/AAAEAAMAAAD6//b/AAAEAAMAAAD6////AAAEAAMAAAA=") +tile_map_data = PackedByteArray("AAD9/wAAAAAEAAMAAAD+/wIAAAAEAAMAAAD+/wQAAAAEAAMAAAD//wAAAAAEAAMAAAD//wMAAAAEAAMAAAAAAAAAAAAEAAMAAAAAAAEAAAAEAAMAAAADAAAAAAAEAAMAAAAFAAAAAAAEAAMAAAAFAAEAAAAEAAMAAAAFAAIAAAAEAAMAAAAEAP3/AAAEAAMAAAALAAQAAAAEAAMAAAAcAP//AAAEAAMAAAAYAPv/AAAEAAMAAAAYAPz/AAAEAAMAAAAaAPv/AAAEAAMAAAAcAPv/AAAEAAMAAAAbAP3/AAAEAAMAAAAcAP7/AAAEAAMAAAAQAPz/AAAEAAMAAAARAPv/AAAEAAMAAAARAPz/AAAEAAMAAAAQAAIAAAAEAAMAAAAQAAQAAAAEAAMAAAAAAP7/AAAEAAQAAAAVAPn/AAAFAAQAAAD2/+v/AAAEAAMAAAD2/+z/AAAEAAMAAAD3/+r/AAAEAAMAAAD3/+v/AAAEAAMAAAD3/+7/AAAEAAMAAAD5/+r/AAAEAAMAAAD5/+3/AAAEAAMAAAD6/+z/AAAEAAMAAAD7/+r/AAAEAAMAAAD8/+r/AAAEAAMAAAD8/+v/AAAEAAMAAAD9/+v/AAAEAAMAAAD9/+z/AAAEAAMAAAD9/+3/AAAEAAMAAAD//+3/AAAEAAMAAAAAAOz/AAAEAAMAAAAAAO3/AAAEAAMAAAABAOv/AAAEAAMAAAACAOv/AAAEAAMAAAACAOz/AAAEAAMAAAACAO7/AAAEAAMAAAADAOz/AAAEAAMAAAAEAOz/AAAEAAMAAAAGAOz/AAAEAAMAAAAGAO7/AAAEAAMAAAAHAOv/AAAEAAMAAAAHAOz/AAAEAAMAAAAHAO7/AAAEAAMAAAAIAOz/AAAEAAMAAAAIAO3/AAAEAAMAAAAJAOz/AAAEAAMAAAAJAO3/AAAEAAMAAAAKAOv/AAAEAAMAAAAKAOz/AAAEAAMAAAAKAO3/AAAEAAMAAAAKAO7/AAAEAAMAAAALAO3/AAAEAAMAAAAMAO3/AAAEAAMAAAANAOv/AAAEAAMAAAANAO7/AAAEAAMAAAAPAOv/AAAEAAMAAAAQAOv/AAAEAAMAAAASAOz/AAAEAAMAAAASAO7/AAAEAAMAAAAUAOv/AAAEAAMAAAAUAO3/AAAEAAMAAAAVAO7/AAAEAAMAAAAWAOv/AAAEAAMAAAAWAO3/AAAEAAMAAAAXAOz/AAAEAAMAAAAXAO7/AAAEAAMAAAAYAO7/AAAEAAMAAAAZAOz/AAAEAAMAAAAdAO7/AAAEAAMAAAAeAO3/AAAEAAMAAAAfAOv/AAAEAAMAAAAfAOz/AAAEAAMAAAAgAO7/AAAEAAMAAAAjAOv/AAAEAAMAAAAjAO3/AAAEAAMAAAAkAOv/AAAEAAMAAAAmAOz/AAAEAAMAAAAmAO3/AAAEAAMAAAAnAO7/AAAEAAMAAAAoAOv/AAAEAAMAAAAhAO//AAAEAAMAAAAhAPD/AAAEAAMAAAAhAPL/AAAEAAMAAAAhAPT/AAAEAAMAAAAhAPX/AAAEAAMAAAAhAPf/AAAEAAMAAAAhAPn/AAAEAAMAAAAhAP3/AAAEAAMAAAAhAAAAAAAEAAMAAAAhAAEAAAAEAAMAAAAiAPD/AAAEAAMAAAAiAPH/AAAEAAMAAAAiAPP/AAAEAAMAAAAiAPf/AAAEAAMAAAAiAPj/AAAEAAMAAAAiAP//AAAEAAMAAAAiAAIAAAAEAAMAAAAiAAMAAAAEAAMAAAAjAPH/AAAEAAMAAAAjAPL/AAAEAAMAAAAjAPX/AAAEAAMAAAAjAPb/AAAEAAMAAAAjAPj/AAAEAAMAAAAjAP7/AAAEAAMAAAAkAO//AAAEAAMAAAAkAPH/AAAEAAMAAAAkAPX/AAAEAAMAAAAkAPb/AAAEAAMAAAAkAPf/AAAEAAMAAAAkAPj/AAAEAAMAAAAkAPn/AAAEAAMAAAAkAP//AAAEAAMAAAAlAPH/AAAEAAMAAAAlAPb/AAAEAAMAAAAlAPr/AAAEAAMAAAAlAPv/AAAEAAMAAAAlAP3/AAAEAAMAAAAlAAEAAAAEAAMAAAAlAAQAAAAEAAMAAAAmAPD/AAAEAAMAAAAmAPP/AAAEAAMAAAAmAPr/AAAEAAMAAAAmAPz/AAAEAAMAAAAmAP//AAAEAAMAAAAmAAIAAAAEAAMAAAD2/+//AAAEAAMAAAD2//H/AAAEAAMAAAD2//X/AAAEAAMAAAD2//b/AAAEAAMAAAD2/wEAAAAEAAMAAAD2/wMAAAAEAAMAAAD3/+//AAAEAAMAAAD3//H/AAAEAAMAAAD3//P/AAAEAAMAAAD3//7/AAAEAAMAAAD3/wAAAAAEAAMAAAD3/wIAAAAEAAMAAAD3/wMAAAAEAAMAAAD4/+//AAAEAAMAAAD4//L/AAAEAAMAAAD4//X/AAAEAAMAAAD4////AAAEAAMAAAD5//D/AAAEAAMAAAD5//P/AAAEAAMAAAD5//f/AAAEAAMAAAD5////AAAEAAMAAAD5/wAAAAAEAAMAAAD6//X/AAAEAAMAAAD6//b/AAAEAAMAAAD6////AAAEAAMAAABiAP3/AAAFAAQAAAA=") tile_set = SubResource("TileSet_r3m5w") [node name="Small Foreground layer" type="TileMapLayer" parent="."] @@ -424,16 +436,16 @@ tile_map_data = PackedByteArray("AAD7//3/AQABAAAAAAD8//3/AQAAAAAAAAD+//3/AQABAAA tile_set = SubResource("TileSet_fpn3g") [node name="Brick Player" parent="." instance=ExtResource("4_hetw8")] -position = Vector2(219, 0) +position = Vector2(929, -123) metadata/_edit_group_ = true [node name="Camera2D" type="Camera2D" parent="."] physics_interpolation_mode = 1 -position = Vector2(219, -84) +position = Vector2(929, -123) limit_left = -176 -limit_top = -368 -limit_right = 672 -limit_bottom = 96 +limit_top = -896 +limit_right = 4128 +limit_bottom = 800 [node name="PhantomCameraHost" type="Node" parent="Camera2D"] process_priority = 300 @@ -446,6 +458,21 @@ position = Vector2(484, -112) [node name="Enemy2" parent="." instance=ExtResource("7_qgddg")] position = Vector2(-14, -34) +[node name="Enemy3" parent="." instance=ExtResource("7_qgddg")] +position = Vector2(1433, -224) + +[node name="Enemy4" parent="." instance=ExtResource("7_qgddg")] +position = Vector2(2211, -62) + +[node name="Enemy5" parent="." instance=ExtResource("7_qgddg")] +position = Vector2(3262, -30) + +[node name="Enemy6" parent="." instance=ExtResource("7_qgddg")] +position = Vector2(3710, 2) + +[node name="Enemy7" parent="." instance=ExtResource("7_qgddg")] +position = Vector2(3945, 34) + [node name="Bullet" parent="." instance=ExtResource("8_c68mx")] position = Vector2(68, -89) @@ -472,7 +499,7 @@ position = Vector2(275, 9) [node name="PhantomCamera2D" type="Node2D" parent="." node_paths=PackedStringArray("follow_target")] top_level = true -position = Vector2(219, -84) +position = Vector2(929, -123) script = ExtResource("13_rsy5s") follow_mode = 1 follow_target = NodePath("../Brick Player") diff --git a/scripts/components/cage_component.gd b/scripts/components/cage_component.gd new file mode 100644 index 0000000..e58de2c --- /dev/null +++ b/scripts/components/cage_component.gd @@ -0,0 +1,40 @@ +class_name CageComponent +extends Node + +@export var lever: LeverComponent +@export var root: Node2D +@export var move_value: Vector2 = Vector2(0, -100) +@export var tween_duration: float = 0.5 +@export var should_free: bool = true + + +func _ready() -> void: + await get_tree().process_frame + if not lever: + var levers_nodes := get_tree().get_nodes_in_group("levers") + print("Found levers: ", levers_nodes) + for lever_node in levers_nodes: + var lever_component: LeverComponent = lever_node.get_node_or_null("LeverComponent") + print("Lever component: ", lever_component) + if lever_component: + lever_component.activated.connect(on_lever_activated) + else: + lever.activated.connect(on_lever_activated) + + if not root: + printerr("CageComponent: root is not set.") + return + + +func on_lever_activated() -> void: + print("Lever activated, moving cage.") + var tween: Tween = create_tween() + var end_position: Vector2 = root.position + move_value + tween.tween_property(root, "position", end_position, tween_duration) + tween.tween_callback(_on_tween_completed) + + +func _on_tween_completed() -> void: + if not should_free: + return + root.queue_free() \ No newline at end of file diff --git a/scripts/components/can_be_launched_component.gd b/scripts/components/can_be_launched_component.gd new file mode 100644 index 0000000..5675e50 --- /dev/null +++ b/scripts/components/can_be_launched_component.gd @@ -0,0 +1,2 @@ +class_name CanBeLaunchedComponent +extends Node \ No newline at end of file diff --git a/scripts/components/collectable.gd b/scripts/components/collectable.gd index 3e9837c..a5400f8 100644 --- a/scripts/components/collectable.gd +++ b/scripts/components/collectable.gd @@ -6,9 +6,9 @@ var has_fade_away: bool = false @export var area2d: Area2D @export var collectable_data: CollectableResource - signal collected(amount: int) + func _ready() -> void: if area2d: area2d.body_entered.connect(_on_area2d_body_entered) @@ -20,6 +20,7 @@ func _ready() -> void: if root.has_node("FadeAwayComponent"): has_fade_away = true + func _on_area2d_body_entered(body: Node2D) -> void: if body.has_node("CanPickUpComponent"): collected.emit(collectable_data.amount) diff --git a/scripts/components/jump_pad_component.gd b/scripts/components/jump_pad_component.gd new file mode 100644 index 0000000..f343ec1 --- /dev/null +++ b/scripts/components/jump_pad_component.gd @@ -0,0 +1,40 @@ +class_name JumpPadComponent +extends Node + +@export var jump_force: float = 10.0 +@export var area2d: Area2D +@export var sprite2d: Sprite2D +@export var start_animation_index: int = 0 +@export var animation_duration: float = 0.5 + + +func _ready() -> void: + if not area2d: + printerr("JumpPadComponent: area2d is not set.") + return + + if not sprite2d: + printerr("JumpPadComponent: sprite2d is not set.") + return + + area2d.body_entered.connect(_on_body_entered) + + +func _on_body_entered(body: Node2D) -> void: + var can_be_launched: CanBeLaunchedComponent = body.get_node_or_null("CanBeLaunchedComponent") + if not can_be_launched: + return + + if body is PlayerController: + handle_launchpad_animation() + body.velocity.y = -jump_force + + +func handle_launchpad_animation() -> void: + if not sprite2d: + return + + var timer := get_tree().create_timer(animation_duration) + sprite2d.frame = start_animation_index + 1 + await timer.timeout + sprite2d.frame = start_animation_index \ No newline at end of file diff --git a/scripts/components/lever_component.gd b/scripts/components/lever_component.gd new file mode 100644 index 0000000..d4a8238 --- /dev/null +++ b/scripts/components/lever_component.gd @@ -0,0 +1,37 @@ +class_name LeverComponent +extends Node + +@export var area2d: Area2D +@export var sprite2d: Sprite2D +@export var start_animation_index: int = 0 +@export var animation_duration: float = 0.5 +signal activated + + +func _ready() -> void: + if not area2d: + printerr("LeverComponent: area2d is not set.") + return + + if not sprite2d: + printerr("LeverComponent: sprite2d is not set.") + return + + area2d.body_entered.connect(_on_body_entered) + + +func _on_body_entered(body: Node2D) -> void: + var trigger_lever: TriggerLeverComponent = body.get_node_or_null("TriggerLeverComponent") + if not trigger_lever: + return + + activate() + + +func activate() -> void: + print("Lever activated.") + activated.emit() + sprite2d.frame = start_animation_index + 1 + var timer := get_tree().create_timer(animation_duration) + await timer.timeout + sprite2d.frame = start_animation_index diff --git a/scripts/components/trigger_lever_component.gd b/scripts/components/trigger_lever_component.gd new file mode 100644 index 0000000..1755cce --- /dev/null +++ b/scripts/components/trigger_lever_component.gd @@ -0,0 +1,2 @@ +class_name TriggerLeverComponent +extends Node \ No newline at end of file diff --git a/sprites/grass_tile.svg b/sprites/grass_tile.svg index 3269780..44ccb27 100644 --- a/sprites/grass_tile.svg +++ b/sprites/grass_tile.svg @@ -27,8 +27,8 @@ inkscape:deskcolor="#505050" inkscape:document-units="px" inkscape:zoom="4.1513748" - inkscape:cx="93.703898" - inkscape:cy="34.566862" + inkscape:cx="39.986753" + inkscape:cy="96.474064" inkscape:window-width="2560" inkscape:window-height="1374" inkscape:window-x="0" @@ -99,7 +99,7 @@ diff --git a/sprites/ppc_tileset.png b/sprites/ppc_tileset.png index 1871edc..ad5d818 100644 Binary files a/sprites/ppc_tileset.png and b/sprites/ppc_tileset.png differ diff --git a/tileset/sliced/sliced.png b/tileset/sliced/sliced.png new file mode 100644 index 0000000..fa15c2c Binary files /dev/null and b/tileset/sliced/sliced.png differ diff --git a/tileset/sliced/sliced.png.import b/tileset/sliced/sliced.png.import new file mode 100644 index 0000000..2a68078 --- /dev/null +++ b/tileset/sliced/sliced.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://i1ebu6ws4clo" +path="res://.godot/imported/sliced.png-fbb3d940c33e37860c896cb6211fcbe0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced.png" +dest_files=["res://.godot/imported/sliced.png-fbb3d940c33e37860c896cb6211fcbe0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sliced2.png b/tileset/sliced/sliced2.png new file mode 100644 index 0000000..49a1ac3 Binary files /dev/null and b/tileset/sliced/sliced2.png differ diff --git a/tileset/sliced/sliced2.png.import b/tileset/sliced/sliced2.png.import new file mode 100644 index 0000000..79f8a78 --- /dev/null +++ b/tileset/sliced/sliced2.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgu78i8vnkyjr" +path="res://.godot/imported/sliced2.png-f9aca6902388c75f74bd82de896e7917.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced2.png" +dest_files=["res://.godot/imported/sliced2.png-f9aca6902388c75f74bd82de896e7917.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sliced3.png b/tileset/sliced/sliced3.png new file mode 100644 index 0000000..932393d Binary files /dev/null and b/tileset/sliced/sliced3.png differ diff --git a/tileset/sliced/sliced3.png.import b/tileset/sliced/sliced3.png.import new file mode 100644 index 0000000..d2b6dd6 --- /dev/null +++ b/tileset/sliced/sliced3.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcwsonxig6m3h" +path="res://.godot/imported/sliced3.png-008c941d9c67ba4be39791e9515f443f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced3.png" +dest_files=["res://.godot/imported/sliced3.png-008c941d9c67ba4be39791e9515f443f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sliced4.png b/tileset/sliced/sliced4.png new file mode 100644 index 0000000..a89092d Binary files /dev/null and b/tileset/sliced/sliced4.png differ diff --git a/tileset/sliced/sliced4.png.import b/tileset/sliced/sliced4.png.import new file mode 100644 index 0000000..0815c5a --- /dev/null +++ b/tileset/sliced/sliced4.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5csvqfu5cbwm" +path="res://.godot/imported/sliced4.png-6c8263bf14dc391359ad65783cf1ade5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced4.png" +dest_files=["res://.godot/imported/sliced4.png-6c8263bf14dc391359ad65783cf1ade5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sliced5.png b/tileset/sliced/sliced5.png new file mode 100644 index 0000000..64eb457 Binary files /dev/null and b/tileset/sliced/sliced5.png differ diff --git a/tileset/sliced/sliced5.png.import b/tileset/sliced/sliced5.png.import new file mode 100644 index 0000000..d205ed5 --- /dev/null +++ b/tileset/sliced/sliced5.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dnott5iefpg8d" +path="res://.godot/imported/sliced5.png-da25a6508463f9d334b37e9861bb3b03.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced5.png" +dest_files=["res://.godot/imported/sliced5.png-da25a6508463f9d334b37e9861bb3b03.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sliced6.png b/tileset/sliced/sliced6.png new file mode 100644 index 0000000..f99d22c Binary files /dev/null and b/tileset/sliced/sliced6.png differ diff --git a/tileset/sliced/sliced6.png.import b/tileset/sliced/sliced6.png.import new file mode 100644 index 0000000..c530ce2 --- /dev/null +++ b/tileset/sliced/sliced6.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cddhdlrkynekw" +path="res://.godot/imported/sliced6.png-cd0b0c96ff6ce9ca1f54ef087081285f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced6.png" +dest_files=["res://.godot/imported/sliced6.png-cd0b0c96ff6ce9ca1f54ef087081285f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sliced7.png b/tileset/sliced/sliced7.png new file mode 100644 index 0000000..e0c6d44 Binary files /dev/null and b/tileset/sliced/sliced7.png differ diff --git a/tileset/sliced/sliced7.png.import b/tileset/sliced/sliced7.png.import new file mode 100644 index 0000000..d6e2de3 --- /dev/null +++ b/tileset/sliced/sliced7.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bk2cnj2adb8ir" +path="res://.godot/imported/sliced7.png-8c06af759412916ffcd8e6dcfb8274e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced7.png" +dest_files=["res://.godot/imported/sliced7.png-8c06af759412916ffcd8e6dcfb8274e8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sliced8.png b/tileset/sliced/sliced8.png new file mode 100644 index 0000000..68e90f9 Binary files /dev/null and b/tileset/sliced/sliced8.png differ diff --git a/tileset/sliced/sliced8.png.import b/tileset/sliced/sliced8.png.import new file mode 100644 index 0000000..029d0e4 --- /dev/null +++ b/tileset/sliced/sliced8.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cghxi204ajn0l" +path="res://.godot/imported/sliced8.png-12803fb1057c2a107058d028827507ef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sliced8.png" +dest_files=["res://.godot/imported/sliced8.png-12803fb1057c2a107058d028827507ef.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/sliced/sprite.png b/tileset/sliced/sprite.png new file mode 100644 index 0000000..93849cb Binary files /dev/null and b/tileset/sliced/sprite.png differ diff --git a/tileset/sliced/sprite.png.import b/tileset/sliced/sprite.png.import new file mode 100644 index 0000000..da3ad16 --- /dev/null +++ b/tileset/sliced/sprite.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://fblpdrogbn58" +path="res://.godot/imported/sprite.png-90e20dc272152895adf57a2d0a502e9c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/sliced/sprite.png" +dest_files=["res://.godot/imported/sprite.png-90e20dc272152895adf57a2d0a502e9c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet1.png b/tileset/terrain-Sheet1.png new file mode 100644 index 0000000..471bac1 Binary files /dev/null and b/tileset/terrain-Sheet1.png differ diff --git a/tileset/terrain-Sheet1.png.import b/tileset/terrain-Sheet1.png.import new file mode 100644 index 0000000..3996aa3 --- /dev/null +++ b/tileset/terrain-Sheet1.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2cp4rb5bq2pf" +path="res://.godot/imported/terrain-Sheet1.png-153aac28b0ec8563218b8be3a176dc88.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet1.png" +dest_files=["res://.godot/imported/terrain-Sheet1.png-153aac28b0ec8563218b8be3a176dc88.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet2.png b/tileset/terrain-Sheet2.png new file mode 100644 index 0000000..3ec92f6 Binary files /dev/null and b/tileset/terrain-Sheet2.png differ diff --git a/tileset/terrain-Sheet2.png.import b/tileset/terrain-Sheet2.png.import new file mode 100644 index 0000000..e308f88 --- /dev/null +++ b/tileset/terrain-Sheet2.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tkw0hrir4315" +path="res://.godot/imported/terrain-Sheet2.png-ed4f74c168ad9a61e074cf9e8ae3cff7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet2.png" +dest_files=["res://.godot/imported/terrain-Sheet2.png-ed4f74c168ad9a61e074cf9e8ae3cff7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet3.png b/tileset/terrain-Sheet3.png new file mode 100644 index 0000000..b18e111 Binary files /dev/null and b/tileset/terrain-Sheet3.png differ diff --git a/tileset/terrain-Sheet3.png.import b/tileset/terrain-Sheet3.png.import new file mode 100644 index 0000000..110fe15 --- /dev/null +++ b/tileset/terrain-Sheet3.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cpdy62eitch3r" +path="res://.godot/imported/terrain-Sheet3.png-1cb172ff0c6de1cc12dea5142efa3a5c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet3.png" +dest_files=["res://.godot/imported/terrain-Sheet3.png-1cb172ff0c6de1cc12dea5142efa3a5c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet4.png b/tileset/terrain-Sheet4.png new file mode 100644 index 0000000..7de70a2 Binary files /dev/null and b/tileset/terrain-Sheet4.png differ diff --git a/tileset/terrain-Sheet4.png.import b/tileset/terrain-Sheet4.png.import new file mode 100644 index 0000000..b279c1f --- /dev/null +++ b/tileset/terrain-Sheet4.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dl02vtudrlymd" +path="res://.godot/imported/terrain-Sheet4.png-0ed282c1b3adccbffd4ce65678bcb14b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet4.png" +dest_files=["res://.godot/imported/terrain-Sheet4.png-0ed282c1b3adccbffd4ce65678bcb14b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet5.png b/tileset/terrain-Sheet5.png new file mode 100644 index 0000000..b08ee75 Binary files /dev/null and b/tileset/terrain-Sheet5.png differ diff --git a/tileset/terrain-Sheet5.png.import b/tileset/terrain-Sheet5.png.import new file mode 100644 index 0000000..ff7fe19 --- /dev/null +++ b/tileset/terrain-Sheet5.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://di3vjxoqv0j1s" +path="res://.godot/imported/terrain-Sheet5.png-78baff9808f8e8be4780e1c7bb130aa3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet5.png" +dest_files=["res://.godot/imported/terrain-Sheet5.png-78baff9808f8e8be4780e1c7bb130aa3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet6.png b/tileset/terrain-Sheet6.png new file mode 100644 index 0000000..effedfe Binary files /dev/null and b/tileset/terrain-Sheet6.png differ diff --git a/tileset/terrain-Sheet6.png.import b/tileset/terrain-Sheet6.png.import new file mode 100644 index 0000000..35dfca8 --- /dev/null +++ b/tileset/terrain-Sheet6.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0b7kpqh13j84" +path="res://.godot/imported/terrain-Sheet6.png-fadb29960c769391175fb882f02aceb5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet6.png" +dest_files=["res://.godot/imported/terrain-Sheet6.png-fadb29960c769391175fb882f02aceb5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet7.png b/tileset/terrain-Sheet7.png new file mode 100644 index 0000000..88e4a2c Binary files /dev/null and b/tileset/terrain-Sheet7.png differ diff --git a/tileset/terrain-Sheet7.png.import b/tileset/terrain-Sheet7.png.import new file mode 100644 index 0000000..de69cc8 --- /dev/null +++ b/tileset/terrain-Sheet7.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgqp8ag0p4ag3" +path="res://.godot/imported/terrain-Sheet7.png-8328b0359058f1c7dec40851f18c5e69.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet7.png" +dest_files=["res://.godot/imported/terrain-Sheet7.png-8328b0359058f1c7dec40851f18c5e69.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet8.png b/tileset/terrain-Sheet8.png new file mode 100644 index 0000000..cc9273e Binary files /dev/null and b/tileset/terrain-Sheet8.png differ diff --git a/tileset/terrain-Sheet8.png.import b/tileset/terrain-Sheet8.png.import new file mode 100644 index 0000000..c3868fc --- /dev/null +++ b/tileset/terrain-Sheet8.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bm7uha0ava07u" +path="res://.godot/imported/terrain-Sheet8.png-97362eb348067439aede9157be7fe901.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet8.png" +dest_files=["res://.godot/imported/terrain-Sheet8.png-97362eb348067439aede9157be7fe901.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/terrain-Sheet9.png b/tileset/terrain-Sheet9.png new file mode 100644 index 0000000..0218fdb Binary files /dev/null and b/tileset/terrain-Sheet9.png differ diff --git a/tileset/terrain-Sheet9.png.import b/tileset/terrain-Sheet9.png.import new file mode 100644 index 0000000..e525b0d --- /dev/null +++ b/tileset/terrain-Sheet9.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfcnejq0sbglg" +path="res://.godot/imported/terrain-Sheet9.png-02938eb0b522959794bfbc79ac67ff57.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/terrain-Sheet9.png" +dest_files=["res://.godot/imported/terrain-Sheet9.png-02938eb0b522959794bfbc79ac67ff57.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village1.png b/tileset/village/village1.png new file mode 100644 index 0000000..03fd359 Binary files /dev/null and b/tileset/village/village1.png differ diff --git a/tileset/village/village1.png.import b/tileset/village/village1.png.import new file mode 100644 index 0000000..7d922ca --- /dev/null +++ b/tileset/village/village1.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dnwye5wqyiq8w" +path="res://.godot/imported/village1.png-6fc7332931f56bd139f6788979dce173.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village1.png" +dest_files=["res://.godot/imported/village1.png-6fc7332931f56bd139f6788979dce173.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village10.png b/tileset/village/village10.png new file mode 100644 index 0000000..1b530cc Binary files /dev/null and b/tileset/village/village10.png differ diff --git a/tileset/village/village10.png.import b/tileset/village/village10.png.import new file mode 100644 index 0000000..8f53660 --- /dev/null +++ b/tileset/village/village10.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6nxngt1yhmhx" +path="res://.godot/imported/village10.png-2f670c9fdc8e1f00e37da24288237cfe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village10.png" +dest_files=["res://.godot/imported/village10.png-2f670c9fdc8e1f00e37da24288237cfe.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village11.png b/tileset/village/village11.png new file mode 100644 index 0000000..7aaaa07 Binary files /dev/null and b/tileset/village/village11.png differ diff --git a/tileset/village/village11.png.import b/tileset/village/village11.png.import new file mode 100644 index 0000000..f9f9b13 --- /dev/null +++ b/tileset/village/village11.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcqbtc0xwkrg5" +path="res://.godot/imported/village11.png-a4cdc24226728a1433e33c0fc53cb6da.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village11.png" +dest_files=["res://.godot/imported/village11.png-a4cdc24226728a1433e33c0fc53cb6da.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village13.png b/tileset/village/village13.png new file mode 100644 index 0000000..526c14a Binary files /dev/null and b/tileset/village/village13.png differ diff --git a/tileset/village/village13.png.import b/tileset/village/village13.png.import new file mode 100644 index 0000000..2114528 --- /dev/null +++ b/tileset/village/village13.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5ba7qng80srr" +path="res://.godot/imported/village13.png-f44c2768d2158e8c72ddcf3fa94d0b99.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village13.png" +dest_files=["res://.godot/imported/village13.png-f44c2768d2158e8c72ddcf3fa94d0b99.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village14.png b/tileset/village/village14.png new file mode 100644 index 0000000..da0fcce Binary files /dev/null and b/tileset/village/village14.png differ diff --git a/tileset/village/village14.png.import b/tileset/village/village14.png.import new file mode 100644 index 0000000..5bb930f --- /dev/null +++ b/tileset/village/village14.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://nd2y4fb8hjrv" +path="res://.godot/imported/village14.png-615d02be3e100b96cef6e31a3a56e975.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village14.png" +dest_files=["res://.godot/imported/village14.png-615d02be3e100b96cef6e31a3a56e975.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village15.png b/tileset/village/village15.png new file mode 100644 index 0000000..7d5f902 Binary files /dev/null and b/tileset/village/village15.png differ diff --git a/tileset/village/village15.png.import b/tileset/village/village15.png.import new file mode 100644 index 0000000..a5c4dd8 --- /dev/null +++ b/tileset/village/village15.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vfxm2rc5ener" +path="res://.godot/imported/village15.png-b4edf98e7bf73422a0fdc757d95f1468.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village15.png" +dest_files=["res://.godot/imported/village15.png-b4edf98e7bf73422a0fdc757d95f1468.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village16.png b/tileset/village/village16.png new file mode 100644 index 0000000..2602a09 Binary files /dev/null and b/tileset/village/village16.png differ diff --git a/tileset/village/village16.png.import b/tileset/village/village16.png.import new file mode 100644 index 0000000..a4f25a5 --- /dev/null +++ b/tileset/village/village16.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://htcxc0328j5l" +path="res://.godot/imported/village16.png-f87e78391e4062f142d743326f6e8456.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village16.png" +dest_files=["res://.godot/imported/village16.png-f87e78391e4062f142d743326f6e8456.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village17.png b/tileset/village/village17.png new file mode 100644 index 0000000..6d02f4a Binary files /dev/null and b/tileset/village/village17.png differ diff --git a/tileset/village/village17.png.import b/tileset/village/village17.png.import new file mode 100644 index 0000000..5a5470d --- /dev/null +++ b/tileset/village/village17.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfjacgswyysl6" +path="res://.godot/imported/village17.png-b3d3463447388e82d12ee335d13f4ca2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village17.png" +dest_files=["res://.godot/imported/village17.png-b3d3463447388e82d12ee335d13f4ca2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village18.png b/tileset/village/village18.png new file mode 100644 index 0000000..77a422f Binary files /dev/null and b/tileset/village/village18.png differ diff --git a/tileset/village/village18.png.import b/tileset/village/village18.png.import new file mode 100644 index 0000000..175208d --- /dev/null +++ b/tileset/village/village18.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dj8cfrbg6mtso" +path="res://.godot/imported/village18.png-f1f9344b459f38c9bde27b3c8e545f7b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village18.png" +dest_files=["res://.godot/imported/village18.png-f1f9344b459f38c9bde27b3c8e545f7b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village19.png b/tileset/village/village19.png new file mode 100644 index 0000000..b74a812 Binary files /dev/null and b/tileset/village/village19.png differ diff --git a/tileset/village/village19.png.import b/tileset/village/village19.png.import new file mode 100644 index 0000000..989f4af --- /dev/null +++ b/tileset/village/village19.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dltrqo2aid0se" +path="res://.godot/imported/village19.png-e0382c3c2804f5344553fd4558a9975a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village19.png" +dest_files=["res://.godot/imported/village19.png-e0382c3c2804f5344553fd4558a9975a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village2.png b/tileset/village/village2.png new file mode 100644 index 0000000..03fd359 Binary files /dev/null and b/tileset/village/village2.png differ diff --git a/tileset/village/village2.png.import b/tileset/village/village2.png.import new file mode 100644 index 0000000..67dca00 --- /dev/null +++ b/tileset/village/village2.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8e7jgldhg6n2" +path="res://.godot/imported/village2.png-994d9a2b6bd64f3f3b13c61029faf7bb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village2.png" +dest_files=["res://.godot/imported/village2.png-994d9a2b6bd64f3f3b13c61029faf7bb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village20.png b/tileset/village/village20.png new file mode 100644 index 0000000..4891e90 Binary files /dev/null and b/tileset/village/village20.png differ diff --git a/tileset/village/village20.png.import b/tileset/village/village20.png.import new file mode 100644 index 0000000..5ef0dc8 --- /dev/null +++ b/tileset/village/village20.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bim26wlc0d1od" +path="res://.godot/imported/village20.png-de4e3f05f88f0bf7e9a8b51b28d234f9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village20.png" +dest_files=["res://.godot/imported/village20.png-de4e3f05f88f0bf7e9a8b51b28d234f9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village21.png b/tileset/village/village21.png new file mode 100644 index 0000000..7b13e24 Binary files /dev/null and b/tileset/village/village21.png differ diff --git a/tileset/village/village21.png.import b/tileset/village/village21.png.import new file mode 100644 index 0000000..098d48f --- /dev/null +++ b/tileset/village/village21.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5hgwffx1b4sx" +path="res://.godot/imported/village21.png-eafc8ee81ded67c2dd67d7a48a0b03fc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village21.png" +dest_files=["res://.godot/imported/village21.png-eafc8ee81ded67c2dd67d7a48a0b03fc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village22.png b/tileset/village/village22.png new file mode 100644 index 0000000..3cffdb9 Binary files /dev/null and b/tileset/village/village22.png differ diff --git a/tileset/village/village22.png.import b/tileset/village/village22.png.import new file mode 100644 index 0000000..6e38d66 --- /dev/null +++ b/tileset/village/village22.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsp4b0g2wgrct" +path="res://.godot/imported/village22.png-d81f5e919eb215455f4b6411c9d487b3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village22.png" +dest_files=["res://.godot/imported/village22.png-d81f5e919eb215455f4b6411c9d487b3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village24.png b/tileset/village/village24.png new file mode 100644 index 0000000..21e944a Binary files /dev/null and b/tileset/village/village24.png differ diff --git a/tileset/village/village24.png.import b/tileset/village/village24.png.import new file mode 100644 index 0000000..1f8a794 --- /dev/null +++ b/tileset/village/village24.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxnq01t7w1bwd" +path="res://.godot/imported/village24.png-56c289f98beb64e9846a4add5dd42dd8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village24.png" +dest_files=["res://.godot/imported/village24.png-56c289f98beb64e9846a4add5dd42dd8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village25.png b/tileset/village/village25.png new file mode 100644 index 0000000..aaaa4af Binary files /dev/null and b/tileset/village/village25.png differ diff --git a/tileset/village/village25.png.import b/tileset/village/village25.png.import new file mode 100644 index 0000000..edde195 --- /dev/null +++ b/tileset/village/village25.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4ih7q5xo22y5" +path="res://.godot/imported/village25.png-28951465a0e967d5da167af1cf38e73d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village25.png" +dest_files=["res://.godot/imported/village25.png-28951465a0e967d5da167af1cf38e73d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village26.png b/tileset/village/village26.png new file mode 100644 index 0000000..33a2fce Binary files /dev/null and b/tileset/village/village26.png differ diff --git a/tileset/village/village26.png.import b/tileset/village/village26.png.import new file mode 100644 index 0000000..798e503 --- /dev/null +++ b/tileset/village/village26.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://eb4ct5m3b885" +path="res://.godot/imported/village26.png-5329b7526936f43864d1b2d0cf51c323.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village26.png" +dest_files=["res://.godot/imported/village26.png-5329b7526936f43864d1b2d0cf51c323.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village27.png b/tileset/village/village27.png new file mode 100644 index 0000000..8f80a10 Binary files /dev/null and b/tileset/village/village27.png differ diff --git a/tileset/village/village27.png.import b/tileset/village/village27.png.import new file mode 100644 index 0000000..e9593ea --- /dev/null +++ b/tileset/village/village27.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hlkicd0fsxvf" +path="res://.godot/imported/village27.png-7cdaa6049f7a99a461d3b8a08ee4b7f7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village27.png" +dest_files=["res://.godot/imported/village27.png-7cdaa6049f7a99a461d3b8a08ee4b7f7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village28.png b/tileset/village/village28.png new file mode 100644 index 0000000..ed5b2e7 Binary files /dev/null and b/tileset/village/village28.png differ diff --git a/tileset/village/village28.png.import b/tileset/village/village28.png.import new file mode 100644 index 0000000..3cb790a --- /dev/null +++ b/tileset/village/village28.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cda0mqxultiga" +path="res://.godot/imported/village28.png-263581e88cbbf65b45f82e2271d14aa2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village28.png" +dest_files=["res://.godot/imported/village28.png-263581e88cbbf65b45f82e2271d14aa2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village29.png b/tileset/village/village29.png new file mode 100644 index 0000000..f65f534 Binary files /dev/null and b/tileset/village/village29.png differ diff --git a/tileset/village/village29.png.import b/tileset/village/village29.png.import new file mode 100644 index 0000000..1fdbc1f --- /dev/null +++ b/tileset/village/village29.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvh5ea4b2o8xg" +path="res://.godot/imported/village29.png-5da56f2606412fa0f4441e1b41c1dab5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village29.png" +dest_files=["res://.godot/imported/village29.png-5da56f2606412fa0f4441e1b41c1dab5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village3.png b/tileset/village/village3.png new file mode 100644 index 0000000..aba82d2 Binary files /dev/null and b/tileset/village/village3.png differ diff --git a/tileset/village/village3.png.import b/tileset/village/village3.png.import new file mode 100644 index 0000000..a216c43 --- /dev/null +++ b/tileset/village/village3.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4lr4wydvjhsy" +path="res://.godot/imported/village3.png-13e88dcc16d82088c7e22e23f295518c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village3.png" +dest_files=["res://.godot/imported/village3.png-13e88dcc16d82088c7e22e23f295518c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village30.png b/tileset/village/village30.png new file mode 100644 index 0000000..f370708 Binary files /dev/null and b/tileset/village/village30.png differ diff --git a/tileset/village/village30.png.import b/tileset/village/village30.png.import new file mode 100644 index 0000000..51ad58e --- /dev/null +++ b/tileset/village/village30.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://byqhcxr55ats0" +path="res://.godot/imported/village30.png-1642a5c62f3675e49dfdda48b6a22065.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village30.png" +dest_files=["res://.godot/imported/village30.png-1642a5c62f3675e49dfdda48b6a22065.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village31.png b/tileset/village/village31.png new file mode 100644 index 0000000..7ffec78 Binary files /dev/null and b/tileset/village/village31.png differ diff --git a/tileset/village/village31.png.import b/tileset/village/village31.png.import new file mode 100644 index 0000000..bbf8f74 --- /dev/null +++ b/tileset/village/village31.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://o1bum524qlra" +path="res://.godot/imported/village31.png-fcc231cbd019a7227df2f069d4241c7d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village31.png" +dest_files=["res://.godot/imported/village31.png-fcc231cbd019a7227df2f069d4241c7d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village32.png b/tileset/village/village32.png new file mode 100644 index 0000000..26a15a9 Binary files /dev/null and b/tileset/village/village32.png differ diff --git a/tileset/village/village32.png.import b/tileset/village/village32.png.import new file mode 100644 index 0000000..85cec1b --- /dev/null +++ b/tileset/village/village32.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jlh84rtme5sn" +path="res://.godot/imported/village32.png-dbbfc422df305f44c8b26d66e9589935.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village32.png" +dest_files=["res://.godot/imported/village32.png-dbbfc422df305f44c8b26d66e9589935.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village33.png b/tileset/village/village33.png new file mode 100644 index 0000000..c7e8486 Binary files /dev/null and b/tileset/village/village33.png differ diff --git a/tileset/village/village33.png.import b/tileset/village/village33.png.import new file mode 100644 index 0000000..d9f405f --- /dev/null +++ b/tileset/village/village33.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6olh41l5nxc8" +path="res://.godot/imported/village33.png-b5085af5ca475bb93de97e2f2ae06c88.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village33.png" +dest_files=["res://.godot/imported/village33.png-b5085af5ca475bb93de97e2f2ae06c88.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village34.png b/tileset/village/village34.png new file mode 100644 index 0000000..f2a45fc Binary files /dev/null and b/tileset/village/village34.png differ diff --git a/tileset/village/village34.png.import b/tileset/village/village34.png.import new file mode 100644 index 0000000..96ced5b --- /dev/null +++ b/tileset/village/village34.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4gn8oadx4oyd" +path="res://.godot/imported/village34.png-fa5d57ca6355046fef43e780bba0adda.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village34.png" +dest_files=["res://.godot/imported/village34.png-fa5d57ca6355046fef43e780bba0adda.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village35.png b/tileset/village/village35.png new file mode 100644 index 0000000..09834a2 Binary files /dev/null and b/tileset/village/village35.png differ diff --git a/tileset/village/village35.png.import b/tileset/village/village35.png.import new file mode 100644 index 0000000..b33d121 --- /dev/null +++ b/tileset/village/village35.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bb6hq2minkpqb" +path="res://.godot/imported/village35.png-779702531696a6aea24a6f538bef1c9a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village35.png" +dest_files=["res://.godot/imported/village35.png-779702531696a6aea24a6f538bef1c9a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village36.png b/tileset/village/village36.png new file mode 100644 index 0000000..fa07504 Binary files /dev/null and b/tileset/village/village36.png differ diff --git a/tileset/village/village36.png.import b/tileset/village/village36.png.import new file mode 100644 index 0000000..45d1aaf --- /dev/null +++ b/tileset/village/village36.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://utklc5p23jt3" +path="res://.godot/imported/village36.png-e4f4c651721371b25c2b4462f601fad0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village36.png" +dest_files=["res://.godot/imported/village36.png-e4f4c651721371b25c2b4462f601fad0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village37.png b/tileset/village/village37.png new file mode 100644 index 0000000..8093217 Binary files /dev/null and b/tileset/village/village37.png differ diff --git a/tileset/village/village37.png.import b/tileset/village/village37.png.import new file mode 100644 index 0000000..56d5d29 --- /dev/null +++ b/tileset/village/village37.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dldx8qtg8lhrt" +path="res://.godot/imported/village37.png-4e5c4fc913cdb0d9f0553c70257fba6e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village37.png" +dest_files=["res://.godot/imported/village37.png-4e5c4fc913cdb0d9f0553c70257fba6e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village38.png b/tileset/village/village38.png new file mode 100644 index 0000000..69367d5 Binary files /dev/null and b/tileset/village/village38.png differ diff --git a/tileset/village/village38.png.import b/tileset/village/village38.png.import new file mode 100644 index 0000000..d55ebb5 --- /dev/null +++ b/tileset/village/village38.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djpy2m6ketf2n" +path="res://.godot/imported/village38.png-d19248d97084cadd3e43a6ed4d2354ac.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village38.png" +dest_files=["res://.godot/imported/village38.png-d19248d97084cadd3e43a6ed4d2354ac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village39.png b/tileset/village/village39.png new file mode 100644 index 0000000..d8d6942 Binary files /dev/null and b/tileset/village/village39.png differ diff --git a/tileset/village/village39.png.import b/tileset/village/village39.png.import new file mode 100644 index 0000000..7612980 --- /dev/null +++ b/tileset/village/village39.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqb7trcqubyie" +path="res://.godot/imported/village39.png-7c6e1341086ce4106a57028f1015f6df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village39.png" +dest_files=["res://.godot/imported/village39.png-7c6e1341086ce4106a57028f1015f6df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village4.png b/tileset/village/village4.png new file mode 100644 index 0000000..2819381 Binary files /dev/null and b/tileset/village/village4.png differ diff --git a/tileset/village/village4.png.import b/tileset/village/village4.png.import new file mode 100644 index 0000000..8d0d30c --- /dev/null +++ b/tileset/village/village4.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbk8s7a02drcu" +path="res://.godot/imported/village4.png-f65e17e98baa8e305e68ffe87420b593.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village4.png" +dest_files=["res://.godot/imported/village4.png-f65e17e98baa8e305e68ffe87420b593.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village40.png b/tileset/village/village40.png new file mode 100644 index 0000000..6d1316b Binary files /dev/null and b/tileset/village/village40.png differ diff --git a/tileset/village/village40.png.import b/tileset/village/village40.png.import new file mode 100644 index 0000000..a2e2f2b --- /dev/null +++ b/tileset/village/village40.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://2orlf7lcxllu" +path="res://.godot/imported/village40.png-3d29eb4acc9d54853960067cf8fcdaf3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village40.png" +dest_files=["res://.godot/imported/village40.png-3d29eb4acc9d54853960067cf8fcdaf3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village41.png b/tileset/village/village41.png new file mode 100644 index 0000000..fbf1b79 Binary files /dev/null and b/tileset/village/village41.png differ diff --git a/tileset/village/village41.png.import b/tileset/village/village41.png.import new file mode 100644 index 0000000..2e2d4f7 --- /dev/null +++ b/tileset/village/village41.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdpl2pgj3omhn" +path="res://.godot/imported/village41.png-19fad29025df1128610244e8028a64c8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village41.png" +dest_files=["res://.godot/imported/village41.png-19fad29025df1128610244e8028a64c8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village42.png b/tileset/village/village42.png new file mode 100644 index 0000000..211dbae Binary files /dev/null and b/tileset/village/village42.png differ diff --git a/tileset/village/village42.png.import b/tileset/village/village42.png.import new file mode 100644 index 0000000..f745076 --- /dev/null +++ b/tileset/village/village42.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://coq6x3x10y5pc" +path="res://.godot/imported/village42.png-f29f9fd7a502efd8cc76c66a7afbe7a4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village42.png" +dest_files=["res://.godot/imported/village42.png-f29f9fd7a502efd8cc76c66a7afbe7a4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village43.png b/tileset/village/village43.png new file mode 100644 index 0000000..49846a1 Binary files /dev/null and b/tileset/village/village43.png differ diff --git a/tileset/village/village43.png.import b/tileset/village/village43.png.import new file mode 100644 index 0000000..3c6828a --- /dev/null +++ b/tileset/village/village43.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8mee0gaw6u5c" +path="res://.godot/imported/village43.png-995b847ebf6a47e34c7a2e1e41d57a19.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village43.png" +dest_files=["res://.godot/imported/village43.png-995b847ebf6a47e34c7a2e1e41d57a19.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village44.png b/tileset/village/village44.png new file mode 100644 index 0000000..723013b Binary files /dev/null and b/tileset/village/village44.png differ diff --git a/tileset/village/village44.png.import b/tileset/village/village44.png.import new file mode 100644 index 0000000..a92e9a5 --- /dev/null +++ b/tileset/village/village44.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dt54wda31cqgo" +path="res://.godot/imported/village44.png-578b41054f82e41ff44046864a9b1bb5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village44.png" +dest_files=["res://.godot/imported/village44.png-578b41054f82e41ff44046864a9b1bb5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village45.png b/tileset/village/village45.png new file mode 100644 index 0000000..17f1c15 Binary files /dev/null and b/tileset/village/village45.png differ diff --git a/tileset/village/village45.png.import b/tileset/village/village45.png.import new file mode 100644 index 0000000..74a416f --- /dev/null +++ b/tileset/village/village45.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dhs5g6gbkd36l" +path="res://.godot/imported/village45.png-96a0f30dff3d2bf2c8a61c6aa3f3b145.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village45.png" +dest_files=["res://.godot/imported/village45.png-96a0f30dff3d2bf2c8a61c6aa3f3b145.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village5.png b/tileset/village/village5.png new file mode 100644 index 0000000..7f033df Binary files /dev/null and b/tileset/village/village5.png differ diff --git a/tileset/village/village5.png.import b/tileset/village/village5.png.import new file mode 100644 index 0000000..737750b --- /dev/null +++ b/tileset/village/village5.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://diid3abohm730" +path="res://.godot/imported/village5.png-85b6ead9ad93a9836d68fe7f0e933d9c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village5.png" +dest_files=["res://.godot/imported/village5.png-85b6ead9ad93a9836d68fe7f0e933d9c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village50.png b/tileset/village/village50.png new file mode 100644 index 0000000..34e8a1d Binary files /dev/null and b/tileset/village/village50.png differ diff --git a/tileset/village/village50.png.import b/tileset/village/village50.png.import new file mode 100644 index 0000000..2b3d5b9 --- /dev/null +++ b/tileset/village/village50.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dywlgvo8tcott" +path="res://.godot/imported/village50.png-7e60b9f8eabd52898bdb89a859937cf1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village50.png" +dest_files=["res://.godot/imported/village50.png-7e60b9f8eabd52898bdb89a859937cf1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village51.png b/tileset/village/village51.png new file mode 100644 index 0000000..bab6c82 Binary files /dev/null and b/tileset/village/village51.png differ diff --git a/tileset/village/village51.png.import b/tileset/village/village51.png.import new file mode 100644 index 0000000..7138329 --- /dev/null +++ b/tileset/village/village51.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bakganarlllc" +path="res://.godot/imported/village51.png-0354957f1fc9a86350e655984716fd86.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village51.png" +dest_files=["res://.godot/imported/village51.png-0354957f1fc9a86350e655984716fd86.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village52.png b/tileset/village/village52.png new file mode 100644 index 0000000..f029224 Binary files /dev/null and b/tileset/village/village52.png differ diff --git a/tileset/village/village52.png.import b/tileset/village/village52.png.import new file mode 100644 index 0000000..fba1eae --- /dev/null +++ b/tileset/village/village52.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjjr85d82biwi" +path="res://.godot/imported/village52.png-0a974aeda48efdd8afd08c0c4f63390b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village52.png" +dest_files=["res://.godot/imported/village52.png-0a974aeda48efdd8afd08c0c4f63390b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village53.png b/tileset/village/village53.png new file mode 100644 index 0000000..90feda2 Binary files /dev/null and b/tileset/village/village53.png differ diff --git a/tileset/village/village53.png.import b/tileset/village/village53.png.import new file mode 100644 index 0000000..44f254f --- /dev/null +++ b/tileset/village/village53.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmtrsodgxgv2t" +path="res://.godot/imported/village53.png-c6e254d54a670104ea3ecf944adce902.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village53.png" +dest_files=["res://.godot/imported/village53.png-c6e254d54a670104ea3ecf944adce902.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village54.png b/tileset/village/village54.png new file mode 100644 index 0000000..772f3ce Binary files /dev/null and b/tileset/village/village54.png differ diff --git a/tileset/village/village54.png.import b/tileset/village/village54.png.import new file mode 100644 index 0000000..acda01e --- /dev/null +++ b/tileset/village/village54.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djfv7b84smgg0" +path="res://.godot/imported/village54.png-2682428ca7dcfc7baba54691952d6caa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village54.png" +dest_files=["res://.godot/imported/village54.png-2682428ca7dcfc7baba54691952d6caa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village6.png b/tileset/village/village6.png new file mode 100644 index 0000000..fd297fc Binary files /dev/null and b/tileset/village/village6.png differ diff --git a/tileset/village/village6.png.import b/tileset/village/village6.png.import new file mode 100644 index 0000000..7d450da --- /dev/null +++ b/tileset/village/village6.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdh51d84twh82" +path="res://.godot/imported/village6.png-0b41435e89aeb3c3d7231e630428a753.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village6.png" +dest_files=["res://.godot/imported/village6.png-0b41435e89aeb3c3d7231e630428a753.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village7.png b/tileset/village/village7.png new file mode 100644 index 0000000..34d3f62 Binary files /dev/null and b/tileset/village/village7.png differ diff --git a/tileset/village/village7.png.import b/tileset/village/village7.png.import new file mode 100644 index 0000000..e601af4 --- /dev/null +++ b/tileset/village/village7.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxrjquqixlvuj" +path="res://.godot/imported/village7.png-a3d0f08ee457ba9c9ac9c968812d0bf7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village7.png" +dest_files=["res://.godot/imported/village7.png-a3d0f08ee457ba9c9ac9c968812d0bf7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village8.png b/tileset/village/village8.png new file mode 100644 index 0000000..179684b Binary files /dev/null and b/tileset/village/village8.png differ diff --git a/tileset/village/village8.png.import b/tileset/village/village8.png.import new file mode 100644 index 0000000..8b92a7d --- /dev/null +++ b/tileset/village/village8.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcacwrcnawh0l" +path="res://.godot/imported/village8.png-b7db40f18b0ada953d94a69089ce8587.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village8.png" +dest_files=["res://.godot/imported/village8.png-b7db40f18b0ada953d94a69089ce8587.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/tileset/village/village9.png b/tileset/village/village9.png new file mode 100644 index 0000000..b33b83f Binary files /dev/null and b/tileset/village/village9.png differ diff --git a/tileset/village/village9.png.import b/tileset/village/village9.png.import new file mode 100644 index 0000000..48b1a01 --- /dev/null +++ b/tileset/village/village9.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dyhcw3w0dg355" +path="res://.godot/imported/village9.png-afb2388b9419e75a8c264d77c9439ce9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tileset/village/village9.png" +dest_files=["res://.godot/imported/village9.png-afb2388b9419e75a8c264d77c9439ce9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1