pprocess

examples/PyGmy/ppygmy_points.py

71:71793e467e7d
2007-01-14 paulb [project @ 2007-01-14 23:03:28 by paulb] Added more documentation resources.
     1 #!/usr/bin/env python     2      3 """     4 An adaptation of pygmy.py ("a rubbish raytracer") employing pprocess     5 functionality in order to take advantage of multiprocessing environments.     6      7 --------     8      9 Copyright (C) 2005 Dave Griffiths    10 Copyright (C) 2006, 2007 Paul Boddie <paul@boddie.org.uk>    11     12 This program is free software; you can redistribute it and/or    13 modify it under the terms of the GNU General Public License    14 as published by the Free Software Foundation; either version 2    15 of the License, or (at your option) any later version.    16     17 This program is distributed in the hope that it will be useful,    18 but WITHOUT ANY WARRANTY; without even the implied warranty of    19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    20 GNU General Public License for more details.    21     22 You should have received a copy of the GNU General Public License    23 along with this program; if not, write to the Free Software    24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.    25 """    26     27 import Image, ImageDraw, random, copy    28 from math import *    29 import pprocess    30 import sys    31     32 def sq(a):    33     return a*a    34         35 class vec:    36     def __init__(self, x, y, z):    37         self.x=float(x)    38         self.y=float(y)    39         self.z=float(z)    40     41     def __add__(self,other):    42         return vec(self.x+other.x,self.y+other.y,self.z+other.z)    43     44     def __sub__(self,other):    45         return vec(self.x-other.x,self.y-other.y,self.z-other.z)    46     47     def __mul__(self,amount):    48         return vec(self.x*amount,self.y*amount,self.z*amount)    49     50     def __div__(self,amount):    51         return vec(self.x/amount,self.y/amount,self.z/amount)    52     53     def __neg__(self):    54         return vec(-self.x,-self.y,-self.z)    55     56     def dot(self,other):    57         return (self.x*other.x)+(self.y*other.y)+(self.z*other.z)    58             59     def cross(self,other):    60         return vec(self.y*other.z - self.z*other.y,    61                     self.z*other.x - self.x*other.z,    62                     self.x*other.y - self.y*other.x)    63     64     def dist(self,other):    65         return sqrt((other.x-self.x)*(other.x-self.x)+    66                     (other.y-self.y)*(other.y-self.y)+    67                     (other.z-self.z)*(other.z-self.z))    68     69     def sq(self):    70         return sq(self.x)+sq(self.y)+sq(self.z)    71     72     def mag(self):    73         return self.dist(vec(0,0,0))    74     75     def norm(self):    76         mag=self.mag()    77         if mag!=0:    78             self.x=self.x/mag    79             self.y=self.y/mag    80             self.z=self.z/mag    81                 82     def reflect(self,normal):    83         vdn=self.dot(normal)*2    84         return self-normal*vdn    85     86 class line:     87     def __init__(self, start, end):    88         self.start=start    89         self.end=end    90                 91     def vec(self):    92         return self.end-self.start    93         94     def closestpoint(self, point):    95         l=self.end-self.start    96         l2=point-self.start            97         t=l.dot(l2)    98         if t<=0: return self.start    99         if t>l.mag(): return self.end   100         return self.start+l*t   101        102 class renderobject:   103     def __init__(self, shader):   104         self.shader=shader           105        106     def intersect(self,l):   107         return "none",vec(0,0,0),vec(0,0,0) # type, position, normal   108    109 class plane(renderobject):   110     def __init__(self,plane,dist,shader):   111         renderobject.__init__(self,shader)   112         self.plane=plane   113         self.dist=dist   114            115     def intersect(self,l):           116         vd=self.plane.dot(l.vec())    117         if vd==0: return "none",vec(0,0,0),vec(0,0,0)   118         v0 = -(self.plane.dot(l.start)+self.dist)   119         t = v0/vd   120         if t<0 or t>1: return "none",vec(0,0,0),vec(0,0,0)   121         return "one",l.start+(l.vec()*t),self.plane   122            123        124 class sphere(renderobject):   125     def __init__(self, pos, radius, shader):   126         renderobject.__init__(self,shader)   127         self.pos=pos   128         self.radius=radius   129    130     def disttoline(self,l):   131         return self.pos.dist(l.closestpoint(self.pos))   132        133     def intersect(self,l):   134         lvec=l.vec()   135         a = sq(lvec.x)+sq(lvec.y)+sq(lvec.z)   136              137         b = 2*(lvec.x*(l.start.x-self.pos.x)+ \   138                lvec.y*(l.start.y-self.pos.y)+ \   139                lvec.z*(l.start.z-self.pos.z))   140                   141         c = self.pos.sq()+l.start.sq() - \   142              2*(self.pos.x*l.start.x+self.pos.y*l.start.y+self.pos.z*l.start.z)-sq(self.radius)   143        144         i = b*b-4*a*c    145    146         intersectiontype="none"   147         pos=vec(0,0,0)   148         norm=vec(0,0,0)   149         t=0   150            151         if i>0 :       152             if i==0:   153                 intersectiontype="one"   154                 t = -b/(2*a);   155             else:     156                 intersectiontype="two"   157                 t = (-b - sqrt( b*b - 4*a*c )) / (2*a)   158                 # just bother with one for the moment   159                 # t2= (-b + sqrt( b*b - 4*a*c )) / (2*a)    160    161             if t>0 and t<1:    162                 pos = l.start+lvec*t   163                 norm=pos-self.pos    164                 norm.norm()   165             else:    166                 intersectiontype="none"   167                    168         return intersectiontype,pos,norm   169            170     def intersects(self,l):   171         return self.disttoline(l)<self.radius   172    173 class light:   174     def __init__(self):   175         pass   176            177     def checkshadow(self, obj, objects,l):   178         # shadowing built into the lights (is this right?)   179         for ob in objects:   180             if ob is not obj:   181                 intersects,pos,norm = ob.intersect(l)   182                 if intersects is not "none":   183                     return 1   184         return 0   185            186     def light(self, obj, objects, pos, normal):   187         pass   188    189 class parallellight(light):   190     def __init__(self, direction, col):   191         direction.norm()   192         self.direction=direction   193         self.col=col   194    195     def inshadow(self, obj, objects, pos):           196         # create a longish line towards the light   197         l = line(pos,pos+self.direction*1000)    198         return self.checkshadow(obj,objects,l)   199    200     def light(self, shaderinfo):   201         if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)   202         return self.col*self.direction.dot(shaderinfo["normal"])   203    204 class pointlight(light):   205     def __init__(self, position, col):   206         self.position=position   207         self.col=col   208        209     def inshadow(self, obj, objects, pos):   210         l = line(pos,self.position)   211         return self.checkshadow(obj,objects,l)   212                213     def light(self, shaderinfo):   214         if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)   215         direction = shaderinfo["position"]-self.position;   216         direction.norm()   217         direction=-direction   218         return self.col*direction.dot(shaderinfo["normal"])   219    220 class shader:   221     def __init__(self):   222         pass   223    224     # a load of helper functions for shaders, need much improvement   225    226     def getreflected(self,shaderinfo):   227         depth=shaderinfo["depth"]   228         col=vec(0,0,0)   229         if depth>0:   230             lray=copy.copy(shaderinfo["ray"])   231             ray=lray.vec()   232             normal=copy.copy(shaderinfo["normal"])   233             ray=ray.reflect(normal)   234             reflected=line(shaderinfo["position"],shaderinfo["position"]+ray)   235             obj=shaderinfo["thisobj"]   236             objects=shaderinfo["objects"]   237             newshaderinfo = copy.copy(shaderinfo)   238             newshaderinfo["ray"]=reflected   239             newshaderinfo["depth"]=depth-1   240             # todo - depth test   241             for ob in objects:   242                 if ob is not obj:   243                     intersects,position,normal = ob.intersect(reflected)   244                     if intersects is not "none":   245                         newshaderinfo["thisobj"]=ob   246                         newshaderinfo["position"]=position   247                         newshaderinfo["normal"]=normal   248                         col=col+ob.shader.shade(newshaderinfo)   249         return col   250    251     def isoccluded(self,ray,shaderinfo):   252         dist=ray.mag()   253         test=line(shaderinfo["position"],shaderinfo["position"]+ray)   254         obj=shaderinfo["thisobj"]   255         objects=shaderinfo["objects"]   256         # todo - depth test   257         for ob in objects:   258             if ob is not obj:   259                 intersects,position,normal = ob.intersect(test)   260                 if intersects is not "none":   261                     return 1   262         return 0   263    264     def doocclusion(self,samples,shaderinfo):   265         # not really very scientific, or good in any way...   266         oc=0.0   267         for i in range(0,samples):   268             ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))   269             ray.norm()   270             ray=ray*2.5   271             if self.isoccluded(ray,shaderinfo):   272                 oc=oc+1   273         oc=oc/float(samples)   274         return 1-oc   275    276     def getcolour(self,ray,shaderinfo):   277         depth=shaderinfo["depth"]   278         col=vec(0,0,0)   279         if depth>0:   280             test=line(shaderinfo["position"],shaderinfo["position"]+ray)   281             obj=shaderinfo["thisobj"]   282             objects=shaderinfo["objects"]   283             newshaderinfo = copy.copy(shaderinfo)   284             newshaderinfo["ray"]=test   285             newshaderinfo["depth"]=depth-1   286             # todo - depth test   287             for ob in objects:   288                 if ob is not obj:   289                     intersects,position,normal = ob.intersect(test)   290                     if intersects is not "none":   291                         newshaderinfo["thisobj"]=ob   292                         newshaderinfo["position"]=position   293                         newshaderinfo["normal"]=normal   294                         col=col+ob.shader.shade(newshaderinfo)   295         return col   296    297     def docolourbleed(self,samples,shaderinfo):   298         # not really very scientific, or good in any way...   299         col=vec(0,0,0)   300         for i in range(0,samples):   301             ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))   302             ray.norm()   303             ray=ray*5   304             col=col+self.getcolour(ray,shaderinfo)   305         col=col/float(samples)   306         return col   307    308     def shade(self,shaderinfo):   309         col=vec(0,0,0)   310         for lite in shaderinfo["lights"]:   311             col=col+lite.light(shaderinfo)   312         return col   313    314 class world:   315     def __init__(self,width,height):   316         self.lights=[]   317         self.objects=[]   318         self.cameratype="persp"   319         self.width=width   320         self.height=height   321         self.backplane=2000.0   322         self.imageplane=5.0   323         self.aspect=self.width/float(self.height)   324    325     def render_point(self, channel, sx, sy):   326    327         """   328         Render the given point, using the 'channel' provided to communicate   329         result data back to the coordinating process, and using 'sx' and 'sy' as   330         the point position. A tuple containing 'sx', 'sy' and a result is   331         returned by this function via the given 'channel'.   332         """   333    334         x=2*(0.5-sx/float(self.width))*self.aspect   335         y=2*(0.5-sy/float(self.height))   336         if self.cameratype=="ortho":   337             ray = line(vec(x,y,0),vec(x,y,self.backplane))   338         else:   339             ray = line(vec(0,0,0),vec(x,y,self.imageplane))   340             ray.end=ray.end*self.backplane   341    342         col=vec(0,0,0)   343         depth=self.backplane   344         shaderinfo={"ray":ray,"lights":self.lights,"objects":self.objects,"depth":2}   345    346         for obj in self.objects:                               347             intersects,position,normal = obj.intersect(ray)   348             if intersects is not "none":   349                 if position.z<depth and position.z>0:   350                     depth=position.z   351                     shaderinfo["thisobj"]=obj   352                     shaderinfo["position"]=position   353                     shaderinfo["normal"]=normal   354                     col=obj.shader.shade(shaderinfo)   355    356         channel.send((sx, sy, col))   357    358     def render(self, filename, limit):   359    360         """   361         Render the image with many processes, saving it to 'filename', using the   362         given process 'limit' to constrain the number of processes used.   363         """   364    365         image = Image.new("RGB", (self.width,self.height))   366         exchange = PyGmyExchange(limit=limit)   367         exchange.draw = ImageDraw.Draw(image)   368         exchange.total = self.width*self.height   369         exchange.count = 0   370    371         for y in range(0, self.height):   372             for x in range(0,self.width):   373                 channel = pprocess.start(self.render_point, x, y)   374                 exchange.add_wait(channel)   375    376         exchange.finish()   377         image.save(filename)   378    379 class PyGmyExchange(pprocess.Exchange):   380    381     "A convenience class for parallelisation."   382    383     def store_data(self, channel):   384    385         "Store the data arriving on the given 'channel'."   386    387         sx, sy, col = channel.receive()   388         self.draw.point((sx,sy),fill=(col.x*255,col.y*255,col.z*255))   389         self.count = self.count + 1   390    391         percent = int((self.count/float(self.total))*100)   392         sys.stdout.write(("\010" * 13) + "%3d%% %3d %3d" % (percent, sx, sy))   393         sys.stdout.flush()   394    395 # vim: tabstop=4 expandtab shiftwidth=4