<commit_before>//! Drawing maps and game states on the screen with Glium.
//!
//! The `drawer` module defines types that know how to draw a graph and a game
//! state on the screen using Glium calls.
//!
//! # Coordinate spaces
//!
//! Although rbattle is a 2D game, which simplifies things a lot, it needs to
//! work with a few different coordinate systems. Working from the the most
//! concrete (pixels on the screen) to the abstract (nodes on the graph):
//!
//! - Window coordinates. The window's area is a rectangular matrix of pixels,
//!   with (0,0) at the upper left, and the positive x and y axes pointing to
//!   the right and down.
//!
//! - Normalized device coordinates (NDC). Normalized device coordinates cover
//!   the window in a resolution-independent way. In NDC, (0,0) is the center of
//!   the screen; the positive x and y axes point to the right and up, as is
//!   traditional in mathematics; and the edges of the window are -1 and 1 for
//!   both axes.
//!
//! - Game coordinates: (-1, -1) and (1, 1) are the lower-left and upper-right
//!   coordinates of the overall display of the game. We choose the
//!   transformation between this and NDC to keep the game display fully visible
//!   and centered in the window.
//!
//! - Graph space coordinates: the coordinate system defined by the VisibleGraph
//!   implementation, where nodes' areas fall in the axis-aligned bounding box
//!   between (0,0) and upper_right, where upper_right is what you get from
//!   VisibleGraph::bounds().

use errors::*;
use map::Map;
use state::{State, MAX_GOOP, OwnedNode};
use math::{compose, scale_transform};
use visible_graph::{GraphPt, VisibleGraph};

use glium::{DrawParameters, Frame, IndexBuffer, Program, Surface, VertexBuffer};
use glium::backend::Facade;
use glium::index::PrimitiveType;
//use glium::vertex::Vertex;

use std::cell::RefCell;

/// A `Drawer` knows how to draw a `State` on a Glium `Frame`.
///
/// A `Drawer` is constructed from a `Map`, and then is given specific `State`
/// values that use that `Map` to draw a complete frame of the game on a Glium
/// `Frame` value, representing one frame of video.
///
/// The `Drawer` is the right place to hold Glium state that persists between
/// frames, like vertex and index buffers for the map.
pub struct Drawer {
    /// Cached information needed to drawing the map, excluding the map itself.
    /// This holds vertex and index buffers, shader programs, transformations,
    /// and the like.
    map: MapDrawer,

    /// Cached information needed to draw outflows.
    outflows: OutflowsDrawer,

    /// Cached information for drawing goop amounts.
    goop: GoopDrawer,
}

impl Drawer {
    pub fn new<G>(display: &Facade, map: &Map<G>) -> Result<Drawer>
        where G: VisibleGraph
    {
        let map_drawer = MapDrawer::new(display, map)?;
        let outflows = OutflowsDrawer::new(display, map)?;
        let goop = GoopDrawer::new(display, map)?;

        Ok(Drawer { map: map_drawer, outflows, goop })
    }

    pub fn draw<G>(&self, frame: &mut Frame, state: &State<G>) -> Result<()>
        where G: VisibleGraph
    {
        let map = &*state.map;

        // Compute the aspect ratio of the window (the "device"), assuming
        // square pixels.
        let (width, height) = frame.get_dimensions();
        let device_aspect = width as f32 / height as f32;

        // Compute the transformation from game coordinates to normalized device
        // coordinates. Depending on their relative aspect ratios, the game may
        // be centered either vertically or horizontally within the window.
        let game_to_device =
            if device_aspect > map.game_aspect {
                // Window is wider than game. Game centered horizontally.
                scale_transform(map.game_aspect / device_aspect, 1.0)
            } else {
                // Game is wider than window. Game centered vertically.
                scale_transform(1.0, device_aspect / map.game_aspect)
            };

        let graph_to_device = compose(game_to_device, map.graph_to_game);

        self.map.draw(frame, &graph_to_device, &state.map)?;
        self.goop.draw(frame, &graph_to_device, &state.nodes, &state.map)?;
        self.outflows.draw(frame, &graph_to_device, &state.nodes)?;
        Ok(())
    }
}

struct MapDrawer {
    /// Shader program for drawing the map.
    program: Program,

    /// Vertexes of the graph's boundary lines.
    vertices: VertexBuffer<GraphVert>,

    /// Indices for the graph's boundary lines.
    indices: IndexBuffer<u32>,

    /// Draw parameters for drawing the map.
    draw_params: DrawParameters<'static>
}

