use nom::{ IResult, bytes::complete::{tag}, number::complete::{le_u32}, }; use sdl2::event::Event; use sdl2::keyboard::Keycode; use std::collections::HashMap; use std::convert::TryInto; use std::ffi::{c_void, CStr, CString}; use std::fs::File; use std::io::Read; use std::path::Path; use std::time::Duration; mod iqm_consts { pub const VERSION: usize = 0; pub const FILESIZE: usize = 1; pub const FLAGS: usize = 2; pub const NUM_TEXT: usize = 3; pub const OFS_TEXT: usize = 4; pub const NUM_MESHES: usize = 5; pub const OFS_MESHES: usize = 6; pub const NUM_VERTEXARRAYS: usize = 7; pub const NUM_VERTEXES: usize = 8; pub const OFS_VERTEXARRAYS: usize = 9; pub const NUM_TRIANGLES: usize = 10; pub const OFS_TRIANGLES: usize = 11; pub const OFS_ADJACENCY: usize = 12; pub const NUM_JOINTS: usize = 13; pub const OFS_JOINTS: usize = 14; pub const NUM_POSES: usize = 15; pub const OFS_POSES: usize = 16; pub const NUM_ANIMS: usize = 17; pub const OFS_ANIMS: usize = 18; pub const NUM_FRAMES: usize = 19; pub const NUM_FRAMECHANNELS: usize = 20; pub const OFS_FRAMES: usize = 21; pub const OFS_BOUNDS: usize = 22; pub const NUM_COMMENT: usize = 23; pub const OFS_COMMENT: usize = 24; pub const NUM_EXTENSIONS: usize = 25; pub const OFS_EXTENSIONS: usize = 26; } #[derive (Debug)] pub struct IqmMesh { name: u32, material: u32, first_vertex: u32, num_vertexes: u32, first_triangle: u32, num_triangles: u32, } #[derive (Debug)] pub struct IqmHeader { fields: [u32; 27], } #[derive (Debug)] pub struct IqmModel { header: IqmHeader, text: Vec , meshes: Vec , } impl IqmHeader { pub fn from_slice (input: &[u8]) -> IResult <&[u8], IqmHeader> { let (input, _) = tag (b"INTERQUAKEMODEL\0")(input)?; let (input, version) = le_u32 (input)?; // I only know how to parse version 2 assert_eq! (version, 2); let mut input = input; let mut fields = [0; 27]; fields [0] = version; for index in 1..fields.len () { let (i, h) = le_u32 (input)?; input = i; fields [usize::from (index)] = h; } Ok ((input, IqmHeader { fields, })) } } impl IqmMesh { pub fn from_slice (input: &[u8]) -> IResult <&[u8], IqmMesh> { let mut result = IqmMesh { name: 0, material: 0, first_vertex: 0, num_vertexes: 0, first_triangle: 0, num_triangles: 0, }; let mut input = input; for field in [ &mut result.name, &mut result.material, &mut result.first_vertex, &mut result.num_vertexes, &mut result.first_triangle, &mut result.num_triangles, ].iter_mut () { let (i, f) = le_u32 (input)?; input = i; **field = f; } Ok ((input, result)) } } impl IqmModel { pub fn from_slice (input: &[u8]) -> IqmModel { let header = IqmHeader::from_slice (input).unwrap ().1; let text = { let offset: usize = header.fields [iqm_consts::OFS_TEXT].try_into ().unwrap (); let num: usize = header.fields [iqm_consts::NUM_TEXT].try_into ().unwrap (); Vec::from (&input [offset..offset + num]) }; let meshes = { let mut meshes = vec! []; let mesh_size = 6 * 4; let meshes_offset: usize = header.fields [iqm_consts::OFS_MESHES].try_into ().unwrap (); for i in 0..header.fields [iqm_consts::NUM_MESHES] { let i: usize = i.try_into ().unwrap (); let offset = meshes_offset + i * mesh_size; let mesh_slice = &input [offset..offset + mesh_size]; meshes.push (IqmMesh::from_slice (mesh_slice).unwrap ().1); } meshes }; IqmModel { header, text, meshes, } } } pub fn load_small_file

