mirror of
https://github.com/servo/servo.git
synced 2025-08-09 15:35:34 +01:00
Make quadtrees generic
This commit is contained in:
parent
b0495eb7fd
commit
d590a7b45a
1 changed files with 120 additions and 179 deletions
|
@ -6,13 +6,14 @@
|
||||||
// been rasterized and which have not.
|
// been rasterized and which have not.
|
||||||
|
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::size::Size2D;
|
|
||||||
use geom::rect::Rect;
|
|
||||||
|
|
||||||
priv enum Quadtype {
|
pub struct Quadtree<T> {
|
||||||
Empty,
|
tile: Option<T>,
|
||||||
Base,
|
origin: Point2D<f32>,
|
||||||
Branch,
|
size: f32,
|
||||||
|
quadrants: [Option<~Quadtree<T>>, ..4],
|
||||||
|
scale: @mut f32,
|
||||||
|
max_tile_size: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
priv enum Quadrant {
|
priv enum Quadrant {
|
||||||
|
@ -22,206 +23,146 @@ priv enum Quadrant {
|
||||||
BR = 3,
|
BR = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Quadtree<T> {
|
||||||
pub struct Quadtree {
|
// Public method to create a new Quadtree
|
||||||
quadtype: Quadtype,
|
pub fn new(x: uint, y: uint, width: uint, height: uint, tile_size: uint, scale: @mut f32) -> Quadtree<T> {
|
||||||
rect: Rect<uint>,
|
if(*scale != 1.0) {
|
||||||
quadrants: [Option<~Quadtree>, ..4],
|
println("Warning: Quadtree: Quadtree initialized while zoomed; this action is unsupported.");
|
||||||
}
|
println("Please set zoom to 1.0 before creating the Quadtree.");
|
||||||
|
}
|
||||||
|
|
||||||
impl Quadtree {
|
// Spaces must be squares and powers of 2, so expand the space until it is
|
||||||
pub fn new(x: uint, y: uint, width: uint, height: uint) -> Quadtree {
|
let longer = width.max(&height);
|
||||||
|
let num_tiles = uint::div_ceil(longer, tile_size);
|
||||||
|
let power_of_two = uint::next_power_of_two(num_tiles);
|
||||||
|
let size = power_of_two * tile_size;
|
||||||
|
|
||||||
Quadtree {
|
Quadtree {
|
||||||
quadtype: Empty,
|
tile: None,
|
||||||
rect: Rect {
|
origin: Point2D(x as f32, y as f32),
|
||||||
origin: Point2D(x, y),
|
size: size as f32,
|
||||||
size: Size2D(width, height),
|
|
||||||
},
|
|
||||||
|
|
||||||
quadrants: [None, None, None, None],
|
quadrants: [None, None, None, None],
|
||||||
|
scale: scale,
|
||||||
|
max_tile_size: tile_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private method to create new children
|
||||||
|
fn new_child(&self, x: f32, y: f32, size: f32) -> Quadtree<T> {
|
||||||
|
Quadtree {
|
||||||
|
tile: None,
|
||||||
|
origin: Point2D(x, y),
|
||||||
|
size: size,
|
||||||
|
quadrants: [None, None, None, None],
|
||||||
|
scale: self.scale,
|
||||||
|
max_tile_size: self.max_tile_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine which child contains a given point
|
/// Determine which child contains a given point
|
||||||
priv fn get_quadrant(&self, x: uint, y: uint) -> Quadrant {
|
fn get_quadrant(&self, x: uint, y: uint) -> Quadrant {
|
||||||
let self_width = self.rect.size.width;
|
let self_x = (self.origin.x * *(self.scale)).ceil() as uint;
|
||||||
let self_height = self.rect.size.height;
|
let self_y = (self.origin.y * *(self.scale)).ceil() as uint;
|
||||||
let self_x = self.rect.origin.x;
|
let self_size = (self.size * *(self.scale)).ceil() as uint;
|
||||||
let self_y = self.rect.origin.y;
|
|
||||||
match (self_width, self_height) {
|
if x < self_x + self_size / 2 {
|
||||||
(1, _) => {
|
if y < self_y + self_size / 2 {
|
||||||
if y < self_y + self_height / 2 {
|
TL
|
||||||
TL
|
} else {
|
||||||
} else {
|
BL
|
||||||
BR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, 1) => {
|
|
||||||
if x < self_x + self_width / 2 {
|
|
||||||
TL
|
|
||||||
} else {
|
|
||||||
BR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if x < self_x + self_width / 2 {
|
|
||||||
if y < self_y + self_height / 2 {
|
|
||||||
TL
|
|
||||||
} else {
|
|
||||||
BL
|
|
||||||
}
|
|
||||||
} else if y < self_y + self_height / 2 {
|
|
||||||
TR
|
|
||||||
} else {
|
|
||||||
BR
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if y < self_y + self_size / 2 {
|
||||||
|
TR
|
||||||
|
} else {
|
||||||
|
BR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change a point from Empty to Base
|
|
||||||
pub fn add_region(&mut self, x: uint, y: uint) {
|
|
||||||
let self_x = self.rect.origin.x;
|
|
||||||
let self_y = self.rect.origin.y;
|
|
||||||
let self_width = self.rect.size.width;
|
|
||||||
let self_height = self.rect.size.height;
|
|
||||||
|
|
||||||
debug!("Quadtree: adding: (%?, %?) w:%?, h:%?", self_x, self_y, self_width, self_height);
|
/// Get the lowest-level (highest resolution) tile associated with a certain pixel
|
||||||
|
pub fn get_tile<'r> (&'r self, x: uint, y: uint) -> &'r Option<T> {
|
||||||
|
let self_x = (self.origin.x * *(self.scale)).ceil() as uint;
|
||||||
|
let self_y = (self.origin.y * *(self.scale)).ceil() as uint;
|
||||||
|
let self_size = (self.size * *(self.scale)).ceil() as uint;
|
||||||
|
|
||||||
if x >= self_x + self_width || x < self_x
|
if x >= self_x + self_size || x < self_x
|
||||||
|| y >= self_y + self_height || y < self_y {
|
|| y >= self_y + self_size || y < self_y {
|
||||||
return; // Out of bounds
|
fail!("Quadtree: Tried to get a tile outside of range");
|
||||||
}
|
|
||||||
match self.quadtype {
|
|
||||||
Base => return,
|
|
||||||
Empty => {
|
|
||||||
if self_width == 1 && self_height == 1 {
|
|
||||||
self.quadtype = Base;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.quadtype = Branch;
|
|
||||||
|
|
||||||
// Initialize children
|
|
||||||
self.quadrants[TL as int] = Some(~Quadtree::new(self_x,
|
|
||||||
self_y,
|
|
||||||
(self_width / 2).max(&1),
|
|
||||||
(self_height / 2).max(&1)));
|
|
||||||
if self_width > 1 && self_height > 1 {
|
|
||||||
self.quadrants[TR as int] = Some(~Quadtree::new(self_x + self_width / 2,
|
|
||||||
self_y,
|
|
||||||
self_width - self_width / 2,
|
|
||||||
self_height / 2));
|
|
||||||
self.quadrants[BL as int] = Some(~Quadtree::new(self_x,
|
|
||||||
self_y + self_height / 2,
|
|
||||||
self_width / 2,
|
|
||||||
self_height - self_height / 2));
|
|
||||||
}
|
|
||||||
self.quadrants[BR as int] = Some(~Quadtree::new(self_x + self_width / 2,
|
|
||||||
self_y + self_height / 2,
|
|
||||||
self_width - self_width / 2,
|
|
||||||
self_height - self_height / 2));
|
|
||||||
}
|
|
||||||
Branch => {} // Fall through
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've made it this far, we know we are a branch and therefore have children
|
let index = self.get_quadrant(x,y) as int;
|
||||||
let index = self.get_quadrant(x, y) as int;
|
|
||||||
|
|
||||||
match self.quadrants[index] {
|
match self.quadrants[index] {
|
||||||
None => fail!("Quadtree: child query failure"),
|
None => &'r self.tile,
|
||||||
Some(ref mut region) => {
|
Some(ref child) => child.get_tile(x, y),
|
||||||
// Recurse if necessary
|
}
|
||||||
match region.quadtype {
|
}
|
||||||
Empty | Branch => {
|
|
||||||
region.add_region(x, y);
|
|
||||||
}
|
/// Add a tile
|
||||||
Base => {} // nothing to do
|
pub fn add_tile(&mut self, x: uint, y: uint, tile: T) {
|
||||||
}
|
let self_x = (self.origin.x * *(self.scale)).ceil() as uint;
|
||||||
}
|
let self_y = (self.origin.y * *(self.scale)).ceil() as uint;
|
||||||
|
let self_size = (self.size * *(self.scale)).ceil() as uint;
|
||||||
|
|
||||||
|
debug!("Quadtree: Adding: (%?, %?) size:%?px", self_x, self_y, self_size);
|
||||||
|
|
||||||
|
if x >= self_x + self_size || x < self_x
|
||||||
|
|| y >= self_y + self_size || y < self_y {
|
||||||
|
fail!("Quadtree: Tried to add tile to invalid region");
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: ideally we could make the assignments in the match,
|
if self_size <= self.max_tile_size { // We are the child
|
||||||
// but borrowed pointers prevent that. So here's a flag instead.
|
self.tile = Some(tile);
|
||||||
let mut base_flag = 0;
|
for vec::each([TL, TR, BL, BR]) |quad| {
|
||||||
|
self.quadrants[*quad as int] = None;
|
||||||
// If all children are Bases, convert self to Base
|
|
||||||
match (&self.quadrants, self_width, self_height) {
|
|
||||||
(&[Some(ref tl_q), _, _, Some(ref br_q)], 1, _) |
|
|
||||||
(&[Some(ref tl_q), _, _, Some(ref br_q)], _, 1) => {
|
|
||||||
match(tl_q.quadtype, br_q.quadtype) {
|
|
||||||
(Base, Base) => {
|
|
||||||
base_flag = 1;
|
|
||||||
}
|
|
||||||
_ => {} // nothing to do
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(&[Some(ref tl_q), Some(ref tr_q), Some(ref bl_q), Some(ref br_q)], _, _) => {
|
} else { //send tile to children
|
||||||
match (tl_q.quadtype, tr_q.quadtype, bl_q.quadtype, br_q.quadtype) {
|
let quad = self.get_quadrant(x, y);
|
||||||
(Base, Base, Base, Base) => {
|
match self.quadrants[quad as int] {
|
||||||
base_flag = 2;
|
Some(ref mut child) => child.add_tile(x, y, tile),
|
||||||
|
None => { //make new child
|
||||||
|
let new_size = self.size / 2.0;
|
||||||
|
let new_x = match quad {
|
||||||
|
TL | BL => self.origin.x,
|
||||||
|
TR | BR => self.origin.x + new_size,
|
||||||
|
};
|
||||||
|
let new_y = match quad {
|
||||||
|
TL | TR => self.origin.y,
|
||||||
|
BL | BR => self.origin.y + new_size,
|
||||||
|
};
|
||||||
|
let mut c = ~self.new_child(new_x, new_y, new_size);
|
||||||
|
c.add_tile(x, y, tile);
|
||||||
|
self.quadrants[quad as int] = Some(c);
|
||||||
|
|
||||||
|
// If we have 4 children, we probably shouldn't be hanging onto a tile
|
||||||
|
// Though this isn't always true if we have grandchildren
|
||||||
|
match self.quadrants {
|
||||||
|
[Some(_), Some(_), Some(_), Some(_)] => {
|
||||||
|
self.tile = None;
|
||||||
}
|
}
|
||||||
_ => {} // nothing to do
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {} // nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
match base_flag {
|
|
||||||
0 => {}
|
|
||||||
1 => {
|
|
||||||
self.quadtype = Base;
|
|
||||||
self.quadrants[TL as int] = None;
|
|
||||||
self.quadrants[BR as int] = None;
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
self.quadtype = Base;
|
|
||||||
self.quadrants[TL as int] = None;
|
|
||||||
self.quadrants[TR as int] = None;
|
|
||||||
self.quadrants[BL as int] = None;
|
|
||||||
self.quadrants[BR as int] = None;
|
|
||||||
}
|
|
||||||
_ => fail!("Quadtree: Unknown flag type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a point is a Base or Empty.
|
|
||||||
pub fn check_region(&self, x: uint, y: uint) -> bool {
|
|
||||||
let self_x = self.rect.origin.x;
|
|
||||||
let self_y = self.rect.origin.y;
|
|
||||||
let self_width = self.rect.size.width;
|
|
||||||
let self_height = self.rect.size.height;
|
|
||||||
|
|
||||||
if x >= self_x + self_width || x < self_x
|
|
||||||
|| y >= self_y + self_height || y < self_y {
|
|
||||||
return false; // out of bounds
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.quadtype {
|
|
||||||
Empty => false,
|
|
||||||
Base => true,
|
|
||||||
Branch => {
|
|
||||||
let index = self.get_quadrant(x,y) as int;
|
|
||||||
match self.quadrants[index] {
|
|
||||||
None => fail!("Quadtree: child query failed"),
|
|
||||||
Some(ref region) => region.check_region(x, y)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_region() {
|
fn test_add_tile() {
|
||||||
let mut t = Quadtree::new(50, 50, 3, 4);
|
let scale = @mut 1.0;
|
||||||
assert!(!t.check_region(50, 50));
|
let mut t = Quadtree::new(50, 30, 20, 20, 10, scale);
|
||||||
t.add_region(50, 50);
|
assert!(t.get_tile(50, 30).is_none());
|
||||||
assert!(t.check_region(50, 50));
|
t.add_tile(50, 30, 1);
|
||||||
assert!(!t.check_region(51, 50));
|
assert!(t.get_tile(50, 30).get() == 1);
|
||||||
assert!(!t.check_region(50, 51));
|
assert!(t.get_tile(59, 39).get() == 1);
|
||||||
t.add_region(53, 50);
|
assert!(t.get_tile(60, 40).is_none());
|
||||||
assert!(!t.check_region(53, 50));
|
*scale = 2.0;
|
||||||
|
assert!(t.get_tile(110, 70).get() == 1);
|
||||||
|
t.add_tile(100, 60, 2);
|
||||||
|
assert!(t.get_tile(109, 69).get() == 2);
|
||||||
|
assert!(t.get_tile(110, 70).get() == 1);
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue