diff --git a/package-lock.json b/package-lock.json index 8f86e5adeac89d096105360c76b4de9fcdd01ed1..a1b1e6ae931bdeff9383beb2c82a7774ab8f7daf 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 7f968c52bbb91bfbb21d101104287834f245ed89..2463e1db504721bbe7c51d4a92b1cec7966d51db 100644 --- a/package.json +++ b/package.json @@ -79,18 +79,18 @@ "@types/benchmark": "^1.0.31", "@types/compression": "0.0.36", "@types/express": "^4.16.1", - "@types/jest": "^24.0.9", - "@types/node": "^11.10.4", - "@types/node-fetch": "^2.1.6", - "@types/react": "^16.8.6", - "@types/react-dom": "^16.8.2", + "@types/jest": "^24.0.11", + "@types/node": "^11.11.6", + "@types/node-fetch": "^2.1.7", + "@types/react": "^16.8.8", + "@types/react-dom": "^16.8.3", "@types/webgl2": "0.0.4", "@types/swagger-ui-dist": "3.0.0", "benchmark": "^2.1.4", "circular-dependency-plugin": "^5.0.2", "concurrently": "^4.1.0", "cpx": "^1.5.0", - "css-loader": "^2.1.0", + "css-loader": "^2.1.1", "extra-watch-webpack-plugin": "^1.0.3", "file-loader": "^3.0.1", "glslify": "^7.0.0", @@ -99,31 +99,31 @@ "graphql-code-generator": "^0.18.0", "graphql-codegen-time": "^0.18.0", "graphql-codegen-typescript-template": "^0.18.0", - "jest": "^24.1.0", + "jest": "^24.5.0", "jest-raw-loader": "^1.0.1", "mini-css-extract-plugin": "^0.5.0", "node-sass": "^4.11.0", - "raw-loader": "^1.0.0", + "raw-loader": "^2.0.0", "resolve-url-loader": "^3.0.1", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "ts-jest": "^24.0.0", - "tslint": "^5.13.1", - "typescript": "^3.3.3", - "uglify-js": "^3.4.9", + "tslint": "^5.14.0", + "typescript": "^3.3.4000", + "uglify-js": "^3.5.1", "util.promisify": "^1.0.0", "webpack": "^4.29.6", - "webpack-cli": "^3.2.3" + "webpack-cli": "^3.3.0" }, "dependencies": { "argparse": "^1.0.10", - "compression": "^1.7.3", + "compression": "^1.7.4", "express": "^4.16.4", "graphql": "^14.1.1", "immutable": "^3.8.2", "node-fetch": "^2.3.0", - "react": "^16.8.4", - "react-dom": "^16.8.4", + "react": "^16.8.5", + "react-dom": "^16.8.5", "rxjs": "^6.4.0", "swagger-ui-dist": "^3.21.0" } diff --git a/src/mol-geo/geometry/mesh/mesh-builder.ts b/src/mol-geo/geometry/mesh/mesh-builder.ts index 2bcaf085159f4597b6ee0c0d915b754c6aeac46c..cfe57234db1d2b4fd8ce2648158cf4d4369e5656 100644 --- a/src/mol-geo/geometry/mesh/mesh-builder.ts +++ b/src/mol-geo/geometry/mesh/mesh-builder.ts @@ -45,7 +45,7 @@ export namespace MeshBuilder { export function addTriangle(state: State, a: Vec3, b: Vec3, c: Vec3) { const { vertices, normals, indices, groups, currentGroup } = state const offset = vertices.elementCount - + // positions ChunkedArray.add3(vertices, a[0], a[1], a[2]); ChunkedArray.add3(vertices, b[0], b[1], b[2]); diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index dc3a3f88e6c4a5010714b4df143f632947665abe..a78083e9ad9d4e1b6fe54c5ec7d45d2479f0abc7 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -31,38 +31,38 @@ export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensi } export const PointsShaderCode = ShaderCode( - require('mol-gl/shader/points.vert'), - require('mol-gl/shader/points.frag'), + require('mol-gl/shader/points.vert').default, + require('mol-gl/shader/points.frag').default, { standardDerivatives: false, fragDepth: false } ) export const SpheresShaderCode = ShaderCode( - require('mol-gl/shader/spheres.vert'), - require('mol-gl/shader/spheres.frag'), + require('mol-gl/shader/spheres.vert').default, + require('mol-gl/shader/spheres.frag').default, { standardDerivatives: false, fragDepth: true } ) export const TextShaderCode = ShaderCode( - require('mol-gl/shader/text.vert'), - require('mol-gl/shader/text.frag'), + require('mol-gl/shader/text.vert').default, + require('mol-gl/shader/text.frag').default, { standardDerivatives: true, fragDepth: false } ) export const LinesShaderCode = ShaderCode( - require('mol-gl/shader/lines.vert'), - require('mol-gl/shader/lines.frag'), + require('mol-gl/shader/lines.vert').default, + require('mol-gl/shader/lines.frag').default, { standardDerivatives: false, fragDepth: false } ) export const MeshShaderCode = ShaderCode( - require('mol-gl/shader/mesh.vert'), - require('mol-gl/shader/mesh.frag'), + require('mol-gl/shader/mesh.vert').default, + require('mol-gl/shader/mesh.frag').default, { standardDerivatives: true, fragDepth: false } ) export const DirectVolumeShaderCode = ShaderCode( - require('mol-gl/shader/direct-volume.vert'), - require('mol-gl/shader/direct-volume.frag'), + require('mol-gl/shader/direct-volume.vert').default, + require('mol-gl/shader/direct-volume.frag').default, { standardDerivatives: false, fragDepth: true } ) diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index 842bf50c45ff974432a23ea8d04d184671b37e23..0840d619d2035ee3e6cd1e28c53c62577238f86c 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -131,6 +131,7 @@ export function createRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCo const valueChanges = createValueChanges() let destroyed = false + let currentProgramId = -1 return { id, @@ -142,9 +143,12 @@ export function createRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCo const program = programs[variant].value const vertexArray = vertexArrays[variant] program.setUniforms(uniformValueEntries) - if (materialId === -1 || materialId !== ctx.currentMaterialId) { - // console.log('materialId changed or -1', materialId) + if (program.id !== currentProgramId || + materialId === -1 || materialId !== ctx.currentMaterialId + ) { + // console.log('program.id changed or materialId changed/-1', materialId) program.setUniforms(materialUniformValueEntries) + currentProgramId = program.id ctx.currentMaterialId = materialId } program.bindTextures(textures) diff --git a/src/mol-io/reader/_spec/mol2.spec.ts b/src/mol-io/reader/_spec/mol2.spec.ts index dc88b16043caf6777ca6571656f2c3cfab6eb38f..04570831700fc0db83cae496f660bbf50ef654ef 100644 --- a/src/mol-io/reader/_spec/mol2.spec.ts +++ b/src/mol-io/reader/_spec/mol2.spec.ts @@ -265,10 +265,10 @@ describe('mol2 reader', () => { expect(molecule.num_subst).toBe(0); expect(molecule.num_feat).toBe(0); expect(molecule.num_sets).toBe(0); - expect(molecule.mol_type).toBe("SMALL") - expect(molecule.charge_type).toBe("GASTEIGER"); - expect(molecule.status_bits).toBe(""); - expect(molecule.mol_comment).toBe(""); + expect(molecule.mol_type).toBe('SMALL') + expect(molecule.charge_type).toBe('GASTEIGER'); + expect(molecule.status_bits).toBe(''); + expect(molecule.mol_comment).toBe(''); // required atom fields expect(atoms.count).toBe(26); @@ -277,7 +277,7 @@ describe('mol2 reader', () => { expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001); expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001); expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001); - expect(atoms.atom_type.value(0)).toBe("O.3"); + expect(atoms.atom_type.value(0)).toBe('O.3'); // optional atom fields expect(atoms.subst_id.value(0)).toBe(1); @@ -316,10 +316,10 @@ describe('mol2 reader', () => { expect(molecule.num_subst).toBe(0); expect(molecule.num_feat).toBe(0); expect(molecule.num_sets).toBe(0); - expect(molecule.mol_type).toBe("SMALL") - expect(molecule.charge_type).toBe("GASTEIGER"); - expect(molecule.status_bits).toBe(""); - expect(molecule.mol_comment).toBe(""); + expect(molecule.mol_type).toBe('SMALL') + expect(molecule.charge_type).toBe('GASTEIGER'); + expect(molecule.status_bits).toBe(''); + expect(molecule.mol_comment).toBe(''); // required atom fields expect(atoms.count).toBe(26); @@ -328,7 +328,7 @@ describe('mol2 reader', () => { expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001); expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001); expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001); - expect(atoms.atom_type.value(0)).toBe("O.3"); + expect(atoms.atom_type.value(0)).toBe('O.3'); // optional atom fields expect(atoms.subst_id.value(0)).toBe(1); @@ -367,10 +367,10 @@ describe('mol2 reader', () => { expect(molecule.num_subst).toBe(0); expect(molecule.num_feat).toBe(0); expect(molecule.num_sets).toBe(0); - expect(molecule.mol_type).toBe("SMALL") - expect(molecule.charge_type).toBe("GASTEIGER"); - expect(molecule.status_bits).toBe(""); - expect(molecule.mol_comment).toBe(""); + expect(molecule.mol_type).toBe('SMALL') + expect(molecule.charge_type).toBe('GASTEIGER'); + expect(molecule.status_bits).toBe(''); + expect(molecule.mol_comment).toBe(''); // required atom fields expect(atoms.count).toBe(26); @@ -379,7 +379,7 @@ describe('mol2 reader', () => { expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001); expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001); expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001); - expect(atoms.atom_type.value(0)).toBe("O.3"); + expect(atoms.atom_type.value(0)).toBe('O.3'); // optional atom fields expect(atoms.subst_id.value(0)).toBe(0); diff --git a/src/mol-io/reader/_spec/ply.spec.ts b/src/mol-io/reader/_spec/ply.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..75325914641932f2495716166566fbdc9d084521 --- /dev/null +++ b/src/mol-io/reader/_spec/ply.spec.ts @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import Ply from '../ply/parser' +import { PlyTable, PlyList } from '../ply/schema'; + +const plyString = `ply +format ascii 1.0 +comment file created by MegaMol +element vertex 6 +property float x +property float y +property float z +property uchar red +property uchar green +property uchar blue +property uchar alpha +property float nx +property float ny +property float nz +property int atomid +property uchar contactcount_r +property uchar contactcount_g +property uchar contactcount_b +property uchar contactsteps_r +property uchar contactsteps_g +property uchar contactsteps_b +property uchar hbonds_r +property uchar hbonds_g +property uchar hbonds_b +property uchar hbondsteps_r +property uchar hbondsteps_g +property uchar hbondsteps_b +property uchar molcount_r +property uchar molcount_g +property uchar molcount_b +property uchar spots_r +property uchar spots_g +property uchar spots_b +property uchar rmsf_r +property uchar rmsf_g +property uchar rmsf_b +element face 2 +property list uchar int vertex_index +end_header +130.901 160.016 163.033 90 159 210 255 -0.382 -0.895 -0.231 181 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212 +131.372 159.778 162.83 90 159 210 255 -0.618 -0.776 -0.129 178 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 141 177 199 +131.682 159.385 163.089 90 159 210 255 -0.773 -0.579 -0.259 180 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 172 196 212 +131.233 160.386 162.11 90 159 210 255 -0.708 -0.383 -0.594 178 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 141 177 199 +130.782 160.539 162.415 90 159 210 255 -0.482 -0.459 -0.746 181 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212 +131.482 160.483 161.621 90 159 210 255 -0.832 -0.431 -0.349 179 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212 +3 0 2 1 +3 3 5 4 +` + +const plyCubeString = `ply +format ascii 1.0 +comment test cube +element vertex 24 +property float32 x +property float32 y +property float32 z +property uint32 material_index +element face 6 +property list uint8 int32 vertex_indices +element material 6 +property uint8 red +property uint8 green +property uint8 blue +end_header +-1 -1 -1 0 +1 -1 -1 0 +1 1 -1 0 +-1 1 -1 0 +1 -1 1 1 +-1 -1 1 1 +-1 1 1 1 +1 1 1 1 +1 1 1 2 +1 1 -1 2 +1 -1 -1 2 +1 -1 1 2 +-1 1 -1 3 +-1 1 1 3 +-1 -1 1 3 +-1 -1 -1 3 +-1 1 1 4 +-1 1 -1 4 +1 1 -1 4 +1 1 1 4 +1 -1 1 5 +1 -1 -1 5 +-1 -1 -1 5 +-1 -1 1 5 +4 0 1 2 3 +4 4 5 6 7 +4 8 9 10 11 +4 12 13 14 15 +4 16 17 18 19 +4 20 21 22 23 +255 0 0 +0 255 0 +0 0 255 +255 255 0 +0 255 255 +255 0 255 +` + + +describe('ply reader', () => { + it('basic', async () => { + const parsed = await Ply(plyString).run(); + if (parsed.isError) return; + const plyFile = parsed.result; + + const vertex = plyFile.getElement('vertex') as PlyTable + if (!vertex) return + const x = vertex.getProperty('x') + if (!x) return + expect(x.value(0)).toEqual(130.901) + + const face = plyFile.getElement('face') as PlyList + if (!face) return + expect(face.value(0)).toEqual({ count: 3, entries: [0, 2, 1]}) + expect(face.value(1)).toEqual({ count: 3, entries: [3, 5, 4]}) + + expect.assertions(3) + }); + + it('material', async () => { + const parsed = await Ply(plyCubeString).run(); + if (parsed.isError) return; + const plyFile = parsed.result; + + const vertex = plyFile.getElement('vertex') as PlyTable + if (!vertex) return + expect(vertex.rowCount).toBe(24) + + const face = plyFile.getElement('face') as PlyList + if (!face) return + expect(face.rowCount).toBe(6) + + const material = plyFile.getElement('face') as PlyTable + if (!material) return + expect(face.rowCount).toBe(6) + + expect.assertions(3) + }); +}); \ No newline at end of file diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts index 2800437dc930bd57af8dafe14913f9d5e15fc105..c5778c7b55a847616c5c36ff57dcf300f9af1926 100644 --- a/src/mol-io/reader/cif/data-model.ts +++ b/src/mol-io/reader/cif/data-model.ts @@ -199,7 +199,7 @@ export namespace CifField { export function ofColumn(column: Column<any>): CifField { const { rowCount, valueKind, areValuesEqual } = column; - + let str: CifField['str'] let int: CifField['int'] let float: CifField['float'] @@ -219,7 +219,6 @@ export namespace CifField { default: throw new Error('unsupported') } - return { __array: void 0, diff --git a/src/mol-io/reader/csv/data-model.ts b/src/mol-io/reader/csv/data-model.ts index 401c7aa2d5855cd530131ac22345bc0052823f9f..7c538467e25bf5eb2143ba569af1af41fe02e3e1 100644 --- a/src/mol-io/reader/csv/data-model.ts +++ b/src/mol-io/reader/csv/data-model.ts @@ -9,12 +9,11 @@ import { CifField as CsvColumn } from '../cif/data-model' export { CsvColumn } export interface CsvFile { - readonly name?: string, readonly table: CsvTable } -export function CsvFile(table: CsvTable, name?: string): CsvFile { - return { name, table }; +export function CsvFile(table: CsvTable): CsvFile { + return { table }; } export interface CsvTable { @@ -27,10 +26,4 @@ export function CsvTable(rowCount: number, columnNames: string[], columns: CsvCo return { rowCount, columnNames: [...columnNames], getColumn(name) { return columns[name]; } }; } -export type CsvColumns = { [name: string]: CsvColumn } - -// export namespace CsvTable { -// export function empty(name: string): Table { -// return { rowCount: 0, name, fieldNames: [], getColumn(name: string) { return void 0; } }; -// }; -// } \ No newline at end of file +export type CsvColumns = { [name: string]: CsvColumn } \ No newline at end of file diff --git a/src/mol-io/reader/ply/parser.ts b/src/mol-io/reader/ply/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd9ce4f2d044a9e0cf62efe7c5f06c87d4258658 --- /dev/null +++ b/src/mol-io/reader/ply/parser.ts @@ -0,0 +1,263 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ReaderResult as Result } from '../result' +import { Task, RuntimeContext } from 'mol-task' +import { PlyFile, PlyType, PlyElement } from './schema'; +import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer'; +import { Column } from 'mol-data/db'; +import { TokenColumn } from '../common/text/column/token'; + +interface State { + data: string + tokenizer: Tokenizer + runtimeCtx: RuntimeContext + + comments: string[] + elementSpecs: ElementSpec[] + elements: PlyElement[] +} + +function State(data: string, runtimeCtx: RuntimeContext): State { + const tokenizer = Tokenizer(data) + return { + data, + tokenizer, + runtimeCtx, + + comments: [], + elementSpecs: [], + elements: [] + } +} + +type ColumnProperty = { kind: 'column', type: PlyType, name: string } +type ListProperty = { kind: 'list', countType: PlyType, dataType: PlyType, name: string } +type Property = ColumnProperty | ListProperty + +type TableElementSpec = { kind: 'table', name: string, count: number, properties: ColumnProperty[] } +type ListElementSpec = { kind: 'list', name: string, count: number, property: ListProperty } +type ElementSpec = TableElementSpec | ListElementSpec + +function markHeader(tokenizer: Tokenizer) { + const endHeaderIndex = tokenizer.data.indexOf('end_header', tokenizer.position) + if (endHeaderIndex === -1) throw new Error(`no 'end_header' record found`) + // TODO set `tokenizer.lineNumber` correctly + tokenizer.tokenStart = tokenizer.position + tokenizer.tokenEnd = endHeaderIndex + tokenizer.position = endHeaderIndex + Tokenizer.eatLine(tokenizer) +} + +function parseHeader(state: State) { + const { tokenizer, comments, elementSpecs } = state + + markHeader(tokenizer) + const headerLines = Tokenizer.getTokenString(tokenizer).split(/\r?\n/) + + if (headerLines[0] !== 'ply') throw new Error(`data not starting with 'ply'`) + if (headerLines[1] !== 'format ascii 1.0') throw new Error(`format not 'ascii 1.0'`) + + let currentName: string | undefined + let currentCount: number | undefined + let currentProperties: Property[] | undefined + + + function addCurrentElementSchema() { + if (currentName !== undefined && currentCount !== undefined && currentProperties !== undefined) { + let isList = false + for (let i = 0, il = currentProperties.length; i < il; ++i) { + const p = currentProperties[i] + if (p.kind === 'list') { + isList = true + break + } + } + if (isList && currentProperties.length !== 1) throw new Error('expected single list property') + if (isList) { + elementSpecs.push({ + kind: 'list', + name: currentName, + count: currentCount, + property: currentProperties[0] as ListProperty + }) + } else { + elementSpecs.push({ + kind: 'table', + name: currentName, + count: currentCount, + properties: currentProperties as ColumnProperty[] + }) + } + } + } + + for (let i = 2, il = headerLines.length; i < il; ++i) { + const l = headerLines[i] + const ls = l.split(' ') + if (l.startsWith('comment')) { + comments.push(l.substr(8)) + } else if (l.startsWith('element')) { + addCurrentElementSchema() + currentProperties = [] + currentName = ls[1] + currentCount = parseInt(ls[2]) + } else if (l.startsWith('property')) { + if (currentProperties === undefined) throw new Error(`properties outside of element`) + if (ls[1] === 'list') { + currentProperties.push({ + kind: 'list', + countType: PlyType(ls[2]), + dataType: PlyType(ls[3]), + name: ls[4] + }) + } else { + currentProperties.push({ + kind: 'column', + type: PlyType(ls[1]), + name: ls[2] + }) + } + } else if (l.startsWith('end_header')) { + addCurrentElementSchema() + } else { + console.warn('unknown header line') + } + } +} + +function parseElements(state: State) { + const { elementSpecs } = state + for (let i = 0, il = elementSpecs.length; i < il; ++i) { + const spec = elementSpecs[i] + if (spec.kind === 'table') parseTableElement(state, spec) + else if (spec.kind === 'list') parseListElement(state, spec) + } +} + +function getColumnSchema(type: PlyType): Column.Schema { + switch (type) { + case 'char': case 'uchar': case 'int8': case 'uint8': + case 'short': case 'ushort': case 'int16': case 'uint16': + case 'int': case 'uint': case 'int32': case 'uint32': + return Column.Schema.int + case 'float': case 'double': case 'float32': case 'float64': + return Column.Schema.float + } +} + +function parseTableElement(state: State, spec: TableElementSpec) { + const { elements, tokenizer } = state + const { count, properties } = spec + const propertyCount = properties.length + const propertyNames: string[] = [] + const propertyTypes: PlyType[] = [] + const propertyTokens: Tokens[] = [] + const propertyColumns = new Map<string, Column<number>>() + + for (let i = 0, il = propertyCount; i < il; ++i) { + const tokens = TokenBuilder.create(tokenizer.data, count * 2) + propertyTokens.push(tokens) + } + + for (let i = 0, il = count; i < il; ++i) { + for (let j = 0, jl = propertyCount; j < jl; ++j) { + Tokenizer.skipWhitespace(tokenizer) + Tokenizer.markStart(tokenizer) + Tokenizer.eatValue(tokenizer) + TokenBuilder.addUnchecked(propertyTokens[j], tokenizer.tokenStart, tokenizer.tokenEnd) + } + } + + for (let i = 0, il = propertyCount; i < il; ++i) { + const { type, name } = properties[i] + const column = TokenColumn(propertyTokens[i], getColumnSchema(type)) + propertyNames.push(name) + propertyTypes.push(type) + propertyColumns.set(name, column) + } + + elements.push({ + kind: 'table', + rowCount: count, + propertyNames, + propertyTypes, + getProperty: (name: string) => propertyColumns.get(name) + }) +} + +function parseListElement(state: State, spec: ListElementSpec) { + const { elements, tokenizer } = state + const { count, property } = spec + + // initial tokens size assumes triangle index data + const tokens = TokenBuilder.create(tokenizer.data, count * 2 * 3) + + const offsets = new Uint32Array(count + 1) + let entryCount = 0 + + for (let i = 0, il = count; i < il; ++i) { + // skip over row entry count as it is determined by line break + Tokenizer.skipWhitespace(tokenizer) + Tokenizer.eatValue(tokenizer) + + while (Tokenizer.skipWhitespace(tokenizer) !== 10) { + ++entryCount + Tokenizer.markStart(tokenizer) + Tokenizer.eatValue(tokenizer) + TokenBuilder.addToken(tokens, tokenizer) + } + offsets[i + 1] = entryCount + } + + // console.log(tokens.indices) + // console.log(offsets) + + /** holds row value entries transiently */ + const listValue = { + entries: [] as number[], + count: 0 + } + + const column = TokenColumn(tokens, getColumnSchema(property.dataType)) + + elements.push({ + kind: 'list', + rowCount: count, + name: property.name, + type: property.dataType, + value: (row: number) => { + const start = offsets[row] + const end = offsets[row + 1] + for (let i = start; i < end; ++i) { + listValue.entries[i - start] = column.value(i) + } + listValue.count = end - start + return listValue + } + }) +} + +async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<PlyFile>> { + const state = State(data, ctx); + ctx.update({ message: 'Parsing...', current: 0, max: data.length }); + parseHeader(state) + // console.log(state.comments) + // console.log(JSON.stringify(state.elementSpecs, undefined, 4)) + parseElements(state) + const { elements, elementSpecs, comments } = state + const elementNames = elementSpecs.map(s => s.name) + const result = PlyFile(elements, elementNames, comments) + return Result.success(result); +} + +export function parse(data: string) { + return Task.create<Result<PlyFile>>('Parse PLY', async ctx => { + return await parseInternal(data, ctx) + }) +} + +export default parse; \ No newline at end of file diff --git a/src/mol-io/reader/ply/schema.ts b/src/mol-io/reader/ply/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5fbcb995ae825d0ea7f829ffd3ea96bca73f715 --- /dev/null +++ b/src/mol-io/reader/ply/schema.ts @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Column } from 'mol-data/db'; + +// http://paulbourke.net/dataformats/ply/ +// https://en.wikipedia.org/wiki/PLY_(file_format) + +export const PlyTypeByteLength = { + 'char': 1, + 'uchar': 1, + 'short': 2, + 'ushort': 2, + 'int': 4, + 'uint': 4, + 'float': 4, + 'double': 8, + + 'int8': 1, + 'uint8': 1, + 'int16': 2, + 'uint16': 2, + 'int32': 4, + 'uint32': 4, + 'float32': 4, + 'float64': 8 +} +export type PlyType = keyof typeof PlyTypeByteLength +export const PlyTypes = new Set(Object.keys(PlyTypeByteLength)) +export function PlyType(str: string) { + if (!PlyTypes.has(str)) throw new Error(`unknown ply type '${str}'`) + return str as PlyType +} + +export interface PlyFile { + readonly comments: ReadonlyArray<string> + readonly elementNames: ReadonlyArray<string> + getElement(name: string): PlyElement | undefined +} + +export function PlyFile(elements: PlyElement[], elementNames: string[], comments: string[]): PlyFile { + const elementMap = new Map<string, PlyElement>() + for (let i = 0, il = elementNames.length; i < il; ++i) { + elementMap.set(elementNames[i], elements[i]) + } + return { + comments, + elementNames, + getElement: (name: string) => { + return elementMap.get(name) + } + }; +} + +export type PlyElement = PlyTable | PlyList + +export interface PlyTable { + readonly kind: 'table' + readonly rowCount: number + readonly propertyNames: ReadonlyArray<string> + readonly propertyTypes: ReadonlyArray<PlyType> + getProperty(name: string): Column<number> | undefined +} + +export interface PlyListValue { + readonly entries: ArrayLike<number> + readonly count: number +} + +export interface PlyList { + readonly kind: 'list' + readonly rowCount: number, + readonly name: string, + readonly type: PlyType, + value: (row: number) => PlyListValue +} \ No newline at end of file diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index 83e73392add5e68fd85bc8db32ace00b5937bc15..2ec368a7a98b95d0c45f4fb0eaa047bd6d7e8937 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -45,8 +45,8 @@ export const GaussianDensitySchema = { } export const GaussianDensityShaderCode = ShaderCode( - require('mol-gl/shader/gaussian-density.vert'), - require('mol-gl/shader/gaussian-density.frag'), + require('mol-gl/shader/gaussian-density.vert').default, + require('mol-gl/shader/gaussian-density.frag').default, { standardDerivatives: false, fragDepth: false } ) diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index 3e8fd5d5ee7765fcb88215a1455655b75357b10e..bb16cc3530375060824f061d2a8176b4112b2325 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/src/mol-math/linear-algebra/3d/vec3.ts @@ -414,6 +414,7 @@ namespace Vec3 { } const angleTempA = zero(), angleTempB = zero(); + /** Computes the angle between 2 vectors, reports in rad. */ export function angle(a: Vec3, b: Vec3) { copy(angleTempA, a); copy(angleTempB, b); @@ -433,6 +434,28 @@ namespace Vec3 { } } + const tmp_dh_ab = Vec3.zero(); + const tmp_dh_cb = Vec3.zero(); + const tmp_dh_bc = Vec3.zero(); + const tmp_dh_dc = Vec3.zero(); + const tmp_dh_abc = Vec3.zero(); + const tmp_dh_bcd = Vec3.zero(); + const tmp_dh_cross = Vec3.zero(); + /** Computes the dihedral angles of 4 related atoms. phi: C, N+1, CA+1, C+1 - psi: N, CA, C, N+1 - omega: CA, C, N+1, CA+1 */ + export function dihedralAngle(a: Vec3, b: Vec3, c: Vec3, d: Vec3) { + Vec3.sub(tmp_dh_ab, a, b); + Vec3.sub(tmp_dh_cb, c, b); + Vec3.sub(tmp_dh_bc, b, c); + Vec3.sub(tmp_dh_dc, d, c); + + Vec3.cross(tmp_dh_abc, tmp_dh_ab, tmp_dh_cb); + Vec3.cross(tmp_dh_bcd, tmp_dh_bc, tmp_dh_dc); + + const angle = Vec3.angle(tmp_dh_abc, tmp_dh_bcd) * 360.0 / (2 * Math.PI); + Vec3.cross(tmp_dh_cross, tmp_dh_abc, tmp_dh_bcd); + return Vec3.dot(tmp_dh_cb, tmp_dh_cross) > 0 ? angle : -angle; + } + /** * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===) */ diff --git a/src/mol-math/linear-algebra/_spec/vec3.spec.ts b/src/mol-math/linear-algebra/_spec/vec3.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..bb98228acaf7b051186c8dd54cc985d81159ce52 --- /dev/null +++ b/src/mol-math/linear-algebra/_spec/vec3.spec.ts @@ -0,0 +1,14 @@ +import { Vec3 } from '../3d' + +describe('vec3', () => { + const vec1 = [ 1, 2, 3 ] as Vec3; + const vec2 = [ 2, 3, 1 ] as Vec3; + const orthVec1 = [ 0, 1, 0 ] as Vec3; + const orthVec2 = [ 1, 0, 0 ] as Vec3; + + it('angle calculation', () => { + expect(Vec3.angle(vec1, vec1) * 360 / (2 * Math.PI)).toBe(0.0); + expect(Vec3.angle(orthVec1, orthVec2) * 360 / (2 * Math.PI)).toBe(90.0); + expect(Vec3.angle(vec1, vec2)).toBeCloseTo(0.666946); + }); +}) \ No newline at end of file diff --git a/src/mol-model-formats/shape/ply.ts b/src/mol-model-formats/shape/ply.ts new file mode 100644 index 0000000000000000000000000000000000000000..edeceded4abdafa8e59e8370fef32b4c0b7f9f56 --- /dev/null +++ b/src/mol-model-formats/shape/ply.ts @@ -0,0 +1,260 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Schäfer, Marco <marco.schaefer@uni-tuebingen.de> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { RuntimeContext, Task } from 'mol-task'; +import { ShapeProvider } from 'mol-model/shape/provider'; +import { Color } from 'mol-util/color'; +import { PlyFile, PlyTable, PlyList } from 'mol-io/reader/ply/schema'; +import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder'; +import { Mesh } from 'mol-geo/geometry/mesh/mesh'; +import { Shape } from 'mol-model/shape'; +import { ChunkedArray } from 'mol-data/util'; +import { arrayMax, fillSerial } from 'mol-util/array'; +import { Column } from 'mol-data/db'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { ColorNames } from 'mol-util/color/tables'; +import { deepClone } from 'mol-util/object'; + +// TODO support 'edge' element, see https://www.mathworks.com/help/vision/ug/the-ply-format.html +// TODO support missing face element + +function createPlyShapeParams(plyFile?: PlyFile) { + const vertex = plyFile && plyFile.getElement('vertex') as PlyTable + const material = plyFile && plyFile.getElement('material') as PlyTable + + const defaultValues = { group: '', vRed: '', vGreen: '', vBlue: '', mRed: '', mGreen: '', mBlue: '' } + + const groupOptions: [string, string][] = [['', '']] + const colorOptions: [string, string][] = [['', '']] + if (vertex) { + for (let i = 0, il = vertex.propertyNames.length; i < il; ++i) { + const name = vertex.propertyNames[i] + const type = vertex.propertyTypes[i] + if ( + type === 'uchar' || type === 'uint8' || + type === 'ushort' || type === 'uint16' || + type === 'uint' || type === 'uint32' + ) groupOptions.push([ name, name ]) + if (type === 'uchar' || type === 'uint8') colorOptions.push([ name, name ]) + } + + // TODO hardcoded as convenience for data provided by MegaMol + if (vertex.propertyNames.includes('atomid')) defaultValues.group = 'atomid' + else if (vertex.propertyNames.includes('material_index')) defaultValues.group = 'material_index' + + if (vertex.propertyNames.includes('red')) defaultValues.vRed = 'red' + if (vertex.propertyNames.includes('green')) defaultValues.vGreen = 'green' + if (vertex.propertyNames.includes('blue')) defaultValues.vBlue = 'blue' + } + + const materialOptions: [string, string][] = [['', '']] + if (material) { + for (let i = 0, il = material.propertyNames.length; i < il; ++i) { + const name = material.propertyNames[i] + const type = material.propertyTypes[i] + if (type === 'uchar' || type === 'uint8') materialOptions.push([ name, name ]) + } + + if (material.propertyNames.includes('red')) defaultValues.mRed = 'red' + if (material.propertyNames.includes('green')) defaultValues.mGreen = 'green' + if (material.propertyNames.includes('blue')) defaultValues.mBlue = 'blue' + } + + const defaultColoring = defaultValues.vRed && defaultValues.vGreen && defaultValues.vBlue ? 'vertex' : + defaultValues.mRed && defaultValues.mGreen && defaultValues.mBlue ? 'material' : 'uniform' + + return { + ...Mesh.Params, + + coloring: PD.MappedStatic(defaultColoring, { + vertex: PD.Group({ + red: PD.Select(defaultValues.vRed, colorOptions, { label: 'Red Property' }), + green: PD.Select(defaultValues.vGreen, colorOptions, { label: 'Green Property' }), + blue: PD.Select(defaultValues.vBlue, colorOptions, { label: 'Blue Property' }), + }, { isFlat: true }), + material: PD.Group({ + red: PD.Select(defaultValues.mRed, materialOptions, { label: 'Red Property' }), + green: PD.Select(defaultValues.mGreen, materialOptions, { label: 'Green Property' }), + blue: PD.Select(defaultValues.mBlue, materialOptions, { label: 'Blue Property' }), + }, { isFlat: true }), + uniform: PD.Group({ + color: PD.Color(ColorNames.grey) + }, { isFlat: true }) + }), + grouping: PD.MappedStatic(defaultValues.group ? 'vertex' : 'none', { + vertex: PD.Group({ + group: PD.Select(defaultValues.group, groupOptions, { label: 'Group Property' }), + }, { isFlat: true }), + none: PD.Group({ }) + }), + } +} + +export const PlyShapeParams = createPlyShapeParams() +export type PlyShapeParams = typeof PlyShapeParams + +async function getMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList, groupIds: ArrayLike<number>, mesh?: Mesh) { + const builderState = MeshBuilder.createState(vertex.rowCount, vertex.rowCount / 4, mesh) + const { vertices, normals, indices, groups } = builderState + + const x = vertex.getProperty('x') + const y = vertex.getProperty('y') + const z = vertex.getProperty('z') + if (!x || !y || !z) throw new Error('missing coordinate properties') + + const nx = vertex.getProperty('nx') + const ny = vertex.getProperty('ny') + const nz = vertex.getProperty('nz') + + const hasNormals = !!nx && !!ny && !!nz + + for (let i = 0, il = vertex.rowCount; i < il; ++i) { + if (i % 100000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding vertex ${i}` }) + + ChunkedArray.add3(vertices, x.value(i), y.value(i), z.value(i)) + if (hasNormals) ChunkedArray.add3(normals, nx!.value(i), ny!.value(i), nz!.value(i)); + ChunkedArray.add(groups, groupIds[i]) + } + + for (let i = 0, il = face.rowCount; i < il; ++i) { + if (i % 100000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding face ${i}` }) + + const { entries, count } = face.value(i) + if (count === 3) { + // triangle + ChunkedArray.add3(indices, entries[0], entries[1], entries[2]) + } else if (count === 4) { + // quadrilateral + ChunkedArray.add3(indices, entries[2], entries[1], entries[0]) + ChunkedArray.add3(indices, entries[2], entries[0], entries[3]) + } + } + + const m = MeshBuilder.getMesh(builderState); + m.normalsComputed = hasNormals + await Mesh.computeNormals(m).runInContext(ctx) + + return m +} + +const int = Column.Schema.int + +type Grouping = { ids: ArrayLike<number>, map: ArrayLike<number> } +function getGrouping(vertex: PlyTable, props: PD.Values<PlyShapeParams>): Grouping { + const { grouping } = props + const { rowCount } = vertex + const column = grouping.name === 'vertex' ? vertex.getProperty(grouping.params.group) : undefined + + const ids = column ? column.toArray({ array: Uint32Array }) : fillSerial(new Uint32Array(rowCount)) + const maxId = arrayMax(ids) // assumes uint ids + const map = new Uint32Array(maxId + 1) + for (let i = 0, il = ids.length; i < il; ++i) map[ids[i]] = i + return { ids, map } +} + +type Coloring = { kind: 'vertex' | 'material' | 'uniform', red: Column<number>, green: Column<number>, blue: Column<number> } +function getColoring(vertex: PlyTable, material: PlyTable | undefined, props: PD.Values<PlyShapeParams>): Coloring { + const { coloring } = props + const { rowCount } = vertex + + let red: Column<number>, green: Column<number>, blue: Column<number> + if (coloring.name === 'vertex') { + red = vertex.getProperty(coloring.params.red) || Column.ofConst(127, rowCount, int) + green = vertex.getProperty(coloring.params.green) || Column.ofConst(127, rowCount, int) + blue = vertex.getProperty(coloring.params.blue) || Column.ofConst(127, rowCount, int) + } else if (coloring.name === 'material') { + red = (material && material.getProperty(coloring.params.red)) || Column.ofConst(127, rowCount, int) + green = (material && material.getProperty(coloring.params.green)) || Column.ofConst(127, rowCount, int) + blue = (material && material.getProperty(coloring.params.blue)) || Column.ofConst(127, rowCount, int) + } else { + const [r, g, b] = Color.toRgb(coloring.params.color) + red = Column.ofConst(r, rowCount, int) + green = Column.ofConst(g, rowCount, int) + blue = Column.ofConst(b, rowCount, int) + } + return { kind: coloring.name, red, green, blue } +} + +function createShape(plyFile: PlyFile, mesh: Mesh, coloring: Coloring, grouping: Grouping) { + const { kind, red, green, blue } = coloring + const { ids, map } = grouping + return Shape.create( + 'ply-mesh', plyFile, mesh, + (groupId: number) => { + const idx = kind === 'material' ? groupId : map[groupId] + return Color.fromRgb(red.value(idx), green.value(idx), blue.value(idx)) + }, + () => 1, // size: constant + (groupId: number) => { + return ids[groupId].toString() + } + ) +} + +function makeShapeGetter() { + let _plyFile: PlyFile | undefined + let _props: PD.Values<PlyShapeParams> | undefined + + let _shape: Shape<Mesh> + let _mesh: Mesh + let _coloring: Coloring + let _grouping: Grouping + + const getShape = async (ctx: RuntimeContext, plyFile: PlyFile, props: PD.Values<PlyShapeParams>, shape?: Shape<Mesh>) => { + + const vertex = plyFile.getElement('vertex') as PlyTable + if (!vertex) throw new Error('missing vertex element') + + const face = plyFile.getElement('face') as PlyList + if (!face) throw new Error('missing face element') + + const material = plyFile.getElement('material') as PlyTable + + let newMesh = false + let newColor = false + + if (!_plyFile || _plyFile !== plyFile) { + newMesh = true + } + + if (!_props || !PD.isParamEqual(PlyShapeParams.grouping, _props.grouping, props.grouping)) { + newMesh = true + } + + if (!_props || !PD.isParamEqual(PlyShapeParams.coloring, _props.coloring, props.coloring)) { + newColor = true + } + + if (newMesh) { + _coloring = getColoring(vertex, material, props) + _grouping = getGrouping(vertex, props) + _mesh = await getMesh(ctx, vertex, face, _grouping.ids, shape && shape.geometry) + _shape = createShape(plyFile, _mesh, _coloring, _grouping) + } else if (newColor) { + _coloring = getColoring(vertex, material, props) + _shape = createShape(plyFile, _mesh, _coloring, _grouping) + } + + _plyFile = plyFile + _props = deepClone(props) + + return _shape + } + return getShape +} + +export function shapeFromPly(source: PlyFile, params?: {}) { + return Task.create<ShapeProvider<PlyFile, Mesh, PlyShapeParams>>('Shape Provider', async ctx => { + return { + label: 'Mesh', + data: source, + params: createPlyShapeParams(source), + getShape: makeShapeGetter(), + geometryUtils: Mesh.Utils + } + }) +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/_spec/pdb.spec.ts b/src/mol-model-formats/structure/_spec/pdb.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e365ed4c9c8f2fc1b01e447e32e5314eeb6bd45 --- /dev/null +++ b/src/mol-model-formats/structure/_spec/pdb.spec.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { guessElementSymbol } from '../pdb/to-cif'; +import { TokenBuilder } from 'mol-io/reader/common/text/tokenizer'; + +const records = [ + ['ATOM 19 HD23 LEU A 1 151.940 143.340 155.670 0.00 0.00', 'H'], + ['ATOM 38 CA SER A 3 146.430 138.150 162.270 0.00 0.00', 'C'], + ['ATOM 38 NA SER A 3 146.430 138.150 162.270 0.00 0.00', 'NA'], + ['ATOM 38 NAA SER A 3 146.430 138.150 162.270 0.00 0.00', 'N'], +] + +describe('PDB to-cif', () => { + it('guess-element-symbol', () => { + for (let i = 0, il = records.length; i < il; ++i) { + const [ data, element ] = records[i] + const tokens = TokenBuilder.create(data, 2) + guessElementSymbol(tokens, data, 12, 16) + expect(data.substring(tokens.indices[0], tokens.indices[1])).toBe(element) + } + }); +}); \ No newline at end of file diff --git a/src/mol-model-formats/structure/pdb/to-cif.ts b/src/mol-model-formats/structure/pdb/to-cif.ts index 853a1b9319eb97121a5394a55e772dde2621608a..0f699f297a4c3f03095cd1514bfb8d7db7c129b8 100644 --- a/src/mol-model-formats/structure/pdb/to-cif.ts +++ b/src/mol-model-formats/structure/pdb/to-cif.ts @@ -8,7 +8,7 @@ import { substringStartsWith } from 'mol-util/string'; import { CifField, CifCategory, CifFrame } from 'mol-io/reader/cif'; import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; -import { TokenBuilder, Tokenizer } from 'mol-io/reader/common/text/tokenizer'; +import { TokenBuilder, Tokenizer, Tokens } from 'mol-io/reader/common/text/tokenizer'; import { PdbFile } from 'mol-io/reader/pdb/schema'; import { parseCryst1, parseRemark350, parseMtrix } from './assembly'; import { WaterNames } from 'mol-model/structure/model/types'; @@ -89,6 +89,43 @@ function getEntityId(residueName: string, isHet: boolean) { return '1'; } +export function guessElementSymbol(tokens: Tokens, str: string, start: number, end: number) { + let s = start, e = end - 1 + + // trim spaces and numbers + let c = str.charCodeAt(s) + while ((c === 32 || (c >= 48 && c <= 57)) && s <= e) c = str.charCodeAt(++s) + c = str.charCodeAt(e) + while ((c === 32 || (c >= 48 && c <= 57)) && e >= s) c = str.charCodeAt(--e) + + ++e + + if (s === e) return TokenBuilder.add(tokens, s, e) // empty + if (s + 1 === e) return TokenBuilder.add(tokens, s, e) // one char + + c = str.charCodeAt(s) + + if (s + 2 === e) { // two chars + const c2 = str.charCodeAt(s + 1) + if ( + ((c === 78 || c === 110) && (c2 === 65 || c2 === 97)) || // NA na Na nA + ((c === 67 || c === 99) && (c2 === 76 || c2 === 108)) || // CL + ((c === 70 || c === 102) && (c2 === 69 || c2 === 101)) // FE + ) return TokenBuilder.add(tokens, s, s + 2) + } + + if ( + c === 67 || c === 99 || // C c + c === 72 || c === 104 || // H h + c === 78 || c === 110 || // N n + c === 79 || c === 111 || // O o + c === 80 || c === 112 || // P p + c === 83 || c === 115 // S s + ) return TokenBuilder.add(tokens, s, s + 1) + + TokenBuilder.add(tokens, s, s) // no reasonable guess, add empty token +} + function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number, isHet: boolean) { const { data: str } = data; const length = e - s; @@ -162,11 +199,10 @@ function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: num if (data.tokenStart < data.tokenEnd) { TokenBuilder.addToken(sites.type_symbol, data); } else { - // "guess" the symbol - TokenBuilder.add(sites.type_symbol, s + 12, s + 13); + guessElementSymbol(sites.type_symbol, str, s + 12, s + 16) } } else { - TokenBuilder.add(sites.type_symbol, s + 12, s + 13); + guessElementSymbol(sites.type_symbol, str, s + 12, s + 16) } sites.label_entity_id[sites.index] = getEntityId(residueName, isHet); diff --git a/src/mol-model/shape/provider.ts b/src/mol-model/shape/provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..dea45e8ba202b2a09da762a1b9a84926e836b64b --- /dev/null +++ b/src/mol-model/shape/provider.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ShapeGetter } from 'mol-repr/shape/representation'; +import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry'; + +export interface ShapeProvider<D, G extends Geometry, P extends Geometry.Params<G>> { + label: string + data: D + params: P + getShape: ShapeGetter<D, G, P> + geometryUtils: GeometryUtils<G> +} \ No newline at end of file diff --git a/src/mol-model/shape/shape.ts b/src/mol-model/shape/shape.ts index 98188c58e360606021be9fa5ae25eb9e8421eeef..0d740ef14a47a24fab8dbb37f0a5c994c466645e 100644 --- a/src/mol-model/shape/shape.ts +++ b/src/mol-model/shape/shape.ts @@ -15,6 +15,8 @@ export interface Shape<G extends Geometry = Geometry> { readonly id: UUID /** A name to describe the shape */ readonly name: string + /** The data used to create the shape */ + readonly sourceData: unknown /** The geometry of the shape, e.g. `Mesh` or `Lines` */ readonly geometry: G /** An array of transformation matrices to describe multiple instances of the geometry */ @@ -30,10 +32,11 @@ export interface Shape<G extends Geometry = Geometry> { } export namespace Shape { - export function create<G extends Geometry>(name: string, geometry: G, getColor: Shape['getColor'], getSize: Shape['getSize'], getLabel: Shape['getLabel'], transforms?: Mat4[]): Shape<G> { + export function create<G extends Geometry>(name: string, sourceData: unknown, geometry: G, getColor: Shape['getColor'], getSize: Shape['getSize'], getLabel: Shape['getLabel'], transforms?: Mat4[]): Shape<G> { return { id: UUID.create22(), name, + sourceData, geometry, transforms: transforms || [Mat4.identity()], get groupCount() { return Geometry.getGroupCount(geometry) }, diff --git a/src/mol-model/structure/model/properties/seconday-structure.ts b/src/mol-model/structure/model/properties/seconday-structure.ts index 49618f0071fc1974657ffd2f01cd1ffaf5c18d31..9434507ebc9ae1a11b2fc02f4cb9e00d0d4c4d0c 100644 --- a/src/mol-model/structure/model/properties/seconday-structure.ts +++ b/src/mol-model/structure/model/properties/seconday-structure.ts @@ -17,12 +17,17 @@ interface SecondaryStructure { } namespace SecondaryStructure { - export type Element = None | Helix | Sheet + export type Element = None | Turn | Helix | Sheet export interface None { kind: 'none' } + export interface Turn { + kind: 'turn', + flags: SecondaryStructureType + } + export interface Helix { kind: 'helix', flags: SecondaryStructureType, diff --git a/src/mol-model/structure/model/properties/utils/guess-element.ts b/src/mol-model/structure/model/properties/utils/guess-element.ts index 05658249f5aed93f771b66cae471b9a74be22955..54a66a8f99537da61f09ebcdab5c4a30e33237fc 100644 --- a/src/mol-model/structure/model/properties/utils/guess-element.ts +++ b/src/mol-model/structure/model/properties/utils/guess-element.ts @@ -12,7 +12,7 @@ function charAtIsNumber(str: string, index: number) { return code >= 48 && code <= 57 } -export function guessElement (str: string) { +export function guessElement(str: string) { let at = str.trim().toUpperCase() if (charAtIsNumber(at, 0)) at = at.substr(1) diff --git a/src/mol-model/structure/model/properties/utils/secondary-structure.ts b/src/mol-model/structure/model/properties/utils/secondary-structure.ts index afa8199475f42a616da01e9c8ec056a416c0fd53..68d8c96acbe8615d4f9f4987269799b281acecae 100644 --- a/src/mol-model/structure/model/properties/utils/secondary-structure.ts +++ b/src/mol-model/structure/model/properties/utils/secondary-structure.ts @@ -2,6 +2,7 @@ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org> */ import { SecondaryStructure } from 'mol-model/structure/model/properties/seconday-structure'; @@ -14,13 +15,120 @@ import { IntAdjacencyGraph } from 'mol-math/graph'; import { BitFlags } from 'mol-util'; import { ElementIndex } from 'mol-model/structure/model/indexing'; import { AtomicHierarchy, AtomicConformation } from '../atomic'; +import { ParamDefinition as PD } from 'mol-util/param-definition' -export function computeSecondaryStructure(hierarchy: AtomicHierarchy, conformation: AtomicConformation): SecondaryStructure { +/** + * TODO bugs to fix: + * - some turns are not detected correctly: see e.g. pdb:1acj - maybe more than 2 hbonds require some residue to donate electrons + * - some sheets are not extended correctly: see e.g. pdb:1acj + * - validate new helix definition + * - validate new ordering of secondary structure elements + */ + + /** max distance between two C-alpha atoms to check for hbond */ +const caMaxDist = 9.0; + +/** + * Constant for electrostatic energy in kcal/mol + * f * q1 * q2 + * Q = -332 * 0.42 * 0.20 + * + * f is the dimensional factor + * + * q1 and q2 are partial charges which are placed on the C,O + * (+q1,-q1) and N,H (-q2,+q2) + */ +const Q = -27.888 + +/** cutoff for hbonds in kcal/mol, must be lower to be consider as an hbond */ +const hbondEnergyCutoff = -0.5 +/** prevent extremely low hbond energies */ +const hbondEnergyMinimal = -9.9 + +interface DSSPContext { + params: Partial<PD.Values<SecondaryStructureComputationParams>>, + getResidueFlag: (f: DSSPType) => SecondaryStructureType, + getFlagName: (f: DSSPType) => String, + + hierarchy: AtomicHierarchy + proteinResidues: SortedArray<ResidueIndex> + /** flags for each residue */ + flags: Uint32Array + hbonds: DsspHbonds, + + torsionAngles: { phi: Float32Array, psi: Float32Array }, + backboneIndices: BackboneAtomIndices, + conformation: AtomicConformation, + ladders: Ladder[], + bridges: Bridge[] +} + +interface Ladder { + previousLadder: number, + nextLadder: number, + firstStart: number, + secondStart: number, + secondEnd: number, + firstEnd: number, + type: BridgeType +} + +const enum BridgeType { + PARALLEL = 0x0, + ANTI_PARALLEL = 0x1 +} + +class Bridge { + partner1: number; + partner2: number; + type: BridgeType; + + constructor(p1: number, p2: number, type: BridgeType) { + this.partner1 = Math.min(p1, p2) + this.partner2 = Math.max(p1, p2) + this.type = type + } +} + +type DSSPType = BitFlags<DSSPType.Flag> +namespace DSSPType { + export const is: (t: DSSPType, f: Flag) => boolean = BitFlags.has + export const create: (f: Flag) => DSSPType = BitFlags.create + export const enum Flag { + _ = 0x0, + H = 0x1, + B = 0x2, + E = 0x4, + G = 0x8, + I = 0x10, + S = 0x20, + T = 0x40, + T3 = 0x80, + T4 = 0x100, + T5 = 0x200, + T3S = 0x400, // marks 3-turn start + T4S = 0x800, + T5S = 0x1000 + } +} + +export const SecondaryStructureComputationParams = { + oldDefinition: PD.Boolean(true, { description: 'Whether to use the old DSSP convention for the annotation of turns and helices, causes them to be two residues shorter' }), + oldOrdering: PD.Boolean(true, { description: 'Alpha-helices are preferred over 3-10 helices' }) +} +export type SecondaryStructureComputationParams = typeof SecondaryStructureComputationParams + +export function computeSecondaryStructure(hierarchy: AtomicHierarchy, + conformation: AtomicConformation) { // TODO use Zhang-Skolnik for CA alpha only parts or for coarse parts with per-residue elements return computeModelDSSP(hierarchy, conformation) } -export function computeModelDSSP(hierarchy: AtomicHierarchy, conformation: AtomicConformation) { +export function computeModelDSSP(hierarchy: AtomicHierarchy, + conformation: AtomicConformation, + params: Partial<PD.Values<SecondaryStructureComputationParams>> = {}): SecondaryStructure { + params = { ...PD.getDefaultValues(SecondaryStructureComputationParams), ...params }; + const { lookup3d, proteinResidues } = calcAtomicTraceLookup3D(hierarchy, conformation) const backboneIndices = calcBackboneAtomIndices(hierarchy, proteinResidues) const hbonds = calcBackboneHbonds(hierarchy, conformation, proteinResidues, backboneIndices, lookup3d) @@ -28,69 +136,103 @@ export function computeModelDSSP(hierarchy: AtomicHierarchy, conformation: Atomi const residueCount = proteinResidues.length const flags = new Uint32Array(residueCount) + // console.log(`calculating secondary structure elements using ${ params.oldDefinition ? 'old' : 'revised'} definition and ${ params.oldOrdering ? 'old' : 'revised'} ordering of secondary structure elements`) + + const torsionAngles = calculateDihedralAngles(hierarchy, conformation, proteinResidues, backboneIndices) + + const ladders: Ladder[] = [] + const bridges: Bridge[] = [] + + const getResidueFlag = params.oldDefinition ? getOriginalResidueFlag : getUpdatedResidueFlag + const getFlagName = params.oldOrdering ? getOriginalFlagName : getUpdatedFlagName + const ctx: DSSPContext = { + params, + getResidueFlag, + getFlagName, + hierarchy, proteinResidues, flags, - hbonds + hbonds, + + torsionAngles, + backboneIndices, + conformation, + ladders, + bridges } - assignBends(ctx) assignTurns(ctx) assignHelices(ctx) + assignBends(ctx) assignBridges(ctx) assignLadders(ctx) assignSheets(ctx) - const assignment = getDSSPAssignment(flags) - + const assignment = getDSSPAssignment(flags, getResidueFlag) const type = new Uint32Array(hierarchy.residues._rowCount) as unknown as SecondaryStructureType[] + const keys: number[] = [] + const elements: SecondaryStructure.Element[] = [] + for (let i = 0, il = proteinResidues.length; i < il; ++i) { - type[proteinResidues[i]] = assignment[i] + const assign = assignment[i] + type[proteinResidues[i]] = assign + const flag = getResidueFlag(flags[i]) + // TODO is this expected behavior? elements will be strictly split depending on 'winning' flag + if (elements.length === 0 /* would fail at very start */ || flag !== (elements[elements.length - 1] as SecondaryStructure.Helix | SecondaryStructure.Sheet | SecondaryStructure.Turn).flags /* flag changed */) { + elements[elements.length] = createElement(mapToKind(assign), flags[i], getResidueFlag) + } + keys[i] = elements.length - 1 } const secondaryStructure: SecondaryStructure = { type, - key: [], // TODO - elements: [] // TODO + key: keys, + elements: elements } + return secondaryStructure } -interface DSSPContext { - hierarchy: AtomicHierarchy - proteinResidues: SortedArray<ResidueIndex> - /** flags for each residue */ - flags: Uint32Array - - hbonds: DsspHbonds +function createElement(kind: string, flag: DSSPType.Flag, getResidueFlag: (f: DSSPType) => SecondaryStructureType): SecondaryStructure.Element { + // TODO would be nice to add more detailed information + if (kind === 'helix') { + return { + kind: 'helix', + flags: getResidueFlag(flag) + } as SecondaryStructure.Helix + } else if (kind === 'sheet') { + return { + kind: 'sheet', + flags: getResidueFlag(flag) + } as SecondaryStructure.Sheet + } else if (kind === 'turn' || kind === 'bend') { + return { + kind: 'turn', + flags: getResidueFlag(flag) + } + } else { + return { + kind: 'none' + } + } } -type DSSPType = BitFlags<DSSPType.Flag> -namespace DSSPType { - export const is: (t: DSSPType, f: Flag) => boolean = BitFlags.has - export const create: (f: Flag) => DSSPType = BitFlags.create - export const enum Flag { - _ = 0x0, - H = 0x1, - B = 0x2, - E = 0x4, - G = 0x8, - I = 0x10, - S = 0x20, - T = 0x40, - T3 = 0x80, - T4 = 0x100, - T5 = 0x200, +function mapToKind(assignment: SecondaryStructureType.Flag) { + if (assignment === SecondaryStructureType.SecondaryStructureDssp.H || assignment === SecondaryStructureType.SecondaryStructureDssp.G || assignment === SecondaryStructureType.SecondaryStructureDssp.I) { + return 'helix' + } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.B || assignment === SecondaryStructureType.SecondaryStructureDssp.E) { + return 'sheet' + } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.T) { + return 'turn' + } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.S) { + return 'bend' + } else { + return 'none' } } -/** max distance between two C-alpha atoms to check for hbond */ -const caMaxDist = 7.0; - -/** min distance between two C-alpha atoms to check for hbond */ -const caMinDist = 4.0; - function calcAtomicTraceLookup3D(hierarchy: AtomicHierarchy, conformation: AtomicConformation) { const { x, y, z } = conformation; const { moleculeType, traceElementIndex } = hierarchy.derived.residue @@ -141,6 +283,104 @@ function calcBackboneAtomIndices(hierarchy: AtomicHierarchy, proteinResidues: So type DsspHbonds = IntAdjacencyGraph<{ readonly energies: ArrayLike<number> }> +/** + * Bend(i) =: [angle ((CW - Ca(i - 2)),(C"(i + 2) - C"(i))) > 70"] + * + * Type: S + */ +function assignBends(ctx: DSSPContext) { + const flags = ctx.flags + const { x, y, z } = ctx.conformation + const { traceElementIndex } = ctx.hierarchy.derived.residue + + const proteinResidues = ctx.proteinResidues + const residueCount = proteinResidues.length + + const position = (i: number, v: Vec3) => Vec3.set(v, x[i], y[i], z[i]) + + const caPosPrev2 = Vec3.zero() + const caPos = Vec3.zero() + const caPosNext2 = Vec3.zero() + + const nIndices = ctx.backboneIndices.nIndices + const cPos = Vec3.zero() + const nPosNext = Vec3.zero() + + f1: for (let i = 2; i < residueCount - 2; i++) { + // check for peptide bond + for (let k = 0; k < 4; k++) { + let index = i + k - 2 + position(traceElementIndex[index], cPos) + position(nIndices[index + 1], nPosNext) + if (Vec3.squaredDistance(cPos, nPosNext) > 6.25 /* max squared peptide bond distance allowed */) { + continue f1 + } + } + + const oRIprev2 = proteinResidues[i - 2] + const oRI = proteinResidues[i] + const oRInext2 = proteinResidues[i + 2] + + const caAtomPrev2 = traceElementIndex[oRIprev2] + const caAtom = traceElementIndex[oRI] + const caAtomNext2 = traceElementIndex[oRInext2] + + position(caAtomPrev2, caPosPrev2) + position(caAtom, caPos) + position(caAtomNext2, caPosNext2) + + const caMinus2 = Vec3.zero() + const caPlus2 = Vec3.zero() + + Vec3.sub(caMinus2, caPosPrev2, caPos) + Vec3.sub(caPlus2, caPos, caPosNext2) + + const angle = Vec3.angle(caMinus2, caPlus2) * 360 / (2 * Math.PI) + if (angle && angle > 70.00) { + flags[i] |= DSSPType.Flag.S + } + } +} + +function calculateDihedralAngles(hierarchy: AtomicHierarchy, conformation: AtomicConformation, proteinResidues: SortedArray<ResidueIndex>, backboneIndices: BackboneAtomIndices): { phi: Float32Array, psi: Float32Array } { + const { cIndices, nIndices } = backboneIndices + const { index } = hierarchy + const { x, y, z } = conformation + const { traceElementIndex } = hierarchy.derived.residue + + const residueCount = proteinResidues.length + const position = (i: number, v: Vec3) => Vec3.set(v, x[i], y[i], z[i]) + + let cPosPrev = Vec3.zero(), caPosPrev = Vec3.zero(), nPosPrev = Vec3.zero() + let cPos = Vec3.zero(), caPos = Vec3.zero(), nPos = Vec3.zero() + let cPosNext = Vec3.zero(), caPosNext = Vec3.zero(), nPosNext = Vec3.zero() + + const phi: Float32Array = new Float32Array(residueCount - 1) + const psi: Float32Array = new Float32Array(residueCount - 1) + + const cAtomPrev = cIndices[-1], caAtomPrev = traceElementIndex[proteinResidues[-1]], nAtomPrev = nIndices[-1] + position(cAtomPrev, cPosPrev), position(caAtomPrev, caPosPrev), position(nAtomPrev, nPosPrev) + const cAtom = cIndices[0], caAtom = traceElementIndex[proteinResidues[0]], nAtom = nIndices[0] + position(cAtom, cPos), position(caAtom, caPos), position(nAtom, nPos) + const cAtomNext = cIndices[1], caAtomNext = traceElementIndex[proteinResidues[1]], nAtomNext = nIndices[1] + position(cAtomNext, cPosNext), position(caAtomNext, caPosNext), position(nAtomNext, nPosNext) + for (let i = 0; i < residueCount - 1; ++i) { + // ignore C-terminal residue as acceptor + if (index.findAtomOnResidue(proteinResidues[i], 'OXT') !== -1) continue + + // returns NaN for missing atoms + phi[i] = Vec3.dihedralAngle(cPosPrev, nPos, caPos, cPos) + psi[i] = Vec3.dihedralAngle(nPos, caPos, cPos, nPosNext) + + cPosPrev = cPos, caPosPrev = caPos, nPosPrev = nPos + cPos = cPosNext, caPos = caPosNext, nPos = nPosNext + + position(cIndices[i + 1], cPosNext), position(traceElementIndex[proteinResidues[i + 1]], caPosNext), position(nIndices[i + 1], nPosNext) + } + + return { phi, psi }; +} + function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConformation, proteinResidues: SortedArray<ResidueIndex>, backboneIndices: BackboneAtomIndices, lookup3d: GridLookup3D): DsspHbonds { const { cIndices, hIndices, nIndices, oIndices } = backboneIndices const { index } = hierarchy @@ -163,8 +403,6 @@ function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConf const cPosPrev = Vec3.zero() const oPosPrev = Vec3.zero() - const caMinDistSq = caMinDist * caMinDist - for (let i = 0, il = proteinResidues.length; i < il; ++i) { const oPI = i const oRI = proteinResidues[i] @@ -183,11 +421,9 @@ function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConf position(cAtom, cPos) position(caAtom, caPos) - const { indices, count, squaredDistances } = lookup3d.find(caPos[0], caPos[1], caPos[2], caMaxDist) + const { indices, count } = lookup3d.find(caPos[0], caPos[1], caPos[2], caMaxDist) for (let j = 0; j < count; ++j) { - if (squaredDistances[j] < caMinDistSq) continue - const nPI = indices[j] // ignore bonds within a residue or to prev or next residue, TODO take chain border into account @@ -245,8 +481,8 @@ function buildHbondGraph(residueCount: number, oAtomResidues: number[], nAtomRes /** Original priority: H,B,E,G,I,T,S */ function getOriginalResidueFlag(f: DSSPType) { if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H - if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E + if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T @@ -254,55 +490,50 @@ function getOriginalResidueFlag(f: DSSPType) { return SecondaryStructureType.Flag.None } +function getOriginalFlagName(f: DSSPType) { + if (DSSPType.is(f, DSSPType.Flag.H)) return 'H' + if (DSSPType.is(f, DSSPType.Flag.E)) return 'E' + if (DSSPType.is(f, DSSPType.Flag.B)) return 'B' + if (DSSPType.is(f, DSSPType.Flag.G)) return 'G' + if (DSSPType.is(f, DSSPType.Flag.I)) return 'I' + if (DSSPType.is(f, DSSPType.Flag.T)) return 'T' + if (DSSPType.is(f, DSSPType.Flag.S)) return 'S' + return '-' +} + /** Version 2.1.0 priority: I,H,B,E,G,T,S */ function getUpdatedResidueFlag(f: DSSPType) { if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H - if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E + if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T if (DSSPType.is(f, DSSPType.Flag.S)) return SecondaryStructureType.SecondaryStructureDssp.S return SecondaryStructureType.Flag.None } -// function geFlagName(f: DSSPType) { -// if (DSSPType.is(f, DSSPType.Flag.I)) return 'I' -// if (DSSPType.is(f, DSSPType.Flag.H)) return 'H' -// if (DSSPType.is(f, DSSPType.Flag.B)) return 'B' -// if (DSSPType.is(f, DSSPType.Flag.E)) return 'E' -// if (DSSPType.is(f, DSSPType.Flag.G)) return 'G' -// if (DSSPType.is(f, DSSPType.Flag.T)) return 'T' -// if (DSSPType.is(f, DSSPType.Flag.S)) return 'S' -// return '-' -// } - -function getDSSPAssignment(flags: Uint32Array, useOriginal = false) { - const getResidueFlag = useOriginal ? getOriginalResidueFlag : getUpdatedResidueFlag +function getUpdatedFlagName(f: DSSPType) { + if (DSSPType.is(f, DSSPType.Flag.I)) return 'I' + if (DSSPType.is(f, DSSPType.Flag.H)) return 'H' + if (DSSPType.is(f, DSSPType.Flag.E)) return 'E' + if (DSSPType.is(f, DSSPType.Flag.B)) return 'B' + if (DSSPType.is(f, DSSPType.Flag.G)) return 'G' + if (DSSPType.is(f, DSSPType.Flag.T)) return 'T' + if (DSSPType.is(f, DSSPType.Flag.S)) return 'S' + return '-' +} + +function getDSSPAssignment(flags: Uint32Array, getResidueFlag: (f: DSSPType) => SecondaryStructureType) { const type = new Uint32Array(flags.length) for (let i = 0, il = flags.length; i < il; ++i) { const f = DSSPType.create(flags[i]) - // console.log(i, geFlagName(f)) type[i] = getResidueFlag(f) } + return type as unknown as ArrayLike<SecondaryStructureType> } -/** - * Constant for electrostatic energy in kcal/mol - * f * q1 * q2 - * Q = -332 * 0.42 * 0.20 - * - * f is the dimensional factor - * - * q1 and q2 are partial charges which are placed on the C,O - * (+q1,-q1) and N,H (-q2,+q2) - */ -const Q = -27.888 - -/** cutoff for hbonds in kcal/mol, must be lower to be consider as an hbond */ -const hbondEnergyCutoff = -0.5 - /** * E = Q * (1/r(ON) + l/r(CH) - l/r(OH) - l/r(CN)) */ @@ -314,7 +545,13 @@ function calcHbondEnergy(oPos: Vec3, cPos: Vec3, nPos: Vec3, hPos: Vec3) { const e1 = Q / distOH - Q / distCH const e2 = Q / distCN - Q / distON - return e1 + e2 + const e = e1 + e2 + + // cap lowest possible energy + if (e < hbondEnergyMinimal) + return hbondEnergyMinimal + + return e } /** @@ -329,24 +566,31 @@ function assignTurns(ctx: DSSPContext) { const { chains, residueAtomSegments, chainAtomSegments } = hierarchy const { label_asym_id } = chains - const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5] + const turnFlag = [DSSPType.Flag.T3S, DSSPType.Flag.T4S, DSSPType.Flag.T5S, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5] - for (let i = 0, il = proteinResidues.length; i < il; ++i) { - const rI = proteinResidues[i] - const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]] + for (let idx = 0; idx < 3; idx++) { + for (let i = 0, il = proteinResidues.length - 1; i < il; ++i) { + const rI = proteinResidues[i] + const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]] - // TODO should take sequence gaps into account - for (let k = 3; k <= 5; ++k) { - if (i + k >= proteinResidues.length) continue - - const rN = proteinResidues[i + k] + // TODO should take sequence gaps into account + const rN = proteinResidues[i + idx + 3] const cN = chainAtomSegments.index[residueAtomSegments.offsets[rN]] // check if on same chain if (!label_asym_id.areValuesEqual(cI, cN)) continue // check if hbond exists - if (hbonds.getDirectedEdgeIndex(i, i + k) !== -1) { - flags[i] |= turnFlag[k] | DSSPType.Flag.T + if (hbonds.getDirectedEdgeIndex(i, i + idx + 3) !== -1) { + flags[i] |= turnFlag[idx + 3] | turnFlag[idx] + if (ctx.params.oldDefinition) { + for (let k = 1; k < idx + 3; ++k) { + flags[i + k] |= turnFlag[idx + 3] | DSSPType.Flag.T + } + } else { + for (let k = 0; k <= idx + 3; ++k) { + flags[i + k] |= turnFlag[idx + 3] | DSSPType.Flag.T + } + } } } } @@ -369,7 +613,7 @@ function assignTurns(ctx: DSSPContext) { * Type: B */ function assignBridges(ctx: DSSPContext) { - const { proteinResidues, hbonds, flags } = ctx + const { proteinResidues, hbonds, flags, bridges } = ctx const { offset, b } = hbonds let i: number, j: number @@ -385,6 +629,8 @@ function assignBridges(ctx: DSSPContext) { if (i !== j && hbonds.getDirectedEdgeIndex(j, i + 1) !== -1) { flags[i] |= DSSPType.Flag.B flags[j] |= DSSPType.Flag.B + // TODO move to constructor, actually omit object all together + bridges[bridges.length] = new Bridge(i, j, BridgeType.PARALLEL) } // Parallel Bridge(i, j) =: [Hbond(j - 1, i) and Hbond(i, j + 1)] @@ -393,6 +639,7 @@ function assignBridges(ctx: DSSPContext) { if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i) !== -1) { flags[i] |= DSSPType.Flag.B flags[j] |= DSSPType.Flag.B + bridges[bridges.length] = new Bridge(j, i, BridgeType.PARALLEL) } // Antiparallel Bridge(i, j) =: [Hbond(i, j) and Hbond(j, i)] @@ -401,6 +648,7 @@ function assignBridges(ctx: DSSPContext) { if (i !== j && hbonds.getDirectedEdgeIndex(j, i) !== -1) { flags[i] |= DSSPType.Flag.B flags[j] |= DSSPType.Flag.B + bridges[bridges.length] = new Bridge(j, i, BridgeType.ANTI_PARALLEL) } // Antiparallel Bridge(i, j) =: [Hbond(i - 1, j + 1) and Hbond(j - 1, i + l)] @@ -409,9 +657,12 @@ function assignBridges(ctx: DSSPContext) { if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i + 1) !== -1) { flags[i] |= DSSPType.Flag.B flags[j] |= DSSPType.Flag.B + bridges[bridges.length] = new Bridge(j, i, BridgeType.ANTI_PARALLEL) } } } + + bridges.sort((a, b) => a.partner1 > b.partner1 ? 1 : a.partner1 < b.partner1 ? -1 : 0) } /** @@ -428,17 +679,41 @@ function assignBridges(ctx: DSSPContext) { function assignHelices(ctx: DSSPContext) { const { proteinResidues, flags } = ctx - const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5] + const turnFlag = [DSSPType.Flag.T3S, DSSPType.Flag.T4S, DSSPType.Flag.T5S, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5] const helixFlag = [0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I] - for (let i = 1, il = proteinResidues.length; i < il; ++i) { - const fI = DSSPType.create(flags[i]) - const fI1 = DSSPType.create(flags[i - 1]) + const helixCheckOrder = ctx.params.oldOrdering ? [4, 3, 5] : [3, 4, 5] + for (let ni = 0; ni < helixCheckOrder.length; ni++) { + const n = helixCheckOrder[ni] + + for (let i = 1, il = proteinResidues.length - n; i < il; i++) { + const fI = DSSPType.create(flags[i]) + const fI1 = DSSPType.create(flags[i - 1]) + const fI2 = DSSPType.create(flags[i + 1]) - for (let k = 3; k <= 5; ++k) { - if (DSSPType.is(fI, turnFlag[k]) && DSSPType.is(fI1, turnFlag[k])) { - for (let l = 0; l < k; ++l) { - flags[i + l] |= helixFlag[k] + // TODO rework to elegant solution which will not break instantly + if (ctx.params.oldOrdering) { + if ((n === 3 && (DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.H)) || // for 3-10 yield to alpha helix + (n === 5 && ((DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI, DSSPType.Flag.G)) || (DSSPType.is(fI2, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.G)))))) { // for pi yield to all other helices + continue + } + } else { + if ((n === 4 && (DSSPType.is(fI, DSSPType.Flag.G) || DSSPType.is(fI2, DSSPType.Flag.G)) || // for alpha helix yield to 3-10 + (n === 5 && ((DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI, DSSPType.Flag.G)) || (DSSPType.is(fI2, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.G)))))) { // for pi yield to all other helices + continue + } + } + + if (DSSPType.is(fI, turnFlag[n]) && DSSPType.is(fI, turnFlag[n - 3]) && // check fI for turn start of proper type + DSSPType.is(fI1, turnFlag[n]) && DSSPType.is(fI1, turnFlag[n - 3])) { // check fI1 accordingly + if (ctx.params.oldDefinition) { + for (let k = 0; k < n; k++) { + flags[i + k] |= helixFlag[n] + } + } else { + for (let k = -1; k <= n; k++) { + flags[i + k] |= helixFlag[n] + } } } } @@ -451,23 +726,137 @@ function assignHelices(ctx: DSSPContext) { * Type: E */ function assignLadders(ctx: DSSPContext) { - // TODO + const { bridges, ladders } = ctx + + // create ladders + for (let bridgeIndex = 0; bridgeIndex < bridges.length; bridgeIndex++) { + const bridge = bridges[bridgeIndex] + let found = false + for (let ladderIndex = 0; ladderIndex < ladders.length; ladderIndex++) { + const ladder = ladders[ladderIndex] + if (shouldExtendLadder(ladder, bridge)) { + found = true + ladder.firstEnd++ + if (bridge.type === BridgeType.PARALLEL) { + ladder.secondEnd++ + } else { + ladder.secondStart-- + } + } + } + + // no suitable assignment: create new ladder with single bridge as content + if (!found) { + ladders[ladders.length] = { + previousLadder: 0, + nextLadder: 0, + firstStart: bridge.partner1, + firstEnd: bridge.partner1, + secondStart: bridge.partner2, + secondEnd: bridge.partner2, + type: bridge.type + } + } + } + + // connect ladders + for (let ladderIndex1 = 0; ladderIndex1 < ladders.length; ladderIndex1++) { + const ladder1 = ladders[ladderIndex1] + for (let ladderIndex2 = ladderIndex1; ladderIndex2 < ladders.length; ladderIndex2++) { + const ladder2 = ladders[ladderIndex2] + if (resemblesBulge(ladder1, ladder2)) { + ladder1.nextLadder = ladderIndex2 + ladder2.previousLadder = ladderIndex1 + } + } + } } /** - * sheet=: set of one or more ladders connected by shared residues - * - * Type: E + * For beta structures, we define: a bulge-linked ladder consists of two ladders or bridges of the same type + * connected by at most one extra residue of one strand and at most four extra residues on the other strand, + * all residues in bulge-linked ladders are marked E, including any extra residues. */ -function assignSheets(ctx: DSSPContext) { - // TODO +function resemblesBulge(ladder1: Ladder, ladder2: Ladder) { + if (!(ladder1.type === ladder2.type && ladder2.firstStart - ladder1.firstEnd < 6 && + ladder1.firstStart < ladder2.firstStart && ladder2.nextLadder === 0)) return false + + if (ladder1.type === BridgeType.PARALLEL) { + return bulgeCriterion2(ladder1, ladder2) + } else { + return bulgeCriterion2(ladder2, ladder1) + } +} + +function bulgeCriterion2(ladder1: Ladder, ladder2: Ladder) { + return ladder2.secondStart - ladder1.secondEnd > 0 && ((ladder2.secondStart - ladder1.secondEnd < 6 && + ladder2.firstStart - ladder1.firstEnd < 3) || ladder2.secondStart - ladder1.secondEnd < 3) +} + +function shouldExtendLadder(ladder: Ladder, bridge: Bridge): boolean { + // in order to extend ladders, same type must be present + if (bridge.type !== ladder.type) return false + + // only extend if residue 1 is sequence neighbor with regard to ladder + if (bridge.partner1 !== ladder.firstEnd + 1) return false + + if (bridge.type === BridgeType.PARALLEL) { + if (bridge.partner2 === ladder.secondEnd + 1) { + return true + } + } else { + if (bridge.partner2 === ladder.secondStart - 1) { + return true + } + } + + return false +} + +function isHelixType(f: DSSPType) { + return DSSPType.is(f, DSSPType.Flag.G) || DSSPType.is(f, DSSPType.Flag.H) || DSSPType.is(f, DSSPType.Flag.I) } /** - * Bend(i) =: [angle ((CW - Ca(i - 2)),(C"(i + 2) - C"(i))) > 70"] + * sheet=: set of one or more ladders connected by shared residues * - * Type: S + * Type: E */ -function assignBends(ctx: DSSPContext) { - // TODO +function assignSheets(ctx: DSSPContext) { + const { ladders, flags } = ctx + for (let ladderIndex = 0; ladderIndex < ladders.length; ladderIndex++) { + const ladder = ladders[ladderIndex] + for (let lcount = ladder.firstStart; lcount <= ladder.firstEnd; lcount++) { + const diff = ladder.firstStart - lcount + const l2count = ladder.secondStart - diff + + if (ladder.firstStart !== ladder.firstEnd) { + flags[lcount] |= DSSPType.Flag.E + flags[l2count] |= DSSPType.Flag.E + } else { + if (!isHelixType(flags[lcount]) && DSSPType.is(flags[lcount], DSSPType.Flag.E)) { + flags[lcount] |= DSSPType.Flag.B + } + if (!isHelixType(flags[l2count]) && DSSPType.is(flags[l2count], DSSPType.Flag.E)) { + flags[l2count] |= DSSPType.Flag.B + } + } + } + + if (ladder.nextLadder === 0) continue + + const conladder = ladders[ladder.nextLadder] + for (let lcount = ladder.firstStart; lcount <= conladder.firstEnd; lcount++) { + flags[lcount] |= DSSPType.Flag.E + } + if (ladder.type === BridgeType.PARALLEL) { + for (let lcount = ladder.secondStart; lcount <= conladder.secondEnd; lcount++) { + flags[lcount] |= DSSPType.Flag.E + } + } else { + for (let lcount = conladder.secondEnd; lcount <= ladder.secondStart; lcount++) { + flags[lcount] |= DSSPType.Flag.E + } + } + } } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/utils/secondary-structure.validation b/src/mol-model/structure/model/properties/utils/secondary-structure.validation new file mode 100644 index 0000000000000000000000000000000000000000..4f1a1f1bba2be14065663ee39ef36e21fb2d575a --- /dev/null +++ b/src/mol-model/structure/model/properties/utils/secondary-structure.validation @@ -0,0 +1,75 @@ +compares Mol* port of DSSP (with default parameters) to the BioJava implementation + +### pdb:1pga ### +# turns # +Mol*: ----------------------TTTTTTTTTTTTTTTT--------TTTT------ +53 turns, 18 openings +DSSP: ----------------------TTTTTTTTTTTTTTTT--------TTTT------ +53 turns, 18 openings + +# bends # +Mol*: ---------SS---------SSSSSSSSSSSSSSSSSS--------SSS------- +23 bends +DSSP: ---------SS---------SSSSSSSSSSSSSSSSSS--------SSS------- +23 bends + +# helices # +Mol*: ----------------------HHHHHHHHHHHHHHTT--------TTTT------ +44 helix elements - 0 3-10, 44 alpha, 0 pi +DSSP: ----------------------HHHHHHHHHHHHHHTT--------TTTT------ +44 helix elements - 0 3-10, 44 alpha, 0 pi + +# all # +Mol*: -EEEEEEE-SS-EEEEEEE-SSHHHHHHHHHHHHHHTT---EEEEETTTTEEEEE- +DSSP: -EEEEEEE-SS-EEEEEEE-SSHHHHHHHHHHHHHHTT---EEEEETTTTEEEEE- + + +### pdb:1bta ### +# turns # +Mol*: ------TTT---TTTTTTTTTTTTT--TT----TTTTTTTTTTT-----------TTTTTTTTT--TTTTTTTTTTTTTTT-------- +127 turns, 44 openings +DSSP: ------TTT---TTTTTTTTTTTTT--TT----TTTTTTTTTTT-----------TTTTTTTTT--TTTTTTTTTTTTTTT-------- +127 turns, 44 openings + +# bends # +Mol*: ------SSS--SSSSSSSSSSSSS---SS---SSSSSSSSSSSSS-SS------SSSSSSSSSSSSSSSSSSSSSSSSSSS-------- +60 bends +DSSP: ------SSS--SSSSSSSSSSSSS---SS---SSSSSSSSSSSSS-SS------SSSSSSSSSSSSSSSSSSSSSSSSSSS-------- +60 bends + +# helices # +Mol*: ------TTT---HHHHHHHHHHHHT--TT----HHHHHHHHTTT-----------TTHHHHTTT--HHHHHHHHHHHHHTT-------- +100 helix elements - 0 3-10, 100 alpha, 0 pi +DSSP: ------TTT---HHHHHHHHHHHHT--TT----HHHHHHHHTTT-----------TTHHHHTTT--HHHHHHHHHHHHHTT-------- +100 helix elements - 0 3-10, 100 alpha, 0 pi + +# all # +Mol*: -EEEEETTT--SHHHHHHHHHHHHT--TT---SHHHHHHHHTTTS-SSEEEEEESTTHHHHTTTSSHHHHHHHHHHHHHTT--EEEEE- +DSSP: -EEEEETTT--SHHHHHHHHHHHHT--TT---SHHHHHHHHTTTS-SSEEEEEESTTHHHHTTTSSHHHHHHHHHHHHHTT--EEEEE- + + +### pdb:1acj ### +# turns # +Mol*: -------TT----------TT----------------TTTTT----------------------------TTTT-TTTTTT----------------------------------TTT------TTT-TTTTTTTTT-----------TTTT---TT-------TTTTTTTTTTTTTTTTTTTTT--TT-------TTTTTTTTTTTT-TTTTTT----------TT-TT----TTTTTTTTTTTTTTTT-----TTTTTTTTTT--TTTTTTTTTTT-----------------------TTTTTTTT----------------TTTTTTT-TT--TT------TTTTTTTTTTTTTT--TTTTTTTTTTT--TTTTT-TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT-------------TT----TTT---TTTTTTTTTTTTT-TTT---TTTTTTTTTTTTTTTTTTTT--------------------------------TTTTTTTTTTTTTTTTTTT- +614 turns, 223 openings +DSSP: -------TT----------TT----------------TTTTT------------------------------TT-TTTTTT----------------------------------TTT------TTT-TTTTTTTTT-----------TTTT---TT-------TTTTTTTTTTTTTTTTTTTTT--TT-------TTTTTTTTTTTT-TTTTTT----------TT-TT----TTTTTTTTTTTTTTTT-----TTTTTTTTTT--TTTTTTTTTTT-----------------------TTTTTTTT----------------TTTTTTT-TT--TT------TTTTTTTTTTT-TT--TTTTTTTTTTT--TTTTT-TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT-------------TT----TTT---TTTTTTTTTTTTT-TTT---TTTTTTTTTTTTTTTTTTTT--------------------------------TTTTTTTTTTTTTTTTTTT- +606 turns, 220 openings + +# bends # +Mol*: --S----SS----------SSS----------S----SS-SSS--------SS-----S-----------SSSS-SSSSSSS--S---S----------SS--SS---------SSSS---S-SSSS--SSSSSSS-----------SSSS----SS-SSS-S-SSSSSSSSSSSSSSSSSSSSS--SSS------SSSSSSSSSSSS-SSSSSS-S----SS--SSSSSS---SSSSSSSSSSSSSSS----S-SSSSSSSSSSS-SSSSSSSSSS--SS--SS--S------SSSSSS-SSSSSSS--S--S-------S--SSSSSSSSSSS--SSS-----SSSSSSSSSSS-SS--SSSSSSSSSSS--SSSSS-SSSSSSSSSSSSSSSSS----SSSSSSSSSSSS-----------SS--S-SSS-S-SS-SSSSSS--SS-SSS---SSSSSSSSSSSSSSSSSSSSSSSS---------SSS------SSSS----SSSS-S----SSSSSSSSSS-- +305 bends +DSSP: --S----SS----------SSS----------S----SS-SSS--------SS-----S-----------SSSS-SSSSSSS--S---S----------SS--SS---------SSSS---S-SSSS--SSSSSSS-----------SSSS----SS-SSS-S-SSSSSSSSSSSSSSSSSSSSS--SSS------SSSSSSSSSSSS-SSSSSS-S----SS--SSSSSS---SSSSSSSSSSSSSSS----S-SSSSSSSSSSS-SSSSSSSSSS--SS--SS--S------SSSSSS-SSSSSSS--S--S-------S--SSSSSSSSSSS--SSS-----SSSSSSSSSSS-SS--SSSSSSSSSSS--SSSSS-SSSSSSSSSSSSSSSSS----SSSSSSSSSSSS-----------SS--S-SSS-S-SS-SSSSSS--SS-SSS---SSSSSSSSSSSSSSSSSSSSSSSS---------SSS------SSSS----SSSS-S----SSSSSSSSSS-- +305 bends + +# helices # +Mol*: -------TT----------TT----------------GGGTT----------------------------TTTT-HHHHTT----------------------------------TTT------GGG-THHHHHHHT-----------HHHH---TT-------HHHHHHHHHHHHHHHHGGGGT--TT-------THHHHHHHHHHH-HHHHTT----------TT-TT----HHHHHHHHHHHHHHTT-----HHHHHHHHHH--HHHHHHHHGGG-----------------------HHHHHHHT----------------HHHHHHH-TT--TT------HHHHHHHHHHHTTT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTT-------------TT----GGG---TTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHT--------------------------------TTHHHHHHHHTHHHHHHHH- +523 helix elements - 27 3-10, 496 alpha, 0 pi +DSSP: -------TT----------TT----------------GGGTT------------------------------TT-HHHHTT----------------------------------TTT------GGG-THHHHHHHT-----------HHHH---TT-------HHHHHHHHHHHHHHHHGGGGT--TT-------THHHHHHHHHHH-HHHHTT----------TT-TT----HHHHHHHHHHHHHHTT-----HHHHHHHHHH--HHHHHHHHGGG-----------------------HHHHHHHT----------------HHHHHHH-TT--TT------HHHHHHHHHHH-TT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTT-------------TT----GGG---TTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHT--------------------------------TTHHHHHHHHTHHHHHHHH- +523 helix elements - 27 3-10, 496 alpha, 0 pi + +# all # +Mol*: --SEEEETTEEEE-EEEEETTEEEEEEEEEE-EE---GGGTTS--EE----SSEEE--S---B-------TTTT-HHHHTTS--S-B-S---EEEEEE-SS--SSEEEEEEE--STTT---S-SGGG-THHHHHHHT-EEEE-----SHHHH---TT-SSS-S-HHHHHHHHHHHHHHHHGGGGTEEEEEEEEEEETHHHHHHHHHHH-HHHHTT-SEEEEES--TTSTTSEEEHHHHHHHHHHHHHHTT---S-HHHHHHHHHHS-HHHHHHHHGGG-SS--SS--S--EEE-SSSSSS-HHHHHHHT-S--S-EEEEEESB-SHHHHHHHSTT--TTS-----HHHHHHHHHHHTTT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTTSS-EEEEEE----TT--S-GGG-SBTTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHTSSSS---------SSS-EEEEESSSS--EEESTTHHHHHHHHTHHHHHHHH- +DSSP: --SEEEETTEEEE-EEEEETTEEEEEEEEEE-EE---GGGTTS--EE----SSEEE--S---B-------SSTT-HHHHTTS--S-B-S---EEEEEE-SS--SSEEEEEEE--STTT---S-SGGG-THHHHHHHT-EEEE-----SHHHH---TT-SSS-S-HHHHHHHHHHHHHHHHGGGGTEEEEEEEEEEETHHHHHHHHHHH-HHHHTT-SEEEEES--TTSTTS-EEHHHHHHHHHHHHHHTT---S-HHHHHHHHHHS-HHHHHHHHGGG-SS--SS--S---EE-SSSSSS-HHHHHHHT-S--S-EEEEEESB-SHHHHHHHSTT--TTS-----HHHHHHHHHHH-TT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTTSS-EEEEEE----TT--S-GGG-SBTTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHTSSSS---------SSS-EEEEESSSS--EEESTTHHHHHHHHTHHHHHHHH- + +TODO fix mismatches e.g. here +TODO move to spec.ts once tests are running \ No newline at end of file diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index 89293da9605b464d3eec6efe7ec3f9d136258b0e..76c0982a1f65792f5597106ad90b011c01420d79 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -275,41 +275,42 @@ export namespace SecondaryStructureType { DoubleHelix = 0x1, Helix = 0x2, Beta = 0x4, - Turn = 0x8, + Bend = 0x8, + Turn = 0x10, // category variant - LeftHanded = 0x10, // helix - RightHanded = 0x20, + LeftHanded = 0x20, // helix + RightHanded = 0x40, - ClassicTurn = 0x40, // turn - InverseTurn = 0x80, + ClassicTurn = 0x80, // turn + InverseTurn = 0x100, // sub-category - HelixOther = 0x100, // protein - Helix27 = 0x200, - Helix3Ten = 0x400, - HelixAlpha = 0x800, - HelixGamma = 0x1000, - HelixOmega = 0x2000, - HelixPi = 0x4000, - HelixPolyproline = 0x8000, - - DoubleHelixOther = 0x10000, // nucleic - DoubleHelixZ = 0x20000, - DoubleHelixA = 0x40000, - DoubleHelixB = 0x80000, - - BetaOther = 0x100000, // protein - BetaStrand = 0x200000, // single strand - BetaSheet = 0x400000, // multiple hydrogen bonded strands - BetaBarell = 0x800000, // closed series of sheets - - TurnOther = 0x1000000, // protein - Turn1 = 0x2000000, - Turn2 = 0x4000000, - Turn3 = 0x8000000, - - NA = 0x10000000, // not applicable/available + HelixOther = 0x200, // protein + Helix27 = 0x400, + Helix3Ten = 0x800, + HelixAlpha = 0x1000, + HelixGamma = 0x2000, + HelixOmega = 0x4000, + HelixPi = 0x8000, + HelixPolyproline = 0x10000, + + DoubleHelixOther = 0x20000, // nucleic + DoubleHelixZ = 0x40000, + DoubleHelixA = 0x80000, + DoubleHelixB = 0x100000, + + BetaOther = 0x200000, // protein + BetaStrand = 0x400000, // single strand + BetaSheet = 0x800000, // multiple hydrogen bonded strands + BetaBarell = 0x1000000, // closed series of sheets + + TurnOther = 0x2000000, // protein + Turn1 = 0x4000000, + Turn2 = 0x8000000, + Turn3 = 0x10000000, + + NA = 0x20000000, // not applicable/available } export const SecondaryStructureMmcif: { [value: string]: number } = { @@ -386,7 +387,7 @@ export namespace SecondaryStructureType { G: Flag.Helix | Flag.Helix3Ten, // 3-helix (310 helix) I: Flag.Helix | Flag.HelixPi, // 5 helix (pi-helix) T: Flag.Turn, // hydrogen bonded turn - S: Flag.Turn, // bend + S: Flag.Bend, // bend } } diff --git a/src/mol-plugin/behavior/dynamic/labels.ts b/src/mol-plugin/behavior/dynamic/labels.ts index be54651502d57168464b9a568c7a69eb7043cec6..16b43e187b7f1b41e48ea825119088240df1e457 100644 --- a/src/mol-plugin/behavior/dynamic/labels.ts +++ b/src/mol-plugin/behavior/dynamic/labels.ts @@ -107,7 +107,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({ private getLabelsShape = (ctx: RuntimeContext, data: LabelsData, props: SceneLabelsProps, shape?: Shape<Text>) => { this.geo = getLabelsText(data, props, this.geo) - return Shape.create('Scene Labels', this.geo, this.getColor, this.getSize, this.getLabel, data.transforms) + return Shape.create('Scene Labels', data, this.geo, this.getColor, this.getSize, this.getLabel, data.transforms) } /** Update structures to be labeled, returns true if changed */ diff --git a/src/mol-plugin/state/actions/data-format.ts b/src/mol-plugin/state/actions/data-format.ts index c8eff16e37cb84cb2d61fc5174cff5e74f55f7ab..3cb8a05da5a2fae2722df766e895f691050b2d53 100644 --- a/src/mol-plugin/state/actions/data-format.ts +++ b/src/mol-plugin/state/actions/data-format.ts @@ -14,6 +14,7 @@ import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume'; import { StateTransforms } from '../transforms'; import { MmcifProvider, PdbProvider, GroProvider } from './structure'; import msgpackDecode from 'mol-io/common/msgpack/decode' +import { PlyProvider } from './shape'; export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> { private _list: { name: string, provider: DataFormatProvider<D> }[] = [] @@ -60,6 +61,7 @@ export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | Plugin this.add('gro', GroProvider) this.add('mmcif', MmcifProvider) this.add('pdb', PdbProvider) + this.add('ply', PlyProvider) }; private _clear() { diff --git a/src/mol-plugin/state/actions/shape.ts b/src/mol-plugin/state/actions/shape.ts new file mode 100644 index 0000000000000000000000000000000000000000..7a1311149ed894a5112b189a1ab0f92f323b79d8 --- /dev/null +++ b/src/mol-plugin/state/actions/shape.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PluginContext } from 'mol-plugin/context'; +import { State, StateBuilder } from 'mol-state'; +import { Task } from 'mol-task'; +import { FileInfo } from 'mol-util/file-info'; +import { PluginStateObject } from '../objects'; +import { StateTransforms } from '../transforms'; +import { DataFormatProvider } from './data-format'; + +export const PlyProvider: DataFormatProvider<any> = { + label: 'PLY', + description: 'PLY', + stringExtensions: ['ply'], + binaryExtensions: [], + isApplicable: (info: FileInfo, data: string) => { + return info.ext === 'ply' + }, + getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, state: State) => { + return Task.create('PLY default builder', async taskCtx => { + const tree = data.apply(StateTransforms.Data.ParsePly) + .apply(StateTransforms.Model.ShapeFromPly) + .apply(StateTransforms.Representation.ShapeRepresentation3D) + await state.updateTree(tree).runInContext(taskCtx) + }) + } +} \ No newline at end of file diff --git a/src/mol-plugin/state/objects.ts b/src/mol-plugin/state/objects.ts index c73a2d03df02236ad1204e229ae0bf8a65816ba5..c3a0a8c979c81d6c9fc04c2fc5c3830c571774dd 100644 --- a/src/mol-plugin/state/objects.ts +++ b/src/mol-plugin/state/objects.ts @@ -6,6 +6,7 @@ */ import { CifFile } from 'mol-io/reader/cif'; +import { PlyFile } from 'mol-io/reader/ply/schema'; import { Model as _Model, Structure as _Structure } from 'mol-model/structure'; import { VolumeData } from 'mol-model/volume'; import { PluginBehavior } from 'mol-plugin/behavior/behavior'; @@ -16,6 +17,8 @@ import { StateObject, StateTransformer } from 'mol-state'; import { Ccp4File } from 'mol-io/reader/ccp4/schema'; import { Dsn6File } from 'mol-io/reader/dsn6/schema'; import { ShapeRepresentation } from 'mol-repr/shape/representation'; +import { Shape as _Shape } from 'mol-model/shape'; +import { ShapeProvider } from 'mol-model/shape/provider'; export type TypeClass = 'root' | 'data' | 'prop' @@ -61,6 +64,7 @@ export namespace PluginStateObject { export namespace Format { export class Json extends Create<any>({ name: 'JSON Data', typeClass: 'Data' }) { } export class Cif extends Create<CifFile>({ name: 'CIF File', typeClass: 'Data' }) { } + export class Ply extends Create<PlyFile>({ name: 'PLY File', typeClass: 'Data' }) { } export class Ccp4 extends Create<Ccp4File>({ name: 'CCP4/MRC/MAP File', typeClass: 'Data' }) { } export class Dsn6 extends Create<Dsn6File>({ name: 'DSN6/BRIX File', typeClass: 'Data' }) { } @@ -71,6 +75,7 @@ export namespace PluginStateObject { | { kind: 'cif', data: CifFile } | { kind: 'ccp4', data: Ccp4File } | { kind: 'dsn6', data: Dsn6File } + | { kind: 'ply', data: PlyFile } // For non-build in extensions | { kind: 'custom', data: unknown, tag: string }) export type BlobData = BlobEntry[] @@ -100,6 +105,11 @@ export namespace PluginStateObject { export class Data extends Create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { } export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { } } + + export namespace Shape { + export class Provider extends Create<ShapeProvider<any, any, any>>({ name: 'Shape Provider', typeClass: 'Object' }) { } + export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>>({ name: 'Shape 3D' }) { } + } } export namespace PluginStateTransform { diff --git a/src/mol-plugin/state/transforms/data.ts b/src/mol-plugin/state/transforms/data.ts index aa0554a568085ad5ac2cb6a93bc0259e33e39b4b..43c82b639de8388404961518f571027401a37003 100644 --- a/src/mol-plugin/state/transforms/data.ts +++ b/src/mol-plugin/state/transforms/data.ts @@ -15,6 +15,7 @@ import { StateTransformer } from 'mol-state'; import { readFromFile, ajaxGetMany } from 'mol-util/data-source'; import * as CCP4 from 'mol-io/reader/ccp4/parser' import * as DSN6 from 'mol-io/reader/dsn6/parser' +import * as PLY from 'mol-io/reader/ply/parser' export { Download } type Download = typeof Download @@ -185,6 +186,23 @@ const ParseCif = PluginStateTransform.BuiltIn({ } }); +export { ParsePly } +type ParsePly = typeof ParsePly +const ParsePly = PluginStateTransform.BuiltIn({ + name: 'parse-ply', + display: { name: 'Parse PLY', description: 'Parse PLY from String data' }, + from: [SO.Data.String], + to: SO.Format.Ply +})({ + apply({ a }) { + return Task.create('Parse PLY', async ctx => { + const parsed = await PLY.parse(a.data).runInContext(ctx); + if (parsed.isError) throw new Error(parsed.message); + return new SO.Format.Ply(parsed.result, { label: parsed.result.comments[0] || 'PLY Data' }); + }); + } +}); + export { ParseCcp4 } type ParseCcp4 = typeof ParseCcp4 const ParseCcp4 = PluginStateTransform.BuiltIn({ diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index dd54e9dc49b54ddf352dd5f4e6888dfe33615734..5f890882adf2887c5739111615d8b8632638c2c7 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -24,6 +24,7 @@ import { trajectoryFromGRO } from 'mol-model-formats/structure/gro'; import { parseGRO } from 'mol-io/reader/gro/parser'; import { parseMolScript } from 'mol-script/language/parser'; import { transpileMolScript } from 'mol-script/script/mol-script/symbols'; +import { shapeFromPly } from 'mol-model-formats/shape/ply'; export { TrajectoryFromBlob }; export { TrajectoryFromMmCif }; @@ -338,7 +339,6 @@ function updateStructureFromQuery(query: QueryFn<Sel>, src: Structure, obj: SO.M return true; } - namespace StructureComplexElement { export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres' } @@ -394,4 +394,24 @@ async function attachProps(model: Model, ctx: PluginContext, taskCtx: RuntimeCon const p = ctx.customModelProperties.get(name); await p.attach(model).runInContext(taskCtx); } -} \ No newline at end of file +} + +export { ShapeFromPly } +type ShapeFromPly = typeof ShapeFromPly +const ShapeFromPly = PluginStateTransform.BuiltIn({ + name: 'shape-from-ply', + display: { name: 'Shape from PLY', description: 'Create Shape from PLY data' }, + from: SO.Format.Ply, + to: SO.Shape.Provider, + params(a) { + return { }; + } +})({ + apply({ a, params }) { + return Task.create('Create shape from PLY', async ctx => { + const shape = await shapeFromPly(a.data, params).runInContext(ctx) + const props = { label: 'Shape' }; + return new SO.Shape.Provider(shape, props); + }); + } +}); \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 0d99a98fc98cfd9920d8ad993959d72fdbd9ab1c..2b71e273fbb64a2aa7db09922100f7aa4f4fd06c 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -30,6 +30,7 @@ import { Color } from 'mol-util/color'; import { Overpaint } from 'mol-theme/overpaint'; import { Transparency } from 'mol-theme/transparency'; import { getStructureOverpaint, getStructureTransparency } from './helpers'; +import { BaseGeometry } from 'mol-geo/geometry/base'; export { StructureRepresentation3D } export { StructureRepresentation3DHelpers } @@ -193,7 +194,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({ } }); - type StructureLabels3D = typeof StructureLabels3D const StructureLabels3D = PluginStateTransform.BuiltIn({ name: 'structure-labels-3d', @@ -514,4 +514,38 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({ return StateTransformer.UpdateResult.Updated; }); } +}); + +// + +export { ShapeRepresentation3D } +type ShapeRepresentation3D = typeof ShapeRepresentation3D +const ShapeRepresentation3D = PluginStateTransform.BuiltIn({ + name: 'shape-representation-3d', + display: '3D Representation', + from: SO.Shape.Provider, + to: SO.Shape.Representation3D, + params: (a, ctx: PluginContext) => { + return a ? a.data.params : BaseGeometry.Params + } +})({ + canAutoUpdate() { + return true; + }, + apply({ a, params }, plugin: PluginContext) { + return Task.create('Shape Representation', async ctx => { + const props = { ...PD.getDefaultValues(a.data.params), params } + const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils) + // TODO set initial state, repr.setState({}) + await repr.createOrUpdate(props, a.data.data).runInContext(ctx); + return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label }); + }); + }, + update({ a, b, oldParams, newParams }, plugin: PluginContext) { + return Task.create('Shape Representation', async ctx => { + const props = { ...b.data.repr.props, ...newParams } + await b.data.repr.createOrUpdate(props, a.data.data).runInContext(ctx); + return StateTransformer.UpdateResult.Updated; + }); + } }); \ No newline at end of file diff --git a/src/mol-plugin/util/structure-labels.ts b/src/mol-plugin/util/structure-labels.ts index c6e944b29219ac96fed56f47556ad3885780ea36..932569da9046774aca3952c380c7174f7d9f31ca 100644 --- a/src/mol-plugin/util/structure-labels.ts +++ b/src/mol-plugin/util/structure-labels.ts @@ -44,7 +44,7 @@ export async function getLabelRepresentation(ctx: RuntimeContext, structure: Str function getLabelsShape(ctx: RuntimeContext, data: LabelsData, props: PD.Values<Text.Params>, shape?: Shape<Text>) { const geo = getLabelsText(data, props, shape && shape.geometry); - return Shape.create('Scene Labels', geo, () => ColorNames.dimgrey, g => data.sizes[g], () => '') + return Shape.create('Scene Labels', data, geo, () => ColorNames.dimgrey, g => data.sizes[g], () => '') } const boundaryHelper = new BoundaryHelper(); diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index ed079f38cfb2430264d257e603b981039ba91a6f..b84a0bffc03e96a677e7fb103f10f52de4b011c3 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -57,9 +57,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa updateState.createNew = true } else if (shape && _shape && shape.id === _shape.id) { // console.log('same shape') - // trigger color update when shape has not changed - updateState.updateColor = true - updateState.updateTransform = true + // nothing to set } else if (shape && _shape && shape.id !== _shape.id) { // console.log('new shape') updateState.updateTransform = true diff --git a/src/mol-theme/color/secondary-structure.ts b/src/mol-theme/color/secondary-structure.ts index 384c5fb0b7d3c37031ef670b0d17a9e7b9f79ade..cb8878f28b77889d830573b5609a3b1c32feed6e 100644 --- a/src/mol-theme/color/secondary-structure.ts +++ b/src/mol-theme/color/secondary-structure.ts @@ -22,6 +22,8 @@ const SecondaryStructureColors = ColorMap({ 'betaTurn': 0x6080FF, 'betaStrand': 0xFFC800, 'coil': 0xFFFFFF, + 'bend': 0x66D8C9 /* biting original color used 0x00FF00 */, + 'turn': 0x00B266, 'dna': 0xAE00FE, 'rna': 0xFD0162, @@ -53,8 +55,10 @@ export function secondaryStructureColor(unit: Unit, element: ElementIndex): Colo return SecondaryStructureColors.alphaHelix } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Beta)) { return SecondaryStructureColors.betaStrand + } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Bend)) { + return SecondaryStructureColors.bend } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Turn)) { - return SecondaryStructureColors.coil + return SecondaryStructureColors.turn } else { const moleculeType = getElementMoleculeType(unit, element) if (moleculeType === MoleculeType.DNA) { diff --git a/src/mol-util/array.ts b/src/mol-util/array.ts index 078ab5319b4db6bdd612c7655390a8e4a0e2225f..d9c19419b1ee4a0f91f79f54033f72ee1c56e65e 100644 --- a/src/mol-util/array.ts +++ b/src/mol-util/array.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -9,7 +9,7 @@ import { NumberArray } from './type-helpers'; // TODO move to mol-math as Vector??? /** Get the maximum value in an array */ -export function arrayMax(array: NumberArray) { +export function arrayMax(array: ArrayLike<number>) { let max = -Infinity for (let i = 0, il = array.length; i < il; ++i) { if (array[i] > max) max = array[i] @@ -18,7 +18,7 @@ export function arrayMax(array: NumberArray) { } /** Get the minimum value in an array */ -export function arrayMin(array: NumberArray) { +export function arrayMin(array: ArrayLike<number>) { let min = Infinity for (let i = 0, il = array.length; i < il; ++i) { if (array[i] < min) min = array[i] @@ -27,7 +27,7 @@ export function arrayMin(array: NumberArray) { } /** Get the sum of values in an array */ -export function arraySum(array: NumberArray, stride = 1, offset = 0) { +export function arraySum(array: ArrayLike<number>, stride = 1, offset = 0) { const n = array.length let sum = 0 for (let i = offset; i < n; i += stride) { @@ -37,12 +37,12 @@ export function arraySum(array: NumberArray, stride = 1, offset = 0) { } /** Get the mean of values in an array */ -export function arrayMean(array: NumberArray, stride = 1, offset = 0) { +export function arrayMean(array: ArrayLike<number>, stride = 1, offset = 0) { return arraySum(array, stride, offset) / (array.length / stride) } /** Get the root mean square of values in an array */ -export function arrayRms(array: NumberArray) { +export function arrayRms(array: ArrayLike<number>) { const n = array.length let sumSq = 0 for (let i = 0; i < n; ++i) { diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index a7f08f3cf1a3d8227703292d4c4078aeb862e499..bfb9311dcdfa65bf5febb10796124bd71d631643 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -284,7 +284,7 @@ export namespace ParamDefinition { return true; } - function isParamEqual(p: Any, a: any, b: any): boolean { + export function isParamEqual(p: Any, a: any, b: any): boolean { if (a === b) return true; if (!a) return !b; if (!b) return !a; diff --git a/src/tests/browser/index.html b/src/tests/browser/index.html index f28af95b2858709af424bf4ff1cbf49396329846..62d647dc8a15e3801ee7119a313c2b86927d1cc7 100644 --- a/src/tests/browser/index.html +++ b/src/tests/browser/index.html @@ -1,38 +1,37 @@ <!DOCTYPE html> <html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - <title>Mol* Browser Test</title> - <style> - * { - margin: 0; - padding: 0; - box-sizing: border-box; - } - html, body { - width: 100%; - height: 100%; - overflow: hidden; - } - </style> - </head> - <body> - <div id="app"></div> - <script type="text/javascript"> - function urlQueryParameter (id) { - if (typeof window === 'undefined') return undefined - const a = new RegExp(`${id}=([^&#=]*)`) - const m = a.exec(window.location.search) - return m ? decodeURIComponent(m[1]) : undefined - } - - const name = urlQueryParameter('name') - if (name) { - const script = document.createElement('script') - script.src = name + '.js' - document.body.appendChild(script) - } - </script> - </body> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> + <title>Mol* Browser Test</title> + <style> + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + html, body { + width: 100%; + height: 100%; + overflow: hidden; + } + </style> + </head> + <body> + <div id="app"></div> + <script type="text/javascript"> + function urlQueryParameter (id) { + if (typeof window === 'undefined') return undefined + const a = new RegExp(`${id}=([^&#=]*)`) + const m = a.exec(window.location.search) + return m ? decodeURIComponent(m[1]) : undefined + } + const name = urlQueryParameter('name') + if (name) { + const script = document.createElement('script') + script.src = name + '.js' + document.body.appendChild(script) + } + </script> + </body> </html> \ No newline at end of file diff --git a/src/tests/browser/render-shape.ts b/src/tests/browser/render-shape.ts index ce513f8132403be892bd02ffa87fe06d5a6f936a..adc6c9eae42b7c5b3c54ff6a68db6007d7ca8871 100644 --- a/src/tests/browser/render-shape.ts +++ b/src/tests/browser/render-shape.ts @@ -68,7 +68,7 @@ async function getSphereMesh(ctx: RuntimeContext, centers: number[], mesh?: Mesh const builderState = MeshBuilder.createState(centers.length * 128, centers.length * 128 / 2, mesh) const t = Mat4.identity() const v = Vec3.zero() - const sphere = Sphere(2) + const sphere = Sphere(3) builderState.currentGroup = 0 for (let i = 0, il = centers.length / 3; i < il; ++i) { // for production, calls to update should be guarded by `if (ctx.shouldUpdate)` @@ -81,8 +81,8 @@ async function getSphereMesh(ctx: RuntimeContext, centers: number[], mesh?: Mesh } const myData = { - centers: [0, 0, 0, 0, 3, 0], - colors: [ColorNames.tomato, ColorNames.springgreen], + centers: [0, 0, 0, 0, 3, 0, 1, 0 , 4], + colors: [ColorNames.tomato, ColorNames.springgreen, ColorNames.springgreen], labels: ['Sphere 0, Instance A', 'Sphere 1, Instance A', 'Sphere 0, Instance B', 'Sphere 1, Instance B'], transforms: [Mat4.identity(), Mat4.fromTranslation(Mat4.zero(), Vec3.create(3, 0, 0))] } @@ -96,8 +96,8 @@ async function getShape(ctx: RuntimeContext, data: MyData, props: {}, shape?: Sh const { centers, colors, labels, transforms } = data const mesh = await getSphereMesh(ctx, centers, shape && shape.geometry) const groupCount = centers.length / 3 - return shape || Shape.create( - 'test', mesh, + return Shape.create( + 'test', data, mesh, (groupId: number) => colors[groupId], // color: per group, same for instances () => 1, // size: constant (groupId: number, instanceId: number) => labels[instanceId * groupCount + groupId], // label: per group and instance @@ -108,10 +108,9 @@ async function getShape(ctx: RuntimeContext, data: MyData, props: {}, shape?: Sh // Init ShapeRepresentation container const repr = ShapeRepresentation(getShape, Mesh.Utils) -async function init() { +export async function init() { // Create shape from myData and add to canvas3d await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p))) - console.log(repr) canvas3d.add(repr) canvas3d.resetCamera()