mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 23:26:52 +08:00
105 lines
3.3 KiB
Go
105 lines
3.3 KiB
Go
// 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},
|
|
}
|
|
}
|