├── .cargo └── config ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Cargo.toml ├── LICENSE.md ├── TODO.md ├── car_top1.png ├── examples ├── decal.rs ├── line.rs └── sprite.rs ├── font.png ├── logo_long.png ├── readme.md ├── src ├── color.rs ├── decal.rs ├── font.png ├── gfx2d.rs ├── gfx2d │ └── vec2d.rs ├── gfx3d.rs ├── gfx3d │ ├── mat4x4.rs │ ├── pipeline.rs │ ├── plane.rs │ ├── triangle.rs │ ├── vec3d.rs │ └── vec4d.rs ├── layer.rs ├── lib.rs ├── sprite.rs └── time.rs └── unwrap_helper.png /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.'cfg(any(windows, unix))'] 2 | rustflags = ["-C", "target-cpu=native", "-C", "opt-level=3"] 3 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(Windows) Launch", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/target/debug/examples/${fileBasenameNoExtension}.exe", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": true, 17 | "preLaunchTask": "cargo build example" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "shell", 8 | "label": "cargo build example", 9 | "command": "cargo", 10 | "args": [ 11 | "build", 12 | "--example", 13 | "${fileBasenameNoExtension}" 14 | ], 15 | "problemMatcher": [ 16 | "$rustc" 17 | ] 18 | }, 19 | { 20 | "type": "shell", 21 | "command": "cargo build", 22 | "problemMatcher": [ 23 | "$rustc" 24 | ], 25 | "group": { 26 | "kind": "build", 27 | "isDefault": true 28 | }, 29 | "label": "rust: cargo build" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pge" 3 | version = "0.1.0" 4 | authors = ["Matt"] 5 | edition = "2021" 6 | description = "A port of olcPixelGameEngine to Rust, a tool used in javidx9's YouTube videos and projects." 7 | license-file = "LICENSE.md" 8 | 9 | [dependencies] 10 | miniquad = "0.4.8" 11 | glam = "0.25.0" 12 | image = "0.24" 13 | 14 | [dev-dependencies] 15 | rand = "0.8" 16 | image = "0.24" 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License (OLC-3) 2 | 3 | Copyright 2018-2019 OneLoneCoder.com 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions or derivations of source code must retain the above 10 | copyright notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions or derivative works in binary form must reproduce 13 | the above copyright notice. This list of conditions and the following 14 | disclaimer must be reproduced in the documentation and/or other 15 | materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | # TODO 3 | * Need to massage the rendering system into a single pipeline and set of bindings. 4 | * Add a trait for layers? As a kind of an interface. 5 | * Implement an egui renderer as a special layer. 6 | * Implement Input 7 | 8 | 9 | 10 | 11 | ## OLD TODOs - below is all from version 1.0 12 | * New high performance decals will require a major overhaul on the underlying window system. 13 | * Window Scaling 14 | * Fill Triangle 15 | * 3D GFX Extension 16 | * Mouse position correction (now we can resize window and this needs more work) 17 | 18 | ### Bugs 19 | 20 | 21 | ### Done 22 | * Keyboard input 23 | * Mouse input (buttons) 24 | * Pixel Blending 25 | * Overdraw for circles when alpha blending 26 | -------------------------------------------------------------------------------- /car_top1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbettcher/rustyPixelGameEngine/3972dc96322d6bfd9685bb7d0edb81244fc9b647/car_top1.png -------------------------------------------------------------------------------- /examples/decal.rs: -------------------------------------------------------------------------------- 1 | use pge::{PGE, Pixel, GameLoop, Sprite, Decal, SpriteRef, Layer, BLANK, WHITE, PixelMode, color}; 2 | 3 | struct GameState { 4 | logo_ref: SpriteRef, 5 | logo_decal: Decal, 6 | } 7 | 8 | impl GameLoop for GameState { 9 | type GameType = GameState; 10 | 11 | fn init(pge: &mut PGE) -> Self { 12 | let data = include_bytes!("../logo_long.png"); 13 | let image = image::load_from_memory_with_format(data, image::ImageFormat::Png).unwrap(); 14 | let raw_image = image.as_bytes(); 15 | 16 | let logo = Sprite::new_with_data(image.width(), image.height(), raw_image); 17 | let (logo_decal, logo_ref) = Decal::new_from_sprite(pge, logo); 18 | 19 | // or 20 | 21 | //let logo_ref = SpriteRef::new_with_data(image.width(), image.height(), raw_image); 22 | //let logo_decal = Decal::new_from_sprite_ref(pge, &logo_ref); 23 | 24 | let layer = Layer::new(pge, 640, 480); 25 | pge.layers.push(layer); 26 | 27 | GameState { 28 | logo_ref, 29 | logo_decal 30 | } 31 | } 32 | 33 | fn update(&mut self, pge: &mut PGE, _dt: f64) { 34 | pge.clear(&WHITE); 35 | 36 | let x = pge.get_mouse_x(); 37 | let y = pge.get_mouse_y(); 38 | 39 | // we can still draw a SpriteRef with the CPU drawing methods and even edit it, 40 | // we just have to use this ugly syntax. 41 | pge.pixel_mode = PixelMode::Alpha; 42 | pge.draw_sprite(x - 50, y, &self.logo_ref.get_sprite(), 1); 43 | pge.current_layer = 1; 44 | pge.clear(&BLANK); 45 | pge.draw_sprite(x + 100, y - 100, &self.logo_ref.get_sprite(), 2); 46 | pge.current_layer = 0; 47 | } 48 | } 49 | 50 | fn main() { 51 | PGE::construct::("Decal", 640, 480, 1, 1); 52 | } -------------------------------------------------------------------------------- /examples/line.rs: -------------------------------------------------------------------------------- 1 | use pge::{PGE, Pixel, GameLoop}; 2 | 3 | struct GameState; 4 | 5 | impl GameLoop for GameState { 6 | type GameType = GameState; 7 | 8 | fn init(_pge: &mut PGE) -> Self { 9 | GameState 10 | } 11 | 12 | fn update(&mut self, pge: &mut PGE, _dt: f64) { 13 | pge.clear(&Pixel::rgb(0,0,100)); 14 | 15 | let x = pge.get_mouse_x(); 16 | let y = pge.get_mouse_y(); 17 | 18 | pge.draw_line(320, 240, x, y, &Pixel::rgb(100,100,100)); 19 | } 20 | } 21 | 22 | fn main() { 23 | PGE::construct::("Line", 640, 480, 2, 2); 24 | } -------------------------------------------------------------------------------- /examples/sprite.rs: -------------------------------------------------------------------------------- 1 | use pge::{PGE, Pixel, GameLoop, Sprite}; 2 | 3 | struct GameState { 4 | logo: Sprite, 5 | } 6 | 7 | impl GameLoop for GameState { 8 | type GameType = GameState; 9 | 10 | fn init(_pge: &mut PGE) -> Self { 11 | let data = include_bytes!("../logo_long.png"); 12 | let image = image::load_from_memory_with_format(data, image::ImageFormat::Png).unwrap(); 13 | let raw_image = image.as_bytes(); 14 | 15 | GameState { 16 | logo: Sprite::new_with_data(image.width(), image.height(), raw_image) 17 | } 18 | } 19 | 20 | fn update(&mut self, pge: &mut PGE, _dt: f64) { 21 | pge.clear(&Pixel::rgb(0,0,100)); 22 | 23 | let x = pge.get_mouse_x(); 24 | let y = pge.get_mouse_y(); 25 | 26 | pge.draw_sprite(x, y, &self.logo, 1); 27 | } 28 | } 29 | 30 | fn main() { 31 | PGE::construct::("Sprite", 640, 480, 2, 2); 32 | } -------------------------------------------------------------------------------- /font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbettcher/rustyPixelGameEngine/3972dc96322d6bfd9685bb7d0edb81244fc9b647/font.png -------------------------------------------------------------------------------- /logo_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbettcher/rustyPixelGameEngine/3972dc96322d6bfd9685bb7d0edb81244fc9b647/logo_long.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # rustyPixelGameEngine 2 | A port of olcPixelGameEngine to Rust, a tool used in javidx9's YouTube videos and projects. This port keeps the original license and the link to documentation is straight to the official project. 3 | 4 | ![Rust](https://github.com/ElementG9/rustyPixelGameEngine/workflows/Rust/badge.svg) 5 | 6 | # Why port to Rust? 7 | * Why not 8 | * Rewriting the algorithms makes them easier to understand 9 | * Rust's tooling is much better than C++'s 10 | 11 | # Goals 12 | * Obtain close to feature parity with PGE 13 | * Beat PGE's speed (cheating allowed 😉) 14 | * Try to stay close to the API style of PGE, but I will stray if it makes sense 15 | 16 | # Differences 17 | * Many things may not be implemented 18 | * Uses a Rust crate called [minifb](https://github.com/emoon/rust_minifb) to handle the window creation and event code instead of SDL 19 | * Debug mode is painfully slow, this is mostly a Rust problem 20 | * Includes some extra folders that can be ignored 21 | * .cargo - Contains a cargo config file used to test for speed 22 | * .vscode - A couple json files used with VSCode 23 | 24 | # Usage 25 | * Install latest stable [Rust](https://www.rust-lang.org/) 26 | * Run `cargo run --example extensiontestgfx2d` 27 | 28 | # Documentation 29 | Please see https://github.com/OneLoneCoder/olcPixelGameEngine/wiki 30 | 31 | # License 32 | This repo uses the OLC-3 license. It can be found in [LICENSE.md](LICENSE.md). 33 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct Color { 5 | pub r: f32, 6 | pub g: f32, 7 | pub b: f32, 8 | pub a: f32, 9 | } 10 | 11 | impl Color { 12 | /// 0-1 rgb 13 | pub fn rgb(r: f32, g: f32, b: f32) -> Self { 14 | Color { 15 | r: r, 16 | g: g, 17 | b: b, 18 | a: 1.0, 19 | } 20 | } 21 | } 22 | 23 | /* 24 | r = new Color(); 25 | r.A = 1 - (1 - fg.A) * (1 - bg.A); 26 | if (r.A < 1.0e-6) return r; // Fully transparent -- R,G,B not important 27 | r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A; 28 | r.G = fg.G * fg.A / r.A + bg.G * bg.A * (1 - fg.A) / r.A; 29 | r.B = fg.B * fg.A / r.A + bg.B * bg.A * (1 - fg.A) / r.A; 30 | */ 31 | 32 | impl Add for Color { 33 | type Output = Color; 34 | 35 | fn add(self, rhs: Color) -> Color { 36 | let a = 1.0 - (1.0 - self.a) * (1.0 - rhs.a); 37 | Color { 38 | r: self.r * self.a / a + rhs.r * rhs.a * (1.0 - self.a) / a, 39 | g: self.g * self.a / a + rhs.g * rhs.a * (1.0 - self.a) / a, 40 | b: self.b * self.a / a + rhs.b * rhs.a * (1.0 - self.a) / a, 41 | a: a, 42 | } 43 | } 44 | } 45 | 46 | impl Sub for Color { 47 | type Output = Color; 48 | 49 | fn sub(self, rhs: Color) -> Color { 50 | Color { 51 | r: self.r - rhs.r, 52 | g: self.g - rhs.g, 53 | b: self.b - rhs.b, 54 | a: self.a - rhs.a, 55 | } 56 | } 57 | } 58 | 59 | impl Mul for Color { 60 | type Output = Color; 61 | 62 | fn mul(self, rhs: f32) -> Color { 63 | Color { 64 | r: self.r * rhs, 65 | g: self.g * rhs, 66 | b: self.b * rhs, 67 | a: self.a * rhs, 68 | } 69 | } 70 | } 71 | 72 | impl Div for Color { 73 | type Output = Color; 74 | 75 | fn div(self, rhs: f32) -> Color { 76 | Color { 77 | r: self.r / rhs, 78 | g: self.g / rhs, 79 | b: self.b / rhs, 80 | a: self.a / rhs, 81 | } 82 | } 83 | } 84 | 85 | impl AddAssign for Color { 86 | fn add_assign(&mut self, rhs: Color) { 87 | self.r = self.r + rhs.r; 88 | self.g = self.g + rhs.g; 89 | self.b = self.b + rhs.b; 90 | self.a = self.a + rhs.a; 91 | } 92 | } 93 | 94 | impl SubAssign for Color { 95 | fn sub_assign(&mut self, rhs: Color) { 96 | self.r = self.r - rhs.r; 97 | self.g = self.g - rhs.g; 98 | self.b = self.b - rhs.b; 99 | self.a = self.a - rhs.a; 100 | } 101 | } 102 | 103 | impl MulAssign for Color { 104 | fn mul_assign(&mut self, rhs: f32) { 105 | self.r = self.r * rhs; 106 | self.g = self.g * rhs; 107 | self.b = self.b * rhs; 108 | self.a = self.a * rhs; 109 | } 110 | } 111 | 112 | impl DivAssign for Color { 113 | fn div_assign(&mut self, rhs: f32) { 114 | self.r = self.r / rhs; 115 | self.g = self.g / rhs; 116 | self.b = self.b / rhs; 117 | self.a = self.a / rhs; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/decal.rs: -------------------------------------------------------------------------------- 1 | use std::{rc::Weak, cell::RefCell}; 2 | 3 | use miniquad::*; 4 | use glam::*; 5 | use crate::*; 6 | 7 | #[derive(Debug)] 8 | pub enum DecalMode { 9 | Normal, 10 | Additive, 11 | Multiplicative, 12 | Stencil, 13 | Illuminate, 14 | Wireframe, 15 | Model3D, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub enum DecalStructure { 20 | Line, 21 | Fan, 22 | Strip, 23 | List, 24 | } 25 | 26 | #[derive(Debug)] 27 | pub struct Decal { 28 | pub sprite: Weak>, 29 | pub texture_id: TextureId, 30 | pub uv_scale: Vec2, 31 | pub width: u32, 32 | pub height: u32, 33 | } 34 | 35 | impl Decal { 36 | // consumes sprite!!!! 37 | // returns a decal and a spriteref 38 | pub fn new_from_sprite(pge: &mut PGE, sprite: Sprite) -> (Decal, SpriteRef) { 39 | let id = pge.create_texture(sprite.width, sprite.height); 40 | pge.update_texture(id, &sprite); 41 | let (width, height) = (sprite.width, sprite.height); 42 | let sprite_ref = SpriteRef::new_from_sprite(sprite); 43 | (Decal { 44 | sprite: Rc::downgrade(&sprite_ref.0), // create a weak pointer to the sprite_ref 45 | texture_id: id, 46 | uv_scale: Vec2::ONE, 47 | width: width, 48 | height: height 49 | }, sprite_ref) 50 | } 51 | 52 | // does not consume anything! 53 | pub fn new_from_sprite_ref(pge: &mut PGE, sprite_ref: &SpriteRef) -> Decal { 54 | let id = pge.create_texture(sprite_ref.width(), sprite_ref.height()); 55 | let sprite = sprite_ref.0.borrow(); 56 | pge.update_texture(id, &sprite); 57 | let (width, height) = (sprite_ref.width(), sprite_ref.height()); 58 | Decal { 59 | sprite: Rc::downgrade(&sprite_ref.0), 60 | texture_id: id, 61 | uv_scale: Vec2::ONE, 62 | width: width, 63 | height: height 64 | } 65 | } 66 | } 67 | 68 | #[derive(Debug)] 69 | pub struct DecalInstance { 70 | pub vertices: Vec, 71 | pub tint: Color, 72 | pub mode: DecalMode, 73 | pub structure: DecalStructure, 74 | } -------------------------------------------------------------------------------- /src/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbettcher/rustyPixelGameEngine/3972dc96322d6bfd9685bb7d0edb81244fc9b647/src/font.png -------------------------------------------------------------------------------- /src/gfx2d.rs: -------------------------------------------------------------------------------- 1 | use crate::{Sprite, PGE}; 2 | 3 | pub mod vec2d; 4 | 5 | pub struct Transform2D { 6 | pub matrix: [[[f32; 3]; 3]; 4], 7 | pub target: usize, 8 | pub source: usize, 9 | pub dirty: bool, 10 | } 11 | 12 | impl Transform2D { 13 | pub fn new() -> Self { 14 | Transform2D { 15 | target: 0, 16 | source: 1, 17 | dirty: true, 18 | matrix: [[[0.0; 3]; 3]; 4] 19 | } 20 | } 21 | 22 | pub fn reset(&mut self) { 23 | self.target = 0; 24 | self.source = 1; 25 | self.dirty = true; 26 | 27 | // Columns Then Rows 28 | 29 | // Matrices 0 & 1 are used as swaps in Transform accumulation 30 | self.matrix[0][0][0] = 1.0; self.matrix[0][1][0] = 0.0; self.matrix[0][2][0] = 0.0; 31 | self.matrix[0][0][1] = 0.0; self.matrix[0][1][1] = 1.0; self.matrix[0][2][1] = 0.0; 32 | self.matrix[0][0][2] = 0.0; self.matrix[0][1][2] = 0.0; self.matrix[0][2][2] = 1.0; 33 | 34 | self.matrix[1][0][0] = 1.0; self.matrix[1][1][0] = 0.0; self.matrix[1][2][0] = 0.0; 35 | self.matrix[1][0][1] = 0.0; self.matrix[1][1][1] = 1.0; self.matrix[1][2][1] = 0.0; 36 | self.matrix[1][0][2] = 0.0; self.matrix[1][1][2] = 0.0; self.matrix[1][2][2] = 1.0; 37 | 38 | // Matrix 2 is a cache matrix to hold the immediate transform operation 39 | // Matrix 3 is a cache matrix to hold the inverted transform 40 | } 41 | 42 | fn multiply(&mut self) { 43 | for c in 0..3 { 44 | for r in 0..3 { 45 | self.matrix[self.target][c][r] = self.matrix[2][0][r] * self.matrix[self.source][c][0] + 46 | self.matrix[2][1][r] * self.matrix[self.source][c][1] + 47 | self.matrix[2][2][r] * self.matrix[self.source][c][2]; 48 | } 49 | } 50 | 51 | std::mem::swap(&mut self.target, &mut self.source); 52 | self.dirty = true; // Any transform multiply dirties the inversion 53 | } 54 | 55 | pub fn rotate(&mut self, theta: f32) { 56 | // Construct Rotation Matrix 57 | self.matrix[2][0][0] = theta.cos(); self.matrix[2][1][0] = theta.sin(); self.matrix[2][2][0] = 0.0; 58 | self.matrix[2][0][1] = -theta.sin(); self.matrix[2][1][1] = theta.cos(); self.matrix[2][2][1] = 0.0; 59 | self.matrix[2][0][2] = 0.0; self.matrix[2][1][2] = 0.0; self.matrix[2][2][2] = 1.0; 60 | self.multiply(); 61 | } 62 | 63 | pub fn scale(&mut self, sx: f32, sy: f32) { 64 | // Construct Scale Matrix 65 | self.matrix[2][0][0] = sx; self.matrix[2][1][0] = 0.0; self.matrix[2][2][0] = 0.0; 66 | self.matrix[2][0][1] = 0.0; self.matrix[2][1][1] = sy; self.matrix[2][2][1] = 0.0; 67 | self.matrix[2][0][2] = 0.0; self.matrix[2][1][2] = 0.0; self.matrix[2][2][2] = 1.0; 68 | self.multiply(); 69 | } 70 | 71 | pub fn shear(&mut self, sx: f32, sy: f32) { 72 | // Construct Shear Matrix 73 | self.matrix[2][0][0] = 1.0; self.matrix[2][1][0] = sx; self.matrix[2][2][0] = 0.0; 74 | self.matrix[2][0][1] = sy; self.matrix[2][1][1] = 1.0; self.matrix[2][2][1] = 0.0; 75 | self.matrix[2][0][2] = 0.0; self.matrix[2][1][2] = 0.0; self.matrix[2][2][2] = 1.0; 76 | self.multiply(); 77 | } 78 | 79 | pub fn translate(&mut self, ox: f32, oy: f32) { 80 | // Construct Translate Matrix 81 | self.matrix[2][0][0] = 1.0; self.matrix[2][1][0] = 0.0; self.matrix[2][2][0] = ox; 82 | self.matrix[2][0][1] = 0.0; self.matrix[2][1][1] = 1.0; self.matrix[2][2][1] = oy; 83 | self.matrix[2][0][2] = 0.0; self.matrix[2][1][2] = 0.0; self.matrix[2][2][2] = 1.0; 84 | self.multiply(); 85 | } 86 | 87 | // TODO: not same API 88 | pub fn forward(&mut self, x: f32, y: f32) -> (f32, f32) 89 | { 90 | (x * self.matrix[self.source][0][0] + y * self.matrix[self.source][1][0] + self.matrix[self.source][2][0], 91 | x * self.matrix[self.source][0][1] + y * self.matrix[self.source][1][1] + self.matrix[self.source][2][1]) 92 | } 93 | 94 | // TODO: not same API 95 | pub fn backward(&mut self, x: f32, y: f32) -> (f32, f32) 96 | { 97 | (x * self.matrix[3][0][0] + y * self.matrix[3][1][0] + self.matrix[3][2][0], 98 | x * self.matrix[3][0][1] + y * self.matrix[3][1][1] + self.matrix[3][2][1]) 99 | } 100 | 101 | pub fn invert(&mut self) 102 | { 103 | // Obviously costly so only do if needed 104 | if self.dirty { 105 | let det = self.matrix[self.source][0][0] * (self.matrix[self.source][1][1] * self.matrix[self.source][2][2] - self.matrix[self.source][1][2] * self.matrix[self.source][2][1]) - 106 | self.matrix[self.source][1][0] * (self.matrix[self.source][0][1] * self.matrix[self.source][2][2] - self.matrix[self.source][2][1] * self.matrix[self.source][0][2]) + 107 | self.matrix[self.source][2][0] * (self.matrix[self.source][0][1] * self.matrix[self.source][1][2] - self.matrix[self.source][1][1] * self.matrix[self.source][0][2]); 108 | 109 | let idet = 1.0 / det; 110 | self.matrix[3][0][0] = (self.matrix[self.source][1][1] * self.matrix[self.source][2][2] - self.matrix[self.source][1][2] * self.matrix[self.source][2][1]) * idet; 111 | self.matrix[3][1][0] = (self.matrix[self.source][2][0] * self.matrix[self.source][1][2] - self.matrix[self.source][1][0] * self.matrix[self.source][2][2]) * idet; 112 | self.matrix[3][2][0] = (self.matrix[self.source][1][0] * self.matrix[self.source][2][1] - self.matrix[self.source][2][0] * self.matrix[self.source][1][1]) * idet; 113 | self.matrix[3][0][1] = (self.matrix[self.source][2][1] * self.matrix[self.source][0][2] - self.matrix[self.source][0][1] * self.matrix[self.source][2][2]) * idet; 114 | self.matrix[3][1][1] = (self.matrix[self.source][0][0] * self.matrix[self.source][2][2] - self.matrix[self.source][2][0] * self.matrix[self.source][0][2]) * idet; 115 | self.matrix[3][2][1] = (self.matrix[self.source][0][1] * self.matrix[self.source][2][0] - self.matrix[self.source][0][0] * self.matrix[self.source][2][1]) * idet; 116 | self.matrix[3][0][2] = (self.matrix[self.source][0][1] * self.matrix[self.source][1][2] - self.matrix[self.source][0][2] * self.matrix[self.source][1][1]) * idet; 117 | self.matrix[3][1][2] = (self.matrix[self.source][0][2] * self.matrix[self.source][1][0] - self.matrix[self.source][0][0] * self.matrix[self.source][1][2]) * idet; 118 | self.matrix[3][2][2] = (self.matrix[self.source][0][0] * self.matrix[self.source][1][1] - self.matrix[self.source][0][1] * self.matrix[self.source][1][0]) * idet; 119 | self.dirty = false; 120 | } 121 | } 122 | } 123 | 124 | pub struct GFX2D; 125 | 126 | impl GFX2D { 127 | pub fn draw_sprite(pge: &mut PGE, sprite: &Sprite, transform: &mut Transform2D) { 128 | // Work out bounding rectangle of sprite 129 | let mut sx: f32 = 0.0; 130 | let mut sy: f32 = 0.0; 131 | let mut ex: f32 = 0.0; 132 | let mut ey: f32 = 0.0; 133 | let a = transform.forward(0.0, 0.0); 134 | sx = sx.min(a.0); sy = sy.min(a.1); 135 | ex = ex.max(a.0); ey = ey.max(a.1); 136 | let b = transform.forward(sprite.width as f32, sprite.height as f32); 137 | sx = sx.min(b.0); sy = sy.min(b.1); 138 | ex = ex.max(b.0); ey = ey.max(b.1); 139 | let c = transform.forward(0.0, sprite.height as f32); 140 | sx = sx.min(c.0); sy = sy.min(c.1); 141 | ex = ex.max(c.0); ey = ey.max(c.1); 142 | let d = transform.forward(sprite.width as f32, 0.0); 143 | sx = sx.min(d.0); sy = sy.min(d.1); 144 | ex = ex.max(d.0); ey = ey.max(d.1); 145 | 146 | // Perform inversion of transform if required 147 | transform.invert(); 148 | 149 | if ex < sx { 150 | std::mem::swap(&mut ex, &mut sx); 151 | } 152 | if ey < sy { 153 | std::mem::swap(&mut ey, &mut sy); 154 | } 155 | 156 | // Iterate through render space, and sample Sprite from suitable texel location 157 | for j in sy as i32 .. ey as i32 { 158 | for i in sx as i32 .. ex as i32 { 159 | let o = transform.backward(i as f32, j as f32); 160 | pge.draw(i, j, &sprite.get_pixel((o.0 + 0.5) as i32, (o.1 + 0.5) as i32)); 161 | } 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /src/gfx2d/vec2d.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub, Mul, Div, AddAssign, SubAssign, MulAssign, DivAssign}; 2 | use super::super::gfx3d::vec3d::Vec3d; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Vec2d 6 | { 7 | pub x: f32, 8 | pub y: f32, 9 | } 10 | 11 | impl Vec2d { 12 | pub fn new(x: f32, y: f32) -> Self { 13 | Vec2d { x, y } 14 | } 15 | 16 | pub fn zero() -> Self { 17 | Vec2d { x: 0.0, y: 0.0 } 18 | } 19 | 20 | pub fn length(&self) -> f32 { 21 | (self.x * self.x + self.y * self.y).sqrt() 22 | } 23 | 24 | pub fn norm(&self) -> Self { 25 | let l = 1.0 / self.length(); 26 | Vec2d { x: self.x * l, y: self.y * l } 27 | } 28 | 29 | pub fn perp(&self) -> Self { 30 | Vec2d { x: -self.y, y: self.x } 31 | } 32 | 33 | pub fn dot(&self, rhs: &Self) -> f32 { 34 | self.x * rhs.x + self.y * rhs.y 35 | } 36 | 37 | pub fn cross(&self, rhs: &Self) -> f32 { 38 | self.x * rhs.y - self.y * rhs.x 39 | } 40 | 41 | pub fn as_vec3d(&self) -> Vec3d { 42 | Vec3d { x: self.x, y: self.y, z: 0.0 } 43 | } 44 | } 45 | 46 | impl Add for Vec2d { 47 | type Output = Vec2d; 48 | 49 | fn add(self, rhs: Vec2d) -> Vec2d { 50 | Vec2d { 51 | x: self.x + rhs.x, 52 | y: self.y + rhs.y, 53 | } 54 | } 55 | } 56 | 57 | impl Sub for Vec2d { 58 | type Output = Vec2d; 59 | 60 | fn sub(self, rhs: Vec2d) -> Vec2d { 61 | Vec2d { 62 | x: self.x - rhs.x, 63 | y: self.y - rhs.y, 64 | } 65 | } 66 | } 67 | 68 | impl Mul for Vec2d { 69 | type Output = Vec2d; 70 | 71 | fn mul(self, rhs: f32) -> Vec2d { 72 | Vec2d { 73 | x: self.x * rhs, 74 | y: self.y * rhs, 75 | } 76 | } 77 | } 78 | 79 | impl Div for Vec2d { 80 | type Output = Vec2d; 81 | 82 | fn div(self, rhs: f32) -> Vec2d { 83 | Vec2d { 84 | x: self.x / rhs, 85 | y: self.y / rhs, 86 | } 87 | } 88 | } 89 | 90 | impl AddAssign for Vec2d { 91 | fn add_assign(&mut self, rhs: Vec2d) { 92 | self.x = self.x + rhs.x; 93 | self.y = self.y + rhs.y; 94 | } 95 | } 96 | 97 | impl SubAssign for Vec2d { 98 | fn sub_assign(&mut self, rhs: Vec2d) { 99 | self.x = self.x - rhs.x; 100 | self.y = self.y - rhs.y; 101 | } 102 | } 103 | 104 | impl MulAssign for Vec2d { 105 | fn mul_assign(&mut self, rhs: f32) { 106 | self.x = self.x * rhs; 107 | self.y = self.y * rhs; 108 | } 109 | } 110 | 111 | impl DivAssign for Vec2d { 112 | fn div_assign(&mut self, rhs: f32) { 113 | self.x = self.x / rhs; 114 | self.y = self.y / rhs; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/gfx3d.rs: -------------------------------------------------------------------------------- 1 | pub mod vec3d; 2 | pub mod vec4d; 3 | pub mod mat4x4; 4 | pub mod triangle; 5 | pub mod plane; 6 | pub mod pipeline; 7 | 8 | pub use self::vec3d::Vec3d; 9 | pub use self::vec4d::Vec4d; 10 | pub use self::triangle::Triangle; 11 | pub use self::plane::Plane; 12 | pub use self::pipeline::Pipeline; 13 | 14 | pub struct Mesh { 15 | pub tris: Vec 16 | } 17 | -------------------------------------------------------------------------------- /src/gfx3d/mat4x4.rs: -------------------------------------------------------------------------------- 1 | use super::vec3d::Vec3d; 2 | use super::vec4d::Vec4d; 3 | use std::ops::{Mul}; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct Mat4x4 { 7 | pub m: [[f32; 4]; 4], 8 | } 9 | 10 | impl Mat4x4 { 11 | pub fn make_identity() -> Self { 12 | Mat4x4 { 13 | m: [ 14 | [1.0, 0.0, 0.0, 0.0], 15 | [0.0, 1.0, 0.0, 0.0], 16 | [0.0, 0.0, 1.0, 0.0], 17 | [0.0, 0.0, 0.0, 1.0], 18 | ], 19 | } 20 | } 21 | 22 | pub fn make_rotation_x(rad: f32) -> Self { 23 | Mat4x4 { 24 | m: [ 25 | [1.0, 0.0, 0.0, 0.0], 26 | [0.0, rad.cos(), rad.sin(), 0.0], 27 | [0.0, -rad.sin(), rad.cos(), 0.0], 28 | [0.0, 0.0, 0.0, 1.0], 29 | ], 30 | } 31 | } 32 | 33 | pub fn make_rotation_y(rad: f32) -> Self { 34 | Mat4x4 { 35 | m: [ 36 | [rad.cos(), 0.0, rad.sin(), 0.0], 37 | [0.0, 1.0, 0.0, 0.0], 38 | [-rad.sin(), 0.0, rad.cos(), 0.0], 39 | [0.0, 0.0, 0.0, 1.0], 40 | ], 41 | } 42 | } 43 | 44 | pub fn make_rotation_z(rad: f32) -> Self { 45 | Mat4x4 { 46 | m: [ 47 | [rad.cos(), rad.sin(), 0.0, 0.0], 48 | [-rad.sin(), rad.cos(), 0.0, 0.0], 49 | [0.0, 0.0, 1.0, 0.0], 50 | [0.0, 0.0, 0.0, 1.0], 51 | ], 52 | } 53 | } 54 | 55 | pub fn make_scale(x: f32, y: f32, z: f32) -> Self { 56 | Mat4x4 { 57 | m: [ 58 | [x, 0.0, 0.0, 0.0], 59 | [0.0, y, 0.0, 0.0], 60 | [0.0, 0.0, z, 0.0], 61 | [0.0, 0.0, 0.0, 1.0], 62 | ], 63 | } 64 | } 65 | 66 | pub fn make_translation(x: f32, y: f32, z: f32) -> Self { 67 | Mat4x4 { 68 | m: [ 69 | [1.0, 0.0, 0.0, 0.0], 70 | [0.0, 1.0, 0.0, 0.0], 71 | [0.0, 0.0, 1.0, 0.0], 72 | [x, y, z, 1.0], 73 | ], 74 | } 75 | } 76 | 77 | pub fn make_projection(fov: f32, ar: f32, near: f32, far: f32) -> Self { 78 | let fov_rad = 1.0 / (fov * 0.5 / 180.0 * 3.14159).tan(); 79 | Mat4x4 { 80 | m: [ 81 | [ar * fov_rad, 0.0, 0.0, 0.0], 82 | [0.0, fov_rad, 0.0, 0.0], 83 | [0.0, 0.0, far / (far - near), 1.0], 84 | [0.0, 0.0, (-far * near) / (far - near), 0.0], 85 | ], 86 | } 87 | } 88 | 89 | pub fn make_point_at(pos: Vec3d, target: Vec3d, up: Vec3d) -> Self { 90 | let new_forward = (target - pos).norm(); 91 | let t = up.dot(&new_forward); 92 | let new_up = (up - (new_forward * t)).norm(); 93 | let new_right = new_up.cross(&new_forward); 94 | 95 | Mat4x4 { 96 | m: [ 97 | [new_right.x, new_right.y, new_right.z, 0.0], 98 | [new_up.x, new_up.y, new_up.z, 0.0], 99 | [new_forward.x, new_forward.y, new_forward.z, 0.0], 100 | [pos.x, pos.y, pos.z, 1.0], 101 | ] 102 | } 103 | } 104 | 105 | pub fn inverse(&self) -> Mat4x4 { 106 | let mut inv = Mat4x4::make_identity(); 107 | 108 | inv.m[0][0] = self.m[1][1] * self.m[2][2] * self.m[3][3] - self.m[1][1] * self.m[2][3] * self.m[3][2] - self.m[2][1] * self.m[1][2] * self.m[3][3] + self.m[2][1] * self.m[1][3] * self.m[3][2] + self.m[3][1] * self.m[1][2] * self.m[2][3] - self.m[3][1] * self.m[1][3] * self.m[2][2]; 109 | inv.m[1][0] = -self.m[1][0] * self.m[2][2] * self.m[3][3] + self.m[1][0] * self.m[2][3] * self.m[3][2] + self.m[2][0] * self.m[1][2] * self.m[3][3] - self.m[2][0] * self.m[1][3] * self.m[3][2] - self.m[3][0] * self.m[1][2] * self.m[2][3] + self.m[3][0] * self.m[1][3] * self.m[2][2]; 110 | inv.m[2][0] = self.m[1][0] * self.m[2][1] * self.m[3][3] - self.m[1][0] * self.m[2][3] * self.m[3][1] - self.m[2][0] * self.m[1][1] * self.m[3][3] + self.m[2][0] * self.m[1][3] * self.m[3][1] + self.m[3][0] * self.m[1][1] * self.m[2][3] - self.m[3][0] * self.m[1][3] * self.m[2][1]; 111 | inv.m[3][0] = -self.m[1][0] * self.m[2][1] * self.m[3][2] + self.m[1][0] * self.m[2][2] * self.m[3][1] + self.m[2][0] * self.m[1][1] * self.m[3][2] - self.m[2][0] * self.m[1][2] * self.m[3][1] - self.m[3][0] * self.m[1][1] * self.m[2][2] + self.m[3][0] * self.m[1][2] * self.m[2][1]; 112 | inv.m[0][1] = -self.m[0][1] * self.m[2][2] * self.m[3][3] + self.m[0][1] * self.m[2][3] * self.m[3][2] + self.m[2][1] * self.m[0][2] * self.m[3][3] - self.m[2][1] * self.m[0][3] * self.m[3][2] - self.m[3][1] * self.m[0][2] * self.m[2][3] + self.m[3][1] * self.m[0][3] * self.m[2][2]; 113 | inv.m[1][1] = self.m[0][0] * self.m[2][2] * self.m[3][3] - self.m[0][0] * self.m[2][3] * self.m[3][2] - self.m[2][0] * self.m[0][2] * self.m[3][3] + self.m[2][0] * self.m[0][3] * self.m[3][2] + self.m[3][0] * self.m[0][2] * self.m[2][3] - self.m[3][0] * self.m[0][3] * self.m[2][2]; 114 | inv.m[2][1] = -self.m[0][0] * self.m[2][1] * self.m[3][3] + self.m[0][0] * self.m[2][3] * self.m[3][1] + self.m[2][0] * self.m[0][1] * self.m[3][3] - self.m[2][0] * self.m[0][3] * self.m[3][1] - self.m[3][0] * self.m[0][1] * self.m[2][3] + self.m[3][0] * self.m[0][3] * self.m[2][1]; 115 | inv.m[3][1] = self.m[0][0] * self.m[2][1] * self.m[3][2] - self.m[0][0] * self.m[2][2] * self.m[3][1] - self.m[2][0] * self.m[0][1] * self.m[3][2] + self.m[2][0] * self.m[0][2] * self.m[3][1] + self.m[3][0] * self.m[0][1] * self.m[2][2] - self.m[3][0] * self.m[0][2] * self.m[2][1]; 116 | inv.m[0][2] = self.m[0][1] * self.m[1][2] * self.m[3][3] - self.m[0][1] * self.m[1][3] * self.m[3][2] - self.m[1][1] * self.m[0][2] * self.m[3][3] + self.m[1][1] * self.m[0][3] * self.m[3][2] + self.m[3][1] * self.m[0][2] * self.m[1][3] - self.m[3][1] * self.m[0][3] * self.m[1][2]; 117 | inv.m[1][2] = -self.m[0][0] * self.m[1][2] * self.m[3][3] + self.m[0][0] * self.m[1][3] * self.m[3][2] + self.m[1][0] * self.m[0][2] * self.m[3][3] - self.m[1][0] * self.m[0][3] * self.m[3][2] - self.m[3][0] * self.m[0][2] * self.m[1][3] + self.m[3][0] * self.m[0][3] * self.m[1][2]; 118 | inv.m[2][2] = self.m[0][0] * self.m[1][1] * self.m[3][3] - self.m[0][0] * self.m[1][3] * self.m[3][1] - self.m[1][0] * self.m[0][1] * self.m[3][3] + self.m[1][0] * self.m[0][3] * self.m[3][1] + self.m[3][0] * self.m[0][1] * self.m[1][3] - self.m[3][0] * self.m[0][3] * self.m[1][1]; 119 | inv.m[3][2] = -self.m[0][0] * self.m[1][1] * self.m[3][2] + self.m[0][0] * self.m[1][2] * self.m[3][1] + self.m[1][0] * self.m[0][1] * self.m[3][2] - self.m[1][0] * self.m[0][2] * self.m[3][1] - self.m[3][0] * self.m[0][1] * self.m[1][2] + self.m[3][0] * self.m[0][2] * self.m[1][1]; 120 | inv.m[0][3] = -self.m[0][1] * self.m[1][2] * self.m[2][3] + self.m[0][1] * self.m[1][3] * self.m[2][2] + self.m[1][1] * self.m[0][2] * self.m[2][3] - self.m[1][1] * self.m[0][3] * self.m[2][2] - self.m[2][1] * self.m[0][2] * self.m[1][3] + self.m[2][1] * self.m[0][3] * self.m[1][2]; 121 | inv.m[1][3] = self.m[0][0] * self.m[1][2] * self.m[2][3] - self.m[0][0] * self.m[1][3] * self.m[2][2] - self.m[1][0] * self.m[0][2] * self.m[2][3] + self.m[1][0] * self.m[0][3] * self.m[2][2] + self.m[2][0] * self.m[0][2] * self.m[1][3] - self.m[2][0] * self.m[0][3] * self.m[1][2]; 122 | inv.m[2][3] = -self.m[0][0] * self.m[1][1] * self.m[2][3] + self.m[0][0] * self.m[1][3] * self.m[2][1] + self.m[1][0] * self.m[0][1] * self.m[2][3] - self.m[1][0] * self.m[0][3] * self.m[2][1] - self.m[2][0] * self.m[0][1] * self.m[1][3] + self.m[2][0] * self.m[0][3] * self.m[1][1]; 123 | inv.m[3][3] = self.m[0][0] * self.m[1][1] * self.m[2][2] - self.m[0][0] * self.m[1][2] * self.m[2][1] - self.m[1][0] * self.m[0][1] * self.m[2][2] + self.m[1][0] * self.m[0][2] * self.m[2][1] + self.m[2][0] * self.m[0][1] * self.m[1][2] - self.m[2][0] * self.m[0][2] * self.m[1][1]; 124 | 125 | let mut det = self.m[0][0] * inv.m[0][0] + self.m[0][1] * inv.m[1][0] + self.m[0][2] * inv.m[2][0] + self.m[0][3] * inv.m[3][0]; 126 | 127 | det = 1.0 / det; 128 | 129 | for i in 0..4 { 130 | for j in 0..4 { 131 | inv.m[i][j] *= det; 132 | } 133 | } 134 | 135 | return inv; 136 | } 137 | } 138 | 139 | impl Mul for Mat4x4 { 140 | type Output = Vec3d; 141 | 142 | fn mul(self, rhs: Vec3d) -> Vec3d { 143 | Vec3d { 144 | x: rhs.x * self.m[0][0] + rhs.y * self.m[1][0] + rhs.z * self.m[2][0] + 1.0 * self.m[3][0], 145 | y: rhs.x * self.m[0][1] + rhs.y * self.m[1][1] + rhs.z * self.m[2][1] + 1.0 * self.m[3][1], 146 | z: rhs.x * self.m[0][2] + rhs.y * self.m[1][2] + rhs.z * self.m[2][2] + 1.0 * self.m[3][2], 147 | //w: rhs.x * self.m[0][3] + rhs.y * self.m[1][3] + rhs.z * self.m[2][3] + 1.0 * self.m[3][3], 148 | } 149 | } 150 | } 151 | 152 | impl Mul for Mat4x4 { 153 | type Output = Vec4d; 154 | 155 | fn mul(self, rhs: Vec4d) -> Vec4d { 156 | Vec4d { 157 | x: rhs.x * self.m[0][0] + rhs.y * self.m[1][0] + rhs.z * self.m[2][0] + rhs.w * self.m[3][0], 158 | y: rhs.x * self.m[0][1] + rhs.y * self.m[1][1] + rhs.z * self.m[2][1] + rhs.w * self.m[3][1], 159 | z: rhs.x * self.m[0][2] + rhs.y * self.m[1][2] + rhs.z * self.m[2][2] + rhs.w * self.m[3][2], 160 | w: rhs.x * self.m[0][3] + rhs.y * self.m[1][3] + rhs.z * self.m[2][3] + rhs.w * self.m[3][3], 161 | } 162 | } 163 | } 164 | 165 | impl Mul for Mat4x4 { 166 | type Output = Mat4x4; 167 | 168 | fn mul(self, rhs: Mat4x4) -> Mat4x4 { 169 | let mut mat: Mat4x4 = Mat4x4::make_identity(); 170 | 171 | for c in 0..4 { 172 | for r in 0..4 { 173 | mat.m[r][c] = self.m[r][0] * rhs.m[0][c] 174 | + self.m[r][1] * rhs.m[1][c] 175 | + self.m[r][2] * rhs.m[2][c] 176 | + self.m[r][3] * rhs.m[3][c]; 177 | } 178 | } 179 | 180 | mat 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/gfx3d/pipeline.rs: -------------------------------------------------------------------------------- 1 | use crate::color::Color; 2 | 3 | use crate::gfx3d::plane::Plane; 4 | use crate::gfx3d::vec4d::Vec4d; 5 | use crate::{PGE}; 6 | use crate::gfx3d::triangle::Triangle; 7 | use crate::Sprite; 8 | use super::vec3d::Vec3d; 9 | use super::mat4x4::Mat4x4; 10 | use std::cmp::{max, min}; 11 | 12 | pub enum RenderOptions { 13 | Wire, Flat, Textured, 14 | } 15 | 16 | pub enum CullDirection { 17 | Cw, Ccw 18 | } 19 | 20 | pub struct Pipeline { 21 | proj: Mat4x4, 22 | view: Mat4x4, 23 | world: Mat4x4, 24 | texture: Sprite, 25 | view_x: f32, 26 | view_y: f32, 27 | view_w: f32, 28 | view_h: f32, 29 | 30 | near: f32, 31 | 32 | depth_buffer: Vec, 33 | } 34 | 35 | impl Pipeline { 36 | pub fn new(width: usize, height: usize) -> Self { 37 | Pipeline { 38 | proj: Mat4x4::make_identity(), 39 | view: Mat4x4::make_identity(), 40 | world: Mat4x4::make_identity(), 41 | texture: Sprite::new(0, 0), 42 | view_x: 0.0, 43 | view_y: 0.0, 44 | view_w: 0.0, 45 | view_h: 0.0, 46 | 47 | near: 0.0, 48 | 49 | depth_buffer: vec![0.0; width * height], 50 | } 51 | } 52 | 53 | pub fn set_projection(&mut self, fov_degrees: f32, aspect_ratio: f32, near: f32, far: f32, left: f32, top: f32, width: f32, height: f32) { 54 | self.proj = Mat4x4::make_projection(fov_degrees, aspect_ratio, near, far); 55 | self.view_x = left; 56 | self.view_y = top; 57 | self.view_w = width; 58 | self.view_h = height; 59 | self.near = near; 60 | } 61 | 62 | pub fn set_camera(&mut self, pos: Vec3d, look_at: Vec3d, up: Vec3d) { 63 | let t = Mat4x4::make_point_at(pos, look_at, up); 64 | self.view = t.inverse(); 65 | } 66 | 67 | pub fn set_transform(&mut self, transform: Mat4x4) { 68 | self.world = transform; 69 | } 70 | 71 | pub fn set_texture(&mut self, texture: &Sprite) { 72 | self.texture = texture.clone(); 73 | } 74 | 75 | pub fn render(&mut self, pge: &mut PGE, triangles: &Vec, tex: &Sprite) { // flags: RenderOptions, cull_dir: CullDirection) { 76 | #[allow(unused_variables)] 77 | let mut tri_count = 0; 78 | let world_view = self.world * self.view; 79 | 80 | // Process Triangles 81 | for tri in triangles { 82 | let mut tri_transformed = Triangle::new(); 83 | tri_transformed.col = tri.col; 84 | // Just copy through texture coordinates 85 | tri_transformed.t = tri.t; 86 | // Transform Triangle from object into projected space 87 | tri_transformed.p[0] = world_view * tri.p[0]; 88 | tri_transformed.p[1] = world_view * tri.p[1]; 89 | tri_transformed.p[2] = world_view * tri.p[2]; 90 | // Calculate Triangle Normal in WorldView Space 91 | let line_1 = tri_transformed.p[1] - tri_transformed.p[0]; 92 | let line_2 = tri_transformed.p[2] - tri_transformed.p[0]; 93 | let tri_world_normal = line_1.cross(&line_2).norm(); 94 | // Cull triangles that face away from viewer 95 | // Clockwise only for now 96 | if tri_world_normal.dot(&tri_transformed.p[0]) > 0.0 { continue; } 97 | 98 | // Clip triangle against near plane 99 | let near_clipped_tris = tri_transformed.clip_against_plane(&Plane { position: Vec4d{x: 0.0, y:0.0, z:self.near, w:1.0}, 100 | normal: Vec4d{x: 0.0, y:0.0, z:1.0, w:1.0} }); 101 | 102 | for near_tri in near_clipped_tris { 103 | let mut tri_projected = near_tri; 104 | tri_projected.col = near_tri.col; 105 | 106 | // Project new triangle 107 | tri_projected.p[0] = self.proj * near_tri.p[0]; 108 | tri_projected.p[1] = self.proj * near_tri.p[1]; 109 | tri_projected.p[2] = self.proj * near_tri.p[2]; 110 | 111 | // Apply Projection to Verts 112 | tri_projected.p[0] /= tri_projected.p[0].w; 113 | tri_projected.p[1] /= tri_projected.p[1].w; 114 | tri_projected.p[2] /= tri_projected.p[2].w; 115 | 116 | // Apply Projection to Tex coords 117 | tri_projected.t[0].x /= tri_projected.p[0].w; 118 | tri_projected.t[1].x /= tri_projected.p[1].w; 119 | tri_projected.t[2].x /= tri_projected.p[2].w; 120 | 121 | tri_projected.t[0].y /= tri_projected.p[0].w; 122 | tri_projected.t[1].y /= tri_projected.p[1].w; 123 | tri_projected.t[2].y /= tri_projected.p[2].w; 124 | 125 | tri_projected.t[0].z = 1.0 / tri_projected.p[0].w; 126 | tri_projected.t[1].z = 1.0 / tri_projected.p[1].w; 127 | tri_projected.t[2].z = 1.0 / tri_projected.p[2].w; 128 | 129 | // Clip against viewport in screen space 130 | // Clip triangles against all four screen edges, this could yield 131 | // a bunch of triangles, so create a queue that we traverse to 132 | // ensure we only test new triangles generated against planes 133 | // Add initial triangle 134 | let mut view_clipped_tris = vec!(tri_projected); 135 | for p in 0..4 { 136 | if let Some(tri_to_clip) = view_clipped_tris.pop() { 137 | // Clip it against a plane. We only need to test each 138 | // subsequent plane, against subsequent new triangles 139 | // as all triangles after a plane clip are guaranteed 140 | // to lie on the inside of the plane. I like how this 141 | // comment is almost completely and utterly justified 142 | let clipped = match p { 143 | 0 => { tri_to_clip.clip_against_plane(&Plane { position: Vec4d{x: 0.0, y:-1.0, z:0.0, w:1.0}, normal: Vec4d{x: 0.0, y:1.0, z:0.0, w:1.0} }) }, 144 | 1 => { tri_to_clip.clip_against_plane(&Plane { position: Vec4d{x: 0.0, y:1.0, z:0.0, w:1.0}, normal: Vec4d{x: 0.0, y:-1.0, z:0.0, w:1.0} }) }, 145 | 2 => { tri_to_clip.clip_against_plane(&Plane { position: Vec4d{x: 1.0, y:0.0, z:0.0, w:1.0}, normal: Vec4d{x: -1.0, y:0.0, z:0.0, w:1.0} }) }, 146 | 3 => { tri_to_clip.clip_against_plane(&Plane { position: Vec4d{x: -1.0, y:0.0, z:0.0, w:1.0}, normal: Vec4d{x: 1.0, y:0.0, z:0.0, w:1.0} }) }, 147 | _ => { panic!() } 148 | }; 149 | 150 | for t in clipped { 151 | view_clipped_tris.push(t); 152 | } 153 | } 154 | } 155 | 156 | for mut tri_raster in view_clipped_tris { 157 | // Scale to viewport 158 | tri_raster.col = tri_projected.col; 159 | 160 | let mut offset_view = Vec4d { x:1.0,y:1.0,z:0.0, w:1.0 }; 161 | tri_raster.p[0] += offset_view; 162 | tri_raster.p[1] += offset_view; 163 | tri_raster.p[2] += offset_view; 164 | tri_raster.p[0].x *= 0.5 * self.view_w; 165 | tri_raster.p[0].y *= 0.5 * self.view_h; 166 | tri_raster.p[1].x *= 0.5 * self.view_w; 167 | tri_raster.p[1].y *= 0.5 * self.view_h; 168 | tri_raster.p[2].x *= 0.5 * self.view_w; 169 | tri_raster.p[2].y *= 0.5 * self.view_h; 170 | offset_view = Vec4d { x:self.view_x, y:self.view_y,z:0.0, w:1.0 }; 171 | tri_raster.p[0] += offset_view; 172 | tri_raster.p[1] += offset_view; 173 | tri_raster.p[2] += offset_view; 174 | 175 | /*pge.fill_triangle(tri_raster.p[0].x as i32, tri_raster.p[0].y as i32, 176 | tri_raster.p[1].x as i32, tri_raster.p[1].y as i32, 177 | tri_raster.p[2].x as i32, tri_raster.p[2].y as i32, 178 | &tri_raster.col);*/ 179 | 180 | //tri_raster.draw_tex(pge, &tex); 181 | 182 | //self.textured_triangle(pge, tri_raster.p[0].x as i32, tri_raster.p[0].y as i32, tri_raster.t[0].x, tri_raster.t[0].y, tri_raster.t[0].z, 183 | // tri_raster.p[1].x as i32, tri_raster.p[1].y as i32, tri_raster.t[1].x, tri_raster.t[1].y, tri_raster.t[1].z, 184 | // tri_raster.p[2].x as i32, tri_raster.p[2].y as i32, tri_raster.t[2].x, tri_raster.t[2].y, tri_raster.t[2].z, 185 | // &tex); 186 | 187 | self.render_triangle_texture(pge, &tri_raster.p[0], &tri_raster.p[1], &tri_raster.p[2], 188 | &tri_raster.t[0], &tri_raster.t[1], &tri_raster.t[2], &tex); 189 | 190 | tri_count += 1; 191 | 192 | /*pge.draw_triangle(tri_raster.p[0].x as i32, tri_raster.p[0].y as i32, 193 | tri_raster.p[1].x as i32, tri_raster.p[1].y as i32, 194 | tri_raster.p[2].x as i32, tri_raster.p[2].y as i32, 195 | &Pixel::rgb(255, 255, 255));*/ 196 | } 197 | } 198 | } 199 | } 200 | 201 | // c1-c3 is the color for each point 202 | pub fn shaded_textured_triangle(&mut self, pge: &mut PGE, mut x1: i32, mut y1: i32, mut u1: f32, mut v1: f32, mut w1: f32, mut c1: Color, 203 | mut x2: i32, mut y2: i32, mut u2: f32, mut v2: f32, mut w2: f32, mut c2: Color, 204 | mut x3: i32, mut y3: i32, mut u3: f32, mut v3: f32, mut w3: f32, mut c3: Color, tex: &Sprite) { 205 | if y2 < y1 206 | { 207 | std::mem::swap(&mut y1, &mut y2); 208 | std::mem::swap(&mut x1, &mut x2); 209 | std::mem::swap(&mut u1, &mut u2); 210 | std::mem::swap(&mut v1, &mut v2); 211 | std::mem::swap(&mut w1, &mut w2); 212 | std::mem::swap(&mut c1, &mut c2); 213 | } 214 | if y3 < y1 215 | { 216 | std::mem::swap(&mut y1, &mut y3); 217 | std::mem::swap(&mut x1, &mut x3); 218 | std::mem::swap(&mut u1, &mut u3); 219 | std::mem::swap(&mut v1, &mut v3); 220 | std::mem::swap(&mut w1, &mut w3); 221 | std::mem::swap(&mut c1, &mut c3); 222 | } 223 | if y3 < y2 224 | { 225 | std::mem::swap(&mut y2, &mut y3); 226 | std::mem::swap(&mut x2, &mut x3); 227 | std::mem::swap(&mut u2, &mut u3); 228 | std::mem::swap(&mut v2, &mut v3); 229 | std::mem::swap(&mut w2, &mut w3); 230 | std::mem::swap(&mut c2, &mut c3); 231 | } 232 | 233 | let mut dy1 = y2 - y1; 234 | let mut dx1 = x2 - x1; 235 | let mut dv1 = v2 - v1; 236 | let mut du1 = u2 - u1; 237 | let mut dw1 = w2 - w1; 238 | let mut _dc1 = c2 - c1; 239 | 240 | let dy2 = y3 - y1; 241 | let dx2 = x3 - x1; 242 | let dv2 = v3 - v1; 243 | let du2 = u3 - u1; 244 | let dw2 = w3 - w1; 245 | let dc2 = c3 - c1; 246 | 247 | let mut tex_u: f32; 248 | let mut tex_v: f32; 249 | let mut tex_w: f32; 250 | let mut _col_r: f32; 251 | let mut _col_g: f32; 252 | let mut _col_b: f32; 253 | let mut _col_a: f32; 254 | 255 | let mut dax_step = 0.0; 256 | let mut du1_step = 0.0; 257 | let mut du2_step = 0.0; 258 | let mut dw1_step = 0.0; 259 | let mut dc1r_step = 0.0; 260 | let mut dc1g_step = 0.0; 261 | let mut dc1b_step = 0.0; 262 | let mut dc1a_step = 0.0; 263 | let mut dbx_step = 0.0; 264 | let mut dv1_step = 0.0; 265 | let mut dv2_step = 0.0; 266 | let mut dw2_step = 0.0; 267 | let mut dc2r_step = 0.0; 268 | let mut dc2g_step = 0.0; 269 | let mut dc2b_step = 0.0; 270 | let mut dc2a_step = 0.0; 271 | 272 | if dy1 > 0 { 273 | dax_step = dx1 as f32 / dy1.abs() as f32; 274 | du1_step = du1 / dy1.abs() as f32; 275 | dv1_step = dv1 / dy1.abs() as f32; 276 | dw1_step = dw1 / dy1.abs() as f32; 277 | dc1r_step = _dc1.r / dy1.abs() as f32; 278 | dc1g_step = _dc1.g / dy1.abs() as f32; 279 | dc1b_step = _dc1.b / dy1.abs() as f32; 280 | dc1a_step = _dc1.a / dy1.abs() as f32; 281 | } 282 | 283 | if dy2 > 0 { 284 | dbx_step = dx2 as f32 / dy2.abs() as f32; 285 | du2_step = du2 / dy2.abs() as f32; 286 | dv2_step = dv2 / dy2.abs() as f32; 287 | dw2_step = dw2 / dy2.abs() as f32; 288 | dc2r_step = dc2.r / dy2.abs() as f32; 289 | dc2g_step = dc2.g / dy2.abs() as f32; 290 | dc2b_step = dc2.b / dy2.abs() as f32; 291 | dc2a_step = dc2.a / dy2.abs() as f32; 292 | } 293 | 294 | if dy1 > 0 { 295 | for i in y1..y2 { 296 | let mut ax = x1 + ((i - y1) as f32 * dax_step) as i32; 297 | let mut bx = x1 + ((i - y1) as f32 * dbx_step) as i32; 298 | 299 | let mut tex_su = u1 + (i - y1) as f32 * du1_step; 300 | let mut tex_sv = v1 + (i - y1) as f32 * dv1_step; 301 | let mut tex_sw = w1 + (i - y1) as f32 * dw1_step; 302 | let mut col_sr = c1.r + (i - y1) as f32 * dc1r_step; 303 | let mut col_sg = c1.g + (i - y1) as f32 * dc1g_step; 304 | let mut col_sb = c1.b + (i - y1) as f32 * dc1b_step; 305 | let mut col_sa = c1.a + (i - y1) as f32 * dc1a_step; 306 | 307 | let mut tex_eu = u1 + (i - y1) as f32 * du2_step; 308 | let mut tex_ev = v1 + (i - y1) as f32 * dv2_step; 309 | let mut tex_ew = w1 + (i - y1) as f32 * dw2_step; 310 | let mut col_er = c1.r + (i - y1) as f32 * dc2r_step; 311 | let mut col_eg = c1.g + (i - y1) as f32 * dc2g_step; 312 | let mut col_eb = c1.b + (i - y1) as f32 * dc2b_step; 313 | let mut col_ea = c1.a + (i - y1) as f32 * dc2a_step; 314 | 315 | if ax > bx { 316 | std::mem::swap(&mut ax, &mut bx); 317 | std::mem::swap(&mut tex_su, &mut tex_eu); 318 | std::mem::swap(&mut tex_sv, &mut tex_ev); 319 | std::mem::swap(&mut tex_sw, &mut tex_ew); 320 | std::mem::swap(&mut col_sr, &mut col_er); 321 | std::mem::swap(&mut col_sg, &mut col_eg); 322 | std::mem::swap(&mut col_sb, &mut col_eb); 323 | std::mem::swap(&mut col_sa, &mut col_ea); 324 | } 325 | 326 | let tstep = 1.0 / (bx - ax) as f32; 327 | let mut t = 0.0; 328 | 329 | for j in ax..bx { 330 | tex_u = (1.0 - t) * tex_su + t * tex_eu; 331 | tex_v = (1.0 - t) * tex_sv + t * tex_ev; 332 | tex_w = (1.0 - t) * tex_sw + t * tex_ew; 333 | _col_r = (1.0 - t) * col_sr + t * col_er; 334 | _col_g = (1.0 - t) * col_sg + t * col_eg; 335 | _col_b = (1.0 - t) * col_sb + t * col_eb; 336 | _col_a = (1.0 - t) * col_sa + t * col_ea; 337 | // TODO - use interpolated color!!!! 338 | if tex_w > self.depth_buffer[(i * pge.screen_width + j) as usize] { 339 | pge.draw(j, i, &tex.sample(tex_u / tex_w, tex_v / tex_w)); 340 | self.depth_buffer[(i * pge.screen_width + j) as usize] = tex_w; 341 | } 342 | t += tstep; 343 | } 344 | 345 | } 346 | } 347 | 348 | dy1 = y3 - y2; 349 | dx1 = x3 - x2; 350 | dv1 = v3 - v2; 351 | du1 = u3 - u2; 352 | dw1 = w3 - w2; 353 | _dc1 = c3 - c2; 354 | 355 | if dy1 > 0 { dax_step = dx1 as f32 / dy1.abs() as f32; } 356 | if dy2 > 0 { dbx_step = dx2 as f32 / dy2.abs() as f32; } 357 | 358 | du1_step = 0.0; 359 | dv1_step = 0.0; 360 | if dy1 > 0 { 361 | du1_step = du1 / dy1.abs() as f32; 362 | dv1_step = dv1 / dy1.abs() as f32; 363 | dw1_step = dw1 / dy1.abs() as f32; 364 | } 365 | 366 | if dy1 > 0 { 367 | for i in y2..y3 { 368 | let mut ax = x2 + ((i - y2) as f32 * dax_step) as i32; 369 | let mut bx = x1 + ((i - y1) as f32 * dbx_step) as i32; 370 | 371 | let mut tex_su = u2 + (i - y2) as f32 * du1_step; 372 | let mut tex_sv = v2 + (i - y2) as f32 * dv1_step; 373 | let mut tex_sw = w2 + (i - y2) as f32 * dw1_step; 374 | let mut col_sr = c2.r + (i - y2) as f32 * dc1r_step; 375 | let mut col_sg = c2.g + (i - y2) as f32 * dc1g_step; 376 | let mut col_sb = c2.b + (i - y2) as f32 * dc1b_step; 377 | let mut col_sa = c2.a + (i - y2) as f32 * dc1a_step; 378 | 379 | let mut tex_eu = u1 + (i - y1) as f32 * du2_step; 380 | let mut tex_ev = v1 + (i - y1) as f32 * dv2_step; 381 | let mut tex_ew = w1 + (i - y1) as f32 * dw2_step; 382 | let mut col_er = c1.r + (i - y1) as f32 * dc2r_step; 383 | let mut col_eg = c1.g + (i - y1) as f32 * dc2g_step; 384 | let mut col_eb = c1.b + (i - y1) as f32 * dc2b_step; 385 | let mut col_ea = c1.a + (i - y1) as f32 * dc2a_step; 386 | 387 | if ax > bx { 388 | std::mem::swap(&mut ax, &mut bx); 389 | std::mem::swap(&mut tex_su, &mut tex_eu); 390 | std::mem::swap(&mut tex_sv, &mut tex_ev); 391 | std::mem::swap(&mut tex_sw, &mut tex_ew); 392 | std::mem::swap(&mut col_sr, &mut col_er); 393 | std::mem::swap(&mut col_sg, &mut col_eg); 394 | std::mem::swap(&mut col_sb, &mut col_eb); 395 | std::mem::swap(&mut col_sa, &mut col_ea); 396 | } 397 | 398 | let tstep = 1.0 / (bx - ax) as f32; 399 | let mut t = 0.0; 400 | 401 | for j in ax..bx { 402 | tex_u = (1.0 - t) * tex_su + t * tex_eu; 403 | tex_v = (1.0 - t) * tex_sv + t * tex_ev; 404 | tex_w = (1.0 - t) * tex_sw + t * tex_ew; 405 | _col_r = (1.0 - t) * col_sr + t * col_er; 406 | _col_g = (1.0 - t) * col_sg + t * col_eg; 407 | _col_b = (1.0 - t) * col_sb + t * col_eb; 408 | _col_a = (1.0 - t) * col_sa + t * col_ea; 409 | // TODO - use interpolated color!!!! 410 | 411 | if tex_w > self.depth_buffer[(i * pge.screen_width + j) as usize] { 412 | pge.draw(j, i, &tex.sample(tex_u / tex_w, tex_v / tex_w)); 413 | self.depth_buffer[(i * pge.screen_width + j) as usize] = tex_w; 414 | } 415 | t += tstep; 416 | } 417 | } 418 | } 419 | } 420 | 421 | pub fn render_triangle_texture(&mut self, pge: &mut PGE, a: &Vec4d, b: &Vec4d, c: &Vec4d, 422 | auv: &Vec3d, buv: &Vec3d, cuv: &Vec3d, 423 | tex: &Sprite) { 424 | // algorithm only fills counter clockwise triangles, so swap as needed 425 | // For a triangle A B C, you can find the winding by computing the cross product (B - A) x (C - A). For 2d tri's, with z=0, it will only have a z component. 426 | // To give all the same winding, swap vertices C and B if this z component is negative. 427 | let cross = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y); 428 | let v0 = a; 429 | let mut v1 = b; 430 | let mut v2 = c; 431 | let v0uv = auv; 432 | let mut v1uv = buv; 433 | let mut v2uv = cuv; 434 | if cross > 0.0 { 435 | std::mem::swap(&mut v1, &mut v2); 436 | std::mem::swap(&mut v1uv, &mut v2uv); 437 | } 438 | 439 | // use fixed-point only for X and Y. Avoid work for Z and W. 440 | let fx_pt_x0 = (v0.x + 0.5) as i32; 441 | let fx_pt_x1 = (v1.x + 0.5) as i32; 442 | let fx_pt_x2 = (v2.x + 0.5) as i32; 443 | let fx_pt_y0 = (v0.y + 0.5) as i32; 444 | let fx_pt_y1 = (v1.y + 0.5) as i32; 445 | let fx_pt_y2 = (v2.y + 0.5) as i32; 446 | let z0 = v0.z; 447 | let mut z1 = v1.z; 448 | let mut z2 = v2.z; 449 | 450 | // texture space 451 | let t0u = v0uv.x; 452 | let mut t1u = v1uv.x; 453 | let mut t2u = v2uv.x; 454 | 455 | let t0v = v0uv.y; 456 | let mut t1v = v1uv.y; 457 | let mut t2v = v2uv.y; 458 | 459 | let t0z = v0uv.z; 460 | let mut t1z = v1uv.z; 461 | let mut t2z = v2uv.z; 462 | 463 | // Fab(x, y) = Ax + By + C = 0 464 | // Fab(x, y) = (ya - yb)x + (xb - xa)y + (xa * yb - xb * ya) = 0 465 | // Compute A = (ya - yb) for the 3 line segments that make up each triangle 466 | let a0 = fx_pt_y1 - fx_pt_y2; 467 | let a1 = fx_pt_y2 - fx_pt_y0; 468 | let a2 = fx_pt_y0 - fx_pt_y1; 469 | 470 | // Compute B = (xb - xa) for the 3 line segments that make up each triangle 471 | let b0 = fx_pt_x2 - fx_pt_x1; 472 | let b1 = fx_pt_x0 - fx_pt_x2; 473 | let b2 = fx_pt_x1 - fx_pt_x0; 474 | 475 | // Compute C = (xa * yb - xb * ya) for the 3 line segments that make up each triangle 476 | let c0 = fx_pt_x1 * fx_pt_y2 - fx_pt_x2 * fx_pt_y1; 477 | let c1 = fx_pt_x2 * fx_pt_y0 - fx_pt_x0 * fx_pt_y2; 478 | let c2 = fx_pt_x0 * fx_pt_y1 - fx_pt_x1 * fx_pt_y0; 479 | 480 | // Determine edges 481 | let is_top_left = |v0: &Vec4d, v1: &Vec4d| -> bool { 482 | v0.y > v1.y 483 | }; 484 | 485 | // We follow fill rules and add a bias 486 | let bias0 = if is_top_left(v1, v2) { 0 } else { -1 }; 487 | let bias1 = if is_top_left(v2, v0) { 0 } else { -1 }; 488 | let bias2 = if is_top_left(v0, v1) { 0 } else { -1 }; 489 | 490 | // Compute triangle area 491 | let tri_area = (fx_pt_x1 - fx_pt_x0) * (fx_pt_y2 - fx_pt_y0) - (fx_pt_x0 - fx_pt_x2) * (fx_pt_y0 - fx_pt_y1); 492 | let one_over_tri_area = 1.0 / tri_area as f32; 493 | 494 | z1 = (z1 - z0) * one_over_tri_area; 495 | z2 = (z2 - z0) * one_over_tri_area; 496 | 497 | t1u = (t1u - t0u) * one_over_tri_area; 498 | t2u = (t2u - t0u) * one_over_tri_area; 499 | 500 | t1v = (t1v - t0v) * one_over_tri_area; 501 | t2v = (t2v - t0v) * one_over_tri_area; 502 | 503 | t1z = (t1z - t0z) * one_over_tri_area; 504 | t2z = (t2z - t0z) * one_over_tri_area; 505 | 506 | // Use bounding box traversal strategy to determine which pixels to rasterize 507 | let start_x = max(min(min(fx_pt_x0, fx_pt_x1), fx_pt_x2), 0);// & 0xFFFFFFFE; 508 | let end_x = min(max(max(fx_pt_x0, fx_pt_x1), fx_pt_x2), pge.screen_width); 509 | 510 | let start_y = max(min(min(fx_pt_y0, fx_pt_y1), fx_pt_y2), 0);// & 0xFFFFFFFE; 511 | let end_y = min(max(max(fx_pt_y0, fx_pt_y1), fx_pt_y2), pge.screen_height); 512 | 513 | let mut row_idx = start_y * pge.screen_width + start_x; 514 | let col = start_x; 515 | let mut row = start_y; 516 | 517 | // Incrementally compute Fab(x, y) for all the pixels inside the bounding box formed by (start_x, end_x) and (start_y, end_y) 518 | let mut alpha0 = (a0 * col) + (b0 * row) + c0 + bias0; 519 | let mut beta0 = (a1 * col) + (b1 * row) + c1 + bias1; 520 | let mut gama0 = (a2 * col) + (b2 * row) + c2 + bias2; 521 | 522 | let zx = a1 as f32 * z1 + a2 as f32 * z2; 523 | 524 | let tux = a1 as f32 * t1u + a2 as f32 * t2u; 525 | let tvx = a1 as f32 * t1v + a2 as f32 * t2v; 526 | let tz = a1 as f32 * t1z + a2 as f32 * t2z; 527 | 528 | for _ in start_y..end_y { 529 | // Compute barycentric coordinates 530 | let mut index = row_idx; 531 | let mut alpha = alpha0; 532 | let mut beta = beta0; 533 | let mut gama = gama0; 534 | 535 | let mut depth = z0 + z1 * beta as f32 + z2 * gama as f32; 536 | 537 | let mut u = t0u + t1u * beta as f32 + t2u * gama as f32; 538 | let mut v = t0v + t1v * beta as f32 + t2v * gama as f32; 539 | let mut uv_z = t0z + t1z * beta as f32 + t2z * gama as f32; 540 | 541 | for _ in start_x..end_x { 542 | //Test Pixel inside triangle 543 | let mask = alpha | beta | gama; 544 | 545 | let previous_depth_value = self.depth_buffer[index as usize]; 546 | let merged_depth = depth.max(previous_depth_value); 547 | let finaldepth = if mask < 0 { previous_depth_value } else { merged_depth }; 548 | 549 | self.depth_buffer[index as usize] = finaldepth; 550 | 551 | if mask > 0 && previous_depth_value < finaldepth { 552 | let one_over_uv_z = 1.0 / uv_z; 553 | let sample = tex.sample(u * one_over_uv_z, v * one_over_uv_z); 554 | pge.draw_target[pge.current_draw_target].data[index as usize] = sample; 555 | } 556 | 557 | // inc per pixel 558 | index += 1; 559 | alpha += a0; 560 | beta += a1; 561 | gama += a2; 562 | depth += zx; 563 | u += tux; 564 | v += tvx; 565 | uv_z += tz; 566 | } 567 | 568 | // inc per row 569 | row += 1; 570 | row_idx += pge.screen_width; 571 | alpha0 += b0; 572 | beta0 += b1; 573 | gama0 += b2; 574 | } 575 | } 576 | 577 | pub fn textured_triangle(&mut self, pge: &mut PGE, mut x1: i32, mut y1: i32, mut u1: f32, mut v1: f32, mut w1: f32, 578 | mut x2: i32, mut y2: i32, mut u2: f32, mut v2: f32, mut w2: f32, 579 | mut x3: i32, mut y3: i32, mut u3: f32, mut v3: f32, mut w3: f32, tex: &Sprite) { 580 | if y2 < y1 581 | { 582 | std::mem::swap(&mut y1, &mut y2); 583 | std::mem::swap(&mut x1, &mut x2); 584 | std::mem::swap(&mut u1, &mut u2); 585 | std::mem::swap(&mut v1, &mut v2); 586 | std::mem::swap(&mut w1, &mut w2); 587 | } 588 | if y3 < y1 589 | { 590 | std::mem::swap(&mut y1, &mut y3); 591 | std::mem::swap(&mut x1, &mut x3); 592 | std::mem::swap(&mut u1, &mut u3); 593 | std::mem::swap(&mut v1, &mut v3); 594 | std::mem::swap(&mut w1, &mut w3); 595 | } 596 | if y3 < y2 597 | { 598 | std::mem::swap(&mut y2, &mut y3); 599 | std::mem::swap(&mut x2, &mut x3); 600 | std::mem::swap(&mut u2, &mut u3); 601 | std::mem::swap(&mut v2, &mut v3); 602 | std::mem::swap(&mut w2, &mut w3); 603 | } 604 | 605 | let mut dy1 = y2 - y1; 606 | let mut dx1 = x2 - x1; 607 | let mut dv1 = v2 - v1; 608 | let mut du1 = u2 - u1; 609 | let mut dw1 = w2 - w1; 610 | 611 | let dy2 = y3 - y1; 612 | let dx2 = x3 - x1; 613 | let dv2 = v3 - v1; 614 | let du2 = u3 - u1; 615 | let dw2 = w3 - w1; 616 | 617 | let mut tex_u: f32; 618 | let mut tex_v: f32; 619 | let mut tex_w: f32; 620 | 621 | let mut dax_step = 0.0; 622 | let mut du1_step = 0.0; 623 | let mut du2_step = 0.0; 624 | let mut dw1_step = 0.0; 625 | let mut dbx_step = 0.0; 626 | let mut dv1_step = 0.0; 627 | let mut dv2_step = 0.0; 628 | let mut dw2_step = 0.0; 629 | 630 | if dy1 > 0 { 631 | dax_step = dx1 as f32 / dy1.abs() as f32; 632 | du1_step = du1 / dy1.abs() as f32; 633 | dv1_step = dv1 / dy1.abs() as f32; 634 | dw1_step = dw1 / dy1.abs() as f32; 635 | } 636 | 637 | if dy2 > 0 { 638 | du2_step = du2 / dy2.abs() as f32; 639 | dv2_step = dv2 / dy2.abs() as f32; 640 | dw2_step = dw2 / dy2.abs() as f32; 641 | dbx_step = dx2 as f32 / dy2.abs() as f32; 642 | } 643 | 644 | if dy1 > 0 { 645 | for i in y1..y2 { 646 | let mut ax = x1 + ((i - y1) as f32 * dax_step) as i32; 647 | let mut bx = x1 + ((i - y1) as f32 * dbx_step) as i32; 648 | 649 | let mut tex_su = u1 + (i - y1) as f32 * du1_step; 650 | let mut tex_sv = v1 + (i - y1) as f32 * dv1_step; 651 | let mut tex_sw = w1 + (i - y1) as f32 * dw1_step; 652 | 653 | let mut tex_eu = u1 + (i - y1) as f32 * du2_step; 654 | let mut tex_ev = v1 + (i - y1) as f32 * dv2_step; 655 | let mut tex_ew = w1 + (i - y1) as f32 * dw2_step; 656 | 657 | if ax > bx { 658 | std::mem::swap(&mut ax, &mut bx); 659 | std::mem::swap(&mut tex_su, &mut tex_eu); 660 | std::mem::swap(&mut tex_sv, &mut tex_ev); 661 | std::mem::swap(&mut tex_sw, &mut tex_ew); 662 | } 663 | 664 | let tstep = 1.0 / (bx - ax) as f32; 665 | let mut t = 0.0; 666 | 667 | for j in ax..bx { 668 | tex_u = (1.0 - t) * tex_su + t * tex_eu; 669 | tex_v = (1.0 - t) * tex_sv + t * tex_ev; 670 | tex_w = (1.0 - t) * tex_sw + t * tex_ew; 671 | let index = (i * pge.screen_width + j) as usize; 672 | if index < self.depth_buffer.len() { 673 | if tex_w > self.depth_buffer[index] { 674 | pge.draw(j, i, &tex.sample(tex_u / tex_w, tex_v / tex_w)); 675 | self.depth_buffer[index] = tex_w; 676 | } 677 | } 678 | t += tstep; 679 | } 680 | 681 | } 682 | } 683 | 684 | dy1 = y3 - y2; 685 | dx1 = x3 - x2; 686 | dv1 = v3 - v2; 687 | du1 = u3 - u2; 688 | dw1 = w3 - w2; 689 | 690 | if dy1 > 0 { dax_step = dx1 as f32 / dy1.abs() as f32; } 691 | if dy2 > 0 { dbx_step = dx2 as f32 / dy2.abs() as f32; } 692 | 693 | du1_step = 0.0; 694 | dv1_step = 0.0; 695 | if dy1 > 0 { 696 | du1_step = du1 / dy1.abs() as f32; 697 | dv1_step = dv1 / dy1.abs() as f32; 698 | dw1_step = dw1 / dy1.abs() as f32; 699 | } 700 | 701 | if dy1 > 0 { 702 | for i in y2..y3 { 703 | let mut ax = x2 + ((i - y2) as f32 * dax_step) as i32; 704 | let mut bx = x1 + ((i - y1) as f32 * dbx_step) as i32; 705 | 706 | let mut tex_su = u2 + (i - y2) as f32 * du1_step; 707 | let mut tex_sv = v2 + (i - y2) as f32 * dv1_step; 708 | let mut tex_sw = w2 + (i - y2) as f32 * dw1_step; 709 | 710 | let mut tex_eu = u1 + (i - y1) as f32 * du2_step; 711 | let mut tex_ev = v1 + (i - y1) as f32 * dv2_step; 712 | let mut tex_ew = w1 + (i - y1) as f32 * dw2_step; 713 | 714 | if ax > bx { 715 | std::mem::swap(&mut ax, &mut bx); 716 | std::mem::swap(&mut tex_su, &mut tex_eu); 717 | std::mem::swap(&mut tex_sv, &mut tex_ev); 718 | std::mem::swap(&mut tex_sw, &mut tex_ew); 719 | } 720 | 721 | let tstep = 1.0 / (bx - ax) as f32; 722 | let mut t = 0.0; 723 | 724 | for j in ax..bx { 725 | tex_u = (1.0 - t) * tex_su + t * tex_eu; 726 | tex_v = (1.0 - t) * tex_sv + t * tex_ev; 727 | tex_w = (1.0 - t) * tex_sw + t * tex_ew; 728 | 729 | let index = (i * pge.screen_width + j) as usize; 730 | if index < self.depth_buffer.len() { 731 | if tex_w > self.depth_buffer[index] { 732 | pge.draw(j, i, &tex.sample(tex_u / tex_w, tex_v / tex_w)); 733 | self.depth_buffer[index] = tex_w; 734 | } 735 | } 736 | t += tstep; 737 | } 738 | } 739 | } 740 | } 741 | 742 | pub fn clear_depth(&mut self) { 743 | // NOTE: sloooooooow!!!!! 744 | /* 745 | for y in 0..self.screen_height { 746 | for x in 0..self.screen_width { 747 | self.draw_target[self.current_draw_target].set_pixel(x as i32, y as i32, &p); 748 | } 749 | } 750 | */ 751 | // Much faster, but still might be slow? 752 | //self.draw_target[self.current_draw_target].clear(p.clone()); 753 | // Proper way, adds about 30 fps 754 | for i in self.depth_buffer.iter_mut() { *i = 0.0; } 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /src/gfx3d/plane.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::vec4d::Vec4d; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Plane { 6 | pub position: Vec4d, 7 | pub normal: Vec4d, 8 | } 9 | 10 | impl Plane { 11 | pub fn line_intersect_plane(&self, start: Vec4d, end: Vec4d) -> (Vec4d, f32) { 12 | let n = self.normal.norm(); 13 | let d = -n.dot(&self.position); 14 | let ad = start.dot(&n); 15 | let bd = end.dot(&n); 16 | let t = (-d - ad) / (bd - ad); 17 | let start_to_end = end - start; 18 | let to_intersect = start_to_end * t; 19 | (start + to_intersect, t) 20 | } 21 | } -------------------------------------------------------------------------------- /src/gfx3d/triangle.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use crate::{Pixel}; 4 | use super::vec3d::Vec3d; 5 | use super::vec4d::Vec4d; 6 | use super::plane::Plane; 7 | 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct Triangle { 10 | pub p: [Vec4d; 3], 11 | pub t: [Vec3d; 3], 12 | pub col: Pixel, 13 | } 14 | 15 | impl Triangle { 16 | 17 | pub fn new() -> Self { 18 | Triangle { p: [Vec4d::zero(); 3], t: [Vec3d::zero(); 3], col: Pixel::rgb(0, 0, 0) } 19 | } 20 | 21 | pub fn clip_against_plane(&self, plane: &Plane) -> Vec { 22 | let pn = plane.normal.norm(); 23 | 24 | let mut inside_points: [Vec4d; 3] = [Vec4d::zero(); 3]; 25 | let mut outside_points: [Vec4d; 3] = [Vec4d::zero(); 3]; 26 | let mut inside_tex: [Vec3d; 3] = [Vec3d::zero(); 3]; 27 | let mut outside_tex: [Vec3d; 3] = [Vec3d::zero(); 3]; 28 | 29 | let mut inside_count = 0; 30 | let mut outside_count = 0; 31 | 32 | let dist = |p: &Vec4d| -> f32 { 33 | pn.dot(p) - pn.dot(&plane.position) 34 | }; 35 | 36 | // test each point 37 | for (vertex, texture) in self.p.iter().zip(self.t.iter()) { 38 | // Get signed distance of each point in triangle to plane 39 | if dist(vertex) >= 0.0 { 40 | inside_points[inside_count] = *vertex; 41 | inside_tex[inside_count] = *texture; 42 | inside_count += 1; 43 | } else { 44 | outside_points[outside_count] = *vertex; 45 | outside_tex[outside_count] = *texture; 46 | outside_count += 1; 47 | } 48 | } 49 | 50 | // Now classify triangle points, and break the input triangle into 51 | // smaller output triangles if required. There are four possible 52 | // outcomes... 53 | 54 | match (inside_count, outside_count) { 55 | // zero inside - clip entire triangle 56 | (0, _) => Vec::::new(), 57 | // all inside - no clipping needed 58 | (3, _) => vec!(*self), 59 | // one inside, two outside - one smaller triangle 60 | (1, 2) => { 61 | let mut new_tri = Triangle::new(); 62 | // The inside point is valid, so keep that... 63 | new_tri.p[0] = inside_points[0]; 64 | new_tri.t[0] = inside_tex[0]; 65 | new_tri.col = self.col; 66 | 67 | let a = plane.line_intersect_plane(inside_points[0], outside_points[0]); 68 | let b = plane.line_intersect_plane(inside_points[0], outside_points[1]); 69 | 70 | new_tri.p[1] = a.0; 71 | new_tri.p[2] = b.0; 72 | 73 | new_tri.t[1] = inside_tex[0] + ((outside_tex[0] - inside_tex[0]) * a.1); 74 | new_tri.t[2] = inside_tex[0] + ((outside_tex[1] - inside_tex[0]) * b.1); 75 | 76 | vec!(new_tri) 77 | }, 78 | // two inside, one outside - clipped to a quad (2 triangles) 79 | (2, 1) => { 80 | let mut new_tri_a = Triangle::new(); 81 | let mut new_tri_b = Triangle::new(); 82 | 83 | // The first triangle consists of the two inside points and a new 84 | // point determined by the location where one side of the triangle 85 | // intersects with the plane 86 | new_tri_a.p[0] = inside_points[0]; 87 | new_tri_a.t[0] = inside_tex[0]; 88 | new_tri_a.col = self.col; 89 | 90 | new_tri_a.p[1] = inside_points[1]; 91 | new_tri_a.t[1] = inside_tex[1]; 92 | 93 | let a = plane.line_intersect_plane(inside_points[0], outside_points[0]); 94 | new_tri_a.p[2] = a.0; 95 | new_tri_a.t[2] = inside_tex[0] + ((outside_tex[0] - inside_tex[0]) * a.1); 96 | 97 | // The second triangle is composed of one of he inside points, a 98 | // new point determined by the intersection of the other side of the 99 | // triangle and the plane, and the newly created point above 100 | new_tri_b.p[0] = inside_points[1]; 101 | new_tri_b.t[0] = inside_tex[1]; 102 | new_tri_b.p[1] = new_tri_a.p[2]; 103 | new_tri_b.t[1] = new_tri_a.t[2]; 104 | new_tri_b.col = self.col; 105 | 106 | let b = plane.line_intersect_plane(inside_points[1], outside_points[0]); 107 | new_tri_b.p[2] = b.0; 108 | new_tri_b.t[2] = inside_tex[1] + ((outside_tex[0] - inside_tex[1]) * b.1); 109 | 110 | vec!(new_tri_a, new_tri_b) 111 | }, 112 | // not possible 113 | (_, _) => panic!(), 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/gfx3d/vec3d.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub, Mul, Div, AddAssign, SubAssign, MulAssign, DivAssign}; 2 | use super::super::gfx2d::vec2d::Vec2d; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Vec3d { 6 | pub x: f32, 7 | pub y: f32, 8 | pub z: f32 9 | } 10 | 11 | impl Vec3d { 12 | pub fn new(x: f32, y: f32, z: f32) -> Self { 13 | Vec3d { x, y, z } 14 | } 15 | 16 | pub fn zero() -> Self { 17 | Vec3d { x: 0.0, y: 0.0, z: 0.0 } 18 | } 19 | 20 | pub fn length(&self) -> f32 { 21 | (self.dot(self)).sqrt() 22 | } 23 | 24 | pub fn norm(&self) -> Self { 25 | let l = 1.0 / self.length(); 26 | Vec3d { x: self.x * l, y: self.y * l, z: self.z * l } 27 | } 28 | 29 | pub fn dot(&self, rhs: &Self) -> f32 { 30 | self.x * rhs.x + self.y * rhs.y + self.z * rhs.z 31 | } 32 | 33 | pub fn cross(&self, rhs: &Self) -> Vec3d { 34 | Vec3d { 35 | x: self.y * rhs.z - self.z * rhs.y, 36 | y: self.z * rhs.x - self.x * rhs.z, 37 | z: self.x * rhs.y - self.y * rhs.x 38 | } 39 | } 40 | 41 | pub fn as_vec2d(&self) -> Vec2d { 42 | Vec2d { x: self.x, y: self.y } 43 | } 44 | } 45 | 46 | impl Add for Vec3d { 47 | type Output = Vec3d; 48 | 49 | fn add(self, rhs: Vec3d) -> Vec3d { 50 | Vec3d { 51 | x: self.x + rhs.x, 52 | y: self.y + rhs.y, 53 | z: self.z + rhs.z 54 | } 55 | } 56 | } 57 | 58 | impl Sub for Vec3d { 59 | type Output = Vec3d; 60 | 61 | fn sub(self, rhs: Vec3d) -> Vec3d { 62 | Vec3d { 63 | x: self.x - rhs.x, 64 | y: self.y - rhs.y, 65 | z: self.z - rhs.z 66 | } 67 | } 68 | } 69 | 70 | impl Mul for Vec3d { 71 | type Output = Vec3d; 72 | 73 | fn mul(self, rhs: f32) -> Vec3d { 74 | Vec3d { 75 | x: self.x * rhs, 76 | y: self.y * rhs, 77 | z: self.z * rhs 78 | } 79 | } 80 | } 81 | 82 | impl Div for Vec3d { 83 | type Output = Vec3d; 84 | 85 | fn div(self, rhs: f32) -> Vec3d { 86 | Vec3d { 87 | x: self.x / rhs, 88 | y: self.y / rhs, 89 | z: self.z / rhs 90 | } 91 | } 92 | } 93 | 94 | impl AddAssign for Vec3d { 95 | fn add_assign(&mut self, rhs: Vec3d) { 96 | self.x = self.x + rhs.x; 97 | self.y = self.y + rhs.y; 98 | self.z = self.z + rhs.z; 99 | } 100 | } 101 | 102 | impl SubAssign for Vec3d { 103 | fn sub_assign(&mut self, rhs: Vec3d) { 104 | self.x = self.x - rhs.x; 105 | self.y = self.y - rhs.y; 106 | self.z = self.z - rhs.z; 107 | } 108 | } 109 | 110 | impl MulAssign for Vec3d { 111 | fn mul_assign(&mut self, rhs: f32) { 112 | self.x = self.x * rhs; 113 | self.y = self.y * rhs; 114 | self.z = self.z * rhs; 115 | } 116 | } 117 | 118 | impl DivAssign for Vec3d { 119 | fn div_assign(&mut self, rhs: f32) { 120 | self.x = self.x / rhs; 121 | self.y = self.y / rhs; 122 | self.z = self.z / rhs; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/gfx3d/vec4d.rs: -------------------------------------------------------------------------------- 1 | use crate::gfx3d::vec3d::Vec3d; 2 | use std::ops::{Add, Sub, Mul, Div, AddAssign, SubAssign, MulAssign, DivAssign}; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Vec4d { 6 | pub x: f32, 7 | pub y: f32, 8 | pub z: f32, 9 | pub w: f32, 10 | } 11 | 12 | impl Vec4d { 13 | pub fn new(x: f32, y: f32, z: f32) -> Self { 14 | Vec4d { x, y, z, w: 1.0 } 15 | } 16 | 17 | pub fn zero() -> Self { 18 | Vec4d { x: 0.0, y: 0.0, z: 0.0, w: 0.0 } 19 | } 20 | 21 | pub fn length(&self) -> f32 { 22 | (self.dot(self)).sqrt() 23 | } 24 | 25 | pub fn norm(&self) -> Self { 26 | let l = 1.0 / self.length(); 27 | Vec4d { x: self.x * l, y: self.y * l, z: self.z * l, w: 1.0 } 28 | } 29 | 30 | pub fn dot(&self, rhs: &Self) -> f32 { 31 | self.x * rhs.x + self.y * rhs.y + self.z * rhs.z 32 | } 33 | 34 | pub fn cross(&self, rhs: &Self) -> Vec4d { 35 | Vec4d { 36 | x: self.y * rhs.z - self.z * rhs.y, 37 | y: self.z * rhs.x - self.x * rhs.z, 38 | z: self.x * rhs.y - self.y * rhs.x, 39 | w: 1.0 40 | } 41 | } 42 | 43 | pub fn as_vec3d(&self) -> Vec3d { 44 | Vec3d { x: self.x, y: self.y, z: self.z } 45 | } 46 | } 47 | 48 | impl Add for Vec4d { 49 | type Output = Vec4d; 50 | 51 | fn add(self, rhs: Vec4d) -> Vec4d { 52 | Vec4d { 53 | x: self.x + rhs.x, 54 | y: self.y + rhs.y, 55 | z: self.z + rhs.z, 56 | w: 1.0, 57 | } 58 | } 59 | } 60 | 61 | impl Sub for Vec4d { 62 | type Output = Vec4d; 63 | 64 | fn sub(self, rhs: Vec4d) -> Vec4d { 65 | Vec4d { 66 | x: self.x - rhs.x, 67 | y: self.y - rhs.y, 68 | z: self.z - rhs.z, 69 | w: 1.0, 70 | } 71 | } 72 | } 73 | 74 | impl Mul for Vec4d { 75 | type Output = Vec4d; 76 | 77 | fn mul(self, rhs: f32) -> Vec4d { 78 | Vec4d { 79 | x: self.x * rhs, 80 | y: self.y * rhs, 81 | z: self.z * rhs, 82 | w: 1.0, 83 | } 84 | } 85 | } 86 | 87 | impl Div for Vec4d { 88 | type Output = Vec4d; 89 | 90 | fn div(self, rhs: f32) -> Vec4d { 91 | Vec4d { 92 | x: self.x / rhs, 93 | y: self.y / rhs, 94 | z: self.z / rhs, 95 | w: 1.0, 96 | } 97 | } 98 | } 99 | 100 | impl AddAssign for Vec4d { 101 | fn add_assign(&mut self, rhs: Vec4d) { 102 | self.x = self.x + rhs.x; 103 | self.y = self.y + rhs.y; 104 | self.z = self.z + rhs.z; 105 | } 106 | } 107 | 108 | impl SubAssign for Vec4d { 109 | fn sub_assign(&mut self, rhs: Vec4d) { 110 | self.x = self.x - rhs.x; 111 | self.y = self.y - rhs.y; 112 | self.z = self.z - rhs.z; 113 | } 114 | } 115 | 116 | impl MulAssign for Vec4d { 117 | fn mul_assign(&mut self, rhs: f32) { 118 | self.x = self.x * rhs; 119 | self.y = self.y * rhs; 120 | self.z = self.z * rhs; 121 | } 122 | } 123 | 124 | impl DivAssign for Vec4d { 125 | fn div_assign(&mut self, rhs: f32) { 126 | self.x = self.x / rhs; 127 | self.y = self.y / rhs; 128 | self.z = self.z / rhs; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/layer.rs: -------------------------------------------------------------------------------- 1 | use glam::Vec2; 2 | 3 | use crate::{decal::DecalInstance, *}; 4 | 5 | /* 6 | Each layer should be almost fully self contained 7 | */ 8 | 9 | pub struct Layer { 10 | pub offset: Vec2, 11 | pub scale: Vec2, 12 | pub show: bool, 13 | pub update: bool, 14 | pub surface: Renderable, 15 | pub decal_instances: Vec, 16 | pub tint: Color, 17 | pub id: usize, 18 | pub pipeline: Pipeline, 19 | pub bindings: Bindings, 20 | pub uniforms: [UniformData; 1], 21 | } 22 | 23 | pub struct UniformData { 24 | pub tint: Vec4, 25 | pub offset: Vec2, 26 | } 27 | 28 | impl Layer { 29 | pub fn new(pge: &mut PGE, width: u32, height: u32) -> Self { 30 | let cpu_bb = SpriteRef::new(width as u32, height as u32); 31 | let cpu_bb_len = cpu_bb.get_data_len(); 32 | 33 | let cpu_bb_tex = pge.ctx.new_texture_from_rgba8(width as u16, height as u16, unsafe { 34 | std::slice::from_raw_parts(cpu_bb.get_data_ptr(), cpu_bb_len * 4) 35 | }); 36 | 37 | pge.ctx.texture_set_filter(cpu_bb_tex, FilterMode::Nearest, MipmapFilterMode::None); 38 | let cpu_bb_weak_ref = Rc::downgrade(&cpu_bb.0); 39 | 40 | // screen space vertex's for 2 triangles 41 | let vertices: [Vertex; 4] = [ 42 | vert(vec2(-1.0, -1.0), vec2(0., 1.)), 43 | vert(vec2( 1.0, -1.0), vec2(1., 1.)), 44 | vert(vec2( 1.0, 1.0), vec2(1., 0.)), 45 | vert(vec2(-1.0, 1.0), vec2(0., 0.)), 46 | ]; 47 | 48 | let vertex_buffer = pge.ctx.new_buffer( 49 | BufferType::VertexBuffer, 50 | BufferUsage::Dynamic, 51 | BufferSource::slice(&vertices), 52 | ); 53 | 54 | let mut indices = Vec::with_capacity(u16::MAX as usize); 55 | for i in (0..u16::MAX).step_by(4) { 56 | indices.push(i + 0); 57 | indices.push(i + 1); 58 | indices.push(i + 2); 59 | indices.push(i + 0); 60 | indices.push(i + 2); 61 | indices.push(i + 3); 62 | } 63 | 64 | let index_buffer = pge.ctx.new_buffer( 65 | BufferType::IndexBuffer, 66 | BufferUsage::Immutable, 67 | BufferSource::slice(&indices), 68 | ); 69 | 70 | let bindings = Bindings { 71 | vertex_buffers: vec![vertex_buffer], 72 | index_buffer: index_buffer, 73 | images: vec![cpu_bb_tex], 74 | }; 75 | 76 | // NOTE: Below is how we update our vertex buffer. 77 | // This is why we set the BufferUsage to Dynamic 78 | // Index buffer is pre filled out 79 | // pge.ctx.buffer_update(bindings.vertex_buffers[0], data) 80 | 81 | // TODO: need to change this to reference a shader inside this file or included with 82 | // include! macro 83 | let shader = pge.ctx 84 | .new_shader( 85 | ShaderSource::Glsl { 86 | vertex: shader::GL_VERTEX, 87 | fragment: shader::GL_FRAGMENT, 88 | }, 89 | shader::meta(), 90 | ) 91 | .unwrap(); 92 | 93 | // TODO: add offset, scale, tint, into the shader as variables 94 | let params = PipelineParams { 95 | color_blend: Some(BlendState::new( 96 | Equation::Add, 97 | BlendFactor::Value(BlendValue::SourceAlpha), 98 | BlendFactor::OneMinusValue(BlendValue::SourceAlpha)) 99 | ), 100 | ..Default::default() 101 | }; 102 | 103 | let pipeline = pge.ctx.new_pipeline( 104 | &[BufferLayout::default()], 105 | &[ 106 | VertexAttribute::new("in_pos", VertexFormat::Float2), 107 | VertexAttribute::new("in_uv", VertexFormat::Float2), 108 | // TODO: add per triangle stuff here? 109 | 110 | ], 111 | shader, 112 | params 113 | ); 114 | 115 | Layer { 116 | offset: Vec2::ZERO, 117 | scale: Vec2::ONE, 118 | tint: WHITE, 119 | show: false, 120 | update: false, 121 | surface: Renderable { 122 | sprite: cpu_bb, 123 | decal: Decal { 124 | sprite: cpu_bb_weak_ref, 125 | texture_id: cpu_bb_tex, 126 | uv_scale: Vec2::ONE, 127 | width: width as u32, 128 | height: height as u32, 129 | }}, 130 | decal_instances: vec![], 131 | id: 1, // TODO: not used 132 | pipeline, 133 | bindings, 134 | // These are per layer and applied to everything on the layer 135 | uniforms: [UniformData { tint: vec4(1.,0.,0.,0.), offset: vec2(-0.1, -0.1) }], 136 | } 137 | } 138 | 139 | pub fn render(&mut self, ctx: &mut Box) { 140 | // always update the back buffer image (it's always index 0) 141 | ctx.texture_update(self.bindings.images[0], unsafe { 142 | let len = self.surface.sprite.get_data_len(); 143 | std::slice::from_raw_parts(self.surface.sprite.get_data_ptr(), len * 4) 144 | }); 145 | 146 | ctx.begin_default_pass(Default::default()); 147 | ctx.apply_pipeline(&self.pipeline); 148 | ctx.apply_bindings(&self.bindings); 149 | //ctx.apply_uniforms_from_bytes(self.uniforms.as_ptr() as *const u8, 1); 150 | //ctx.apply_uniforms(UniformsSource::table(&shader::U)); 151 | ctx.draw(0, 6, 1); 152 | ctx.end_render_pass(); 153 | 154 | ctx.commit_frame(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::{max, min}, rc::Rc}; 2 | 3 | pub use layer::{Layer, UniformData}; 4 | use miniquad::*; 5 | use glam::*; 6 | pub use sprite::*; 7 | pub use decal::*; 8 | 9 | mod layer; 10 | mod sprite; 11 | mod decal; 12 | 13 | #[allow(unused_variables)] 14 | pub trait GameLoop { 15 | type GameType; 16 | 17 | fn init(pge: &mut PGE) -> Self::GameType where Self: Sized; 18 | fn update(&mut self, pge: &mut PGE, dt: f64) {} 19 | fn fixed_update(&mut self, pge: &mut PGE, dt: f64) {} 20 | } 21 | 22 | #[repr(C)] 23 | #[derive(Debug, Clone, Copy)] 24 | pub struct Pixel { 25 | pub r: u8, 26 | pub g: u8, 27 | pub b: u8, 28 | pub a: u8, 29 | } 30 | 31 | type Color = Pixel; 32 | 33 | impl Pixel { 34 | pub fn rgb(r: u8, g: u8, b: u8) -> Self { 35 | Pixel{r, g, b, a:255} 36 | } 37 | 38 | pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self { 39 | Pixel{r, g, b, a} 40 | } 41 | 42 | // HACK? 43 | pub fn from_rgba_to_bgra(&mut self) { 44 | std::mem::swap(&mut self.b, &mut self.r); 45 | } 46 | } 47 | 48 | #[inline(always)] 49 | #[must_use] 50 | pub const fn color(r: u8, g: u8, b: u8, a: u8) -> Color { 51 | Color { r, g, b, a } 52 | } 53 | 54 | // Colors 55 | pub const WHITE: Color = color(255, 255, 255, 255); 56 | pub const GREY: Color = color(192, 192, 192, 255); 57 | pub const DARK_GREY: Color = color(128, 128, 128, 255); 58 | pub const VERY_DARK_GREY: Color = color(64, 64, 64, 255); 59 | pub const RED: Color = color(255, 0, 0, 255); 60 | pub const DARK_RED: Color = color(128, 0, 0, 255); 61 | pub const VERY_DARK_RED: Color = color(64, 0, 0, 255); 62 | pub const YELLOW: Color = color(255, 255, 0, 255); 63 | pub const DARK_YELLOW: Color = color(128, 128, 0, 255); 64 | pub const VERY_DARK_YELLOW: Color = color(64, 64, 0, 255); 65 | pub const GREEN: Color = color(0, 255, 0, 255); 66 | pub const DARK_GREEN: Color = color(0, 128, 0, 255); 67 | pub const VERY_DARK_GREEN: Color = color(0, 64, 0, 255); 68 | pub const CYAN: Color = color(0, 255, 255, 255); 69 | pub const DARK_CYAN: Color = color(0, 128, 128, 255); 70 | pub const VERY_DARK_CYAN: Color = color(0, 64, 64, 255); 71 | pub const BLUE: Color = color(0, 0, 255, 255); 72 | pub const DARK_BLUE: Color = color(0, 0, 128, 255); 73 | pub const VERY_DARK_BLUE: Color = color(0, 0, 64, 255); 74 | pub const MAGENTA: Color = color(255, 0, 255, 255); 75 | pub const DARK_MAGENTA: Color = color(128, 0, 128, 255); 76 | pub const VERY_DARK_MAGENTA: Color = color(64, 0, 64, 255); 77 | pub const BLACK: Color = color(0, 0, 0, 255); 78 | pub const BLANK: Color = color(0, 0, 0, 0); 79 | 80 | pub enum PixelMode { 81 | Normal, Mask, Alpha, Custom 82 | } 83 | 84 | #[derive(Debug)] 85 | pub struct Renderable { 86 | sprite: SpriteRef, 87 | decal: Decal, 88 | } 89 | 90 | pub struct PGE { 91 | pub screen_width: usize, 92 | pub screen_height: usize, 93 | pub pixel_mode: PixelMode, 94 | pub blend_factor: f32, 95 | pub func_pixel_mode: Option, 96 | pub font: Sprite, 97 | 98 | /// Engine internal stuff 99 | pub layers: Vec, 100 | pub current_layer: usize, 101 | //keyboard_map: HashMap<> TODO: 102 | pixel_width: i32, 103 | pixel_height: i32, 104 | ctx: Box, 105 | inv_screen_size: Vec2, 106 | 107 | // timing stuff 108 | accumulator: f64, 109 | current_time: f64, 110 | pub dt: f64, 111 | pub time: f64, 112 | pub frames: usize, 113 | pub fixed_frames: usize, 114 | 115 | // input stuff 116 | pub mouse_pos: IVec2, 117 | } 118 | 119 | impl PGE { 120 | pub fn construct(app_name: &str, width: usize, height: usize, pix_width: usize, pix_height: usize) 121 | where GT: GameLoop + 'static 122 | { 123 | let mut conf = conf::Conf::default(); 124 | 125 | conf.window_title = app_name.to_owned(); 126 | conf.window_width = (width * pix_width) as i32; 127 | conf.window_height = (height * pix_height) as i32; 128 | conf.window_resizable = false; 129 | 130 | miniquad::start(conf, move || { 131 | Box::new(App:: { 132 | game: None, 133 | pge: PGE::new(width, height, pix_width, pix_height), 134 | }) 135 | }); 136 | } 137 | 138 | fn new(width: usize, height: usize, pix_width: usize, pix_height: usize) -> Self { 139 | let mut ctx = window::new_rendering_backend(); 140 | 141 | let back_buffer = Sprite::new(width as u32, height as u32); 142 | 143 | //#[rustfmt::skip] 144 | let vertices: [Vertex; 4] = [ 145 | vert(vec2(-1.0, -1.0), vec2(0., 1.)), 146 | vert(vec2( 1.0, -1.0), vec2(1., 1.)), 147 | vert(vec2( 1.0, 1.0), vec2(1., 0.)), 148 | vert(vec2(-1.0, 1.0), vec2(0., 0.)), 149 | ]; 150 | let vertex_buffer = ctx.new_buffer( 151 | BufferType::VertexBuffer, 152 | BufferUsage::Immutable, 153 | BufferSource::slice(&vertices), 154 | ); 155 | 156 | let indices: [u16; 6] = [0, 1, 2, 0, 2, 3]; 157 | let index_buffer = ctx.new_buffer( 158 | BufferType::IndexBuffer, 159 | BufferUsage::Immutable, 160 | BufferSource::slice(&indices), 161 | ); 162 | 163 | let bb_sprite_ref = SpriteRef::new_from_sprite(back_buffer); 164 | let len = bb_sprite_ref.get_data_len(); 165 | 166 | let bb_texture = ctx.new_texture_from_rgba8(width as u16, height as u16, unsafe { 167 | std::slice::from_raw_parts(bb_sprite_ref.get_data_ptr(), len * 4) 168 | }); 169 | 170 | ctx.texture_set_filter(bb_texture, FilterMode::Nearest, MipmapFilterMode::None); 171 | 172 | let bindings = Bindings { 173 | vertex_buffers: vec![vertex_buffer], 174 | index_buffer: index_buffer, 175 | images: vec![bb_texture], 176 | }; 177 | 178 | let shader = ctx 179 | .new_shader( 180 | ShaderSource::Glsl { 181 | vertex: shader::GL_VERTEX, 182 | fragment: shader::GL_FRAGMENT, 183 | }, 184 | shader::meta(), 185 | ) 186 | .unwrap(); 187 | 188 | let params = PipelineParams { 189 | color_blend: Some(BlendState::new( 190 | Equation::Add, 191 | BlendFactor::Value(BlendValue::SourceAlpha), 192 | BlendFactor::OneMinusValue(BlendValue::SourceAlpha)) 193 | ), 194 | ..Default::default() 195 | }; 196 | 197 | let pipeline = ctx.new_pipeline( 198 | &[BufferLayout::default()], 199 | &[ 200 | VertexAttribute::new("in_pos", VertexFormat::Float2), 201 | VertexAttribute::new("in_uv", VertexFormat::Float2), 202 | ], 203 | shader, 204 | params 205 | ); 206 | 207 | let bb_sprite_ref2 = Rc::downgrade(&bb_sprite_ref.0); 208 | 209 | 210 | PGE { 211 | screen_width: width, 212 | screen_height: height, 213 | pixel_width: pix_width as i32, 214 | pixel_height: pix_height as i32, 215 | pixel_mode: PixelMode::Normal, 216 | blend_factor: 1.0, 217 | func_pixel_mode: None, 218 | font: PGE::construct_font_sheet(), 219 | // TODO: first layer is created inline as it currently requires pge to create one 220 | layers: vec![ 221 | Layer { 222 | offset: Vec2::ZERO, 223 | scale: Vec2::ONE, 224 | show: true, 225 | update: true, 226 | pipeline, 227 | bindings, 228 | uniforms: [UniformData { tint: vec4(1.,1.,1.,0.5), offset: vec2(0.1,0.1) }], 229 | surface: Renderable { 230 | sprite: bb_sprite_ref, 231 | decal: Decal { 232 | sprite: bb_sprite_ref2, 233 | texture_id: bb_texture, 234 | uv_scale: Vec2::ONE, 235 | width: width as u32, 236 | height: height as u32, 237 | }}, 238 | decal_instances: vec![], 239 | tint: BLANK, 240 | id: 0 } 241 | ], 242 | current_layer: 0, 243 | ctx, 244 | accumulator: 0.0, 245 | current_time: date::now(), 246 | dt: 1.0 / 60.0, 247 | time: 0.0, 248 | frames: 0, 249 | fixed_frames: 0, 250 | mouse_pos: IVec2::ZERO, 251 | inv_screen_size: vec2(1.0 / width as f32, 1.0 / height as f32) 252 | } 253 | } 254 | 255 | pub fn set_draw_target(&mut self, layer: usize, dirty: bool) { 256 | if layer < self.layers.len() { 257 | // we could set a draw target, but instead just keep track of the layer 258 | self.layers[layer].update = dirty; 259 | self.current_layer = layer; 260 | } 261 | } 262 | 263 | pub fn get_mouse_x(&mut self) -> i32 { 264 | self.mouse_pos.x 265 | } 266 | 267 | pub fn get_mouse_y(&mut self) -> i32 { 268 | self.mouse_pos.y 269 | } 270 | 271 | pub fn create_texture(&mut self, width: u32, height: u32) -> TextureId { 272 | let texture = self.ctx.new_texture( 273 | TextureAccess::Static, 274 | TextureSource::Empty, 275 | TextureParams { 276 | kind: TextureKind::Texture2D, 277 | format: TextureFormat::RGBA8, 278 | wrap: TextureWrap::Clamp, 279 | min_filter: FilterMode::Linear, 280 | mag_filter: FilterMode::Linear, 281 | mipmap_filter: MipmapFilterMode::Linear, 282 | width: width, 283 | height: height, 284 | allocate_mipmaps: false, 285 | sample_count: 1, 286 | }); 287 | texture 288 | } 289 | 290 | pub fn update_texture(&mut self, id: TextureId, sprite: &Sprite) { 291 | self.ctx.texture_update(id, unsafe { 292 | std::slice::from_raw_parts(sprite.pixel_data.as_ptr() as *const u8, sprite.pixel_data.len() * 4) 293 | }); 294 | } 295 | 296 | pub fn read_texture(&mut self, id: TextureId, sprite: &mut Sprite) { 297 | let (tw, th) = self.ctx.texture_size(id); 298 | if tw == sprite.width && th == sprite.height { 299 | let bytes: &mut [u8] = unsafe { 300 | std::slice::from_raw_parts_mut(sprite.pixel_data.as_ptr() as *mut u8, sprite.pixel_data.len() * 4) 301 | }; 302 | self.ctx.texture_read_pixels(id, bytes); 303 | } 304 | } 305 | 306 | pub fn delete_texture(&mut self, id: TextureId) { 307 | self.ctx.delete_texture(id); 308 | } 309 | 310 | // draws the given decal into the current layer 311 | pub fn draw_decal(&mut self, pos: Vec2, decal: &Decal, scale: Vec2, tint: &Color) { 312 | let screen_space_pos = vec2( 313 | (pos.x * self.inv_screen_size.x) * 2.0 - 1.0, 314 | (pos.x * self.inv_screen_size.x) * 2.0 - 1.0); 315 | let screen_space_dim = vec2( 316 | screen_space_pos.x + (2.0 * decal.width as f32 * self.inv_screen_size.x) * scale.x, 317 | screen_space_pos.y - (2.0 * decal.height as f32 * self.inv_screen_size.y) * scale.y, 318 | ); 319 | 320 | let di = DecalInstance { 321 | vertices: vec![ 322 | vert(screen_space_pos, vec2(0., 0.)), 323 | vert(vec2(screen_space_pos.x, screen_space_dim.y), vec2(0., 1.)), 324 | vert(screen_space_dim, vec2(1., 1.)), 325 | vert(vec2(screen_space_dim.x, screen_space_pos.y), vec2(1., 0.))], 326 | tint: *tint, 327 | mode: DecalMode::Normal, // TODO: get this from decal mode 328 | structure: DecalStructure::Strip // TODO: this will likely have to create a new vertex 329 | // & index buffer if changed 330 | }; 331 | 332 | self.layers[self.current_layer].decal_instances.push(di); 333 | } 334 | 335 | #[inline] 336 | pub fn draw(&mut self, x: i32, y: i32, p: &Pixel) { 337 | if self.current_layer < self.layers.len() { 338 | match self.pixel_mode { 339 | PixelMode::Normal => { 340 | self.layers[self.current_layer].surface.sprite.set_pixel(x, y, p); 341 | } 342 | PixelMode::Mask => { 343 | if p.a == 255 { 344 | self.layers[self.current_layer].surface.sprite.set_pixel(x, y, p); 345 | } 346 | }, 347 | PixelMode::Alpha => { 348 | let d = self.layers[self.current_layer].surface.sprite.get_pixel(x, y); 349 | let a = (p.a as f32 / 255.0) * self.blend_factor; 350 | let c = 1.0 - a; 351 | // cheat: use fused multiply add 352 | let r = a.mul_add(p.r as f32, c * d.r as f32); 353 | let g = a.mul_add(p.g as f32, c * d.g as f32); 354 | let b = a.mul_add(p.b as f32, c * d.b as f32); 355 | let a = a.mul_add(p.a as f32, c * d.a as f32); 356 | self.layers[self.current_layer].surface.sprite.set_pixel(x, y, &Pixel::rgba(r as u8, g as u8, b as u8, a as u8)); 357 | }, 358 | PixelMode::Custom => { 359 | if let Some(fpm) = self.func_pixel_mode { 360 | fpm(x, y, &self.layers[self.current_layer].surface.sprite.get_pixel(x, y), p); 361 | } 362 | } 363 | } 364 | } 365 | } 366 | 367 | //#[rustfmt::skip] 368 | pub fn draw_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, p: &Pixel) { 369 | let mut x = x1; 370 | let mut y = y1; 371 | let dx = i32::abs(x2 - x1); 372 | let dy = i32::abs(y2 - y1); 373 | let sx = if x1 < x2 { 1 } else { -1 }; 374 | let sy = if y1 < y2 { 1 } else { -1 }; 375 | let mut err = if dx > dy { dx / 2 } else { -dy / 2 }; 376 | 377 | loop { 378 | self.draw(x, y, p); 379 | if sx > 0 && sy > 0 { if x >= x2 && y >= y2 { break }}; 380 | if sx > 0 && sy < 0 { if x >= x2 && y <= y2 { break }}; 381 | if sx < 0 && sy > 0 { if x <= x2 && y >= y2 { break }}; 382 | if sx < 0 && sy < 0 { if x <= x2 && y <= y2 { break }}; 383 | if err > -dx {err = err - dy; x = x + sx; } 384 | if err < dy {err = err + dx; y = y + sy; } 385 | } 386 | } 387 | 388 | pub fn draw_circle(&mut self, x: i32, y: i32, radius: i32, p: &Pixel) { 389 | let mut x0 = 0; 390 | let mut y0 = radius; 391 | let mut d = 3 - 2 * radius; 392 | if radius <= 0 { return } 393 | 394 | while y0 >= x0 // only formulate 1/8 of circle 395 | { 396 | self.draw(x - x0, y - y0, p);//upper left left 397 | self.draw(x - y0, y - x0, p);//upper upper left 398 | self.draw(x + y0, y - x0, p);//upper upper right 399 | self.draw(x + x0, y - y0, p);//upper right right 400 | self.draw(x - x0, y + y0, p);//lower left left 401 | self.draw(x - y0, y + x0, p);//lower lower left 402 | self.draw(x + y0, y + x0, p);//lower lower right 403 | self.draw(x + x0, y + y0, p);//lower right right 404 | if d < 0 { d += 4 * x0 + 6; x0 += 1; } 405 | else { x0 += 1; y0 -= 1; d += 4 * (x0 - y0) + 10; } 406 | } 407 | } 408 | 409 | pub fn fill_circle(&mut self, x: i32, y: i32, radius: i32, p: &Pixel) { 410 | let mut x0 = 0; 411 | let mut y0 = radius; 412 | let mut d = 3 - 2 * radius; 413 | if radius <= 0 { return } 414 | 415 | let mut scanline_rendered: [bool; 2048] = [false; 2048]; 416 | 417 | while y0 >= x0 418 | { 419 | // Modified to draw scan-lines instead of edges 420 | if y-y0 > 0 && y-y0 < self.screen_height as i32 { 421 | if scanline_rendered[(y - y0) as usize] == false { 422 | self.draw_line(x - x0, y - y0, x + x0, y - y0, p); 423 | scanline_rendered[(y - y0) as usize] = true; 424 | } 425 | } 426 | if y-x0 > 0 && y-x0 < self.screen_height as i32 { 427 | if scanline_rendered[(y - x0) as usize] == false { 428 | self.draw_line(x - y0, y - x0, x + y0, y - x0, p); 429 | scanline_rendered[(y - x0) as usize] = true; 430 | } 431 | } 432 | if y+y0 > 0 && y+y0 < self.screen_height as i32 { 433 | if scanline_rendered[(y + y0) as usize] == false { 434 | self.draw_line(x - x0, y + y0, x + x0, y + y0, p); 435 | scanline_rendered[(y + y0) as usize] = true; 436 | } 437 | } 438 | if y+x0 > 0 && y+x0 < self.screen_height as i32 { 439 | if scanline_rendered[(y + x0) as usize] == false { 440 | self.draw_line(x - y0, y + x0, x + y0, y + x0, p); 441 | scanline_rendered[(y + x0) as usize] = true; 442 | } 443 | } 444 | 445 | if d < 0 { x0 += 1; d += 4 * x0 + 6; } 446 | else { x0 += 1; y0 -= 1; d += 4 * (x0 - y0) + 10; } 447 | } 448 | } 449 | 450 | pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, p: &Pixel) { 451 | self.draw_line(x, y, x+w, y, p); 452 | self.draw_line(x+w, y, x+w, y+h, p); 453 | self.draw_line(x+w, y+h, x, y+h, p); 454 | self.draw_line(x, y+h, x, y, p); 455 | } 456 | 457 | pub fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, p: &Pixel) { 458 | let mut x1 = x; 459 | let mut y1 = y; 460 | let mut x2 = x + w; 461 | let mut y2 = y + h; 462 | 463 | if x < 0 { x1 = 0; } 464 | if x >= self.screen_width as i32 { x1 = self.screen_width as i32; } 465 | if y < 0 { y1 = 0; } 466 | if y >= self.screen_height as i32 { y1 = self.screen_height as i32; } 467 | 468 | if x2 < 0 { x2 = 0; } 469 | if x2 >= self.screen_width as i32 { x2 = self.screen_width as i32; } 470 | if y2 < 0 { y2 = 0; } 471 | if y2 >= self.screen_height as i32 { y2 = self.screen_height as i32; } 472 | 473 | for j in y1..y2 { 474 | for i in x1..x2 { 475 | self.draw(i, j, p); 476 | } 477 | } 478 | } 479 | 480 | pub fn draw_triangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, p: &Pixel) { 481 | self.draw_line(x1, y1, x2, y2, p); 482 | self.draw_line(x2, y2, x3, y3, p); 483 | self.draw_line(x3, y3, x1, y1, p); 484 | } 485 | 486 | pub fn fill_triangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, col: &Pixel) { 487 | // we use tuples for this for now 488 | let v0 = (x1, y1); 489 | let mut v1 = (x2, y2); 490 | let mut v2 = (x3, y3); 491 | 492 | // algorithm only fills counter clockwise triangles, so swap as needed 493 | // For a triangle A B C, you can find the winding by computing the cross product (B - A) x (C - A). For 2d tri's, with z=0, it will only have a z component. 494 | // To give all the same winding, swap vertices C and B if this z component is negative. 495 | let cross = (v1.1 - v0.1) * (v2.0 - v1.0) - (v1.0 - v0.0) * (v2.1 - v1.1); 496 | if cross > 0 { std::mem::swap(&mut v1, &mut v2) } 497 | 498 | // Compute triangle bounding box and clip to screen bounds 499 | let min_x = max(min(min(v0.0, v1.0), v2.0), 0); 500 | let max_x = min(max(max(v0.0, v1.0), v2.0), self.screen_width as i32 - 1); 501 | let min_y = max(min(min(v0.1, v1.1), v2.1), 0); 502 | let max_y = min(max(max(v0.1, v1.1), v2.1), self.screen_height as i32 - 1); 503 | 504 | // Triangle setup 505 | let a01 = v0.1 - v1.1; 506 | let b01 = v1.0 - v0.0; 507 | let a12 = v1.1 - v2.1; 508 | let b12 = v2.0 - v1.0; 509 | let a20 = v2.1 - v0.1; 510 | let b20 = v0.0 - v2.0; 511 | 512 | // Determine edges 513 | let is_top_left = |v0: (i32, i32), v1: (i32, i32)| -> bool { 514 | v0.1 > v1.1 515 | }; 516 | 517 | // We follow fill rules and add a bias 518 | let bias0 = if is_top_left(v1, v2) { 0 } else { -1 }; 519 | let bias1 = if is_top_left(v2, v0) { 0 } else { -1 }; 520 | let bias2 = if is_top_left(v0, v1) { 0 } else { -1 }; 521 | 522 | // Determine barycentric coordinates 523 | let orient2d = |a: (i32,i32), b: (i32,i32), c: (i32,i32)| -> i32 { 524 | (b.0-a.0)*(c.1-a.1) - (b.1-a.1)*(c.0-a.0) 525 | }; 526 | 527 | let mut p = (min_x, min_y); 528 | let mut w0_row = orient2d(v1, v2, p) + bias0; 529 | let mut w1_row = orient2d(v2, v0, p) + bias1; 530 | let mut w2_row = orient2d(v0, v1, p) + bias2; 531 | 532 | // Rasterize 533 | for y in min_y..max_y { 534 | p.1 = y; 535 | // Barycentric coordinates at start of row 536 | let mut w0 = w0_row; 537 | let mut w1 = w1_row; 538 | let mut w2 = w2_row; 539 | 540 | for x in min_x..max_x { 541 | p.0 = x; 542 | // If p is on or inside all edges, render pixel. 543 | if (w0 | w1 | w2) >= 0 { 544 | self.draw(p.0, p.1, col); 545 | } 546 | 547 | // One step to the right 548 | w0 += a12; 549 | w1 += a20; 550 | w2 += a01; 551 | } 552 | // One row step 553 | w0_row += b12; 554 | w1_row += b20; 555 | w2_row += b01; 556 | } 557 | } 558 | 559 | pub fn draw_sprite(&mut self, x: i32, y: i32, sprite: &Sprite, scale: usize) { 560 | if scale > 1 { 561 | for j in 0..sprite.height as i32 { 562 | for i in 0..sprite.width as i32 { 563 | for is in 0..scale { 564 | for js in 0..scale { 565 | self.draw(x + (i * scale as i32) + is as i32, y + (j * scale as i32) + js as i32, &sprite.get_pixel(i, j)); 566 | } 567 | } 568 | } 569 | } 570 | } else { 571 | for j in 0..sprite.height as i32 { 572 | for i in 0..sprite.width as i32 { 573 | self.draw(x + i, y + j, &sprite.get_pixel(i, j)); 574 | } 575 | } 576 | } 577 | } 578 | 579 | pub fn draw_parital_sprite(&mut self, x: i32, y: i32, sprite: &Sprite, ox: i32, oy: i32, w: i32, h: i32, scale: usize) { 580 | if scale > 1 { 581 | for j in 0..h as i32 { 582 | for i in 0..w as i32 { 583 | for is in 0..scale { 584 | for js in 0..scale { 585 | self.draw(x + (i * scale as i32) + is as i32, y + (j * scale as i32) + js as i32, &sprite.get_pixel(ox + i, oy + j)); 586 | } 587 | } 588 | } 589 | } 590 | } else { 591 | for j in 0..h as i32 { 592 | for i in 0..w as i32 { 593 | self.draw(x + i, y + j, &sprite.get_pixel(ox + i, oy + j)); 594 | } 595 | } 596 | } 597 | } 598 | 599 | pub fn draw_string(&mut self, x: i32, y: i32, text: &str, col: &Pixel, scale: i32) { 600 | let mut sx: i32 = 0; 601 | let mut sy: i32 = 0; 602 | 603 | if col.a != 255 { self.pixel_mode = PixelMode::Alpha; } 604 | else { self.pixel_mode = PixelMode::Mask; } 605 | 606 | for c in text.chars() { 607 | if c == '\n' { 608 | sx = 0; 609 | sy += 8 * scale; 610 | } else { 611 | let mut ox: i32 = 15; 612 | let mut oy: i32 = 5; 613 | if c.is_ascii() { 614 | ox = ((c as u32 - 32) % 16) as i32; 615 | oy = ((c as u32 - 32) / 16) as i32; 616 | } 617 | if scale > 1 { 618 | for j in 0..8 as i32 { 619 | for i in 0..8 as i32 { 620 | if self.font.get_pixel(i + ox * 8, j + oy * 8).r > 0 { 621 | for js in 0..scale as i32 { 622 | for is in 0..scale as i32 { 623 | self.draw(x + sx + (i*scale) + is, y + sy + (j*scale) + js, &col); 624 | } 625 | } 626 | } 627 | } 628 | } 629 | } else { 630 | for j in 0..8 as i32 { 631 | for i in 0..8 as i32 { 632 | if self.font.get_pixel(i + ox * 8, j + oy * 8).r > 0 { 633 | self.draw(x + sx + i, y + sy + j, &col); 634 | } 635 | } 636 | } 637 | } 638 | sx += 8 * scale; 639 | } 640 | } 641 | } 642 | 643 | pub fn clear(&mut self, p: &Pixel) { 644 | self.layers[self.current_layer].surface.sprite.clear(*p); 645 | } 646 | 647 | fn construct_font_sheet() -> Sprite { 648 | let data = include_bytes!("../font.png"); 649 | let image = image::load_from_memory_with_format(data, image::ImageFormat::Png).unwrap(); 650 | let raw_image = image.as_bytes(); 651 | Sprite::new_with_data(128, 48, raw_image) 652 | } 653 | 654 | pub fn render(&mut self) { 655 | for layer in &mut self.layers { 656 | layer.render(&mut self.ctx); 657 | } 658 | } 659 | } 660 | 661 | pub struct App { 662 | pub pge: PGE, 663 | pub game: Option>>, 664 | } 665 | 666 | impl EventHandler for App where T: GameLoop + 'static { 667 | fn mouse_motion_event(&mut self, x: f32, y: f32) { 668 | // Mouse coords come in screen space 669 | // But leave in pixel space 670 | let x = x as i32; 671 | let y = y as i32; 672 | self.pge.mouse_pos.x = x / self.pge.pixel_width; 673 | self.pge.mouse_pos.y = y / self.pge.pixel_height; 674 | 675 | if self.pge.mouse_pos.x >= self.pge.screen_width as i32 { 676 | self.pge.mouse_pos.x = self.pge.screen_width as i32 - 1; 677 | } 678 | if self.pge.mouse_pos.y >= self.pge.screen_height as i32 { 679 | self.pge.mouse_pos.y = self.pge.screen_height as i32 - 1; 680 | } 681 | 682 | if self.pge.mouse_pos.x < 0 683 | { self.pge.mouse_pos.x = 0; } 684 | if self.pge.mouse_pos.y < 0 685 | { self.pge.mouse_pos.y = 0; } 686 | } 687 | 688 | fn update(&mut self) { 689 | if let Some(game) = &mut self.game { 690 | let new_time = date::now(); 691 | let frame_time = new_time - self.pge.current_time; 692 | let dt = self.pge.dt; 693 | self.pge.current_time = new_time; 694 | self.pge.accumulator += frame_time; 695 | 696 | // we always call update at max frame rate 697 | game.update(&mut self.pge, frame_time); 698 | self.pge.frames += 1; 699 | 700 | // fixed update is only called at a fixed rate 701 | while self.pge.accumulator >= frame_time { 702 | game.fixed_update(&mut self.pge, dt); 703 | self.pge.accumulator -= dt; 704 | self.pge.time += dt; 705 | self.pge.fixed_frames += 1; 706 | } 707 | } else { 708 | self.game = Some(Box::new(T::init(&mut self.pge))) 709 | } 710 | } 711 | 712 | fn draw(&mut self) { 713 | self.pge.render(); 714 | } 715 | } 716 | 717 | #[inline(always)] 718 | #[must_use] 719 | pub const fn vert(pos: Vec2, uv: Vec2) -> Vertex { 720 | Vertex { pos, uv } 721 | } 722 | 723 | #[repr(C)] 724 | #[derive(Debug, Clone)] 725 | pub struct Vertex { 726 | pub pos: Vec2, 727 | pub uv: Vec2, 728 | } 729 | 730 | mod shader { 731 | use miniquad::*; 732 | 733 | pub const GL_VERTEX: &str = r#"#version 100 734 | attribute vec2 in_pos; 735 | attribute vec2 in_uv; 736 | 737 | varying lowp vec2 texcoord; 738 | uniform lowp vec2 offset; 739 | 740 | void main() { 741 | gl_Position = vec4(in_pos + offset, 0, 1); 742 | texcoord = in_uv; 743 | }"#; 744 | 745 | pub const GL_FRAGMENT: &str = r#"#version 100 746 | varying lowp vec2 texcoord; 747 | 748 | uniform sampler2D tex; 749 | //uniform lowp vec4 tint; 750 | 751 | void main() { 752 | lowp vec4 color = texture2D(tex, texcoord); 753 | gl_FragColor = vec4(color.xyz, color.w); 754 | }"#; 755 | 756 | pub fn meta() -> ShaderMeta { 757 | ShaderMeta { 758 | images: vec!["tex".to_string()], 759 | uniforms: UniformBlockLayout { uniforms: vec![ 760 | UniformDesc { 761 | name: "tint".to_string(), 762 | uniform_type: UniformType::Float4, 763 | array_count: 1 764 | }, 765 | UniformDesc { 766 | name: "offset".to_string(), 767 | uniform_type: UniformType::Float2, 768 | array_count: 1 769 | } 770 | ] }, 771 | } 772 | } 773 | } -------------------------------------------------------------------------------- /src/sprite.rs: -------------------------------------------------------------------------------- 1 | use std::{rc::Rc, cell::{RefCell, Ref}}; 2 | use crate::*; 3 | 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum Mode { 7 | Normal, 8 | Periodic, 9 | Clamp, 10 | } 11 | 12 | #[derive(Debug)] 13 | pub enum Flip { 14 | None, 15 | Horizontal, 16 | Vertical, 17 | } 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct SpriteRef(pub Rc>); 21 | 22 | impl SpriteRef { 23 | pub fn new(width: u32, height: u32) -> SpriteRef { 24 | SpriteRef(Rc::new(RefCell::new(Sprite::new(width, height)))) 25 | } 26 | 27 | pub fn new_with_data(width: u32, height: u32, data: &[u8]) -> SpriteRef { 28 | SpriteRef(Rc::new(RefCell::new(Sprite::new_with_data(width, height, data)))) 29 | } 30 | 31 | // consumes sprite! 32 | pub fn new_from_sprite(sprite: Sprite) -> SpriteRef { 33 | SpriteRef(Rc::new(RefCell::new(sprite))) 34 | } 35 | 36 | #[inline] 37 | pub fn get_sprite<'a>(&'a self) -> Ref<'a, Sprite> { 38 | self.0.borrow() 39 | } 40 | 41 | #[inline] 42 | pub fn get_pixel(&self, x: i32, y: i32) -> Pixel { 43 | let sprite = self.0.borrow(); 44 | if x >= 0 && x < sprite.width as i32 && y >= 0 && y < sprite.height as i32 { 45 | sprite.pixel_data[(y * sprite.width as i32 + x) as usize].clone() 46 | } else { 47 | Pixel::rgba(0,0,0,0) 48 | } 49 | } 50 | 51 | #[inline] 52 | pub fn set_pixel(&mut self, x: i32, y: i32, p: &Pixel) { 53 | let width = self.0.borrow().width; 54 | let height = self.0.borrow().height; 55 | if x >= 0 && x < width as i32 && y >= 0 && y < height as i32 { 56 | if let Ok(sprite) = &mut self.0.try_borrow_mut() { 57 | sprite.pixel_data[(y * width as i32 + x) as usize] = p.clone(); 58 | } 59 | } 60 | } 61 | 62 | pub fn clear(&mut self, p: Pixel) { 63 | let mut sprite = self.0.borrow_mut(); 64 | sprite.clear(p); 65 | } 66 | 67 | pub unsafe fn get_data_ptr(&self) -> *const u8 { 68 | self.0.borrow().pixel_data.as_ptr() as *const u8 69 | } 70 | 71 | pub fn width(&self) -> u32 { 72 | self.0.borrow().width 73 | } 74 | 75 | pub fn height(&self) -> u32 { 76 | self.0.borrow().height 77 | } 78 | 79 | pub fn get_data_len(&self) -> usize { 80 | self.0.borrow().pixel_data.len() 81 | } 82 | 83 | pub fn clone(&self) -> Rc> { 84 | self.0.clone() 85 | } 86 | } 87 | 88 | #[derive(Debug, Clone)] 89 | pub struct Sprite { 90 | pub width: u32, 91 | pub height: u32, 92 | pub sample_mode: Mode, 93 | pub pixel_data: Vec, 94 | } 95 | 96 | impl Sprite { 97 | pub fn new(width: u32, height: u32) -> Sprite { 98 | Sprite { 99 | width, 100 | height, 101 | sample_mode: Mode::Normal, 102 | pixel_data: vec![BLANK; (width * height) as usize], 103 | } 104 | } 105 | 106 | pub fn new_with_data(width: u32, height: u32, data: &[u8]) -> Sprite { 107 | unsafe { 108 | Sprite { 109 | width, 110 | height, 111 | sample_mode: Mode::Normal, 112 | pixel_data: std::slice::from_raw_parts(data.as_ptr() as *const Pixel, data.len() / 4).to_vec(), 113 | } 114 | } 115 | } 116 | 117 | pub fn from_rgba_to_bgra(&mut self) { 118 | for x in 0..self.width { 119 | for y in 0..self.height { 120 | self.pixel_data[(y * self.width + x) as usize].from_rgba_to_bgra(); 121 | } 122 | } 123 | } 124 | 125 | #[inline] 126 | pub fn get_pixel(&self, x: i32, y: i32) -> Pixel { 127 | if x >= 0 && x < self.width as i32 && y >= 0 && y < self.height as i32 { 128 | self.pixel_data[(y * self.width as i32 + x) as usize].clone() 129 | } else { 130 | Pixel::rgba(0,0,0,0) 131 | } 132 | } 133 | 134 | #[inline] 135 | pub fn set_pixel(&mut self, x: i32, y: i32, p: &Pixel) { 136 | if x >= 0 && x < self.width as i32 && y >= 0 && y < self.height as i32 { 137 | self.pixel_data[(y * self.width as i32 + x) as usize] = p.clone(); 138 | } 139 | } 140 | 141 | #[inline] 142 | pub fn sample(&self, x: f32, y: f32) -> Pixel { 143 | let sx = (x * self.width as f32) as i32; 144 | let sy = (y * self.height as f32) as i32; 145 | self.get_pixel(sx, sy) 146 | } 147 | 148 | #[inline] 149 | pub fn sample_bl(&self, mut u: f32, mut v: f32) -> Pixel { 150 | u = u * self.width as f32 - 0.5; 151 | v = v * self.height as f32 - 0.5; 152 | let x = u.floor() as i32; // cast to int rounds toward zero, not downward 153 | let y = v.floor() as i32; // Thanks @joshinils 154 | let u_ratio = u - x as f32; 155 | let v_ratio = v - y as f32; 156 | let u_opposite = 1.0 - u_ratio; 157 | let v_opposite = 1.0 - v_ratio; 158 | 159 | let p1 = self.get_pixel(x.max(0), y.max(0)); 160 | let p2 = self.get_pixel((x+1).min(self.width as i32 - 1), y.max(0)); 161 | let p3 = self.get_pixel(x.max(0), (y+1).min(self.height as i32 - 1)); 162 | let p4 = self.get_pixel((x+1).min(self.width as i32 - 1), (y+1).min(self.height as i32 - 1)); 163 | 164 | Pixel::rgb(((p1.r as f32 * u_opposite + p2.r as f32 * u_ratio) * v_opposite + (p3.r as f32 * u_opposite + p4.r as f32 * u_ratio) * v_ratio) as u8, 165 | ((p1.g as f32 * u_opposite + p2.g as f32 * u_ratio) * v_opposite + (p3.g as f32 * u_opposite + p4.g as f32 * u_ratio) * v_ratio) as u8, 166 | ((p1.b as f32 * u_opposite + p2.b as f32 * u_ratio) * v_opposite + (p3.b as f32 * u_opposite + p4.b as f32 * u_ratio) * v_ratio) as u8) 167 | } 168 | 169 | pub fn get_data(&self) -> &[Pixel] { 170 | self.pixel_data.as_slice() 171 | } 172 | 173 | pub fn clear(&mut self, p: Pixel) { 174 | for i in 0..self.pixel_data.len() { 175 | self.pixel_data[i] = p; 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | //! Functions and types relating to measuring and manipulating time. 2 | 3 | use std::time::Duration; 4 | 5 | //use crate::Context; 6 | 7 | /// Converts a `std::time::Duration` to an `f64`. This is less accurate, but 8 | /// usually more useful. 9 | pub fn duration_to_f64(duration: Duration) -> f64 { 10 | let seconds = duration.as_secs() as f64; 11 | let nanos = f64::from(duration.subsec_nanos()) * 1e-9; 12 | seconds + nanos 13 | } 14 | 15 | /// Converts an `f64` to a `std::time::Duration`. 16 | pub fn f64_to_duration(duration: f64) -> Duration { 17 | debug_assert!(duration >= 0.0); 18 | let seconds = duration.trunc() as u64; 19 | let nanos = (duration.fract() * 1e9) as u32; 20 | Duration::new(seconds, nanos) 21 | } 22 | /* 23 | /// Gets the update tick rate of the application, in ticks per second. 24 | pub fn get_tick_rate(ctx: &Context) -> f64 { 25 | 1.0 / duration_to_f64(ctx.tick_rate) 26 | } 27 | 28 | /// Sets the update tick rate of the application, in ticks per second. 29 | pub fn set_tick_rate(ctx: &mut Context, tick_rate: f64) { 30 | ctx.tick_rate = f64_to_duration(1.0 / tick_rate); 31 | }*/ -------------------------------------------------------------------------------- /unwrap_helper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattbettcher/rustyPixelGameEngine/3972dc96322d6bfd9685bb7d0edb81244fc9b647/unwrap_helper.png --------------------------------------------------------------------------------