Skip to content
Snippets Groups Projects
Commit 9854f4b6 authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: model index animation modes

parent 1cc4a050
No related branches found
No related tags found
No related merge requests found
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#controls > button { #controls > button {
display: block; display: block;
width: 100%; width: 100%;
text-align: left;
} }
#controls > hr { #controls > hr {
...@@ -57,18 +58,30 @@ ...@@ -57,18 +58,30 @@
BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }); BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId });
BasicMolStarWrapper.toggleSpin(); BasicMolStarWrapper.toggleSpin();
addControl('Toggle Spin', () => BasicMolStarWrapper.toggleSpin()); addHeader('Source');
addSeparator();
addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format })); addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format }));
addControl('Load Assembly 1', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId })); addControl('Load Assembly 1', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
addSeparator(); addSeparator();
addControl('Play Forward', () => BasicMolStarWrapper.animate.modelIndex.forward(8 /** maxFPS */)); addHeader('Camera');
addControl('Toggle Spin', () => BasicMolStarWrapper.toggleSpin());
addSeparator();
addHeader('Animation');
// adjust this number to make the animation faster or slower
// requires to "restart" the animation if changed
BasicMolStarWrapper.animate.modelIndex.maxFPS = 4;
addControl('Play To End', () => BasicMolStarWrapper.animate.modelIndex.onceForward());
addControl('Play To Start', () => BasicMolStarWrapper.animate.modelIndex.onceBackward());
addControl('Play Palindrome', () => BasicMolStarWrapper.animate.modelIndex.palindrome());
addControl('Play Loop', () => BasicMolStarWrapper.animate.modelIndex.loop());
addControl('Stop', () => BasicMolStarWrapper.animate.modelIndex.stop()); addControl('Stop', () => BasicMolStarWrapper.animate.modelIndex.stop());
addControl('Play Backward', () => BasicMolStarWrapper.animate.modelIndex.backward(8 /** maxFPS */));
////////////////////////////////////////////////////////
function addControl(label, action) { function addControl(label, action) {
var btn = document.createElement('button'); var btn = document.createElement('button');
...@@ -81,6 +94,12 @@ ...@@ -81,6 +94,12 @@
var hr = document.createElement('hr'); var hr = document.createElement('hr');
document.getElementById('controls').appendChild(hr); document.getElementById('controls').appendChild(hr);
} }
function addHeader(header) {
var h = document.createElement('h3');
h.innerText = header;
document.getElementById('controls').appendChild(h);
}
</script> </script>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -102,8 +102,11 @@ class BasicWrapper { ...@@ -102,8 +102,11 @@ class BasicWrapper {
animate = { animate = {
modelIndex: { modelIndex: {
forward: (maxFPS = 8) => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, maxFPS | 0), direction: 'forward' }) }, maxFPS: 8,
backward: (maxFPS = 8) => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, maxFPS | 0), direction: 'backward' }) }, onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) },
onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) },
palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) },
loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) },
stop: () => this.plugin.state.animation.stop() stop: () => this.plugin.state.animation.stop()
} }
} }
......
...@@ -9,16 +9,20 @@ import { PluginStateObject } from '../objects'; ...@@ -9,16 +9,20 @@ import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms'; import { StateTransforms } from '../transforms';
import { StateSelection } from 'mol-state/state/selection'; import { StateSelection } from 'mol-state/state/selection';
import { PluginCommands } from 'mol-plugin/command'; import { PluginCommands } from 'mol-plugin/command';
import { ParamDefinition } from 'mol-util/param-definition'; import { ParamDefinition as PD } from 'mol-util/param-definition';
export const AnimateModelIndex = PluginStateAnimation.create({ export const AnimateModelIndex = PluginStateAnimation.create({
name: 'built-in.animate-model-index', name: 'built-in.animate-model-index',
display: { name: 'Animate Model Index' }, display: { name: 'Animate Model Index' },
params: () => ({ params: () => ({
direction: ParamDefinition.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]), mode: PD.MappedStatic('once', {
maxFPS: ParamDefinition.Numeric(3, { min: 0.5, max: 30, step: 0.5 }) once: PD.Group({ direction: PD.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]) }),
palindrome: PD.Group({ }),
loop: PD.Group({ }),
}, { options: [['once', 'Once'], ['palindrome', 'Palindrome'], ['loop', 'Loop']] }),
maxFPS: PD.Numeric(3, { min: 0.5, max: 30, step: 0.5 })
}), }),
initialState: () => ({ }), initialState: () => ({} as { palindromeDirections?: { [id: string]: -1 | 1 | undefined } }),
async apply(animState, t, ctx) { async apply(animState, t, ctx) {
// limit fps // limit fps
if (t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.maxFPS) { if (t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.maxFPS) {
...@@ -31,7 +35,9 @@ export const AnimateModelIndex = PluginStateAnimation.create({ ...@@ -31,7 +35,9 @@ export const AnimateModelIndex = PluginStateAnimation.create({
const update = state.build(); const update = state.build();
const dir = ctx.params.direction === 'backward' ? -1 : 1; const params = ctx.params;
const palindromeDirections = animState.palindromeDirections || { };
let isEnd = false;
for (const m of models) { for (const m of models) {
const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]); const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
...@@ -39,13 +45,35 @@ export const AnimateModelIndex = PluginStateAnimation.create({ ...@@ -39,13 +45,35 @@ export const AnimateModelIndex = PluginStateAnimation.create({
const traj = parent.obj as PluginStateObject.Molecule.Trajectory; const traj = parent.obj as PluginStateObject.Molecule.Trajectory;
update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory, update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
old => { old => {
let modelIndex = (old.modelIndex + dir) % traj.data.length; const len = traj.data.length;
if (modelIndex < 0) modelIndex += traj.data.length; 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 }; return { modelIndex };
}); });
} }
await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update }); await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update });
if (params.mode.name === 'once' && isEnd) return { kind: 'finished' };
if (params.mode.name === 'palindrome') return { kind: 'next', state: { palindromeDirections } };
return { kind: 'next', state: {} }; return { kind: 'next', state: {} };
} }
}) })
\ No newline at end of file
...@@ -76,7 +76,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat ...@@ -76,7 +76,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
this.register(animation); this.register(animation);
} }
this.updateParams({ current: animation.name }); this.updateParams({ current: animation.name });
this.updateParams(params); this.updateCurrentParams(params);
this.start(); this.start();
} }
...@@ -92,6 +92,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat ...@@ -92,6 +92,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
} }
stop() { stop() {
if (typeof this._frame !== 'undefined') cancelAnimationFrame(this._frame);
this.updateState({ animationState: 'stopped' }); this.updateState({ animationState: 'stopped' });
this.triggerUpdate(); this.triggerUpdate();
} }
...@@ -100,7 +101,10 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat ...@@ -100,7 +101,10 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
return this.latestState.animationState === 'playing'; return this.latestState.animationState === 'playing';
} }
private _frame: number | undefined = void 0;
private animate = async (t: number) => { private animate = async (t: number) => {
this._frame = void 0;
if (this._current.startedTime < 0) this._current.startedTime = t; if (this._current.startedTime < 0) this._current.startedTime = t;
const newState = await this._current.anim.apply( const newState = await this._current.anim.apply(
this._current.state, this._current.state,
...@@ -112,9 +116,9 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat ...@@ -112,9 +116,9 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
} else if (newState.kind === 'next') { } else if (newState.kind === 'next') {
this._current.state = newState.state; this._current.state = newState.state;
this._current.lastTime = t - this._current.startedTime; this._current.lastTime = t - this._current.startedTime;
if (this.latestState.animationState === 'playing') requestAnimationFrame(this.animate); if (this.latestState.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
} else if (newState.kind === 'skip') { } else if (newState.kind === 'skip') {
if (this.latestState.animationState === 'playing') requestAnimationFrame(this.animate); if (this.latestState.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
} }
} }
......
...@@ -29,8 +29,10 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent< ...@@ -29,8 +29,10 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent<
render() { render() {
const params = this.props.params; const params = this.props.params;
const values = this.props.values; const values = this.props.values;
const keys = Object.keys(params);
if (keys.length === 0) return null;
return <div style={{ width: '100%' }}> return <div style={{ width: '100%' }}>
{Object.keys(params).map(key => { {keys.map(key => {
const param = params[key]; const param = params[key];
if (param.isHidden) return null; if (param.isHidden) return null;
const Control = controlFor(param); const Control = controlFor(param);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment