diff --git a/src/examples/basic-wrapper/index.ts b/src/examples/basic-wrapper/index.ts index e7cd468d510c23a26c8b40c7abf8299b854bb7c3..f2dfbcefc92c0a9057f332d261e018bc049bd809 100644 --- a/src/examples/basic-wrapper/index.ts +++ b/src/examples/basic-wrapper/index.ts @@ -7,7 +7,7 @@ import { EmptyLoci } from '../../mol-model/loci'; import { StructureSelection } from '../../mol-model/structure'; import { createPlugin, DefaultPluginSpec } from '../../mol-plugin'; -import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in'; +import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index'; import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; import { PluginCommands } from '../../mol-plugin/commands'; import { PluginContext } from '../../mol-plugin/context'; diff --git a/src/examples/proteopedia-wrapper/index.ts b/src/examples/proteopedia-wrapper/index.ts index d810d2af23f95e9ea863fc5ac859142ca76c84ea..ebd4a00e5462fd51cb571c15ce1fb1dcc33236b3 100644 --- a/src/examples/proteopedia-wrapper/index.ts +++ b/src/examples/proteopedia-wrapper/index.ts @@ -7,7 +7,7 @@ import * as ReactDOM from 'react-dom'; import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d'; import { createPlugin, DefaultPluginSpec } from '../../mol-plugin'; -import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in'; +import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index'; import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params'; import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects'; import { StateTransforms } from '../../mol-plugin-state/transforms'; diff --git a/src/extensions/mp4-export/encoder.ts b/src/extensions/mp4-export/encoder.ts index 932b4f0b89bb299aefc4089533e7398b4de6ccdb..9ac3fd80576c6b25caca78f5567f6d0e1cc4c877 100644 --- a/src/extensions/mp4-export/encoder.ts +++ b/src/extensions/mp4-export/encoder.ts @@ -1,6 +1,6 @@ import * as HME from 'h264-mp4-encoder'; import { canvasToBlob } from '../../mol-canvas3d/util'; -import { AnimateAssemblyUnwind } from '../../mol-plugin-state/animation/built-in'; +import { AnimateAssemblyUnwind } from '../../mol-plugin-state/animation/built-in/assembly-unwind'; import { PluginContext } from '../../mol-plugin/context'; export class Mp4Encoder { diff --git a/src/mol-plugin-state/animation/built-in.ts b/src/mol-plugin-state/animation/built-in.ts deleted file mode 100644 index ad3a541de2b089d28bd2524118c177d510d0a97f..0000000000000000000000000000000000000000 --- a/src/mol-plugin-state/animation/built-in.ts +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { PluginStateAnimation } from './model'; -import { PluginStateObject } from '../objects'; -import { StateTransforms } from '../transforms'; -import { StateSelection, StateTransform } from '../../mol-state'; -import { PluginCommands } from '../../mol-plugin/commands'; -import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { PluginContext } from '../../mol-plugin/context'; - -export const AnimateModelIndex = PluginStateAnimation.create({ - name: 'built-in.animate-model-index', - display: { name: 'Animate Trajectory' }, - params: () => ({ - mode: PD.MappedStatic('palindrome', { - 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 }) - }), - canApply(ctx) { - const state = ctx.state.data; - const models = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Model.ModelFromTrajectory)); - for (const m of models) { - const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]); - if (parent && parent.obj && parent.obj.data.frameCount > 1) return { canApply: true }; - } - return { canApply: false, reason: 'No trajectory to animate' }; - }, - 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) { - return { kind: 'skip' }; - } - - const state = ctx.plugin.state.data; - const models = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Model.ModelFromTrajectory)); - - if (models.length === 0) { - // nothing more to do here - return { kind: 'finished' }; - } - - const update = state.build(); - - const params = ctx.params; - const palindromeDirections = animState.palindromeDirections || { }; - let isEnd = false, allSingles = true; - - 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; - if (traj.data.frameCount <= 1) continue; - - update.to(m).update(old => { - const len = traj.data.frameCount; - if (len !== 1) { - allSingles = false; - } 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; - } - } 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 }; - }); - } - - if (!allSingles) { - await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); - } - - if (allSingles || (params.mode.name === 'once' && isEnd)) return { kind: 'finished' }; - 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: (plugin: PluginContext) => { - const targets: [string, string][] = [['all', 'All']]; - const structures = plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)); - - for (const s of structures) { - targets.push([s.transform.ref, s.obj!.data.models[0].label]); - } - - return { - durationInMs: PD.Numeric(3000, { min: 100, max: 10000, step: 100}), - playOnce: PD.Boolean(false), - target: PD.Select(targets[0][0], targets) - }; - }, - canApply(plugin) { - const state = plugin.state.data; - const root = StateTransform.RootRef; - const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root)); - return { canApply: reprs.length > 0 }; - }, - initialState: () => ({ t: 0 }), - setup(params, plugin) { - const state = plugin.state.data; - const root = !params.target || params.target === 'all' ? StateTransform.RootRef : params.target; - const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root)); - - const update = state.build(); - let changed = false; - for (const r of reprs) { - const unwinds = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, r.transform.ref)); - if (unwinds.length > 0) continue; - - changed = true; - update.to(r) - .apply(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, { t: 0 }, { tags: 'animate-assembly-unwind' }); - } - - if (!changed) return; - - return update.commit({ doNotUpdateCurrent: true }); - }, - teardown(_, plugin) { - const state = plugin.state.data; - const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) - .withTag('animate-assembly-unwind')); - if (reprs.length === 0) return; - - const update = state.build(); - for (const r of reprs) update.delete(r.transform.ref); - return update.commit(); - }, - async apply(animState, t, ctx) { - const state = ctx.plugin.state.data; - const root = !ctx.params.target || ctx.params.target === 'all' ? StateTransform.RootRef : ctx.params.target; - const anims = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, root)); - - if (anims.length === 0) { - return { kind: 'finished' }; - } - - const update = state.build(); - - const d = (t.current - t.lastApplied) / ctx.params.durationInMs; - let newTime = (animState.t + d), finished = false; - if (ctx.params.playOnce && newTime >= 1) { - finished = true; - newTime = 1; - } else { - newTime = newTime % 1; - } - - for (const m of anims) { - update.to(m).update({ t: newTime }); - } - - await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); - - if (finished) return { kind: 'finished' }; - 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 setup(_, plugin) { - const state = plugin.state.data; - const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D)); - - const update = state.build(); - let changed = false; - for (const r of reprs) { - const explodes = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.ExplodeStructureRepresentation3D, r.transform.ref)); - if (explodes.length > 0) continue; - - changed = true; - update.to(r.transform.ref) - .apply(StateTransforms.Representation.ExplodeStructureRepresentation3D, { t: 0 }, { tags: 'animate-units-explode' }); - } - - if (!changed) return; - - return update.commit({ doNotUpdateCurrent: true }); - }, - teardown(_, plugin) { - const state = plugin.state.data; - const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) - .withTag('animate-units-explode')); - if (reprs.length === 0) return; - - const update = state.build(); - for (const r of reprs) update.delete(r.transform.ref); - return update.commit(); - }, - async apply(animState, t, ctx) { - const state = ctx.plugin.state.data; - const anims = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.ExplodeStructureRepresentation3D)); - - if (anims.length === 0) { - 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).update({ t: newTime }); - } - - await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); - - 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 }) - }), - canApply(plugin) { - return { canApply: plugin.managers.snapshot.state.entries.size > 1 }; - }, - initialState: () => ({ }), - async apply(animState, t, ctx) { - - const snapshots = ctx.plugin.managers.snapshot.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.data; - 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(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/animation/built-in/assembly-unwind.ts b/src/mol-plugin-state/animation/built-in/assembly-unwind.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae4396e001d065ee2eda680b3ca88aa6eb4f80a9 --- /dev/null +++ b/src/mol-plugin-state/animation/built-in/assembly-unwind.ts @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginStateAnimation } from '../model'; +import { PluginStateObject } from '../../objects'; +import { StateTransforms } from '../../transforms'; +import { StateSelection, StateTransform } from '../../../mol-state'; +import { PluginCommands } from '../../../mol-plugin/commands'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { PluginContext } from '../../../mol-plugin/context'; + +export const AnimateAssemblyUnwind = PluginStateAnimation.create({ + name: 'built-in.animate-assembly-unwind', + display: { name: 'Unwind Assembly' }, + params: (plugin: PluginContext) => { + const targets: [string, string][] = [['all', 'All']]; + const structures = plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)); + + for (const s of structures) { + targets.push([s.transform.ref, s.obj!.data.models[0].label]); + } + + return { + durationInMs: PD.Numeric(3000, { min: 100, max: 10000, step: 100}), + playOnce: PD.Boolean(false), + target: PD.Select(targets[0][0], targets) + }; + }, + canApply(plugin) { + const state = plugin.state.data; + const root = StateTransform.RootRef; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root)); + return { canApply: reprs.length > 0 }; + }, + initialState: () => ({ t: 0 }), + setup(params, plugin) { + const state = plugin.state.data; + const root = !params.target || params.target === 'all' ? StateTransform.RootRef : params.target; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root)); + + const update = state.build(); + let changed = false; + for (const r of reprs) { + const unwinds = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, r.transform.ref)); + if (unwinds.length > 0) continue; + + changed = true; + update.to(r) + .apply(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, { t: 0 }, { tags: 'animate-assembly-unwind' }); + } + + if (!changed) return; + + return update.commit({ doNotUpdateCurrent: true }); + }, + teardown(_, plugin) { + const state = plugin.state.data; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) + .withTag('animate-assembly-unwind')); + if (reprs.length === 0) return; + + const update = state.build(); + for (const r of reprs) update.delete(r.transform.ref); + return update.commit(); + }, + async apply(animState, t, ctx) { + const state = ctx.plugin.state.data; + const root = !ctx.params.target || ctx.params.target === 'all' ? StateTransform.RootRef : ctx.params.target; + const anims = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, root)); + + if (anims.length === 0) { + return { kind: 'finished' }; + } + + const update = state.build(); + + const d = (t.current - t.lastApplied) / ctx.params.durationInMs; + let newTime = (animState.t + d), finished = false; + if (ctx.params.playOnce && newTime >= 1) { + finished = true; + newTime = 1; + } else { + newTime = newTime % 1; + } + + for (const m of anims) { + update.to(m).update({ t: newTime }); + } + + await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + + if (finished) return { kind: 'finished' }; + return { kind: 'next', state: { t: newTime } }; + } +}); \ No newline at end of file diff --git a/src/mol-plugin-state/animation/built-in/camera-spin.ts b/src/mol-plugin-state/animation/built-in/camera-spin.ts new file mode 100644 index 0000000000000000000000000000000000000000..d75c017fd8c29ce1b065c67961111dc9abca145f --- /dev/null +++ b/src/mol-plugin-state/animation/built-in/camera-spin.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Camera } from '../../../mol-canvas3d/camera'; +import { clamp } from '../../../mol-math/interpolate'; +import { Quat, Vec3 } from '../../../mol-math/linear-algebra/3d'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { PluginStateAnimation } from '../model'; + +const _dir = Vec3(), _axis = Vec3(), _rot = Quat(); + +export const AnimateCameraSpin = PluginStateAnimation.create({ + name: 'built-in.animate-camera-spin', + display: { name: 'Camera Spin' }, + params: () => ({ + durationInMs: PD.Numeric(4000, { min: 100, max: 20000, step: 100 }), + direction: PD.Select<'cw' | 'ccw'>('cw', [['cw', 'Clockwise'], ['ccw', 'Counter Clockwise']], { cycle: true }), + skipLastFrame: PD.Boolean(true) + }), + initialState: () => ({ }), + async apply(animState: { snapshot: Camera.Snapshot }, t, ctx) { + if (t.current === 0) { + return { kind: 'next', state: animState }; + } else if (ctx.params.skipLastFrame && t.current >= ctx.params.durationInMs) { + return { kind: 'finished' }; + } + + const camera = ctx.plugin.canvas3d?.camera!; + if (camera.state.radiusMax < 0.0001) { + return { kind: 'finished' }; + } + + const delta = clamp((t.current - t.lastApplied) / ctx.params.durationInMs, 0, 1); + const angle = 2 * Math.PI * delta * (ctx.params.direction === 'ccw' ? -1 : 1); + + Vec3.sub(_dir, camera.position, camera.target); + Vec3.normalize(_axis, camera.up); + Quat.setAxisAngle(_rot, _axis, angle); + Vec3.transformQuat(_dir, _dir, _rot); + const position = Vec3.add(Vec3(), camera.target, _dir); + ctx.plugin.canvas3d?.requestCameraReset({ snapshot: { position }, durationMs: 0 }); + + if (t.current >= ctx.params.durationInMs) { + return { kind: 'finished' }; + } + + return { kind: 'next', state: animState }; + } +}); \ No newline at end of file diff --git a/src/mol-plugin-state/animation/built-in/explode-units.ts b/src/mol-plugin-state/animation/built-in/explode-units.ts new file mode 100644 index 0000000000000000000000000000000000000000..e29a7188b32656d102a14651c737093f5e09bf9e --- /dev/null +++ b/src/mol-plugin-state/animation/built-in/explode-units.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginCommands } from '../../../mol-plugin/commands'; +import { StateSelection } from '../../../mol-state'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { PluginStateObject } from '../../objects'; +import { StateTransforms } from '../../transforms'; +import { PluginStateAnimation } from '../model'; + +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 setup(_, plugin) { + const state = plugin.state.data; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D)); + + const update = state.build(); + let changed = false; + for (const r of reprs) { + const explodes = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.ExplodeStructureRepresentation3D, r.transform.ref)); + if (explodes.length > 0) continue; + + changed = true; + update.to(r.transform.ref) + .apply(StateTransforms.Representation.ExplodeStructureRepresentation3D, { t: 0 }, { tags: 'animate-units-explode' }); + } + + if (!changed) return; + + return update.commit({ doNotUpdateCurrent: true }); + }, + teardown(_, plugin) { + const state = plugin.state.data; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) + .withTag('animate-units-explode')); + if (reprs.length === 0) return; + + const update = state.build(); + for (const r of reprs) update.delete(r.transform.ref); + return update.commit(); + }, + async apply(animState, t, ctx) { + const state = ctx.plugin.state.data; + const anims = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.ExplodeStructureRepresentation3D)); + + if (anims.length === 0) { + 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).update({ t: newTime }); + } + + await PluginCommands.State.Update(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/animation/built-in/model-index.ts b/src/mol-plugin-state/animation/built-in/model-index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bdefea48c00cb86cb3fc1e6fc40a4e519759e6fe --- /dev/null +++ b/src/mol-plugin-state/animation/built-in/model-index.ts @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginCommands } from '../../../mol-plugin/commands'; +import { StateSelection } from '../../../mol-state'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { PluginStateObject } from '../../objects'; +import { StateTransforms } from '../../transforms'; +import { PluginStateAnimation } from '../model'; + +export const AnimateModelIndex = PluginStateAnimation.create({ + name: 'built-in.animate-model-index', + display: { name: 'Animate Trajectory' }, + params: () => ({ + mode: PD.MappedStatic('palindrome', { + 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 }) + }), + canApply(ctx) { + const state = ctx.state.data; + const models = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Model.ModelFromTrajectory)); + for (const m of models) { + const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]); + if (parent && parent.obj && parent.obj.data.frameCount > 1) return { canApply: true }; + } + return { canApply: false, reason: 'No trajectory to animate' }; + }, + 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) { + return { kind: 'skip' }; + } + + const state = ctx.plugin.state.data; + const models = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Model.ModelFromTrajectory)); + + if (models.length === 0) { + // nothing more to do here + return { kind: 'finished' }; + } + + const update = state.build(); + + const params = ctx.params; + const palindromeDirections = animState.palindromeDirections || { }; + let isEnd = false, allSingles = true; + + 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; + if (traj.data.frameCount <= 1) continue; + + update.to(m).update(old => { + const len = traj.data.frameCount; + if (len !== 1) { + allSingles = false; + } 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; + } + } 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 }; + }); + } + + if (!allSingles) { + await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + } + + if (allSingles || (params.mode.name === 'once' && isEnd)) return { kind: 'finished' }; + if (params.mode.name === 'palindrome') return { kind: 'next', state: { palindromeDirections } }; + return { kind: 'next', state: {} }; + } +}); \ No newline at end of file diff --git a/src/mol-plugin-state/animation/built-in/state-interpolation.ts b/src/mol-plugin-state/animation/built-in/state-interpolation.ts new file mode 100644 index 0000000000000000000000000000000000000000..60aeaa7395be0590f19a4394e277b52c33127f7c --- /dev/null +++ b/src/mol-plugin-state/animation/built-in/state-interpolation.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginCommands } from '../../../mol-plugin/commands'; +import { StateTransform } from '../../../mol-state'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { PluginStateAnimation } from '../model'; + +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 }) + }), + canApply(plugin) { + return { canApply: plugin.managers.snapshot.state.entries.size > 1 }; + }, + initialState: () => ({ }), + async apply(animState, t, ctx) { + + const snapshots = ctx.plugin.managers.snapshot.state.entries; + if (snapshots.size < 2) return { kind: 'finished' }; + + 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.data; + 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(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + + return { kind: 'next', state: { } }; + } +}); \ No newline at end of file diff --git a/src/mol-plugin/animation-loop.ts b/src/mol-plugin/animation-loop.ts index 12bc87d5842301444cb734a39c6a6b2c8b3bae4b..15a4cf4bde7b0bd22d8796787b422081a3d87267 100644 --- a/src/mol-plugin/animation-loop.ts +++ b/src/mol-plugin/animation-loop.ts @@ -11,9 +11,9 @@ export class PluginAnimationLoop { private currentFrame: any = void 0; private _isAnimating = false; - tick(t: number, options?: { isSynchronous?: boolean, manualDraw?: boolean }) { + async tick(t: number, options?: { isSynchronous?: boolean, manualDraw?: boolean }) { + await this.plugin.managers.animation.tick(t, options?.isSynchronous); this.plugin.canvas3d?.tick(t as now.Timestamp, options); - return this.plugin.managers.animation.tick(t, options?.isSynchronous); } private frame = () => { diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 288db73b74ac69038d1b8f4ebad103cb5730abd1..9b0628f0c846178534ffd2822502c19d35348cf1 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -8,7 +8,6 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { StateActions } from '../mol-plugin-state/actions'; -import { AnimateAssemblyUnwind, AnimateModelIndex, AnimateStateInterpolation, AnimateUnitsExplode } from '../mol-plugin-state/animation/built-in'; import { StateTransforms } from '../mol-plugin-state/transforms'; import { VolumeStreamingCustomControls } from '../mol-plugin-ui/custom/volume'; import { Plugin } from '../mol-plugin-ui/plugin'; @@ -18,6 +17,9 @@ import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreami import { PluginContext } from './context'; import { PluginSpec } from './spec'; import { AssignColorVolume } from '../mol-plugin-state/actions/volume'; +import { AnimateModelIndex } from '../mol-plugin-state/animation/built-in/model-index'; +import { AnimateAssemblyUnwind } from '../mol-plugin-state/animation/built-in/assembly-unwind'; +import { AnimateCameraSpin } from '../mol-plugin-state/animation/built-in/camera-spin'; export const DefaultPluginSpec: PluginSpec = { actions: [ @@ -87,8 +89,7 @@ export const DefaultPluginSpec: PluginSpec = { animations: [ AnimateModelIndex, AnimateAssemblyUnwind, - AnimateUnitsExplode, - AnimateStateInterpolation + AnimateCameraSpin ] };