pub struct Vector3D { pub x: f32, pub y: f32, pub z: f32, } impl Vector3D { pub fn new(x: f32, y: f32, z: f32) -> Vector3D { Vector3D { x: x, y: y, z: z, } } pub fn norm(self) -> f32 { return (self.x*self.x+self.y*self.y+self.z*self.z).sqrt(); } pub fn normalized(self, l: f32) -> Vector3D { return self*(l/self.norm()); } } impl fmt::Display for Vector3D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({},{},{})", self.x, self.y, self.z) } } impl Add for Vector3D { type Output = Vector3D; fn add(self, other: Vector3D) -> Vector3D { Vector3D { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z} } } impl Sub for Vector3D { type Output = Vector3D; fn sub(self, other: Vector3D) -> Vector3D { Vector3D { x: self.x - other.x, y: self.y - other.y, z: self.z - other.z} } } impl Mul for Vector3D { type Output = f32; fn mul(self, other: Vector3D) -> f32 { return self.x*other.x + self.y*other.y + self.z*other.z; } } impl Mul<f32> for Vector3D { type Output = Vector3D; fn mul(self, other: f32) -> Vector3D { Vector3D { x: self.x * other, y: self.y * other, z: self.z * other} } } impl BitXor for Vector3D { type Output = Vector3D; fn bitxor(self, v: Vector3D) -> Vector3D { Vector3D { x: self.y*vz-self.z*vy, y: self.z*vx-self.x*vz, z: self.x*vy-self.y*vx} } }
let x = Vector3D::new(1.0, 1.0, 1.0); let y = x*2.0; do_something_else(x); // error!
normalized()
normalized()
, because there, as you can see, self
both to the right and left of the multiplication operator. That is, we are trying to move it 2 times in a row. By default, all user structures in Rust are relocatable.#[derive(Copy, Clone)]
. This tells the compiler that our structure is copied and it can be copied by simple byte duplication. Now in calls like the one above, a copy of the data will be sent to our operators, and the original will remain available after the call. It looks difficult, but due to this additional complication the compiler does not allow me to write code with memory errors (for example, Use After Free ). use std::fmt; use std::ops::Add; use std::ops::Sub; use std::ops::Mul; use std::ops::BitXor; use num::traits::NumCast; #[derive(Copy, Clone)] pub struct Vector3D<T> { pub x: T, pub y: T, pub z: T, } impl<T> Vector3D<T> { pub fn new(x: T, y: T, z: T) -> Vector3D<T> { Vector3D { x: x, y: y, z: z, } } } impl<T: NumCast> Vector3D<T> { pub fn to<V: NumCast>(self) -> Vector3D<V> { Vector3D { x: NumCast::from(self.x).unwrap(), y: NumCast::from(self.y).unwrap(), z: NumCast::from(self.z).unwrap(), } } } impl Vector3D<f32> { pub fn norm(self) -> f32 { return (self.x*self.x+self.y*self.y+self.z*self.z).sqrt(); } pub fn normalized(self, l: f32) -> Vector3D<f32> { return self*(l/self.norm()); } } impl<T: fmt::Display> fmt::Display for Vector3D<T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({},{},{})", self.x, self.y, self.z) } } impl<T: Add<Output = T>> Add for Vector3D<T> { type Output = Vector3D<T>; fn add(self, other: Vector3D<T>) -> Vector3D<T> { Vector3D { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z} } } impl<T: Sub<Output = T>> Sub for Vector3D<T> { type Output = Vector3D<T>; fn sub(self, other: Vector3D<T>) -> Vector3D<T> { Vector3D { x: self.x - other.x, y: self.y - other.y, z: self.z - other.z} } } impl<T: Mul<Output = T> + Add<Output = T>> Mul for Vector3D<T> { type Output = T; fn mul(self, other: Vector3D<T>) -> T { return self.x*other.x + self.y*other.y + self.z*other.z; } } impl<T: Mul<Output = T> + Copy> Mul<T> for Vector3D<T> { type Output = Vector3D<T>; fn mul(self, other: T) -> Vector3D<T> { Vector3D { x: self.x * other, y: self.y * other, z: self.z * other} } } impl<T: Mul<Output = T> + Sub<Output = T> + Copy> BitXor for Vector3D<T> { type Output = Vector3D<T>; fn bitxor(self, v: Vector3D<T>) -> Vector3D<T> { Vector3D { x: self.y*vz-self.z*vy, y: self.z*vx-self.x*vz, z: self.x*vy-self.y*vx} } }
x: self.y*vz-self.z*vy, y: self.z*vx-self.x*vz, z: self.x*vy-self.y*vx
using links or make sure that T is copyable. All elementary types in Rust allow copying. In general, we don’t expect anyone to try to put lists or files in x, y, z or something else complicated, so we’ll like the copy option. “Stop, and what is this even for <Output = T>
?”, The attentive reader will ask. And it will be right, because without this record the code will not work. The fact is that Rust does not guarantee that the result of addition or multiplication will be of the same type as the operands. Therefore, we additionally specify here that we need T, which implements multiplication in such a way that the result of multiplication is also of type T. Is it difficult? I warned you. pub trait Canvas { fn canvas(&mut self) -> &mut Vec<Vec<u32>>; fn zbuffer(&mut self) -> &mut Vec<Vec<i32>>; fn xsize(&self) -> usize; fn ysize(&self) -> usize; fn new(x: usize, y: usize) -> Self; fn show(&mut self); fn wait_for_enter(&mut self); fn set(&mut self, x: i32, y: i32, color: u32) { if x < 0 || y < 0 { return; } if x >= self.xsize() as i32 || y >= self.ysize() as i32{ return; } self.canvas()[x as usize][y as usize] = color; } fn triangle(&mut self, mut p0: Vector3D<i32>, mut p1: Vector3D<i32>, mut p2: Vector3D<i32>, color: u32) { //... } }
pub struct SdlCanvas { sdl_context: Sdl, renderer: Renderer, canvas: Vec<Vec<u32>>, zbuffer: Vec<Vec<i32>>, xsize: usize, ysize: usize, } impl Canvas for SdlCanvas { fn new(x: usize, y: usize) -> SdlCanvas { //... SdlCanvas { sdl_context: sdl_context, renderer: renderer, canvas: vec![vec![0;y];x], zbuffer: vec![vec![std::i32::MIN; y]; x], xsize: x, ysize: y, } } fn show(&mut self) { let mut texture = self.renderer.create_texture_streaming(PixelFormatEnum::RGB24, (self.xsize as u32, self.ysize as u32)).unwrap(); // ... self.renderer.present(); } fn wait_for_enter(&mut self) { //... } fn canvas(&mut self) -> &mut Vec<Vec<u32>>{ &mut self.canvas } fn zbuffer(&mut self) -> &mut Vec<Vec<i32>>{ &mut self.zbuffer } fn xsize(&self) -> usize{ self.xsize } fn ysize(&self) -> usize{ self.ysize } }
// ...
Those interested can see the full code in the corresponding snapshot of the repository . As you can see, this all looks a bit unusual, but in fact it is very similar to our usual inheritance and interfaces. By code size is not even much different. The only moment impurities do not allow to require the presence of any variables in the structures. So we had to create getters for some of the variables we need in a universal implementation and in SdlCanvas, in turn, to implement them. The corresponding article Rust by Example greatly helped in the implementation of the above. #pragma pack(push,1) struct TGA_Header { char idlength; char colormaptype; // ... short width; short height; char bitsperpixel; char imagedescriptor; }; #pragma pack(pop) // ... TGA_Header header; in.read((char *)&header, sizeof(header));
const HEADERSIZE: usize = 18; // 18 = sizeof(TgaHeader) #[repr(C, packed)] struct TgaHeader { idlength: i8, colormaptype: i8, // ... width: i16, height: i16, bitsperpixel: i8, imagedescriptor: i8, } // ... let mut file = File::open(&path).unwrap(); let mut header_bytes: [u8; HEADERSIZE] = [0; HEADERSIZE]; file.read(&mut header_bytes); let header = unsafe { mem::transmute::<[u8; HEADERSIZE], TgaHeader>(header_bytes) };
#[repr(C, packed)]
. Thus, our structure is now located in memory as it was located in the harsh, ancient times when the TGA was invented. In addition, here we use unsafe code. It is understandable, the interpretation in the area in memory as a kind of structure without any checks completely breaks the idea of ​​static typing. Fortunately, this code will most often work. (but not always, there are still all sorts of nuances with byte order ) And of course you also noticed that I set the size of the buffer as a constant. What about sizeof, you ask? Well, it is in Rust, but it is not calculated as a constant expression , but is considered at runtime. The size of the array should be known at the compilation stage. These are the pies. fn read(path: &str) -> TgaCanvas{ let path = Path::new(path); let mut file = BufReader::new(File::open(&path).unwrap()); let mut header_bytes: [u8; HEADERSIZE] = [0; HEADERSIZE]; file.read(&mut header_bytes); let header = unsafe { mem::transmute::<[u8; HEADERSIZE], TgaHeader>(header_bytes) }; let xsize = header.width as usize; let ysize = header.height as usize; debug!("read header: width = {}, height = {}", xsize, ysize); let bytespp = header.bitsperpixel>>3; debug!("bytes per pixel - {}", bytespp); let mut canvas = vec![vec![0;ysize];xsize]; for iy in 0..ysize{ for ix in 0..xsize{ if bytespp == 1 { let mut bytes: [u8; 1] = [0; 1]; file.read(&mut bytes); let intensity = bytes[0] as u32; canvas[ix][iy] = intensity + intensity*256 + intensity*256*256; } else if bytespp == 3 { let mut bytes: [u8; 3] = [0; 3]; file.read(&mut bytes); canvas[ix][iy] = bytes[2] as u32 + bytes[1] as u32*256 + bytes[0] as u32*256*256; } else if bytespp == 4 { let mut bytes: [u8; 4] = [0; 4]; file.read(&mut bytes); if ix == 0 { debug!("{} {} {} {}", bytes[0], bytes[1], bytes[2], bytes[3]); } canvas[ix][iy] = bytes[2] as u32 + ((bytes[1] as u32) << (8*1)) + ((bytes[0] as u32) << (8*2)); //debug!("{}", canvas[ix][iy]); } } debug!("{}", canvas[0][iy]); } TgaCanvas { canvas: canvas, zbuffer: vec![vec![std::i32::MIN; ysize]; xsize], xsize: xsize, ysize: ysize, } }
BufReader::new(File::open(&path).unwrap());
that if you replace BufReader::new(File::open(&path).unwrap());
on just File::open(&path).unwrap();
, the bug was not shown. I even thought that this is a bug in BufReader, because in theory it should only provide buffering, without interfering with the byte stream.buffer.len()
bytes will be read, although this is often the case. But not always. In BufReader, the buffer ends and it returns as many bytes as it is left in it and, meanwhile, starts filling the buffer in the background again. If I got the point right. As a result, at some point, I skipped a few bytes and then the image was damaged. This behavior on the part of read () is documented, but, in my opinion, violates the principle of least surprise . Although only I can not read all the documentation for the methods used ...new(file_path: &String) -> Model
, but the problem here is that then you have to write in all calls not Model::new("african_head.obj");
, and Model::new("african_head.obj".to_string());
. I was offended by the language for this perversion and decided, in turn, also to pervert. Here is my concatenation code: let texture_path_string = file_path.rsplitn(2, '.').last().unwrap().to_string() + "_diffuse.tga"; let texture_path_str = texture_path_string.split("*").next().unwrap();
Source: https://habr.com/ru/post/262235/
All Articles