Refactor project for easier extension
This commit is contained in:
parent
2504d4ee24
commit
dd3d9fd2f2
12 changed files with 963 additions and 514 deletions
4
build.rs
4
build.rs
|
@ -10,8 +10,8 @@ const VK_HEADER: &[&str] = &[
|
|||
const FN_PREFIX: &str = "PFN_";
|
||||
|
||||
const SHADER: &[&str] = &[
|
||||
"src/overlay/shader/single_color.vert",
|
||||
"src/overlay/shader/single_color.frag",
|
||||
"src/overlay/elements/shader/single_color.vert",
|
||||
"src/overlay/elements/shader/single_color.frag",
|
||||
];
|
||||
|
||||
fn query_vulkan_function_typedefs() {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
mod pedals;
|
||||
mod pipeline;
|
||||
mod radar;
|
||||
|
||||
pub use pedals::*;
|
||||
pub use radar::*;
|
|
@ -0,0 +1,66 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
|
||||
use ui::prelude::*;
|
||||
|
||||
use crate::overlay::{rfactor_data::DataReceiver, UiOverlay};
|
||||
|
||||
pub struct Pedals {
|
||||
gui: Arc<GuiBuilder>,
|
||||
|
||||
brake: Arc<ProgressBar>,
|
||||
throttle: Arc<ProgressBar>,
|
||||
}
|
||||
|
||||
impl Pedals {
|
||||
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
|
||||
const DESC: &str = include_str!("ui_files/pedals.xml");
|
||||
|
||||
let gui = GuiBuilder::from_str(gui_handler, DESC)?;
|
||||
|
||||
let brake = gui.element("brake")?;
|
||||
let throttle = gui.element("throttle")?;
|
||||
|
||||
Ok(Self {
|
||||
gui,
|
||||
brake,
|
||||
throttle,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pedals {
|
||||
fn drop(&mut self) {
|
||||
self.gui.disable().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl UiOverlay for Pedals {
|
||||
fn enable_ui(&mut self) -> Result<()> {
|
||||
self.gui.enable()
|
||||
}
|
||||
}
|
||||
|
||||
impl DataReceiver for Pedals {
|
||||
fn scoring_update(&mut self, _vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn telemetry_update(
|
||||
&mut self,
|
||||
player_id: Option<i32>,
|
||||
telemetries: &[rF2VehicleTelemetry],
|
||||
) -> Result<()> {
|
||||
if let Some(id) = player_id {
|
||||
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
|
||||
self.brake
|
||||
.set_progress(telemetry.unfiltered_throttle as f32)?;
|
||||
self.throttle
|
||||
.set_progress(telemetry.unfiltered_throttle as f32)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ use vulkan_rs::prelude::*;
|
|||
|
||||
use std::{mem, sync::Arc};
|
||||
|
||||
use super::rendering::PositionOnlyVertex;
|
||||
use super::radar::PositionOnlyVertex;
|
||||
|
||||
pub struct SingleColorPipeline {
|
||||
pipeline: Arc<Pipeline>,
|
||||
|
@ -11,7 +11,12 @@ pub struct SingleColorPipeline {
|
|||
}
|
||||
|
||||
impl SingleColorPipeline {
|
||||
pub fn new(device: Arc<Device>, renderpass: &Arc<RenderPass>) -> Result<Self> {
|
||||
pub fn new(
|
||||
device: Arc<Device>,
|
||||
renderpass: &Arc<RenderPass>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Self> {
|
||||
let vertex_shader = ShaderModule::from_slice(
|
||||
device.clone(),
|
||||
include_bytes!("shader/single_color.vert.spv"),
|
||||
|
@ -36,6 +41,23 @@ impl SingleColorPipeline {
|
|||
.add_descriptor_set_layout(&descriptor_layout)
|
||||
.build(device.clone())?;
|
||||
|
||||
let viewport = VkViewport {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: width as f32,
|
||||
height: height as f32,
|
||||
minDepth: 0.0,
|
||||
maxDepth: 1.0,
|
||||
};
|
||||
|
||||
let scissor = VkRect2D {
|
||||
offset: VkOffset2D { x: 0, y: 0 },
|
||||
extent: VkExtent2D {
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
};
|
||||
|
||||
let pipeline = Pipeline::new_graphics()
|
||||
.set_vertex_shader(
|
||||
vertex_shader.clone(),
|
||||
|
@ -60,6 +82,8 @@ impl SingleColorPipeline {
|
|||
.default_color_blend(vec![VkPipelineColorBlendAttachmentState::default()])
|
||||
.default_rasterization(VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE)
|
||||
.default_multisample(VK_SAMPLE_COUNT_1_BIT)
|
||||
.add_viewport(viewport)
|
||||
.add_scissor(scissor)
|
||||
.build(device, &pipeline_layout, &renderpass, 0)?;
|
||||
|
||||
Ok(Self {
|
|
@ -0,0 +1,495 @@
|
|||
use anyhow::Result;
|
||||
use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3, Vector4};
|
||||
use rfactor_sm_reader::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vulkan_rs::prelude::*;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::pipeline::SingleColorPipeline;
|
||||
|
||||
use crate::{
|
||||
overlay::{rendering::Rendering, rfactor_data::DataReceiver, UiOverlay},
|
||||
write_log,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PositionOnlyVertex {
|
||||
pub position: Vector4<f32>,
|
||||
}
|
||||
|
||||
impl PositionOnlyVertex {
|
||||
///
|
||||
/// corners[0] - bottom left
|
||||
/// corners[1] - top left
|
||||
/// corners[2] - top right
|
||||
/// corners[3] - bottom right
|
||||
///
|
||||
pub fn from_2d_corners(ortho: Matrix4<f32>, corners: [Vector2<f32>; 4]) -> [Self; 6] {
|
||||
[
|
||||
Self {
|
||||
position: ortho * corners[0].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[1].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[2].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[2].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[3].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[0].extend(0.0).extend(1.0),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_vec(v: rF2Vec3) -> Vector3<f32> {
|
||||
vec3(v.x as f32, v.y as f32, v.z as f32)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
|
||||
pub struct RadarConfig {
|
||||
pub radar_scale: f32,
|
||||
pub radar_center_factor: f32,
|
||||
pub radar_transparency: f32,
|
||||
pub height_scale: f32,
|
||||
pub width_scale: f32,
|
||||
pub radar_car_distance: f32,
|
||||
pub safe_color: Vector3<f32>,
|
||||
pub danger_color: Vector3<f32>,
|
||||
}
|
||||
|
||||
impl RadarConfig {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
radar_scale: 1.0,
|
||||
radar_center_factor: 0.25,
|
||||
radar_transparency: 0.5,
|
||||
height_scale: 0.15,
|
||||
width_scale: 0.4,
|
||||
radar_car_distance: 20.0,
|
||||
safe_color: vec3(0.0, 0.75, 0.0),
|
||||
danger_color: vec3(0.75, 0.0, 0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Radar {
|
||||
// config
|
||||
config: RadarConfig,
|
||||
|
||||
// radar objects
|
||||
background: Option<RadarObject>,
|
||||
player_car: RadarObject,
|
||||
cars: Vec<RadarObject>,
|
||||
|
||||
// buffer car objects, to prevent recreating them every update
|
||||
car_handles: Vec<RadarObject>,
|
||||
|
||||
// game info
|
||||
player_id: Option<i32>,
|
||||
|
||||
// math objects
|
||||
radar_center: Vector2<f32>,
|
||||
ortho: Matrix4<f32>,
|
||||
_window_width: u32,
|
||||
_window_height: u32,
|
||||
radar_extent: f32,
|
||||
car_width: f32,
|
||||
car_height: f32,
|
||||
|
||||
device: Arc<Device>,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
|
||||
pipeline: SingleColorPipeline,
|
||||
render_target: RenderTarget,
|
||||
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Radar {
|
||||
pub fn new(
|
||||
config: RadarConfig,
|
||||
device: Arc<Device>,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
rendering: &Rendering,
|
||||
) -> Result<Self> {
|
||||
write_log!(" =================== create RFactorData ===================");
|
||||
|
||||
let radar_extent = rendering.swapchain().width() as f32 * 0.075 * config.radar_scale;
|
||||
let car_height = radar_extent * config.height_scale;
|
||||
let car_width = car_height * config.width_scale;
|
||||
let radar_center = vec2(
|
||||
rendering.swapchain().width() as f32 / 2.0,
|
||||
rendering.swapchain().height() as f32 / 2.0
|
||||
- rendering.swapchain().height() as f32 * config.radar_center_factor,
|
||||
);
|
||||
|
||||
let flip_y = matrix4_from_diagonal(vec3(1.0, -1.0, 1.0));
|
||||
let ortho = flip_y
|
||||
* ortho(
|
||||
0.0,
|
||||
rendering.swapchain().width() as f32,
|
||||
0.0,
|
||||
rendering.swapchain().height() as f32,
|
||||
-1.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
let render_target = RenderTarget::builder()
|
||||
.add_sub_pass(
|
||||
SubPass::builder(
|
||||
rendering.swapchain().width(),
|
||||
rendering.swapchain().height(),
|
||||
)
|
||||
.set_prepared_targets(&rendering.images(), 0, [0.0, 0.0, 0.0, 1.0], false)
|
||||
.build(&device, &queue)?,
|
||||
)
|
||||
.build(&device)?;
|
||||
|
||||
let pipeline = SingleColorPipeline::new(
|
||||
device.clone(),
|
||||
render_target.render_pass(),
|
||||
rendering.swapchain().width(),
|
||||
rendering.swapchain().height(),
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
|
||||
background: if config.radar_transparency == 0.0 {
|
||||
None
|
||||
} else {
|
||||
Some(RadarObject::new(
|
||||
device.clone(),
|
||||
pipeline.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, config.radar_transparency],
|
||||
)?)
|
||||
},
|
||||
player_car: RadarObject::new(
|
||||
device.clone(),
|
||||
pipeline.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: rendering.swapchain().width(),
|
||||
_window_height: rendering.swapchain().height(),
|
||||
radar_extent,
|
||||
car_width,
|
||||
car_height,
|
||||
|
||||
device,
|
||||
queue,
|
||||
|
||||
render_target,
|
||||
pipeline,
|
||||
|
||||
enabled: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_car_object(&self, offset: Vector2<f32>, color: [f32; 4]) -> Result<RadarObject> {
|
||||
write_log!(" =================== create car object ===================");
|
||||
|
||||
RadarObject::new(
|
||||
self.device.clone(),
|
||||
&self.pipeline.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<f32>,
|
||||
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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render(&self, image_index: u32) -> Result<Arc<CommandBuffer>> {
|
||||
let command_buffer =
|
||||
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
|
||||
|
||||
{
|
||||
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
||||
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||
))?;
|
||||
|
||||
self.render_target
|
||||
.begin(&recorder, VK_SUBPASS_CONTENTS_INLINE, image_index as usize);
|
||||
|
||||
recorder.bind_pipeline(self.pipeline.pipeline())?;
|
||||
|
||||
for object in self.objects() {
|
||||
let buffer = &object.position_buffer;
|
||||
|
||||
recorder.bind_descriptor_sets_minimal(&[&object.descriptor_set]);
|
||||
recorder.bind_vertex_buffer(buffer);
|
||||
recorder.draw_complete_single_instance(buffer.size() as u32);
|
||||
}
|
||||
|
||||
self.render_target.end(&recorder);
|
||||
}
|
||||
|
||||
Ok(command_buffer)
|
||||
}
|
||||
|
||||
fn objects(&self) -> Vec<&RadarObject> {
|
||||
write_log!(" =================== get objects of radar ===================");
|
||||
|
||||
let mut objects = 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() {
|
||||
if let Some(background) = &self.background {
|
||||
objects.push(background);
|
||||
}
|
||||
|
||||
for other_player_cars in &self.cars {
|
||||
objects.push(other_player_cars);
|
||||
}
|
||||
|
||||
objects.push(&self.player_car);
|
||||
}
|
||||
}
|
||||
|
||||
objects
|
||||
}
|
||||
}
|
||||
|
||||
impl UiOverlay for Radar {
|
||||
fn enable_ui(&mut self) -> Result<()> {
|
||||
self.enabled = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DataReceiver for Radar {
|
||||
fn scoring_update(&mut self, _vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn telemetry_update(
|
||||
&mut self,
|
||||
player_id: Option<i32>,
|
||||
telemetries: &[rF2VehicleTelemetry],
|
||||
) -> Result<()> {
|
||||
self.cars.clear();
|
||||
|
||||
if !self.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(player_id) = player_id {
|
||||
// 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
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RadarObject {
|
||||
descriptor_set: Arc<DescriptorSet>,
|
||||
|
||||
// uniform buffer
|
||||
color_buffer: Arc<Buffer<f32>>,
|
||||
|
||||
// vertex buffer
|
||||
position_buffer: Arc<Buffer<PositionOnlyVertex>>,
|
||||
}
|
||||
|
||||
impl RadarObject {
|
||||
fn new(
|
||||
device: Arc<Device>,
|
||||
descriptor_layout: &Arc<DescriptorSetLayout>,
|
||||
positions: [PositionOnlyVertex; 6],
|
||||
color: [f32; 4],
|
||||
) -> Result<Self> {
|
||||
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<f32>,
|
||||
offset: Vector2<f32>,
|
||||
player_rotation: impl Into<Deg<f32>>,
|
||||
rotation: impl Into<Deg<f32>>,
|
||||
radar_center: Vector2<f32>,
|
||||
car_width: f32,
|
||||
car_height: f32,
|
||||
color: [f32; 4],
|
||||
) -> Result<()> {
|
||||
self.position_buffer.fill(&Radar::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)
|
||||
}
|
||||
}
|
||||
|
||||
struct CarPosition {
|
||||
pub position: Vector3<f32>,
|
||||
pub rotation: Rad<f32>,
|
||||
}
|
||||
|
||||
impl CarPosition {
|
||||
fn new(position: Vector3<f32>, orientation: [Vector3<f32>; 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<f32>) -> Matrix4<f32> {
|
||||
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),
|
||||
)
|
||||
}
|
235
src/overlay/elements/ui_files/gui.xsd
Normal file
235
src/overlay/elements/ui_files/gui.xsd
Normal file
|
@ -0,0 +1,235 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<!-- definition of attributes -->
|
||||
<xs:attribute name="id" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="x_slot" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="y_slot" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="x_dim" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="y_dim" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="x_size" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="y_size" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="normal" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="selected" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="click_sound" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="hover_sound" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="select_mode">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="none"></xs:enumeration>
|
||||
<xs:enumeration value="bigger"></xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="icon" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="icon_margin" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="text_color" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="text_ratio" type="xs:decimal"></xs:attribute>
|
||||
<xs:attribute name="text_alignment">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="left"></xs:enumeration>
|
||||
<xs:enumeration value="right"></xs:enumeration>
|
||||
<xs:enumeration value="top"></xs:enumeration>
|
||||
<xs:enumeration value="bottom"></xs:enumeration>
|
||||
<xs:enumeration value="center"></xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="select" type="xs:boolean"></xs:attribute>
|
||||
<xs:attribute name="x_offset" type="xs:integer"></xs:attribute>
|
||||
<xs:attribute name="y_offset" type="xs:integer"></xs:attribute>
|
||||
<xs:attribute name="width" type="xs:integer"></xs:attribute>
|
||||
<xs:attribute name="height" type="xs:integer"></xs:attribute>
|
||||
<xs:attribute name="padding" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="margin" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="vert_align">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="top"></xs:enumeration>
|
||||
<xs:enumeration value="middle"></xs:enumeration>
|
||||
<xs:enumeration value="bottom"></xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="hori_align">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="left"></xs:enumeration>
|
||||
<xs:enumeration value="middle"></xs:enumeration>
|
||||
<xs:enumeration value="right"></xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="background" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="foreground" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="button_normal" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="button_selected" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="reference_width" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="reference_height" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="layer" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="line_count" type="xs:nonNegativeInteger"></xs:attribute>
|
||||
<xs:attribute name="west_neighbour" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="east_neighbour" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="north_neighbour" type="xs:string"></xs:attribute>
|
||||
<xs:attribute name="south_neighbour" type="xs:string"></xs:attribute>
|
||||
|
||||
<!-- definition of complex elements -->
|
||||
<xs:element name="root">
|
||||
<xs:complexType>
|
||||
<xs:choice maxOccurs="unbounded">
|
||||
<xs:element name="grid" />
|
||||
</xs:choice>
|
||||
<xs:attribute ref="reference_width"></xs:attribute>
|
||||
<xs:attribute ref="reference_height"></xs:attribute>
|
||||
<xs:attribute ref="layer"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="grid">
|
||||
<xs:complexType>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="grid" />
|
||||
<xs:element name="button" />
|
||||
<xs:element name="label" />
|
||||
<xs:element name="progressbar" />
|
||||
<xs:element name="textfield" />
|
||||
<xs:element name="icon" />
|
||||
</xs:choice>
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_dim"></xs:attribute>
|
||||
<xs:attribute ref="y_dim"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="x_offset"></xs:attribute>
|
||||
<xs:attribute ref="y_offset"></xs:attribute>
|
||||
<xs:attribute ref="width"></xs:attribute>
|
||||
<xs:attribute ref="height"></xs:attribute>
|
||||
<xs:attribute ref="padding"></xs:attribute>
|
||||
<xs:attribute ref="margin"></xs:attribute>
|
||||
<xs:attribute ref="vert_align"></xs:attribute>
|
||||
<xs:attribute ref="hori_align"></xs:attribute>
|
||||
<xs:attribute ref="background"></xs:attribute>
|
||||
<xs:attribute ref="button_normal"></xs:attribute>
|
||||
<xs:attribute ref="button_selected"></xs:attribute>
|
||||
<xs:attribute ref="click_sound"></xs:attribute>
|
||||
<xs:attribute ref="hover_sound"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="button">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="select"></xs:attribute>
|
||||
<xs:attribute ref="normal"></xs:attribute>
|
||||
<xs:attribute ref="selected"></xs:attribute>
|
||||
<xs:attribute ref="click_sound"></xs:attribute>
|
||||
<xs:attribute ref="hover_sound"></xs:attribute>
|
||||
<xs:attribute ref="select_mode"></xs:attribute>
|
||||
<xs:attribute ref="icon"></xs:attribute>
|
||||
<xs:attribute ref="icon_margin"></xs:attribute>
|
||||
<xs:attribute ref="text_color"></xs:attribute>
|
||||
<xs:attribute ref="text_ratio"></xs:attribute>
|
||||
<xs:attribute ref="text_alignment"></xs:attribute>
|
||||
<xs:attribute ref="west_neighbour"></xs:attribute>
|
||||
<xs:attribute ref="east_neighbour"></xs:attribute>
|
||||
<xs:attribute ref="north_neighbour"></xs:attribute>
|
||||
<xs:attribute ref="south_neighbour"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="label">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="text_color"></xs:attribute>
|
||||
<xs:attribute ref="text_ratio"></xs:attribute>
|
||||
<xs:attribute ref="text_alignment"></xs:attribute>
|
||||
<xs:attribute ref="background"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="multi_line_label">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="line_count"></xs:attribute>
|
||||
<xs:attribute ref="text_color"></xs:attribute>
|
||||
<xs:attribute ref="text_ratio"></xs:attribute>
|
||||
<xs:attribute ref="text_alignment"></xs:attribute>
|
||||
<xs:attribute ref="background"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="multi_line_textfield">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="line_count"></xs:attribute>
|
||||
<xs:attribute ref="text_color"></xs:attribute>
|
||||
<xs:attribute ref="text_ratio"></xs:attribute>
|
||||
<xs:attribute ref="text_alignment"></xs:attribute>
|
||||
<xs:attribute ref="background"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="progressbar">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="text_color"></xs:attribute>
|
||||
<xs:attribute ref="text_ratio"></xs:attribute>
|
||||
<xs:attribute ref="text_alignment"></xs:attribute>
|
||||
<xs:attribute ref="background"></xs:attribute>
|
||||
<xs:attribute ref="foreground"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="textfield">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="text_color"></xs:attribute>
|
||||
<xs:attribute ref="text_ratio"></xs:attribute>
|
||||
<xs:attribute ref="text_alignment"></xs:attribute>
|
||||
<xs:attribute ref="background"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="icon">
|
||||
<xs:complexType>
|
||||
<xs:attribute ref="id"></xs:attribute>
|
||||
<xs:attribute ref="x_slot"></xs:attribute>
|
||||
<xs:attribute ref="y_slot"></xs:attribute>
|
||||
<xs:attribute ref="x_size"></xs:attribute>
|
||||
<xs:attribute ref="y_size"></xs:attribute>
|
||||
<xs:attribute ref="icon"></xs:attribute>
|
||||
<xs:attribute ref="margin"></xs:attribute>
|
||||
<xs:attribute ref="background"></xs:attribute>
|
||||
<xs:attribute ref="text_color"></xs:attribute>
|
||||
<xs:attribute ref="text_ratio"></xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
</xs:schema>
|
11
src/overlay/elements/ui_files/pedals.xml
Normal file
11
src/overlay/elements/ui_files/pedals.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<root reference_width="2560" reference_height="1440">
|
||||
<grid x_dim="5" y_dim="2" x_offset="-600" y_offset="-350" width="250" height="300"
|
||||
vert_align="bottom" hori_align="right" margin="10" padding="10" background="#686868">
|
||||
<progressbar id="brake" x_slot="0" y_slot="0" y_size="2" background="#f44444"
|
||||
foreground="#e30000"></progressbar>
|
||||
<progressbar id="throttle" x_slot="1" y_slot="0"
|
||||
y_size="2" background="#51fd51"
|
||||
foreground="#00b900"></progressbar>
|
||||
</grid>
|
||||
</root>
|
|
@ -2,21 +2,31 @@ use crate::write_log;
|
|||
|
||||
use self::{
|
||||
rendering::Rendering,
|
||||
rfactor_data::{RFactorData, RadarConfig},
|
||||
rfactor_data::{DataReceiver, RFactorData},
|
||||
};
|
||||
|
||||
mod pipeline;
|
||||
mod elements;
|
||||
mod rendering;
|
||||
mod rfactor_data;
|
||||
|
||||
use anyhow::Result;
|
||||
use assetpath::AssetPath;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use vulkan_rs::prelude::*;
|
||||
|
||||
use elements::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait UiOverlay: DataReceiver {
|
||||
fn enable_ui(&mut self) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct OverlayConfig {
|
||||
pub radar_config: RadarConfig,
|
||||
|
@ -41,6 +51,8 @@ pub struct Overlay {
|
|||
rendering: Option<Rendering>,
|
||||
gui_handler: Option<Arc<GuiHandler>>,
|
||||
|
||||
ui_elements: Vec<Rc<RefCell<dyn UiOverlay>>>,
|
||||
|
||||
rfactor_data: Option<RFactorData>,
|
||||
}
|
||||
|
||||
|
@ -54,6 +66,7 @@ impl Overlay {
|
|||
queue: None,
|
||||
rendering: None,
|
||||
gui_handler: None,
|
||||
ui_elements: Vec::new(),
|
||||
|
||||
rfactor_data: None,
|
||||
}
|
||||
|
@ -104,13 +117,16 @@ impl Overlay {
|
|||
|
||||
write_log!("-> create rendering: old cleared");
|
||||
|
||||
let rendering = Rendering::new(self.device(), self.queue(), swapchain.clone())?;
|
||||
let mut rendering = Rendering::new(self.queue(), swapchain.clone())?;
|
||||
|
||||
write_log!("-> create rendering: new created");
|
||||
|
||||
// only font is used
|
||||
let mut create_info = GuiHandlerCreateInfo::default();
|
||||
create_info.font_path = AssetPath::from(self.config.font_path.clone());
|
||||
create_info.font_path.assume_prefix_free();
|
||||
|
||||
// provide trait required by GuiHandler
|
||||
let ctx = Arc::new(ContextImpl::new(
|
||||
self.device(),
|
||||
self.queue(),
|
||||
|
@ -118,38 +134,75 @@ impl Overlay {
|
|||
rendering.images().clone(),
|
||||
));
|
||||
|
||||
self.gui_handler = Some(GuiHandler::new(
|
||||
create_info,
|
||||
&(ctx as Arc<dyn ContextInterface>),
|
||||
)?);
|
||||
// create GuiHandler
|
||||
let gui_handler = GuiHandler::new(create_info, &(ctx as Arc<dyn ContextInterface>))?;
|
||||
|
||||
self.rendering = Some(rendering);
|
||||
// create ui elements
|
||||
|
||||
write_log!("-> create rendering: new created");
|
||||
// create radar
|
||||
let radar = Rc::new(RefCell::new(Radar::new(
|
||||
self.config.radar_config,
|
||||
self.device(),
|
||||
self.queue(),
|
||||
&rendering,
|
||||
)?));
|
||||
|
||||
// create pedals
|
||||
let pedals = Rc::new(RefCell::new(Pedals::new(&gui_handler)?));
|
||||
|
||||
// add rendering callbacks
|
||||
rendering.add_render_callback({
|
||||
let radar = radar.clone();
|
||||
|
||||
move |index| radar.borrow().render(index)
|
||||
});
|
||||
|
||||
rendering.add_render_callback({
|
||||
let gui_handler = gui_handler.clone();
|
||||
let device = self.device();
|
||||
let queue = self.queue();
|
||||
|
||||
move |index| {
|
||||
let command_buffer =
|
||||
CommandBuffer::new_primary().build(device.clone(), queue.clone())?;
|
||||
|
||||
{
|
||||
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
||||
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||
))?;
|
||||
|
||||
gui_handler.process(&mut recorder, &TargetMode::Mono(index as usize))?;
|
||||
}
|
||||
|
||||
Ok(command_buffer)
|
||||
}
|
||||
});
|
||||
|
||||
// add ui elements to local list
|
||||
self.ui_elements.push(radar);
|
||||
self.ui_elements.push(pedals);
|
||||
|
||||
write_log!("-> create rendering: end");
|
||||
|
||||
self.rendering = Some(rendering);
|
||||
self.gui_handler = Some(gui_handler);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> Result<()> {
|
||||
let swapchain = self.rendering.as_ref().unwrap().swapchain().clone();
|
||||
|
||||
if self.rfactor_data.is_none() {
|
||||
self.rfactor_data = RFactorData::new(
|
||||
self.config.radar_config,
|
||||
self.device(),
|
||||
self.rendering
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.single_color_pipeline()
|
||||
.descriptor_layout(),
|
||||
swapchain.width(),
|
||||
swapchain.height(),
|
||||
)
|
||||
.ok();
|
||||
self.rfactor_data = RFactorData::new().ok();
|
||||
|
||||
write_log!("created RFactorData");
|
||||
if let Some(data) = &mut self.rfactor_data {
|
||||
write_log!("created RFactorData");
|
||||
|
||||
for receiver in self.ui_elements.iter() {
|
||||
receiver.borrow_mut().enable_ui()?;
|
||||
data.add_receiver(receiver.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check twice for rfactor data, because of borrowing rules
|
||||
|
@ -157,12 +210,7 @@ impl Overlay {
|
|||
rfactor.update()?;
|
||||
}
|
||||
|
||||
let objects = match &self.rfactor_data {
|
||||
Some(rfactor) => rfactor.objects(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
self.rendering.as_mut().unwrap().render(swapchain, &objects)
|
||||
self.rendering.as_ref().unwrap().render()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use cgmath::{Matrix4, Vector2, Vector4};
|
||||
use vulkan_rs::prelude::*;
|
||||
|
||||
use std::{
|
||||
|
@ -7,61 +6,19 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{pipeline::SingleColorPipeline, rfactor_data::RenderObject};
|
||||
use crate::write_log;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PositionOnlyVertex {
|
||||
pub position: Vector4<f32>,
|
||||
}
|
||||
|
||||
impl PositionOnlyVertex {
|
||||
///
|
||||
/// corners[0] - bottom left
|
||||
/// corners[1] - top left
|
||||
/// corners[2] - top right
|
||||
/// corners[3] - bottom right
|
||||
///
|
||||
pub fn from_2d_corners(ortho: Matrix4<f32>, corners: [Vector2<f32>; 4]) -> [Self; 6] {
|
||||
[
|
||||
Self {
|
||||
position: ortho * corners[0].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[1].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[2].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[2].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[3].extend(0.0).extend(1.0),
|
||||
},
|
||||
Self {
|
||||
position: ortho * corners[0].extend(0.0).extend(1.0),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rendering {
|
||||
swapchain: Arc<Swapchain>,
|
||||
pipeline: SingleColorPipeline,
|
||||
render_target: RenderTarget,
|
||||
command_buffer: Arc<CommandBuffer>,
|
||||
images: Vec<Arc<Image>>,
|
||||
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
|
||||
render_callbacks: Vec<Box<dyn Fn(u32) -> Result<Arc<CommandBuffer>>>>,
|
||||
}
|
||||
|
||||
impl Rendering {
|
||||
pub fn new(
|
||||
device: Arc<Device>,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
swapchain: Arc<Swapchain>,
|
||||
) -> Result<Self> {
|
||||
pub fn new(queue: Arc<Mutex<Queue>>, swapchain: Arc<Swapchain>) -> Result<Self> {
|
||||
crate::write_log!("-> Rendering ctor: begin");
|
||||
let vk_images = swapchain.vk_images()?;
|
||||
write_log!(format!(
|
||||
|
@ -77,14 +34,6 @@ impl Rendering {
|
|||
};
|
||||
write_log!("-> Rendering ctor: wrapped images");
|
||||
|
||||
let render_target = RenderTarget::builder()
|
||||
.add_sub_pass(
|
||||
SubPass::builder(swapchain.width(), swapchain.height())
|
||||
.set_prepared_targets(&images, 0, [0.0, 0.0, 0.0, 1.0], false)
|
||||
.build(&device, &queue)?,
|
||||
)
|
||||
.build(&device)?;
|
||||
|
||||
write_log!("-> Rendering ctor: created render_target");
|
||||
|
||||
write_log!(format!(
|
||||
|
@ -95,12 +44,11 @@ impl Rendering {
|
|||
|
||||
Ok(Self {
|
||||
swapchain,
|
||||
pipeline: SingleColorPipeline::new(device.clone(), render_target.render_pass())?,
|
||||
render_target,
|
||||
command_buffer: CommandBuffer::new_primary().build(device.clone(), queue.clone())?,
|
||||
images,
|
||||
|
||||
queue,
|
||||
|
||||
render_callbacks: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -108,62 +56,24 @@ impl Rendering {
|
|||
&self.swapchain
|
||||
}
|
||||
|
||||
pub fn single_color_pipeline(&self) -> &SingleColorPipeline {
|
||||
&self.pipeline
|
||||
pub fn add_render_callback<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(u32) -> Result<Arc<CommandBuffer>> + 'static,
|
||||
{
|
||||
self.render_callbacks.push(Box::new(f));
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
swapchain: Arc<Swapchain>,
|
||||
objects: &[&dyn RenderObject],
|
||||
) -> Result<()> {
|
||||
pub fn render(&self) -> Result<()> {
|
||||
let image_index = self.swapchain.current_index();
|
||||
|
||||
let viewport = [VkViewport {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: swapchain.width() as f32,
|
||||
height: swapchain.height() as f32,
|
||||
minDepth: 0.0,
|
||||
maxDepth: 1.0,
|
||||
}];
|
||||
|
||||
let scissor = [VkRect2D {
|
||||
offset: VkOffset2D { x: 0, y: 0 },
|
||||
extent: VkExtent2D {
|
||||
width: swapchain.width(),
|
||||
height: swapchain.height(),
|
||||
},
|
||||
}];
|
||||
|
||||
{
|
||||
let mut recorder = self.command_buffer.begin(VkCommandBufferBeginInfo::new(
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
||||
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||
))?;
|
||||
|
||||
self.render_target
|
||||
.begin(&recorder, VK_SUBPASS_CONTENTS_INLINE, image_index as usize);
|
||||
|
||||
recorder.bind_pipeline(self.pipeline.pipeline())?;
|
||||
recorder.set_scissor(&scissor);
|
||||
recorder.set_viewport(&viewport);
|
||||
|
||||
write_log!(format!("-> Rendering {} objects", objects.len()));
|
||||
|
||||
for object in objects {
|
||||
let buffer = object.buffer();
|
||||
|
||||
recorder.bind_descriptor_sets_minimal(&[object.descriptor()]);
|
||||
recorder.bind_vertex_buffer(buffer);
|
||||
recorder.draw_complete_single_instance(buffer.size() as u32);
|
||||
}
|
||||
|
||||
self.render_target.end(&recorder);
|
||||
}
|
||||
let command_buffers: Vec<Arc<CommandBuffer>> = self
|
||||
.render_callbacks
|
||||
.iter()
|
||||
.map(|c| c(image_index))
|
||||
.collect::<Result<Vec<Arc<CommandBuffer>>>>()?;
|
||||
|
||||
let queue = self.queue.lock().unwrap();
|
||||
queue.minimal_submit(Duration::from_secs(10), &[self.command_buffer.clone()])?;
|
||||
queue.minimal_submit(Duration::from_secs(10), &command_buffers)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,196 +1,52 @@
|
|||
use anyhow::Result;
|
||||
use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3};
|
||||
use rfactor_sm_reader::*;
|
||||
use vulkan_rs::prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use super::rendering::PositionOnlyVertex;
|
||||
use crate::write_log;
|
||||
|
||||
fn convert_vec(v: rF2Vec3) -> Vector3<f32> {
|
||||
vec3(v.x as f32, v.y as f32, v.z as f32)
|
||||
}
|
||||
use super::UiOverlay;
|
||||
|
||||
pub trait RenderObject {
|
||||
fn descriptor(&self) -> &Arc<DescriptorSet>;
|
||||
fn buffer(&self) -> &Arc<Buffer<PositionOnlyVertex>>;
|
||||
}
|
||||
pub trait DataReceiver {
|
||||
fn scoring_update(&mut self, vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()>;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
|
||||
pub struct RadarConfig {
|
||||
pub radar_scale: f32,
|
||||
pub radar_center_factor: f32,
|
||||
pub radar_transparency: f32,
|
||||
pub height_scale: f32,
|
||||
pub width_scale: f32,
|
||||
pub radar_car_distance: f32,
|
||||
pub safe_color: Vector3<f32>,
|
||||
pub danger_color: Vector3<f32>,
|
||||
}
|
||||
|
||||
impl RadarConfig {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
radar_scale: 1.0,
|
||||
radar_center_factor: 0.25,
|
||||
radar_transparency: 0.5,
|
||||
height_scale: 0.15,
|
||||
width_scale: 0.4,
|
||||
radar_car_distance: 20.0,
|
||||
safe_color: vec3(0.0, 0.75, 0.0),
|
||||
danger_color: vec3(0.75, 0.0, 0.0),
|
||||
}
|
||||
}
|
||||
fn telemetry_update(
|
||||
&mut self,
|
||||
player_id: Option<i32>,
|
||||
telemetries: &[rF2VehicleTelemetry],
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct RFactorData {
|
||||
// config
|
||||
config: RadarConfig,
|
||||
|
||||
// rf2 memory mapped data
|
||||
telemetry_reader: TelemetryReader,
|
||||
scoring_reader: ScoringReader,
|
||||
|
||||
// radar objects
|
||||
background: Option<RadarObject>,
|
||||
player_car: RadarObject,
|
||||
cars: Vec<RadarObject>,
|
||||
|
||||
// buffer car objects, to prevent recreating them every update
|
||||
car_handles: Vec<RadarObject>,
|
||||
|
||||
// game info
|
||||
start_time: Instant,
|
||||
player_id: Option<i32>,
|
||||
|
||||
// math objects
|
||||
radar_center: Vector2<f32>,
|
||||
ortho: Matrix4<f32>,
|
||||
_window_width: u32,
|
||||
_window_height: u32,
|
||||
radar_extent: f32,
|
||||
car_width: f32,
|
||||
car_height: f32,
|
||||
|
||||
start_time: Instant,
|
||||
|
||||
device: Arc<Device>,
|
||||
descriptor_layout: Arc<DescriptorSetLayout>,
|
||||
receivers: Vec<Rc<RefCell<dyn UiOverlay>>>,
|
||||
}
|
||||
|
||||
impl RFactorData {
|
||||
pub fn new(
|
||||
config: RadarConfig,
|
||||
device: Arc<Device>,
|
||||
descriptor_layout: &Arc<DescriptorSetLayout>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Self> {
|
||||
pub fn new() -> Result<Self> {
|
||||
write_log!(" =================== create RFactorData ===================");
|
||||
|
||||
let radar_extent = width as f32 * 0.075 * config.radar_scale;
|
||||
let car_height = radar_extent * config.height_scale;
|
||||
let car_width = car_height * config.width_scale;
|
||||
let radar_center = vec2(
|
||||
width as f32 / 2.0,
|
||||
height as f32 / 2.0 - height as f32 * config.radar_center_factor,
|
||||
);
|
||||
|
||||
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: if config.radar_transparency == 0.0 {
|
||||
None
|
||||
} else {
|
||||
Some(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, config.radar_transparency],
|
||||
)?)
|
||||
},
|
||||
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(),
|
||||
|
||||
start_time,
|
||||
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(),
|
||||
receivers: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_car_object(&self, offset: Vector2<f32>, color: [f32; 4]) -> Result<RadarObject> {
|
||||
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<f32>,
|
||||
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),
|
||||
],
|
||||
)
|
||||
pub fn add_receiver(&mut self, receiver: Rc<RefCell<dyn UiOverlay>>) {
|
||||
self.receivers.push(receiver);
|
||||
}
|
||||
|
||||
fn now(&self) -> f32 {
|
||||
|
@ -200,8 +56,6 @@ impl RFactorData {
|
|||
pub fn update(&mut self) -> Result<()> {
|
||||
write_log!(" =================== update RFactorData ===================");
|
||||
|
||||
let mut should_render = false;
|
||||
|
||||
// get scoring info
|
||||
if let Some((scoring_info, vehicle_scorings)) =
|
||||
self.scoring_reader.vehicle_scoring(self.now())
|
||||
|
@ -224,223 +78,23 @@ impl RFactorData {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(id) = &self.player_id {
|
||||
if let Some(vehicle_scoring) =
|
||||
vehicle_scorings.iter().find(|scoring| scoring.mID == *id)
|
||||
{
|
||||
should_render = vehicle_scoring.mInPits != 0;
|
||||
}
|
||||
for receiver in self.receivers.iter() {
|
||||
receiver.borrow_mut().scoring_update(&vehicle_scorings)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
// check telemetry data
|
||||
write_log!("before telemetry update");
|
||||
if let Some(telemetries) = self.telemetry_reader.query_telemetry(self.now()) {
|
||||
write_log!("new telemetry update");
|
||||
|
||||
self.cars.clear();
|
||||
|
||||
if should_render {
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
for receiver in self.receivers.iter() {
|
||||
receiver
|
||||
.borrow_mut()
|
||||
.telemetry_update(self.player_id, &telemetries)?;
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
if let Some(background) = &self.background {
|
||||
objects.push(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<DescriptorSet>,
|
||||
|
||||
// uniform buffer
|
||||
color_buffer: Arc<Buffer<f32>>,
|
||||
|
||||
// vertex buffer
|
||||
position_buffer: Arc<Buffer<PositionOnlyVertex>>,
|
||||
}
|
||||
|
||||
impl RadarObject {
|
||||
fn new(
|
||||
device: Arc<Device>,
|
||||
descriptor_layout: &Arc<DescriptorSetLayout>,
|
||||
positions: [PositionOnlyVertex; 6],
|
||||
color: [f32; 4],
|
||||
) -> Result<Self> {
|
||||
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<f32>,
|
||||
offset: Vector2<f32>,
|
||||
player_rotation: impl Into<Deg<f32>>,
|
||||
rotation: impl Into<Deg<f32>>,
|
||||
radar_center: Vector2<f32>,
|
||||
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<DescriptorSet> {
|
||||
&self.descriptor_set
|
||||
}
|
||||
|
||||
fn buffer(&self) -> &Arc<Buffer<PositionOnlyVertex>> {
|
||||
&self.position_buffer
|
||||
}
|
||||
}
|
||||
|
||||
struct CarPosition {
|
||||
pub position: Vector3<f32>,
|
||||
pub rotation: Rad<f32>,
|
||||
}
|
||||
|
||||
impl CarPosition {
|
||||
fn new(position: Vector3<f32>, orientation: [Vector3<f32>; 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<f32>) -> Matrix4<f32> {
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue