Skip to main content

oxigdal_wasm/
rendering.rs

1//! Advanced canvas rendering and viewport management
2//!
3//! This module provides comprehensive canvas context management, double buffering,
4//! progressive rendering, viewport transformations, and zoom/pan optimizations for
5//! high-performance geospatial data visualization in the browser.
6//!
7//! # Overview
8//!
9//! The rendering module is the core visualization engine, providing:
10//!
11//! - **Double Buffering**: Eliminates flickering during updates
12//! - **Progressive Rendering**: Loads low-res first, then high-res
13//! - **Viewport Management**: Pan, zoom, fit-to-bounds operations
14//! - **History System**: Undo/redo for viewport changes
15//! - **Transform System**: Affine transformations for efficient rendering
16//! - **Canvas Buffers**: Off-screen rendering for compositing
17//! - **Animation Management**: Frame rate monitoring and adaptive rendering
18//! - **Quality Levels**: Dynamic quality adjustment based on performance
19//!
20//! # Double Buffering
21//!
22//! Double buffering prevents screen tearing and flickering:
23//!
24//! ```text
25//! Frame N:
26//!   Front Buffer: Currently displayed
27//!   Back Buffer:  Being updated with new tiles
28//!
29//! Frame N+1:
30//!   Swap buffers
31//!   Front Buffer: New frame (was back buffer)
32//!   Back Buffer:  Ready for next update
33//! ```
34//!
35//! Benefits:
36//! - No partial updates visible to user
37//! - Smooth transitions between frames
38//! - Allows complex compositing operations
39//!
40//! # Progressive Rendering
41//!
42//! Progressive rendering improves perceived performance:
43//!
44//! 1. **Initial Load**: Show lowest resolution immediately
45//! 2. **Progressive Enhancement**: Load higher resolutions incrementally
46//! 3. **Final Quality**: Display full resolution when available
47//!
48//! ```text
49//! Time    Level  Resolution  Tiles  Status
50//! ────────────────────────────────────────
51//! 0ms     4      128x128     1      ▓ Loaded
52//! 50ms    3      256x256     4      ▓ Loaded
53//! 150ms   2      512x512     16     ▓ Loading...
54//! 400ms   1      1024x1024   64     ░ Pending
55//! 1000ms  0      2048x2048   256    ░ Pending
56//! ```
57//!
58//! # Viewport Transformations
59//!
60//! The viewport system handles all coordinate transformations:
61//!
62//! ## World to Screen
63//! Converts world coordinates (tile space) to screen coordinates (pixels):
64//! ```text
65//! world_x, world_y  →  [Transform Matrix]  →  screen_x, screen_y
66//! ```
67//!
68//! ## Screen to World
69//! Converts screen coordinates back to world coordinates:
70//! ```text
71//! screen_x, screen_y  →  [Inverse Transform]  →  world_x, world_y
72//! ```
73//!
74//! # Transform Matrix
75//!
76//! The transform is represented as:
77//! ```text
78//! [ sx  0  tx ]   [ x ]
79//! [ 0  sy  ty ] * [ y ]
80//! [ 0   0   1 ]   [ 1 ]
81//! ```
82//!
83//! Where:
84//! - `sx, sy`: Scale factors (zoom level)
85//! - `tx, ty`: Translation (pan offset)
86//! - `rotation`: Rotation angle (optional)
87//!
88//! # Example Usage
89//!
90//! ```ignore
91//! use oxigdal_wasm::rendering::{CanvasRenderer, ViewportState, ViewportTransform};
92//!
93//! // Create renderer for 800x600 canvas
94//! let mut renderer = CanvasRenderer::new(800, 600, 4)?;
95//!
96//! // Setup viewport to show entire image
97//! let mut viewport = ViewportState::new(800, 600);
98//! viewport.fit_to_bounds((0.0, 0.0, 4096.0, 4096.0));
99//!
100//! // Begin rendering frame
101//! renderer.begin_frame();
102//!
103//! // Draw tiles
104//! for coord in visible_tiles {
105//!     let tile_data = cache.get(&coord)?;
106//!     renderer.draw_tile(coord, tile_data, 256, 256)?;
107//! }
108//!
109//! // Swap buffers to display
110//! renderer.swap_buffers();
111//!
112//! // Get as ImageData for canvas
113//! let image_data = renderer.front_buffer_image_data()?;
114//! ctx.putImageData(&image_data, 0, 0)?;
115//! ```
116//!
117//! # Viewport Operations
118//!
119//! ## Pan
120//! ```rust
121//! use oxigdal_wasm::CanvasRenderer;
122//! let mut renderer = CanvasRenderer::new(800, 600, 4).expect("Create failed");
123//! renderer.pan(100.0, 50.0); // Move right 100px, down 50px
124//! ```
125//!
126//! ## Zoom
127//! ```rust
128//! use oxigdal_wasm::CanvasRenderer;
129//! let mut renderer = CanvasRenderer::new(800, 600, 4).expect("Create failed");
130//! renderer.zoom(2.0, 400.0, 300.0); // 2x zoom at center
131//! ```
132//!
133//! ## Fit to Bounds
134//! ```rust
135//! use oxigdal_wasm::ViewportState;
136//! let mut viewport = ViewportState::new(800, 600);
137//! viewport.fit_to_bounds((0.0, 0.0, 4096.0, 4096.0));
138//! ```
139//!
140//! ## Undo/Redo
141//! ```rust
142//! use oxigdal_wasm::CanvasRenderer;
143//! let mut renderer = CanvasRenderer::new(800, 600, 4).expect("Create failed");
144//! if renderer.undo() {
145//!     println!("Undid last viewport change");
146//! }
147//!
148//! if renderer.redo() {
149//!     println!("Redid viewport change");
150//! }
151//! ```
152//!
153//! # Performance Optimization
154//!
155//! ## Quality Levels
156//! Adjust quality based on frame rate:
157//! ```rust
158//! use oxigdal_wasm::{CanvasRenderer, RenderQuality};
159//!
160//! let mut renderer = CanvasRenderer::new(800, 600, 4).expect("Create failed");
161//! renderer.set_quality(RenderQuality::Low);    // Fast
162//! renderer.set_quality(RenderQuality::Medium); // Balanced
163//! renderer.set_quality(RenderQuality::High);   // Slow
164//! ```
165//!
166//! ## Animation Management
167//! Monitor frame rate and adapt:
168//! ```ignore
169//! use oxigdal_wasm::rendering::AnimationManager;
170//!
171//! let mut animator = AnimationManager::new(60.0);
172//!
173//! requestAnimationFrame(|timestamp| {
174//!     animator.record_frame(timestamp);
175//!
176//!     if animator.is_below_target() {
177//!         // Reduce quality or skip tiles
178//!         renderer.set_quality(RenderQuality::Low);
179//!     }
180//! });
181//! ```
182//!
183//! # Memory Management
184//!
185//! Buffers are automatically sized based on canvas dimensions:
186//! ```text
187//! Buffer Size = width * height * 4 bytes (RGBA)
188//! Example: 800x600 = 1,920,000 bytes ≈ 1.8 MB per buffer
189//! Total (double buffered): ~3.6 MB
190//! ```
191//!
192//! # Best Practices
193//!
194//! 1. **Initialize Once**: Create renderer once, reuse across frames
195//! 2. **Double Buffer**: Always use swap_buffers() for smooth updates
196//! 3. **Progressive Load**: Start with low-res, enhance progressively
197//! 4. **Monitor FPS**: Adjust quality if frame rate drops
198//! 5. **Batch Updates**: Draw multiple tiles before swapping
199//! 6. **Clear Appropriately**: Clear with background color, not transparent
200//! 7. **History Limits**: Cap undo history to prevent memory growth
201
202use serde::{Deserialize, Serialize};
203use std::collections::VecDeque;
204use wasm_bindgen::prelude::*;
205use web_sys::ImageData;
206
207use crate::error::{CanvasError, WasmError, WasmResult};
208use crate::tile::TileCoord;
209
210/// Default canvas buffer size in MB
211#[allow(dead_code)]
212pub const DEFAULT_CANVAS_BUFFER_SIZE_MB: usize = 50;
213
214/// Maximum viewport history size
215pub const MAX_VIEWPORT_HISTORY: usize = 50;
216
217/// Rendering quality levels
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
219pub enum RenderQuality {
220    /// Low quality (fast)
221    Low,
222    /// Medium quality (balanced)
223    Medium,
224    /// High quality (slow)
225    High,
226    /// Ultra quality (very slow)
227    Ultra,
228}
229
230impl RenderQuality {
231    /// Returns the tile resolution multiplier for this quality level
232    pub const fn resolution_multiplier(&self) -> f64 {
233        match self {
234            Self::Low => 0.5,
235            Self::Medium => 1.0,
236            Self::High => 1.5,
237            Self::Ultra => 2.0,
238        }
239    }
240
241    /// Returns the interpolation quality for this level
242    pub const fn interpolation_quality(&self) -> &'static str {
243        match self {
244            Self::Low => "low",
245            Self::Medium => "medium",
246            Self::High => "high",
247            Self::Ultra => "high",
248        }
249    }
250}
251
252/// Viewport transformation matrix
253#[derive(Debug, Clone, Copy, PartialEq)]
254pub struct ViewportTransform {
255    /// Translation X
256    pub tx: f64,
257    /// Translation Y
258    pub ty: f64,
259    /// Scale X
260    pub sx: f64,
261    /// Scale Y
262    pub sy: f64,
263    /// Rotation angle in radians
264    pub rotation: f64,
265}
266
267impl ViewportTransform {
268    /// Creates a new transform with specified parameters (affine matrix form)
269    /// Parameters: sx, shy, shx, sy, tx, ty (2D affine transform)
270    /// For simpler cases, use identity(), translate(), scale(), or rotate()
271    pub const fn new(sx: f64, _shy: f64, _shx: f64, sy: f64, tx: f64, ty: f64) -> Self {
272        // Simplified: ignores shear components for now
273        Self {
274            tx,
275            ty,
276            sx,
277            sy,
278            rotation: 0.0,
279        }
280    }
281
282    /// Creates a new identity transform
283    pub const fn identity() -> Self {
284        Self {
285            tx: 0.0,
286            ty: 0.0,
287            sx: 1.0,
288            sy: 1.0,
289            rotation: 0.0,
290        }
291    }
292
293    /// Creates a translation transform
294    pub const fn translate(tx: f64, ty: f64) -> Self {
295        Self {
296            tx,
297            ty,
298            sx: 1.0,
299            sy: 1.0,
300            rotation: 0.0,
301        }
302    }
303
304    /// Creates a scale transform
305    pub const fn scale(sx: f64, sy: f64) -> Self {
306        Self {
307            tx: 0.0,
308            ty: 0.0,
309            sx,
310            sy,
311            rotation: 0.0,
312        }
313    }
314
315    /// Creates a uniform scale transform
316    pub const fn uniform_scale(s: f64) -> Self {
317        Self::scale(s, s)
318    }
319
320    /// Creates a rotation transform
321    pub const fn rotate(rotation: f64) -> Self {
322        Self {
323            tx: 0.0,
324            ty: 0.0,
325            sx: 1.0,
326            sy: 1.0,
327            rotation,
328        }
329    }
330
331    /// Transforms a point
332    pub fn transform_point(&self, x: f64, y: f64) -> (f64, f64) {
333        let cos_r = self.rotation.cos();
334        let sin_r = self.rotation.sin();
335
336        let x_scaled = x * self.sx;
337        let y_scaled = y * self.sy;
338
339        let x_rotated = x_scaled * cos_r - y_scaled * sin_r;
340        let y_rotated = x_scaled * sin_r + y_scaled * cos_r;
341
342        (x_rotated + self.tx, y_rotated + self.ty)
343    }
344
345    /// Inverse transforms a point
346    pub fn inverse_transform_point(&self, x: f64, y: f64) -> (f64, f64) {
347        let cos_r = self.rotation.cos();
348        let sin_r = self.rotation.sin();
349
350        let x_translated = x - self.tx;
351        let y_translated = y - self.ty;
352
353        let x_rotated = x_translated * cos_r + y_translated * sin_r;
354        let y_rotated = -x_translated * sin_r + y_translated * cos_r;
355
356        (x_rotated / self.sx, y_rotated / self.sy)
357    }
358
359    /// Composes two transforms
360    pub fn compose(&self, other: &Self) -> Self {
361        Self {
362            tx: self.tx + other.tx * self.sx,
363            ty: self.ty + other.ty * self.sy,
364            sx: self.sx * other.sx,
365            sy: self.sy * other.sy,
366            rotation: self.rotation + other.rotation,
367        }
368    }
369}
370
371impl Default for ViewportTransform {
372    fn default() -> Self {
373        Self::identity()
374    }
375}
376
377/// Viewport state with transformation and bounds
378#[derive(Debug, Clone, PartialEq)]
379pub struct ViewportState {
380    /// Canvas width in pixels
381    pub canvas_width: u32,
382    /// Canvas height in pixels
383    pub canvas_height: u32,
384    /// Viewport transformation
385    pub transform: ViewportTransform,
386    /// Visible bounds in world coordinates (min_x, min_y, max_x, max_y)
387    pub visible_bounds: (f64, f64, f64, f64),
388    /// Current zoom level
389    pub zoom_level: u32,
390}
391
392impl ViewportState {
393    /// Creates a new viewport state
394    pub fn new(canvas_width: u32, canvas_height: u32) -> Self {
395        Self {
396            canvas_width,
397            canvas_height,
398            transform: ViewportTransform::identity(),
399            visible_bounds: (0.0, 0.0, canvas_width as f64, canvas_height as f64),
400            zoom_level: 0,
401        }
402    }
403
404    /// Updates the viewport transform
405    pub fn update_transform(&mut self, transform: ViewportTransform) {
406        self.transform = transform;
407        self.update_visible_bounds();
408    }
409
410    /// Updates visible bounds based on current transform
411    fn update_visible_bounds(&mut self) {
412        let (min_x, min_y) = self.transform.inverse_transform_point(0.0, 0.0);
413        let (max_x, max_y) = self
414            .transform
415            .inverse_transform_point(self.canvas_width as f64, self.canvas_height as f64);
416
417        self.visible_bounds = (
418            min_x.min(max_x),
419            min_y.min(max_y),
420            min_x.max(max_x),
421            min_y.max(max_y),
422        );
423    }
424
425    /// Pans the viewport
426    pub fn pan(&mut self, dx: f64, dy: f64) {
427        self.transform.tx += dx;
428        self.transform.ty += dy;
429        self.update_visible_bounds();
430    }
431
432    /// Zooms the viewport
433    pub fn zoom(&mut self, factor: f64, center_x: f64, center_y: f64) {
434        // Ensure factor is positive to maintain valid transform
435        let factor = factor.abs().max(0.01);
436
437        // Transform center point to world coordinates
438        let (world_x, world_y) = self.transform.inverse_transform_point(center_x, center_y);
439
440        // Apply zoom
441        self.transform.sx *= factor;
442        self.transform.sy *= factor;
443
444        // Adjust translation to keep center point fixed
445        let (new_screen_x, new_screen_y) = self.transform.transform_point(world_x, world_y);
446        self.transform.tx += center_x - new_screen_x;
447        self.transform.ty += center_y - new_screen_y;
448
449        self.update_visible_bounds();
450    }
451
452    /// Fits the viewport to bounds
453    pub fn fit_to_bounds(&mut self, bounds: (f64, f64, f64, f64)) {
454        let (min_x, min_y, max_x, max_y) = bounds;
455        let width = max_x - min_x;
456        let height = max_y - min_y;
457
458        let scale_x = self.canvas_width as f64 / width;
459        let scale_y = self.canvas_height as f64 / height;
460        let scale = scale_x.min(scale_y);
461
462        self.transform.sx = scale;
463        self.transform.sy = scale;
464        self.transform.tx = -min_x * scale;
465        self.transform.ty = -min_y * scale;
466
467        self.update_visible_bounds();
468    }
469
470    /// Returns the tiles visible in this viewport
471    pub fn visible_tiles(&self, tile_size: u32) -> Vec<TileCoord> {
472        let (min_x, min_y, max_x, max_y) = self.visible_bounds;
473
474        let min_tile_x = (min_x / tile_size as f64).floor() as u32;
475        let min_tile_y = (min_y / tile_size as f64).floor() as u32;
476        let max_tile_x = (max_x / tile_size as f64).ceil() as u32;
477        let max_tile_y = (max_y / tile_size as f64).ceil() as u32;
478
479        let mut tiles = Vec::new();
480        for y in min_tile_y..=max_tile_y {
481            for x in min_tile_x..=max_tile_x {
482                tiles.push(TileCoord::new(self.zoom_level, x, y));
483            }
484        }
485
486        tiles
487    }
488}
489
490/// Viewport history for undo/redo
491pub struct ViewportHistory {
492    /// History stack
493    history: VecDeque<ViewportState>,
494    /// Current position in history
495    current_index: usize,
496    /// Maximum history size
497    max_size: usize,
498}
499
500impl ViewportHistory {
501    /// Creates a new viewport history
502    pub fn new(max_size: usize) -> Self {
503        Self {
504            history: VecDeque::new(),
505            current_index: 0,
506            max_size,
507        }
508    }
509
510    /// Pushes a new state to history
511    pub fn push(&mut self, state: ViewportState) {
512        // Remove any states after current index (when we've undone some actions)
513        while self.history.len() > self.current_index + 1 {
514            self.history.pop_back();
515        }
516
517        // Add new state
518        self.history.push_back(state);
519
520        // Limit history size
521        if self.history.len() > self.max_size {
522            self.history.pop_front();
523            // After popping from front, don't adjust current_index if we're at max
524        } else {
525            self.current_index = self.history.len() - 1;
526        }
527    }
528
529    /// Undoes to the previous state
530    pub fn undo(&mut self) -> Option<&ViewportState> {
531        if self.current_index > 0 {
532            self.current_index -= 1;
533            self.history.get(self.current_index)
534        } else {
535            None
536        }
537    }
538
539    /// Redoes to the next state
540    pub fn redo(&mut self) -> Option<&ViewportState> {
541        if self.current_index + 1 < self.history.len() {
542            self.current_index += 1;
543            self.history.get(self.current_index)
544        } else {
545            None
546        }
547    }
548
549    /// Returns the current state
550    pub fn current(&self) -> Option<&ViewportState> {
551        self.history.get(self.current_index)
552    }
553
554    /// Checks if undo is available
555    pub const fn can_undo(&self) -> bool {
556        self.current_index > 0
557    }
558
559    /// Checks if redo is available
560    pub fn can_redo(&self) -> bool {
561        self.current_index + 1 < self.history.len()
562    }
563
564    /// Returns the current history index
565    pub const fn current_index(&self) -> usize {
566        self.current_index
567    }
568
569    /// Clears the history
570    pub fn clear(&mut self) {
571        self.history.clear();
572        self.current_index = 0;
573    }
574}
575
576/// Canvas buffer for double buffering
577pub struct CanvasBuffer {
578    /// Buffer data
579    data: Vec<u8>,
580    /// Buffer width
581    width: u32,
582    /// Buffer height
583    height: u32,
584    /// Whether the buffer is dirty
585    dirty: bool,
586}
587
588impl CanvasBuffer {
589    /// Creates a new canvas buffer
590    pub fn new(width: u32, height: u32) -> WasmResult<Self> {
591        if width == 0 || height == 0 {
592            return Err(WasmError::InvalidOperation {
593                operation: "CanvasBuffer::new".to_string(),
594                reason: "Width and height must be greater than zero".to_string(),
595            });
596        }
597
598        let size = (width as usize) * (height as usize) * 4;
599        let data = vec![0u8; size];
600
601        Ok(Self {
602            data,
603            width,
604            height,
605            dirty: true,
606        })
607    }
608
609    /// Resizes the buffer
610    pub fn resize(&mut self, width: u32, height: u32) -> WasmResult<()> {
611        if width == 0 || height == 0 {
612            return Err(WasmError::InvalidOperation {
613                operation: "CanvasBuffer::resize".to_string(),
614                reason: "Width and height must be greater than zero".to_string(),
615            });
616        }
617
618        if width != self.width || height != self.height {
619            let size = (width as usize) * (height as usize) * 4;
620            self.data.resize(size, 0);
621            self.width = width;
622            self.height = height;
623            self.dirty = true;
624        }
625        Ok(())
626    }
627
628    /// Clears the buffer
629    pub fn clear(&mut self) {
630        self.data.fill(0);
631        self.dirty = true;
632    }
633
634    /// Clears the buffer with a color
635    pub fn clear_with_color(&mut self, r: u8, g: u8, b: u8, a: u8) {
636        for chunk in self.data.chunks_exact_mut(4) {
637            chunk[0] = r;
638            chunk[1] = g;
639            chunk[2] = b;
640            chunk[3] = a;
641        }
642        self.dirty = true;
643    }
644
645    /// Draws a tile to the buffer
646    pub fn draw_tile(
647        &mut self,
648        tile_data: &[u8],
649        tile_x: u32,
650        tile_y: u32,
651        tile_width: u32,
652        tile_height: u32,
653    ) -> WasmResult<()> {
654        let expected_size = (tile_width * tile_height * 4) as usize;
655        if tile_data.len() != expected_size {
656            return Err(WasmError::Canvas(CanvasError::BufferSizeMismatch {
657                expected: expected_size,
658                actual: tile_data.len(),
659            }));
660        }
661
662        // Check bounds
663        if tile_x + tile_width > self.width || tile_y + tile_height > self.height {
664            return Err(WasmError::Canvas(CanvasError::InvalidDimensions {
665                width: tile_x + tile_width,
666                height: tile_y + tile_height,
667                reason: "Tile extends beyond buffer bounds".to_string(),
668            }));
669        }
670
671        // Copy tile data to buffer
672        for y in 0..tile_height {
673            let src_offset = (y * tile_width * 4) as usize;
674            let dst_offset = (((tile_y + y) * self.width + tile_x) * 4) as usize;
675            let row_size = (tile_width * 4) as usize;
676
677            self.data[dst_offset..dst_offset + row_size]
678                .copy_from_slice(&tile_data[src_offset..src_offset + row_size]);
679        }
680
681        self.dirty = true;
682        Ok(())
683    }
684
685    /// Composites a tile onto the buffer with alpha blending
686    pub fn composite_tile(
687        &mut self,
688        tile_data: &[u8],
689        tile_width: u32,
690        tile_height: u32,
691        dst_x: u32,
692        dst_y: u32,
693        opacity: f32,
694    ) -> WasmResult<()> {
695        let expected_size = (tile_width * tile_height * 4) as usize;
696        if tile_data.len() != expected_size {
697            return Err(WasmError::Canvas(CanvasError::BufferSizeMismatch {
698                expected: expected_size,
699                actual: tile_data.len(),
700            }));
701        }
702
703        // Clamp opacity to valid range
704        let opacity = opacity.clamp(0.0, 1.0);
705
706        // Calculate intersection with buffer bounds
707        let max_x = (dst_x + tile_width).min(self.width);
708        let max_y = (dst_y + tile_height).min(self.height);
709
710        if dst_x >= self.width || dst_y >= self.height {
711            return Ok(()); // Tile is completely outside buffer
712        }
713
714        // Composite each pixel with alpha blending
715        for y in dst_y..max_y {
716            for x in dst_x..max_x {
717                let src_idx = (((y - dst_y) * tile_width + (x - dst_x)) * 4) as usize;
718                let dst_idx = ((y * self.width + x) * 4) as usize;
719
720                if src_idx + 3 < tile_data.len() && dst_idx + 3 < self.data.len() {
721                    let src_alpha = (f32::from(tile_data[src_idx + 3]) / 255.0) * opacity;
722                    let dst_alpha = 1.0 - src_alpha;
723
724                    // Alpha blending
725                    for c in 0..3 {
726                        let src_val = f32::from(tile_data[src_idx + c]);
727                        let dst_val = f32::from(self.data[dst_idx + c]);
728                        let blended = (src_val * src_alpha + dst_val * dst_alpha).clamp(0.0, 255.0);
729                        self.data[dst_idx + c] = blended as u8;
730                    }
731
732                    // Composite alpha
733                    let new_alpha = (src_alpha
734                        + dst_alpha * f32::from(self.data[dst_idx + 3]) / 255.0)
735                        .clamp(0.0, 1.0);
736                    self.data[dst_idx + 3] = (new_alpha * 255.0) as u8;
737                }
738            }
739        }
740
741        self.dirty = true;
742        Ok(())
743    }
744
745    /// Returns the buffer data
746    pub fn data(&self) -> &[u8] {
747        &self.data
748    }
749
750    /// Marks the buffer as clean
751    pub fn mark_clean(&mut self) {
752        self.dirty = false;
753    }
754
755    /// Checks if the buffer is dirty
756    pub const fn is_dirty(&self) -> bool {
757        self.dirty
758    }
759
760    /// Returns the buffer dimensions
761    pub const fn dimensions(&self) -> (u32, u32) {
762        (self.width, self.height)
763    }
764
765    /// Creates ImageData from the buffer
766    pub fn to_image_data(&self) -> Result<ImageData, JsValue> {
767        let clamped = wasm_bindgen::Clamped(self.data.as_slice());
768        ImageData::new_with_u8_clamped_array_and_sh(clamped, self.width, self.height)
769    }
770}
771
772/// Progressive rendering manager
773pub struct ProgressiveRenderer {
774    /// Render queue (tiles sorted by priority)
775    queue: Vec<(TileCoord, u32)>, // (coord, priority)
776    /// Tiles currently being rendered
777    rendering: Vec<TileCoord>,
778    /// Completed tiles
779    completed: Vec<TileCoord>,
780    /// Maximum parallel renders
781    max_parallel: usize,
782}
783
784impl ProgressiveRenderer {
785    /// Creates a new progressive renderer
786    pub fn new(max_parallel: usize) -> Self {
787        Self {
788            queue: Vec::new(),
789            rendering: Vec::new(),
790            completed: Vec::new(),
791            max_parallel,
792        }
793    }
794
795    /// Adds tiles to the render queue
796    pub fn add_tiles(&mut self, tiles: Vec<TileCoord>, priority: u32) {
797        for coord in tiles {
798            if !self.is_completed(&coord) && !self.is_rendering(&coord) {
799                self.queue.push((coord, priority));
800            }
801        }
802
803        // Sort by priority (higher priority first)
804        self.queue.sort_by_key(|x| std::cmp::Reverse(x.1));
805    }
806
807    /// Gets the next tiles to render
808    pub fn next_batch(&mut self) -> Vec<TileCoord> {
809        let available = self.max_parallel.saturating_sub(self.rendering.len());
810        let mut batch = Vec::new();
811
812        for _ in 0..available {
813            if let Some((coord, _)) = self.queue.pop() {
814                self.rendering.push(coord);
815                batch.push(coord);
816            } else {
817                break;
818            }
819        }
820
821        batch
822    }
823
824    /// Marks a tile as completed
825    pub fn mark_completed(&mut self, coord: TileCoord) {
826        if let Some(pos) = self.rendering.iter().position(|c| *c == coord) {
827            self.rendering.remove(pos);
828            self.completed.push(coord);
829        }
830    }
831
832    /// Checks if a tile is completed
833    pub fn is_completed(&self, coord: &TileCoord) -> bool {
834        self.completed.contains(coord)
835    }
836
837    /// Checks if a tile is rendering
838    pub fn is_rendering(&self, coord: &TileCoord) -> bool {
839        self.rendering.contains(coord)
840    }
841
842    /// Clears all state
843    pub fn clear(&mut self) {
844        self.queue.clear();
845        self.rendering.clear();
846        self.completed.clear();
847    }
848
849    /// Returns rendering statistics
850    pub fn stats(&self) -> ProgressiveRenderStats {
851        ProgressiveRenderStats {
852            queued: self.queue.len(),
853            rendering: self.rendering.len(),
854            completed: self.completed.len(),
855        }
856    }
857}
858
859/// Progressive rendering statistics
860#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
861pub struct ProgressiveRenderStats {
862    /// Number of tiles queued
863    pub queued: usize,
864    /// Number of tiles rendering
865    pub rendering: usize,
866    /// Number of tiles completed
867    pub completed: usize,
868}
869
870impl ProgressiveRenderStats {
871    /// Returns the total number of tiles
872    pub const fn total(&self) -> usize {
873        self.queued + self.rendering + self.completed
874    }
875
876    /// Returns the completion percentage
877    pub fn completion_percentage(&self) -> f64 {
878        let total = self.total();
879        if total == 0 {
880            100.0
881        } else {
882            (self.completed as f64 / total as f64) * 100.0
883        }
884    }
885}
886
887/// Canvas renderer with double buffering and progressive rendering
888pub struct CanvasRenderer {
889    /// Front buffer
890    front_buffer: CanvasBuffer,
891    /// Back buffer
892    back_buffer: CanvasBuffer,
893    /// Progressive renderer
894    progressive: ProgressiveRenderer,
895    /// Viewport state
896    viewport: ViewportState,
897    /// Viewport history
898    history: ViewportHistory,
899    /// Render quality
900    quality: RenderQuality,
901}
902
903impl CanvasRenderer {
904    /// Creates a new canvas renderer
905    pub fn new(width: u32, height: u32, max_parallel: usize) -> WasmResult<Self> {
906        Ok(Self {
907            front_buffer: CanvasBuffer::new(width, height)?,
908            back_buffer: CanvasBuffer::new(width, height)?,
909            progressive: ProgressiveRenderer::new(max_parallel),
910            viewport: ViewportState::new(width, height),
911            history: ViewportHistory::new(MAX_VIEWPORT_HISTORY),
912            quality: RenderQuality::Medium,
913        })
914    }
915
916    /// Resizes the renderer
917    pub fn resize(&mut self, width: u32, height: u32) -> WasmResult<()> {
918        self.front_buffer.resize(width, height)?;
919        self.back_buffer.resize(width, height)?;
920        self.viewport.canvas_width = width;
921        self.viewport.canvas_height = height;
922        Ok(())
923    }
924
925    /// Begins a new frame
926    pub fn begin_frame(&mut self) {
927        self.back_buffer.clear();
928    }
929
930    /// Draws a tile to the back buffer
931    pub fn draw_tile(
932        &mut self,
933        coord: TileCoord,
934        tile_data: &[u8],
935        tile_width: u32,
936        tile_height: u32,
937    ) -> WasmResult<()> {
938        // Transform tile coordinates to screen coordinates
939        let world_x = f64::from(coord.x * tile_width);
940        let world_y = f64::from(coord.y * tile_height);
941        let (screen_x, screen_y) = self.viewport.transform.transform_point(world_x, world_y);
942
943        self.back_buffer.draw_tile(
944            tile_data,
945            screen_x as u32,
946            screen_y as u32,
947            tile_width,
948            tile_height,
949        )?;
950
951        self.progressive.mark_completed(coord);
952        Ok(())
953    }
954
955    /// Swaps the buffers
956    pub fn swap_buffers(&mut self) {
957        std::mem::swap(&mut self.front_buffer, &mut self.back_buffer);
958        self.front_buffer.mark_clean();
959    }
960
961    /// Returns the front buffer as ImageData
962    pub fn front_buffer_image_data(&self) -> Result<ImageData, JsValue> {
963        self.front_buffer.to_image_data()
964    }
965
966    /// Updates the viewport
967    pub fn update_viewport(&mut self, state: ViewportState) {
968        self.history.push(self.viewport.clone());
969        self.viewport = state;
970    }
971
972    /// Pans the viewport
973    pub fn pan(&mut self, dx: f64, dy: f64) {
974        let old_state = self.viewport.clone();
975        self.viewport.pan(dx, dy);
976        if old_state != self.viewport {
977            self.history.push(old_state);
978        }
979    }
980
981    /// Zooms the viewport
982    pub fn zoom(&mut self, factor: f64, center_x: f64, center_y: f64) {
983        let old_state = self.viewport.clone();
984        self.viewport.zoom(factor, center_x, center_y);
985        if old_state != self.viewport {
986            self.history.push(old_state);
987        }
988    }
989
990    /// Undoes the last viewport change
991    pub fn undo(&mut self) -> bool {
992        if let Some(state) = self.history.undo() {
993            self.viewport = state.clone();
994            true
995        } else {
996            false
997        }
998    }
999
1000    /// Redoes the next viewport change
1001    pub fn redo(&mut self) -> bool {
1002        if let Some(state) = self.history.redo() {
1003            self.viewport = state.clone();
1004            true
1005        } else {
1006            false
1007        }
1008    }
1009
1010    /// Sets the render quality
1011    pub fn set_quality(&mut self, quality: RenderQuality) {
1012        self.quality = quality;
1013    }
1014
1015    /// Returns the current viewport state
1016    pub const fn viewport(&self) -> &ViewportState {
1017        &self.viewport
1018    }
1019
1020    /// Returns progressive rendering statistics
1021    pub fn progressive_stats(&self) -> ProgressiveRenderStats {
1022        self.progressive.stats()
1023    }
1024}
1025
1026/// Animation frame manager
1027pub struct AnimationManager {
1028    /// Frame times (in milliseconds)
1029    frame_times: VecDeque<f64>,
1030    /// Maximum frame time samples
1031    max_samples: usize,
1032    /// Last frame timestamp
1033    last_frame: Option<f64>,
1034    /// Target FPS
1035    target_fps: f64,
1036}
1037
1038impl AnimationManager {
1039    /// Creates a new animation manager
1040    pub fn new(target_fps: f64) -> Self {
1041        Self {
1042            frame_times: VecDeque::new(),
1043            max_samples: 60,
1044            last_frame: None,
1045            target_fps,
1046        }
1047    }
1048
1049    /// Records a frame
1050    pub fn record_frame(&mut self, timestamp: f64) {
1051        if let Some(last) = self.last_frame {
1052            let frame_time = timestamp - last;
1053            self.frame_times.push_back(frame_time);
1054
1055            if self.frame_times.len() > self.max_samples {
1056                self.frame_times.pop_front();
1057            }
1058        }
1059
1060        self.last_frame = Some(timestamp);
1061    }
1062
1063    /// Returns the current FPS
1064    pub fn current_fps(&self) -> f64 {
1065        if self.frame_times.is_empty() {
1066            return 0.0;
1067        }
1068
1069        let avg_frame_time: f64 =
1070            self.frame_times.iter().sum::<f64>() / self.frame_times.len() as f64;
1071        if avg_frame_time > 0.0 {
1072            1000.0 / avg_frame_time
1073        } else {
1074            0.0
1075        }
1076    }
1077
1078    /// Returns the average frame time in milliseconds
1079    pub fn average_frame_time(&self) -> f64 {
1080        if self.frame_times.is_empty() {
1081            return 0.0;
1082        }
1083
1084        self.frame_times.iter().sum::<f64>() / self.frame_times.len() as f64
1085    }
1086
1087    /// Checks if the frame rate is below target
1088    pub fn is_below_target(&self) -> bool {
1089        self.current_fps() < self.target_fps
1090    }
1091
1092    /// Returns frame statistics
1093    pub fn stats(&self) -> AnimationStats {
1094        AnimationStats {
1095            current_fps: self.current_fps(),
1096            average_frame_time_ms: self.average_frame_time(),
1097            target_fps: self.target_fps,
1098        }
1099    }
1100}
1101
1102/// Animation statistics
1103#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
1104pub struct AnimationStats {
1105    /// Current FPS
1106    pub current_fps: f64,
1107    /// Average frame time in milliseconds
1108    pub average_frame_time_ms: f64,
1109    /// Target FPS
1110    pub target_fps: f64,
1111}
1112
1113#[cfg(test)]
1114mod tests {
1115    use super::*;
1116
1117    #[test]
1118    fn test_viewport_transform() {
1119        let transform = ViewportTransform::translate(10.0, 20.0);
1120        let (x, y) = transform.transform_point(0.0, 0.0);
1121        assert_eq!(x, 10.0);
1122        assert_eq!(y, 20.0);
1123    }
1124
1125    #[test]
1126    fn test_viewport_transform_inverse() {
1127        let transform = ViewportTransform::translate(10.0, 20.0);
1128        let (x, y) = transform.inverse_transform_point(10.0, 20.0);
1129        assert!((x - 0.0).abs() < 0.001);
1130        assert!((y - 0.0).abs() < 0.001);
1131    }
1132
1133    #[test]
1134    fn test_viewport_state() {
1135        let mut state = ViewportState::new(800, 600);
1136        state.pan(10.0, 20.0);
1137        assert_eq!(state.transform.tx, 10.0);
1138        assert_eq!(state.transform.ty, 20.0);
1139    }
1140
1141    #[test]
1142    fn test_viewport_history() {
1143        let mut history = ViewportHistory::new(10);
1144        let state1 = ViewportState::new(800, 600);
1145        let mut state2 = ViewportState::new(800, 600);
1146        state2.pan(10.0, 20.0);
1147
1148        history.push(state1);
1149        history.push(state2);
1150
1151        assert!(history.can_undo());
1152        history.undo();
1153        assert!(history.can_redo());
1154    }
1155
1156    #[test]
1157    fn test_canvas_buffer() {
1158        let mut buffer = CanvasBuffer::new(256, 256).expect("Failed to create buffer");
1159        assert!(buffer.is_dirty());
1160
1161        buffer.mark_clean();
1162        assert!(!buffer.is_dirty());
1163
1164        buffer.clear();
1165        assert!(buffer.is_dirty());
1166    }
1167
1168    #[test]
1169    fn test_progressive_renderer() {
1170        let mut renderer = ProgressiveRenderer::new(4);
1171        let tiles = vec![
1172            TileCoord::new(0, 0, 0),
1173            TileCoord::new(0, 1, 0),
1174            TileCoord::new(0, 0, 1),
1175        ];
1176
1177        renderer.add_tiles(tiles, 10);
1178        let batch = renderer.next_batch();
1179        assert!(!batch.is_empty());
1180        assert!(batch.len() <= 4);
1181    }
1182
1183    #[test]
1184    fn test_animation_manager() {
1185        let mut manager = AnimationManager::new(60.0);
1186        manager.record_frame(0.0);
1187        manager.record_frame(16.67); // ~60 FPS
1188        manager.record_frame(33.34);
1189
1190        let fps = manager.current_fps();
1191        assert!(fps > 50.0 && fps < 70.0);
1192    }
1193
1194    #[test]
1195    fn test_render_quality() {
1196        assert_eq!(RenderQuality::Low.resolution_multiplier(), 0.5);
1197        assert_eq!(RenderQuality::Medium.resolution_multiplier(), 1.0);
1198        assert_eq!(RenderQuality::High.resolution_multiplier(), 1.5);
1199        assert_eq!(RenderQuality::Ultra.resolution_multiplier(), 2.0);
1200    }
1201}