| // Copyright ©2022 The Gonum Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package r3_test |
| |
| import ( |
| "fmt" |
| |
| "gonum.org/v1/gonum/spatial/r3" |
| ) |
| |
| func ExampleTriangle_icosphere() { |
| // This example generates a 3D icosphere from |
| // a starting icosahedron by subdividing surfaces. |
| // See https://schneide.blog/2016/07/15/generating-an-icosphere-in-c/. |
| const subdivisions = 5 |
| // vertices is a slice of r3.Vec |
| // triangles is a slice of [3]int indices |
| // referencing the vertices. |
| vertices, triangles := icosahedron() |
| for i := 0; i < subdivisions; i++ { |
| vertices, triangles = subdivide(vertices, triangles) |
| } |
| var faces []r3.Triangle |
| for _, t := range triangles { |
| var face r3.Triangle |
| for i := 0; i < 3; i++ { |
| face[i] = vertices[t[i]] |
| } |
| faces = append(faces, face) |
| } |
| fmt.Println(faces) |
| // The 3D rendering of the icosphere is left as an exercise to the reader. |
| } |
| |
| // edgeIdx represents an edge of the icosahedron |
| type edgeIdx [2]int |
| |
| func subdivide(vertices []r3.Vec, triangles [][3]int) ([]r3.Vec, [][3]int) { |
| // We generate a lookup table of all newly generated vertices so as to not |
| // duplicate new vertices. edgeIdx has lower index first. |
| lookup := make(map[edgeIdx]int) |
| var result [][3]int |
| for _, triangle := range triangles { |
| var mid [3]int |
| for edge := 0; edge < 3; edge++ { |
| lookup, mid[edge], vertices = subdivideEdge(lookup, vertices, triangle[edge], triangle[(edge+1)%3]) |
| } |
| newTriangles := [][3]int{ |
| {triangle[0], mid[0], mid[2]}, |
| {triangle[1], mid[1], mid[0]}, |
| {triangle[2], mid[2], mid[1]}, |
| {mid[0], mid[1], mid[2]}, |
| } |
| result = append(result, newTriangles...) |
| } |
| return vertices, result |
| } |
| |
| // subdivideEdge takes the vertices list and indices first and second which |
| // refer to the edge that will be subdivided. |
| // lookup is a table of all newly generated vertices from |
| // previous calls to subdivideEdge so as to not duplicate vertices. |
| func subdivideEdge(lookup map[edgeIdx]int, vertices []r3.Vec, first, second int) (map[edgeIdx]int, int, []r3.Vec) { |
| key := edgeIdx{first, second} |
| if first > second { |
| // Swap to ensure edgeIdx always has lower index first. |
| key[0], key[1] = key[1], key[0] |
| } |
| vertIdx, vertExists := lookup[key] |
| if !vertExists { |
| // If edge not already subdivided add |
| // new dividing vertex to lookup table. |
| edge0 := vertices[first] |
| edge1 := vertices[second] |
| point := r3.Unit(r3.Add(edge0, edge1)) // vertex at a normalized position. |
| vertices = append(vertices, point) |
| vertIdx = len(vertices) - 1 |
| lookup[key] = vertIdx |
| } |
| return lookup, vertIdx, vertices |
| } |
| |
| // icosahedron returns an icosahedron mesh. |
| func icosahedron() (vertices []r3.Vec, triangles [][3]int) { |
| const ( |
| radiusSqrt = 1.0 // Example designed for unit sphere generation. |
| X = radiusSqrt * .525731112119133606 |
| Z = radiusSqrt * .850650808352039932 |
| N = 0.0 |
| ) |
| return []r3.Vec{ |
| {-X, N, Z}, {X, N, Z}, {-X, N, -Z}, {X, N, -Z}, |
| {N, Z, X}, {N, Z, -X}, {N, -Z, X}, {N, -Z, -X}, |
| {Z, X, N}, {-Z, X, N}, {Z, -X, N}, {-Z, -X, N}, |
| }, [][3]int{ |
| {0, 1, 4}, {0, 4, 9}, {9, 4, 5}, {4, 8, 5}, |
| {4, 1, 8}, {8, 1, 10}, {8, 10, 3}, {5, 8, 3}, |
| {5, 3, 2}, {2, 3, 7}, {7, 3, 10}, {7, 10, 6}, |
| {7, 6, 11}, {11, 6, 0}, {0, 6, 1}, {6, 10, 1}, |
| {9, 11, 0}, {9, 2, 11}, {9, 5, 2}, {7, 11, 2}, |
| } |
| } |