The Great Refactor (Part 2)

3 minute read

63 Days Until I Can Walk

Another quiet day of refactoring, but I’m starting to converge on an organisation for my code that I like.

I’ve continued to break my code up into modules and have started thinking more about how I am representing information in the application. My ray caster has been broken into three structs—one for walls, one for floors, and one for ceilings. Using traits or enums (I haven’t decided yet), I should be able to implement a renderer where it is possible to swap in and out the structs that define the logic for rendering the three types of surface. Mostly this will be useful if we need to save some CPU cycles at runtime, so we might switch off floor or ceiling texturing.

I’m not entirely sure what the most “Rust” way to do this is, however. Normally I would write some sort of builder or factory, but I’m not sure if that is appropriate here.

Another dimension to the problem is that I have spent too long writing Java over the last few years, and I’m fighting my brain to stop from creating multiple abstractions and traits in the name of modularity and Object Orientation. At one point I found myself writing the following enum to keep track of what texture code is assigned to a wall/floor/ceiling tile:

enum TextureId {
	None,
	Textured(u8)
}

The idea being that it is possible for a surface to not have a texture. But if there is a texture then I should use a numerical code to index my texture map and extract it. But as I looked at this, I felt strong YAGNI vibes and decided to simply store u8s in my tiles. If I really want to have the option of a surface being untextured, I’ll use an Optional, but I doubt I’m going to need it.

I’ve started to move my code for colouring and blending into a single sensible struct:

struct Colour {
	r: u8,
	g: u8,
	b: u8,
	a: u8,
}

impl Colour {
	pub fn new(r: u8, g: u8, b: u8, a: u8) -> Colour {
		Colour { r, g, b, a }
	}

	pub fn blend(self, other: &Colour) -> Colour {
		let (r, g, b, a) = Colour::blend_colours(self.r, self.g, self.b, self.a, other.r, other.g, other.b, other.a);
		Colour { r, g, b, a }
	}

	pub fn tuple(&self) -> (u8, u8, u8, u8) {
		(self.r, self.g, self.b, self.a)
	}

	fn alpha_blend(c1: f64, a1: f64, c2: f64, a2: f64, ao: f64) -> f64 {
		(c1 * a1 + c2 * a2 * (1.0 - a1)) / ao
	}

	fn blend_colours(r1: u8, g1: u8, b1: u8, a1: u8, r2:u8, g2:u8, b2:u8, a2:u8) -> (u8, u8, u8, u8) {
		let fa1 = a1 as f64 / 255.0;
		let fa2 = a2 as f64 / 255.0;
		let fao = Colour::alpha_blend(1.0, fa1, 1.0, fa2, 1.0);

		let r = Colour::alpha_blend(r1 as f64, fa1, r2 as f64, fa2, fao) as u8;
		let g = Colour::alpha_blend(g1 as f64, fa1, g2 as f64, fa2, fao) as u8;
		let b = Colour::alpha_blend(b1 as f64, fa1, b2 as f64, fa2, fao) as u8;
		let a = (255.0 * fao) as u8;

		(r, g, b, a)
	}
}

This is still a little messier than I’d like. In particular, I want to get rid of all those floating point numbers. But this grouping makes a lot more sense than the mish-mash of functions I had before. This will also make it much easier to write colour data to the output buffer.

I have started a package for collision detection which will separate code for intersection with walls and objects from the code for the player. I can see this code undergoing a significant change, but my modifications may make it slower. Right now there is a single large function which checks for intersections with walls and objects (the latter being stubbed in, but it will happen). I could create collision objects—bounding boxes etc. and bind these to objects so that different objects can support different types of collision. But realistically, every object is going to use a bounding box in this type of engine. So I’m at risk of overcomplicating things here.

This refactor is going to take longer than expected. Partially because it is complicated and I want to take my time with it. But also because tomorrow I won’t have as much time to code as I have another hospital appointment in the afternoon.

I’ll keep posting, but my hope is that the Great Refactor will be over soon.