molstar
Version:
A comprehensive macromolecular library.
301 lines • 16.2 kB
JavaScript
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.GlbExporter = void 0;
var tslib_1 = require("tslib");
var ascii_1 = require("../../mol-io/common/ascii");
var binary_1 = require("../../mol-io/common/binary");
var linear_algebra_1 = require("../../mol-math/linear-algebra");
var version_1 = require("../../mol-plugin/version");
var color_1 = require("../../mol-util/color/color");
var array_1 = require("../../mol-util/array");
var mesh_exporter_1 = require("./mesh-exporter");
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
var v3fromArray = linear_algebra_1.Vec3.fromArray;
var v3normalize = linear_algebra_1.Vec3.normalize;
var v3toArray = linear_algebra_1.Vec3.toArray;
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
var UNSIGNED_BYTE = 5121;
var UNSIGNED_INT = 5125;
var FLOAT = 5126;
var ARRAY_BUFFER = 34962;
var ELEMENT_ARRAY_BUFFER = 34963;
var GlbExporter = /** @class */ (function (_super) {
(0, tslib_1.__extends)(GlbExporter, _super);
function GlbExporter(style, boundingBox) {
var _this = _super.call(this) || this;
_this.style = style;
_this.fileExtension = 'glb';
_this.nodes = [];
_this.meshes = [];
_this.accessors = [];
_this.bufferViews = [];
_this.binaryBuffer = [];
_this.byteOffset = 0;
var tmpV = (0, linear_algebra_1.Vec3)();
linear_algebra_1.Vec3.add(tmpV, boundingBox.min, boundingBox.max);
linear_algebra_1.Vec3.scale(tmpV, tmpV, -0.5);
_this.centerTransform = linear_algebra_1.Mat4.fromTranslation((0, linear_algebra_1.Mat4)(), tmpV);
return _this;
}
GlbExporter.vec3MinMax = function (a) {
var min = [Infinity, Infinity, Infinity];
var max = [-Infinity, -Infinity, -Infinity];
for (var i = 0, il = a.length; i < il; i += 3) {
for (var j = 0; j < 3; ++j) {
min[j] = Math.min(a[i + j], min[j]);
max[j] = Math.max(a[i + j], max[j]);
}
}
return [min, max];
};
GlbExporter.prototype.addBuffer = function (buffer, componentType, type, count, target, min, max, normalized) {
this.binaryBuffer.push(buffer);
var bufferViewOffset = this.bufferViews.length;
this.bufferViews.push({
buffer: 0,
byteOffset: this.byteOffset,
byteLength: buffer.byteLength,
target: target
});
this.byteOffset += buffer.byteLength;
var accessorOffset = this.accessors.length;
this.accessors.push({
bufferView: bufferViewOffset,
byteOffset: 0,
componentType: componentType,
count: count,
type: type,
min: min,
max: max,
normalized: normalized
});
return accessorOffset;
};
GlbExporter.prototype.addGeometryBuffers = function (vertices, normals, indices, vertexCount, drawCount, isGeoTexture) {
var tmpV = (0, linear_algebra_1.Vec3)();
var stride = isGeoTexture ? 4 : 3;
var vertexArray = new Float32Array(vertexCount * 3);
var normalArray = new Float32Array(vertexCount * 3);
var indexArray;
// position
for (var i = 0; i < vertexCount; ++i) {
v3fromArray(tmpV, vertices, i * stride);
v3toArray(tmpV, vertexArray, i * 3);
}
// normal
for (var i = 0; i < vertexCount; ++i) {
v3fromArray(tmpV, normals, i * stride);
v3normalize(tmpV, tmpV);
v3toArray(tmpV, normalArray, i * 3);
}
// face
if (!isGeoTexture) {
indexArray = indices.slice(0, drawCount);
}
var _a = GlbExporter.vec3MinMax(vertexArray), vertexMin = _a[0], vertexMax = _a[1];
var vertexBuffer = vertexArray.buffer;
var normalBuffer = normalArray.buffer;
var indexBuffer = isGeoTexture ? undefined : indexArray.buffer;
if (!binary_1.IsNativeEndianLittle) {
vertexBuffer = (0, binary_1.flipByteOrder)(new Uint8Array(vertexBuffer), 4);
normalBuffer = (0, binary_1.flipByteOrder)(new Uint8Array(normalBuffer), 4);
if (!isGeoTexture)
indexBuffer = (0, binary_1.flipByteOrder)(new Uint8Array(indexBuffer), 4);
}
return {
vertexAccessorIndex: this.addBuffer(vertexBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER, vertexMin, vertexMax),
normalAccessorIndex: this.addBuffer(normalBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER),
indexAccessorIndex: isGeoTexture ? undefined : this.addBuffer(indexBuffer, UNSIGNED_INT, 'SCALAR', drawCount, ELEMENT_ARRAY_BUFFER)
};
};
GlbExporter.prototype.addColorBuffer = function (values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors) {
var groupCount = values.uGroupCount.ref.value;
var uAlpha = values.uAlpha.ref.value;
var dTransparency = values.dTransparency.ref.value;
var tTransparency = values.tTransparency.ref.value;
var colorArray = new Uint8Array(vertexCount * 4);
for (var i = 0; i < vertexCount; ++i) {
var color = GlbExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, i);
var alpha = uAlpha;
if (dTransparency) {
var group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
var transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
alpha *= 1 - transparency;
}
color = color_1.Color.sRGBToLinear(color);
color_1.Color.toArray(color, colorArray, i * 4);
colorArray[i * 4 + 3] = Math.round(alpha * 255);
}
var colorBuffer = colorArray.buffer;
if (!binary_1.IsNativeEndianLittle) {
colorBuffer = (0, binary_1.flipByteOrder)(new Uint8Array(colorBuffer), 4);
}
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
};
GlbExporter.prototype.addMeshWithColors = function (input) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
var mesh, values, isGeoTexture, webgl, ctx, t, colorType, dTransparency, aTransform, instanceCount, interpolatedColors, stride, sameGeometryBuffers, sameColorBuffer, vertexAccessorIndex, normalAccessorIndex, indexAccessorIndex, colorAccessorIndex, meshIndex, instanceIndex, _a, vertices, normals, indices, groups, vertexCount, drawCount, accessorIndices, node;
return (0, tslib_1.__generator)(this, function (_b) {
switch (_b.label) {
case 0:
mesh = input.mesh, values = input.values, isGeoTexture = input.isGeoTexture, webgl = input.webgl, ctx = input.ctx;
t = (0, linear_algebra_1.Mat4)();
colorType = values.dColorType.ref.value;
dTransparency = values.dTransparency.ref.value;
aTransform = values.aTransform.ref.value;
instanceCount = values.uInstanceCount.ref.value;
if (colorType === 'volume' || colorType === 'volumeInstance') {
stride = isGeoTexture ? 4 : 3;
interpolatedColors = GlbExporter.getInterpolatedColors(mesh.vertices, mesh.vertexCount, values, stride, colorType, webgl);
}
sameGeometryBuffers = mesh !== undefined;
sameColorBuffer = sameGeometryBuffers && colorType !== 'instance' && !colorType.endsWith('Instance') && !dTransparency;
return [4 /*yield*/, ctx.update({ isIndeterminate: false, current: 0, max: instanceCount })];
case 1:
_b.sent();
instanceIndex = 0;
_b.label = 2;
case 2:
if (!(instanceIndex < instanceCount)) return [3 /*break*/, 6];
if (!ctx.shouldUpdate) return [3 /*break*/, 4];
return [4 /*yield*/, ctx.update({ current: instanceIndex + 1 })];
case 3:
_b.sent();
_b.label = 4;
case 4:
// create a glTF mesh if needed
if (instanceIndex === 0 || !sameGeometryBuffers || !sameColorBuffer) {
_a = GlbExporter.getInstance(input, instanceIndex), vertices = _a.vertices, normals = _a.normals, indices = _a.indices, groups = _a.groups, vertexCount = _a.vertexCount, drawCount = _a.drawCount;
// create geometry buffers if needed
if (instanceIndex === 0 || !sameGeometryBuffers) {
accessorIndices = this.addGeometryBuffers(vertices, normals, indices, vertexCount, drawCount, isGeoTexture);
vertexAccessorIndex = accessorIndices.vertexAccessorIndex;
normalAccessorIndex = accessorIndices.normalAccessorIndex;
indexAccessorIndex = accessorIndices.indexAccessorIndex;
}
// create a color buffer if needed
if (instanceIndex === 0 || !sameColorBuffer) {
colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors);
}
// glTF mesh
meshIndex = this.meshes.length;
this.meshes.push({
primitives: [{
attributes: {
POSITION: vertexAccessorIndex,
NORMAL: normalAccessorIndex,
COLOR_0: colorAccessorIndex
},
indices: indexAccessorIndex,
material: 0
}]
});
}
// node
linear_algebra_1.Mat4.fromArray(t, aTransform, instanceIndex * 16);
linear_algebra_1.Mat4.mul(t, this.centerTransform, t);
node = {
mesh: meshIndex,
matrix: t.slice()
};
this.nodes.push(node);
_b.label = 5;
case 5:
++instanceIndex;
return [3 /*break*/, 2];
case 6: return [2 /*return*/];
}
});
});
};
GlbExporter.prototype.getData = function () {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
var binaryBufferLength, gltf, createChunk, jsonString, jsonBuffer, _a, jsonChunk, jsonChunkLength, _b, binaryChunk, binaryChunkLength, glbBufferLength, header, headerDataView, glbBuffer, glb, offset, _i, glbBuffer_1, buffer;
return (0, tslib_1.__generator)(this, function (_c) {
binaryBufferLength = this.byteOffset;
gltf = {
asset: {
version: '2.0',
generator: "Mol* " + version_1.PLUGIN_VERSION
},
scenes: [{
nodes: (0, array_1.fillSerial)(new Array(this.nodes.length))
}],
nodes: this.nodes,
meshes: this.meshes,
buffers: [{
byteLength: binaryBufferLength,
}],
bufferViews: this.bufferViews,
accessors: this.accessors,
materials: [{
pbrMetallicRoughness: {
baseColorFactor: [1, 1, 1, 1],
metallicFactor: this.style.metalness,
roughnessFactor: this.style.roughness
}
}]
};
createChunk = function (chunkType, data, byteLength, padChar) {
var padding = null;
if (byteLength % 4 !== 0) {
var pad = 4 - (byteLength % 4);
byteLength += pad;
padding = new Uint8Array(pad);
padding.fill(padChar);
}
var preamble = new ArrayBuffer(8);
var preambleDataView = new DataView(preamble);
preambleDataView.setUint32(0, byteLength, true);
preambleDataView.setUint32(4, chunkType, true);
var chunk = (0, tslib_1.__spreadArray)([preamble], data, true);
if (padding) {
chunk.push(padding.buffer);
}
return [chunk, 8 + byteLength];
};
jsonString = JSON.stringify(gltf);
jsonBuffer = new Uint8Array(jsonString.length);
(0, ascii_1.asciiWrite)(jsonBuffer, jsonString);
_a = createChunk(0x4E4F534A, [jsonBuffer.buffer], jsonBuffer.length, 0x20), jsonChunk = _a[0], jsonChunkLength = _a[1];
_b = createChunk(0x004E4942, this.binaryBuffer, binaryBufferLength, 0x00), binaryChunk = _b[0], binaryChunkLength = _b[1];
glbBufferLength = 12 + jsonChunkLength + binaryChunkLength;
header = new ArrayBuffer(12);
headerDataView = new DataView(header);
headerDataView.setUint32(0, 0x46546C67, true); // magic number "glTF"
headerDataView.setUint32(4, 2, true); // version
headerDataView.setUint32(8, glbBufferLength, true); // length
glbBuffer = (0, tslib_1.__spreadArray)((0, tslib_1.__spreadArray)([header], jsonChunk, true), binaryChunk, true);
glb = new Uint8Array(glbBufferLength);
offset = 0;
for (_i = 0, glbBuffer_1 = glbBuffer; _i < glbBuffer_1.length; _i++) {
buffer = glbBuffer_1[_i];
glb.set(new Uint8Array(buffer), offset);
offset += buffer.byteLength;
}
return [2 /*return*/, { glb: glb }];
});
});
};
GlbExporter.prototype.getBlob = function (ctx) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
var _a;
return (0, tslib_1.__generator)(this, function (_b) {
switch (_b.label) {
case 0:
_a = Blob.bind;
return [4 /*yield*/, this.getData()];
case 1: return [2 /*return*/, new (_a.apply(Blob, [void 0, [(_b.sent()).glb], { type: 'model/gltf-binary' }]))()];
}
});
});
};
return GlbExporter;
}(mesh_exporter_1.MeshExporter));
exports.GlbExporter = GlbExporter;
//# sourceMappingURL=glb-exporter.js.map
;