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