diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index 5064c7359c795f95573924b3f9c966e1c5859c0a..44ba2975516385dcf35260e7eddc971dd48c80b6 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/src/mol-math/geometry/symmetry-operator.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Vec3, Mat4, Mat3 } from '../linear-algebra/3d' +import { Vec3, Mat4, Mat3, Quat } from '../linear-algebra/3d' interface SymmetryOperator { readonly name: string, @@ -64,6 +64,26 @@ namespace SymmetryOperator { return create(name, t, { id: '', operList: [] }, ncsId); } + const _q1 = Quat.identity(), _q2 = 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); + + const _t = 1 - t; + // interpolate rotation + Mat4.getRotation(_q2, m); + Quat.slerp(_q2, _q1, _q2, _t); + const angle = Quat.getAxisAngle(_axis, _q2); + Mat4.fromRotation(out, angle, _axis); + + // interpolate translation + Mat4.setValue(out, 0, 3, _t * Mat4.getValue(m, 0, 3)); + Mat4.setValue(out, 1, 3, _t * Mat4.getValue(m, 1, 3)); + Mat4.setValue(out, 2, 3, _t * Mat4.getValue(m, 2, 3)); + + 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-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index abf5cfb9660c20a43039510c799baa7e568f88f2..c8a307e8de9a57ab893dab678ce1bff9d1203076 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/src/mol-math/linear-algebra/3d/mat4.ts @@ -22,6 +22,7 @@ import Vec3 from './vec3'; import Quat from './quat'; import { degToRad } from '../../misc'; import { NumberArray } from 'mol-util/type-helpers'; +import Mat3 from './mat3'; interface Mat4 extends Array<number> { [d: number]: number, '@type': 'mat4', length: 16 } interface ReadonlyMat4 extends Array<number> { readonly [d: number]: number, '@type': 'mat4', length: 16 } @@ -661,6 +662,22 @@ namespace Mat4 { return out; } + /** + * Copies the mat3 into upper-left 3x3 values. + */ + export function fromMat3(out: Mat4, a: Mat3) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[4] = a[3]; + out[5] = a[4]; + out[6] = a[5]; + out[8] = a[6]; + out[9] = a[7]; + out[10] = a[8]; + return out; + } + export function makeTable(m: Mat4) { let ret = ''; for (let i = 0; i < 4; i++) { diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index de55868e977da7566d2d48a95042ac64caa57235..99fb4cf56adc549b2a5a26bb5d788b905794b233 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -26,6 +26,7 @@ import { compile } from 'mol-script/runtime/query/compiler'; import { Overpaint } from 'mol-theme/overpaint'; import { parseMolScript } from 'mol-script/language/parser'; import { transpileMolScript } from 'mol-script/script/mol-script/symbols'; +import { SymmetryOperator } from 'mol-math/geometry'; export const HighlightLoci = PluginBehavior.create({ name: 'representation-highlight-loci', @@ -290,4 +291,67 @@ export namespace ColorRepresentation3D { } export class Obj extends PluginStateObject.CreateBehavior<Behavior>({ name: 'Color Representation3D Behavior' }) { } +} + +export namespace UnwindAssemblyRepresentation3D { + export const Params = { + t: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }) + } + export type Params = PD.Values<typeof Params> + + export class Behavior implements PluginBehavior<Params> { + private currentT = 0; + private repr: StateObjectTracker<PluginStateObject.Molecule.Representation3D>; + private structure: StateObjectTracker<PluginStateObject.Molecule.Structure>; + private transforms: StructureUnitTransforms; + + private updateData() { + const reprUpdated = this.repr.update(); + const strUpdated = this.structure.update(); + if (strUpdated && this.structure.data) { + this.transforms = new StructureUnitTransforms(this.structure.data); + } + return reprUpdated || strUpdated; + } + + register(ref: string): void { + this.repr.setQuery(StateSelection.Generators.byRef(ref).ancestorOfType([PluginStateObject.Molecule.Representation3D])); + this.structure.setQuery(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure])); + this.update(this.params); + } + + private transMat = Mat4.zero(); + + update(params: Params): boolean | Promise<boolean> { + if (!this.updateData() && params.t === this.currentT) return false; + this.currentT = params.t; + if (!this.structure.data || !this.repr.data) return true; + + const structure = this.structure.data; + for (let i = 0, _i = structure.units.length; i < _i; i++) { + const u = structure.units[i]; + SymmetryOperator.lerpFromIdentity(this.transMat, u.conformation.operator, this.currentT); + this.transforms.setTransform(this.transMat, u); + } + + this.repr.data.setState({ unitTransforms: this.transforms }); + this.ctx.canvas3d.add(this.repr.data); + this.ctx.canvas3d.requestDraw(true); + + return true; + } + + unregister(): void { + this.update({ t: 0 }) + this.repr.cell = void 0; + this.structure.cell = void 0; + } + + constructor(private ctx: PluginContext, private params: Params) { + this.repr = new StateObjectTracker(ctx.state.dataState); + this.structure = new StateObjectTracker(ctx.state.dataState); + } + } + + export class Obj extends PluginStateObject.CreateBehavior<Behavior>({ name: 'Explode Representation3D Behavior' }) { } } \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index e1fdc8e433b63667617ca29118711fc0ca4e21a6..e9832b21aa9866dcbe07e9e2180444947846319c 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -12,7 +12,7 @@ import * as ReactDOM from 'react-dom'; import { PluginSpec } from './spec'; import { StateTransforms } from './state/transforms'; import { PluginBehaviors } from './behavior'; -import { AnimateModelIndex } from './state/animation/built-in'; +import { AnimateModelIndex, AnimateAssemblyUnwind } 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'; @@ -47,6 +47,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.StructureLabels3D), PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D), + PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D), PluginSpec.Action(StateTransforms.Representation.ColorStructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D), @@ -64,7 +65,8 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Behavior(StructureRepresentationInteraction) ], animations: [ - AnimateModelIndex + AnimateModelIndex, + AnimateAssemblyUnwind ] } diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts index 91f581b1ad65433989e16a3906994cc85b8a93ec..91d78700e5aefb88a7529948993000d61a65b26d 100644 --- a/src/mol-plugin/state/animation/built-in.ts +++ b/src/mol-plugin/state/animation/built-in.ts @@ -10,6 +10,7 @@ import { StateTransforms } from '../transforms'; import { StateSelection } from 'mol-state'; import { PluginCommands } from 'mol-plugin/command'; import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { UnwindAssemblyRepresentation3D } from 'mol-plugin/behavior/dynamic/representation'; export const AnimateModelIndex = PluginStateAnimation.create({ name: 'built-in.animate-model-index', @@ -81,4 +82,35 @@ export const AnimateModelIndex = PluginStateAnimation.create({ if (params.mode.name === 'palindrome') return { kind: 'next', state: { palindromeDirections } }; return { kind: 'next', state: {} }; } +}) + +export const AnimateAssemblyUnwind = PluginStateAnimation.create({ + name: 'built-in.animate-assembly-unwind', + display: { name: 'Unwind Assembly' }, + params: () => ({ + durationInMs: PD.Numeric(3000, { min: 100, max: 10000, step: 100}) + }), + initialState: () => ({ t: 0 }), + async apply(animState, t, ctx) { + const state = ctx.plugin.state.dataState; + const anims = state.selectQ(q => q.ofType(UnwindAssemblyRepresentation3D.Obj)); + + if (anims.length === 0) { + // nothing more to do here + return { kind: 'finished' }; + } + + const update = state.build(); + + const d = (t.current - t.lastApplied) / ctx.params.durationInMs; + const newTime = (animState.t + d) % 1; + + for (const m of anims) { + update.to(m.transform.ref).update(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, _ => ({ t: newTime })); + } + + await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + + return { kind: 'next', state: { t: newTime } }; + } }) \ No newline at end of file diff --git a/src/mol-plugin/state/objects.ts b/src/mol-plugin/state/objects.ts index c30240a4be52390a8bd85775db11b77a303b5463..3f2f1c21259f32c0d85f06a837bdb1df96150b5b 100644 --- a/src/mol-plugin/state/objects.ts +++ b/src/mol-plugin/state/objects.ts @@ -44,7 +44,6 @@ export namespace PluginStateObject { } export class Root extends Create({ name: 'Root', typeClass: 'Root' }) { } - export class Group extends Create({ name: 'Group', typeClass: 'Group' }) { } export namespace Data { @@ -82,6 +81,7 @@ export namespace PluginStateObject { export class Model extends Create<_Model>({ name: 'Model', typeClass: 'Object' }) { } export class Structure extends Create<_Structure>({ name: 'Structure', typeClass: 'Object' }) { } export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any> | ShapeRepresentation<any, any, any>>({ name: 'Structure 3D' }) { } + export class Representation3DState extends Create<{}>({ name: 'Structure 3D State', typeClass: 'Object' }) { } } export namespace Volume { diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 1617502a8f9c81a6f5744df1cdca3fc94060c1c2..2badf987c4c2d814979d00709f9d4ea7e579240f 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -7,7 +7,7 @@ import { Structure } from 'mol-model/structure'; import { VolumeData, VolumeIsoValue } from 'mol-model/volume'; -import { ExplodeRepresentation3D, ColorRepresentation3D } from 'mol-plugin/behavior/dynamic/representation'; +import { ExplodeRepresentation3D, ColorRepresentation3D, UnwindAssemblyRepresentation3D } from 'mol-plugin/behavior/dynamic/representation'; import { PluginContext } from 'mol-plugin/context'; import { RepresentationProvider } from 'mol-repr/representation'; import { BuiltInStructureRepresentationsName } from 'mol-repr/structure/registry'; @@ -30,6 +30,7 @@ export { StructureRepresentation3D } export { StructureRepresentation3DHelpers } export { StructureLabels3D} export { ExplodeStructureRepresentation3D } +export { UnwindStructureAssemblyRepresentation3D } export { ColorStructureRepresentation3D } export { VolumeRepresentation3D } @@ -229,6 +230,29 @@ const StructureLabels3D = PluginStateTransform.BuiltIn({ } }); +type UnwindStructureAssemblyRepresentation3D = typeof UnwindStructureAssemblyRepresentation3D +const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({ + name: 'unwind-structure-assembly-representation-3d', + display: 'Unwind Assembly 3D Representation', + from: SO.Molecule.Representation3D, + to: UnwindAssemblyRepresentation3D.Obj, + params: UnwindAssemblyRepresentation3D.Params +})({ + canAutoUpdate() { + return true; + }, + apply({ params }, plugin: PluginContext) { + return new UnwindAssemblyRepresentation3D.Obj(new UnwindAssemblyRepresentation3D.Behavior(plugin, params), { label: `Unwind T = ${params.t.toFixed(2)}` }); + }, + update({ b, newParams }) { + return Task.create('Update Unwind', async () => { + const updated = await b.data.update(newParams); + b.label = `Unwind T = ${newParams.t.toFixed(2)}`; + return updated ? StateTransformer.UpdateResult.Updated : StateTransformer.UpdateResult.Unchanged; + }); + } +}); + type ExplodeStructureRepresentation3D = typeof ExplodeStructureRepresentation3D const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({ name: 'explode-structure-representation-3d',