import macro from 'vtk.js/Sources/macros'; import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import atomElem from 'vtk.js/Utilities/XMLConverter/chemistry/elements.json';
const { vtkErrorMacro, vtkDebugMacro } = macro;
const ATOMS = {}; atomElem.atoms.forEach((a) => { ATOMS[a.atomicNumber] = a; });
function vtkMoleculeToRepresentation(publicAPI, model) { const bondPositionData = []; const bondScaleData = []; const bondOrientationData = []; const bondColorData = [];
model.classHierarchy.push('vtkMoleculeToRepresentation');
function addBond( position, orientation, length, color = [1.0, 1.0, 1.0], radius = model.bondRadius ) { bondScaleData.push(length); bondScaleData.push(radius);
bondOrientationData.push(orientation[0]); bondOrientationData.push(orientation[1]); bondOrientationData.push(orientation[2]);
bondPositionData.push(position[0]); bondPositionData.push(position[1]); bondPositionData.push(position[2]);
for (let i = 0; i < color.length; ++i) { bondColorData.push(color[i] * 255); } }
publicAPI.requestData = (inData, outData) => { const moleculedata = inData[0];
if (!moleculedata) { vtkErrorMacro('Invalid or missing input'); return 1; }
const SphereData = vtkPolyData.newInstance(); const StickData = vtkPolyData.newInstance();
let numPts = 0; let numBonds = 0; let pointsArray = null; let atomicNumber = null; let bondIndex = null; let bondOrder = null;
bondPositionData.length = 0; bondScaleData.length = 0; bondOrientationData.length = 0; bondColorData.length = 0;
if (moleculedata.getAtoms()) { if (moleculedata.getAtoms().coords !== undefined) { if (moleculedata.getAtoms().coords['3d'] !== undefined) { pointsArray = moleculedata.getAtoms().coords['3d']; numPts = pointsArray.length / 3; } } if (moleculedata.getAtoms().elements !== undefined) { if (moleculedata.getAtoms().elements.number !== undefined) { atomicNumber = moleculedata.getAtoms().elements.number; } } } if (moleculedata.getBonds()) { if (moleculedata.getBonds().connections !== undefined) { if (moleculedata.getBonds().connections.index !== undefined) { bondIndex = moleculedata.getBonds().connections.index; numBonds = bondIndex.length / 2; } } if (moleculedata.getBonds().order !== undefined) { bondOrder = moleculedata.getBonds().order; } }
const pointsData = []; const scaleData = []; const colorData = [];
const radiusArray = []; const covalentArray = []; const colorArray = [];
vtkDebugMacro('Checking for bonds with tolerance ', model.tolerance);
let ptsIdx = 0; for (let i = 0; i < numPts; i++) { if (atomicNumber) { radiusArray.push(ATOMS[atomicNumber[i]][model.radiusType]); covalentArray.push(ATOMS[atomicNumber[i]].radiusCovalent); colorArray.push(ATOMS[atomicNumber[i]].elementColor[0]); colorArray.push(ATOMS[atomicNumber[i]].elementColor[1]); colorArray.push(ATOMS[atomicNumber[i]].elementColor[2]); }
if (model.hideElements.indexOf(ATOMS[atomicNumber[i]].id) !== -1) { continue; }
ptsIdx = i * 3; pointsData.push(pointsArray[ptsIdx]); pointsData.push(pointsArray[ptsIdx + 1]); pointsData.push(pointsArray[ptsIdx + 2]);
if (radiusArray.length > 0) { scaleData.push(radiusArray[i] * model.atomicRadiusScaleFactor); }
if (colorArray.length > 0) { ptsIdx = i * 3; colorData.push(colorArray[ptsIdx] * 255); colorData.push(colorArray[ptsIdx + 1] * 255); colorData.push(colorArray[ptsIdx + 2] * 255); } }
if (!bondIndex) { bondIndex = []; bondOrder = []; for (let i = 0; i < numPts; i++) { for (let j = i + 1; j < numPts; j++) { const cutoff = covalentArray[i] + covalentArray[j] + model.tolerance; const jPtsIdx = j * 3; const iPtsIdx = i * 3; const diff = [ pointsArray[jPtsIdx], pointsArray[jPtsIdx + 1], pointsArray[jPtsIdx + 2], ]; diff[0] -= pointsArray[iPtsIdx]; diff[1] -= pointsArray[iPtsIdx + 1]; diff[2] -= pointsArray[iPtsIdx + 2];
if ( Math.abs(diff[0]) > cutoff || Math.abs(diff[1]) > cutoff || Math.abs(diff[2]) > cutoff ) { continue; }
const cutoffSq = cutoff * cutoff; const diffsq = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]; if (diffsq < cutoffSq && diffsq > 0.1) { bondIndex.push(i); bondIndex.push(j); bondOrder.push(1); } } } numBonds = bondIndex.length / 2; }
for (let index = 0; index < numBonds; index++) { const i = bondIndex[index * 2]; const j = bondIndex[index * 2 + 1];
if ( model.hideElements.indexOf(ATOMS[atomicNumber[i]].id) !== -1 || model.hideElements.indexOf(ATOMS[atomicNumber[j]].id) !== -1 ) { continue; }
const jPtsIdx = j * 3; const iPtsIdx = i * 3; const diff = [ pointsArray[jPtsIdx], pointsArray[jPtsIdx + 1], pointsArray[jPtsIdx + 2], ]; diff[0] -= pointsArray[iPtsIdx]; diff[1] -= pointsArray[iPtsIdx + 1]; diff[2] -= pointsArray[iPtsIdx + 2]; const diffsq = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];
let bondDelta = (2 + model.deltaBondFactor) * model.bondRadius;
const r = Math.min( radiusArray[i] * model.atomicRadiusScaleFactor, radiusArray[j] * model.atomicRadiusScaleFactor ); const t = (bondOrder[index] - 1) * bondDelta + 2 * model.bondRadius; if (t > 2 * r * 0.6) { model.bondRadius *= (2 * r * 0.6) / t; bondDelta = (2 + model.deltaBondFactor) * model.bondRadius; }
const oddOrEven = bondOrder[index] % 2; for (let k = oddOrEven; k < bondOrder[index] + oddOrEven; k++) { let offset = ((Math.floor(k / 2) * 2 + 1 - oddOrEven) * bondDelta) / 2;
const vectUnitJI = [ diff[0] / Math.sqrt(diffsq), diff[1] / Math.sqrt(diffsq), diff[2] / Math.sqrt(diffsq), ]; const vectUnitJIperp = [0, 0, 0]; for (let coord = 0; coord < 3; coord++) { if (Math.abs(vectUnitJI[coord]) < 0.000001) { continue; } vectUnitJIperp[coord] = -( vectUnitJI[(coord + 2) % 3] * vectUnitJI[(coord + 2) % 3] + vectUnitJI[(coord + 1) % 3] * vectUnitJI[(coord + 1) % 3] ) / vectUnitJI[coord]; vectUnitJIperp[(coord + 1) % 3] = vectUnitJI[(coord + 1) % 3]; vectUnitJIperp[(coord + 2) % 3] = vectUnitJI[(coord + 2) % 3]; vtkMath.normalize(vectUnitJIperp); break; }
offset *= (-1) ** (k % 2);
let bondPos;
if ( atomicNumber && atomicNumber[i] !== atomicNumber[j] && colorArray.length > 0 ) { const bondLength = Math.sqrt(diffsq) / 2.0;
bondPos = [ pointsArray[jPtsIdx] - (bondLength * vectUnitJI[0]) / 2.0 + offset * vectUnitJIperp[0], pointsArray[jPtsIdx + 1] - (bondLength * vectUnitJI[1]) / 2.0 + offset * vectUnitJIperp[1], pointsArray[jPtsIdx + 2] - (bondLength * vectUnitJI[2]) / 2.0 + offset * vectUnitJIperp[2], ];
addBond( bondPos, vectUnitJI, bondLength, colorArray.slice(jPtsIdx, jPtsIdx + 3) );
bondPos = [ pointsArray[iPtsIdx] + (bondLength * vectUnitJI[0]) / 2.0 + offset * vectUnitJIperp[0], pointsArray[iPtsIdx + 1] + (bondLength * vectUnitJI[1]) / 2.0 + offset * vectUnitJIperp[1], pointsArray[iPtsIdx + 2] + (bondLength * vectUnitJI[2]) / 2.0 + offset * vectUnitJIperp[2], ]; addBond( bondPos, vectUnitJI, bondLength, colorArray.slice(iPtsIdx, iPtsIdx + 3) ); } else { const bondLength = Math.sqrt(diffsq);
bondPos = [ pointsArray[jPtsIdx] - diff[0] / 2.0 + offset * vectUnitJIperp[0], pointsArray[jPtsIdx + 1] - diff[1] / 2.0 + offset * vectUnitJIperp[1], pointsArray[jPtsIdx + 2] - diff[2] / 2.0 + offset * vectUnitJIperp[2], ]; if (colorArray.length > 0) { addBond( bondPos, vectUnitJI, bondLength, colorArray.slice(iPtsIdx, iPtsIdx + 3) ); } else { addBond(bondPos, vectUnitJI, bondLength); } } } }
SphereData.getPoints().setData(pointsData, 3);
if (radiusArray) { const scales = vtkDataArray.newInstance({ numberOfComponents: 1, values: scaleData, name: publicAPI.getSphereScaleArrayName(), }); SphereData.getPointData().addArray(scales); }
if (colorArray.length > 0) { const colors = vtkDataArray.newInstance({ numberOfComponents: 3, values: Uint8Array.from(colorData), name: 'colors', }); SphereData.getPointData().setScalars(colors); }
StickData.getPoints().setData(bondPositionData, 3);
const stickScales = vtkDataArray.newInstance({ numberOfComponents: 2, values: bondScaleData, name: 'stickScales', }); StickData.getPointData().addArray(stickScales);
const orientation = vtkDataArray.newInstance({ numberOfComponents: 3, values: bondOrientationData, name: 'orientation', }); StickData.getPointData().addArray(orientation);
if (colorArray.length > 0) { const bondColors = vtkDataArray.newInstance({ numberOfComponents: 3, values: Uint8Array.from(bondColorData), name: 'colors', }); StickData.getPointData().setScalars(bondColors); }
outData[0] = SphereData; outData[1] = StickData;
return 1; }; }
const DEFAULT_VALUES = { sphereScaleArrayName: 'radius', tolerance: 0.45, atomicRadiusScaleFactor: 0.3, bondRadius: 0.075, deltaBondFactor: 0.6, radiusType: 'radiusVDW', hideElements: '', };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model); macro.setGet(publicAPI, model, [ 'atomicRadiusScaleFactor', 'bondRadius', 'deltaBondFactor', 'hideElements', 'radiusType', 'sphereScaleArrayName', 'tolerance', ]); macro.algo(publicAPI, model, 1, 2); vtkMoleculeToRepresentation(publicAPI, model); }
export const newInstance = macro.newInstance( extend, 'vtkMoleculeToRepresentation' );
export default { newInstance, extend };
|