planetkit 0.0.1

High-level toolkit for building games based around voxel globes.
Documentation
use slog::Logger;

use grid::GridPoint3;
use grid::cell_shape;
use super::spec::Spec;
use super::{Globe, Cursor, ChunkOrigin};
use super::chunk::Material;
use types::Pt3;
use render;

// TODO: between this and "draw" we now have some confusing names.
// Shuffle this code into something that implies it's just about
// generating geometry for other components/systems, e.g., drawing
// and physics.

// `View` doesn't store a reference to a `Globe`,
// to avoid complex lifetime wrangling; we might want
// to load and unload globes and their views out of
// step with each other. E.g. we might use a `Globe`
// to create some geometry for a moon, and then never
// use the `Globe` itself again.
//
// Instead, the rendering subsystem will provide us with that
// globe when it wants us to build geometry.
pub struct View {
    spec: Spec,
    log: Logger,
}

impl View {
    pub fn new(globe_spec: Spec, parent_log: &Logger) -> View {
        View {
            spec: globe_spec,
            log: parent_log.new(o!()),
        }
    }

    /// Creates chunk geometry with vertex positions specified
    /// relative to the bottom-middle of the chunk origin cell.

    // TODO: don't take a reference to a chunk
    // in this method; to make geometry for this
    // chunk we'll eventually need to have data for adjacent chunks
    // loaded, and rebase some of the edge positions
    // on those adjacent chunks to get their cell data.
    //
    // **OR** we can have a step before this that
    // ensures we have all adjacent cell data cached
    // in extra rows/columns along the edges of this chunk.
    // The latter probably makes more sense for memory
    // locality in the hot path. Sometimes we might want
    // to ask further afield, though, (e.g. five cells
    // into another chunk) so decide whether you want
    // a general interface that can fetch as necessary,
    // commit to always caching as much as you
    // might ever need, or some combination.
    pub fn make_chunk_geometry(
        &self,
        globe: &Globe,
        origin: ChunkOrigin,
        vertex_data: &mut Vec<render::Vertex>,
        index_data: &mut Vec<u32>,
    ) {
        trace!(self.log, "Building chunk geometry"; "origin" => format!("{:?}", origin));

        // We store the geometry relative to the bottom-center of the chunk origin cell.
        let chunk_origin_pos = self.spec.cell_bottom_center(*origin.pos());

        let mut cursor = Cursor::new_in_chunk(globe, origin);

        // Include cells _on_ the far edge of the chunk;
        // even though we don't own them we'll need to draw part of them.
        let end_x = origin.pos().x + self.spec.chunk_resolution[0];
        let end_y = origin.pos().y + self.spec.chunk_resolution[1];
        // Chunks don't share cells in the z-direction,
        // but do in the x- and y-directions.
        let end_z = origin.pos().z + self.spec.chunk_resolution[2] - 1;
        for cell_z in origin.pos().z..(end_z + 1) {
            for cell_y in origin.pos().y..(end_y + 1) {
                for cell_x in origin.pos().x..(end_x + 1) {
                    // Use cell center as first vertex of each triangle.
                    let grid_point = GridPoint3::new(origin.pos().root, cell_x, cell_y, cell_z);

                    cursor.set_pos(grid_point);

                    if self.cull_cell(&cursor) {
                        continue;
                    }

                    let mut cell_color = {
                        // Eww... can I please have non-lexical borrow scopes? :)
                        let cell = cursor.cell().expect("We shouldn't be trying to build geometry for a chunk that isn't loaded.");

                        // TEMP color dirt as green, ocean as blue.
                        // TEMP: Randomly mutate cell color to make it easier to see edges.
                        let mut inner_cell_color = if cell.material == Material::Dirt {
                            // Grassy green
                            [0.0, 0.4, 0.0]
                        } else if cell.material == Material::Water {
                            // Ocean blue
                            [0.0, 0.1, 0.7]
                        } else {
                            // Don't draw air or anything else we don't understand.
                            continue;
                        };
                        for color_channel in &mut inner_cell_color {
                            *color_channel *= 1.0 - 0.5 * cell.shade;
                        }

                        inner_cell_color
                    };

                    // TODO: use functions that return just the bit they care
                    // about and... maths. This is silly.
                    let first_top_vertex_index = vertex_data.len() as u32;

                    // TODO: don't switch; split all this out into calls
                    // over different ranges of cells.
                    //
                    // For now, put the most specific cases first.
                    let cell_shape = if cell_x == 0 && cell_y == 0 {
                        cell_shape::NORTH_PORTION
                    } else if cell_x == end_x && cell_y == end_y {
                        cell_shape::SOUTH_PORTION
                    } else if cell_x == end_x && cell_y == 0 {
                        cell_shape::WEST_PORTION
                    } else if cell_x == 0 && cell_y == end_y {
                        cell_shape::EAST_PORTION
                    } else if cell_y == 0 {
                        cell_shape::NORTH_WEST_PORTION
                    } else if cell_x == 0 {
                        cell_shape::NORTH_EAST_PORTION
                    } else if cell_x == end_x {
                        cell_shape::SOUTH_WEST_PORTION
                    } else if cell_y == end_y {
                        cell_shape::SOUTH_EAST_PORTION
                    } else {
                        cell_shape::FULL_HEX
                    };

                    // Emit each top vertex of whatever shape we're using for this cell.
                    let offsets = &cell_shape.top_outline_dir_offsets;
                    for offset in offsets.iter() {
                        let vertex_pt3 = Pt3::from_coordinates(
                            self.spec.cell_top_vertex(grid_point, *offset) -
                                chunk_origin_pos,
                        );
                        vertex_data.push(render::Vertex::new_from_pt3(vertex_pt3, cell_color));
                    }

                    // Emit triangles for the top of the cell. All triangles
                    // will contain the first vertex, plus two others.
                    for i in 1..(offsets.len() as u32 - 1) {
                        index_data.extend_from_slice(
                            &[
                                first_top_vertex_index,
                                first_top_vertex_index + i,
                                first_top_vertex_index + i + 1,
                            ],
                        );
                    }

                    // Emit each top vertex of whatever shape we're using for this cell
                    // AGAIN for the top of the sides, so they can have a different colour.
                    // Darken the top of the sides slightly to fake lighting.
                    for color_channel in &mut cell_color {
                        *color_channel *= 0.9;
                    }
                    let first_side_top_vertex_index = first_top_vertex_index + offsets.len() as u32;
                    for offset in offsets.iter() {
                        let vertex_pt3 = Pt3::from_coordinates(
                            self.spec.cell_top_vertex(grid_point, *offset) -
                                chunk_origin_pos,
                        );
                        vertex_data.push(render::Vertex::new_from_pt3(vertex_pt3, cell_color));
                    }

                    // Emit each bottom vertex of whatever shape we're using for this cell.
                    // Darken the bottom of the sides substantially to fake lighting.
                    for color_channel in &mut cell_color {
                        *color_channel *= 0.5;
                    }
                    let first_side_bottom_vertex_index = first_side_top_vertex_index +
                        offsets.len() as u32;
                    for offset in offsets.iter() {
                        let vertex_pt3 = Pt3::from_coordinates(
                            self.spec.cell_bottom_vertex(grid_point, *offset) -
                                chunk_origin_pos,
                        );
                        vertex_data.push(render::Vertex::new_from_pt3(vertex_pt3, cell_color));
                    }

                    // Emit triangles for the cell sides.
                    for ab_i in 0..(offsets.len() as u32) {
                        let cd_i = (ab_i + 1) % offsets.len() as u32;
                        let a_i = first_side_top_vertex_index + ab_i;
                        let b_i = first_side_bottom_vertex_index + ab_i;
                        let c_i = first_side_bottom_vertex_index + cd_i;
                        let d_i = first_side_top_vertex_index + cd_i;
                        index_data.extend_from_slice(&[a_i, b_i, d_i, d_i, b_i, c_i]);
                    }
                }
            }
        }
    }

    fn cull_cell(&self, cursor: &Cursor) -> bool {
        use grid::Neighbors;

        let resolution = cursor.globe().spec().root_resolution;

        let grid_point = cursor.pos();
        let mut neighbor_cursor = cursor.clone();

        // If none of the neighboring cells contain air,
        // then we won't render the cell at all.
        let neighbors = Neighbors::new(grid_point, resolution);
        for neighbor_pos in neighbors {
            neighbor_cursor.set_pos(neighbor_pos);
            if let Some(neighbor) = neighbor_cursor.cell() {
                if neighbor.material == Material::Air {
                    // This cell can be seen; we can't cull it.
                    return false;
                }
            }
        }

        // If there was no reason to save it,
        // then assume we can cull it!
        true
    }
}