-
-
Save cgbeutler/9d4134e7937b211927f969c0ad3b01f0 to your computer and use it in GitHub Desktop.
tool | |
class_name ShapePolygon2D extends Polygon2D | |
func _get_configuration_warning() -> String: | |
if shape == null: | |
return "Shape resource is null" | |
if shape is ConvexPolygonShape2D and len(shape.points) <= 1: | |
return "ConvexPolygonShape2D has too few points to draw" | |
if shape is ConcavePolygonShape2D: | |
if len(shape.segments) <= 1: | |
return "ConcavePolygonShape2D has too few points to draw" | |
if len(shape.segments) > 2: | |
for i in range(2, len(shape.segments), 2): | |
if shape.segments[i].distance_squared_to(shape.segments[i-1]) > 0.01: | |
return "ConcavePolygonShape2D segments are disconnected. It will not be displayed properly." | |
return "" | |
export var shape :Shape2D setget set_shape | |
func set_shape( value :Shape2D ): | |
if shape == value: | |
update() | |
return | |
if shape != null: | |
shape.disconnect("changed", self, "__on_shape_changed") | |
shape = value | |
if shape != null: | |
shape.connect("changed", self, "__on_shape_changed") | |
__on_shape_changed() | |
func __on_shape_changed(): | |
update_configuration_warning() | |
polygon = PoolVector2Array() | |
if shape == null: return | |
elif shape is CapsuleShape2D: polygon = capsule(shape.radius, shape.height) | |
elif shape is ConvexPolygonShape2D: polygon = shape.points | |
elif shape is RayShape2D: polygon = arrow(shape.length) | |
elif shape is CircleShape2D: polygon = circle(shape.radius) | |
elif shape is RectangleShape2D: polygon = rectangle(shape.extents) | |
elif shape is ConcavePolygonShape2D: polygon = concave_to_polygons(shape.segments) | |
elif shape is SegmentShape2D: polygon = segment(shape.a, shape.b) | |
elif shape is LineShape2D: polygon = line_to_polygon(shape.normal, shape.d) | |
else: push_error("Unknown shape") | |
#### Helper functions #### | |
static func circle( radius :float = 1.0, center :Vector2 = Vector2.ZERO ) -> PoolVector2Array: | |
var segments :int = int(4*floor(radius/32.0)+16) | |
var points = []; points.resize(segments+1) | |
var segment_size = TAU / segments | |
for i in range(segments): | |
points[i] = Vector2( cos(i * segment_size) * radius + center.x, sin(i * segment_size) * radius + center.y ) | |
points[segments] = points[0] | |
return PoolVector2Array(points) | |
static func rectangle( extents :Vector2 ) -> PoolVector2Array: | |
return PoolVector2Array([ | |
Vector2(-extents.x,-extents.y), Vector2(extents.x,-extents.y), | |
Vector2(extents.x,extents.y), Vector2(-extents.x,extents.y), | |
]) | |
static func capsule( radius :float, height :float ) -> PoolVector2Array: | |
radius = clamp(radius, 0.5, INF) | |
height = clamp(height, 0.0, INF) | |
var hheight = clamp( height / 2.0, 0, INF ) | |
var cap_circle :PoolVector2Array = circle( radius ) | |
var points := []; points.resize(len(cap_circle) + 2) | |
var arch_count = int((len(cap_circle) / 2.0) + 1) | |
for i in range( arch_count ): | |
points[i] = cap_circle[i] + Vector2(0,hheight) | |
for i in range( arch_count - 1, len(cap_circle) ): | |
points[i + 1] = cap_circle[i] + Vector2(0,-hheight) | |
points[-1] = points[0] | |
return PoolVector2Array(points) | |
static func arrow( length :float ): | |
length = 1.0 if is_zero_approx(length) else clamp(abs(length),1.0,INF) *sign(length) | |
var tip := Vector2.DOWN * length | |
var head_dw = clamp(length * 0.1, 1, 4) | |
var head_dh = clamp(length * 0.2, 2, 8) | |
return PoolVector2Array([ | |
tip + Vector2(-0.51, -head_dh), Vector2(-0.51,0.0), Vector2(0.51,0.0), tip + Vector2(0.51, -head_dh), # stem | |
tip + Vector2(head_dw, -head_dh), tip, tip + Vector2(-head_dw, -head_dh), # head | |
]) | |
# NOTE: This ignores holes and disconnected areas and just renders the first string of connected segments one polygon | |
static func concave_to_polygons( p_segments :PoolVector2Array ) -> PoolVector2Array: | |
if not len(p_segments): return PoolVector2Array() | |
var result := PoolVector2Array([p_segments[0]]) | |
for i in range(1, len(p_segments)): | |
if (i%2): #segment tail | |
result.push_back(p_segments[i]) | |
else: #segment head | |
if p_segments[i].distance_squared_to(p_segments[i-1]) > 0.01: | |
result.push_back(p_segments[i]) # disjoint segment started | |
return result | |
static func segment( a :Vector2, b :Vector2 ): | |
var hthick := (a-b).tangent().normalized() * 0.51 | |
return PoolVector2Array([ | |
a + hthick, b + hthick, b - hthick, a - hthick | |
]) | |
static func line_to_polygon( normal :Vector2, d :float ) -> PoolVector2Array: | |
normal = normal.normalized() | |
var tangent := normal.tangent() * 2048.0 | |
return PoolVector2Array([ | |
tangent + (normal*d), -tangent + (normal*d), | |
-tangent + (normal*(d-1.01)), tangent + (normal*(d-1.01)), | |
]) |
You should be able to copy this script anywhere in your project directory. The "code_name" at the beginning will tell the Editor to register this script as a named type. In other words, once copied into your project, this will show up in the "Add Node" and "Change Node Type" menus under the name Shape Polygon 2D.
When you add this node type to the scene, the editor for that node will have a "Shape" slot. You can copy a shape from some other node (like a collision node) and paste it there.
If you don't know, I'd recommend learning more about resources. Resources are used to share data across multiple nodes.
can this new node be animated ?
can this new node be animated ?
I think so, but it is not really optimized for that and may redo a bunch of math each frame. I mostly wrote this for a debugging tool, so you may check for other solutions if you plan on using it in production.
If you really want to use this, animating transforms should be okay, its just changing the shape that would be potentially problematic at scale.
It's great to see a workaround for quick and easy shapes in Godot, but I'm not quite sure what I'm to do with this code since I'm so new to the Godot engine. It would be nice to see a writeup around how to integrate this into my GD env.