Maybe Optimising Raycasting?
45 Days Until I Can Walk
So, I’ve always been unhappy with how I merged the horizontal and vertial intersections produced by the raycaster. The whole point of performing this merge is to allow me to implement transparency, but the problem was that I was declaring a new vector and performing a rather awkward merge as shown below:
This merge requires a second pass over both vectors of slices, and just generally feels very clunky with the manually updated usize
variables and trifecta of while loops. A better solution which I have landed on is to use iterators and to merge the values yielded by these iterators as they are traversed. This tidies up my code beautifully as shown below:
The logic here is simple. We initialize two iterators—one for a horizontal ray and one for a vertical ray—which will keep track of the state of the ray, and yields the next wall intersection as the ray is cast. There are two if statements for the edge cases where the ray is pointing straight up (vertical intersections cannot be hit in this case) and straight across (horizontal intersections cannot be hit). In either of these events, we simply cast the other ray all the way to completion and return all the intersections encountered.
Otherwise we make use of the IterTools crate which supplies us with a kmerge_by
method. By providing an iterator of iterators (achieved using vec![ray_h, ray_v].into_iter()
), kmerge_by
will traverse both iterators and merge them in sorted order using the closure provided. This assumes that the input iterators are already in sorted order. With this implementation, there is no need to cast, then merge the intersections in a separate loop. Instead the rays are cast in simultaneously, and the one closest to the camera is inserted into the resulting vector.
The definition of struct Ray
itself is a little strange, and I’m not quite happy with it. I feel like it could be improved with the use of traits, but I’ll come back to that later. For now, Ray
is a container struct which holds all the information necessary to keep track of a ray’s position, and two function pointers which point to appropriate methods depending on whether the ray is being cast horizontally or vertically.
The two function pointers, cast_ray
and check_undefined
feel like a hack, although overall they seem to work quite well. On initialization, depending on whether we are casting a vertical or horizontal ray, the Ray
struct is initialized with these references pointing to the appropriate implementation of cast ray. See below for the initialization of a horizontal ray.
While this does work, it feels like the correct thing to do here would actually be to define a Ray trait, then have two separate Ray structs (RayH
and RayV
) that would implement both this trait and the Iterator trait. These could then be passed to kmerge_by
. So, I will investigate this a little more as it feels like the correct solution.
In Other News
I have accepted a very short-term job with my old university to work on a research project. This will give me a little cash-flow, which is obviously a good thing. I may be working less on Fourteen Screws in the near future as a result.
My intention is for Fourteen Screws to become a teaching resource, and so where possible I plan to write posts here about my academic work too. This site will remain as is until the counter to my walking again hits zero. After that, however, I will be restructuring this site into a more general purpose teaching platform. At least that’s the plan. But please bear with me as I dive into some more research over the next few weeks.