pprocess

examples/PyGmy/ppygmy.py

50:bee0a934d1aa
2006-10-01 paulb [project @ 2006-10-01 00:57:02 by paulb] Added PyGmy example.
     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 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     31 def sq(a):    32     return a*a    33         34 class vec:    35     def __init__(self, x, y, z):    36         self.x=float(x)    37         self.y=float(y)    38         self.z=float(z)    39     40     def __add__(self,other):    41         return vec(self.x+other.x,self.y+other.y,self.z+other.z)    42     43     def __sub__(self,other):    44         return vec(self.x-other.x,self.y-other.y,self.z-other.z)    45     46     def __mul__(self,amount):    47         return vec(self.x*amount,self.y*amount,self.z*amount)    48     49     def __div__(self,amount):    50         return vec(self.x/amount,self.y/amount,self.z/amount)    51     52     def __neg__(self):    53         return vec(-self.x,-self.y,-self.z)    54     55     def dot(self,other):    56         return (self.x*other.x)+(self.y*other.y)+(self.z*other.z)    57             58     def cross(self,other):    59         return vec(self.y*other.z - self.z*other.y,    60                     self.z*other.x - self.x*other.z,    61                     self.x*other.y - self.y*other.x)    62     63     def dist(self,other):    64         return sqrt((other.x-self.x)*(other.x-self.x)+    65                     (other.y-self.y)*(other.y-self.y)+    66                     (other.z-self.z)*(other.z-self.z))    67     68     def sq(self):    69         return sq(self.x)+sq(self.y)+sq(self.z)    70     71     def mag(self):    72         return self.dist(vec(0,0,0))    73     74     def norm(self):    75         mag=self.mag()    76         if mag!=0:    77             self.x=self.x/mag    78             self.y=self.y/mag    79             self.z=self.z/mag    80                 81     def reflect(self,normal):    82         vdn=self.dot(normal)*2    83         return self-normal*vdn    84     85 class line:     86     def __init__(self, start, end):    87         self.start=start    88         self.end=end    89                 90     def vec(self):    91         return self.end-self.start    92         93     def closestpoint(self, point):    94         l=self.end-self.start    95         l2=point-self.start            96         t=l.dot(l2)    97         if t<=0: return self.start    98         if t>l.mag(): return self.end    99         return self.start+l*t   100        101 class renderobject:   102     def __init__(self, shader):   103         self.shader=shader           104        105     def intersect(self,l):   106         return "none",vec(0,0,0),vec(0,0,0) # type, position, normal   107    108 class plane(renderobject):   109     def __init__(self,plane,dist,shader):   110         renderobject.__init__(self,shader)   111         self.plane=plane   112         self.dist=dist   113            114     def intersect(self,l):           115         vd=self.plane.dot(l.vec())    116         if vd==0: return "none",vec(0,0,0),vec(0,0,0)   117         v0 = -(self.plane.dot(l.start)+self.dist)   118         t = v0/vd   119         if t<0 or t>1: return "none",vec(0,0,0),vec(0,0,0)   120         return "one",l.start+(l.vec()*t),self.plane   121            122        123 class sphere(renderobject):   124     def __init__(self, pos, radius, shader):   125         renderobject.__init__(self,shader)   126         self.pos=pos   127         self.radius=radius   128    129     def disttoline(self,l):   130         return self.pos.dist(l.closestpoint(self.pos))   131        132     def intersect(self,l):   133         lvec=l.vec()   134         a = sq(lvec.x)+sq(lvec.y)+sq(lvec.z)   135              136         b = 2*(lvec.x*(l.start.x-self.pos.x)+ \   137                lvec.y*(l.start.y-self.pos.y)+ \   138                lvec.z*(l.start.z-self.pos.z))   139                   140         c = self.pos.sq()+l.start.sq() - \   141              2*(self.pos.x*l.start.x+self.pos.y*l.start.y+self.pos.z*l.start.z)-sq(self.radius)   142        143         i = b*b-4*a*c    144    145         intersectiontype="none"   146         pos=vec(0,0,0)   147         norm=vec(0,0,0)   148         t=0   149            150         if i>0 :       151             if i==0:   152                 intersectiontype="one"   153                 t = -b/(2*a);   154             else:     155                 intersectiontype="two"   156                 t = (-b - sqrt( b*b - 4*a*c )) / (2*a)   157                 # just bother with one for the moment   158                 # t2= (-b + sqrt( b*b - 4*a*c )) / (2*a)    159    160             if t>0 and t<1:    161                 pos = l.start+lvec*t   162                 norm=pos-self.pos    163                 norm.norm()   164             else:    165                 intersectiontype="none"   166                    167         return intersectiontype,pos,norm   168            169     def intersects(self,l):   170         return self.disttoline(l)<self.radius   171    172 class light:   173     def __init__(self):   174         pass   175            176     def checkshadow(self, obj, objects,l):   177         # shadowing built into the lights (is this right?)   178         for ob in objects:   179             if ob is not obj:   180                 intersects,pos,norm = ob.intersect(l)   181                 if intersects is not "none":   182                     return 1   183         return 0   184            185     def light(self, obj, objects, pos, normal):   186         pass   187    188 class parallellight(light):   189     def __init__(self, direction, col):   190         direction.norm()   191         self.direction=direction   192         self.col=col   193    194     def inshadow(self, obj, objects, pos):           195         # create a longish line towards the light   196         l = line(pos,pos+self.direction*1000)    197         return self.checkshadow(obj,objects,l)   198    199     def light(self, shaderinfo):   200         if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)   201         return self.col*self.direction.dot(shaderinfo["normal"])   202    203 class pointlight(light):   204     def __init__(self, position, col):   205         self.position=position   206         self.col=col   207        208     def inshadow(self, obj, objects, pos):   209         l = line(pos,self.position)   210         return self.checkshadow(obj,objects,l)   211                212     def light(self, shaderinfo):   213         if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)   214         direction = shaderinfo["position"]-self.position;   215         direction.norm()   216         direction=-direction   217         return self.col*direction.dot(shaderinfo["normal"])   218    219 class shader:   220     def __init__(self):   221         pass   222    223     # a load of helper functions for shaders, need much improvement   224    225     def getreflected(self,shaderinfo):   226         depth=shaderinfo["depth"]   227         col=vec(0,0,0)   228         if depth>0:   229             lray=copy.copy(shaderinfo["ray"])   230             ray=lray.vec()   231             normal=copy.copy(shaderinfo["normal"])   232             ray=ray.reflect(normal)   233             reflected=line(shaderinfo["position"],shaderinfo["position"]+ray)   234             obj=shaderinfo["thisobj"]   235             objects=shaderinfo["objects"]   236             newshaderinfo = copy.copy(shaderinfo)   237             newshaderinfo["ray"]=reflected   238             newshaderinfo["depth"]=depth-1   239             # todo - depth test   240             for ob in objects:   241                 if ob is not obj:   242                     intersects,position,normal = ob.intersect(reflected)   243                     if intersects is not "none":   244                         newshaderinfo["thisobj"]=ob   245                         newshaderinfo["position"]=position   246                         newshaderinfo["normal"]=normal   247                         col=col+ob.shader.shade(newshaderinfo)   248         return col   249    250     def isoccluded(self,ray,shaderinfo):   251         dist=ray.mag()   252         test=line(shaderinfo["position"],shaderinfo["position"]+ray)   253         obj=shaderinfo["thisobj"]   254         objects=shaderinfo["objects"]   255         # todo - depth test   256         for ob in objects:   257             if ob is not obj:   258                 intersects,position,normal = ob.intersect(test)   259                 if intersects is not "none":   260                     return 1   261         return 0   262    263     def doocclusion(self,samples,shaderinfo):   264         # not really very scientific, or good in any way...   265         oc=0.0   266         for i in range(0,samples):   267             ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))   268             ray.norm()   269             ray=ray*2.5   270             if self.isoccluded(ray,shaderinfo):   271                 oc=oc+1   272         oc=oc/float(samples)   273         return 1-oc   274    275     def getcolour(self,ray,shaderinfo):   276         depth=shaderinfo["depth"]   277         col=vec(0,0,0)   278         if depth>0:   279             test=line(shaderinfo["position"],shaderinfo["position"]+ray)   280             obj=shaderinfo["thisobj"]   281             objects=shaderinfo["objects"]   282             newshaderinfo = copy.copy(shaderinfo)   283             newshaderinfo["ray"]=test   284             newshaderinfo["depth"]=depth-1   285             # todo - depth test   286             for ob in objects:   287                 if ob is not obj:   288                     intersects,position,normal = ob.intersect(test)   289                     if intersects is not "none":   290                         newshaderinfo["thisobj"]=ob   291                         newshaderinfo["position"]=position   292                         newshaderinfo["normal"]=normal   293                         col=col+ob.shader.shade(newshaderinfo)   294         return col   295    296     def docolourbleed(self,samples,shaderinfo):   297         # not really very scientific, or good in any way...   298         col=vec(0,0,0)   299         for i in range(0,samples):   300             ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))   301             ray.norm()   302             ray=ray*5   303             col=col+self.getcolour(ray,shaderinfo)   304         col=col/float(samples)   305         return col   306    307     def shade(self,shaderinfo):   308         col=vec(0,0,0)   309         for lite in shaderinfo["lights"]:   310             col=col+lite.light(shaderinfo)   311         return col   312    313 class world:   314     def __init__(self,width,height):   315         self.lights=[]   316         self.objects=[]   317         self.cameratype="persp"   318         self.width=width   319         self.height=height   320         self.backplane=2000.0   321         self.imageplane=5.0   322         self.aspect=self.width/float(self.height)   323    324     def render_row(self, channel, sy):   325    326         """   327         Render the given row, using the 'channel' provided to communicate   328         result data back to the coordinating process, and using 'sy' as the row   329         position. A tuple containing 'sy' and a list of result numbers is   330         returned by this function via the given 'channel'.   331         """   332    333         row = []   334         for sx in range(0,self.width):   335             x=2*(0.5-sx/float(self.width))*self.aspect   336             y=2*(0.5-sy/float(self.height))   337             if self.cameratype=="ortho":   338                 ray = line(vec(x,y,0),vec(x,y,self.backplane))   339             else:   340                 ray = line(vec(0,0,0),vec(x,y,self.imageplane))   341                 ray.end=ray.end*self.backplane   342    343             col=vec(0,0,0)   344             depth=self.backplane   345             shaderinfo={"ray":ray,"lights":self.lights,"objects":self.objects,"depth":2}   346    347             for obj in self.objects:                               348                 intersects,position,normal = obj.intersect(ray)   349                 if intersects is not "none":   350                     if position.z<depth and position.z>0:   351                         depth=position.z   352                         shaderinfo["thisobj"]=obj   353                         shaderinfo["position"]=position   354                         shaderinfo["normal"]=normal   355                         col=obj.shader.shade(shaderinfo)   356             row.append(col)   357    358         channel.send((sy, row))   359    360     def render(self, filename, limit):   361    362         """   363         Render the image with many processes, saving it to 'filename', using the   364         given process 'limit' to constrain the number of processes used.   365         """   366    367         image = Image.new("RGB", (self.width,self.height))   368         draw = ImageDraw.Draw(image)   369         total = self.width*self.height   370         count = 0   371         nproc = 0   372    373         exchange = pprocess.Exchange()   374         y = 0   375         while y < self.height or nproc > 0:   376             if nproc < limit:   377                 channel = pprocess.start(self.render_row, y)   378                 exchange.add(channel)   379                 y += 1   380                 nproc += 1   381    382             for channel in exchange.ready():   383                 nproc -= 1   384                 sy, row = channel.receive()   385                 sx = 0   386                 for col in row:   387                     draw.point((sx,sy),fill=(col.x*255,col.y*255,col.z*255))   388                     #depth=depth/self.backplane   389                     #draw.point((sx,sy),fill=(depth*255,depth*255,depth*255))   390                     count=count+1   391                     sx += 1   392    393                 percentstr = str(int((count/float(total))*100))+"%"   394                 print "\b\b\b"+percentstr   395    396         image.save(filename)   397    398 # vim: tabstop=4 expandtab shiftwidth=4