tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

rectangle_occlusion.rs (7526B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 //! A simple occlusion culling algorithm for axis-aligned rectangles.
      6 //!
      7 //! ## Output
      8 //!
      9 //! Occlusion culling results in two lists of rectangles:
     10 //! 
     11 //! - The opaque list should be rendered first. None of its rectangles overlap so order doesn't matter
     12 //!   within the opaque pass.
     13 //! - The non-opaque list (or alpha list) which should be rendered in back-to-front order after the opaque pass.
     14 //!
     15 //! The output has minimal overdraw (no overdraw at all for opaque items and as little as possible for alpha ones).
     16 //!
     17 //! ## Algorithm overview
     18 //!
     19 //! The occlusion culling algorithm works in front-to-back order, accumulating rectangle in opaque and non-opaque lists.
     20 //! Each time a rectangle is added, it is first tested against existing opaque rectangles and potentially split into visible
     21 //! sub-rectangles, or even discarded completely. The front-to-back order ensures that once a rectangle is added it does not
     22 //! have to be modified again, making the underlying data structure trivial (append-only).
     23 //!
     24 //! ## splitting
     25 //!
     26 //! Partially visible rectangles are split into up to 4 visible sub-rectangles by each intersecting occluder.
     27 //!
     28 //! ```ascii
     29 //!  +----------------------+       +----------------------+
     30 //!  | rectangle            |       |                      |
     31 //!  |                      |       |                      |
     32 //!  |  +-----------+       |       +--+-----------+-------+
     33 //!  |  |occluder   |       |  -->  |  |\\\\\\\\\\\|       |
     34 //!  |  +-----------+       |       +--+-----------+-------+
     35 //!  |                      |       |                      |
     36 //!  +----------------------+       +----------------------+
     37 //! ```
     38 //!
     39 //! In the example above the rectangle is split into 4 visible parts with the central occluded part left out.
     40 //!
     41 //! This implementation favors longer horizontal bands instead creating nine-patches to deal with the corners.
     42 //! The advantage is that it produces less rectangles which is good for the performance of the algorithm and
     43 //! for SWGL which likes long horizontal spans, however it would cause artifacts if the resulting rectangles
     44 //! were to be drawn with a non-axis-aligned transformation.
     45 //!
     46 //! ## Performance
     47 //!
     48 //! The cost of the algorithm grows with the number of opaque rectangle as each new rectangle is tested against
     49 //! all previously added opaque rectangles.
     50 //!
     51 //! Note that opaque rectangles can either be added as opaque or non-opaque. This means a trade-off between
     52 //! overdraw and number of rectangles can be explored to adjust performance: Small opaque rectangles, especially
     53 //! towards the front of the scene, could be added as non-opaque to avoid causing many splits while adding only 
     54 //! a small amount of overdraw.
     55 //!
     56 //! This implementation is intended to be used with a small number of (opaque) items. A similar implementation
     57 //! could use a spatial acceleration structure for opaque rectangles to perform better with a large amount of
     58 //! occluders.
     59 //!
     60 
     61 use euclid::point2;
     62 use smallvec::SmallVec;
     63 use api::units::*;
     64 
     65 /// A visible part of a rectangle after occlusion culling.
     66 #[derive(Debug, PartialEq)]
     67 pub struct Item<K> {
     68    pub rectangle: DeviceBox2D,
     69    pub key: K,
     70 }
     71 
     72 /// A builder that applies occlusion culling with rectangles provided in front-to-back order.
     73 pub struct FrontToBackBuilder<K> {
     74    opaque_items: Vec<Item<K>>,
     75    alpha_items: Vec<Item<K>>,
     76 }
     77 
     78 impl<K> FrontToBackBuilder<K> where K: Copy {
     79 
     80    /// Pre-allocating constructor.
     81    pub fn with_capacity(opaque: usize, alpha: usize) -> Self {
     82        FrontToBackBuilder {
     83            opaque_items: Vec::with_capacity(opaque),
     84            alpha_items: Vec::with_capacity(alpha),
     85        }
     86    }
     87 
     88    /// Add a rectangle, potentially splitting it and discarding the occluded parts if any.
     89    ///
     90    /// Returns true the rectangle is at least partially visible.
     91    pub fn add(&mut self, rect: &DeviceBox2D, is_opaque: bool, key: K) -> bool {
     92        let mut fragments: SmallVec<[DeviceBox2D; 16]> = SmallVec::new();
     93        fragments.push(*rect);
     94 
     95        for item in &self.opaque_items {
     96            if fragments.is_empty() {
     97                break;
     98            }
     99            if item.rectangle.intersects(rect) {
    100                apply_occluder(&item.rectangle, &mut fragments);
    101            }
    102        }
    103 
    104        let list = if is_opaque {
    105            &mut self.opaque_items
    106        } else {
    107            &mut self.alpha_items
    108        };
    109 
    110        for rect in &fragments {
    111            list.push(Item {
    112                rectangle: *rect,
    113                key,
    114            });
    115        }
    116 
    117        !fragments.is_empty()
    118    }
    119 
    120    /// Returns true if the provided rect is at least partially visible, without adding it.
    121    pub fn test(&self, rect: &DeviceBox2D) -> bool {
    122        let mut fragments: SmallVec<[DeviceBox2D; 16]> = SmallVec::new();
    123        fragments.push(*rect);
    124 
    125        for item in &self.opaque_items {
    126            if item.rectangle.intersects(rect) {
    127                apply_occluder(&item.rectangle, &mut fragments);
    128            }
    129        }
    130 
    131        !fragments.is_empty()
    132    }
    133 
    134    /// The visible opaque rectangles (front-to-back order).
    135    pub fn opaque_items(&self) -> &[Item<K>] {
    136        &self.opaque_items
    137    }
    138 
    139    /// The visible non-opaque rectangles (front-to-back order).
    140    pub fn alpha_items(&self) -> &[Item<K>] {
    141        &self.alpha_items
    142    }
    143 }
    144 
    145 
    146 // Split out the parts of the rects in the provided vector
    147 fn apply_occluder(occluder: &DeviceBox2D, rects: &mut SmallVec<[DeviceBox2D; 16]>) {
    148    // Iterate in reverse order so that we can push new rects at the back without
    149    // visiting them;
    150    let mut i = rects.len() - 1;
    151    loop {
    152        let r = rects[i];
    153 
    154        if r.intersects(occluder) {
    155            let top = r.min.y < occluder.min.y;
    156            let bottom = r.max.y > occluder.max.y;
    157            let left = r.min.x < occluder.min.x;
    158            let right = r.max.x > occluder.max.x;
    159 
    160            if top {
    161                rects.push(DeviceBox2D {
    162                    min: r.min,
    163                    max: point2(r.max.x, occluder.min.y),
    164                });
    165            }
    166 
    167            if bottom {
    168                rects.push(DeviceBox2D {
    169                    min: point2(r.min.x, occluder.max.y),
    170                    max: r.max,
    171                });
    172            }
    173 
    174            if left {
    175                let min_y = r.min.y.max(occluder.min.y);
    176                let max_y = r.max.y.min(occluder.max.y);
    177                rects.push(DeviceBox2D {
    178                    min: point2(r.min.x, min_y),
    179                    max: point2(occluder.min.x, max_y),
    180                });
    181            }
    182 
    183            if right {
    184                let min_y = r.min.y.max(occluder.min.y);
    185                let max_y = r.max.y.min(occluder.max.y);
    186                rects.push(DeviceBox2D {
    187                    min: point2(occluder.max.x, min_y),
    188                    max: point2(r.max.x, max_y),
    189                });
    190            }
    191 
    192            // Remove the original rectangle, replacing it with
    193            // one of the new ones we just added, or popping it
    194            // if it is the last item.
    195            if i == rects.len() {
    196                rects.pop();
    197            } else {
    198                rects.swap_remove(i);
    199            }
    200        }
    201 
    202        if i == 0 {
    203            break;
    204        }
    205 
    206        i -= 1;
    207    }
    208 }