1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/examples/PyGmy/ppygmy_queue.py Sat Sep 15 01:22:07 2007 +0000
1.3 @@ -0,0 +1,390 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +An adaptation of pygmy.py ("a rubbish raytracer") employing pprocess
1.8 +functionality in order to take advantage of multiprocessing environments.
1.9 +
1.10 +--------
1.11 +
1.12 +Copyright (C) 2005 Dave Griffiths
1.13 +Copyright (C) 2006, 2007 Paul Boddie <paul@boddie.org.uk>
1.14 +
1.15 +This program is free software; you can redistribute it and/or
1.16 +modify it under the terms of the GNU General Public License
1.17 +as published by the Free Software Foundation; either version 2
1.18 +of the License, or (at your option) any later version.
1.19 +
1.20 +This program is distributed in the hope that it will be useful,
1.21 +but WITHOUT ANY WARRANTY; without even the implied warranty of
1.22 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.23 +GNU General Public License for more details.
1.24 +
1.25 +You should have received a copy of the GNU General Public License
1.26 +along with this program; if not, write to the Free Software
1.27 +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
1.28 +"""
1.29 +
1.30 +import Image, ImageDraw, random, copy
1.31 +from math import *
1.32 +import pprocess
1.33 +import sys
1.34 +
1.35 +def sq(a):
1.36 + return a*a
1.37 +
1.38 +class vec:
1.39 + def __init__(self, x, y, z):
1.40 + self.x=float(x)
1.41 + self.y=float(y)
1.42 + self.z=float(z)
1.43 +
1.44 + def __add__(self,other):
1.45 + return vec(self.x+other.x,self.y+other.y,self.z+other.z)
1.46 +
1.47 + def __sub__(self,other):
1.48 + return vec(self.x-other.x,self.y-other.y,self.z-other.z)
1.49 +
1.50 + def __mul__(self,amount):
1.51 + return vec(self.x*amount,self.y*amount,self.z*amount)
1.52 +
1.53 + def __div__(self,amount):
1.54 + return vec(self.x/amount,self.y/amount,self.z/amount)
1.55 +
1.56 + def __neg__(self):
1.57 + return vec(-self.x,-self.y,-self.z)
1.58 +
1.59 + def dot(self,other):
1.60 + return (self.x*other.x)+(self.y*other.y)+(self.z*other.z)
1.61 +
1.62 + def cross(self,other):
1.63 + return vec(self.y*other.z - self.z*other.y,
1.64 + self.z*other.x - self.x*other.z,
1.65 + self.x*other.y - self.y*other.x)
1.66 +
1.67 + def dist(self,other):
1.68 + return sqrt((other.x-self.x)*(other.x-self.x)+
1.69 + (other.y-self.y)*(other.y-self.y)+
1.70 + (other.z-self.z)*(other.z-self.z))
1.71 +
1.72 + def sq(self):
1.73 + return sq(self.x)+sq(self.y)+sq(self.z)
1.74 +
1.75 + def mag(self):
1.76 + return self.dist(vec(0,0,0))
1.77 +
1.78 + def norm(self):
1.79 + mag=self.mag()
1.80 + if mag!=0:
1.81 + self.x=self.x/mag
1.82 + self.y=self.y/mag
1.83 + self.z=self.z/mag
1.84 +
1.85 + def reflect(self,normal):
1.86 + vdn=self.dot(normal)*2
1.87 + return self-normal*vdn
1.88 +
1.89 +class line:
1.90 + def __init__(self, start, end):
1.91 + self.start=start
1.92 + self.end=end
1.93 +
1.94 + def vec(self):
1.95 + return self.end-self.start
1.96 +
1.97 + def closestpoint(self, point):
1.98 + l=self.end-self.start
1.99 + l2=point-self.start
1.100 + t=l.dot(l2)
1.101 + if t<=0: return self.start
1.102 + if t>l.mag(): return self.end
1.103 + return self.start+l*t
1.104 +
1.105 +class renderobject:
1.106 + def __init__(self, shader):
1.107 + self.shader=shader
1.108 +
1.109 + def intersect(self,l):
1.110 + return "none",vec(0,0,0),vec(0,0,0) # type, position, normal
1.111 +
1.112 +class plane(renderobject):
1.113 + def __init__(self,plane,dist,shader):
1.114 + renderobject.__init__(self,shader)
1.115 + self.plane=plane
1.116 + self.dist=dist
1.117 +
1.118 + def intersect(self,l):
1.119 + vd=self.plane.dot(l.vec())
1.120 + if vd==0: return "none",vec(0,0,0),vec(0,0,0)
1.121 + v0 = -(self.plane.dot(l.start)+self.dist)
1.122 + t = v0/vd
1.123 + if t<0 or t>1: return "none",vec(0,0,0),vec(0,0,0)
1.124 + return "one",l.start+(l.vec()*t),self.plane
1.125 +
1.126 +
1.127 +class sphere(renderobject):
1.128 + def __init__(self, pos, radius, shader):
1.129 + renderobject.__init__(self,shader)
1.130 + self.pos=pos
1.131 + self.radius=radius
1.132 +
1.133 + def disttoline(self,l):
1.134 + return self.pos.dist(l.closestpoint(self.pos))
1.135 +
1.136 + def intersect(self,l):
1.137 + lvec=l.vec()
1.138 + a = sq(lvec.x)+sq(lvec.y)+sq(lvec.z)
1.139 +
1.140 + b = 2*(lvec.x*(l.start.x-self.pos.x)+ \
1.141 + lvec.y*(l.start.y-self.pos.y)+ \
1.142 + lvec.z*(l.start.z-self.pos.z))
1.143 +
1.144 + c = self.pos.sq()+l.start.sq() - \
1.145 + 2*(self.pos.x*l.start.x+self.pos.y*l.start.y+self.pos.z*l.start.z)-sq(self.radius)
1.146 +
1.147 + i = b*b-4*a*c
1.148 +
1.149 + intersectiontype="none"
1.150 + pos=vec(0,0,0)
1.151 + norm=vec(0,0,0)
1.152 + t=0
1.153 +
1.154 + if i>0 :
1.155 + if i==0:
1.156 + intersectiontype="one"
1.157 + t = -b/(2*a);
1.158 + else:
1.159 + intersectiontype="two"
1.160 + t = (-b - sqrt( b*b - 4*a*c )) / (2*a)
1.161 + # just bother with one for the moment
1.162 + # t2= (-b + sqrt( b*b - 4*a*c )) / (2*a)
1.163 +
1.164 + if t>0 and t<1:
1.165 + pos = l.start+lvec*t
1.166 + norm=pos-self.pos
1.167 + norm.norm()
1.168 + else:
1.169 + intersectiontype="none"
1.170 +
1.171 + return intersectiontype,pos,norm
1.172 +
1.173 + def intersects(self,l):
1.174 + return self.disttoline(l)<self.radius
1.175 +
1.176 +class light:
1.177 + def __init__(self):
1.178 + pass
1.179 +
1.180 + def checkshadow(self, obj, objects,l):
1.181 + # shadowing built into the lights (is this right?)
1.182 + for ob in objects:
1.183 + if ob is not obj:
1.184 + intersects,pos,norm = ob.intersect(l)
1.185 + if intersects is not "none":
1.186 + return 1
1.187 + return 0
1.188 +
1.189 + def light(self, obj, objects, pos, normal):
1.190 + pass
1.191 +
1.192 +class parallellight(light):
1.193 + def __init__(self, direction, col):
1.194 + direction.norm()
1.195 + self.direction=direction
1.196 + self.col=col
1.197 +
1.198 + def inshadow(self, obj, objects, pos):
1.199 + # create a longish line towards the light
1.200 + l = line(pos,pos+self.direction*1000)
1.201 + return self.checkshadow(obj,objects,l)
1.202 +
1.203 + def light(self, shaderinfo):
1.204 + if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)
1.205 + return self.col*self.direction.dot(shaderinfo["normal"])
1.206 +
1.207 +class pointlight(light):
1.208 + def __init__(self, position, col):
1.209 + self.position=position
1.210 + self.col=col
1.211 +
1.212 + def inshadow(self, obj, objects, pos):
1.213 + l = line(pos,self.position)
1.214 + return self.checkshadow(obj,objects,l)
1.215 +
1.216 + def light(self, shaderinfo):
1.217 + if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)
1.218 + direction = shaderinfo["position"]-self.position;
1.219 + direction.norm()
1.220 + direction=-direction
1.221 + return self.col*direction.dot(shaderinfo["normal"])
1.222 +
1.223 +class shader:
1.224 + def __init__(self):
1.225 + pass
1.226 +
1.227 + # a load of helper functions for shaders, need much improvement
1.228 +
1.229 + def getreflected(self,shaderinfo):
1.230 + depth=shaderinfo["depth"]
1.231 + col=vec(0,0,0)
1.232 + if depth>0:
1.233 + lray=copy.copy(shaderinfo["ray"])
1.234 + ray=lray.vec()
1.235 + normal=copy.copy(shaderinfo["normal"])
1.236 + ray=ray.reflect(normal)
1.237 + reflected=line(shaderinfo["position"],shaderinfo["position"]+ray)
1.238 + obj=shaderinfo["thisobj"]
1.239 + objects=shaderinfo["objects"]
1.240 + newshaderinfo = copy.copy(shaderinfo)
1.241 + newshaderinfo["ray"]=reflected
1.242 + newshaderinfo["depth"]=depth-1
1.243 + # todo - depth test
1.244 + for ob in objects:
1.245 + if ob is not obj:
1.246 + intersects,position,normal = ob.intersect(reflected)
1.247 + if intersects is not "none":
1.248 + newshaderinfo["thisobj"]=ob
1.249 + newshaderinfo["position"]=position
1.250 + newshaderinfo["normal"]=normal
1.251 + col=col+ob.shader.shade(newshaderinfo)
1.252 + return col
1.253 +
1.254 + def isoccluded(self,ray,shaderinfo):
1.255 + dist=ray.mag()
1.256 + test=line(shaderinfo["position"],shaderinfo["position"]+ray)
1.257 + obj=shaderinfo["thisobj"]
1.258 + objects=shaderinfo["objects"]
1.259 + # todo - depth test
1.260 + for ob in objects:
1.261 + if ob is not obj:
1.262 + intersects,position,normal = ob.intersect(test)
1.263 + if intersects is not "none":
1.264 + return 1
1.265 + return 0
1.266 +
1.267 + def doocclusion(self,samples,shaderinfo):
1.268 + # not really very scientific, or good in any way...
1.269 + oc=0.0
1.270 + for i in range(0,samples):
1.271 + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))
1.272 + ray.norm()
1.273 + ray=ray*2.5
1.274 + if self.isoccluded(ray,shaderinfo):
1.275 + oc=oc+1
1.276 + oc=oc/float(samples)
1.277 + return 1-oc
1.278 +
1.279 + def getcolour(self,ray,shaderinfo):
1.280 + depth=shaderinfo["depth"]
1.281 + col=vec(0,0,0)
1.282 + if depth>0:
1.283 + test=line(shaderinfo["position"],shaderinfo["position"]+ray)
1.284 + obj=shaderinfo["thisobj"]
1.285 + objects=shaderinfo["objects"]
1.286 + newshaderinfo = copy.copy(shaderinfo)
1.287 + newshaderinfo["ray"]=test
1.288 + newshaderinfo["depth"]=depth-1
1.289 + # todo - depth test
1.290 + for ob in objects:
1.291 + if ob is not obj:
1.292 + intersects,position,normal = ob.intersect(test)
1.293 + if intersects is not "none":
1.294 + newshaderinfo["thisobj"]=ob
1.295 + newshaderinfo["position"]=position
1.296 + newshaderinfo["normal"]=normal
1.297 + col=col+ob.shader.shade(newshaderinfo)
1.298 + return col
1.299 +
1.300 + def docolourbleed(self,samples,shaderinfo):
1.301 + # not really very scientific, or good in any way...
1.302 + col=vec(0,0,0)
1.303 + for i in range(0,samples):
1.304 + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))
1.305 + ray.norm()
1.306 + ray=ray*5
1.307 + col=col+self.getcolour(ray,shaderinfo)
1.308 + col=col/float(samples)
1.309 + return col
1.310 +
1.311 + def shade(self,shaderinfo):
1.312 + col=vec(0,0,0)
1.313 + for lite in shaderinfo["lights"]:
1.314 + col=col+lite.light(shaderinfo)
1.315 + return col
1.316 +
1.317 +class world:
1.318 + def __init__(self,width,height):
1.319 + self.lights=[]
1.320 + self.objects=[]
1.321 + self.cameratype="persp"
1.322 + self.width=width
1.323 + self.height=height
1.324 + self.backplane=2000.0
1.325 + self.imageplane=5.0
1.326 + self.aspect=self.width/float(self.height)
1.327 +
1.328 + def render_row(self, channel, sy):
1.329 +
1.330 + """
1.331 + Render the given row, using the 'channel' provided to communicate
1.332 + result data back to the coordinating process, and using 'sy' as the row
1.333 + position. A tuple containing 'sy' and a list of result numbers is
1.334 + returned by this function via the given 'channel'.
1.335 + """
1.336 +
1.337 + row = []
1.338 + for sx in range(0,self.width):
1.339 + x=2*(0.5-sx/float(self.width))*self.aspect
1.340 + y=2*(0.5-sy/float(self.height))
1.341 + if self.cameratype=="ortho":
1.342 + ray = line(vec(x,y,0),vec(x,y,self.backplane))
1.343 + else:
1.344 + ray = line(vec(0,0,0),vec(x,y,self.imageplane))
1.345 + ray.end=ray.end*self.backplane
1.346 +
1.347 + col=vec(0,0,0)
1.348 + depth=self.backplane
1.349 + shaderinfo={"ray":ray,"lights":self.lights,"objects":self.objects,"depth":2}
1.350 +
1.351 + for obj in self.objects:
1.352 + intersects,position,normal = obj.intersect(ray)
1.353 + if intersects is not "none":
1.354 + if position.z<depth and position.z>0:
1.355 + depth=position.z
1.356 + shaderinfo["thisobj"]=obj
1.357 + shaderinfo["position"]=position
1.358 + shaderinfo["normal"]=normal
1.359 + col=obj.shader.shade(shaderinfo)
1.360 + row.append(col)
1.361 +
1.362 + channel.send((sy, row))
1.363 +
1.364 + def render(self, filename, limit):
1.365 +
1.366 + """
1.367 + Render the image with many processes, saving it to 'filename', using the
1.368 + given process 'limit' to constrain the number of processes used.
1.369 + """
1.370 +
1.371 + image = Image.new("RGB", (self.width, self.height))
1.372 + draw = ImageDraw.Draw(image)
1.373 + total = self.width * self.height
1.374 + count = 0
1.375 +
1.376 + queue = pprocess.Queue(limit=limit)
1.377 + render_row = queue.manage(self.render_row)
1.378 +
1.379 + for y in range(0, self.height):
1.380 + render_row(y)
1.381 +
1.382 + for sy, row in queue:
1.383 + for sx, col in enumerate(row):
1.384 + draw.point((sx,sy),fill=(col.x*255,col.y*255,col.z*255))
1.385 + count = count + 1
1.386 +
1.387 + percent = int((count/float(total))*100)
1.388 + sys.stdout.write(("\010" * 9) + "%3d%% %3d" % (percent, sy))
1.389 + sys.stdout.flush()
1.390 +
1.391 + image.save(filename)
1.392 +
1.393 +# vim: tabstop=4 expandtab shiftwidth=4