mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
auto merge of #557 : eschweic/servo/master, r=pcwalton
The compositor now keeps track of what has been rendered and what needs rendering based on the window rect, and asks the renderer for tiles only when needed.
This commit is contained in:
commit
4cf1ac9e69
4 changed files with 722 additions and 316 deletions
|
@ -11,7 +11,6 @@ use servo_msg::compositor_msg::{RenderListener, IdleRenderState, RenderingRender
|
|||
use servo_msg::compositor_msg::{LayerBufferSet};
|
||||
use font_context::FontContext;
|
||||
use geom::matrix2d::Matrix2D;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use geom::rect::Rect;
|
||||
use opts::Opts;
|
||||
|
@ -19,7 +18,6 @@ use render_context::RenderContext;
|
|||
|
||||
use std::cell::Cell;
|
||||
use std::comm::{Chan, Port, SharedChan};
|
||||
use std::uint;
|
||||
|
||||
use servo_util::time::{ProfilerChan, profile};
|
||||
use servo_util::time;
|
||||
|
@ -33,12 +31,28 @@ pub struct RenderLayer {
|
|||
|
||||
pub enum Msg {
|
||||
RenderMsg(RenderLayer),
|
||||
ReRenderMsg(f32),
|
||||
ReRenderMsg(~[BufferRequest], f32),
|
||||
PaintPermissionGranted,
|
||||
PaintPermissionRevoked,
|
||||
ExitMsg(Chan<()>),
|
||||
}
|
||||
|
||||
/// A request from the compositor to the renderer for tiles that need to be (re)displayed.
|
||||
pub struct BufferRequest {
|
||||
// The rect in pixels that will be drawn to the screen
|
||||
screen_rect: Rect<uint>,
|
||||
|
||||
// The rect in page coordinates that this tile represents
|
||||
page_rect: Rect<f32>,
|
||||
}
|
||||
|
||||
pub fn BufferRequest(screen_rect: Rect<uint>, page_rect: Rect<f32>) -> BufferRequest {
|
||||
BufferRequest {
|
||||
screen_rect: screen_rect,
|
||||
page_rect: page_rect,
|
||||
}
|
||||
}
|
||||
|
||||
#[deriving(Clone)]
|
||||
pub struct RenderChan {
|
||||
chan: SharedChan<Msg>,
|
||||
|
@ -119,18 +133,19 @@ impl<C: RenderListener + Send> RenderTask<C> {
|
|||
loop {
|
||||
match self.port.recv() {
|
||||
RenderMsg(render_layer) => {
|
||||
self.render_layer = Some(render_layer);
|
||||
self.render(1.0);
|
||||
if self.paint_permission {
|
||||
self.compositor.new_layer(render_layer.size, self.opts.tile_size);
|
||||
}
|
||||
ReRenderMsg(scale) => {
|
||||
self.render(scale);
|
||||
self.render_layer = Some(render_layer);
|
||||
}
|
||||
ReRenderMsg(tiles, scale) => {
|
||||
self.render(tiles, scale);
|
||||
}
|
||||
PaintPermissionGranted => {
|
||||
self.paint_permission = true;
|
||||
match self.last_paint_msg {
|
||||
Some((ref layer_buffer_set, layer_size)) => {
|
||||
self.compositor.paint(self.id, layer_buffer_set.clone(), layer_size);
|
||||
self.compositor.set_render_state(IdleRenderState);
|
||||
match self.render_layer {
|
||||
Some(ref render_layer) => {
|
||||
self.compositor.new_layer(render_layer.size, self.opts.tile_size);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
@ -146,50 +161,39 @@ impl<C: RenderListener + Send> RenderTask<C> {
|
|||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, scale: f32) {
|
||||
debug!("render_task: rendering");
|
||||
|
||||
fn render(&mut self, tiles: ~[BufferRequest], scale: f32) {
|
||||
let render_layer;
|
||||
match (self.render_layer) {
|
||||
None => return,
|
||||
match self.render_layer {
|
||||
Some(ref r_layer) => {
|
||||
render_layer = r_layer;
|
||||
}
|
||||
_ => return, // nothing to do
|
||||
}
|
||||
|
||||
self.compositor.set_render_state(RenderingRenderState);
|
||||
do time::profile(time::RenderingCategory, self.profiler_chan.clone()) {
|
||||
let tile_size = self.opts.tile_size;
|
||||
|
||||
// FIXME: Try not to create a new array here.
|
||||
let mut new_buffers = ~[];
|
||||
|
||||
// Divide up the layer into tiles.
|
||||
do time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone()) {
|
||||
let mut y = 0;
|
||||
while y < (render_layer.size.height as f32 * scale).ceil() as uint {
|
||||
let mut x = 0;
|
||||
while x < (render_layer.size.width as f32 * scale).ceil() as uint {
|
||||
// Figure out the dimension of this tile.
|
||||
let right = uint::min(x + tile_size, (render_layer.size.width as f32 * scale).ceil() as uint);
|
||||
let bottom = uint::min(y + tile_size, (render_layer.size.height as f32 * scale).ceil() as uint);
|
||||
let width = right - x;
|
||||
let height = bottom - y;
|
||||
|
||||
let tile_rect = Rect(Point2D(x as f32 / scale, y as f32 / scale), Size2D(width as f32, height as f32));
|
||||
let screen_rect = Rect(Point2D(x, y), Size2D(width, height));
|
||||
for tiles.iter().advance |tile| {
|
||||
let width = tile.screen_rect.size.width;
|
||||
let height = tile.screen_rect.size.height;
|
||||
|
||||
let buffer = LayerBuffer {
|
||||
draw_target: DrawTarget::new_with_fbo(self.opts.render_backend,
|
||||
self.share_gl_context,
|
||||
Size2D(width as i32,
|
||||
height as i32),
|
||||
Size2D(width as i32, height as i32),
|
||||
B8G8R8A8),
|
||||
rect: tile_rect,
|
||||
screen_pos: screen_rect,
|
||||
rect: tile.page_rect,
|
||||
screen_pos: tile.screen_rect,
|
||||
resolution: scale,
|
||||
stride: (width * 4) as uint
|
||||
};
|
||||
|
||||
|
||||
{
|
||||
// Build the render context.
|
||||
let ctx = RenderContext {
|
||||
|
@ -218,11 +222,8 @@ impl<C: RenderListener + Send> RenderTask<C> {
|
|||
|
||||
new_buffers.push(buffer);
|
||||
|
||||
x += tile_size;
|
||||
}
|
||||
|
||||
y += tile_size;
|
||||
}
|
||||
}
|
||||
|
||||
let layer_buffer_set = LayerBufferSet {
|
||||
|
|
|
@ -10,7 +10,7 @@ use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClick
|
|||
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent};
|
||||
|
||||
|
||||
use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState};
|
||||
use servo_msg::compositor_msg::{RenderListener, LayerBuffer, LayerBufferSet, RenderState};
|
||||
use servo_msg::compositor_msg::{ReadyState, ScriptListener};
|
||||
use servo_msg::constellation_msg::{CompositorAck, ConstellationChan};
|
||||
use servo_msg::constellation_msg;
|
||||
|
@ -28,6 +28,7 @@ use extra::timer;
|
|||
use geom::matrix::identity;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use geom::rect::Rect;
|
||||
use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format};
|
||||
use layers::layers::{ImageData, WithDataFn};
|
||||
use layers::layers::{TextureLayerKind, TextureLayer, TextureManager};
|
||||
|
@ -40,6 +41,10 @@ use servo_util::time::ProfilerChan;
|
|||
use extra::arc;
|
||||
pub use windowing;
|
||||
|
||||
use extra::time::precise_time_s;
|
||||
use compositing::quadtree::Quadtree;
|
||||
mod quadtree;
|
||||
|
||||
/// The implementation of the layers-based compositor.
|
||||
#[deriving(Clone)]
|
||||
pub struct CompositorChan {
|
||||
|
@ -70,6 +75,16 @@ impl RenderListener for CompositorChan {
|
|||
self.chan.send(Paint(id, layer_buffer_set, new_size))
|
||||
}
|
||||
|
||||
fn new_layer(&self, page_size: Size2D<uint>, tile_size: uint) {
|
||||
self.chan.send(NewLayer(page_size, tile_size))
|
||||
}
|
||||
fn resize_layer(&self, page_size: Size2D<uint>) {
|
||||
self.chan.send(ResizeLayer(page_size))
|
||||
}
|
||||
fn delete_layer(&self) {
|
||||
self.chan.send(DeleteLayer)
|
||||
}
|
||||
|
||||
fn set_render_state(&self, render_state: RenderState) {
|
||||
self.chan.send(ChangeRenderState(render_state))
|
||||
}
|
||||
|
@ -102,6 +117,15 @@ pub enum Msg {
|
|||
GetSize(Chan<Size2D<int>>),
|
||||
/// Requests the compositors GL context.
|
||||
GetGLContext(Chan<AzGLContext>),
|
||||
|
||||
// TODO: Attach layer ids and epochs to these messages
|
||||
/// Alerts the compositor that there is a new layer to be rendered.
|
||||
NewLayer(Size2D<uint>, uint),
|
||||
/// Alerts the compositor that the current layer has changed size.
|
||||
ResizeLayer(Size2D<uint>),
|
||||
/// Alerts the compositor that the current layer has been deleted.
|
||||
DeleteLayer,
|
||||
|
||||
/// Requests that the compositor paint the given layer buffer set for the given page size.
|
||||
Paint(uint, arc::ARC<LayerBufferSet>, Size2D<uint>),
|
||||
/// Alerts the compositor to the current status of page loading.
|
||||
|
@ -199,9 +223,106 @@ impl CompositorTask {
|
|||
let local_zoom = @mut 1f32;
|
||||
// Channel to the current renderer.
|
||||
// FIXME: This probably shouldn't be stored like this.
|
||||
|
||||
let render_chan: @mut Option<RenderChan> = @mut None;
|
||||
let pipeline_id: @mut Option<uint> = @mut None;
|
||||
|
||||
// Quadtree for this layer
|
||||
// FIXME: This should be one-per-layer
|
||||
let quadtree: @mut Option<Quadtree<~LayerBuffer>> = @mut None;
|
||||
|
||||
// Keeps track of if we have performed a zoom event and how recently.
|
||||
let zoom_action = @mut false;
|
||||
let zoom_time = @mut 0f;
|
||||
|
||||
// Extract tiles from the given quadtree and build and display the render tree.
|
||||
let build_layer_tree: @fn(&Quadtree<~LayerBuffer>) = |quad: &Quadtree<~LayerBuffer>| {
|
||||
// Iterate over the children of the container layer.
|
||||
let mut current_layer_child = root_layer.first_child;
|
||||
|
||||
let all_tiles = quad.get_all_tiles();
|
||||
for all_tiles.iter().advance |buffer| {
|
||||
let width = buffer.screen_pos.size.width as uint;
|
||||
let height = buffer.screen_pos.size.height as uint;
|
||||
debug!("osmain: compositing buffer rect %?", &buffer.rect);
|
||||
|
||||
// Find or create a texture layer.
|
||||
let texture_layer;
|
||||
current_layer_child = match current_layer_child {
|
||||
None => {
|
||||
debug!("osmain: adding new texture layer");
|
||||
texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager,
|
||||
buffer.screen_pos.size);
|
||||
root_layer.add_child(TextureLayerKind(texture_layer));
|
||||
None
|
||||
}
|
||||
Some(TextureLayerKind(existing_texture_layer)) => {
|
||||
texture_layer = existing_texture_layer;
|
||||
texture_layer.manager = @buffer.draw_target.clone() as @TextureManager;
|
||||
|
||||
// Move on to the next sibling.
|
||||
do current_layer_child.get().with_common |common| {
|
||||
common.next_sibling
|
||||
}
|
||||
}
|
||||
Some(_) => fail!(~"found unexpected layer kind"),
|
||||
};
|
||||
|
||||
let origin = buffer.rect.origin;
|
||||
let origin = Point2D(origin.x as f32, origin.y as f32);
|
||||
|
||||
// Set the layer's transform.
|
||||
let transform = identity().translate(origin.x * *world_zoom, origin.y * *world_zoom, 0.0);
|
||||
let transform = transform.scale(width as f32 * *world_zoom / buffer.resolution, height as f32 * *world_zoom / buffer.resolution, 1.0);
|
||||
texture_layer.common.set_transform(transform);
|
||||
|
||||
}
|
||||
|
||||
// Delete leftover layers
|
||||
while current_layer_child.is_some() {
|
||||
let trash = current_layer_child.get();
|
||||
do current_layer_child.get().with_common |common| {
|
||||
current_layer_child = common.next_sibling;
|
||||
}
|
||||
root_layer.remove_child(trash);
|
||||
}
|
||||
// Reset zoom
|
||||
*local_zoom = 1f32;
|
||||
root_layer.common.set_transform(identity().translate(-world_offset.x,
|
||||
-world_offset.y,
|
||||
0.0));
|
||||
*recomposite = true;
|
||||
};
|
||||
|
||||
let ask_for_tiles: @fn() = || {
|
||||
match *quadtree {
|
||||
Some(ref mut quad) => {
|
||||
let valid = |tile: &~LayerBuffer| -> bool {
|
||||
tile.resolution == *world_zoom
|
||||
};
|
||||
let (tile_request, redisplay) = quad.get_tile_rects(Rect(Point2D(world_offset.x as int,
|
||||
world_offset.y as int),
|
||||
*window_size), valid, *world_zoom);
|
||||
|
||||
if !tile_request.is_empty() {
|
||||
match *render_chan {
|
||||
Some(ref chan) => {
|
||||
chan.send(ReRenderMsg(tile_request, *world_zoom));
|
||||
}
|
||||
_ => {
|
||||
println("Warning: Compositor: Cannot send tile request, no render chan initialized");
|
||||
}
|
||||
}
|
||||
} else if redisplay {
|
||||
build_layer_tree(quad);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
fail!("Compositor: Tried to ask for tiles without an initialized quadtree");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let update_layout_callbacks: @fn(LayoutChan) = |layout_chan: LayoutChan| {
|
||||
let layout_chan_clone = layout_chan.clone();
|
||||
do window.set_navigation_callback |direction| {
|
||||
|
@ -247,18 +368,9 @@ impl CompositorTask {
|
|||
}
|
||||
WindowMouseDownEvent(button, layer_mouse_point) => {
|
||||
event = MouseDownEvent(button, world_mouse_point(layer_mouse_point));
|
||||
|
||||
}
|
||||
WindowMouseUpEvent(button, layer_mouse_point) => {
|
||||
|
||||
// rerender layer at new zoom level
|
||||
// FIXME: this should happen when the user stops zooming, definitely not here
|
||||
match *render_chan {
|
||||
Some(ref r_chan) => {
|
||||
r_chan.send(ReRenderMsg(*world_zoom));
|
||||
}
|
||||
None => {} // Nothing to do
|
||||
}
|
||||
|
||||
event = MouseUpEvent(button, world_mouse_point(layer_mouse_point));
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +378,7 @@ impl CompositorTask {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
let check_for_messages: @fn(&Port<Msg>) = |port: &Port<Msg>| {
|
||||
// Handle messages
|
||||
while port.peek() {
|
||||
|
@ -292,6 +405,20 @@ impl CompositorTask {
|
|||
|
||||
GetGLContext(chan) => chan.send(current_gl_context()),
|
||||
|
||||
NewLayer(new_size, tile_size) => {
|
||||
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
|
||||
*quadtree = Some(Quadtree::new(0, 0, new_size.width, new_size.height, tile_size));
|
||||
ask_for_tiles();
|
||||
|
||||
}
|
||||
ResizeLayer(new_size) => {
|
||||
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
|
||||
// TODO: update quadtree, ask for tiles
|
||||
}
|
||||
DeleteLayer => {
|
||||
// TODO: create secondary layer tree, keep displaying until new tiles come in
|
||||
}
|
||||
|
||||
Paint(id, new_layer_buffer_set, new_size) => {
|
||||
match *pipeline_id {
|
||||
Some(pipeline_id) => if id != pipeline_id { loop; },
|
||||
|
@ -300,69 +427,26 @@ impl CompositorTask {
|
|||
|
||||
debug!("osmain: received new frame");
|
||||
|
||||
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
|
||||
|
||||
let quad;
|
||||
match *quadtree {
|
||||
Some(ref mut q) => quad = q,
|
||||
None => fail!("Compositor: given paint command with no quadtree initialized"),
|
||||
}
|
||||
|
||||
let new_layer_buffer_set = new_layer_buffer_set.get();
|
||||
|
||||
// Iterate over the children of the container layer.
|
||||
let mut current_layer_child = root_layer.first_child;
|
||||
|
||||
for new_layer_buffer_set.buffers.iter().advance |buffer| {
|
||||
let width = buffer.rect.size.width as uint;
|
||||
let height = buffer.rect.size.height as uint;
|
||||
|
||||
debug!("osmain: compositing buffer rect %?", &buffer.rect);
|
||||
|
||||
// Find or create a texture layer.
|
||||
let texture_layer;
|
||||
current_layer_child = match current_layer_child {
|
||||
None => {
|
||||
debug!("osmain: adding new texture layer");
|
||||
texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager,
|
||||
buffer.screen_pos.size);
|
||||
root_layer.add_child(TextureLayerKind(texture_layer));
|
||||
None
|
||||
}
|
||||
Some(TextureLayerKind(existing_texture_layer)) => {
|
||||
texture_layer = existing_texture_layer;
|
||||
texture_layer.manager = @buffer.draw_target.clone() as @TextureManager;
|
||||
|
||||
// Move on to the next sibling.
|
||||
do current_layer_child.get().with_common |common| {
|
||||
common.next_sibling
|
||||
}
|
||||
}
|
||||
Some(_) => fail!(~"found unexpected layer kind"),
|
||||
};
|
||||
|
||||
let origin = buffer.screen_pos.origin;
|
||||
let origin = Point2D(origin.x as f32, origin.y as f32);
|
||||
|
||||
// Set the layer's transform.
|
||||
let transform = identity().translate(origin.x, origin.y, 0.0);
|
||||
let transform = transform.scale(width as f32, height as f32, 1.0);
|
||||
texture_layer.common.set_transform(transform);
|
||||
// FIXME: Don't copy the buffers here
|
||||
quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y,
|
||||
*world_zoom, ~buffer.clone());
|
||||
}
|
||||
|
||||
// Delete leftover layers
|
||||
while current_layer_child.is_some() {
|
||||
let trash = current_layer_child.get();
|
||||
do current_layer_child.get().with_common |common| {
|
||||
current_layer_child = common.next_sibling;
|
||||
}
|
||||
root_layer.remove_child(trash);
|
||||
}
|
||||
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
|
||||
|
||||
// Reset zoom
|
||||
*local_zoom = 1f32;
|
||||
root_layer.common.set_transform(identity().translate(-world_offset.x,
|
||||
-world_offset.y,
|
||||
0.0));
|
||||
build_layer_tree(quad);
|
||||
|
||||
// TODO: Recycle the old buffers; send them back to the renderer to reuse if
|
||||
// it wishes.
|
||||
|
||||
*recomposite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -409,6 +493,11 @@ impl CompositorTask {
|
|||
|
||||
root_layer.common.set_transform(scroll_transform);
|
||||
|
||||
// FIXME: ask_for_tiles() should be called here, but currently this sends a flood of requests
|
||||
// to the renderer, which slows the application dramatically. Instead, ask_for_tiles() is only
|
||||
// called on a click event.
|
||||
ask_for_tiles();
|
||||
|
||||
*recomposite = true;
|
||||
}
|
||||
|
||||
|
@ -416,6 +505,8 @@ impl CompositorTask {
|
|||
|
||||
// When the user pinch-zooms, scale the layer
|
||||
do window.set_zoom_callback |magnification| {
|
||||
*zoom_action = true;
|
||||
*zoom_time = precise_time_s();
|
||||
let old_world_zoom = *world_zoom;
|
||||
|
||||
// Determine zoom amount
|
||||
|
@ -465,6 +556,13 @@ impl CompositorTask {
|
|||
}
|
||||
|
||||
timer::sleep(&uv_global_loop::get(), 100);
|
||||
|
||||
// If a pinch-zoom happened recently, ask for tiles at the new resolution
|
||||
if *zoom_action && precise_time_s() - *zoom_time > 0.3 {
|
||||
*zoom_action = false;
|
||||
ask_for_tiles();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.shutdown_chan.send(())
|
||||
|
|
|
@ -8,11 +8,29 @@
|
|||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use geom::rect::Rect;
|
||||
use std::uint::{div_ceil, next_power_of_two};
|
||||
use std::vec::build_sized;
|
||||
use gfx::render_task::BufferRequest;
|
||||
|
||||
priv enum Quadtype {
|
||||
Empty,
|
||||
Base,
|
||||
Branch,
|
||||
/// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls
|
||||
/// at this level are in pixel coordinates.
|
||||
pub struct Quadtree<T> {
|
||||
root: QuadtreeNode<T>,
|
||||
max_tile_size: uint,
|
||||
}
|
||||
|
||||
/// A node in the tree. All method calls at this level are in page coordinates.
|
||||
struct QuadtreeNode<T> {
|
||||
/// The tile belonging to this node. Note that parent nodes can have tiles.
|
||||
tile: Option<T>,
|
||||
/// The position of the node in page coordinates.
|
||||
origin: Point2D<f32>,
|
||||
/// The width and height of the node in page coordinates.
|
||||
size: f32,
|
||||
/// The node's children.
|
||||
quadrants: [Option<~QuadtreeNode<T>>, ..4],
|
||||
/// If this node is marked for rendering
|
||||
render_flag: bool,
|
||||
}
|
||||
|
||||
priv enum Quadrant {
|
||||
|
@ -22,206 +40,488 @@ priv enum Quadrant {
|
|||
BR = 3,
|
||||
}
|
||||
|
||||
impl<T> Quadtree<T> {
|
||||
/// Public method to create a new Quadtree
|
||||
pub fn new(x: uint, y: uint, width: uint, height: uint, tile_size: uint) -> Quadtree<T> {
|
||||
// Spaces must be squares and powers of 2, so expand the space until it is
|
||||
let longer = width.max(&height);
|
||||
let num_tiles = div_ceil(longer, tile_size);
|
||||
let power_of_two = next_power_of_two(num_tiles);
|
||||
let size = power_of_two * tile_size;
|
||||
|
||||
pub struct Quadtree {
|
||||
quadtype: Quadtype,
|
||||
rect: Rect<uint>,
|
||||
quadrants: [Option<~Quadtree>, ..4],
|
||||
}
|
||||
|
||||
|
||||
impl Quadtree {
|
||||
pub fn new(x: uint, y: uint, width: uint, height: uint) -> Quadtree {
|
||||
Quadtree {
|
||||
quadtype: Empty,
|
||||
rect: Rect {
|
||||
origin: Point2D(x, y),
|
||||
size: Size2D(width, height),
|
||||
},
|
||||
|
||||
root: QuadtreeNode {
|
||||
tile: None,
|
||||
origin: Point2D(x as f32, y as f32),
|
||||
size: size as f32,
|
||||
quadrants: [None, None, None, None],
|
||||
render_flag: false,
|
||||
},
|
||||
max_tile_size: tile_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine which child contains a given point
|
||||
priv fn get_quadrant(&self, x: uint, y: uint) -> Quadrant {
|
||||
let self_width = self.rect.size.width;
|
||||
let self_height = self.rect.size.height;
|
||||
let self_x = self.rect.origin.x;
|
||||
let self_y = self.rect.origin.y;
|
||||
match (self_width, self_height) {
|
||||
(1, _) => {
|
||||
if y < self_y + self_height / 2 {
|
||||
TL
|
||||
} else {
|
||||
BR
|
||||
/// Return the maximum allowed tile size
|
||||
pub fn get_tile_size(&self) -> uint {
|
||||
self.max_tile_size
|
||||
}
|
||||
/// Get a tile at a given pixel position and scale.
|
||||
pub fn get_tile<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option<T> {
|
||||
self.root.get_tile(x as f32 / scale, y as f32 / scale)
|
||||
}
|
||||
/// Add a tile associtated with a given pixel position and scale.
|
||||
pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) {
|
||||
self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale);
|
||||
}
|
||||
/// Get the tile rect in screen and page coordinates for a given pixel position
|
||||
pub fn get_tile_rect(&mut self, x: uint, y: uint, scale: f32) -> BufferRequest {
|
||||
self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, scale, self.max_tile_size as f32 / scale)
|
||||
}
|
||||
/// Get all the tiles in the tree
|
||||
pub fn get_all_tiles<'r>(&'r self) -> ~[&'r T] {
|
||||
self.root.get_all_tiles()
|
||||
}
|
||||
/// Ask a tile to be deleted from the quadtree. This tries to delete a tile that is far from the
|
||||
/// given point in pixel coordinates.
|
||||
pub fn remove_tile(&mut self, x: uint, y: uint, scale: f32) {
|
||||
self.root.remove_tile(x as f32 / scale, y as f32 / scale);
|
||||
}
|
||||
/// Given a window rect in page coordinates and a function to check if an existing tile is "valid"
|
||||
/// (i.e. is the correct resolution), this function returns a list of BufferRequests for tiles that
|
||||
/// need to be rendered. It also returns a boolean if the window needs to be redisplayed, i.e. if
|
||||
/// no tiles need to be rendered, but the display tree needs to be rebuilt. This can occur when the
|
||||
/// user zooms out and cached tiles need to be displayed on top of higher resolution tiles.
|
||||
pub fn get_tile_rects(&mut self, window: Rect<int>, valid: &fn(&T) -> bool, scale: f32) ->
|
||||
(~[BufferRequest], bool) {
|
||||
|
||||
self.root.get_tile_rects(Rect(Point2D(window.origin.x as f32 / scale, window.origin.y as f32 / scale),
|
||||
Size2D(window.size.width as f32 / scale, window.size.height as f32 / scale)),
|
||||
valid, scale, self.max_tile_size as f32 / scale)
|
||||
}
|
||||
|
||||
/// Generate html to visualize the tree. For debugging purposes only.
|
||||
pub fn get_html(&self) -> ~str {
|
||||
static HEADER: &'static str = "<!DOCTYPE html><html>";
|
||||
fmt!("%s<body>%s</body></html>", HEADER, self.root.get_html())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<T> QuadtreeNode<T> {
|
||||
/// Private method to create new children
|
||||
fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode<T> {
|
||||
QuadtreeNode {
|
||||
tile: None,
|
||||
origin: Point2D(x, y),
|
||||
size: size,
|
||||
quadrants: [None, None, None, None],
|
||||
render_flag: false,
|
||||
}
|
||||
}
|
||||
(_, 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 {
|
||||
|
||||
/// Determine which child contains a given point in page coords.
|
||||
fn get_quadrant(&self, x: f32, y: f32) -> Quadrant {
|
||||
if x < self.origin.x + self.size / 2.0 {
|
||||
if y < self.origin.y + self.size / 2.0 {
|
||||
TL
|
||||
} else {
|
||||
BL
|
||||
}
|
||||
} else if y < self_y + self_height / 2 {
|
||||
} else if y < self.origin.y + self.size / 2.0 {
|
||||
TR
|
||||
} else {
|
||||
BR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the lowest-level (highest resolution) tile associated with a given position in page coords.
|
||||
fn get_tile<'r> (&'r self, x: f32, y: f32) -> &'r Option<T> {
|
||||
if x >= self.origin.x + self.size || x < self.origin.x
|
||||
|| y >= self.origin.y + self.size || y < self.origin.y {
|
||||
fail!("Quadtree: Tried to get a tile outside of range");
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
if x >= self_x + self_width || x < self_x
|
||||
|| y >= self_y + self_height || y < self_y {
|
||||
return; // Out of bounds
|
||||
}
|
||||
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;
|
||||
|
||||
match self.quadrants[index] {
|
||||
None => &'r self.tile,
|
||||
Some(ref child) => child.get_tile(x, y),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all tiles in the tree, parents first.
|
||||
fn get_all_tiles<'r>(&'r self) -> ~[&'r T] {
|
||||
let mut ret = ~[];
|
||||
|
||||
match self.tile {
|
||||
Some(ref tile) => ret = ~[tile],
|
||||
None => {}
|
||||
}
|
||||
|
||||
for self.quadrants.iter().advance |quad| {
|
||||
match *quad {
|
||||
Some(ref child) => ret = ret + child.get_all_tiles(),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Add a tile associated with a given position in page coords. If the tile size exceeds the maximum,
|
||||
/// the node will be split and the method will recurse until the tile size is within limits.
|
||||
fn add_tile(&mut self, x: f32, y: f32, tile: T, tile_size: f32) {
|
||||
debug!("Quadtree: Adding: (%?, %?) size:%?px", self.origin.x, self.origin.y, self.size);
|
||||
|
||||
if x >= self.origin.x + self.size || x < self.origin.x
|
||||
|| y >= self.origin.y + self.size || y < self.origin.y {
|
||||
fail!("Quadtree: Tried to add tile to invalid region");
|
||||
}
|
||||
|
||||
if self.size <= tile_size { // We are the child
|
||||
self.tile = Some(tile);
|
||||
// FIXME: This should be inline, but currently won't compile
|
||||
let quads = [TL, TR, BL, BR];
|
||||
for quads.iter().advance |quad| {
|
||||
self.quadrants[*quad as int] = None;
|
||||
}
|
||||
self.render_flag = false;
|
||||
} else { // Send tile to children
|
||||
let quad = self.get_quadrant(x, y);
|
||||
match self.quadrants[quad as int] {
|
||||
Some(ref mut child) => child.add_tile(x, y, tile, tile_size),
|
||||
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 = ~QuadtreeNode::new_child(new_x, new_y, new_size);
|
||||
c.add_tile(x, y, tile, tile_size);
|
||||
self.quadrants[quad as int] = Some(c);
|
||||
|
||||
// If my tile is completely occluded, get rid of it.
|
||||
// FIXME: figure out a better way to determine if a tile is completely occluded
|
||||
// e.g. this alg doesn't work if a tile is covered by its grandchildren
|
||||
match self.quadrants {
|
||||
[Some(ref tl_child), Some(ref tr_child), Some(ref bl_child), Some(ref br_child)] => {
|
||||
match (&tl_child.tile, &tr_child.tile, &bl_child.tile, &br_child.tile) {
|
||||
(&Some(_), &Some(_), &Some(_), &Some(_)) => self.tile = None,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a tile rect in screen and page coords for a given position in page coords
|
||||
fn get_tile_rect(&mut self, x: f32, y: f32, scale: f32, tile_size: f32) -> BufferRequest {
|
||||
if x >= self.origin.x + self.size || x < self.origin.x
|
||||
|| y >= self.origin.y + self.size || y < self.origin.y {
|
||||
fail!("Quadtree: Tried to query a tile rect outside of range");
|
||||
}
|
||||
|
||||
if self.size <= tile_size {
|
||||
let self_x = (self.origin.x * scale).ceil() as uint;
|
||||
let self_y = (self.origin.y * scale).ceil() as uint;
|
||||
let self_size = (self.size * scale).ceil() as uint;
|
||||
return BufferRequest(Rect(Point2D(self_x, self_y), Size2D(self_size, self_size)),
|
||||
Rect(Point2D(self.origin.x, self.origin.y), Size2D(self.size, self.size)));
|
||||
}
|
||||
|
||||
let quad = self.get_quadrant(x,y);
|
||||
match self.quadrants[quad as int] {
|
||||
None => {
|
||||
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 = ~QuadtreeNode::new_child(new_x, new_y, new_size);
|
||||
c.render_flag = true;
|
||||
let result = c.get_tile_rect(x, y, scale, tile_size);
|
||||
self.quadrants[quad as int] = Some(c);
|
||||
result
|
||||
}
|
||||
Some(ref mut child) => child.get_tile_rect(x, y, scale, tile_size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a tile that is far from the given input point in page coords. Returns true if the child
|
||||
/// has no tiles and needs to be deleted.
|
||||
fn remove_tile(&mut self, x: f32, y: f32) -> bool {
|
||||
match (&self.tile, &self.quadrants) {
|
||||
(&Some(_), &[None, None, None, None]) => {
|
||||
self.tile = None;
|
||||
return true;
|
||||
}
|
||||
(&Some(_), _) => {
|
||||
self.tile = None;
|
||||
return false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// This is a hacky heuristic to find a tile that is "far away". There are better methods.
|
||||
let quad = self.get_quadrant(x, y);
|
||||
let my_child = match quad {
|
||||
TL => {
|
||||
match (&self.quadrants[BR as int], &self.quadrants[BL as int], &self.quadrants[TR as int]) {
|
||||
(&Some(_), _, _) => BR,
|
||||
(&None, &Some(_), _) => BL,
|
||||
(&None, &None, &Some(_)) => TR,
|
||||
_ => TL,
|
||||
}
|
||||
}
|
||||
TR => {
|
||||
match (&self.quadrants[BL as int], &self.quadrants[BR as int], &self.quadrants[TL as int]) {
|
||||
(&Some(_), _, _) => BL,
|
||||
(&None, &Some(_), _) => BR,
|
||||
(&None, &None, &Some(_)) => TL,
|
||||
_ => TR,
|
||||
}
|
||||
}
|
||||
BL => {
|
||||
match (&self.quadrants[TR as int], &self.quadrants[TL as int], &self.quadrants[BR as int]) {
|
||||
(&Some(_), _, _) => TR,
|
||||
(&None, &Some(_), _) => TL,
|
||||
(&None, &None, &Some(_)) => BR,
|
||||
_ => BL,
|
||||
}
|
||||
}
|
||||
BR => {
|
||||
match (&self.quadrants[TL as int], &self.quadrants[TR as int], &self.quadrants[BL as int]) {
|
||||
(&Some(_), _, _) => TL,
|
||||
(&None, &Some(_), _) => TR,
|
||||
(&None, &None, &Some(_)) => BL,
|
||||
_ => BR,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match self.quadrants[my_child as int] {
|
||||
Some(ref mut child) if !child.remove_tile(x, y) => {
|
||||
return false;
|
||||
}
|
||||
Some(_) => {} // fall through
|
||||
None => fail!("Quadtree: child query failure"),
|
||||
Some(ref mut region) => {
|
||||
// Recurse if necessary
|
||||
match region.quadtype {
|
||||
Empty | Branch => {
|
||||
region.add_region(x, y);
|
||||
}
|
||||
Base => {} // nothing to do
|
||||
|
||||
// child.remove_tile() returned true
|
||||
self.quadrants[my_child as int] = None;
|
||||
match self.quadrants {
|
||||
[None, None, None, None] => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a window rect in page coordinates and a tile validation function, returns a BufferRequest array
|
||||
/// and a redisplay boolean. See QuadTree function description for more details.
|
||||
fn get_tile_rects(&mut self, window: Rect<f32>, valid: &fn(&T) -> bool, scale: f32, tile_size: f32) ->
|
||||
(~[BufferRequest], bool) {
|
||||
|
||||
let w_x = window.origin.x;
|
||||
let w_y = window.origin.y;
|
||||
let w_width = window.size.width;
|
||||
let w_height = window.size.height;
|
||||
let s_x = self.origin.x;
|
||||
let s_y = self.origin.y;
|
||||
let s_size = self.size;
|
||||
|
||||
if w_x < s_x || w_x + w_width > s_x + s_size
|
||||
|| w_y < s_y || w_y + w_height > s_y + s_size {
|
||||
println(fmt!("window: %?, %?, %?, %?; self: %?, %?, %?", w_x, w_y, w_width, w_height, s_x, s_y, s_size));
|
||||
fail!("Quadtree: tried to query an invalid tile rect");
|
||||
}
|
||||
|
||||
if s_size <= tile_size { // We are the child
|
||||
match self.tile {
|
||||
Some(ref tile) if valid(tile) => {
|
||||
let redisplay = match self.quadrants {
|
||||
[None, None, None, None] => false,
|
||||
_ => true,
|
||||
};
|
||||
if redisplay {
|
||||
// FIXME: This should be inline, but currently won't compile
|
||||
let quads = [TL, TR, BL, BR];
|
||||
for quads.iter().advance |quad| {
|
||||
self.quadrants[*quad as int] = None;
|
||||
}
|
||||
}
|
||||
return (~[], redisplay);
|
||||
}
|
||||
None if self.render_flag => {
|
||||
return(~[], false);
|
||||
}
|
||||
_ => {
|
||||
return (~[self.get_tile_rect(s_x, s_y, scale, tile_size)], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: ideally we could make the assignments in the match,
|
||||
// but borrowed pointers prevent that. So here's a flag instead.
|
||||
let mut base_flag = 0;
|
||||
// Otherwise, we either have children or will have children
|
||||
let w_tl_quad = self.get_quadrant(w_x, w_y);
|
||||
let w_br_quad = self.get_quadrant(w_x + w_width, w_y + w_height);
|
||||
|
||||
// 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;
|
||||
// Figure out which quadrants the window is in
|
||||
let builder = |push: &fn(Quadrant)| {
|
||||
match (w_tl_quad, w_br_quad) {
|
||||
(tl, br) if tl as int == br as int => {
|
||||
push(tl);
|
||||
}
|
||||
_ => {} // nothing to do
|
||||
(TL, br) => {
|
||||
push(TL);
|
||||
push(br);
|
||||
match br {
|
||||
BR => {
|
||||
push(TR);
|
||||
push(BL);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
(&[Some(ref tl_q), Some(ref tr_q), Some(ref bl_q), Some(ref br_q)], _, _) => {
|
||||
match (tl_q.quadtype, tr_q.quadtype, bl_q.quadtype, br_q.quadtype) {
|
||||
(Base, Base, Base, Base) => {
|
||||
base_flag = 2;
|
||||
}
|
||||
_ => {} // nothing to do
|
||||
(tl, br) => {
|
||||
push(tl);
|
||||
push(br);
|
||||
}
|
||||
}
|
||||
_ => {} // nothing to do
|
||||
}
|
||||
};
|
||||
|
||||
match base_flag {
|
||||
0 => {}
|
||||
1 => {
|
||||
self.quadtype = Base;
|
||||
self.quadrants[TL as int] = None;
|
||||
self.quadrants[BR as int] = None;
|
||||
let quads_to_check = build_sized(4, builder);
|
||||
|
||||
let mut ret = ~[];
|
||||
let mut redisplay = false;
|
||||
|
||||
for quads_to_check.iter().advance |quad| {
|
||||
match self.quadrants[*quad as int] {
|
||||
Some(ref mut child) => {
|
||||
// Recurse into child
|
||||
let new_window = match *quad {
|
||||
TL => Rect(window.origin,
|
||||
Size2D(w_width.min(&(s_x + s_size / 2.0 - w_x)),
|
||||
w_height.min(&(s_y + s_size / 2.0 - w_y)))),
|
||||
TR => Rect(Point2D(w_x.max(&(s_x + s_size / 2.0)),
|
||||
w_y),
|
||||
Size2D(w_width.min(&(w_x + w_width - (s_x + s_size / 2.0))),
|
||||
w_height.min(&(s_y + s_size / 2.0 - w_y)))),
|
||||
BL => Rect(Point2D(w_x,
|
||||
w_y.max(&(s_y + s_size / 2.0))),
|
||||
Size2D(w_width.min(&(s_x + s_size / 2.0 - w_x)),
|
||||
w_height.min(&(w_y + w_height - (s_y + s_size / 2.0))))),
|
||||
BR => Rect(Point2D(w_x.max(&(s_x + s_size / 2.0)),
|
||||
w_y.max(&(s_y + s_size / 2.0))),
|
||||
Size2D(w_width.min(&(w_x + w_width - (s_x + s_size / 2.0))),
|
||||
w_height.min(&(w_y + w_height - (s_y + s_size / 2.0))))),
|
||||
|
||||
};
|
||||
let (c_ret, c_redisplay) = child.get_tile_rects(new_window, |x| valid(x), scale, tile_size);
|
||||
ret = ret + c_ret;
|
||||
redisplay = redisplay || c_redisplay;
|
||||
}
|
||||
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;
|
||||
None => {
|
||||
// Figure out locations of future children
|
||||
let (x_start, y_start, x_end, y_end) = match *quad {
|
||||
TL => (w_x,
|
||||
w_y,
|
||||
(w_x + w_width).min(&(s_x + s_size / 2.0)),
|
||||
(w_y + w_height).min(&(s_y + s_size / 2.0))),
|
||||
TR => (w_x.max(&(s_x + s_size / 2.0)),
|
||||
w_y,
|
||||
(w_x + w_width + tile_size).min(&(s_x + s_size)),
|
||||
(w_y + w_height).min(&(s_y + s_size / 2.0))),
|
||||
BL => (w_x,
|
||||
w_y.max(&(s_y + s_size / 2.0)),
|
||||
(w_x + w_width).min(&(s_x + s_size / 2.0)),
|
||||
(w_y + w_height + tile_size).min(&(s_y + s_size))),
|
||||
BR => (w_x.max(&(s_x + s_size / 2.0)),
|
||||
w_y.max(&(s_y + s_size / 2.0)),
|
||||
(w_x + w_width + tile_size).min(&(s_x + s_size)),
|
||||
(w_y + w_height + tile_size).min(&(s_y + s_size))),
|
||||
};
|
||||
let size = (((x_end - x_start) / tile_size).ceil() *
|
||||
((y_end - y_start) / tile_size).ceil()) as uint;
|
||||
|
||||
let builder = |push: &fn(BufferRequest)| {
|
||||
let mut y = y_start;
|
||||
while y < y_end {
|
||||
let mut x = x_start;
|
||||
while x < x_end {
|
||||
push(self.get_tile_rect(x, y, scale, tile_size));
|
||||
x = x + tile_size;
|
||||
}
|
||||
y = y + tile_size;
|
||||
}
|
||||
};
|
||||
ret = ret + build_sized(size, builder);
|
||||
}
|
||||
_ => 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
|
||||
(ret, redisplay)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
/// Generate html to visualize the tree.
|
||||
/// This is really inefficient, but it's for testing only.
|
||||
fn get_html(&self) -> ~str {
|
||||
let mut ret = ~"";
|
||||
match self.tile {
|
||||
Some(ref tile) => {
|
||||
ret = fmt!("%s%?", ret, tile);
|
||||
}
|
||||
None => {
|
||||
ret = fmt!("%sNO TILE", ret);
|
||||
}
|
||||
}
|
||||
match self.quadrants {
|
||||
[None, None, None, None] => {}
|
||||
_ => {
|
||||
ret = fmt!("%s<table border=1><tr>", ret);
|
||||
// FIXME: This should be inline, but currently won't compile
|
||||
let quads = [TL, TR, BL, BR];
|
||||
for quads.iter().advance |quad| {
|
||||
match self.quadrants[*quad as int] {
|
||||
Some(ref child) => {
|
||||
ret = fmt!("%s<td>%s</td>", ret, child.get_html());
|
||||
}
|
||||
None => {
|
||||
ret = fmt!("%s<td>EMPTY CHILD</td>", ret);
|
||||
}
|
||||
}
|
||||
match *quad {
|
||||
TR => ret = fmt!("%s</tr><tr>", ret),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ret = fmt!("%s</table>\n", ret);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_add_region() {
|
||||
let mut t = Quadtree::new(50, 50, 3, 4);
|
||||
assert!(!t.check_region(50, 50));
|
||||
t.add_region(50, 50);
|
||||
assert!(t.check_region(50, 50));
|
||||
assert!(!t.check_region(51, 50));
|
||||
assert!(!t.check_region(50, 51));
|
||||
t.add_region(53, 50);
|
||||
assert!(!t.check_region(53, 50));
|
||||
|
||||
fn test_add_tile() {
|
||||
let mut t = Quadtree::new(50, 30, 20, 20, 10);
|
||||
assert!(t.get_tile(50, 30, 1.0).is_none());
|
||||
t.add_tile(50, 30, 1.0, 1);
|
||||
assert!(t.get_tile(50, 30, 1.0).get() == 1);
|
||||
assert!(t.get_tile(59, 39, 1.0).get() == 1);
|
||||
assert!(t.get_tile(60, 40, 1.0).is_none());
|
||||
assert!(t.get_tile(110, 70, 2.0).get() == 1);
|
||||
t.add_tile(100, 60, 2.0, 2);
|
||||
assert!(t.get_tile(109, 69, 2.0).get() == 2);
|
||||
assert!(t.get_tile(110, 70, 2.0).get() == 1);
|
||||
}
|
|
@ -19,8 +19,12 @@ pub struct LayerBuffer {
|
|||
// The rect in pixels that will be drawn to the screen.
|
||||
screen_pos: Rect<uint>,
|
||||
|
||||
// The scale at which this tile is rendered
|
||||
resolution: f32,
|
||||
|
||||
// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH.
|
||||
stride: uint
|
||||
stride: uint,
|
||||
|
||||
}
|
||||
|
||||
/// A set of layer buffers. This is an atomic unit used to switch between the front and back
|
||||
|
@ -49,6 +53,9 @@ pub enum ReadyState {
|
|||
/// submit them to be drawn to the display.
|
||||
pub trait RenderListener {
|
||||
fn get_gl_context(&self) -> AzGLContext;
|
||||
fn new_layer(&self, Size2D<uint>, uint);
|
||||
fn resize_layer(&self, Size2D<uint>);
|
||||
fn delete_layer(&self);
|
||||
fn paint(&self, id: uint, layer_buffer_set: arc::ARC<LayerBufferSet>, new_size: Size2D<uint>);
|
||||
fn set_render_state(&self, render_state: RenderState);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue