#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Point {
pub fn new(x: f32, y: f32) -> Point {
Point { x, y }
}
}
impl Vec2 {
pub fn new(x: f32, y: f32) -> Vec2 {
Vec2 { x, y }
}
}
impl std::ops::Add<Vec2> for Point {
type Output = Point;
fn add(self, v: Vec2) -> Point {
Point::new(self.x + v.x, self.y + v.y)
}
}
pub const TRI_ROW_HEIGHT: f32 = 0.866_025_4;
pub const TRI_HALF_BASE: f32 = 0.5;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CellShape {
Square,
UpTriangle,
DownTriangle,
}
impl CellShape {
pub fn size(self) -> Vec2 {
match self {
CellShape::Square => Vec2::new(1.0, 1.0),
CellShape::UpTriangle | CellShape::DownTriangle => Vec2::new(1.0, TRI_ROW_HEIGHT),
}
}
pub fn vertices(self, origin: Point) -> ([Point; 4], usize) {
self.vertices_sized(origin, self.size())
}
pub fn vertices_sized(self, origin: Point, size: Vec2) -> ([Point; 4], usize) {
let (x, y) = (origin.x, origin.y);
let (w, h) = (size.x, size.y);
match self {
CellShape::Square => (
[
Point::new(x, y),
Point::new(x + w, y),
Point::new(x + w, y + h),
Point::new(x, y + h),
],
4,
),
CellShape::UpTriangle => (
[
Point::new(x + w / 2.0, y),
Point::new(x + w, y + h),
Point::new(x, y + h),
Point::default(),
],
3,
),
CellShape::DownTriangle => (
[
Point::new(x, y),
Point::new(x + w, y),
Point::new(x + w / 2.0, y + h),
Point::default(),
],
3,
),
}
}
pub fn family_edge(self, origin: Point, family: usize, near: bool) -> (Point, Point) {
let (p, _) = self.vertices(origin);
match self {
CellShape::Square => match (family, near) {
(0, true) => (p[0], p[1]), (0, false) => (p[3], p[2]), (_, true) => (p[3], p[0]), (_, false) => (p[1], p[2]), },
CellShape::UpTriangle => match family {
0 => (p[1], p[2]), 1 => (p[2], p[0]), _ => (p[0], p[1]), },
CellShape::DownTriangle => match family {
0 => (p[0], p[1]), 1 => (p[1], p[2]), _ => (p[2], p[0]), },
}
}
pub fn triangle_edge_is_near(self, family: usize) -> bool {
match self {
CellShape::UpTriangle => family == 1,
CellShape::DownTriangle => family != 1,
CellShape::Square => true,
}
}
pub fn center(self, origin: Point) -> Point {
let h = TRI_ROW_HEIGHT;
match self {
CellShape::Square => Point::new(origin.x + 0.5, origin.y + 0.5),
CellShape::UpTriangle => Point::new(origin.x + 0.5, origin.y + 2.0 * h / 3.0),
CellShape::DownTriangle => Point::new(origin.x + 0.5, origin.y + h / 3.0),
}
}
pub fn shrunk(self, origin: Point, factor: f32) -> ([Point; 4], usize) {
let c = self.center(origin);
let (mut points, n) = self.vertices(origin);
for p in points.iter_mut().take(n) {
p.x = c.x + (p.x - c.x) * factor;
p.y = c.y + (p.y - c.y) * factor;
}
(points, n)
}
pub fn contains(self, origin: Point, p: Point) -> bool {
let (points, n) = self.vertices(origin);
let mut sign = 0.0f32;
for i in 0..n {
let (a, b) = (points[i], points[(i + 1) % n]);
let cross = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
if cross != 0.0 {
if sign == 0.0 {
sign = cross.signum();
} else if cross.signum() != sign {
return false;
}
}
}
true
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Guide {
pub from: Point,
pub to: Point,
pub family: usize,
pub index: usize,
pub emphasis: bool,
}
pub const CLUE_BOX: f32 = 0.7;
pub const CLUE_BOX_SHORT: f32 = 0.95;
pub const CLUE_GAP: f32 = 0.18;
pub const CLUE_PAD: f32 = 0.9;
pub const ANALYSIS_MARK_RADIUS: f32 = 0.2;
const _: () = assert!(2.0 * ANALYSIS_MARK_RADIUS < CLUE_PAD);
pub(crate) const TRI_LANE_DIR: [Vec2; 3] = [
Vec2 { x: 1.0, y: 0.0 },
Vec2 {
x: 0.5,
y: -TRI_ROW_HEIGHT,
},
Vec2 {
x: 0.5,
y: TRI_ROW_HEIGHT,
},
];
pub fn tri_clue_rhombus(
center: Point,
family: usize,
edge_dir: Vec2,
size: f32,
short: f32,
) -> [Point; 4] {
let dir = TRI_LANE_DIR[family];
let (ax, ay) = (dir.x * size / 2.0, dir.y * size / 2.0);
let (bx, by) = (edge_dir.x * short / 2.0, edge_dir.y * short / 2.0);
[
Point::new(center.x + ax + bx, center.y + ay + by),
Point::new(center.x - ax + bx, center.y - ay + by),
Point::new(center.x - ax - bx, center.y - ay - by),
Point::new(center.x + ax - bx, center.y + ay - by),
]
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct GutterLane {
pub lane: usize,
pub anchor: Point,
pub outward: Vec2,
pub edge_dir: Vec2,
pub reversed: bool,
}
impl GutterLane {
pub fn clue_box_center(&self, i: usize) -> Point {
let out = CLUE_PAD + CLUE_BOX / 2.0 + i as f32 * (CLUE_BOX + CLUE_GAP);
Point::new(
self.anchor.x + self.outward.x * out,
self.anchor.y + self.outward.y * out,
)
}
pub fn clue_run_length(count: usize) -> f32 {
if count == 0 {
0.0
} else {
CLUE_PAD + count as f32 * (CLUE_BOX + CLUE_GAP)
}
}
}