Beispiel #1
0
    def __init__(self,
                 diffuseColor,
                 specularColor=VectorN((1, 1, 1)),
                 hardness=18):
        self.mAmbient = diffuseColor.pairwise(VectorN((.3, .3, .3)))
        self.mDiffuse = diffuseColor
        self.mSpecular = specularColor

        self.mHardness = hardness
Beispiel #2
0
 def __init__(self, filename, offset, scale, color):
     self.triangles = []
     self.verticies = []
     smallest_x, smallest_y, smallest_z = None, None, None
     largest_x, largest_y, largest_z = None, None, None
     with open(filename) as f:
         for line in f:
             if line[0] == 'v':
                 lineSplit = line.split(' ')
                 self.verticies.append(
                     VectorN(offset[0] + float(lineSplit[1]) * scale,
                             offset[1] + float(lineSplit[2]) * scale,
                             offset[2] + float(lineSplit[3]) * scale))
                 vert = self.verticies[len(self.verticies) - 1]
                 if smallest_x == None:
                     smallest_x = vert[0]
                 if smallest_y == None:
                     smallest_y = vert[1]
                 if smallest_z == None:
                     smallest_z = vert[2]
                 if largest_x == None:
                     largest_x = vert[0]
                 if largest_y == None:
                     largest_y = vert[1]
                 if largest_z == None:
                     largest_z = vert[2]
                 if smallest_x > vert[0]:
                     smallest_x = vert[0]
                 if smallest_y > vert[1]:
                     smallest_y = vert[1]
                 if smallest_z == vert[2]:
                     smallest_z = vert[2]
                 if largest_x < vert[0]:
                     largest_x = vert[0]
                 if largest_y < vert[1]:
                     largest_y = vert[1]
                 if largest_z < vert[2]:
                     largest_z = vert[2]
             if line[0] == 'f':
                 lineSplit = line.split(' ')
                 self.triangles.append(
                     VectorN(
                         float(lineSplit[1]) - 1,
                         float(lineSplit[2]) - 1,
                         float(lineSplit[3]) - 1))
     self.mColor = color
     self.mAABB = AABB(VectorN(smallest_x, smallest_y, smallest_z),
                       VectorN(largest_x, largest_y, largest_z),
                       self.mColor)
Beispiel #3
0
    def getColorOfHitRecursive(self, hitData, recursionDepth=5):

        if not hitData:
            if recursionDepth == 5:
                return self.mBGColor

            else:
                return VectorN(3)

        if recursionDepth == 5:
            self.mFinalLightComponent = self.getColorOfHit(hitData)

        objNormal = hitData.getNormal()
        vectorToCam = -hitData.mRay.mDirection

        refectionVectorParallel = vectorToCam.dot(objNormal) * objNormal
        reflectionVector = 2 * refectionVectorParallel - vectorToCam

        self.mFinalLightComponent = .5*self.mFinalLightComponent + \
                                    .5*self.getColorOfHitRecursive(self.rayCast(
                                        Ray(hitData.mIntersectionPoints[0]+objNormal*.001, reflectionVector))
                                                                   , recursionDepth=recursionDepth-1)

        if recursionDepth == 5:
            self.mFinalLightComponent[0] = min(1, self.mFinalLightComponent[0])
            self.mFinalLightComponent[1] = min(1, self.mFinalLightComponent[1])
            self.mFinalLightComponent[2] = min(1, self.mFinalLightComponent[2])

            return (self.mFinalLightComponent * 255).iTuple()

        else:
            return self.getColorOfHit(hitData)
Beispiel #4
0
    def getNormal(self, point):
        """
        Gets the normal at a point.
        The passed in point is assumed to be on the cylinder
        :param point: Point (VectorN) on object to calculate the normal
        :return: a normalized VectorN
        """

        if point[1] <= self.mBase[1]:
            # The point is on the bottom plane
            return VectorN((0, -1, 0))

        elif point[1] >= self.mBase[1] + self.mHeight:
            # The point is on the top plane
            return VectorN((0, 1, 0))

        else:
            # The point is now assumed to be on the cylindrical portion
            return (point - VectorN(
                (self.mBase[0], point[1], self.mBase[2]))) / self.mRadius
