I've seen one or two examples of this but they seem quite complex considering what I'm trying to do. I'm wanting to generate some vector3's for my navmesh agents to wonder to but to prevent them from wondering off the reservation I'd like to keep them within a simple fixed radius.

What would be the best way to do this?

  • var radius = 3.0 # 3 meters or whatever
    var new_pos = Vector3(randf_range(-1.0, 1.0), 0.0, randf_range(-1.0, 1.0)).normalized() * randf_range(0.0, radius)

Assuming the radius is around the centre (translate it if not), you generate a normalized vector in the desired direction and multiply it with the radius. That's the limit to compare the agent's position to.

Bear with me, this is just pseudo code so if you're wanting it normalised would it be something akin to.

Vector3 (1 * (rand_range(1, 5) , 0, 1 * rand_range(1, 5)

Or am I thinking way too simply?

After you get the position, the vector around the center. You then can add it onto the vector you want to use as the center location.

var radius = 3.0 # 3 meters or whatever
var new_pos = Vector3(randf_range(-1.0, 1.0), 0.0, randf_range(-1.0, 1.0)).normalized() * randf_range(0.0, radius)

Hmmm well this is typically weird of me, I've somehow managed to form a perfect rectangle out of all the placeholder spheres I've been spawning, shouldn't it be a circle? lol.

extends Spatial

var delay = 10.0
export (PackedScene) var RandomPositionPlaceholder

func _process(_delta):
	delay -= 0.1
	if delay <= 0.0:
		var randomPositionPlaceholderInstance = RandomPositionPlaceholder.instance()
		randomPositionPlaceholderInstance.transform.origin = Vector3(self.global_transform.origin.x * rand_range(-0.005, 0.005), 0, self.global_transform.origin.z * rand_range(-0.005, 0.005))
		add_child(randomPositionPlaceholderInstance)
		delay = 10.0

    You choosing a range of random values along the X and Z dimensions, so a rectangle makes sense.

    If you want a circle, use polar coordinates and choose random values for the angle and length of the vector.

    Lethn

    Looks like you forgot to normalize the direction vector before multiplying it with the radius.

    extends Spatial
    
    var delay = 10.0
    export (PackedScene) var RandomPositionPlaceholder
    
    func _process(_delta):
    	delay -= 0.1
    	if delay <= 0.0:
    		var randomPositionPlaceholderInstance = RandomPositionPlaceholder.instance()
    		randomPositionPlaceholderInstance.transform.origin = Vector3(self.global_transform.origin.x * rand_range(-0.005, 0.005), 0, self.global_transform.origin.z * rand_range(-0.005, 0.005)).normalized()
    		add_child(randomPositionPlaceholderInstance)
    		delay = 10.0

    lol now I'm forming a perfect doughnut, this is kind of interesting, I do need to make sure it's a proper radius now though.

      Get an average between the prenormalised and normalized value for each?

      Lethn

      Look at @cybereality 's and my post above to see where the nromalization goes. With normalizing everything you create the offset (the internal border) of the circle, making it a doughnut. This is the sequence: 1.) Find the direction, 2.) normalize the direction, 3.) multiply with the radius or distance.

      Lethn randomPositionPlaceholderInstance.transform.origin = Vector3(self.global_transform.origin.x * rand_range(-0.005, 0.005), 0, self.global_transform.origin.z * rand_range(-0.005, 0.005)).normalized()

      The confusion might have been caused by integrating three steps in one line of code, a technique I also love.

      It should be something like this (not checked):

      Vector3(self.global_transform.origin.x * rand_range(-0.005, 0.005), 0, self.global_transform.origin.z).normalized() * rand_range(-0.005, 0.005)

      or in single steps:

      var direction = Vector3(self.global_transform.origin.x * rand_range(-0.005, 0.005), 0, self.global_transform.origin.z)
      var direction_normalized = direction.normalized()
      var distance = rand_range(-0.005, 0.005)
      var distance_limit = direction_normalized * distance

      Hope that makes it clearer.

        Pixophir Thank you, I'll keep experimenting, I got kind of sidetracked by the weird shapes I was making 😃 These are actually kind of useful techniques to know because say I've got a rectangular or square room and I want the agents to wonder about in that particular area, I can do that now no problem.

        Okay, I think I've got it all sorted guys, thanks for the help, here's the result of what I've got and I'm fairly happy with it now, it's just going to be a matter of making the randomised vector3 co-ordinates match the overall radius of the village centre. In fact, with the minimum amounts I should be able to keep the villagers from clipping into the village centre at all if I get my maths right.

        extends Spatial
        
        var delay = 5
        export (PackedScene) var RandomPositionPlaceholder
        
        func _process(_delta):
        	delay -= 0.1
        	if delay <= 0.0:
        		var randomPositionPlaceholderInstance = RandomPositionPlaceholder.instance()
        		randomPositionPlaceholderInstance.transform.origin = Vector3(self.global_transform.origin.x * rand_range(-5.0, 5.0), 0, self.global_transform.origin.z).normalized() * rand_range(-5.0, 5.0)
        		add_child(randomPositionPlaceholderInstance)
        		delay = 5

        By the way @Pixophir I couldn't get your multi-line code to work, the last line was giving me errors for some reason.

        Yeah, it wasn't checked. What error did you get, and did you realize that I edited that line because there initially was an error in it ? Always doubt my writing :-)

        Btw., the distribution of the blue balls isn't equal in directions. I assume that is wanted.

          Pixophir I was wondering if that was just an over time thing? It does seem to focus on the centre more than around the edges but I was thinking maybe that was just because it was due to it being truly random. I think I'm fine with it like this, in case you're wondering this is for agent patrolling so I'm not using it as a poisson sample or anything.

          Yep, the distributions are uneven.

          Using the method where you pick a position in a square, normalise, then scale by random distance gives you this:

          This is because there is a lot of area in the corners that are outside of the circle. All that area needs to be pulled back into the circle. Or put another way, the distance from the centre to the edge is 255 pixels, but the distance from the centre to the corner is 360.

          The next one is where you pick a random angle and random radius, using cos and sin (so polar coordinates).

          This one bunches up in the middle.
          This is because any radius has equal chance of being picked, but there's less area for pixels so they are higher density.

          Here's the best one:

          This is identical to the second method, pick a random angle and radius. But do a square root of the random value used for the radius. This is called inverse transform sampling.

          I don't have a GDScript version, but here's the code from my C++ test that made the pics above:

          kf::Vec2 p;
          float angle = rnd.norm() * 3.14159265 * 2.0;
          float rad = sqrt(rnd.norm()) * maxRadius;
          p.x = cos(angle)*rad;
          p.y = sin(angle)*rad;

          rnd.norm() is my function to get a random value between 0 and 1. You must do the sqrt to the value while it's still 0 to 1.

          Here's a cool page describing the technique: https://programming.guide/random-point-within-circle.html

          Edit: of course most of the time nobody would notice the difference, it's only when lots of things are spawned and visible at once that the pattern is noticeable.

          Ok, a Godot version:

          	var angle = rand_range(0,PI*2)
          	var radius = sqrt(randf()) * maxRadius
          	var pos = Vector3(cos(angle)*radius, 0, sin(angle)*radius)

          Yeah, I was considering mentioning the distribution, but it only really matters for high density (like particle simulations). If you are only placing like 10 or 15 objects in a decent sized radius, you will never notice.

          LOL yeah, thanks for all the information, it might come in handy if I want to do something more accurate in the future but that's way overkill for some simple randomised AI wandering. I'm going to have villagers wondering around a certain radius and the main goal is just keeping them from going off the reservation too much. Again, the sphere's are purely just a visual aid so I can see where the co-ordinates are hitting each time.