Commit feea46a1 authored by Andrei Sobolev's avatar Andrei Sobolev
Browse files

Merge branch 'bz-viewer-fixes' into 'master'

Structure viewer: InstancedMesh atom rendering + UX improvements

See merge request !68
parents cc88b039 b72a5dec
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ export default class BZViewOptionsDropDown extends DropDownMenu{
    this.REC_LATTICE_AXES_OPTION = 'show-lattice-vectors-parameters'

    this.addOption(this.REC_UNIT_CELL_OPTION, 'Show reciprocal cell', true)
    this.addOption(this.REC_CART_AXES_OPTION, 'Show Cartesian axes', true)
    this.addOption(this.REC_CART_AXES_OPTION, 'Show Cartesian axes', false)
    this.addOption(this.REC_LATTICE_AXES_OPTION, 'Show reciprocal axes', true)
  }

+8 −1
Original line number Diff line number Diff line
@@ -72,6 +72,12 @@ export default class BZViewer {
    transpose33(invert33(data)).forEach(v => {
      recCell.push(multiplyScalar(v,2.0 * Math.PI))
    })
    // Scale up small BZs (large supercells) so arrows stay proportional to the cell
    const minLen = Math.min(...recCell.map(v => Math.sqrt(v[0]**2 + v[1]**2 + v[2]**2)))
    if (minLen < 1.0) {
      const displayScale = 1.0 / minLen
      recCell = recCell.map(v => v.map(c => c * displayScale))
    }
    this.recCell = util.createBasisVectors(recCell)
    // console.log('this.recCell',this.recCell);
    this.root = new THREE.Group()
@@ -91,7 +97,7 @@ export default class BZViewer {
    // this.bz = new THREE.Geometry()
    this.latticeVectors = this.createLatticeVectors(this.recCell)
    this.recCartAxes = this.createRecCartAxes()
    // this.recCartAxes.visible = false
    this.recCartAxes.visible = false
    this.root.add(this.latticeVectors)
    this.root.add(this.recCartAxes)
    // this.bz_vertices = this.request_bz_vertices(recCell)
@@ -291,6 +297,7 @@ export default class BZViewer {
    // this.viewerControls.appendChild(this.heading)
    this.hostElement.appendChild(this.viewerControls);
    this.viewerControls.className = 'viewing-controls'
    this.viewerControls.addEventListener('pointerdown', e => e.stopPropagation())

    this.viewOptionsDropdown = new BZViewOptionsDropDown()//this)
    this.viewerControls.appendChild(this.viewOptionsDropdown.e)
+2 −2
Original line number Diff line number Diff line
@@ -164,7 +164,7 @@ export default class State{
   */
  repeatStructure(dimArray, setChange = false){  // multiply? structure
    let supercell = generateSupercell(dimArray, this.structure)
    if (supercell.atoms.length < 30000){
    if (supercell.atoms.length < 100000){
      if (setChange) this.change(CHANGE.STRUCTURE.SUPERCELL, undefined, [this.structure, supercell]).do()
      else this.setStructure(supercell, true)
      return true
@@ -181,7 +181,7 @@ export default class State{
 */
  async makeSlab(slabData, setChange = false){
    let slab = await generateSlab(slabData, this.structure)
    if (slab.atoms.length < 30000){
    if (slab.atoms.length < 100000){
      if (setChange) this.change(CHANGE.STRUCTURE.SURFACE, undefined, [this.structure, slab]).do()
      this.setStructure(slab, true)
      return true
+79 −89
Original line number Diff line number Diff line
@@ -30,22 +30,24 @@ export default class AtomSelector{
        this.camera = threeViewer.camera
        this.raycaster = new THREE.Raycaster()
        // These are set up externally when the Viewer is initilizated with data
        this.atoms // atom meshes
        this.atoms // atom group
        this.atomMeshes // InstancedMesh array for raycasting
        this.structure // structure data

        // Hovering related variables
        this.hoverAtom = null
        this.hoverAtomIndex = -1
        this.keypressed = null
        // Selection related variables
        this.selAtoms = []
        this.selAtomIndices = []

        // Dragging related variables
        this.draggingEnabled = false
        this.dragging = false // The dragging is happening at the moment
        this.dragStart = null
        this.draggingAtomStartPos = null
        this.draggingAtom = null
        this.draggingAtomIndex = null
        this.draggingAtomIndex = -1
        this.draggingMesh = null
        this.draggingInstanceIdx = -1

        const hostElement = threeViewer.hostElement

@@ -73,7 +75,11 @@ export default class AtomSelector{
                inc.sub(this.camera.position) // The position of the camaera has to be substructed
                let newPos = inc.clone().add(this.draggingAtomStartPos)
                //console.log('mousemove', this.camera.position, inc, newPos )
                this.draggingAtom.position.copy(newPos)
                const m = new THREE.Matrix4()
                this.draggingMesh.getMatrixAt(this.draggingInstanceIdx, m)
                m.setPosition(newPos)
                this.draggingMesh.setMatrixAt(this.draggingInstanceIdx, m)
                this.draggingMesh.instanceMatrix.needsUpdate = true
                this.viewer.render()
                // The selected atom info area is cleared while displacing
                this.infoCanvas.clearSelectedAtomArea() // this.viewer.infoCanvas.clearSelectedAtomArea()
@@ -89,7 +95,7 @@ export default class AtomSelector{
        })

        canvas.addEventListener('pointerdown', e => {
            if (this.draggingEnabled && this.hoverAtom !== null && this.keypressed === null){
            if (this.draggingEnabled && this.hoverAtomIndex >= 0 && this.keypressed === null){
                // Claim capture on the overlay canvas so pointermove/pointerup still bubble
                // to hostElement while keeping TrackballControls from setting its own capture
                // (which would redirect the click event away from this canvas).
@@ -97,64 +103,79 @@ export default class AtomSelector{
                canvas.setPointerCapture(e.pointerId)
                this.dragging = true
                this.dragStart = this.mouse.clone()
                this.draggingAtomStartPos = this.hoverAtom.position.clone()
                this.draggingAtom = this.hoverAtom
                const { mesh, instanceIdx } = this.structureViewer.atomLookup[this.hoverAtomIndex]
                this.draggingMesh = mesh
                this.draggingInstanceIdx = instanceIdx
                const m = new THREE.Matrix4()
                mesh.getMatrixAt(instanceIdx, m)
                this.draggingAtomStartPos = new THREE.Vector3().setFromMatrixPosition(m)
                this.draggingAtomIndex = this.hoverAtomIndex
                this.viewer.controls.noRotate = true
            }
        }, false)

        // Atoms selection
        hostElement.addEventListener('click', e => {
            console.log('click', this.hoverAtom, e)
            // SK: Add atom removal
            if (this.draggingEnabled && this.keypressed === 68 && this.hoverAtom) {
                this.moduleState.removeAtom(this.atoms.children.indexOf(this.hoverAtom))
                this.hoverAtom = null
            if (this.draggingEnabled && this.keypressed === 68 && this.hoverAtomIndex >= 0) {
                this.moduleState.removeAtom(this.hoverAtomIndex)
                this.hoverAtomIndex = -1
            }
            if (this.hoverAtom !== null) {
                // Shift key is needed to select atoms
                if (e.shiftKey && this.selAtoms.indexOf(this.hoverAtom) < 0) {
                    this.selAtoms.push(this.hoverAtom)
                    this.highlightAtomMesh(this.hoverAtom, true)

            if (this.hoverAtomIndex < 0) {
                this._clearSelection()
                return
            }

            if (e.shiftKey) {
                if (!this.selAtomIndices.includes(this.hoverAtomIndex)) {
                    this.selAtomIndices.push(this.hoverAtomIndex)
                    this.highlightAtom(this.hoverAtomIndex, true)
                }
            } else {
                this._clearSelection()
            }

            this.infoCanvas.clearDATAtomsArea()

                if (this.selAtoms.length >= 2 && this.selAtoms.length <= 4){
                if (this.selAtomIndices.length >= 2 && this.selAtomIndices.length <= 4){
                    const atoms = this.structure.atoms
                    const p1Index = this.atoms.children.indexOf(this.selAtoms[0])
                    const p2Index = this.atoms.children.indexOf(this.selAtoms[1])
                    const p1Index = this.selAtomIndices[0]
                    const p2Index = this.selAtomIndices[1]
                    const p1 = atoms[p1Index].position
                    const p2 = atoms[p2Index].position
                    if (this.selAtoms.length === 2){
                    if (this.selAtomIndices.length === 2){
                        let d = getDistance(p1, p2)
                        this.infoCanvas.showDATAtomsInfo(
                            'Distance (atom#'+(p1Index+1)+' - atom#'+(p2Index+1)+'): '+d.toFixed(5)+'Å')
                    } else if (this.selAtoms.length === 3){
                        const p3Index = this.atoms.children.indexOf(this.selAtoms[2])
                    } else if (this.selAtomIndices.length === 3){
                        const p3Index = this.selAtomIndices[2]
                        const p3 = atoms[p3Index].position
                        this.infoCanvas.showDATAtomsInfo(
                            'Angle (atom#'+(p1Index+1)+' - atom#'+(p2Index+1)+' - atom#'+(p3Index+1)+'): '+getAngle(p1, p2, p3).toFixed(5)+'º')
                    } else if (this.selAtoms.length === 4){
                        const p3Index = this.atoms.children.indexOf(this.selAtoms[2])
                    } else if (this.selAtomIndices.length === 4){
                        const p3Index = this.selAtomIndices[2]
                        const p3 = atoms[p3Index].position
                        const p4Index = this.atoms.children.indexOf(this.selAtoms[3])
                        const p4Index = this.selAtomIndices[3]
                        const p4 = atoms[p4Index].position
                        this.infoCanvas.showDATAtomsInfo(
                            'Torsion angle (atoms: #'+(p1Index+1)+' - #'+(p2Index+1)+' - #'+(p3Index+1)+' - #'+(p4Index+1)+'): '+getTorsionAngle(p1, p2, p3, p4).toFixed(5)+'º')
                    }
                }
            }
        })

        hostElement.addEventListener('pointerup', e => {
            if (this.draggingEnabled && this.dragging){
                this.dragging = false
                this.dragStart = null
                const atomIndex = this.atoms.children.indexOf(this.draggingAtom)
                //this.atomDisplacementListener(atomIndex, this.draggingAtom.position)
                this.moduleState.updateAtomPosition(atomIndex, this.draggingAtom.position.toArray(), false)
                this.draggingAtom = null
                const atomIndex = this.draggingAtomIndex
                const m = new THREE.Matrix4()
                this.draggingMesh.getMatrixAt(this.draggingInstanceIdx, m)
                const pos = new THREE.Vector3().setFromMatrixPosition(m)
                this.moduleState.updateAtomPosition(atomIndex, pos.toArray(), false)
                this.draggingAtomIndex = -1
                this.draggingMesh = null
                this.draggingInstanceIdx = -1
                this.viewer.controls.noRotate = false
                // The selected atom info area is shown again with the new position data
                this.atomHitListener(atomIndex) // this.atomReleaseListener(atomIndex)
@@ -178,45 +199,25 @@ export default class AtomSelector{
     */
    checkIntersection(){
        let change = false
        if (this.atoms !== undefined ){
            // update the picking ray with the camera and mouse position
            // convenience method to set the raycaster origin and direction
            this.raycaster.setFromCamera( this.mouse, this.camera ) // this.raycaster.ray

            // calculate objects intersecting the picking ray
            let intersects = this.raycaster.intersectObjects( this.atoms.children );
            //if (intersects.length > 0) console.log('intersects', intersects,this.raycaster)// atoms.children)

            // If the mouse pointer is on one (or more) atoms &&
            // that atom (the first) is different from the previous one (continuous hovering)
            if (intersects.length > 0 && this.hoverAtom !== intersects[0].object){

                // if (this.hoverAtom !== null)
                //     this.highlightAtomMesh(this.hoverAtom, false)//changeColor(this.hoverAtom, 0.3)

                this.hoverAtom = intersects[0].object

        if (this.atomMeshes !== undefined){
            this.raycaster.setFromCamera(this.mouse, this.camera)
            const intersects = this.raycaster.intersectObjects(this.atomMeshes)

            if (intersects.length > 0) {
                const hit = intersects[0]
                const globalIndex = hit.object.userData.globalIndices[hit.instanceId]
                if (this.hoverAtomIndex !== globalIndex) {
                    this.hoverAtomIndex = globalIndex
                    change = true
                //console.log('intersects', )
                const atomIndex = this.atoms.children.indexOf(this.hoverAtom)

                // this.highlightAtomMesh(this.hoverAtom, true)//changeColor(this.hoverAtom, -0.3)
                //this.viewer.infoCanvas.showAtomInfo()
                this.atomHitListener(atomIndex)

            // If no mouse pointer is on an atom && there was a previous hovered, this is set to normal
            }else if (intersects.length === 0 && this.hoverAtom !== null){
                // this.highlightAtomMesh(this.hoverAtom, false)//changeColor(this.hoverAtom, 0.3)
                this.hoverAtom = null
                    this.atomHitListener(this.hoverAtomIndex)
                }
            } else if (intersects.length === 0 && this.hoverAtomIndex >= 0){
                this.hoverAtomIndex = -1
                change = true
                this.infoCanvas.clearSelectedAtomArea()
            }
            // Two more circunstances
            // No mouse pointer on an atom && no atom hovered in the previous step -> Do nothing
            // Mouse pointer on an atom && that atom is the previously hovered -> Do nothing
        }
        return change

    }


@@ -231,37 +232,26 @@ export default class AtomSelector{


    /**
     * Highligths an atom mesh
     * @param  {object} atom Atom object to highlight
     * @param  {boolean} on
     *   If true the atom is highlighted if false it's toned-down
     * Highlights an atom by index, delegating to the viewer's instanced highlight.
     * Ignores turn-off requests for atoms that are part of the active selection.
     * @param  {int} atomIndex Index of atom to highlight
     * @param  {boolean} on If true the atom is highlighted; if false it's restored
     */
    highlightAtomMesh(atom, on){
        const atomIndex = this.atoms.children.indexOf(atom)
    highlightAtom(atomIndex, on){
        if (atomIndex < 0) return
        else this.highlightAtom(atomIndex, on)
        if (!on && this.selAtomIndices.includes(atomIndex)) return
        this.structureViewer.highlightAtom(atomIndex, on)
    }


    /**
     * Highligths an atom mesh
     * @param  {int} atomIndex Index of atom to highlight
     * @param  {boolean} on
     *   If true the atom is highlighted if false it's toned-down
     * Clears the current atom selection, restoring all highlighted colors,
     * and clears the measurement info area.
     */
    highlightAtom(atomIndex, on){
        //console.log('highlightAtom', this.atoms,atomIndex, on)
        const atom = this.atoms.children[atomIndex]

        if (atom === undefined || this.selAtoms.indexOf(atom) >= 0)
            return

        let color = new THREE.Color(
            Conf.getSpeciesColor(this.structure.atoms[atomIndex].species))// State.getStructure().atoms[atomIndex].species))
        if (on) { color.r -= 0.4; color.g -= 0.4; color.b -= 0.4 }

        atom.material.color = color
        this.viewer.render()
    _clearSelection(){
        this.selAtomIndices.forEach(i => this.structureViewer.highlightAtom(i, false))
        this.selAtomIndices = []
        this.infoCanvas.clearDATAtomsArea()
    }

}
+152 −83

File changed.

Preview size limit exceeded, changes collapsed.

Loading