Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Vector2 circle points generation method to RandomNumberGenerator #43103

Closed
wants to merge 2 commits into from
Closed

Conversation

Xrayez
Copy link
Contributor

@Xrayez Xrayez commented Oct 26, 2020

Solves the 2D part for godotengine/godot-proposals#1731.
Closes #34301, resolves #28264.

godot_randv_circle_range

Provides uniform distribution for generating points in a circle (unit on the circle's boundary by default, but any radius can be specified).

Added documentation and unit tests (tests via separate commit).

func _ready():
	var rng = RandomNumberGenerator.new()
	var point: Vector2
	point = rng.randv_circle(0.0, 1.0) # Full circle, first picture.
	point = rng.randv_circle(0.5, 1.0) # Ring, second picture.
	point = rng.randv_circle() # Boundary, normalized unit vector, third picture.

Test project:
randv_circle.zip


P.S. This is probably the first feature PR with both documentation and unit tests written ever in Godot. 🙂

@Xrayez Xrayez requested a review from a team as a code owner October 26, 2020 19:04
@KoBeWi KoBeWi added this to the 4.0 milestone Oct 26, 2020
@Zireael07
Copy link
Contributor

Large asteroid belts coming as soon as this lands <3 <3 <3

return Vector2(r * Math::cos(t), r * Math::sin(t));
}

