diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f538b93002a18d9778a6bc76a906fa17211226f..4e8354d4cf1303b8aeb313ce30c90317ab6e6d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,12 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] - Enable odd dash count (1,3,5) +- Add principal axes spec and fix edge cases - Add a uniform color theme for NtC tube that still paints residue and segment dividers in a different color -- Support points & lines in glTF export +- Mesh exporter improvements + - Support points & lines in glTF export + - Set alphaMode and doubleSided in glTF export + - Fix flipped cylinder caps - Fix bond assignments `struct_conn` records referencing waters - Add StructConn extension providing functions for inspecting struct_conns - Fix `PluginState.setSnapshot` triggering unnecessary state updates @@ -18,6 +22,7 @@ Note that since we don't clearly distinguish between a public and private interf - Parse HEADER record when reading PDB file - Support `ignoreHydrogens` in interactions representation - Add hydroxyproline (HYP) commonly present in collagen molecules to the list of amino acids +- Fix assemblies for Archive PDB files (do not generate unique `label_asym_id` if `REMARK 350` is present) ## [v3.34.0] - 2023-04-16 diff --git a/src/extensions/geo-export/glb-exporter.ts b/src/extensions/geo-export/glb-exporter.ts index 6e3c69fe81988e910025d2ef9fcf960516ab4030..9544aaa822e554c65fed97fafa925502ab23a166 100644 --- a/src/extensions/geo-export/glb-exporter.ts +++ b/src/extensions/geo-export/glb-exporter.ts @@ -170,8 +170,8 @@ export class GlbExporter extends MeshExporter<GlbData> { return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true); } - private addMaterial(metalness: number, roughness: number) { - const hash = `${metalness}|${roughness}`; + private addMaterial(metalness: number, roughness: number, doubleSided: boolean, alpha: boolean) { + const hash = `${metalness}|${roughness}|${doubleSided}`; if (!this.materialMap.has(hash)) { this.materialMap.set(hash, this.materials.length); this.materials.push({ @@ -179,7 +179,9 @@ export class GlbExporter extends MeshExporter<GlbData> { baseColorFactor: [1, 1, 1, 1], metallicFactor: metalness, roughnessFactor: roughness - } + }, + doubleSided, + alphaMode: alpha ? 'BLEND' : 'OPAQUE', }); } return this.materialMap.get(hash)!; @@ -198,8 +200,10 @@ export class GlbExporter extends MeshExporter<GlbData> { const instanceCount = values.uInstanceCount.ref.value; const metalness = values.uMetalness.ref.value; const roughness = values.uRoughness.ref.value; + const doubleSided = values.uDoubleSided?.ref.value || values.hasReflection.ref.value; + const alpha = values.uAlpha.ref.value < 1; - const material = this.addMaterial(metalness, roughness); + const material = this.addMaterial(metalness, roughness, doubleSided, alpha); let interpolatedColors: Uint8Array | undefined; if (webgl && mesh && (colorType === 'volume' || colorType === 'volumeInstance')) { diff --git a/src/extensions/geo-export/mesh-exporter.ts b/src/extensions/geo-export/mesh-exporter.ts index 9bd034d96c1f22fde455ab3cff14cb08cdaff9dc..011a2c41fa5ff2e09669bb4f81cc0bf761a7fd43 100644 --- a/src/extensions/geo-export/mesh-exporter.ts +++ b/src/extensions/geo-export/mesh-exporter.ts @@ -29,11 +29,15 @@ import { unpackRGBToInt } from '../../mol-util/number-packing'; import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter'; import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util'; import { assertUnreachable } from '../../mol-util/type-helpers'; +import { ValueCell } from '../../mol-util/value-cell'; 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; type MeshMode = 'points' | 'lines' | 'triangles' @@ -47,7 +51,7 @@ export interface AddMeshInput { drawCount: number } | undefined meshes: Mesh[] | undefined - values: BaseValues + values: BaseValues & { readonly uDoubleSided?: ValueCell<any> } isGeoTexture: boolean mode: MeshMode webgl: WebGLContext | undefined @@ -509,6 +513,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements private async addCylinders(values: CylindersValues, webgl: WebGLContext, ctx: RuntimeContext) { const start = Vec3(); const end = Vec3(); + const dir = Vec3(); const aStart = values.aStart.ref.value; const aEnd = values.aEnd.ref.value; @@ -546,12 +551,16 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements 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) * aScale[i]; const cap = aCap[i]; - const topCap = cap === 1 || cap === 3; - const bottomCap = cap >= 2; + 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 }; state.currentGroup = aGroup[i]; addCylinder(state, start, end, 1, cylinderProps); diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index d39cbba843d79881eea6e8151cee67217e1cd040..294fd5a9c354ed19d3245d0e33ad81c1490ac0e5 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/src/mol-math/linear-algebra/3d/vec3.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -24,6 +24,8 @@ import { Mat3 } from './mat3'; import { Quat } from './quat'; import { EPSILON } from './common'; +const _isFinite = isFinite; + export { ReadonlyVec3 }; interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 } @@ -48,6 +50,10 @@ namespace Vec3 { return out; } + export function isFinite(a: Vec3): boolean { + return _isFinite(a[0]) && _isFinite(a[1]) && _isFinite(a[2]); + } + export function hasNaN(a: Vec3) { return isNaN(a[0]) || isNaN(a[1]) || isNaN(a[2]); } @@ -636,4 +642,4 @@ namespace Vec3 { export const negUnitZ: ReadonlyVec3 = create(0, 0, -1); } -export { Vec3 }; \ No newline at end of file +export { Vec3 }; diff --git a/src/mol-math/linear-algebra/_spec/principal-axes.spec.ts b/src/mol-math/linear-algebra/_spec/principal-axes.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a866bd483f68ea05edab08d0584d99425078d3a --- /dev/null +++ b/src/mol-math/linear-algebra/_spec/principal-axes.spec.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Gianluca Tomasello <giagitom@gmail.com> + */ + +import { NumberArray } from '../../../mol-util/type-helpers'; +import { PrincipalAxes } from '../matrix/principal-axes'; + +describe('PrincipalAxes', () => { + it('same-cartesian-plane', () => { + const positions: NumberArray = [ // same y coordinate + 0.1945, -0.0219, -0.0416, + -0.0219, -0.0219, -0.0119, + ]; + const { origin } = PrincipalAxes.ofPositions(positions).boxAxes; + expect(origin[0] !== Infinity && origin[1] !== Infinity && origin[2] !== Infinity).toBe(true); + }); +}); diff --git a/src/mol-math/linear-algebra/matrix/principal-axes.ts b/src/mol-math/linear-algebra/matrix/principal-axes.ts index 90091e1e123a20a112fb2439edf4ef1f9982e163..79fcc46f05bf57369d1951e727183ed15ff1fed9 100644 --- a/src/mol-math/linear-algebra/matrix/principal-axes.ts +++ b/src/mol-math/linear-algebra/matrix/principal-axes.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { Matrix } from './matrix'; @@ -123,12 +124,17 @@ namespace PrincipalAxes { const dirB = Vec3.setMagnitude(Vec3(), a.dirB, (d2a + d2b) / 2); const dirC = Vec3.setMagnitude(Vec3(), a.dirC, (d3a + d3b) / 2); + const okDirA = Vec3.isFinite(dirA); + const okDirB = Vec3.isFinite(dirB); + const okDirC = Vec3.isFinite(dirC); + const origin = Vec3(); const addCornerHelper = function (d1: number, d2: number, d3: number) { Vec3.copy(tmpBoxVec, center); - Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirA, d1); - Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirB, d2); - Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirC, d3); + + if (okDirA) Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirA, d1); + if (okDirB) Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirB, d2); + if (okDirC) Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirC, d3); Vec3.add(origin, origin, tmpBoxVec); }; addCornerHelper(d1a, d2a, d3a); diff --git a/src/mol-model-formats/structure/pdb/atom-site.ts b/src/mol-model-formats/structure/pdb/atom-site.ts index c8b1c996077de39d522b6916f161b69de7fcb27b..26eaaeaccc5a64db8a2b68f129bd495ed1120f32 100644 --- a/src/mol-model-formats/structure/pdb/atom-site.ts +++ b/src/mol-model-formats/structure/pdb/atom-site.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -39,7 +39,7 @@ export function getAtomSiteTemplate(data: string, count: number) { }; } -export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } { +export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>, options: { hasAssemblies: boolean }): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } { const pdbx_PDB_model_num = CifField.ofStrings(sites.pdbx_PDB_model_num); const auth_asym_id = CifField.ofTokens(sites.auth_asym_id); const auth_seq_id = CifField.ofTokens(sites.auth_seq_id); @@ -87,7 +87,9 @@ export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): { if (asymIdCounts.has(asymId)) { // only change the chains name if there are TER records // otherwise assume repeated chain name use is from interleaved chains - if (terIndices.has(i)) { + // also don't change the chains name if there are assemblies + // as those require the original chain name + if (terIndices.has(i) && !options.hasAssemblies) { const asymIdCount = asymIdCounts.get(asymId)! + 1; asymIdCounts.set(asymId, asymIdCount); currLabelAsymId = `${asymId}_${asymIdCount}`; diff --git a/src/mol-model-formats/structure/pdb/to-cif.ts b/src/mol-model-formats/structure/pdb/to-cif.ts index 99d6184b523f7d663eb546ffaf54e93e1debe76b..27962c3bdea31999ee473c02a8decb3efd1fa2e1 100644 --- a/src/mol-model-formats/structure/pdb/to-cif.ts +++ b/src/mol-model-formats/structure/pdb/to-cif.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -54,6 +54,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { let modelNum = 0, modelStr = ''; let conectRange: [number, number] | undefined = undefined; + let hasAssemblies = false; const terIndices = new Set<number>(); for (let i = 0, _i = lines.count; i < _i; i++) { @@ -152,6 +153,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { } helperCategories.push(...parseRemark350(lines, i, j)); i = j - 1; + hasAssemblies = true; } break; case 'S': @@ -208,7 +210,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i)); } - const atom_site = getAtomSite(atomSite, terIndices); + const atom_site = getAtomSite(atomSite, terIndices, { hasAssemblies }); if (!isPdbqt) delete atom_site.partial_charge; if (conectRange) {