How would I get a random point on a hemisphere?

Hello!

So I am trying to make a renderer, And I need to make a ray reflect randomly.

So If I can get a random point on a hemisphere, I can use that to get a random direction.

I also want to be able to rotate the hemisphere using Ray.Normal

I don’t know if there is a better way to do this.

Here are some examples of what I want:

The green sphere is the hemisphere, The red lines are the rays, the white ball is the random point on the hemisphere.

Example 2:

Example3

3 Likes

Maybe this will help.

1 Like

No, I want a random point on a hemisphere not a sphere.

1 Like

A Hemisphere is basically Half a Sphere.

1 Like

You mean half a sphere?

I think you can edit the code to make it half a sphere

1 Like

AxisAngle has written a great tutorial about choosing random points on and in circles and spheres.

The code in the tutorial for choosing a random point on a unit sphere is this:

local a = 2*math.pi*math.random()
local x = 2*math.random() - 1
local r = math.sqrt(1 - x*x)
local y, z = r*math.cos(a), r*math.sin(a)

With a simple change to this, we get code that generates a random point on a hemisphere (half a sphere) of a unit sphere (sphere with radius 1). r is the radius of the cross section circle on which the chosen point is.

local a = 2*math.pi*math.random()
local x = math.random()
local r = math.sqrt(1 - x*x)
local y, z = r*math.cos(a), r*math.sin(a)
5 Likes

Yea, but I need to rotate it using Ray.Normal.

So it would work when it hit a wall.

1 Like

Sorry, I didn’t read your whole original post before writing my earlier reply.

CFrame.fromAxisAngle gives a CFrame that is got by rotating the identity CFrame the given amount of radians around the given axis. By changing the axis direction to opposite (which can be done by swapping the vectors in the cross product or by negating the cross product), the rotation direction will be opposite as well. I don’t know which of the two possible axis directions gives correct results so you may need to swap the vectors to get the code working.

The cosine of the angle between two vector a and b is a:Dot(b)/(a.Magnitude*b.Magnitude). In the case of unit vectors (Magnitude 1), this can be simplified to a:Dot(b). When b is the unit up vector (y axis vector) Vector3.new(0, 1, 0), the dot product can be simplified to a.Y. Thus, the angle is acos(a.Y)

Here’s the code.

local function getRandomPointOnUnitHemisphereWhichIsOnSurface(surfaceNormal)
	local cframeRotationAxis = surfaceNormal:Cross(Vector3.yAxis).Unit
	local cframeRotationAngle = math.acos(surfaceNormal.Y) 
	local centerCFrame = CFrame.fromAxisAngle(cframeRotationAxis, cframeRotationAngle)

	local y = math.random() -- distance from the sphere center in the direction of the normal
	local circleRadius = math.sqrt(1 - y*y) -- radius of the chosen cross section circle whose distance from sphere center is y.
	local angleOnCircle = 2*math.pi*math.random() -- angle on the aforementioned circle
	local x, z = circleRadius*math.cos(angleOnCircle), circleRadius*math.sin(angleOnCircle)

	return centerCFrame * Vector3.new(x, y, z)
end
3 Likes

I am getting an error.

invalid argument #1 to 'cos' (number expected, got nil)

And I am having a hard time understanding the code, Can you tell me what the “center” argument should be? is it Ray.Position

Is there any other easier way to do this?

1 Like

I initially forgot to change a to angleOnCircle in math.cos and math.sin. That should be fixed now. And yes, center should be the point where the ray hit the surface. And I don’t know of any easier way to choose the point. Actually, I realized I also forgot to add the position to centerCFrame and I hadn’t made the rotation axis vector a unit vector (the documentation on CFrame.fromAxisAngle implies it should be a unit vector). I’ve fixed these this now.

Edit: I just realized that I was a bit dumb. Since you need a direction and not a world space position on the hemisphere, the sphere center position is unnecessary. I’ve changed the code again.

2 Likes

A really elegant way to generate a random point on a hemisphere is to start by generating a random point, d on a sphere, then taking the direction through the center of the hemisphere, n, and flipping d if d:Dot(n) < 0

local a = 2*math.pi*math.random()
local x = 2*math.random() - 1
local r = math.sqrt(1 - x*x)
local y, z = r*math.cos(a), r*math.sin(a)
local d = Vector3.new(x, y, z)

if d:Dot(n) < 0 then
    d = -d
end
2 Likes

Is there some kind of a problem with the way I did it in my code (like not uniform distribution or some other problem)?

Edit: I understand now. The benefit of your way is that there’s no need for the CFrame stuff. Thanks for mentioning this alternative way. It’s definitely better. But shouldn’t x be 2*math.random()-1 in that code?

2 Likes

Sorry, I forgot about this post.

Can you put this in a function?
Am bad at math.

And how would I rotate the hemisphere to the normal of a part?

Theres a built in function for this. Random.new():NextUnitVector()

@axisangle’s code already works for any normal (n in the code is the normal). You can shorten the code by using the function Random:NextUnitVector() mentioned by @Soliform , though. I wasn’t aware of such a function existing.

The following code combines @AxisAngle’s solution and Random:NextUnitVector().

local randomVectorGenerator = Random.new()

local function getRandomUnitVectorOnHemisphere(surfaceNormal)
    local randomUnitVectorOnSphere = randomVectorGenerator:NextUnitVector()
    local randomUnitVectorOnHemisphere = if randomUnitVectorOnSphere:Dot(surfaceNormal)  >= 0 then randomUnitVectorOnSphere else -randomUnitVectorOnSphere
    return randomUnitVectorOnHemisphere
end

If you didn’t understand the logic behind using dot product to check whether the vector should be flipped, this explanation may help:

The angle between two vectors is in the range [0 degrees, 180 degrees]. 90 degree angle means that they are perpendicular and 180 degree angle means that they have opposite directions.

The dot product is positive when the angle between the normal and the random vector is less than 90 degrees, zero if it’s exactly 90 degrees and negative if it’s more than 90 degrees. The angle being more than 90 degrees means that the random vector points into the surface which is why in this case it’s flipped so that it points out of the surface.

2 Likes

Thank you very much for helping me!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.