molstar
Version:
A comprehensive macromolecular library.
710 lines (709 loc) • 34.6 kB
JavaScript
/**
* 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);
}
}
}