Sphere
There are several ways to programmatically generate a sphere. One way that requires little magic 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 that we can think of as the prime meridian. We rotate it repeatedly around the y-axis to produce all the lines of longitude.
Let's call the positions on the prime meridian 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:
static 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);
// ...
}
}
static 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, we use these rotation formulas to produce xz-coordinates:
Hold on. Recall that the z-coordinate of the seed positions is 0. That means some terms cancel:
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. We can 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.