gonum.org/v1/gonum@v0.14.0/spatial/r3/icosahedron_example_test.go (about) 1 // Copyright ©2022 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package r3_test 6 7 import ( 8 "fmt" 9 10 "gonum.org/v1/gonum/spatial/r3" 11 ) 12 13 func ExampleTriangle_icosphere() { 14 // This example generates a 3D icosphere from 15 // a starting icosahedron by subdividing surfaces. 16 // See https://schneide.blog/2016/07/15/generating-an-icosphere-in-c/. 17 const subdivisions = 5 18 // vertices is a slice of r3.Vec 19 // triangles is a slice of [3]int indices 20 // referencing the vertices. 21 vertices, triangles := icosahedron() 22 for i := 0; i < subdivisions; i++ { 23 vertices, triangles = subdivide(vertices, triangles) 24 } 25 var faces []r3.Triangle 26 for _, t := range triangles { 27 var face r3.Triangle 28 for i := 0; i < 3; i++ { 29 face[i] = vertices[t[i]] 30 } 31 faces = append(faces, face) 32 } 33 fmt.Println(faces) 34 // The 3D rendering of the icosphere is left as an exercise to the reader. 35 } 36 37 // edgeIdx represents an edge of the icosahedron 38 type edgeIdx [2]int 39 40 func subdivide(vertices []r3.Vec, triangles [][3]int) ([]r3.Vec, [][3]int) { 41 // We generate a lookup table of all newly generated vertices so as to not 42 // duplicate new vertices. edgeIdx has lower index first. 43 lookup := make(map[edgeIdx]int) 44 var result [][3]int 45 for _, triangle := range triangles { 46 var mid [3]int 47 for edge := 0; edge < 3; edge++ { 48 lookup, mid[edge], vertices = subdivideEdge(lookup, vertices, triangle[edge], triangle[(edge+1)%3]) 49 } 50 newTriangles := [][3]int{ 51 {triangle[0], mid[0], mid[2]}, 52 {triangle[1], mid[1], mid[0]}, 53 {triangle[2], mid[2], mid[1]}, 54 {mid[0], mid[1], mid[2]}, 55 } 56 result = append(result, newTriangles...) 57 } 58 return vertices, result 59 } 60 61 // subdivideEdge takes the vertices list and indices first and second which 62 // refer to the edge that will be subdivided. 63 // lookup is a table of all newly generated vertices from 64 // previous calls to subdivideEdge so as to not duplicate vertices. 65 func subdivideEdge(lookup map[edgeIdx]int, vertices []r3.Vec, first, second int) (map[edgeIdx]int, int, []r3.Vec) { 66 key := edgeIdx{first, second} 67 if first > second { 68 // Swap to ensure edgeIdx always has lower index first. 69 key[0], key[1] = key[1], key[0] 70 } 71 vertIdx, vertExists := lookup[key] 72 if !vertExists { 73 // If edge not already subdivided add 74 // new dividing vertex to lookup table. 75 edge0 := vertices[first] 76 edge1 := vertices[second] 77 point := r3.Unit(r3.Add(edge0, edge1)) // vertex at a normalized position. 78 vertices = append(vertices, point) 79 vertIdx = len(vertices) - 1 80 lookup[key] = vertIdx 81 } 82 return lookup, vertIdx, vertices 83 } 84 85 // icosahedron returns an icosahedron mesh. 86 func icosahedron() (vertices []r3.Vec, triangles [][3]int) { 87 const ( 88 radiusSqrt = 1.0 // Example designed for unit sphere generation. 89 X = radiusSqrt * .525731112119133606 90 Z = radiusSqrt * .850650808352039932 91 N = 0.0 92 ) 93 return []r3.Vec{ 94 {-X, N, Z}, {X, N, Z}, {-X, N, -Z}, {X, N, -Z}, 95 {N, Z, X}, {N, Z, -X}, {N, -Z, X}, {N, -Z, -X}, 96 {Z, X, N}, {-Z, X, N}, {Z, -X, N}, {-Z, -X, N}, 97 }, [][3]int{ 98 {0, 1, 4}, {0, 4, 9}, {9, 4, 5}, {4, 8, 5}, 99 {4, 1, 8}, {8, 1, 10}, {8, 10, 3}, {5, 8, 3}, 100 {5, 3, 2}, {2, 3, 7}, {7, 3, 10}, {7, 10, 6}, 101 {7, 6, 11}, {11, 6, 0}, {0, 6, 1}, {6, 10, 1}, 102 {9, 11, 0}, {9, 2, 11}, {9, 5, 2}, {7, 11, 2}, 103 } 104 }