Fix 3D transform math and add some unit tests

This commit is contained in:
Patrick Walton 2019-01-16 11:22:01 -08:00
parent 05d2b26fa7
commit a95db7dff9
3 changed files with 163 additions and 20 deletions

View File

@ -14,6 +14,7 @@ use gl::types::{GLchar, GLfloat, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
use jemallocator;
use pathfinder_geometry::point::Point2DF32;
use pathfinder_geometry::transform::Transform2DF32;
use pathfinder_geometry::transform3d::Transform3DF32;
use pathfinder_renderer::builder::SceneBuilder;
use pathfinder_renderer::gpu_data::{Batch, BuiltScene, SolidTileScenePrimitive};
use pathfinder_renderer::paint::ObjectShader;
@ -81,13 +82,17 @@ fn main() {
let mut renderer = Renderer::new(&Size2D::new(drawable_width, drawable_height));
//let mut scale = 1.0;
let mut theta = 0.0;
//let mut theta = 0.0;
let mut transform = Transform3DF32::from_perspective(f32::PI / 4.0, 4.0 / 3.0, 0.01, 1000.0);
transform = transform.pre_mul(Transform3DF32::from_translation(0.0, 0.0, -100.0));
while !exit {
let mut scene = base_scene.clone();
scene.transform(&Transform2DF32::from_rotation(theta));
scene.transform_3d(&transform);
//scene.transform(&Transform2DF32::from_rotation(theta));
//scene.transform(&Transform2DF32::from_scale(&Point2DF32::splat(scale)));
theta += 0.001;
//theta += 0.001;
//scale -= 0.0003;
//scale += 0.0001;

View File

@ -101,7 +101,7 @@ impl Mul<Point2DF32> for Point2DF32 {
// 3D homogeneous points.
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct Point4DF32(pub F32x4);
impl Point4DF32 {

View File

@ -10,13 +10,15 @@
//! 3D transforms that can be applied to paths.
use crate::point::Point4DF32;
use crate::point::{Point2DF32, Point4DF32};
use crate::segment::Segment;
use crate::simd::F32x4;
use euclid::Size2D;
/// An transform, optimized with SIMD.
///
/// In column-major order.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transform3DF32 {
c0: F32x4,
c1: F32x4,
@ -52,7 +54,7 @@ impl Transform3DF32 {
}
#[inline]
pub fn scale(x: f32, y: f32, z: f32) -> Transform3DF32 {
pub fn from_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,
@ -60,7 +62,7 @@ impl Transform3DF32 {
}
#[inline]
pub fn translate(x: f32, y: f32, z: f32) -> Transform3DF32 {
pub fn from_translation(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,
@ -68,7 +70,7 @@ impl Transform3DF32 {
}
// TODO(pcwalton): Optimize.
pub fn rotate(roll: f32, pitch: f32, yaw: f32) -> Transform3DF32 {
pub fn from_rotation(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());
@ -87,9 +89,25 @@ impl Transform3DF32 {
0.0, 0.0, 0.0, 1.0)
}
/// Just like `glOrtho()`.
#[inline]
pub fn from_ortho(left: f32, right: f32, bottom: f32, top: f32, near_val: f32, far_val: f32)
-> Transform3DF32 {
let x_inv = 1.0 / (right - left);
let y_inv = 1.0 / (top - bottom);
let z_inv = 1.0 / (far_val - near_val);
let tx = -(right + left) * x_inv;
let ty = -(top + bottom) * y_inv;
let tz = -(far_val + near_val) * z_inv;
Transform3DF32::row_major(2.0 * x_inv, 0.0, 0.0, tx,
0.0, 2.0 * y_inv, 0.0, ty,
0.0, 0.0, -2.0 * z_inv, tz,
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 {
pub fn from_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;
@ -115,24 +133,24 @@ impl Transform3DF32 {
//
// https://stackoverflow.com/a/18508113
#[inline]
pub fn post_mul(&self, other: &Transform3DF32) -> Transform3DF32 {
pub fn pre_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),
c0: mul_col(self.c0, other),
c1: mul_col(self.c1, other),
c2: mul_col(self.c2, other),
c3: mul_col(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]));
fn mul_col(a_col: F32x4, b: &Transform3DF32) -> F32x4 {
let (a0, a1) = (F32x4::splat(a_col[0]), F32x4::splat(a_col[1]));
let (a2, a3) = (F32x4::splat(a_col[2]), F32x4::splat(a_col[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)
pub fn post_mul(&self, other: &Transform3DF32) -> Transform3DF32 {
other.pre_mul(self)
}
#[inline]
@ -144,3 +162,123 @@ impl Transform3DF32 {
Point4DF32(term0 + term1 + term2 + term3)
}
}
/// Transforms a path with a SIMD 3D transform.
pub struct Transform3DF32PathIter<I>
where
I: Iterator<Item = Segment>,
{
iter: I,
transform: Transform3DF32,
window_size: Size2D<u32>,
}
impl<I> Iterator for Transform3DF32PathIter<I>
where
I: Iterator<Item = Segment>,
{
type Item = Segment;
#[inline]
fn next(&mut self) -> Option<Segment> {
let mut segment = self.iter.next()?;
if !segment.is_none() {
segment.baseline.set_from(&self.transform_point(&segment.baseline.from()));
segment.baseline.set_to(&self.transform_point(&segment.baseline.to()));
if !segment.is_line() {
segment.ctrl.set_from(&self.transform_point(&segment.ctrl.from()));
if !segment.is_quadratic() {
segment.ctrl.set_to(&self.transform_point(&segment.ctrl.to()));
}
}
}
Some(segment)
}
}
impl<I> Transform3DF32PathIter<I>
where
I: Iterator<Item = Segment>,
{
#[inline]
pub fn new(iter: I, transform: &Transform3DF32, window_size: &Size2D<u32>)
-> Transform3DF32PathIter<I> {
Transform3DF32PathIter {
iter,
transform: *transform,
window_size: *window_size,
}
}
#[inline]
fn transform_point(&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
}
}
#[cfg(test)]
mod test {
use crate::point::Point4DF32;
use crate::transform3d::Transform3DF32;
#[test]
fn test_post_mul() {
let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0,
9.0, 2.0, 6.0, 5.0,
3.0, 5.0, 8.0, 9.0,
7.0, 9.0, 3.0, 2.0);
let b = Transform3DF32::row_major(3.0, 8.0, 4.0, 6.0,
2.0, 6.0, 4.0, 3.0,
3.0, 8.0, 3.0, 2.0,
7.0, 9.0, 5.0, 0.0);
let c = Transform3DF32::row_major(58.0, 107.0, 53.0, 29.0,
84.0, 177.0, 87.0, 72.0,
106.0, 199.0, 101.0, 49.0,
62.0, 152.0, 83.0, 75.0);
assert_eq!(a.post_mul(&b), c);
}
#[test]
fn test_pre_mul() {
let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0,
9.0, 2.0, 6.0, 5.0,
3.0, 5.0, 8.0, 9.0,
7.0, 9.0, 3.0, 2.0);
let b = Transform3DF32::row_major(3.0, 8.0, 4.0, 6.0,
2.0, 6.0, 4.0, 3.0,
3.0, 8.0, 3.0, 2.0,
7.0, 9.0, 5.0, 0.0);
let c = Transform3DF32::row_major(135.0, 93.0, 110.0, 103.0,
93.0, 61.0, 85.0, 82.0,
104.0, 52.0, 90.0, 86.0,
117.0, 50.0, 122.0, 125.0);
assert_eq!(a.pre_mul(&b), c);
}
#[test]
fn test_transform_point() {
let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0,
9.0, 2.0, 6.0, 5.0,
3.0, 5.0, 8.0, 9.0,
7.0, 9.0, 3.0, 2.0);
let p = Point4DF32::new(3.0, 8.0, 4.0, 6.0);
let q = Point4DF32::new(63.0, 97.0, 135.0, 117.0);
assert_eq!(a.transform_point(p), q);
}
#[test]
fn test_transpose() {
let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0,
9.0, 2.0, 6.0, 5.0,
3.0, 5.0, 8.0, 9.0,
7.0, 9.0, 3.0, 2.0);
let b = Transform3DF32::row_major(3.0, 9.0, 3.0, 7.0,
1.0, 2.0, 5.0, 9.0,
4.0, 6.0, 8.0, 3.0,
5.0, 5.0, 9.0, 2.0);
assert_eq!(a.transpose(), b);
}
}