diff --git a/src/mol-plugin/behavior.ts b/src/mol-plugin/behavior.ts index 682fe1b1ccaf0fa07e7b818bca10ded4ecd815ff..a079d9eff4213f48b67838c4f2c521805b12f4a3 100644 --- a/src/mol-plugin/behavior.ts +++ b/src/mol-plugin/behavior.ts @@ -14,7 +14,6 @@ import * as StaticMisc from './behavior/static/misc' import * as DynamicRepresentation from './behavior/dynamic/representation' import * as DynamicCamera from './behavior/dynamic/camera' import * as DynamicCustomProps from './behavior/dynamic/custom-props' -import * as DynamicAnimation from './behavior/dynamic/animation' import * as DynamicLabels from './behavior/dynamic/labels' export const BuiltInPluginBehaviors = { @@ -28,6 +27,5 @@ export const PluginBehaviors = { Representation: DynamicRepresentation, Camera: DynamicCamera, CustomProps: DynamicCustomProps, - Animation: DynamicAnimation, Labels: DynamicLabels } \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/animation.ts b/src/mol-plugin/behavior/dynamic/animation.ts deleted file mode 100644 index d3bf85519531e2ccb828e17ca9c6838518f89b7f..0000000000000000000000000000000000000000 --- a/src/mol-plugin/behavior/dynamic/animation.ts +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (c) 2018 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 { PluginBehavior } from '../behavior'; -import { ParamDefinition as PD } from 'mol-util/param-definition' -import { degToRad } from 'mol-math/misc'; -import { Mat4, Vec3 } from 'mol-math/linear-algebra'; -import { PluginStateObject as SO, PluginStateObject } from '../../state/objects'; -import { StateObjectCell, State, StateSelection } from 'mol-state'; -import { StructureUnitTransforms } from 'mol-model/structure/structure/util/unit-transforms'; -import { UUID } from 'mol-util'; - -const StructureAnimationParams = { - rotate: PD.Boolean(false), - rotateValue: PD.Numeric(0, { min: 0, max: 360, step: 0.1 }), - explode: PD.Boolean(false), - explodeValue: PD.Numeric(0, { min: 0, max: 100, step: 0.1 }), -} -type StructureAnimationProps = PD.Values<typeof StructureAnimationParams> - -/** - * TODO - * - animation class is just for testing purposes, needs better API - */ -export const StructureAnimation = PluginBehavior.create<StructureAnimationProps>({ - name: 'structure-animation', - category: 'representation', - display: { name: 'Structure Animation' }, - canAutoUpdate: () => true, - ctor: class extends PluginBehavior.Handler<StructureAnimationProps> { - private tmpMat = Mat4.identity() - private rotMat = Mat4.identity() - private transMat = Mat4.identity() - private animMat = Mat4.identity() - private transVec = Vec3.zero() - private rotVec = Vec3.create(0, 1, 0) - private centerVec = Vec3.zero() - - private rotateAnimHandle = -1 - private explodeAnimHandle = -1 - - private updatedUnitTransforms = new Set<SO.Molecule.Structure>() - private structureUnitTransforms = new Map<UUID, StructureUnitTransforms>() - - constructor(protected ctx: PluginContext, protected params: StructureAnimationProps) { - super(ctx, params) - this.update(params) - } - - private getUnitTransforms(structure: SO.Molecule.Structure) { - let unitTransforms = this.structureUnitTransforms.get(structure.id) - if (!unitTransforms) { - unitTransforms = new StructureUnitTransforms(structure.data) - this.structureUnitTransforms.set(structure.id, unitTransforms) - } - return unitTransforms - } - - rotate(rad: number) { - this.updatedUnitTransforms.clear() - const state = this.ctx.state.dataState - const reprs = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Structure.Representation3D)) - Mat4.rotate(this.rotMat, this.tmpMat, rad, this.rotVec) - for (const r of reprs) { - if (!SO.isRepresentation3D(r.obj)) return - const structure = getRootStructure(r, state) - if (!structure || !SO.Molecule.Structure.is(structure.obj)) continue - - const unitTransforms = this.getUnitTransforms(structure.obj) - - if (!this.updatedUnitTransforms.has(structure.obj)) { - for (let i = 0, il = structure.obj.data.units.length; i < il; ++i) { - const u = structure.obj.data.units[i] - Vec3.transformMat4(this.centerVec, u.lookup3d.boundary.sphere.center, u.conformation.operator.matrix) - - Vec3.negate(this.transVec, Vec3.copy(this.transVec, this.centerVec)) - Mat4.fromTranslation(this.transMat, this.transVec) - Mat4.mul(this.animMat, this.rotMat, this.transMat) - - Vec3.copy(this.transVec, this.centerVec) - Mat4.fromTranslation(this.transMat, this.transVec) - Mat4.mul(this.animMat, this.transMat, this.animMat) - - unitTransforms.setTransform(this.animMat, u) - } - this.updatedUnitTransforms.add(structure.obj) - } - - r.obj.data.repr.setState({ unitTransforms }) - this.ctx.canvas3d.add(r.obj.data.repr) - } - this.ctx.canvas3d.requestDraw(true) - } - - animateRotate(play: boolean) { - if (play) { - const animateRotate = (t: number) => { - this.rotate(degToRad((t / 10) % 360)) - this.rotateAnimHandle = requestAnimationFrame(animateRotate) - } - this.rotateAnimHandle = requestAnimationFrame(animateRotate) - } else { - cancelAnimationFrame(this.rotateAnimHandle) - } - } - - explode(p: number) { - this.updatedUnitTransforms.clear() - const state = this.ctx.state.dataState - const reprs = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Structure.Representation3D)); - for (const r of reprs) { - if (!SO.isRepresentation3D(r.obj)) return - const structure = getRootStructure(r, state) - if (!structure || !SO.Molecule.Structure.is(structure.obj)) continue - - const unitTransforms = this.getUnitTransforms(structure.obj) - const d = structure.obj.data.boundary.sphere.radius * (p / 100) - - if (!this.updatedUnitTransforms.has(structure.obj)) { - for (let i = 0, il = structure.obj.data.units.length; i < il; ++i) { - const u = structure.obj.data.units[i] - Vec3.transformMat4(this.centerVec, u.lookup3d.boundary.sphere.center, u.conformation.operator.matrix) - - Vec3.sub(this.transVec, this.centerVec, structure.obj.data.boundary.sphere.center) - Vec3.setMagnitude(this.transVec, this.transVec, d) - Mat4.fromTranslation(this.animMat, this.transVec) - - unitTransforms.setTransform(this.animMat, u) - } - this.updatedUnitTransforms.add(structure.obj) - } - - r.obj.data.repr.setState({ unitTransforms }) - this.ctx.canvas3d.add(r.obj.data.repr) - } - this.ctx.canvas3d.requestDraw(true) - } - - animateExplode(play: boolean) { - if (play) { - const animateExplode = (t: number) => { - this.explode((Math.sin(t * 0.001) + 1) * 50) - this.explodeAnimHandle = requestAnimationFrame(animateExplode) - } - this.explodeAnimHandle = requestAnimationFrame(animateExplode) - } else { - cancelAnimationFrame(this.explodeAnimHandle) - } - } - - register(): void { } - - update(p: StructureAnimationProps) { - let updated = !PD.areEqual(StructureAnimationParams, this.params, p) - if (this.params.rotate !== p.rotate) { - this.params.rotate = p.rotate - this.animateRotate(this.params.rotate) - } - if (this.params.explode !== p.explode) { - this.params.explode = p.explode - this.animateExplode(this.params.explode) - } - if (this.params.rotateValue !== p.rotateValue) { - this.params.rotateValue = p.rotateValue - this.rotate(degToRad(this.params.rotateValue)) - } - if (this.params.explodeValue !== p.explodeValue) { - this.params.explodeValue = p.explodeValue - this.explode(this.params.explodeValue) - } - return updated; - } - - unregister() { - cancelAnimationFrame(this.rotateAnimHandle) - cancelAnimationFrame(this.explodeAnimHandle) - } - }, - params: () => StructureAnimationParams -}); - -// - -function getRootStructure(root: StateObjectCell, state: State) { - return state.select(StateSelection.Generators.byValue(root).rootOfType([PluginStateObject.Molecule.Structure]))[0]; -} \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index e9832b21aa9866dcbe07e9e2180444947846319c..b98bea448d9fe2eedfa59c975eb177ce42cfb160 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, AnimateAssemblyUnwind } from './state/animation/built-in'; +import { AnimateModelIndex, AnimateAssemblyUnwind, AnimateUnitsExplode } 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'; @@ -58,7 +58,6 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci), PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider), PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 20, extraRadius: 4 }), - PluginSpec.Behavior(PluginBehaviors.Animation.StructureAnimation, { rotate: false, rotateValue: 0, explode: false, explodeValue: 0 }), // PluginSpec.Behavior(PluginBehaviors.Labels.SceneLabels), PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true }), PluginSpec.Behavior(PluginBehaviors.CustomProps.RCSBAssemblySymmetry, { autoAttach: true }), @@ -66,7 +65,8 @@ export const DefaultPluginSpec: PluginSpec = { ], animations: [ AnimateModelIndex, - AnimateAssemblyUnwind + AnimateAssemblyUnwind, + AnimateUnitsExplode, ] } diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts index d60b81ffe46abaf37323d945027a03e6d2dbaf74..6d7ed7033917e07df09b972a28328ed451e7e8c4 100644 --- a/src/mol-plugin/state/animation/built-in.ts +++ b/src/mol-plugin/state/animation/built-in.ts @@ -111,6 +111,38 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({ await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + return { kind: 'next', state: { t: newTime } }; + } +}) + +export const AnimateUnitsExplode = PluginStateAnimation.create({ + name: 'built-in.animate-units-explode', + display: { name: 'Explode Units' }, + 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.rootsOfType(PluginStateObject.Molecule.Structure.Representation3DState) + .filter(c => c.transform.transformer === StateTransforms.Representation.ExplodeStructureRepresentation3D)); + + 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.ExplodeStructureRepresentation3D, _ => ({ 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