nglviewer / ngl

WebGL protein viewer
http://nglviewer.org/ngl/
MIT License
669 stars 170 forks source link

Superpose different viewers #921

Closed gbayarri closed 2 years ago

gbayarri commented 2 years ago

Hi,

Does anyone now if there is some way to superpose two different structures loaded in different viewers?

For example, if I have these two viewers loading similar structures, I would like to have them aligned one against the other:

Screenshot from 2022-04-06 11-54-23

Thanks.

fredludlow commented 2 years ago

There's an example of doing it in a single stage here: https://github.com/nglviewer/ngl/blob/master/examples/scripts/test/superposition.js

I don't think there's any particular reason why the two structures need to belong to the same stage, so you could adapt it to something like:


Promise.all([
  stageA.loadFile('data://sp-ido40.mol2').then(function (o) {
    o.addRepresentation('ball+stick')
    return o
  }),

  stageB.loadFile('data://sp-after.mol2').then(function (o) {
    o.addRepresentation('licorice')
    return o
  })
]).then(function (ol) {
  var sp = new NGL.Superposition(ol[0].structure, ol[1].structure)
  sp.transform(ol[0].structure)
  ol[0].updateRepresentations({ 'position': true })
  console.log(sp)
  ol[1].autoView()
})

Additionally, I guess you'd want to keep the two cameras in the sync. Ideally there'd be a signal indicating that the scene has changed - this could be added fairly easily. Without that, a quick way to do this would be to have an interval timer that checks the orientation of each stage m = stage.viewerControls.getOrientation(), tracks changes in each and applies changes stage.viewerControls.orient(m) from one to the other - would need a little bit of thought to work both ways and avoid race conditions etc but seems fairly straightforward.

gbayarri commented 2 years ago

Thanks, @fredludlow it works perfectly, though I had to add an autoView() for both of the components.

As you said, I've added the cameras sync, so the final code would be:

Promise.all([
  stageA.loadFile('data://sp-ido40.mol2').then(function (o) {
    o.addRepresentation('ball+stick')
    return o
  }),

  stageB.loadFile('data://sp-after.mol2').then(function (o) {
    o.addRepresentation('licorice')
    return o
  })
]).then(function (ol) {
  var sp = new NGL.Superposition(ol[0].structure, ol[1].structure)
  sp.transform(ol[0].structure)
  ol[0].updateRepresentations({ 'position': true })
  ol[0].autoView()
  ol[1].autoView()
  stageA.mouseObserver.signals.dragged.add( function(){ stageB.viewerControls.orient( stageA.viewerControls.getOrientation() ); } );    
  stageB.mouseObserver.signals.dragged.add( function(){ stageA.viewerControls.orient( stageB.viewerControls.getOrientation() ); } );
  stageA.mouseObserver.signals.scrolled.add( function(){ stageB.viewerControls.orient( stageA.viewerControls.getOrientation() ); } );
  stageB.mouseObserver.signals.scrolled.add( function(){ stageA.viewerControls.orient( stageB.viewerControls.getOrientation() ); } );
})