def testSubtraction(self): testMatrix1 = Matrix() testMatrix2 = Matrix() testMatrix3 = testMatrix1 - testMatrix2 for row in range(4): for col in range(4): self.assertTrue(testMatrix3.getValue(row, col) == 0) testMatrix1.setValue(0, 3, 2.5) testMatrix1.setValue(2, 2, 4.2) testMatrix1.setValue(3, 0, -301) testMatrix2.setValue(0, 3, -1) testMatrix2.setValue(0, 0, -2) testMatrix2.setValue(3, 0, 2) testMatrix4 = testMatrix1 - testMatrix2 self.assertTrue(testMatrix4.getValue(0, 3) == 3.5) self.assertTrue(testMatrix4.getValue(2, 2) == 4.2) self.assertTrue(testMatrix4.getValue(3, 0) == -303) self.assertTrue(testMatrix4.getValue(0, 0) == 2.0) self.assertTrue(testMatrix4.getValue(2, 1) == 0)
def testCreation(self): testMatrix = Matrix() for row in range(4): for col in range(4): self.assertTrue(testMatrix.getValue(row, col) == 0) testMatrix.setValue(0, 0, 1.893) testMatrix.setValue(2, 1, -200.1) testMatrix.setValue(3, 2, 4) self.assertTrue(testMatrix.getValue(0, 0) == 1.893) self.assertTrue(testMatrix.getValue(2, 1) == -200.1) self.assertTrue(testMatrix.getValue(3, 2) == 4)
def testTransform(self): testVector1 = Vector3(8.2, 1.524, 1.5) testMatrix = Matrix() testMatrix.setValue(3, 3, 1) testMatrix.setValue(2, 2, 3) testMatrix.setValue(1, 1, -1) testMatrix.setValue(0, 0, 2) testVector2 = testVector1.transform(testMatrix) self.assertTrue(testVector2.x == 16.4) self.assertTrue(testVector2.y == -1.524) self.assertTrue(testVector2.z == 4.5)
from Matrix import Matrix m = Matrix (3,3) m.setValue (3, 5.5) print m.getValue(3) m.printMatrix()
def testMultiplication(self): testMatrix1 = Matrix() testMatrix2 = Matrix() testMatrix3 = testMatrix1 * testMatrix2 for row in range(4): for col in range(4): self.assertTrue(testMatrix3.getValue(row, col) == 0) testMatrix1.setValue(0, 3, 2.5) testMatrix1.setValue(2, 2, 4.2) testMatrix1.setValue(3, 0, -301) testMatrix2.setValue(0, 3, -1) testMatrix2.setValue(0, 0, -2) testMatrix2.setValue(3, 0, 2) testMatrix4 = testMatrix1 * testMatrix2 self.assertTrue(testMatrix4.getValue(0, 3) == 0) self.assertTrue(testMatrix4.getValue(2, 2) == 0) self.assertTrue(testMatrix4.getValue(3, 0) == 602) self.assertTrue(testMatrix4.getValue(0, 0) == 5) self.assertTrue(testMatrix4.getValue(2, 1) == 0) self.assertTrue(testMatrix4.getValue(3, 3) == 301) testMatrix5 = Matrix() testMatrix5.setValue(0, 0, .5) testMatrix5.setValue(0, 1, 7) testMatrix5.setValue(0, 2, 2.9) testMatrix5.setValue(0, 3, -1.0) testMatrix5.setValue(1, 0, 9) testMatrix5.setValue(1, 1, 1.1) testMatrix5.setValue(1, 2, -302.01) testMatrix5.setValue(1, 3, 21.0) testMatrix5.setValue(2, 0, 0) testMatrix5.setValue(2, 1, 1.8) testMatrix5.setValue(2, 2, 9.9) testMatrix5.setValue(2, 3, 10) testMatrix5.setValue(3, 0, -4) testMatrix5.setValue(3, 1, 2) testMatrix5.setValue(3, 2, 1) testMatrix5.setValue(3, 3, 8.9) testMatrix6 = Matrix() testMatrix6.setValue(0, 0, 18.2) testMatrix6.setValue(0, 1, 2) testMatrix6.setValue(0, 2, 4) testMatrix6.setValue(0, 3, 6) testMatrix6.setValue(1, 0, 1) testMatrix6.setValue(1, 1, 2) testMatrix6.setValue(1, 2, 4) testMatrix6.setValue(1, 3, 3) testMatrix6.setValue(2, 0, 9) testMatrix6.setValue(2, 1, 8) testMatrix6.setValue(2, 2, 7) testMatrix6.setValue(2, 3, 5) testMatrix6.setValue(3, 0, .1) testMatrix6.setValue(3, 1, .8) testMatrix6.setValue(3, 2, -.5) testMatrix6.setValue(3, 3, -12) testMatrix7 = testMatrix5 * testMatrix6 self.assertTrue(round(testMatrix7.getValue(0, 0), 2) == 42.1) self.assertTrue(round(testMatrix7.getValue(0, 1), 2) == 37.4) self.assertTrue(round(testMatrix7.getValue(0, 2), 2) == 50.8) self.assertTrue(round(testMatrix7.getValue(0, 3), 2) == 50.5) self.assertTrue(round(testMatrix7.getValue(1, 0), 2) == -2551.09) self.assertTrue(round(testMatrix7.getValue(1, 1), 2) == -2379.08) self.assertTrue(round(testMatrix7.getValue(1, 2), 2) == -2084.17) self.assertTrue(round(testMatrix7.getValue(1, 3), 2) == -1704.75) self.assertTrue(round(testMatrix7.getValue(2, 0), 2) == 91.9) self.assertTrue(round(testMatrix7.getValue(2, 1), 2) == 90.8) self.assertTrue(round(testMatrix7.getValue(2, 2), 2) == 71.5) self.assertTrue(round(testMatrix7.getValue(2, 3), 2) == -65.1) self.assertTrue(round(testMatrix7.getValue(3, 0), 2) == -60.91) self.assertTrue(round(testMatrix7.getValue(3, 1), 2) == 11.12) self.assertTrue(round(testMatrix7.getValue(3, 2), 2) == -5.45) self.assertTrue(round(testMatrix7.getValue(3, 3), 2) == -119.8)
class Raytracer(object): """Raytracer defines a renderer that accepts a Scene and uses the basic ray tracing algorithm to render it""" def __init__(self, scene, maxRayDepth=3): """ Create a Raytracer object Keyword arguments: scene (Scene) -- the scene to render maxRayDepth (int) -- the maximum recursive steps for the ray tracing """ self.scene = scene self.maxRayDepth = maxRayDepth #initialize private variables to be used at render time self._curRayDepth = 1 self._toWorldTrans = Matrix() self._viewWidth = 0 self._viewHeight = 0 def _initWorldTransformations(self): """ Initiliaze transformations and viewport variables to allow straigtforward world-to-view transformations at render time. """ #initialize the transformation matrix using the scene's camera self._toWorldTrans.setValue(0, 0, self.scene.camera.u.x) self._toWorldTrans.setValue(1, 0, self.scene.camera.u.y) self._toWorldTrans.setValue(2, 0, self.scene.camera.u.z) self._toWorldTrans.setValue(0, 1, self.scene.camera.v.x) self._toWorldTrans.setValue(1, 1, self.scene.camera.v.y) self._toWorldTrans.setValue(2, 1, self.scene.camera.v.z) self._toWorldTrans.setValue(0, 2, self.scene.camera.n.x) self._toWorldTrans.setValue(1, 2, self.scene.camera.n.y) self._toWorldTrans.setValue(2, 2, self.scene.camera.n.z) self._toWorldTrans.setValue(0, 3, self.scene.camera.lookAt.x) self._toWorldTrans.setValue(1, 3, self.scene.camera.lookAt.y) self._toWorldTrans.setValue(2, 3, self.scene.camera.lookAt.z) self._toWorldTrans.setValue(3, 3, 1.0) #compute the dimensions of the viewport using the scene's camera self._viewWidth = math.tan(math.radians(self.scene.camera.fov)) * abs(self.scene.camera.lookFrom.z - \ self.scene.camera.lookAt.z) * 2 self._viewHeight = self._viewWidth def renderScene(self, imageWidth, imageHeight, outputPath): """ Render the scene with the specified parameters Keyword arguments: imageWidth (int) -- width in pixels of the image to render, this value must be the same as imageHeight imageHeight (int) -- height in pixels of the image to render, this value must be the same as imageWidth outputPath (str) -- path to write the rendered image Note -- the current impelementation is limited to square images, so imageWidth must equal imageHeight """ if not imageWidth == imageHeight: print "Error -- imageWidth must equal imageHeight" return #initialize world-to-view transformations and image self._initWorldTransformations() outputImg = Image.new("RGB", (imageWidth, imageHeight)) #iterate over each pixel for i in range(imageWidth): for j in range(imageHeight): #get the direction from the camera to the current pixel direction = self._rayDirection(j, i, imageWidth, imageHeight, self.scene.camera) self._curRayDepth = 1 #recursively trace a ray into the scene color = self._shootRay(self.scene.camera.lookFrom, direction) #set the pixel in the image outputImg.putpixel((j, i), (int(color.x * 255), int(color.y * 255), int(color.z * 255))) #save the output image outputImg.save(outputPath) def _rayDirection(self, pixelX, pixelY, imageWidth, imageHeight, camera): """ Compute the direction of the ray with the given parameters Keyword arguments: pixelX (int) -- the horizontal pixel index of the image, (0, 0) is the top left of the image pixelY (int) -- the vertical pixel index of the image, (0, 0) is the top left of the image imageWidth (int) -- the total number of pixels horizontally in the image imageHeight (int) -- the total number of pixels vertically in the image camera (Camera) -- the camera that defines the view into the scene Returns a Vector3 representing the direction of the ray given the parameters """ #since (0, 0) is the top left of the image, flip the y coordinate newPixelY = imageHeight - pixelY #compute the x and y values given the view dimensions newX = (pixelX / float(imageWidth - 1)) * self._viewWidth newY = (newPixelY / float(imageHeight - 1)) * self._viewHeight newX -= self._viewWidth / 2.0 newY -= self._viewHeight / 2.0 #compute the world space position of the pixel pixelPos = Vector3(newX, newY, 0.0) pixelPos = pixelPos.transform(self._toWorldTrans) pixelPos.normalize() direction = Vector3(pixelPos.x - camera.lookFrom.x, pixelPos.y - camera.lookFrom.y, pixelPos.z - camera.lookFrom.z) return direction def _shootRay(self, lookFrom, direction): """ Shoot a ray into the scene recursively Keyword arguments: lookFrom (Point3) -- the initial position of the ray direction (Vector3) -- the direction of the ray Returns an RGB color value represented as a Point3 """ #find an intersection with an object in the scene intersection, object = self._findIntersection(lookFrom, direction) #if there was no intersection return the background color if intersection.x == float('inf'): return self.scene.bgColor #evaluate the shader of the intersected object curColor = self._calculateColor(intersection, direction, object, self.scene.light) if self._curRayDepth < self.maxRayDepth and object.shader.isReflective(): reflection = object.getReflection(intersection, direction) #move the intersection along the direction a small amount to avoid repeated self collision movedIntersection = object.moveIntersection(intersection, reflection) self._curRayDepth += 1 #recursive return curColor + self._shootRay(movedIntersection, reflection) return curColor def _findIntersection(self, lookFrom, direction): """ Find the nearest intersection given the parameters Keyword arguments: lookFrom (Point3) -- the initial position of the ray direction (Vector3) -- the direction of the ray Returns an intersection and object if there was an intersection, returns a Point3 at infinity if there was no intersection. """ shortestDist = float('inf') dist = shortestDist nearestIntersection = Point3(float('inf'), float('inf'), float('inf')) intersectedObject = 0 for object in self.scene.objects: intersection = object.findIntersection(lookFrom, direction) if not intersection.x == float('inf'): dist = lookFrom.distSquared(intersection) if dist < shortestDist: shortestDist = dist nearestIntersection = intersection intersectedObject = object return nearestIntersection, intersectedObject def _calculateColor(self, intersection, rayDirection, object, light): """ Compute the color value with the given parameters Keyword arguments: intersection (Point3) -- the intersection point where the color is being evaluated rayDirection (Vector3) -- the incoming direction of the ray object (Primitive) -- the object that has been intersected light (Light) -- the light illuminating the scene Returns an RGB color value represented as a Point3 """ #get the direction to the light lightDirection = light.getDirection(intersection) dirToLight = Vector3(lightDirection.x * -1, lightDirection.y * -1, lightDirection.z * -1) #move the intersection toward the light a small amount to prevent self intersection movedIntersection = object.moveIntersection(intersection, dirToLight) #check if the current intersection is in shadow if object.shader.isReflective() == False and self._inShadow(movedIntersection, dirToLight, light): inShadowColor = Point3(self.scene.ambientColor.x * object.shader.diffuse.x, self.scene.ambientColor.x * object.shader.diffuse.y, self.scene.ambientColor.x * object.shader.diffuse.z) return inShadowColor else: #the intersection is not in shadow, so evaluate the full shader objectColor = object.getColor(intersection, dirToLight, rayDirection, self.scene.ambientColor, self.scene.light.color, self.scene.bgColor) return objectColor def _inShadow(self, intersection, direction, light): """ Check whether the intersection is in shadow given the direction and light Keyword arguments: intersection (Point3) -- the intersection point to check direction (Vector3) -- the direction towards the light source light (Light) -- the light illuminating the scene Returns True if the intersection is in shadow, and False if it is not """ for object in self.scene.objects: newIntersection = object.findIntersection(intersection, direction) dist = intersection.distSquared(newIntersection) distToLight = intersection.distSquared(light.position) if newIntersection.x != float('inf') and dist < distToLight: return True return False