Merge pull request 'Merge initial implementation of Pedal and Leader Board widgets' (#3) from ui into master
Reviewed-on: #3
This commit is contained in:
commit
144c7ab5f0
24 changed files with 1899 additions and 513 deletions
5
.vscode/tasks.json
vendored
5
.vscode/tasks.json
vendored
|
@ -4,11 +4,14 @@
|
||||||
{
|
{
|
||||||
"type": "cargo",
|
"type": "cargo",
|
||||||
"command": "build",
|
"command": "build",
|
||||||
|
"args": [
|
||||||
|
"--release"
|
||||||
|
],
|
||||||
"problemMatcher": [
|
"problemMatcher": [
|
||||||
"$rustc"
|
"$rustc"
|
||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"label": "rust: cargo build --release"
|
"label": "rust: cargo build"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
|
|
|
@ -10,10 +10,14 @@ crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vulkan-rs = { git = "https://gavania.de/hodasemi/vulkan_lib.git" }
|
vulkan-rs = { git = "https://gavania.de/hodasemi/vulkan_lib.git" }
|
||||||
|
assetpath = { git = "https://gavania.de/hodasemi/vulkan_lib.git" }
|
||||||
|
utilities = { git = "https://gavania.de/hodasemi/utilities.git" }
|
||||||
rfactor_sm_reader = { git = "https://gavania.de/hodasemi/rfactor_sm_reader.git" }
|
rfactor_sm_reader = { git = "https://gavania.de/hodasemi/rfactor_sm_reader.git" }
|
||||||
|
ui = { git = "https://gavania.de/hodasemi/ui.git" }
|
||||||
|
|
||||||
anyhow = { version = "1.0.68", features = ["backtrace"] }
|
anyhow = { version = "1.0.68", features = ["backtrace"] }
|
||||||
cgmath = { version = "0.18.0", features = ["swizzle", "serde"] }
|
cgmath = { version = "0.18.0", features = ["swizzle", "serde"] }
|
||||||
paste = "1.0.11"
|
paste = "1.0.11"
|
||||||
serde = "1.0.152"
|
serde = "1.0.152"
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
|
ringbuf = "0.3.2"
|
||||||
|
|
23
README.md
23
README.md
|
@ -2,16 +2,31 @@
|
||||||
This project is an attempt to render custom HUD elements for rFactor2 by being a Vulkan layer.
|
This project is an attempt to render custom HUD elements for rFactor2 by being a Vulkan layer.
|
||||||
|
|
||||||
# Current state
|
# Current state
|
||||||
I just started doing it. That means it isn't very usable right now. I'm working on a radar right now as the first element.
|
I would consider the following elements as working:
|
||||||
|
|
||||||
|
* Leader Board (enabled on in race)
|
||||||
|
* Radar
|
||||||
|
* Pedals
|
||||||
|
|
||||||
# How to enable
|
# How to enable
|
||||||
* Build this repository `cargo build --release`
|
|
||||||
* Change the path where the `libvk_layer_rs.so` is located (`library_path` parameter in the rFactorOverlay.json file)
|
### Archlinux based
|
||||||
* Put the rFactorOverlay.json into a layer directory ([layer directories](https://vulkan.lunarg.com/doc/view/1.3.216.0/mac/loader_and_layer_interface.html#user-content-linux-layer-discovery))
|
Simply use the PKGBUILD from the pkgbuild directory ([How to use it](https://wiki.archlinux.org/title/Makepkg))
|
||||||
|
|
||||||
|
### Manual installation
|
||||||
|
1) Build this repository `cargo build --release`
|
||||||
|
2) Change the path where the `libvk_layer_rs.so` is located (`library_path` parameter in the rFactorOverlay.json file)
|
||||||
|
3) Put the rFactorOverlay.json into a layer directory ([layer directories](https://vulkan.lunarg.com/doc/view/1.3.216.0/mac/loader_and_layer_interface.html#user-content-linux-layer-discovery))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
* Add `RFACTOR_HUD=1` to steam launch command (example: `RFACTOR_HUD=1 %command%`)
|
* Add `RFACTOR_HUD=1` to steam launch command (example: `RFACTOR_HUD=1 %command%`)
|
||||||
|
|
||||||
A config file is generated the first time you start it at: `$HOME/.config/rFactorHUD/config.json`
|
A config file is generated the first time you start it at: `$HOME/.config/rFactorHUD/config.json`
|
||||||
|
|
||||||
|
### Requirement
|
||||||
|
You need to have rFactor2 memory plugin shared file enabled ([rF2SharedMemoryMapPlugin_Wine](https://github.com/schlegp/rF2SharedMemoryMapPlugin_Wine))
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
|
|
||||||
## Vulkan Layer
|
## Vulkan Layer
|
||||||
|
|
6
build.rs
6
build.rs
|
@ -10,8 +10,10 @@ const VK_HEADER: &[&str] = &[
|
||||||
const FN_PREFIX: &str = "PFN_";
|
const FN_PREFIX: &str = "PFN_";
|
||||||
|
|
||||||
const SHADER: &[&str] = &[
|
const SHADER: &[&str] = &[
|
||||||
"src/overlay/shader/single_color.vert",
|
"src/overlay/elements/radar/single_color.vert",
|
||||||
"src/overlay/shader/single_color.frag",
|
"src/overlay/elements/radar/single_color.frag",
|
||||||
|
"src/overlay/elements/pedals/history.vert",
|
||||||
|
"src/overlay/elements/pedals/history.frag",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn query_vulkan_function_typedefs() {
|
fn query_vulkan_function_typedefs() {
|
||||||
|
|
BIN
font.png
Normal file
BIN
font.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
244
src/overlay/elements/gui.xsd
Normal file
244
src/overlay/elements/gui.xsd
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
<?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="direction">
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:enumeration value="left_to_right"></xs:enumeration>
|
||||||
|
<xs:enumeration value="bottom_to_top"></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:attribute ref="direction"></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>
|
12
src/overlay/elements/leaderboard/leaderboard_entry.xml
Normal file
12
src/overlay/elements/leaderboard/leaderboard_entry.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
|
<root>
|
||||||
|
<grid id="grid" x_dim="9" y_dim="1">
|
||||||
|
<label id="place" x_slot="0" y_slot="0" text_color="black" text_alignment="right"></label>
|
||||||
|
<label
|
||||||
|
id="name"
|
||||||
|
x_slot="1" y_slot="0" x_size="6" text_color="black" text_alignment="left"></label>
|
||||||
|
<label
|
||||||
|
id="time_behind"
|
||||||
|
x_slot="7" y_slot="0" x_size="2" text_color="black" text_alignment="right"></label>
|
||||||
|
</grid>
|
||||||
|
</root>
|
5
src/overlay/elements/leaderboard/leaderboard_grid.xml
Normal file
5
src/overlay/elements/leaderboard/leaderboard_grid.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
|
<root reference_width="2560" reference_height="1440">
|
||||||
|
<grid id="main_grid" x_dim="1" y_dim="25" x_offset="10" y_offset="10" width="350" height="875"
|
||||||
|
vert_align="top" hori_align="left" margin="0" padding="0"> </grid>
|
||||||
|
</root>
|
291
src/overlay/elements/leaderboard/mod.rs
Normal file
291
src/overlay/elements/leaderboard/mod.rs
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
use std::{
|
||||||
|
ffi::{c_char, CStr},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use utilities::prelude::Color;
|
||||||
|
|
||||||
|
use crate::overlay::{
|
||||||
|
rfactor_data::{DataReceiver, GamePhase},
|
||||||
|
UiOverlay,
|
||||||
|
};
|
||||||
|
use crate::write_log;
|
||||||
|
|
||||||
|
pub struct LeaderBoard {
|
||||||
|
gui_handler: Arc<GuiHandler>,
|
||||||
|
gui: Arc<GuiBuilder>,
|
||||||
|
main_grid: Arc<Grid>,
|
||||||
|
|
||||||
|
entries: Vec<LeaderBoardEntry>,
|
||||||
|
|
||||||
|
entry_backgrounds: [Color; 2],
|
||||||
|
player_background: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeaderBoard {
|
||||||
|
const GRID: &str = include_str!("leaderboard_grid.xml");
|
||||||
|
const ENTRY: &str = include_str!("leaderboard_entry.xml");
|
||||||
|
|
||||||
|
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
|
||||||
|
let gui = GuiBuilder::from_str(gui_handler, Self::GRID)?;
|
||||||
|
|
||||||
|
let main_grid = gui.element("main_grid")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
gui_handler: gui_handler.clone(),
|
||||||
|
gui,
|
||||||
|
main_grid,
|
||||||
|
|
||||||
|
entries: Vec::new(),
|
||||||
|
|
||||||
|
entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
|
||||||
|
player_background: Color::try_from("#b4bf26")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn c_char_to_string(c: [c_char; 32usize]) -> String {
|
||||||
|
unsafe { CStr::from_ptr(&c as *const c_char) }
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
|
||||||
|
for vehicle_scoring in vehicle_scorings {
|
||||||
|
let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);
|
||||||
|
|
||||||
|
// check driver list
|
||||||
|
match self
|
||||||
|
.entries
|
||||||
|
.iter_mut()
|
||||||
|
.find(|entry| vehicle_scoring.mID == entry.id())
|
||||||
|
{
|
||||||
|
Some(entry) => {
|
||||||
|
if entry.name() != driver_name {
|
||||||
|
entry.change_name(driver_name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.update_place(vehicle_scoring.mPlace)?;
|
||||||
|
entry.update_time_behind_leader(vehicle_scoring.mTimeBehindLeader)?;
|
||||||
|
entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let entry = LeaderBoardEntry::new(
|
||||||
|
&self.gui_handler,
|
||||||
|
vehicle_scoring.mID,
|
||||||
|
driver_name,
|
||||||
|
vehicle_scoring.mPlace,
|
||||||
|
vehicle_scoring.mTimeBehindLeader,
|
||||||
|
vehicle_scoring.mTimeBehindNext,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.entries.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_log!("create entries");
|
||||||
|
|
||||||
|
// check if entry count in grid is the same as the gathered entries
|
||||||
|
let force_update = if !self.entries.is_empty()
|
||||||
|
&& self
|
||||||
|
.main_grid
|
||||||
|
.child_at(0, self.entries.len() - 1)?
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
for i in 0..self.entries.len() {
|
||||||
|
self.main_grid.detach(0, i)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if any entry needs resorting
|
||||||
|
if force_update || self.entries.iter().any(|entry| entry.needs_resorting()) {
|
||||||
|
write_log!("leader board update required");
|
||||||
|
|
||||||
|
self.entries
|
||||||
|
.sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));
|
||||||
|
|
||||||
|
for (i, entry) in self.entries.iter_mut().enumerate() {
|
||||||
|
entry.resorting_finished();
|
||||||
|
entry.change_background_color(self.entry_backgrounds[i % 2])?;
|
||||||
|
|
||||||
|
self.main_grid.attach(entry.snippet(), 0, i, 1, 1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.entries.is_empty() {
|
||||||
|
self.gui.disable()?;
|
||||||
|
} else {
|
||||||
|
self.gui.enable()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiOverlay for LeaderBoard {}
|
||||||
|
|
||||||
|
impl DataReceiver for LeaderBoard {
|
||||||
|
fn scoring_update(
|
||||||
|
&mut self,
|
||||||
|
phase: GamePhase,
|
||||||
|
vehicle_scorings: &[VehicleScoringInfoV01],
|
||||||
|
) -> Result<()> {
|
||||||
|
write_log!("=================== leader board: scoring update ===================");
|
||||||
|
|
||||||
|
match phase {
|
||||||
|
GamePhase::TestDay => self.gui.disable()?,
|
||||||
|
GamePhase::Practice => self.gui.disable()?,
|
||||||
|
GamePhase::Qualifying => self.gui.disable()?,
|
||||||
|
GamePhase::Warmup => self.gui.disable()?,
|
||||||
|
GamePhase::Race => {
|
||||||
|
self.race_leaderboard(vehicle_scorings)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_log!("leader board update finished");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_update(
|
||||||
|
&mut self,
|
||||||
|
player_id: Option<i32>,
|
||||||
|
_telemetries: &[rF2VehicleTelemetry],
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(player_id) = player_id {
|
||||||
|
if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) {
|
||||||
|
entry.change_background_color(self.player_background)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LeaderBoardEntry {
|
||||||
|
id: i32,
|
||||||
|
|
||||||
|
name: String,
|
||||||
|
place: u8,
|
||||||
|
time_behind_leader: f64,
|
||||||
|
time_behind_next: f64,
|
||||||
|
|
||||||
|
snippet: Arc<GuiSnippet>,
|
||||||
|
|
||||||
|
grid: Arc<Grid>,
|
||||||
|
name_label: Arc<Label>,
|
||||||
|
place_label: Arc<Label>,
|
||||||
|
time_behind_label: Arc<Label>,
|
||||||
|
|
||||||
|
place_updated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeaderBoardEntry {
|
||||||
|
pub fn new(
|
||||||
|
gui_handler: &Arc<GuiHandler>,
|
||||||
|
id: i32,
|
||||||
|
name: String,
|
||||||
|
place: u8,
|
||||||
|
time_behind_leader: f64,
|
||||||
|
time_behind_next: f64,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let snippet = GuiSnippet::from_str(gui_handler, LeaderBoard::ENTRY)?;
|
||||||
|
|
||||||
|
let background = snippet.element("grid")?;
|
||||||
|
let name_label: Arc<Label> = snippet.element("name")?;
|
||||||
|
let place_label: Arc<Label> = snippet.element("place")?;
|
||||||
|
let time_behind_label: Arc<Label> = snippet.element("time_behind")?;
|
||||||
|
|
||||||
|
name_label.set_text(&name)?;
|
||||||
|
place_label.set_text(place)?;
|
||||||
|
time_behind_label.set_text(format!("{:.3}", time_behind_leader))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
|
||||||
|
name,
|
||||||
|
place,
|
||||||
|
time_behind_leader,
|
||||||
|
time_behind_next,
|
||||||
|
|
||||||
|
snippet,
|
||||||
|
|
||||||
|
grid: background,
|
||||||
|
name_label,
|
||||||
|
place_label,
|
||||||
|
time_behind_label,
|
||||||
|
|
||||||
|
place_updated: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> i32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn place(&self) -> u8 {
|
||||||
|
self.place
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_name(&mut self, name: String) -> Result<()> {
|
||||||
|
self.name = name;
|
||||||
|
|
||||||
|
self.name_label.set_text(&self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snippet(&self) -> Arc<GuiSnippet> {
|
||||||
|
self.snippet.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_background_color(&self, color: Color) -> Result<()> {
|
||||||
|
self.grid.set_background(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_place(&mut self, place: u8) -> Result<()> {
|
||||||
|
if self.place != place {
|
||||||
|
self.place_updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.place = place;
|
||||||
|
|
||||||
|
self.place_label.set_text(self.place)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_time_behind_leader(&mut self, time: f64) -> Result<()> {
|
||||||
|
self.time_behind_leader = time;
|
||||||
|
|
||||||
|
// check if we are leader
|
||||||
|
if self.time_behind_leader == 0.0 {
|
||||||
|
self.time_behind_label.set_text("---")
|
||||||
|
} else {
|
||||||
|
self.time_behind_label
|
||||||
|
.set_text(format!("+{:.3}", self.time_behind_leader))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
|
||||||
|
self.time_behind_next = time;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_resorting(&self) -> bool {
|
||||||
|
self.place_updated
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resorting_finished(&mut self) {
|
||||||
|
self.place_updated = false;
|
||||||
|
}
|
||||||
|
}
|
48
src/overlay/elements/mod.rs
Normal file
48
src/overlay/elements/mod.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
mod leaderboard;
|
||||||
|
mod pedals;
|
||||||
|
mod radar;
|
||||||
|
mod watermark;
|
||||||
|
|
||||||
|
pub use leaderboard::*;
|
||||||
|
pub use pedals::*;
|
||||||
|
pub use radar::*;
|
||||||
|
pub use watermark::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PositionOnlyVertex {
|
||||||
|
pub position: cgmath::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: cgmath::Matrix4<f32>,
|
||||||
|
corners: [cgmath::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),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
12
src/overlay/elements/pedals/history.frag
Normal file
12
src/overlay/elements/pedals/history.frag
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform Color {
|
||||||
|
vec4 val;
|
||||||
|
} color;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = color.val;
|
||||||
|
}
|
296
src/overlay/elements/pedals/mod.rs
Normal file
296
src/overlay/elements/pedals/mod.rs
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
mod pipeline;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use cgmath::{ortho, vec4, Matrix4};
|
||||||
|
use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
|
||||||
|
use ringbuf::{HeapRb, Rb};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use vulkan_rs::prelude::*;
|
||||||
|
|
||||||
|
use crate::overlay::{
|
||||||
|
rfactor_data::{DataReceiver, GamePhase},
|
||||||
|
UiOverlay,
|
||||||
|
};
|
||||||
|
use crate::write_log;
|
||||||
|
|
||||||
|
use self::pipeline::HistoryPipeline;
|
||||||
|
|
||||||
|
use super::PositionOnlyVertex;
|
||||||
|
|
||||||
|
pub struct Pedals {
|
||||||
|
gui: Arc<GuiBuilder>,
|
||||||
|
|
||||||
|
brake: Arc<ProgressBar>,
|
||||||
|
throttle: Arc<ProgressBar>,
|
||||||
|
_history: Arc<Icon>,
|
||||||
|
|
||||||
|
throttle_samples: HeapRb<f32>,
|
||||||
|
brake_samples: HeapRb<f32>,
|
||||||
|
|
||||||
|
ortho: Matrix4<f32>,
|
||||||
|
|
||||||
|
device: Arc<Device>,
|
||||||
|
queue: Arc<Mutex<Queue>>,
|
||||||
|
|
||||||
|
render_target: RenderTarget,
|
||||||
|
pipeline: HistoryPipeline,
|
||||||
|
|
||||||
|
brake_descriptor: Arc<DescriptorSet>,
|
||||||
|
brake_vertex_buffer: Arc<Buffer<PositionOnlyVertex>>,
|
||||||
|
throttle_descriptor: Arc<DescriptorSet>,
|
||||||
|
throttle_vertex_buffer: Arc<Buffer<PositionOnlyVertex>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pedals {
|
||||||
|
pub fn new(
|
||||||
|
gui_handler: &Arc<GuiHandler>,
|
||||||
|
device: Arc<Device>,
|
||||||
|
queue: Arc<Mutex<Queue>>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
const DESC: &str = include_str!("pedals.xml");
|
||||||
|
|
||||||
|
let gui = GuiBuilder::from_str(gui_handler, DESC)?;
|
||||||
|
|
||||||
|
let brake = gui.element("brake")?;
|
||||||
|
let throttle = gui.element("throttle")?;
|
||||||
|
let history: Arc<Icon> = gui.element("history")?;
|
||||||
|
|
||||||
|
let (icon_width, icon_height) = history.extent();
|
||||||
|
let history_image = Image::empty(
|
||||||
|
icon_width as u32,
|
||||||
|
icon_height as u32,
|
||||||
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||||
|
VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
)
|
||||||
|
.format(VK_FORMAT_R8G8B8A8_UNORM)
|
||||||
|
.attach_sampler(Sampler::nearest_sampler().build(&device)?)
|
||||||
|
.build(&device, &queue)?;
|
||||||
|
|
||||||
|
history_image.convert_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)?;
|
||||||
|
|
||||||
|
history.set_icon(&history_image)?;
|
||||||
|
|
||||||
|
let render_target = RenderTarget::builder()
|
||||||
|
.add_sub_pass(
|
||||||
|
SubPass::builder(history_image.width(), history_image.height())
|
||||||
|
.set_prepared_targets(&[history_image.clone()], 0, [0.3, 0.3, 0.3, 1.0], true)
|
||||||
|
.build(&device, &queue)?,
|
||||||
|
)
|
||||||
|
.build(&device)?;
|
||||||
|
|
||||||
|
let pipeline = HistoryPipeline::new(
|
||||||
|
device.clone(),
|
||||||
|
render_target.render_pass(),
|
||||||
|
history_image.width(),
|
||||||
|
history_image.height(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let ortho = ortho(0.0, history_image.width() as f32, -0.01, 1.01, -1.0, 1.0);
|
||||||
|
|
||||||
|
let descriptor_pool = DescriptorPool::builder()
|
||||||
|
.set_layout(pipeline.descriptor_layout().clone())
|
||||||
|
.set_descriptor_set_count(2)
|
||||||
|
.build(device.clone())?;
|
||||||
|
|
||||||
|
let brake_color_buffer: Arc<Buffer<f32>> = Buffer::builder()
|
||||||
|
.set_usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)
|
||||||
|
.set_memory_usage(MemoryUsage::CpuOnly)
|
||||||
|
.set_data(&[0.9, 0.0, 0.0, 1.0])
|
||||||
|
.build(device.clone())?;
|
||||||
|
|
||||||
|
write_log!("allocate brake descriptor");
|
||||||
|
|
||||||
|
let brake_descriptor = descriptor_pool.prepare_set().allocate()?;
|
||||||
|
brake_descriptor.update(&[DescriptorWrite::uniform_buffers(0, &[&brake_color_buffer])])?;
|
||||||
|
|
||||||
|
let throttle_color_buffer: Arc<Buffer<f32>> = Buffer::builder()
|
||||||
|
.set_usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)
|
||||||
|
.set_memory_usage(MemoryUsage::CpuOnly)
|
||||||
|
.set_data(&[0.0, 0.9, 0.0, 1.0])
|
||||||
|
.build(device.clone())?;
|
||||||
|
|
||||||
|
write_log!("allocate throttle descriptor");
|
||||||
|
|
||||||
|
let throttle_descriptor = descriptor_pool.prepare_set().allocate()?;
|
||||||
|
throttle_descriptor.update(&[DescriptorWrite::uniform_buffers(
|
||||||
|
0,
|
||||||
|
&[&throttle_color_buffer],
|
||||||
|
)])?;
|
||||||
|
|
||||||
|
let mut throttle_samples = HeapRb::new(icon_width as usize);
|
||||||
|
let mut brake_samples = HeapRb::new(icon_width as usize);
|
||||||
|
|
||||||
|
for _ in 0..icon_width {
|
||||||
|
throttle_samples.push_overwrite(0.0);
|
||||||
|
brake_samples.push_overwrite(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let brake_vertex_buffer = Self::create_vertex_buffer(&device, icon_width as VkDeviceSize)?;
|
||||||
|
let throttle_vertex_buffer =
|
||||||
|
Self::create_vertex_buffer(&device, icon_width as VkDeviceSize)?;
|
||||||
|
|
||||||
|
let me = Self {
|
||||||
|
gui,
|
||||||
|
|
||||||
|
brake,
|
||||||
|
throttle,
|
||||||
|
_history: history,
|
||||||
|
|
||||||
|
throttle_samples,
|
||||||
|
brake_samples,
|
||||||
|
|
||||||
|
ortho,
|
||||||
|
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
|
||||||
|
render_target,
|
||||||
|
pipeline,
|
||||||
|
|
||||||
|
brake_descriptor,
|
||||||
|
brake_vertex_buffer,
|
||||||
|
throttle_descriptor,
|
||||||
|
throttle_vertex_buffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
me.update_vertex_buffers()?;
|
||||||
|
|
||||||
|
Ok(me)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_vertex_buffer(
|
||||||
|
device: &Arc<Device>,
|
||||||
|
size: VkDeviceSize,
|
||||||
|
) -> Result<Arc<Buffer<PositionOnlyVertex>>> {
|
||||||
|
Buffer::builder()
|
||||||
|
.set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
|
||||||
|
.set_memory_usage(MemoryUsage::CpuOnly)
|
||||||
|
.set_size(size)
|
||||||
|
.build(device.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_vertex_buffers(&self) -> Result<()> {
|
||||||
|
self.update_vertex_buffer(
|
||||||
|
&self.throttle_vertex_buffer,
|
||||||
|
self.throttle_samples.as_slices(),
|
||||||
|
)?;
|
||||||
|
self.update_vertex_buffer(&self.brake_vertex_buffer, self.brake_samples.as_slices())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_vertex_buffer(
|
||||||
|
&self,
|
||||||
|
buffer: &Arc<Buffer<PositionOnlyVertex>>,
|
||||||
|
(data1, data2): (&[f32], &[f32]),
|
||||||
|
) -> Result<()> {
|
||||||
|
let points = data1
|
||||||
|
.iter()
|
||||||
|
.chain(data2.iter())
|
||||||
|
.enumerate()
|
||||||
|
.map(|(x, &date)| PositionOnlyVertex {
|
||||||
|
position: self.ortho * vec4(x as f32, date, 0.0, 1.0),
|
||||||
|
})
|
||||||
|
.collect::<Vec<PositionOnlyVertex>>();
|
||||||
|
|
||||||
|
buffer.fill(&points)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self) -> 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, 0);
|
||||||
|
|
||||||
|
recorder.bind_pipeline(self.pipeline.pipeline())?;
|
||||||
|
|
||||||
|
recorder.bind_descriptor_sets_minimal(&[&self.throttle_descriptor]);
|
||||||
|
recorder.bind_vertex_buffer(&self.throttle_vertex_buffer);
|
||||||
|
recorder.draw_complete_single_instance(self.throttle_vertex_buffer.size() as u32);
|
||||||
|
|
||||||
|
recorder.bind_descriptor_sets_minimal(&[&self.brake_descriptor]);
|
||||||
|
recorder.bind_vertex_buffer(&self.brake_vertex_buffer);
|
||||||
|
recorder.draw_complete_single_instance(self.brake_vertex_buffer.size() as u32);
|
||||||
|
|
||||||
|
self.render_target.end(&recorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(command_buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiOverlay for Pedals {}
|
||||||
|
|
||||||
|
impl DataReceiver for Pedals {
|
||||||
|
fn scoring_update(
|
||||||
|
&mut self,
|
||||||
|
_phase: GamePhase,
|
||||||
|
_vehicle_scoring: &[VehicleScoringInfoV01],
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_update(
|
||||||
|
&mut self,
|
||||||
|
player_id: Option<i32>,
|
||||||
|
telemetries: &[rF2VehicleTelemetry],
|
||||||
|
) -> Result<()> {
|
||||||
|
match player_id {
|
||||||
|
Some(id) => {
|
||||||
|
self.gui.enable()?;
|
||||||
|
|
||||||
|
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
|
||||||
|
let brake = 1.0 - telemetry.unfiltered_brake as f32;
|
||||||
|
let throttle = 1.0 - telemetry.unfiltered_throttle as f32;
|
||||||
|
|
||||||
|
self.throttle.set_progress(throttle)?;
|
||||||
|
self.brake.set_progress(brake)?;
|
||||||
|
|
||||||
|
self.throttle_samples.push_overwrite(throttle);
|
||||||
|
self.brake_samples.push_overwrite(brake);
|
||||||
|
|
||||||
|
self.update_vertex_buffers()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.gui.disable()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use ringbuf::{HeapRb, Rb};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rb_test() {
|
||||||
|
const CAP: usize = 10;
|
||||||
|
|
||||||
|
let mut buf = HeapRb::new(CAP);
|
||||||
|
|
||||||
|
for _ in 0..CAP {
|
||||||
|
buf.push_overwrite(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:?}", buf.as_slices());
|
||||||
|
|
||||||
|
buf.push_overwrite(40);
|
||||||
|
buf.push_overwrite(40);
|
||||||
|
buf.push_overwrite(40);
|
||||||
|
buf.push_overwrite(40);
|
||||||
|
|
||||||
|
println!("{:?}", buf.as_slices());
|
||||||
|
}
|
||||||
|
}
|
14
src/overlay/elements/pedals/pedals.xml
Normal file
14
src/overlay/elements/pedals/pedals.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?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="10" y_dim="2" x_offset="-850" y_offset="-190" width="200" height="160"
|
||||||
|
vert_align="bottom" hori_align="right" margin="3" padding="3" background="#686868">
|
||||||
|
<progressbar id="brake" x_slot="0" y_slot="0" y_size="2" background="#494949"
|
||||||
|
direction="bottom_to_top" foreground="#e30000"></progressbar>
|
||||||
|
<progressbar id="throttle"
|
||||||
|
x_slot="1" y_slot="0" y_size="2" background="#494949" direction="bottom_to_top"
|
||||||
|
foreground="#00b900"></progressbar>
|
||||||
|
|
||||||
|
<icon id="history" x_slot="2" x_size="8" y_slot="0"
|
||||||
|
y_size="2"></icon>
|
||||||
|
</grid>
|
||||||
|
</root>
|
102
src/overlay/elements/pedals/pipeline.rs
Normal file
102
src/overlay/elements/pedals/pipeline.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use vulkan_rs::prelude::*;
|
||||||
|
|
||||||
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
|
use super::super::PositionOnlyVertex;
|
||||||
|
|
||||||
|
pub struct HistoryPipeline {
|
||||||
|
pipeline: Arc<Pipeline>,
|
||||||
|
descriptor_layout: Arc<DescriptorSetLayout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HistoryPipeline {
|
||||||
|
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!("history.vert.spv"),
|
||||||
|
ShaderType::Vertex,
|
||||||
|
)?;
|
||||||
|
let fragment_shader = ShaderModule::from_slice(
|
||||||
|
device.clone(),
|
||||||
|
include_bytes!("history.frag.spv"),
|
||||||
|
ShaderType::Fragment,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let descriptor_layout = DescriptorSetLayout::builder()
|
||||||
|
.add_layout_binding(
|
||||||
|
0,
|
||||||
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||||
|
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.build(device.clone())?;
|
||||||
|
|
||||||
|
let pipeline_layout = PipelineLayout::builder()
|
||||||
|
.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(),
|
||||||
|
vec![VkVertexInputBindingDescription {
|
||||||
|
binding: 0,
|
||||||
|
stride: mem::size_of::<PositionOnlyVertex>() as u32,
|
||||||
|
inputRate: VK_VERTEX_INPUT_RATE_VERTEX,
|
||||||
|
}],
|
||||||
|
vec![
|
||||||
|
// position
|
||||||
|
VkVertexInputAttributeDescription {
|
||||||
|
location: 0,
|
||||||
|
binding: 0,
|
||||||
|
format: VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.set_fragment_shader(fragment_shader.clone())
|
||||||
|
.input_assembly(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, false)
|
||||||
|
.default_depth_stencil(false, false)
|
||||||
|
.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 {
|
||||||
|
descriptor_layout,
|
||||||
|
pipeline,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pipeline(&self) -> &Arc<Pipeline> {
|
||||||
|
&self.pipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn descriptor_layout(&self) -> &Arc<DescriptorSetLayout> {
|
||||||
|
&self.descriptor_layout
|
||||||
|
}
|
||||||
|
}
|
454
src/overlay/elements/radar/mod.rs
Normal file
454
src/overlay/elements/radar/mod.rs
Normal file
|
@ -0,0 +1,454 @@
|
||||||
|
mod pipeline;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3};
|
||||||
|
use rfactor_sm_reader::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use vulkan_rs::prelude::*;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use pipeline::SingleColorPipeline;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
overlay::{
|
||||||
|
rendering::Rendering,
|
||||||
|
rfactor_data::{DataReceiver, GamePhase},
|
||||||
|
UiOverlay,
|
||||||
|
},
|
||||||
|
write_log,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::PositionOnlyVertex;
|
||||||
|
|
||||||
|
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>,
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Radar {
|
||||||
|
pub fn new(
|
||||||
|
config: RadarConfig,
|
||||||
|
device: Arc<Device>,
|
||||||
|
queue: Arc<Mutex<Queue>>,
|
||||||
|
rendering: &Rendering,
|
||||||
|
) -> Result<Self> {
|
||||||
|
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(),
|
||||||
|
|
||||||
|
radar_center,
|
||||||
|
ortho,
|
||||||
|
_window_width: rendering.swapchain().width(),
|
||||||
|
_window_height: rendering.swapchain().height(),
|
||||||
|
radar_extent,
|
||||||
|
car_width,
|
||||||
|
car_height,
|
||||||
|
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
|
||||||
|
render_target,
|
||||||
|
pipeline,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_log!(format!("obj count {}", objects.len()));
|
||||||
|
|
||||||
|
objects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiOverlay for Radar {}
|
||||||
|
|
||||||
|
impl DataReceiver for Radar {
|
||||||
|
fn scoring_update(
|
||||||
|
&mut self,
|
||||||
|
_phase: GamePhase,
|
||||||
|
_vehicle_scoring: &[VehicleScoringInfoV01],
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_update(
|
||||||
|
&mut self,
|
||||||
|
player_id: Option<i32>,
|
||||||
|
telemetries: &[rF2VehicleTelemetry],
|
||||||
|
) -> Result<()> {
|
||||||
|
write_log!(" ============================ Radar telemetry udpate ======================");
|
||||||
|
write_log!(format!("player id {:?}", player_id));
|
||||||
|
|
||||||
|
self.cars.clear();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_log!(format!("other cars: {:?}", self.cars.len()));
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use vulkan_rs::prelude::*;
|
||||||
|
|
||||||
use std::{mem, sync::Arc};
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
use super::rendering::PositionOnlyVertex;
|
use super::PositionOnlyVertex;
|
||||||
|
|
||||||
pub struct SingleColorPipeline {
|
pub struct SingleColorPipeline {
|
||||||
pipeline: Arc<Pipeline>,
|
pipeline: Arc<Pipeline>,
|
||||||
|
@ -11,15 +11,20 @@ pub struct SingleColorPipeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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(
|
let vertex_shader = ShaderModule::from_slice(
|
||||||
device.clone(),
|
device.clone(),
|
||||||
include_bytes!("shader/single_color.vert.spv"),
|
include_bytes!("single_color.vert.spv"),
|
||||||
ShaderType::Vertex,
|
ShaderType::Vertex,
|
||||||
)?;
|
)?;
|
||||||
let fragment_shader = ShaderModule::from_slice(
|
let fragment_shader = ShaderModule::from_slice(
|
||||||
device.clone(),
|
device.clone(),
|
||||||
include_bytes!("shader/single_color.frag.spv"),
|
include_bytes!("single_color.frag.spv"),
|
||||||
ShaderType::Fragment,
|
ShaderType::Fragment,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -36,6 +41,23 @@ impl SingleColorPipeline {
|
||||||
.add_descriptor_set_layout(&descriptor_layout)
|
.add_descriptor_set_layout(&descriptor_layout)
|
||||||
.build(device.clone())?;
|
.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()
|
let pipeline = Pipeline::new_graphics()
|
||||||
.set_vertex_shader(
|
.set_vertex_shader(
|
||||||
vertex_shader.clone(),
|
vertex_shader.clone(),
|
||||||
|
@ -60,6 +82,8 @@ impl SingleColorPipeline {
|
||||||
.default_color_blend(vec![VkPipelineColorBlendAttachmentState::default()])
|
.default_color_blend(vec![VkPipelineColorBlendAttachmentState::default()])
|
||||||
.default_rasterization(VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE)
|
.default_rasterization(VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE)
|
||||||
.default_multisample(VK_SAMPLE_COUNT_1_BIT)
|
.default_multisample(VK_SAMPLE_COUNT_1_BIT)
|
||||||
|
.add_viewport(viewport)
|
||||||
|
.add_scissor(scissor)
|
||||||
.build(device, &pipeline_layout, &renderpass, 0)?;
|
.build(device, &pipeline_layout, &renderpass, 0)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
8
src/overlay/elements/radar/single_color.vert
Normal file
8
src/overlay/elements/radar/single_color.vert
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 position;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = position;
|
||||||
|
}
|
49
src/overlay/elements/watermark/mod.rs
Normal file
49
src/overlay/elements/watermark/mod.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
use crate::overlay::{
|
||||||
|
rfactor_data::{DataReceiver, GamePhase},
|
||||||
|
UiOverlay,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Watermark {
|
||||||
|
gui: Arc<GuiBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Watermark {
|
||||||
|
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
|
||||||
|
const DESC: &str = include_str!("watermark.xml");
|
||||||
|
|
||||||
|
let gui = GuiBuilder::from_str(gui_handler, DESC)?;
|
||||||
|
|
||||||
|
Ok(Self { gui })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiOverlay for Watermark {}
|
||||||
|
|
||||||
|
impl DataReceiver for Watermark {
|
||||||
|
fn scoring_update(
|
||||||
|
&mut self,
|
||||||
|
phase: GamePhase,
|
||||||
|
_vehicle_scoring: &[VehicleScoringInfoV01],
|
||||||
|
) -> Result<()> {
|
||||||
|
match phase {
|
||||||
|
GamePhase::TestDay => self.gui.enable()?,
|
||||||
|
_ => self.gui.disable()?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_update(
|
||||||
|
&mut self,
|
||||||
|
_player_id: Option<i32>,
|
||||||
|
_telemetries: &[rF2VehicleTelemetry],
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
8
src/overlay/elements/watermark/watermark.xml
Normal file
8
src/overlay/elements/watermark/watermark.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?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="1" y_dim="1" x_offset="10" y_offset="10" width="300" height="50"
|
||||||
|
vert_align="top" hori_align="left" margin="2" padding="2" background="#c9c9c9">
|
||||||
|
<label x_slot="0" y_slot="0" text_color="#c9c9c9" text_ratio="1.0"
|
||||||
|
background="black">Vulkan Overlay Enabled</label>
|
||||||
|
</grid>
|
||||||
|
</root>
|
|
@ -2,28 +2,60 @@ use crate::write_log;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
rendering::Rendering,
|
rendering::Rendering,
|
||||||
rfactor_data::{DataConfig, RFactorData},
|
rfactor_data::{DataReceiver, RFactorData},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod pipeline;
|
mod elements;
|
||||||
mod rendering;
|
mod rendering;
|
||||||
mod rfactor_data;
|
mod rfactor_data;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::sync::{Arc, Mutex};
|
use assetpath::AssetPath;
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
rc::Rc,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
use ui::{guihandler::guihandler::Font, prelude::*};
|
||||||
use vulkan_rs::prelude::*;
|
use vulkan_rs::prelude::*;
|
||||||
|
|
||||||
|
use elements::*;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub trait UiOverlay: DataReceiver {}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct UiSelectorConfig {
|
||||||
|
pub enable_watermark: bool,
|
||||||
|
|
||||||
|
pub enable_radar: bool,
|
||||||
|
pub enable_pedals: bool,
|
||||||
|
pub enable_leaderboard: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiSelectorConfig {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
enable_watermark: true,
|
||||||
|
enable_radar: true,
|
||||||
|
enable_pedals: true,
|
||||||
|
enable_leaderboard: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct OverlayConfig {
|
pub struct OverlayConfig {
|
||||||
pub data_config: DataConfig,
|
pub ui_config: UiSelectorConfig,
|
||||||
|
pub radar_config: RadarConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayConfig {
|
impl OverlayConfig {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
data_config: DataConfig::new(),
|
ui_config: UiSelectorConfig::new(),
|
||||||
|
radar_config: RadarConfig::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +67,9 @@ pub struct Overlay {
|
||||||
device: Option<Arc<Device>>,
|
device: Option<Arc<Device>>,
|
||||||
queue: Option<Arc<Mutex<Queue>>>,
|
queue: Option<Arc<Mutex<Queue>>>,
|
||||||
rendering: Option<Rendering>,
|
rendering: Option<Rendering>,
|
||||||
|
gui_handler: Option<Arc<GuiHandler>>,
|
||||||
|
|
||||||
|
ui_elements: Vec<Rc<RefCell<dyn UiOverlay>>>,
|
||||||
|
|
||||||
rfactor_data: Option<RFactorData>,
|
rfactor_data: Option<RFactorData>,
|
||||||
}
|
}
|
||||||
|
@ -48,6 +83,8 @@ impl Overlay {
|
||||||
device: None,
|
device: None,
|
||||||
queue: None,
|
queue: None,
|
||||||
rendering: None,
|
rendering: None,
|
||||||
|
gui_handler: None,
|
||||||
|
ui_elements: Vec::new(),
|
||||||
|
|
||||||
rfactor_data: None,
|
rfactor_data: None,
|
||||||
}
|
}
|
||||||
|
@ -98,44 +135,192 @@ impl Overlay {
|
||||||
|
|
||||||
write_log!("-> create rendering: old cleared");
|
write_log!("-> create rendering: old cleared");
|
||||||
|
|
||||||
self.rendering = Some(Rendering::new(self.device(), self.queue(), swapchain)?);
|
let mut rendering = Rendering::new(self.queue(), swapchain.clone())?;
|
||||||
|
|
||||||
write_log!("-> create rendering: new created");
|
write_log!("-> create rendering: new created");
|
||||||
|
|
||||||
|
// only font is used
|
||||||
|
let mut create_info = GuiHandlerCreateInfo::default();
|
||||||
|
|
||||||
|
create_info.font = Font::Bytes(include_bytes!("../../font.png"));
|
||||||
|
|
||||||
|
// required to not crash
|
||||||
|
create_info.resource_directory = AssetPath::from("");
|
||||||
|
create_info.resource_directory.assume_prefix_free();
|
||||||
|
|
||||||
|
// provide trait required by GuiHandler
|
||||||
|
let ctx = Arc::new(ContextImpl::new(
|
||||||
|
self.device(),
|
||||||
|
self.queue(),
|
||||||
|
swapchain,
|
||||||
|
rendering.images().clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// create GuiHandler
|
||||||
|
let gui_handler = GuiHandler::new(create_info, &(ctx as Arc<dyn ContextInterface>))?;
|
||||||
|
write_log!("GuiHandler successfully created");
|
||||||
|
|
||||||
|
// create ui elements
|
||||||
|
|
||||||
|
// create watermark
|
||||||
|
if self.config.ui_config.enable_watermark {
|
||||||
|
let watermark = Rc::new(RefCell::new(Watermark::new(&gui_handler)?));
|
||||||
|
|
||||||
|
self.ui_elements.push(watermark);
|
||||||
|
|
||||||
|
write_log!("Watermark successfully created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create radar
|
||||||
|
if self.config.ui_config.enable_radar {
|
||||||
|
let radar = Rc::new(RefCell::new(Radar::new(
|
||||||
|
self.config.radar_config,
|
||||||
|
self.device(),
|
||||||
|
self.queue(),
|
||||||
|
&rendering,
|
||||||
|
)?));
|
||||||
|
|
||||||
|
rendering.add_render_callback({
|
||||||
|
let radar = radar.clone();
|
||||||
|
|
||||||
|
move |index| radar.borrow().render(index)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.ui_elements.push(radar);
|
||||||
|
|
||||||
|
write_log!("Radar successfully created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create pedals
|
||||||
|
if self.config.ui_config.enable_pedals {
|
||||||
|
let pedals = Rc::new(RefCell::new(Pedals::new(
|
||||||
|
&gui_handler,
|
||||||
|
self.device(),
|
||||||
|
self.queue(),
|
||||||
|
)?));
|
||||||
|
|
||||||
|
self.ui_elements.push(pedals.clone());
|
||||||
|
|
||||||
|
rendering.add_render_callback(move |_| pedals.borrow().render());
|
||||||
|
|
||||||
|
write_log!("Pedals successfully created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create leaderboard
|
||||||
|
if self.config.ui_config.enable_leaderboard {
|
||||||
|
let leaderboard = Rc::new(RefCell::new(LeaderBoard::new(&gui_handler)?));
|
||||||
|
self.ui_elements.push(leaderboard);
|
||||||
|
|
||||||
|
write_log!("Leader Board successfully created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// add rendering callbacks
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
write_log!("render callbacks added");
|
||||||
|
|
||||||
|
self.rendering = Some(rendering);
|
||||||
|
self.gui_handler = Some(gui_handler);
|
||||||
|
|
||||||
write_log!("-> create rendering: end");
|
write_log!("-> create rendering: end");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self) -> Result<()> {
|
pub fn render(&mut self) -> Result<()> {
|
||||||
let swapchain = self.rendering.as_ref().unwrap().swapchain().clone();
|
|
||||||
|
|
||||||
if self.rfactor_data.is_none() {
|
if self.rfactor_data.is_none() {
|
||||||
self.rfactor_data = RFactorData::new(
|
self.rfactor_data = RFactorData::new().ok();
|
||||||
self.config.data_config,
|
|
||||||
self.device(),
|
|
||||||
self.rendering
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.single_color_pipeline()
|
|
||||||
.descriptor_layout(),
|
|
||||||
swapchain.width(),
|
|
||||||
swapchain.height(),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
|
if let Some(data) = &mut self.rfactor_data {
|
||||||
write_log!("created RFactorData");
|
write_log!("created RFactorData");
|
||||||
|
|
||||||
|
for receiver in self.ui_elements.iter() {
|
||||||
|
data.add_receiver(receiver.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check twice for rfactor data, because of borrowing rules
|
|
||||||
if let Some(rfactor) = &mut self.rfactor_data {
|
if let Some(rfactor) = &mut self.rfactor_data {
|
||||||
rfactor.update()?;
|
rfactor.update()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let objects = match &self.rfactor_data {
|
self.rendering.as_ref().unwrap().render()
|
||||||
Some(rfactor) => rfactor.objects(),
|
}
|
||||||
None => Vec::new(),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
self.rendering.as_mut().unwrap().render(swapchain, &objects)
|
struct ContextImpl {
|
||||||
|
device: Arc<Device>,
|
||||||
|
queue: Arc<Mutex<Queue>>,
|
||||||
|
swapchain: Arc<Swapchain>,
|
||||||
|
images: Vec<Arc<Image>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextImpl {
|
||||||
|
fn new(
|
||||||
|
device: Arc<Device>,
|
||||||
|
queue: Arc<Mutex<Queue>>,
|
||||||
|
swapchain: Arc<Swapchain>,
|
||||||
|
images: Vec<Arc<Image>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
swapchain,
|
||||||
|
images,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextInterface for ContextImpl {
|
||||||
|
fn device(&self) -> &Arc<Device> {
|
||||||
|
&self.device
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue(&self) -> &Arc<Mutex<Queue>> {
|
||||||
|
&self.queue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self) -> VkFormat {
|
||||||
|
self.swapchain.format()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_layout(&self) -> VkImageLayout {
|
||||||
|
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_count(&self) -> usize {
|
||||||
|
self.images.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn images(&self) -> TargetMode<Vec<Arc<Image>>> {
|
||||||
|
TargetMode::Mono(self.images.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
self.swapchain.width()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
self.swapchain.height()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cgmath::{Matrix4, Vector2, Vector4};
|
|
||||||
use vulkan_rs::prelude::*;
|
use vulkan_rs::prelude::*;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -7,60 +6,19 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{pipeline::SingleColorPipeline, rfactor_data::RenderObject};
|
|
||||||
use crate::write_log;
|
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 {
|
pub struct Rendering {
|
||||||
swapchain: Arc<Swapchain>,
|
swapchain: Arc<Swapchain>,
|
||||||
pipeline: SingleColorPipeline,
|
images: Vec<Arc<Image>>,
|
||||||
render_target: RenderTarget,
|
|
||||||
command_buffer: Arc<CommandBuffer>,
|
|
||||||
|
|
||||||
queue: Arc<Mutex<Queue>>,
|
queue: Arc<Mutex<Queue>>,
|
||||||
|
|
||||||
|
render_callbacks: Vec<Box<dyn Fn(u32) -> Result<Arc<CommandBuffer>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rendering {
|
impl Rendering {
|
||||||
pub fn new(
|
pub fn new(queue: Arc<Mutex<Queue>>, swapchain: Arc<Swapchain>) -> Result<Self> {
|
||||||
device: Arc<Device>,
|
|
||||||
queue: Arc<Mutex<Queue>>,
|
|
||||||
swapchain: Arc<Swapchain>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
crate::write_log!("-> Rendering ctor: begin");
|
crate::write_log!("-> Rendering ctor: begin");
|
||||||
let vk_images = swapchain.vk_images()?;
|
let vk_images = swapchain.vk_images()?;
|
||||||
write_log!(format!(
|
write_log!(format!(
|
||||||
|
@ -76,14 +34,6 @@ impl Rendering {
|
||||||
};
|
};
|
||||||
write_log!("-> Rendering ctor: wrapped images");
|
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!("-> Rendering ctor: created render_target");
|
||||||
|
|
||||||
write_log!(format!(
|
write_log!(format!(
|
||||||
|
@ -94,11 +44,11 @@ impl Rendering {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
swapchain,
|
swapchain,
|
||||||
pipeline: SingleColorPipeline::new(device.clone(), render_target.render_pass())?,
|
images,
|
||||||
render_target,
|
|
||||||
command_buffer: CommandBuffer::new_primary().build(device.clone(), queue.clone())?,
|
|
||||||
|
|
||||||
queue,
|
queue,
|
||||||
|
|
||||||
|
render_callbacks: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,63 +56,34 @@ impl Rendering {
|
||||||
&self.swapchain
|
&self.swapchain
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn single_color_pipeline(&self) -> &SingleColorPipeline {
|
pub fn add_render_callback<F>(&mut self, f: F)
|
||||||
&self.pipeline
|
where
|
||||||
|
F: Fn(u32) -> Result<Arc<CommandBuffer>> + 'static,
|
||||||
|
{
|
||||||
|
self.render_callbacks.push(Box::new(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(&self) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
swapchain: Arc<Swapchain>,
|
|
||||||
objects: &[&dyn RenderObject],
|
|
||||||
) -> Result<()> {
|
|
||||||
let image_index = self.swapchain.current_index();
|
let image_index = self.swapchain.current_index();
|
||||||
|
|
||||||
let viewport = [VkViewport {
|
let command_buffers: Vec<Arc<CommandBuffer>> = self
|
||||||
x: 0.0,
|
.render_callbacks
|
||||||
y: 0.0,
|
.iter()
|
||||||
width: swapchain.width() as f32,
|
.map(|c| c(image_index))
|
||||||
height: swapchain.height() as f32,
|
.collect::<Result<Vec<Arc<CommandBuffer>>>>()?;
|
||||||
minDepth: 0.0,
|
|
||||||
maxDepth: 1.0,
|
|
||||||
}];
|
|
||||||
|
|
||||||
let scissor = [VkRect2D {
|
write_log!(format!(
|
||||||
offset: VkOffset2D { x: 0, y: 0 },
|
"submitting {} commandbuffer(s)",
|
||||||
extent: VkExtent2D {
|
command_buffers.len()
|
||||||
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 queue = self.queue.lock().unwrap();
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn images(&self) -> &Vec<Arc<Image>> {
|
||||||
|
&self.images
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,196 +1,81 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3};
|
|
||||||
use rfactor_sm_reader::*;
|
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;
|
use crate::write_log;
|
||||||
|
|
||||||
fn convert_vec(v: rF2Vec3) -> Vector3<f32> {
|
use super::UiOverlay;
|
||||||
vec3(v.x as f32, v.y as f32, v.z as f32)
|
|
||||||
|
pub trait DataReceiver {
|
||||||
|
fn scoring_update(
|
||||||
|
&mut self,
|
||||||
|
phase: GamePhase,
|
||||||
|
vehicle_scoring: &[VehicleScoringInfoV01],
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
fn telemetry_update(
|
||||||
|
&mut self,
|
||||||
|
player_id: Option<i32>,
|
||||||
|
telemetries: &[rF2VehicleTelemetry],
|
||||||
|
) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RenderObject {
|
#[derive(Clone, Copy, Debug)]
|
||||||
fn descriptor(&self) -> &Arc<DescriptorSet>;
|
pub enum GamePhase {
|
||||||
fn buffer(&self) -> &Arc<Buffer<PositionOnlyVertex>>;
|
TestDay,
|
||||||
|
Practice,
|
||||||
|
Qualifying,
|
||||||
|
Warmup,
|
||||||
|
Race,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
|
impl TryFrom<i32> for GamePhase {
|
||||||
pub struct DataConfig {
|
type Error = anyhow::Error;
|
||||||
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 DataConfig {
|
fn try_from(value: i32) -> Result<Self> {
|
||||||
pub const fn new() -> Self {
|
Ok(match value {
|
||||||
Self {
|
0 => Self::TestDay,
|
||||||
radar_scale: 1.0,
|
1..=4 => Self::Practice,
|
||||||
radar_center_factor: 0.25,
|
5..=8 => Self::Qualifying,
|
||||||
radar_transparency: 0.5,
|
9 => Self::Warmup,
|
||||||
height_scale: 0.15,
|
10..=13 => Self::Race,
|
||||||
width_scale: 0.4,
|
|
||||||
radar_car_distance: 20.0,
|
_ => return Err(anyhow::anyhow!("Failed to parse GamePhase from: {}", value)),
|
||||||
safe_color: vec3(0.0, 0.75, 0.0),
|
})
|
||||||
danger_color: vec3(0.75, 0.0, 0.0),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RFactorData {
|
pub struct RFactorData {
|
||||||
// config
|
|
||||||
config: DataConfig,
|
|
||||||
|
|
||||||
// rf2 memory mapped data
|
// rf2 memory mapped data
|
||||||
telemetry_reader: TelemetryReader,
|
telemetry_reader: TelemetryReader,
|
||||||
scoring_reader: ScoringReader,
|
scoring_reader: ScoringReader,
|
||||||
|
|
||||||
// radar objects
|
start_time: Instant,
|
||||||
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>,
|
player_id: Option<i32>,
|
||||||
|
|
||||||
// math objects
|
receivers: Vec<Rc<RefCell<dyn UiOverlay>>>,
|
||||||
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>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RFactorData {
|
impl RFactorData {
|
||||||
pub fn new(
|
pub fn new() -> Result<Self> {
|
||||||
config: DataConfig,
|
|
||||||
device: Arc<Device>,
|
|
||||||
descriptor_layout: &Arc<DescriptorSetLayout>,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> Result<Self> {
|
|
||||||
write_log!(" =================== create RFactorData ===================");
|
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();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
|
||||||
|
|
||||||
telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?,
|
telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?,
|
||||||
scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?,
|
scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?,
|
||||||
|
|
||||||
background: if config.radar_transparency == 0.0 {
|
start_time,
|
||||||
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(),
|
|
||||||
|
|
||||||
player_id: None,
|
player_id: None,
|
||||||
|
|
||||||
radar_center,
|
receivers: Vec::new(),
|
||||||
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<f32>, color: [f32; 4]) -> Result<RadarObject> {
|
pub fn add_receiver(&mut self, receiver: Rc<RefCell<dyn UiOverlay>>) {
|
||||||
write_log!(" =================== create car object ===================");
|
self.receivers.push(receiver);
|
||||||
|
|
||||||
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),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn now(&self) -> f32 {
|
fn now(&self) -> f32 {
|
||||||
|
@ -200,8 +85,6 @@ impl RFactorData {
|
||||||
pub fn update(&mut self) -> Result<()> {
|
pub fn update(&mut self) -> Result<()> {
|
||||||
write_log!(" =================== update RFactorData ===================");
|
write_log!(" =================== update RFactorData ===================");
|
||||||
|
|
||||||
let mut should_render = false;
|
|
||||||
|
|
||||||
// get scoring info
|
// get scoring info
|
||||||
if let Some((scoring_info, vehicle_scorings)) =
|
if let Some((scoring_info, vehicle_scorings)) =
|
||||||
self.scoring_reader.vehicle_scoring(self.now())
|
self.scoring_reader.vehicle_scoring(self.now())
|
||||||
|
@ -224,223 +107,29 @@ impl RFactorData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = &self.player_id {
|
let phase = GamePhase::try_from(scoring_info.mSession)?;
|
||||||
if let Some(vehicle_scoring) =
|
|
||||||
vehicle_scorings.iter().find(|scoring| scoring.mID == *id)
|
write_log!(format!("GamePhase: {:?}", phase));
|
||||||
{
|
|
||||||
should_render = vehicle_scoring.mInPits != 0;
|
for receiver in self.receivers.iter() {
|
||||||
}
|
receiver
|
||||||
|
.borrow_mut()
|
||||||
|
.scoring_update(phase, &vehicle_scorings)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if player id is set (a map is loaded), check telemetry data
|
// check telemetry data
|
||||||
if let Some(player_id) = &self.player_id {
|
|
||||||
write_log!("before telemetry update");
|
write_log!("before telemetry update");
|
||||||
if let Some(telemetries) = self.telemetry_reader.query_telemetry(self.now()) {
|
if let Some(telemetries) = self.telemetry_reader.query_telemetry(self.now()) {
|
||||||
write_log!("new telemetry update");
|
write_log!("new telemetry update");
|
||||||
|
|
||||||
self.cars.clear();
|
for receiver in self.receivers.iter() {
|
||||||
|
receiver
|
||||||
if should_render {
|
.borrow_mut()
|
||||||
// make sure there are enough cars in buffer
|
.telemetry_update(self.player_id, &telemetries)?;
|
||||||
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(())
|
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