impl MapDrawer {
    fn new<G>(display: &Facade, map: &Map<G>) -> Result<MapDrawer><commit_msg>Drawer: Have `draw` return a window-to-game transform. Ugly.<commit_after>//! Drawing maps and game states on the screen with Glium.
//!
//! The `drawer` module defines types that know how to draw a graph and a game
//! state on the screen using Glium calls.
//!
//! # Coordinate spaces
//!
//! Although rbattle is a 2D game, which simplifies things a lot, it needs to
//! work with a few different coordinate systems. Working from the the most
//! concrete (pixels on the screen) to the abstract (nodes on the graph):
//!
//! - Window coordinates. The window's area is a rectangular matrix of pixels,
//!   with (0,0) at the upper left, and the positive x and y axes pointing to
//!   the right and down.
//!
//! - Normalized device coordinates (NDC). Normalized device coordinates cover
//!   the window in a resolution-independent way. In NDC, (0,0) is the center of
//!   the screen; the positive x and y axes point to the right and up, as is
//!   traditional in mathematics; and the edges of the window are -1 and 1 for
//!   both axes.
//!
//! - Game coordinates: (-1, -1) and (1, 1) are the lower-left and upper-right
//!   coordinates of the overall display of the game. We choose the
//!   transformation between this and NDC to keep the game display fully visible
//!   and centered in the window.
//!
//! - Graph space coordinates: the coordinate system defined by the VisibleGraph
//!   implementation, where nodes' areas fall in the axis-aligned bounding box
//!   between (0,0) and upper_right, where upper_right is what you get from
//!   VisibleGraph::bounds().

use errors::*;
use map::Map;
use state::{State, MAX_GOOP, OwnedNode};
use math::{compose, inverse, scale_transform, translate_transform};
use visible_graph::{GraphPt, VisibleGraph};

use glium::{DrawParameters, Frame, IndexBuffer, Program, Surface, VertexBuffer};
use glium::backend::Facade;
use glium::index::PrimitiveType;
//use glium::vertex::Vertex;

use std::cell::RefCell;

/// A `Drawer` knows how to draw a `State` on a Glium `Frame`.
///
/// A `Drawer` is constructed from a `Map`, and then is given specific `State`
/// values that use that `Map` to draw a complete frame of the game on a Glium
/// `Frame` value, representing one frame of video.
///
/// The `Drawer` is the right place to hold Glium state that persists between
/// frames, like vertex and index buffers for the map.
pub struct Drawer {
    /// Cached information needed to drawing the map, excluding the map itself.
    /// This holds vertex and index buffers, shader programs, transformations,
    /// and the like.
    map: MapDrawer,

    /// Cached information needed to draw outflows.
    outflows: OutflowsDrawer,

    /// Cached information for drawing goop amounts.
    goop: GoopDrawer,
}

impl Drawer {
    pub fn new<G>(display: &Facade, map: &Map<G>) -> Result<Drawer>
        where G: VisibleGraph
    {
        let map_drawer = MapDrawer::new(display, map)?;
        let outflows = OutflowsDrawer::new(display, map)?;
        let goop = GoopDrawer::new(display, map)?;

        Ok(Drawer { map: map_drawer, outflows, goop })
    }

    /// Draw `state` on `frame`
    ///
    /// Return the current transformation from window coordinates to game
    /// coordinates, for use by the controller.
    pub fn draw<G>(&self, frame: &mut Frame, state: &State<G>) -> Result<[[f32; 3]; 3]>
        where G: VisibleGraph
    {
        let map = &*state.map;

        // Compute the aspect ratio of the window (the "device"), assuming
        // square pixels.
        let (width, height) = frame.get_dimensions();
        let device_aspect = width as f32 / height as f32;

        // Compute the transformation from game coordinates to normalized device
        // coordinates. Depending on their relative aspect ratios, the game may
        // be centered either vertically or horizontally within the window.
        let game_to_device =
            if device_aspect > map.game_aspect {
                // Window is wider than game. Game centered horizontally.
                scale_transform(map.game_aspect / device_aspect, 1.0)
            } else {
                // Game is wider than window. Game centered vertically.
                scale_transform(1.0, device_aspect / map.game_aspect)
            };

        let graph_to_device = compose(game_to_device, map.graph_to_game);

        self.map.draw(frame, &graph_to_device, &state.map)?;
        self.goop.draw(frame, &graph_to_device, &state.nodes, &state.map)?;
        self.outflows.draw(frame, &graph_to_device, &state.nodes)?;

        // Compute the transformation from window coordinates (pixels) to game
        // coordinates, for the mouse handling to use. In window coordinates:
        //
        // - The positive y axis points down, not up.
        // - The origin is at the upper left, not in the center.
        // - Values range from (0,0) to (width,height), not (-1,-1) to (1,1).
        //
        // We compute this in two steps: first the transformation from window
        // coordinates to normalized device coordinates, and then the
        // transformation from there to game coordinates.
        let window_to_device
            = compose(translate_transform(-1.0, 1.0),
                      scale_transform(2.0 / (width as f32), -2.0 / (height as f32)));
        let device_to_game = inverse(game_to_device)
            .expect("graph_to_game transformation should be invertible");

        let window_to_game = compose(device_to_game, window_to_device);

        Ok(window_to_game)
    }
}

struct MapDrawer {
    /// Shader program for drawing the map.
    program: Program,

    /// Vertexes of the graph's boundary lines.
    vertices: VertexBuffer<GraphVert>,

    /// Indices for the graph's boundary lines.
    indices: IndexBuffer<u32>,

    /// Draw parameters for drawing the map.
    draw_params: DrawParameters<'static>
}

impl MapDrawer {
    fn new<G>(display: &Facade, map: &Map<G>) -> Result<MapDrawer>
{'fraction_non_alphanumeric': 0.10920451366222139, 'fraction_numerical': 0.006341508906089714, 'mean_word_length': 2.7934205871949063, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5645311', 'n_tokens_mistral': 3160, 'n_tokens_neox': 2998, 'n_words': 1230}