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

mol-plugin: focus camera on loci select behavior

parent cc20355f
No related branches found
No related tags found
No related merge requests found
...@@ -82,6 +82,13 @@ class Camera implements Object3D { ...@@ -82,6 +82,13 @@ class Camera implements Object3D {
return ret; return ret;
} }
focus(target: Vec3, radius: number) {
const position = Vec3.zero();
Vec3.scale(position, this.state.direction, -radius);
Vec3.add(position, position, target);
this.setState({ target, position });
}
// lookAt(target: Vec3) { // lookAt(target: Vec3) {
// cameraLookAt(this.position, this.up, this.direction, target); // cameraLookAt(this.position, this.up, this.direction, target);
// } // }
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from 'mol-math/linear-algebra/3d';
export class CentroidHelper {
private count = 0;
center: Vec3 = Vec3.zero();
radiusSq = 0;
reset() {
Vec3.set(this.center, 0, 0, 0);
this.radiusSq = 0;
this.count = 0;
}
includeStep(p: Vec3) {
Vec3.add(this.center, this.center, p);
this.count++;
}
finishedIncludeStep() {
if (this.count === 0) return;
Vec3.scale(this.center, this.center, 1 / this.count);
}
radiusStep(p: Vec3) {
const d = Vec3.squaredDistance(p, this.center);
if (d > this.radiusSq) this.radiusSq = d;
}
constructor() {
}
}
\ No newline at end of file
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
import { StructureElement } from './structure' import { StructureElement } from './structure'
import { Link } from './structure/structure/unit/links' import { Link } from './structure/structure/unit/links'
import { Shape } from './shape'; import { Shape } from './shape';
import { Sphere3D } from 'mol-math/geometry';
import { CentroidHelper } from 'mol-math/geometry/centroid-helper';
import { Vec3 } from 'mol-math/linear-algebra';
import { OrderedSet } from 'mol-data/int';
/** A Loci that includes every loci */ /** A Loci that includes every loci */
export const EveryLoci = { kind: 'every-loci' as 'every-loci' } export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
...@@ -37,4 +41,62 @@ export function areLociEqual(lociA: Loci, lociB: Loci) { ...@@ -37,4 +41,62 @@ export function areLociEqual(lociA: Loci, lociB: Loci) {
return false return false
} }
export type Loci = StructureElement.Loci | Link.Loci | EveryLoci | EmptyLoci | Shape.Loci
\ No newline at end of file export { Loci }
type Loci = StructureElement.Loci | Link.Loci | EveryLoci | EmptyLoci | Shape.Loci
namespace Loci {
const sphereHelper = new CentroidHelper(), tempPos = Vec3.zero();
export function getBoundingSphere(loci: Loci): Sphere3D | undefined {
if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
sphereHelper.reset();
if (loci.kind === 'element-loci') {
for (const e of loci.elements) {
const { indices } = e;
const pos = e.unit.conformation.position;
const { elements } = e.unit;
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
pos(elements[OrderedSet.getAt(indices, i)], tempPos);
sphereHelper.includeStep(tempPos);
}
}
sphereHelper.finishedIncludeStep();
for (const e of loci.elements) {
const { indices } = e;
const pos = e.unit.conformation.position;
const { elements } = e.unit;
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
pos(elements[OrderedSet.getAt(indices, i)], tempPos);
sphereHelper.radiusStep(tempPos);
}
}
} else if (loci.kind === 'link-loci') {
for (const e of loci.links) {
let pos = e.aUnit.conformation.position;
pos(e.aUnit.elements[e.aIndex], tempPos);
sphereHelper.includeStep(tempPos);
pos = e.bUnit.conformation.position;
pos(e.bUnit.elements[e.bIndex], tempPos);
sphereHelper.includeStep(tempPos);
}
sphereHelper.finishedIncludeStep();
for (const e of loci.links) {
let pos = e.aUnit.conformation.position;
pos(e.aUnit.elements[e.aIndex], tempPos);
sphereHelper.radiusStep(tempPos);
pos = e.bUnit.conformation.position;
pos(e.bUnit.elements[e.bIndex], tempPos);
sphereHelper.radiusStep(tempPos);
}
} else if (loci.kind === 'group-loci') {
// TODO
return void 0;
}
return Sphere3D.create(Vec3.clone(sphereHelper.center), Math.sqrt(sphereHelper.radiusSq));
}
}
\ No newline at end of file
...@@ -62,20 +62,20 @@ export function residueLabel(model: Model, rI: number) { ...@@ -62,20 +62,20 @@ export function residueLabel(model: Model, rI: number) {
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}` return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
} }
const centerPos = Vec3.zero() // const centerPos = Vec3.zero()
const centerMin = Vec3.zero() // const centerMin = Vec3.zero()
export function getCenterAndRadius(centroid: Vec3, unit: Unit, indices: ArrayLike<number>) { // export function getCenterAndRadius(centroid: Vec3, unit: Unit, indices: ArrayLike<number>) {
const pos = unit.conformation.position // const pos = unit.conformation.position
const { elements } = unit // const { elements } = unit
Vec3.set(centroid, 0, 0, 0) // Vec3.set(centroid, 0, 0, 0)
for (let i = 0, il = indices.length; i < il; ++i) { // for (let i = 0, il = indices.length; i < il; ++i) {
pos(elements[indices[i]], centerPos) // pos(elements[indices[i]], centerPos)
Vec3.add(centroid, centroid, centerPos) // Vec3.add(centroid, centroid, centerPos)
Vec3.min(centerMin, centerMin, centerPos) // Vec3.min(centerMin, centerMin, centerPos)
} // }
Vec3.scale(centroid, centroid, 1/indices.length) // Vec3.scale(centroid, centroid, 1/indices.length)
return Vec3.distance(centerMin, centroid) // return Vec3.distance(centerMin, centroid)
} // }
const matrixPos = Vec3.zero() const matrixPos = Vec3.zero()
export function getPositionMatrix(unit: Unit, indices: ArrayLike<number>) { export function getPositionMatrix(unit: Unit, indices: ArrayLike<number>) {
......
...@@ -11,6 +11,7 @@ import * as StaticRepresentation from './behavior/static/representation' ...@@ -11,6 +11,7 @@ import * as StaticRepresentation from './behavior/static/representation'
import * as StaticCamera from './behavior/static/camera' import * as StaticCamera from './behavior/static/camera'
import * as DynamicRepresentation from './behavior/dynamic/representation' import * as DynamicRepresentation from './behavior/dynamic/representation'
import * as DynamicCamera from './behavior/dynamic/camera'
export const BuiltInPluginBehaviors = { export const BuiltInPluginBehaviors = {
State: StaticState, State: StaticState,
...@@ -19,5 +20,6 @@ export const BuiltInPluginBehaviors = { ...@@ -19,5 +20,6 @@ export const BuiltInPluginBehaviors = {
} }
export const PluginBehaviors = { export const PluginBehaviors = {
Representation: DynamicRepresentation Representation: DynamicRepresentation,
Camera: DynamicCamera,
} }
\ No newline at end of file
...@@ -11,6 +11,7 @@ import { PluginContext } from 'mol-plugin/context'; ...@@ -11,6 +11,7 @@ import { PluginContext } from 'mol-plugin/context';
import { PluginCommand } from '../command'; import { PluginCommand } from '../command';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ParamDefinition } from 'mol-util/param-definition'; import { ParamDefinition } from 'mol-util/param-definition';
import { shallowEqual } from 'mol-util';
export { PluginBehavior } export { PluginBehavior }
...@@ -26,7 +27,7 @@ namespace PluginBehavior { ...@@ -26,7 +27,7 @@ namespace PluginBehavior {
export class Root extends PluginStateObject.Create({ name: 'Root', typeClass: 'Root' }) { } export class Root extends PluginStateObject.Create({ name: 'Root', typeClass: 'Root' }) { }
export class Behavior extends PluginStateObject.CreateBehavior<PluginBehavior>({ name: 'Behavior' }) { } export class Behavior extends PluginStateObject.CreateBehavior<PluginBehavior>({ name: 'Behavior' }) { }
export interface Ctor<P = undefined> { new(ctx: PluginContext, params?: P): PluginBehavior<P> } export interface Ctor<P = undefined> { new(ctx: PluginContext, params: P): PluginBehavior<P> }
export interface CreateParams<P> { export interface CreateParams<P> {
name: string, name: string,
...@@ -63,7 +64,7 @@ namespace PluginBehavior { ...@@ -63,7 +64,7 @@ namespace PluginBehavior {
} }
export function simpleCommandHandler<T>(cmd: PluginCommand<T>, action: (data: T, ctx: PluginContext) => void | Promise<void>) { export function simpleCommandHandler<T>(cmd: PluginCommand<T>, action: (data: T, ctx: PluginContext) => void | Promise<void>) {
return class implements PluginBehavior<undefined> { return class implements PluginBehavior<{}> {
private sub: PluginCommand.Subscription | undefined = void 0; private sub: PluginCommand.Subscription | undefined = void 0;
register(): void { register(): void {
this.sub = cmd.subscribe(this.ctx, data => action(data, this.ctx)); this.sub = cmd.subscribe(this.ctx, data => action(data, this.ctx));
...@@ -76,7 +77,7 @@ namespace PluginBehavior { ...@@ -76,7 +77,7 @@ namespace PluginBehavior {
} }
} }
export abstract class Handler implements PluginBehavior<undefined> { export abstract class Handler<P = { }> implements PluginBehavior<P> {
private subs: PluginCommand.Subscription[] = []; private subs: PluginCommand.Subscription[] = [];
protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) { protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) {
this.subs.push(cmd.subscribe(this.ctx, action)); this.subs.push(cmd.subscribe(this.ctx, action));
...@@ -92,8 +93,12 @@ namespace PluginBehavior { ...@@ -92,8 +93,12 @@ namespace PluginBehavior {
for (const s of this.subs) s.unsubscribe(); for (const s of this.subs) s.unsubscribe();
this.subs = []; this.subs = [];
} }
constructor(protected ctx: PluginContext) { update(params: P): boolean {
if (shallowEqual(params, this.params)) return false;
this.params = params;
return true;
}
constructor(protected ctx: PluginContext, protected params: P) {
} }
} }
} }
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Loci } from 'mol-model/loci';
import { ParamDefinition } from 'mol-util/param-definition';
import { PluginBehavior } from '../behavior';
export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extraRadius: number }>({
name: 'focus-loci-on-select',
ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number }> {
register(): void {
this.subscribeObservable(this.ctx.behaviors.canvas.selectLoci, current => {
if (!this.ctx.canvas3d) return;
const sphere = Loci.getBoundingSphere(current.loci);
if (!sphere) return;
this.ctx.canvas3d.camera.focus(sphere.center, Math.max(sphere.radius + this.params.extraRadius, this.params.minRadius));
});
}
},
params: () => ({
minRadius: ParamDefinition.Numeric(10, { min: 1, max: 50, step: 1 }),
extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the boundning sphere radius of the Loci.' })
}),
display: { name: 'Focus Loci on Select', group: 'Camera' }
});
\ No newline at end of file
...@@ -34,9 +34,18 @@ export const SelectLoci = PluginBehavior.create({ ...@@ -34,9 +34,18 @@ export const SelectLoci = PluginBehavior.create({
name: 'representation-select-loci', name: 'representation-select-loci',
ctor: class extends PluginBehavior.Handler { ctor: class extends PluginBehavior.Handler {
register(): void { register(): void {
this.subscribeObservable(this.ctx.behaviors.canvas.selectLoci, ({ loci }) => { let prevLoci: Loci = EmptyLoci, prevRepr: any = void 0;
this.subscribeObservable(this.ctx.behaviors.canvas.selectLoci, current => {
if (!this.ctx.canvas3d) return; if (!this.ctx.canvas3d) return;
this.ctx.canvas3d.mark(loci, MarkerAction.Toggle); if (current.repr !== prevRepr || !areLociEqual(current.loci, prevLoci)) {
this.ctx.canvas3d.mark(prevLoci, MarkerAction.Deselect);
this.ctx.canvas3d.mark(current.loci, MarkerAction.Select);
prevLoci = current.loci;
prevRepr = current.repr;
} else {
this.ctx.canvas3d.mark(current.loci, MarkerAction.Toggle);
}
// this.ctx.canvas3d.mark(loci, MarkerAction.Toggle);
}); });
} }
}, },
......
...@@ -33,7 +33,8 @@ const DefaultSpec: PluginSpec = { ...@@ -33,7 +33,8 @@ const DefaultSpec: PluginSpec = {
behaviors: [ behaviors: [
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci), PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci), PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider) PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 20, extraRadius: 4 })
] ]
} }
......
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