Bladeren bron

mol-plugin: unwind assembly animation

David Sehnal 6 jaren geleden
bovenliggende
commit
7dc1fbdb03

+ 21 - 1
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.

+ 17 - 0
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++) {

+ 64 - 0
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' }) { }
 }

+ 4 - 2
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
     ]
 }
 

+ 32 - 0
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 } };
+    }
 })

+ 1 - 1
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 {

+ 25 - 1
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',