Deprecated: Creation of dynamic property wpdb::$categories is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$post2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$link2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Return type of WP_Theme::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 554

Deprecated: Return type of WP_Theme::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 595

Deprecated: Return type of WP_Theme::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 535

Deprecated: Return type of WP_Theme::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 544

Deprecated: Return type of WP_REST_Request::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 960

Deprecated: Return type of WP_REST_Request::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 980

Deprecated: Return type of WP_REST_Request::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 992

Deprecated: Return type of WP_REST_Request::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 1003

Deprecated: Return type of WP_Block_List::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 151

Deprecated: Return type of WP_Block_List::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 175

Deprecated: Return type of WP_Block_List::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 164

Deprecated: Return type of WP_Block_List::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 186

Deprecated: Return type of WP_Block_List::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 138

Deprecated: Return type of WP_Block_List::offsetExists($index) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 75

Deprecated: Return type of WP_Block_List::offsetGet($index) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 89

Deprecated: Return type of WP_Block_List::offsetSet($index, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 110

Deprecated: Return type of WP_Block_List::offsetUnset($index) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 127

Deprecated: Return type of WP_Block_List::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 199

Deprecated: Creation of dynamic property Automattic\Jetpack\Connection\Manager::$error_handler is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-connection/src/class-manager.php on line 91

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Instagram_Gallery::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-instagram-gallery.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Mailchimp::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php on line 17

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 21

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 22

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Gutenberg_Available_Extensions::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php on line 20

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 24

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Subscribers::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php on line 15

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property WPCOM_Widget_Goodreads::$shelves is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/modules/widgets/goodreads.php on line 33

Deprecated: DateTime::__construct(): Passing null to parameter #1 ($datetime) of type string is deprecated in /var/www/twodee/blog/wp-includes/script-loader.php on line 348

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_classic_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 347

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 349

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block_side is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 350

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$panels_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 351

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$used_block_buttons is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 354

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198
» CS 488: Lecture 7 – Geometric Modeling

teaching machines

CS 488: Lecture 7 – Geometric Modeling

February 15, 2021 by . Filed under
Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383
graphics-3d, lectures, spring-2021.

Dear students:

Our goal today is to generate a few models using code. The algorithmic description of shapes is sometimes called geometric modeling. Not all models can be easily generated via algorithms, and we are likely to build most of our models in a 3D modeling program rather than in code. However, programmatically generating shapes is instructive. It helps us develop an intimacy with the structure of a model. Today we will become intimate with a plane, a sphere, and a cylinder.

Plane

We’ve seen how to model a single quadrilateral with two separate triangles or four indexed vertices. Quadrilaterals are useful for modeling walls or floors. But what if we want a surface that isn’t so flat? We need to decimate that quadrilateral into a quilt of quadrilaterals. We’ll call this shape a plane. A mathematical plane goes on forever. Our plane will be bounded.

The table or grid of points of the plane can be generated with a couple of loops:

function generatePlane(width, height, nlatitudes, nlongitudes)
  positions = []

  for ilongitude through nlongitudes
    x = ilongitude / (nlongitudes - 1) * width - width * 0.5
    for ilatitude through nlatitudes
      y = ilatitude / (nlatitudes - 1) * height - height * 0.5
      positions.push(x, y, 0)

  return {positions}
}

When we upload these to the GPU and render them as points, we see the intersection points of our grid. To fill in the pixels between these intersections, we have two choices: enumerate separate positions for each triangle, or use indexed geometry. Given that a single vertex may be shared by as many as six triangles, indexed geometry is a better choice.

Let’s externalize our thinking about this grid with this diagram:

Given our algorithm above, what is the index of the vertex whose ilatitude is 0 and ilongitude is 0? That’s vertex 0. How about the one whose ilatitude is 1 and ilongitude is 0? That’s vertex 1. How about the one whose ilatitude is 1 and whose ilongitude is 0? That’s vertex 4.

In general, we can determine the index through this formula:

index = ilongitude * nlatitudes + ilatitude

Here’s the complete list of indices:

We iterate through this table using a fencepost algorithm and issue index-triplets.

function generatePlane(width, height, nlatitudes, nlongitudes)
  // generate positions

  indices = []
  for ilongitude to nlongitudes - 1
    iNextLongitude = ilongitude + 1

    for ilatitude to nlatitudes - 1
      iNextLatitude = ilatitude + 1

      // Bottom-left triangle
      indices.push([
        ilongitude * nlatitudes + ilatitude,
        iNextLongitude * nlatitudes + ilatitude,
        ilongitude * nlatitudes + iNextLatitude,
      ])

      // Top-right triangle
      indices.push([
        iNextLongitude * nlatitudes + ilatitude,
        iNextLongitude * nlatitudes + iNextLatitude,
        ilongitude * nlatitudes + iNextLatitude,
      ])

  return {positions, indices}

When we render this, our grid is nicely filled. But it doesn’t look any different than a simple quadrilateral. We add some extra details to the internal vertices, like random colors:

function generatePlane(width, height, nlatitudes, nlongitudes)
  positions = []
  colors = []

  for ilongitude in nlongitudes
    x = ilongitude / nlongitudes * width - width * 0.5
    for ilatitude in nlatitudes
      y = ilatitude / nlatitudes * height - height * 0.5
      positions.push(x, y, 0)
      colors.push(random(), random(), random())

  // generate faces

  return {positions, colors, faces}

Perturbing the positions is also possible. That’s how we’d generate terrain, which seems to be a rite of passage for every budding graphics developer.

To shade the plane, we need a normal at each vertex. Since the plane is flat, the normals all point the same direction. It feels silly to make this uniform normal an attribute, but we do it anyway, to prepare for less flat geometry:

