Sphere

How to 3D

Chapter 3: Meshes

Sphere

There are several ways to programmatically generate a sphere. One less magical way is to generate positions on a semicircle that goes from the sphere's south pole to its north pole. This semicircle forms a line of longitude. We rotate it repeatedly around the y-axis to produce all the lines of longitude.

Let's call the positions on the semicircle the seed positions. We calculate them by iterating between the poles, which are at -90 and 90 degrees of latitude, or \(-\frac{\pi}{2}\) and \(\frac{\pi}{2}\) radians. To compute the intermediate angles, we use lerp with the proportionalized latitude. These angles and the sphere's radius form polar coordinates, which we convert to Cartesian coordinates:

export class Prefab {
  sphere(radius: number, longitudeCount: number, latitudeCount: number) {
    const positions: Vector3[] = [];

    for (let lat = 0; lat < latitudeCount; ++lat) {
      // First find the position on the prime meridian.
      const latRadians = lerp(-Math.PI * 0.5, Math.PI * 0.5, lat / (latitudeCount - 1));
      let x = radius * Math.cos(latRadians);
      let y = radius * Math.sin(latRadians);

      // ...
    }
  }
}
export class Prefab {
  sphere(radius: number, longitudeCount: number, latitudeCount: number) {
    const positions: Vector3[] = [];

    for (let lat = 0; lat < latitudeCount; ++lat) {
      // First find the position on the prime meridian.
      const latRadians = lerp(-Math.PI * 0.5, Math.PI * 0.5, lat / (latitudeCount - 1));
      let x = radius * Math.cos(latRadians);
      let y = radius * Math.sin(latRadians);

      // ...
    }
  }
}

The z-coordinate of these seed positions is implicitly 0. When we rotate this semicircle, the positions will break out of this plane. But how do we rotate them? By using the equations we examined last chapter. Since we're rotating around the y-axis, the rotation formulas produce xz-coordinates:

$$\begin{aligned} x' &= x \cos b - z \sin b \\ z' &= x \sin b + z \cos b \end{aligned}$$

Hold on. Recall that the z-coordinate of the seed positions is 0. That means some terms cancel:

$$\begin{aligned} x' &= x \cos b \\ z' &= x \sin b \end{aligned}$$

Angle \(b\) is the proportionalized longitude applied to \(2 \pi\). To produce many rotated versions of the semicircle, we iterate through the longitude indices. On each iteration, we emit the rotated seed position:

export class Prefab {
  sphere(radius: number, longitudeCount: number, latitudeCount: number) {
    const positions: Vector3[] = [];

    for (let lat = 0; lat < latitudeCount; ++lat) {
      // ...

      for (let lon = 0; lon < longitudeCount; ++lon) {
        const lonRadians = lon / longitudeCount * -2 * Math.PI;
        positions.push(new Vector3(
          x * Math.cos(lonRadians),
          y,
          x * Math.sin(lonRadians)
        ));
      }
    }
  }
}
export class Prefab {
  sphere(radius: number, longitudeCount: number, latitudeCount: number) {
    const positions: Vector3[] = [];

    for (let lat = 0; lat < latitudeCount; ++lat) {
      // ...

      for (let lon = 0; lon < longitudeCount; ++lon) {
        const lonRadians = lon / longitudeCount * -2 * Math.PI;
        positions.push(new Vector3(
          x * Math.cos(lonRadians),
          y,
          x * Math.sin(lonRadians)
        ));
      }
    }
  }
}

The code to generate the triangle indices is identical to the code we wrote for cylinders. And that rounds out our library of prefabricated models. This idea of molding a grid into a surface is flexible; use it to produce many other surfaces of revolution or extrusion.

Seeing our own procedurally generated shapes is gratifying, but discerning their shape is difficult when they are flatly shaded. Let's fix this by taking a first step toward lighting.

← ConeCross Product →