Beispiel #5
0
 def one_line(self, y, screen, allShapes, cam):
     for x in range(0, screen.get_width()):
         rayOrigin = cam.getPixelPosition(x, y)
         rayDirection = rayOrigin - cam.position
         R = Ray(rayOrigin, rayDirection)
         closest, distance = self.get_closest(R, allShapes)
         pt = R.getPT(distance)
         if closest == None:
             screen.set_at((x, y), (30, 30, 30))
         else:
             cDiff = VectorN(0, 0, 0)
             cSpec = VectorN(0, 0, 0)
             closest_color_items = closest.get_material()
             amb_c = self.amb.p_mul(closest_color_items.ambient)
             total = amb_c
             normal = closest.get_normal(pt, distance)
             for light in self.lights:
                 shadowRay = Ray(pt + 0.001 * normal, light.position - pt)
                 shadowCheck, other = self.get_closest(shadowRay, allShapes)
                 light_dist = (light.position - pt).magnitude()
                 # print(other,light_dist,shadowCheck,pt)
                 if shadowCheck != None and other >= 0 and other != 20000 and other > .0001 and other < light_dist:
                     continue
                 l_dir = light.position - pt
                 l_dir = l_dir.normalized()
                 dStr = l_dir.dot(normal)
                 if dStr <= 0:
                     cDiff = VectorN(0, 0, 0)
                 else:
                     cDiff = dStr * light.diffuse.p_mul(
                         closest_color_items.diffuse)
                 R = 2 * (l_dir.dot(normal)) * normal - l_dir
                 V = (cam.position - pt).normalized()
                 sStr = V.dot(R)
                 if sStr <= 0:
                     cSpec = VectorN(0, 0, 0)
                 else:
                     cSpec = (sStr**closest_color_items.shiny) * (
                         light.specular.p_mul(closest_color_items.specular))
                 total += cDiff + cSpec
             screen.set_at((x, y), total.clamp() * 255)
Beispiel #6
0
 def __init__(self, p1, p2, color):
     self.color = color
     self.mMinPoint = [0, 0, 0]
     self.mMaxPoint = [0, 0, 0]
     if p1[0] > p2[0]:
         self.mMinPoint[0] = p2[0]
         self.mMaxPoint[0] = p1[0]
     else:
         self.mMinPoint[0] = p1[0]
         self.mMaxPoint[0] = p2[0]
     if p1[1] > p2[1]:
         self.mMinPoint[1] = p2[1]
         self.mMaxPoint[1] = p1[1]
     else:
         self.mMinPoint[1] = p1[1]
         self.mMaxPoint[1] = p2[1]
     if p1[2] > p2[2]:
         self.mMinPoint[2] = p2[2]
         self.mMaxPoint[2] = p1[2]
     else:
         self.mMinPoint[2] = p1[2]
         self.mMaxPoint[2] = p2[2]
     self.plane_list = [
         Plane(VectorN(-1, 0, 0), -self.mMinPoint[0], self.color),
         Plane(VectorN(1, 0, 0), self.mMaxPoint[0], self.color),
         Plane(VectorN(0, -1, 0), -self.mMinPoint[1], self.color),
         Plane(VectorN(0, 1, 0), self.mMaxPoint[1], self.color),
         Plane(VectorN(0, 0, -1), -self.mMinPoint[2], self.color),
         Plane(VectorN(0, 0, 1), self.mMaxPoint[2], self.color)
     ]
Beispiel #7
0
                y += 1
            elapsed = self.clk.tick() / 1000
            keysPressed = pygame.key.get_pressed()
            evt = pygame.event.poll()
            if keysPressed[pygame.K_ESCAPE]:
                running = False
                break
            if evt.type == pygame.QUIT:
                running = False
                break
            pygame.display.flip()
        pygame.quit()


