polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
271 lines (207 loc) • 7.05 kB
text/typescript
import {BufferGeometry} from 'three/src/core/BufferGeometry';
import {Float32BufferAttribute} from 'three/src/core/BufferAttribute';
import {Vector3} from 'three/src/math/Vector3';
// import {Vector2} from 'three/src/math/Vector2';
interface PolyHedronBufferGeometryParameters {
vertices: number[];
indices: number[];
radius: number;
detail: number;
}
// Angle around the Y axis, counter-clockwise when looking from above.
function azimuth(vector: Vector3) {
return Math.atan2(vector.z, -vector.x);
}
// Angle above the XZ plane.
function inclination(vector: Vector3) {
return Math.atan2(-vector.y, Math.sqrt(vector.x * vector.x + vector.z * vector.z));
}
// from three/src/geometries/PolyhedronGeometry
export class PolyhedronBufferGeometry extends BufferGeometry {
parameters: PolyHedronBufferGeometryParameters;
constructor(vertices: number[], indices: number[], radius: number, detail: number, points_only: boolean) {
super();
this.type = 'PolyhedronBufferGeometry';
this.parameters = {
vertices: vertices,
indices: indices,
radius: radius,
detail: detail,
};
radius = radius || 1;
detail = detail || 0;
// default buffer data
const vertexBuffer: number[] = [];
const uvBuffer: number[] = [];
const vertices_by_pos: Map<number, Map<number, Set<number>>> = new Map();
// the subdivision creates the vertex buffer data
subdivide(detail);
// all vertices should lie on a conceptual sphere with a given radius
applyRadius(radius);
// finally, create the uv data
generateUVs();
// build non-indexed geometry
this.setAttribute('position', new Float32BufferAttribute(vertexBuffer, 3));
this.setAttribute('uv', new Float32BufferAttribute(uvBuffer, 2));
if (!points_only) {
this.setAttribute('normal', new Float32BufferAttribute(vertexBuffer.slice(), 3));
if (detail === 0) {
this.computeVertexNormals(); // flat normals
} else {
this.normalizeNormals(); // smooth normals
}
}
// helper functions
function subdivide(detail: number) {
const a = new Vector3();
const b = new Vector3();
const c = new Vector3();
// 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
getVertexByIndex(indices[i + 0], a);
getVertexByIndex(indices[i + 1], b);
getVertexByIndex(indices[i + 2], c);
// perform subdivision
subdivideFace(a, b, c, detail);
}
}
function subdivideFace(a: Vector3, b: Vector3, c: Vector3, detail: number) {
const cols = detail + 1;
// we use this multidimensional array as a data structure for creating the subdivision
const v: Vector3[][] = [];
// construct all of the vertices for this subdivision
for (let i = 0; i <= cols; i++) {
v[i] = [];
const aj = a.clone().lerp(c, i / cols);
const bj = b.clone().lerp(c, i / cols);
const rows = cols - i;
for (let j = 0; j <= rows; j++) {
if (j === 0 && i === cols) {
v[i][j] = aj;
} else {
v[i][j] = aj.clone().lerp(bj, j / rows);
}
}
}
// 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) {
pushVertex(v[i][k + 1]);
pushVertex(v[i + 1][k]);
pushVertex(v[i][k]);
} else {
pushVertex(v[i][k + 1]);
pushVertex(v[i + 1][k + 1]);
pushVertex(v[i + 1][k]);
}
}
}
}
function applyRadius(radius: number) {
const vertex = new Vector3();
// iterate over the entire buffer and apply the radius to each vertex
for (let i = 0; i < vertexBuffer.length; i += 3) {
vertex.x = vertexBuffer[i + 0];
vertex.y = vertexBuffer[i + 1];
vertex.z = vertexBuffer[i + 2];
vertex.normalize().multiplyScalar(radius);
vertexBuffer[i + 0] = vertex.x;
vertexBuffer[i + 1] = vertex.y;
vertexBuffer[i + 2] = vertex.z;
}
}
function generateUVs() {
const vertex = new Vector3();
for (let i = 0; i < vertexBuffer.length; i += 3) {
vertex.x = vertexBuffer[i + 0];
vertex.y = vertexBuffer[i + 1];
vertex.z = vertexBuffer[i + 2];
const u = azimuth(vertex) / 2 / Math.PI + 0.5;
const v = inclination(vertex) / Math.PI + 0.5;
uvBuffer.push(u, 1 - v);
}
// correctUVs and correctSeam are currently not used
// as they seem to create incorrect uvs when using points only
// correctUVs();
// correctSeam();
}
// function correctSeam() {
// // handle case when face straddles the seam, see #3269
// for (let i = 0; i < uvBuffer.length; i += 6) {
// // uv data of a single face
// const x0 = uvBuffer[i + 0];
// const x1 = uvBuffer[i + 2];
// const x2 = uvBuffer[i + 4];
// const max = Math.max(x0, x1, x2);
// const min = Math.min(x0, x1, x2);
// // 0.9 is somewhat arbitrary
// if (max > 0.9 && min < 0.1) {
// if (x0 < 0.2) uvBuffer[i + 0] += 1;
// if (x1 < 0.2) uvBuffer[i + 2] += 1;
// if (x2 < 0.2) uvBuffer[i + 4] += 1;
// }
// }
// }
function pushVertex(vertex: Vector3) {
if (points_only) {
let mx = vertices_by_pos.get(vertex.x);
if (mx) {
const my = mx.get(vertex.y);
if (my && my.has(vertex.z)) {
return;
}
}
if (!mx) {
mx = new Map();
vertices_by_pos.set(vertex.x, mx);
}
let my = mx.get(vertex.y);
if (!my) {
my = new Set();
mx.set(vertex.y, my);
}
my.add(vertex.z);
}
vertexBuffer.push(vertex.x, vertex.y, vertex.z);
}
function getVertexByIndex(index: number, vertex: Vector3) {
const stride = index * 3;
vertex.x = vertices[stride + 0];
vertex.y = vertices[stride + 1];
vertex.z = vertices[stride + 2];
}
// function correctUVs() {
// const a = new Vector3();
// const b = new Vector3();
// const c = new Vector3();
// const centroid = new Vector3();
// const uvA = new Vector2();
// const uvB = new Vector2();
// const uvC = new Vector2();
// for (let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6) {
// a.set(vertexBuffer[i + 0], vertexBuffer[i + 1], vertexBuffer[i + 2]);
// b.set(vertexBuffer[i + 3], vertexBuffer[i + 4], vertexBuffer[i + 5]);
// c.set(vertexBuffer[i + 6], vertexBuffer[i + 7], vertexBuffer[i + 8]);
// uvA.set(uvBuffer[j + 0], uvBuffer[j + 1]);
// uvB.set(uvBuffer[j + 2], uvBuffer[j + 3]);
// uvC.set(uvBuffer[j + 4], uvBuffer[j + 5]);
// centroid.copy(a).add(b).add(c).divideScalar(3);
// const azi = azimuth(centroid);
// correctUV(uvA, j + 0, a, azi);
// correctUV(uvB, j + 2, b, azi);
// correctUV(uvC, j + 4, c, azi);
// }
// }
// function correctUV(uv: Vector2, stride: number, vector: Vector3, azimuth: number) {
// if (azimuth < 0 && uv.x === 1) {
// uvBuffer[stride] = uv.x - 1;
// }
// if (vector.x === 0 && vector.z === 0) {
// uvBuffer[stride] = azimuth / 2 / Math.PI + 0.5;
// }
// }
}
}