diff options
Diffstat (limited to 'src/addons/rmsmartshape/shapes/point_array.gd')
-rw-r--r-- | src/addons/rmsmartshape/shapes/point_array.gd | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/src/addons/rmsmartshape/shapes/point_array.gd b/src/addons/rmsmartshape/shapes/point_array.gd new file mode 100644 index 0000000..34d9c1c --- /dev/null +++ b/src/addons/rmsmartshape/shapes/point_array.gd @@ -0,0 +1,415 @@ +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 +export var _points: Dictionary = {} setget set_points +# Contains all keys; the order of the keys determines the order of the points +export var _point_order: Array = [] setget set_point_order +# Key is tuple of point_keys; Value is the CONSTRAINT enum +export var _constraints = {} setget set_constraints +# Next key value to generate +export var _next_key = 0 setget set_next_key + +var _constraints_enabled: bool = true + +signal constraint_removed(key1, key2) + +################### +# HANDLING POINTS # +################### + + +func _init(): + # Required by Godot to correctly make unique instances of this resource + _points = {} + _point_order = [] + _constraints = {} + _next_key = 0 + + +func set_points(ps: Dictionary): + # Called by Godot when loading from a saved scene + for k in ps: + var p = ps[k] + p.connect("changed", self, "_on_point_changed", [p]) + _points = ps + property_list_changed_notify() + + +func set_point_order(po: Array): + _point_order = po + property_list_changed_notify() + + +func set_constraints(cs: Dictionary): + _constraints = cs + property_list_changed_notify() + + +func set_next_key(i: int): + _next_key = i + property_list_changed_notify() + + +func __generate_key(next: int) -> int: + if not is_key_valid(next): + return __generate_key(max(next + 1, 0)) + return next + + +func _generate_key() -> int: + var next = __generate_key(_next_key) + _next_key = next + 1 + return next + + +func get_next_key() -> int: + """ + Will return the next key that will be used when adding a point + """ + return __generate_key(_next_key) + + +func is_key_valid(k: int) -> bool: + if k < 0: + return false + if _points.has(k): + return false + return true + + +func add_point(point: Vector2, idx: int = -1, use_key: int = -1) -> int: + var next_key = use_key + if next_key == -1 or not is_key_valid(next_key): + next_key = _generate_key() + var new_point = SS2D_Point.new(point) + new_point.connect("changed", self, "_on_point_changed", [new_point]) + _points[next_key] = new_point + _point_order.push_back(next_key) + if idx != -1: + set_point_index(next_key, idx) + return next_key + + +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_point_at_index(idx: int) -> int: + return _points[_point_order[idx]].duplicate(true) + + +func get_point(key: int) -> int: + return _points[key].duplicate(true) + + +func set_point(key: int, value: SS2D_Point): + if has_point(key): + _points[key] = value.duplicate(true) + + +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: + break + idx += 1 + return idx + return -1 + + +func invert_point_order(): + _point_order.invert() + + +func set_point_index(key: int, idx: int): + if not has_point(key): + return + var old_idx = get_point_index(key) + if idx < 0 or idx >= _points.size(): + idx = _points.size() - 1 + if idx == old_idx: + return + _point_order.remove(old_idx) + _point_order.insert(idx, key) + + +func has_point(key: int) -> bool: + return _points.has(key) + + +func get_all_point_keys() -> Array: + """ + _point_order should contain every single point ONLY ONCE + """ + return _point_order.duplicate(true) + + +func remove_point(key: int) -> bool: + if has_point(key): + remove_constraints(key) + var p = _points[key] + if p.is_connected("changed", self, "_on_point_changed"): + p.disconnect("changed", self, "_on_point_changed") + _point_order.remove(get_point_index(key)) + _points.erase(key) + return true + return false + + +func clear(): + _points.clear() + _point_order.clear() + _constraints.clear() + _next_key = 0 + emit_signal("changed") + + +func set_point_in(key: int, value: Vector2): + if has_point(key): + _points[key].point_in = value + + +func get_point_in(key: int) -> Vector2: + if has_point(key): + return _points[key].point_in + return Vector2(0, 0) + + +func set_point_out(key: int, value: Vector2): + if has_point(key): + _points[key].point_out = value + + +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): + if has_point(key): + _points[key].position = value + + +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): + if has_point(key): + _points[key].properties = value + + +func get_point_properties(key: int) -> SS2D_VertexProperties: + if has_point(key): + return _points[key].properties.duplicate(true) + var new_props = SS2D_VertexProperties.new() + return new_props + + +func get_key_from_point(p: SS2D_Point) -> int: + for k in _points: + if p == _points[k]: + return k + return -1 + + +func _on_point_changed(p: SS2D_Point): + var key = get_key_from_point(p) + if _updating_constraints: + _keys_to_update_constraints.push_back(key) + else: + update_constraints(key) + + +############### +# CONSTRAINTS # +############### + +var _updating_constraints = false +var _keys_to_update_constraints = [] + + +func disable_constraints(): + _constraints_enabled = false + + +func enable_constraints(): + _constraints_enabled = true + + +func _update_constraints(src: int): + if not _constraints_enabled: + return + var constraints = get_point_constraints(src) + for tuple in constraints: + var constraint = constraints[tuple] + if constraint == CONSTRAINT.NONE: + continue + var dst = get_other_value_from_tuple(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)) + + +func update_constraints(src: int): + """ + Will mutate points based on constraints + values from Passed key will be used to update constrained points + """ + 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.empty(): + var key_set = _keys_to_update_constraints.duplicate(true) + _keys_to_update_constraints.clear() + for k in key_set: + _update_constraints(k) + + _updating_constraints = false + emit_signal("changed") + + +func get_point_constraints(key1: int) -> Dictionary: + """ + Will Return all constraints for a given key + """ + var constraints = {} + for tuple in _constraints: + if tuple.has(key1): + constraints[tuple] = _constraints[tuple] + return constraints + + +func get_point_constraint(key1: int, key2: int) -> int: + """ + Will Return the constraint for a pair of keys + """ + var t = create_tuple(key1, key2) + var keys = _constraints.keys() + var t_index = find_tuple_in_array_of_tuples(keys, t) + if t_index == -1: + return CONSTRAINT.NONE + var t_key = keys[t_index] + return _constraints[t_key] + + +func set_constraint(key1: int, key2: int, constraint: int): + var t = create_tuple(key1, key2) + var existing_tuples = _constraints.keys() + var existing_t_index = find_tuple_in_array_of_tuples(existing_tuples, t) + if existing_t_index != -1: + t = existing_tuples[existing_t_index] + _constraints[t] = constraint + if _constraints[t] == CONSTRAINT.NONE: + _constraints.erase(t) + emit_signal("constraint_removed", key1, key2) + else: + update_constraints(key1) + + +func remove_constraints(key1: int): + var constraints = get_point_constraints(key1) + for tuple in constraints: + var constraint = constraints[tuple] + var key2 = get_other_value_from_tuple(tuple, key1) + set_constraint(key1, key2, CONSTRAINT.NONE) + + +func remove_constraint(key1: int, key2: int): + set_constraint(key1, key2, CONSTRAINT.NONE) + + +func get_all_constraints_of_type(type: int) -> int: + var constraints = [] + for t in _constraints: + var c = _constraints[t] + if c == type: + constraints.push_back(t) + return constraints + + +######## +# MISC # +######## +func debug_print(): + for k in get_all_point_keys(): + var pos = get_point_position(k) + var _in = get_point_in(k) + var out = get_point_out(k) + print("%s = P:%s | I:%s | O:%s" % [k, pos, _in, out]) + + +func duplicate(sub_resource: bool = false): + var _new = __new() + _new._next_key = _next_key + if sub_resource: + var new_point_dict = {} + for k in _points: + new_point_dict[k] = _points[k].duplicate(true) + _new._points = new_point_dict + _new._point_order = _point_order.duplicate(true) + + _new._constraints = {} + for tuple in _constraints: + _new._constraints[tuple] = _constraints[tuple] + else: + _new._points = _points + _new._point_order = _point_order + _new._constraints = _constraints + return _new + + +# Workaround (class cannot reference itself) +func __new(): + return get_script().new() + + +######### +# TUPLE # +######### + +static func create_tuple(a: int, b: int) -> Array: + return [a, b] + +static func get_other_value_from_tuple(t: Array, value: int) -> int: + if t[0] == value: + return t[1] + elif t[1] == value: + return t[0] + return -1 + +static func tuples_are_equal(t1: Array, t2: Array) -> bool: + return (t1[0] == t2[0] and t1[1] == t2[1]) or (t1[0] == t2[1] and t1[1] == t2[0]) + +static func find_tuple_in_array_of_tuples(tuple_array: Array, t: Array) -> int: + for i in range(tuple_array.size()): + var other = tuple_array[i] + if tuples_are_equal(t, other): + return i + return -1 |