if __name__ == "__main__":
    light1 = Light(VectorN(0, 50, 0), VectorN(1.0, 1.0, 1.0),
                   VectorN(1.0, 1.0, 1.0), VectorN(0.0, 0.0, 0.0))
    light2 = Light(VectorN(50, 50, -50), VectorN(0.4, 0, 0),
                   VectorN(0, 0.6, 0), VectorN(0, 0, 0))
    cam = Camera(VectorN(-15.0, 19.0, -30.0), VectorN(2.0, 5.0, 3.0),
                 VectorN(0.0, 1.0, 0.0), 60.0, 1.5, pygame.Surface((300, 200)))
    sphere = Material(VectorN(0.3, 0, 0), VectorN(1, 0, 0), VectorN(1, 1, 1),
                      10.0)
    plane1 = Material(VectorN(0, 0.5, 0), VectorN(0, 1, 0), VectorN(1, 0, 0),
                      2.0)
    plane2 = Material(VectorN(0, 0, 0.1), VectorN(0, 0, 1), VectorN(1, 0, 1),
                      6.0)
    aabb = Material(VectorN(0.5, 0.3, 0.1), VectorN(1, 1, 0),
                    VectorN(0.5, 1.0, 0.5), 30.0)
    mesh = Material(VectorN(0.2, 0, 0.4), VectorN(0.7, 0, 1), VectorN(1, 1, 1),
                    50.0)
Beispiel #8
0
    def __init__(self,
                 renderSurface,
                 sceneAmbient=VectorN((1, 1, 1)),
                 bgColor=(50, 50, 50)):
        """
        This is the raytracer class, it takes in the surface to render onto.
        :param surface: a pygame.Surface object
        :return: N/A
        """

        self.mRenderSurface = renderSurface
        self.mObjects = []
        self.mLights = []
        self.mBGColor = bgColor
        self.mSceneAmbient = sceneAmbient

        # Pygame Screen Variables
        self.mPyWidth, self.mPyHeight = self.mRenderSurface.get_size()

        self.mPyAspectRatio = self.mPyWidth / self.mPyHeight

        # Camera Variables
        self.mCamX = VectorN((1, 0, 0))
        self.mCamY = VectorN((0, 1, 0))
        self.mCamZ = VectorN((0, 0, 1))

        self.mCamPos = VectorN(3)

        self.mCamFOV = 45
        self.mCamNear = 1.0
        self.mCamCOI = VectorN(3)

        self.mCamUp = VectorN((0, 1, 0))

        # Virtual ViewPlane Variables
        self.mViewOrigin = VectorN(3)

        self.mViewHeight = self.mPyHeight + 0
        self.mHalfViewHeight = self.mViewHeight / 2

        self.mViewWidth = self.mPyWidth + 0
        self.mHalfViewWidth = self.mViewWidth / 2

        self.mVirtualPyWidthRatio = self.mViewWidth / self.mPyWidth
        self.mVirtualPyHeightRatio = self.mViewHeight / self.mPyHeight

        self.mFinalLightComponent = VectorN(3)

        #Tween variables, these are mostly offsets
        self.mIsTweening = True

        self.mTValues = []
        self.mCurTweenFrame = 0
        self.mNumTweenFrames = 0

        self.mTweenCamPos = VectorN(3)
        self.mTweenCamCOI = VectorN(3)
        self.mTweenCamUp = VectorN(3)
        self.mTweenCamFOV = 0
        self.mTweenCamNear = 0
