[pypy-svn] r76662 - in pypy/benchmarks: . own
cfbolz at codespeak.net
cfbolz at codespeak.net
Wed Aug 18 11:14:21 CEST 2010
Author: cfbolz
Date: Wed Aug 18 11:14:19 2010
New Revision: 76662
Added:
pypy/benchmarks/own/raytrace-simple.py (contents, props changed)
Modified:
pypy/benchmarks/benchmarks.py
Log:
add a simple pure-python ray tracer
Modified: pypy/benchmarks/benchmarks.py
==============================================================================
--- pypy/benchmarks/benchmarks.py (original)
+++ pypy/benchmarks/benchmarks.py Wed Aug 18 11:14:19 2010
@@ -44,7 +44,7 @@
}
for name in ['float', 'nbody_modified', 'meteor-contest', 'fannkuch',
- 'spectral-norm', 'chaos', 'telco', 'go', 'pyflate-fast']:
+ 'spectral-norm', 'chaos', 'telco', 'go', 'pyflate-fast', 'raytrace-simple']:
_register_new_bm(name, name, globals(), **opts.get(name, {}))
for name in ['names', 'iteration', 'tcp', 'pb']:#, 'accepts', 'web']:
if name == 'web':
Added: pypy/benchmarks/own/raytrace-simple.py
==============================================================================
--- (empty file)
+++ pypy/benchmarks/own/raytrace-simple.py Wed Aug 18 11:14:19 2010
@@ -0,0 +1,377 @@
+# This file contains definitions for a simple raytracer.
+# Copyright Callum and Tony Garnock-Jones, 2008.
+# This file may be freely redistributed under the MIT license,
+# http://www.opensource.org/licenses/mit-license.php
+
+# from http://www.lshift.net/blog/2008/10/29/toy-raytracer-in-python
+from __future__ import with_statement
+
+import math
+
+EPSILON = 0.00001
+
+class Vector(object):
+ def __init__(self, initx, inity, initz):
+ self.x = initx
+ self.y = inity
+ self.z = initz
+
+ def __str__(self):
+ return '(%s,%s,%s)' % (self.x, self.y, self.z)
+
+ def __repr__(self):
+ return 'Vector(%s,%s,%s)' % (self.x, self.y, self.z)
+
+ def magnitude(self):
+ return math.sqrt(self.dot(self))
+
+ def __add__(self, other):
+ if other.isPoint():
+ return Point(self.x + other.x, self.y + other.y, self.z + other.z)
+ else:
+ return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
+
+ def __sub__(self, other):
+ other.mustBeVector()
+ return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
+
+ def scale(self, factor):
+ return Vector(factor * self.x, factor * self.y, factor * self.z)
+
+ def dot(self, other):
+ other.mustBeVector()
+ return (self.x * other.x) + (self.y * other.y) + (self.z * other.z)
+
+ def cross(self, other):
+ other.mustBeVector()
+ return Vector(self.y * other.z - self.z * other.y,
+ self.z * other.x - self.x * other.z,
+ self.x * other.y - self.y * other.x)
+
+ def normalized(self):
+ return self.scale(1.0 / self.magnitude())
+
+ def negated(self):
+ return self.scale(-1)
+
+ def __eq__(self, other):
+ return (self.x == other.x) and (self.y == other.y) and (self.z == other.z)
+
+ def isVector(self):
+ return True
+
+ def isPoint(self):
+ return False
+
+ def mustBeVector(self):
+ return self
+
+ def mustBePoint(self):
+ raise 'Vectors are not points!'
+
+ def reflectThrough(self, normal):
+ d = normal.scale(self.dot(normal))
+ return self - d.scale(2)
+
+Vector.ZERO = Vector(0,0,0)
+Vector.RIGHT = Vector(1,0,0)
+Vector.UP = Vector(0,1,0)
+Vector.OUT = Vector(0,0,1)
+
+assert Vector.RIGHT.reflectThrough(Vector.UP) == Vector.RIGHT
+assert Vector(-1,-1,0).reflectThrough(Vector.UP) == Vector(-1,1,0)
+
+class Point(object):
+ def __init__(self, initx, inity, initz):
+ self.x = initx
+ self.y = inity
+ self.z = initz
+
+ def __str__(self):
+ return '(%s,%s,%s)' % (self.x, self.y, self.z)
+
+ def __repr__(self):
+ return 'Point(%s,%s,%s)' % (self.x, self.y, self.z)
+
+ def __add__(self, other):
+ other.mustBeVector()
+ return Point(self.x + other.x, self.y + other.y, self.z + other.z)
+
+ def __sub__(self, other):
+ if other.isPoint():
+ return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
+ else:
+ return Point(self.x - other.x, self.y - other.y, self.z - other.z)
+
+ def isVector(self):
+ return False
+
+ def isPoint(self):
+ return True
+
+ def mustBeVector(self):
+ raise 'Points are not vectors!'
+
+ def mustBePoint(self):
+ return self
+
+class Sphere(object):
+ def __init__(self, centre, radius):
+ centre.mustBePoint()
+ self.centre = centre
+ self.radius = radius
+
+ def __repr__(self):
+ return 'Sphere(%s,%s)' % (repr(self.centre), self.radius)
+
+ def intersectionTime(self, ray):
+ cp = self.centre - ray.point
+ v = cp.dot(ray.vector)
+ discriminant = (self.radius * self.radius) - (cp.dot(cp) - v*v)
+ if discriminant < 0:
+ return None
+ else:
+ return v - math.sqrt(discriminant)
+
+ def normalAt(self, p):
+ return (p - self.centre).normalized()
+
+class Halfspace(object):
+ def __init__(self, point, normal):
+ self.point = point
+ self.normal = normal.normalized()
+
+ def __repr__(self):
+ return 'Halfspace(%s,%s)' % (repr(self.point), repr(self.normal))
+
+ def intersectionTime(self, ray):
+ v = ray.vector.dot(self.normal)
+ if v:
+ return 1 / -v
+ else:
+ return None
+
+ def normalAt(self, p):
+ return self.normal
+
+class Ray(object):
+ def __init__(self, point, vector):
+ self.point = point
+ self.vector = vector.normalized()
+
+ def __repr__(self):
+ return 'Ray(%s,%s)' % (repr(self.point), repr(self.vector))
+
+ def pointAtTime(self, t):
+ return self.point + self.vector.scale(t)
+
+Point.ZERO = Point(0,0,0)
+
+a = Vector(3,4,12)
+b = Vector(1,1,1)
+
+class PpmCanvas(object):
+ def __init__(self, width, height, filenameBase):
+ import array
+ self.bytes = array.array('B', [0] * (width * height * 3))
+ for i in range(width * height):
+ self.bytes[i * 3 + 2] = 255
+ self.width = width
+ self.height = height
+ self.filenameBase = filenameBase
+
+ def plot(self, x, y, r, g, b):
+ i = ((self.height - y - 1) * self.width + x) * 3
+ self.bytes[i ] = max(0, min(255, int(r * 255)))
+ self.bytes[i+1] = max(0, min(255, int(g * 255)))
+ self.bytes[i+2] = max(0, min(255, int(b * 255)))
+
+ def save(self):
+ with open(self.filenameBase + '.ppm', 'wb') as f:
+ f.write('P6 %d %d 255\n' % (self.width, self.height))
+ f.write(self.bytes.tostring())
+
+def firstIntersection(intersections):
+ result = None
+ for i in intersections:
+ candidateT = i[1]
+ if candidateT is not None and candidateT > -EPSILON:
+ if result is None or candidateT < result[1]:
+ result = i
+ return result
+
+class Scene(object):
+ def __init__(self):
+ self.objects = []
+ self.lightPoints = []
+ self.position = Point(0, 1.8, 10)
+ self.lookingAt = Point.ZERO
+ self.fieldOfView = 45
+ self.recursionDepth = 0
+
+ def moveTo(self, p):
+ self.position = p
+
+ def lookAt(self, p):
+ self.lookingAt = p
+
+ def addObject(self, object, surface):
+ self.objects.append((object, surface))
+
+ def addLight(self, p):
+ self.lightPoints.append(p)
+
+ def render(self, canvas):
+ #print 'Computing field of view'
+ fovRadians = math.pi * (self.fieldOfView / 2.0) / 180.0
+ halfWidth = math.tan(fovRadians)
+ halfHeight = 0.75 * halfWidth
+ width = halfWidth * 2
+ height = halfHeight * 2
+ pixelWidth = width / (canvas.width - 1)
+ pixelHeight = height / (canvas.height - 1)
+
+ eye = Ray(self.position, self.lookingAt - self.position)
+ vpRight = eye.vector.cross(Vector.UP).normalized()
+ vpUp = vpRight.cross(eye.vector).normalized()
+
+ #print 'Looping over pixels'
+ previousfraction = 0
+ for y in range(canvas.height):
+ currentfraction = float(y) / canvas.height
+ if currentfraction - previousfraction > 0.05:
+ canvas.save()
+ #print '%d%% complete' % (currentfraction * 100)
+ previousfraction = currentfraction
+ for x in range(canvas.width):
+ xcomp = vpRight.scale(x * pixelWidth - halfWidth)
+ ycomp = vpUp.scale(y * pixelHeight - halfHeight)
+ ray = Ray(eye.point, eye.vector + xcomp + ycomp)
+ colour = self.rayColour(ray)
+ canvas.plot(x,y,*colour)
+
+ #print 'Complete.'
+
+ def rayColour(self, ray):
+ if self.recursionDepth > 3:
+ return (0,0,0)
+ try:
+ self.recursionDepth = self.recursionDepth + 1
+ intersections = [(o, o.intersectionTime(ray), s) for (o, s) in self.objects]
+ i = firstIntersection(intersections)
+ if i is None:
+ return (0,0,0) ## the background colour
+ else:
+ (o, t, s) = i
+ p = ray.pointAtTime(t)
+ return s.colourAt(self, ray, p, o.normalAt(p))
+ finally:
+ self.recursionDepth = self.recursionDepth - 1
+
+ def _lightIsVisible(self, l, p):
+ for (o, s) in self.objects:
+ t = o.intersectionTime(Ray(p,l - p))
+ if t is not None and t > EPSILON:
+ return False
+ return True
+
+ def visibleLights(self, p):
+ result = []
+ for l in self.lightPoints:
+ if self._lightIsVisible(l, p):
+ result.append(l)
+ return result
+
+def addColours(a, scale, b):
+ return (a[0] + scale * b[0],
+ a[1] + scale * b[1],
+ a[2] + scale * b[2])
+
+class SimpleSurface(object):
+ def __init__(self, **kwargs):
+ self.baseColour = kwargs.get('baseColour', (1,1,1))
+ self.specularCoefficient = kwargs.get('specularCoefficient', 0.2)
+ self.lambertCoefficient = kwargs.get('lambertCoefficient', 0.6)
+ self.ambientCoefficient = 1.0 - self.specularCoefficient - self.lambertCoefficient
+
+ def baseColourAt(self, p):
+ return self.baseColour
+
+ def colourAt(self, scene, ray, p, normal):
+ b = self.baseColourAt(p)
+
+ c = (0,0,0)
+ if self.specularCoefficient > 0:
+ reflectedRay = Ray(p, ray.vector.reflectThrough(normal))
+ #print p, normal, ray.vector, reflectedRay.vector
+ reflectedColour = scene.rayColour(reflectedRay)
+ c = addColours(c, self.specularCoefficient, reflectedColour)
+
+ if self.lambertCoefficient > 0:
+ lambertAmount = 0
+ for lightPoint in scene.visibleLights(p):
+ contribution = (lightPoint - p).normalized().dot(normal)
+ if contribution > 0:
+ lambertAmount = lambertAmount + contribution
+ lambertAmount = min(1,lambertAmount)
+ c = addColours(c, self.lambertCoefficient * lambertAmount, b)
+
+ if self.ambientCoefficient > 0:
+ c = addColours(c, self.ambientCoefficient, b)
+
+ return c
+
+class CheckerboardSurface(SimpleSurface):
+ def __init__(self, **kwargs):
+ SimpleSurface.__init__(self, **kwargs)
+ self.otherColour = kwargs.get('otherColour', (0,0,0))
+ self.checkSize = kwargs.get('checkSize', 1)
+
+ def baseColourAt(self, p):
+ v = p - Point.ZERO
+ v.scale(1.0 / self.checkSize)
+ if (int(abs(v.x) + 0.5) + \
+ int(abs(v.y) + 0.5) + \
+ int(abs(v.z) + 0.5)) \
+ % 2:
+ return self.otherColour
+ else:
+ return self.baseColour
+
+def _main():
+ Canvas = PpmCanvas
+ c = Canvas(100,100,'test_raytrace')
+ #c = Canvas(640,480,'test_raytrace_big')
+ s = Scene()
+ s.addLight(Point(30, 30, 10))
+ s.addLight(Point(-10, 100, 30))
+ s.lookAt(Point(0, 3, 0))
+ s.addObject(Sphere(Point(1,3,-10), 2), SimpleSurface(baseColour = (1,1,0)))
+ for y in range(6):
+ s.addObject(Sphere(Point(-3 - y * 0.4, 2.3, -5), 0.4),
+ SimpleSurface(baseColour = (y / 6.0, 1 - y / 6.0, 0.5)))
+ s.addObject(Halfspace(Point(0,0,0), Vector.UP), CheckerboardSurface())
+ s.render(c)
+
+def main(n):
+ import time
+ times = []
+ for i in range(5):
+ _main() # warmup
+ for i in range(n):
+ t1 = time.time()
+ _main()
+ t2 = time.time()
+ times.append(t2 - t1)
+ return times
+
+if __name__ == "__main__":
+ import util, optparse
+ parser = optparse.OptionParser(
+ usage="%prog [options]",
+ description="Test the performance of the raytrace benchmark")
+ util.add_standard_options_to(parser)
+ options, args = parser.parse_args()
+
+ util.run_benchmark(options, options.num_runs, main)
+
More information about the Pypy-commit
mailing list