def build_map(self): """The build_map routine takes the grid defined in build_grid, and creates the associated set of images""" self.images.clear() self.set_screen_scale([1, 1]) scaled = [] for floor in range(len(self.poly_arr)): LB, TR = self.get_poly(floor) # Create an image the same size as the rectangle and map pixels 1 to 1 w = int(TR[0] - LB[0]) h = int(LB[1] - TR[1]) # Y is flipped if w < 1 or h < 0: continue img = Image.new("RGB", (w, h)) pixels = [None] * (w * h) half = w * h / 2 A = m2d.rand_colour3() B = m2d.rand_colour3() for i in range(w * h): pixels[i] = A if i < half else B img.putdata(pixels) self.images.append(img) photo = ImageTk.PhotoImage(image=img) scaled.append(photo) return scaled
def build_car(self, position, rays): """Builds the Tkinter objects for displaying the car. This must be called prior to draw_car.""" car = list() car.append(self.canvas.create_polygon(AABB_to_vertices(CAR_BODY), fill="blue")) car.append(self.canvas.create_polygon(AABB_to_vertices(CAR_FRONT), fill="red")) verts = translate_vertices(rotate_polygon(CAR_BODY, self.car_orn, [0, 0]), position) self.update_object_coords(car[0], verts) # We have to rotate the local transformation of the front-AABB verts = translate_vertices(rotate_polygon(CAR_FRONT, self.car_orn, [0, 0]), position) rot = m2d.rot_vec(FRONT_TRANS, self.car_orn) verts = list(map(lambda x: m2d.add(x, rot), verts)) self.update_object_coords(car[1], verts) ray_points = [] for i in range(rays): vec = m2d.rot_vec(RAY_LINE, i*self.ray_dtheta) verts = flatten([self.car_pos, m2d.add(self.car_pos, vec)]) ray_points.append(m2d.add(self.car_pos, vec)) car.append(self.canvas.create_line(*verts, fill="blue")) i1 = len(ray_points) - 1 for i0 in range(len(ray_points)): car.append(self.canvas.create_line(*[ray_points[i1], ray_points[i0]], fill="blue")) i1 = i0 return car
def update_car(self): """Updates the car each frame by updating the scene graph created by build_car.""" verts = translate_vertices(rotate_polygon(CAR_BODY, self.car_orn, [0, 0]), self.car_pos) self.update_object_coords(self.car[0], verts) # We have to rotate the local transformation of the front-AABB verts = translate_vertices(rotate_polygon(CAR_FRONT, self.car_orn, [0, 0]), self.car_pos) rot = m2d.rot_vec(FRONT_TRANS, self.car_orn) verts = list(map(lambda x: m2d.add(x, rot), verts)) self.update_object_coords(self.car[1], verts) ray_points = [] for i in range(2, 2 + self.car_rays): vec = m2d.rot_vec(RAY_LINE, self.car_orn + i*self.ray_dtheta) self.update_object_coords(self.car[i], [self.car_pos, m2d.add(self.car_pos, vec)]) ray_points.append(m2d.add(self.car_pos, vec)) i1 = len(ray_points) - 1 for i0 in range(self.car_rays): i = i0 + 2 + self.car_rays self.update_object_coords(self.car[i], [ray_points[i1], ray_points[i0]]) i1 = i0 self.visit_tiles(ray_points) for i in range(len(self.car)): self.canvas.tag_raise(self.car[i])
def build_car(self, position, rays): """Builds the mesh for the car and the view triangles for intersecting mesh geometry""" car = list() # Two rectangles and rays - 1 triangles # car[0] car.append(self.canvas.create_polygon(self.AABB_to_vertices(CAR_BODY), fill="blue")) # car[1] car.append(self.canvas.create_polygon(self.AABB_to_vertices(CAR_FRONT), fill="red")) verts = self.translate_vertices(self.rotate_polygon(CAR_BODY, self.car_orn, [0, 0]), position) self.update_object_coords(car[0], verts) # We have to rotate the local transformation of the front-AABB verts = self.translate_vertices(self.rotate_polygon(CAR_FRONT, self.car_orn, [0, 0]), position) rot = m2d.rot_vec(FRONT_TRANS, self.car_orn) verts = list(map(lambda x: m2d.add(x, rot), verts)) self.update_object_coords(car[1], verts) ray_points = [] for i in range(rays): vec = m2d.rot_vec(RAY_LINE, i*self.ray_dtheta) verts = self.flatten([self.car_pos, m2d.add(self.car_pos, vec)]) ray_points.append(m2d.add(self.car_pos, vec)) car.append(self.canvas.create_line(*verts, fill="black")) i1 = len(ray_points) - 1 for i0 in range(len(ray_points)): car.append(self.canvas.create_line(*[ray_points[i1], ray_points[i0]], fill="black")) i1 = i0 return car
def setup_window(self): from tkinter import Canvas self.master.bind('<Destroy>', self.on_destroy) if self.is_test: self.master.bind('<Left>', lambda x: self.cmd_turn_car(self.car_orn - .1)) self.master.bind('<Right>', lambda x: self.cmd_turn_car(self.car_orn + .1)) self.master.bind('<Up>', lambda x: self.cmd_move_car(m2d.add(self.car_pos, m2d.mul(m2d.make_polar(self.car_orn), 5)))) self.master.bind('<Down>', lambda x: self.cmd_move_car(m2d.add(self.car_pos, m2d.mul(m2d.make_polar(self.car_orn), -5)))) self.canvas = Canvas(self.master, width=512, height=512) self.canvas.pack(fill="both", expand=True) self.canvas.bind('<Configure>', self.on_resize) self.width = self.canvas.winfo_width() self.height = self.canvas.winfo_height() return True
def __init__(self, master, send_q, resp_q, floor_file, walls_file, is_test=True): self.master = master master.title("PathfinderSim Display Window") master.geometry("512x600") # Parse the input files self.floors = OBJModel(floor_file) self.walls = OBJModel(walls_file) self.floors.parse() self.walls.parse() self.walls_AABB = self.walls.model_AABB() # cache this self.floors_AABB = self.floors.model_AABB() # cached self.floor_polys = [] self.floors_seen = [False] * self.floors.get_prim_count() self.floors_id = [] # Intercept the destroy event so we can shutdown gracefully master.bind("<Destroy>", self.on_destroy) self.is_test = is_test if is_test: master.bind('<Left>', lambda x: self.cmd_turn_car(self.car_orn - .1)) master.bind('<Right>', lambda x: self.cmd_turn_car(self.car_orn + .1)) master.bind('<Up>', lambda x: self.cmd_move_car(m2d.add(self.car_pos, m2d.mul(m2d.make_polar(self.car_orn), 5)))) master.bind('<Down>', lambda x: self.cmd_move_car(m2d.add(self.car_pos, m2d.mul(m2d.make_polar(self.car_orn), -5)))) self.canvas = Canvas(master, width=512, height=512) self.canvas.pack(fill="both", expand=True) self.canvas.bind('<Configure>', self.on_resize) self.width = self.canvas.winfo_width() self.height = self.canvas.winfo_height() self.car_pos = [100, 100] self.car_orn = 0 self.car_rays = 12 self.ray_dtheta = 2.*math.pi/self.car_rays # Async comms to class in separate thread self.command_q = send_q self.response_q = resp_q self.shutdown_flag = False self.button = Button(master, text="Quit", command=self.shutdown) self.button.pack() self.draw_map() self.car = self.build_car(self.car_pos, 12)
def visit_tiles(self, scale, car_pos, ray_points): # Test each ray against each polygon, if intersecting, rasterise the triangle into the buffer, setting the # visible flag to true (0, 0, 0), i.e. black, if the pixel has been visited # Return an index containing the tile ids updated changed = [] car_rays = len(ray_points) i1 = len(ray_points) - 1 for i0 in range(car_rays): tri = [car_pos, ray_points[i1], ray_points[i0]] if not m2d.is_ccw(tri): tri.reverse() for tile in range(self.poly_count()): LB, TR = self.get_poly(tile) poly = verts([LB, TR]) if m2d.test_intersection(tri, poly): w = int(TR[0] - LB[0]) h = int(LB[1] - TR[1]) # Y is flipped img = self.images[tile] img_w, img_h = img.size pixels = list(img.getdata()) cx = int(LB[0] + w / 2) cy = int(LB[1] - h / 2) def cb(coord): if 0 <= coord[0] < img_w and 0 <= coord[1] < img_h: idx = coord[1] * img_w + coord[0] if 0 < idx < len(pixels): pixels[idx] = (0, 0, 0) itri = list( map( lambda x: [ int(round((x[0] - cx + w / 2) / scale[0], 4)), int(round((x[1] - cy + h / 2) / scale[1], 4)) ], tri)) tr.rasterise(itri, cb) img.putdata(pixels) changed.append(tile) i1 = i0 return changed
def draw_car(self): verts = self.translate_vertices(self.rotate_polygon(CAR_BODY, self.car_orn, [0, 0]), self.car_pos) self.update_object_coords(self.car[0], verts) # We have to rotate the local transformation of the front-AABB verts = self.translate_vertices(self.rotate_polygon(CAR_FRONT, self.car_orn, [0, 0]), self.car_pos) rot = m2d.rot_vec(FRONT_TRANS, self.car_orn) verts = list(map(lambda x: m2d.add(x, rot), verts)) self.update_object_coords(self.car[1], verts) ray_points = [] for i in range(2, 2 + self.car_rays): vec = m2d.rot_vec(RAY_LINE, self.car_orn + i*self.ray_dtheta) self.update_object_coords(self.car[i], [self.car_pos, m2d.add(self.car_pos, vec)]) ray_points.append(m2d.add(self.car_pos, vec)) i1 = len(ray_points) - 1 for i0 in range(self.car_rays): i = i0 + 2 + self.car_rays self.update_object_coords(self.car[i], [ray_points[i1], ray_points[i0]]) i1 = i0 # Test each rectangle that has not been seen against the ray triangles i1 = len(ray_points) - 1 for i0 in range(self.car_rays): tri = [self.car_pos, ray_points[i1], ray_points[i0]] if not m2d.is_ccw(tri): tri.reverse() for j in range(len(self.floor_polys)): if self.floors_seen[j]: continue elif m2d.test_intersection(tri, self.floor_polys[j]): self.canvas.itemconfig(self.floors_id[j], fill='lightblue') self.floors_seen[j] = True i1 = i0
def draw_map(self): # We need to draw the OBJ file in 2D centre = [ (self.walls_AABB[1][0] - self.walls_AABB[0][0])/2 + self.walls_AABB[0][0], (self.walls_AABB[1][1] - self.walls_AABB[0][1])/2 + self.walls_AABB[0][1] ] bias = [self.width/2 - centre[0]*self.width, self.height/2 - centre[1]*self.height] scale = [self.width, self.height] self.floor_polys = [] self.floors_id = [] for floor in range(int(self.floors.get_prim_count())): prim = self.floors.get_prim(floor) if len(prim) != 3: continue A = self.floors.get_position(prim[0])[:-1] B = self.floors.get_position(prim[2])[:-1] P0 = m2d.scale_bias(A, scale, bias) P1 = m2d.scale_bias(B, scale, bias) colour = m2d.rand_colour() if self.floors_seen[floor]: colour = "lightblue" id = self.canvas.create_rectangle(P0[0], P0[1], P1[0], P1[1], fill=colour) self.floors_id.append(id) verts, _ = self.AABB_to_vertices([P0, P1]) self.floor_polys.append(verts) for wall in range(int(self.walls.get_prim_count())): prim = self.walls.get_prim(wall) if len(prim) != 3: continue A = self.walls.get_position(prim[0])[:-1] B = self.walls.get_position(prim[1])[:-1] P0 = m2d.scale_bias(A, scale, bias) P1 = m2d.scale_bias(B, scale, bias) self.canvas.create_line(P0[0], P0[1], P1[0], P1[1], fill="red", width=2)
def rasterise(vertices, callback): if len(vertices) != 3: print("Error, incorrect number of vertices") return False v0 = vertices[0] v1 = vertices[1] v2 = vertices[2] d0 = m2d.sub(v1, v0) d1 = m2d.sub(v2, v0) mnmx = minmax(v0, v1, v2) k = kross(d0, d1) for x in range(mnmx[0][0], mnmx[0][1]+1): for y in range(mnmx[1][0], mnmx[1][1]+1): q = m2d.sub([x, y], v0) s = float(kross(q, d1) / k) t = float(kross(d0, q) / k) if s >= 0 and t >= 0 and (s + t <= 1): callback((x, y))
def update_fnc(): global pixels global angle global img global photo global canvas print(angle) canvas.delete("all") pixels = [(255, 255, 255)] * size new_verts = list(map(lambda x: (int(x[0]), int(x[1])), m2d.rotate(vertices, angle, centre))) print(new_verts) rasterise(new_verts, callback) img.putdata(pixels) photo = ImageTk.PhotoImage(image=img) canvas.create_image(w/2, h/2, image=photo) angle += increment canvas.after(200, update_fnc)
def build_grid(self): scale = self.obj.scale dims = self.obj.dims centre = m2d.mul(compute_centre(self.bound), dims[0]) print("Map Dims:", scale, dims, centre) prim_count = self.obj.get_prim_count() min_dims = m2d.mul(m2d.sub(self.bound[PB][:-1], self.bound[PA][:-1]), dims[0]) print(min_dims) tile_dims = [] ss_offset = [dims[0] / 2, dims[1] / 2] # Screen space offset ss_scale = [1, -1] # Flip y axis on screen # Find minimum dimensions for floor in range(prim_count): if floor % 2 == 1: continue # Skip odd numbered primitives (the other tri in the quad) prim = self.obj.get_prim(floor) if len(prim) != 3: continue A = self.obj.get_position( prim[0])[:-1] # Left Bottom corner, truncate z coord B = self.obj.get_position(prim[1])[:-1] # Right Bottom corner C = self.obj.get_position(prim[2])[:-1] # Left Top corner lb = m2d.sub(m2d.cp_mul([A[X], A[Y]], dims), centre) rt = m2d.sub(m2d.cp_mul([B[X], C[Y]], dims), centre) # Move the polygon into screen-space for direct display by the Display Window slb = m2d.add(m2d.cp_mul(lb, ss_scale), ss_offset) srt = m2d.add(m2d.cp_mul(rt, ss_scale), ss_offset) self.poly_arr.append([slb, srt]) tile_delta = m2d.sub(rt, lb) print(floor, A, B, tile_delta) if tile_delta[X] < min_dims[X]: min_dims[X] = tile_delta[X] if tile_delta[Y] < min_dims[Y]: min_dims[Y] = tile_delta[Y] tile_dims.append(tile_delta) print(min_dims) self.min_dims = min_dims # Compute the greatest common divisor for each axis x_dims = list(map(lambda x: x[0], tile_dims)) y_dims = list(map(lambda x: x[1], tile_dims)) xmin = functools.reduce(lambda x, y: gcd(int(x), int(y)), x_dims) ymin = functools.reduce(lambda x, y: gcd(int(x), int(y)), y_dims) print( "X Axis GCD:", xmin, x_dims) # Seems to be 1 in most cases... will have to be by pixel print("Y Axis GCD:", ymin, y_dims) print("Polygons:", self.poly_arr)
def set_screen_scale(self, scale): if m2d.is_vec2(scale): self.screen_scale = scale
def get_poly(self, idx): poly = self.poly_arr[idx] return [ m2d.cp_mul(poly[0], self.screen_scale), m2d.cp_mul(poly[1], self.screen_scale) ]
root = Tk() root.title("Triangle Rasterisation Test") root.geometry("512x512") canvas = Canvas(root, width=512, height=512) canvas.pack(fill="both", expand=True) w = 512 h = 512 size = w*h img = Image.new("RGB", (w, h)) pixels = [(255, 255, 255)] * size img.putdata(pixels) photo = ImageTk.PhotoImage(image=img) vertices = [(100, 100), (200, 200), (300, 100)] centre = m2d.find_centre(vertices) # Check rotation & various close-to-degenerate tests increment = math.pi/30. angle = 0. def callback(coord): global pixels pixels[coord[1]*w + coord[0]] = (0, 0, 0) def update_fnc(): global pixels global angle global img global photo global canvas
def rotate_polygon(AABB, rotation, centre): verts, centre2 = AABB_to_vertices(AABB) return m2d.rotate(verts, rotation, centre2 if not centre else centre)