how to get a random tile in a radious?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Gagust

Hi, I want to select a random tile, inside in a determinate radius.
I am working in a procedural generator of islands, it work verry well :), and to place randomly tiles like trees for example, i would like use diferents ways, one of this could be put a tree, take his cor and randomly place others trees near in a radius

I thought 2 ways to do it:
1- make an array with all near tiles in a radius after, select randomly in the array
2- select a random cor point inside in a radius after, place in the nearest tile

radius like that

I don’t know much about maths, less about cos() and sin(), how can I do one, both or others ways

English isn’t my native language, I tried to do the better that I could :3

I can help you, but I didn’t the whole question.

theoMCI | 2020-03-19 15:44

let’s say we have one point (a center), and a radius, for example 4 tiles aways from the center, how can I get the tiles that are in this imagin circle?, do you understand me?

Gagust | 2020-03-19 18:40

:bust_in_silhouette: Reply From: Zylann

I put together an example script with a tilemap, showing how to obtain grid positions within a radius, and also a few different methods to pick a random position within a circle. (note that if you have an array of positions you can also pick a random item from it directlty).

extends Node


onready var _tilemap = $TileMap

var _radius_in_tiles = 4.0 # Note: decimal values work too, you may try
var _center_in_tiles = Vector2(5, 5) # Hardcoded, you may choose


func _ready():
	var positions = get_tile_positions_in_circle()
	for pos in positions:
		_tilemap.set_cellv(pos, 0)

	# Get random position around point. There are several ways to do it.
	var tile_position = pick_random_position_v1()
	# Remove a tile for testing
	_tilemap.set_cellv(tile_position, -1)

	# Or simply use the array computed earlier.
	tile_position = positions[randi() % len(positions)]
	_tilemap.set_cellv(tile_position, -1)


func get_tile_positions_in_circle():
	# Get the rectangle bounding the circle
	var radius_vec = Vector2(_radius_in_tiles, _radius_in_tiles)
	var min_pos = (_center_in_tiles - radius_vec).floor()
	var max_pos = (_center_in_tiles + radius_vec).ceil()
	
	# Convert to integer so we can use range for loop
	var min_x = int(min_pos.x)
	var max_x = int(max_pos.x)
	var min_y = int(min_pos.y)
	var max_y = int(max_pos.y)
	
	var positions = []
	
	# Gather all points that are within the radius
	for y in range(min_y, max_y):
		for x in range(min_x, max_x):
			var tile_pos = Vector2(x, y)
			if tile_pos.distance_to(_center_in_tiles) < _radius_in_tiles:
				 positions.append(tile_pos)
	
	return positions


func pick_random_position_v1():
	# Trigonometry solution.
	# It's fast and simple, but points have a higher chance to be near the center.
	# In some cases this is acceptable.
	var angle = rand_range(-PI, PI)
	var direction = Vector2(cos(angle), sin(angle))
	return _center_in_tiles + direction * _radius_in_tiles


func pick_random_position_v2():
	# Improved trigonometric solution.
	# Density of results will be the same at any distance from center.
	# https://programming.guide/random-point-within-circle.html
	var angle = rand_range(-PI, PI)
	var direction = Vector2(cos(angle), sin(angle))
	var distance = _radius_in_tiles * sqrt(rand_range(0.0, 1.0))
	return (_center_in_tiles + direction * distance).floor()


func pick_random_position_v3():
	# So-called "Monte-carlo" solution.
	# Generate random points until it is inside the radius.
	# Can be used in more complex shape scenarios as simple prototyping solution.
	for attempt in 100:
		var pos = _center_in_tiles + Vector2(
			rand_range(-_radius_in_tiles, _radius_in_tiles),
			rand_range(-_radius_in_tiles, _radius_in_tiles))
		if pos.distance_to(_center_in_tiles) > _radius_in_tiles:
			return pos.floor()
	return _center_in_tiles.floor()

Thank you very much :3

Gagust | 2020-03-19 22:54

I have one problem

# Gather all points that are within the radius
for y in range(min_y, max_y):
    for x in range(min_x, max_x):
        var tile_pos = Vector2(x, y)
        if tile_pos.distance_to(_center_in_tiles) < _radius_in_tiles:
             positions.append(tile_pos)

here at the end

if tile_pos.distance_to(_center_in_tiles) < _radius_in_tiles:
             positions.append(tile_pos)

shows an error that says: Invalid operands ‘Vector2’ and ‘float’ in operator ‘<’
I did somethig worng, or it’s a mistake of you?

extends TileMap

var radio = 4.0
var centro = Vector2()

var positions = []

func _ready():
var rv = Vector2(radio, radio)
var min_pos = (centro - rv).floor()
var max_pos = (centro + rv).ceil()

var min_X = int(min_pos.x)
var min_Y = int(min_pos.y)
var max_X = int(max_pos.x)
var max_Y = int(max_pos.y)

for y in range(min_Y, max_Y):
	for x in range(min_X, max_X):
		var tile_pos = Vector2(x, y)
		if tile_pos.direction_to(centro) < radio:
			positions.append(tile_pos)

for i in range(positions.size()):
	set_cellv(positions[i], 0)

Gagust | 2020-03-20 00:31

if tile_pos.direction_to(centro) < radio:

You wrote direction_to, it must be distance_to.

Zylann | 2020-03-20 00:39

ho sorry, very stupid mistake

Gagust | 2020-03-20 02:09