Last active
April 17, 2022 19:24
-
-
Save nisovin/525fc19f7b3095adc2be0162ce5c1bfe to your computer and use it in GitHub Desktop.
Find a random point in a collision shape
This file contains 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
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