Beispiel #9
0
class Raytracer(object):
    def __init__(self,
                 renderSurface,
                 sceneAmbient=VectorN((1, 1, 1)),
                 bgColor=(50, 50, 50)):
        """
        This is the raytracer class, it takes in the surface to render onto.
        :param surface: a pygame.Surface object
        :return: N/A
        """

        self.mRenderSurface = renderSurface
        self.mObjects = []
        self.mLights = []
        self.mBGColor = bgColor
        self.mSceneAmbient = sceneAmbient

        # Pygame Screen Variables
        self.mPyWidth, self.mPyHeight = self.mRenderSurface.get_size()

        self.mPyAspectRatio = self.mPyWidth / self.mPyHeight

        # Camera Variables
        self.mCamX = VectorN((1, 0, 0))
        self.mCamY = VectorN((0, 1, 0))
        self.mCamZ = VectorN((0, 0, 1))

        self.mCamPos = VectorN(3)

        self.mCamFOV = 45
        self.mCamNear = 1.0
        self.mCamCOI = VectorN(3)

        self.mCamUp = VectorN((0, 1, 0))

        # Virtual ViewPlane Variables
        self.mViewOrigin = VectorN(3)

        self.mViewHeight = self.mPyHeight + 0
        self.mHalfViewHeight = self.mViewHeight / 2

        self.mViewWidth = self.mPyWidth + 0
        self.mHalfViewWidth = self.mViewWidth / 2

        self.mVirtualPyWidthRatio = self.mViewWidth / self.mPyWidth
        self.mVirtualPyHeightRatio = self.mViewHeight / self.mPyHeight

        self.mFinalLightComponent = VectorN(3)

        #Tween variables, these are mostly offsets
        self.mIsTweening = True

        self.mTValues = []
        self.mCurTweenFrame = 0
        self.mNumTweenFrames = 0

        self.mTweenCamPos = VectorN(3)
        self.mTweenCamCOI = VectorN(3)
        self.mTweenCamUp = VectorN(3)
        self.mTweenCamFOV = 0
        self.mTweenCamNear = 0

    def setCamera(self, camPos, camCOI, camUp, camFOV, camNear, noTween=True):
        """
        This function sets up the camera for the raytracer

        This includes calculating the ViewOrigin of the virtual View Plane
        :param camPos: Position of the camera in world space
        :param camCOI: The focus point of the camera, where it faces
        :param camUp: General 'up' direction of the camera
        :param camFOV: The Vertical angle of the view of the camera
        :param camNear: The distance from the camera to the Virtual Plane, travelling along camZ
        :return: None
        """

        self.mCamPos = camPos
        self.mCamFOV = camFOV
        self.mCamNear = camNear
        self.mCamCOI = camCOI

        self.mCamZ = (self.mCamCOI - self.mCamPos).normalized_copy()

        self.mCamX = camUp.cross(self.mCamZ).normalized_copy()
        self.mCamY = self.mCamZ.cross(self.mCamX).normalized_copy()

        self.mHalfViewHeight = math.tan(math.radians(
            self.mCamFOV / 2)) * self.mCamNear
        self.mViewHeight = self.mHalfViewHeight * 2

        self.mHalfViewWidth = self.mHalfViewHeight * self.mPyAspectRatio
        self.mViewWidth = self.mHalfViewWidth * 2

        self.mVirtualPyWidthRatio = self.mViewWidth / self.mPyWidth
        self.mVirtualPyHeightRatio = self.mViewHeight / self.mPyHeight

        self.mViewOrigin = self.mCamPos + self.mCamNear*self.mCamZ \
                           + self.mHalfViewHeight*self.mCamY \
                           - self.mHalfViewWidth*self.mCamX

    def setCameraTweenDest(self,
                           numFrames,
                           camPos=None,
                           camCOI=None,
                           camUP=None,
                           camFOV=None,
                           camNear=None):
        """
        This sets up the tween dest, which specify where the camera should go to.
        :param camPos: a Vector3
        :param camCOI: a Vector3
        :param camUP: a Vector3
        :param camFOV: a Float
        :param camNear: a Float
        :return: None
        """

        self.mIsTweening = True

        self.mTValues = []
        for i in range(1, numFrames + 1):
            self.mTValues.append(i / numFrames)

        self.mNumTweenFrames = numFrames

        if camPos:
            self.mTweenCamPos = camPos - self.mCamPos

        if camCOI:
            self.mTweenCamCOI = camCOI - self.mCamCOI

        if camUP:
            self.mTweenCamUp = camUP - self.mCamUp

        if camFOV:
            self.mTweenCamFOV = camFOV - self.mCamFOV

        if camNear:
            self.mTweenCamNear = camNear - self.mCamNear

    def updateTween(self, function=None):
        """
        This updates the tween state by one frame
        :return: True if done with tweening, False otherwise
        """

        if not function:
            function = lambda t: 3 * t**2 - 2 * t**3

        if self.mIsTweening:
            self.mCamPos += self.mTweenCamPos * function(
                self.mTValues[self.mCurTweenFrame])
            self.mCamCOI += self.mTweenCamCOI * function(
                self.mTValues[self.mCurTweenFrame])
            self.mCamUp += self.mTweenCamUp * function(
                self.mTValues[self.mCurTweenFrame])
            self.mCamFOV += self.mTweenCamFOV * function(
                self.mTValues[self.mCurTweenFrame])
            self.mCamNear += self.mTweenCamNear * function(
                self.mTValues[self.mCurTweenFrame])

            self.setCamera(self.mCamPos, self.mCamCOI, self.mCamUp,
                           self.mCamFOV, self.mCamNear)

            self.mCurTweenFrame += 1

            if self.mCurTweenFrame >= self.mNumTweenFrames:
                self.mIsTweening = False

        return self.mIsTweening

    def rotateAboutYAxis(self, angle):
        """
        This function rotates the camera position around the Y-Axis a specified angular amount.
        :param angle: the amount to rotate, in radians
        :return: None
        """

        cosAngle = math.cos(angle)
        sinAngle = math.sin(angle)

        self.mCamPos[
            0] = self.mCamPos[0] * cosAngle - self.mCamPos[2] * sinAngle
        self.mCamPos[
            2] = self.mCamPos[0] * sinAngle + self.mCamPos[2] * cosAngle

        self.setCamera(self.mCamPos, self.mCamCOI, self.mCamUp, self.mCamFOV,
                       self.mCamNear)

    def calculatePixelPos(self, ix, iy):
        """
        This function contverts a pygame pixel position (ix, iy) into a Virtual View Plane Position
        :param ix: the X Position of the point
        :param iy: the Y Position of the point
        :return: a 3D VectorN, representing the world space position of the 2D input point.
        """

        virtualPixel = self.mViewOrigin \
                       + self.mVirtualPyWidthRatio*ix * self.mCamX\
                       - self.mVirtualPyHeightRatio*iy * self.mCamY

        return virtualPixel

    def rayCast(self, ray, isShadow=False, light=None):
        """
        This casts ray into the world, testing it on every object in the mObjects list
        :param ray:
        :return:
        """

        if light:
            lightDist2 = (light.mPos - ray.mOrigin).magnitudeSquared()

        resultList = []

        # First create the list
        for Object in self.mObjects:
            result = Object.rayHit(ray)

            if result:
                if isShadow and light:
                    for distance in result.mIntersectionDistances:
                        if distance * distance <= lightDist2:
                            return result
                else:
                    resultList.append(result)

        # Next find the smallest distance, assuming there were results
        if len(resultList) == 0 or len(
                resultList[-1].mIntersectionDistances) == 0:
            return None

        # If is light got this far, it should return None.
        if isShadow and light:
            return None

        distIndex = 0

        curReturnResult = resultList[-1]

        for result in resultList:
            for i in range(len(result.mIntersectionDistances)):
                if result.mIntersectionDistances[
                        i] < curReturnResult.mIntersectionDistances[distIndex]:
                    distIndex = i
                    curReturnResult = result

        # Convert the filled hit result into just holding the smallest distance.
        curReturnResult.mIntersectionPoints = [
            curReturnResult.mIntersectionPoints[distIndex]
        ]
        curReturnResult.mIntersectionDistances = [
            curReturnResult.mIntersectionDistances[distIndex]
        ]

        return curReturnResult

    def getColorOfHit(self, hitData):
        """
        This returns the color of the result passed in, no special effects right now
        If hitData is None, jut returns the background color

        :param hitData: a RayHitResult Object, or None
        :return: A tuple of integers
        """

        if hitData:
            # return hitData.mHitObject.mMaterial.getPygameColor()

            ambient = hitData.mHitObject.mMaterial.mAmbient.pairwise(
                self.mSceneAmbient)

            if len(self.mLights):
                objNormal = hitData.getNormal()
                vectorToCam = -hitData.mRay.mDirection

                for light in self.mLights:
                    lightVector = (
                        light.mPos -
                        (hitData.mIntersectionPoints[0])).normalized_copy()
                    # Check collisions for shadow here

                    if self.rayCast(Ray(hitData.mIntersectionPoints[0] +
                                        objNormal * .001,
                                        lightVector,
                                        isNormalized=True),
                                    isShadow=True,
                                    light=light):
                        continue

                    # Check Light intensity if spotlight here
                    lightPortion = VectorN(3)

                    lightIntensity = light.getIntensity(
                        hitData.mIntersectionPoints[0])
                    if lightIntensity:

                        # Check Diffuse light
                        diffuseStrength = lightVector.dot(objNormal)
                        # print(diffuseStrength, lightVector, objNormal)

                        if diffuseStrength > 0:
                            lightPortion += diffuseStrength * (
                                light.mDiffuse.pairwise(
                                    hitData.mHitObject.mMaterial.mDiffuse))

                        # Check Specular
                        lightVectorParallel = objNormal * diffuseStrength
                        reflectionVector = 2 * lightVectorParallel - lightVector

                        specularStrength = reflectionVector.dot(vectorToCam)

                        if specularStrength > 0:
                            # print(specularStrength)
                            lightPortion += specularStrength**hitData.mHitObject.mMaterial.mHardness * (
                                light.mSpecular.pairwise(
                                    hitData.mHitObject.mMaterial.mSpecular))

                        ambient += lightPortion * lightIntensity

            return ambient

    def getColorOfHitRecursive(self, hitData, recursionDepth=5):

        if not hitData:
            if recursionDepth == 5:
                return self.mBGColor

            else:
                return VectorN(3)

        if recursionDepth == 5:
            self.mFinalLightComponent = self.getColorOfHit(hitData)

        objNormal = hitData.getNormal()
        vectorToCam = -hitData.mRay.mDirection

        refectionVectorParallel = vectorToCam.dot(objNormal) * objNormal
        reflectionVector = 2 * refectionVectorParallel - vectorToCam

        self.mFinalLightComponent = .5*self.mFinalLightComponent + \
                                    .5*self.getColorOfHitRecursive(self.rayCast(
                                        Ray(hitData.mIntersectionPoints[0]+objNormal*.001, reflectionVector))
                                                                   , recursionDepth=recursionDepth-1)

        if recursionDepth == 5:
            self.mFinalLightComponent[0] = min(1, self.mFinalLightComponent[0])
            self.mFinalLightComponent[1] = min(1, self.mFinalLightComponent[1])
            self.mFinalLightComponent[2] = min(1, self.mFinalLightComponent[2])

            return (self.mFinalLightComponent * 255).iTuple()

        else:
            return self.getColorOfHit(hitData)

    def renderOneLine(self, iy):
        """
        This renders the world onto one line of the pygame window

        :param iy: the y value of the line
        :return: None
        """

        for x in range(0, self.mPyWidth):
            direction = self.calculatePixelPos(x, iy) - self.mCamPos
            color = self.getColorOfHitRecursive(
                self.rayCast(Ray(self.mCamPos, direction)))

            self.mRenderSurface.set_at((x, iy), color)
