UNPKG

molstar

Version:

A comprehensive macromolecular library.

196 lines (195 loc) 9.46 kB
/** * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> * @author Gianluca Tomasello <giagitom@gmail.com> */ import { Vec3 } from '../../../../mol-math/linear-algebra'; import { cantorPairing, ChunkedArray } from '../../../../mol-data/util'; const normalVector = Vec3(); const capNormalSmoothingVector = Vec3(); const surfacePoint = Vec3(); const controlPoint = Vec3(); const u = Vec3(); const v = Vec3(); function add2AndScale2(out, a, b, sa, sb) { out[0] = (a[0] * sa) + (b[0] * sb); out[1] = (a[1] * sa) + (b[1] * sb); out[2] = (a[2] * sa) + (b[2] * sb); } function add3AndScale2(out, a, b, c, sa, sb) { out[0] = (a[0] * sa) + (b[0] * sb) + c[0]; out[1] = (a[1] * sa) + (b[1] * sb) + c[1]; out[2] = (a[2] * sa) + (b[2] * sb) + c[2]; } // avoiding namespace lookup improved performance in Chrome (Aug 2020) const v3fromArray = Vec3.fromArray; const v3normalize = Vec3.normalize; const v3scaleAndAdd = Vec3.scaleAndAdd; const v3cross = Vec3.cross; const v3slerp = Vec3.slerp; const v3dot = Vec3.dot; const v3unitX = Vec3.unitX; const caAdd3 = ChunkedArray.add3; const CosSinCache = new Map(); function getCosSin(radialSegments, shift) { const offset = shift ? 1 : 0; const hash = cantorPairing(radialSegments, offset); if (!CosSinCache.has(hash)) { const cos = []; const sin = []; for (let j = 0; j < radialSegments; ++j) { const t = (j * 2 + offset) / radialSegments * Math.PI; cos[j] = Math.cos(t); sin[j] = Math.sin(t); } CosSinCache.set(hash, { cos, sin }); } return CosSinCache.get(hash); } export function addTube(state, controlPoints, normalVectors, binormalVectors, linearSegments, radialSegments, widthValues, heightValues, startCap, endCap, crossSection, roundCap = false) { const { currentGroup, vertices, normals, indices, groups } = state; let vertexCount = vertices.elementCount; const { cos, sin } = getCosSin(radialSegments, crossSection === 'rounded'); const q1 = Math.round(radialSegments / 4); const q3 = q1 * 3; const roundCapFlag = roundCap && linearSegments && (startCap || endCap); let halfLinearSegments; const doubleRoundCap = roundCapFlag && startCap && endCap; if (doubleRoundCap) halfLinearSegments = linearSegments / 2; for (let i = 0; i <= linearSegments; ++i) { const i3 = i * 3; v3fromArray(u, normalVectors, i3); v3fromArray(v, binormalVectors, i3); v3fromArray(controlPoint, controlPoints, i3); let width = widthValues[i]; let height = heightValues[i]; let capSmoothingFactor; if (roundCapFlag) { const sc = doubleRoundCap ? i <= halfLinearSegments : startCap; if (doubleRoundCap) { capSmoothingFactor = Math.max(Number.EPSILON, Math.sqrt(1 - Math.pow((sc ? halfLinearSegments - i : i - halfLinearSegments) / halfLinearSegments, 2))); } else { capSmoothingFactor = Math.max(Number.EPSILON, Math.sqrt(1 - Math.pow((sc ? linearSegments - i : i) / linearSegments, 2))); } width *= capSmoothingFactor; height *= capSmoothingFactor; v3cross(capNormalSmoothingVector, sc ? v : u, sc ? u : v); v3normalize(capNormalSmoothingVector, capNormalSmoothingVector); } const rounded = crossSection === 'rounded' && height > width; for (let j = 0; j < radialSegments; ++j) { if (rounded) { add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[j], width * sin[j]); const h = v3dot(v, v3unitX) < 0 ? (j < q1 || j >= q3) ? height - width : -height + width : (j >= q1 && j < q3) ? -height + width : height - width; v3scaleAndAdd(surfacePoint, surfacePoint, u, h); if (j === q1 || j === q1 - 1) { add2AndScale2(normalVector, u, v, 0, 1); } else if (j === q3 || j === q3 - 1) { add2AndScale2(normalVector, u, v, 0, -1); } else { add2AndScale2(normalVector, u, v, cos[j], sin[j]); } } else { add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]); add2AndScale2(normalVector, u, v, width * cos[j], height * sin[j]); } v3normalize(normalVector, normalVector); caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]); if (roundCapFlag) { v3slerp(normalVector, capNormalSmoothingVector, normalVector, capSmoothingFactor); } caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); } } const radialSegmentsHalf = Math.round(radialSegments / 2); for (let i = 0; i < linearSegments; ++i) { // the triangles are arranged such that opposing triangles of the sheet align // which prevents triangle intersection within tight curves for (let j = 0; j < radialSegmentsHalf; ++j) { caAdd3(indices, vertexCount + i * radialSegments + (j + 1) % radialSegments, // a vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c vertexCount + i * radialSegments + j // b ); caAdd3(indices, vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c vertexCount + (i + 1) * radialSegments + j, // d vertexCount + i * radialSegments + j // b ); } for (let j = radialSegmentsHalf; j < radialSegments; ++j) { caAdd3(indices, vertexCount + i * radialSegments + (j + 1) % radialSegments, // a vertexCount + (i + 1) * radialSegments + j, // d vertexCount + i * radialSegments + j // b ); caAdd3(indices, vertexCount + (i + 1) * radialSegments + (j + 1) % radialSegments, // c vertexCount + (i + 1) * radialSegments + j, // d vertexCount + i * radialSegments + (j + 1) % radialSegments); } } if (startCap) { const offset = 0; const centerVertex = vertices.elementCount; v3fromArray(u, normalVectors, offset); v3fromArray(v, binormalVectors, offset); v3fromArray(controlPoint, controlPoints, offset); v3cross(normalVector, v, u); caAdd3(vertices, controlPoint[0], controlPoint[1], controlPoint[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); const width = roundCapFlag ? 0 : widthValues[0]; let height = roundCapFlag ? 0 : heightValues[0]; const rounded = crossSection === 'rounded' && height > width; if (rounded) height -= width; vertexCount = vertices.elementCount; for (let i = 0; i < radialSegments; ++i) { if (rounded) { add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]); v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height); } else { add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]); } caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); caAdd3(indices, vertexCount + (i + 1) % radialSegments, vertexCount + i, centerVertex); } } if (endCap) { const offset = linearSegments * 3; const centerVertex = vertices.elementCount; v3fromArray(u, normalVectors, offset); v3fromArray(v, binormalVectors, offset); v3fromArray(controlPoint, controlPoints, offset); v3cross(normalVector, u, v); caAdd3(vertices, controlPoint[0], controlPoint[1], controlPoint[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); const width = roundCapFlag ? 0 : widthValues[linearSegments]; let height = roundCapFlag ? 0 : heightValues[linearSegments]; const rounded = crossSection === 'rounded' && height > width; if (rounded) height -= width; vertexCount = vertices.elementCount; for (let i = 0; i < radialSegments; ++i) { if (rounded) { add3AndScale2(surfacePoint, u, v, controlPoint, width * cos[i], width * sin[i]); v3scaleAndAdd(surfacePoint, surfacePoint, u, (i < q1 || i >= q3) ? height : -height); } else { add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[i], width * sin[i]); } caAdd3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]); caAdd3(normals, normalVector[0], normalVector[1], normalVector[2]); caAdd3(indices, vertexCount + i, vertexCount + (i + 1) % radialSegments, centerVertex); } } const addedVertexCount = (linearSegments + 1) * radialSegments + (startCap ? radialSegments + 1 : 0) + (endCap ? radialSegments + 1 : 0); ChunkedArray.addRepeat(groups, addedVertexCount, currentGroup); }