use glam::{Mat4, Vec2, Vec3, Vec4, Quat, EulerRot};
use half::f16 as F16;
use rayon::prelude::*;
use super::frame::{Frame, PixelBuffer, PixelFormat};
use super::space;
#[inline]
pub fn is_identity(position: [f32; 3], rotation: [f32; 3], scale: [f32; 3], pivot: [f32; 3]) -> bool {
position[0] == pivot[0]
&& position[1] == pivot[1]
&& position[2] == pivot[2]
&& rotation[0] == 0.0
&& rotation[1] == 0.0
&& rotation[2] == 0.0
&& scale[0] == 1.0
&& scale[1] == 1.0
&& scale[2] == 1.0
}
#[inline]
fn is_orthographic_vp(vp: Mat4) -> bool {
let row3 = vp.row(3);
row3.x.abs() < 1e-6 && row3.y.abs() < 1e-6 && (row3.w - 1.0).abs() < 1e-6
}
#[inline]
fn unproject_to_plane(
ndc: Vec2,
inv_vp: Mat4,
plane_point: Vec3,
plane_normal: Vec3,
) -> Option<Vec3> {
let near_clip = Vec4::new(ndc.x, ndc.y, -1.0, 1.0);
let far_clip = Vec4::new(ndc.x, ndc.y, 1.0, 1.0);
let near_world4 = inv_vp * near_clip;
let far_world4 = inv_vp * far_clip;
if near_world4.w.abs() < 1e-6 || far_world4.w.abs() < 1e-6 {
return None;
}
let near_world = Vec3::new(
near_world4.x / near_world4.w,
near_world4.y / near_world4.w,
near_world4.z / near_world4.w,
);
let far_world = Vec3::new(
far_world4.x / far_world4.w,
far_world4.y / far_world4.w,
far_world4.z / far_world4.w,
);
let ray_dir = far_world - near_world;
let denom = ray_dir.dot(plane_normal);
if denom.abs() < 1e-6 {
return None; }
let t = (plane_point - near_world).dot(plane_normal) / denom;
Some(near_world + ray_dir * t)
}
#[inline]
fn layer_plane_normal(rotation: [f32; 3]) -> Vec3 {
let quat = Quat::from_euler(
EulerRot::ZYX,
-rotation[2], -rotation[1], -rotation[0], );
quat * Vec3::Z
}
pub fn build_inverse_transform(
position: [f32; 3],
rotation: [f32; 3],
scale: [f32; 3],
pivot: [f32; 3],
) -> Mat4 {
let pos = Vec3::from(position);
let pvt = Vec3::from(pivot);
let inv_scale = Vec3::new(
if scale[0].abs() > f32::EPSILON { 1.0 / scale[0] } else { 0.0 },
if scale[1].abs() > f32::EPSILON { 1.0 / scale[1] } else { 0.0 },
if scale[2].abs() > f32::EPSILON { 1.0 / scale[2] } else { 0.0 },
);
let inv_rot = Quat::from_euler(
EulerRot::XYZ, rotation[0], rotation[1],
rotation[2],
);
Mat4::from_translation(pvt)
* Mat4::from_scale(inv_scale)
* Mat4::from_quat(inv_rot)
* Mat4::from_translation(-pos)
}
pub fn build_model_matrix(
position: [f32; 3],
rotation: [f32; 3],
scale: [f32; 3],
pivot: [f32; 3],
) -> Mat4 {
let pos = Vec3::from(position);
let pvt = Vec3::from(pivot);
let scl = Vec3::from(scale);
let rot = Quat::from_euler(
EulerRot::ZYX,
-rotation[2], -rotation[1],
-rotation[0],
);
Mat4::from_translation(pos)
* Mat4::from_quat(rot)
* Mat4::from_scale(scl)
* Mat4::from_translation(-pvt)
}
pub fn build_inverse_mvp(model: Mat4, view_projection: Mat4) -> Mat4 {
let mvp = view_projection * model;
mvp.inverse()
}
pub fn build_inverse_matrix_3x3(
position: [f32; 3],
rotation_z: f32,
scale: [f32; 3],
pivot: [f32; 3],
src_size: (usize, usize),
) -> [f32; 9] {
use glam::Affine2;
let pos = Vec2::new(position[0], position[1]);
let pvt = Vec2::new(pivot[0], pivot[1]);
let inv_scale = Vec2::new(
if scale[0].abs() > f32::EPSILON { 1.0 / scale[0] } else { 0.0 },
if scale[1].abs() > f32::EPSILON { 1.0 / scale[1] } else { 0.0 },
);
let inv = Affine2::from_translation(pvt)
* Affine2::from_angle(rotation_z)
* Affine2::from_scale(inv_scale)
* Affine2::from_translation(-pos);
let src_half = Vec2::new(src_size.0 as f32 * 0.5, src_size.1 as f32 * 0.5);
let object_to_src = Affine2::from_translation(Vec2::new(src_half.x, src_half.y))
* Affine2::from_scale(Vec2::new(1.0, -1.0));
let total = object_to_src * inv;
let m = total.matrix2;
let t = total.translation;
[
m.x_axis.x, m.x_axis.y, 0.0, m.y_axis.x, m.y_axis.y, 0.0, t.x, t.y, 1.0, ]
}
#[inline]
fn sample_f32(buffer: &[f32], width: usize, height: usize, x: f32, y: f32) -> [f32; 4] {
if x < 0.0 || y < 0.0 || x >= width as f32 || y >= height as f32 {
return [0.0, 0.0, 0.0, 0.0];
}
let x0 = x.floor() as usize;
let y0 = y.floor() as usize;
let x1 = (x0 + 1).min(width - 1);
let y1 = (y0 + 1).min(height - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let idx00 = (y0 * width + x0) * 4;
let idx10 = (y0 * width + x1) * 4;
let idx01 = (y1 * width + x0) * 4;
let idx11 = (y1 * width + x1) * 4;
let mut result = [0.0f32; 4];
for c in 0..4 {
let c00 = buffer[idx00 + c];
let c10 = buffer[idx10 + c];
let c01 = buffer[idx01 + c];
let c11 = buffer[idx11 + c];
let top = c00 * (1.0 - fx) + c10 * fx;
let bottom = c01 * (1.0 - fx) + c11 * fx;
result[c] = top * (1.0 - fy) + bottom * fy;
}
result
}
#[inline]
fn sample_f16(buffer: &[F16], width: usize, height: usize, x: f32, y: f32) -> [f32; 4] {
if x < 0.0 || y < 0.0 || x >= width as f32 || y >= height as f32 {
return [0.0, 0.0, 0.0, 0.0];
}
let x0 = x.floor() as usize;
let y0 = y.floor() as usize;
let x1 = (x0 + 1).min(width - 1);
let y1 = (y0 + 1).min(height - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let idx00 = (y0 * width + x0) * 4;
let idx10 = (y0 * width + x1) * 4;
let idx01 = (y1 * width + x0) * 4;
let idx11 = (y1 * width + x1) * 4;
let mut result = [0.0f32; 4];
for c in 0..4 {
let c00 = buffer[idx00 + c].to_f32();
let c10 = buffer[idx10 + c].to_f32();
let c01 = buffer[idx01 + c].to_f32();
let c11 = buffer[idx11 + c].to_f32();
let top = c00 * (1.0 - fx) + c10 * fx;
let bottom = c01 * (1.0 - fx) + c11 * fx;
result[c] = top * (1.0 - fy) + bottom * fy;
}
result
}
#[inline]
fn sample_u8(buffer: &[u8], width: usize, height: usize, x: f32, y: f32) -> [f32; 4] {
if x < 0.0 || y < 0.0 || x >= width as f32 || y >= height as f32 {
return [0.0, 0.0, 0.0, 0.0];
}
let x0 = x.floor() as usize;
let y0 = y.floor() as usize;
let x1 = (x0 + 1).min(width - 1);
let y1 = (y0 + 1).min(height - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let idx00 = (y0 * width + x0) * 4;
let idx10 = (y0 * width + x1) * 4;
let idx01 = (y1 * width + x0) * 4;
let idx11 = (y1 * width + x1) * 4;
let mut result = [0.0f32; 4];
for c in 0..4 {
let c00 = buffer[idx00 + c] as f32 / 255.0;
let c10 = buffer[idx10 + c] as f32 / 255.0;
let c01 = buffer[idx01 + c] as f32 / 255.0;
let c11 = buffer[idx11 + c] as f32 / 255.0;
let top = c00 * (1.0 - fx) + c10 * fx;
let bottom = c01 * (1.0 - fx) + c11 * fx;
result[c] = top * (1.0 - fy) + bottom * fy;
}
result
}
pub fn transform_frame(
src: &Frame,
canvas: (usize, usize),
position: [f32; 3],
rotation: [f32; 3],
scale: [f32; 3],
pivot: [f32; 3],
) -> Frame {
transform_frame_with_camera(src, canvas, position, rotation, scale, pivot, None)
}
pub fn transform_frame_with_camera(
src: &Frame,
canvas: (usize, usize),
position: [f32; 3],
rotation: [f32; 3],
scale: [f32; 3],
pivot: [f32; 3],
view_projection: Option<Mat4>,
) -> Frame {
let src_w = src.width();
let src_h = src.height();
let (dst_w, dst_h) = canvas;
let comp_size = canvas;
let src_size = (src_w, src_h);
let inv_model = build_inverse_transform(position, rotation, scale, pivot);
let plane_point = Vec3::from(position);
let plane_normal = layer_plane_normal(rotation);
let half_w = dst_w as f32 * 0.5;
let half_h = dst_h as f32 * 0.5;
let camera_info: Option<(Mat4, Mat4, bool)> = view_projection.map(|vp| {
let inv_vp = vp.inverse();
let is_ortho = is_orthographic_vp(vp);
(vp, inv_vp, is_ortho)
});
let src_buffer = src.buffer();
let src_format = src.pixel_format();
let transform_point = |frame_pt: Vec2| -> Option<Vec2> {
match camera_info {
Some((_vp, inv_vp, is_ortho)) => {
let layer_is_tilted = (plane_normal - Vec3::Z).length_squared() > 1e-6;
if is_ortho && !layer_is_tilted {
let ndc = Vec3::new(frame_pt.x / half_w, frame_pt.y / half_h, 0.0);
let world_pt = inv_vp.transform_point3(ndc);
let obj_pt3 = inv_model.transform_point3(world_pt);
Some(Vec2::new(obj_pt3.x, obj_pt3.y))
} else {
let ndc = Vec2::new(frame_pt.x / half_w, frame_pt.y / half_h);
let world_pt = unproject_to_plane(ndc, inv_vp, plane_point, plane_normal)?;
let obj_pt3 = inv_model.transform_point3(world_pt);
Some(Vec2::new(obj_pt3.x, obj_pt3.y))
}
}
None => {
let layer_is_tilted = (plane_normal - Vec3::Z).length_squared() > 1e-6;
if layer_is_tilted {
let ray_origin = Vec3::new(frame_pt.x, frame_pt.y, 10000.0);
let ray_dir = Vec3::NEG_Z;
let denom = ray_dir.dot(plane_normal);
if denom.abs() < 1e-6 {
return None; }
let t = (plane_point - ray_origin).dot(plane_normal) / denom;
let world_pt = ray_origin + ray_dir * t;
let obj_pt3 = inv_model.transform_point3(world_pt);
Some(Vec2::new(obj_pt3.x, obj_pt3.y))
} else {
let frame_pt3 = Vec3::new(frame_pt.x, frame_pt.y, 0.0);
let obj_pt3 = inv_model.transform_point3(frame_pt3);
Some(Vec2::new(obj_pt3.x, obj_pt3.y))
}
}
}
};
match (src_buffer.as_ref(), src_format) {
(PixelBuffer::F32(buf), PixelFormat::RgbaF32) => {
let mut dst_buf = vec![0.0f32; dst_w * dst_h * 4];
dst_buf
.par_chunks_mut(dst_w * 4)
.enumerate()
.for_each(|(y, row)| {
for x in 0..dst_w {
let dst_pt = Vec2::new(x as f32 + 0.5, y as f32 + 0.5);
let frame_pt = space::image_to_frame(dst_pt, comp_size);
let color = if let Some(obj_pt) = transform_point(frame_pt) {
let src_pt = space::object_to_src(obj_pt, src_size);
sample_f32(buf, src_w, src_h, src_pt.x, src_pt.y)
} else {
[0.0, 0.0, 0.0, 0.0] };
let idx = x * 4;
row[idx..idx + 4].copy_from_slice(&color);
}
});
Frame::from_f32_buffer(dst_buf, dst_w, dst_h)
}
(PixelBuffer::F16(buf), PixelFormat::RgbaF16) => {
let mut dst_buf = vec![F16::ZERO; dst_w * dst_h * 4];
dst_buf
.par_chunks_mut(dst_w * 4)
.enumerate()
.for_each(|(y, row)| {
for x in 0..dst_w {
let dst_pt = Vec2::new(x as f32 + 0.5, y as f32 + 0.5);
let frame_pt = space::image_to_frame(dst_pt, comp_size);
let color = if let Some(obj_pt) = transform_point(frame_pt) {
let src_pt = space::object_to_src(obj_pt, src_size);
sample_f16(buf, src_w, src_h, src_pt.x, src_pt.y)
} else {
[0.0, 0.0, 0.0, 0.0]
};
let idx = x * 4;
row[idx] = F16::from_f32(color[0]);
row[idx + 1] = F16::from_f32(color[1]);
row[idx + 2] = F16::from_f32(color[2]);
row[idx + 3] = F16::from_f32(color[3]);
}
});
Frame::from_f16_buffer(dst_buf, dst_w, dst_h)
}
(PixelBuffer::U8(buf), PixelFormat::Rgba8) => {
let mut dst_buf = vec![0u8; dst_w * dst_h * 4];
dst_buf
.par_chunks_mut(dst_w * 4)
.enumerate()
.for_each(|(y, row)| {
for x in 0..dst_w {
let dst_pt = Vec2::new(x as f32 + 0.5, y as f32 + 0.5);
let frame_pt = space::image_to_frame(dst_pt, comp_size);
let color = if let Some(obj_pt) = transform_point(frame_pt) {
let src_pt = space::object_to_src(obj_pt, src_size);
sample_u8(buf, src_w, src_h, src_pt.x, src_pt.y)
} else {
[0.0, 0.0, 0.0, 0.0]
};
let idx = x * 4;
row[idx] = (color[0] * 255.0).clamp(0.0, 255.0) as u8;
row[idx + 1] = (color[1] * 255.0).clamp(0.0, 255.0) as u8;
row[idx + 2] = (color[2] * 255.0).clamp(0.0, 255.0) as u8;
row[idx + 3] = (color[3] * 255.0).clamp(0.0, 255.0) as u8;
}
});
Frame::from_u8_buffer(dst_buf, dst_w, dst_h)
}
_ => {
log::warn!("transform_frame: unsupported format {:?}, returning copy", src_format);
src.clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_check() {
assert!(is_identity([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0]));
assert!(!is_identity([10.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0]));
assert!(!is_identity([0.0, 0.0, 0.0], [0.1, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0]));
assert!(!is_identity([0.0, 0.0, 0.0], [0.0, 0.0, 0.1], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0]));
assert!(!is_identity([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [2.0, 1.0, 1.0], [0.0, 0.0, 0.0]));
assert!(!is_identity([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [5.0, 0.0, 0.0]));
}
#[test]
fn test_transform_identity() {
let buf = vec![1.0f32, 0.0, 0.0, 1.0].repeat(16);
let frame = Frame::from_f32_buffer(buf, 4, 4);
let result = transform_frame(
&frame,
(4, 4),
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
[0.0, 0.0, 0.0],
);
assert_eq!(result.width(), 4);
assert_eq!(result.height(), 4);
}
}