1use std::rc::Rc;
4
5use wasm_bindgen::JsCast;
6use web_sys::HtmlImageElement;
7use web_sys::WebGlTexture;
8
9use crate::gl::Bind;
10use crate::Color32;
11use crate::{gl, GL};
12
13#[derive(Debug)]
15pub struct Texture {
16 texture: Option<WebGlTexture>,
17 pub width: u32,
19 pub height: u32,
21 pub slot: u32,
23}
24
25impl Default for Texture {
26 fn default() -> Self {
27 Self {
28 texture: None,
29 width: 1,
30 height: 1,
31 slot: 0,
32 }
33 }
34}
35
36impl Bind for Texture {
37 fn bind(&self, gl: &GL) {
38 gl.active_texture(GL::TEXTURE0 + self.slot);
39 gl.bind_texture(GL::TEXTURE_2D, self.texture.as_ref());
40 }
41 fn unbind(&self, gl: &GL) {
42 gl.active_texture(GL::TEXTURE0 + self.slot);
43 gl.bind_texture(GL::TEXTURE_2D, None);
44 }
45}
46
47impl Drop for Texture {
48 fn drop(&mut self) {
49 let gl = gl::get_context();
50
51 gl.delete_texture(self.texture.as_ref());
52 }
53}
54
55impl Texture {
56 pub fn new(gl: &GL, image: &HtmlImageElement) -> Self {
58 let (width, height) = (image.width(), image.height());
59
60 let texture = gl.create_texture();
61 gl.active_texture(GL::TEXTURE0);
62 gl.bind_texture(GL::TEXTURE_2D, texture.as_ref());
63 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, GL::NEAREST as i32);
64 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, GL::NEAREST as i32);
65 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, GL::REPEAT as i32);
66 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, GL::REPEAT as i32);
67 gl.pixel_storei(GL::UNPACK_FLIP_Y_WEBGL, 1);
69 gl.tex_image_2d_with_u32_and_u32_and_html_image_element(
70 GL::TEXTURE_2D,
71 0,
72 GL::RGBA as i32,
73 GL::RGBA,
74 GL::UNSIGNED_BYTE,
75 image,
76 )
77 .expect("Failed to load texture");
78 gl.generate_mipmap(GL::TEXTURE_2D);
79
80 Self {
81 width,
82 height,
83 texture,
84 ..Default::default()
85 }
86 }
87
88 pub fn new_with_element_id(gl: &GL, image_src: &str) -> Self {
90 let document: web_sys::Document = web_sys::window().unwrap().document().unwrap();
91 let image = document
92 .get_element_by_id(image_src)
93 .unwrap()
94 .dyn_into::<web_sys::HtmlImageElement>()
95 .unwrap();
96 Self::new(gl, &image)
97 }
98
99 pub fn new_with_texture_id(gl: &GL, count: u32) -> Self {
101 Self::new_with_element_id(gl, &format!("texture{}", count))
102 }
103
104 pub fn new_from_pixels(gl: &GL, width: u32, height: u32, pixels: &[u8]) -> Self {
106 assert!(pixels.len() == (width * height * 4) as usize);
107 let texture = gl.create_texture();
108 gl.active_texture(GL::TEXTURE0);
109 gl.bind_texture(GL::TEXTURE_2D, texture.as_ref());
110 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, GL::NEAREST as i32);
111 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, GL::NEAREST as i32);
112 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, GL::REPEAT as i32);
113 gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, GL::REPEAT as i32);
114 gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
115 GL::TEXTURE_2D,
116 0,
117 GL::RGBA as i32,
118 width as i32,
119 height as i32,
120 0,
121 GL::RGBA,
122 GL::UNSIGNED_BYTE,
123 Some(pixels),
124 )
125 .expect("Failed to generate texture");
126 gl.generate_mipmap(GL::TEXTURE_2D);
127
128 Self {
129 width,
130 height,
131 texture,
132 ..Default::default()
133 }
134 }
135
136 pub fn colored(gl: &GL, color: Color32) -> Self {
140 Self::new_from_pixels(gl, 1, 1, &<[u8; 4]>::from(color))
141 }
142
143 pub fn white(gl: &GL) -> Self {
145 Self::colored(gl, Color32::WHITE)
146 }
147
148 pub fn checkerboard(gl: &GL) -> Self {
150 Self::checkerboard_colored(gl, Color32::WHITE, Color32::BLACK)
151 }
152
153 pub fn checkerboard_colored(gl: &GL, color1: Color32, color2: Color32) -> Self {
155 let size = 8;
156 let mut pixels = Vec::<u8>::with_capacity(size * size);
157 for x_offset in 0..size {
158 for y_offset in 0..size {
159 let color = if (x_offset + y_offset) % 2 == 0 {
160 color1
161 } else {
162 color2
163 };
164 pixels.append(&mut Vec::from(color));
165 }
166 }
167 Self::new_from_pixels(gl, size as u32, size as u32, &pixels)
168 }
169}
170
171#[derive(Debug, Clone)]
175pub struct SubTexture {
176 texture: Option<Rc<Texture>>,
177 min: [f32; 2],
178 max: [f32; 2],
179}
180
181impl Default for SubTexture {
182 fn default() -> Self {
183 Self {
184 texture: None,
185 min: [0.0, 0.0],
186 max: [1.0, 1.0],
187 }
188 }
189}
190
191impl SubTexture {
192 pub fn new(texture: Rc<Texture>) -> Self {
194 Self {
195 texture: Some(texture),
196 ..Default::default()
197 }
198 }
199
200 pub fn new_with_coords(texture: Rc<Texture>, uv: Color32) -> Self {
202 Self {
203 texture: Some(texture),
204 min: [uv.x(), uv.z()],
205 max: [uv.y(), uv.w()],
206 }
207 }
208
209 pub fn get_uv_coords(&self) -> [[f32; 2]; 4] {
211 let (min, max) = (self.min, self.max);
212 [
213 [min[0], min[1]],
214 [min[0], max[1]],
215 [max[0], max[1]],
216 [max[0], min[1]],
217 ]
218 }
219
220 pub fn create_tiles_from_spritesheet(
224 texture: Rc<Texture>,
225 horizontal_cells: u32,
226 vertical_cells: u32,
227 ) -> Vec<SubTexture> {
228 assert!(horizontal_cells > 0 && vertical_cells > 0);
229 let mut tiles: Vec<SubTexture> = Vec::new();
230 for cell_x in 0..horizontal_cells {
231 for cell_y in 0..vertical_cells {
232 let uv = Color32(
233 cell_x as f32 / horizontal_cells as f32,
234 (cell_x + 1) as f32 / horizontal_cells as f32,
235 cell_y as f32 / vertical_cells as f32,
236 (cell_y + 1) as f32 / vertical_cells as f32,
237 );
238 let tile = SubTexture::new_with_coords(Rc::clone(&texture), uv);
239 tiles.push(tile);
240 }
241 }
242 tiles
243 }
244}
245
246impl Bind for SubTexture {
247 fn bind(&self, gl: &GL) {
248 if let Some(texture) = &self.texture {
249 texture.bind(gl);
250 }
251 }
252 fn unbind(&self, gl: &GL) {
253 gl.bind_texture(GL::TEXTURE_2D, None);
254 }
255}