1#[cfg(target_arch = "wasm32")]
16pub mod audio_web;
17pub mod camera;
18pub mod color;
19pub mod depth;
20pub mod light;
21pub mod material;
22pub mod photon;
23pub mod poly;
24pub mod raster;
25pub mod shapes;
26pub mod toon;
27pub mod vtex;
28#[cfg(target_arch = "wasm32")]
29pub mod webgl;
30#[cfg(feature = "gpu")]
31pub mod wgpu_raster;
32
33pub use camera::Camera3D;
34pub use depth::DepthQueue;
35pub use light::Light;
36pub use material::LingMaterial;
37pub use toon::ToonConfig;
38
39pub const UNLIT: u32 = 0x0100_0000;
42pub const RGB_MASK: u32 = 0x00FF_FFFF;
43
44#[derive(Clone, Copy)]
50pub struct ShadowParams {
51 pub base: f32,
53 pub grow: f32,
55 pub alpha: f32,
57 pub fade: f32,
59 pub soft: f32,
61}
62
63impl Default for ShadowParams {
64 fn default() -> Self {
65 Self { base: 14.0, grow: 0.6, alpha: 0.55, fade: 0.012, soft: 0.45 }
66 }
67}
68
69#[cfg(not(target_arch = "wasm32"))]
72pub struct GfxState {
73 pub window: Option<minifb::Window>,
74 pub buffer: Vec<u32>,
75 pub distort_buf: Vec<u32>,
77 pub width: usize,
78 pub height: usize,
79 pub color: u32,
81 pub camera: Camera3D,
83 pub lights: Vec<Light>,
85 pub ambient: f32,
87 pub depth_queue: DepthQueue,
89 pub mouse_dx: f32,
91 pub mouse_dy: f32,
92 pub last_mx: f32,
94 pub last_my: f32,
95 pub mouse_captured: bool,
97 pub shade_mode: u8,
99 pub shade: ling_graphics::shading::ShadeParams,
101 pub blend: u8,
103 pub alpha: f32,
106 pub antialias: bool,
110 pub font_antialias: bool,
116 pub mesh_hue: f32,
119 pub mesh_hue_gain: f32,
121 pub frame_blur: f32,
124 pub prev_frame: Vec<u32>,
126 pub shadow: ShadowParams,
128 pub linear_blend: bool,
131 pub grad_oklab: bool,
134 pub depth_test: bool,
137 pub depth_buf: Vec<f32>,
142 pub zbuf_needs_clear: bool,
145 pub post_done: bool,
148 pub fog_color: u32,
151 pub fog_start: f32,
152 pub fog_end: f32,
153 pub flat_shade: bool,
156 pub vsync: bool,
158 pub edge_set: poly::EdgeSet,
160 pub material: Option<LingMaterial>,
163 pub normal_override: Option<[f32; 3]>,
165 pub toon: ToonConfig,
167 pub meshes: Vec<Vec<([f32; 9], u32)>>,
170 pub mesh_capture: Option<Vec<([f32; 9], u32)>>,
173 pub mesh_free: Vec<usize>,
175 pub mesh_cache: std::collections::HashMap<i64, usize>,
177 pub was_active: bool,
180 pub focus_grace_frames: u8,
186 pub topmost_window: bool,
192 #[cfg(windows)]
203 pub raw_keys_prev: [bool; 256],
204 #[cfg(windows)]
209 pub raw_keys_down_since: [f64; 256],
210 #[cfg(windows)]
214 pub raw_keys_last_fire: [f64; 256],
215 #[cfg(windows)]
223 pub hwnd: isize,
224 pub want_quit: bool,
228}
229
230#[cfg(not(target_arch = "wasm32"))]
231impl GfxState {
232 #[allow(clippy::new_without_default)]
233 pub fn new() -> Self {
234 Self {
235 window: None,
236 buffer: Vec::new(),
237 distort_buf: Vec::new(),
238 width: 0,
239 height: 0,
240 color: 0x00FF_FFFF,
241 camera: Camera3D::default(),
242 lights: Vec::new(),
243 ambient: 0.15,
244 depth_queue: DepthQueue::default(),
245 mouse_dx: 0.0,
246 mouse_dy: 0.0,
247 last_mx: f32::NAN,
248 last_my: f32::NAN,
249 mouse_captured: false,
250 shade_mode: 2,
251 shade: ling_graphics::shading::ShadeParams::default(),
252 blend: 0,
253 alpha: 1.0,
254 antialias: false,
255 font_antialias: false,
256 mesh_hue: 0.0,
257 mesh_hue_gain: 1.0,
258 frame_blur: 0.0,
259 prev_frame: Vec::new(),
260 shadow: ShadowParams::default(),
261 linear_blend: false,
262 grad_oklab: true,
263 depth_test: false,
264 depth_buf: Vec::new(),
265 zbuf_needs_clear: true,
266 post_done: false,
267 fog_color: 0x0000_0000,
268 fog_start: 0.0,
269 fog_end: 0.0,
270 flat_shade: false,
271 vsync: true,
272 edge_set: poly::EdgeSet::default(),
273 material: None,
274 normal_override: None,
275 toon: ToonConfig::default(),
276 meshes: Vec::new(),
277 mesh_capture: None,
278 mesh_free: Vec::new(),
279 mesh_cache: std::collections::HashMap::new(),
280 was_active: true,
281 focus_grace_frames: 0,
282 topmost_window: false,
283 #[cfg(windows)]
284 raw_keys_prev: [false; 256],
285 #[cfg(windows)]
286 raw_keys_down_since: [0.0; 256],
287 #[cfg(windows)]
288 raw_keys_last_fire: [0.0; 256],
289 #[cfg(windows)]
290 hwnd: 0,
291 want_quit: false,
292 }
293 }
294
295 #[inline]
299 pub fn input_suppressed(&mut self) -> bool {
300 self.focus_grace_frames > 0 || !self.window.as_mut().map(|w| w.is_active()).unwrap_or(true)
301 }
302
303 #[inline]
305 pub fn fog_apply(&self, color: u32, depth: f32) -> u32 {
306 if self.fog_end <= 0.0 {
307 return color;
308 }
309 let span = self.fog_end - self.fog_start;
310 if span <= 0.0 {
311 return color;
312 }
313 let f = ((depth - self.fog_start) / span).clamp(0.0, 1.0);
314 if f <= 0.0 {
315 return color;
316 }
317 let lerp = |a: u32, b: u32| -> u32 { (a as f32 + (b as f32 - a as f32) * f) as u32 & 0xff };
318 let r = lerp((color >> 16) & 0xff, (self.fog_color >> 16) & 0xff);
319 let g = lerp((color >> 8) & 0xff, (self.fog_color >> 8) & 0xff);
320 let b = lerp(color & 0xff, self.fog_color & 0xff);
321 (r << 16) | (g << 8) | b
322 }
323
324 pub fn sync_projection(&mut self) {
325 self.camera.cx = self.width as f32 / 2.0;
326 self.camera.cy = self.height as f32 / 2.0;
327 self.camera.focal = self.height as f32;
328 self.camera.zdist = 5.0;
329 }
330
331 pub fn toon_post_process(&mut self) {
334 let w = self.width;
335 let h = self.height;
336 if self.buffer.len() < w * h {
337 return;
338 }
339 toon::apply(&self.toon, &mut self.buffer, &self.depth_buf, w, h);
340 }
341}
342
343#[cfg(target_arch = "wasm32")]
346thread_local! {
347 static WASM_KEYS_PRESSED: std::cell::RefCell<std::collections::HashSet<String>> =
348 std::cell::RefCell::new(std::collections::HashSet::new());
349 static WASM_KEYS_DOWN: std::cell::RefCell<std::collections::HashSet<String>> =
350 std::cell::RefCell::new(std::collections::HashSet::new());
351}
352
353#[cfg(target_arch = "wasm32")]
355pub fn wasm_key_down(key: &str) {
356 let key = normalize_key(key);
357 WASM_KEYS_DOWN.with(|keys_down| {
358 let mut down = keys_down.borrow_mut();
359 if !down.contains(&key) {
360 WASM_KEYS_PRESSED.with(|keys_pressed| {
361 keys_pressed.borrow_mut().insert(key.clone());
362 });
363 }
364 down.insert(key);
365 });
366}
367
368#[cfg(target_arch = "wasm32")]
370pub fn wasm_key_up(key: &str) {
371 let key = normalize_key(key);
372 WASM_KEYS_DOWN.with(|keys_down| {
373 keys_down.borrow_mut().remove(&key);
374 });
375}
376
377#[cfg(target_arch = "wasm32")]
379pub fn audio_resume() {
380 audio_web::resume();
381}
382
383#[cfg(target_arch = "wasm32")]
385pub fn wasm_clear_frame_keys() {
386 WASM_KEYS_PRESSED.with(|keys| {
387 keys.borrow_mut().clear();
388 });
389}
390
391#[cfg(target_arch = "wasm32")]
393pub fn wasm_is_key_pressed(key: &str) -> bool {
394 let key = normalize_key(key);
395 WASM_KEYS_PRESSED.with(|keys| keys.borrow().contains(&key))
396}
397
398#[cfg(target_arch = "wasm32")]
400pub fn wasm_is_key_down(key: &str) -> bool {
401 let key = normalize_key(key);
402 WASM_KEYS_DOWN.with(|keys| keys.borrow().contains(&key))
403}
404
405#[cfg(target_arch = "wasm32")]
408thread_local! {
409 static WASM_MOUSE_X: std::cell::Cell<f32> = std::cell::Cell::new(0.0);
410 static WASM_MOUSE_Y: std::cell::Cell<f32> = std::cell::Cell::new(0.0);
411 static WASM_MOUSE_DX: std::cell::Cell<f32> = std::cell::Cell::new(0.0);
412 static WASM_MOUSE_DY: std::cell::Cell<f32> = std::cell::Cell::new(0.0);
413 static WASM_MOUSE_LEFT: std::cell::Cell<bool> = std::cell::Cell::new(false);
414 static WASM_MOUSE_RIGHT: std::cell::Cell<bool> = std::cell::Cell::new(false);
415 static WASM_MOUSE_MIDDLE: std::cell::Cell<bool> = std::cell::Cell::new(false);
416}
417
418#[cfg(target_arch = "wasm32")]
420pub fn wasm_mouse_move(x: f32, y: f32) {
421 let dx = WASM_MOUSE_X.with(|c| x - c.replace(x));
422 let dy = WASM_MOUSE_Y.with(|c| y - c.replace(y));
423 WASM_MOUSE_DX.with(|c| c.set(c.get() + dx));
424 WASM_MOUSE_DY.with(|c| c.set(c.get() + dy));
425}
426
427#[cfg(target_arch = "wasm32")]
430pub fn wasm_mouse_button(button: u32, pressed: bool, x: f32, y: f32) {
431 wasm_mouse_move(x, y);
432 match button {
433 0 => WASM_MOUSE_LEFT.with(|c| c.set(pressed)),
434 1 => WASM_MOUSE_MIDDLE.with(|c| c.set(pressed)),
435 2 => WASM_MOUSE_RIGHT.with(|c| c.set(pressed)),
436 _ => {},
437 }
438}
439
440#[cfg(target_arch = "wasm32")]
441pub fn wasm_mouse_x() -> f32 {
442 WASM_MOUSE_X.with(|c| c.get())
443}
444#[cfg(target_arch = "wasm32")]
445pub fn wasm_mouse_y() -> f32 {
446 WASM_MOUSE_Y.with(|c| c.get())
447}
448#[cfg(target_arch = "wasm32")]
449pub fn wasm_mouse_dx() -> f32 {
450 WASM_MOUSE_DX.with(|c| c.get())
451}
452#[cfg(target_arch = "wasm32")]
453pub fn wasm_mouse_dy() -> f32 {
454 WASM_MOUSE_DY.with(|c| c.get())
455}
456#[cfg(target_arch = "wasm32")]
457pub fn wasm_mouse_down() -> bool {
458 WASM_MOUSE_LEFT.with(|c| c.get())
459}
460
461#[cfg(target_arch = "wasm32")]
462pub fn wasm_gamepad_button(_index: u32, _button: &str, _pressed: bool, _value: f32) {}
463#[cfg(target_arch = "wasm32")]
464pub fn wasm_gamepad_axis(_index: u32, _axis: u32, _value: f32) {}
465#[cfg(target_arch = "wasm32")]
466pub fn wasm_gamepad_connected(_index: u32, _name: &str) {}
467#[cfg(target_arch = "wasm32")]
468pub fn wasm_gamepad_disconnected(_index: u32) {}
469#[cfg(target_arch = "wasm32")]
470pub fn wasm_mouse_down_right() -> bool {
471 WASM_MOUSE_RIGHT.with(|c| c.get())
472}
473#[cfg(target_arch = "wasm32")]
474pub fn wasm_mouse_down_middle() -> bool {
475 WASM_MOUSE_MIDDLE.with(|c| c.get())
476}
477
478#[cfg(target_arch = "wasm32")]
481pub fn wasm_clear_frame_mouse_delta() {
482 WASM_MOUSE_DX.with(|c| c.set(0.0));
483 WASM_MOUSE_DY.with(|c| c.set(0.0));
484}
485
486#[cfg(target_arch = "wasm32")]
488pub fn wasm_play_audio_buffer(pcm_data: &[f32], sample_rate: u32) {
489 let id = audio_web::add_sample(pcm_data, 1, sample_rate);
490 if id >= 0 {
491 audio_web::play_sample(id as usize, 0.0, 0.0, 0.0, 1.0, false);
492 }
493}
494
495#[cfg(target_arch = "wasm32")]
497pub fn wasm_set_master_volume(volume: f32) {
498 audio_web::set_master_volume(volume);
499}
500
501#[cfg(target_arch = "wasm32")]
503fn normalize_key(key: &str) -> String {
504 match key {
505 " " => "space".to_string(),
506 "ArrowUp" => "up".to_string(),
507 "ArrowDown" => "down".to_string(),
508 "ArrowLeft" => "left".to_string(),
509 "ArrowRight" => "right".to_string(),
510 "Enter" => "enter".to_string(),
511 "Escape" => "escape".to_string(),
512 "Shift" | "ShiftLeft" | "ShiftRight" => "shift".to_string(),
513 "Control" | "ControlLeft" | "ControlRight" => "ctrl".to_string(),
514 "Alt" | "AltLeft" | "AltRight" => "alt".to_string(),
515 "Tab" => "tab".to_string(),
516 "Backspace" => "backspace".to_string(),
517 _ => key.to_lowercase(),
518 }
519}
520
521#[cfg(target_arch = "wasm32")]
524pub struct GfxState {
525 pub width: usize,
526 pub height: usize,
527 pub color: u32,
529 pub fill_r: f32,
531 pub fill_g: f32,
532 pub fill_b: f32,
533 pub camera: Camera3D,
534 pub lights: Vec<Light>,
535 pub ambient: f32,
536 pub depth_queue: DepthQueue,
538 pub shade_mode: u8,
539 pub shade: ling_graphics::shading::ShadeParams,
540 pub buffer: Vec<u32>,
543 pub distort_buf: Vec<u32>,
545 pub blend: u8,
547 pub alpha: f32,
549 pub mesh_hue: f32,
551 pub mesh_hue_gain: f32,
553 pub frame_blur: f32,
555 pub prev_frame: Vec<u32>,
557 pub antialias: bool,
559 pub font_antialias: bool,
560 pub shadow: ShadowParams,
562 pub linear_blend: bool,
564 pub grad_oklab: bool,
566 pub depth_test: bool,
568 pub depth_buf: Vec<f32>,
570 pub zbuf_needs_clear: bool,
572 pub post_done: bool,
574 pub fog_color: u32,
577 pub fog_start: f32,
578 pub fog_end: f32,
579 pub flat_shade: bool,
581 pub keys_pressed: std::collections::HashSet<String>,
583 pub keys_down: std::collections::HashSet<String>,
585 pub edge_set: poly::EdgeSet,
587 pub material: Option<LingMaterial>,
589 pub normal_override: Option<[f32; 3]>,
591 pub toon: ToonConfig,
593 pub meshes: Vec<Vec<([f32; 9], u32)>>,
595 pub mesh_capture: Option<Vec<([f32; 9], u32)>>,
597 pub mesh_free: Vec<usize>,
599 pub mesh_cache: std::collections::HashMap<i64, usize>,
601}
602
603#[cfg(target_arch = "wasm32")]
604impl GfxState {
605 #[allow(clippy::new_without_default)]
606 pub fn new() -> Self {
607 Self {
608 width: 800,
609 height: 600,
610 color: 0x00FF_FFFF,
611 fill_r: 0.0,
612 fill_g: 0.0,
613 fill_b: 0.0,
614 camera: Camera3D::default(),
615 lights: Vec::new(),
616 ambient: 0.15,
617 depth_queue: DepthQueue::default(),
618 shade_mode: 2,
619 shade: ling_graphics::shading::ShadeParams::default(),
620 buffer: vec![0u32; 800 * 600],
621 distort_buf: Vec::new(),
622 blend: 0,
623 alpha: 1.0,
624 antialias: false,
625 font_antialias: false,
626 mesh_hue: 0.0,
627 mesh_hue_gain: 1.0,
628 frame_blur: 0.0,
629 prev_frame: Vec::new(),
630 shadow: ShadowParams::default(),
631 linear_blend: false,
632 grad_oklab: true,
633 depth_test: false,
634 depth_buf: Vec::new(),
635 zbuf_needs_clear: true,
636 post_done: false,
637 fog_color: 0x0000_0000,
638 fog_start: 0.0,
639 fog_end: 0.0,
640 flat_shade: false,
641 keys_pressed: std::collections::HashSet::new(),
642 keys_down: std::collections::HashSet::new(),
643 edge_set: poly::EdgeSet::default(),
644 material: None,
645 normal_override: None,
646 toon: ToonConfig::default(),
647 meshes: Vec::new(),
648 mesh_capture: None,
649 mesh_free: Vec::new(),
650 mesh_cache: std::collections::HashMap::new(),
651 }
652 }
653
654 pub fn clear_frame_keys(&mut self) {
656 self.keys_pressed.clear();
657 }
658
659 pub fn on_key_down(&mut self, key: String) {
661 if !self.keys_down.contains(&key) {
662 self.keys_pressed.insert(key.clone());
663 }
664 self.keys_down.insert(key);
665 }
666
667 pub fn on_key_up(&mut self, key: String) {
669 self.keys_down.remove(&key);
670 }
671
672 #[inline]
675 pub fn fog_apply(&self, color: u32, depth: f32) -> u32 {
676 if self.fog_end <= 0.0 {
677 return color;
678 }
679 let span = self.fog_end - self.fog_start;
680 if span <= 0.0 {
681 return color;
682 }
683 let f = ((depth - self.fog_start) / span).clamp(0.0, 1.0);
684 if f <= 0.0 {
685 return color;
686 }
687 let lerp = |a: u32, b: u32| -> u32 { (a as f32 + (b as f32 - a as f32) * f) as u32 & 0xff };
688 let r = lerp((color >> 16) & 0xff, (self.fog_color >> 16) & 0xff);
689 let g = lerp((color >> 8) & 0xff, (self.fog_color >> 8) & 0xff);
690 let b = lerp(color & 0xff, self.fog_color & 0xff);
691 (r << 16) | (g << 8) | b
692 }
693
694 pub fn sync_projection(&mut self) {
695 self.camera.cx = self.width as f32 / 2.0;
696 self.camera.cy = self.height as f32 / 2.0;
697 self.camera.focal = self.height as f32;
698 self.camera.zdist = 5.0;
699 }
700
701 pub fn toon_post_process(&mut self) {
703 let w = self.width;
704 let h = self.height;
705 if self.buffer.len() < w * h {
706 return;
707 }
708 toon::apply(&self.toon, &mut self.buffer, &self.depth_buf, w, h);
709 }
710}
711
712impl GfxState {
715 #[inline]
718 #[allow(clippy::too_many_arguments)]
719 pub fn submit_triangle(
720 &mut self,
721 ax: f32,
722 ay: f32,
723 az: f32,
724 bx: f32,
725 by: f32,
726 bz: f32,
727 cx: f32,
728 cy: f32,
729 cz: f32,
730 ) {
731 let ux = bx - ax;
732 let uy = by - ay;
733 let uz = bz - az;
734 let vx = cx - ax;
735 let vy = cy - ay;
736 let vz = cz - az;
737 let normal = self
738 .normal_override
739 .unwrap_or([uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx]);
740
741 let (c0, c1, c2) = if self.flat_shade {
742 (self.color, self.color, self.color)
743 } else if let Some(mut m) = self.material.clone() {
744 m.albedo = self.color;
748 let cam_x = self.camera.cx;
749 let cam_y = self.camera.cy;
750 let cam_z = self.camera.zdist;
751 let amb = self.ambient;
752 let s0 = crate::gfx::material::shade(
753 &m,
754 normal,
755 [cam_x - ax, cam_y - ay, cam_z - az],
756 [ax, ay, az],
757 &self.lights,
758 amb,
759 );
760 let s1 = crate::gfx::material::shade(
761 &m,
762 normal,
763 [cam_x - bx, cam_y - by, cam_z - bz],
764 [bx, by, bz],
765 &self.lights,
766 amb,
767 );
768 let s2 = crate::gfx::material::shade(
769 &m,
770 normal,
771 [cam_x - cx, cam_y - cy, cam_z - cz],
772 [cx, cy, cz],
773 &self.lights,
774 amb,
775 );
776 (s0, s1, s2)
777 } else {
778 crate::gfx::light::compute_lit_color_vertices(
779 self.color,
780 normal,
781 [ax, ay, az],
782 [bx, by, bz],
783 [cx, cy, cz],
784 &self.lights,
785 self.ambient,
786 )
787 };
788
789 let near = -self.camera.zdist + 0.05;
790 let vw = [
791 (ax, ay, az, self.camera.depth(ax, ay, az), c0),
792 (bx, by, bz, self.camera.depth(bx, by, bz), c1),
793 (cx, cy, cz, self.camera.depth(cx, cy, cz), c2),
794 ];
795 let mut poly: [(f32, f32, f32, u32); 4] = [(0.0, 0.0, 0.0, 0); 4];
796 let mut pn = 0usize;
797 let mut ei = 0;
798 while ei < 3 {
799 let a = vw[ei];
800 let b = vw[(ei + 1) % 3];
801 let ain = a.3 > near;
802 let bin = b.3 > near;
803 if ain && pn < 4 {
804 poly[pn] = (a.0, a.1, a.2, a.4);
805 pn += 1;
806 }
807 if ain != bin && pn < 4 {
808 let tt = (near - a.3) / (b.3 - a.3);
809 poly[pn] = (
810 a.0 + (b.0 - a.0) * tt,
811 a.1 + (b.1 - a.1) * tt,
812 a.2 + (b.2 - a.2) * tt,
813 crate::gfx::light::lerp_color(a.4, b.4, tt),
814 );
815 pn += 1;
816 }
817 ei += 1;
818 }
819 if pn < 3 {
820 return;
821 }
822 let mut proj: [(f32, f32, f32, u32); 4] = [(0.0, 0.0, 0.0, 0); 4];
823 let mut pi = 0;
824 while pi < pn {
825 let (sx, sy, sz) = self.camera.project(poly[pi].0, poly[pi].1, poly[pi].2);
826 let fc = self.fog_apply(poly[pi].3, sz);
827 proj[pi] = (sx, sy, sz, fc);
828 pi += 1;
829 }
830 let mut fk = 1;
831 while fk + 1 < pn {
832 self.depth_queue.push_triangle_g_zv(
833 proj[0].0,
834 proj[0].1,
835 proj[0].2,
836 proj[0].3,
837 proj[fk].0,
838 proj[fk].1,
839 proj[fk].2,
840 proj[fk].3,
841 proj[fk + 1].0,
842 proj[fk + 1].1,
843 proj[fk + 1].2,
844 proj[fk + 1].3,
845 3,
846 self.flat_shade,
847 );
848 fk += 1;
849 }
850 }
851
852 pub fn mesh_register(&mut self, tris: Vec<([f32; 9], u32)>) -> usize {
855 if let Some(id) = self.mesh_free.pop() {
856 self.meshes[id] = tris;
857 id
858 } else {
859 let id = self.meshes.len();
860 self.meshes.push(tris);
861 id
862 }
863 }
864
865 #[allow(clippy::too_many_arguments)]
870 pub fn mesh_draw(
871 &mut self,
872 id: usize,
873 ox: f32,
874 oy: f32,
875 oz: f32,
876 rx: f32,
877 ry: f32,
878 rz: f32,
879 ux: f32,
880 uy: f32,
881 uz: f32,
882 s: f32,
883 use_baked_color: bool,
884 ) {
885 if id >= self.meshes.len() {
886 return;
887 }
888 let fx = ry * uz - rz * uy;
889 let fy = rz * ux - rx * uz;
890 let fz = rx * uy - ry * ux;
891 let pen = self.color;
892 let mesh = std::mem::take(&mut self.meshes[id]);
893 for (t, col) in &mesh {
894 if use_baked_color {
895 self.color = *col;
896 }
897 let wx0 = ox + s * (t[0] * rx + t[1] * ux + t[2] * fx);
898 let wy0 = oy + s * (t[0] * ry + t[1] * uy + t[2] * fy);
899 let wz0 = oz + s * (t[0] * rz + t[1] * uz + t[2] * fz);
900 let wx1 = ox + s * (t[3] * rx + t[4] * ux + t[5] * fx);
901 let wy1 = oy + s * (t[3] * ry + t[4] * uy + t[5] * fy);
902 let wz1 = oz + s * (t[3] * rz + t[4] * uz + t[5] * fz);
903 let wx2 = ox + s * (t[6] * rx + t[7] * ux + t[8] * fx);
904 let wy2 = oy + s * (t[6] * ry + t[7] * uy + t[8] * fy);
905 let wz2 = oz + s * (t[6] * rz + t[7] * uz + t[8] * fz);
906 self.submit_triangle(wx0, wy0, wz0, wx1, wy1, wz1, wx2, wy2, wz2);
907 }
908 self.color = pen;
909 self.meshes[id] = mesh;
910 }
911}