1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/examples/PyGmy/ppygmy.py Sun Oct 01 00:57:02 2006 +0000
1.3 @@ -0,0 +1,398 @@
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 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 +
1.34 +def sq(a):
1.35 + return a*a
1.36 +
1.37 +class vec:
1.38 + def __init__(self, x, y, z):
1.39 + self.x=float(x)
1.40 + self.y=float(y)
1.41 + self.z=float(z)
1.42 +
1.43 + def __add__(self,other):
1.44 + return vec(self.x+other.x,self.y+other.y,self.z+other.z)
1.45 +
1.46 + def __sub__(self,other):
1.47 + return vec(self.x-other.x,self.y-other.y,self.z-other.z)
1.48 +
1.49 + def __mul__(self,amount):
1.50 + return vec(self.x*amount,self.y*amount,self.z*amount)
1.51 +
1.52 + def __div__(self,amount):
1.53 + return vec(self.x/amount,self.y/amount,self.z/amount)
1.54 +
1.55 + def __neg__(self):
1.56 + return vec(-self.x,-self.y,-self.z)
1.57 +
1.58 + def dot(self,other):
1.59 + return (self.x*other.x)+(self.y*other.y)+(self.z*other.z)
1.60 +
1.61 + def cross(self,other):
1.62 + return vec(self.y*other.z - self.z*other.y,
1.63 + self.z*other.x - self.x*other.z,
1.64 + self.x*other.y - self.y*other.x)
1.65 +
1.66 + def dist(self,other):
1.67 + return sqrt((other.x-self.x)*(other.x-self.x)+
1.68 + (other.y-self.y)*(other.y-self.y)+
1.69 + (other.z-self.z)*(other.z-self.z))
1.70 +
1.71 + def sq(self):
1.72 + return sq(self.x)+sq(self.y)+sq(self.z)
1.73 +
1.74 + def mag(self):
1.75 + return self.dist(vec(0,0,0))
1.76 +
1.77 + def norm(self):
1.78 + mag=self.mag()
1.79 + if mag!=0:
1.80 + self.x=self.x/mag
1.81 + self.y=self.y/mag
1.82 + self.z=self.z/mag
1.83 +
1.84 + def reflect(self,normal):
1.85 + vdn=self.dot(normal)*2
1.86 + return self-normal*vdn
1.87 +
1.88 +class line:
1.89 + def __init__(self, start, end):
1.90 + self.start=start
1.91 + self.end=end
1.92 +
1.93 + def vec(self):
1.94 + return self.end-self.start
1.95 +
1.96 + def closestpoint(self, point):
1.97 + l=self.end-self.start
1.98 + l2=point-self.start
1.99 + t=l.dot(l2)
1.100 + if t<=0: return self.start
1.101 + if t>l.mag(): return self.end
1.102 + return self.start+l*t
1.103 +
1.104 +class renderobject:
1.105 + def __init__(self, shader):
1.106 + self.shader=shader
1.107 +
1.108 + def intersect(self,l):
1.109 + return "none",vec(0,0,0),vec(0,0,0) # type, position, normal
1.110 +
1.111 +class plane(renderobject):
1.112 + def __init__(self,plane,dist,shader):
1.113 + renderobject.__init__(self,shader)
1.114 + self.plane=plane
1.115 + self.dist=dist
1.116 +
1.117 + def intersect(self,l):
1.118 + vd=self.plane.dot(l.vec())
1.119 + if vd==0: return "none",vec(0,0,0),vec(0,0,0)
1.120 + v0 = -(self.plane.dot(l.start)+self.dist)
1.121 + t = v0/vd
1.122 + if t<0 or t>1: return "none",vec(0,0,0),vec(0,0,0)
1.123 + return "one",l.start+(l.vec()*t),self.plane
1.124 +
1.125 +
1.126 +class sphere(renderobject):
1.127 + def __init__(self, pos, radius, shader):
1.128 + renderobject.__init__(self,shader)
1.129 + self.pos=pos
1.130 + self.radius=radius
1.131 +
1.132 + def disttoline(self,l):
1.133 + return self.pos.dist(l.closestpoint(self.pos))
1.134 +
1.135 + def intersect(self,l):
1.136 + lvec=l.vec()
1.137 + a = sq(lvec.x)+sq(lvec.y)+sq(lvec.z)
1.138 +
1.139 + b = 2*(lvec.x*(l.start.x-self.pos.x)+ \
1.140 + lvec.y*(l.start.y-self.pos.y)+ \
1.141 + lvec.z*(l.start.z-self.pos.z))
1.142 +
1.143 + c = self.pos.sq()+l.start.sq() - \
1.144 + 2*(self.pos.x*l.start.x+self.pos.y*l.start.y+self.pos.z*l.start.z)-sq(self.radius)
1.145 +
1.146 + i = b*b-4*a*c
1.147 +
1.148 + intersectiontype="none"
1.149 + pos=vec(0,0,0)
1.150 + norm=vec(0,0,0)
1.151 + t=0
1.152 +
1.153 + if i>0 :
1.154 + if i==0:
1.155 + intersectiontype="one"
1.156 + t = -b/(2*a);
1.157 + else:
1.158 + intersectiontype="two"
1.159 + t = (-b - sqrt( b*b - 4*a*c )) / (2*a)
1.160 + # just bother with one for the moment
1.161 + # t2= (-b + sqrt( b*b - 4*a*c )) / (2*a)
1.162 +
1.163 + if t>0 and t<1:
1.164 + pos = l.start+lvec*t
1.165 + norm=pos-self.pos
1.166 + norm.norm()
1.167 + else:
1.168 + intersectiontype="none"
1.169 +
1.170 + return intersectiontype,pos,norm
1.171 +
1.172 + def intersects(self,l):
1.173 + return self.disttoline(l)<self.radius
1.174 +
1.175 +class light:
1.176 + def __init__(self):
1.177 + pass
1.178 +
1.179 + def checkshadow(self, obj, objects,l):
1.180 + # shadowing built into the lights (is this right?)
1.181 + for ob in objects:
1.182 + if ob is not obj:
1.183 + intersects,pos,norm = ob.intersect(l)
1.184 + if intersects is not "none":
1.185 + return 1
1.186 + return 0
1.187 +
1.188 + def light(self, obj, objects, pos, normal):
1.189 + pass
1.190 +
1.191 +class parallellight(light):
1.192 + def __init__(self, direction, col):
1.193 + direction.norm()
1.194 + self.direction=direction
1.195 + self.col=col
1.196 +
1.197 + def inshadow(self, obj, objects, pos):
1.198 + # create a longish line towards the light
1.199 + l = line(pos,pos+self.direction*1000)
1.200 + return self.checkshadow(obj,objects,l)
1.201 +
1.202 + def light(self, shaderinfo):
1.203 + if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)
1.204 + return self.col*self.direction.dot(shaderinfo["normal"])
1.205 +
1.206 +class pointlight(light):
1.207 + def __init__(self, position, col):
1.208 + self.position=position
1.209 + self.col=col
1.210 +
1.211 + def inshadow(self, obj, objects, pos):
1.212 + l = line(pos,self.position)
1.213 + return self.checkshadow(obj,objects,l)
1.214 +
1.215 + def light(self, shaderinfo):
1.216 + if self.inshadow(shaderinfo["thisobj"],shaderinfo["objects"],shaderinfo["position"]): return vec(0,0,0)
1.217 + direction = shaderinfo["position"]-self.position;
1.218 + direction.norm()
1.219 + direction=-direction
1.220 + return self.col*direction.dot(shaderinfo["normal"])
1.221 +
1.222 +class shader:
1.223 + def __init__(self):
1.224 + pass
1.225 +
1.226 + # a load of helper functions for shaders, need much improvement
1.227 +
1.228 + def getreflected(self,shaderinfo):
1.229 + depth=shaderinfo["depth"]
1.230 + col=vec(0,0,0)
1.231 + if depth>0:
1.232 + lray=copy.copy(shaderinfo["ray"])
1.233 + ray=lray.vec()
1.234 + normal=copy.copy(shaderinfo["normal"])
1.235 + ray=ray.reflect(normal)
1.236 + reflected=line(shaderinfo["position"],shaderinfo["position"]+ray)
1.237 + obj=shaderinfo["thisobj"]
1.238 + objects=shaderinfo["objects"]
1.239 + newshaderinfo = copy.copy(shaderinfo)
1.240 + newshaderinfo["ray"]=reflected
1.241 + newshaderinfo["depth"]=depth-1
1.242 + # todo - depth test
1.243 + for ob in objects:
1.244 + if ob is not obj:
1.245 + intersects,position,normal = ob.intersect(reflected)
1.246 + if intersects is not "none":
1.247 + newshaderinfo["thisobj"]=ob
1.248 + newshaderinfo["position"]=position
1.249 + newshaderinfo["normal"]=normal
1.250 + col=col+ob.shader.shade(newshaderinfo)
1.251 + return col
1.252 +
1.253 + def isoccluded(self,ray,shaderinfo):
1.254 + dist=ray.mag()
1.255 + test=line(shaderinfo["position"],shaderinfo["position"]+ray)
1.256 + obj=shaderinfo["thisobj"]
1.257 + objects=shaderinfo["objects"]
1.258 + # todo - depth test
1.259 + for ob in objects:
1.260 + if ob is not obj:
1.261 + intersects,position,normal = ob.intersect(test)
1.262 + if intersects is not "none":
1.263 + return 1
1.264 + return 0
1.265 +
1.266 + def doocclusion(self,samples,shaderinfo):
1.267 + # not really very scientific, or good in any way...
1.268 + oc=0.0
1.269 + for i in range(0,samples):
1.270 + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))
1.271 + ray.norm()
1.272 + ray=ray*2.5
1.273 + if self.isoccluded(ray,shaderinfo):
1.274 + oc=oc+1
1.275 + oc=oc/float(samples)
1.276 + return 1-oc
1.277 +
1.278 + def getcolour(self,ray,shaderinfo):
1.279 + depth=shaderinfo["depth"]
1.280 + col=vec(0,0,0)
1.281 + if depth>0:
1.282 + test=line(shaderinfo["position"],shaderinfo["position"]+ray)
1.283 + obj=shaderinfo["thisobj"]
1.284 + objects=shaderinfo["objects"]
1.285 + newshaderinfo = copy.copy(shaderinfo)
1.286 + newshaderinfo["ray"]=test
1.287 + newshaderinfo["depth"]=depth-1
1.288 + # todo - depth test
1.289 + for ob in objects:
1.290 + if ob is not obj:
1.291 + intersects,position,normal = ob.intersect(test)
1.292 + if intersects is not "none":
1.293 + newshaderinfo["thisobj"]=ob
1.294 + newshaderinfo["position"]=position
1.295 + newshaderinfo["normal"]=normal
1.296 + col=col+ob.shader.shade(newshaderinfo)
1.297 + return col
1.298 +
1.299 + def docolourbleed(self,samples,shaderinfo):
1.300 + # not really very scientific, or good in any way...
1.301 + col=vec(0,0,0)
1.302 + for i in range(0,samples):
1.303 + ray=vec(random.randrange(-100,100),random.randrange(-100,100),random.randrange(-100,100))
1.304 + ray.norm()
1.305 + ray=ray*5
1.306 + col=col+self.getcolour(ray,shaderinfo)
1.307 + col=col/float(samples)
1.308 + return col
1.309 +
1.310 + def shade(self,shaderinfo):
1.311 + col=vec(0,0,0)
1.312 + for lite in shaderinfo["lights"]:
1.313 + col=col+lite.light(shaderinfo)
1.314 + return col
1.315 +
1.316 +class world:
1.317 + def __init__(self,width,height):
1.318 + self.lights=[]
1.319 + self.objects=[]
1.320 + self.cameratype="persp"
1.321 + self.width=width
1.322 + self.height=height
1.323 + self.backplane=2000.0
1.324 + self.imageplane=5.0
1.325 + self.aspect=self.width/float(self.height)
1.326 +
1.327 + def render_row(self, channel, sy):
1.328 +
1.329 + """
1.330 + Render the given row, using the 'channel' provided to communicate
1.331 + result data back to the coordinating process, and using 'sy' as the row
1.332 + position. A tuple containing 'sy' and a list of result numbers is
1.333 + returned by this function via the given 'channel'.
1.334 + """
1.335 +
1.336 + row = []
1.337 + for sx in range(0,self.width):
1.338 + x=2*(0.5-sx/float(self.width))*self.aspect
1.339 + y=2*(0.5-sy/float(self.height))
1.340 + if self.cameratype=="ortho":
1.341 + ray = line(vec(x,y,0),vec(x,y,self.backplane))
1.342 + else:
1.343 + ray = line(vec(0,0,0),vec(x,y,self.imageplane))
1.344 + ray.end=ray.end*self.backplane
1.345 +
1.346 + col=vec(0,0,0)
1.347 + depth=self.backplane
1.348 + shaderinfo={"ray":ray,"lights":self.lights,"objects":self.objects,"depth":2}
1.349 +
1.350 + for obj in self.objects:
1.351 + intersects,position,normal = obj.intersect(ray)
1.352 + if intersects is not "none":
1.353 + if position.z<depth and position.z>0:
1.354 + depth=position.z
1.355 + shaderinfo["thisobj"]=obj
1.356 + shaderinfo["position"]=position
1.357 + shaderinfo["normal"]=normal
1.358 + col=obj.shader.shade(shaderinfo)
1.359 + row.append(col)
1.360 +
1.361 + channel.send((sy, row))
1.362 +
1.363 + def render(self, filename, limit):
1.364 +
1.365 + """
1.366 + Render the image with many processes, saving it to 'filename', using the
1.367 + given process 'limit' to constrain the number of processes used.
1.368 + """
1.369 +
1.370 + image = Image.new("RGB", (self.width,self.height))
1.371 + draw = ImageDraw.Draw(image)
1.372 + total = self.width*self.height
1.373 + count = 0
1.374 + nproc = 0
1.375 +
1.376 + exchange = pprocess.Exchange()
1.377 + y = 0
1.378 + while y < self.height or nproc > 0:
1.379 + if nproc < limit:
1.380 + channel = pprocess.start(self.render_row, y)
1.381 + exchange.add(channel)
1.382 + y += 1
1.383 + nproc += 1
1.384 +
1.385 + for channel in exchange.ready():
1.386 + nproc -= 1
1.387 + sy, row = channel.receive()
1.388 + sx = 0
1.389 + for col in row:
1.390 + draw.point((sx,sy),fill=(col.x*255,col.y*255,col.z*255))
1.391 + #depth=depth/self.backplane
1.392 + #draw.point((sx,sy),fill=(depth*255,depth*255,depth*255))
1.393 + count=count+1
1.394 + sx += 1
1.395 +
1.396 + percentstr = str(int((count/float(total))*100))+"%"
1.397 + print "\b\b\b"+percentstr
1.398 +
1.399 + image.save(filename)
1.400 +
1.401 +# vim: tabstop=4 expandtab shiftwidth=4