molstar
Version:
A comprehensive macromolecular library.
382 lines • 19.7 kB
JavaScript
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { __awaiter, __generator } from "tslib";
import { sort, arraySwap } from '../../mol-data/util';
import { getTrilinearlyInterpolated } from '../../mol-geo/geometry/mesh/color-smoothing';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
import { Vec3 } from '../../mol-math/linear-algebra';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
var GeoExportName = 'geo-export';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
var v3fromArray = Vec3.fromArray;
var MeshExporter = /** @class */ (function () {
function MeshExporter() {
}
MeshExporter.getSizeFromTexture = function (tSize, i) {
var r = tSize.array[i * 3];
var g = tSize.array[i * 3 + 1];
var b = tSize.array[i * 3 + 2];
return decodeFloatRGB(r, g, b) / sizeDataFactor;
};
MeshExporter.getSize = function (values, instanceIndex, group) {
var tSize = values.tSize.ref.value;
var 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':
var groupCount = values.uGroupCount.ref.value;
size = MeshExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group);
break;
}
return size * values.uSizeFactor.ref.value;
};
MeshExporter.getGroup = function (groups, i) {
var i4 = i * 4;
var r = groups[i4];
var g = groups[i4 + 1];
var b = groups[i4 + 2];
if (groups instanceof Float32Array) {
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
}
return decodeFloatRGB(r, g, b);
};
MeshExporter.getInterpolatedColors = function (vertices, vertexCount, values, stride, colorType, webgl) {
var colorGridTransform = values.uColorGridTransform.ref.value;
var colorGridDim = values.uColorGridDim.ref.value;
var colorTexDim = values.uColorTexDim.ref.value;
var aTransform = values.aTransform.ref.value;
var instanceCount = values.uInstanceCount.ref.value;
if (!webgl.namedFramebuffers[GeoExportName]) {
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
}
var framebuffer = webgl.namedFramebuffers[GeoExportName];
var width = colorTexDim[0], height = colorTexDim[1];
var colorGrid = new Uint8Array(width * height * 4);
framebuffer.bind();
values.tColorGrid.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, colorGrid);
var interpolated = getTrilinearlyInterpolated({ vertexCount: vertexCount, instanceCount: instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType: colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
return interpolated.array;
};
MeshExporter.quantizeColors = function (colorArray, vertexCount) {
if (vertexCount <= 1024)
return;
var rgb = Vec3();
var min = Vec3();
var max = Vec3();
var sum = Vec3();
var colorMap = new Map();
var colorComparers = [
function (colors, i, j) { return (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]); },
function (colors, i, j) { return (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]); },
function (colors, i, j) { return (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]); },
];
var medianCut = function (colors, l, r, depth) {
if (l > r)
return;
if (l === r || depth >= 10) {
// Find the average color.
Vec3.set(sum, 0, 0, 0);
for (var 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)));
var averageColor = Color.fromArray(rgb, 0);
for (var 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 (var i = l; i <= r; ++i) {
Color.toVec3(rgb, colors[i]);
for (var j = 0; j < 3; ++j) {
Vec3.min(min, min, rgb);
Vec3.max(max, max, rgb);
}
}
var 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);
var 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.
var colorSet = new Set();
for (var i = 0; i < vertexCount; ++i) {
colorSet.add(Color.fromArray(colorArray, i * 3));
}
var colors = Array.from(colorSet);
medianCut(colors, 0, colors.length - 1, 0);
// Map actual colors to quantized colors.
for (var i = 0; i < vertexCount; ++i) {
var color = colorMap.get(Color.fromArray(colorArray, i * 3));
Color.toArray(color, colorArray, i * 3);
}
};
MeshExporter.getInstance = function (input, instanceIndex) {
var mesh = input.mesh, meshes = input.meshes;
if (mesh !== undefined) {
return mesh;
}
else {
var mesh_1 = meshes[instanceIndex];
return {
vertices: mesh_1.vertexBuffer.ref.value,
normals: mesh_1.normalBuffer.ref.value,
indices: mesh_1.indexBuffer.ref.value,
groups: mesh_1.groupBuffer.ref.value,
vertexCount: mesh_1.vertexCount,
drawCount: mesh_1.triangleCount * 3
};
}
};
MeshExporter.getColor = function (values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, vertexIndex) {
var groupCount = values.uGroupCount.ref.value;
var colorType = values.dColorType.ref.value;
var uColor = values.uColor.ref.value;
var tColor = values.tColor.ref.value.array;
var dOverpaint = values.dOverpaint.ref.value;
var tOverpaint = values.tOverpaint.ref.value.array;
var color;
switch (colorType) {
case 'uniform':
color = Color.fromNormalizedArray(uColor, 0);
break;
case 'instance':
color = Color.fromArray(tColor, instanceIndex * 3);
break;
case 'group': {
var group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
color = Color.fromArray(tColor, group * 3);
break;
}
case 'groupInstance': {
var group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
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 (dOverpaint) {
var group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
var overpaintColor = Color.fromArray(tOverpaint, (instanceIndex * groupCount + group) * 4);
var overpaintAlpha = tOverpaint[(instanceIndex * groupCount + group) * 4 + 3] / 255;
color = Color.interpolate(color, overpaintColor, overpaintAlpha);
}
return color;
};
MeshExporter.prototype.addMesh = function (values, webgl, ctx) {
return __awaiter(this, void 0, void 0, function () {
var aPosition, aNormal, aGroup, originalData, indices, vertexCount, drawCount;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
aPosition = values.aPosition.ref.value;
aNormal = values.aNormal.ref.value;
aGroup = values.aGroup.ref.value;
originalData = Mesh.getOriginalData(values);
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;
}
return [4 /*yield*/, this.addMeshWithColors({ mesh: { vertices: aPosition, normals: aNormal, indices: indices, groups: aGroup, vertexCount: vertexCount, drawCount: drawCount }, meshes: undefined, values: values, isGeoTexture: false, webgl: webgl, ctx: ctx })];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
MeshExporter.prototype.addLines = function (values, webgl, ctx) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/];
});
});
};
MeshExporter.prototype.addPoints = function (values, webgl, ctx) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/];
});
});
};
MeshExporter.prototype.addSpheres = function (values, webgl, ctx) {
return __awaiter(this, void 0, void 0, function () {
var center, aPosition, aGroup, instanceCount, vertexCount, meshes, sphereCount, detail, instanceIndex, state, i, group, radius;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
center = Vec3();
aPosition = values.aPosition.ref.value;
aGroup = values.aGroup.ref.value;
instanceCount = values.instanceCount.ref.value;
vertexCount = values.uVertexCount.ref.value;
meshes = [];
sphereCount = vertexCount / 4 * instanceCount;
if (sphereCount < 2000)
detail = 3;
else if (sphereCount < 20000)
detail = 2;
else
detail = 1;
for (instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
state = MeshBuilder.createState(512, 256);
for (i = 0; i < vertexCount; i += 4) {
v3fromArray(center, aPosition, i * 3);
group = aGroup[i];
radius = MeshExporter.getSize(values, instanceIndex, group);
state.currentGroup = group;
addSphere(state, center, radius, detail);
}
meshes.push(MeshBuilder.getMesh(state));
}
return [4 /*yield*/, this.addMeshWithColors({ mesh: undefined, meshes: meshes, values: values, isGeoTexture: false, webgl: webgl, ctx: ctx })];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
MeshExporter.prototype.addCylinders = function (values, webgl, ctx) {
return __awaiter(this, void 0, void 0, function () {
var start, end, aStart, aEnd, aScale, aCap, aGroup, instanceCount, vertexCount, meshes, cylinderCount, radialSegments, instanceIndex, state, i, group, radius, cap, topCap, bottomCap, cylinderProps;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
start = Vec3();
end = Vec3();
aStart = values.aStart.ref.value;
aEnd = values.aEnd.ref.value;
aScale = values.aScale.ref.value;
aCap = values.aCap.ref.value;
aGroup = values.aGroup.ref.value;
instanceCount = values.instanceCount.ref.value;
vertexCount = values.uVertexCount.ref.value;
meshes = [];
cylinderCount = vertexCount / 6 * instanceCount;
if (cylinderCount < 2000)
radialSegments = 36;
else if (cylinderCount < 20000)
radialSegments = 24;
else
radialSegments = 12;
for (instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
state = MeshBuilder.createState(512, 256);
for (i = 0; i < vertexCount; i += 6) {
v3fromArray(start, aStart, i * 3);
v3fromArray(end, aEnd, i * 3);
group = aGroup[i];
radius = MeshExporter.getSize(values, instanceIndex, group) * aScale[i];
cap = aCap[i];
topCap = cap === 1 || cap === 3;
bottomCap = cap >= 2;
cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap: topCap, bottomCap: bottomCap, radialSegments: radialSegments };
state.currentGroup = aGroup[i];
addCylinder(state, start, end, 1, cylinderProps);
}
meshes.push(MeshBuilder.getMesh(state));
}
return [4 /*yield*/, this.addMeshWithColors({ mesh: undefined, meshes: meshes, values: values, isGeoTexture: false, webgl: webgl, ctx: ctx })];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
MeshExporter.prototype.addTextureMesh = function (values, webgl, ctx) {
return __awaiter(this, void 0, void 0, function () {
var framebuffer, _a, width, height, vertices, normals, groups, vertexCount, drawCount;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!webgl.namedFramebuffers[GeoExportName]) {
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
}
framebuffer = webgl.namedFramebuffers[GeoExportName];
_a = values.uGeoTexDim.ref.value, width = _a[0], height = _a[1];
vertices = new Float32Array(width * height * 4);
normals = new Float32Array(width * height * 4);
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);
vertexCount = values.uVertexCount.ref.value;
drawCount = values.drawCount.ref.value;
return [4 /*yield*/, this.addMeshWithColors({ mesh: { vertices: vertices, normals: normals, indices: undefined, groups: groups, vertexCount: vertexCount, drawCount: drawCount }, meshes: undefined, values: values, isGeoTexture: true, webgl: webgl, ctx: ctx })];
case 1:
_b.sent();
return [2 /*return*/];
}
});
});
};
MeshExporter.prototype.add = function (renderObject, webgl, ctx) {
if (!renderObject.state.visible)
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);
}
};
return MeshExporter;
}());
export { MeshExporter };
//# sourceMappingURL=mesh-exporter.js.map