renderer.rs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. use base64::{Engine as _, engine::general_purpose};
  2. use crate::{ Camera };
  3. use crate::scene::{ Scene };
  4. use crate::trig;
  5. use crate::render::raycast;
  6. use serde_json;
  7. use shared::consts;
  8. use shared::fp::{ ToFixedPoint };
  9. macro_rules! colour_to_buf {
  10. ($colour:expr, $buf:expr, $idx:expr) => {
  11. ($buf[$idx + 0], $buf[$idx + 1], $buf[$idx + 2], $buf[$idx + 3]) = $colour.tuple();
  12. }
  13. }
  14. macro_rules! blend_colour_to_buf {
  15. ($colour:expr, $buf:expr, $idx:expr) => {
  16. let blended = $colour.blend(&Colour::new($buf[$idx + 0], $buf[$idx + 1], $buf[$idx + 2], $buf[$idx + 3]));
  17. colour_to_buf!(blended, $buf, $idx);
  18. }
  19. }
  20. macro_rules! screen_idx {
  21. ($x:expr, $y:expr) => {
  22. 4 * ($x + $y * consts::PROJECTION_PLANE_WIDTH) as usize
  23. }
  24. }
  25. macro_rules! put_surface_pixel {
  26. ($intersect:expr, $buf:expr, $idx:expr, $textures:expr) => {
  27. if $intersect.is_some() {
  28. let intersect = $intersect.unwrap();
  29. let texture = $textures.get(intersect.texture, intersect.x, false);
  30. let pixel = &texture[intersect.y as usize];
  31. colour_to_buf!(pixel, $buf, $idx);
  32. }
  33. }
  34. }
  35. macro_rules! blend_surface_pixel {
  36. ($intersect:expr, $pixel: expr, $textures:expr) => {
  37. if $intersect.is_some() {
  38. let intersect = $intersect.unwrap();
  39. let texture = $textures.get(intersect.texture, intersect.x, false);
  40. let pixel = &texture[intersect.y as usize];
  41. $pixel.blend(pixel)
  42. } else {
  43. $pixel
  44. }
  45. }
  46. }
  47. pub struct Colour {
  48. pub r: u8,
  49. pub g: u8,
  50. pub b: u8,
  51. pub a: u8,
  52. }
  53. impl Colour {
  54. pub fn new(r: u8, g: u8, b: u8, a: u8) -> Colour {
  55. Colour { r, g, b, a }
  56. }
  57. pub fn blend(self, other: &Colour) -> Colour {
  58. let (r, g, b, a) = Colour::blend_colours(self.r, self.g, self.b, self.a, other.r, other.g, other.b, other.a);
  59. Colour { r, g, b, a }
  60. }
  61. pub fn tuple(&self) -> (u8, u8, u8, u8) {
  62. (self.r, self.g, self.b, self.a)
  63. }
  64. fn alpha_blend(c1: f64, a1: f64, c2: f64, a2: f64, ao: f64) -> f64 {
  65. (c1 * a1 + c2 * a2 * (1.0 - a1)) / ao
  66. }
  67. fn blend_colours(r1: u8, g1: u8, b1: u8, a1: u8, r2:u8, g2:u8, b2:u8, a2:u8) -> (u8, u8, u8, u8) {
  68. let fa1 = a1 as f64 / 255.0;
  69. let fa2 = a2 as f64 / 255.0;
  70. let fao = Colour::alpha_blend(1.0, fa1, 1.0, fa2, 1.0);
  71. let r = Colour::alpha_blend(r1 as f64, fa1, r2 as f64, fa2, fao) as u8;
  72. let g = Colour::alpha_blend(g1 as f64, fa1, g2 as f64, fa2, fao) as u8;
  73. let b = Colour::alpha_blend(b1 as f64, fa1, b2 as f64, fa2, fao) as u8;
  74. let a = (255.0 * fao) as u8;
  75. (r, g, b, a)
  76. }
  77. }
  78. pub struct RenderParameters<'a> {
  79. texture: &'a [Colour],
  80. tex_idx: &'a [usize],
  81. y_min: i32,
  82. y_max: i32,
  83. }
  84. impl RenderParameters<'_> {
  85. pub fn new<'a>(texture: &'a [Colour], tex_idx: &'a [usize], y_min: i32, y_max: i32,) -> RenderParameters<'a> {
  86. RenderParameters { texture, tex_idx, y_min, y_max }
  87. }
  88. }
  89. pub struct TextureMap {
  90. texture_width: usize,
  91. texture_height: usize,
  92. texture_size: usize,
  93. textures: Vec<Colour>
  94. }
  95. impl TextureMap {
  96. pub fn new(texture_width: usize, texture_height: usize, channels: Vec<u8>) -> TextureMap {
  97. let texture_size = texture_width * texture_height;
  98. let mut textures = Vec::new();
  99. textures.reserve(channels.len() / 4);
  100. for i in (0..channels.len()).step_by(4) {
  101. textures.push(Colour::new(channels[i], channels[i + 1], channels[i + 2], channels[i + 3]));
  102. }
  103. TextureMap { texture_width, texture_height, texture_size, textures }
  104. }
  105. pub fn empty() -> TextureMap {
  106. TextureMap { texture_width: 0, texture_height: 0, texture_size: 0, textures: vec![] }
  107. }
  108. pub fn get(&self, code: u32, column: i32, flipped: bool) -> &[Colour] {
  109. let column = if flipped { self.texture_width - 1 - column as usize } else { column as usize };
  110. let head: usize = (self.texture_size * code as usize + column as usize * self.texture_width) as usize;
  111. let tail: usize = head + self.texture_height;
  112. &self.textures[head..tail]
  113. }
  114. }
  115. impl TryFrom<&serde_json::Value> for TextureMap {
  116. type Error = &'static str;
  117. fn try_from(json: &serde_json::Value) -> Result<Self, Self::Error> {
  118. let width = json["width"].as_u64().unwrap() as usize;
  119. let height = json["height"].as_u64().unwrap() as usize;
  120. let byte_str = json["textures"].as_str().unwrap();
  121. let bytes: Vec<u8> = general_purpose::STANDARD_NO_PAD.decode(byte_str).expect("failed to decode textures");
  122. Ok(TextureMap::new(width, height, bytes))
  123. }
  124. }
  125. pub struct Renderer {
  126. textures: TextureMap,
  127. }
  128. impl Renderer {
  129. pub fn new(textures: TextureMap) -> Renderer {
  130. Renderer{ textures }
  131. }
  132. pub fn render_column(&self, buf: &mut[u8], origin_x: i32, origin_y: i32, angle: i32, column: i32, camera: &Camera, scene: &Scene) {
  133. let parameters = self.intersect_to_render_params(origin_x, origin_y, angle, column, camera, scene);
  134. let y_min = parameters[0].y_min;
  135. let y_max = parameters[0].y_max;
  136. // draw ceiling
  137. for y in 0..y_min {
  138. let intersect = raycast::find_ceiling_intersection(origin_x, origin_y, angle, y, column, scene);
  139. put_surface_pixel!(intersect, buf, screen_idx!(column, y), self.textures);
  140. }
  141. // draw walls
  142. for y in y_min..=y_max {
  143. let mut pixel = Colour::new(0, 0, 0, 0);
  144. let idx: usize = screen_idx!(column, y);
  145. for intersect in parameters.iter() {
  146. if y < intersect.y_min || y > intersect.y_max { break; } // terminate early if either we're above/below the tallest wall
  147. if pixel.a >= 255 { break; } // or the pixel is solid
  148. let tex_y = intersect.tex_idx[y as usize];
  149. pixel = pixel.blend(&intersect.texture[tex_y]);
  150. }
  151. // blend in the floor or ceiling through transparent areas if necessary
  152. if pixel.a < 255 {
  153. let intersect = if y > camera.horizon() {
  154. raycast::find_floor_intersection(origin_x, origin_y, angle, y, column, scene)
  155. } else {
  156. raycast::find_ceiling_intersection(origin_x, origin_y, angle, y, column, scene)
  157. };
  158. pixel = blend_surface_pixel!(intersect, pixel, self.textures);
  159. }
  160. blend_colour_to_buf!(pixel, buf, idx);
  161. }
  162. // draw floor
  163. for y in y_max..consts::PROJECTION_PLANE_HEIGHT {
  164. let intersect = raycast::find_floor_intersection(origin_x, origin_y, angle, y, column, scene);
  165. put_surface_pixel!(intersect, buf, screen_idx!(column, y), self.textures);
  166. }
  167. }
  168. pub fn render(&self, buf: &mut[u8], scene: &Scene, camera: &Camera) {
  169. self.render_background(buf);
  170. // angle is the direction camera is facing
  171. // need to start out sweep 30 degrees to the left
  172. let mut angle = if camera.angle() < trig::ANGLE_30 {
  173. camera.angle() - trig::ANGLE_30 + trig::ANGLE_360
  174. } else {
  175. camera.angle() - trig::ANGLE_30
  176. };
  177. // ray casting uses fixed point notation, so convert camera coordinates to fixed point
  178. let origin_x = camera.x().to_fp();
  179. let origin_y = camera.y().to_fp();
  180. // sweep of the rays will be through 60 degrees
  181. for sweep in 0..trig::ANGLE_60 {
  182. self.render_column(buf, origin_x, origin_y, angle, sweep, &camera, &scene);
  183. angle += 1;
  184. if angle >= trig::ANGLE_360 {
  185. angle -= trig::ANGLE_360;
  186. }
  187. }
  188. }
  189. fn render_background(&self, buf: &mut[u8]) {
  190. let ceiling = Colour::new(0x38, 0x38, 0x38, 0xFF);
  191. let floor = Colour::new(0x70, 0x70, 0x70, 0xFF);
  192. for y in 0..consts::PROJECTION_PLANE_HORIZON {
  193. for x in 0..consts::PROJECTION_PLANE_WIDTH {
  194. colour_to_buf!(ceiling, buf, screen_idx!(x, y));
  195. }
  196. }
  197. for y in consts::PROJECTION_PLANE_HORIZON..consts::PROJECTION_PLANE_HEIGHT {
  198. for x in 0..consts::PROJECTION_PLANE_WIDTH {
  199. colour_to_buf!(floor, buf, screen_idx!(x, y));
  200. }
  201. }
  202. }
  203. fn intersect_to_render_params(&self, origin_x: i32, origin_y: i32, angle: i32, column: i32, camera: &Camera, scene: &Scene) -> Vec<RenderParameters> {
  204. let intersects = raycast::find_wall_intersections(origin_x, origin_y, angle, column, scene);
  205. // for each intersection, get a reference to its texture and figure out how
  206. // it should be drawn
  207. return intersects.iter().map(|intersect| {
  208. let dist = intersect.dist;
  209. let wall_height = trig::wall_height(dist);
  210. let mid_height = wall_height >> 1;
  211. let y_min = std::cmp::max(0, camera.horizon() - mid_height);
  212. let y_max = std::cmp::min(consts::PROJECTION_PLANE_HEIGHT - 1, camera.horizon() + mid_height);
  213. let tex_idx = trig::wall_texture_index(wall_height);
  214. let texture = self.textures.get(intersect.texture, intersect.texture_column, intersect.reverse);
  215. RenderParameters::new(texture, tex_idx, y_min, y_max)
  216. }).collect();
  217. }
  218. }
  219. impl TryFrom<&serde_json::Value> for Renderer {
  220. type Error = &'static str;
  221. fn try_from(json: &serde_json::Value) -> Result<Self, Self::Error> {
  222. let textures = TextureMap::try_from(&json["texture_map"]).ok().unwrap();
  223. Ok(Renderer::new(textures))
  224. }
  225. }