// Don't make `globe` public; we re-export the
// main bits at this level below.
mod globe;
mod globe_ext;
pub mod icosahedron;
mod spec;
pub mod chunk;
mod view;
mod gen;
mod chunk_view;
mod chunk_view_system;
mod chunk_system;
mod cursor;
mod chunk_origin;
mod iters;
mod chunk_shared_points;
mod chunk_pair;
#[cfg(test)]
mod tests;
use types::*;
// TODO: be selective in what you export; no wildcards!
pub use self::globe::Globe;
pub use self::spec::*;
pub use self::view::*;
pub use self::chunk_view::*;
pub use self::chunk_view_system::*;
pub use self::chunk_system::ChunkSystem;
pub use self::cursor::{Cursor, CursorMut};
pub use self::chunk_origin::*;
pub use self::iters::*;
pub use self::chunk_shared_points::ChunkSharedPoints;
use grid::{GridCoord, GridPoint3, Root, PosInOwningRoot};
// TODO: move project into icosahedron module.
// Project a position in a given root quad into a unit sphere.
// Assumes that one corner is represented in `pt_in_root_quad`
// as (0, 0) and the opposite is (1, 1).
#[cfg_attr(feature = "cargo-clippy", allow(many_single_char_names))]
pub fn project(root: Root, mut pt_in_root_quad: Pt2) -> Pt3 {
// An icosahedron can be flattened into a net comprising 20 triangles:
//
// ● ● ● ● ●
// / \ / \ / \ / \ / \
// / \ / \ / \ / \ / \
// ●-----●-----●-----●-----●-----●
// \ / \ / \ / \ / \ / \
// \ / \ / \ / \ / \ / \
// ●-----●-----●-----●-----●-----●
// \ / \ / \ / \ / \ /
// \ / \ / \ / \ / \ /
// ● ● ● ● ●
//
// We can then break this into 5 "root quads", each of which comprises
// a strip of four triangles running all the way from the north pole
// of the globe down to the south. These root quads are twice as long
// (y-axis) as they are wide (x-axis).
//
// Highlighting the first quad:
//
// ● ◌ ◌ ◌ ◌
// /·\ / \ / \ / \ / \
// /···\ / \ / \ / \ / \
// ● - - ●-----◌-----◌-----◌-----◌
// \···/·\ / \ / \ / \ / \
// \·/···\ / \ / \ / \ / \
// ● - - ●-----◌-----◌-----◌-----◌
// \···/ \ / \ / \ / \ /
// \·/ \ / \ / \ / \ /
// ● ◌ ◌ ◌ ◌
//
// So we need to set up some points and vectors based on the
// root we're operating in, and then the math depends on which
// triangle `pt_in_root_quad` is in.
//
// In the diagram below, each point is labelled with this information
// in the same order:
//
// - A name for this vertex (e.g. 'a') for use in calculations below
// - The (x, y) coordinates for this vertex in voxmap space
// - The indexes of the triangle, and the index of the vertex within
// each triangle, expressed as `i_j`. For many vertices there will
// be multiple triangles containing it.
//
// a (0, 0)
// 0_0
// ●
// / \
// / \
// b / 0 \ c (0, 1)
// (1, 0) / \ 0_2
// 0_1 ●---------● 1_1
// 1_2 \ / \ 2_0
// \ 1 / \
// \ / 2 \
// d \ / \
// (1, 1) ●---------● e (0, 2)
// 1_0 \ / 2_2
// 2_1 \ 3 / 3_1
// 3_2 \ /
// \ /
// f ●
// (1, 2)
// 3_0
//
// TODO: cache all this stuff somewhere. It's tiny, and we'll use it heaps.
use self::icosahedron::{FACES, VERTICES};
let triangle_indices = [
root.index as usize * 4,
root.index as usize * 4 + 1,
root.index as usize * 4 + 2,
root.index as usize * 4 + 3,
];
let faces = [
FACES[triangle_indices[0]],
FACES[triangle_indices[1]],
FACES[triangle_indices[2]],
FACES[triangle_indices[3]],
];
let a: Pt3 = Pt3::new(
VERTICES[faces[0][0]][0],
VERTICES[faces[0][0]][1],
VERTICES[faces[0][0]][2],
);
let b: Pt3 = Pt3::new(
VERTICES[faces[0][1]][0],
VERTICES[faces[0][1]][1],
VERTICES[faces[0][1]][2],
);
let c: Pt3 = Pt3::new(
VERTICES[faces[1][1]][0],
VERTICES[faces[1][1]][1],
VERTICES[faces[1][1]][2],
);
let d: Pt3 = Pt3::new(
VERTICES[faces[1][0]][0],
VERTICES[faces[1][0]][1],
VERTICES[faces[1][0]][2],
);
let e: Pt3 = Pt3::new(
VERTICES[faces[3][1]][0],
VERTICES[faces[3][1]][1],
VERTICES[faces[3][1]][2],
);
let f: Pt3 = Pt3::new(
VERTICES[faces[3][0]][0],
VERTICES[faces[3][0]][1],
VERTICES[faces[3][0]][2],
);
// Triangle 0
let ab = b - a;
let ac = c - a;
// Triangle 1
let db = b - d;
let dc = c - d;
// Triangle 2
let cd = d - c;
let ce = e - c;
// Triangle 3
let fd = d - f;
let fe = e - f;
// It'll be easier to do the math we need here if the positions
// lie between (0, 0) and (1, 2).
pt_in_root_quad[1] *= 2.0;
// Decide which triangle we're in.
let pos_on_icosahedron = if pt_in_root_quad[0] + pt_in_root_quad[1] < 1.0 {
// In triangle 0.
a + ab * pt_in_root_quad[0] + ac * pt_in_root_quad[1]
} else if pt_in_root_quad[1] < 1.0 {
// In triangle 1.
d + dc * (1.0 - pt_in_root_quad[0]) + db * (1.0 - pt_in_root_quad[1])
} else if pt_in_root_quad[0] + pt_in_root_quad[1] < 2.0 {
// In triangle 2.
// Bring the y-value back into [0, 1] so we can just repeat the math from above.
pt_in_root_quad[1] -= 1.0;
c + cd * pt_in_root_quad[0] + ce * pt_in_root_quad[1]
} else {
// In triangle 3.
// Bring the y-value back into [0, 1] so we can just repeat the math from above.
pt_in_root_quad[1] -= 1.0;
f + fe * (1.0 - pt_in_root_quad[0]) + fd * (1.0 - pt_in_root_quad[1])
};
Pt3::from_coordinates(pos_on_icosahedron.coords.normalize())
}
/// Calculate the origin of a chunk that contains the given `pos`,
/// with the guarantee that the chunk will be in the same root even
/// if `pos` is on the edge of that root.
///
/// Note that this pays no attention to what chunk _owns_ the cell,
/// so you should assume that any chunk in this root that contains
/// the position at all may be returned.
pub fn origin_of_chunk_in_same_root_containing(
pos: GridPoint3,
root_resolution: [GridCoord; 2],
chunk_resolution: [GridCoord; 3],
) -> ChunkOrigin {
// Calculate x-position of a containing chunk.
let end_x = root_resolution[0];
let chunk_origin_x = if pos.x == end_x {
// Instead of trying to find a chunk beyond those that exist,
// just use the last chunk in the x-direction; `pos` is in that.
(end_x / chunk_resolution[0] - 1) * chunk_resolution[0]
} else {
pos.x / chunk_resolution[0] * chunk_resolution[0]
};
// Calculate y-position of a containing chunk.
let end_y = root_resolution[1];
let chunk_origin_y = if pos.y == end_y {
// Instead of trying to find a chunk beyond those that exist,
// just use the last chunk in the y-direction; `pos` is in that.
(end_y / chunk_resolution[1] - 1) * chunk_resolution[1]
} else {
pos.y / chunk_resolution[1] * chunk_resolution[1]
};
// Z-position is easy; there's no sharing of cells on the z-axis.
let chunk_origin_z = pos.z / chunk_resolution[2] * chunk_resolution[2];
ChunkOrigin::new(
GridPoint3::new(pos.root, chunk_origin_x, chunk_origin_y, chunk_origin_z),
root_resolution,
chunk_resolution,
)
}
pub fn origin_of_chunk_owning(
pos_in_owning_root: PosInOwningRoot,
root_resolution: [GridCoord; 2],
chunk_resolution: [GridCoord; 3],
) -> ChunkOrigin {
let pos: GridPoint3 = pos_in_owning_root.into();
// Figure out what chunk this is in.
let end_x = root_resolution[0];
let end_y = root_resolution[1];
let last_chunk_x = (end_x / chunk_resolution[0] - 1) * chunk_resolution[0];
let last_chunk_y = (end_y / chunk_resolution[1] - 1) * chunk_resolution[1];
// Cells aren't shared by chunks in the z-direction, so the z-origin
// is the same across all cases. Small mercies.
let chunk_origin_z = pos.z / chunk_resolution[2] * chunk_resolution[2];
if pos.x == 0 && pos.y == 0 {
// Chunk at (0, 0) owns north pole.
ChunkOrigin::new(
GridPoint3::new(pos.root, 0, 0, chunk_origin_z),
root_resolution,
chunk_resolution,
)
} else if pos.x == end_x && pos.y == end_y {
// Chunk at (last_chunk_x, last_chunk_y) owns south pole.
ChunkOrigin::new(
GridPoint3::new(pos.root, last_chunk_x, last_chunk_y, chunk_origin_z),
root_resolution,
chunk_resolution,
)
} else {
// Chunks own cells on their edge at `local_x == 0`, and their edge at `local_y == chunk_resolution`.
// The cells on other edges belong to adjacent chunks.
let chunk_origin_x = pos.x / chunk_resolution[0] * chunk_resolution[0];
// Shift everything down by one in y-direction.
let chunk_origin_y = (pos.y - 1) / chunk_resolution[1] * chunk_resolution[1];
ChunkOrigin::new(
GridPoint3::new(pos.root, chunk_origin_x, chunk_origin_y, chunk_origin_z),
root_resolution,
chunk_resolution,
)
}
}
pub fn is_point_on_chunk_edge(point: GridPoint3, chunk_resolution: [GridCoord; 3]) -> bool {
(point.x % chunk_resolution[0]) == point.x
||
(point.y % chunk_resolution[1]) == point.y
||
(point.z % chunk_resolution[2]) == point.z
||
// Last z-coordinate of cell is _just before_ the next multiple
// of chunk z-resolution.
((point.z + 1) % chunk_resolution[2]) == (point.z + 1)
}