A few things.
It looks like the vec3
crate does all calculations by reference, which is why you need all the lifetimes specified. For something like [T; 3]
where T
is a f64
or f32
, the calculation code cany be made simpler by taking everything by value and just make sure it implements the Copy
trait (so that you don't need to worry about ownership). If done that way, you can get rid of all the references and lifetimes in the code.
A common trick with a bunch of trait bounds is to move them all into to one trait and then just use that trait. Here that would be. It doesn't save much trouble here, but its particularly useful to know about if you're doing math operations.
trait RayValue:
for<'a, 'b> std::ops::Mul<&'b Self, Output = Self>
+ for<'a> std::ops::AddAssign<&'a Self>
+ num_traits::identities::Zero
{
}
impl<T> Ray<T> where T: RayValue,
{
...
}
Regarding the traits themselves, given the constraint of having all the calculations by reference, the definition looks fine. Basically what the for<'a,'b>
is doing is making sure that the trait is valid for all combinations of lifetimes for the two arguments to the multiply function.
Since you're just learning rust, you might try just implementing your own vector operations as a learning experience rather than tying yourself to another implementation. Here's an example of just implementing the vec yourself. Obviously you need to keep adding implementations for the various math operations you need, but I found it useful in the past to understand how Rust traits work. The code below would be a starting point for that. You can see the implementation of at()
gets a lot simpler with a more friendly version of Vec3
#[derive(Clone, Copy)]
pub struct Vec3<T> {
x: T,
y: T,
z: T,
}
impl<T> std::ops::Add<Self> for Vec3<T> where T: num_traits::Float{
type Output = Self;
fn add(self, other: Self) -> Self {
Vec3 {
x: self.x + other.x,
y: self.y + other.y,
z: self.z + other.z,
}
}
}
impl<T> std::ops::Mul<T> for Vec3<T> where T: num_traits::Float {
type Output = Self;
fn mul(self, other: T) -> Self {
Vec3 {
x: self.x * other,
y: self.y * other,
z: self.z * other,
}
}
}
impl<T> num_traits::Zero for Vec3<T> where T: num_traits::Float {
fn zero() -> Self {
Vec3{x:T::zero(), y:T::zero(), z:T::zero()}
}
fn is_zero(&self) -> bool {
self.x == T::zero() && self.y == T::zero() && self.z == T::zero()
}
}
pub struct Ray<T> {
origin: Vec3<T>,
dir: Vec3<T>,
}
impl<T> Ray<T>
where
T: num_traits::Float,
{
pub fn at(&self, t: T) -> Vec3<T> {
self.origin + self.dir * t
}
pub fn new(origin: Vec3<T>, dir: Vec3<T>) -> Ray<T> {
return Ray { origin, dir };
}
}