/** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ // adapted from three.js, MIT License Copyright 2010-2018 three.js authors import { Vec3 } from 'mol-math/linear-algebra' import { computeIndexedVertexNormals, appplyRadius } from '../util' import { Primitive } from './primitive'; export const DefaultPolyhedronProps = { radius: 1, detail: 0 } export type PolyhedronProps = Partial<typeof DefaultPolyhedronProps> export function Polyhedron(_vertices: ArrayLike<number>, _indices: ArrayLike<number>, props?: PolyhedronProps): Primitive { const { radius, detail } = { ...DefaultPolyhedronProps, ...props } const builder = createBuilder() const { vertices, indices } = builder // the subdivision creates the vertex buffer data subdivide(detail); // all vertices should lie on a conceptual sphere with a given radius appplyRadius(vertices, radius); const normals = new Float32Array(vertices.length); computeIndexedVertexNormals(vertices, indices, normals) return { vertices: new Float32Array(vertices), normals: new Float32Array(normals), indices: new Uint32Array(indices) } // helper functions function subdivide(detail: number) { const a = Vec3.zero() const b = Vec3.zero() const c = Vec3.zero() // iterate over all faces and apply a subdivison with the given detail value for (let i = 0; i < _indices.length; i += 3) { // get the vertices of the face Vec3.fromArray(a, _vertices, _indices[ i + 0 ] * 3) Vec3.fromArray(b, _vertices, _indices[ i + 1 ] * 3) Vec3.fromArray(c, _vertices, _indices[ i + 2 ] * 3) // perform subdivision subdivideFace(a, b, c, detail) } } function subdivideFace(a: Vec3, b: Vec3, c: Vec3, detail: number) { const cols = Math.pow(2, detail) // we use this multidimensional array as a data structure for creating the subdivision const v: Vec3[][] = [] // construct all of the vertices for this subdivision for (let i = 0; i <= cols; ++i) { v[i] = [] const aj = Vec3.zero() Vec3.lerp(aj, a, c, i / cols) const bj = Vec3.zero() Vec3.lerp(bj, b, c, i / cols) const rows = cols - i for (let j = 0; j <= rows; ++j) { if (j === 0 && i === cols) { v[i][j] = aj } else { const abj = Vec3.zero() Vec3.lerp(abj, aj, bj, j / rows) v[i][j] = abj } } } // construct all of the faces for (let i = 0; i < cols; ++i) { for (let j = 0; j < 2 * (cols - i) - 1; ++j) { const k = Math.floor(j / 2) if (j % 2 === 0) { builder.add builder.add(v[i][k + 1], v[i + 1][k], v[i][k]) } else { builder.add(v[i][k + 1], v[i + 1][k + 1], v[i + 1][k]) } } } } } interface Builder { vertices: number[] indices: number[] add: (v1: Vec3, v2: Vec3, v3: Vec3) => void } function createBuilder(): Builder { const vertices: number[] = [] const indices: number[] = [] const vertexMap = new Map<string, number>() function addVertex(v: Vec3) { const key = `${v[0].toFixed(5)}|${v[1].toFixed(5)}|${v[2].toFixed(5)}` let idx = vertexMap.get(key) if (idx === undefined) { idx = vertices.length / 3 vertexMap.set(key, idx) vertices.push(...v) } return idx } return { vertices, indices, add: (v1: Vec3, v2: Vec3, v3: Vec3) => { indices.push(addVertex(v1), addVertex(v2), addVertex(v3)) } } }