From 05d2b26fa7099edacf811c7ced1230c66845390c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Jan 2019 19:15:33 -0800 Subject: [PATCH] Implement some 3D transform code, untested as of yet --- geometry/src/lib.rs | 1 + geometry/src/monotonic.rs | 1 - geometry/src/point.rs | 63 ++++++++++++++++ geometry/src/simd.rs | 12 +++ geometry/src/transform3d.rs | 146 ++++++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 geometry/src/transform3d.rs diff --git a/geometry/src/lib.rs b/geometry/src/lib.rs index 0875dd24..6bb98066 100644 --- a/geometry/src/lib.rs +++ b/geometry/src/lib.rs @@ -28,4 +28,5 @@ pub mod segments; pub mod simd; pub mod stroke; pub mod transform; +pub mod transform3d; pub mod util; diff --git a/geometry/src/monotonic.rs b/geometry/src/monotonic.rs index 6e6589e4..67aa35ec 100644 --- a/geometry/src/monotonic.rs +++ b/geometry/src/monotonic.rs @@ -10,7 +10,6 @@ //! Converts paths to monotonically increasing/decreasing segments. -use crate::outline::Contour; use crate::segment::{Segment, SegmentKind}; use arrayvec::ArrayVec; diff --git a/geometry/src/point.rs b/geometry/src/point.rs index 873441cb..5f3fdfe2 100644 --- a/geometry/src/point.rs +++ b/geometry/src/point.rs @@ -14,6 +14,8 @@ use crate::simd::F32x4; use euclid::Point2D; use std::ops::{Add, Mul, Sub}; +// 2D points. + #[derive(Clone, Copy, Debug, Default)] pub struct Point2DF32(pub F32x4); @@ -38,6 +40,12 @@ impl Point2DF32 { Point2D::new(self.0[0], self.0[1]) } + // TODO(pcwalton): Optimize this! + #[inline] + pub fn to_4d(self) -> Point4DF32 { + Point4DF32::new(self.0[0], self.0[1], 0.0, 1.0) + } + #[inline] pub fn x(&self) -> f32 { self.0[0] @@ -90,3 +98,58 @@ impl Mul for Point2DF32 { Point2DF32(self.0 * other.0) } } + +// 3D homogeneous points. + +#[derive(Clone, Copy, Debug, Default)] +pub struct Point4DF32(pub F32x4); + +impl Point4DF32 { + #[inline] + pub fn new(x: f32, y: f32, z: f32, w: f32) -> Point4DF32 { + Point4DF32(F32x4::new(x, y, z, w)) + } + + #[inline] + pub fn splat(value: f32) -> Point4DF32 { + Point4DF32(F32x4::splat(value)) + } + + #[inline] + pub fn to_2d(self) -> Point2DF32 { + Point2DF32(self.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 w(self) -> f32 { + self.0[3] + } + + #[inline] + pub fn perspective_divide(self) -> Point4DF32 { + self * Point4DF32::splat(1.0 / self.w()) + } +} + +impl Mul for Point4DF32 { + type Output = Point4DF32; + #[inline] + fn mul(self, other: Point4DF32) -> Point4DF32 { + Point4DF32(self.0 * other.0) + } +} diff --git a/geometry/src/simd.rs b/geometry/src/simd.rs index 9898bf17..950eab8a 100644 --- a/geometry/src/simd.rs +++ b/geometry/src/simd.rs @@ -151,6 +151,11 @@ mod scalar { (F32x4([self[0], other[0], self[1], other[1]]), F32x4([self[2], other[2], self[3], other[3]])) } + + #[inline] + pub fn transpose4(a: &mut F32x4, b: &mut F32x4, c: &mut F32x4, d: &mut F32x4) { + unimplemented!() + } } impl Index for F32x4 { @@ -442,6 +447,13 @@ mod x86 { ) } } + + #[inline] + pub fn transpose_4x4(a: &mut F32x4, b: &mut F32x4, c: &mut F32x4, d: &mut F32x4) { + unsafe { + x86_64::_MM_TRANSPOSE4_PS(&mut a.0, &mut b.0, &mut c.0, &mut d.0) + } + } } impl Default for F32x4 { diff --git a/geometry/src/transform3d.rs b/geometry/src/transform3d.rs new file mode 100644 index 00000000..733afe28 --- /dev/null +++ b/geometry/src/transform3d.rs @@ -0,0 +1,146 @@ +// pathfinder/geometry/src/transform3d.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! 3D transforms that can be applied to paths. + +use crate::point::Point4DF32; +use crate::simd::F32x4; + +/// An transform, optimized with SIMD. +/// +/// In column-major order. +#[derive(Clone, Copy)] +pub struct Transform3DF32 { + c0: F32x4, + c1: F32x4, + c2: F32x4, + c3: F32x4, +} + +impl Default for Transform3DF32 { + #[inline] + fn default() -> Transform3DF32 { + Transform3DF32 { + c0: F32x4::new(1.0, 0.0, 0.0, 0.0), + c1: F32x4::new(0.0, 1.0, 0.0, 0.0), + c2: F32x4::new(0.0, 0.0, 1.0, 0.0), + c3: F32x4::new(0.0, 0.0, 0.0, 1.0), + } + } +} + +impl Transform3DF32 { + #[inline] + pub fn row_major(m00: f32, m01: f32, m02: f32, m03: f32, + m10: f32, m11: f32, m12: f32, m13: f32, + m20: f32, m21: f32, m22: f32, m23: f32, + m30: f32, m31: f32, m32: f32, m33: f32) + -> Transform3DF32 { + Transform3DF32 { + c0: F32x4::new(m00, m10, m20, m30), + c1: F32x4::new(m01, m11, m21, m31), + c2: F32x4::new(m02, m12, m22, m32), + c3: F32x4::new(m03, m13, m23, m33), + } + } + + #[inline] + pub fn scale(x: f32, y: f32, z: f32) -> Transform3DF32 { + Transform3DF32::row_major( x, 0.0, 0.0, 0.0, + 0.0, y, 0.0, 0.0, + 0.0, 0.0, z, 0.0, + 0.0, 0.0, 0.0, 1.0) + } + + #[inline] + pub fn translate(x: f32, y: f32, z: f32) -> Transform3DF32 { + Transform3DF32::row_major(1.0, 0.0, 0.0, x, + 0.0, 1.0, 0.0, y, + 0.0, 0.0, 1.0, z, + 0.0, 0.0, 0.0, 1.0) + } + + // TODO(pcwalton): Optimize. + pub fn rotate(roll: f32, pitch: f32, yaw: f32) -> Transform3DF32 { + let (cos_roll, sin_roll) = (roll.cos(), roll.sin()); + let (cos_pitch, sin_pitch) = (pitch.cos(), pitch.sin()); + let (cos_yaw, sin_yaw) = (yaw.cos(), yaw.sin()); + let m00 = cos_yaw * cos_pitch; + let m01 = cos_yaw * sin_pitch * sin_roll - sin_yaw * cos_roll; + let m02 = cos_yaw * sin_pitch * sin_roll + sin_yaw * sin_roll; + let m10 = sin_yaw * cos_pitch; + let m11 = sin_yaw * sin_pitch * sin_roll + cos_yaw * cos_roll; + let m12 = sin_yaw * sin_pitch * cos_roll + cos_yaw * sin_roll; + let m20 = -sin_pitch; + let m21 = cos_pitch * sin_roll; + let m22 = cos_pitch * cos_roll; + Transform3DF32::row_major(m00, m01, m02, 0.0, + m10, m11, m12, 0.0, + m20, m21, m22, 0.0, + 0.0, 0.0, 0.0, 1.0) + } + + /// Just like `gluPerspective()`. + #[inline] + pub fn perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> Transform3DF32 { + let f = 1.0 / (fov_y * 0.5).tan(); + let z_denom = 1.0 / (z_near - z_far); + let m00 = f / aspect; + let m11 = f; + let m22 = (z_far + z_near) * z_denom; + let m23 = 2.0 * z_far * z_near * z_denom; + let m32 = -1.0; + Transform3DF32::row_major(m00, 0.0, 0.0, 0.0, + 0.0, m11, 0.0, 0.0, + 0.0, 0.0, m22, m23, + 0.0, 0.0, m32, 0.0) + } + + #[inline] + pub fn transpose(&self) -> Transform3DF32 { + let mut m = *self; + F32x4::transpose_4x4(&mut m.c0, &mut m.c1, &mut m.c2, &mut m.c3); + m + } + + // FIXME(pcwalton): Is this right, due to transposition? I think we may have to reverse the + // two. + // + // https://stackoverflow.com/a/18508113 + #[inline] + pub fn post_mul(&self, other: &Transform3DF32) -> Transform3DF32 { + return Transform3DF32 { + c0: mul_row(self.c0, other), + c1: mul_row(self.c1, other), + c2: mul_row(self.c2, other), + c3: mul_row(self.c3, other), + }; + + fn mul_row(a_row: F32x4, b: &Transform3DF32) -> F32x4 { + let (a0, a1) = (F32x4::splat(a_row[0]), F32x4::splat(a_row[1])); + let (a2, a3) = (F32x4::splat(a_row[2]), F32x4::splat(a_row[3])); + a0 * b.c0 + a1 * b.c1 + a2 * b.c2 + a3 * b.c3 + } + } + + #[inline] + pub fn pre_mul(&self, other: &Transform3DF32) -> Transform3DF32 { + other.post_mul(self) + } + + #[inline] + pub fn transform_point(&self, point: Point4DF32) -> Point4DF32 { + let term0 = self.c0 * F32x4::splat(point.x()); + let term1 = self.c1 * F32x4::splat(point.y()); + let term2 = self.c2 * F32x4::splat(point.z()); + let term3 = self.c3 * F32x4::splat(point.w()); + Point4DF32(term0 + term1 + term2 + term3) + } +}