1.1 --- a/examples/PyGmy/ppygmy_points.py Sun Sep 16 00:02:49 2007 +0000
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,395 +0,0 @@
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_point(self, channel, sx, sy):
1.329 -
1.330 - """
1.331 - Render the given point, using the 'channel' provided to communicate
1.332 - result data back to the coordinating process, and using 'sx' and 'sy' as
1.333 - the point position. A tuple containing 'sx', 'sy' and a result is
1.334 - returned by this function via the given 'channel'.
1.335 - """
1.336 -
1.337 - x=2*(0.5-sx/float(self.width))*self.aspect
1.338 - y=2*(0.5-sy/float(self.height))
1.339 - if self.cameratype=="ortho":
1.340 - ray = line(vec(x,y,0),vec(x,y,self.backplane))
1.341 - else:
1.342 - ray = line(vec(0,0,0),vec(x,y,self.imageplane))
1.343 - ray.end=ray.end*self.backplane
1.344 -
1.345 - col=vec(0,0,0)
1.346 - depth=self.backplane
1.347 - shaderinfo={"ray":ray,"lights":self.lights,"objects":self.objects,"depth":2}
1.348 -
1.349 - for obj in self.objects:
1.350 - intersects,position,normal = obj.intersect(ray)
1.351 - if intersects is not "none":
1.352 - if position.z<depth and position.z>0:
1.353 - depth=position.z
1.354 - shaderinfo["thisobj"]=obj
1.355 - shaderinfo["position"]=position
1.356 - shaderinfo["normal"]=normal
1.357 - col=obj.shader.shade(shaderinfo)
1.358 -
1.359 - channel.send((sx, sy, col))
1.360 -
1.361 - def render(self, filename, limit):
1.362 -
1.363 - """
1.364 - Render the image with many processes, saving it to 'filename', using the
1.365 - given process 'limit' to constrain the number of processes used.
1.366 - """
1.367 -
1.368 - image = Image.new("RGB", (self.width,self.height))
1.369 - exchange = PyGmyExchange(limit=limit)
1.370 - exchange.draw = ImageDraw.Draw(image)
1.371 - exchange.total = self.width*self.height
1.372 - exchange.count = 0
1.373 -
1.374 - for y in range(0, self.height):
1.375 - for x in range(0,self.width):
1.376 - channel = pprocess.start(self.render_point, x, y)
1.377 - exchange.add_wait(channel)
1.378 -
1.379 - exchange.finish()
1.380 - image.save(filename)
1.381 -
1.382 -class PyGmyExchange(pprocess.Exchange):
1.383 -
1.384 - "A convenience class for parallelisation."
1.385 -
1.386 - def store_data(self, channel):
1.387 -
1.388 - "Store the data arriving on the given 'channel'."
1.389 -
1.390 - sx, sy, col = channel.receive()
1.391 - self.draw.point((sx,sy),fill=(col.x*255,col.y*255,col.z*255))
1.392 - self.count = self.count + 1
1.393 -
1.394 - percent = int((self.count/float(self.total))*100)
1.395 - sys.stdout.write(("\010" * 13) + "%3d%% %3d %3d" % (percent, sx, sy))
1.396 - sys.stdout.flush()
1.397 -
1.398 -# vim: tabstop=4 expandtab shiftwidth=4