2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/examples/PyGmy/ppygmy_pmap.py Wed Sep 12 00:14:38 2007 +0000
2.3 @@ -0,0 +1,385 @@
2.4 +#!/usr/bin/env python
2.5 +
2.6 +"""
2.7 +An adaptation of pygmy.py ("a rubbish raytracer") employing pprocess
2.8 +functionality in order to take advantage of multiprocessing environments.
2.9 +
2.10 +--------
2.11 +
2.12 +Copyright (C) 2005 Dave Griffiths
2.13 +Copyright (C) 2006, 2007 Paul Boddie <paul@boddie.org.uk>
2.14 +
2.15 +This program is free software; you can redistribute it and/or
2.16 +modify it under the terms of the GNU General Public License
2.17 +as published by the Free Software Foundation; either version 2
2.18 +of the License, or (at your option) any later version.
2.19 +
2.20 +This program is distributed in the hope that it will be useful,
2.21 +but WITHOUT ANY WARRANTY; without even the implied warranty of
2.22 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2.23 +GNU General Public License for more details.
2.24 +
2.25 +You should have received a copy of the GNU General Public License
2.26 +along with this program; if not, write to the Free Software
2.27 +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
2.28 +"""
2.29 +
2.30 +import Image, ImageDraw, random, copy
2.31 +from math import *
2.32 +import pprocess
2.33 +import sys
2.34 +
2.35 +def sq(a):
2.36 + return a*a
2.37 +
2.38 +class vec:
2.39 + def __init__(self, x, y, z):
2.40 + self.x=float(x)
2.41 + self.y=float(y)
2.42 + self.z=float(z)
2.43 +
2.44 + def __add__(self,other):
2.45 + return vec(self.x+other.x,self.y+other.y,self.z+other.z)
2.46 +
2.47 + def __sub__(self,other):
2.48 + return vec(self.x-other.x,self.y-other.y,self.z-other.z)
2.49 +
2.50 + def __mul__(self,amount):
2.51 + return vec(self.x*amount,self.y*amount,self.z*amount)
2.52 +
2.53 + def __div__(self,amount):
2.54 + return vec(self.x/amount,self.y/amount,self.z/amount)
2.55 +
2.56 + def __neg__(self):
2.57 + return vec(-self.x,-self.y,-self.z)
2.58 +
2.59 + def dot(self,other):
2.60 + return (self.x*other.x)+(self.y*other.y)+(self.z*other.z)
2.61 +
2.62 + def cross(self,other):
2.63 + return vec(self.y*other.z - self.z*other.y,
2.64 + self.z*other.x - self.x*other.z,
2.65 + self.x*other.y - self.y*other.x)
2.66 +
2.67 + def dist(self,other):
2.68 + return sqrt((other.x-self.x)*(other.x-self.x)+
2.69 + (other.y-self.y)*(other.y-self.y)+
2.70 + (other.z-self.z)*(other.z-self.z))
2.71 +
2.72 + def sq(self):
2.73 + return sq(self.x)+sq(self.y)+sq(self.z)
2.74 +
2.75 + def mag(self):
2.76 + return self.dist(vec(0,0,0))
2.77 +
2.78 + def norm(self):
2.79 + mag=self.mag()
2.80 + if mag!=0:
2.81 + self.x=self.x/mag
2.82 + self.y=self.y/mag
2.83 + self.z=self.z/mag
2.84 +
2.85 + def reflect(self,normal):
2.86 + vdn=self.dot(normal)*2
2.87 + return self-normal*vdn
2.88 +
2.89 +class line:
2.90 + def __init__(self, start, end):
2.91 + self.start=start
2.92 + self.end=end
2.93 +
2.94 + def vec(self):
2.95 + return self.end-self.start
2.96 +
2.97 + def closestpoint(self, point):
2.98 + l=self.end-self.start
2.99 + l2=point-self.start
2.100 + t=l.dot(l2)
2.101 + if t<=0: return self.start
2.102 + if t>l.mag(): return self.end
2.103 + return self.start+l*t
2.104 +
2.105 +class renderobject:
2.106 + def __init__(self, shader):
2.107 + self.shader=shader
2.108 +
2.109 + def intersect(self,l):
2.110 + return "none",vec(0,0,0),vec(0,0,0) # type, position, normal
2.111 +
2.112 +class plane(renderobject):
2.113 + def __init__(self,plane,dist,shader):
2.114 + renderobject.__init__(self,shader)
2.115 + self.plane=plane
2.116 + self.dist=dist
2.117 +
2.118 + def intersect(self,l):
2.119 + vd=self.plane.dot(l.vec())
2.120 + if vd==0: return "none",vec(0,0,0),vec(0,0,0)
2.121 + v0 = -(self.plane.dot(l.start)+self.dist)
2.122 + t = v0/vd
2.123 + if t<0 or t>1: return "none",vec(0,0,0),vec(0,0,0)
2.124 + return "one",l.start+(l.vec()*t),self.plane
2.125 +
2.126 +
2.127 +class sphere(renderobject):
2.128 + def __init__(self, pos, radius, shader):
2.129 + renderobject.__init__(self,shader)
2.130 + self.pos=pos
2.131 + self.radius=radius
2.132 +
2.133 + def disttoline(self,l):
2.134 + return self.pos.dist(l.closestpoint(self.pos))
2.135 +
2.136 + def intersect(self,l):
2.137 + lvec=l.vec()
2.138 + a = sq(lvec.x)+sq(lvec.y)+sq(lvec.z)
2.139 +
2.140 + b = 2*(lvec.x*(l.start.x-self.pos.x)+ \
2.141 + lvec.y*(l.start.y-self.pos.y)+ \
2.142 + lvec.z*(l.start.z-self.pos.z))
2.143 +
2.144 + c = self.pos.sq()+l.start.sq() - \
2.145 + 2*(self.pos.x*l.start.x+self.pos.y*l.start.y+self.pos.z*l.start.z)-sq(self.radius)
2.146 +
2.147 + i = b*b-4*a*c
2.148 +
2.149 + intersectiontype="none"
2.150 + pos=vec(0,0,0)
2.151 + norm=vec(0,0,0)
2.152 + t=0
2.153 +
2.154 + if i>0 :
2.155 + if i==0:
2.156 + intersectiontype="one"
2.157 + t = -b/(2*a);
2.158 + else:
2.159 + intersectiontype="two"
2.160 + t = (-b - sqrt( b*b - 4*a*c )) / (2*a)
2.161 + # just bother with one for the moment
2.162 + # t2= (-b + sqrt( b*b - 4*a*c )) / (2*a)
2.163 +
2.164 + if t>0 and t<1:
2.165 + pos = l.start+lvec*t
2.166 + norm=pos-self.pos
2.167 + norm.norm()
2.168 + else:
2.169 + intersectiontype="none"
2.170 +
2.171 + return intersectiontype,pos,norm
2.172 +
2.173 + def intersects(self,l):
2.174 + return self.disttoline(l)<self.radius
2.175 +
2.176 +class light:
2.177 + def __init__(self):
2.178 + pass
2.179 +
2.180 + def checkshadow(self, obj, objects,l):
2.181 + # shadowing built into the lights (is this right?)
2.182 + for ob in objects:
2.183 + if ob is not obj:
2.184 + intersects,pos,norm = ob.intersect(l)
2.185 + if intersects is not "none":
2.186 + return 1
2.187 + return 0
2.188 +
2.189 + def light(self, obj, objects, pos, normal):
2.190 + pass
2.191 +
2.192 +class parallellight(light):
2.193 + def __init__(self, direction, col):
2.194 + direction.norm()
2.195 + self.direction=direction
2.196 + self.col=col
2.197 +
2.198 + def inshadow(self, obj, objects, pos):
2.199 + # create a longish line towards the light
2.200 + l = line(pos,pos+self.direction*1000)
2.201 + return self.checkshadow(obj,objects,l)
2.202 +
2.203 + def light(self, shaderinfo):
2.204 + if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)
2.205 + return self.col*self.direction.dot(shaderinfo["normal"])
2.206 +
2.207 +class pointlight(light):
2.208 + def __init__(self, position, col):
2.209 + self.position=position
2.210 + self.col=col
2.211 +
2.212 + def inshadow(self, obj, objects, pos):
2.213 + l = line(pos,self.position)
2.214 + return self.checkshadow(obj,objects,l)
2.215 +
2.216 + def light(self, shaderinfo):
2.217 + if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)
2.218 + direction = shaderinfo["position"]-self.position;
2.219 + direction.norm()
2.220 + direction=-direction
2.221 + return self.col*direction.dot(shaderinfo["normal"])
2.222 +
2.223 +class shader:
2.224 + def __init__(self):
2.225 + pass
2.226 +
2.227 + # a load of helper functions for shaders, need much improvement
2.228 +
2.229 + def getreflected(self,shaderinfo):
2.230 + depth=shaderinfo["depth"]
2.231 + col=vec(0,0,0)
2.232 + if depth>0:
2.233 + lray=copy.copy(shaderinfo["ray"])
2.234 + ray=lray.vec()
2.235 + normal=copy.copy(shaderinfo["normal"])
2.236 + ray=ray.reflect(normal)
2.237 + reflected=line(shaderinfo["position"],shaderinfo["position"]+ray)
2.238 + obj=shaderinfo["thisobj"]
2.239 + objects=shaderinfo["objects"]
2.240 + newshaderinfo = copy.copy(shaderinfo)
2.241 + newshaderinfo["ray"]=reflected
2.242 + newshaderinfo["depth"]=depth-1
2.243 + # todo - depth test
2.244 + for ob in objects:
2.245 + if ob is not obj:
2.246 + intersects,position,normal = ob.intersect(reflected)
2.247 + if intersects is not "none":
2.248 + newshaderinfo["thisobj"]=ob
2.249 + newshaderinfo["position"]=position
2.250 + newshaderinfo["normal"]=normal
2.251 + col=col+ob.shader.shade(newshaderinfo)
2.252 + return col
2.253 +
2.254 + def isoccluded(self,ray,shaderinfo):
2.255 + dist=ray.mag()
2.256 + test=line(shaderinfo["position"],shaderinfo["position"]+ray)
2.257 + obj=shaderinfo["thisobj"]
2.258 + objects=shaderinfo["objects"]
2.259 + # todo - depth test
2.260 + for ob in objects:
2.261 + if ob is not obj:
2.262 + intersects,position,normal = ob.intersect(test)
2.263 + if intersects is not "none":
2.264 + return 1
2.265 + return 0
2.266 +
2.267 + def doocclusion(self,samples,shaderinfo):
2.268 + # not really very scientific, or good in any way...
2.269 + oc=0.0
2.270 + for i in range(0,samples):
2.271 + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))
2.272 + ray.norm()
2.273 + ray=ray*2.5
2.274 + if self.isoccluded(ray,shaderinfo):
2.275 + oc=oc+1
2.276 + oc=oc/float(samples)
2.277 + return 1-oc
2.278 +
2.279 + def getcolour(self,ray,shaderinfo):
2.280 + depth=shaderinfo["depth"]
2.281 + col=vec(0,0,0)
2.282 + if depth>0:
2.283 + test=line(shaderinfo["position"],shaderinfo["position"]+ray)
2.284 + obj=shaderinfo["thisobj"]
2.285 + objects=shaderinfo["objects"]
2.286 + newshaderinfo = copy.copy(shaderinfo)
2.287 + newshaderinfo["ray"]=test
2.288 + newshaderinfo["depth"]=depth-1
2.289 + # todo - depth test
2.290 + for ob in objects:
2.291 + if ob is not obj:
2.292 + intersects,position,normal = ob.intersect(test)
2.293 + if intersects is not "none":
2.294 + newshaderinfo["thisobj"]=ob
2.295 + newshaderinfo["position"]=position
2.296 + newshaderinfo["normal"]=normal
2.297 + col=col+ob.shader.shade(newshaderinfo)
2.298 + return col
2.299 +
2.300 + def docolourbleed(self,samples,shaderinfo):
2.301 + # not really very scientific, or good in any way...
2.302 + col=vec(0,0,0)
2.303 + for i in range(0,samples):
2.304 + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))
2.305 + ray.norm()
2.306 + ray=ray*5
2.307 + col=col+self.getcolour(ray,shaderinfo)
2.308 + col=col/float(samples)
2.309 + return col
2.310 +
2.311 + def shade(self,shaderinfo):
2.312 + col=vec(0,0,0)
2.313 + for lite in shaderinfo["lights"]:
2.314 + col=col+lite.light(shaderinfo)
2.315 + return col
2.316 +
2.317 +class world:
2.318 + def __init__(self,width,height):
2.319 + self.lights=[]
2.320 + self.objects=[]
2.321 + self.cameratype="persp"
2.322 + self.width=width
2.323 + self.height=height
2.324 + self.backplane=2000.0
2.325 + self.imageplane=5.0
2.326 + self.aspect=self.width/float(self.height)
2.327 +
2.328 + def render_row(self, sy):
2.329 +
2.330 + """
2.331 + Render the given row, using the 'channel' provided to communicate
2.332 + result data back to the coordinating process, and using 'sy' as the row
2.333 + position. A tuple containing 'sy' and a list of result numbers is
2.334 + returned by this function via the given 'channel'.
2.335 + """
2.336 +
2.337 + row = []
2.338 + for sx in range(0,self.width):
2.339 + x=2*(0.5-sx/float(self.width))*self.aspect
2.340 + y=2*(0.5-sy/float(self.height))
2.341 + if self.cameratype=="ortho":
2.342 + ray = line(vec(x,y,0),vec(x,y,self.backplane))
2.343 + else:
2.344 + ray = line(vec(0,0,0),vec(x,y,self.imageplane))
2.345 + ray.end=ray.end*self.backplane
2.346 +
2.347 + col=vec(0,0,0)
2.348 + depth=self.backplane
2.349 + shaderinfo={"ray":ray,"lights":self.lights,"objects":self.objects,"depth":2}
2.350 +
2.351 + for obj in self.objects:
2.352 + intersects,position,normal = obj.intersect(ray)
2.353 + if intersects is not "none":
2.354 + if position.z<depth and position.z>0:
2.355 + depth=position.z
2.356 + shaderinfo["thisobj"]=obj
2.357 + shaderinfo["position"]=position
2.358 + shaderinfo["normal"]=normal
2.359 + col=obj.shader.shade(shaderinfo)
2.360 + row.append(col)
2.361 +
2.362 + return row
2.363 +
2.364 + def render(self, filename, limit):
2.365 +
2.366 + """
2.367 + Render the image with many processes, saving it to 'filename', using the
2.368 + given process 'limit' to constrain the number of processes used.
2.369 + """
2.370 +
2.371 + image = Image.new("RGB", (self.width, self.height))
2.372 + draw = ImageDraw.Draw(image)
2.373 + total = self.width * self.height
2.374 + count = 0
2.375 +
2.376 + heights = [[h] for h in xrange(0, self.height)]
2.377 +
2.378 + for sy, row in enumerate(pprocess.pmap(self.render_row, heights, limit)):
2.379 + for sx, col in enumerate(row):
2.380 + draw.point((sx,sy),fill=(col.x*255,col.y*255,col.z*255))
2.381 + count = count + 1
2.382 + percent = int((count/float(total))*100)
2.383 + sys.stdout.write(("\010" * 9) + "%3d%% %3d" % (percent, sy))
2.384 + sys.stdout.flush()
2.385 +
2.386 + image.save(filename)
2.387 +
2.388 +# vim: tabstop=4 expandtab shiftwidth=4