Skip to content

Effective vek

Yoan edited this page Apr 30, 2020 · 5 revisions

This page lacks some more content. There are quite a bunch of interesting tricks vek allows for.

The map() methods allow for concise element-wise conversions

If you need that Vec3<i32> cast to a Vec3<f32> (or any other type), you may do so via map():

let v = Vec3::<i32>::zero(); // or any value, really
let v = v.map(|x| x as f32);

(Note that since 0.10.3, you should consider let v: Vec4<f32> = v.as_();, which uses AsPrimitive, for trivial safe conversions).

This is also the way to go for LERP-ing integer vectors or getting the average of a Rgba<u8>: Convert to float, perform the operation, then convert back to integers (optionally using round(), ceil(), etc).

All matrix types have map() too, which works the same way. In addition, row-major matrices have map_rows() and column-major ones have map_cols() (for, respectively, performing some operation on rows or columns).

Some geometric constructs (such as Rect and Aabr) also have map(). If you would like other types to have a map() API, feel free to suggest that in an issue!

Generic input parameters allow for nicer ways to express intent

You'll often encounter function signatures such as this one (from Mat4) :

pub fn scaling_3d<V: Into<Vec3<T>>>(v: V) -> Self where T: Zero + One { ... }

The Into<Vec3<T>> bound allows you to call this function in all sorts of convenient ways!

// A vector can be obtained from a single value by setting all elements to it
// as long as T is Copy.
// Concise uniform scale!
let _ = Mat4::scaling_3d(5_f32);
// A vector can be obtained from a tuple with identical number of elements;
let _ = Mat4::scaling_3d((5_f32, 4_f32, 3_f32));
// A vector can be obtained from an array with identical number of elements;
let _ = Mat4::scaling_3d([5_f32, 4., 3.]);
// Any T implements Into<T> by returning itself, so this works!
let v = Vec3::new(5_f32, 4., 3.);
let _ = Mat4::scaling_3d(v);
// A Vec4 is converted to a Vec3 by dropping the `w` element (not dividing by it!)
let v = Vec4::new(5_f32, 4., 3., 1.);
let _ = Mat4::scaling_3d(v);
let v = Vec4::new(5_f32, 4., 3., 0.);
let _ = Mat4::scaling_3d(v);

It's also worth noting that repr_simd vectors can be converted to repr_c ones via From, and the other way around as well.

Another example is ShuffleMask4 for Vec4:

pub fn shuffled<M: Into<ShuffleMask4>>(self, mask: M) -> Self where T: Copy { ... }

The Into<ShuffleMask4> bound allows you to call shuffled in any of the following ways :

let a = Vec4::<u32>::new(0,1,2,3);
assert_eq!(a.shuffled((0,1,2,3)), Vec4::new(0,1,2,3));
assert_eq!(a.shuffled([0,1,2,3]), Vec4::new(0,1,2,3));
assert_eq!(a.shuffled(1), Vec4::broadcast(1));

There are cases where generic input parameters would cause confusion

For instance, indexing a matrix with a Vec2 is not allowed; If it was, what would the meaning be ?
Would x mean "row index" because it is the first element, or "column index" because it represents a horizontal offset ?
Therefore, you can only index matrices with explicit (i, j) tuples, i being the row index and j the column index.

(Another way is to index the public member, like m.cols[j][i] or m.rows[i][j]).

Also notice how Mat4<T> doesn't accept V: Into<Vec4<T>> as a right-hand-side operand: This means we can't do mat4 * vec3, and it's on purpose ! It's because the w element of Vec4 matters here, and we have no idea which would make more sense: set it to 1, or 0 ? When w is 0, all translational effects of the transform are cancelled. When w is 1, they are applied normally.

However, transforming 3D vectors is a very common use case, so mul_point and mul_direction methods are provided for Mat4.
These take V: Into<Vec3<T>>, dropping any w element and replacing it by, respectively, 1 and 0.

Switching matrix storage layouts

The matrix API was designed such that users could fearlessly decide, at any point in the development cycle, to move from column-major to row-major, and the other way around as well.
Correctness is enforced by encoding the current layout in the names of appropriate functions (and the matrix's public member, either rows or cols).

Because these names carry information about the current layout, and are therefore made layout-specific, they will turn into hard compile-time errors when you decide to use another layout - which is what you want, instead of exhibiting funny behaviour at run-time in places you haven't been careful enough to refactor accordingly.

Examples include as_col_ptr vs as_row_ptr, as_col_slice vs as_row_slice, and map_cols vs map_rows.