diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a36621 --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Ray caster execution products +*.png + +# 2to3 backups +*.bak + +# PyCharm +.idea/ + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json diff --git a/CSG.py b/CSG.py index 72acaa6..9a25458 100644 --- a/CSG.py +++ b/CSG.py @@ -1,42 +1,45 @@ +from builtins import object from geom3 import Vector3, Point3, Ray3, dot, unit from hit import Hit + class Intersection(object): def __init__(self, objects): - self.objs = objects + self.objs = objects def intersect(self, ray): - hit = Hit(self, ray, None, None) - for o in self.objs: - hit = hit.intersection(o.intersect(ray)) - if hit == None or hit.miss(): - if self.objs.index(o) != 0: - self.objs.remove(o) - self.objs = [o] + self.objs - return None - return hit + hit = Hit(self, ray, float('-inf'), float('-inf')) + for o in self.objs: + hit = hit.intersection(o.intersect(ray)) + if hit is None or hit.miss(): + if self.objs.index(o) != 0: + self.objs.remove(o) + self.objs = [o] + self.objs + return None + return hit + class Union(object): def __init__(self, objects): - self.objs = objects + self.objs = objects def intersect(self, ray): - hit = Hit(self, ray, None, None) - for o in self.objs: - hit = hit.union(o.intersect(ray)) - if hit.entry < hit.exit: - return hit - return None + hit = Hit(self, ray, float('-inf'), float('-inf')) + for o in self.objs: + hit = hit.union(o.intersect(ray)) + if hit.entry < hit.exit: + return hit + return None + class Difference(object): def __init__(self, objects): - self.objs = objects + self.objs = objects def intersect(self, ray): - hit = self.objs[0].intersect(ray) - if hit is not None: - hit = hit.difference(self.objs[1].intersect(ray)) - if hit and hit.entry < hit.exit: - return hit - return None - + hit = self.objs[0].intersect(ray) + if hit is not None: + hit = hit.difference(self.objs[1].intersect(ray)) + if hit and hit.entry < hit.exit: + return hit + return None diff --git a/antiAlaising.py b/antiAlaising.py index 425e7f4..e7e6e20 100644 --- a/antiAlaising.py +++ b/antiAlaising.py @@ -1,79 +1,102 @@ +from __future__ import division +from builtins import range +from builtins import object +from past.utils import old_div from geom3 import Point3, Vector3, Ray3, cross, dot, unit, length from colour import Colour from math import sqrt import random + class NoAA(object): def __init__(self, eyepoint, rayfunc): - self.eye = eyepoint - self.rayfunc = rayfunc - + self.eye = eyepoint + self.rayfunc = rayfunc + def getPixel(self, pixelBox): - (x, y, x2, y2) = pixelBox - pixelCentre = Point3((x + x2)/2, (y2 + y)/2, 1) - ray = Ray3(self.eye, pixelCentre - self.eye) - return self.rayfunc(ray) + (x, y, x2, y2) = pixelBox + pixelCentre = Point3(old_div((x + x2), 2), old_div((y2 + y), 2), 1) + ray = Ray3(self.eye, pixelCentre - self.eye) + return self.rayfunc(ray) + class SuperSampling(object): def __init__(self, eyepoint, rayfunc, subPixels=4): - self.eye = eyepoint - self.rayfunc = rayfunc - self.subPixels= int(sqrt(subPixels)) + self.eye = eyepoint + self.rayfunc = rayfunc + self.subPixels = int(sqrt(subPixels)) def getPixel(self, pixelBox): - (x, y, x2, y2) = pixelBox - pitch = (x2 - x)/ (self.subPixels * 2) - xx = x - yy = y - count = 0 - colour = Colour(0,0,0) - for col in range(self.subPixels): - for row in range(self.subPixels): - colour += self.rayfunc(Ray3(self.eye, (Point3(xx + pitch,yy + pitch,1) - self.eye))) + (x, y, x2, y2) = pixelBox + pitch = old_div((x2 - x), (self.subPixels * 2)) + xx = x + yy = y + count = 0 + colour = Colour(0, 0, 0) + for col in range(self.subPixels): + for row in range(self.subPixels): + colour += self.rayfunc(Ray3(self.eye, + (Point3(xx + pitch, yy + pitch, 1) - self.eye))) count += 1 - yy += pitch * 2 - yy = y - xx += pitch * 2 - assert count == self.subPixels * self.subPixels - return colour / count + yy += pitch * 2 + yy = y + xx += pitch * 2 + assert count == self.subPixels * self.subPixels + return old_div(colour, count) class Jitter(object): def __init__(self, eyepoint, rayfunc, subPixels=4): - self.eye = eyepoint - self.rayfunc = rayfunc - self.subPixels= int(sqrt(subPixels)) + self.eye = eyepoint + self.rayfunc = rayfunc + self.subPixels = int(sqrt(subPixels)) def getPixel(self, pixelBox): - (x, y, x2, y2) = pixelBox - pitch = (x2 - x)/ self.subPixels - xx = x - yy = y - count = 0 - colour = Colour(0,0,0) - for col in range(self.subPixels): - for row in range(self.subPixels): - colour += self.rayfunc(Ray3(self.eye, (Point3(xx + random.uniform(0, pitch) ,yy + random.uniform(0, pitch),1) - self.eye))) + (x, y, x2, y2) = pixelBox + pitch = old_div((x2 - x), self.subPixels) + xx = x + yy = y + count = 0 + colour = Colour(0, 0, 0) + for col in range(self.subPixels): + for row in range(self.subPixels): + colour += self.rayfunc( + Ray3( + self.eye, + (Point3( + xx + + random.uniform( + 0, + pitch), + yy + + random.uniform( + 0, + pitch), + 1) - + self.eye))) count += 1 - yy += pitch - yy = y - xx += pitch - assert count == self.subPixels * self.subPixels - return colour / count + yy += pitch + yy = y + xx += pitch + assert count == self.subPixels * self.subPixels + return old_div(colour, count) + class Jitter2(object): def __init__(self, eyepoint, rayfunc, subPixels=4): - self.eye = eyepoint - self.rayfunc = rayfunc - self.subPixels= subPixels + self.eye = eyepoint + self.rayfunc = rayfunc + self.subPixels = subPixels def getPixel(self, pixelBox): - (x, y, x2, y2) = pixelBox - pixSize = x2 - x - colour = Colour(0,0,0) - for i in range(self.subPixels): - colour += self.rayfunc(Ray3(self.eye, (Point3(random.uniform(x,x2) ,random.uniform(y,y2),1) - self.eye))) - return colour / self.subPixels - - - + (x, y, x2, y2) = pixelBox + pixSize = x2 - x + colour = Colour(0, 0, 0) + for i in range(self.subPixels): + colour += self.rayfunc( + Ray3( + self.eye, (Point3( + random.uniform( + x, x2), random.uniform( + y, y2), 1) - self.eye))) + return old_div(colour, self.subPixels) diff --git a/camera.py b/camera.py index c8a8514..f147b06 100755 --- a/camera.py +++ b/camera.py @@ -1,109 +1,123 @@ +from __future__ import division +from builtins import range +from builtins import object +from past.utils import old_div import math from geom3 import * -import Image +from PIL import Image from colour import Colour class Camera(object): def __init__(self, scene, eyePoint, size): - self.eyePoint = eyePoint - self.size = size - self.fov = 45 - self.viewUp = Vector3(0,1,0) - self.at = Point3(.5,.5,.5) - self.scene = scene - self.maxLength = 0 - self.pixels = [0,0,0,0,0,0,0,0,0] + self.eyePoint = eyePoint + self.size = size + self.fov = 45 + self.viewUp = Vector3(0, 1, 0) + self.at = Point3(.5, .5, .5) + self.scene = scene + self.maxLength = 0 + self.pixels = [0, 0, 0, 0, 0, 0, 0, 0, 0] - self.Update() + self.Update() def Update(self): - self.n = unit(self.at - self.eyePoint) - self.u = unit(cross(self.viewUp, self.n)) - self.v = cross(self.n, self.u) - self.VPC = self.eyePoint - self.n - self.Wp = (2 * math.tan(math.radians(self.fov) / 2)) / self.size + self.n = unit(self.at - self.eyePoint) + self.u = unit(cross(self.viewUp, self.n)) + self.v = cross(self.n, self.u) + self.VPC = self.eyePoint - self.n + self.Wp = (2 * math.tan(math.radians(self.fov) / 2)) / self.size def lookAt(self, point): - """Set the point to look at""" - self.at = point - self.Update() + """Set the point to look at""" + self.at = point + self.Update() def setFoV(self, angle): - """Set the Feild of view""" - self.fov = angle - self.Update() + """Set the Feild of view""" + self.fov = angle + self.Update() def setUp(self, vector): - self.viewUp = unit(vector) - self.Update() + self.viewUp = unit(vector) + self.Update() def getPixelCenter(self, x, y): - x = x - self.size/2 - y = y - self.size/2 - return self.VPC + x * self.Wp * self.u + y * self.Wp * self.v + x = x - self.size // 2 + y = y - self.size // 2 + return self.VPC + x * self.Wp * self.u + y * self.Wp * self.v def getRay(self, x, y): - return Ray3(self.eyePoint, self.eyePoint - self.getPixelCenter(x, y)) + return Ray3(self.eyePoint, self.eyePoint - self.getPixelCenter(x, y)) def processRay(self, ray): - hit = self.scene.intersect(ray) - hit.calcReflections(self.scene) + hit = self.scene.intersect(ray) + hit.calcReflections(self.scene) hit.calcLights(self.scene) return hit def pixelColour(self, x, y, samples=1): - pitch = .5 / samples - colour = Colour(0,0,0) - count = 0 - for subX in range(samples): - for subY in range(samples): - count += 1 - ray = self.getRay(x - .5 + (subX+1) * pitch + subX*pitch, - y - .5 + (subY+1) * pitch + subY*pitch) - hit = self.processRay(ray) - colour = colour + hit.colour() - #depth = math.log((hit.length() + 0.1), 10) - #colour = colour + Colour(depth, depth, depth) - - self.pixels[samples] += 1 + pitch = .5 / samples + colour = Colour(0, 0, 0) + count = 0 + for subX in range(samples): + for subY in range(samples): + count += 1 + ray = self.getRay(x - .5 + (subX + 1) * pitch + subX * pitch, + y - .5 + (subY + 1) * pitch + subY * pitch) + hit = self.processRay(ray) + colour = colour + hit.colour() + #depth = math.log((hit.length() + 0.1), 10) + #colour = colour + Colour(depth, depth, depth) + + self.pixels[samples] += 1 return colour / count def aa(self, x, y): - """Detect if the pixel x,y is at an edge, then anti-alais it""" - - #This code is ugly, I can't work out a more sane way to do it - M = self.size -1 - (p1, p2, p3, p4, p6, p7, p8, p9) = [(0,0,0)] * 8 - if x > 1 and y > 1: p1 = self.img.getpixel((x-1, y-1)) - if y > 1: p2 = self.img.getpixel((x, y-1)) - if x < M and y > 1: p3 = self.img.getpixel((x+1, y-1)) - if x > 1: p4 = self.img.getpixel((x-1, y)) - p5 = self.img.getpixel((x, y)) - if x < M: p6 = self.img.getpixel((x+1, y)) - if x > 1 and y < M: p7 = self.img.getpixel((x-1, y+1)) - if y < M: p8 = self.img.getpixel((x, y+1)) - if x < M and y < M: p9 = self.img.getpixel((x+1, y+1)) - - #print p1, p2, p3, p4, p5, p6, p7, p8, p9 - - r = abs((p1[0] + 2 * p2[0] + p3[0]) - (p7[0] + 2 * p8[0] + p9[0])) + abs((p2[0] + 2 * p6[0] + p9[0]) - (p1[0] + 2 * p4[0] + p7[0])) - g = abs((p1[1] + 2 * p2[1] + p3[1]) - (p7[1] + 2 * p8[1] + p9[1])) + abs((p2[1] + 2 * p6[1] + p9[1]) - (p1[1] + 2 * p4[1] + p7[1])) - b = abs((p1[2] + 2 * p2[2] + p3[2]) - (p7[2] + 2 * p8[2] + p9[2])) + abs( (p2[2] + 2 * p6[2] + p9[2]) - (p1[2] + 2 * p4[2] + p7[2]) ) - - sum = r + g + b - - if sum > 1200: # We do 3 levels of AA - colour = self.pixelColour(x, y, 4) # 16 samples per pixel - return colour.intColour() - #return Colour(1,1,0).intColour() - elif sum > 900: - colour = self.pixelColour(x, y, 3) # 9 samples per pixel - return colour.intColour() - #return Colour(0,1,1).intColour() - elif sum > 200: - colour = self.pixelColour(x, y, 2) # 4 samples per pixel - return colour.intColour() - #return Colour(1,1,1).intColour() - return p5 - + """Detect if the pixel x,y is at an edge, then anti-alais it""" + + # This code is ugly, I can't work out a more sane way to do it + M = self.size - 1 + (p1, p2, p3, p4, p6, p7, p8, p9) = [(0, 0, 0)] * 8 + if x > 1 and y > 1: + p1 = self.img.getpixel((x - 1, y - 1)) + if y > 1: + p2 = self.img.getpixel((x, y - 1)) + if x < M and y > 1: + p3 = self.img.getpixel((x + 1, y - 1)) + if x > 1: + p4 = self.img.getpixel((x - 1, y)) + p5 = self.img.getpixel((x, y)) + if x < M: + p6 = self.img.getpixel((x + 1, y)) + if x > 1 and y < M: + p7 = self.img.getpixel((x - 1, y + 1)) + if y < M: + p8 = self.img.getpixel((x, y + 1)) + if x < M and y < M: + p9 = self.img.getpixel((x + 1, y + 1)) + + # print p1, p2, p3, p4, p5, p6, p7, p8, p9 + + r = abs((p1[0] + 2 * p2[0] + p3[0]) - (p7[0] + 2 * p8[0] + p9[0])) + \ + abs((p2[0] + 2 * p6[0] + p9[0]) - (p1[0] + 2 * p4[0] + p7[0])) + g = abs((p1[1] + 2 * p2[1] + p3[1]) - (p7[1] + 2 * p8[1] + p9[1])) + \ + abs((p2[1] + 2 * p6[1] + p9[1]) - (p1[1] + 2 * p4[1] + p7[1])) + b = abs((p1[2] + 2 * p2[2] + p3[2]) - (p7[2] + 2 * p8[2] + p9[2])) + \ + abs((p2[2] + 2 * p6[2] + p9[2]) - (p1[2] + 2 * p4[2] + p7[2])) + + sum = r + g + b + + if sum > 1200: # We do 3 levels of AA + colour = self.pixelColour(x, y, 4) # 16 samples per pixel + return colour.intColour() + # return Colour(1,1,0).intColour() + elif sum > 900: + colour = self.pixelColour(x, y, 3) # 9 samples per pixel + return colour.intColour() + # return Colour(0,1,1).intColour() + elif sum > 200: + colour = self.pixelColour(x, y, 2) # 4 samples per pixel + return colour.intColour() + # return Colour(1,1,1).intColour() + return p5 diff --git a/colour.py b/colour.py index c29af56..e80b6b0 100644 --- a/colour.py +++ b/colour.py @@ -1,3 +1,5 @@ +from __future__ import division +from __future__ import print_function # The Colour class, a class to represent an RGB colour # supporting addition and multiplication operations. # Subtraction and division aren't currently supported. @@ -6,13 +8,17 @@ # @version June 2009. +from builtins import str +from builtins import object +from past.utils import old_div + + def toInt(floatVal): """Support function: convert a colour component in the range 0 - 1 to the range 0 .. 255.""" return max(0, min(255, int(256.0 * floatVal))) - class Colour(object): """Represents an RGB triple of floats, usually in the range 0 - 1. Can be multiplied on the left by a scalar or multiplied by another colour (which @@ -25,22 +31,18 @@ def __init__(self, r, g, b): self.g = g self.b = b - def __mul__(self, other): """Multiplication operator for colour * colour""" return Colour(self.r * other.r, self.g * other.g, self.b * other.b) - def __rmul__(self, factor): """Reverse multiplication operator supports scalar * colour""" return Colour(factor * self.r, factor * self.g, factor * self.b) - def __add__(self, other): """Plus operator for two colours""" return Colour(self.r + other.r, self.g + other.g, self.b + other.b) - def __iadd__(self, other): """+= operator for two colours. Componentwise addition to self.""" self.r += other.r @@ -48,37 +50,35 @@ def __iadd__(self, other): self.b += other.b return self - def __div__(self, other): - """Division operator for colours. Divides a colour by a scaler""" - return Colour(self.r / other, self.g / other, self.b / other) - + def __truediv__(self, other): + """Division operator for colours. Divides a colour by a scaler""" + return Colour(self.r / other, self.g / other, self.b / other) def intColour(self): """Return an RGB triple of self's RGB components, each multiplied by 256 and clamped to the range 0..255""" return (toInt(self.r), toInt(self.g), toInt(self.b)) - def __str__(self): """Implementation of the str function""" return "(%.2f,%.2f,%.2f)" % (self.r, self.g, self.b) - def __repr__(self): """The string representation (e.g. for serialisation) of self""" - return "Colour(%f,%f,%f)" % (self.r, self.g, self.b) + return "Colour(%f,%f,%f)" % (self.r, self.g, self.b) -# Demo code to run if this file is run directly rather than just being imported. +# Demo code to run if this file is run directly rather than just being +# imported. if __name__ == "__main__": colourA = Colour(0.5, 0.2, 0.5) - print "colourA:", str(colourA) + print("colourA:", str(colourA)) colourB = 0.5 * colourA - print "colourB:", str(colourB) + print("colourB:", str(colourB)) reflectance = Colour(1, 0.5, 0.2) - print "reflectance:", str(reflectance) - print "colourB * reflectance", reflectance * colourB + print("reflectance:", str(reflectance)) + print("colourB * reflectance", reflectance * colourB) colourA += colourB - print "After adding in colourB, colourA is: ", str(colourA) - print "In (0..255) ints, colour A is:", colourA.intColour() + print("After adding in colourB, colourA is: ", str(colourA)) + print("In (0..255) ints, colour A is:", colourA.intColour()) diff --git a/geom3.py b/geom3.py index 438b3fa..4050729 100644 --- a/geom3.py +++ b/geom3.py @@ -1,66 +1,83 @@ """Geometry module for use with COSC363, 2009. Defines Vector3, Point3, Line3, and Ray3 classes for 3D geometry. """ - +from __future__ import division +from __future__ import print_function + +from builtins import str +from builtins import range +from past.utils import old_div +from builtins import object from math import sqrt epsilon = 1.e-10 # Default epsilon for equality testing of points and vectors + class GeomException(Exception): - def __init__(self, message = None): + def __init__(self, message=None): Exception.__init__(self, message) -#================================================================ +# ================================================================ # # Point3 class # -#================================================================ +# ================================================================ + class Point3(object): """Represents a Point in 3-space with coordinates x, y, z. Note the distinction between vectors and points. Points cannot, for example, be added or scaled.""" - + def __init__(self, x, y=None, z=None): """Constructor takes a Point3, a Vector3, a 3-tuple or a 3-list or any other 3-sequence as a sole argument, or values x, y and z.""" if y is None and z is None: - self.x, self.y, self.z = x # Unpack a 3-sequence into coords + self.x, self.y, self.z = x # Unpack a 3-sequence into coords else: self.x, self.y, self.z = x, y, z # Constructor taking x, y, z def __sub__(self, other): """P1 - P2 returns a vector. P - v returns a point""" if isinstance(other, Point3): - return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) + return Vector3( + self.x - other.x, + self.y - other.y, + self.z - other.z) elif isinstance(other, Vector3): - return Point3(self.x - other.dx, self.y - other.dy, self.z - other.dz) + return Point3( + self.x - other.dx, + self.y - other.dy, + self.z - other.dz) else: return NotImplemented - + def __add__(self, other): """P + v is P translated by v""" if isinstance(other, Vector3): - return Point3(self.x + other.dx, self.y + other.dy, self.z + other.dz) + return Point3( + self.x + other.dx, + self.y + other.dy, + self.z + other.dz) else: return NotImplemented def __iter__(self): """Iterator over the coordinates""" return [self.x, self.y, self.z].__iter__() - + def __eq__(self, other): - """Equality of points is equality of all coordinates to within + """Equality of points is equality of all coordinates to within epsilon (defaults to 1.e-10).""" return (abs(self.x - other.x) < epsilon and - abs(self.y - other.y) < epsilon and - abs(self.z - other.z) < epsilon) + abs(self.y - other.y) < epsilon and + abs(self.z - other.z) < epsilon) def __ne__(self, other): """Inequality of points is inequality of any coordinates""" return not self.__eq__(other) - + def __getitem__(self, i): """P[i] is x, y, z for i in 0, 1, 2 resp.""" return [self.x, self.y, self.z][i] @@ -73,15 +90,16 @@ def __repr__(self): """String representation including class""" return "Point3" + str(self) -#================================================================ +# ================================================================ # # Vector3 class # -#================================================================ +# ================================================================ + class Vector3(object): """Represents a vector in 3-space with coordinates dx, dy, dz.""" - + def __init__(self, dx, dy=None, dz=None): """Constructor takes a Point3, a Vector3, a 3-tuple or a 3-list or any other 3-sequence as a sole argument, or @@ -90,26 +108,32 @@ def __init__(self, dx, dy=None, dz=None): self.dx, self.dy, self.dz = dx # Constructor taking pt, vec, list or tuple as arg else: self.dx, self.dy, self.dz = dx, dy, dz # Constructor taking x, y, z - + def __sub__(self, other): """Vector difference""" - return Vector3(self.dx-other.dx, self.dy-other.dy, self.dz-other.dz) + return Vector3( + self.dx - other.dx, + self.dy - other.dy, + self.dz - other.dz) def __add__(self, other): """Vector sum""" - return Vector3(self.dx+other.dx, self.dy+other.dy, self.dz+other.dz) + return Vector3( + self.dx + other.dx, + self.dy + other.dy, + self.dz + other.dz) def __mul__(self, scale): """v * r for r a float is scaling of vector v by r""" - return Vector3(scale*self.dx, scale*self.dy, scale*self.dz) + return Vector3(scale * self.dx, scale * self.dy, scale * self.dz) def __rmul__(self, scale): """r * v for r a float is scaling of vector v by r""" return self.__mul__(scale) - def __div__(self, scale): + def __truediv__(self, scale): """Division of a vector by a float r is scaling by (1/r)""" - return self.__mul__(1.0/scale) + return self.__mul__(1.0 / scale) def __neg__(self): """Negation of a vector is negation of all its coordinates""" @@ -124,30 +148,30 @@ def __getitem__(self, i): return [self.dx, self.dy, self.dz][i] def __eq__(self, other): - """Equality of vectors is equality of all coordinates to within + """Equality of vectors is equality of all coordinates to within epsilon (defaults to 1.e-10).""" return (abs(self.dx - other.dx) < epsilon and - abs(self.dy - other.dy) < epsilon and - abs(self.dz - other.dz) < epsilon) + abs(self.dy - other.dy) < epsilon and + abs(self.dz - other.dz) < epsilon) def __ne__(self, other): """Inequality of vectors is inequality of any coordinates""" return not self.__eq__(other) - - + def dot(self, other): """The usual dot product""" - return self.dx*other.dx + self.dy*other.dy + self.dz*other.dz + return self.dx * other.dx + self.dy * other.dy + self.dz * other.dz def cross(self, other): """The usual cross product""" return Vector3(self.dy * other.dz - self.dz * other.dy, - self.dz * other.dx - self.dx * other.dz, - self.dx * other.dy - self.dy * other.dx) + self.dz * other.dx - self.dx * other.dz, + self.dx * other.dy - self.dy * other.dx) def norm(self): """A normalised version of self""" - return self/length(self) + return old_div(self, length(self)) + def __str__(self): """Minimal string representation in parentheses""" return ("(%.3f,%.3f,%.3f)") % (self.dx, self.dy, self.dz) @@ -156,41 +180,44 @@ def __repr__(self): """String representation with class included""" return "Vector3" + str(self) -#================================================================ +# ================================================================ # # Line class # -#================================================================ +# ================================================================ + class Line3(object): """A line is defined by two points in space""" - + def __init__(self, p1, p2): """Constructor takes two points (or anything convertible to Point3)""" self.p1 = Point3(p1) self.p2 = Point3(p2) - + def pos(self, alpha): """The position p1 + alpha*(p2-p1) on the line""" - return self.p1 + alpha * (self.p2-self.p1) - + return self.p1 + alpha * (self.p2 - self.p1) + def repr(self): """String representation of a line""" return "Line3(%.3g, %.3g)" % (p1, p2) -#================================================================ +# ================================================================ # # Ray class # -#================================================================ - +# ================================================================ + + class Ray3(object): """A ray is a directed line, defined by a start point and a direction""" - + def __init__(self, start, dir): - """Constructor takes a start point (or something convertible to point) and + """Constructor takes a start point (or something convertible to point) and a direction vector (which need not be normalised).""" - self.start = Point3(start) # Ensure start point represented as a Point3 + self.start = Point3( + start) # Ensure start point represented as a Point3 self.dir = unit(Vector3(dir)) # Direction vector def pos(self, t): @@ -202,94 +229,93 @@ def pos(self, t): def __repr__(self): return "Ray3(%s,%s)" % (str(self.start), str(self.dir)) - - -#================================================================ + + +# ================================================================ # # Global functions on points and vectors # -#================================================================ +# ================================================================ def dot(v1, v2): """Dot product of two vectors""" return v1.dot(v2) + def cross(v1, v2): """Cross product of two vectors""" return v1.cross(v2) + def length(v): """Length of vector""" return sqrt(v.dot(v)) + def unit(v): """A unit vector in the direction of v""" return v / length(v) -#================================================================ + +# ================================================================ # # Simple unit tests if module is run as main # -#================================================================ +# ================================================================ if __name__ == '__main__': - + # Simple tests of all basic vector operations - - v1 = Vector3(1,2,3) - v2 = Vector3(3,2,1) - assert Vector3((1,2,3)) == v1 - assert Vector3([1,2,3]) == v1 - assert Vector3(Point3(1,2,3)) == v1 - assert v1 + v2 == Vector3(4,4,4) - assert v1 - v2 == Vector3(-2,0,2) - assert v1 * 3 == Vector3(3,6,9) - assert 3 * v1 == Vector3(3,6,9) - assert v1/2.0 == Vector3(0.5,1,1.5) - assert -v1 == Vector3(-1,-2,-3) + + v1 = Vector3(1, 2, 3) + v2 = Vector3(3, 2, 1) + assert Vector3((1, 2, 3)) == v1 + assert Vector3([1, 2, 3]) == v1 + assert Vector3(Point3(1, 2, 3)) == v1 + assert v1 + v2 == Vector3(4, 4, 4) + assert v1 - v2 == Vector3(-2, 0, 2) + assert v1 * 3 == Vector3(3, 6, 9) + assert 3 * v1 == Vector3(3, 6, 9) + assert old_div(v1, 2.0) == Vector3(0.5, 1, 1.5) + assert -v1 == Vector3(-1, -2, -3) assert v1[0] == 1 and v1[1] == 2 and v1[2] == 3 - assert list(v1) == [1,2,3] + assert list(v1) == [1, 2, 3] assert str(v1) == "(1.000,2.000,3.000)" assert eval(repr(v1)) == v1 assert v1.dot(v2) == 10 - assert v1.dot(v2) == dot(v1,v2) - assert v1.cross(v2) == Vector3(-4,8,-4) - assert length(unit(Vector3(2,3,4))) == 1.0 - assert length(Vector3(2,3,4).norm()) == 1.0 - + assert v1.dot(v2) == dot(v1, v2) + assert v1.cross(v2) == Vector3(-4, 8, -4) + assert length(unit(Vector3(2, 3, 4))) == 1.0 + assert length(Vector3(2, 3, 4).norm()) == 1.0 + # Tests on points - - p1 = Point3(2,4,6) - p2 = Point3(4,7,3) - assert Point3((2,4,6)) == p1 - assert Point3([2,4,6]) == p1 - assert Point3(Vector3(2,4,6)) == p1 - assert [p1[i] for i in range(3)] == [2,4,6] - assert p1-p2 == Vector3(-2,-3,3) - assert p1+v1 == Point3(3,6,9) + + p1 = Point3(2, 4, 6) + p2 = Point3(4, 7, 3) + assert Point3((2, 4, 6)) == p1 + assert Point3([2, 4, 6]) == p1 + assert Point3(Vector3(2, 4, 6)) == p1 + assert [p1[i] for i in range(3)] == [2, 4, 6] + assert p1 - p2 == Vector3(-2, -3, 3) + assert p1 + v1 == Point3(3, 6, 9) assert str(p1) == "(2.000,4.000,6.000)" assert eval(repr(p1)) == p1 try: p1 + p2 assert False - except TypeError: pass + except TypeError: + pass try: 3 * p1 assert False - except TypeError: pass + except TypeError: + pass # Some simple and arbitrary tests on lines and rays - - xRay = Ray3(Point3(0,0,0), Vector3(1,0,0)) - yRay = Ray3((0,0,0), (0,1,0)) - zRay = Ray3((0,0,0), (0,0,1)) - assert xRay.pos(1.0) == Point3(1,0,0) - assert xRay.pos(2) == Point3(2,0,0) - - print "Passed all tests" - - - - - + xRay = Ray3(Point3(0, 0, 0), Vector3(1, 0, 0)) + yRay = Ray3((0, 0, 0), (0, 1, 0)) + zRay = Ray3((0, 0, 0), (0, 0, 1)) + assert xRay.pos(1.0) == Point3(1, 0, 0) + assert xRay.pos(2) == Point3(2, 0, 0) + print("Passed all tests") diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..865ae38 --- /dev/null +++ b/helpers.py @@ -0,0 +1,5 @@ +from math import isinf + + +def isninf(value): + return (value < 0) and isinf(value) diff --git a/hit.py b/hit.py index b3df0f9..39c34da 100755 --- a/hit.py +++ b/hit.py @@ -1,145 +1,164 @@ -from geom3 import Ray3, dot, unit +from __future__ import print_function +from builtins import str +from builtins import object +from geom3 import Ray3, dot, unit from colour import Colour +from helpers import isninf class BlankHit(object): """Null hit""" + def __init__(self, colour): - self.mycolour = colour - self.entry = () # () is positive infinity (oppisit of None) - self.exit = () + self.mycolour = colour + self.entry = float("inf") # () is positive infinity (oppisit of None) + self.exit = float("inf") def colour(self): - return self.mycolour + return self.mycolour def calcLights(self, scene): - return + return def calcReflections(self, scene, depth=0): - return + return def length(self): - return 0.0 + return 0.0 + class Hit(object): """Hit objects are return by objects when rays are intersected""" - def __init__(self, obj, ray, entry, exit, normal=None, material=None, TexCords=None): + + def __init__( + self, + obj, + ray, + entry, + exit, + normal=None, + material=None, + TexCords=None): self.obj = obj - self.entry = entry - self.exit = exit - self.normal = normal - self.normal2 = None - self.mat = material - self.texCords = TexCords - self.ray = ray - - #Undefined variables - self.reflection = None - self.bgcolour = None - self.lights = [None] - + self.entry = entry + self.exit = exit + self.normal = normal + self.normal2 = None + self.mat = material + self.texCords = TexCords + self.ray = ray + + # Undefined variables + self.reflection = None + self.bgcolour = None + self.lights = [None] + # temp ambiant for when colour is called before calcLights self.ambient = Colour(0.8, 0.8, 0.8) def __repr__(self): - return "Hit " + str(self.obj) + " Along " + str(self.ray) + " entry: " + str(self.entry) + " exit: " + str(self.exit) + return "Hit " + str(self.obj) + " Along " + str(self.ray) + \ + " entry: " + str(self.entry) + " exit: " + str(self.exit) def __lt__(self, other): - return self.entry < other.entry + return self.entry < other.entry def __gt__(self, other): - return self.entry > other.entry + return self.entry > other.entry def intersection(self, other): - """Returns the intersection of 2 hits""" - ret = self - if other is None : - return None #fixme: Is this the best option? - if other.entry > self.entry: - ret.entry = other.entry - ret.mat = other.mat - ret.normal = other.normal - ret.texCords = other.texCords - if other.exit: - if self.exit: - if self.exit > other.exit: - ret.exit = other.exit - ret.normal2 = other.normal2 - else: - ret.exit = other.exit - return ret + """Returns the intersection of 2 hits""" + ret = self + if other is None: + return None # fixme: Is this the best option? + if other.entry > self.entry: + ret.entry = other.entry + ret.mat = other.mat + ret.normal = other.normal + ret.texCords = other.texCords + if not isninf(other.exit): + if not isninf(self.exit): + if self.exit > other.exit: + ret.exit = other.exit + ret.normal2 = other.normal2 + else: + ret.exit = other.exit + return ret def union(self, other): - """Returns the union of 2 hits""" - ret = self - if other is None : - return ret - if other.entry: - if not (self.entry and self.entry < other.entry): - ret.entry = other.entry - ret.mat = other.mat - ret.normal = other.normal - ret.texCords = other.texCords - if other.exit: - if other.exit > self.exit: - ret.exit = other.exit - return ret - + """Returns the union of 2 hits""" + ret = self + if other is None: + return ret + if other.entry: + if not (self.entry and self.entry < other.entry): + ret.entry = other.entry + ret.mat = other.mat + ret.normal = other.normal + ret.texCords = other.texCords + if other.exit: + if other.exit > self.exit: + ret.exit = other.exit + return ret + def difference(self, other): - """Returns the driffence of 2 hits""" - ret = self - if other is None : - return ret - if other.exit: - if other.exit > self.entry and other.entry < self.entry: - ret.entry = other.exit - ret.mat = other.mat - if other.normal2 is None: - print other - ret.normal = -other.normal2 - ret.texCords = other.texCords - return ret - + """Returns the driffence of 2 hits""" + ret = self + if other is None: + return ret + if other.exit and not isninf(other.exit): + if other.exit > self.entry and other.entry < self.entry: + ret.entry = other.exit + ret.mat = other.mat + if other.normal2 is None: + print(other) + ret.normal = -other.normal2 + ret.texCords = other.texCords + return ret + def miss(self): - return (self.exit < 0 and self.exit != None) or (self.exit != None and self.entry != None and (self.entry - self.exit) > 0.00000001) + return ( + self.exit < 0 and not isninf(self.exit)) or ( + not isninf(self.exit) and not isninf(self.entry) and ( + self.entry - self.exit) > 0.00000001) def calcLights(self, scene): - """Calculate lights for the hit. - Note: Call this after calcReflections so reflections get lights too""" - if self.entry < 0: - return - self.lights = [l.atPoint(self.ray.pos(self.entry)) for l in scene.lights] - self.ambient = scene.ambient - if self.reflection: - self.reflection.calcLights(scene) # recurse to reflections - + """Calculate lights for the hit. + Note: Call this after calcReflections so reflections get lights too""" + if self.entry < 0: + return + self.lights = [l.atPoint(self.ray.pos(self.entry)) + for l in scene.lights] + self.ambient = scene.ambient + if self.reflection: + self.reflection.calcLights(scene) # recurse to reflections + def calcReflections(self, scene, depth=0): - if self.mat.reflectivity and depth < 100 and self.entry > 0: - dir = self.ray.dir - norm = self.normal - if norm is None: - print self.obj, self.entry, self.exit - Rdir = -2 * dir.dot(norm) * norm + dir - ray = Ray3(self.ray.pos(self.entry) + Rdir * 0.0000001, Rdir) - self.reflection = scene.intersect(ray) - if self.reflection is not None: - self.reflection.calcReflections(scene, depth+1) - else: - self.bgcolour = scene.background + if self.mat.reflectivity and depth < 100 and self.entry > 0: + dir = self.ray.dir + norm = self.normal + if norm is None: + print(self.obj, self.entry, self.exit) + Rdir = -2 * dir.dot(norm) * norm + dir + ray = Ray3(self.ray.pos(self.entry) + Rdir * 0.0000001, Rdir) + self.reflection = scene.intersect(ray) + if self.reflection is not None: + self.reflection.calcReflections(scene, depth + 1) + else: + self.bgcolour = scene.background def colour(self): - if self.entry < 0: - return Colour(0,0,0) - colour = self.mat.litColour(self.normal, self.ambient, self.lights, - -self.ray.dir, self.texCords) - if self.reflection: - colour += self.mat.reflectivity * self.reflection.colour() - elif self.bgcolour: - colour += self.mat.reflectivity * self.bgcolour - return colour + if self.entry < 0: + return Colour(0, 0, 0) + colour = self.mat.litColour(self.normal, self.ambient, self.lights, + -self.ray.dir, self.texCords) + if self.reflection: + colour += self.mat.reflectivity * self.reflection.colour() + elif self.bgcolour: + colour += self.mat.reflectivity * self.bgcolour + return colour def length(self): - length = self.entry - if self.reflection is not None: - length += self.reflection.length() - return length - + length = self.entry + if self.reflection is not None: + length += self.reflection.length() + return length diff --git a/light.py b/light.py index 8fb5cdb..cea5367 100755 --- a/light.py +++ b/light.py @@ -1,61 +1,64 @@ #from colour import Colour +from builtins import object from geom3 import * import math + class Light(object): """A Basic light at inifinite distance""" def __init__(self, scene, dir, colour): - self.colour = colour - self.dir = dir - self.scene = scene - self.offset = 0.0000001 * self.dir + self.colour = colour + self.dir = dir + self.scene = scene + self.offset = 0.0000001 * self.dir def atPoint(self, point): - shadowRay = Ray3(point + self.offset, self.dir) - shadowTest = self.scene.intersect(shadowRay) - if shadowTest.entry is (): - return LightHit(self.colour, self.dir) - return None + shadowRay = Ray3(point + self.offset, self.dir) + shadowTest = self.scene.intersect(shadowRay) + if shadowTest.entry is (): + return LightHit(self.colour, self.dir) + return None + class LightHit(object): def __init__(self, colour, vector): - self.colour = colour - self.vector = vector + self.colour = colour + self.vector = vector + class PointLight(object): def __init__(self, scene, point, colour): - self.colour = colour - self.point = point - self.scene = scene - self.offset = 0.0000001 + self.colour = colour + self.point = point + self.scene = scene + self.offset = 0.0000001 def atPoint(self, point): - dir = unit(self.point - point) - shadowRay = Ray3(point + self.offset * dir, dir) - shadowTest = self.scene.intersect(shadowRay) - if shadowTest is None or shadowTest.entry > length(self.point - point): - return LightHit(self.colour, dir) - return None + dir = unit(self.point - point) + shadowRay = Ray3(point + self.offset * dir, dir) + shadowTest = self.scene.intersect(shadowRay) + if shadowTest is None or shadowTest.entry > length(self.point - point): + return LightHit(self.colour, dir) + return None + class SpotLight(object): def __init__(self, scene, point, lookAt, angle, colour): - self.colour = colour - self.point = point - self.vector = unit(point - lookAt) - self.angle = math.cos(math.radians(angle)) - self.scene = scene - self.offset = 0.0000001 + self.colour = colour + self.point = point + self.vector = unit(point - lookAt) + self.angle = math.cos(math.radians(angle)) + self.scene = scene + self.offset = 0.0000001 def atPoint(self, point): - dir = unit(self.point - point) - angle = dot(dir, self.vector) - if angle < self.angle: - return None - shadowRay = Ray3(point + self.offset * dir, dir) - shadowTest = self.scene.intersect(shadowRay) - if shadowTest is None or shadowTest.entry > length(self.point - point): - return LightHit(self.colour, dir) - return None - - + dir = unit(self.point - point) + angle = dot(dir, self.vector) + if angle < self.angle: + return None + shadowRay = Ray3(point + self.offset * dir, dir) + shadowTest = self.scene.intersect(shadowRay) + if shadowTest is None or shadowTest.entry > length(self.point - point): + return LightHit(self.colour, dir) + return None diff --git a/material.py b/material.py index 3df827d..10b660b 100644 --- a/material.py +++ b/material.py @@ -1,40 +1,47 @@ +from builtins import object from geom3 import dot, unit from colour import Colour + class Material(object): """A Material is something that can be illuminated by lighting to yield a colour. It is assumed that the ambient colour of the material is the same as its diffuse colour and there is no self-emission.""" - - def __init__(self, diffuseColour, specularColour = None, shininess = None, - reflectivity = None, texture = None): + + def __init__(self, diffuseColour, specularColour=None, shininess=None, + reflectivity=None, texture=None): """Initialise the diffuse and specular reflectances plus the specular highlight exponent. specularColour and shininess are both None for a purely diffuse surface""" self.diffuseColour = diffuseColour self.specularColour = specularColour self.shininess = shininess - self.reflectivity = reflectivity - self.texture = texture + self.reflectivity = reflectivity + self.texture = texture - - def litColour(self, normal, ambientLight, lights, viewVector, texCords=None): + def litColour( + self, + normal, + ambientLight, + lights, + viewVector, + texCords=None): """The RGB colour of this material with the given surface normal under the given lighting when viewed from an eyepoint in the viewVector direction.""" - - diffcol = self.diffuseColour - if self.texture: - diffcol = self.texture.colour(texCords) - - colour = ambientLight * diffcol - - for light in filter(lambda x: x != None, lights) : - if self.shininess: - H = unit(light.vector + viewVector) - colour += (max(0, normal.dot(light.vector)) * diffcol + - normal.dot(H)**self.shininess * self.specularColour) * light.colour - colour += max(0, normal.dot(light.vector)) * diffcol * light.colour - - return colour + + diffcol = self.diffuseColour + if self.texture: + diffcol = self.texture.colour(texCords) + + colour = ambientLight * diffcol + + for light in [x for x in lights if x is not None]: + if self.shininess: + H = unit(light.vector + viewVector) + colour += (max(0, normal.dot(light.vector)) * diffcol + normal.dot(H) + ** self.shininess * self.specularColour) * light.colour + colour += max(0, normal.dot(light.vector)) * diffcol * light.colour + + return colour diff --git a/plane.py b/plane.py index 0e20c49..875d554 100644 --- a/plane.py +++ b/plane.py @@ -5,14 +5,18 @@ plane and a normal method that returns the normal at a given point (which is irrelevant for a plane as the normal is the same everywhere).""" +from __future__ import division +from builtins import object from geom3 import Vector3, Point3, Ray3, dot, unit from math import sqrt from hit import Hit +from helpers import isninf + class Plane(object): """A ray-traceable plane""" - + def __init__(self, point, normal, material): """Create a plane through a given point with given normal and surface material""" @@ -23,32 +27,27 @@ def __init__(self, point, normal, material): def intersect(self, ray): """Returns a hit, or None if the ray is parallel to the plane""" - t = None - hit = None - angle = ray.dir.dot(self.norm) - if angle != 0: - t = (self.point - ray.start).dot(self.norm)/angle - if angle < 0: - hit = Hit(self, ray, t, (), self.norm, self.mat) - else : - hit = Hit(self, ray, None, t, self.norm, self.mat) - else: - vector = unit(ray.start - self.point) - if vector.dot(self.norm) < 0: - hit = Hit(self, ray, None, (), self.norm, self.mat) - else: - return None - if self.mat.texture and hit.entry > 0: - hit.texCords = self.texCords(ray.pos(t)) - return hit - + t = None + hit = None + angle = ray.dir.dot(self.norm) + if angle != 0: + t = (self.point - ray.start).dot(self.norm) / angle + if angle < 0: + hit = Hit(self, ray, t, float('inf'), self.norm, self.mat) + else: + hit = Hit(self, ray, float('-inf'), t, self.norm, self.mat) + else: + vector = unit(ray.start - self.point) + if vector.dot(self.norm) < 0: + hit = Hit(self, ray, float('-inf'), float('inf'), self.norm, self.mat) + else: + return None + if (self.mat.texture is not None and not isninf(hit.entry)) > 0: + hit.texCords = self.texCords(ray.pos(t)) + return hit def texCords(self, point): - vect = point - self.point - u = vect.dx - v = vect.dz - return (u, v) - - - - + vect = point - self.point + u = vect.dx + v = vect.dz + return (u, v) diff --git a/rayCaster.py b/rayCaster.py index f247295..d5e4bf3 100755 --- a/rayCaster.py +++ b/rayCaster.py @@ -1,31 +1,42 @@ #!/usr/bin/python +from __future__ import division +from __future__ import print_function +from future import standard_library +standard_library.install_aliases() +from builtins import range +from builtins import object +from past.utils import old_div import cProfile from antiAlaising import * -import Image +from PIL import Image import sys import time -from Tkinter import Tk, Canvas, PhotoImage +from tkinter import Tk, Canvas, PhotoImage from camera import Camera -sys.path.insert(0,"scenes/") +sys.path.insert(0, "scenes/") definition = __import__(sys.argv[1]) #definition = __import__("basic") WIN_SIZE = definition.camera.size + class rayCaster(object): def __init__(self): self.root = Tk() self.root.title("Ray Tracer") - canvas = Canvas(self.root, width=WIN_SIZE , height=WIN_SIZE ) - self.image = PhotoImage(master=self.root, width=WIN_SIZE, height=WIN_SIZE) - imageCentre = (WIN_SIZE / 2 + 2, WIN_SIZE / 2 + 2) - canvas.create_image(imageCentre, image = self.image) + canvas = Canvas(self.root, width=WIN_SIZE, height=WIN_SIZE) + self.image = PhotoImage( + master=self.root, + width=WIN_SIZE, + height=WIN_SIZE) + imageCentre = (WIN_SIZE // 2 + 2, WIN_SIZE // 2 + 2) + canvas.create_image(imageCentre, image=self.image) canvas.pack() # Enqueue a callback to the ray tracer to start it going - self.root.after(0, lambda : self.trace() ) + self.root.after(0, lambda: self.trace()) return def putImageRow(self, row, colours): @@ -34,7 +45,7 @@ def putImageRow(self, row, colours): Tk uses horrible hexadecimal formatted colours, packed into a string separated by spaces and all enclosed in braces." """ - + hexColours = ["#%02x%02x%02x" % colour for colour in colours] rowColourString = "{" + " ".join(hexColours) + "}" self.image.put(rowColourString, to=(0, row)) @@ -45,8 +56,8 @@ def trace(self): camera = definition.camera camera.img = Image.new("RGB", (camera.size, camera.size)) - print "ScottTracer" - print "\tTracing Rays... 0%", + print("ScottTracer") + print("\tTracing Rays... 0%", end=' ') sys.stdout.flush() count = 0 @@ -64,19 +75,19 @@ def trace(self): percentage = (count / max * 100) self.putImageRow(row, ROW) if percentage - lastPercentage > .9: - print "\b\b\b\b\b\b%4.0f%%" % percentage, + print("\b\b\b\b\b\b%4.0f%%" % percentage, end=' ') sys.stdout.flush() lastPercentage = percentage - print "\b\b\b\b\b\b Done (%f sec)" % (time.clock() - t0) + print("\b\b\b\b\b\b Done (%f sec)" % (time.clock() - t0)) - print "\tAnti-alasing... 0%", + print("\tAnti-alasing... 0%", end=' ') sys.stdout.flush() t0 = time.clock() count = 0 lastPercentage = 0 for row in range(WIN_SIZE): ROW = [] - self.putImageRow(row, [(255,255,255)] * WIN_SIZE) + self.putImageRow(row, [(255, 255, 255)] * WIN_SIZE) for col in range(WIN_SIZE): count += 1 @@ -86,15 +97,17 @@ def trace(self): percentage = (count / max * 100) self.putImageRow(row, ROW) if percentage - lastPercentage > .9: - print "\b\b\b\b\b\b%4.0f%%" % percentage, + print("\b\b\b\b\b\b%4.0f%%" % percentage, end=' ') sys.stdout.flush() lastPercentage = percentage - print "\b\b\b\b\b\b (%f sec)" % (time.clock() - t0) + print("\b\b\b\b\b\b (%f sec)" % (time.clock() - t0)) + + print(camera.pixels) - print camera.pixels + # Display image in default image-viewer application + camera.img.save(sys.argv[1] + ".png") - camera.img.save(sys.argv[1] + ".png") # Display image in default image-viewer application caster = rayCaster() -#cProfile.run("caster.root.mainloop()") +# cProfile.run("caster.root.mainloop()") caster.root.mainloop() diff --git a/scene.py b/scene.py index 6724d89..5ab772e 100644 --- a/scene.py +++ b/scene.py @@ -4,28 +4,30 @@ the object hit, in a pair. Written for COSC363. @author Richard Lobb, June 22, 2009.""" +from builtins import object from hit import BlankHit +import helpers + class Scene(object): - def __init__(self, objs = []): + def __init__(self, objs=[]): """Constructor takes a list of scene objects, each of which must provide an 'intersect' method that returns the ray t value or None of the first intersection between the ray and the object""" self.objs = objs - def intersect(self, ray): """Intersect the given ray with all objects in the scene, returning the pair (obj, t) of the first hit or None if there are no hits""" minHit = BlankHit(self.background) - for o in self.objs: - hit = o.intersect(ray) - if hit and (hit.entry > 0): - if minHit == None or hit < minHit: + for o in self.objs: + hit = o.intersect(ray) + # hit.entry can be None when hit is not None + if hit and not helpers.isninf(hit.entry) and (hit.entry > 0): + if minHit is None or hit < minHit: minHit = hit - return minHit - + return minHit diff --git a/scenes/balls.py b/scenes/balls.py index e678455..3b71379 100644 --- a/scenes/balls.py +++ b/scenes/balls.py @@ -11,39 +11,41 @@ WIN_SIZE = 300 # Screen window size (square) -SHINY_RED = Material(Colour(0.7, 0.1, 0.2), Colour(0.4,0.4,0.4), 100, .2) -SHINY_BLUE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8,0.8,0.8), 200, .3) -MATT_GREEN = Material(Colour(0.1,0.85, 0.1)) -CHECK_FLOOR = Material(None, None, None, None, Texture_Check(6, Colour(0,0,0), Colour(0.5,0.5,0.5))) +SHINY_RED = Material(Colour(0.7, 0.1, 0.2), Colour(0.4, 0.4, 0.4), 100, .2) +SHINY_BLUE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8, 0.8, 0.8), 200, .3) +MATT_GREEN = Material(Colour(0.1, 0.85, 0.1)) +CHECK_FLOOR = Material( + None, None, None, None, Texture_Check( + 6, Colour( + 0, 0, 0), Colour( + 0.5, 0.5, 0.5))) scene = Scene([ - Sphere(Point3(0.35,0.6,0.5), 0.25, SHINY_BLUE), - #Difference([ - Intersection([ # Cube - #Plane(Point3(0.2,0.0,0.5), Vector3(0,-1,0), CHECK_FLOOR), - Plane(Point3(0.1,0.175,0.8), Vector3(0.5, 1,0.1), SHINY_BLUE), - #Plane(Point3(0.1,0.1,0.5), Vector3(-1,0,0), CHECK_FLOOR), - #Plane(Point3(0.4,0.1,0.5), Vector3( 1,0,0), CHECK_FLOOR), - #Plane(Point3(0.5,0.1,0.8), Vector3(0,0, 1), CHECK_FLOOR), - #Plane(Point3(0.5,0.1,0.5), Vector3(0,0,-1), CHECK_FLOOR), - Sphere(Point3(0.1,0.175,0.8), 0.175, SHINY_BLUE), - ]), - #Sphere(Point3(0.1,0.175,0.8), 0.165, SHINY_BLUE)]), - #Sphere(Point3(0.75,0.15,.2), 0.15, SHINY_RED), - Plane(Point3(0,0,0), Vector3(0,1,0), CHECK_FLOOR) - ]) + Sphere(Point3(0.35, 0.6, 0.5), 0.25, SHINY_BLUE), + # Difference([ + Intersection([ # Cube + #Plane(Point3(0.2,0.0,0.5), Vector3(0,-1,0), CHECK_FLOOR), + Plane(Point3(0.1, 0.175, 0.8), Vector3(0.5, 1, 0.1), SHINY_BLUE), + #Plane(Point3(0.1,0.1,0.5), Vector3(-1,0,0), CHECK_FLOOR), + #Plane(Point3(0.4,0.1,0.5), Vector3( 1,0,0), CHECK_FLOOR), + #Plane(Point3(0.5,0.1,0.8), Vector3(0,0, 1), CHECK_FLOOR), + #Plane(Point3(0.5,0.1,0.5), Vector3(0,0,-1), CHECK_FLOOR), + Sphere(Point3(0.1, 0.175, 0.8), 0.175, SHINY_BLUE), + ]), + # Sphere(Point3(0.1,0.175,0.8), 0.165, SHINY_BLUE)]), + #Sphere(Point3(0.75,0.15,.2), 0.15, SHINY_RED), + Plane(Point3(0, 0, 0), Vector3(0, 1, 0), CHECK_FLOOR) +]) scene.lights = [ - #Light(scene, unit(Vector3(2,5,3)), Colour(0.6, 0.6, 0.6)), - #Light(scene, unit(Vector3(-4,3,0)), Colour(0.7, 0.7, 0.7)), - PointLight(scene, Point3(.5, 1.1, 1.2), Colour(0.9, 0.9, 0.9)), - ] + #Light(scene, unit(Vector3(2,5,3)), Colour(0.6, 0.6, 0.6)), + #Light(scene, unit(Vector3(-4,3,0)), Colour(0.7, 0.7, 0.7)), + PointLight(scene, Point3(.5, 1.1, 1.2), Colour(0.9, 0.9, 0.9)), +] scene.background = Colour(0, 0, 0) -scene.ambient = Colour(0.4, 0.4, 0.4) +scene.ambient = Colour(0.4, 0.4, 0.4) -camera = Camera(scene, Point3(0.5, 0.2, 1.6),WIN_SIZE) -camera.lookAt(Point3(0.5,0.2,0.3)) +camera = Camera(scene, Point3(0.5, 0.2, 1.6), WIN_SIZE) +camera.lookAt(Point3(0.5, 0.2, 0.3)) #camera.lookAt(Point3(0.1,0.1, 0.9)) - - diff --git a/scenes/balls2.py b/scenes/balls2.py index e511b16..9cf779c 100644 --- a/scenes/balls2.py +++ b/scenes/balls2.py @@ -11,45 +11,51 @@ WIN_SIZE = 800 # Screen window size (square) -SHINY_RED = Material(Colour(0.7, 0.1, 0.2), Colour(0.4,0.4,0.4), 100, .2) -SHINY_BLUE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8,0.8,0.8), 200, .3) -MATT_GREEN = Material(Colour(0.1,0.85, 0.1)) -CHECK_FLOOR = Material(None, None, None, None, Texture_Check(6, Colour(0,0,0), Colour(0.5,0.5,0.5))) +SHINY_RED = Material(Colour(0.7, 0.1, 0.2), Colour(0.4, 0.4, 0.4), 100, .2) +SHINY_BLUE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8, 0.8, 0.8), 200, .3) +MATT_GREEN = Material(Colour(0.1, 0.85, 0.1)) +CHECK_FLOOR = Material( + None, None, None, None, Texture_Check( + 6, Colour( + 0, 0, 0), Colour( + 0.5, 0.5, 0.5))) scene = Scene([ - Sphere(Point3(0.35,0.6,0.5), 0.25, SHINY_BLUE), - Difference([ - Intersection([ # Bowl - Plane(Point3(0.1,0.175,0.8), Vector3(0.4, 1,0.3), SHINY_BLUE), - Sphere(Point3(0.1,0.175,0.8), 0.175, SHINY_BLUE), - ]), - Sphere(Point3(0.1,0.175,0.8), 0.165, SHINY_BLUE)]), - Sphere(Point3(0.75,0.17,.8), 0.17, SHINY_RED), - Plane(Point3(0,0,0), Vector3(0,1,0), CHECK_FLOOR), - Difference([ - Intersection([ # Cube - Plane(Point3(0.2,0.0,0.5), Vector3(0,-1,0), MATT_GREEN), - Plane(Point3(0.1,0.08,0.8), Vector3(0, 1,0), MATT_GREEN), - Plane(Point3(0.3,0.1,0.5), Vector3(-1,0,0), MATT_GREEN), - Plane(Point3(0.5,0.1,0.5), Vector3( 1,0,0), MATT_GREEN), - Plane(Point3(0.5,0.1,1.3), Vector3(0,0, 1), MATT_GREEN), - Plane(Point3(0.5,0.1, 1), Vector3(0,0,-1), MATT_GREEN)]), - Sphere(Point3(0.4, .1, 1.3), 0.1, SHINY_RED)]), - ]) + Sphere(Point3(0.35, 0.6, 0.5), 0.25, SHINY_BLUE), + Difference([ + Intersection([ # Bowl + Plane( + Point3( + 0.1, 0.175, 0.8), Vector3( + 0.4, 1, 0.3), SHINY_BLUE), + Sphere(Point3(0.1, 0.175, 0.8), 0.175, SHINY_BLUE), + ]), + Sphere(Point3(0.1, 0.175, 0.8), 0.165, SHINY_BLUE)]), + Sphere(Point3(0.75, 0.17, .8), 0.17, SHINY_RED), + Plane(Point3(0, 0, 0), Vector3(0, 1, 0), CHECK_FLOOR), + Difference([ + Intersection([ # Cube + Plane(Point3(0.2, 0.0, 0.5), Vector3(0, -1, 0), MATT_GREEN), + Plane(Point3(0.1, 0.08, 0.8), Vector3(0, 1, 0), MATT_GREEN), + Plane(Point3(0.3, 0.1, 0.5), Vector3(-1, 0, 0), MATT_GREEN), + Plane(Point3(0.5, 0.1, 0.5), Vector3(1, 0, 0), MATT_GREEN), + Plane(Point3(0.5, 0.1, 1.3), Vector3(0, 0, 1), MATT_GREEN), + Plane(Point3(0.5, 0.1, 1), Vector3(0, 0, -1), MATT_GREEN)]), + Sphere(Point3(0.4, .1, 1.3), 0.1, SHINY_RED)]), +]) scene.lights = [ - #Light(scene, unit(Vector3(2,5,3)), Colour(0.6, 0.6, 0.6)), - #Light(scene, unit(Vector3(-4,3,0)), Colour(0.7, 0.7, 0.7)), - PointLight(scene, Point3(.5, 1.1, 1.2), Colour(0.9, 0.9, 0.9)), - SpotLight(scene, Point3(.5,.5,1.5), Point3(0.4,0.0,1.15), 25, Colour(0.9, 0.9, 0.9)) - ] + #Light(scene, unit(Vector3(2,5,3)), Colour(0.6, 0.6, 0.6)), + #Light(scene, unit(Vector3(-4,3,0)), Colour(0.7, 0.7, 0.7)), + PointLight(scene, Point3(.5, 1.1, 1.2), Colour(0.9, 0.9, 0.9)), + SpotLight(scene, Point3(.5, .5, 1.5), Point3( + 0.4, 0.0, 1.15), 25, Colour(0.9, 0.9, 0.9)) +] scene.background = Colour(0, 0, 0) -scene.ambient = Colour(0.1, 0.1, 0.1) - - -camera = Camera(scene, Point3(0, 0.2, 2),WIN_SIZE) -#camera.lookAt(Point3(0.5,0.2,0.3)) -camera.lookAt(Point3(0.5,0.3, 0.5)) -#camera.setFoV(90) +scene.ambient = Colour(0.1, 0.1, 0.1) +camera = Camera(scene, Point3(0, 0.2, 2), WIN_SIZE) +# camera.lookAt(Point3(0.5,0.2,0.3)) +camera.lookAt(Point3(0.5, 0.3, 0.5)) +# camera.setFoV(90) diff --git a/scenes/basic.py b/scenes/basic.py index edcb08b..c7c8c93 100755 --- a/scenes/basic.py +++ b/scenes/basic.py @@ -11,34 +11,33 @@ WIN_SIZE = 300 # Screen window size (square) -SHINY_RED = Material(Colour(0.7, 0.7, 0.7), Colour(0.4,0.4,0.4), 100, .2) -SHINY_SPHERE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8,0.8,0.8), 200, .3) -MATT_CUBE = Material(Colour(0.7,0.7, 0.7), Colour(0.9,0.9,0.9), 300, .5) -CHECK_FLOOR = Material(None, None, None, None, Texture_Check(6, Colour(.1,.1,.1), Colour(0.7,0.7,0.7))) +SHINY_RED = Material(Colour(0.7, 0.7, 0.7), Colour(0.4, 0.4, 0.4), 100, .2) +SHINY_SPHERE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8, 0.8, 0.8), 200, .3) +MATT_CUBE = Material(Colour(0.7, 0.7, 0.7), Colour(0.9, 0.9, 0.9), 300, .5) +CHECK_FLOOR = Material(None, None, None, None, Texture_Check( + 6, Colour(.1, .1, .1), Colour(0.7, 0.7, 0.7))) scene = Scene([ - Intersection([ # Cube - Plane(Point3(0.5,0.0,0.5), Vector3(0,-1,0), MATT_CUBE), - Plane(Point3(0.5,0.1,0.5), Vector3(0, 1,0), MATT_CUBE), - Plane(Point3(0.2,0.0,0.5), Vector3(-1,.3,0), MATT_CUBE), - Plane(Point3(0.8,0.0,0.5), Vector3( 1,.3,0), MATT_CUBE), - Plane(Point3(0.5,0.0,0.8), Vector3(0,.3, 1), MATT_CUBE), - Plane(Point3(0.5,0.0,0.2), Vector3(0,.3,-1), MATT_CUBE)]), - Sphere(Point3(0.5,0.3,0.5), 0.2, SHINY_SPHERE), - Plane(Point3(0,0,0), Vector3(0,1,0), CHECK_FLOOR), + Intersection([ # Cube + Plane(Point3(0.5, 0.0, 0.5), Vector3(0, -1, 0), MATT_CUBE), + Plane(Point3(0.5, 0.1, 0.5), Vector3(0, 1, 0), MATT_CUBE), + Plane(Point3(0.2, 0.0, 0.5), Vector3(-1, .3, 0), MATT_CUBE), + Plane(Point3(0.8, 0.0, 0.5), Vector3(1, .3, 0), MATT_CUBE), + Plane(Point3(0.5, 0.0, 0.8), Vector3(0, .3, 1), MATT_CUBE), + Plane(Point3(0.5, 0.0, 0.2), Vector3(0, .3, -1), MATT_CUBE)]), + Sphere(Point3(0.5, 0.3, 0.5), 0.2, SHINY_SPHERE), + Plane(Point3(0, 0, 0), Vector3(0, 1, 0), CHECK_FLOOR), ]) scene.lights = [ - SpotLight(scene, Point3( 1, 1, 1), Point3(0.5,0.5,0.5), 20, Colour(.8,0,0)), - SpotLight(scene, Point3( 1.2, 1, -1), Point3(0.5,0.5,0.5), 25, Colour(0,1,0)), - SpotLight(scene, Point3(-1, 1, 1.2), Point3(0.5,0.5,0.5), 25, Colour(0,0,1)), + SpotLight(scene, Point3(1, 1, 1), Point3(0.5, 0.5, 0.5), 20, Colour(.8, 0, 0)), + SpotLight(scene, Point3(1.2, 1, -1), Point3(0.5, 0.5, 0.5), 25, Colour(0, 1, 0)), + SpotLight(scene, Point3(-1, 1, 1.2), Point3(0.5, 0.5, 0.5), 25, Colour(0, 0, 1)), ] scene.background = Colour(0, 0, 0) -scene.ambient = Colour(0.3, 0.3, 0.3) +scene.ambient = Colour(0.3, 0.3, 0.3) -camera = Camera(scene, Point3(1.5, 0.9, 1.6),WIN_SIZE) -camera.lookAt(Point3(0.5,0.1,0.5)) +camera = Camera(scene, Point3(1.5, 0.9, 1.6), WIN_SIZE) +camera.lookAt(Point3(0.5, 0.1, 0.5)) camera.setFoV(30) - - diff --git a/scenes/pointLight.py b/scenes/pointLight.py index a3270c5..2372c45 100755 --- a/scenes/pointLight.py +++ b/scenes/pointLight.py @@ -11,39 +11,36 @@ WIN_SIZE = 300 # Screen window size (square) -SHINY_RED = Material(Colour(0.7, 0.7, 0.7), Colour(0.4,0.4,0.4), 100, .2) -SHINY_SPHERE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8,0.8,0.8), 200, .3) -MATT_CUBE = Material(Colour(0.7,0.7, 0.7), Colour(0.9,0.9,0.9), 300, .5) -MATT_PLANE = Material(Colour(0.6,0.6,0.6)) +SHINY_RED = Material(Colour(0.7, 0.7, 0.7), Colour(0.4, 0.4, 0.4), 100, .2) +SHINY_SPHERE = Material(Colour(0.2, 0.3, 0.7), Colour(0.8, 0.8, 0.8), 200, .3) +MATT_CUBE = Material(Colour(0.7, 0.7, 0.7), Colour(0.9, 0.9, 0.9), 300, .5) +MATT_PLANE = Material(Colour(0.6, 0.6, 0.6)) scene = Scene([ - Intersection([ # Cube - Plane(Point3(0.5,0.0,0.5), Vector3(0,-1,0), MATT_CUBE), - Plane(Point3(0.5,0.1,0.5), Vector3(0, 1,0), MATT_CUBE), - Plane(Point3(0.2,0.0,0.5), Vector3(-1,.3,0), MATT_CUBE), - Plane(Point3(0.8,0.0,0.5), Vector3( 1,.3,0), MATT_CUBE), - Plane(Point3(0.5,0.0,0.8), Vector3(0,.3, 1), MATT_CUBE), - Plane(Point3(0.5,0.0,0.2), Vector3(0,.3,-1), MATT_CUBE)]), - Intersection([ # Cube - Plane(Point3(0.5,0.0,0.5), Vector3(0,-1,0), MATT_CUBE), - Plane(Point3(0.5,0.1,0.5), Vector3(0, 1,0), MATT_CUBE), - Plane(Point3(0.2,0.0,0.5), Vector3(-1,.3,0), MATT_CUBE), - Plane(Point3(0.8,0.0,0.5), Vector3( 1,.3,0), MATT_CUBE), - Plane(Point3(0.5,0.0,0.8), Vector3(0,.3, 1), MATT_CUBE), - Plane(Point3(0.5,0.0,0.2), Vector3(0,.3,-1), MATT_CUBE)]), - Plane(Point3(0.5,0.0,0.0), Vector3(0,1,0) MATT_PLANE), - Plane(Point3(0.5,0.0,-1), Vector3(0,0,1) MATT_PLANE), + Intersection([ # Cube + Plane(Point3(0.5, 0.0, 0.5), Vector3(0, -1, 0), MATT_CUBE), + Plane(Point3(0.5, 0.1, 0.5), Vector3(0, 1, 0), MATT_CUBE), + Plane(Point3(0.2, 0.0, 0.5), Vector3(-1, .3, 0), MATT_CUBE), + Plane(Point3(0.8, 0.0, 0.5), Vector3(1, .3, 0), MATT_CUBE), + Plane(Point3(0.5, 0.0, 0.8), Vector3(0, .3, 1), MATT_CUBE), + Plane(Point3(0.5, 0.0, 0.2), Vector3(0, .3, -1), MATT_CUBE)]), + Intersection([ # Cube + Plane(Point3(0.5, 0.0, 0.5), Vector3(0, -1, 0), MATT_CUBE), + Plane(Point3(0.5, 0.1, 0.5), Vector3(0, 1, 0), MATT_CUBE), + Plane(Point3(0.2, 0.0, 0.5), Vector3(-1, .3, 0), MATT_CUBE), + Plane(Point3(0.8, 0.0, 0.5), Vector3(1, .3, 0), MATT_CUBE), + Plane(Point3(0.5, 0.0, 0.8), Vector3(0, .3, 1), MATT_CUBE), + Plane(Point3(0.5, 0.0, 0.2), Vector3(0, .3, -1), MATT_CUBE)]), + Plane(Point3(0.5, 0.0, 0.0), Vector3(0, 1, 0), MATT_PLANE), + Plane(Point3(0.5, 0.0, -1), Vector3(0, 0, 1), MATT_PLANE), ]) - -scene.lights = [ - SpotLight(scene, Point3(-1, 1, 1.2), Point3(0.5,0.5,0.5), 25, Colour(0,0,1)), -] + +scene.lights = [SpotLight(scene, Point3(-1, 1, 1.2), + Point3(0.5, 0.5, 0.5), 25, Colour(0, 0, 1)), ] scene.background = Colour(0, 0, 0) -scene.ambient = Colour(0.3, 0.3, 0.3) +scene.ambient = Colour(0.3, 0.3, 0.3) -camera = Camera(scene, Point3(1.5, 0.9, 1.6),WIN_SIZE) -camera.lookAt(Point3(0.5,0.1,0.5)) +camera = Camera(scene, Point3(1.5, 0.9, 1.6), WIN_SIZE) +camera.lookAt(Point3(0.5, 0.1, 0.5)) camera.setFoV(30) - - diff --git a/sphere.py b/sphere.py index c47139c..6ca1543 100644 --- a/sphere.py +++ b/sphere.py @@ -5,13 +5,16 @@ sphere and a normal method that returns the surface at a given point on the sphere surface.""" +from builtins import str +from builtins import object from geom3 import Vector3, Point3, Ray3, dot, unit from math import sqrt from hit import Hit + class Sphere(object): """A ray-traceable sphere""" - + def __init__(self, centre, radius, material): """Create a sphere with a given centre point, radius and surface material""" @@ -19,43 +22,41 @@ def __init__(self, centre, radius, material): self.radius = radius self.material = material - def normal(self, p): """The surface normal at the given point on the sphere""" return unit(p - self.centre) - def intersect(self, ray): """The ray t value of the first intersection point of the ray with self, or None if no intersection occurs""" hit = None q = self.centre - ray.start vDotQ = dot(ray.dir, q) - squareDiffs = dot(q, q) - self.radius*self.radius + squareDiffs = dot(q, q) - self.radius * self.radius discrim = vDotQ * vDotQ - squareDiffs if discrim >= 0: root = sqrt(discrim) t0 = (vDotQ - root) t1 = (vDotQ + root) if t0 < t1: - hit = Hit(self, ray, t0, t1, None, self.material) - else: - hit = Hit(self, ray, t1, t0, None, self.material) - if hit.entry > 0: - hit.normal = self.normal(ray.pos(hit.entry)) - if hit.exit > 0: - hit.normal2 = self.normal(ray.pos(hit.exit)) + hit = Hit(self, ray, t0, t1, None, self.material) + else: + hit = Hit(self, ray, t1, t0, None, self.material) + if hit.entry > 0: + hit.normal = self.normal(ray.pos(hit.entry)) + if hit.exit > 0: + hit.normal2 = self.normal(ray.pos(hit.exit)) return hit - def __repr__(self): return "Sphere(%s, %.3f)" % (str(self.centre), self.radius) # Two simple sanity tests if module is run directly + if __name__ == "__main__": - sphere = Sphere(Point3(1,0,0), 1, None) - ray = Ray3(Point3(1,0,5), Vector3(0,0,-1)) - missingRay = Ray3(Point3(1,0,5), Vector3(0,0,1)) + sphere = Sphere(Point3(1, 0, 0), 1, None) + ray = Ray3(Point3(1, 0, 5), Vector3(0, 0, -1)) + missingRay = Ray3(Point3(1, 0, 5), Vector3(0, 0, 1)) assert abs(sphere.intersect(ray) - 4.0) < 0.00001 assert sphere.intersect(missingRay) is None diff --git a/texture.py b/texture.py index c8447af..8d88d18 100644 --- a/texture.py +++ b/texture.py @@ -1,17 +1,20 @@ +from builtins import object + + class Texture_Check(object): def __init__(self, size, c1, c2): - self.colour1 = c1 - self.colour2 = c2 - self.size = size + self.colour1 = c1 + self.colour2 = c2 + self.size = size def colour(self, cords): - (u, v) = cords - u = u * self.size - v = v * self.size - if u < 0: - u = u - 1 - if v < 0: - v = v - 1 - if(int(u) % 2 != int(v) % 2): - return self.colour1 - return self.colour2 + (u, v) = cords + u = u * self.size + v = v * self.size + if u < 0: + u = u - 1 + if v < 0: + v = v - 1 + if(int(u) % 2 != int(v) % 2): + return self.colour1 + return self.colour2