Commit f9f55b39 authored by Sam Rushing's avatar Sam Rushing

separate quadtrees for background, viewports, moving objects

use cython quadtree implementation
parent f50c1993
# -*- Mode: Python -*- # -*- Mode: Python -*-
from coro.http.websocket import handler, websocket from coro.http.websocket import handler, websocket
import math
import pickle import pickle
import random import random
import re import re
import region
import quadtree import quadtree
import coro import coro
W = coro.write_stderr W = coro.write_stderr
# need a way to declare a dirty region, and send a single
# redraw command for all the dirtied objects. So we need to separate
# object movement from redrawing... probably with a timer of some kind,
# accumulate dirty rects, then redraw in one swell foop
# another thing to consider: sending deltas rather than the entire list.
# for example, if the viewport hasn't moved, then the list of rectangles
# won't be changing. Can we send a diff? [this might just be easier with layers]
# for layers see: http://stackoverflow.com/questions/3008635/html5-canvas-element-multiple-layers
colors = ['red', 'green', 'blue', 'magenta', 'purple', 'plum', 'orange'] colors = ['red', 'green', 'blue', 'magenta', 'purple', 'plum', 'orange']
# sample 'box' object. # sample 'box' object.
class box: class box (quadtree.ob):
def __init__ (self, color, rect): def __init__ (self, color, (l,t,r,b)):
self.color = color self.color = color
self.rect = rect self.set_rect (l,t,r,b)
def move (self, dx, dy):
x0, y0, x1, y1 = self.get_rect()
self.set_rect (
int(x0 + dx), int(y0 + dy), int(x1 + dx), int(y0 + dy)
)
def cmd (self, xoff, yoff):
# command to draw me relative to <xoff,yoff>?
x0, y0, x1, y1 = self.get_rect()
return 'B,%s,%d,%d,%d,%d' % (self.color, x0-xoff, y0-yoff, x1-x0, y1-y0)
def __repr__ (self):
return '<box (%d,%d,%d,%d)>' % self.get_rect()
class circle (box):
def __init__ (self, color, center, radius):
x, y = center
r = radius
self.color = color
self.center = center
self.radius = radius
self.set_rect (*self.get_rect())
def get_rect (self): def get_rect (self):
return self.rect x, y = self.center
r = self.radius
return x-r, y-r, x+r, y+r
def move (self, dx, dy):
x, y = self.center
self.center = int(x + dx), int(y + dy)
self.set_rect (*self.get_rect())
def cmd (self, xoff, yoff):
# command to draw me relative to <xoff,yoff>?
x, y = self.center
return 'C,%s,%d,%d,%d' % (self.color, x-xoff, y-yoff, self.radius)
def __repr__ (self): def __repr__ (self):
return '<box (%d,%d,%d,%d)>' % self.rect return '<circle (%d,%d) radius=%d)>' % (self.center[0], self.center[1], self.radius)
# should have multiple qt's:
# 1) for the background, immutable
# 2) for the client viewports
# 3) for moving objects
class field: class field:
def __init__ (self, w=1024*20, h=1024*20): def __init__ (self, w=1024*20, h=1024*20):
self.w = w self.w = w
self.h = h self.h = h
self.clients = set() self.Q_views = quadtree.quadtree()
self.Q = quadtree.quadtree() self.Q_back = quadtree.quadtree()
self.Q_obs = quadtree.quadtree()
self.generate_random_field() self.generate_random_field()
def generate_random_field (self): def generate_random_field (self):
for i in range (5000): for i in range (5000):
c = random.choice (colors)
x = random.randint (0, self.w - 100) x = random.randint (0, self.w - 100)
y = random.randint (0, self.h - 100) y = random.randint (0, self.h - 100)
w = random.randint (50, 300) w = random.randint (50, 300)
h = random.randint (50, 300) h = random.randint (50, 300)
c = random.choice (colors)
b = box (c, (x, y, x+w, y+h)) b = box (c, (x, y, x+w, y+h))
self.Q.insert (b) self.Q_back.insert (b)
for i in range (1000):
coro.spawn (self.wanderer)
def new_conn (self, *args, **kwargs): def new_conn (self, *args, **kwargs):
c = field_conn (self, *args, **kwargs) c = field_conn (self, *args, **kwargs)
self.clients.add (c) self.Q_views.insert (c)
def new_ob (self, ob):
self.Q_obs.insert (ob)
#W ('new ob %r\n' % (self.Q_obs,))
#self.Q_obs.dump()
def move_ob (self, ob, dx, dy):
r0 = ob.get_rect()
self.Q_obs.delete (ob)
ob.move (dx, dy)
r1 = ob.get_rect()
self.Q_obs.insert (ob)
#self.Q_obs.dump()
# notify any viewers
r2 = region.union (r0, r1)
for client in self.Q_views.search (r2):
client.draw_window()
class field_conn (websocket): sleep = 0.1
def wanderer (self):
# spawn me!
x = random.randint (100, self.w - 100)
y = random.randint (100, self.h - 100)
c = random.choice (colors)
ob = circle (c, (x, y), 25)
self.new_ob (ob)
while 1:
# pick a random direction
heading = random.randint (0, 360) * (math.pi / 180.0)
speed = (random.random() * 10) + 3
dx = math.cos (heading) * speed
dy = math.sin (heading) * speed
# go that way for 20 steps
for i in range (20):
self.move_ob (ob, dx, dy)
# not working yet...
## if not ob.range_check (0, 0, self.w, self.h):
## W ('%r hit an edge!\n' % (ob,))
## dx = - (dx * 5)
## dy = - (dy * 5)
## self.move_ob (ob, dx, dy)
coro.sleep_relative (self.sleep)
class field_conn (websocket, quadtree.ob):
def __init__ (self, field, *args, **kwargs): def __init__ (self, field, *args, **kwargs):
websocket.__init__ (self, *args, **kwargs) websocket.__init__ (self, *args, **kwargs)
...@@ -52,12 +144,13 @@ class field_conn (websocket): ...@@ -52,12 +144,13 @@ class field_conn (websocket):
self.field = field self.field = field
rx = random.randint (0, self.field.w-1024) rx = random.randint (0, self.field.w-1024)
ry = random.randint (0, self.field.h-1024) ry = random.randint (0, self.field.h-1024)
self.rect = (rx, ry, rx+1024, ry+1024) self.set_rect (rx, ry, rx+1024, ry+1024)
self.draw_window() self.draw_window()
self.mouse_down = None, None self.mouse_down = None, None
def move_window (self, mx, my): def move_window (self, mx, my):
x0, y0, x1, y1 = self.rect self.field.Q_views.delete (self)
x0, y0, x1, y1 = self.get_rect()
x0 = x0 + mx; y0 = y0 + my x0 = x0 + mx; y0 = y0 + my
x1 = x1 + mx; y1 = y1 + my x1 = x1 + mx; y1 = y1 + my
if x0 < 0: if x0 < 0:
...@@ -68,17 +161,20 @@ class field_conn (websocket): ...@@ -68,17 +161,20 @@ class field_conn (websocket):
x1 = self.field.w-1024; x0 = x1 - 1024 x1 = self.field.w-1024; x0 = x1 - 1024
if y1 > self.field.h-1024: if y1 > self.field.h-1024:
y1 = self.field.h-1024; y0 = y1 - 1024 y1 = self.field.h-1024; y0 = y1 - 1024
self.rect = (x0, y0, x1, y1) self.set_rect (x0, y0, x1, y1)
self.field.Q_views.insert (self)
self.draw_window() self.draw_window()
self.send_text ('M pos=%d,%d' % self.rect[:2]) self.send_text ('M pos=%d,%d' % self.get_rect()[:2])
def draw_qt (self, r, Q):
px, py = self.get_rect()[:2]
for ob in Q.search (self.rect):
r.append (ob.cmd (px, py))
def draw_window (self): def draw_window (self):
r = ['F'] r = ['F']
px, py = self.rect[:2] self.draw_qt (r, self.field.Q_back)
for ob in self.field.Q.search_gen (self.rect): self.draw_qt (r, self.field.Q_obs)
c = ob.color
x0, y0, x1, y1 = ob.get_rect()
r.append ('B,%s,%d,%d,%d,%d' % (c, x0-px, y0-py, x1-x0, y1-y0))
self.send_text ('|'.join (r)) self.send_text ('|'.join (r))
def send_text (self, payload): def send_text (self, payload):
...@@ -100,7 +196,7 @@ class field_conn (websocket): ...@@ -100,7 +196,7 @@ class field_conn (websocket):
elif ascii == 68: # D elif ascii == 68: # D
self.move_window (10, 0) self.move_window (10, 0)
elif ascii == 82: # R elif ascii == 82: # R
x0, y0 = self.rect[:2] x0, y0 = self.get_rect()[:2]
self.move_window (-x0, -y0) self.move_window (-x0, -y0)
elif event[0] == 'MD': elif event[0] == 'MD':
self.on_mouse_down (int (event[1]), int (event[2])) self.on_mouse_down (int (event[1]), int (event[2]))
...@@ -135,15 +231,6 @@ class field_conn (websocket): ...@@ -135,15 +231,6 @@ class field_conn (websocket):
def on_mouse_up (self, x1, y1): def on_mouse_up (self, x1, y1):
x0, y0 = self.mouse_down x0, y0 = self.mouse_down
self.mouse_down = None, None self.mouse_down = None, None
# 1) draw a box in the region chosen
#if x0 > x1:
# x0, x1 = x1, x0
#if y0 > y1:
# y0, y1 = y1, y0
#px, py = self.rect[:2]
#b = box (random.choice (colors), (x0+px, y0+py, x1+px, y1+py))
#self.field.Q.insert (b)
# 2) or move the window
if x0 is not None: if x0 is not None:
self.move_window (x0-x1, y0-y1) self.move_window (x0-x1, y0-y1)
self.draw_window() self.draw_window()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment