// -*- coding: utf-8 -*- // // Copyright 2021-2023 Michael Büsch // // Derived from https://github.com/mlochen/dungeon.git // Copyright (C) 2020 Marco Lochen // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // use crate::draw::Draw; use crate::util::{degrees, radians}; use crate::vec2d::Vec2D; use crate::wall::{VisibleWallsIter, Wall}; use crate::world::World; use graphics::{Landscape3D, Point3D, Render3D, View3D}; use std::f32::{consts::FRAC_PI_2, EPSILON}; const EYE_LEVEL: f32 = 1.4; // 1.0 => wall top edge; 2.0 => wall hor. center line. pub struct Render { world: World, sky_color: u32, image_height: f32, image_width: f32, image_height_half: f32, image_width_half: f32, } impl Render { pub fn new(world: World) -> Render { let mut self_ = Render { world, sky_color: 0, image_height: 0.0, image_width: 0.0, image_height_half: 0.0, image_width_half: 0.0, }; self_.set_sky_color(0xFF007CB0); self_.set_fov_angle(86.0); self_ } pub fn set_sky_color(&mut self, color: u32) { self.sky_color = color; } fn project_point(&self, point: Vec2D) -> Vec2D { let player = self.world.get_player(); let player_pos = player.get_pos(); let player_dir = player.get_dir(); // Vector from player to point. let player_to_point = point - player_pos; let to_point_dir = player_to_point.normalized(); // 1 -> point is right hand side of player fov center line. let rhs = if player_dir.phi_to_is_positive(&to_point_dir) { 1.0 } else { -1.0 }; // Angle diff on horizontal x/z plane from player view angle to point. let h_phi = player_dir.phi_to(&to_point_dir); // Z depth of the horizontal (x/z plane) fov triangle. let h_z_fov = self.image_width_half / (player.get_fov_phi() / 2.0).tan(); // Get abs horiz. dist from player to point in view direction. (triangle in x/z plane) let h_z_pt = (player_to_point.len() * h_phi.cos()).abs(); let v_fact = 1.0 / h_z_pt.max(EPSILON); // Calculate offsets from screen center point. let x_center_offs = h_z_fov * h_phi.tan() * rhs; let y_center_offs = h_z_fov * v_fact; // Calculate the projected coordinates. let lim = self.image_width.max(self.image_height) * 64.0; let x = (self.image_width_half + x_center_offs).clamp(-lim, lim); let y = (self.image_height_half + y_center_offs).clamp(-lim, lim); Vec2D::new(x, y) } fn corrected_wall_points(&self, wall: &Wall) -> (Vec2D, Vec2D) { let (p1, p2) = (wall.get_p1(), wall.get_p2()); //TODO (p1, p2) } } impl View3D for Render { fn set_position(&mut self, pos: &Point3D) { self.world .get_player_mut() .set_pos(Vec2D::new(pos.x() * -1.0, pos.z())) } fn get_position(&self) -> Point3D { let player_pos = self.world.get_player().get_pos(); Point3D::new(player_pos.x() * -1.0, 0.0, player_pos.y()) } fn set_view_distance(&mut self, _view_dist: i32) {} fn get_view_distance(&self) -> i32 { i32::MAX } fn set_fov_angle(&mut self, fov_angle: f32) { self.world.get_player_mut().set_fov_phi(radians(fov_angle)); } fn get_fov_angle(&self) -> f32 { degrees(self.world.get_player().get_fov_phi()) } fn set_yaw_angle(&mut self, yaw_angle: f32) { self.world .get_player_mut() .set_phi(radians((yaw_angle * -1.0) + 90.0)); } fn get_yaw_angle(&self) -> f32 { (degrees(self.world.get_player().get_phi()) * -1.0) - 90.0 } fn set_roll_angle(&mut self, _roll_angle: f32) {} fn get_roll_angle(&self) -> f32 { 0.0 } fn set_pitch_angle(&mut self, _pitch_angle: f32) {} fn get_pitch_angle(&self) -> f32 { 0.0 } } impl Render3D for Render { fn render_scene(&mut self, render_buf: &mut [u32], image_width: usize, image_height: usize) { self.image_width = image_width as f32; self.image_height = image_height as f32; self.image_width_half = self.image_width / 2.0; self.image_height_half = self.image_height / 2.0; render_buf.fill(self.sky_color); let mut draw = Draw::new(render_buf, image_width, image_height); // Draw sky and ground. //TODO // Draw switches. //TODO // Draw walls and enemies. let player_dir = self.world.get_player().get_dir(); let player_pos = self.world.get_player().get_pos(); let walls = VisibleWallsIter::new(&self.world, self.world.get_player()).sorted_by_distance(false); for wall in walls { let (mut p1, mut p2) = (wall.get_p1(), wall.get_p2()); let player_to_p1 = p1 - player_pos; let player_to_p2 = p2 - player_pos; let p1_phi = player_dir.phi_to(&player_to_p1); let p2_phi = player_dir.phi_to(&player_to_p2); if p1_phi > FRAC_PI_2 || p2_phi > FRAC_PI_2 { // > 90 deg // Wall is partially behind player. Correct the points. let res = self.corrected_wall_points(wall); p1 = res.0; p2 = res.1; } // Project the wall bottom points. let p1_proj = self.project_point(p1); let p2_proj = self.project_point(p2); // Calculate the top points. let a = p1_proj; // bottom let b = Vec2D::new( // top p1_proj.x(), p1_proj.y() - ((p1_proj.y() - self.image_height_half) * EYE_LEVEL), ); let c = Vec2D::new( // top p2_proj.x(), p2_proj.y() - ((p2_proj.y() - self.image_height_half) * EYE_LEVEL), ); let d = p2_proj; // bottom // Draw the wall tetragon. //TODO color draw.fill_tetragon((&a, &b, &c, &d), 0); } // Draw crosshair. //TODO // Draw health. //TODO // Draw ammo. //TODO // Draw overlay. //TODO } } impl Landscape3D for Render { fn get_terrain_height_at(&self, _point: Point3D) -> f32 { 0.0 } } // vim: ts=4 sw=4 expandtab