use anyhow::Result; use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3}; use rfactor_sm_reader::*; use vulkan_rs::prelude::*; use std::{sync::Arc, time::Instant}; use super::rendering::PositionOnlyVertex; use crate::write_log; fn convert_vec(v: rF2Vec3) -> Vector3 { vec3(v.x as f32, v.y as f32, v.z as f32) } pub trait RenderObject { fn descriptor(&self) -> &Arc; fn buffer(&self) -> &Arc>; } pub struct DataConfig { pub radar_scale: f32, pub radar_car_distance: f32, pub safe_color: Vector3, pub danger_color: Vector3, } pub struct RFactorData { // config config: DataConfig, // rf2 memory mapped data telemetry_reader: TelemetryReader, scoring_reader: ScoringReader, // radar objects background: RadarObject, player_car: RadarObject, cars: Vec, // buffer car objects, to prevent recreating them every update car_handles: Vec, // game info player_id: Option, // math objects radar_center: Vector2, ortho: Matrix4, _window_width: u32, _window_height: u32, radar_extent: f32, car_width: f32, car_height: f32, start_time: Instant, device: Arc, descriptor_layout: Arc, } impl RFactorData { pub fn new( config: DataConfig, device: Arc, descriptor_layout: &Arc, width: u32, height: u32, ) -> Result { write_log!(" =================== create RFactorData ==================="); let radar_extent = width as f32 * 0.075 * config.radar_scale; let car_height = radar_extent * 0.15; let car_width = car_height / 2.5; let radar_center = vec2( width as f32 / 2.0, height as f32 / 2.0 - height as f32 * 0.25, ); let flip_y = matrix4_from_diagonal(vec3(1.0, -1.0, 1.0)); let ortho = flip_y * ortho(0.0, width as f32, 0.0, height as f32, -1.0, 1.0); let start_time = Instant::now(); Ok(Self { config, telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?, scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?, background: RadarObject::new( device.clone(), descriptor_layout, PositionOnlyVertex::from_2d_corners( ortho * Matrix4::from_translation(radar_center.extend(0.0)), [ vec2(-radar_extent, -radar_extent), vec2(-radar_extent, radar_extent), vec2(radar_extent, radar_extent), vec2(radar_extent, -radar_extent), ], ), [0.5, 0.5, 0.5, 0.5], )?, player_car: RadarObject::new( device.clone(), descriptor_layout, PositionOnlyVertex::from_2d_corners( ortho * Matrix4::from_translation(radar_center.extend(0.0)), [ vec2(-car_width, -car_height), vec2(-car_width, car_height), vec2(car_width, car_height), vec2(car_width, -car_height), ], ), [0.0, 0.9, 0.0, 0.9], )?, cars: Vec::new(), car_handles: Vec::new(), player_id: None, radar_center, ortho, _window_width: width, _window_height: height, radar_extent, car_width, car_height, start_time, device, descriptor_layout: descriptor_layout.clone(), }) } fn create_car_object(&self, offset: Vector2, color: [f32; 4]) -> Result { write_log!(" =================== create car object ==================="); RadarObject::new( self.device.clone(), &self.descriptor_layout, Self::create_car_vertices( self.ortho * Matrix4::from_translation(self.radar_center.extend(0.0)) * Matrix4::from_translation(offset.extend(0.0)), self.car_width, self.car_height, ), color, ) } fn create_car_vertices( mvp: Matrix4, car_width: f32, car_height: f32, ) -> [PositionOnlyVertex; 6] { PositionOnlyVertex::from_2d_corners( mvp, [ vec2(-car_width, -car_height), vec2(-car_width, car_height), vec2(car_width, car_height), vec2(car_width, -car_height), ], ) } fn now(&self) -> f32 { self.start_time.elapsed().as_secs_f32() } pub fn update(&mut self) -> Result<()> { write_log!(" =================== update RFactorData ==================="); // get scoring info if let Some((scoring_info, vehicle_scorings)) = self.scoring_reader.vehicle_scoring(self.now()) { write_log!(format!( "new scoring info: vehicles: {}", scoring_info.mNumVehicles )); // check for player id if scoring_info.mNumVehicles == 0 { self.player_id = None; } else if self.player_id.is_none() { for vehicle_scoring in vehicle_scorings.iter() { if vehicle_scoring.mIsPlayer != 0 { write_log!(format!("player found: {}", vehicle_scoring.mID)); self.player_id = Some(vehicle_scoring.mID); break; } } } } // if player id is set (a map is loaded), check telemetry data if let Some(player_id) = &self.player_id { write_log!("before telemetry update"); if let Some(telemetries) = self.telemetry_reader.query_telemetry(self.now()) { write_log!("new telemetry update"); // make sure there are enough cars in buffer if self.car_handles.len() < telemetries.len() { let size_diff = telemetries.len() - self.car_handles.len(); for _ in 0..size_diff { self.car_handles .push(self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?); } } let mut player_position = CarPosition::default(); let mut other_positions = Vec::new(); for telemetry in telemetries { let car = CarPosition::new( convert_vec(telemetry.position), [ convert_vec(telemetry.orientation[0]), convert_vec(telemetry.orientation[1]), convert_vec(telemetry.orientation[2]), ], ); if telemetry.id == *player_id { player_position = car } else { other_positions.push(car); } } // update radar objects self.cars.clear(); let mut buffer_car_index = 0; for other_position in other_positions { let diff = player_position.position - other_position.position; let distance = diff.magnitude(); // check if car is close enough to the players car if distance < self.config.radar_car_distance { let offset = diff.xz() * (self.radar_extent / self.config.radar_car_distance); let buffered_car = self.car_handles[buffer_car_index].clone(); buffer_car_index += 1; buffered_car.update( self.ortho, offset, player_position.rotation, other_position.rotation, self.radar_center, self.car_width, self.car_height, [0.9, 0.9, 0.0, 0.9], )?; self.cars.push(buffered_car); } } } } Ok(()) } pub fn objects(&self) -> Vec<&dyn RenderObject> { write_log!(" =================== get objects of RFactorData ==================="); let mut objects: Vec<&dyn RenderObject> = Vec::new(); // only draw radar when player is loaded into a map if let Some(_player_id) = &self.player_id { // only draw radar when any car is near enough if !self.cars.is_empty() { objects.push(&self.background); for other_player_cars in &self.cars { objects.push(other_player_cars); } objects.push(&self.player_car); } } objects } } #[derive(Clone)] struct RadarObject { descriptor_set: Arc, // uniform buffer color_buffer: Arc>, // vertex buffer position_buffer: Arc>, } impl RadarObject { fn new( device: Arc, descriptor_layout: &Arc, positions: [PositionOnlyVertex; 6], color: [f32; 4], ) -> Result { let color_buffer = Buffer::builder() .set_usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) .set_memory_usage(MemoryUsage::CpuOnly) .set_data(&color) .build(device.clone())?; let position_buffer = Buffer::builder() .set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) .set_memory_usage(MemoryUsage::CpuOnly) .set_data(&positions) .build(device.clone())?; let descriptor_pool = DescriptorPool::builder() .set_layout(descriptor_layout.clone()) .build(device.clone())?; let descriptor_set = descriptor_pool.prepare_set().allocate()?; descriptor_set.update(&[DescriptorWrite::uniform_buffers(0, &[&color_buffer])])?; Ok(Self { descriptor_set, color_buffer, position_buffer, }) } pub fn update( &self, ortho: Matrix4, offset: Vector2, player_rotation: impl Into>, rotation: impl Into>, radar_center: Vector2, car_width: f32, car_height: f32, color: [f32; 4], ) -> Result<()> { self.position_buffer .fill(&RFactorData::create_car_vertices( ortho * Matrix4::from_translation(radar_center.extend(0.0)) * Matrix4::from_angle_z(-player_rotation.into()) * Matrix4::from_translation(offset.extend(0.0)) * Matrix4::from_angle_z(rotation.into()), car_width, car_height, ))?; self.color_buffer.fill(&color) } } impl RenderObject for RadarObject { fn descriptor(&self) -> &Arc { &self.descriptor_set } fn buffer(&self) -> &Arc> { &self.position_buffer } } struct CarPosition { pub position: Vector3, pub rotation: Rad, } impl CarPosition { fn new(position: Vector3, orientation: [Vector3; 3]) -> Self { Self { position, rotation: Rad(orientation[2].x.atan2(orientation[2].z)), } } } impl Default for CarPosition { fn default() -> Self { Self { position: vec3(0.0, 0.0, 0.0), rotation: Rad(0.0), } } } const fn matrix4_from_diagonal(diagonal: Vector3) -> Matrix4 { Matrix4::from_cols( vec4(diagonal.x, 0.0, 0.0, 0.0), vec4(0.0, diagonal.y, 0.0, 0.0), vec4(0.0, 0.0, diagonal.z, 0.0), vec4(0.0, 0.0, 0.0, 1.0), ) }