function generatePlane(width, height, nlatitudes, nlongitudes)
  positions = []
  colors = []
  normals = []

  for ilongitude in nlongitudes
    x = ilongitude / nlongitudes * width - width * 0.5
    for ilatitude in nlatitudes
      y = ilatitude / nlatitudes * height - height * 0.5
      positions.push(x, y, 0)
      colors.push(random(), random(), random())
      normals.push(0, 0, 1)

  // generate faces

  return {positions, colors, normals, faces}

Sphere

Generating a sphere is surprisingly like generating a plane. They are shaped very differently, of course, but their faces are not connected differently. We can use very similar code to generate the indices. We start with the positions.

To calculate the positions, we could look up the parametric equations for a sphere, but we’re going to take a different approach that I think feels less like magic. We first sample the right half of a circle in the z = 0 plane, starting at -90 degrees at the bottom of the circle and working our way up to 90 degrees at the top. This gives us a set of seed points at various latitudes:

function generateSphere(nlatitudes, nlongitudes)
  seeds = []
  for ilatitude to nlatitudes
    radians = ilatitude / (nlatitudes - 1) * pi - pi / 2
    seeds.append([cos radians, sin radians, 0])
  // ...

Then we spin these seed points around the y-axis to produce lines of longitude:

function generateSphere(nlatitudes, nlongitudes)
  // generate seeds

  positions = []
  normals = []
  for ilongitude to nlongitudes
    degrees = ilongitude / nlongitudes * 360
    rotation = Matrix4.rotateY(degrees)
    for ilatitude to nlatitudes
      position = rotation * seeds[ilatitude]
      positions.push(position)
      normals.push(position)

  return positions

When we render these positions as points, we see what looks like a globe. We can connect these points together exactly as we did for the plane:

function generateSphere(nlatitudes, nlongitudes)
  // generate positions

  indices = []
  for ilongitude to nlongitudes - 1
    iNextLongitude = ilongitude + 1

    for ilatitude to nlatitudes - 1
      iNextLatitude = ilatitude + 1

      // Bottom-left triangle
      indices.push([
        ilongitude * nlatitudes + ilatitude,
        ilongitude * nlatitudes + iNextLatitude,
        iNextLongitude * nlatitudes + ilatitude,
      ])

      // Top-right triangle
      indices.push([
        ilongitude * nlatitudes + iNextLatitude,
        iNextLongitude * nlatitudes + iNextLatitude,
        iNextLongitude * nlatitudes + ilatitude,
      ])

  return {positions, indices}

However, we want the last line of longitude to wrap around connect back to the first. A little modulus can help make that happen:

for ilongitude to nlongitudes
  iNextLongitude = (ilongitude + 1) % nlongitudes
  ...

We add these positions and indices to our vertex buffer objects and we have a filled sphere.

Cylinder

A plane is flat. A sphere is round. We now generate a shape that’s both round and flat: a cylinder. Cylinders exhibit the same connectivity as planes and spheres—save for the end caps. Let’s start by generating their positions with a little trigonometry. We’ll walk around the circle again:

function generateCylinder(radius, height, nlatitudes, nlongitudes)
  for ilongitude to nlongitudes
    radians = ilongitude / nlongitudes * 2 * pi
    x = radius * cos radians
    z = radius * sin radians

As with the plane and sphere, we walk along each line of longitude at regular latitudes:

function generateCylinder(radius, height, nlatitudes, nlongitudes)
  positions = []
  for ilongitude to nlongitudes
    radians = ilongitude / nlongitudes * 2 * pi
    x = radius * cos radians
    z = radius * sin radians
    for ilatitude to nlatitudes
      y = ilatitude / (nlatitudes - 1) * height - 0.5 * height
      positions.push(x, y, z)
      normals.push(cos radians, 0, sin radians) 

  // generate faces

  return {positions, normals, indices}

As with the sphere, the normals point outward. We zero out the y-coordinate since the normals don’t point up or down as we traverse up the cylinder.

To add end caps, we issue vertices independent of the cylinder wall. The vertices on the end caps have different normals than the ones on the wall, just as we saw with the vertices of the cube. We add the end caps one at a time, sampling the perimeter of each circle. To triangulate this circle, we include a vertex in the middle of the cap. The triangles all converge on this center point like slices of pizza:

function generateCylinder(radius, height, nlatitudes, nlongitudes)
  // generate wall positions
  // generate wall faces

  // Generate top cap.
  icenter = positions.length / 3
  positions.push(0, height * 0.5, 0)
  for ilongitude to nlongitudes
    radians = ilongitude / nlongitudes * 2 * pi
    x = radius * cos radians
    z = radius * sin radians
    positions.push(x, height * 0.5, z)
    indices.push(icenter, icenter + ilongitude, icenter + (ilongitude + 1) % nlongitudes)

  // Generate bottom cap.
  icenter = positions.length / 3
  for ilongitude to nlongitudes
    radians = ilongitude / nlongitudes * 2 * pi
    x = radius * cos radians
    z = radius * sin radians
    positions.push(x, -height * 0.5, z)
    indices.push(icenter, icenter + ilongitude, icenter + (ilongitude + 1) % nlongitudes)

  return {positions, normals, indices}

Horizon

We’ve picked off shapes whose normals are relatively easy to calculate. Next time we’ll look at how to compute normals for arbitrary shapes using just the geometry itself. This will give us the power to render non-algorithmic models.

TODO

Here’s your TODO list:

See you next time.

Sincerely,

P.S. It’s time for a haiku!

Vote for the best math
Geometry beats them all
‘Cuz the eyes have it