UNPKG

molstar

Version:

A comprehensive macromolecular library.

710 lines (709 loc) 34.6 kB
/** * Copyright (c) 2021-2026 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Sukolsak Sakshuwong <sukolsak@stanford.edu> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { sort, arraySwap } from '../../mol-data/util.js'; import { getTrilinearlyInterpolated } from '../../mol-geo/geometry/mesh/color-smoothing.js'; import { Mesh } from '../../mol-geo/geometry/mesh/mesh.js'; import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder.js'; import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere.js'; import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder.js'; import { sizeDataFactor } from '../../mol-geo/geometry/size-data.js'; import { Vec3 } from '../../mol-math/linear-algebra.js'; import { Color } from '../../mol-util/color/color.js'; import { unpackRGBToInt } from '../../mol-util/number-packing.js'; import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util.js'; import { assertUnreachable } from '../../mol-util/type-helpers.js'; import { ColorTheme } from '../../mol-theme/color.js'; import { computeFrenetFrames } from '../../mol-math/linear-algebra/3d/frenet-frames.js'; import { addTube } from '../../mol-geo/geometry/mesh/builder/tube.js'; import { arrayCopyOffset } from '../../mol-util/array.js'; const GeoExportName = 'geo-export'; // avoiding namespace lookup improved performance in Chrome (Aug 2020) const v3fromArray = Vec3.fromArray; const v3sub = Vec3.sub; const v3dot = Vec3.dot; const v3unitY = Vec3.unitY; export class MeshExporter { constructor() { this.options = { includeHidden: false, linesAsTriangles: false, pointsAsTriangles: false, primitivesQuality: 'auto', }; } static getSizeFromTexture(tSize, i) { const r = tSize.array[i * 3]; const g = tSize.array[i * 3 + 1]; const b = tSize.array[i * 3 + 2]; return unpackRGBToInt(r, g, b) / sizeDataFactor; } static getSize(values, instanceIndex, group, vertexIndex) { const tSize = values.tSize.ref.value; let size = 0; switch (values.dSizeType.ref.value) { case 'uniform': size = values.uSize.ref.value; break; case 'instance': size = MeshExporter.getSizeFromTexture(tSize, instanceIndex); break; case 'group': size = MeshExporter.getSizeFromTexture(tSize, group); break; case 'groupInstance': const groupCount = values.uGroupCount.ref.value; size = MeshExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group); break; case 'vertex': size = MeshExporter.getSizeFromTexture(tSize, vertexIndex); break; case 'vertexInstance': const vertexCount = values.uVertexCount.ref.value; size = MeshExporter.getSizeFromTexture(tSize, instanceIndex * vertexCount + vertexIndex); break; } return size * values.uSizeFactor.ref.value; } static getGroup(groups, i) { const i4 = i * 4; const r = groups[i4]; const g = groups[i4 + 1]; const b = groups[i4 + 2]; if (groups instanceof Float32Array) { return unpackRGBToInt(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5); } return unpackRGBToInt(r, g, b); } static getInterpolatedColors(webgl, input) { const { values, vertexCount, vertices, colorType, stride } = input; const colorGridTransform = values.uColorGridTransform.ref.value; const colorGridDim = values.uColorGridDim.ref.value; const colorTexDim = values.uColorTexDim.ref.value; const aTransform = values.aTransform.ref.value; const instanceCount = values.uInstanceCount.ref.value; const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array; const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 }); return interpolated.array; } static getInterpolatedOverpaint(webgl, input) { const { values, vertexCount, vertices, colorType, stride } = input; const overpaintGridTransform = values.uOverpaintGridTransform.ref.value; const overpaintGridDim = values.uOverpaintGridDim.ref.value; const overpaintTexDim = values.uOverpaintTexDim.ref.value; const aTransform = values.aTransform.ref.value; const instanceCount = values.uInstanceCount.ref.value; const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array; const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 }); return interpolated.array; } static getInterpolatedTransparency(webgl, input) { const { values, vertexCount, vertices, colorType, stride } = input; const transparencyGridTransform = values.uTransparencyGridTransform.ref.value; const transparencyGridDim = values.uTransparencyGridDim.ref.value; const transparencyTexDim = values.uTransparencyTexDim.ref.value; const aTransform = values.aTransform.ref.value; const instanceCount = values.uInstanceCount.ref.value; const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array; const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 }); return interpolated.array; } static quantizeColors(colorArray, vertexCount) { if (vertexCount <= 1024) return; const rgb = Vec3(); const min = Vec3(); const max = Vec3(); const sum = Vec3(); const colorMap = new Map(); const colorComparers = [ (colors, i, j) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]), (colors, i, j) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]), (colors, i, j) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]), ]; const medianCut = (colors, l, r, depth) => { if (l > r) return; if (l === r || depth >= 10) { // Find the average color. Vec3.set(sum, 0, 0, 0); for (let i = l; i <= r; ++i) { Color.toVec3(rgb, colors[i]); Vec3.add(sum, sum, rgb); } Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1))); const averageColor = Color.fromArray(rgb, 0); for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor); return; } // Find the color channel with the greatest range. Vec3.set(min, 255, 255, 255); Vec3.set(max, 0, 0, 0); for (let i = l; i <= r; ++i) { Color.toVec3(rgb, colors[i]); for (let j = 0; j < 3; ++j) { Vec3.min(min, min, rgb); Vec3.max(max, max, rgb); } } let k = 0; if (max[1] - min[1] > max[k] - min[k]) k = 1; if (max[2] - min[2] > max[k] - min[k]) k = 2; sort(colors, l, r + 1, colorComparers[k], arraySwap); const m = (l + r) >> 1; medianCut(colors, l, m, depth + 1); medianCut(colors, m + 1, r, depth + 1); }; // Create an array of unique colors and use the median cut algorithm. const colorSet = new Set(); for (let i = 0; i < vertexCount; ++i) { colorSet.add(Color.fromArray(colorArray, i * 3)); } const colors = Array.from(colorSet); medianCut(colors, 0, colors.length - 1, 0); // Map actual colors to quantized colors. for (let i = 0; i < vertexCount; ++i) { const color = colorMap.get(Color.fromArray(colorArray, i * 3)); Color.toArray(color, colorArray, i * 3); } } static getInstance(input, instanceIndex) { const { mesh, meshes } = input; if (mesh !== undefined) { return mesh; } else { const mesh = meshes[instanceIndex]; return { vertices: mesh.vertexBuffer.ref.value, normals: mesh.normalBuffer.ref.value, indices: mesh.indexBuffer.ref.value, groups: mesh.groupBuffer.ref.value, vertexCount: mesh.vertexCount, drawCount: mesh.triangleCount * 3, vertexMapping: input.vertexMapping, }; } } static getColor(vertexIndex, geoData, interpolatedColors, interpolatedOverpaint) { const { values, groups, instanceIndex, isGeoTexture, mode } = geoData; const groupCount = values.uGroupCount.ref.value; const colorType = values.dColorType.ref.value; const uColor = values.uColor.ref.value; const tColor = values.tColor.ref.value.array; const overpaintType = values.dOverpaintType.ref.value; const dOverpaint = values.dOverpaint.ref.value; const tOverpaint = values.tOverpaint.ref.value.array; const usePalette = values.dUsePalette.ref.value; let vertexCount = geoData.vertexCount; if (geoData.vertexMapping) { vertexIndex = geoData.vertexMapping[vertexIndex]; vertexCount = values.uVertexCount.ref.value; } else if (mode === 'lines') { vertexIndex *= 2; vertexCount *= 2; } const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : values.dGeometryType.ref.value === 'spheres' ? values.tPositionGroup.ref.value.array[vertexIndex * 4 + 3] : values.aGroup.ref.value[vertexIndex]; let color; switch (colorType) { case 'uniform': color = Color.fromNormalizedArray(uColor, 0); break; case 'instance': color = Color.fromArray(tColor, instanceIndex * 3); break; case 'group': { color = Color.fromArray(tColor, group * 3); break; } case 'groupInstance': { color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3); break; } case 'vertex': color = Color.fromArray(tColor, vertexIndex * 3); break; case 'vertexInstance': color = Color.fromArray(tColor, (instanceIndex * vertexCount + vertexIndex) * 3); break; case 'volume': color = Color.fromArray(interpolatedColors, vertexIndex * 3); break; case 'volumeInstance': color = Color.fromArray(interpolatedColors, (instanceIndex * vertexCount + vertexIndex) * 3); break; default: throw new Error('Unsupported color type.'); } if (usePalette) { const palette = values.tPalette.ref.value; const paletteArray = palette.array; const paletteLength = paletteArray.length / 3; const [r, g, b] = Color.toRgb(color); const paletteValue = ((r * 256 * 256 + g * 256 + b) - 1) / ColorTheme.PaletteScale; const fIndex = paletteValue * (paletteLength - 1); if (palette.filter === 'nearest') { const index = Math.round(fIndex); color = Color.fromArray(paletteArray, index * 3); } else { // linear const index0 = Math.floor(fIndex); const index1 = index0 + 1; const t = fIndex - index0; const color0 = Color.fromArray(paletteArray, index0 * 3); const color1 = Color.fromArray(paletteArray, index1 * 3); color = Color.interpolate(color0, color1, t); } } if (dOverpaint) { let overpaintColor; let overpaintAlpha; switch (overpaintType) { case 'groupInstance': { const idx = (instanceIndex * groupCount + group) * 4; overpaintColor = Color.fromArray(tOverpaint, idx); overpaintAlpha = tOverpaint[idx + 3] / 255; break; } case 'vertexInstance': { const idx = (instanceIndex * vertexCount + vertexIndex) * 4; overpaintColor = Color.fromArray(tOverpaint, idx); overpaintAlpha = tOverpaint[idx + 3] / 255; break; } case 'volumeInstance': { const idx = (instanceIndex * vertexCount + vertexIndex) * 4; overpaintColor = Color.fromArray(interpolatedOverpaint, idx); overpaintAlpha = interpolatedOverpaint[idx + 3] / 255; break; } default: throw new Error('Unsupported overpaint type.'); } // interpolate twice to avoid darkening due to empty overpaint overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha); color = Color.interpolate(color, overpaintColor, overpaintAlpha); } return color; } static getTransparency(vertexIndex, geoData, interpolatedTransparency) { const { values, instanceIndex, isGeoTexture, mode, groups } = geoData; const groupCount = values.uGroupCount.ref.value; const dTransparency = values.dTransparency.ref.value; const tTransparency = values.tTransparency.ref.value.array; const transparencyType = values.dTransparencyType.ref.value; let vertexCount = geoData.vertexCount; if (geoData.vertexMapping) { vertexIndex = geoData.vertexMapping[vertexIndex]; vertexCount = values.uVertexCount.ref.value; } else if (mode === 'lines') { vertexIndex *= 2; vertexCount *= 2; } const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : values.dGeometryType.ref.value === 'spheres' ? values.tPositionGroup.ref.value.array[vertexIndex * 4 + 3] : values.aGroup.ref.value[vertexIndex]; let transparency = 0; if (dTransparency) { switch (transparencyType) { case 'groupInstance': { const idx = (instanceIndex * groupCount + group); transparency = tTransparency[idx] / 255; break; } case 'vertexInstance': { const idx = (instanceIndex * vertexCount + vertexIndex); transparency = tTransparency[idx] / 255; break; } case 'volumeInstance': { const idx = (instanceIndex * vertexCount + vertexIndex); transparency = interpolatedTransparency[idx] / 255; break; } default: throw new Error('Unsupported transparency type.'); } } return transparency; } async addMesh(values, webgl, ctx) { const aPosition = values.aPosition.ref.value; const aNormal = values.aNormal.ref.value; const aGroup = values.aGroup.ref.value; const originalData = Mesh.getOriginalData(values); let indices; let vertexCount; let drawCount; if (originalData) { indices = originalData.indexBuffer; vertexCount = originalData.vertexCount; drawCount = originalData.triangleCount * 3; } else { indices = values.elements.ref.value; vertexCount = values.uVertexCount.ref.value; drawCount = values.drawCount.ref.value; } await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: aNormal, indices, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, mode: 'triangles', webgl, ctx }); } async addLineStrips(values, webgl, ctx) { const aStart = values.aStart.ref.value; const aEnd = values.aEnd.ref.value; const aGroup = values.aGroup.ref.value; const stripCount = values.stripCount.ref.value; const stripOffsets = values.stripOffsets.ref.value; const aMapping = values.aMapping.ref.value; if (this.options.linesAsTriangles) { const instanceCount = values.instanceCount.ref.value; const meshes = []; const radialSegments = 6; const vertexMapping = []; for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { const state = MeshBuilder.createState(512, 256); for (let s = 0; s < stripCount; ++s) { const stripStart = stripOffsets[s]; const stripEnd = stripOffsets[s + 1]; // Collect segments for this strip (only end-side vertices) const segmentIndices = []; for (let v = stripStart; v < stripEnd; v += 2) { const mappingY = aMapping[v * 2 + 1]; if (mappingY < 0) continue; segmentIndices.push(v); } if (segmentIndices.length === 0) continue; const nPoints = segmentIndices.length + 1; const linearSegments = nPoints - 1; const curvePoints = new Float32Array(nPoints * 3); const curveOrigIndices = []; const widthValues = new Float32Array(nPoints); const heightValues = new Float32Array(nPoints); // First point: start of first segment const v0 = segmentIndices[0]; arrayCopyOffset(curvePoints, aStart, 0, v0 * 3, 3); curveOrigIndices.push(v0); const radius0 = MeshExporter.getSize(values, instanceIndex, aGroup[v0], v0) * 0.03; widthValues[0] = radius0; heightValues[0] = radius0; // Subsequent points: end of each segment for (let j = 0; j < segmentIndices.length; ++j) { const v = segmentIndices[j]; arrayCopyOffset(curvePoints, aEnd, (j + 1) * 3, v * 3, 3); curveOrigIndices.push(v); const radius = MeshExporter.getSize(values, instanceIndex, aGroup[v], v) * 0.03; widthValues[j + 1] = radius; heightValues[j + 1] = radius; } const normalVectors = new Float32Array(nPoints * 3); const binormalVectors = new Float32Array(nPoints * 3); computeFrenetFrames(curvePoints, normalVectors, binormalVectors, nPoints); addTube(state, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, widthValues, heightValues, true, true, 'elliptical'); // Build vertex mapping if (instanceIndex === 0) { for (let i = 0; i <= linearSegments; ++i) { for (let j = 0; j < radialSegments; ++j) { vertexMapping.push(curveOrigIndices[i]); } } for (let j = 0; j <= radialSegments; ++j) { vertexMapping.push(curveOrigIndices[0]); } for (let j = 0; j <= radialSegments; ++j) { vertexMapping.push(curveOrigIndices[linearSegments]); } } } meshes.push(MeshBuilder.getMesh(state)); } await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, mode: 'triangles', webgl, ctx, vertexMapping }); } else { // Decompose strips into individual line segments let nLineSegments = 0; for (let s = 0; s < stripCount; ++s) { const stripStart = stripOffsets[s]; const stripEnd = stripOffsets[s + 1]; for (let v = stripStart; v < stripEnd; v += 2) { const mappingY = aMapping[v * 2 + 1]; if (mappingY < 0) continue; nLineSegments++; } } const vertexCount = nLineSegments * 2; const drawCount = nLineSegments; const vertices = new Float32Array(vertexCount * 3); const vertexMapping = []; let vertexIndex = 0; for (let s = 0; s < stripCount; ++s) { const stripStart = stripOffsets[s]; const stripEnd = stripOffsets[s + 1]; for (let v = stripStart; v < stripEnd; v += 2) { const mappingY = aMapping[v * 2 + 1]; if (mappingY < 0) continue; arrayCopyOffset(vertices, aStart, vertexIndex * 3, v * 3, 3); vertexMapping[vertexIndex] = v; vertexIndex++; arrayCopyOffset(vertices, aEnd, vertexIndex * 3, v * 3, 3); vertexMapping[vertexIndex] = v; vertexIndex++; } } await this.addMeshWithColors({ mesh: { vertices, normals: undefined, indices: undefined, groups: aGroup, vertexCount, drawCount, vertexMapping }, meshes: undefined, values, isGeoTexture: false, mode: 'lines', webgl, ctx }); } } async addLineSegments(values, webgl, ctx) { const aStart = values.aStart.ref.value; const aEnd = values.aEnd.ref.value; const aGroup = values.aGroup.ref.value; const vertexCount = (values.uVertexCount.ref.value / 4) * 2; const drawCount = values.drawCount.ref.value / (2 * 3); if (this.options.linesAsTriangles) { const start = Vec3(); const end = Vec3(); const instanceCount = values.instanceCount.ref.value; const meshes = []; const radialSegments = 6; const topCap = true; const bottomCap = true; const vertexMapping = []; for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { const state = MeshBuilder.createState(512, 256); for (let i = 0, il = vertexCount * 2; i < il; i += 4) { v3fromArray(start, aStart, i * 3); v3fromArray(end, aEnd, i * 3); const group = aGroup[i / 4]; const radius = MeshExporter.getSize(values, instanceIndex, group, i / 4) * 0.03; const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments }; const vertexOffset = state.vertices.elementCount; addCylinder(state, start, end, 1, cylinderProps); if (instanceIndex === 0) { for (let vi = vertexOffset; vi < state.vertices.elementCount; ++vi) { vertexMapping.push(i); } } } meshes.push(MeshBuilder.getMesh(state)); } await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, mode: 'triangles', webgl, ctx, vertexMapping }); } else { const n = vertexCount / 2; const vertices = new Float32Array(n * 2 * 3); for (let i = 0; i < n; ++i) { arrayCopyOffset(vertices, aStart, i * 6, i * 4 * 3, 3); arrayCopyOffset(vertices, aEnd, i * 6 + 3, i * 4 * 3, 3); } await this.addMeshWithColors({ mesh: { vertices, normals: undefined, indices: undefined, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, mode: 'lines', webgl, ctx }); } } async addLines(values, webgl, ctx) { if (values.stripCount.ref.value !== 0) { await this.addLineStrips(values, webgl, ctx); } else { await this.addLineSegments(values, webgl, ctx); } } async addPoints(values, webgl, ctx) { const aPosition = values.aPosition.ref.value; const aGroup = values.aGroup.ref.value; const vertexCount = values.uVertexCount.ref.value; const drawCount = values.drawCount.ref.value; if (this.options.pointsAsTriangles) { const center = Vec3(); const instanceCount = values.instanceCount.ref.value; const meshes = []; const detail = 0; const vertexMapping = []; for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { const state = MeshBuilder.createState(512, 256); for (let i = 0; i < vertexCount; ++i) { v3fromArray(center, aPosition, i * 3); const group = aGroup[i]; const radius = MeshExporter.getSize(values, instanceIndex, group, i) * 0.03; const vertexOffset = state.vertices.elementCount; addSphere(state, center, radius, detail); if (instanceIndex === 0) { for (let vi = vertexOffset; vi < state.vertices.elementCount; ++vi) { vertexMapping.push(i); } } } meshes.push(MeshBuilder.getMesh(state)); } await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, mode: 'triangles', webgl, ctx, vertexMapping }); } else { await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: undefined, indices: undefined, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, mode: 'points', webgl, ctx }); } } async addSpheres(values, webgl, ctx) { const center = Vec3(); const aPosition = values.centerBuffer.ref.value; const aGroup = values.groupBuffer.ref.value; const instanceCount = values.instanceCount.ref.value; const vertexCount = values.uVertexCount.ref.value; const meshes = []; const sphereCount = (vertexCount / 6) * instanceCount; let detail; switch (this.options.primitivesQuality) { case 'auto': if (sphereCount < 2000) detail = 3; else if (sphereCount < 20000) detail = 2; else detail = 1; break; case 'high': detail = 3; break; case 'medium': detail = 2; break; case 'low': detail = 1; break; default: assertUnreachable(this.options.primitivesQuality); } const vertexMapping = []; for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { const state = MeshBuilder.createState(512, 256); for (let i = 0; i < sphereCount; ++i) { v3fromArray(center, aPosition, i * 3); const group = aGroup[i]; const radius = MeshExporter.getSize(values, instanceIndex, group, i); const vertexOffset = state.vertices.elementCount; addSphere(state, center, radius, detail); if (instanceIndex === 0) { for (let vi = vertexOffset; vi < state.vertices.elementCount; ++vi) { vertexMapping.push(i); } } } meshes.push(MeshBuilder.getMesh(state)); } await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, mode: 'triangles', webgl, ctx, vertexMapping }); } async addCylinders(values, webgl, ctx) { const start = Vec3(); const end = Vec3(); const dir = Vec3(); const aStart = values.aStart.ref.value; const aEnd = values.aEnd.ref.value; const aScale = values.aScale.ref.value; const aCap = values.aCap.ref.value; const aGroup = values.aGroup.ref.value; const instanceCount = values.instanceCount.ref.value; const vertexCount = values.uVertexCount.ref.value; const meshes = []; const cylinderCount = vertexCount / 6 * instanceCount; let radialSegments; switch (this.options.primitivesQuality) { case 'auto': if (cylinderCount < 2000) radialSegments = 36; else if (cylinderCount < 20000) radialSegments = 24; else radialSegments = 12; break; case 'high': radialSegments = 36; break; case 'medium': radialSegments = 24; break; case 'low': radialSegments = 12; break; default: assertUnreachable(this.options.primitivesQuality); } const vertexMapping = []; for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { const state = MeshBuilder.createState(512, 256); for (let i = 0; i < vertexCount; i += 6) { v3fromArray(start, aStart, i * 3); v3fromArray(end, aEnd, i * 3); v3sub(dir, end, start); const group = aGroup[i]; const radius = MeshExporter.getSize(values, instanceIndex, group, i) * aScale[i]; const cap = aCap[i]; let topCap = cap === 1 || cap === 3; let bottomCap = cap >= 2; if (v3dot(v3unitY, dir) > 0) { [bottomCap, topCap] = [topCap, bottomCap]; } const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments }; const vertexOffset = state.vertices.elementCount; addCylinder(state, start, end, 1, cylinderProps); if (instanceIndex === 0) { for (let vi = vertexOffset; vi < state.vertices.elementCount; ++vi) { vertexMapping.push(i); } } } meshes.push(MeshBuilder.getMesh(state)); } await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, mode: 'triangles', webgl, ctx, vertexMapping }); } async addTextureMesh(values, webgl, ctx) { if (!webgl.namedFramebuffers[GeoExportName]) { webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer(); } const framebuffer = webgl.namedFramebuffers[GeoExportName]; const [width, height] = values.uGeoTexDim.ref.value; const vertices = new Float32Array(width * height * 4); const normals = new Float32Array(width * height * 4); const groups = webgl.isWebGL2 ? new Uint8Array(width * height * 4) : new Float32Array(width * height * 4); framebuffer.bind(); values.tPosition.ref.value.attachFramebuffer(framebuffer, 0); webgl.readPixels(0, 0, width, height, vertices); values.tNormal.ref.value.attachFramebuffer(framebuffer, 0); webgl.readPixels(0, 0, width, height, normals); values.tGroup.ref.value.attachFramebuffer(framebuffer, 0); webgl.readPixels(0, 0, width, height, groups); const vertexCount = values.uVertexCount.ref.value; const drawCount = values.drawCount.ref.value; await this.addMeshWithColors({ mesh: { vertices, normals, indices: undefined, groups, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: true, mode: 'triangles', webgl, ctx }); } add(renderObject, webgl, ctx) { if (!renderObject.state.visible && !this.options.includeHidden) return; if (renderObject.values.drawCount.ref.value === 0) return; if (renderObject.values.instanceCount.ref.value === 0) return; switch (renderObject.type) { case 'mesh': return this.addMesh(renderObject.values, webgl, ctx); case 'lines': return this.addLines(renderObject.values, webgl, ctx); case 'points': return this.addPoints(renderObject.values, webgl, ctx); case 'spheres': return this.addSpheres(renderObject.values, webgl, ctx); case 'cylinders': return this.addCylinders(renderObject.values, webgl, ctx); case 'texture-mesh': return this.addTextureMesh(renderObject.values, webgl, ctx); } } }