UNPKG

3dmol

Version:

JavaScript/TypeScript molecular visualization library

954 lines (756 loc) 34.8 kB
import { Vector3 } from "./WebGL/math"; import { Geometry } from "./WebGL"; import { Color, Colored } from "colors"; //define enum values /** * Enum for cylinder cap styles. * @readonly * @enum * @property NONE * @property FLAT * @property ROUND */ export enum CAP { NONE = 0, FLAT = 1, ROUND = 2 }; export interface Point { x: number; y: number; z: number; } /** * Lower level utilities for creating WebGL shape geometries. * These are not intended for general consumption. * @namespace */ export namespace GLDraw { // Rotation matrix around z and x axis - // according to y basis vector // TODO: Try to optimize this (square roots?) function getRotationMatrix(dx: number, dy: number, dz: number) { var dxy = Math.hypot(dx, dy); var dyz; var sinA, cosA, sinB, cosB; // about z axis - Phi if (dxy < 0.0001) { sinA = 0; cosA = 1; } else { sinA = -dx / dxy; cosA = dy / dxy; } // recast dy in terms of new axes - z is the same dy = -sinA * dx + cosA * dy; dyz = Math.hypot(dy, dz); // about new x axis - Theta if (dyz < 0.0001) { sinB = 0; cosB = 1; } else { sinB = dz / dyz; cosB = dy / dyz; } var rot = new Float32Array(9); rot[0] = cosA; rot[1] = sinA; rot[2] = 0; rot[3] = -sinA * cosB; rot[4] = cosA * cosB; rot[5] = sinB; rot[6] = sinA * sinB; rot[7] = -cosA * sinB; rot[8] = cosB; return rot; }; // memoize capped cylinder for given radius cylVertexCache class CylVertexCache { // memoize both rounded and flat caps (hemisphere and circle) cache: any = {}; // Ortho normal vectors for cylinder radius/ sphere cap equator and cones // Direction is j basis (0,1,0) basisVectors: any; constructor() { //initialize basisVectors let nvecs = []; let subdivisions = 4; // including the initial 2, eg. 4 => 16 subintervals let N = Math.pow(2, subdivisions); // eg. 2**4 = 16 subintervals in total let i = 2; // start with 2 subdivisions already done let M = Math.pow(2, i); // 4 let spacing = N / M; // 16/4 = 4; if there were 5 subdivs, then 32/4 = 8. let j: number; nvecs[0] = new Vector3(-1, 0, 0); nvecs[spacing] = new Vector3(0, 0, 1); nvecs[spacing * 2] = new Vector3(1, 0, 0); nvecs[spacing * 3] = new Vector3(0, 0, -1); for (i = 3; i <= subdivisions; i++) { // eg. i=3, we need to add 2**(3-1) = 4 new vecs. Call it M. // their spacing is N/M, eg. N=16, M=4, N/M=4; M=8, N/M=2. // they start off at half this spacing // and are equal to the average of the two vectors on either side M = Math.pow(2, (i - 1)); spacing = N / M; for (j = 0; j < (M - 1); j++) { nvecs[spacing / 2 + j * spacing] = nvecs[j * spacing].clone().add(nvecs[(j + 1) * spacing]).normalize(); } // treat the last one specially so it wraps around to zero j = M - 1; nvecs[spacing / 2 + j * spacing] = nvecs[j * spacing].clone().add(nvecs[0]).normalize(); } this.basisVectors = nvecs; }; getVerticesForRadius(radius: any, cap: CAP, capType: any) { if (typeof (this.cache) !== "undefined" && this.cache[radius] !== undefined) if (this.cache[radius][cap + capType] !== undefined) return this.cache[radius][cap + capType]; var w = this.basisVectors.length; var nvecs = [], norms = []; var n; for (var i = 0; i < w; i++) { // bottom nvecs.push(this.basisVectors[i].clone().multiplyScalar(radius)); // top nvecs.push(this.basisVectors[i].clone().multiplyScalar(radius)); // NOTE: this normal is used for constructing sphere caps - // cylinder normals taken care of in drawCylinder n = this.basisVectors[i].clone().normalize(); norms.push(n); norms.push(n); } // norms[0] var verticesRows = []; // Require that heightSegments is even and >= 2 // Equator points at h/2 (theta = pi/2) // (repeated) polar points at 0 and h (theta = 0 and pi) var heightSegments = 10, widthSegments = w; // 16 or however many // basis vectors for // cylinder if (heightSegments % 2 !== 0 || !heightSegments) { console.error("heightSegments must be even"); return null; } var phiStart = 0; var phiLength = Math.PI * 2; var thetaStart = 0; var thetaLength = Math.PI; var x: number, y:number; var polar = false, equator = false; for (y = 0; y <= heightSegments; y++) { polar = (y === 0 || y === heightSegments) ? true : false; equator = (y === heightSegments / 2) ? true : false; var verticesRow = [], toRow = []; for (x = 0; x <= widthSegments; x++) { // Two vertices rows for equator pointing to previously // constructed cyl points if (equator) { var xi = (x < widthSegments) ? 2 * x : 0; toRow.push(xi + 1); verticesRow.push(xi); continue; } var u = x / widthSegments; var v = y / heightSegments; // Only push first polar point if (!polar || x === 0) { if (x < widthSegments) { var vertex = new Vector3(); vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); if (cap == 1) vertex.y = 0; else vertex.y = radius * Math.cos(thetaStart + v * thetaLength); vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); if (Math.abs(vertex.x) < 1e-5) vertex.x = 0; if (Math.abs(vertex.y) < 1e-5) vertex.y = 0; if (Math.abs(vertex.z) < 1e-5) vertex.z = 0; if (cap == CAP.FLAT) { n = new Vector3(0, Math.cos(thetaStart + v * thetaLength), 0); n.normalize(); } else { n = new Vector3(vertex.x, vertex.y, vertex.z); n.normalize(); } nvecs.push(vertex); norms.push(n); verticesRow.push(nvecs.length - 1); } // last point is just the first point for this row else { verticesRow.push(nvecs.length - widthSegments); } } // x > 0; index to already added point else if (polar) verticesRow.push(nvecs.length - 1); } // extra equator row if (equator) verticesRows.push(toRow); verticesRows.push(verticesRow); } var obj = { vertices: nvecs, normals: norms, verticesRows: verticesRows, w: widthSegments, h: heightSegments }; if (!(radius in this.cache)) this.cache[radius] = {}; this.cache[radius][cap + capType] = obj; return obj; } }; var cylVertexCache = new CylVertexCache(); /** * Create a cylinder * @memberof GLDraw * @param {Geometry} * geo * @param {Point} * from * @param {Point} * to * @param {number} * radius * @param {Color} * color * @param {CAP} fromCap - 0 for none, 1 for flat, 2 for round * @param {CAP} toCap = 0 for none, 1 for flat, 2 for round * * */ export function drawCylinder(geo: Geometry, from: any, to: any, radius: number, color: Color | Color[], fromCap:CAP|string = 0, toCap:CAP|string = 0) { if (!from || !to) return; let getcap = function(c: CAP|string): CAP { if(typeof c === "string") { let s = <string>c; if(s.toLowerCase() == 'flat') { return CAP.FLAT; } else if(s.toLowerCase() == 'round') { return CAP.ROUND; } else { return CAP.NONE; } } else { return <CAP>c; } } fromCap = getcap(fromCap); toCap = getcap(toCap); // vertices var drawcaps = toCap || fromCap; color = color || ({ r: 0, g: 0, b: 0 } as Color); var e = getRotationMatrix(to.x-from.x, to.y-from.y, to.z-from.z); // get orthonormal vectors from cache // TODO: Will have orient with model view matrix according to direction var vobj = cylVertexCache.getVerticesForRadius(radius, toCap, "to"); // w (n) corresponds to the number of orthonormal vectors for cylinder // (default 16) var n = vobj.w, h = vobj.h; // get orthonormal vector var n_verts = (drawcaps) ? h * n + 2 : 2 * n; var geoGroup = geo.updateGeoGroup(n_verts); var vertices = vobj.vertices, normals = vobj.normals, verticesRows = vobj.verticesRows; var toRow = verticesRows[h / 2], fromRow = verticesRows[h / 2 + 1]; var start = geoGroup.vertices; var offset, faceoffset; var i, x, y, z; var vertexArray = geoGroup.vertexArray; var normalArray = geoGroup.normalArray; var colorArray = geoGroup.colorArray; var faceArray = geoGroup.faceArray; // add vertices, opposing vertices paired together for (i = 0; i < n; ++i) { var vi = 2 * i; x = e[0] * vertices[vi].x + e[3] * vertices[vi].y + e[6] * vertices[vi].z; y = e[1] * vertices[vi].x + e[4] * vertices[vi].y + e[7] * vertices[vi].z; z = e[5] * vertices[vi].y + e[8] * vertices[vi].z; // var xn = x/radius, yn = y/radius, zn = z/radius; offset = 3 * (start + vi); faceoffset = geoGroup.faceidx; // from vertexArray[offset] = x + from.x; vertexArray[offset + 1] = y + from.y; vertexArray[offset + 2] = z + from.z; // to vertexArray[offset + 3] = x + to.x; vertexArray[offset + 4] = y + to.y; vertexArray[offset + 5] = z + to.z; // normals normalArray[offset] = x; normalArray[offset + 3] = x; normalArray[offset + 1] = y; normalArray[offset + 4] = y; normalArray[offset + 2] = z; normalArray[offset + 5] = z; // colors colorArray[offset] = (color as Color).r; colorArray[offset + 3] = (color as Color).r; colorArray[offset + 1] = (color as Color).g; colorArray[offset + 4] = (color as Color).g; colorArray[offset + 2] = (color as Color).b; colorArray[offset + 5] = (color as Color).b; // faces // 0 - 2 - 1 faceArray[faceoffset] = fromRow[i] + start; faceArray[faceoffset + 1] = fromRow[i + 1] + start; faceArray[faceoffset + 2] = toRow[i] + start; // 1 - 2 - 3 faceArray[faceoffset + 3] = toRow[i] + start; faceArray[faceoffset + 4] = fromRow[i + 1] + start; faceArray[faceoffset + 5] = toRow[i + 1] + start; geoGroup.faceidx += 6; } // SPHERE CAPS if (drawcaps) { // h - sphere rows, verticesRows.length - 2 var ystart = (toCap) ? 0 : h / 2; var yend = (fromCap) ? h + 1 : h / 2 + 1; var v1, v2, v3, v4, x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4, nx1, nx2, nx3, nx4, ny1, ny2, ny3, ny4, nz1, nz2, nz3, nz4, v1offset, v2offset, v3offset, v4offset; for (y = ystart; y < yend; y++) { if (y === h / 2) continue; // n number of points for each level (verticesRows[i].length - // 1) var cap = (y <= h / 2) ? to : from; var toObj = cylVertexCache.getVerticesForRadius(radius, toCap, "to"); var fromObj = cylVertexCache.getVerticesForRadius(radius, fromCap, "from"); if (cap === to) { vertices = toObj.vertices; normals = toObj.normals; verticesRows = toObj.verticesRows; } else if (cap == from) { vertices = fromObj.vertices; normals = fromObj.normals; verticesRows = fromObj.verticesRows; } for (x = 0; x < n; x++) { faceoffset = geoGroup.faceidx; v1 = verticesRows[y][x + 1]; v1offset = (v1 + start) * 3; v2 = verticesRows[y][x]; v2offset = (v2 + start) * 3; v3 = verticesRows[y + 1][x]; v3offset = (v3 + start) * 3; v4 = verticesRows[y + 1][x + 1]; v4offset = (v4 + start) * 3; // rotate sphere vectors x1 = e[0] * vertices[v1].x + e[3] * vertices[v1].y + e[6] * vertices[v1].z; x2 = e[0] * vertices[v2].x + e[3] * vertices[v2].y + e[6] * vertices[v2].z; x3 = e[0] * vertices[v3].x + e[3] * vertices[v3].y + e[6] * vertices[v3].z; x4 = e[0] * vertices[v4].x + e[3] * vertices[v4].y + e[6] * vertices[v4].z; y1 = e[1] * vertices[v1].x + e[4] * vertices[v1].y + e[7] * vertices[v1].z; y2 = e[1] * vertices[v2].x + e[4] * vertices[v2].y + e[7] * vertices[v2].z; y3 = e[1] * vertices[v3].x + e[4] * vertices[v3].y + e[7] * vertices[v3].z; y4 = e[1] * vertices[v4].x + e[4] * vertices[v4].y + e[7] * vertices[v4].z; z1 = e[5] * vertices[v1].y + e[8] * vertices[v1].z; z2 = e[5] * vertices[v2].y + e[8] * vertices[v2].z; z3 = e[5] * vertices[v3].y + e[8] * vertices[v3].z; z4 = e[5] * vertices[v4].y + e[8] * vertices[v4].z; vertexArray[v1offset] = x1 + cap.x; vertexArray[v2offset] = x2 + cap.x; vertexArray[v3offset] = x3 + cap.x; vertexArray[v4offset] = x4 + cap.x; vertexArray[v1offset + 1] = y1 + cap.y; vertexArray[v2offset + 1] = y2 + cap.y; vertexArray[v3offset + 1] = y3 + cap.y; vertexArray[v4offset + 1] = y4 + cap.y; vertexArray[v1offset + 2] = z1 + cap.z; vertexArray[v2offset + 2] = z2 + cap.z; vertexArray[v3offset + 2] = z3 + cap.z; vertexArray[v4offset + 2] = z4 + cap.z; colorArray[v1offset] = (color as Color).r; colorArray[v2offset] = (color as Color).r; colorArray[v3offset] = (color as Color).r; colorArray[v4offset] = (color as Color).r; colorArray[v1offset + 1] = (color as Color).g; colorArray[v2offset + 1] = (color as Color).g; colorArray[v3offset + 1] = (color as Color).g; colorArray[v4offset + 1] = (color as Color).g; colorArray[v1offset + 2] = (color as Color).b; colorArray[v2offset + 2] = (color as Color).b; colorArray[v3offset + 2] = (color as Color).b; colorArray[v4offset + 2] = (color as Color).b; nx1 = e[0] * normals[v1].x + e[3] * normals[v1].y + e[6] * normals[v1].z; nx2 = e[0] * normals[v2].x + e[3] * normals[v2].y + e[6] * normals[v2].z; nx3 = e[0] * normals[v3].x + e[3] * normals[v3].y + e[6] * normals[v3].z; nx4 = e[0] * normals[v4].x + e[3] * normals[v4].y + e[6] * normals[v4].z; ny1 = e[1] * normals[v1].x + e[4] * normals[v1].y + e[7] * normals[v1].z; ny2 = e[1] * normals[v2].x + e[4] * normals[v2].y + e[7] * normals[v2].z; ny3 = e[1] * normals[v3].x + e[4] * normals[v3].y + e[7] * normals[v3].z; ny4 = e[1] * normals[v4].x + e[4] * normals[v4].y + e[7] * normals[v4].z; nz1 = e[5] * normals[v1].y + e[8] * normals[v1].z; nz2 = e[5] * normals[v2].y + e[8] * normals[v2].z; nz3 = e[5] * normals[v3].y + e[8] * normals[v3].z; nz4 = e[5] * normals[v4].y + e[8] * normals[v4].z; // if (Math.abs(vobj.sphereVertices[v1].y) === radius) { if (y === 0) {//to center circle // face = [v1, v3, v4]; // norm = [n1, n3, n4]; normalArray[v1offset] = nx1; normalArray[v3offset] = nx3; normalArray[v4offset] = nx4; normalArray[v1offset + 1] = ny1; normalArray[v3offset + 1] = ny3; normalArray[v4offset + 1] = ny4; normalArray[v1offset + 2] = nz1; normalArray[v3offset + 2] = nz3; normalArray[v4offset + 2] = nz4; faceArray[faceoffset] = v1 + start; faceArray[faceoffset + 1] = v3 + start; faceArray[faceoffset + 2] = v4 + start; geoGroup.faceidx += 3; } // else if (Math.abs(vobj.sphereVertices[v3].y) === radius) // { else if (y === yend - 1) {//from end center circle // face = [v1, v2, v3]; // norm = [n1, n2, n3]; normalArray[v1offset] = nx1; normalArray[v2offset] = nx2; normalArray[v3offset] = nx3; normalArray[v1offset + 1] = ny1; normalArray[v2offset + 1] = ny2; normalArray[v3offset + 1] = ny3; normalArray[v1offset + 2] = nz1; normalArray[v2offset + 2] = nz2; normalArray[v3offset + 2] = nz3; faceArray[faceoffset] = v1 + start; faceArray[faceoffset + 1] = v2 + start; faceArray[faceoffset + 2] = v3 + start; geoGroup.faceidx += 3; } else { // the rest of the circles // face = [v1, v2, v3, v4]; // norm = [n1, n2, n3, n4]; normalArray[v1offset] = nx1; normalArray[v2offset] = nx2; normalArray[v4offset] = nx4; normalArray[v1offset + 1] = ny1; normalArray[v2offset + 1] = ny2; normalArray[v4offset + 1] = ny4; normalArray[v1offset + 2] = nz1; normalArray[v2offset + 2] = nz2; normalArray[v4offset + 2] = nz4; normalArray[v2offset] = nx2; normalArray[v3offset] = nx3; normalArray[v4offset] = nx4; normalArray[v2offset + 1] = ny2; normalArray[v3offset + 1] = ny3; normalArray[v4offset + 1] = ny4; normalArray[v2offset + 2] = nz2; normalArray[v3offset + 2] = nz3; normalArray[v4offset + 2] = nz4; faceArray[faceoffset] = v1 + start; faceArray[faceoffset + 1] = v2 + start; faceArray[faceoffset + 2] = v4 + start; faceArray[faceoffset + 3] = v2 + start; faceArray[faceoffset + 4] = v3 + start; faceArray[faceoffset + 5] = v4 + start; geoGroup.faceidx += 6; } } } } geoGroup.vertices += n_verts; }; /** Create a cone * @memberof GLDraw * @param {Geometry} * geo * @param {Point} * from * @param {Point} * to * @param {number} * radius * @param {Color} * color * */ export function drawCone (geo: Geometry, from: any, to: any, radius: number, color?: Color) { if (!from || !to) return; // TODO: check if from and to do not contain x,y,z and if so generate a center based on the passed selections color = color || ({ r: 0, g: 0, b: 0 } as Color); let ndir = new Vector3(to.x-from.x, to.y-from.y, to.z-from.z); var e = getRotationMatrix(ndir.x, ndir.y, ndir.z); ndir = ndir.normalize(); // n vertices around bottom plust the two points var n = cylVertexCache.basisVectors.length; var basis = cylVertexCache.basisVectors; var n_verts = n + 2; //setup geo structures var geoGroup = geo.updateGeoGroup(n_verts); var start = geoGroup.vertices; var offset, faceoffset; var i, x, y, z; var vertexArray = geoGroup.vertexArray; var normalArray = geoGroup.normalArray; var colorArray = geoGroup.colorArray; var faceArray = geoGroup.faceArray; offset = start * 3; //base point first vertex vertexArray[offset] = from.x; vertexArray[offset + 1] = from.y; vertexArray[offset + 2] = from.z; normalArray[offset] = -ndir.x; normalArray[offset + 1] = -ndir.y; normalArray[offset + 2] = -ndir.z; colorArray[offset] = color.r; colorArray[offset + 1] = color.g; colorArray[offset + 2] = color.b; //second vertex top vertexArray[offset + 3] = to.x; vertexArray[offset + 4] = to.y; vertexArray[offset + 5] = to.z; normalArray[offset + 3] = ndir.x; normalArray[offset + 4] = ndir.y; normalArray[offset + 5] = ndir.z; colorArray[offset + 3] = color.r; colorArray[offset + 4] = color.g; colorArray[offset + 5] = color.b; offset += 6; // add circle vertices for (i = 0; i < n; ++i) { var vec = basis[i].clone(); vec.multiplyScalar(radius); x = e[0] * vec.x + e[3] * vec.y + e[6] * vec.z; y = e[1] * vec.x + e[4] * vec.y + e[7] * vec.z; z = e[5] * vec.y + e[8] * vec.z; // from vertexArray[offset] = x + from.x; vertexArray[offset + 1] = y + from.y; vertexArray[offset + 2] = z + from.z; // normals normalArray[offset] = x; normalArray[offset + 1] = y; normalArray[offset + 2] = z; // colors colorArray[offset] = color.r; colorArray[offset + 1] = color.g; colorArray[offset + 2] = color.b; offset += 3; } geoGroup.vertices += (n + 2); //faces faceoffset = geoGroup.faceidx; for (i = 0; i < n; i++) { //two neighboring circle vertices var v1 = start + 2 + i; var v2 = start + 2 + ((i + 1) % n); faceArray[faceoffset] = v1; faceArray[faceoffset + 1] = v2; faceArray[faceoffset + 2] = start; faceoffset += 3; faceArray[faceoffset] = v1; faceArray[faceoffset + 1] = v2; faceArray[faceoffset + 2] = start + 1; faceoffset += 3; } geoGroup.faceidx += 6 * n; }; interface MyObject { vertices: any[]; verticesRows: any[][]; normals: any[]; } // Sphere component sphereVertexCache class SphereVertexCache { private cache = new Map<number, Map<number, any>>(); //sphereQuality then radius constructor() {} getVerticesForRadius(radius: number, sphereQuality: any) { sphereQuality = sphereQuality || 2; if (!this.cache.has(sphereQuality)) { this.cache.set(sphereQuality, new Map<number,any>()); } let radiusCache = this.cache.get(sphereQuality); if (radiusCache.has(radius)) return radiusCache.get(radius); var obj: MyObject = { vertices: [], verticesRows: [], normals: [] }; // scale quality with radius heuristically var widthSegments = 16 * sphereQuality; var heightSegments = 10 * sphereQuality; if (radius < 1) { widthSegments = 10 * sphereQuality; heightSegments = 8 * sphereQuality; } var phiStart = 0; var phiLength = Math.PI * 2; var thetaStart = 0; var thetaLength = Math.PI; var x, y; for (y = 0; y <= heightSegments; y++) { let verticesRow = []; for (x = 0; x <= widthSegments; x++) { let u = x / widthSegments; let v = y / heightSegments; let vx = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); let vy = radius * Math.cos(thetaStart + v * thetaLength); let vz = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); var n = new Vector3(vx, vy, vz); n.normalize(); obj.vertices.push({x: vx, y: vy, z: vz}); obj.normals.push(n); verticesRow.push(obj.vertices.length - 1); } obj.verticesRows.push(verticesRow); } radiusCache.set(radius, obj); return obj; } }; var sphereVertexCache = new SphereVertexCache(); /** Create a sphere. * @memberof GLDraw * @param {Geometry} * geo * @param {Point} * pos * @param {number} * radius * @param {Color} * color * @param {number} * sphereQuality - Quality of sphere (default 2, higher increases number of triangles) */ export function drawSphere(geo:Geometry, pos: any, radius: number, color: Colored, sphereQuality?: number) { var vobj = sphereVertexCache.getVerticesForRadius(radius, sphereQuality); var vertices = vobj.vertices; var normals = vobj.normals; var geoGroup = geo.updateGeoGroup(vertices.length); var start = geoGroup.vertices; var vertexArray = geoGroup.vertexArray; var colorArray = geoGroup.colorArray; var faceArray = geoGroup.faceArray; var lineArray = geoGroup.lineArray; var normalArray = geoGroup.normalArray; for (let i = 0, il = vertices.length; i < il; ++i) { let offset = 3 * (start + i); let v = vertices[i]; vertexArray[offset] = (v.x + pos.x); vertexArray[offset + 1] = (v.y + pos.y); vertexArray[offset + 2] = (v.z + pos.z); colorArray[offset] = (color as Colored).r; colorArray[offset + 1] = (color as Colored).g; colorArray[offset + 2] = (color as Colored).b; } geoGroup.vertices += vertices.length; let verticesRows = vobj.verticesRows; let h = verticesRows.length - 1; for (let y = 0; y < h; y++) { let w = verticesRows[y].length - 1; for (let x = 0; x < w; x++) { let faceoffset = geoGroup.faceidx, lineoffset = geoGroup.lineidx; let v1 = verticesRows[y][x + 1] + start, v1offset = v1 * 3; let v2 = verticesRows[y][x] + start, v2offset = v2 * 3; let v3 = verticesRows[y + 1][x] + start, v3offset = v3 * 3; let v4 = verticesRows[y + 1][x + 1] + start, v4offset = v4 * 3; let n1 = normals[v1 - start]; let n2 = normals[v2 - start]; let n3 = normals[v3 - start]; let n4 = normals[v4 - start]; if (Math.abs(vertices[v1 - start].y) === radius) { // face = [v1, v3, v4]; // norm = [n1, n3, n4]; normalArray[v1offset] = n1.x; normalArray[v3offset] = n3.x; normalArray[v4offset] = n4.x; normalArray[v1offset + 1] = n1.y; normalArray[v3offset + 1] = n3.y; normalArray[v4offset + 1] = n4.y; normalArray[v1offset + 2] = n1.z; normalArray[v3offset + 2] = n3.z; normalArray[v4offset + 2] = n4.z; faceArray[faceoffset] = v1; faceArray[faceoffset + 1] = v3; faceArray[faceoffset + 2] = v4; lineArray[lineoffset] = v1; lineArray[lineoffset + 1] = v3; lineArray[lineoffset + 2] = v1; lineArray[lineoffset + 3] = v4; lineArray[lineoffset + 4] = v3; lineArray[lineoffset + 5] = v4; geoGroup.faceidx += 3; geoGroup.lineidx += 6; } else if (Math.abs(vertices[v3 - start].y) === radius) { // face = [v1, v2, v3]; // norm = [n1, n2, n3]; normalArray[v1offset] = n1.x; normalArray[v2offset] = n2.x; normalArray[v3offset] = n3.x; normalArray[v1offset + 1] = n1.y; normalArray[v2offset + 1] = n2.y; normalArray[v3offset + 1] = n3.y; normalArray[v1offset + 2] = n1.z; normalArray[v2offset + 2] = n2.z; normalArray[v3offset + 2] = n3.z; faceArray[faceoffset] = v1; faceArray[faceoffset + 1] = v2; faceArray[faceoffset + 2] = v3; lineArray[lineoffset] = v1; lineArray[lineoffset + 1] = v2; lineArray[lineoffset + 2] = v1; lineArray[lineoffset + 3] = v3; lineArray[lineoffset + 4] = v2; lineArray[lineoffset + 5] = v3; geoGroup.faceidx += 3; geoGroup.lineidx += 6; } else { // face = [v1, v2, v3, v4]; // norm = [n1, n2, n3, n4]; normalArray[v1offset] = n1.x; normalArray[v2offset] = n2.x; normalArray[v4offset] = n4.x; normalArray[v1offset + 1] = n1.y; normalArray[v2offset + 1] = n2.y; normalArray[v4offset + 1] = n4.y; normalArray[v1offset + 2] = n1.z; normalArray[v2offset + 2] = n2.z; normalArray[v4offset + 2] = n4.z; normalArray[v2offset] = n2.x; normalArray[v3offset] = n3.x; normalArray[v4offset] = n4.x; normalArray[v2offset + 1] = n2.y; normalArray[v3offset + 1] = n3.y; normalArray[v4offset + 1] = n4.y; normalArray[v2offset + 2] = n2.z; normalArray[v3offset + 2] = n3.z; normalArray[v4offset + 2] = n4.z; faceArray[faceoffset] = v1; faceArray[faceoffset + 1] = v2; faceArray[faceoffset + 2] = v4; faceArray[faceoffset + 3] = v2; faceArray[faceoffset + 4] = v3; faceArray[faceoffset + 5] = v4; lineArray[lineoffset] = v1; lineArray[lineoffset + 1] = v2; lineArray[lineoffset + 2] = v1; lineArray[lineoffset + 3] = v4; lineArray[lineoffset + 4] = v2; lineArray[lineoffset + 5] = v3; lineArray[lineoffset + 6] = v3; lineArray[lineoffset + 7] = v4; geoGroup.faceidx += 6; geoGroup.lineidx += 8; } } } }; }