diff --git a/src/mol-plugin/behavior/dynamic/animation.ts b/src/mol-plugin/behavior/dynamic/animation.ts index 9216f827a91a048cdf036c13a75b6c0ac203cdf5..62a77c0dc6e6b0ccf85eedf6f8f7f5b0faec210d 100644 --- a/src/mol-plugin/behavior/dynamic/animation.ts +++ b/src/mol-plugin/behavior/dynamic/animation.ts @@ -15,7 +15,9 @@ import { StateObjectCell, State } from 'mol-state'; const StructureAnimationParams = { rotate: PD.Boolean(false), - explode: 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> @@ -38,7 +40,11 @@ function getRootStructure(root: StateObjectCell, state: State) { return parent && parent.obj ? parent.obj as PluginStateObject.Molecule.Structure : undefined } -// TODO this is just for testing purposes +/** + * TODO + * - animation class is just for testing purposes, needs better API + * - allow per-unit transform `unitTransform: { [unitId: number]: Mat4 }` + */ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps>({ name: 'structure-animation', display: { name: 'Structure Animation', group: 'Animation' }, @@ -58,62 +64,68 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> this.update(params) } - rotate(play: boolean) { + rotate(rad: number) { + const state = this.ctx.state.dataState + const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.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) continue + + Vec3.negate(this.transVec, Vec3.copy(this.transVec, structure.data.boundary.sphere.center)) + Mat4.fromTranslation(this.transMat, this.transVec) + Mat4.mul(this.animMat, this.rotMat, this.transMat) + + Vec3.copy(this.transVec, structure.data.boundary.sphere.center) + Mat4.fromTranslation(this.transMat, this.transVec) + Mat4.mul(this.animMat, this.transMat, this.animMat) + + r.obj.data.setState({ transform: this.animMat }) + this.ctx.canvas3d.add(r.obj.data) + this.ctx.canvas3d.requestDraw(true) + } + } + + animateRotate(play: boolean) { if (play) { - const state = this.ctx.state.dataState - const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)); - const rotate = (t: number) => { - const rad = degToRad((t / 10) % 360) - 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) continue - - Vec3.negate(this.transVec, Vec3.copy(this.transVec, structure.data.boundary.sphere.center)) - Mat4.fromTranslation(this.transMat, this.transVec) - Mat4.mul(this.animMat, this.rotMat, this.transMat) - - Vec3.copy(this.transVec, structure.data.boundary.sphere.center) - Mat4.fromTranslation(this.transMat, this.transVec) - Mat4.mul(this.animMat, this.transMat, this.animMat) - - r.obj.data.setState({ transform: this.animMat }) - this.ctx.canvas3d.add(r.obj.data) - this.ctx.canvas3d.requestDraw(true) - } - this.rotateAnimHandle = requestAnimationFrame(rotate) + const animateRotate = (t: number) => { + this.rotate(degToRad((t / 10) % 360)) + this.rotateAnimHandle = requestAnimationFrame(animateRotate) } - this.rotateAnimHandle = requestAnimationFrame(rotate) + this.rotateAnimHandle = requestAnimationFrame(animateRotate) } else { cancelAnimationFrame(this.rotateAnimHandle) } } - explode(play: boolean) { + explode(d: number) { + const state = this.ctx.state.dataState + const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)); + for (const r of reprs) { + if (!SO.isRepresentation3D(r.obj)) return + const structure = getStructure(r, state) + if (!structure) continue + const rootStructure = getRootStructure(r, state) + if (!rootStructure) continue + + Vec3.sub(this.transVec, structure.data.boundary.sphere.center, rootStructure.data.boundary.sphere.center) + Vec3.setMagnitude(this.transVec, this.transVec, d) + Mat4.fromTranslation(this.animMat, this.transVec) + + r.obj.data.setState({ transform: this.animMat }) + this.ctx.canvas3d.add(r.obj.data) + this.ctx.canvas3d.requestDraw(true) + } + } + + animateExplode(play: boolean) { if (play) { - const state = this.ctx.state.dataState - const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)); - const explode = (t: number) => { - const d = (Math.sin(t * 0.001) + 1) * 5 - for (const r of reprs) { - if (!SO.isRepresentation3D(r.obj)) return - const structure = getStructure(r, state) - if (!structure) continue - const rootStructure = getRootStructure(r, state) - if (!rootStructure) continue - - Vec3.sub(this.transVec, structure.data.boundary.sphere.center, rootStructure.data.boundary.sphere.center) - Vec3.setMagnitude(this.transVec, this.transVec, d) - Mat4.fromTranslation(this.animMat, this.transVec) - - r.obj.data.setState({ transform: this.animMat }) - this.ctx.canvas3d.add(r.obj.data) - this.ctx.canvas3d.requestDraw(true) - } - this.explodeAnimHandle = requestAnimationFrame(explode) + const animateExplode = (t: number) => { + this.explode((Math.sin(t * 0.001) + 1) * 5) + this.explodeAnimHandle = requestAnimationFrame(animateExplode) } - this.explodeAnimHandle = requestAnimationFrame(explode) + this.explodeAnimHandle = requestAnimationFrame(animateExplode) } else { cancelAnimationFrame(this.explodeAnimHandle) } @@ -125,11 +137,19 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> let updated = PD.areEqual(StructureAnimationParams, this.params, p) if (this.params.rotate !== p.rotate) { this.params.rotate = p.rotate - this.rotate(this.params.rotate) + this.animateRotate(this.params.rotate) } if (this.params.explode !== p.explode) { this.params.explode = p.explode - this.explode(this.params.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; } diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index e0872207471cf72d6fd175c79f7c234965b5e5ec..ad6c9ebc6c105583ba435c90d6ac77d13a6fa063 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -37,7 +37,7 @@ const DefaultSpec: 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, explode: false }), + PluginSpec.Behavior(PluginBehaviors.Animation.StructureAnimation, { rotate: false, rotateValue: 0, explode: false, explodeValue: 0 }), PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true }), PluginSpec.Behavior(PluginBehaviors.CustomProps.RCSBAssemblySymmetry, { autoAttach: true }), ]