1
votes

I am trying to get random X Y Z coordinates on the surface of a sphere, based on the spheres center in X Y Z, and it's radius in PHP. Currently, my system is just purely random, hoping for a position on the planets center. I'm inept in math so please bear with me (dyscalculia and dyslexia).

Right now I have just random chaos as follows (note randint is a custom function that checks for rand_int, mt_rand and rand and uses the newest.

            $nx = randint( abs( $poo['x'] - $max_distance_x ), abs( $poo['x'] + $max_distance_x ) );
            $ny = randint( abs( $poo['y'] - $max_distance_y ), abs( $poo['y'] + $max_distance_y ) );
            $nz = randint( abs( $poo['z'] - $max_distance_z ), abs( $poo['z'] + $max_distance_z ) );

but I have the planet radius $pradius, and planet XYZ center array $poo poo(x, y, z), and I think I can get random surface X Y Z, just not sure. I've looked at other languages but am having trouble understanding anything to port.

Update

Based on the two methods provided in an answer I have come up with the following two functions. However both to not function correctly.

The following function only produces craters (points) on the top of the planetoid (north pole), even though it should be calculating from planetoid center at it's radius.

function basic_spheroid_point( $cx, $cy, $cz, $r ) {

    $x = randint(-$r, $r);
    $y = randint(-$r, $r);
    $z = randint(-$r, $r);

    $dx = $x - $cx;
    $dy = $y - $cy;
    $dz = $z - $cz;

    $dd = sqrt( ( $dx * $dx ) + ( $dy * $dy ) + ( $dz * $dz ) );

    if ( $dd > $r )

        return basic_spheroid_point( $cx, $cy, $cz, $r );

    $dx = $r * $dx / $dd;
    $dy = $r * $dy / $dd;
    $dz = $r * $dz / $dd;

    return array( $cx + $dx, $cy + $dy, $cz + $dz );

}

This function bunches craters at the North Pole of the planet, though there is also global coverage craters so it is working partially.

function uniform_spheroid_point($cx, $cy, $cz, $r) {

    $ap = randint( 0, 359 );
    $aq = randint( 0, 359 );

    $dx = $r* cos( $ap ) * cos( $aq );
    $dy = $r * sin( $aq );
    $dz = $r * sin( $ap ) * cos( $aq );

    return array( $cx + $dx, $cy + $dy, $cz + $dz );

 } 
1
Getting random points on the surface is easy; getting random points uniformly distributed on the surface (i.e. any spot is as likely to be hit as any other) is tricky. Do you care about that?Beta
That is somewhat of a concern, actually, yes. As with my current setup, even being just chaos, it does seem to favor patterns and you get a lot of "bunches". This is ultimately to be used for a "crater" generator. While some do overlap it shouldn't look like a missile barrage in certain areas heh. The reason I need a surface XYZ is because the craters in the program this is generating for uses Y to sit craters on the surface, where other shaders are already anchored and only deal in X and Z.WASasquatch

1 Answers

1
votes

You're currently selecting a point at random from the interior of a parallelepiped with side lengths 2*max_distance_x, 2*max_distance_y and 2*max_distance_z. Let us assume that these are all the same and equivalent to the radius r of the sphere on whose surface you'd like to randomly select points. Then, your method would select randomly from a cube of side length 2r. Essentially none of your random points will actually be on the surface of the sphere of radius r when selected in this way.

However - given a point in the cube, what you can do is this:

  1. calculate the vector from the center point to your random point (the "delta"). For example, if your center point is 100,100,100 and your radius is 100 and you randomly pick point 50,100,150, the vector you're looking for is -50,0,50.

  2. calculate the length of the vector from step 1. This uses the formula for the distance between two points, extended so that it contemplates the point's three coordinates: sqrt(dx^2 + dy^2 +dz^2). For example, our distance is sqrt((-50)^2 + 0^2 + 50^2) = sqrt(2500 + 0 + 2500) = 50sqrt(2).

  3. scale the vector from step 1 by a factor equal to r/d where d is the vector's length as determined in step 2. For our example, we will scale the vector by a factor of r/d = 100/(50sqrt(2)) = 2/sqrt(2) = sqrt(2). This gives -50sqrt(2), 0, 50sqrt(2).

  4. Now, add the scaled vector from step 3 to the center point to get a point on the surface of the sphere of radius r. In our example, the point on the surface of the sphere is 100-50sqrt(2), 100, 100+50sqrt(2).

Now the only issue with this is that some points are more likely to be chosen than others. The reason for this is that some points on the surface of the sphere have more of the cube outside them than other points do. Specifically, a point of the sphere lying on the bounding cube has no points further outside it, but the point on the sphere that intersects a line connecting the center of the cube and one of its corners has a lot of space outside the sphere). To get a truly uniform distribution of points, you'd need to exclude any points selected at random which do not lie inside or on the surface of the sphere. If you do that, you will get a uniform distribution of points by the above method. Because the volume of the cube is 8r^3 and the volume of the sphere is 4/3pir^3, and because 4/3pi ~ 4, you have about a 50% chance at each draw of getting a point that you have to throw away. On average, you'd expect to get one good point in every two draws. You should not typically need many random draws to get a good one, but it is technically unbounded.

If you want to make sure every random draw is a good one, I might suggest selecting two angles uniformly at random from 0 to 360 degrees. Then, use those angles to identify a point on the sphere. So, for instance, suppose you first draw angle p and then angle q. Angle p can determine the plain from which the point will be taken. This plane will intersect the sphere in a circular cross section. Angle q can then determine which point on this intersected circle is returned as the random point. Suppose these angles give point (x', y', z'). Well...

y' = r*sin(q) … since nothing else determines the y coordinate except q
x' = r*cos(p)*cos(q)
z' = r*sin(p)*cos(q)

This has the advantage of not needing to reject any random samples, and the disadvantage of requiring relatively more costly trigonometric operations.

EDIT: Pseudocode for each method

Method 1:

RandomPointOnSphere(centerX, centerY, centerZ, radius)
 1. x = random(-radius, radius)
 2. y = random(-radius, radius)
 3. z = random(-radius, radius)
 4. dx = x - centerX
 5. dy = y - centerY
 6. dz = z - centerZ
 7. dd = sqrt(dx*dx + dy*dy + dz*dz)
 8. if dd > radius then return RandomPointOnSphere(centerX, centerY, centerZ, radius)
 9. dx = radius * dx / dd
10. dy = radius * dy / dd
11. dz = radius * dz / dd
12. return (centerX + dx, centerY + dy, centerZ + dz)

Method 2:

RandomPointOnSphere(centerX, centerY, centerZ, radius)
 1. angleP = random(0, 359)
 2. angleQ = random(0, 359)
 3. dx = radius* cos(angleP) * cos(angleQ)
 4. dy = radius * sin(angleQ)
 5. dz = radius * sin(angleP) * cos(angleQ)
 6. return (centerX + dx, centerY + dy, centerZ + dz)