Skip to content

Instantly share code, notes, and snippets.

@cgbeutler
Created January 7, 2021 00:12
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cgbeutler/9d4134e7937b211927f969c0ad3b01f0 to your computer and use it in GitHub Desktop.
Save cgbeutler/9d4134e7937b211927f969c0ad3b01f0 to your computer and use it in GitHub Desktop.
A Polygon2D that can take a Shape2D.
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)),
])
@cgbeutler
Copy link
Author

Don't forget to turn on the polygon's anti-aliasing if you want it to look smoother.

@theomarkkuspaul
Copy link

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.

@cgbeutler
Copy link
Author

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.

@jordanlis
Copy link

can this new node be animated ?

@cgbeutler
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment