diff --git a/rlbot/src/lib.rs b/rlbot/src/lib.rs index 6a33913..a3bd586 100644 --- a/rlbot/src/lib.rs +++ b/rlbot/src/lib.rs @@ -8,6 +8,7 @@ use rlbot_flat::planus::{self, ReadAsRoot}; use thiserror::Error; pub mod agents; +pub mod render; pub mod state_builder; pub mod util; diff --git a/rlbot/src/render.rs b/rlbot/src/render.rs new file mode 100644 index 0000000..f2833df --- /dev/null +++ b/rlbot/src/render.rs @@ -0,0 +1,208 @@ +use rlbot_flat::flat::{ + Color, Line3D, PolyLine3D, Rect2D, Rect3D, RenderAnchor, RenderGroup, RenderMessage, String2D, + String3D, TextHAlign, TextVAlign, Vector3, +}; + +#[rustfmt::skip] +pub mod colors { + use rlbot_flat::flat::Color; + pub const TRANSPARENT: Color = Color { r: 0, g: 0, b: 0, a: 0 }; + pub const BLACK: Color = Color { r: 0, g: 0, b: 0, a: 255 }; + pub const WHITE: Color = Color { r: 255, g: 255, b: 255, a: 255 }; + pub const RED: Color = Color { r: 255, g: 0, b: 0, a: 255 }; + pub const GREEN: Color = Color { r: 0, g: 128, b: 0, a: 255 }; + pub const BLUE: Color = Color { r: 0, g: 0, b: 255, a: 255 }; + pub const LIME: Color = Color { r: 0, g: 255, b: 0, a: 255 }; + pub const YELLOW: Color = Color { r: 255, g: 255, b: 0, a: 255 }; + pub const ORANGE: Color = Color { r: 255, g: 128, b: 0, a: 255 }; + pub const CYAN: Color = Color { r: 0, g: 255, b: 255, a: 255 }; + pub const PINK: Color = Color { r: 255, g: 0, b: 255, a: 255 }; + pub const PURPLE: Color = Color { r: 128, g: 0, b: 128, a: 255 }; + pub const TEAL: Color = Color { r: 0, g: 128, b: 128, a: 255 }; +} + +/// The Renderer allows of easy construction of [RenderGroup]s for in-game debug rendering. +/// When done, call [build] and queue the resulting [RenderGroup] in the packet queue. +/// +/// Example: +/// ```ignore +/// use rlbot::render::{Renderer}; +/// use rlbot::render::colors::{BLUE, GREEN, RED}; +/// let mut draw = Renderer::new(0); +/// draw.line_3d(car.pos, car.pos + car.forward() * 120., RED); +/// draw.line_3d(car.pos, car.pos + car.rightward() * 120., GREEN); +/// draw.line_3d(car.pos, car.pos + car.upward() * 120., BLUE); +/// packet_queue.push(draw.build()); +/// ``` +pub struct Renderer { + pub group: RenderGroup, +} + +impl Renderer { + /// Create a new Renderer. + /// Each render group must have a unique id. + /// Re-using an id will result in overwriting (watch out when using hiveminds). + pub fn new(group_id: i32) -> Self { + Self { + group: RenderGroup { + render_messages: vec![], + id: group_id, + }, + } + } + + /// Get the resulting [RenderGroup]. + pub fn build(self) -> RenderGroup { + self.group + } + + /// Add a [RenderMessage] to this group. + pub fn push(&mut self, message: impl Into) { + self.group.render_messages.push(message.into()); + } + + /// Draws a line between two anchors in 3d space. + pub fn line_3d( + &mut self, + start: impl Into, + end: impl Into, + color: Color, + ) { + self.group.render_messages.push( + Line3D { + start: Box::new(start.into()), + end: Box::new(end.into()), + color, + } + .into(), + ); + } + + /// Draws a line going through each of the provided points. + pub fn polyline_3d( + &mut self, + points: impl IntoIterator>, + color: Color, + ) { + self.group.render_messages.push( + PolyLine3D { + points: points.into_iter().map(|p| p.into()).collect(), + color, + } + .into(), + ); + } + + /// Draws text in 2d space. + /// X and y uses screen-space coordinates, i.e. 0.1 is 10% of the screen width/height. + /// Use `set_resolution` to change to pixel coordinates. + /// Characters of the font are 20 pixels tall and 10 pixels wide when `scale == 1.0`. + /// Consider using [push] and `..default()` when using multiple default values. + pub fn string_2d( + &mut self, + text: String, + x: f32, + y: f32, + scale: f32, + foreground: Color, + background: Color, + h_align: TextHAlign, + v_align: TextVAlign, + ) { + self.group.render_messages.push( + String2D { + text, + x, + y, + scale, + foreground, + background, + h_align, + v_align, + } + .into(), + ); + } + + /// Draws text anchored in 3d space. + /// Characters of the font are 20 pixels tall and 10 pixels wide when `scale == 1.0`. + /// Consider using [push] and `..default()` when using multiple default values. + pub fn string_3d( + &mut self, + text: String, + anchor: impl Into, + scale: f32, + foreground: Color, + background: Color, + h_align: TextHAlign, + v_align: TextVAlign, + ) { + self.group.render_messages.push( + String3D { + text, + anchor: Box::new(anchor.into()), + scale, + foreground, + background, + h_align, + v_align, + } + .into(), + ); + } + + /// Draws a rectangle anchored in 2d space. + /// X, y, width, and height uses screen-space coordinates, i.e. 0.1 is 10% of the screen width/height. + /// Use `set_resolution` to change to pixel coordinates. + /// Consider using [push] and `..default()` when using multiple default values. + pub fn rect_2d( + &mut self, + x: f32, + y: f32, + width: f32, + height: f32, + color: Color, + h_align: TextHAlign, + v_align: TextVAlign, + ) { + self.group.render_messages.push( + Rect2D { + x, + y, + width, + height, + color, + h_align, + v_align, + } + .into(), + ); + } + + /// Draws a rectangle anchored in 3d space. + /// Width and height are screen-space sizes, i.e. 0.1 is 10% of the screen width/height. + /// Use `set_resolution` to change to pixel coordinates. + /// The size does not change based on distance to the camera. + /// Consider using [push] and `..default()` when using multiple default values. + pub fn rect_3d( + &mut self, + anchor: impl Into, + width: f32, + height: f32, + color: Color, + h_align: TextHAlign, + v_align: TextVAlign, + ) { + self.group.render_messages.push( + Rect3D { + anchor: Box::new(anchor.into()), + width, + height, + color, + h_align, + v_align, + } + .into(), + ); + } +} diff --git a/rlbot_flat/src/glam_compat.rs b/rlbot_flat/src/glam_compat.rs index c5a9125..f05ec44 100644 --- a/rlbot_flat/src/glam_compat.rs +++ b/rlbot_flat/src/glam_compat.rs @@ -32,3 +32,21 @@ impl From for flat::Vector3 { } } } + +impl From for flat::RenderAnchor { + fn from(value: glam::Vec3) -> Self { + Self { + world: value.into(), + relative: None, + } + } +} + +impl From for flat::RenderAnchor { + fn from(value: glam::Vec3A) -> Self { + Self { + world: value.into(), + relative: None, + } + } +} diff --git a/rlbot_flat/src/lib.rs b/rlbot_flat/src/lib.rs index e9ab58f..d9ded84 100644 --- a/rlbot_flat/src/lib.rs +++ b/rlbot_flat/src/lib.rs @@ -50,8 +50,55 @@ impl From for flat::InterfacePacket { flat::InterfacePacket { message } } } + impl From for flat::CorePacket { fn from(message: flat::CoreMessage) -> Self { flat::CorePacket { message } } } + +macro_rules! from_render_message { + ( $( $t:ident ),* ) => { + $( + impl From for flat::RenderMessage { + fn from(value: flat::$t) -> Self { + Self { + variety: flat::RenderType::$t(Box::new(value)), + } + } + } + )* + }; +} + +from_render_message!(Line3D, PolyLine3D, String2D, String3D, Rect2D, Rect3D); + +impl From for flat::RenderAnchor { + fn from(value: flat::Vector3) -> Self { + Self { + world: value, + relative: None, + } + } +} + +impl From for flat::RenderAnchor { + fn from(value: flat::RelativeAnchor) -> Self { + Self { + world: flat::Vector3::default(), + relative: Some(value), + } + } +} + +impl From for flat::RelativeAnchor { + fn from(value: flat::CarAnchor) -> Self { + flat::RelativeAnchor::CarAnchor(Box::new(value)) + } +} + +impl From for flat::RelativeAnchor { + fn from(value: flat::BallAnchor) -> Self { + flat::RelativeAnchor::BallAnchor(Box::new(value)) + } +}