Skip to content

Instantly share code, notes, and snippets.

@nisovin
Last active April 17, 2022 19:24
Show Gist options
  • Save nisovin/525fc19f7b3095adc2be0162ce5c1bfe to your computer and use it in GitHub Desktop.
Save nisovin/525fc19f7b3095adc2be0162ce5c1bfe to your computer and use it in GitHub Desktop.
Find a random point in a collision shape
var rng := RandomNumberGenerator.new()
func _ready():
rng.randomize()
# example usage
var shape = $CollisionShape2D
for i in 500:
var sprite = Sprite.new()
sprite.texture = load("res://icon.png")
sprite.scale = Vector2(0.05, 0.05)
sprite.position = random_point_in_collision_shape(shape)
add_child(sprite)
func random_point_in_collision_shape(shape: Node2D) -> Vector2:
if shape is CollisionShape2D:
return shape.to_global(random_point_in_shape2d(shape.shape))
elif shape is CollisionPolygon2D:
return shape.to_global(random_point_in_polygon(shape.polygon))
else:
assert(false)
return Vector2.ZERO
func random_point_in_shape2d(shape: Shape2D) -> Vector2:
if shape is RectangleShape2D:
return Vector2(rng.randf_range(-shape.extents.x, shape.extents.x), rng.randf_range(-shape.extents.y, shape.extents.y))
elif shape is CircleShape2D:
return Vector2.RIGHT.rotated(rng.randf_range(0, 2 * PI)) * sqrt(rng.randf()) * shape.radius
elif shape is CapsuleShape2D:
#var v1 = Vector2.RIGHT.rotated(rng.randf_range(0, 2 * PI)) * sqrt(rng.randf()) * shape.radius
#return v1 + Vector2(0, rng.randf_range(-shape.height / 2, shape.height / 2))
var half_height = shape.radius + shape.height / 2
for i in 10: # this seems to be a good number, it never seems to reach this high
var v = Vector2(rng.randf_range(-shape.radius, shape.radius), rng.randf_range(-half_height, half_height))
if v.y < shape.height / 2 and v.y > -shape.height / 2: return v
if v.y > shape.height / 2 and v.distance_squared_to(Vector2(0, shape.height / 2)) < shape.radius * shape.radius:
return v
if v.y < -shape.height / 2 and v.distance_squared_to(Vector2(0, -shape.height / 2)) < shape.radius * shape.radius:
return v
assert(false) # can be removed, but it's nice to know if it fails to find a point in the 10 attempts allowed
return Vector2.ZERO
elif shape is ConvexPolygonShape2D:
return random_point_in_polygon(shape.points)
elif shape is ConcavePolygonShape2D:
return random_point_in_polygon(shape.segments)
elif shape is SegmentShape2D:
return shape.a + (shape.b - shape.a) * rng.randf()
elif shape is RayShape2D:
return Vector2(0, shape.length * rng.randf())
else:
assert(false, "Invalid Shape2D type")
return Vector2.ZERO
func random_point_in_polygon(points: PoolVector2Array) -> Vector2:
var vmin = points[0]
var vmax = points[0]
for point in points:
if point.x < vmin.x:
vmin.x = point.x
elif point.x > vmax.x:
vmax.x = point.x
if point.y < vmin.y:
vmin.y = point.y
elif point.y > vmax.y:
vmax.y = point.y
for i in 20: # this may need to be adjusted, especially if your polygons cover a very small percentage of the area of their bounding box
var v = Vector2(rng.randf_range(vmin.x, vmax.x), rng.randf_range(vmin.y, vmax.y))
if Geometry.is_point_in_polygon(v, points):
return v
assert(false) # can be removed, but it's nice to know when the attempts all fail
return points[0]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment