diff --git a/README.md b/README.md index 7ae0e66..24f0145 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Grid Co-ordinate Reference Calculations -This repo contains sample code for working with regular hexagon and triangle grids. +This repo contains sample code for working with regular hexagon and triangle grids. It's been written as clearly and compactly as possible, to serve as a tutorial and reference for how to work with these grids. -There's many different co-ordinate systems for working with grids - this guide only covers the systems that are easiest to work with, and convert between. +There's many different coordinate systems for working with grids - this guide only covers the systems that are easiest to work with, and convert between. ## Available Grids @@ -12,7 +12,7 @@ There's many different co-ordinate systems for working with grids - this guide o [![](img/square.png)](src/square.py) ### [flat_topped_hex.py](src/flat_topped_hex.py) - +Reference guide can be found here: https://www.redblobgames.com/grids/hexagons/ [![](img/flat_topped_hex.png)](src/flat_topped_hex.py) ### [updown_tri.py](src/updown_tri.py) @@ -25,11 +25,11 @@ There's many different co-ordinate systems for working with grids - this guide o ## Usage -The code here focuses on keeping the methods as readable as possible. In real life usage, I'd recommend using classes to represent a cell and the grid as a whole. +The code here focuses on keeping the methods as readable as possible. You may want to add abstraction on top of them. The code uses a three-coordinate system for referring to hexes and triangles. This is simplest for working with in memory, but it's not a good format for storage. You can use methods like `hex_rect_index`/`hex_rect_deindex` to convert from co-ordinates to/from a single integer that addresses the cells starting at 0 and counting upwards without gaps, allowing you to store cell values efficiently in a 1d array or file. -Note that some important grid methods, like path finding, are not included. These methods are the same for any type of grid, you can find good references elsewhere. +Note that some important grid methods, like path finding, are not included. You can find implementations for those in NetworkX. ## Ports diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d850be6 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from .src import flat_topped_hex, flat_topped_trihex \ No newline at end of file diff --git a/src/flat_topped_hex.py b/src/flat_topped_hex.py index 5c2667e..b14e3db 100644 --- a/src/flat_topped_hex.py +++ b/src/flat_topped_hex.py @@ -21,16 +21,16 @@ from __future__ import division -from math import floor, ceil, sqrt -from settings import edge_length -from common import mod -from updown_tri import pick_tri, tri_line_intersect, tri_rect_intersect +from math import sqrt + +from .updown_tri import pick_tri, tri_line_intersect, tri_rect_intersect, tri_rotate_60 sqrt3 = sqrt(3) + # Basics ####################################################################### -def hex_center(x, y, z): +def hex_center(x, y, z, edge_length=1.0): """Returns the center of a given hex in cartesian co-ordinates""" # Each unit of x, y, z moves you in the direction of one of the corners of # the hex, in linear combination. @@ -38,20 +38,27 @@ def hex_center(x, y, z): # NB: This function has the nice property that if you pass in x,y,z values that # sum to 1 or -1 (not a valid hex), it'll return co-ordinates for the vertices of the # hexes. - return ((1 * x - 0.5 * y - 0.5 * z) * edge_length, - ( sqrt3 / 2 * y - sqrt3 / 2 * z) * edge_length) + return ((1 * x - 0.5 * y - 0.5 * z) * edge_length, + (sqrt3 / 2 * y - sqrt3 / 2 * z) * edge_length) -def hex_corners(x, y, z): + +def hex_corners(x, y, z, edge_length=1.0): """Returns the six corners of a given hex in cartesian co-ordinates""" return [ - hex_center(x , y , z - 1), - hex_center(x , y + 1, z ), - hex_center(x - 1, y , z ), - hex_center(x , y , z + 1), - hex_center(x , y - 1, z ), - hex_center(x + 1, y , z ), + hex_center(x, y, z - 1, edge_length=edge_length), + hex_center(x, y + 1, z, edge_length=edge_length), + hex_center(x - 1, y, z, edge_length=edge_length), + hex_center(x, y, z + 1, edge_length=edge_length), + hex_center(x, y - 1, z, edge_length=edge_length), + hex_center(x + 1, y, z, edge_length=edge_length), ] + +def hex_shift(x, y, z, dx, dy, dz): + """Given hex, shift it by a given offset (one can use hexes around origin for offsets)""" + return x + dx, y + dy, z + dz + + def tri_to_hex(x, y, z): """Given a triangle co-ordinate as specified in updown_tri, finds the hex that contains it""" # Rotate the co-ordinate system by 30 degrees, and discretize. @@ -63,82 +70,92 @@ def tri_to_hex(x, y, z): round((z - y) / 3), ) + def hex_to_tris(x, y, z): - """Given a hex, returns the co-ordinates of the 6 triangles it contains, using co-ordinates as described in updown_tri""" + """ Given a hex, returns the co-ordinates of the 6 triangles it contains, using co-ordinates as + described in updown_tri""" a = x - y b = y - z c = z - x return [ - (a + 1, b , c ), - (a + 1, b + 1, c ), - (a , b + 1, c ), - (a , b + 1, c + 1), - (a , b , c + 1), - (a + 1, b , c + 1), + (a + 1, b, c), + (a + 1, b + 1, c), + (a, b + 1, c), + (a, b + 1, c + 1), + (a, b, c + 1), + (a + 1, b, c + 1), ] + def pick_hex(x, y): """Returns the hex that contains a given cartesian co-ordinate point""" (a, b, c) = pick_tri(x, y) return tri_to_hex(a, b, c) - + def hex_neighbours(x, y, z): """Returns the hexes that share an edge with the given hex""" return [ - (x + 1, y , z - 1), - (x , y + 1, z - 1), - (x - 1, y + 1, z ), - (x - 1, y , z + 1), - (x , y - 1, z + 1), - (x + 1, y - 1, z ), + (x + 1, y, z - 1), + (x, y + 1, z - 1), + (x - 1, y + 1, z), + (x - 1, y, z + 1), + (x, y - 1, z + 1), + (x + 1, y - 1, z), ] + def hex_dist(x1, y1, z1, x2, y2, z2): """Returns how many steps one hex is from another""" return (abs(x1 - x2) + abs(y1 - y2) + abs(z1 - z2)) // 2 + # Symmetry ##################################################################### -def hex_rotate_60(x, y, z, n = 1): +def hex_rotate_60(x, y, z, n=1): """Rotates the given hex n * 60 degrees counter clockwise around the origin, and returns the co-ordinates of the new hex.""" - n = mod(n, 6) + n = n % 6 if n == 0: - return (x, y, z) + return x, y, z if n == 1: - return (-y, -z, -x) + return -y, -z, -x if n == 2: - return (z, x, y) + return z, x, y if n == 3: - return (-x, -y, -z) + return -x, -y, -z if n == 4: - return (y, z, x) + return y, z, x if n == 5: - return (-z, -x, -y) + return -z, -x, -y + -def hex_rotate_about_60(x, y, z, about_x, about_y, about_z, n = 1): +def hex_rotate_about_60(x, y, z, about_x, about_y, about_z, n=1): """Rotates the given hex n* 60 degress counter clockwise about the given hex and return the co-ordinates of the new hex.""" (a, b, c) = tri_rotate_60(x - about_x, y - about_y, z - about_z) - return (a + about_x, y + about_y, z + about_z) + return a + about_x, y + about_y, z + about_z + def hex_reflect_y(x, y, z): """Reflects the given hex through the x-axis and returns the co-ordinates of the new hex""" - return (x, z, y) + return x, z, y + def hex_reflect_x(x, y, z): """Reflects the given hex through the y-axis and returns the co-ordinates of the new hex""" - return (-x, -z, -y) + return -x, -z, -y + -def hex_reflect_by(x, y, z, n = 0): +def hex_reflect_by(x, y, z, n=0): """Reflects the given hex through the x-axis rotated counter clockwise by n * 30 degrees and returns the co-ordinates of the new hex""" (a, b, c) = hex_reflect_y(x, y, z) return hex_rotate_60(a, b, c, n) + # Shapes ####################################################################### def hex_disc(x, y, z, r): @@ -146,7 +163,8 @@ def hex_disc(x, y, z, r): for dx in range(-r, r + 1): for dy in range(max(-r, -dx - r), min(r, -dx + r) + 1): dz = -dx - dy - yield (x + dx, y + dy, z + dz) + yield x + dx, y + dy, z + dz + def hex_line_intersect(x1, y1, x2, y2): """Returns hexes that intersect the line specified in cartesian co-ordinates""" @@ -157,8 +175,10 @@ def hex_line_intersect(x1, y1, x2, y2): yield hex prev = hex + def hex_line(x1, y1, z1, x2, y2, z2): - """Returns the hexes in a shortest path from one hex to another, staying as close to the straight line as possible""" + """Returns the hexes in a shortest path from one hex to another, staying as close to the straight line as + possible """ # Note that drawing a straight line from one hex to another can touch hexes not returned by this method. n = hex_dist(x1, y1, z1, x2, y2, z2) c1 = hex_center(x1, y1, z1) @@ -169,6 +189,7 @@ def hex_line(x1, y1, z1, x2, y2, z2): py = c1[1] + (c2[1] - c1[1]) * t yield pick_hex(px, py) + def hex_rect_intersect(x, y, width, height): """Returns the hexes that intersect the rectangle specified in cartesian co-ordinates""" prev = None @@ -183,6 +204,7 @@ def hex_rect_intersect(x, y, width, height): yield hex prev = hex + def hex_rect(rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=False): """Returns the hexes in a rectangle that includes the given hex in the bottom left, that extends `height` hexes upwards, and `width` hexes to the right. @@ -192,7 +214,7 @@ def hex_rect(rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=Fa for dx in range(width): # yield a vertical column for dy in range(height + (dx % 2) * odd_height): - yield (x, y + dy, z - dy) + yield x, y + dy, z - dy # Move one column along, staying at the bottom of the rect x += 1 if dx % 2 == int(inc_bottom): @@ -200,8 +222,10 @@ def hex_rect(rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=Fa else: y -= 1 + def hex_rect_knoll(x, y, z, rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=False): - """Given a hex and a rectangle, gives a pair of integer cartesian co-ordinates that identify the square in the rectangle""" + """Given a hex and a rectangle, gives a pair of integer cartesian co-ordinates that identify the + square in the rectangle""" dx = x - rect_x if dx < 0 or dx >= width: return None @@ -211,10 +235,12 @@ def hex_rect_knoll(x, y, z, rect_x, rect_y, rect_z, width, height, inc_bottom=Fa dy = y - base_dy return (dx, dy) + def hex_rect_unknoll(dx, dy, rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=False): """Given a co-ordinate pair and a rectangle, reverses hex_rect_knoll""" oy = - (dx // 2) - (dx % 2) * int(inc_bottom) - return (rect_x + dx, rect_y + oy + dy, rect_z - dx - oy - dy) + return rect_x + dx, rect_y + oy + dy, rect_z - dx - oy - dy + def hex_rect_index(x, y, z, rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=False): """Given a hex and a rectangle, gives a linear position of the hex. @@ -232,6 +258,7 @@ def hex_rect_index(x, y, z, rect_x, rect_y, rect_z, width, height, inc_bottom=Fa return None return left_count + dy + def hex_rect_deindex(index, rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=False): """Performs the inverse of hex_rect_index Equivalent to list(hex_rect(...))[index]""" @@ -247,14 +274,16 @@ def hex_rect_deindex(index, rect_x, rect_y, rect_z, width, height, inc_bottom=Fa dy = index return hex_rect_unknoll(dx, dy, rect_x, rect_y, rect_z, width, height, inc_bottom, inc_top) + def hex_rect_size(rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_top=False): """Returns the number of hexes in a given rectangle. Equivalent to len(list(hex_rect(...)))""" odd_height = int(inc_bottom) + int(inc_top) - 1 return height * width + odd_height * (width // 2) + # Nesting ## ################################################################### - + # Based on work in https://observablehq.com/@sanderevers/hexagon-tiling-of-an-hexagonal-grid # Groups hexes into larger shapes that is each a disc around a given center hex # These parent discs are themselves roughly hexagon shaped and roughly laid out like a pointy topped hexagon grid. @@ -263,6 +292,7 @@ def hex_rect_size(rect_x, rect_y, rect_z, width, height, inc_bottom=False, inc_t parent_area = 3 * parent_radius * parent_radius + 3 * parent_radius + 1 parent_shift = 3 * parent_radius + 2 + def hex_parent(x, y, z): """Returns the parent hex containing the given hex.""" a = (z + y * parent_shift) // parent_area @@ -274,6 +304,7 @@ def hex_parent(x, y, z): (1 + b - a) // 3, ) + def hex_parent_center_child(x, y, z): """Returns the central hex of a given parent hex.""" a = y - z @@ -285,7 +316,8 @@ def hex_parent_center_child(x, y, z): (parent_shift * b + a) // 3, ) + def hex_parent_children(x, y, z): """Returns all children hex of a given parent hex""" cx, cy, cz = hex_parent_center_child(x, y, z) - return hex_disc(cx, cy, cz, parent_radius) \ No newline at end of file + return hex_disc(cx, cy, cz, parent_radius) diff --git a/src/flat_topped_trihex.py b/src/flat_topped_trihex.py index e4262d8..3ada965 100644 --- a/src/flat_topped_trihex.py +++ b/src/flat_topped_trihex.py @@ -26,9 +26,9 @@ from __future__ import division from math import floor, ceil, sqrt -from settings import edge_length -from common import mod -from updown_tri import pick_tri, tri_line_intersect, tri_rect_intersect + + +from .updown_tri import pick_tri, tri_line_intersect, tri_rect_intersect sqrt3 = sqrt(3) @@ -44,7 +44,7 @@ def trihex_cell_type(a, b, c): if n == -1: return "tri_down" -def trihex_center(a, b, c): +def trihex_center(a, b, c, edge_length=1.0): """Returns the center of a given trihex in cartesian co-ordinates""" return (( a + -c) * edge_length, (-sqrt3 / 3 * a + sqrt3 * 2 / 3 * b - sqrt3 / 3 * c) * edge_length) diff --git a/src/settings.py b/src/settings.py deleted file mode 100644 index 5e116ed..0000000 --- a/src/settings.py +++ /dev/null @@ -1,2 +0,0 @@ -# This is the side length of the triangle/hex -edge_length = 1 \ No newline at end of file diff --git a/src/square.py b/src/square.py index e437624..b3f1eee 100644 --- a/src/square.py +++ b/src/square.py @@ -14,16 +14,17 @@ # the cartesian x-axis and y-axis from 0 to 1. from __future__ import division -from math import floor, ceil, sqrt -from settings import edge_length -from common import mod + +from math import floor, ceil + # Basics ####################################################################### -def square_center(x, y): +def square_center(x, y, edge_length=1): """Returns the center of a given square in cartesian co-ordinates""" - return (x * edge_length, y * edge_length) + return x * edge_length, y * edge_length + def square_corners(x, y): """Returns the four corners of a given square in cartesian co-ordinates""" @@ -34,13 +35,15 @@ def square_corners(x, y): square_center(x - 0.5, y + 0.5), ] -def pick_square(x, y): + +def pick_square(x, y, edge_length=1): """Returns the square that contains a given cartesian co-ordinate point""" return ( floor(x / edge_length), floor(y / edge_length), ) + def square_neighbours(x, y): """Returns the squares that share an edge with the given square""" return ( @@ -50,47 +53,54 @@ def square_neighbours(x, y): (x, y - 1), ) + def square_dist(x1, y1, x2, y2): """Returns how many steps one square is from another""" return abs(x1 - x2) + abs(y1 - y2) + # Symmetry ##################################################################### -def square_rotate_90(x, y, n = 1): +def square_rotate_90(x, y, n=1): """Rotates the given square n * 90 degrees counter clockwise around the (0, 0) square, and returns the co-ordinates of the new square.""" - n = mod(n, 4) + n = n % 4 if n == 0: - return (x, y) + return x, y if n == 1: - return (-y, x) + return -y, x if n == 2: - return (-x, -y) + return -x, -y if n == 3: - return (y, -x) + return y, -x -def square_rotate_about_90(x, y, about_x, about_y, n = 1): + +def square_rotate_about_90(x, y, about_x, about_y, n=1): """Rotates the given square n * 90 degress counter clockwise about the given square and return the co-ordinates of the new square.""" - x1, y2 = square_rotate_90(x - about_x, y - about_y) - return (x2 + about_x, y2 + about_y) + x2, y2 = square_rotate_90(x - about_x, y - about_y) + return x2 + about_x, y2 + about_y + def square_reflect_y(x, y): """Reflects the given square through the x-axis and returns the co-ordinates of the new square""" - return (x, -y) + return x, -y + def square_reflect_x(x, y): """Reflects the given square through the y-axis and returns the co-ordinates of the new square""" - return (-x, y) + return -x, y -def square_reflect_by(x, y, n = 0): + +def square_reflect_by(x, y, n=0): """Reflects the given square through the x-axis rotated counter clockwise by n * 45 degrees and returns the co-ordinates of the new square""" (x2, y2) = square_reflect_y(x, y) return square_rotate_90(x2, y2, n) + # Shapes ####################################################################### def square_disc(x, y, r): @@ -99,7 +109,8 @@ def square_disc(x, y, r): for dy in range(-r + abs(dx), r - abs(dx) + 1): yield (x + dx, y + dy) -def square_line_intersect(x1, y1, x2, y2): + +def square_line_intersect(x1, y1, x2, y2, edge_length=1): """Returns squares that intersect the line specified in cartesian co-ordinates""" x1 /= edge_length y1 /= edge_length @@ -115,48 +126,58 @@ def square_line_intersect(x1, y1, x2, y2): ty = (y - int(dy <= 0) - y2) / dy idx = abs(1 / dx) idy = abs(1 / dy) - yield (x, y) + yield x, y while True: if tx <= ty: - if tx > 1: return + if tx > 1: + return x += stepx tx += idx else: - if ty > 1: return + if ty > 1: + return y += stepy ty += idy - yield (tx, ty) + yield tx, ty + def square_line(x1, y1, x2, y2): - """Returns the squares in a shortest path from one square to another, staying as close to the straight line as possible""" + """Returns the squares in a shortest path from one square to another, + staying as close to the straight line as possible""" (fx1, fy1) = square_center(x1, y1) (fx2, fy2) = square_center(x2, y2) return square_line_intersect(fx1, fy1, fx2, fy2) -def square_rect_intersect(x, y, width, height): + +def square_rect_intersect(x, y, width, height, edge_length=1): """Returns the square that intersect the rectangle specified in cartesian co-ordinates""" minx = floor(x / edge_length) maxx = ceil((x + width) / edge_length) miny = floor(y / edge_length) maxy = ceil((y + height) / edge_length) for x in range(minx, maxx + 1): - for y in range(miny, maxy + 1): + for y in range(miny, maxy + 1): yield (x, y) + def square_rect(rect_x, rect_y, width, height): """Returns the squares in a rectangle that includes the given sququre in the bottom left, that extends `height` squares upwards, and `width` squares to the right.""" for dx in range(width): for dy in range(height): - yield (rect_x + x, rect_y + y) + yield rect_x + dx, rect_y + dy + def square_rect_knoll(x, y, rect_x, rect_y, width, height): - """Given a square and a rectangle, gives a pair of integer cartesian co-ordinates that identify the square in the rectangle""" - return (x - rect_x, y - rect_y) + """Given a square and a rectangle, gives a pair of integer cartesian co-ordinates that + identify the square in the rectangle""" + return x - rect_x, y - rect_y + def square_rect_unknoll(dx, dy, rect_x, rect_y, width, height): """Given a co-ordinate pair and a rectangle, reverses square_rect_knoll""" - return (x + rect_x, y + rect_y) + return dx + rect_x, dy + rect_y + def square_rect_index(x, y, rect_x, rect_y, width, height): """Given a square and a rectangle, gives a linear position of the square. @@ -170,33 +191,39 @@ def square_rect_index(x, y, rect_x, rect_y, width, height): return None return dx + dy * width + def square_rect_deindex(index, rect_x, rect_y, width, height): """Performs the inverse of square_rect_index Equivalent to list(square_rect(...))[index]""" dx = index % width dy = index // width assert dx >= 0 and dy < height - return (rect_x + dx, rect_y + dy) + return rect_x + dx, rect_y + dy + def square_rect_size(rect_x, rect_y, width, height): """Returns the number of squares in a given rectangle. Equivalent to len(list(square_rect(...)))""" return width * height + # Nesting ## ################################################################### parent_width = 3 parent_height = 2 + def square_parent(x, y): """Returns the parent square containing the given square.""" - return (x // parent_width, y // parent_height) + return x // parent_width, y // parent_height + def square_parent_rect(x, y): """Returns the left, bottom, width, height of th rect of a given parent square.""" - return (x * parent_width, y * parent_height, parent_width, parent_height) + return x * parent_width, y * parent_height, parent_width, parent_height + def square_parent_children(x, y): """Returns all children squares of a given parent square""" (x, y, width, height) = square_parent_rect(x, y) - return square_rect(x, y, width, height) \ No newline at end of file + return square_rect(x, y, width, height) diff --git a/src/test_flat_topped_hex.py b/src/test_flat_topped_hex.py index 77692e4..841e22a 100644 --- a/src/test_flat_topped_hex.py +++ b/src/test_flat_topped_hex.py @@ -2,6 +2,7 @@ from updown_tri import tri_center import unittest + class TestFlatToppedHex(unittest.TestCase): def test_rect1(self): @@ -60,12 +61,11 @@ def test_hex_line_intersect(self): (4, -3, -1) ]) - def test_hex_line(self): self.assertListEqual(list(hex_line(0, 0, 0, 4, -3, -1)), [ (0, 0, 0), (1, -1, 0), - (2, -1, -1), # (2, -2, 0) would be an equally valid choice + (2, -1, -1), # (2, -2, 0) would be an equally valid choice (3, -2, -1), (4, -3, -1) ]) @@ -106,6 +106,5 @@ def test_parent(x, y, z, px, py, pz): test_parent(10, -4, -6, 2, -2, 0) - if __name__ == '__main__': unittest.main() diff --git a/src/updown_tri.py b/src/updown_tri.py index 84a384f..306998d 100644 --- a/src/updown_tri.py +++ b/src/updown_tri.py @@ -19,14 +19,13 @@ # To find the neighbours of a down triangle, add 1 to a co-ordinate, and subtract one for neighbours of an up triangle. from math import floor, ceil, sqrt -from settings import edge_length -from common import mod + sqrt3 = sqrt(3) # Basics ####################################################################### -def tri_center(a, b, c): +def tri_center(a, b, c, edge_length=1.0): """Returns the center of a given triangle in cartesian co-ordinates""" # Each unit of a, b, c moves you in the direction of one of the edges of a # down triangle, in linear combination. @@ -57,7 +56,7 @@ def tri_corners(a, b, c): tri_center(a, -1 + b, c), ] -def pick_tri(x, y): +def pick_tri(x, y, edge_length=1.0): """Returns the triangle that contains a given cartesian co-ordinate point""" # Using dot product, measures which row and diagonals a given point occupies. # Or equivalently, multiply by the inverse matrix to tri_center @@ -105,7 +104,7 @@ def tri_disc(a, b, c, r): def tri_rotate_60(a, b, c, n = 1): """Rotates the given triangle n * 60 degrees counter clockwise around the origin, and returns the co-ordinates of the new triangle.""" - n = mod(n, 6) + n = n% 6 if n == 0: return (a, b, c) if n == 1: @@ -143,7 +142,7 @@ def tri_reflect_by(a, b, c, n = 0): # Shapes ####################################################################### -def tri_line_intersect(x1, y1, x2, y2): +def tri_line_intersect(x1, y1, x2, y2, edge_length=1.0): """Returns the triangles that intersect the line specified in cartesian co-ordinates""" x1 /= edge_length y1 /= edge_length @@ -197,7 +196,7 @@ def tri_line(a1, b1, c1, a2, b2, c2): (x2, y2) = tri_center(a2, b2, c2) return tri_line_intersect(x1, y1, x2, y2) -def tri_rect_intersect(x, y, width, height): +def tri_rect_intersect(x, y, width, height, edge_length=1.0): """Returns the tris that intersect the rectangle specified in cartesian co-ordinates""" assert width >= 0, "Rectangle should have non-negative width" assert height >= 0, "Rectangle should have non-negative height"