Beispiel #10
0
    def getColorOfHit(self, hitData):
        """
        This returns the color of the result passed in, no special effects right now
        If hitData is None, jut returns the background color

        :param hitData: a RayHitResult Object, or None
        :return: A tuple of integers
        """

        if hitData:
            # return hitData.mHitObject.mMaterial.getPygameColor()

            ambient = hitData.mHitObject.mMaterial.mAmbient.pairwise(
                self.mSceneAmbient)

            if len(self.mLights):
                objNormal = hitData.getNormal()
                vectorToCam = -hitData.mRay.mDirection

                for light in self.mLights:
                    lightVector = (
                        light.mPos -
                        (hitData.mIntersectionPoints[0])).normalized_copy()
                    # Check collisions for shadow here

                    if self.rayCast(Ray(hitData.mIntersectionPoints[0] +
                                        objNormal * .001,
                                        lightVector,
                                        isNormalized=True),
                                    isShadow=True,
                                    light=light):
                        continue

                    # Check Light intensity if spotlight here
                    lightPortion = VectorN(3)

                    lightIntensity = light.getIntensity(
                        hitData.mIntersectionPoints[0])
                    if lightIntensity:

                        # Check Diffuse light
                        diffuseStrength = lightVector.dot(objNormal)
                        # print(diffuseStrength, lightVector, objNormal)

                        if diffuseStrength > 0:
                            lightPortion += diffuseStrength * (
                                light.mDiffuse.pairwise(
                                    hitData.mHitObject.mMaterial.mDiffuse))

                        # Check Specular
                        lightVectorParallel = objNormal * diffuseStrength
                        reflectionVector = 2 * lightVectorParallel - lightVector

                        specularStrength = reflectionVector.dot(vectorToCam)

                        if specularStrength > 0:
                            # print(specularStrength)
                            lightPortion += specularStrength**hitData.mHitObject.mMaterial.mHardness * (
                                light.mSpecular.pairwise(
                                    hitData.mHitObject.mMaterial.mSpecular))

                        ambient += lightPortion * lightIntensity

            return ambient