mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
13 Commits
v2.0.0-dev
...
v2.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa2b8542bf | ||
|
|
901522f500 | ||
|
|
62b63c1aa5 | ||
|
|
24b36f41da | ||
|
|
c9c890782c | ||
|
|
f2c539ebd8 | ||
|
|
feb922ca91 | ||
|
|
25127bb84b | ||
|
|
8fb01d2157 | ||
|
|
c09357ea75 | ||
|
|
9f2513dae0 | ||
|
|
11a52c0390 | ||
|
|
e955dc7e94 |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.0-dev.0",
|
||||
"version": "2.0.0-dev.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.0-dev.0",
|
||||
"version": "2.0.0-dev.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
// adjust this number to make the animation faster or slower
|
||||
// requires to "restart" the animation if changed
|
||||
BasicMolStarWrapper.animate.modelIndex.maxFPS = 30;
|
||||
BasicMolStarWrapper.animate.modelIndex.targetFps = 30;
|
||||
|
||||
addControl('Play To End', () => BasicMolStarWrapper.animate.modelIndex.onceForward());
|
||||
addControl('Play To Start', () => BasicMolStarWrapper.animate.modelIndex.onceBackward());
|
||||
|
||||
@@ -83,13 +83,17 @@ class BasicWrapper {
|
||||
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
|
||||
}
|
||||
|
||||
private animateModelIndexTargetFps() {
|
||||
return Math.max(1, this.animate.modelIndex.targetFps | 0);
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
|
||||
targetFps: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: {} } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
// adjust this number to make the animation faster or slower
|
||||
// requires to "restart" the animation if changed
|
||||
PluginWrapper.animate.modelIndex.maxFPS = 30;
|
||||
PluginWrapper.animate.modelIndex.targetFps = 30;
|
||||
|
||||
addControl('Play To End', () => PluginWrapper.animate.modelIndex.onceForward());
|
||||
addControl('Play To Start', () => PluginWrapper.animate.modelIndex.onceBackward());
|
||||
|
||||
@@ -272,13 +272,17 @@ class MolStarProteopediaWrapper {
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
|
||||
}
|
||||
|
||||
private animateModelIndexTargetFps() {
|
||||
return Math.max(1, this.animate.modelIndex.targetFps | 0);
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
|
||||
targetFps: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: {} } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { quad_vert } from '../../../mol-gl/shader/quad.vert';
|
||||
import { isosurface_frag } from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
|
||||
import { calcActiveVoxels } from './active-voxels';
|
||||
import { isWebGL2 } from '../../webgl/compat';
|
||||
import { Scheduler } from '../../../mol-task';
|
||||
|
||||
const IsosurfaceSchema = {
|
||||
...QuadSchema,
|
||||
@@ -185,26 +186,36 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
renderable.render();
|
||||
|
||||
gl.flush();
|
||||
gl.finish();
|
||||
|
||||
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
function delay() {
|
||||
return new Promise(r => Scheduler.setImmediate(r));
|
||||
}
|
||||
|
||||
export async function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('calcActiveVoxels');
|
||||
// apply advanced magic to solve incomplete buffer rendering issue
|
||||
await delay();
|
||||
|
||||
// console.time('createHistogramPyramid');
|
||||
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
|
||||
// apply advanced magic to solve incomplete buffer rendering issue
|
||||
await delay();
|
||||
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createHistogramPyramid');
|
||||
|
||||
// console.time('createIsosurfaceBuffers');
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture);
|
||||
// apply advanced magic to solve incomplete buffer rendering issue
|
||||
await delay();
|
||||
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
|
||||
|
||||
@@ -58,24 +58,44 @@ async function handleAtoms(state: State, count: number): Promise<PsfFile['atoms'
|
||||
const charge = TokenBuilder.create(tokenizer.data, count * 2);
|
||||
const mass = TokenBuilder.create(tokenizer.data, count * 2);
|
||||
|
||||
const { position } = tokenizer;
|
||||
const line = readLine(tokenizer).trim();
|
||||
tokenizer.position = position;
|
||||
|
||||
// LAMMPS full
|
||||
// AtomID ResID AtomName AtomType Charge Mass Unused0
|
||||
const isLammpsFull = line.split(reWhitespace).length === 7;
|
||||
const n = isLammpsFull ? 6 : 8;
|
||||
|
||||
const { length } = tokenizer;
|
||||
let linesAlreadyRead = 0;
|
||||
await chunkedSubtask(state.runtimeCtx, 10, void 0, chunkSize => {
|
||||
await chunkedSubtask(state.runtimeCtx, 100000, void 0, chunkSize => {
|
||||
const linesToRead = Math.min(count - linesAlreadyRead, chunkSize);
|
||||
for (let i = 0; i < linesToRead; ++i) {
|
||||
for (let j = 0; j < 8; ++j) {
|
||||
for (let j = 0; j < n; ++j) {
|
||||
skipWhitespace(tokenizer);
|
||||
markStart(tokenizer);
|
||||
eatValue(tokenizer);
|
||||
switch (j) {
|
||||
case 0: TokenBuilder.addUnchecked(atomId, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 1: TokenBuilder.addUnchecked(segmentName, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 2: TokenBuilder.addUnchecked(residueId, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 3: TokenBuilder.addUnchecked(residueName, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 4: TokenBuilder.addUnchecked(atomName, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 5: TokenBuilder.addUnchecked(atomType, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 6: TokenBuilder.addUnchecked(charge, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 7: TokenBuilder.addUnchecked(mass, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
if (isLammpsFull) {
|
||||
switch (j) {
|
||||
case 0: TokenBuilder.addUnchecked(atomId, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 1: TokenBuilder.addUnchecked(residueId, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 2: TokenBuilder.addUnchecked(atomName, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 3: TokenBuilder.addUnchecked(atomType, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 4: TokenBuilder.addUnchecked(charge, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 5: TokenBuilder.addUnchecked(mass, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
}
|
||||
} else {
|
||||
switch (j) {
|
||||
case 0: TokenBuilder.addUnchecked(atomId, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 1: TokenBuilder.addUnchecked(segmentName, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 2: TokenBuilder.addUnchecked(residueId, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 3: TokenBuilder.addUnchecked(residueName, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 4: TokenBuilder.addUnchecked(atomName, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 5: TokenBuilder.addUnchecked(atomType, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 6: TokenBuilder.addUnchecked(charge, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 7: TokenBuilder.addUnchecked(mass, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ignore any extra columns
|
||||
@@ -89,9 +109,13 @@ async function handleAtoms(state: State, count: number): Promise<PsfFile['atoms'
|
||||
return {
|
||||
count,
|
||||
atomId: TokenColumn(atomId)(Column.Schema.int),
|
||||
segmentName: TokenColumn(segmentName)(Column.Schema.str),
|
||||
segmentName: isLammpsFull
|
||||
? TokenColumn(residueId)(Column.Schema.str)
|
||||
: TokenColumn(segmentName)(Column.Schema.str),
|
||||
residueId: TokenColumn(residueId)(Column.Schema.int),
|
||||
residueName: TokenColumn(residueName)(Column.Schema.str),
|
||||
residueName: isLammpsFull
|
||||
? TokenColumn(residueId)(Column.Schema.str)
|
||||
: TokenColumn(residueName)(Column.Schema.str),
|
||||
atomName: TokenColumn(atomName)(Column.Schema.str),
|
||||
atomType: TokenColumn(atomType)(Column.Schema.str),
|
||||
charge: TokenColumn(charge)(Column.Schema.float),
|
||||
|
||||
@@ -119,6 +119,8 @@ export namespace Model {
|
||||
ModelSymmetry.Provider.set(m, symmetry);
|
||||
}
|
||||
|
||||
TrajectoryInfo.set(m, { index: i, size: frames.length });
|
||||
|
||||
trajectory.push(m);
|
||||
}
|
||||
return { trajectory, srcIndexArray };
|
||||
|
||||
@@ -14,13 +14,24 @@ import { PluginStateAnimation } from '../model';
|
||||
export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
name: 'built-in.animate-model-index',
|
||||
display: { name: 'Animate Trajectory' },
|
||||
isExportable: true,
|
||||
params: () => ({
|
||||
mode: PD.MappedStatic('palindrome', {
|
||||
mode: PD.MappedStatic('loop', {
|
||||
palindrome: PD.Group({ }),
|
||||
loop: PD.Group({ }),
|
||||
once: PD.Group({ direction: PD.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]) }, { isFlat: true })
|
||||
}, { options: [['palindrome', 'Palindrome'], ['loop', 'Loop'], ['once', 'Once']] }),
|
||||
maxFPS: PD.Numeric(15, { min: 1, max: 60, step: 1 })
|
||||
duration: PD.MappedStatic('fixed', {
|
||||
fixed: PD.Group({
|
||||
durationInS: PD.Numeric(5, { min: 1, max: 120, step: 0.1 }, { description: 'Duration in seconds' })
|
||||
}, { isFlat: true }),
|
||||
computed: PD.Group({
|
||||
targetFps: PD.Numeric(30, { min: 5, max: 250, step: 1 }, { label: 'Target FPS' })
|
||||
}, { isFlat: true }),
|
||||
sequential: PD.Group({
|
||||
maxFps: PD.Numeric(30, { min: 5, max: 60, step: 1 })
|
||||
}, { isFlat: true })
|
||||
})
|
||||
}),
|
||||
canApply(ctx) {
|
||||
const state = ctx.state.data;
|
||||
@@ -31,10 +42,30 @@ export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
}
|
||||
return { canApply: false, reason: 'No trajectory to animate' };
|
||||
},
|
||||
getDuration: (p, ctx) => {
|
||||
if (p.duration?.name === 'fixed') {
|
||||
return { kind: 'fixed', durationMs: p.duration.params.durationInS * 1000 };
|
||||
} else if (p.duration.name === 'computed') {
|
||||
const state = ctx.state.data;
|
||||
const models = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Model.ModelFromTrajectory));
|
||||
|
||||
let maxDuration = 0;
|
||||
for (const m of models) {
|
||||
const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, PluginStateObject.Molecule.Trajectory);
|
||||
if (!parent || !parent.obj) continue;
|
||||
const traj = parent.obj;
|
||||
maxDuration = Math.max(Math.ceil(1000 * traj.data.frameCount / p.duration.params.targetFps), maxDuration);
|
||||
}
|
||||
|
||||
return { kind: 'fixed', durationMs: maxDuration };
|
||||
}
|
||||
return { kind: 'unknown' };
|
||||
},
|
||||
initialState: () => ({} as { palindromeDirections?: { [id: string]: -1 | 1 | undefined } }),
|
||||
async apply(animState, t, ctx) {
|
||||
// limit fps
|
||||
if (t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.maxFPS) {
|
||||
|
||||
if (ctx.params.duration.name === 'sequential' && t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.duration.params.maxFps) {
|
||||
return { kind: 'skip' };
|
||||
}
|
||||
|
||||
@@ -65,27 +96,45 @@ export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
} else {
|
||||
return old;
|
||||
}
|
||||
let dir: -1 | 1 = 1;
|
||||
if (params.mode.name === 'once') {
|
||||
dir = params.mode.params.direction === 'backward' ? -1 : 1;
|
||||
// if we are at start or end already, do nothing.
|
||||
if ((dir === -1 && old.modelIndex === 0) || (dir === 1 && old.modelIndex === len - 1)) {
|
||||
isEnd = true;
|
||||
return old;
|
||||
|
||||
if (params.duration.name === 'sequential') {
|
||||
let dir: -1 | 1 = 1;
|
||||
if (params.mode.name === 'once') {
|
||||
dir = params.mode.params.direction === 'backward' ? -1 : 1;
|
||||
// if we are at start or end already, do nothing.
|
||||
if ((dir === -1 && old.modelIndex === 0) || (dir === 1 && old.modelIndex === len - 1)) {
|
||||
isEnd = true;
|
||||
return old;
|
||||
}
|
||||
} else if (params.mode.name === 'palindrome') {
|
||||
if (old.modelIndex === 0) dir = 1;
|
||||
else if (old.modelIndex === len - 1) dir = -1;
|
||||
else dir = palindromeDirections[m.transform.ref] || 1;
|
||||
}
|
||||
} else if (params.mode.name === 'palindrome') {
|
||||
if (old.modelIndex === 0) dir = 1;
|
||||
else if (old.modelIndex === len - 1) dir = -1;
|
||||
else dir = palindromeDirections[m.transform.ref] || 1;
|
||||
palindromeDirections[m.transform.ref] = dir;
|
||||
|
||||
let modelIndex = (old.modelIndex + dir) % len;
|
||||
if (modelIndex < 0) modelIndex += len;
|
||||
|
||||
isEnd = isEnd || (dir === -1 && modelIndex === 0) || (dir === 1 && modelIndex === len - 1);
|
||||
|
||||
return { modelIndex };
|
||||
} else {
|
||||
const durationInMs = params.duration.name === 'fixed'
|
||||
? params.duration.params.durationInS * 1000
|
||||
: Math.ceil(1000 * traj.data.frameCount / params.duration.params.targetFps);
|
||||
|
||||
let phase: number = (t.current % durationInMs) / durationInMs;
|
||||
|
||||
if (params.mode.name === 'palindrome') {
|
||||
phase = 2 * phase;
|
||||
if (phase > 1) phase = 2 - phase;
|
||||
}
|
||||
|
||||
const modelIndex = Math.min(Math.floor(traj.data.frameCount * phase), traj.data.frameCount - 1);
|
||||
isEnd = isEnd || modelIndex === traj.data.frameCount - 1;
|
||||
return { modelIndex };
|
||||
}
|
||||
palindromeDirections[m.transform.ref] = dir;
|
||||
|
||||
let modelIndex = (old.modelIndex + dir) % len;
|
||||
if (modelIndex < 0) modelIndex += len;
|
||||
|
||||
isEnd = isEnd || (dir === -1 && modelIndex === 0) || (dir === 1 && modelIndex === len - 1);
|
||||
|
||||
return { modelIndex };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -345,9 +345,9 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({
|
||||
to: SO.Molecule.Model,
|
||||
params: a => {
|
||||
if (!a) {
|
||||
return { modelIndex: PD.Numeric(0, {}, { description: 'Zero-based index of the model' }) };
|
||||
return { modelIndex: PD.Numeric(0, {}, { description: 'Zero-based index of the model', immediateUpdate: true }) };
|
||||
}
|
||||
return { modelIndex: PD.Converted(plus1, minus1, PD.Numeric(1, { min: 1, max: a.data.frameCount, step: 1 }, { description: 'Model Index' })) };
|
||||
return { modelIndex: PD.Converted(plus1, minus1, PD.Numeric(1, { min: 1, max: a.data.frameCount, step: 1 }, { description: 'Model Index', immediateUpdate: true })) };
|
||||
}
|
||||
})({
|
||||
isApplicable: a => a.data.frameCount > 0,
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { StructureHierarchyRef, ModelRef, TrajectoryRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { Model } from '../../mol-model/structure';
|
||||
import { ModelRef, StructureHierarchyRef, TrajectoryRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { StateSelection } from '../../mol-state';
|
||||
import { CollapsableControls, CollapsableState } from '../base';
|
||||
import { ActionMenu } from '../controls/action-menu';
|
||||
import { Button, IconButton, ExpandGroup } from '../controls/common';
|
||||
import { Button, ExpandGroup, IconButton } from '../controls/common';
|
||||
import { BookmarksOutlinedSvg, MoleculeSvg } from '../controls/icons';
|
||||
import { ParameterControls } from '../controls/parameters';
|
||||
import { StructureFocusControls } from './focus';
|
||||
import { UpdateTransformControl } from '../state/update-transform';
|
||||
import { StructureFocusControls } from './focus';
|
||||
import { StructureSelectionStatsControls } from './selection';
|
||||
import { StateSelection } from '../../mol-state';
|
||||
import { MoleculeSvg, BookmarksOutlinedSvg } from '../controls/icons';
|
||||
import { Model } from '../../mol-model/structure';
|
||||
|
||||
interface StructureSourceControlState extends CollapsableState {
|
||||
isBusy: boolean,
|
||||
@@ -214,13 +214,30 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
|
||||
mng.hierarchy.applyPreset(trajectories, item.value as any);
|
||||
}
|
||||
|
||||
updateStructureModel = async (params: any) => {
|
||||
const { selection } = this.plugin.managers.structure.hierarchy;
|
||||
const m = selection.structures[0].model!;
|
||||
this.plugin.state.updateTransform(this.plugin.state.data, m.cell.transform.ref, params, 'Model Index');
|
||||
// TODO: ?? PluginCommands.Camera.Reset(this.plugin);
|
||||
private updateModelQueueParams: any = void 0;
|
||||
private isUpdatingModel = false;
|
||||
|
||||
private async _updateStructureModel() {
|
||||
if (!this.updateModelQueueParams || this.isUpdatingModel) return;
|
||||
const params = this.updateModelQueueParams;
|
||||
this.updateModelQueueParams = void 0;
|
||||
|
||||
try {
|
||||
this.isUpdatingModel = true;
|
||||
const { selection } = this.plugin.managers.structure.hierarchy;
|
||||
const m = selection.structures[0].model!;
|
||||
await this.plugin.state.updateTransform(this.plugin.state.data, m.cell.transform.ref, params, 'Model Index');
|
||||
} finally {
|
||||
this.isUpdatingModel = false;
|
||||
this._updateStructureModel();
|
||||
}
|
||||
}
|
||||
|
||||
updateStructureModel = (params: any) => {
|
||||
this.updateModelQueueParams = params;
|
||||
this._updateStructureModel();
|
||||
};
|
||||
|
||||
get modelIndex() {
|
||||
const { selection } = this.plugin.managers.structure.hierarchy;
|
||||
if (selection.structures.length !== 1) return null;
|
||||
|
||||
@@ -181,7 +181,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
|
||||
|
||||
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
|
||||
|
||||
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
const gv = await extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
|
||||
const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
|
||||
@@ -238,7 +238,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
|
||||
|
||||
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
|
||||
|
||||
const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
const gv = await extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
|
||||
const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -23,6 +23,9 @@ import { SpheresBuilder } from '../../../../mol-geo/geometry/spheres/spheres-bui
|
||||
import { isTrace, isH } from './common';
|
||||
import { Sphere3D } from '../../../../mol-math/geometry';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3add = Vec3.add;
|
||||
|
||||
type ElementProps = {
|
||||
ignoreHydrogens: boolean,
|
||||
traceOnly: boolean,
|
||||
@@ -59,27 +62,38 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
|
||||
const v = Vec3();
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
const ignore = makeElementIgnoreTest(unit, props);
|
||||
const l = StructureElement.Location.create(structure);
|
||||
l.unit = unit;
|
||||
|
||||
const l = StructureElement.Location.create(structure, unit);
|
||||
const themeSize = theme.size.size;
|
||||
const center = Vec3();
|
||||
let maxSize = 0;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < elementCount; i++) {
|
||||
if (ignore && ignore(unit, elements[i])) continue;
|
||||
|
||||
l.element = elements[i];
|
||||
pos(elements[i], v);
|
||||
v3add(center, center, v);
|
||||
count += 1;
|
||||
|
||||
builderState.currentGroup = i;
|
||||
const size = theme.size.size(l);
|
||||
const size = themeSize(l);
|
||||
if (size > maxSize) maxSize = size;
|
||||
|
||||
addSphere(builderState, v, size * sizeFactor, detail);
|
||||
}
|
||||
|
||||
// re-use boundingSphere if it has not changed much
|
||||
let boundingSphere: Sphere3D;
|
||||
Vec3.scale(center, center, 1 / count);
|
||||
if (mesh && Vec3.distance(center, mesh.boundingSphere.center) / mesh.boundingSphere.radius < 1.0) {
|
||||
boundingSphere = Sphere3D.clone(mesh.boundingSphere);
|
||||
} else {
|
||||
boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxSize * sizeFactor + 0.05);
|
||||
}
|
||||
|
||||
const m = MeshBuilder.getMesh(builderState);
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxSize * sizeFactor + 0.05);
|
||||
m.setBoundingSphere(sphere);
|
||||
m.setBoundingSphere(boundingSphere);
|
||||
|
||||
return m;
|
||||
}
|
||||
@@ -98,21 +112,35 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru
|
||||
const ignore = makeElementIgnoreTest(unit, props);
|
||||
|
||||
const l = StructureElement.Location.create(structure, unit);
|
||||
const themeSize = theme.size.size;
|
||||
const center = Vec3();
|
||||
let maxSize = 0;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < elementCount; i++) {
|
||||
if (ignore?.(unit, elements[i])) continue;
|
||||
|
||||
pos(elements[i], v);
|
||||
builder.add(v[0], v[1], v[2], i);
|
||||
v3add(center, center, v);
|
||||
count += 1;
|
||||
|
||||
l.element = elements[i];
|
||||
const size = theme.size.size(l);
|
||||
const size = themeSize(l);
|
||||
if (size > maxSize) maxSize = size;
|
||||
}
|
||||
|
||||
// re-use boundingSphere if it has not changed much
|
||||
let boundingSphere: Sphere3D;
|
||||
Vec3.scale(center, center, 1 / count);
|
||||
if (spheres && Vec3.distance(center, spheres.boundingSphere.center) / spheres.boundingSphere.radius < 1.0) {
|
||||
boundingSphere = Sphere3D.clone(spheres.boundingSphere);
|
||||
} else {
|
||||
boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxSize * props.sizeFactor + 0.05);
|
||||
}
|
||||
|
||||
const s = builder.getSpheres();
|
||||
s.setBoundingSphere(Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxSize * props.sizeFactor + 0.05));
|
||||
s.setBoundingSphere(boundingSphere);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
|
||||
|
||||
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
|
||||
|
||||
const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, false, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
const gv = await extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, false, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
|
||||
|
||||
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -33,6 +33,8 @@ import { OccupancyColorThemeProvider } from './color/occupancy';
|
||||
import { OperatorNameColorThemeProvider } from './color/operator-name';
|
||||
import { OperatorHklColorThemeProvider } from './color/operator-hkl';
|
||||
import { PartialChargeColorThemeProvider } from './color/partial-charge';
|
||||
import { AtomIdColorThemeProvider } from './color/atom-id';
|
||||
import { EntityIdColorThemeProvider } from './color/entity-id';
|
||||
|
||||
export type LocationColor = (location: Location, isSecondary: boolean) => Color
|
||||
|
||||
@@ -80,10 +82,12 @@ namespace ColorTheme {
|
||||
}
|
||||
|
||||
export const BuiltIn = {
|
||||
'atom-id': AtomIdColorThemeProvider,
|
||||
'carbohydrate-symbol': CarbohydrateSymbolColorThemeProvider,
|
||||
'chain-id': ChainIdColorThemeProvider,
|
||||
'element-index': ElementIndexColorThemeProvider,
|
||||
'element-symbol': ElementSymbolColorThemeProvider,
|
||||
'entity-id': EntityIdColorThemeProvider,
|
||||
'entity-source': EntitySourceColorThemeProvider,
|
||||
'hydrophobicity': HydrophobicityColorThemeProvider,
|
||||
'illustrative': IllustrativeColorThemeProvider,
|
||||
|
||||
90
src/mol-theme/color/atom-id.ts
Normal file
90
src/mol-theme/color/atom-id.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StructureProperties, StructureElement, Bond, Structure } from '../../mol-model/structure';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { ColorTheme, LocationColor } from '../color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
|
||||
import { TableLegend, ScaleLegend } from '../../mol-util/legend';
|
||||
|
||||
const DefaultList = 'many-distinct';
|
||||
const DefaultColor = Color(0xFAFAFA);
|
||||
const Description = 'Gives every atom a color based on its `label_atom_id` value.';
|
||||
|
||||
export const AtomIdColorThemeParams = {
|
||||
...getPaletteParams({ type: 'colors', colorList: DefaultList }),
|
||||
};
|
||||
export type AtomIdColorThemeParams = typeof AtomIdColorThemeParams
|
||||
export function getAtomIdColorThemeParams(ctx: ThemeDataContext) {
|
||||
const params = PD.clone(AtomIdColorThemeParams);
|
||||
return params;
|
||||
}
|
||||
|
||||
function getAtomIdSerialMap(structure: Structure) {
|
||||
const map = new Map<string, number>();
|
||||
for (const m of structure.models) {
|
||||
const { label_atom_id } = m.atomicHierarchy.atoms;
|
||||
for (let i = 0, il = label_atom_id.rowCount; i < il; ++i) {
|
||||
const id = label_atom_id.value(i);
|
||||
if (!map.has(id)) map.set(id, map.size);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function AtomIdColorTheme(ctx: ThemeDataContext, props: PD.Values<AtomIdColorThemeParams>): ColorTheme<AtomIdColorThemeParams> {
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure.root);
|
||||
const atomIdSerialMap = getAtomIdSerialMap(ctx.structure.root);
|
||||
|
||||
const labelTable = Array.from(atomIdSerialMap.keys());
|
||||
const valueLabel = (i: number) => labelTable[i];
|
||||
|
||||
const palette = getPalette(atomIdSerialMap.size, props, { valueLabel });
|
||||
legend = palette.legend;
|
||||
|
||||
color = (location: Location): Color => {
|
||||
let serial: number | undefined = undefined;
|
||||
if (StructureElement.Location.is(location)) {
|
||||
const id = StructureProperties.atom.label_atom_id(location);
|
||||
serial = atomIdSerialMap.get(id);
|
||||
} else if (Bond.isLocation(location)) {
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
const id = StructureProperties.atom.label_atom_id(l);
|
||||
serial = atomIdSerialMap.get(id);
|
||||
}
|
||||
return serial === undefined ? DefaultColor : palette.color(serial);
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
factory: AtomIdColorTheme,
|
||||
granularity: 'group',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
};
|
||||
}
|
||||
|
||||
export const AtomIdColorThemeProvider: ColorTheme.Provider<AtomIdColorThemeParams, 'atom-id'> = {
|
||||
name: 'atom-id',
|
||||
label: 'Atom Id',
|
||||
category: ColorTheme.Category.Atom,
|
||||
factory: AtomIdColorTheme,
|
||||
getParams: getAtomIdColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(AtomIdColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
|
||||
};
|
||||
98
src/mol-theme/color/entity-id.ts
Normal file
98
src/mol-theme/color/entity-id.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StructureProperties, StructureElement, Bond, Structure } from '../../mol-model/structure';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { ColorTheme, LocationColor } from '../color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
|
||||
import { TableLegend, ScaleLegend } from '../../mol-util/legend';
|
||||
|
||||
const DefaultList = 'many-distinct';
|
||||
const DefaultColor = Color(0xFAFAFA);
|
||||
const Description = 'Gives every chain a color based on its `label_entity_id` value.';
|
||||
|
||||
export const EntityIdColorThemeParams = {
|
||||
...getPaletteParams({ type: 'colors', colorList: DefaultList }),
|
||||
};
|
||||
export type EntityIdColorThemeParams = typeof EntityIdColorThemeParams
|
||||
export function getEntityIdColorThemeParams(ctx: ThemeDataContext) {
|
||||
const params = PD.clone(EntityIdColorThemeParams);
|
||||
return params;
|
||||
}
|
||||
|
||||
function key(entityId: string, modelIndex: number) {
|
||||
return `${entityId}|${modelIndex}`;
|
||||
}
|
||||
|
||||
function getEntityIdSerialMap(structure: Structure) {
|
||||
const map = new Map<string, number>();
|
||||
for (let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
const { label_entity_id } = structure.models[i].atomicHierarchy.chains;
|
||||
for (let j = 0, jl = label_entity_id.rowCount; j < jl; ++j) {
|
||||
const k = key(label_entity_id.value(j), i);
|
||||
if (!map.has(k)) map.set(k, map.size);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function EntityIdColorTheme(ctx: ThemeDataContext, props: PD.Values<EntityIdColorThemeParams>): ColorTheme<EntityIdColorThemeParams> {
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure.root);
|
||||
const entityIdSerialMap = getEntityIdSerialMap(ctx.structure.root);
|
||||
|
||||
const labelTable = Array.from(entityIdSerialMap.keys());
|
||||
const valueLabel = (i: number) => labelTable[i];
|
||||
|
||||
const palette = getPalette(entityIdSerialMap.size, props, { valueLabel });
|
||||
legend = palette.legend;
|
||||
|
||||
color = (location: Location): Color => {
|
||||
let serial: number | undefined = undefined;
|
||||
if (StructureElement.Location.is(location)) {
|
||||
const atomId = StructureProperties.chain.label_entity_id(location);
|
||||
const modelIndex = location.structure.models.indexOf(location.unit.model);
|
||||
const k = key(atomId, modelIndex);
|
||||
serial = entityIdSerialMap.get(k);
|
||||
} else if (Bond.isLocation(location)) {
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
const atomId = StructureProperties.chain.label_entity_id(l);
|
||||
const modelIndex = l.structure.models.indexOf(l.unit.model);
|
||||
const k = key(atomId, modelIndex);
|
||||
serial = entityIdSerialMap.get(k);
|
||||
}
|
||||
return serial === undefined ? DefaultColor : palette.color(serial);
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
factory: EntityIdColorTheme,
|
||||
granularity: 'group',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
};
|
||||
}
|
||||
|
||||
export const EntityIdColorThemeProvider: ColorTheme.Provider<EntityIdColorThemeParams, 'entity-id'> = {
|
||||
name: 'entity-id',
|
||||
label: 'Entity Id',
|
||||
category: ColorTheme.Category.Chain,
|
||||
factory: EntityIdColorTheme,
|
||||
getParams: getEntityIdColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(EntityIdColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
|
||||
};
|
||||
Reference in New Issue
Block a user