progress
This commit is contained in:
parent
c0ce496ce0
commit
e9c8cdbf48
8 changed files with 340 additions and 37 deletions
|
@ -11,7 +11,8 @@ const vec4 top = vec4(0.5, 0.5, 0.5, 1.0);
|
|||
const vec4 bot = vec4(0.4, 0.4, 0.4, 1.0);
|
||||
|
||||
void main() {
|
||||
outcolor = 0.2 * (bot * (1 - pos.y) + top * (1 - pos.x)) + 0.2;
|
||||
outcolor = vec4(0.2 * (0.4 * (1 - pos.y) + 0.5 * (1 - pos.x)) + 0.2);
|
||||
outcolor.w = 1.0;
|
||||
// if ((pos.x > -threshold && pos.x < threshold)
|
||||
// || (pos.y > -threshold && pos.y < threshold)
|
||||
// || (pos.x > 1.0-threshold && pos.x < 1.0+threshold)
|
||||
|
|
|
@ -9,4 +9,4 @@ uniform mat4 projection;
|
|||
void main() {
|
||||
pos = vec4(point, 0.0, 1.0);
|
||||
gl_Position = projection * target * pos;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub struct PlayerData {
|
||||
pub position: [u32; 2],
|
||||
pub position: [i32; 2],
|
||||
pub color: [u32; 3],
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,9 @@ pub struct PlayerData {
|
|||
pub struct BlockData {
|
||||
pub movable: bool,
|
||||
pub orientation: u32,
|
||||
pub position: [u32; 2],
|
||||
pub position: [i32; 2],
|
||||
pub color: [u32; 3],
|
||||
pub segments: Vec<[u32; 4]>,
|
||||
pub segments: Vec<[i32; 4]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
47
src/enums.rs
47
src/enums.rs
|
@ -1,11 +1,13 @@
|
|||
#[derive(Copy, Clone)]
|
||||
use std::ops::Add;
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Copy, Clone)]
|
||||
pub enum Board {
|
||||
Left = 0,
|
||||
Right = 1,
|
||||
}
|
||||
|
||||
impl From<u32> for Board {
|
||||
fn from(n: u32) -> Self {
|
||||
impl From<i32> for Board {
|
||||
fn from(n: i32) -> Self {
|
||||
match n {
|
||||
0 => Board::Left,
|
||||
1 => Board::Right,
|
||||
|
@ -40,7 +42,27 @@ pub enum PushDir {
|
|||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
impl PushDir {
|
||||
pub fn as_pair(&self) -> (i32, i32) {
|
||||
match self {
|
||||
PushDir::Up => (0, -1),
|
||||
PushDir::Down => (0, 1),
|
||||
PushDir::Left => (-1, 0),
|
||||
PushDir::Right => (1, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<PushDir> for (i32, i32, Board) {
|
||||
type Output = (i32, i32, Board);
|
||||
|
||||
fn add(self, rhs: PushDir) -> Self::Output {
|
||||
let offset = rhs.as_pair();
|
||||
(self.0 + offset.0, self.1 + offset.1, self.2)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialOrd, PartialEq)]
|
||||
pub enum Shape {
|
||||
Full = 0,
|
||||
TopRight = 1,
|
||||
|
@ -49,8 +71,8 @@ pub enum Shape {
|
|||
BottomRight = 4,
|
||||
}
|
||||
|
||||
impl From<u32> for Shape {
|
||||
fn from(n: u32) -> Self {
|
||||
impl From<i32> for Shape {
|
||||
fn from(n: i32) -> Self {
|
||||
match n {
|
||||
0 => Shape::Full,
|
||||
1 => Shape::TopRight,
|
||||
|
@ -61,3 +83,16 @@ impl From<u32> for Shape {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn is_opposite(&self, other: &Shape) -> bool {
|
||||
use Shape::*;
|
||||
match (self, other) {
|
||||
(TopRight, BottomLeft)
|
||||
| (BottomLeft, TopRight)
|
||||
| (TopLeft, BottomRight)
|
||||
| (BottomRight, TopLeft) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
src/game.rs
58
src/game.rs
|
@ -1,7 +1,10 @@
|
|||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use glium::glutin::{ElementState, Event, VirtualKeyCode, WindowEvent};
|
||||
use glium::{Display, Frame};
|
||||
|
||||
use crate::enums::PushDir;
|
||||
use crate::level::Level;
|
||||
use crate::renderer::Renderer;
|
||||
use crate::resources::Resources;
|
||||
|
@ -20,6 +23,7 @@ pub struct Game<'a> {
|
|||
pub display: &'a Display,
|
||||
levels: Vec<Level>,
|
||||
current_level: usize,
|
||||
keymap: HashMap<VirtualKeyCode, bool>,
|
||||
}
|
||||
|
||||
impl<'a> Game<'a> {
|
||||
|
@ -41,6 +45,26 @@ impl<'a> Game<'a> {
|
|||
display,
|
||||
levels,
|
||||
current_level: 0,
|
||||
keymap: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: Event) {
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::Resized(size) => self.resources.window_dimensions = size.into(),
|
||||
WindowEvent::KeyboardInput { input, .. } => {
|
||||
if let Some(code) = &input.virtual_keycode {
|
||||
if let ElementState::Pressed = &input.state {
|
||||
self.keymap.insert(*code, true);
|
||||
} else {
|
||||
self.keymap.insert(*code, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +76,39 @@ impl<'a> Game<'a> {
|
|||
self.levels.iter().nth(self.current_level).unwrap()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, delta: Duration) {}
|
||||
pub fn get_current_level_mut(&mut self) -> &mut Level {
|
||||
self.levels.iter_mut().nth(self.current_level).unwrap()
|
||||
}
|
||||
|
||||
pub fn is_pressed(&self, code: &VirtualKeyCode) -> bool {
|
||||
if let Some(true) = self.keymap.get(code) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, delta: Duration) {
|
||||
macro_rules! shit {
|
||||
($key:expr, $player:expr, $movement:expr) => {
|
||||
if self.is_pressed(&$key) {
|
||||
let mut level = self.get_current_level_mut();
|
||||
level.handle_movement($player, $movement);
|
||||
self.keymap.insert($key, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
shit!(VirtualKeyCode::W, true, PushDir::Up);
|
||||
shit!(VirtualKeyCode::A, true, PushDir::Left);
|
||||
shit!(VirtualKeyCode::S, true, PushDir::Down);
|
||||
shit!(VirtualKeyCode::D, true, PushDir::Right);
|
||||
|
||||
shit!(VirtualKeyCode::I, false, PushDir::Up);
|
||||
shit!(VirtualKeyCode::J, false, PushDir::Left);
|
||||
shit!(VirtualKeyCode::K, false, PushDir::Down);
|
||||
shit!(VirtualKeyCode::L, false, PushDir::Right);
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer) {
|
||||
let level = self.get_current_level();
|
||||
|
|
223
src/level.rs
223
src/level.rs
|
@ -1,26 +1,35 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::data::LevelData;
|
||||
use crate::enums::{Board, Orientation, Shape};
|
||||
use crate::enums::{Board, Orientation, PushDir, Shape};
|
||||
use crate::renderer::Renderer;
|
||||
use crate::{GAME_HEIGHT, GAME_WIDTH};
|
||||
|
||||
pub struct Level {
|
||||
dimensions: (u32, u32),
|
||||
move_stack: VecDeque<()>,
|
||||
cell_map: CellMap,
|
||||
blocks: Vec<Block>,
|
||||
player1: Player,
|
||||
player2: Player,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Block {
|
||||
position: (u32, u32),
|
||||
movable: bool,
|
||||
position: (i32, i32),
|
||||
color: (f32, f32, f32),
|
||||
orientation: Orientation,
|
||||
segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialOrd, PartialEq)]
|
||||
pub struct Segment(i32, i32, Shape, Board);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Segment(u32, u32, Shape, Board);
|
||||
pub struct Player {
|
||||
pub position: [i32; 2],
|
||||
pub color: [u32; 3],
|
||||
}
|
||||
|
||||
impl Level {
|
||||
pub fn from_json(data: impl AsRef<str>) -> Level {
|
||||
|
@ -31,6 +40,7 @@ impl Level {
|
|||
.blocks
|
||||
.iter()
|
||||
.map(|block| {
|
||||
let movable = block.movable;
|
||||
let position = (block.position[0], block.position[1]);
|
||||
let segments = block
|
||||
.segments
|
||||
|
@ -46,6 +56,7 @@ impl Level {
|
|||
block.color[2] as f32 / 256.0,
|
||||
);
|
||||
Block {
|
||||
movable,
|
||||
position,
|
||||
color,
|
||||
segments,
|
||||
|
@ -54,40 +65,138 @@ impl Level {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let player1 = Player {
|
||||
position: data.player1.position,
|
||||
color: data.player1.color,
|
||||
};
|
||||
let player2 = Player {
|
||||
position: data.player2.position,
|
||||
color: data.player2.color,
|
||||
};
|
||||
|
||||
Level {
|
||||
dimensions: (data.dimensions[0], data.dimensions[1]),
|
||||
move_stack: VecDeque::new(),
|
||||
cell_map: CellMap::new(),
|
||||
blocks,
|
||||
player1,
|
||||
player2,
|
||||
}
|
||||
}
|
||||
|
||||
// player1: true -> player1, false -> player2
|
||||
// TODO: don't use a boolean here
|
||||
pub fn handle_movement(&mut self, player1: bool, direction: PushDir) -> bool {
|
||||
let mut player = if player1 {
|
||||
&self.player1
|
||||
} else {
|
||||
&self.player2
|
||||
};
|
||||
|
||||
// TODO: check out of bounds
|
||||
let movement = direction.as_pair();
|
||||
let x = player.position[0] + movement.0;
|
||||
let y = player.position[1] + movement.1;
|
||||
|
||||
let result = self.can_move(player1, direction).clone();
|
||||
let mut player = if player1 {
|
||||
&mut self.player1
|
||||
} else {
|
||||
&mut self.player2
|
||||
};
|
||||
|
||||
if let Some(_) = result {
|
||||
player.position[0] = x;
|
||||
player.position[1] = y;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: don't use a boolean here
|
||||
pub fn can_move(&self, player1: bool, direction: PushDir) -> Option<()> {
|
||||
// an absolute segment (as opposed to relative to a block)
|
||||
#[derive(Copy, Clone, PartialOrd, PartialEq)]
|
||||
struct ASegment(i32, i32, Shape, Board);
|
||||
|
||||
fn can_push(src: Segment, dst: Segment) -> bool {
|
||||
if src.3 != dst.3 {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
let player = if player1 {
|
||||
(
|
||||
self.player1.position[0],
|
||||
self.player1.position[1],
|
||||
Board::Left,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
self.player2.position[0],
|
||||
self.player2.position[1],
|
||||
Board::Right,
|
||||
)
|
||||
};
|
||||
|
||||
// check to make sure that the player isn't trying to go out of bounds
|
||||
let target = player + direction;
|
||||
if target.0 < 0
|
||||
|| target.0 >= self.dimensions.0 as i32
|
||||
|| target.1 < 0
|
||||
|| target.1 >= self.dimensions.1 as i32
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// check if we're sharing a triangle cell
|
||||
if let CellContents::Double(a, b) = self.cell_map.get(player) {}
|
||||
|
||||
// 08/06 pickup
|
||||
// need to determine whether or not segment should hold a reference back to block or not?
|
||||
// either way, segment in the cellmap should hold block information
|
||||
// maybe cellmap should just carry a block index? seems hacky
|
||||
// using refs to manage the whole thing is messy and probably doesn't work
|
||||
// ???
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer) {
|
||||
let playfield_ratio = (2 * self.dimensions.0 + 6) as f64 / (self.dimensions.1 + 4) as f64;
|
||||
let screen_ratio = GAME_WIDTH as f64 / GAME_HEIGHT as f64;
|
||||
// board positioning calculations
|
||||
let playfield_ratio = (2 * self.dimensions.0 + 6) as f32 / (self.dimensions.1 + 4) as f32;
|
||||
let screen_ratio = renderer.window.0 / renderer.window.1;
|
||||
|
||||
let cols = self.dimensions.0 as i32;
|
||||
let rows = self.dimensions.1 as i32;
|
||||
|
||||
let (scale, xoff, yoff) = if playfield_ratio > screen_ratio {
|
||||
let scale = GAME_WIDTH / (2 * self.dimensions.0 + 6);
|
||||
let yoff = GAME_HEIGHT / 2 - (self.dimensions.1 + 4) * scale / 2;
|
||||
let scale = renderer.window.0 as i32 / (2 * cols + 6);
|
||||
let yoff = renderer.window.1 as i32 / 2 - (rows + 4) * scale / 2;
|
||||
(scale, 0, yoff)
|
||||
} else {
|
||||
let scale = GAME_HEIGHT / (self.dimensions.1 + 4);
|
||||
let xoff = GAME_WIDTH / 2 - (2 * self.dimensions.0 + 6) * scale / 2;
|
||||
let scale = renderer.window.1 as i32 / (rows + 4);
|
||||
let xoff = renderer.window.0 as i32 / 2 - (2 * cols + 6) * scale / 2;
|
||||
(scale, xoff, 0)
|
||||
};
|
||||
|
||||
self.render_boards(renderer, scale, (xoff, yoff));
|
||||
}
|
||||
|
||||
fn render_boards(&self, renderer: &mut Renderer, scale: u32, offset: (u32, u32)) {
|
||||
fn render_boards(&self, renderer: &mut Renderer, scale: i32, offset: (i32, i32)) {
|
||||
let left_off = (offset.0 + 2 * scale, offset.1 + 2 * scale);
|
||||
let right_off = (
|
||||
offset.0 + (4 + self.dimensions.0) * scale,
|
||||
offset.0 + (4 + self.dimensions.0 as i32) * scale,
|
||||
offset.1 + 2 * scale,
|
||||
);
|
||||
|
||||
// render the grid
|
||||
for x in 0..self.dimensions.0 {
|
||||
for y in 0..self.dimensions.1 {
|
||||
// TODO: do this in one single pass instead of once for each cell
|
||||
for x in 0..self.dimensions.0 as i32 {
|
||||
for y in 0..self.dimensions.1 as i32 {
|
||||
renderer.render_cell((left_off.0 + x * scale, left_off.1 + y * scale), scale);
|
||||
renderer.render_cell((right_off.0 + x * scale, right_off.1 + y * scale), scale);
|
||||
}
|
||||
|
@ -105,5 +214,89 @@ impl Level {
|
|||
renderer.render_segment(location, scale, block.color, block.orientation, segment.2);
|
||||
}
|
||||
}
|
||||
|
||||
// render player
|
||||
self.render_player(renderer, &self.player1, scale, left_off);
|
||||
self.render_player(renderer, &self.player2, scale, right_off);
|
||||
}
|
||||
|
||||
fn render_player(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
player: &Player,
|
||||
scale: i32,
|
||||
offset: (i32, i32),
|
||||
) {
|
||||
let location = (
|
||||
offset.0 + player.position[0] * scale + 4,
|
||||
offset.1 + player.position[1] * scale + 4,
|
||||
);
|
||||
renderer.render_segment(
|
||||
location,
|
||||
(scale - 8),
|
||||
(
|
||||
player.color[0] as f32,
|
||||
player.color[1] as f32,
|
||||
player.color[2] as f32,
|
||||
),
|
||||
Orientation::Both,
|
||||
Shape::Full,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct CellMap(HashMap<(i32, i32, Board), CellContents>);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum CellContents {
|
||||
Empty,
|
||||
Player,
|
||||
Single(Segment),
|
||||
|
||||
// invariant: .0 < .1
|
||||
Double(Segment, Segment),
|
||||
}
|
||||
|
||||
impl CellMap {
|
||||
pub fn new() -> Self {
|
||||
CellMap(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn get(&self, loc: (i32, i32, Board)) -> CellContents {
|
||||
self.0
|
||||
.get(&loc)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| CellContents::Empty)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, loc: (i32, i32, Board)) {
|
||||
self.0.remove(&loc);
|
||||
}
|
||||
|
||||
pub fn add(&mut self, loc: (i32, i32, Board), segment: &Segment) -> bool {
|
||||
let contents = self.get(loc).clone();
|
||||
match contents {
|
||||
CellContents::Empty => {
|
||||
// just add it like normal
|
||||
self.0.insert(loc, CellContents::Single(*segment));
|
||||
true
|
||||
}
|
||||
CellContents::Single(existing) => {
|
||||
if existing.2.is_opposite(&segment.2) {
|
||||
self.0.insert(
|
||||
loc,
|
||||
if *segment < existing {
|
||||
CellContents::Double(*segment, existing)
|
||||
} else {
|
||||
CellContents::Double(existing, *segment)
|
||||
},
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
CellContents::Player | CellContents::Double(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ fn main() {
|
|||
println!("size: {:?}", window.get_inner_size());
|
||||
}
|
||||
|
||||
let game = Game::new(&display);
|
||||
let mut game = Game::new(&display);
|
||||
|
||||
let mut closed = false;
|
||||
let mut prev = Instant::now();
|
||||
|
@ -54,9 +54,11 @@ fn main() {
|
|||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => closed = true,
|
||||
_ => (),
|
||||
_ => game.handle_event(event),
|
||||
});
|
||||
|
||||
game.update(delta);
|
||||
|
||||
let mut target = display.draw();
|
||||
target.clear(None, Some((0.0, 0.0, 0.0, 1.0)), true, None, None);
|
||||
let mut renderer = game.create_renderer(&mut target);
|
||||
|
|
|
@ -5,9 +5,9 @@ use nalgebra::{Matrix4, Vector4};
|
|||
|
||||
use crate::enums::{Orientation, Shape};
|
||||
use crate::game::Game;
|
||||
use crate::{GAME_HEIGHT, GAME_WIDTH};
|
||||
|
||||
pub struct Renderer<'a, 'b> {
|
||||
pub window: (f32, f32),
|
||||
target: &'a mut Frame,
|
||||
display: &'b Display,
|
||||
cell_program: &'b Program,
|
||||
|
@ -18,6 +18,10 @@ pub struct Renderer<'a, 'b> {
|
|||
impl<'a, 'b> Renderer<'a, 'b> {
|
||||
pub fn new(game: &'b Game, target: &'a mut Frame) -> Self {
|
||||
Renderer {
|
||||
window: (
|
||||
game.resources.window_dimensions.0 as f32,
|
||||
game.resources.window_dimensions.1 as f32,
|
||||
),
|
||||
target,
|
||||
display: &game.display,
|
||||
cell_program: game.resources.get_shader("cell").unwrap(),
|
||||
|
@ -26,7 +30,7 @@ impl<'a, 'b> Renderer<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render_cell(&mut self, location: (u32, u32), scale: u32) {
|
||||
pub fn render_cell(&mut self, location: (i32, i32), scale: i32) {
|
||||
#[derive(Copy, Clone)]
|
||||
struct Vertex {
|
||||
point: [f32; 2],
|
||||
|
@ -43,8 +47,14 @@ impl<'a, 'b> Renderer<'a, 'b> {
|
|||
vertices.push(Vertex { point: [1.0, 0.0] });
|
||||
let vertex_buffer = VertexBuffer::new(self.display, &vertices).unwrap();
|
||||
|
||||
let projection =
|
||||
glm::ortho::<f32>(0.0, GAME_WIDTH as f32, GAME_HEIGHT as f32, 0.0, -1.0, 1.0);
|
||||
let projection = glm::ortho::<f32>(
|
||||
0.0,
|
||||
self.window.0 as f32,
|
||||
self.window.1 as f32,
|
||||
0.0,
|
||||
-1.0,
|
||||
1.0,
|
||||
);
|
||||
let mut matrix = Matrix4::<f32>::identity();
|
||||
matrix = matrix.append_nonuniform_scaling(&[scale as f32, scale as f32, 1.0].into());
|
||||
matrix = matrix.append_translation(&[location.0 as f32, location.1 as f32, 0.0].into());
|
||||
|
@ -73,8 +83,8 @@ impl<'a, 'b> Renderer<'a, 'b> {
|
|||
|
||||
pub fn render_segment(
|
||||
&mut self,
|
||||
location: (u32, u32),
|
||||
scale: u32,
|
||||
location: (i32, i32),
|
||||
scale: i32,
|
||||
color: (f32, f32, f32),
|
||||
orientation: Orientation,
|
||||
shape: Shape,
|
||||
|
@ -175,8 +185,14 @@ impl<'a, 'b> Renderer<'a, 'b> {
|
|||
let vertex_buffer = VertexBuffer::new(self.display, &vertices).unwrap();
|
||||
let tint = Vector4::from([color.0, color.1, color.2, 1.0f32]);
|
||||
|
||||
let projection =
|
||||
glm::ortho::<f32>(0.0, GAME_WIDTH as f32, GAME_HEIGHT as f32, 0.0, -1.0, 1.0);
|
||||
let projection = glm::ortho::<f32>(
|
||||
0.0,
|
||||
self.window.0 as f32,
|
||||
self.window.1 as f32,
|
||||
0.0,
|
||||
-1.0,
|
||||
1.0,
|
||||
);
|
||||
let mut matrix = Matrix4::<f32>::identity();
|
||||
matrix = matrix.append_nonuniform_scaling(&[scale as f32, scale as f32, 1.0].into());
|
||||
matrix = matrix.append_translation(&[location.0 as f32, location.1 as f32, 0.0].into());
|
||||
|
|
Loading…
Add table
Reference in a new issue