_FORCE_INLINE_ Vector2 randv_range_circle(real_t p_min_radius = 0.0, real_t p_max_radius = 1.0) {
Copy link
Member

@Chaosus Chaosus Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subjectively, I think randv_range_circle should be named randv_circle_range(to filter it better alphabetically) or even randv_ring (radically)

Copy link
Contributor Author

@Xrayez Xrayez Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like randv_ring, and perhaps range is not the best choice here because it might falsely suggest that it's going to generate arc/sector range rather than radius range. That said, going for consistency with existing *_range methods could actually hurt the meaning.

Except that if we rename randv_range_circle to randv_ring, if you have min_radius == max_radius, then it's not really a ring anymore, but restricted to circle's boundary, there's a reason why godotengine/godot-proposals#1731 lists rand_point_on_circle to handle this case, both in terms of use cases and effeciency.

That way, randv_circle_range and randv_ring_range could be added later to restrict the generation to a sector, but that's likely overkill for Godot, and I'm not sure how trivial it is to implement it. But then again, randv_circle_sector and randv_ring_sector would probably make more sense anyways (mostly talking about extending RandomNumberGenerator via modules now).

That said, there are many different ways to name this, I'm fine with either (but prefer less verbose names myself).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed randv_range_circlerandv_ring for now. 🙂

@Xrayez
Copy link
Contributor Author

Xrayez commented Oct 27, 2020

Apart from randv_range_circlerandv_ring rename, I also propose to add another method to generate points on circle's boundary (third picture). Looking at the test cases I've written, it just asks for it, and the implementation behind randv_on_circle(radius) would be more efficient compared to randv_ring(radius, radius), and likely more intuitive for users, and this is the method to use if you want to generate unit length vectors.

So, to list all the proposed methods:

  • randv_circle (can also rename to randv_in_circle)
  • randv_on_circle
  • randv_ring (can also rename to randv_in_ring)

Again, refer to Unity Random API. The implementation is superior to Unity because this PR allows you to override unit circle and specify different radius, but it does generate points in unit circles by default. 😉

And by the way, rotation can also be randomized using this API and Godot's existing Vector2 API:

var rng = RandomNumberGenerator.new()
$Sprite.rotation = rng.randv_on_circle().angle()

@Xrayez Xrayez changed the title Add randv_circle and randv_range_circle to RandomNumberGenerator Add Vector2 circle generation methods to RandomNumberGenerator Oct 27, 2020
@Xrayez
Copy link
Contributor Author

Xrayez commented Oct 27, 2020

I can also remove randv_ring implementation, if you think it's too specific... But it does handle all cases.

So, alternatively, the current randv_ring can be renamed to just randv_circle(min_radius = 0.0, max_radius = 1.0).

With:

  • min_radius = 0.0, max_radius = 1.0: covers randv_circle functionality.
  • min_radius = 1.0, max_radius = 1.0: covers randv_on_circle functionality.
  • min_radius = 0.5, max_radius = 1.0: covers randv_ring functionality.

At expense of performance for simple use cases, of course.

@Chaosus
Copy link
Member

Chaosus commented Oct 27, 2020

Or maybe it's better to be like Unity (https://docs.unity3d.com/ScriptReference/Random.html) where you generate a unit vector and the user should manually multiply it by a scalar to provide a required radius for a circle or a sphere.

@Xrayez
Copy link
Contributor Author

Xrayez commented Oct 27, 2020

Performance is not priority for Godot development as seen in best practices, so I'd prefer to have general-purpose solution if we go this route. The default range can be set to 1.0..1.0 and this will already produce unit vectors with current implementation.

I'm also not sure whether limited floating point precision could allow high enough granularity, what I mean is that if you generate thousands of points within the unit length and multiply by scalar, then you may suffer from bad distribution within an area, also given the default Godot's floating point epsilon:

#define CMP_EPSILON 0.00001

@Xrayez Xrayez changed the title Add Vector2 circle generation methods to RandomNumberGenerator Add Vector2 circle points generation methods to RandomNumberGenerator Oct 27, 2020
@groud
Copy link
Member

groud commented Oct 27, 2020

I agree with Chaosus here, there is not need to provide a range version of the function here.
Simply allow generating a random unit vector then let the user multiply it. That's a simple and efficient API.

@Xrayez
Copy link
Contributor Author

Xrayez commented Oct 27, 2020

Applied suggested changes, only randv_circle is there now.

@Xrayez Xrayez changed the title Add Vector2 circle points generation methods to RandomNumberGenerator Add Vector2 circle points generation method to RandomNumberGenerator Oct 27, 2020
@Xrayez
Copy link
Contributor Author

Xrayez commented Nov 6, 2020

See alternative PR which should be simpler to maintain, and which won't bloat Godot's binary sizes much: #43343.

@Xrayez
Copy link
Contributor Author

Xrayez commented May 4, 2021

I agree with Chaosus here, there is not need to provide a range version of the function here.
Simply allow generating a random unit vector then let the user multiply it. That's a simple and efficient API.

I've closed #43343 for the reasons outlined in #43343 (comment).

I'm going to assume that groud is going to say that this PR should be more feature-complete when comparing to #43343, similarly to rationale slightly unrelated to this PR #48237 (comment):

Consequently, I think it would be more feature-complete [emphasis mine] to rename the function to from_polar(...) and to allow a second argument (that could be 1.0 by default), that would allowing providing a length too.

Nonetheless, this PR also allows to provide minimum and maximum radiuses similarly (instead of having to multiply by vector via script), which also doesn't suffer from uniformity issues as described in #43343 (comment), so this should be feature-complete.

@groud
Copy link
Member

groud commented May 4, 2021

I'm going to assume that groud is going to say that this PR should be more feature-complete when comparing to #43343, similarly to rationale slightly unrelated to this PR #48237 (comment):

Well, no. My opinion is that they are different APIs that should be considered differently.

Here, I believe that a random number generator should provide very basic elements only to build upon, as it's easy to go feature creep in that kind of areas. There are plenty of ways to generate random things. Like you may want to generate the direction uniformly, but the length with a normal distribution. As a consequence, I'd rather keep the API simple, with a simple generate_random_unit_vector() function, as it covers most use cases and keep the function very simple.

For the Vector2 API, my reasoning is different, as I believe that from_angle() is not explicit enough about what it does.
So, as I explained, I believe from_polar() would be easier to understand and more explicit. I does not mean the length parameter is very important there anyway (it is indeed not, as you can use a multiplication by a scalar), but think that using an explicit name there outweighs the benefits of a simpler API.

@Xrayez
Copy link
Contributor Author

Xrayez commented May 4, 2021

Here, I believe that a random number generator should provide very basic elements only to build upon, as it's easy to go feature creep in that kind of areas.

You can also say that this PR is a feature creep as well, since RandomNumberGenerator already provides necessary functions to achieve the same thing via script.

So, that's why I'm assuming you'd rather prefer a more feature-complete solution here, because otherwise you'd already reject the idea in the first place. Or have you changed your opinion in the meantime?

To clarify, it's not my task to point to contradictions for the sake of it, I'd just like to better understand your current opinion on this based on previous (even marginally related) discussions, and I'm sorry if it looks rude when I quote stuff.

There are plenty of ways to generate random things. Like you may want to generate the direction uniformly, but the length with a normal distribution.

Again, all these things are currently possible to achieve with this PR, either via script or completely relying on Godot's implementation. I recall you've also said that we could also document the scope, that's also how feature-creep can be resolved.

That said, I don't see a major problem with those statements.

@Xrayez Xrayez closed this Jun 25, 2021
@Xrayez Xrayez reopened this Dec 24, 2021
@Calinou Calinou removed the archived label Dec 24, 2021
@akien-mga
Copy link
Member

Closing as this contributor can no longer update this further.

@Zireael07
Copy link
Contributor

Can someone else pick this up instead?

@KoBeWi
Copy link
Member

KoBeWi commented Nov 18, 2022

Here's a GDScript one-liner if anyone is interested:

func random_point_on_ring(outer_radius: float, inner_radius := 0.0):
	return Vector2.RIGHT.rotated(randf() * TAU) * sqrt(rand_range(pow(1 - (outer_radius - inner_radius) / outer_radius, 2), 1)) * outer_radius

@SirLich
Copy link
Contributor

SirLich commented Mar 31, 2024

Updated snippet for Godot 4 (rand_range renamed to randf_range):

func random_point_on_ring(outer_radius: float, inner_radius := 0.0):
	return Vector2.RIGHT.rotated(randf() * TAU) * sqrt(randf_range(pow(1 - (outer_radius - inner_radius) / outer_radius, 2), 1)) * outer_radius

@AThousandShips AThousandShips removed this from the 4.0 milestone Mar 31, 2024
@KoBeWi
Copy link
Member

KoBeWi commented Mar 31, 2024

In Godot 4 you can use Vector2.from_angle() instead of RIGHT.rotated() and ** instead of pow().

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

Successfully merging this pull request may close these issues.

New Vector convenience method - rand Vector2.random and Vector3.random constructors
9 participants