# HG changeset patch # User paulb # Date 1189961657 0 # Node ID 07efb8b50f735ae9648e2fa409086719dd5fc747 # Parent 905d8ec730036039aaf30fff4a8eb6d847c4b875 [project @ 2007-09-16 16:54:17 by paulb] Added an example of channel/process reuse. diff -r 905d8ec73003 -r 07efb8b50f73 examples/PyGmy/ppygmy_reuse.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/PyGmy/ppygmy_reuse.py Sun Sep 16 16:54:17 2007 +0000 @@ -0,0 +1,415 @@ +#!/usr/bin/env python + +""" +An adaptation of pygmy.py ("a rubbish raytracer") employing pprocess +functionality in order to take advantage of multiprocessing environments. + +-------- + +Copyright (C) 2005 Dave Griffiths +Copyright (C) 2006, 2007 Paul Boddie + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" + +import Image, ImageDraw, random, copy +from math import * +import pprocess +import sys + +def sq(a): + return a*a + +class vec: + def __init__(self, x, y, z): + self.x=float(x) + self.y=float(y) + self.z=float(z) + + def __add__(self,other): + return vec(self.x+other.x,self.y+other.y,self.z+other.z) + + def __sub__(self,other): + return vec(self.x-other.x,self.y-other.y,self.z-other.z) + + def __mul__(self,amount): + return vec(self.x*amount,self.y*amount,self.z*amount) + + def __div__(self,amount): + return vec(self.x/amount,self.y/amount,self.z/amount) + + def __neg__(self): + return vec(-self.x,-self.y,-self.z) + + def dot(self,other): + return (self.x*other.x)+(self.y*other.y)+(self.z*other.z) + + def cross(self,other): + return vec(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 dist(self,other): + return sqrt((other.x-self.x)*(other.x-self.x)+ + (other.y-self.y)*(other.y-self.y)+ + (other.z-self.z)*(other.z-self.z)) + + def sq(self): + return sq(self.x)+sq(self.y)+sq(self.z) + + def mag(self): + return self.dist(vec(0,0,0)) + + def norm(self): + mag=self.mag() + if mag!=0: + self.x=self.x/mag + self.y=self.y/mag + self.z=self.z/mag + + def reflect(self,normal): + vdn=self.dot(normal)*2 + return self-normal*vdn + +class line: + def __init__(self, start, end): + self.start=start + self.end=end + + def vec(self): + return self.end-self.start + + def closestpoint(self, point): + l=self.end-self.start + l2=point-self.start + t=l.dot(l2) + if t<=0: return self.start + if t>l.mag(): return self.end + return self.start+l*t + +class renderobject: + def __init__(self, shader): + self.shader=shader + + def intersect(self,l): + return "none",vec(0,0,0),vec(0,0,0) # type, position, normal + +class plane(renderobject): + def __init__(self,plane,dist,shader): + renderobject.__init__(self,shader) + self.plane=plane + self.dist=dist + + def intersect(self,l): + vd=self.plane.dot(l.vec()) + if vd==0: return "none",vec(0,0,0),vec(0,0,0) + v0 = -(self.plane.dot(l.start)+self.dist) + t = v0/vd + if t<0 or t>1: return "none",vec(0,0,0),vec(0,0,0) + return "one",l.start+(l.vec()*t),self.plane + + +class sphere(renderobject): + def __init__(self, pos, radius, shader): + renderobject.__init__(self,shader) + self.pos=pos + self.radius=radius + + def disttoline(self,l): + return self.pos.dist(l.closestpoint(self.pos)) + + def intersect(self,l): + lvec=l.vec() + a = sq(lvec.x)+sq(lvec.y)+sq(lvec.z) + + b = 2*(lvec.x*(l.start.x-self.pos.x)+ \ + lvec.y*(l.start.y-self.pos.y)+ \ + lvec.z*(l.start.z-self.pos.z)) + + c = self.pos.sq()+l.start.sq() - \ + 2*(self.pos.x*l.start.x+self.pos.y*l.start.y+self.pos.z*l.start.z)-sq(self.radius) + + i = b*b-4*a*c + + intersectiontype="none" + pos=vec(0,0,0) + norm=vec(0,0,0) + t=0 + + if i>0 : + if i==0: + intersectiontype="one" + t = -b/(2*a); + else: + intersectiontype="two" + t = (-b - sqrt( b*b - 4*a*c )) / (2*a) + # just bother with one for the moment + # t2= (-b + sqrt( b*b - 4*a*c )) / (2*a) + + if t>0 and t<1: + pos = l.start+lvec*t + norm=pos-self.pos + norm.norm() + else: + intersectiontype="none" + + return intersectiontype,pos,norm + + def intersects(self,l): + return self.disttoline(l)0: + lray=copy.copy(shaderinfo["ray"]) + ray=lray.vec() + normal=copy.copy(shaderinfo["normal"]) + ray=ray.reflect(normal) + reflected=line(shaderinfo["position"],shaderinfo["position"]+ray) + obj=shaderinfo["thisobj"] + objects=shaderinfo["objects"] + newshaderinfo = copy.copy(shaderinfo) + newshaderinfo["ray"]=reflected + newshaderinfo["depth"]=depth-1 + # todo - depth test + for ob in objects: + if ob is not obj: + intersects,position,normal = ob.intersect(reflected) + if intersects is not "none": + newshaderinfo["thisobj"]=ob + newshaderinfo["position"]=position + newshaderinfo["normal"]=normal + col=col+ob.shader.shade(newshaderinfo) + return col + + def isoccluded(self,ray,shaderinfo): + dist=ray.mag() + test=line(shaderinfo["position"],shaderinfo["position"]+ray) + obj=shaderinfo["thisobj"] + objects=shaderinfo["objects"] + # todo - depth test + for ob in objects: + if ob is not obj: + intersects,position,normal = ob.intersect(test) + if intersects is not "none": + return 1 + return 0 + + def doocclusion(self,samples,shaderinfo): + # not really very scientific, or good in any way... + oc=0.0 + for i in range(0,samples): + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100)) + ray.norm() + ray=ray*2.5 + if self.isoccluded(ray,shaderinfo): + oc=oc+1 + oc=oc/float(samples) + return 1-oc + + def getcolour(self,ray,shaderinfo): + depth=shaderinfo["depth"] + col=vec(0,0,0) + if depth>0: + test=line(shaderinfo["position"],shaderinfo["position"]+ray) + obj=shaderinfo["thisobj"] + objects=shaderinfo["objects"] + newshaderinfo = copy.copy(shaderinfo) + newshaderinfo["ray"]=test + newshaderinfo["depth"]=depth-1 + # todo - depth test + for ob in objects: + if ob is not obj: + intersects,position,normal = ob.intersect(test) + if intersects is not "none": + newshaderinfo["thisobj"]=ob + newshaderinfo["position"]=position + newshaderinfo["normal"]=normal + col=col+ob.shader.shade(newshaderinfo) + return col + + def docolourbleed(self,samples,shaderinfo): + # not really very scientific, or good in any way... + col=vec(0,0,0) + for i in range(0,samples): + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100)) + ray.norm() + ray=ray*5 + col=col+self.getcolour(ray,shaderinfo) + col=col/float(samples) + return col + + def shade(self,shaderinfo): + col=vec(0,0,0) + for lite in shaderinfo["lights"]: + col=col+lite.light(shaderinfo) + return col + +class world: + def __init__(self,width,height): + self.lights=[] + self.objects=[] + self.cameratype="persp" + self.width=width + self.height=height + self.backplane=2000.0 + self.imageplane=5.0 + self.aspect=self.width/float(self.height) + + def render_row(self, channel, sy): + + """ + Render the given row, using the 'channel' provided to communicate + result data back to the coordinating process. The row position 'sy', + provided for the first row, will subsequently be read from the + 'channel' for the rendering of new rows. A tuple containing 'sy' and a + list of result numbers is returned by this function via the given + 'channel'. + """ + + while 1: + + row = [] + for sx in range(0,self.width): + x=2*(0.5-sx/float(self.width))*self.aspect + y=2*(0.5-sy/float(self.height)) + if self.cameratype=="ortho": + ray = line(vec(x,y,0),vec(x,y,self.backplane)) + else: + ray = line(vec(0,0,0),vec(x,y,self.imageplane)) + ray.end=ray.end*self.backplane + + col=vec(0,0,0) + depth=self.backplane + shaderinfo={"ray":ray,"lights":self.lights,"objects":self.objects,"depth":2} + + for obj in self.objects: + intersects,position,normal = obj.intersect(ray) + if intersects is not "none": + if position.z0: + depth=position.z + shaderinfo["thisobj"]=obj + shaderinfo["position"]=position + shaderinfo["normal"]=normal + col=obj.shader.shade(shaderinfo) + row.append(col) + + channel.send((sy, row)) + t = channel.receive() + if t is None: + break + (sy,), kw = t + + def render(self, filename, limit): + + """ + Render the image with many processes, saving it to 'filename', using the + given process 'limit' to constrain the number of processes used. + """ + + image = Image.new("RGB", (self.width, self.height)) + exchange = PyGmyExchange(self.width, self.height, image, limit=limit, reuse=1) + render_row = exchange.manage(self.render_row) + + for y in range(0, self.height): + render_row(y) + + exchange.finish() + image.save(filename) + +class PyGmyExchange(pprocess.Exchange): + + "A convenience class for parallelisation." + + def __init__(self, width, height, image, *args, **kw): + + """ + Initialise the exchange, adding extra PyGmy-specific data such as the + 'width' and 'height' of the eventual 'image'. + """ + + pprocess.Exchange.__init__(self, *args, **kw) + self.draw = ImageDraw.Draw(image) + self.total = width * height + self.count = 0 + + def store_data(self, channel): + + "Store the data arriving on the given 'channel'." + + sy, row = channel.receive() + for sx, col in enumerate(row): + self.draw.point((sx,sy),fill=(col.x*255,col.y*255,col.z*255)) + self.count = self.count + 1 + + percent = int((self.count/float(self.total))*100) + sys.stdout.write(("\010" * 9) + "%3d%% %3d" % (percent, sy)) + sys.stdout.flush() + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 905d8ec73003 -r 07efb8b50f73 examples/PyGmy/scene_reuse.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/PyGmy/scene_reuse.py Sun Sep 16 16:54:17 2007 +0000 @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +""" +An example scene from... + +http://www.pawfal.org/index.php?page=PyGmy +""" + +import math +from ppygmy_reuse import * +import sys + +class everythingshader(shader): + def __init__(self): + pass + + def shade(self,shaderinfo): + col = shader.shade(self,shaderinfo) + ref = self.getreflected(shaderinfo) + col = col*0.5+ref*0.5 + return col*self.doocclusion(10,shaderinfo) + +class spotshader(shader): + def __init__(self): + pass + + def shade(self,shaderinfo): + col = shader.shade(self,shaderinfo) + position=shaderinfo["position"] + jitter=(math.sin(position.x)+math.cos(position.z)) + if jitter>0.5: col=col/2 + ref = self.getreflected(shaderinfo) + return ref*0.5+col*0.5*self.doocclusion(10,shaderinfo) + +if __name__ == "__main__": + w = world(300,200) + numballs=10.0 + offset = vec(0,-5,55) + rad=12.0 + radperball=(2*3.141)/numballs + + for i in range(0,int(numballs)): + x=sin(0.3+radperball*float(i))*rad + y=cos(0.3+radperball*float(i))*rad + w.objects.append(sphere(vec(x,0,y)+offset,2,everythingshader())) + + w.objects.append(sphere(vec(3,3,0)+offset,5,everythingshader())) + w.objects.append(plane(vec(0,1,0),7,spotshader())) + w.lights.append(parallellight(vec(1,1,-1),vec(0.3,0.9,0.1))) + w.lights.append(pointlight(vec(5,100,-5),vec(0.5,0.5,1))) + + if len(sys.argv) > 1: + if "--help" in sys.argv: + print "Specify a limit to the number of processes." + print "For example:" + print "python", sys.argv[0], "4" + sys.exit(1) + else: + limit = int(sys.argv[1]) + else: + limit = 1 + + print "Number of processes:", limit + w.render("test.tif", limit) + +# vim: tabstop=4 expandtab shiftwidth=4