From 2f08c2419b8d5b717aa216061bee0781f11730b1 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Wed, 3 Apr 2019 15:52:22 +0200 Subject: [PATCH] wip snapshot interpolation proof of concept --- src/mol-math/geometry/symmetry-operator.ts | 22 +++++++- .../structure/structure/structure.ts | 2 +- src/mol-plugin/index.ts | 5 +- src/mol-plugin/state/actions/structure.ts | 9 ++++ src/mol-plugin/state/animation/built-in.ts | 50 +++++++++++++++++++ src/mol-plugin/state/transforms/model.ts | 50 ++++++++++++++++++- .../state/transforms/representation.ts | 15 +++++- src/mol-state/transformer.ts | 5 +- 8 files changed, 152 insertions(+), 6 deletions(-) diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index 44ba29755..6f3a71fe5 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/src/mol-math/geometry/symmetry-operator.ts @@ -5,6 +5,7 @@ */ import { Vec3, Mat4, Mat3, Quat } from '../linear-algebra/3d' +import { lerp as scalar_lerp } from 'mol-math/interpolate'; interface SymmetryOperator { readonly name: string, @@ -64,7 +65,7 @@ namespace SymmetryOperator { return create(name, t, { id: '', operList: [] }, ncsId); } - const _q1 = Quat.identity(), _q2 = Quat.zero(), _axis = Vec3.zero(); + const _q1 = Quat.identity(), _q2 = Quat.zero(), _q3 = Quat.zero(), _axis = Vec3.zero(); export function lerpFromIdentity(out: Mat4, op: SymmetryOperator, t: number): Mat4 { const m = op.inverse; if (op.isIdentity) return Mat4.copy(out, m); @@ -84,6 +85,25 @@ namespace SymmetryOperator { return out; } + export function slerp(out: Mat4, src: Mat4, tar: Mat4, t: number): Mat4 { + if (Math.abs(t) <= 0.00001) return Mat4.copy(out, src); + if (Math.abs(t - 1) <= 0.00001) return Mat4.copy(out, tar); + + // interpolate rotation + Mat4.getRotation(_q2, src); + Mat4.getRotation(_q3, tar); + Quat.slerp(_q3, _q2, _q3, t); + const angle = Quat.getAxisAngle(_axis, _q3); + Mat4.fromRotation(out, angle, _axis); + + // interpolate translation + Mat4.setValue(out, 0, 3, scalar_lerp(Mat4.getValue(src, 0, 3), Mat4.getValue(tar, 0, 3), t)); + Mat4.setValue(out, 1, 3, scalar_lerp(Mat4.getValue(src, 1, 3), Mat4.getValue(tar, 1, 3), t)); + Mat4.setValue(out, 2, 3, scalar_lerp(Mat4.getValue(src, 2, 3), Mat4.getValue(tar, 2, 3), t)); + + return out; + } + /** * Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix). * Keep `name`, `assembly`, `ncsId` and `hkl` properties from second. diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 8d45b5146..f3bde2da2 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -381,7 +381,7 @@ namespace Structure { const units: Unit[] = []; for (const u of s.units) { const old = u.conformation.operator; - const op = SymmetryOperator.create(old.name, transform, { id: '', operList: [] }, old.ncsId, old.hkl); + const op = SymmetryOperator.create(old.name, transform, old.assembly, old.ncsId, old.hkl); units.push(u.applyOperator(u.id, op)); } diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 7650a7447..8b9610bba 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -12,10 +12,11 @@ import * as ReactDOM from 'react-dom'; import { PluginSpec } from './spec'; import { StateTransforms } from './state/transforms'; import { PluginBehaviors } from './behavior'; -import { AnimateModelIndex, AnimateAssemblyUnwind, AnimateUnitsExplode } from './state/animation/built-in'; +import { AnimateModelIndex, AnimateAssemblyUnwind, AnimateUnitsExplode, AnimateStateInterpolation } from './state/animation/built-in'; import { StateActions } from './state/actions'; import { InitVolumeStreaming, BoxifyVolumeStreaming, CreateVolumeStreamingBehavior } from './behavior/dynamic/volume-streaming/transformers'; import { StructureRepresentationInteraction } from './behavior/dynamic/selection/structure-representation-interaction'; +import { TransformStructureConformation } from './state/actions/structure'; export const DefaultPluginSpec: PluginSpec = { actions: [ @@ -38,6 +39,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Model.TrajectoryFromPDB), PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel), PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel), + PluginSpec.Action(TransformStructureConformation), PluginSpec.Action(StateTransforms.Model.StructureFromModel), PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory), PluginSpec.Action(StateTransforms.Model.UserStructureSelection), @@ -66,6 +68,7 @@ export const DefaultPluginSpec: PluginSpec = { AnimateModelIndex, AnimateAssemblyUnwind, AnimateUnitsExplode, + AnimateStateInterpolation ] } diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 21cb82fe3..c32e25c47 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -287,6 +287,15 @@ export const EnableModelCustomProps = StateAction.build({ return state.updateTree(root); }); +export const TransformStructureConformation = StateAction.build({ + display: { name: 'Transform Conformation' }, + from: PluginStateObject.Molecule.Structure, + params: StateTransforms.Model.TransformStructureConformation.definition.params, +})(({ ref, params, state }) => { + const root = state.build().to(ref).insert(StateTransforms.Model.TransformStructureConformation, params as any); + return state.updateTree(root); +}); + export const StructureFromSelection = StateAction.build({ display: { name: 'Selection Structure', description: 'Create a new Structure from the current selection.' }, from: PluginStateObject.Molecule.Structure, diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts index 0c3ab20c2..4bbab98fe 100644 --- a/src/mol-plugin/state/animation/built-in.ts +++ b/src/mol-plugin/state/animation/built-in.ts @@ -229,4 +229,54 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({ return { kind: 'next', state: { t: newTime } }; } +}) + +export const AnimateStateInterpolation = PluginStateAnimation.create({ + name: 'built-in.animate-state-interpolation', + display: { name: 'Animate State Interpolation' }, + params: () => ({ + transtionDurationInMs: PD.Numeric(2000, { min: 100, max: 30000, step: 10 }) + }), + initialState: () => ({ }), + async apply(animState, t, ctx) { + + const snapshots = ctx.plugin.state.snapshots.state.entries; + if (snapshots.size < 2) return { kind: 'finished' }; + + // const totalTime = (snapshots.size - 1) * ctx.params.transtionDurationInMs; + const currentT = (t.current % ctx.params.transtionDurationInMs) / ctx.params.transtionDurationInMs; + + let srcIndex = Math.floor(t.current / ctx.params.transtionDurationInMs) % snapshots.size; + let tarIndex = Math.ceil(t.current / ctx.params.transtionDurationInMs); + if (tarIndex === srcIndex) tarIndex++; + tarIndex = tarIndex % snapshots.size; + + const _src = snapshots.get(srcIndex)!.snapshot, _tar = snapshots.get(tarIndex)!.snapshot; + + if (!_src.data || !_tar.data) return { kind: 'skip' }; + + const src = _src.data.tree.transforms, tar = _tar.data.tree.transforms; + + const state = ctx.plugin.state.dataState; + const update = state.build(); + + for (const s of src) { + for (const t of tar) { + if (t.ref !== s.ref) continue; + if (t.version === s.version) continue; + + const e = StateTransform.fromJSON(s), f = StateTransform.fromJSON(t); + + if (!e.transformer.definition.interpolate) { + update.to(s.ref).update(currentT <= 0.5 ? e.params : f.params); + } else { + update.to(s.ref).update(e.transformer.definition.interpolate(e.params, f.params, currentT, ctx.plugin)); + } + } + } + + await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + + return { kind: 'next', state: { } }; + } }) \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 5f890882a..01ef8d7dd 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -6,7 +6,7 @@ */ import { parsePDB } from 'mol-io/reader/pdb/parser'; -import { Vec3 } from 'mol-math/linear-algebra'; +import { Vec3, Mat4, Quat } from 'mol-math/linear-algebra'; import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; import { trajectoryFromPDB } from 'mol-model-formats/structure/pdb'; import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry, QueryFn } from 'mol-model/structure'; @@ -25,6 +25,7 @@ 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'; +import { SymmetryOperator } from 'mol-math/geometry'; export { TrajectoryFromBlob }; export { TrajectoryFromMmCif }; @@ -34,6 +35,7 @@ export { ModelFromTrajectory }; export { StructureFromModel }; export { StructureAssemblyFromModel }; export { StructureSymmetryFromModel }; +export { TransformStructureConformation } export { StructureSelection }; export { UserStructureSelection }; export { StructureComplexElement }; @@ -251,6 +253,52 @@ const StructureSymmetryFromModel = PluginStateTransform.BuiltIn({ } }); +const _translation = Vec3.zero(), _m = Mat4.zero(), _n = Mat4.zero(); +type TransformStructureConformation = typeof TransformStructureConformation +const TransformStructureConformation = PluginStateTransform.BuiltIn({ + name: 'transform-structure-conformation', + display: { name: 'Transform Conformation' }, + from: SO.Molecule.Structure, + to: SO.Molecule.Structure, + params: { + axis: PD.Vec3(Vec3.create(1, 0, 0)), + angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }), + translation: PD.Vec3(Vec3.create(0, 0, 0)), + } +})({ + canAutoUpdate() { + return true; + }, + apply({ a, params }) { + // TODO: optimze + + const center = a.data.boundary.sphere.center; + Mat4.fromTranslation(_m, Vec3.negate(_translation, center)); + Mat4.fromTranslation(_n, Vec3.add(_translation, center, params.translation)); + const rot = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * params.angle, Vec3.normalize(Vec3.zero(), params.axis)); + + const m = Mat4.zero(); + Mat4.mul3(m, _n, rot, _m); + + const s = Structure.transform(a.data, m); + const props = { label: `${a.label}`, description: `Transformed` }; + return new SO.Molecule.Structure(s, props); + }, + interpolate(src, tar, t) { + // TODO: optimize + const u = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * src.angle, Vec3.normalize(Vec3.zero(), src.axis)); + Mat4.setTranslation(u, src.translation); + const v = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * tar.angle, Vec3.normalize(Vec3.zero(), tar.axis)); + Mat4.setTranslation(v, tar.translation); + const m = SymmetryOperator.slerp(Mat4.zero(), u, v, t); + const rot = Mat4.getRotation(Quat.zero(), m); + const axis = Vec3.zero(); + const angle = Quat.getAxisAngle(axis, rot); + const translation = Mat4.getTranslation(Vec3.zero(), m); + return { axis, angle, translation }; + } +}); + type StructureSelection = typeof StructureSelection const StructureSelection = PluginStateTransform.BuiltIn({ name: 'structure-selection', diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 2b71e273f..f0873a69e 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -15,7 +15,7 @@ import { BuiltInVolumeRepresentationsName } from 'mol-repr/volume/registry'; import { VolumeParams } from 'mol-repr/volume/representation'; import { StateTransformer } from 'mol-state'; import { Task } from 'mol-task'; -import { BuiltInColorThemeName, ColorTheme } from 'mol-theme/color'; +import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from 'mol-theme/color'; import { BuiltInSizeThemeName, SizeTheme } from 'mol-theme/size'; import { createTheme, ThemeRegistryContext } from 'mol-theme/theme'; import { ParamDefinition as PD } from 'mol-util/param-definition'; @@ -191,6 +191,19 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({ await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx); return StateTransformer.UpdateResult.Updated; }); + }, + interpolate(src, tar, t) { + if (src.colorTheme.name !== 'uniform' || tar.colorTheme.name !== 'uniform') { + return t <= 0.5 ? src : tar; + } + BuiltInColorThemes + const from = src.colorTheme.params.value as Color, to = tar.colorTheme.params.value as Color; + const value = Color.interpolate(from, to, t); + return { + type: t <= 0.5 ? src.type : tar.type, + colorTheme: { name: 'uniform', params: { value } }, + sizeTheme: t <= 0.5 ? src.sizeTheme : tar.sizeTheme, + }; } }); diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts index 01759e738..4e60189b7 100644 --- a/src/mol-state/transformer.ts +++ b/src/mol-state/transformer.ts @@ -86,6 +86,9 @@ namespace Transformer { /** By default, returns true */ isSerializable?(params: P): { isSerializable: true } | { isSerializable: false; reason: string }, + /** Parameter interpolation */ + interpolate?(src: P, target: P, t: number, globalCtx: unknown): P + /** Custom conversion to and from JSON */ readonly customSerialization?: { toJSON(params: P, obj?: B): any, fromJSON(data: any): P } } @@ -95,7 +98,7 @@ namespace Transformer { readonly from: StateObject.Ctor[], readonly to: StateObject.Ctor[], readonly display: { readonly name: string, readonly description?: string }, - params?(a: A | undefined, globalCtx: unknown): { [K in keyof P]: PD.Any }, + params?(a: A | undefined, globalCtx: unknown): { [K in keyof P]: PD.Any } } const registry = new Map<Id, Transformer<any, any>>(); -- GitLab