(name: P) -> Vec where P: AsRef { let mut f = File::open (name).unwrap (); let len = f.metadata ().unwrap ().len (); if len > 1024 * 1024 { panic! ("File is too big"); } let mut data = vec! [0u8; len.try_into ().unwrap ()]; f.read (&mut data [..]).unwrap (); data } const VERT_SHADER_SRC: &str = " #define lowp #define mediump #define highp #line 0 uniform highp mat4 uni_model; uniform highp mat4 uni_viewproj; attribute highp vec4 attr_pos; attribute mediump vec2 attr_uv; attribute lowp vec3 attr_normal; varying lowp vec4 vary_color; varying mediump vec2 vary_uv; void main (void) { vary_uv = attr_uv; lowp vec4 light_color = vec4 (1.0); vary_color = light_color; vec4 world_pos = uni_model * attr_pos; gl_Position = uni_viewproj * world_pos; }"; const FRAG_SHADER_SRC: &str = " #define lowp #define mediump #define highp #line 0 uniform lowp sampler2D uni_texture; varying lowp vec4 vary_color; varying mediump vec2 vary_uv; void main (void) { gl_FragColor = texture2D (uni_texture, vary_uv) * vary_color; //gl_FragColor = vec4 (1.0, 0.0, 1.0, 1.0); } "; pub struct ShaderObject { id: u32, } impl ShaderObject { pub fn id (&self) -> u32 { self.id } pub fn new (shader_type: u32, source: &str) -> Result { let id = unsafe { gl::CreateShader (shader_type) }; let sources = [ source.as_ptr () as *const i8, ]; let lengths = [ source.len ().try_into ().unwrap (), ]; let success = unsafe { gl::ShaderSource (id, sources.len ().try_into ().unwrap (), sources.as_ptr (), lengths.as_ptr ()); gl::CompileShader (id); let mut success = 0; gl::GetShaderiv (id, gl::COMPILE_STATUS, &mut success); success == 1 }; if success { Ok (ShaderObject { id, }) } else { let mut info_log = vec! [0u8; 4096]; let mut log_length = 0; unsafe { gl::GetShaderInfoLog (id, (info_log.len () - 1).try_into ().unwrap (), &mut log_length, info_log.as_mut_ptr () as *mut i8); } info_log.truncate (log_length.try_into ().unwrap ()); let info = String::from_utf8 (info_log).unwrap (); Err (info) } } } impl Drop for ShaderObject { fn drop (&mut self) { unsafe { gl::DeleteShader (self.id); } } } pub struct ShaderProgram { id: u32, } impl ShaderProgram { pub fn new (vert: &ShaderObject, frag: &ShaderObject) -> Result { let id = unsafe { gl::CreateProgram () }; unsafe { gl::AttachShader (id, vert.id ()); gl::AttachShader (id, frag.id ()); gl::LinkProgram (id); } let success = unsafe { let mut success = 0; gl::GetProgramiv (id, gl::LINK_STATUS, &mut success); success == 1 }; if success { Ok (ShaderProgram { id, }) } else { let mut info_log = vec! [0u8; 4096]; let mut log_length = 0; unsafe { gl::GetProgramInfoLog (id, (info_log.len () - 1).try_into ().unwrap (), &mut log_length, info_log.as_mut_ptr () as *mut i8); } info_log.truncate (log_length.try_into ().unwrap ()); let info = String::from_utf8 (info_log).unwrap (); Err (info) } } pub fn get_uniform_location (&self, name: &CStr) -> i32 { unsafe { gl::UseProgram (self.id); gl::GetUniformLocation (self.id, name.as_ptr ()) } } pub fn get_attribute_location (&self, name: &CStr) -> i32 { unsafe { gl::UseProgram (self.id); gl::GetAttribLocation (self.id, name.as_ptr ()) } } } impl Drop for ShaderProgram { fn drop (&mut self) { unsafe { gl::DeleteProgram (self.id); } } } fn main () { let sdl_context = sdl2::init ().unwrap (); let video_subsystem = sdl_context.video ().unwrap (); let window = video_subsystem.window ("OpenGL? In my Rust?", 1280, 720) .position_centered () .opengl () .build () .unwrap (); gl::load_with (|s| { let result = video_subsystem.gl_get_proc_address (s) as *const _; //println! ("{:?}", result); result }); assert! (gl::ClearColor::is_loaded ()); let gl_ctx = window.gl_create_context ().unwrap (); window.gl_make_current (&gl_ctx).unwrap (); // I love how with Rust I can throw unwrap ()s everywhere // It's safer than C / C++'s default behavior of unchecked errors // It's more programmer-friendly and explicit than C#'s unchecked // exceptions that can appear almost anywhere at runtime with no // compile-time warning // And I'm still not actually checking errors - Just checkmarking // that I know where they are. let vert_shader = ShaderObject::new (gl::VERTEX_SHADER, VERT_SHADER_SRC).unwrap (); let frag_shader = ShaderObject::new (gl::FRAGMENT_SHADER, FRAG_SHADER_SRC).unwrap (); let shader_program = ShaderProgram::new (&vert_shader, &frag_shader).unwrap (); let unis: HashMap <_, _> = vec! [ "model", "viewproj", ].iter () .map (|name| { let mut s = String::from ("uni_"); s.push_str (name); let c_str = CString::new (s.as_bytes ()).unwrap (); (String::from (*name), shader_program.get_uniform_location (&c_str)) }) .collect (); println! ("uni_model: {}", unis ["model"]); println! ("uni_viewproj: {}", unis ["viewproj"]); let attrs: HashMap <_, u32> = vec! [ "pos", "uv", //"normal", ].iter () .map (|name| { let mut s = String::from ("attr_"); s.push_str (name); let c_str = CString::new (s.as_bytes ()).unwrap (); let loc = shader_program.get_attribute_location (&c_str); (String::from (*name), loc.try_into ().unwrap ()) }) .collect (); println! ("attr_pos: {}", attrs ["pos"]); let tri_mesh: Vec = vec! [ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, ]; let id_mat: Vec = vec! [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ]; let indexes: Vec = vec! [0, 1, 2]; const FALSE_U8: u8 = 0; unsafe { gl::EnableVertexAttribArray (attrs ["pos"]); let stride = 4 * 2; gl::VertexAttribPointer (attrs ["pos"], 2, gl::FLOAT, FALSE_U8, stride, &tri_mesh [0] as *const f32 as *const c_void); gl::UniformMatrix4fv (unis ["viewproj"], 1, FALSE_U8, &id_mat [0]); gl::UniformMatrix4fv (unis ["model"], 1, FALSE_U8, &id_mat [0]); } let mut event_pump = sdl_context.event_pump ().unwrap (); 'running: loop { let _mouse = event_pump.mouse_state (); for event in event_pump.poll_iter() { match event { Event::Quit {..} | Event::KeyDown { keycode: Some (Keycode::Escape), .. } => { break 'running }, _ => (), } } window.gl_make_current (&gl_ctx).unwrap (); unsafe { gl::ClearColor (1.0f32, 0.0f32, 1.0f32, 1.0f32); gl::Clear (gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); gl::DrawElements (gl::TRIANGLES, 3, gl::UNSIGNED_SHORT, &indexes [0] as *const u16 as *const c_void); } window.gl_swap_window (); ::std::thread::sleep (Duration::from_millis (15)); } } #[cfg (test)] mod tests { use super::*; #[test] pub fn iqm () { let data = load_small_file ("pumpking.iqm"); { let model = IqmHeader::from_slice (&data [..]).unwrap ().1; assert_eq! (model.fields [1], 90368); assert_eq! (model.fields [2], 0); assert_eq! (model.fields [iqm_consts::VERSION], 2); } { let model = IqmModel::from_slice (&data [..]); println! ("{:?}", model.meshes); } } }