molstar
Version:
A comprehensive macromolecular library.
215 lines (214 loc) • 10.6 kB
JavaScript
/**
* Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.UsdzExporter = void 0;
const ascii_1 = require("../../mol-io/common/ascii");
const linear_algebra_1 = require("../../mol-math/linear-algebra");
const version_1 = require("../../mol-plugin/version");
const mol_util_1 = require("../../mol-util");
const color_1 = require("../../mol-util/color/color");
const zip_1 = require("../../mol-util/zip/zip");
const mesh_exporter_1 = require("./mesh-exporter");
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = linear_algebra_1.Vec3.fromArray;
const v3transformMat4 = linear_algebra_1.Vec3.transformMat4;
const v3transformMat3 = linear_algebra_1.Vec3.transformMat3;
const mat3directionTransform = linear_algebra_1.Mat3.directionTransform;
class UsdzExporter extends mesh_exporter_1.MeshExporter {
addMaterial(color, alpha, metalness, roughness) {
const hash = `${color}|${alpha}|${metalness}|${roughness}`;
if (this.materialMap.has(hash))
return this.materialMap.get(hash);
const materialKey = this.materialMap.size;
this.materialMap.set(hash, materialKey);
const [r, g, b] = color_1.Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
this.materials.push(`
def Material "material${materialKey}"
{
token outputs:surface.connect = </material${materialKey}/shader.outputs:surface>
def Shader "shader"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (${r},${g},${b})
float inputs:opacity = ${alpha}
float inputs:metallic = ${metalness}
float inputs:roughness = ${roughness}
token outputs:surface
}
}
`);
return materialKey;
}
async addMeshWithColors(input) {
const { mesh, values, isGeoTexture, mode, webgl, ctx } = input;
if (mode !== 'triangles')
return;
const t = (0, linear_algebra_1.Mat4)();
const n = (0, linear_algebra_1.Mat3)();
const tmpV = (0, linear_algebra_1.Vec3)();
const stride = isGeoTexture ? 4 : 3;
const colorType = values.dColorType.ref.value;
const overpaintType = values.dOverpaintType.ref.value;
const transparencyType = values.dTransparencyType.ref.value;
const uAlpha = values.uAlpha.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const metalness = values.uMetalness.ref.value;
const roughness = values.uRoughness.ref.value;
let interpolatedColors;
if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) {
interpolatedColors = UsdzExporter.getInterpolatedColors(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType });
}
let interpolatedOverpaint;
if (webgl && mesh && overpaintType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: overpaintType });
}
let interpolatedTransparency;
if (webgl && mesh && transparencyType === 'volumeInstance') {
const stride = isGeoTexture ? 4 : 3;
interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl, { vertices: mesh.vertices, vertexCount: mesh.vertexCount, values, stride, colorType: transparencyType });
}
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
if (ctx.shouldUpdate)
await ctx.update({ current: instanceIndex + 1 });
const { vertices, normals, indices, groups, vertexCount, drawCount } = UsdzExporter.getInstance(input, instanceIndex);
linear_algebra_1.Mat4.fromArray(t, aTransform, instanceIndex * 16);
linear_algebra_1.Mat4.mul(t, this.centerTransform, t);
mat3directionTransform(n, t);
const vertexBuilder = mol_util_1.StringBuilder.create();
const normalBuilder = mol_util_1.StringBuilder.create();
const indexBuilder = mol_util_1.StringBuilder.create();
// position
for (let i = 0; i < vertexCount; ++i) {
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
mol_util_1.StringBuilder.writeSafe(vertexBuilder, (i === 0) ? '(' : ',(');
mol_util_1.StringBuilder.writeFloat(vertexBuilder, tmpV[0], 10000);
mol_util_1.StringBuilder.writeSafe(vertexBuilder, ',');
mol_util_1.StringBuilder.writeFloat(vertexBuilder, tmpV[1], 10000);
mol_util_1.StringBuilder.writeSafe(vertexBuilder, ',');
mol_util_1.StringBuilder.writeFloat(vertexBuilder, tmpV[2], 10000);
mol_util_1.StringBuilder.writeSafe(vertexBuilder, ')');
}
// normal
for (let i = 0; i < vertexCount; ++i) {
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
mol_util_1.StringBuilder.writeSafe(normalBuilder, (i === 0) ? '(' : ',(');
mol_util_1.StringBuilder.writeFloat(normalBuilder, tmpV[0], 100);
mol_util_1.StringBuilder.writeSafe(normalBuilder, ',');
mol_util_1.StringBuilder.writeFloat(normalBuilder, tmpV[1], 100);
mol_util_1.StringBuilder.writeSafe(normalBuilder, ',');
mol_util_1.StringBuilder.writeFloat(normalBuilder, tmpV[2], 100);
mol_util_1.StringBuilder.writeSafe(normalBuilder, ')');
}
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture, mode };
// face
for (let i = 0; i < drawCount; ++i) {
const v = isGeoTexture ? i : indices[i];
if (i > 0)
mol_util_1.StringBuilder.writeSafe(indexBuilder, ',');
mol_util_1.StringBuilder.writeInteger(indexBuilder, v);
}
// color
const quantizedColors = new Uint8Array(drawCount * 3);
for (let i = 0; i < drawCount; i += 3) {
const v = isGeoTexture ? i : indices[i];
const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
color_1.Color.toArray(color, quantizedColors, i);
}
UsdzExporter.quantizeColors(quantizedColors, vertexCount);
// material
const faceIndicesByMaterial = new Map();
for (let i = 0; i < drawCount; i += 3) {
const color = color_1.Color.fromArray(quantizedColors, i);
const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
const materialKey = this.addMaterial(color, alpha, metalness, roughness);
let faceIndices = faceIndicesByMaterial.get(materialKey);
if (faceIndices === undefined) {
faceIndices = [];
faceIndicesByMaterial.set(materialKey, faceIndices);
}
faceIndices.push(i / 3);
}
// If this mesh uses only one material, bind it to the material directly.
// Otherwise, use GeomSubsets to bind it to multiple materials.
let materialBinding;
if (faceIndicesByMaterial.size === 1) {
const materialKey = faceIndicesByMaterial.keys().next().value;
materialBinding = `rel material:binding = </material${materialKey}>`;
}
else {
const geomSubsets = [];
faceIndicesByMaterial.forEach((faceIndices, materialKey) => {
geomSubsets.push(`
def GeomSubset "g${materialKey}"
{
uniform token elementType = "face"
uniform token familyName = "materialBind"
int[] indices = [${faceIndices.join(',')}]
rel material:binding = </material${materialKey}>
}
`);
});
materialBinding = geomSubsets.join('');
}
this.meshes.push(`
def Mesh "mesh${this.meshes.length}"
{
int[] faceVertexCounts = [${new Array(drawCount / 3).fill(3).join(',')}]
int[] faceVertexIndices = [${mol_util_1.StringBuilder.getString(indexBuilder)}]
point3f[] points = [${mol_util_1.StringBuilder.getString(vertexBuilder)}]
normal3f[] primvars:normals = [${mol_util_1.StringBuilder.getString(normalBuilder)}] (
interpolation = "vertex"
)
uniform token subdivisionScheme = "none"
${materialBinding}
}
`);
}
}
async getData(ctx) {
const header = `#usda 1.0
(
customLayerData = {
string creator = "Mol* ${version_1.PLUGIN_VERSION}"
}
metersPerUnit = 1
)
`;
const usda = [header, ...this.materials, ...this.meshes].join('');
const usdaData = new Uint8Array(usda.length);
(0, ascii_1.asciiWrite)(usdaData, usda);
const zipDataObj = {
['model.usda']: usdaData
};
return {
usdz: await (0, zip_1.zip)(ctx, zipDataObj, true)
};
}
async getBlob(ctx) {
const { usdz } = await this.getData(ctx);
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
}
constructor(boundingBox, radius) {
super();
this.fileExtension = 'usdz';
this.meshes = [];
this.materials = [];
this.materialMap = new Map();
const t = (0, linear_algebra_1.Mat4)();
// scale the model so that it fits within 1 meter
linear_algebra_1.Mat4.fromUniformScaling(t, Math.min(1 / (radius * 2), 1));
// translate the model so that it sits on the ground plane (y = 0)
linear_algebra_1.Mat4.translate(t, t, linear_algebra_1.Vec3.create(-(boundingBox.min[0] + boundingBox.max[0]) / 2, -boundingBox.min[1], -(boundingBox.min[2] + boundingBox.max[2]) / 2));
this.centerTransform = t;
}
}
exports.UsdzExporter = UsdzExporter;
;