Created
June 20, 2022 15:01
-
-
Save meloonics/791f284cbf319edd1a739fbf2f303600 to your computer and use it in GitHub Desktop.
Script for Area2D to wrap its CollisionPolygon2D around a given Polygon2D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
####################################################################################### | |
# ~~~~~~~~ Polygon-Snapping Area2D - written by meloonics in Godot 3.4, 2022 ~~~~~~~~ # | |
# HOW TO USE: # | |
# 1. Make a scene which has Area2D as root and a CollisionPolygon2D as child. # | |
# Call it whatever, but don't rename the CollisionPolygon2D. # | |
# 2. Attach this script to the root. # | |
# 3. Add the scene as child to either Polygon2D or CollisionPolygon2D. Won't work otherwise, no idea why you think it would smh my head | |
# 4. Adjust offset, distance and thickness to your liking. # | |
# The CollisionShape of the Area2D should update automatically. # | |
# 5. Don't overdo it with the thickness in concave shapes, or the polygon will break # | |
# 6. Feel free to adjust the maximum values of the below export vars. # | |
# or remove them. personally I like to have a slider for my floats # | |
# 7. If you accidentally drew your polygon counterclockwise and the shape is now # | |
# on the inside of it, toggle "invert_normals" to fix it. # | |
# 8. Shameless plug time: # | |
# https://meloonics.itch.io/ # | |
# https://github.com/meloonics # | |
# https://www.youtube.com/c/LucyLavend <-- not me, but subscribe anyways # | |
####################################################################################### | |
tool | |
extends Area2D | |
# v-- these ones! | |
export(float, 0.0, 2000.0) var offset = 0.0 | |
export(float, 1.0, 2000.0) var distance = 1.0 | |
export(float, 1.0, 100.0) var thickness = 10.0 | |
export(bool) var invert_normals = false | |
var poly : PoolVector2Array | |
var vertex_normals : PoolVector2Array | |
var edge_normals : PoolVector2Array | |
var segment : PoolVector2Array | |
var segment_normals : PoolVector2Array | |
var segment_polygon : PoolVector2Array | |
func _ready() -> void: | |
if !Engine.editor_hint: | |
poggers_in_chat() | |
func _process(delta : float) -> void: | |
if Engine.editor_hint: | |
poggers_in_chat() | |
#Used for debugging. Activate by uncommenting the "update()"-call in Line 220 | |
func _draw() -> void: | |
if Engine.editor_hint: | |
for i in poly.size(): | |
draw_circle(poly[i], 3.0, Color.red) | |
draw_line(poly[i], poly[i] + vertex_normals[i] * 20.0, Color.red) | |
var next_vertex = poly[posmod(i + 1, poly.size())] | |
var middle = poly[i] + poly[i].direction_to(next_vertex) * (poly[i].distance_to(next_vertex) / 2) | |
draw_line(middle, middle + edge_normals[i] * 20.0, Color.red) | |
func poggers_in_chat() -> void: | |
if is_parent_valid(): | |
construct_area_polygon() | |
else: | |
push_error("Invalid parent: Need CollisionPolygon2D or Polygon2D!") | |
set_process(false) | |
func clone_polygon() -> PoolVector2Array: | |
var poly : PoolVector2Array = get_parent().polygon | |
return poly | |
func is_parent_valid() -> bool: | |
return get_parent() is CollisionPolygon2D or get_parent() is Polygon2D | |
func construct_area_polygon() -> void: | |
poly = clone_polygon() | |
if poly.size() == 0: | |
$CollisionPolygon2D.polygon = PoolVector2Array([]) | |
return | |
investigate_polygon(poly) | |
segment = PoolVector2Array([]) | |
segment_normals = PoolVector2Array([]) | |
segment_polygon = PoolVector2Array([]) | |
var total_length : float = 0.0 | |
for i in poly.size(): | |
var next = poly[posmod(i + 1, poly.size())] | |
total_length += poly[i].distance_to(next) | |
if offset >= total_length: | |
offset = fmod(offset, total_length) | |
distance = clamp(distance, 1.0, total_length) | |
var remaining_length : float = offset | |
var current_idx : int = 0 | |
var found_ending : bool = false | |
var ending : Vector2 | |
var ending_normal : Vector2 | |
var beginning : Vector2 | |
var beginning_normal : Vector2 | |
#find beginning point and potentially end-point | |
while true: | |
#find current vertex, next vertex and the distance between the two | |
var next_idx : int = posmod(current_idx + 1, poly.size()) | |
var cv : Vector2 = poly[current_idx] | |
var nv : Vector2 = poly[next_idx] | |
var dist : float = cv.distance_to(nv) | |
# check if start-point is between current and next vertex: | |
if remaining_length < dist: | |
var point : Vector2 | |
point = cv + cv.direction_to(nv) * remaining_length | |
beginning = point | |
if Array(poly).has(point): | |
beginning_normal = vertex_normals[current_idx] | |
else: | |
beginning_normal = edge_normals[current_idx] | |
#Now check, if the end-point is between the current vertices as well | |
dist = beginning.distance_to(nv) | |
remaining_length = distance | |
if remaining_length < dist: | |
found_ending = true | |
ending = beginning + beginning.direction_to(nv) * remaining_length | |
if Array(poly).has(ending): | |
if ending == beginning: | |
ending_normal = vertex_normals[current_idx] | |
else: | |
ending_normal = vertex_normals[next_idx] | |
else: | |
ending_normal = edge_normals[current_idx] | |
#if no ending has been found, subtract the remaining distance and add next vertex to segment. | |
#also set next vertex as starting point for next loop | |
else: | |
remaining_length -= dist | |
current_idx = next_idx | |
segment.append(beginning) | |
segment.append(nv) | |
segment_normals.append(beginning_normal) | |
segment_normals.append(vertex_normals[current_idx]) | |
break | |
else: | |
remaining_length -= dist | |
current_idx = next_idx | |
if found_ending: | |
segment.append(ending) | |
segment_normals.append(ending_normal) | |
else: | |
#Here we look for the ending if none has been found, and all in-between-vertices | |
while true: | |
#same as before, could have probably done this in previous loop but wanted to keep it readable | |
var next_idx : int = posmod(current_idx + 1, poly.size()) | |
var cv : Vector2 = poly[current_idx] | |
var nv : Vector2 = poly[next_idx] | |
var dist : float = cv.distance_to(nv) | |
if remaining_length < dist: | |
ending = cv + cv.direction_to(nv) * remaining_length | |
segment.append(ending) | |
if Array(poly).has(ending): | |
if ending == cv: | |
ending_normal = vertex_normals[current_idx] | |
else: | |
ending_normal = vertex_normals[next_idx] | |
else: | |
ending_normal = edge_normals[current_idx] | |
segment_normals.append(ending_normal) | |
break | |
else: | |
remaining_length -= dist | |
segment.append(nv) | |
segment_normals.append(vertex_normals[next_idx]) | |
current_idx = next_idx | |
#PHEW! WHAT A RIDE! Now we finally stitch together the new polygon for the Area2D | |
#Basically we make a copy of the segment-array, offset each point by their normal-vector | |
#and invert it | |
segment_polygon.append_array(segment) | |
var inv_array : PoolVector2Array | |
for i in segment.size(): | |
var normal : Vector2 | |
if invert_normals: | |
normal = -segment_normals[i] * thickness | |
else: | |
normal = segment_normals[i] * thickness | |
inv_array.append(segment[i] + normal) | |
inv_array.invert() | |
segment_polygon.append_array(inv_array) | |
#BOOM! We're DONE! | |
$CollisionPolygon2D.polygon = segment_polygon | |
func investigate_polygon(poly : PoolVector2Array) -> void: | |
# Here we gather information about vertex- and edge-normals of the given polygon. | |
vertex_normals = PoolVector2Array([]) | |
edge_normals = PoolVector2Array([]) | |
for i in poly.size(): | |
var vi : Vector2 = poly[i] | |
var vleft : Vector2 = poly[posmod(i - 1, poly.size())] | |
var vright : Vector2 = poly[posmod(i + 1, poly.size())] | |
var angle = vi.direction_to(vleft).angle_to(vi.direction_to(vright)) | |
var normal = vi.direction_to(vleft).rotated(angle / 2) | |
vertex_normals.append(normal * (-1 if angle < 0 else 1)) | |
var edge_normal = vi.direction_to(vright).rotated(-PI/2) | |
edge_normals.append(edge_normal) | |
#update() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment