first open gl base
This commit is contained in:
commit
0cf7d5eb43
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
1835
Cargo.lock
generated
Normal file
1835
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "proj"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
gl = "0.14.0"
|
||||||
|
glutin = "0.32.3"
|
||||||
|
glutin-winit = "0.5.0"
|
||||||
|
raw-window-handle = "0.6.2"
|
||||||
|
winit = "0.30.12"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
gl_generator = "0.14"
|
||||||
|
cfg_aliases = "0.2.1"
|
||||||
114
assets/models/small_house/materials.mtl
Normal file
114
assets/models/small_house/materials.mtl
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
newmtl mat0
|
||||||
|
Ka 0.73 0.41 0.78
|
||||||
|
Kd 0.50 0.14 0.58
|
||||||
|
|
||||||
|
newmtl mat1
|
||||||
|
Ka 0.61 0.15 0.69
|
||||||
|
Kd 0.34 0.02 0.44
|
||||||
|
|
||||||
|
newmtl mat2
|
||||||
|
Ka 0.40 0.23 0.72
|
||||||
|
Kd 0.13 0.04 0.49
|
||||||
|
|
||||||
|
newmtl mat3
|
||||||
|
Ka 0.50 0.87 0.92
|
||||||
|
Kd 0.22 0.74 0.83
|
||||||
|
|
||||||
|
newmtl mat4
|
||||||
|
Ka 0.00 0.74 0.83
|
||||||
|
Kd 0.00 0.52 0.66
|
||||||
|
|
||||||
|
newmtl mat5
|
||||||
|
Ka 0.01 0.61 0.90
|
||||||
|
Kd 0.00 0.34 0.79
|
||||||
|
|
||||||
|
newmtl mat6
|
||||||
|
Ka 0.97 0.73 0.82
|
||||||
|
Kd 0.94 0.50 0.65
|
||||||
|
|
||||||
|
newmtl mat7
|
||||||
|
Ka 0.94 0.38 0.57
|
||||||
|
Kd 0.87 0.12 0.29
|
||||||
|
|
||||||
|
newmtl mat8
|
||||||
|
Ka 0.96 0.26 0.21
|
||||||
|
Kd 0.91 0.05 0.03
|
||||||
|
|
||||||
|
newmtl mat9
|
||||||
|
Ka 0.55 0.76 0.29
|
||||||
|
Kd 0.27 0.55 0.07
|
||||||
|
|
||||||
|
newmtl mat10
|
||||||
|
Ka 0.30 0.69 0.31
|
||||||
|
Kd 0.07 0.44 0.08
|
||||||
|
|
||||||
|
newmtl mat11
|
||||||
|
Ka 0.00 0.59 0.53
|
||||||
|
Kd 0.00 0.31 0.25
|
||||||
|
|
||||||
|
newmtl mat12
|
||||||
|
Ka 1.00 0.92 0.23
|
||||||
|
Kd 1.00 0.83 0.04
|
||||||
|
|
||||||
|
newmtl mat13
|
||||||
|
Ka 1.00 0.60 0.00
|
||||||
|
Kd 1.00 0.33 0.00
|
||||||
|
|
||||||
|
newmtl mat14
|
||||||
|
Ka 1.00 0.34 0.13
|
||||||
|
Kd 1.00 0.09 0.01
|
||||||
|
|
||||||
|
newmtl mat15
|
||||||
|
Ka 0.81 0.85 0.86
|
||||||
|
Kd 0.63 0.70 0.72
|
||||||
|
|
||||||
|
newmtl mat16
|
||||||
|
Ka 0.47 0.56 0.61
|
||||||
|
Kd 0.19 0.28 0.34
|
||||||
|
|
||||||
|
newmtl mat17
|
||||||
|
Ka 0.27 0.35 0.39
|
||||||
|
Kd 0.06 0.10 0.13
|
||||||
|
|
||||||
|
newmtl mat18
|
||||||
|
Ka 1.00 0.80 0.53
|
||||||
|
Kd 1.00 0.61 0.25
|
||||||
|
|
||||||
|
newmtl mat19
|
||||||
|
Ka 0.87 0.60 0.27
|
||||||
|
Kd 0.74 0.33 0.06
|
||||||
|
|
||||||
|
newmtl mat20
|
||||||
|
Ka 0.47 0.33 0.28
|
||||||
|
Kd 0.19 0.09 0.06
|
||||||
|
|
||||||
|
newmtl mat21
|
||||||
|
Ka 1.00 1.00 1.00
|
||||||
|
Kd 1.00 1.00 1.00
|
||||||
|
|
||||||
|
newmtl mat22
|
||||||
|
Ka 0.62 0.62 0.62
|
||||||
|
Kd 0.35 0.35 0.35
|
||||||
|
|
||||||
|
newmtl mat23
|
||||||
|
Ka 0.10 0.10 0.10
|
||||||
|
Kd 0.01 0.01 0.01
|
||||||
|
|
||||||
|
newmtl mat24
|
||||||
|
Ka 0.58 0.65 1.00
|
||||||
|
Kd 0.83 0.89 0.87
|
||||||
|
Ks 1 1 1
|
||||||
|
illum 4
|
||||||
|
Ns 300
|
||||||
|
d 0.4
|
||||||
|
Ni 1.5
|
||||||
|
|
||||||
|
newmtl mat25
|
||||||
|
Ka 1.00 0.65 0.67
|
||||||
|
Kd 0.83 0.89 0.87
|
||||||
|
Ks 1 1 1
|
||||||
|
illum 4
|
||||||
|
Ns 300
|
||||||
|
d 0.4
|
||||||
|
Ni 1.5
|
||||||
|
|
||||||
6238
assets/models/small_house/model.obj
Normal file
6238
assets/models/small_house/model.obj
Normal file
File diff suppressed because it is too large
Load Diff
3
assets/models/triangle/model.obj
Normal file
3
assets/models/triangle/model.obj
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
v -0.5 -0.5 1.0
|
||||||
|
v 0.0 0.5 1.0
|
||||||
|
v 0.5 -0.5 1.0
|
||||||
41
build.rs
Normal file
41
build.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use cfg_aliases::cfg_aliases;
|
||||||
|
use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// XXX this is taken from glutin/build.rs.
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
|
// Setup alias to reduce `cfg` boilerplate.
|
||||||
|
cfg_aliases! {
|
||||||
|
// Systems.
|
||||||
|
android_platform: { target_os = "android" },
|
||||||
|
wasm_platform: { target_family = "wasm" },
|
||||||
|
macos_platform: { target_os = "macos" },
|
||||||
|
ios_platform: { target_os = "ios" },
|
||||||
|
apple: { any(ios_platform, macos_platform) },
|
||||||
|
free_unix: { all(unix, not(apple), not(android_platform)) },
|
||||||
|
|
||||||
|
// Native displays.
|
||||||
|
x11_platform: { all(feature = "x11", free_unix, not(wasm_platform)) },
|
||||||
|
wayland_platform: { all(feature = "wayland", free_unix, not(wasm_platform)) },
|
||||||
|
|
||||||
|
// Backends.
|
||||||
|
egl_backend: { all(feature = "egl", any(windows, unix), not(apple), not(wasm_platform)) },
|
||||||
|
glx_backend: { all(feature = "glx", x11_platform, not(wasm_platform)) },
|
||||||
|
wgl_backend: { all(feature = "wgl", windows, not(wasm_platform)) },
|
||||||
|
cgl_backend: { all(macos_platform, not(wasm_platform)) },
|
||||||
|
}
|
||||||
|
|
||||||
|
let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
|
||||||
|
let mut file = File::create(dest.join("gl_bindings.rs")).unwrap();
|
||||||
|
Registry::new(Api::Gles2, (3, 0), Profile::Core, Fallbacks::All, [])
|
||||||
|
.write_bindings(StructGenerator, &mut file)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
196
src/app/app.rs
Normal file
196
src/app/app.rs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
use std::num::NonZero;
|
||||||
|
use glutin::config::{ ConfigTemplateBuilder, GetGlConfig };
|
||||||
|
use glutin::context::PossiblyCurrentContext;
|
||||||
|
use glutin::display::GetGlDisplay;
|
||||||
|
use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext};
|
||||||
|
use glutin::surface::{GlSurface, Surface, SwapInterval, WindowSurface};
|
||||||
|
use glutin_winit::{DisplayBuilder, GlWindow};
|
||||||
|
use winit::application::ApplicationHandler;
|
||||||
|
use winit::event::KeyEvent;
|
||||||
|
use winit::keyboard::{Key, NamedKey};
|
||||||
|
use winit::window::{ Window, WindowAttributes };
|
||||||
|
use winit::{event, window};
|
||||||
|
|
||||||
|
use crate::app::gl::{gl_config_picker, gl_create_context};
|
||||||
|
use crate::app::renderer::Renderer;
|
||||||
|
use crate::models::ModelData;
|
||||||
|
|
||||||
|
enum GlDisplayCreationState {
|
||||||
|
Builder(Box<DisplayBuilder>),
|
||||||
|
Init
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
gl_surface: Surface<WindowSurface>,
|
||||||
|
window: Window,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
gl_display: GlDisplayCreationState,
|
||||||
|
gl_context: Option<PossiblyCurrentContext>,
|
||||||
|
template: ConfigTemplateBuilder,
|
||||||
|
|
||||||
|
renderer: Option<Renderer>,
|
||||||
|
current_scene: Option<String>,
|
||||||
|
scene_data: ModelData,
|
||||||
|
|
||||||
|
state: Option<AppState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(scene_data: ModelData) -> Self {
|
||||||
|
let template = ConfigTemplateBuilder::new()
|
||||||
|
.with_alpha_size(8)
|
||||||
|
.with_transparency(cfg!(target_os = "macos"));
|
||||||
|
|
||||||
|
let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes()));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
template,
|
||||||
|
gl_display: GlDisplayCreationState::Builder(Box::new(display_builder)),
|
||||||
|
gl_context: None,
|
||||||
|
renderer: None,
|
||||||
|
state: None,
|
||||||
|
scene_data,
|
||||||
|
current_scene: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_current_scene(&mut self, current_scene: &str) {
|
||||||
|
self.current_scene = Some(current_scene.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn window_attributes() -> WindowAttributes {
|
||||||
|
window::Window::default_attributes()
|
||||||
|
.with_transparent(false)
|
||||||
|
.with_title("Test Window")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationHandler for App {
|
||||||
|
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||||
|
let (window, gl_config) = match &self.gl_display {
|
||||||
|
GlDisplayCreationState::Builder(builder) => {
|
||||||
|
let (window, gl_config) = match builder.clone().build(
|
||||||
|
event_loop,
|
||||||
|
self.template.clone(),
|
||||||
|
gl_config_picker
|
||||||
|
) {
|
||||||
|
Ok((window, gl_config)) => (window.unwrap(), gl_config),
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Encountered Error {:#?}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.gl_display = GlDisplayCreationState::Init;
|
||||||
|
self.gl_context = Some(gl_create_context(&window, &gl_config).treat_as_possibly_current());
|
||||||
|
|
||||||
|
(window, gl_config)
|
||||||
|
},
|
||||||
|
GlDisplayCreationState::Init => {
|
||||||
|
let gl_config = self.gl_context.as_ref().unwrap().config();
|
||||||
|
match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) {
|
||||||
|
Ok(window) => (window, gl_config),
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Todo: Handle error for {:#?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let attrs = window
|
||||||
|
.build_surface_attributes(Default::default())
|
||||||
|
.expect("Failed to build surface attributes");
|
||||||
|
|
||||||
|
let gl_surface = unsafe {
|
||||||
|
gl_config.display().create_window_surface(&gl_config, &attrs).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let gl_context = self.gl_context.as_ref().unwrap();
|
||||||
|
gl_context.make_current(&gl_surface).unwrap();
|
||||||
|
|
||||||
|
if self.renderer.is_none() {
|
||||||
|
let mut renderer = Renderer::new(&gl_context.display());
|
||||||
|
|
||||||
|
renderer.upload_scene_data(&self.scene_data);
|
||||||
|
|
||||||
|
println!("Built Renderer");
|
||||||
|
|
||||||
|
self.renderer.get_or_insert_with(|| renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(res) = gl_surface.set_swap_interval(
|
||||||
|
gl_context,
|
||||||
|
SwapInterval::Wait(NonZero::new(1).unwrap())
|
||||||
|
) {
|
||||||
|
eprintln!("Error setting vsync: {:?}", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(self.state.replace(AppState { gl_surface, window }).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||||
|
println!("Android window removed");
|
||||||
|
self.state = None;
|
||||||
|
self.gl_context = Some(
|
||||||
|
self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(
|
||||||
|
&mut self,
|
||||||
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||||
|
_window_id: window::WindowId,
|
||||||
|
event: event::WindowEvent,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
event::WindowEvent::Resized(size) if size.width != 0 && size.height != 0 => {
|
||||||
|
if let Some(AppState { gl_surface, window: _ }) = &self.state {
|
||||||
|
let gl_context = self.gl_context.as_ref().unwrap();
|
||||||
|
|
||||||
|
gl_surface.resize(
|
||||||
|
gl_context,
|
||||||
|
NonZero::new(size.width).unwrap(),
|
||||||
|
NonZero::new(size.height).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let renderer = self.renderer.as_ref().unwrap();
|
||||||
|
renderer.resize(size.width as i32, size.height as i32);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event::WindowEvent::CloseRequested
|
||||||
|
| event::WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, .. } => {
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||||
|
let _gl_display = self.gl_context.take().unwrap().display();
|
||||||
|
|
||||||
|
self.state = None;
|
||||||
|
|
||||||
|
#[cfg(egl_backend)]
|
||||||
|
#[allow(irrefutable_let_patterns)]
|
||||||
|
if let glutin::display::Display::Egl(display) = _gl_display {
|
||||||
|
unsafe {
|
||||||
|
display.terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||||
|
if let Some(AppState { gl_surface, window }) = self.state.as_ref() {
|
||||||
|
let gl_context = self.gl_context.as_ref().unwrap();
|
||||||
|
let renderer = self.renderer.as_ref().unwrap();
|
||||||
|
|
||||||
|
if let Some(current_scene) = &self.current_scene {
|
||||||
|
renderer.render(current_scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.request_redraw();
|
||||||
|
gl_surface.swap_buffers(gl_context).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/app/gl.rs
Normal file
53
src/app/gl.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#![allow(clippy::all)]
|
||||||
|
#![allow(unsafe_op_in_unsafe_fn)]
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
|
||||||
|
|
||||||
|
pub use Gles2 as Gl;
|
||||||
|
|
||||||
|
use glutin::config::{Config, GlConfig};
|
||||||
|
use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentContext, Version};
|
||||||
|
use glutin::display::GetGlDisplay;
|
||||||
|
use glutin::prelude::GlDisplay;
|
||||||
|
use raw_window_handle::HasWindowHandle;
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
pub fn gl_config_picker(configs: Box<dyn Iterator<Item = Config> + '_>) -> Config {
|
||||||
|
configs
|
||||||
|
.reduce(|accum, config| {
|
||||||
|
let transparency_check = config.supports_transparency().unwrap_or(false)
|
||||||
|
& !accum.supports_transparency().unwrap_or(false);
|
||||||
|
|
||||||
|
if transparency_check || config.num_samples() > accum.num_samples() {
|
||||||
|
config
|
||||||
|
} else {
|
||||||
|
accum
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gl_create_context(window: &Window, gl_config: &Config) -> NotCurrentContext {
|
||||||
|
let raw_window_handle = window.window_handle().ok().map(|wh| wh.as_raw());
|
||||||
|
let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle);
|
||||||
|
|
||||||
|
let fallback_context_attributes = ContextAttributesBuilder::new()
|
||||||
|
.with_context_api(ContextApi::Gles(None))
|
||||||
|
.build(raw_window_handle);
|
||||||
|
|
||||||
|
let legacy_context_attributes = ContextAttributesBuilder::new()
|
||||||
|
.with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1))))
|
||||||
|
.build(raw_window_handle);
|
||||||
|
|
||||||
|
let gl_display = gl_config.display();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl_display.create_context(gl_config, &context_attributes).unwrap_or_else(|_| {
|
||||||
|
gl_display.create_context(gl_config, &fallback_context_attributes).unwrap_or_else(|_| {
|
||||||
|
gl_display
|
||||||
|
.create_context(gl_config, &legacy_context_attributes)
|
||||||
|
.expect("Failed to create context")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
208
src/app/renderer.rs
Normal file
208
src/app/renderer.rs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
use glutin::prelude::GlDisplay;
|
||||||
|
use crate::app::gl;
|
||||||
|
use crate::models::{self, Model, ModelData, RenderObject, Scene, ShaderSource};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::ffi::{ CStr, CString };
|
||||||
|
|
||||||
|
fn get_gl_string(gl: &gl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr>{
|
||||||
|
unsafe {
|
||||||
|
let s = gl.GetString(variant);
|
||||||
|
(!s.is_null()).then(|| CStr::from_ptr(s.cast()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Renderer {
|
||||||
|
gl: gl::Gl,
|
||||||
|
shaders: HashMap<String, gl::types::GLuint>,
|
||||||
|
scenes: HashMap<String, Scene>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer {
|
||||||
|
pub fn new<D: GlDisplay>(gl_display: &D) -> Self {
|
||||||
|
let gl = gl::Gl::load_with(|symbol| {
|
||||||
|
let symbol = CString::new(symbol).unwrap();
|
||||||
|
gl_display.get_proc_address(symbol.as_c_str()).cast()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) {
|
||||||
|
println!("Running on {}", renderer.to_string_lossy());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(version) = get_gl_string(&gl, gl::VERSION) {
|
||||||
|
println!("OpenGL version {}", version.to_string_lossy());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) {
|
||||||
|
println!("Shaders version on {}", shaders_version.to_string_lossy());
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
gl,
|
||||||
|
shaders: HashMap::new(),
|
||||||
|
scenes: HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload_scene_data(&mut self, scene_data: &ModelData) {
|
||||||
|
let objects = scene_data.objects_data
|
||||||
|
.iter()
|
||||||
|
.map(|(mesh, shader_src)| {
|
||||||
|
let shader = self.create_shader(shader_src);
|
||||||
|
let render_object = self.upload_mesh(mesh);
|
||||||
|
(render_object, shader)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(RenderObject, u32)>>();
|
||||||
|
println!("Uploading Scene {}", scene_data.name);
|
||||||
|
self.scenes.insert(scene_data.name.clone(), Model { objects });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload_mesh(&self, mesh: &models::Mesh) -> RenderObject {
|
||||||
|
unsafe {
|
||||||
|
let gl = &self.gl;
|
||||||
|
|
||||||
|
let mut vao = std::mem::zeroed();
|
||||||
|
gl.GenVertexArrays(1, &mut vao);
|
||||||
|
gl.BindVertexArray(vao);
|
||||||
|
|
||||||
|
let mut vbo = std::mem::zeroed();
|
||||||
|
gl.GenBuffers(1, &mut vbo);
|
||||||
|
gl.BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||||
|
let vertex_data = mesh.vertices
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
[v.x, v.y, v.z, 1.0, 0.1, 0.1]
|
||||||
|
})
|
||||||
|
.collect::<Vec<[f32; 6]>>();
|
||||||
|
|
||||||
|
gl.BufferData(
|
||||||
|
gl::ARRAY_BUFFER,
|
||||||
|
(vertex_data.len() * std::mem::size_of::<[f32; 6]>()) as gl::types::GLsizeiptr,
|
||||||
|
vertex_data.as_ptr() as *const _,
|
||||||
|
gl::STATIC_DRAW,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pos_attrib = 0;
|
||||||
|
let color_attrib = 1;
|
||||||
|
|
||||||
|
gl.VertexAttribPointer(
|
||||||
|
pos_attrib as gl::types::GLuint,
|
||||||
|
3,
|
||||||
|
gl::FLOAT,
|
||||||
|
0,
|
||||||
|
6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
|
||||||
|
std::ptr::null()
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.VertexAttribPointer(
|
||||||
|
color_attrib as gl::types::GLuint,
|
||||||
|
3,
|
||||||
|
gl::FLOAT,
|
||||||
|
0,
|
||||||
|
6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
|
||||||
|
(3 * std::mem::size_of::<f32>()) as *const _,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.EnableVertexAttribArray(pos_attrib as gl::types::GLuint);
|
||||||
|
gl.EnableVertexAttribArray(color_attrib as gl::types::GLuint);
|
||||||
|
|
||||||
|
RenderObject {
|
||||||
|
vao,
|
||||||
|
vbo,
|
||||||
|
_ebo: None,
|
||||||
|
vertex_count: mesh.vertices.len() as i32,
|
||||||
|
_index_count: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_shader(gl: &gl::Gl, shader: gl::types::GLenum, source: &[u8]) -> gl::types::GLuint {
|
||||||
|
unsafe {
|
||||||
|
let shader = gl.CreateShader(shader);
|
||||||
|
gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null());
|
||||||
|
gl.CompileShader(shader);
|
||||||
|
shader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_shader(&self, shader_src: &ShaderSource) -> gl::types::GLuint {
|
||||||
|
unsafe {
|
||||||
|
let vertex_shader = Renderer::compile_shader(&self.gl, gl::VERTEX_SHADER, shader_src.vertex_src);
|
||||||
|
let fragment_shader = Renderer::compile_shader(&self.gl, gl::FRAGMENT_SHADER, shader_src.fragment_src);
|
||||||
|
|
||||||
|
let program = self.gl.CreateProgram();
|
||||||
|
|
||||||
|
self.gl.AttachShader(program, vertex_shader);
|
||||||
|
self.gl.AttachShader(program, fragment_shader);
|
||||||
|
|
||||||
|
self.gl.LinkProgram(program);
|
||||||
|
|
||||||
|
self.gl.DeleteShader(vertex_shader);
|
||||||
|
self.gl.DeleteShader(fragment_shader);
|
||||||
|
|
||||||
|
program
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_shader(&mut self, name: &str, shader: gl::types::GLuint) {
|
||||||
|
if let None = self.shaders.get(name) {
|
||||||
|
self.shaders.insert(name.to_string(), shader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_shader(&self, name: &str) -> Option<&gl::types::GLuint> {
|
||||||
|
self.shaders.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_shader(&mut self, name: &str) {
|
||||||
|
self.shaders.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&self, width: i32, height: i32) {
|
||||||
|
unsafe {
|
||||||
|
self.gl.Viewport(0, 0, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_object(&self, obj: &RenderObject, shader: gl::types::GLuint) {
|
||||||
|
unsafe {
|
||||||
|
self.UseProgram(shader);
|
||||||
|
|
||||||
|
self.BindBuffer(gl::ARRAY_BUFFER, obj.vbo);
|
||||||
|
self.BindVertexArray(obj.vao);
|
||||||
|
|
||||||
|
self.DrawArrays(gl::TRIANGLES, 0, obj.vertex_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self, current_scene: &String) {
|
||||||
|
unsafe {
|
||||||
|
self.ClearColor(1.0, 1.0, 1.0, 0.9);
|
||||||
|
self.Clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(scene) = self.scenes.get(current_scene) {
|
||||||
|
for model in scene.models {
|
||||||
|
self.draw_object(obj, *shader);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("Unknown Scene Requested: {}", current_scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Renderer {
|
||||||
|
type Target = gl::Gl;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.gl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Renderer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.shaders.values().for_each(|shader| self.gl.DeleteProgram(*shader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/lib.rs
Normal file
27
src/lib.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
pub mod models {
|
||||||
|
pub mod mesh;
|
||||||
|
pub use mesh::*;
|
||||||
|
|
||||||
|
pub mod vertex;
|
||||||
|
pub use vertex::*;
|
||||||
|
|
||||||
|
pub mod model;
|
||||||
|
pub use model::*;
|
||||||
|
|
||||||
|
pub mod scene;
|
||||||
|
pub use scene::*;
|
||||||
|
|
||||||
|
pub mod material;
|
||||||
|
pub use material::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod app {
|
||||||
|
pub mod renderer;
|
||||||
|
pub mod app;
|
||||||
|
pub mod gl;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod shaders {
|
||||||
|
pub mod shaders;
|
||||||
|
pub use shaders::*;
|
||||||
|
}
|
||||||
33
src/main.rs
Normal file
33
src/main.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use proj::{app::app::App, models::{Mesh, ModelData, ShaderSource}, shaders::{FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_SOURCE}};
|
||||||
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
|
const TRIANGLE_OBJ_PATH: &str = "assets/models/small_house/model.obj";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
let event_loop = EventLoop::new()
|
||||||
|
.expect("Should be able to have Event Loop");
|
||||||
|
|
||||||
|
let mut mesh = Mesh::new();
|
||||||
|
if let Err(e) = mesh.load_obj(TRIANGLE_OBJ_PATH) {
|
||||||
|
panic!("{}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let shader_src = ShaderSource {
|
||||||
|
vertex_src: VERTEX_SHADER_SOURCE,
|
||||||
|
fragment_src: FRAGMENT_SHADER_SOURCE
|
||||||
|
};
|
||||||
|
|
||||||
|
let scene_data = ModelData {
|
||||||
|
name: "home".to_string(),
|
||||||
|
objects_data: vec![(mesh, shader_src)]
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut app = App::new(scene_data);
|
||||||
|
|
||||||
|
app.set_current_scene("home");
|
||||||
|
|
||||||
|
let event_res = event_loop.run_app(&mut app);
|
||||||
|
|
||||||
|
println!("{:#?}", event_res);
|
||||||
|
}
|
||||||
18
src/models/material.rs
Normal file
18
src/models/material.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Material {
|
||||||
|
pub ambient: [f32; 3],
|
||||||
|
pub diffuse: [f32; 3],
|
||||||
|
pub specular: [f32; 3],
|
||||||
|
pub shader_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Material {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
ambient: Default::default(),
|
||||||
|
diffuse: Default::default(),
|
||||||
|
specular: Default::default(),
|
||||||
|
shader_name: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/models/mesh.rs
Normal file
124
src/models/mesh.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::models::Material;
|
||||||
|
|
||||||
|
use super::Vertex;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Mesh {
|
||||||
|
pub vertices: Vec<Vertex>,
|
||||||
|
pub material_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const NEW_MATERIAL_DELIMITER: &str = "newmtl ";
|
||||||
|
|
||||||
|
fn parse_rgb(values: &str) -> Result<[f32; 3], Box<dyn std::error::Error>> {
|
||||||
|
let mut rgb = [0.0; 3];
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
let values = values.split_whitespace();
|
||||||
|
|
||||||
|
for v in values {
|
||||||
|
let parsed = v.parse::<f32>()?;
|
||||||
|
rgb[i] = parsed;
|
||||||
|
i += 1;
|
||||||
|
if i > 2 {
|
||||||
|
todo!("Implement custom error handling: too many values");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if i != 3 {
|
||||||
|
todo!("Implement custom error handling: too few values");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(rgb)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mesh {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
vertices: vec![],
|
||||||
|
material_name: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_obj(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
if line.starts_with("v ") {
|
||||||
|
let coords: Result<Vec<f32>, _> = line
|
||||||
|
.split_whitespace()
|
||||||
|
.skip(1)
|
||||||
|
.take(3)
|
||||||
|
.map(|s| s.parse::<f32>())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match coords {
|
||||||
|
Ok(v) if v.len() == 3 => {
|
||||||
|
self.vertices.push(Vertex { x: v[0], y: v[1], z: v[2] })
|
||||||
|
},
|
||||||
|
_ => eprintln!("Warning: Invalid vertex line: {}", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:#?}", self.vertices);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_material(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
let mut lines = content.lines().peekable();
|
||||||
|
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() {
|
||||||
|
continue ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.starts_with(NEW_MATERIAL_DELIMITER) {
|
||||||
|
let mat_name = line.strip_prefix(NEW_MATERIAL_DELIMITER).unwrap();
|
||||||
|
|
||||||
|
while let Some(&next_line) = lines.peek() {
|
||||||
|
let next = next_line.trim();
|
||||||
|
|
||||||
|
if next.starts_with(NEW_MATERIAL_DELIMITER) || next.is_empty() {
|
||||||
|
lines.next();
|
||||||
|
|
||||||
|
let mut material = Material::new();
|
||||||
|
if let Some((key, values)) = next.split_once(' ') {
|
||||||
|
match key {
|
||||||
|
"Ka" => {
|
||||||
|
match parse_rgb(values) {
|
||||||
|
Ok(v) => material.ka = v,
|
||||||
|
Err(e) => eprintln!("Error parsing Ka values: {}", e.to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Kd" => {
|
||||||
|
match parse_rgb(values) {
|
||||||
|
Ok(v) => material.kd = v,
|
||||||
|
Err(e) => eprintln!("Error parsing Kd values: {}", e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"Ks" => {
|
||||||
|
match parse_rgb(values) {
|
||||||
|
Ok(v) => material.ks = v,
|
||||||
|
Err(e) => eprintln!("Error parsing Ks values: {}", e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { eprintln!("Unknown key: {key}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.materials.insert(mat_name.to_string(), material);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:#?}", self.vertices);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/models/model.rs
Normal file
23
src/models/model.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use crate::models::Mesh;
|
||||||
|
|
||||||
|
pub struct ShaderSource {
|
||||||
|
pub vertex_src: &'static [u8],
|
||||||
|
pub fragment_src: &'static [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Model {
|
||||||
|
pub objects: Vec<RenderObject>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RenderObject {
|
||||||
|
pub vao: gl::types::GLuint,
|
||||||
|
pub vbo: gl::types::GLuint,
|
||||||
|
pub _ebo: Option<gl::types::GLuint>,
|
||||||
|
pub vertex_count: i32,
|
||||||
|
pub _index_count: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ModelData {
|
||||||
|
pub name: String,
|
||||||
|
pub meshes: Vec<Mesh>
|
||||||
|
}
|
||||||
8
src/models/scene.rs
Normal file
8
src/models/scene.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::models::{Material, Model};
|
||||||
|
|
||||||
|
pub struct Scene {
|
||||||
|
pub models: Vec<Model>,
|
||||||
|
pub materials: HashMap<String, Material>,
|
||||||
|
}
|
||||||
7
src/models/vertex.rs
Normal file
7
src/models/vertex.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Vertex {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
26
src/shaders/shaders.rs
Normal file
26
src/shaders/shaders.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
pub const VERTEX_SHADER_SOURCE: &[u8] = b"
|
||||||
|
#version 100
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
attribute vec3 position;
|
||||||
|
attribute vec3 color;
|
||||||
|
|
||||||
|
varying vec3 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(position, 1.0);
|
||||||
|
v_color = color;
|
||||||
|
}
|
||||||
|
\0";
|
||||||
|
|
||||||
|
pub const FRAGMENT_SHADER_SOURCE: &[u8] = b"
|
||||||
|
#version 100
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
varying vec3 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = vec4(v_color, 1.0);
|
||||||
|
}
|
||||||
|
\0";
|
||||||
Loading…
x
Reference in New Issue
Block a user