Raw File
view.rs
/*
ENSnano, a 3d graphical application for DNA nanostructures.
    Copyright (C) 2021  Nicolas Levy <nicolaspierrelevy@gmail.com> and Nicolas Schabanel <nicolas.schabanel@ens-lyon.fr>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
use super::data::{
    FlatTorsion, FreeEnd, GpuVertex, Helix, HelixModel, Shift, Strand, StrandVertex,
};
use super::{CameraPtr, FlatIdx, FlatNucl};
use crate::utils::bindgroup_manager::{DynamicBindGroup, UniformBindGroup};
use crate::utils::texture::Texture;
use crate::utils::Ndc;
use crate::{DrawArea, PhySize};
use iced_wgpu::wgpu;
use std::collections::{BTreeSet, HashMap};
use std::rc::Rc;
use wgpu::{Device, Queue, RenderPipeline};

mod helix_view;
use helix_view::{HelixView, StrandView};
mod background;
mod insertion;
mod rectangle;
use super::FlatSelection;
use crate::consts::SAMPLE_COUNT;
use crate::utils::{chars2d as chars, circles2d as circles};
use background::Background;
use chars::CharDrawer;
pub use chars::CharInstance;
pub use circles::CircleInstance;
use circles::{CircleDrawer, CircleKind};
use iced_winit::winit::dpi::PhysicalPosition;
use insertion::InsertionDrawer;
pub use insertion::InsertionInstance;
use rectangle::Rectangle;

const SHOW_SUGGESTION: bool = false;

pub struct View {
    device: Rc<Device>,
    queue: Rc<Queue>,
    depth_texture: Texture,
    helices: Vec<Helix>,
    helices_view: Vec<HelixView>,
    helices_background: Vec<HelixView>,
    strands: Vec<StrandView>,
    pasted_strands: Vec<StrandView>,
    helices_model: Vec<HelixModel>,
    models: DynamicBindGroup,
    globals_top: UniformBindGroup,
    globals_bottom: UniformBindGroup,
    helices_pipeline: RenderPipeline,
    strand_pipeline: RenderPipeline,
    camera_top: CameraPtr,
    camera_bottom: CameraPtr,
    splited: bool,
    was_updated: bool,
    area_size: PhySize,
    free_end: Option<FreeEnd>,
    background: Background,
    circle_drawer_top: CircleDrawer,
    circle_drawer_bottom: CircleDrawer,
    rotation_widget: CircleDrawer,
    insertion_drawer: InsertionDrawer,
    char_drawers_top: HashMap<char, CharDrawer>,
    char_drawers_bottom: HashMap<char, CharDrawer>,
    char_map_top: HashMap<char, Vec<CharInstance>>,
    char_map_bottom: HashMap<char, Vec<CharInstance>>,
    selection: FlatSelection,
    show_sec: bool,
    suggestions: Vec<(FlatNucl, FlatNucl)>,
    suggestions_view: Vec<StrandView>,
    selected_strands: Vec<StrandView>,
    candidate_strands: Vec<StrandView>,
    selected_helices: Vec<FlatIdx>,
    candidate_helices: Vec<FlatIdx>,
    suggestion_candidate: Option<(FlatNucl, FlatNucl)>,
    torsions: HashMap<(FlatNucl, FlatNucl), FlatTorsion>,
    show_torsion: bool,
    rectangle: Rectangle,
}

impl View {
    pub(super) fn new(
        device: Rc<Device>,
        queue: Rc<Queue>,
        area: DrawArea,
        camera_top: CameraPtr,
        camera_bottom: CameraPtr,
        splited: bool,
    ) -> Self {
        let depth_texture =
            Texture::create_depth_texture(device.as_ref(), &area.size, SAMPLE_COUNT);
        let models = DynamicBindGroup::new(device.clone(), queue.clone());
        let globals_top = UniformBindGroup::new(
            device.clone(),
            queue.clone(),
            camera_top.borrow().get_globals(),
        );
        let globals_bottom = UniformBindGroup::new(
            device.clone(),
            queue.clone(),
            camera_bottom.borrow().get_globals(),
        );

        let depth_stencil_state = Some(wgpu::DepthStencilState {
            format: wgpu::TextureFormat::Depth32Float,
            depth_write_enabled: true,
            depth_compare: wgpu::CompareFunction::Less,
            stencil: wgpu::StencilState {
                front: wgpu::StencilFaceState::IGNORE,
                back: wgpu::StencilFaceState::IGNORE,
                read_mask: 0,
                write_mask: 0,
            },
            bias: Default::default(),
            clamp_depth: false,
        });

        let helices_pipeline = helices_pipeline_descr(
            &device,
            globals_top.get_layout(), // the layout is the same for both globals
            models.get_layout(),
            depth_stencil_state.clone(),
        );
        let strand_pipeline = strand_pipeline_descr(
            &device,
            globals_top.get_layout(),
            depth_stencil_state.clone(),
        );

        let background = Background::new(&device, globals_top.get_layout(), &depth_stencil_state);
        let circle_drawer_top = CircleDrawer::new(
            device.clone(),
            queue.clone(),
            globals_top.get_layout(),
            CircleKind::FullCircle,
        );
        let circle_drawer_bottom = CircleDrawer::new(
            device.clone(),
            queue.clone(),
            globals_top.get_layout(),
            CircleKind::FullCircle,
        );
        let rotation_widget = CircleDrawer::new(
            device.clone(),
            queue.clone(),
            globals_top.get_layout(),
            CircleKind::RotationWidget,
        );
        let rectangle = Rectangle::new(&device, queue.clone());
        let chars = [
            'A', 'T', 'G', 'C', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-',
        ];
        let mut char_drawers_top = HashMap::new();
        let mut char_map_top = HashMap::new();
        let mut char_drawers_bottom = HashMap::new();
        let mut char_map_bottom = HashMap::new();
        for c in chars.iter() {
            char_drawers_top.insert(
                *c,
                CharDrawer::new(device.clone(), queue.clone(), globals_top.get_layout(), *c),
            );
            char_drawers_bottom.insert(
                *c,
                CharDrawer::new(device.clone(), queue.clone(), globals_top.get_layout(), *c),
            );
            char_map_top.insert(*c, Vec::new());
            char_map_bottom.insert(*c, Vec::new());
        }

        let insertion_drawer = InsertionDrawer::new(
            device.clone(),
            queue.clone(),
            globals_top.get_layout(),
            depth_stencil_state.clone(),
        );

        Self {
            device,
            queue,
            depth_texture,
            helices: Vec::new(),
            helices_view: Vec::new(),
            strands: Vec::new(),
            pasted_strands: Vec::new(),
            helices_model: Vec::new(),
            helices_background: Vec::new(),
            models,
            globals_top,
            globals_bottom,
            helices_pipeline,
            strand_pipeline,
            camera_top,
            camera_bottom,
            splited,
            was_updated: false,
            area_size: area.size,
            free_end: None,
            background,
            circle_drawer_top,
            circle_drawer_bottom,
            rotation_widget,
            char_drawers_top,
            char_map_top,
            char_drawers_bottom,
            char_map_bottom,
            selection: FlatSelection::Nothing,
            show_sec: false,
            suggestions: vec![],
            suggestions_view: vec![],
            selected_strands: vec![],
            candidate_strands: vec![],
            selected_helices: vec![],
            candidate_helices: vec![],
            suggestion_candidate: None,
            torsions: HashMap::new(),
            show_torsion: false,
            rectangle,
            insertion_drawer,
        }
    }

    pub fn set_show_sec(&mut self, show_sec: bool) {
        self.show_sec = show_sec;
        self.was_updated = true;
    }

    pub fn set_show_torsion(&mut self, show: bool) {
        self.show_torsion = show;
        self.was_updated = true;
    }

    pub fn set_splited(&mut self, splited: bool) {
        self.was_updated = true;
        self.splited = splited;
    }

    pub fn resize(&mut self, area: DrawArea) {
        self.depth_texture =
            Texture::create_depth_texture(self.device.clone().as_ref(), &area.size, SAMPLE_COUNT);
        self.area_size = area.size;
        self.was_updated = true;
    }

    fn add_helix(&mut self, helix: &Helix) {
        let id_helix = self.helices_view.len() as u32;
        self.helices_view.push(HelixView::new(
            self.device.clone(),
            self.queue.clone(),
            false,
        ));
        self.helices_background.push(HelixView::new(
            self.device.clone(),
            self.queue.clone(),
            true,
        ));
        self.helices_view[id_helix as usize].update(&helix);
        self.helices_background[id_helix as usize].update(&helix);
        self.helices_model.push(helix.model());
        self.models.update(self.helices_model.as_slice());
    }

    pub fn rm_helices(&mut self, helices: BTreeSet<FlatIdx>) {
        if self.helices.len() == 0 {
            // self was already reseted
            return;
        }
        for h in helices.iter().rev() {
            self.helices.remove(h.0);
            self.helices_background.remove(h.0);
            self.helices_view.remove(h.0);
            self.helices_model.remove(h.0);
        }
    }

    pub fn set_suggestions(&mut self, suggestions: Vec<(FlatNucl, FlatNucl)>) {
        self.suggestions = suggestions;
    }

    pub fn set_torsions(&mut self, torsions: HashMap<(FlatNucl, FlatNucl), FlatTorsion>) {
        self.torsions = torsions
    }

    pub fn update_helices(&mut self, helices: &[Helix]) {
        for (i, h) in self.helices_view.iter_mut().enumerate() {
            self.helices_model[i] = helices[i].model();
            self.helices_background[i].update(&helices[i]);
            h.update(&helices[i])
        }
        for helix in helices.iter().skip(self.helices_view.len()) {
            self.add_helix(helix)
        }
        self.models.update(self.helices_model.as_slice());
        self.helices = helices.to_vec();
        self.was_updated = true;
    }

    pub fn add_strand(&mut self, strand: &Strand, helices: &[Helix]) {
        self.strands
            .push(StrandView::new(self.device.clone(), self.queue.clone()));
        let other_cam = if self.splited {
            &self.camera_bottom
        } else {
            &self.camera_top
        };
        self.strands.iter_mut().last().unwrap().update(
            &strand,
            helices,
            &self.free_end,
            &self.camera_top,
            other_cam,
        );
    }

    pub fn reset(&mut self) {
        self.helices.clear();
        self.helices_model.clear();
        self.helices_view.clear();
        self.strands.clear();
        self.helices_background.clear();
    }

    pub fn update_strands(&mut self, strands: &[Strand], helices: &[Helix]) {
        self.strands.truncate(strands.len());
        for (i, s) in self.strands.iter_mut().enumerate() {
            let other_cam = if self.splited {
                &self.camera_bottom
            } else {
                &self.camera_top
            };
            if i < strands.len() {
                s.update(
                    &strands[i],
                    helices,
                    &self.free_end,
                    &self.camera_top,
                    other_cam,
                );
            }
        }
        for strand in strands.iter().skip(self.strands.len()) {
            self.add_strand(strand, helices)
        }
        let mut insertions = Vec::new();
        for s in strands.iter() {
            for i in s.get_insertions(helices) {
                insertions.push(i);
            }
        }
        self.insertion_drawer.new_instances(insertions);
        self.was_updated = true;
    }

    pub fn update_selection(&mut self, strands: &[Strand], helices: &[Helix]) {
        self.selected_strands.clear();
        for s in strands.iter() {
            let mut strand_view = StrandView::new(self.device.clone(), self.queue.clone());
            strand_view.update(s, helices, &None, &self.camera_top, &self.camera_bottom);
            self.selected_strands.push(strand_view);
        }
        self.was_updated = true;
    }

    pub fn update_candidate(&mut self, strands: &[Strand], helices: &[Helix]) {
        self.candidate_strands.clear();
        for s in strands.iter() {
            let mut strand_view = StrandView::new(self.device.clone(), self.queue.clone());
            strand_view.update(s, helices, &None, &self.camera_top, &self.camera_bottom);
            self.candidate_strands.push(strand_view);
        }
        self.was_updated = true;
    }

    pub fn update_pasted_strand(&mut self, strand: &[Strand], helices: &[Helix]) {
        self.pasted_strands = strand
            .iter()
            .map(|strand| {
                let mut pasted_strand = StrandView::new(self.device.clone(), self.queue.clone());
                pasted_strand.update(
                    strand,
                    helices,
                    &None,
                    &self.camera_top,
                    &self.camera_bottom,
                );
                pasted_strand
            })
            .collect();
    }

    pub fn set_free_end(&mut self, free_end: Option<FreeEnd>) {
        self.free_end = free_end;
    }

    pub fn needs_redraw(&self) -> bool {
        if self.splited {
            self.camera_top.borrow().was_updated()
                | self.was_updated
                | self.camera_bottom.borrow().was_updated()
        } else {
            self.camera_top.borrow().was_updated() | self.was_updated
        }
    }

    pub fn set_selection(&mut self, selection: FlatSelection) {
        self.selection = selection;
    }

    pub fn set_selected_helices(&mut self, selection: Vec<FlatIdx>) {
        self.selected_helices = selection;
    }

    pub fn set_candidate_helices(&mut self, selection: Vec<FlatIdx>) {
        self.candidate_helices = selection;
    }

    pub fn center_selection(&mut self) -> Option<(FlatNucl, FlatNucl)> {
        self.camera_top.borrow_mut().zoom_closer();
        match self.selection {
            FlatSelection::Bound(_, n1, n2) => {
                self.helices[n1.helix].make_visible(n1.position, self.camera_top.clone());
                let world_pos_1 = self.helices[n1.helix].get_nucl_position(&n1, Shift::No);
                let world_pos_2 = self.helices[n2.helix].get_nucl_position(&n2, Shift::No);
                let screen_pos_1 = self
                    .camera_top
                    .borrow()
                    .world_to_norm_screen(world_pos_1.x, world_pos_1.y);
                let screen_pos_2 = self
                    .camera_top
                    .borrow()
                    .world_to_norm_screen(world_pos_2.x, world_pos_2.y);
                if (screen_pos_1.0 - screen_pos_2.0) * (screen_pos_1.0 - screen_pos_2.0)
                    + (screen_pos_1.1 - screen_pos_2.1) * (screen_pos_1.1 - screen_pos_2.1)
                    > 0.25
                {
                    // Center the topmost nucleotide on the top camera
                    if screen_pos_1.1 < screen_pos_2.1 {
                        Some((n1, n2))
                    } else {
                        Some((n2, n1))
                    }
                } else {
                    None
                }
            }
            FlatSelection::Nucleotide(
                _,
                FlatNucl {
                    helix, position, ..
                },
            ) => {
                self.helices[helix].make_visible(position, self.camera_top.clone());
                None
            }
            _ => None,
        }
    }

    pub fn center_split(&mut self, n1: FlatNucl, n2: FlatNucl) {
        let zoom = self.camera_top.borrow().get_globals().zoom;
        self.camera_bottom.borrow_mut().set_zoom(zoom);
        self.helices[n1.helix].make_visible(n1.position, self.camera_top.clone());
        self.helices[n2.helix].make_visible(n2.position, self.camera_bottom.clone());
    }

    /// Center the top camera on a nucleotide
    pub fn center_nucl(&mut self, nucl: FlatNucl, bottom: bool) {
        let helix = nucl.helix;
        let position = self.helices[helix].get_pivot(nucl.position);
        if bottom {
            self.camera_bottom.borrow_mut().set_center(position);
        } else {
            self.camera_top.borrow_mut().set_center(position);
        }
    }

    pub fn update_rectangle(&mut self, c1: PhysicalPosition<f64>, c2: PhysicalPosition<f64>) {
        if self.splited {
            if (c1.y < self.area_size.height as f64 / 2.)
                != (c2.y < self.area_size.height as f64 / 2.)
            {
                self.rectangle.update_corners(None);
            } else {
                self.rectangle.update_corners(Some([
                    Ndc::from_physical(c1, self.area_size),
                    Ndc::from_physical(c2, self.area_size),
                ]));
            }
        } else {
            self.rectangle.update_corners(Some([
                Ndc::from_physical(c1, self.area_size),
                Ndc::from_physical(c2, self.area_size),
            ]));
        }
        self.was_updated = true;
    }

    pub fn clear_rectangle(&mut self) {
        self.rectangle.update_corners(None);
        self.was_updated = true;
    }

    pub fn draw(
        &mut self,
        encoder: &mut wgpu::CommandEncoder,
        target: &wgpu::TextureView,
        _area: DrawArea,
    ) {
        let mut need_new_circles = false;
        if let Some(globals) = self.camera_top.borrow_mut().update() {
            self.globals_top.update(globals);
            need_new_circles = true;
        }
        if let Some(globals) = self.camera_bottom.borrow_mut().update() {
            self.globals_bottom.update(globals);
            need_new_circles = true;
        }
        if need_new_circles || self.was_updated {
            let instances_top = self.generate_circle_instances(&self.camera_top);
            let instances_bottom = self.generate_circle_instances(&self.camera_bottom);
            if SHOW_SUGGESTION {
                self.view_suggestion();
            }
            self.circle_drawer_top.new_instances(Rc::new(instances_top));
            self.circle_drawer_bottom
                .new_instances(Rc::new(instances_bottom));
            self.generate_char_instances();
        }

        let clear_color = wgpu::Color {
            r: 0.,
            g: 0.,
            b: 0.,
            a: 0.,
        };

        let msaa_texture = if SAMPLE_COUNT > 1 {
            Some(crate::utils::texture::Texture::create_msaa_texture(
                self.device.clone().as_ref(),
                &self.area_size,
                SAMPLE_COUNT,
                wgpu::TextureFormat::Bgra8UnormSrgb,
            ))
        } else {
            None
        };

        let attachment = if msaa_texture.is_some() {
            msaa_texture.as_ref().unwrap()
        } else {
            target
        };

        let resolve_target = if msaa_texture.is_some() {
            Some(target)
        } else {
            None
        };

        let bottom = false;
        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: None,
            color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                attachment,
                resolve_target,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Clear(clear_color),
                    store: true,
                },
            }],
            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
                attachment: &self.depth_texture.view,
                depth_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(1.),
                    store: true,
                }),
                stencil_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(0),
                    store: true,
                }),
            }),
        });
        if self.splited {
            render_pass.set_viewport(
                0.,
                0.,
                self.area_size.width as f32,
                self.area_size.height as f32 / 2.,
                0.,
                1.,
            );
            render_pass.set_scissor_rect(0, 0, self.area_size.width, self.area_size.height / 2);
        }
        render_pass.set_bind_group(0, self.globals_top.get_bindgroup(), &[]);
        render_pass.set_bind_group(1, self.models.get_bindgroup(), &[]);
        self.background.draw(&mut render_pass);

        render_pass.set_pipeline(&self.helices_pipeline);

        for background in self.helices_background.iter() {
            background.draw(&mut render_pass);
        }
        for helix in self.helices_view.iter() {
            helix.draw(&mut render_pass);
        }
        self.circle_drawer_top.draw(&mut render_pass);
        self.rotation_widget.draw(&mut render_pass);
        for drawer in self.char_drawers_top.values_mut() {
            drawer.draw(&mut render_pass);
        }
        self.insertion_drawer.draw(&mut render_pass);
        render_pass.set_pipeline(&self.strand_pipeline);
        for strand in self.strands.iter() {
            strand.draw(&mut render_pass, bottom);
        }
        for strand in self.pasted_strands.iter() {
            strand.draw(&mut render_pass, bottom);
        }
        for suggestion in self.suggestions_view.iter() {
            suggestion.draw(&mut render_pass, bottom);
        }
        for highlight in self.selected_strands.iter() {
            highlight.draw(&mut render_pass, bottom);
        }
        for highlight in self.candidate_strands.iter() {
            highlight.draw(&mut render_pass, bottom);
        }
        drop(render_pass);
        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: None,
            color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                attachment,
                resolve_target,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Load,
                    store: true,
                },
            }],
            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
                attachment: &self.depth_texture.view,
                depth_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(1.),
                    store: true,
                }),
                stencil_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(0),
                    store: true,
                }),
            }),
        });
        if self.splited {
            render_pass.set_viewport(
                0.,
                0.,
                self.area_size.width as f32,
                self.area_size.height as f32 / 2.,
                0.,
                1.,
            );
            render_pass.set_scissor_rect(0, 0, self.area_size.width, self.area_size.height / 2);
        }
        render_pass.set_bind_group(0, self.globals_top.get_bindgroup(), &[]);
        render_pass.set_bind_group(1, self.models.get_bindgroup(), &[]);
        self.background.draw_border(&mut render_pass);

        render_pass.set_pipeline(&self.strand_pipeline);
        for strand in self.strands.iter() {
            strand.draw_split(&mut render_pass, bottom);
        }
        for strand in self.pasted_strands.iter() {
            strand.draw_split(&mut render_pass, bottom);
        }
        for suggestion in self.suggestions_view.iter() {
            suggestion.draw_split(&mut render_pass, bottom);
        }
        for highlight in self.selected_strands.iter() {
            highlight.draw_split(&mut render_pass, bottom);
        }
        for highlight in self.candidate_strands.iter() {
            highlight.draw_split(&mut render_pass, bottom);
        }

        drop(render_pass);
        if self.splited {
            let bottom = true;
            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: None,
                color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                    attachment,
                    resolve_target,
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Load,
                        store: true,
                    },
                }],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
                    attachment: &self.depth_texture.view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.),
                        store: true,
                    }),
                    stencil_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(0),
                        store: true,
                    }),
                }),
            });
            render_pass.set_viewport(
                0.,
                self.area_size.height as f32 / 2.,
                self.area_size.width as f32,
                self.area_size.height as f32 / 2.,
                0.,
                1.,
            );
            render_pass.set_scissor_rect(
                0,
                self.area_size.height / 2,
                self.area_size.width,
                self.area_size.height / 2,
            );
            render_pass.set_bind_group(0, self.globals_bottom.get_bindgroup(), &[]);
            render_pass.set_bind_group(1, self.models.get_bindgroup(), &[]);
            self.background.draw(&mut render_pass);

            render_pass.set_pipeline(&self.helices_pipeline);

            for background in self.helices_background.iter() {
                background.draw(&mut render_pass);
            }
            for helix in self.helices_view.iter() {
                helix.draw(&mut render_pass);
            }
            self.circle_drawer_bottom.draw(&mut render_pass);
            self.rotation_widget.draw(&mut render_pass);
            for drawer in self.char_drawers_bottom.values_mut() {
                drawer.draw(&mut render_pass);
            }
            self.insertion_drawer.draw(&mut render_pass);
            render_pass.set_pipeline(&self.strand_pipeline);
            for strand in self.strands.iter() {
                strand.draw(&mut render_pass, bottom);
            }
            for strand in self.pasted_strands.iter() {
                strand.draw(&mut render_pass, bottom);
            }
            for suggestion in self.suggestions_view.iter() {
                suggestion.draw(&mut render_pass, bottom);
            }
            for highlight in self.selected_strands.iter() {
                highlight.draw(&mut render_pass, bottom);
            }
            for highlight in self.candidate_strands.iter() {
                highlight.draw(&mut render_pass, bottom);
            }
            drop(render_pass);
            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: None,
                color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                    attachment,
                    resolve_target,
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Load,
                        store: true,
                    },
                }],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
                    attachment: &self.depth_texture.view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.),
                        store: true,
                    }),
                    stencil_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(0),
                        store: true,
                    }),
                }),
            });
            render_pass.set_viewport(
                0.,
                self.area_size.height as f32 / 2.,
                self.area_size.width as f32,
                self.area_size.height as f32 / 2.,
                0.,
                1.,
            );
            render_pass.set_scissor_rect(
                0,
                self.area_size.height / 2,
                self.area_size.width,
                self.area_size.height / 2,
            );
            render_pass.set_bind_group(0, self.globals_bottom.get_bindgroup(), &[]);
            render_pass.set_bind_group(1, self.models.get_bindgroup(), &[]);
            self.background.draw_border(&mut render_pass);

            render_pass.set_pipeline(&self.strand_pipeline);
            for strand in self.strands.iter() {
                strand.draw_split(&mut render_pass, bottom);
            }
            for strand in self.pasted_strands.iter() {
                strand.draw_split(&mut render_pass, bottom);
            }
            for suggestion in self.suggestions_view.iter() {
                suggestion.draw_split(&mut render_pass, bottom);
            }
            for highlight in self.selected_strands.iter() {
                highlight.draw_split(&mut render_pass, bottom);
            }
            for highlight in self.candidate_strands.iter() {
                highlight.draw_split(&mut render_pass, bottom);
            }
        }
        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: None,
            color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                attachment,
                resolve_target,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Load,
                    store: true,
                },
            }],
            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
                attachment: &self.depth_texture.view,
                depth_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(1.),
                    store: true,
                }),
                stencil_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(0),
                    store: true,
                }),
            }),
        });
        self.rectangle.draw(&mut render_pass);
        self.was_updated = false;
    }

    /// Return all the circles that must be displayed to represent the flatscene.
    ///
    /// Currently these circles are:
    ///  * Helices circles
    ///  * Cross-over suggestions
    ///  * Torsion indications
    fn generate_circle_instances(&self, camera: &CameraPtr) -> Vec<CircleInstance> {
        let mut ret = Vec::new();
        self.collect_helices_circles(&mut ret, camera);
        self.collect_suggestions(&mut ret);
        if self.show_torsion {
            self.collect_torsion_indications(&mut ret);
        }
        ret
    }

    /// Add the helices circles to the list of circle instances
    fn collect_helices_circles(&self, circles: &mut Vec<CircleInstance>, camera: &CameraPtr) {
        for h in self.helices.iter() {
            if let Some(circle) = h.get_circle(camera) {
                circles.push(circle);
            }
            for circle in h.handle_circles() {
                circles.push(circle)
            }
        }
        for h_id in self.selected_helices.iter() {
            if let Some(mut circle) = self.helices.get(h_id.0).and_then(|h| h.get_circle(camera)) {
                circle.set_radius(circle.radius * 1.4);
                circle.set_color(0xFF_FF0000);
                circles.push(circle);
            }
        }

        for h_id in self.candidate_helices.iter() {
            if let Some(mut circle) = self.helices.get(h_id.0).and_then(|h| h.get_circle(camera)) {
                circle.set_radius(circle.radius * 1.4);
                circle.set_color(0xFF_00FF00);
                circles.push(circle);
            }
        }
    }

    /// Collect the cross-over suggestions
    fn collect_suggestions(&self, circles: &mut Vec<CircleInstance>) {
        let mut last_blue = None;
        let mut k = 1000;
        for (n1, n2) in self.suggestions.iter() {
            // Don't change the color if the value of n1 hasn't change, so that all suggested
            // cross-overs for n1 appears with the same color
            if last_blue != Some(n1) {
                k += 1;
                last_blue = Some(n1);
            }
            let color = {
                let hue = (k as f64 * (1. + 5f64.sqrt()) / 2.).fract() * 360.;
                let saturation = (k as f64 * 7. * (1. + 5f64.sqrt() / 2.)).fract() * 0.4 + 0.6;
                let value = (k as f64 * 11. * (1. + 5f64.sqrt() / 2.)).fract() * 0.7 + 0.3;
                let hsv = color_space::Hsv::new(hue, saturation, value);
                let rgb = color_space::Rgb::from(hsv);
                (0xFF << 24) | ((rgb.r as u32) << 16) | ((rgb.g as u32) << 8) | (rgb.b as u32)
            };
            let h1 = &self.helices[n1.helix];
            let h2 = &self.helices[n2.helix];
            circles.push(h1.get_circle_nucl(n1.position, n1.forward, color));
            circles.push(h2.get_circle_nucl(n2.position, n2.forward, color));
        }
    }

    /// Collect the torsion indications.
    /// The radius and color of the circles depends on the strangth amplitude.
    fn collect_torsion_indications(&self, circles: &mut Vec<CircleInstance>) {
        for ((n0, n1), torsion) in self.torsions.iter() {
            let multiplier = ((torsion.strength_prime5 - torsion.strength_prime3).abs() / 200.)
                .max(0.08)
                .min(1.);
            let color = torsion_color(torsion.strength_prime5 - torsion.strength_prime3);
            let h0 = &self.helices[n0.helix];
            let mut circle = h0.get_circle_nucl(n0.position, n0.forward, color);
            circle.radius *= multiplier;
            if let Some(friend) = torsion.friend {
                // The circle center should be placed between the two friend cross-overs
                let circle2 = h0.get_circle_nucl(friend.0.position, n0.forward, color);
                circle.center = (circle.center + circle2.center) / 2.;
            }
            circles.push(circle);
            let h1 = &self.helices[n1.helix];
            let mut circle = h1.get_circle_nucl(n1.position, n1.forward, color);
            circle.radius *= multiplier;
            if let Some(friend) = torsion.friend {
                // The circle center should be placed between the two friend cross-overs
                let circle2 = h1.get_circle_nucl(friend.1.position, n1.forward, color);
                circle.center = (circle.center + circle2.center) / 2.;
            }
            circles.push(circle);
        }
    }

    fn view_suggestion(&mut self) {
        self.suggestions_view.clear();
        for (n1, n2) in self.suggestions.iter() {
            let mut view = StrandView::new(self.device.clone(), self.queue.clone());
            view.set_indication(*n1, *n2, &self.helices);
            self.suggestions_view.push(view);
        }
    }

    pub fn set_candidate_suggestion(
        &mut self,
        candidate: Option<FlatNucl>,
        other: Option<FlatNucl>,
    ) {
        self.suggestions_view.clear();
        self.was_updated |= self.suggestion_candidate != candidate.zip(other);
        if let Some((n1, n2)) = candidate.zip(other) {
            let mut view = StrandView::new(self.device.clone(), self.queue.clone());
            view.set_indication(n1, n2, &self.helices);
            self.suggestions_view.push(view);
        }
        self.suggestion_candidate = candidate.zip(other);
    }

    fn generate_char_instances(&mut self) {
        for v in self.char_map_top.values_mut() {
            v.clear();
        }
        for v in self.char_map_bottom.values_mut() {
            v.clear();
        }

        for h in self.helices.iter() {
            h.add_char_instances(
                &self.camera_top,
                &mut self.char_map_top,
                &self.char_drawers_top,
                self.show_sec,
            );
            h.add_char_instances(
                &self.camera_bottom,
                &mut self.char_map_bottom,
                &self.char_drawers_bottom,
                self.show_sec,
            )
        }

        for (c, v) in self.char_map_top.iter() {
            self.char_drawers_top
                .get_mut(c)
                .unwrap()
                .new_instances(Rc::new(v.clone()))
        }
        for (c, v) in self.char_map_bottom.iter() {
            self.char_drawers_bottom
                .get_mut(c)
                .unwrap()
                .new_instances(Rc::new(v.clone()))
        }
    }

    pub fn set_wheels(&mut self, wheels: Vec<CircleInstance>) {
        self.was_updated = true;
        self.rotation_widget.new_instances(Rc::new(wheels));
    }
}

fn helices_pipeline_descr(
    device: &Device,
    globals_layout: &wgpu::BindGroupLayout,
    models_layout: &wgpu::BindGroupLayout,
    depth_stencil: Option<wgpu::DepthStencilState>,
) -> wgpu::RenderPipeline {
    let vs_module = &device.create_shader_module(&wgpu::include_spirv!("view/grid.vert.spv"));
    let fs_module = &device.create_shader_module(&wgpu::include_spirv!("view/grid.frag.spv"));
    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
        bind_group_layouts: &[globals_layout, models_layout],
        push_constant_ranges: &[],
        label: None,
    });
    let color_targets = &[wgpu::ColorTargetState {
        format: wgpu::TextureFormat::Bgra8UnormSrgb,
        color_blend: wgpu::BlendState::REPLACE,
        alpha_blend: wgpu::BlendState::REPLACE,
        write_mask: wgpu::ColorWrite::ALL,
    }];
    let primitive_state = wgpu::PrimitiveState {
        topology: wgpu::PrimitiveTopology::TriangleList,
        front_face: wgpu::FrontFace::Ccw,
        cull_mode: wgpu::CullMode::None,
        ..Default::default()
    };

    let desc = wgpu::RenderPipelineDescriptor {
        layout: Some(&pipeline_layout),
        fragment: Some(wgpu::FragmentState {
            module: &fs_module,
            entry_point: "main",
            targets: color_targets,
        }),
        primitive: primitive_state,
        depth_stencil,
        vertex: wgpu::VertexState {
            module: &vs_module,
            entry_point: "main",
            buffers: &[wgpu::VertexBufferLayout {
                array_stride: std::mem::size_of::<GpuVertex>() as u64,
                step_mode: wgpu::InputStepMode::Vertex,
                attributes: &wgpu::vertex_attr_array![0 => Float2, 1 => Float2, 2 => Uint, 3 => Uint],
            }],
        },
        multisample: wgpu::MultisampleState {
            count: SAMPLE_COUNT,
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        label: None,
    };

    device.create_render_pipeline(&desc)
}

fn strand_pipeline_descr(
    device: &Device,
    globals: &wgpu::BindGroupLayout,
    depth_stencil: Option<wgpu::DepthStencilState>,
) -> wgpu::RenderPipeline {
    let vs_module = &device.create_shader_module(&wgpu::include_spirv!("view/strand.vert.spv"));
    let fs_module = &device.create_shader_module(&wgpu::include_spirv!("view/strand.frag.spv"));
    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
        bind_group_layouts: &[globals],
        push_constant_ranges: &[],
        label: None,
    });
    let color_targets = &[wgpu::ColorTargetState {
        format: wgpu::TextureFormat::Bgra8UnormSrgb,
        color_blend: wgpu::BlendState {
            src_factor: wgpu::BlendFactor::SrcAlpha,
            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
            operation: wgpu::BlendOperation::Add,
        },
        alpha_blend: wgpu::BlendState {
            src_factor: wgpu::BlendFactor::One,
            dst_factor: wgpu::BlendFactor::One,
            operation: wgpu::BlendOperation::Add,
        },
        write_mask: wgpu::ColorWrite::ALL,
    }];

    let primitive_state = wgpu::PrimitiveState {
        front_face: wgpu::FrontFace::Ccw,
        cull_mode: wgpu::CullMode::None,
        topology: wgpu::PrimitiveTopology::TriangleList,
        ..Default::default()
    };

    let desc = wgpu::RenderPipelineDescriptor {
        primitive: primitive_state,
        layout: Some(&pipeline_layout),
        fragment: Some(wgpu::FragmentState {
            module: &fs_module,
            entry_point: "main",
            targets: color_targets,
        }),
        depth_stencil,
        vertex: wgpu::VertexState {
            buffers: &[wgpu::VertexBufferLayout {
                array_stride: std::mem::size_of::<StrandVertex>() as u64,
                step_mode: wgpu::InputStepMode::Vertex,
                attributes: &wgpu::vertex_attr_array![0 => Float2, 1 => Float2, 2 => Float4, 3 => Float, 4 => Float],
            }],
            module: &vs_module,
            entry_point: "main",
        },
        multisample: wgpu::MultisampleState {
            count: SAMPLE_COUNT,
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        label: None,
    };

    device.create_render_pipeline(&desc)
}

fn torsion_color(strength: f32) -> u32 {
    const RED_HUE: f32 = 0.;
    const BLUE_HUE: f32 = 240.;
    const MAX_STRENGTH: f32 = 200.;
    let hue = if strength > 0. { RED_HUE } else { BLUE_HUE };
    //println!("strength {}", strength);
    let sat = (strength / MAX_STRENGTH).min(1.).max(-1.);
    let val = (strength / MAX_STRENGTH).min(1.).max(-1.);
    let hsv = color_space::Hsv::new(hue as f64, sat.abs() as f64, val.abs() as f64);
    let rgb = color_space::Rgb::from(hsv);
    (0xFF << 24) | ((rgb.r as u32) << 16) | ((rgb.g as u32) << 8) | (rgb.b as u32)
}
back to top