This commit is contained in:
Patrick Walton 2019-01-25 14:28:53 -08:00
parent 7da8bdef89
commit 938bd30a78
10 changed files with 476 additions and 55 deletions

View File

@ -90,19 +90,19 @@ fn main() {
let window_size = Size2D::new(drawable_width, drawable_height);
let mut base_scene = load_scene(&options, &window_size);
let base_transform = Transform2DF32::from_scale(&Point2DF32::splat(1.0 / 800.0));
base_scene.transform(&base_transform);
while !exit {
let rotation = Transform3DF32::from_rotation(-camera_yaw, -camera_pitch, 0.0);
camera_position = camera_position + rotation.transform_point(camera_velocity);
let mut transform = Transform3DF32::from_perspective(FRAC_PI_4, 4.0 / 3.0, 0.01, 100.0);
transform =
transform.post_mul(&Transform3DF32::from_rotation(camera_yaw, camera_pitch, 0.0));
transform = transform.post_mul(&Transform3DF32::from_rotation(camera_yaw,
camera_pitch,
0.0));
transform = transform.post_mul(&Transform3DF32::from_translation(-camera_position.x(),
-camera_position.y(),
-camera_position.z()));
transform = transform.post_mul(&Transform3DF32::from_scale(1.0 / 800.0, 1.0 / 800.0, 1.0));
let perspective = Perspective::new(&transform, &window_size);
let mut scene = base_scene.clone();

View File

@ -10,7 +10,7 @@
use crate::line_segment::LineSegmentF32;
use crate::outline::{Contour, PointFlags};
use crate::point::Point2DF32;
use crate::point::{Point2DF32, Point3DF32};
use crate::segment::Segment;
use crate::simd::F32x4;
use crate::util::lerp;
@ -353,3 +353,82 @@ enum EdgeRelativeLocation {
Inside,
Outside,
}
// 3D quad clipping
pub struct PolygonClipper3D {
subject: Vec<Point3DF32>,
}
impl PolygonClipper3D {
#[inline]
pub fn new(subject: Vec<Point3DF32>) -> PolygonClipper3D {
PolygonClipper3D { subject }
}
pub fn clip(mut self) -> Vec<Point3DF32> {
// TODO(pcwalton): Fast path for completely contained polygon?
self.clip_against(Edge3D::Left);
self.clip_against(Edge3D::Right);
self.clip_against(Edge3D::Bottom);
self.clip_against(Edge3D::Top);
self.clip_against(Edge3D::Near);
self.clip_against(Edge3D::Far);
self.subject
}
fn clip_against(&mut self, edge: Edge3D) {
let input = mem::replace(&mut self.subject, vec![]);
let mut prev = match input.last() {
None => return,
Some(point) => *point,
};
for next in input {
if edge.point_is_inside(next) {
if !edge.point_is_inside(prev) {
self.subject.push(edge.line_intersection(prev, next));
}
self.subject.push(next);
} else if edge.point_is_inside(prev) {
self.subject.push(edge.line_intersection(prev, next));
}
prev = next;
}
}
}
#[derive(Clone, Copy)]
enum Edge3D {
Left,
Right,
Bottom,
Top,
Near,
Far
}
impl Edge3D {
#[inline]
fn point_is_inside(self, point: Point3DF32) -> bool {
match self {
Edge3D::Left => point.x() > -1.0, Edge3D::Right => point.x() < 1.0,
Edge3D::Bottom => point.y() > -1.0, Edge3D::Top => point.y() < 1.0,
Edge3D::Near => point.z() > -1.0, Edge3D::Far => point.z() < 1.0,
}
}
fn line_intersection(self, prev: Point3DF32, next: Point3DF32) -> Point3DF32 {
let (x0, x1) = match self {
Edge3D::Left | Edge3D::Right => (prev.x(), next.x()),
Edge3D::Bottom | Edge3D::Top => (prev.y(), next.y()),
Edge3D::Near | Edge3D::Far => (prev.z(), next.z()),
};
let x = match self {
Edge3D::Left | Edge3D::Bottom | Edge3D::Near => -1.0,
Edge3D::Right | Edge3D::Top | Edge3D::Far => 1.0,
};
prev.lerp(next, (x0 - x) / (x1 - x0))
}
}

View File

@ -21,7 +21,7 @@ pub struct LineSegmentF32(pub F32x4);
impl LineSegmentF32 {
#[inline]
pub fn new(from: &Point2DF32, to: &Point2DF32) -> LineSegmentF32 {
LineSegmentF32(from.0.as_f64x2().interleave(to.0.as_f64x2()).0.as_f32x4())
LineSegmentF32(from.0.combine_axaybxby(to.0))
}
#[inline]
@ -36,12 +36,12 @@ impl LineSegmentF32 {
#[inline]
pub fn set_from(&mut self, point: &Point2DF32) {
self.0 = point.0.as_f64x2().combine_low_high(self.0.as_f64x2()).as_f32x4()
self.0 = point.0.combine_axaybzbw(self.0)
}
#[inline]
pub fn set_to(&mut self, point: &Point2DF32) {
self.0 = self.0.as_f64x2().interleave(point.0.as_f64x2()).0.as_f32x4()
self.0 = self.0.combine_axaybxby(point.0)
}
#[allow(clippy::wrong_self_convention)]
@ -97,8 +97,8 @@ impl LineSegmentF32 {
let (from_from, to_to) = (self.0.xyxy(), self.0.zwzw());
let d_d = to_to - from_from;
let mid_mid = from_from + d_d * F32x4::splat(t);
(LineSegmentF32(from_from.as_f64x2().interleave(mid_mid.as_f64x2()).0.as_f32x4()),
LineSegmentF32(mid_mid.as_f64x2().interleave(to_to.as_f64x2()).0.as_f32x4()))
(LineSegmentF32(from_from.combine_axaybxby(mid_mid)),
LineSegmentF32(mid_mid.combine_axaybxby(to_to)))
}
// Returns the left segment first, followed by the right segment.

View File

@ -302,7 +302,7 @@ impl Contour {
#[inline]
pub fn apply_perspective(&mut self, perspective: &Perspective) {
for (point_index, point) in self.points.iter_mut().enumerate() {
*point = perspective.transform_point(point);
*point = perspective.transform_point_2d(point);
union_rect(&mut self.bounds, *point, point_index == 0);
}

View File

@ -105,6 +105,55 @@ impl Mul<Point2DF32> for Point2DF32 {
}
}
// 3D points.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct Point3DF32(pub F32x4);
impl Point3DF32 {
#[inline]
pub fn new(x: f32, y: f32, z: f32) -> Point3DF32 {
Point3DF32(F32x4::new(x, y, z, 1.0))
}
#[inline]
pub fn from_euclid_2d(point: &Point2D<f32>) -> Point3DF32 {
Point3DF32::new(point.x, point.y, 0.0)
}
#[inline]
pub fn x(self) -> f32 {
self.0[0]
}
#[inline]
pub fn y(self) -> f32 {
self.0[1]
}
#[inline]
pub fn z(self) -> f32 {
self.0[2]
}
#[inline]
pub fn to_2d(self) -> Point2DF32 {
Point2DF32(self.0)
}
#[inline]
pub fn to_4d(self) -> Point4DF32 {
let mut point = Point4DF32(self.0);
point.set_w(1.0);
point
}
#[inline]
pub fn lerp(self, other: Point3DF32, t: f32) -> Point3DF32 {
Point3DF32(self.0 + (other.0 - self.0) * F32x4::splat(t))
}
}
// 3D homogeneous points.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
@ -167,8 +216,13 @@ impl Point4DF32 {
}
#[inline]
pub fn perspective_divide(self) -> Point4DF32 {
self * Point4DF32::splat(1.0 / self.w())
pub fn perspective_divide(self) -> Point3DF32 {
Point3DF32(self.0 * F32x4::splat(1.0 / self.w()))
}
#[inline]
pub fn approx_eq(&self, other: &Point4DF32, epsilon: f32) -> bool {
self.0.approx_eq(other.0, epsilon)
}
}

View File

@ -196,12 +196,12 @@ impl<'s> CubicSegment<'s> {
let tttt = F32x4::splat(t);
let (p0p3, p1p2) = (self.0.baseline.0, self.0.ctrl.0);
let p0p1 = p0p3.as_f64x2().interleave(p1p2.as_f64x2()).0.as_f32x4();
let p0p1 = p0p3.combine_axaybxby(p1p2);
// p01 = lerp(p0, p1, t), p12 = lerp(p1, p2, t), p23 = lerp(p2, p3, t)
let p01p12 = p0p1 + tttt * (p1p2 - p0p1);
let pxxp23 = p1p2 + tttt * (p0p3 - p1p2);
let p12p23 = p01p12.as_f64x2().interleave(pxxp23.as_f64x2()).1.as_f32x4();
let p12p23 = p01p12.combine_azawbzbw(pxxp23);
// p012 = lerp(p01, p12, t), p123 = lerp(p12, p23, t)
let p012p123 = p01p12 + tttt * (p12p23 - p01p12);
@ -210,10 +210,10 @@ impl<'s> CubicSegment<'s> {
// p0123 = lerp(p012, p123, t)
let p0123 = p012p123 + tttt * (p123 - p012p123);
let baseline0 = p0p3.as_f64x2().interleave(p0123.as_f64x2()).0.as_f32x4();
let ctrl0 = p01p12.as_f64x2().interleave(p012p123.as_f64x2()).0.as_f32x4();
let baseline1 = p0123.as_f64x2().combine_low_high(p0p3.as_f64x2()).as_f32x4();
let ctrl1 = p012p123.as_f64x2().interleave(p12p23.as_f64x2()).1.as_f32x4();
let baseline0 = p0p3.combine_axaybxby(p0123);
let ctrl0 = p01p12.combine_axaybxby(p012p123);
let baseline1 = p0123.combine_axaybzbw(p0p3);
let ctrl1 = p012p123.combine_azawbzbw(p12p23);
(Segment {
baseline: LineSegmentF32(baseline0),

View File

@ -337,11 +337,11 @@ mod scalar {
#[cfg(all(not(feature = "pf-no-simd"), any(target_arch = "x86", target_arch = "x86_64")))]
mod x86 {
use std::arch::x86_64::{self, __m128, __m128d, __m128i};
use std::arch::x86_64::{self, __m128, __m128i};
use std::cmp::PartialEq;
use std::fmt::{self, Debug, Formatter};
use std::mem;
use std::ops::{Add, AddAssign, Index, IndexMut, Mul, Sub};
use std::ops::{Add, AddAssign, Index, IndexMut, Mul, Neg, Sub};
// 32-bit floats
@ -372,6 +372,14 @@ mod x86 {
unsafe { F32x4(x86_64::_mm_max_ps(self.0, other.0)) }
}
#[inline]
pub fn abs(self) -> F32x4 {
unsafe {
let tmp = x86_64::_mm_srli_epi32(I32x4::splat(-1).0, 1);
F32x4(x86_64::_mm_and_ps(x86_64::_mm_castsi128_ps(tmp), self.0))
}
}
#[inline]
pub fn packed_eq(self, other: F32x4) -> U32x4 {
unsafe {
@ -381,13 +389,18 @@ mod x86 {
}
}
// Casts these packed floats to 64-bit floats.
//
// NB: This is a pure bitcast and does no actual conversion; only use this if you know what
// you're doing.
#[inline]
pub fn as_f64x2(self) -> F64x2 {
unsafe { F64x2(x86_64::_mm_castps_pd(self.0)) }
pub fn packed_gt(self, other: F32x4) -> U32x4 {
unsafe {
U32x4(x86_64::_mm_castps_si128(x86_64::_mm_cmpgt_ps(
self.0, other.0,
)))
}
}
#[inline]
pub fn approx_eq(self, other: F32x4, epsilon: f32) -> bool {
(self - other).abs().packed_gt(F32x4::splat(epsilon)).is_all_zeroes()
}
// Converts these packed floats to integers.
@ -403,6 +416,11 @@ mod x86 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, self.0, 0b0101_0000)) }
}
#[inline]
pub fn xxzz(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, self.0, 0b1010_0000)) }
}
#[inline]
pub fn xyxy(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, self.0, 0b0100_0100)) }
@ -433,6 +451,11 @@ mod x86 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, self.0, 0b1011_0001)) }
}
#[inline]
pub fn yyww(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, self.0, 0b1111_0101)) }
}
#[inline]
pub fn yzxw(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, self.0, 0b1100_1001)) }
@ -474,15 +497,60 @@ mod x86 {
}
#[inline]
pub fn interleave(self, other: F32x4) -> (F32x4, F32x4) {
pub fn wyzx(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, self.0, 0b0010_0111)) }
}
#[inline]
pub fn combine_axbxayby(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_unpacklo_ps(self.0, other.0)) }
}
#[inline]
pub fn combine_axaybxby(self, other: F32x4) -> F32x4 {
unsafe {
(
F32x4(x86_64::_mm_unpacklo_ps(self.0, other.0)),
F32x4(x86_64::_mm_unpackhi_ps(self.0, other.0)),
)
let this = x86_64::_mm_castps_pd(self.0);
let other = x86_64::_mm_castps_pd(other.0);
let result = x86_64::_mm_unpacklo_pd(this, other);
F32x4(x86_64::_mm_castpd_ps(result))
}
}
#[inline]
pub fn combine_axaybzbw(self, other: F32x4) -> F32x4 {
unsafe {
let this = x86_64::_mm_castps_pd(self.0);
let other = x86_64::_mm_castps_pd(other.0);
let result = x86_64::_mm_shuffle_pd(this, other, 0b10);
F32x4(x86_64::_mm_castpd_ps(result))
}
}
#[inline]
pub fn combine_axazbxbz(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, other.0, 0b1000_1000)) }
}
#[inline]
pub fn combine_ayawbybw(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, other.0, 0b1101_1101)) }
}
#[inline]
pub fn combine_azawbzbw(self, other: F32x4) -> F32x4 {
unsafe {
let this = x86_64::_mm_castps_pd(self.0);
let other = x86_64::_mm_castps_pd(other.0);
let result = x86_64::_mm_unpackhi_pd(this, other);
F32x4(x86_64::_mm_castpd_ps(result))
}
}
#[inline]
pub fn combine_azbzawbw(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_unpackhi_ps(self.0, other.0)) }
}
#[inline]
pub fn transpose_4x4(a: &mut F32x4, b: &mut F32x4, c: &mut F32x4, d: &mut F32x4) {
unsafe {
@ -490,6 +558,7 @@ mod x86 {
}
}
// FIXME(pcwalton): Move to `Point4DF32`!
#[inline]
pub fn cross(&self, other: F32x4) -> F32x4 {
self.yzxw() * other.zxyw() - self.zxyw() * other.yzxw()
@ -563,10 +632,19 @@ mod x86 {
}
}
// 64-bit floats
impl Neg for F32x4 {
type Output = F32x4;
#[inline]
fn neg(self) -> F32x4 {
F32x4::default() - self
}
}
/*
// Two pairs of 32-bit floats
#[derive(Clone, Copy)]
pub struct F64x2(pub __m128d);
pub struct F64x2x2(pub __m128d);
impl F64x2 {
// Shuffles
@ -600,6 +678,7 @@ mod x86 {
}
}
}
*/
// 32-bit signed integers
@ -657,6 +736,11 @@ mod x86 {
fn is_all_ones(&self) -> bool {
unsafe { x86_64::_mm_test_all_ones(self.0) != 0 }
}
#[inline]
fn is_all_zeroes(&self) -> bool {
unsafe { x86_64::_mm_test_all_zeros(self.0, self.0) != 0 }
}
}
impl Index<usize> for U32x4 {

View File

@ -13,14 +13,89 @@
use crate::point::Point2DF32;
use crate::segment::Segment;
use crate::simd::F32x4;
use crate::transform3d::Transform3DF32;
use euclid::{Point2D, Rect, Size2D, Transform2D};
use lyon_path::PathEvent;
use std::ops::Sub;
/// A 2x2 matrix, optimized with SIMD, in column-major order.
#[derive(Clone, Copy, Debug)]
pub struct Matrix2x2F32(pub F32x4);
impl Default for Matrix2x2F32 {
#[inline]
fn default() -> Matrix2x2F32 {
Self::from_scale(&Point2DF32::splat(1.0))
}
}
impl Matrix2x2F32 {
#[inline]
pub fn from_scale(scale: &Point2DF32) -> Matrix2x2F32 {
Matrix2x2F32(F32x4::new(scale.x(), 0.0, 0.0, scale.y()))
}
#[inline]
pub fn from_rotation(theta: f32) -> Matrix2x2F32 {
let (sin_theta, cos_theta) = (theta.sin(), theta.cos());
Matrix2x2F32(F32x4::new(cos_theta, sin_theta, -sin_theta, cos_theta))
}
#[inline]
pub fn row_major(m11: f32, m12: f32, m21: f32, m22: f32) -> Matrix2x2F32 {
Matrix2x2F32(F32x4::new(m11, m21, m12, m22))
}
#[inline]
pub fn post_mul(&self, other: &Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0.xyxy() * other.0.xxzz() + self.0.zwzw() * other.0.yyww())
}
#[inline]
pub fn pre_mul(&self, other: &Matrix2x2F32) -> Matrix2x2F32 {
other.post_mul(self)
}
#[inline]
pub fn entrywise_mul(&self, other: &Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0 * other.0)
}
#[inline]
pub fn adjugate(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.0.wyzx() * F32x4::new(1.0, -1.0, -1.0, 1.0))
}
#[inline]
pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 {
let halves = self.0 * point.0.xxyy();
Point2DF32(halves + halves.zwzw())
}
#[inline]
pub fn det(&self) -> f32 {
self.0[0] * self.0[3] - self.0[2] * self.0[1]
}
#[inline]
pub fn inverse(&self) -> Matrix2x2F32 {
Matrix2x2F32(F32x4::splat(1.0 / self.det()) * self.adjugate().0)
}
}
impl Sub<Matrix2x2F32> for Matrix2x2F32 {
type Output = Matrix2x2F32;
#[inline]
fn sub(self, other: Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0 - other.0)
}
}
/// An affine transform, optimized with SIMD.
#[derive(Clone, Copy, Debug)]
pub struct Transform2DF32 {
// Row-major order.
matrix: F32x4,
matrix: Matrix2x2F32,
vector: Point2DF32,
}
@ -35,16 +110,15 @@ impl Transform2DF32 {
#[inline]
pub fn from_scale(scale: &Point2DF32) -> Transform2DF32 {
Transform2DF32 {
matrix: F32x4::new(scale.x(), 0.0, 0.0, scale.y()),
matrix: Matrix2x2F32::from_scale(scale),
vector: Point2DF32::default(),
}
}
#[inline]
pub fn from_rotation(theta: f32) -> Transform2DF32 {
let (sin_theta, cos_theta) = (theta.sin(), theta.cos());
Transform2DF32 {
matrix: F32x4::new(cos_theta, -sin_theta, sin_theta, cos_theta),
matrix: Matrix2x2F32::from_rotation(theta),
vector: Point2DF32::default(),
}
}
@ -52,7 +126,7 @@ impl Transform2DF32 {
#[inline]
pub fn from_translation(vector: &Point2DF32) -> Transform2DF32 {
Transform2DF32 {
matrix: F32x4::new(1.0, 0.0, 0.0, 1.0),
matrix: Matrix2x2F32::default(),
vector: *vector,
}
}
@ -61,15 +135,14 @@ impl Transform2DF32 {
pub fn row_major(m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32)
-> Transform2DF32 {
Transform2DF32 {
matrix: F32x4::new(m11, m12, m21, m22),
matrix: Matrix2x2F32::row_major(m11, m12, m21, m22),
vector: Point2DF32::new(m31, m32),
}
}
#[inline]
pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 {
let bxbzbybw = point.0.xxyy() * self.matrix.xzyw();
Point2DF32(bxbzbybw + bxbzbybw.zwzw() + self.vector.0)
self.matrix.transform_point(point) + self.vector
}
// TODO(pcwalton): SIMD.
@ -89,9 +162,7 @@ impl Transform2DF32 {
#[inline]
pub fn post_mul(&self, other: &Transform2DF32) -> Transform2DF32 {
let lhs = self.matrix.xwxw() * other.matrix;
let rhs = self.matrix.zyzy() * other.matrix.yxwz();
let matrix = lhs + rhs;
let matrix = self.matrix.post_mul(&other.matrix);
let vector = other.transform_point(&self.vector);
Transform2DF32 { matrix, vector }
}
@ -100,6 +171,15 @@ impl Transform2DF32 {
pub fn pre_mul(&self, other: &Transform2DF32) -> Transform2DF32 {
other.post_mul(self)
}
// TODO(pcwalton): Optimize better with SIMD.
#[inline]
pub fn to_3d(&self) -> Transform3DF32 {
Transform3DF32::row_major(self.matrix.0[0], self.matrix.0[1], 0.0, self.vector.x(),
self.matrix.0[2], self.matrix.0[3], 0.0, self.vector.y(),
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0)
}
}
/// Transforms a path with a SIMD 2D transform.

View File

@ -10,10 +10,12 @@
//! 3D transforms that can be applied to paths.
use crate::point::{Point2DF32, Point4DF32};
use crate::point::{Point2DF32, Point3DF32, Point4DF32};
use crate::segment::Segment;
use crate::simd::F32x4;
use crate::transform::Matrix2x2F32;
use euclid::{Point2D, Rect, Size2D};
use std::ops::{Add, Neg};
/// An transform, optimized with SIMD.
///
@ -121,6 +123,21 @@ impl Transform3DF32 {
0.0, 0.0, m32, 0.0)
}
// +- -+
// | A B |
// | C D |
// +- -+
#[inline]
pub fn from_submatrices(a: Matrix2x2F32, b: Matrix2x2F32, c: Matrix2x2F32, d: Matrix2x2F32)
-> Transform3DF32 {
Transform3DF32 {
c0: a.0.combine_axaybxby(c.0),
c1: a.0.combine_azawbzbw(c.0),
c2: b.0.combine_axaybxby(d.0),
c3: b.0.combine_azawbzbw(d.0),
}
}
#[inline]
pub fn transpose(&self) -> Transform3DF32 {
let mut m = *self;
@ -161,6 +178,69 @@ impl Transform3DF32 {
let term3 = self.c3 * F32x4::splat(point.w());
Point4DF32(term0 + term1 + term2 + term3)
}
#[inline]
pub fn upper_left(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c0.combine_axaybxby(self.c1))
}
#[inline]
pub fn upper_right(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c2.combine_axaybxby(self.c3))
}
#[inline]
pub fn lower_left(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c0.combine_azawbzbw(self.c1))
}
#[inline]
pub fn lower_right(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c2.combine_azawbzbw(self.c3))
}
// https://en.wikipedia.org/wiki/Invertible_matrix#Blockwise_inversion
pub fn inverse(&self) -> Transform3DF32 {
// Extract submatrices.
let (a, b) = (self.upper_left(), self.upper_right());
let (c, d) = (self.lower_left(), self.lower_right());
// Compute temporary matrices.
let a_inv = a.inverse();
let x = c.post_mul(&a_inv);
let y = (d - x.post_mul(&b)).inverse();
let z = a_inv.post_mul(&b);
// Compute new submatrices.
let (a_new, b_new) = (a_inv + z.post_mul(&y).post_mul(&x), (-z).post_mul(&y));
let (c_new, d_new) = ((-y).post_mul(&x), y);
// Construct inverse.
Transform3DF32::from_submatrices(a_new, b_new, c_new, d_new)
}
pub fn approx_eq(&self, other: &Transform3DF32, epsilon: f32) -> bool {
self.c0.approx_eq(other.c0, epsilon) &&
self.c1.approx_eq(other.c1, epsilon) &&
self.c2.approx_eq(other.c2, epsilon) &&
self.c3.approx_eq(other.c3, epsilon)
}
}
impl Add<Matrix2x2F32> for Matrix2x2F32 {
type Output = Matrix2x2F32;
#[inline]
fn add(self, other: Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0 + other.0)
}
}
impl Neg for Matrix2x2F32 {
type Output = Matrix2x2F32;
#[inline]
fn neg(self) -> Matrix2x2F32 {
Matrix2x2F32(-self.0)
}
}
#[derive(Clone, Copy, Debug)]
@ -176,20 +256,25 @@ impl Perspective {
}
#[inline]
pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 {
pub fn transform_point_2d(&self, point: &Point2DF32) -> Point2DF32 {
let point = self.transform.transform_point(point.to_4d()).perspective_divide().to_2d();
let window_size = self.window_size.to_f32();
let size_scale = Point2DF32::new(window_size.width * 0.5, window_size.height * 0.5);
(point + Point2DF32::splat(1.0)) * size_scale
}
#[inline]
pub fn transform_point_3d(&self, point: &Point3DF32) -> Point3DF32 {
self.transform.transform_point(point.to_4d()).perspective_divide()
}
// TODO(pcwalton): SIMD?
#[inline]
pub fn transform_rect(&self, rect: &Rect<f32>) -> Rect<f32> {
let upper_left = self.transform_point(&Point2DF32::from_euclid(rect.origin));
let upper_right = self.transform_point(&Point2DF32::from_euclid(rect.top_right()));
let lower_left = self.transform_point(&Point2DF32::from_euclid(rect.bottom_left()));
let lower_right = self.transform_point(&Point2DF32::from_euclid(rect.bottom_right()));
let upper_left = self.transform_point_2d(&Point2DF32::from_euclid(rect.origin));
let upper_right = self.transform_point_2d(&Point2DF32::from_euclid(rect.top_right()));
let lower_left = self.transform_point_2d(&Point2DF32::from_euclid(rect.bottom_left()));
let lower_right = self.transform_point_2d(&Point2DF32::from_euclid(rect.bottom_right()));
let min_x = upper_left.x().min(upper_right.x()).min(lower_left.x()).min(lower_right.x());
let min_y = upper_left.y().min(upper_right.y()).min(lower_left.y()).min(lower_right.y());
let max_x = upper_left.x().max(upper_right.x()).max(lower_left.x()).max(lower_right.x());
@ -218,12 +303,13 @@ where
fn next(&mut self) -> Option<Segment> {
let mut segment = self.iter.next()?;
if !segment.is_none() {
segment.baseline.set_from(&self.perspective.transform_point(&segment.baseline.from()));
segment.baseline.set_to(&self.perspective.transform_point(&segment.baseline.to()));
segment.baseline.set_from(&self.perspective
.transform_point_2d(&segment.baseline.from()));
segment.baseline.set_to(&self.perspective.transform_point_2d(&segment.baseline.to()));
if !segment.is_line() {
segment.ctrl.set_from(&self.perspective.transform_point(&segment.ctrl.from()));
segment.ctrl.set_from(&self.perspective.transform_point_2d(&segment.ctrl.from()));
if !segment.is_quadratic() {
segment.ctrl.set_to(&self.perspective.transform_point(&segment.ctrl.to()));
segment.ctrl.set_to(&self.perspective.transform_point_2d(&segment.ctrl.to()));
}
}
}
@ -303,4 +389,24 @@ mod test {
5.0, 5.0, 9.0, 2.0);
assert_eq!(a.transpose(), b);
}
#[test]
fn test_inverse() {
// Random matrix.
let m = Transform3DF32::row_major(0.86277982, 0.15986552, 0.90739898, 0.60066808,
0.17386167, 0.016353 , 0.8535783 , 0.12969608,
0.0946466 , 0.43248631, 0.63480505, 0.08154603,
0.50305436, 0.48359687, 0.51057162, 0.24812012);
let p0 = Point4DF32::new(0.95536648, 0.80633691, 0.16357357, 0.5477598);
let p1 = m.transform_point(p0);
let m_inv = m.inverse();
let m_inv_exp =
Transform3DF32::row_major(-2.47290136 , 3.48865688, -6.12298336 , 6.17536696 ,
0.00124033357, -1.72561993, 2.16876606 , 0.186227748,
-0.375021729 , 1.53883017, -0.0558194403, 0.121857058,
5.78300323 , -6.87635769, 8.30196620 , -9.10374060);
assert!(m_inv.approx_eq(&m_inv_exp, 0.0001));
let p2 = m_inv.transform_point(p1);
assert!(p0.approx_eq(&p2, 0.0001));
}
}

View File

@ -16,7 +16,9 @@ use crate::tiles::Tiler;
use crate::z_buffer::ZBuffer;
use euclid::Rect;
use hashbrown::HashMap;
use pathfinder_geometry::clip::PolygonClipper3D;
use pathfinder_geometry::outline::Outline;
use pathfinder_geometry::point::Point3DF32;
use pathfinder_geometry::transform3d::Perspective;
use pathfinder_geometry::transform::Transform2DF32;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
@ -117,6 +119,9 @@ impl Scene {
}
pub fn apply_perspective(&mut self, perspective: &Perspective) {
let quad = self.clip_bounding_quad_with_perspective(perspective);
println!("bounds={:?} quad={:?}", self.bounds, quad);
let mut bounds = Rect::zero();
for (object_index, object) in self.objects.iter_mut().enumerate() {
object.outline.apply_perspective(perspective);
@ -132,6 +137,19 @@ impl Scene {
//println!("new bounds={:?}", bounds);
self.bounds = bounds;
}
fn clip_bounding_quad_with_perspective(&self, perspective: &Perspective) -> Vec<Point3DF32> {
let mut points = vec![
Point3DF32::from_euclid_2d(&self.bounds.origin),
Point3DF32::from_euclid_2d(&self.bounds.top_right()),
Point3DF32::from_euclid_2d(&self.bounds.bottom_right()),
Point3DF32::from_euclid_2d(&self.bounds.bottom_left()),
];
for point in &mut points {
*point = perspective.transform_point_3d(point);
}
PolygonClipper3D::new(points).clip()
}
}
#[derive(Clone, Debug)]