@sveltejs/gl
Version:
A (very experimental) project to bring WebGL to Svelte
236 lines (184 loc) • 6.12 kB
JavaScript
import Geometry from './Geometry.mjs';
import { memoize, normalize } from '../internal/utils.mjs';
// adapted from https://github.com/mrdoob/three.js/blob/master/src/geometries/PolyhedronGeometry.js
// MIT licensed https://github.com/mrdoob/three.js/blob/dev/LICENSE
function lerp(a, b, t) {
return a.map((aa, i) => {
const bb = b[i];
return aa + (bb - aa) * t;
});
}
function set2(vector, a, b) {
vector[0] = a;
vector[1] = b;
}
function set3(vector, a, b, c) {
vector[0] = a;
vector[1] = b;
vector[2] = c;
}
function correct_uvs(vertex_buffer, uv_buffer) {
const a = new Float32Array(3);
const b = new Float32Array(3);
const c = new Float32Array(3);
const centroid = new Float32Array(3);
const uv_a = new Float32Array(2);
const uv_b = new Float32Array(2);
const uv_c = new Float32Array(2);
for (let i = 0, j = 0; i < vertex_buffer.length; i += 9, j += 6) {
set3(a, vertex_buffer[i + 0], vertex_buffer[i + 1], vertex_buffer[i + 2]);
set3(b, vertex_buffer[i + 3], vertex_buffer[i + 4], vertex_buffer[i + 5]);
set3(c, vertex_buffer[i + 6], vertex_buffer[i + 7], vertex_buffer[i + 8]);
set2(uv_a, uv_buffer[j + 0], uv_buffer[j + 1]);
set2(uv_b, uv_buffer[j + 2], uv_buffer[j + 3]);
set2(uv_c, uv_buffer[j + 4], uv_buffer[j + 5]);
centroid[0] = (a[0] + b[0] + c[0]) / 3;
centroid[1] = (a[1] + b[1] + c[1]) / 3;
centroid[2] = (a[2] + b[2] + c[2]) / 3;
const azi = azimuth(centroid);
correct_uv(uv_buffer, uv_a, j + 0, a, azi);
correct_uv(uv_buffer, uv_b, j + 2, b, azi);
correct_uv(uv_buffer, uv_c, j + 4, c, azi);
}
}
function correct_uv(uv_buffer, uv, stride, vector, azimuth) {
if ((azimuth < 0) && (uv[0] === 1)) {
uv_buffer[stride] = uv[0] - 1;
}
if ((vector[0] === 0) && (vector[2] === 0)) {
uv_buffer[stride] = azimuth / 2 / Math.PI + 0.5;
}
}
function correct_seam(uv_buffer) {
// handle case when face straddles the seam
for (var i = 0; i < uv_buffer.length; i += 6) {
// uv data of a single face
var x0 = uv_buffer[i + 0];
var x1 = uv_buffer[i + 2];
var x2 = uv_buffer[i + 4];
var max = Math.max(x0, x1, x2);
var min = Math.min(x0, x1, x2);
// 0.9 is somewhat arbitrary
if (max > 0.9 && min < 0.1) {
if (x0 < 0.2) uv_buffer[i + 0] += 1;
if (x1 < 0.2) uv_buffer[i + 2] += 1;
if (x2 < 0.2) uv_buffer[i + 4] += 1;
}
}
}
// Angle around the Y axis, counter-clockwise when looking from above.
function azimuth(vector) {
return Math.atan2(vector[2], - vector[0]);
}
// Angle above the XZ plane.
function inclination(vector) {
return Math.atan2(-vector[1], Math.sqrt((vector[0] * vector[0]) + (vector[2] * vector[2])));
}
function compute_vertex_normals(position) {
const cb = new Float32Array(3);
const ab = new Float32Array(3);
const normals = new Float32Array(position.length);
for (let i = 0; i < position.length; i += 9 ) {
const pa = position.subarray(i + 0, i + 3);
const pb = position.subarray(i + 3, i + 6);
const pc = position.subarray(i + 6, i + 9);
set3(cb, pc[0] - pb[0], pc[1] - pb[1], pc[2] - pb[2]);
set3(ab, pa[0] - pb[0], pa[1] - pb[1], pa[2] - pb[2]);
// cb x ab
const x = cb[1] * ab[2] - cb[2] * ab[1];
const y = cb[2] * ab[0] - cb[0] * ab[2];
const z = cb[0] * ab[1] - cb[1] * ab[0];
normals[i + 0] = normals[i + 3] = normals[i + 6] = x;
normals[i + 1] = normals[i + 4] = normals[i + 7] = y;
normals[i + 2] = normals[i + 5] = normals[i + 8] = z;
}
return normals;
}
function create_vertex_buffer(vertices, indices, subdivisions) {
const vertex_buffer = [];
const a = new Float32Array(3);
const b = new Float32Array(3);
const c = new Float32Array(3);
for (let i = 0; i < indices.length; i += 3) {
// get the vertices of the face
get_vertex_data(indices[i + 0], a);
get_vertex_data(indices[i + 1], b);
get_vertex_data(indices[i + 2], c);
// perform subdivision
subdivide_face(a, b, c, subdivisions);
}
function get_vertex_data(index, out) {
const offset = index * 3;
out[0] = vertices[offset + 0];
out[1] = vertices[offset + 1];
out[2] = vertices[offset + 2];
}
function push_vertex(vertex) {
vertex_buffer.push(vertex[0], vertex[1], vertex[2]);
}
function subdivide_face(a, b, c, subdivisions) {
const cols = Math.pow(2, subdivisions);
// we use this multidimensional array as a data structure for creating the subdivision
const v = [];
// construct all of the vertices for this subdivision
for (let i = 0; i <= cols; i++) {
v[i] = [];
const aj = lerp(a, c, i / cols);
const bj = lerp(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 {
v[i][j] = lerp(aj, 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) {
push_vertex(v[i][k + 1]);
push_vertex(v[i + 1][k]);
push_vertex(v[i][k]);
} else {
push_vertex(v[i][k + 1]);
push_vertex(v[i + 1][k + 1]);
push_vertex(v[i + 1][k]);
}
}
}
}
return new Float32Array(vertex_buffer);
}
export default memoize((vertices, indices, subdivisions = 0, shading = 'flat') => {
var uv_buffer = [];
// the subdivision creates the vertex buffer data
const vertex_buffer = create_vertex_buffer(vertices, indices, subdivisions);
for (let i = 0; i < vertex_buffer.length; i += 3) {
const vertex = new Float32Array(vertex_buffer.buffer, i * 4, 3);
// all vertices should lie on a conceptual sphere with a given radius
normalize(vertex);
var u = azimuth(vertex) / 2 / Math.PI + 0.5;
var v = inclination(vertex) / Math.PI + 0.5;
uv_buffer.push(u, 1 - v);
}
correct_uvs(vertex_buffer, uv_buffer);
correct_seam(uv_buffer);
const position_buffer = new Float32Array(vertex_buffer);
return new Geometry({
position: {
data: position_buffer,
size: 3
},
normal: {
data: shading === 'smooth' ? position_buffer : compute_vertex_normals(position_buffer),
size: 3
},
uv: {
data: new Float32Array(uv_buffer),
size: 2
}
});
});