aboutsummaryrefslogtreecommitdiff
path: root/src/addons/rmsmartshape/RMSmartShape2D.gd
diff options
context:
space:
mode:
authorRaffaele Picca <picster@pixelgod.net>2021-02-18 20:31:25 +0100
committerRaffaele Picca <picster@pixelgod.net>2021-02-18 20:31:25 +0100
commit6406cd4708aeec04c23ffdf098e3319a404e7ed9 (patch)
tree8faedb9b3f733cc6ee901407e3ac4d4ed8ae6012 /src/addons/rmsmartshape/RMSmartShape2D.gd
parentc9b3470e43aed98e2a93a332cfc92c3b9ca05163 (diff)
Initial Commit
Diffstat (limited to 'src/addons/rmsmartshape/RMSmartShape2D.gd')
-rw-r--r--src/addons/rmsmartshape/RMSmartShape2D.gd1656
1 files changed, 1656 insertions, 0 deletions
diff --git a/src/addons/rmsmartshape/RMSmartShape2D.gd b/src/addons/rmsmartshape/RMSmartShape2D.gd
new file mode 100644
index 0000000..e9aa425
--- /dev/null
+++ b/src/addons/rmsmartshape/RMSmartShape2D.gd
@@ -0,0 +1,1656 @@
+tool
+extends Node2D
+class_name RMSmartShape2D, "./assets/LEGACY_shape.png"
+
+"""
+- This class assumes that points are in clockwise orientation
+- This class does not support polygons with a counter-clockwise orientation
+ - To remedy this, it contains functions to detect and invert the orientation if needed
+ - Inverting the orientation will need to be called by the code using this class
+ - Inverting the orientation isn't autmoatically done by the class
+ - This would change the indices of points and would cause weird issues
+"""
+
+enum DIRECTION {
+ TOP,
+ RIGHT,
+ BOTTOM,
+ LEFT,
+ TOP_LEFT_INNER,
+ TOP_RIGHT_INNER,
+ BOTTOM_RIGHT_INNER,
+ BOTTOM_LEFT_INNER,
+ TOP_LEFT_OUTER,
+ TOP_RIGHT_OUTER,
+ BOTTOM_RIGHT_OUTER,
+ BOTTOM_LEFT_OUTER,
+ FILL
+}
+
+
+func _dir_to_string(d: int):
+ match d:
+ DIRECTION.TOP:
+ return "TOP"
+ DIRECTION.RIGHT:
+ return "RIGHT"
+ DIRECTION.LEFT:
+ return "LEFT"
+ DIRECTION.BOTTOM:
+ return "BOTTOM"
+ DIRECTION.FILL:
+ return "FILL"
+ DIRECTION.TOP_LEFT_INNER:
+ return "TOP-LEFT-INNER"
+ DIRECTION.TOP_RIGHT_INNER:
+ return "TOP-RIGHT-INNER"
+ DIRECTION.BOTTOM_RIGHT_INNER:
+ return "BOTTOM-RIGHT-INNER"
+ DIRECTION.BOTTOM_LEFT_INNER:
+ return "BOTTOM-LEFT-INNER"
+ DIRECTION.TOP_LEFT_OUTER:
+ return "TOP-LEFT-OUTER"
+ DIRECTION.TOP_RIGHT_OUTER:
+ return "TOP-RIGHT-OUTER"
+ DIRECTION.BOTTOM_RIGHT_OUTER:
+ return "BOTTOM-RIGHT-OUTER"
+ DIRECTION.BOTTOM_LEFT_OUTER:
+ return "BOTTOM-LEFT-OUTER"
+ return "???"
+
+
+class MeshInfo:
+ extends Reference
+ """
+ Extends from Reference to avoid memory leaks
+ Used to organize all requested meshes to be rendered by their texture
+ """
+ var texture: Texture = null
+ var normal_texture: Texture = null
+ var meshes: Array
+ var direction: int
+
+
+class QuadInfo:
+ extends Reference
+ """
+ Extends from Reference to avoid memory leaks
+ Used to describe the welded quads that form the edge data
+ """
+ var pt_a: Vector2
+ var pt_b: Vector2
+ var pt_c: Vector2
+ var pt_d: Vector2
+
+ var tex: Texture
+ var normal_tex: Texture
+ var color: Color
+
+ var flip_texture: bool = false
+ var width_factor: float = 1.0
+ var direction: int
+ var control_point_index: int
+
+ func get_length() -> float:
+ return (pt_d.distance_to(pt_a) + pt_c.distance_to(pt_b)) / 2.0
+
+ func different_render(q: QuadInfo) -> bool:
+ """
+ Will return true if this quad is part of a different render sequence than q
+ """
+ if (
+ q.direction != direction
+ or q.tex != tex
+ or q.flip_texture != flip_texture
+ or q.normal_tex != normal_tex
+ ):
+ return true
+ return false
+
+
+export (bool) var editor_debug = false setget _set_editor_debug
+export (Curve2D) var curve: Curve2D = null setget _set_curve
+export (bool) var closed_shape = false setget _set_close_shape
+export (bool) var auto_update_collider = false setget _set_auto_update_collider
+export (int, 1, 8) var tessellation_stages = 5 setget _set_tessellation_stages
+export (int, 1, 8) var tessellation_tolerence = 4 setget _set_tolerence
+export (bool) var use_global_space = false setget _set_use_global_space
+export (NodePath) var collision_polygon_node
+export (int, 1, 512) var collision_bake_interval = 20
+export (bool) var draw_edges: bool = false setget _set_has_edge
+export (bool) var flip_edges: bool = false setget _set_flip_edge
+
+export (Resource) var shape_material = RMS2D_Material.new() setget _set_material
+
+# This will set true if it is time to rebake mesh, should prevent unnecessary
+# mesh creation unless a change to a property deems it necessary
+var _dirty: bool = true
+
+var vertex_properties = RMS2D_VertexPropertiesArray.new(0)
+
+# For rendering fill and edges
+var meshes: Array = Array()
+var _quads: Array
+
+# Reduce clockwise check if points don't change
+var is_clockwise: bool = false setget , are_points_clockwise
+
+# Signals
+signal points_modified
+signal on_dirty_update
+signal on_closed_change
+
+
+#########
+# GODOT #
+#########
+func _init():
+ pass
+
+func _has_minimum_point_count() -> bool:
+ if closed_shape:
+ return get_point_count() >= 3
+ return get_point_count() >= 2
+
+func _ready():
+ if curve == null:
+ curve = Curve2D.new()
+
+
+func _process(delta):
+ if not is_inside_tree():
+ return
+ _on_dirty_update()
+
+
+func _enter_tree():
+ pass
+
+
+func _exit_tree():
+ if shape_material != null:
+ if ClassDB.class_has_signal("RMS2D_Material", "changed"):
+ shape_material.disconnect("changed", self, "_handle_material_change")
+
+
+func _on_dirty_update():
+ if _dirty:
+ fix_close_shape()
+ if auto_update_collider:
+ bake_collision()
+ bake_mesh()
+ update()
+ _dirty = false
+ emit_signal("on_dirty_update")
+
+
+"""
+Will make sure a shape is closed or open after removing / adding / changing a point
+"""
+
+
+func fix_close_shape():
+ if not _has_minimum_point_count():
+ return
+ var point_count = get_point_count()
+ var first_point = curve.get_point_position(0)
+ var final_point = curve.get_point_position(point_count - 1)
+ if closed_shape and first_point != final_point:
+ add_point_to_curve(get_point_position(0))
+ set_as_dirty()
+ elif (
+ not closed_shape
+ and get_point_position(0) == get_point_position(point_count - 1)
+ and point_count > 2
+ ):
+ remove_point(point_count - 1)
+ set_as_dirty()
+
+
+func _draw():
+ if not is_inside_tree():
+ return
+
+ # Draw fill
+ var mesh_transform = Transform2D()
+ for mesh in meshes:
+ if (
+ mesh != null
+ and mesh.meshes.size() != 0
+ and mesh.texture != null
+ and mesh.direction == DIRECTION.FILL
+ ):
+ for m in mesh.meshes:
+ draw_mesh(m, mesh.texture, mesh.normal_texture, mesh_transform)
+
+ # Draw Left and Right
+ for mesh in meshes:
+ if (
+ mesh != null
+ and mesh.meshes.size() != 0
+ and mesh.texture != null
+ and (mesh.direction == DIRECTION.LEFT or mesh.direction == DIRECTION.RIGHT)
+ ):
+ for m in mesh.meshes:
+ draw_mesh(m, mesh.texture, mesh.normal_texture)
+
+ # Draw Bottom
+ for mesh in meshes:
+ if (
+ mesh != null
+ and mesh.meshes.size() != 0
+ and mesh.texture != null
+ and mesh.direction == DIRECTION.BOTTOM
+ ):
+ for m in mesh.meshes:
+ draw_mesh(m, mesh.texture, mesh.normal_texture)
+
+ # Draw Top
+ for mesh in meshes:
+ if (
+ mesh != null
+ and mesh.meshes.size() != 0
+ and mesh.texture != null
+ and mesh.direction == DIRECTION.TOP
+ ):
+ for m in mesh.meshes:
+ draw_mesh(m, mesh.texture, mesh.normal_texture)
+
+ # Draw Corners
+ for mesh in meshes:
+ if (
+ mesh != null
+ and mesh.meshes.size() != 0
+ and mesh.texture != null
+ and _is_corner_direction(mesh.direction)
+ ):
+ for m in mesh.meshes:
+ draw_mesh(m, mesh.texture, mesh.normal_texture)
+
+ # Draw edge quads for debug purposes (ONLY IN EDITOR)
+ if Engine.editor_hint and editor_debug:
+ for q in _quads:
+ var t: QuadInfo = q
+ draw_line(t.pt_a, t.pt_b, t.color)
+ draw_line(t.pt_b, t.pt_c, t.color)
+ draw_line(t.pt_c, t.pt_d, t.color)
+ draw_line(t.pt_d, t.pt_a, t.color)
+
+ var _range
+ if not closed_shape:
+ _range = range(1, _quads.size())
+ else:
+ _range = range(_quads.size())
+
+ for index in _range:
+ if not (index % 3 == 0):
+ continue
+ # Skip the first and last vert if the shape isn't closed
+ if not closed_shape and (index == 0 or index == _quads.size()):
+ continue
+ var this_quad: QuadInfo = _quads[index % _quads.size()]
+ draw_circle(this_quad.pt_a, 3, Color(0.5, 0, 0))
+ draw_circle(this_quad.pt_b, 3, Color(0, 0, 0.5))
+ draw_circle(this_quad.pt_c, 3, Color(0, 0.5, 0))
+ draw_circle(this_quad.pt_d, 3, Color(0.5, 0, 0.5))
+ for index in _range:
+ if not ((index + 1) % 3 == 0):
+ continue
+ # Skip the first and last vert if the shape isn't closed
+ if not closed_shape and (index == 0 or index == _quads.size()):
+ continue
+ var this_quad: QuadInfo = _quads[index % _quads.size()]
+ draw_circle(this_quad.pt_a, 2, Color(0.75, 0, 0))
+ draw_circle(this_quad.pt_b, 2, Color(0, 0, 0.75))
+ draw_circle(this_quad.pt_c, 2, Color(0, 0.75, 0))
+ draw_circle(this_quad.pt_d, 2, Color(0.75, 0, 0.75))
+ for index in _range:
+ if not ((index + 2) % 3 == 0):
+ continue
+ # Skip the first and last vert if the shape isn't closed
+ if not closed_shape and (index == 0 or index == _quads.size()):
+ continue
+ var this_quad: QuadInfo = _quads[index % _quads.size()]
+ draw_circle(this_quad.pt_a, 1, Color(1, 0, 0))
+ draw_circle(this_quad.pt_b, 1, Color(0, 0, 1))
+ draw_circle(this_quad.pt_c, 1, Color(0, 1, 0))
+ draw_circle(this_quad.pt_d, 1, Color(1, 0, 1))
+
+
+#####################
+# SETTERS / GETTERS #
+#####################
+func _set_tessellation_stages(value: int):
+ tessellation_stages = value
+ set_as_dirty()
+
+
+func _set_tolerence(value: int):
+ tessellation_tolerence = value
+ set_as_dirty()
+
+
+func _set_material(value: RMS2D_Material):
+ 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()
+
+
+func _set_close_shape(value):
+ closed_shape = value
+ fix_close_shape()
+ emit_signal("on_closed_change")
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+
+func _set_auto_update_collider(value: bool):
+ auto_update_collider = value
+ if auto_update_collider:
+ bake_collision()
+
+
+func _set_curve(value: Curve2D):
+ curve = value
+
+ if vertex_properties.resize(curve.get_point_count()):
+ set_as_dirty()
+ emit_signal("points_modified")
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+
+######################
+# SET/GET FOR ARRAYS #
+######################
+func set_point_width(width: float, at_position: int):
+ if vertex_properties.set_width(width, at_position):
+ set_as_dirty()
+ emit_signal("points_modified")
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+
+func get_point_width(at_position: int) -> float:
+ return vertex_properties.get_width(at_position)
+
+
+func is_closed_shape() -> bool:
+ return closed_shape
+
+
+func set_point_texture_index(point_index: int, tex_index: int):
+ if vertex_properties.set_texture_idx(tex_index, point_index):
+ set_as_dirty()
+ emit_signal("points_modified")
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+
+func get_point_texture_index(at_position: int) -> int:
+ return vertex_properties.get_texture_idx(at_position)
+
+
+func get_point_texture_flip(at_position: int) -> bool:
+ return vertex_properties.get_flip(at_position)
+
+
+func set_point_texture_flip(flip: bool, at_position: int):
+ if vertex_properties.set_flip(flip, at_position):
+ set_as_dirty()
+ emit_signal("points_modified")
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+
+######################
+######################
+######################
+
+
+func set_point_in(idx: int, p: Vector2):
+ if curve != null:
+ curve.set_point_in(idx, p)
+ set_as_dirty()
+ emit_signal("points_modified")
+
+
+func set_point_out(idx: int, p: Vector2):
+ if curve != null:
+ curve.set_point_out(idx, p)
+ set_as_dirty()
+ emit_signal("points_modified")
+
+
+func get_point_in(idx: int) -> Vector2:
+ if curve != null:
+ return curve.get_point_in(idx)
+ return Vector2(0, 0)
+
+
+func get_point_out(idx: int) -> Vector2:
+ if curve != null:
+ return curve.get_point_out(idx)
+ return Vector2(0, 0)
+
+
+func get_closest_point(to_point: Vector2):
+ if curve != null:
+ return curve.get_closest_point(to_point)
+ return null
+
+
+func get_closest_offset(to_point: Vector2):
+ if curve != null:
+ return curve.get_closest_offset(to_point)
+ return null
+
+
+func _set_editor_debug(value: bool):
+ editor_debug = value
+ set_as_dirty()
+
+
+func _set_flip_edge(value):
+ flip_edges = value
+ set_as_dirty()
+
+
+func _set_has_edge(value):
+ draw_edges = value
+ set_as_dirty()
+
+
+func _set_use_global_space(value):
+ use_global_space = value
+ set_as_dirty()
+
+
+func get_point_count():
+ if curve == null:
+ return 0
+ return curve.get_point_count()
+
+
+func get_point_position(at_position: int):
+ if curve != null:
+ if at_position < curve.get_point_count() and at_position >= 0:
+ return curve.get_point_position(at_position)
+ return null
+
+
+############
+# GEOMETRY #
+############
+func _add_mesh(mesh: ArrayMesh, texture: Texture, normal_texture: Texture, direction: int):
+ var found: bool = false
+
+ # Is there already a MeshInfo with these textures?
+ for m in meshes:
+ if m.texture == texture and m.normal_texture == normal_texture:
+ # if so, add this mesh to that MeshInfo
+ m.meshes.push_back(mesh)
+ found = true
+
+ if not found:
+ # If not, make a new mesh for these textures
+ var m = MeshInfo.new()
+ m.meshes = [mesh]
+ m.texture = texture
+ m.normal_texture = normal_texture
+ m.direction = direction
+ meshes.push_back(m)
+
+
+func _add_uv_to_surface_tool(surface_tool: SurfaceTool, uv: Vector2):
+ surface_tool.add_uv(uv)
+ surface_tool.add_uv2(uv)
+
+
+func are_points_clockwise() -> bool:
+ if not _has_minimum_point_count():
+ return true
+ var sum = 0.0
+ var point_count = curve.get_point_count()
+ for i in point_count:
+ var pt = curve.get_point_position(i)
+ var pt2 = curve.get_point_position((i + 1) % point_count)
+ sum += pt.cross(pt2)
+
+ is_clockwise = sum > 0.0
+ return is_clockwise
+
+
+func _weld_quads(quads: Array, custom_scale: float = 1.0):
+ for index in range(quads.size()):
+ # Skip the first and last vert if the shape isn't closed
+ if not closed_shape and (index == 0 or index == quads.size()):
+ continue
+
+ var previous_quad: QuadInfo = quads[(index - 1) % quads.size()]
+ var this_quad: QuadInfo = quads[index % quads.size()]
+ var next_quad: QuadInfo = quads[(index + 1) % quads.size()]
+
+ var needed_length: float = 0.0
+ if previous_quad.tex != null and this_quad.tex != null:
+ needed_length = (
+ (
+ previous_quad.tex.get_size().y
+ + (this_quad.tex.get_size().y * this_quad.width_factor)
+ )
+ * 0.5
+ )
+
+ if (
+ not _is_corner_direction(previous_quad.direction)
+ and not _is_corner_direction(this_quad.direction)
+ ):
+ var pt1 = (previous_quad.pt_d + this_quad.pt_a) * 0.5
+ var pt2 = (previous_quad.pt_c + this_quad.pt_b) * 0.5
+
+ var mid_point: Vector2 = (pt1 + pt2) * 0.5
+ var half_line: Vector2 = (
+ (pt2 - mid_point).normalized()
+ * needed_length
+ * custom_scale
+ * 0.5
+ )
+
+ if half_line != Vector2.ZERO:
+ pt2 = mid_point + half_line
+ pt1 = mid_point - half_line
+
+ this_quad.pt_a = pt1
+ this_quad.pt_b = pt2
+ previous_quad.pt_d = pt1
+ previous_quad.pt_c = pt2
+ else:
+ if _is_outer_direction(previous_quad.direction):
+ this_quad.pt_a = previous_quad.pt_c
+ this_quad.pt_b = previous_quad.pt_b
+
+ elif _is_inner_direction(previous_quad.direction):
+ this_quad.pt_a = previous_quad.pt_d
+ this_quad.pt_b = previous_quad.pt_a
+
+ if _is_outer_direction(this_quad.direction):
+ previous_quad.pt_d = this_quad.pt_a
+ previous_quad.pt_c = this_quad.pt_b
+
+ elif _is_inner_direction(this_quad.direction):
+ previous_quad.pt_d = this_quad.pt_d
+ previous_quad.pt_c = this_quad.pt_c
+
+func _is_cardinal_direction(d: int) -> bool:
+ """
+ Takes a values from the DIRECTION enum
+ If the direction is a cardinal direction (Top,Bottom,Left,Right)
+ Will return true
+ else return false
+ """
+ match d:
+ DIRECTION.TOP:
+ return true
+ DIRECTION.LEFT:
+ return true
+ DIRECTION.RIGHT:
+ return true
+ DIRECTION.BOTTOM:
+ return true
+ return false
+
+
+func _is_corner_direction(d: int) -> bool:
+ match d:
+ DIRECTION.TOP_LEFT_INNER:
+ return true
+ DIRECTION.TOP_RIGHT_INNER:
+ return true
+ DIRECTION.BOTTOM_RIGHT_INNER:
+ return true
+ DIRECTION.BOTTOM_LEFT_INNER:
+ return true
+ DIRECTION.TOP_LEFT_OUTER:
+ return true
+ DIRECTION.TOP_RIGHT_OUTER:
+ return true
+ DIRECTION.BOTTOM_RIGHT_OUTER:
+ return true
+ DIRECTION.BOTTOM_LEFT_OUTER:
+ return true
+ return false
+
+
+func _is_inner_direction(d: int) -> bool:
+ match d:
+ DIRECTION.TOP_LEFT_INNER:
+ return true
+ DIRECTION.TOP_RIGHT_INNER:
+ return true
+ DIRECTION.BOTTOM_RIGHT_INNER:
+ return true
+ DIRECTION.BOTTOM_LEFT_INNER:
+ return true
+ return false
+
+
+func _is_outer_direction(d: int) -> bool:
+ match d:
+ DIRECTION.TOP_LEFT_OUTER:
+ return true
+ DIRECTION.TOP_RIGHT_OUTER:
+ return true
+ DIRECTION.BOTTOM_RIGHT_OUTER:
+ return true
+ DIRECTION.BOTTOM_LEFT_OUTER:
+ return true
+ return false
+
+
+func _get_direction_three_points(
+ point: Vector2, point_next: Vector2, point_prev: Vector2, top_tilt: float, bottom_tilt: float
+) -> int:
+ var ab = point - point_prev
+ var bc = point_next - point
+ var dot_prod = ab.dot(bc)
+ var determinant = (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 = rad2deg(angle)
+
+ var clockwise = are_points_clockwise()
+ var dir = 0
+ var ab_dir = _get_direction_two_points(point_prev, point, top_tilt, bottom_tilt)
+ var bc_dir = _get_direction_two_points(point, point_next, top_tilt, bottom_tilt)
+ var corner_range = 15.0
+ if _in_range(abs(deg), 90.0 - corner_range, 90.0 + corner_range):
+ var ab_normal = ab.tangent().normalized()
+ var bc_normal = bc.tangent().normalized()
+ var averaged = (ab_normal + bc_normal) / 2.0
+ if not clockwise:
+ averaged *= -1.0
+
+ var inner = false
+ if deg < 0:
+ inner = true
+ if flip_edges:
+ inner = not inner
+ dir = _vector_to_corner_dir(averaged, inner)
+
+ else:
+ dir = _get_direction_two_points(point, point_next, top_tilt, bottom_tilt)
+ #var dirs = [_dir_to_string(ab_dir), _dir_to_string(bc_dir), _dir_to_string(dir)]
+ #print("===")
+ #print("AB: %s | BC: %s" % [str(ab), str(bc)])
+ #print(("dot: %s | deg: %s | dirs: %s"% [str(dot_prod), str(deg), dirs]))
+ return dir
+
+
+func _in_range(v: float, low: float, high: float) -> bool:
+ return (v >= low) and (v <= high)
+
+
+func to_positive_angle(angle: float) -> float:
+ angle = fmod(angle, 360)
+ if angle < 0:
+ angle += 360
+ return angle
+
+
+func _vector_to_corner_dir(vec: Vector2, inner: bool) -> int:
+ var deg = rad2deg(vec.angle()) + 90.0
+ deg = to_positive_angle(deg)
+
+ if _in_range(deg, 0.0, 90.0):
+ if inner:
+ return DIRECTION.BOTTOM_LEFT_INNER
+ return DIRECTION.TOP_RIGHT_OUTER
+ if _in_range(deg, 90.0, 180.0):
+ if inner:
+ return DIRECTION.TOP_LEFT_INNER
+ return DIRECTION.BOTTOM_RIGHT_OUTER # Correct
+ if _in_range(deg, 180.0, 270.0):
+ if inner:
+ return DIRECTION.TOP_RIGHT_INNER
+ return DIRECTION.BOTTOM_LEFT_OUTER # Correct
+ if _in_range(deg, 270.0, 360.0):
+ if inner:
+ return DIRECTION.BOTTOM_RIGHT_INNER
+ return DIRECTION.TOP_LEFT_OUTER
+
+ return -1
+
+
+func _get_direction_two_points(point, point_next, top_tilt, bottom_tilt) -> int:
+ var v1: Vector2 = point
+ var v2: Vector2 = point_next
+
+ if use_global_space:
+ v1 = get_global_transform().xform(point)
+ v2 = get_global_transform().xform(point_next)
+
+ var clockwise = are_points_clockwise()
+ var top_mid = 0.0
+ var bottom_mid = PI
+ if not clockwise:
+ top_mid = PI
+ bottom_mid = 0
+
+ var angle = atan2(v2.y - v1.y, v2.x - v1.x)
+
+ #Precedence is given to top
+ if abs(top_mid - abs(angle)) <= deg2rad(top_tilt):
+ return DIRECTION.TOP
+
+ #And then to bottom
+ if abs(bottom_mid - abs(angle)) <= deg2rad(bottom_tilt):
+ return DIRECTION.BOTTOM
+
+ if angle > 0:
+ if clockwise:
+ return DIRECTION.RIGHT
+ return DIRECTION.LEFT
+ else:
+ if clockwise:
+ return DIRECTION.LEFT
+ return DIRECTION.RIGHT
+
+
+func _adjust_mesh_quad_segment(quads: Array, quad_indices: Array):
+ var total_length: float = 0.0
+ for quad_index in quad_indices:
+ total_length += quads[quad_index].get_length()
+
+ # Iterate over the quads now until change in direction or texture or looped around
+ var mesh_start_index: int = quad_indices[0]
+ var st: SurfaceTool = SurfaceTool.new()
+ var first_quad = quads[quad_indices[0]]
+ # All quads should not differ. Should have same tex and normal_tex
+ var tex: Texture = first_quad.tex
+ var normal_tex: Texture = first_quad.normal_tex
+
+ var length: float = 0.0
+ var mesh_direction: int
+ var change_in_length: float = -1.0
+ if tex != null and change_in_length == -1.0:
+ change_in_length = (
+ (round(total_length / tex.get_size().x) * tex.get_size().x)
+ / total_length
+ )
+
+ st.begin(Mesh.PRIMITIVE_TRIANGLES)
+ for quad_index in quad_indices:
+ var this_quad: QuadInfo = quads[quad_index % quads.size()]
+ var next_quad: QuadInfo = quads[(quad_index + 1) % quads.size()]
+ var section_length: float = this_quad.get_length() * change_in_length
+ if section_length == 0:
+ section_length = tex.get_size().x
+
+ st.add_color(Color.white)
+
+ # A
+ if tex != null:
+ if not this_quad.flip_texture:
+ _add_uv_to_surface_tool(st, Vector2(length / tex.get_size().x, 0))
+ else:
+ _add_uv_to_surface_tool(
+ st, Vector2((total_length * change_in_length - length) / tex.get_size().x, 0)
+ )
+ st.add_vertex(_to_vector3(this_quad.pt_a))
+
+ # B
+ if tex != null:
+ if not this_quad.flip_texture:
+ _add_uv_to_surface_tool(st, Vector2(length / tex.get_size().x, 1))
+ else:
+ _add_uv_to_surface_tool(
+ st, Vector2((total_length * change_in_length - length) / tex.get_size().x, 1)
+ )
+ st.add_vertex(_to_vector3(this_quad.pt_b))
+
+ # C
+ if tex != null:
+ if not this_quad.flip_texture:
+ _add_uv_to_surface_tool(
+ st, Vector2((length + section_length) / tex.get_size().x, 1)
+ )
+ else:
+ _add_uv_to_surface_tool(
+ st,
+ Vector2(
+ (
+ (total_length * change_in_length - (section_length + length))
+ / tex.get_size().x
+ ),
+ 1
+ )
+ )
+ st.add_vertex(_to_vector3(this_quad.pt_c))
+
+ # A
+ if tex != null:
+ if not this_quad.flip_texture:
+ _add_uv_to_surface_tool(st, Vector2(length / tex.get_size().x, 0))
+ else:
+ _add_uv_to_surface_tool(
+ st, Vector2((total_length * change_in_length - length) / tex.get_size().x, 0)
+ )
+ st.add_vertex(_to_vector3(this_quad.pt_a))
+
+ # C
+ if tex != null:
+ if not this_quad.flip_texture:
+ _add_uv_to_surface_tool(
+ st, Vector2((length + section_length) / tex.get_size().x, 1)
+ )
+ else:
+ _add_uv_to_surface_tool(
+ st,
+ Vector2(
+ (
+ (total_length * change_in_length - (length + section_length))
+ / tex.get_size().x
+ ),
+ 1
+ )
+ )
+ st.add_vertex(_to_vector3(this_quad.pt_c))
+
+ # D
+ if tex != null:
+ if not this_quad.flip_texture:
+ _add_uv_to_surface_tool(
+ st, Vector2((length + section_length) / tex.get_size().x, 0)
+ )
+ else:
+ _add_uv_to_surface_tool(
+ st,
+ Vector2(
+ (
+ (total_length * change_in_length - (length + section_length))
+ / tex.get_size().x
+ ),
+ 0
+ )
+ )
+ st.add_vertex(_to_vector3(this_quad.pt_d))
+ length += section_length
+
+ st.index()
+ st.generate_normals()
+ #st.generate_tangents()
+ _add_mesh(st.commit(), tex, normal_tex, mesh_direction)
+
+
+func _adjust_mesh_quads(quads: Array):
+ """
+ The purpose of this function is to adjust mesh quads so they look good
+ Afterward, they are added to the mesh
+ Not intended for collision quads
+ """
+ if quads.size() < 1:
+ return
+
+ var quad_range
+ var quad_range_len = 0
+ var global_index = 0
+ if not closed_shape:
+ quad_range = range(1, quads.size())
+ quad_range_len = quads.size() - 1
+ global_index = 1
+ else:
+ quad_range = range(quads.size())
+ quad_range_len = quads.size()
+
+ # Weld quads if weld_edges is on
+ if shape_material.weld_edges:
+ _weld_quads(quads)
+
+ var quad_segments = []
+
+ # Get initial start index
+ var initial_start_index = 0
+ if closed_shape:
+ for i in quad_range:
+ initial_start_index = i
+ var prev_quad_index = (i - 1) % quads.size()
+ var this_quad: QuadInfo = quads[i]
+ var prev_quad: QuadInfo = quads[prev_quad_index]
+ if (
+ (i + 1 == quads.size() and not closed_shape)
+ or this_quad.different_render(prev_quad)
+ ):
+ break
+
+ var start_index: int = initial_start_index
+ for j in quad_range:
+ var new_segment = []
+ for i in range(quads.size()):
+ var length_index = (start_index + i) % quads.size()
+ var length_index_next: int = (start_index + i + 1) % quads.size()
+ new_segment.push_back(length_index)
+
+ var this_quad: QuadInfo = quads[length_index]
+ var next_quad: QuadInfo = quads[length_index_next]
+ # Break if change detected
+ if (
+ (length_index + 1 == quads.size() and not closed_shape)
+ or this_quad.different_render(next_quad)
+ ):
+ break
+ quad_segments.push_back(new_segment)
+ start_index += new_segment.size()
+ j += new_segment.size()
+ if (
+ (start_index == quads.size() and not closed_shape)
+ or ((start_index) % quads.size() == initial_start_index and closed_shape)
+ ):
+ break
+ for segment in quad_segments:
+ _adjust_mesh_quad_segment(quads, segment)
+
+
+func get_vertices() -> Array:
+ var verts = []
+ for i in range(0, curve.get_point_count(), 1):
+ verts.push_back(curve.get_point_position(i))
+ return verts
+
+
+func get_distance_as_ratio_from_tessellated_point(points, tess_points, tess_point_index) -> float:
+ """
+ Returns a float between 0.0 and 1.0
+ 0.0 means that this tessellated point is at the same position as the vertex
+ 0.5 means that this tessellated point is half way between this vertex and the next
+ 0.999 means that this tessellated point is basically at the next vertex
+ 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
+ """
+ if tess_point_index == 0:
+ return 0.0
+
+ var vertex_idx = -1
+ # The total tessellated points betwen two verts
+ var tess_point_count = 0
+ # The index of the passed tess_point_index relative to the starting vert
+ var tess_index_count = 0
+ for i in range(0, tess_points.size(), 1):
+ var tp = tess_points[i]
+ var p = points[vertex_idx + 1]
+ tess_point_count += 1
+ if i < tess_point_index:
+ tess_index_count += 1
+ if tp == p:
+ if i < tess_point_index:
+ vertex_idx += 1
+ tess_point_count = 0
+ tess_index_count = 0
+ else:
+ break
+
+ return float(tess_index_count) / float(tess_point_count)
+
+
+func get_vertex_idx_from_tessellated_point(points, tess_points, tess_point_index) -> int:
+ if tess_point_index == 0:
+ return 0
+
+ #print("============")
+ #print("points: %s | tess_p: %s | tess_p_i: %s" % [str(points.size()), str(tess_points.size()), str(tess_point_index)])
+ var vertex_idx = -1
+ for i in range(0, tess_point_index + 1, 1):
+ var tp = tess_points[i]
+ var p = points[vertex_idx + 1]
+ if tp == p:
+ #print("i: %s | p: %s | tp: %s" % [str(i), str(p), str(tp)])
+ vertex_idx += 1
+ return vertex_idx
+
+
+func get_tessellated_points() -> PoolVector2Array:
+ # Point 0 will be the same on both the curve points and the vertecies
+ # Point size - 1 will be the same on both the curve points and the vertecies
+ if not _has_minimum_point_count():
+ return PoolVector2Array()
+ var points = curve.tessellate(tessellation_stages)
+ points[0] = curve.get_point_position(0)
+ points[points.size() - 1] = curve.get_point_position(curve.get_point_count() - 1)
+ return points
+
+
+func _get_next_point_index(idx: int, points: Array, closed: bool) -> int:
+ var new_idx = idx
+ if closed_shape:
+ new_idx = (idx + 1) % points.size()
+ else:
+ new_idx = int(min(idx + 1, points.size() - 1))
+
+ if points[idx] == points[new_idx] and closed:
+ new_idx = _get_next_point_index(new_idx, points, closed)
+ return new_idx
+
+
+func _get_previous_point_index(idx: int, points: Array, closed: bool) -> int:
+ var new_idx = idx
+ if closed_shape:
+ new_idx = idx - 1
+ if new_idx < 0:
+ new_idx += points.size()
+ else:
+ new_idx = int(max(idx - 1, 0))
+
+ if points[idx] == points[new_idx] and closed:
+ new_idx = _get_previous_point_index(new_idx, points, closed)
+ return new_idx
+
+
+func _build_corner_quad(
+ pt_next: Vector2,
+ pt: Vector2,
+ pt_prev: Vector2,
+ pt_width: float,
+ pt_prev_width: float,
+ direction: int,
+ custom_scale: float,
+ custom_offset: float,
+ custom_extends: float
+) -> QuadInfo:
+ var texture = null
+ var texture_normal = null
+ match direction:
+ DIRECTION.TOP_LEFT_INNER:
+ texture = shape_material.top_left_inner_texture
+ texture_normal = shape_material.top_left_inner_texture_normal
+ DIRECTION.TOP_RIGHT_INNER:
+ texture = shape_material.top_right_inner_texture
+ texture_normal = shape_material.top_right_inner_texture_normal
+ DIRECTION.BOTTOM_RIGHT_INNER:
+ texture = shape_material.bottom_right_inner_texture
+ texture_normal = shape_material.bottom_right_inner_texture_normal
+ DIRECTION.BOTTOM_LEFT_INNER:
+ texture = shape_material.bottom_left_inner_texture
+ texture_normal = shape_material.bottom_left_inner_texture_normal
+ DIRECTION.TOP_LEFT_OUTER:
+ texture = shape_material.top_left_outer_texture
+ texture_normal = shape_material.top_left_outer_texture_normal
+ DIRECTION.TOP_RIGHT_OUTER:
+ texture = shape_material.top_right_outer_texture
+ texture_normal = shape_material.top_right_outer_texture_normal
+ DIRECTION.BOTTOM_RIGHT_OUTER:
+ texture = shape_material.bottom_right_outer_texture
+ texture_normal = shape_material.bottom_right_outer_texture_normal
+ DIRECTION.BOTTOM_LEFT_OUTER:
+ texture = shape_material.bottom_left_outer_texture
+ texture_normal = shape_material.bottom_left_outer_texture_normal
+
+ var new_quad = QuadInfo.new()
+ if texture == null:
+ return new_quad
+
+ var tex_size = texture.get_size()
+ var extents = tex_size / 2.0
+ var delta_12 = pt - pt_prev
+ var delta_23 = pt_next - pt
+ var normal_23 = Vector2(delta_23.y, -delta_23.x).normalized()
+ var normal_12 = Vector2(delta_12.y, -delta_12.x).normalized()
+ var width = (pt_prev_width + pt_width) / 2.0
+ var center = pt + (delta_12.normalized() * extents)
+
+ var offset_12 = (normal_12 * custom_scale * pt_width * extents)
+ var offset_23 = (normal_23 * custom_scale * pt_prev_width * extents)
+ var custom_offset_13 = ((normal_12 + normal_23) * custom_offset * extents)
+ if flip_edges:
+ offset_12 *= -1
+ offset_23 *= -1
+ custom_offset_13 *= -1
+
+ var pt_d = (
+ pt
+ + (offset_23)
+ + (offset_12)
+ + custom_offset_13
+ )
+ var pt_a = (
+ pt
+ - (offset_23)
+ + (offset_12)
+ + custom_offset_13
+ #+ offset_12
+ )
+ #var pt_c = pt + (center + offset_23) - (center + offset_12) + custom_offset_13
+ var pt_c = (
+ pt
+ + (offset_23)
+ - (offset_12)
+ + custom_offset_13
+ )
+ var pt_b = (
+ pt
+ - (offset_23)
+ - (offset_12)
+ + custom_offset_13
+ )
+
+ #if custom_offset != 1.0 and custom_offset != 0.0:
+ #print(("n1:%s | n2:%s | d1:%s | d2:%s | o1:%s | o2:%s"% [normal_12, normal_23, delta_12, delta_23, offset_12, offset_23]))
+ new_quad.pt_a = pt_a
+ new_quad.pt_b = pt_b
+ new_quad.pt_c = pt_c
+ new_quad.pt_d = pt_d
+
+ new_quad.direction = direction
+ new_quad.tex = texture
+ new_quad.normal_tex = texture_normal
+
+ return new_quad
+
+
+func _build_quads(custom_scale: float = 1.0, custom_offset: float = 0, custom_extends: float = 0.0) -> Array:
+ """
+ This function will generate an array of quads and return them
+ """
+ # The remainder of the code build up the edge quads
+ var quads: Array = []
+ var tex: Texture = null
+ var tex_normal: Texture = null
+ var tex_size: Vector2
+ var tex_index: int = 0
+
+ var tess_points = get_tessellated_points()
+ var tess_count = tess_points.size()
+
+ var points = get_vertices()
+
+ var top_tilt = shape_material.top_texture_tilt
+ var bottom_tilt = shape_material.bottom_texture_tilt
+
+ var is_clockwise: bool = are_points_clockwise()
+ var corner_quad_indicies = []
+
+ for tess_index in tess_count - 1:
+ var tess_index_next = _get_next_point_index(tess_index, tess_points, closed_shape)
+ var tess_index_prev = _get_previous_point_index(tess_index, tess_points, closed_shape)
+ var tess_pt = tess_points[tess_index]
+ var tess_pt_next = tess_points[tess_index_next]
+ var tess_pt_prev = tess_points[tess_index_prev]
+
+ var pt_index = get_vertex_idx_from_tessellated_point(points, tess_points, tess_index)
+ var pt_index_next = get_vertex_idx_from_tessellated_point(
+ points, tess_points, tess_index_next
+ )
+ var pt_index_prev = get_vertex_idx_from_tessellated_point(
+ points, tess_points, tess_index_prev
+ )
+
+ var cardinal_direction = DIRECTION.TOP
+ var corner_direction = null
+ var is_cardinal_direction = true
+
+ if closed_shape:
+ cardinal_direction = _get_direction_two_points(
+ tess_pt, tess_pt_next, top_tilt, bottom_tilt
+ )
+ corner_direction = _get_direction_three_points(
+ tess_pt, tess_pt_next, tess_pt_prev, top_tilt, bottom_tilt
+ )
+ is_cardinal_direction = _is_cardinal_direction(corner_direction)
+
+ tex = null
+ tex_normal = null
+ if shape_material != null:
+ var material_textures_diffuse = null
+ var material_textures_normal = null
+ match cardinal_direction:
+ DIRECTION.TOP:
+ material_textures_diffuse = shape_material.top_texture
+ material_textures_normal = shape_material.top_texture_normal
+ DIRECTION.BOTTOM:
+ material_textures_diffuse = shape_material.bottom_texture
+ material_textures_normal = shape_material.bottom_texture_normal
+ DIRECTION.LEFT:
+ material_textures_diffuse = shape_material.left_texture
+ material_textures_normal = shape_material.left_texture_normal
+ DIRECTION.RIGHT:
+ material_textures_diffuse = shape_material.right_texture
+ material_textures_normal = shape_material.right_texture_normal
+ if material_textures_diffuse != null:
+ if not material_textures_diffuse.empty():
+ tex_index = (
+ abs(vertex_properties.get_texture_idx(pt_index))
+ % material_textures_diffuse.size()
+ )
+ if material_textures_diffuse.size() > tex_index:
+ tex = material_textures_diffuse[tex_index]
+ if material_textures_normal != null:
+ if material_textures_normal.size() > tex_index:
+ tex_normal = material_textures_normal[tex_index]
+ if tex != null:
+ tex_size = tex.get_size()
+
+ # Get Perpendicular Vector
+ var delta = tess_pt_next - tess_pt
+ var delta_normal = delta.normalized()
+ var vtx_normal = Vector2(delta.y, -delta.x).normalized()
+ # TODO
+ # This causes weird rendering if the texture isn't a square
+ # IE, if taller than wide, left/right edges look skinny, whereas top/bottom looks normal
+ # if wider than tall, top/bottom edges look skinny, whereas left/right looks normal
+ var vtx:Vector2 = vtx_normal * (tex_size * 0.5)
+
+ var scale_in: float = 1
+ var scale_out: float = 1
+
+ var width = vertex_properties.get_width(pt_index)
+ if width != 0.0:
+ scale_in = width
+
+ if not are_points_clockwise():
+ vtx *= -1
+
+ if flip_edges: # allow developer to override
+ vtx *= -1
+
+ var clr: Color = Color.white
+ match cardinal_direction:
+ DIRECTION.TOP:
+ clr = Color.green
+ DIRECTION.LEFT:
+ clr = Color.yellow
+ DIRECTION.RIGHT:
+ clr = Color.red
+ DIRECTION.BOTTOM:
+ clr = Color.blue
+
+ var offset = Vector2.ZERO
+ if tex != null and custom_offset != 0.0:
+ offset = vtx
+ offset *= custom_offset
+
+ if not closed_shape:
+ if tex != null:
+ if tess_index == 0:
+ tess_pt -= (
+ (tess_pt_next - tess_pt).normalized()
+ * tex.get_size()
+ * custom_extends
+ )
+ if tess_index == tess_count - 2 and tex != null:
+ tess_pt_next -= (
+ (tess_pt - tess_pt_next).normalized()
+ * tex.get_size()
+ * custom_extends
+ )
+
+ var ratio = get_distance_as_ratio_from_tessellated_point(points, tess_points, tess_index)
+ var w1 = vertex_properties.get_width(pt_index)
+ var w2 = vertex_properties.get_width(pt_index_next)
+ var w = lerp(w1, w2, ratio)
+ #print("(id1: %s, id2: %s) 1: %s |R: %8f |2: %s = %s" % [str(pt_index), str(pt_index_next), str(w1), ratio, str(w2), str(w)])
+
+ var new_quad = QuadInfo.new()
+ var final_offset_scale_in = (vtx * scale_in) * custom_scale
+ var final_offset_scale_out = (vtx * scale_out) * custom_scale
+ #print("VTX: %s | S_in: %s | CS: %s" % [str(vtx), str(scale_in), str(custom_scale)])
+ var pt_a = tess_pt + final_offset_scale_in + offset
+ var pt_b = tess_pt - final_offset_scale_in + offset
+ var pt_c = tess_pt_next - final_offset_scale_out + offset
+ var pt_d = tess_pt_next + final_offset_scale_out + offset
+ new_quad.pt_a = pt_a
+ new_quad.pt_b = pt_b
+ new_quad.pt_c = pt_c
+ new_quad.pt_d = pt_d
+ new_quad.color = clr
+ new_quad.direction = cardinal_direction
+ new_quad.tex = tex
+ new_quad.normal_tex = tex_normal
+ new_quad.flip_texture = vertex_properties.get_flip(pt_index)
+ new_quad.width_factor = w
+
+ if not is_cardinal_direction and shape_material.use_corners:
+ var prev_width = vertex_properties.get_width(pt_index_prev)
+ var new_quad2 = _build_corner_quad(
+ tess_pt_next,
+ tess_pt,
+ tess_pt_prev,
+ width,
+ prev_width,
+ corner_direction,
+ custom_scale,
+ custom_offset,
+ custom_extends
+ )
+ if new_quad2.tex != null:
+ var previous_quad = null
+ if quads.size() > 0:
+ previous_quad = quads[quads.size() - 1]
+ new_quad2.color = Color.purple
+ new_quad2.flip_texture = vertex_properties.get_flip(pt_index)
+ new_quad2.width_factor = w
+ quads.push_back(new_quad2)
+
+ corner_quad_indicies.push_back(quads.size() - 1)
+
+ var quad2_size = new_quad2.tex.get_size()
+ var quad2_offset = (quad2_size / 2.0) * delta_normal
+ new_quad.pt_a += quad2_offset
+ new_quad.pt_b += quad2_offset
+ #new_quad.tex = new_quad2.tex
+ if previous_quad != null:
+ var rotated = Vector2(0, 0)
+ if are_points_clockwise():
+ rotated = Vector2(-quad2_offset.y, quad2_offset.x)
+ else:
+ rotated = Vector2(-quad2_offset.y, quad2_offset.x)
+ if _is_inner_direction(corner_direction):
+ rotated *= -1
+ previous_quad.pt_c += rotated
+ previous_quad.pt_d += rotated
+
+ quads.push_back(new_quad)
+
+ for corner_quad_index in corner_quad_indicies:
+ pass
+
+ return quads
+
+
+func bake_collision():
+ if collision_polygon_node == null or not is_inside_tree():
+ return
+
+ if has_node(collision_polygon_node):
+ var col_polygon = get_node(collision_polygon_node)
+ var points: PoolVector2Array = PoolVector2Array()
+ var collision_quads = Array()
+
+ var collision_width = 1.0
+ var collision_offset = 0.0
+ var collision_extends = 1.0
+
+ if shape_material != null:
+ collision_width = shape_material.collision_width
+ collision_offset = shape_material.collision_offset + shape_material.render_offset
+ collision_extends = shape_material.collision_extends
+
+ if closed_shape:
+ var old_interval = curve.bake_interval
+ col_polygon.transform = transform
+ col_polygon.scale = Vector2.ONE
+
+ curve.bake_interval = old_interval
+
+ #var curve_points = get_tessellated_points()
+ #for i in curve_points:
+ #points.push_back(
+ #col_polygon.get_global_transform().xform_inv(get_global_transform().xform(i))
+ #)
+ collision_quads = _build_quads(collision_width, collision_offset, collision_extends)
+ for quad in collision_quads:
+ if _is_cardinal_direction(quad.direction):
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(quad.pt_a)
+ )
+ )
+ elif _is_inner_direction(quad.direction):
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(quad.pt_d)
+ )
+ )
+ elif _is_outer_direction(quad.direction):
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(quad.pt_a)
+ )
+ )
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(quad.pt_d)
+ )
+ )
+ curve.bake_interval = collision_bake_interval
+ else:
+ collision_quads = _build_quads(collision_width, collision_offset, collision_extends)
+ _weld_quads(collision_quads, collision_width)
+
+ if not collision_quads.empty():
+ # PT A
+ for quad in collision_quads:
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(quad.pt_a)
+ )
+ )
+
+ # PT D
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(
+ collision_quads[collision_quads.size() - 1].pt_d
+ )
+ )
+ )
+
+ # PT C
+ for quad_index in collision_quads.size():
+ var quad = collision_quads[collision_quads.size() - 1 - quad_index]
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(quad.pt_c)
+ )
+ )
+
+ # PT B
+ points.push_back(
+ col_polygon.get_global_transform().xform_inv(
+ get_global_transform().xform(collision_quads[0].pt_b)
+ )
+ )
+
+ col_polygon.polygon = points
+
+
+func bake_mesh(force: bool = false):
+ if not _dirty and not force:
+ return
+ # Clear Meshes
+ for mesh in meshes:
+ if mesh.meshes != null:
+ mesh.meshes.clear()
+ meshes.resize(0)
+
+ # Cant make a mesh without enough points
+ var points = get_tessellated_points()
+ var point_count = points.size()
+ if (closed_shape and point_count < 3) or (not closed_shape and point_count < 2):
+ return
+
+ var is_clockwise: bool = are_points_clockwise()
+ _quads = Array()
+
+ # Produce Fill Mesh
+ var fill_points:PoolVector2Array = PoolVector2Array()
+ fill_points.resize(point_count)
+ for i in point_count:
+ fill_points[i] = points[i]
+
+ var fill_tris: PoolIntArray = Geometry.triangulate_polygon(fill_points)
+ var st: SurfaceTool
+
+ if closed_shape and shape_material.fill_texture != null:
+ st = SurfaceTool.new()
+ st.begin(Mesh.PRIMITIVE_TRIANGLES)
+
+ for i in range(0, fill_tris.size() - 1, 3):
+ st.add_color(Color.white)
+ _add_uv_to_surface_tool(st, _convert_local_space_to_uv(points[fill_tris[i]]))
+ st.add_vertex(Vector3(points[fill_tris[i]].x, points[fill_tris[i]].y, 0))
+ st.add_color(Color.white)
+ _add_uv_to_surface_tool(st, _convert_local_space_to_uv(points[fill_tris[i + 1]]))
+ st.add_vertex(Vector3(points[fill_tris[i + 1]].x, points[fill_tris[i + 1]].y, 0))
+ st.add_color(Color.white)
+ _add_uv_to_surface_tool(st, _convert_local_space_to_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()
+ _add_mesh(
+ st.commit(),
+ shape_material.fill_texture,
+ shape_material.fill_texture_normal,
+ DIRECTION.FILL
+ )
+
+ if closed_shape and not draw_edges:
+ return
+
+ # Build Edge Quads
+ _quads = _build_quads(1.0, shape_material.render_offset)
+ _adjust_mesh_quads(_quads)
+
+
+#########
+# CURVE #
+#########
+func invert_point_order():
+ var verts = get_vertices()
+
+ # Store inverted verts and properties
+ var inverted_properties = []
+ var inverted = []
+ for i in range(0, verts.size(), 1):
+ var vert = verts[i]
+ var prop = vertex_properties.properties[i]
+ inverted.push_front(vert)
+ inverted_properties.push_front(prop)
+
+ # Clear Verts, add Inverted Verts
+ curve.clear_points()
+ _quads = []
+ meshes = []
+ add_points_to_curve(inverted, -1, false)
+
+ # Set Inverted Properties
+ for i in range(0, inverted_properties.size(), 1):
+ var prop = inverted_properties[i]
+ vertex_properties.properties[inverted_properties.size() - i] = prop
+
+ # Update and set as dirty
+ set_as_dirty()
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+func clear_points():
+ curve.clear_points()
+ vertex_properties = RMS2D_VertexPropertiesArray.new(0)
+ _quads = []
+ meshes = []
+
+func add_points_to_curve(verts:Array, starting_index: int = -1, update:bool = true):
+ for i in range(0, verts.size(), 1):
+ var v = verts[i]
+ if starting_index != -1:
+ curve.add_point(v, Vector2.ZERO, Vector2.ZERO, starting_index + i)
+ vertex_properties.add_point(starting_index + i)
+ else:
+ curve.add_point(v, Vector2.ZERO, Vector2.ZERO, starting_index)
+ vertex_properties.add_point(starting_index)
+
+ if update:
+ _add_point_update()
+
+func add_point_to_curve(position:Vector2, index:int = -1, update:bool = true):
+ curve.add_point(position, Vector2.ZERO, Vector2.ZERO, index)
+ vertex_properties.add_point(index)
+
+ if update:
+ _add_point_update()
+
+func _add_point_update():
+ set_as_dirty()
+ emit_signal("points_modified")
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+func _is_curve_index_in_range(i: int) -> bool:
+ if curve.get_point_count() > i and i >= 0:
+ return true
+ return false
+
+
+func _is_array_index_in_range(a: Array, i: int) -> bool:
+ if a.size() > i and i >= 0:
+ return true
+ return false
+
+
+func set_point_position(at_position: int, position: Vector2):
+ if curve != null:
+ if _is_curve_index_in_range(at_position):
+ curve.set_point_position(at_position, position)
+ set_as_dirty()
+ emit_signal("points_modified")
+
+
+func remove_point(idx: int):
+ curve.remove_point(idx)
+ if vertex_properties.remove_point(idx):
+ set_as_dirty()
+ emit_signal("points_modified")
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+
+func resize_points(size: int):
+ if size < 0:
+ size = 0
+
+ curve.resize(size)
+ if vertex_properties.resize(size):
+ set_as_dirty()
+
+ if Engine.editor_hint:
+ property_list_changed_notify()
+
+
+########
+# MISC #
+########
+func set_as_dirty():
+ _dirty = true
+
+
+func _handle_material_change():
+ set_as_dirty()
+
+
+func _convert_local_space_to_uv(point: Vector2, custom_size: Vector2 = Vector2(0, 0)):
+ var pt: Vector2 = point
+ var tex_size = Vector2(0, 0)
+ if custom_size != Vector2(0, 0):
+ tex_size = custom_size
+ else:
+ tex_size = shape_material.fill_texture.get_size()
+
+ var size: Vector2 = tex_size #* Vector2(1.0 / scale.x, 1.0 / scale.y)
+ var rslt: Vector2 = Vector2(pt.x / size.x, pt.y / size.y)
+ return rslt
+
+
+func _to_vector3(vector: Vector2):
+ return Vector3(vector.x, vector.y, 0)