//! Point projection. use super::graphics::{CoordAxis, GraphicsState}; use raw::types::{F26Dot6, Point}; impl GraphicsState<'_> { /// Updates cached state that is derived from projection vectors. pub fn update_projection_state(&mut self) { // 1.0 in 2.14 fixed point. const ONE: i32 = 0x4000; // Based on Compute_Funcs() at // . // FreeType uses function pointers to select between various "modes" // but we use the CoordAxis type instead. if self.freedom_vector.x == ONE { self.fdotp = self.proj_vector.x; } else if self.freedom_vector.y == ONE { self.fdotp = self.proj_vector.y; } else { let px = self.proj_vector.x; let py = self.proj_vector.y; let fx = self.freedom_vector.x; let fy = self.freedom_vector.y; self.fdotp = (px * fx + py * fy) >> 14; } self.proj_axis = CoordAxis::Both; if self.proj_vector.x == ONE { self.proj_axis = CoordAxis::X; } else if self.proj_vector.y == ONE { self.proj_axis = CoordAxis::Y; } self.dual_proj_axis = CoordAxis::Both; if self.dual_proj_vector.x == ONE { self.dual_proj_axis = CoordAxis::X; } else if self.dual_proj_vector.y == ONE { self.dual_proj_axis = CoordAxis::Y; } self.freedom_axis = CoordAxis::Both; if self.fdotp == ONE { if self.freedom_vector.x == ONE { self.freedom_axis = CoordAxis::X; } else if self.freedom_vector.y == ONE { self.freedom_axis = CoordAxis::Y; } } // At small sizes, fdotp can become too small resulting in overflows // and spikes. if self.fdotp.abs() < 0x400 { self.fdotp = ONE; } } /// Computes the projection of vector given by (v1 - v2) along the /// current projection vector. #[inline(always)] pub fn project(&self, v1: Point, v2: Point) -> F26Dot6 { match self.proj_axis { // CoordAxis::X => v1.x - v2.x, // CoordAxis::Y => v1.y - v2.y, CoordAxis::Both => { // let dx = v1.x - v2.x; let dy = v1.y - v2.y; F26Dot6::from_bits(dot14( dx.to_bits(), dy.to_bits(), self.proj_vector.x, self.proj_vector.y, )) } } } /// Computes the projection of vector given by (v1 - v2) along the /// current dual projection vector. #[inline(always)] pub fn dual_project(&self, v1: Point, v2: Point) -> F26Dot6 { match self.dual_proj_axis { // CoordAxis::X => v1.x - v2.x, // CoordAxis::Y => v1.y - v2.y, CoordAxis::Both => { // https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2402 let dx = v1.x - v2.x; let dy = v1.y - v2.y; F26Dot6::from_bits(dot14( dx.to_bits(), dy.to_bits(), self.dual_proj_vector.x, self.dual_proj_vector.y, )) } } } /// Computes the projection of vector given by (v1 - v2) along the /// current dual projection vector for unscaled points. #[inline(always)] pub fn dual_project_unscaled(&self, v1: Point, v2: Point) -> i32 { match self.dual_proj_axis { // CoordAxis::X => v1.x - v2.x, // CoordAxis::Y => v1.y - v2.y, CoordAxis::Both => { // https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2402 let dx = v1.x - v2.x; let dy = v1.y - v2.y; dot14(dx, dy, self.dual_proj_vector.x, self.dual_proj_vector.y) } } } } /// Dot product for vectors in 2.14 fixed point. fn dot14(ax: i32, ay: i32, bx: i32, by: i32) -> i32 { let mut v1 = ax as i64 * bx as i64; let v2 = ay as i64 * by as i64; v1 += v2; v1 += 0x2000 + (v1 >> 63); (v1 >> 14) as i32 } #[cfg(test)] mod tests { use super::{super::math, CoordAxis, F26Dot6, GraphicsState, Point}; #[test] fn project_one_axis() { let mut state = GraphicsState { proj_vector: math::normalize14(1, 0), ..Default::default() }; state.update_projection_state(); assert_eq!(state.proj_axis, CoordAxis::X); assert_eq!(state.proj_vector, Point::new(0x4000, 0)); let cases = &[ (Point::new(0, 0), Point::new(0, 0), 0), (Point::new(100, 100), Point::new(0, 0), 100), (Point::new(42, 100), Point::new(100, 0), -58), (Point::new(0, 0), Point::new(100, 100), -100), ]; test_project_cases(&state, cases); } #[test] fn project_both_axes() { let mut state = GraphicsState { proj_vector: math::normalize14(0x4000, 0x4000), ..Default::default() }; state.update_projection_state(); assert_eq!(state.proj_axis, CoordAxis::Both); let cases = &[ (Point::new(0, 0), Point::new(0, 0), 0), (Point::new(100, 100), Point::new(0, 0), 141), (Point::new(42, 100), Point::new(100, 0), 30), (Point::new(0, 0), Point::new(100, 100), -141), ]; test_project_cases(&state, cases); } fn test_project_cases(state: &GraphicsState, cases: &[(Point, Point, i32)]) { for (v1, v2, expected) in cases.iter().copied() { let v1 = v1.map(F26Dot6::from_bits); let v2 = v2.map(F26Dot6::from_bits); let result = state.project(v1, v2).to_bits(); assert_eq!( result, expected, "project({v1:?}, {v2:?}) = {result} (expected {expected})" ); } } }