Big redesign for the 0.3 series
[vrm.git] / vrm.py
1 #!BPY
2 """
3 Name: 'VRM'
4 Blender: 241
5 Group: 'Export'
6 Tooltip: 'Vector Rendering Method Export Script 0.3'
7 """
8
9 # ---------------------------------------------------------------------
10 #    Copyright (c) 2006 Antonio Ospite
11 #
12 #    This program is free software; you can redistribute it and/or modify
13 #    it under the terms of the GNU General Public License as published by
14 #    the Free Software Foundation; either version 2 of the License, or
15 #    (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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
25 #
26 # ---------------------------------------------------------------------
27 #
28 #    NOTE: I do not know who is the original author of 'vrm'.
29 #    The present code is almost entirely rewritten from scratch,
30 #    but if I have to give credits to anyone, please let me know,
31 #    so I can update the copyright.
32 #
33 # ---------------------------------------------------------------------
34 #
35 # Additional credits:
36 #   Thanks to Emilio Aguirre for S2flender from which I took inspirations :)
37 #   Thanks to Anthony C. D'Agostino for the backface.py script   
38 #
39 # ---------------------------------------------------------------------
40
41 import Blender
42 from Blender import Scene, Object, NMesh, Lamp, Camera
43 from Blender.Mathutils import *
44 from math import *
45
46
47 # ---------------------------------------------------------------------
48 #
49 ## Projections classes
50 #
51 # ---------------------------------------------------------------------
52
53 class Projection:
54     def __init__(self):
55         print "New projection"
56
57 class PerspectiveProjection(Projection):
58     def __init___(self):
59         Projection.__init__(self)
60         print "Perspective"
61
62     def doProjection():
63         print "do a perspective projection!!"
64
65 def Perspective(fovy, aspect, near, far):
66     top = near * tan(fovy * pi / 360.0)
67     bottom = -top
68     left = bottom*aspect
69     right= top*aspect
70     x = (2.0 * near) / (right-left)
71     y = (2.0 * near) / (top-bottom)
72     a = (right+left) / (right-left)
73     b = (top+bottom) / (top - bottom)
74     c = - ((far+near) / (far-near))
75     d = - ((2*far*near)/(far-near))
76     return Matrix([x,0.0,a,0.0],[0.0,y,b,0.0],[0.0,0.0,c,d],[0.0,0.0,-1.0,0.0])
77
78 def flatten_new(v, cameraObj, canvasSize, obMesh):
79     
80     cam = cameraObj.getInverseMatrix()
81     cam.transpose() 
82
83     # Changing the view mode
84     cmra = cameraObj.getData()
85  
86     #if cmra.type:
87     #    print "Ortho"
88         #m2 = Ortho(fovy,float(w*ax)/float(h*ay),cmra.clipStart, cmra.clipEnd,17) #cmra.scale) 
89     #else:
90     #    print "Perspective"
91     
92     #Create Frustum 
93     #frustum = _Frustum(cam,m2)
94     
95     m1 = Matrix()
96     mP = Matrix()
97     
98     fovy = atan(0.5/(float(canvasSize[0])/float(canvasSize[1]))/(cmra.lens/32))
99     fovy = fovy * 360/pi
100
101     m2 = Perspective(fovy,float(canvasSize[0])/float(canvasSize[1]),cmra.clipStart, cmra.clipEnd) 
102
103     m1 = obMesh.matrixWorld #mat
104     m1.transpose()
105     mP = cam * m1
106     mP = m2  * mP
107     
108     #Transform the vertices to global coordinates
109     p = mP*Vector([v.co[0],v.co[1],v.co[2],1.0])
110     #tf.append(p)
111     #p = m1*Vector([v.co[0],v.co[1],v.co[2],1.0])
112     #t2.append([p[0],p[1],p[2]])
113
114     mW = canvasSize[0]/2
115     mH = canvasSize[1]/2
116     
117     if p[3]<=0:
118         p[0] = int(p[0]*mW)+mW
119         p[1] = int(p[1]*mH)+mH
120     else:
121         p[0] = int((p[0]/p[3])*mW)+mW
122         p[1] = int((p[1]/p[3])*mH)+mH
123         
124     # Mirror and translate along y
125     p[1] *= -1
126     p[1] += canvasSize[1]
127     
128     return p
129
130
131
132 # distance from camera Z'
133 def Distance(PX,PY,PZ):
134     
135     dist = sqrt(PX*PX+PY*PY+PZ*PZ)
136     return dist
137
138 def RotatePoint(PX,PY,PZ,AngleX,AngleY,AngleZ):
139     
140     NewPoint = []
141     # Rotate X
142     NewY = (PY * cos(AngleX))-(PZ * sin(AngleX))
143     NewZ = (PZ * cos(AngleX))+(PY * sin(AngleX))
144     # Rotate Y
145     PZ = NewZ
146     PY = NewY
147     NewZ = (PZ * cos(AngleY))-(PX * sin(AngleY))
148     NewX = (PX * cos(AngleY))+(PZ * sin(AngleY))
149     PX = NewX
150     PZ = NewZ
151     # Rotate Z
152     NewX = (PX * cos(AngleZ))-(PY * sin(AngleZ))
153     NewY = (PY * cos(AngleZ))+(PX * sin(AngleZ))
154     NewPoint.append(NewX)
155     NewPoint.append(NewY)
156     NewPoint.append(NewZ)
157     return NewPoint
158
159 def flatten(vertx, verty, vertz, cameraObj, canvasSize):
160
161     camera = cameraObj.getData()
162     Lens = camera.getLens()       # The Camera lens
163
164     xres = canvasSize[0]      # X res for output
165     yres = canvasSize[1]      # Y res for output
166     ratio = xres/yres
167
168     fov = atan(ratio * 16.0 / Lens)  # Get fov stuff
169     
170     dist = xres/2*tan(fov)         # Calculate dist from pinhole camera to image plane
171
172     screenxy=[0,0,vertz]
173     x=-vertx
174     y=verty
175     z=vertz
176
177     #----------------------------        
178     # calculate x'=dist*x/z & y'=dist*x/z
179     #----------------------------
180     screenxy[0]=int(xres/2.0+4*x*dist/z)
181     screenxy[1]=int(yres/2.0+4*y*dist/z)
182     return screenxy
183
184 ## Backface culling routine
185 #
186
187 def isFaceVisible(face, obj, cameraObj):
188     """
189     Determine if the face is visible from the current camera.
190     """
191     numvert = len(face)
192     # backface culling
193     a = []
194     a.append(face[0][0])
195     a.append(face[0][1])
196     a.append(face[0][2])
197     a = RotatePoint(a[0], a[1], a[2], obj.RotX, obj.RotY, obj.RotZ)
198     a[0] += obj.LocX - cameraObj.LocX
199     a[1] += obj.LocY - cameraObj.LocY
200     a[2] += obj.LocZ - cameraObj.LocZ
201     b = []
202     b.append(face[1][0])
203     b.append(face[1][1])
204     b.append(face[1][2])
205     b = RotatePoint(b[0], b[1], b[2], obj.RotX, obj.RotY, obj.RotZ)
206     b[0] += obj.LocX - cameraObj.LocX
207     b[1] += obj.LocY - cameraObj.LocY
208     b[2] += obj.LocZ - cameraObj.LocZ
209     c = []
210     c.append(face[numvert-1][0])
211     c.append(face[numvert-1][1])
212     c.append(face[numvert-1][2])
213     c = RotatePoint(c[0], c[1], c[2], obj.RotX, obj.RotY, obj.RotZ)
214     c[0] += obj.LocX - cameraObj.LocX
215     c[1] += obj.LocY - cameraObj.LocY
216     c[2] += obj.LocZ - cameraObj.LocZ
217
218     norm = [0,0,0]
219     norm[0] = (b[1] - a[1])*(c[2] - a[2]) - (c[1] - a[1])*(b[2] - a[2])
220     norm[1] = -((b[0] - a[0])*(c[2] - a[2]) - (c[0] - a[0])*(b[2] - a[2]))
221     norm[2] = (b[0] - a[0])*(c[1] - a[1]) - (c[0] - a[0])*(b[1] - a[1])
222
223     d = norm[0]*a[0] + norm[1]*a[1] + norm[2]*a[2]
224     return (d<0)
225
226
227 # ---------------------------------------------------------------------
228 #
229 ## Mesh representation class
230 #
231 # ---------------------------------------------------------------------
232
233 # TODO: a class to represent the needed properties of a 2D vector image
234
235
236 # ---------------------------------------------------------------------
237 #
238 ## Vector Drawing Classes
239 #
240 # ---------------------------------------------------------------------
241
242 ## A generic Writer
243
244 class VectorWriter:
245     """
246     A class for printing output in a vectorial format.
247
248     Given a 2D representation of the 3D scene the class is responsible to
249     write it is a vector format.
250
251     Every subclasses of VectorWriter must have at last the following public
252     methods:
253         - printCanvas(mesh) --- where mesh is as specified before.
254     """
255     
256     def __init__(self, fileName, canvasSize):
257         """Open the file named #fileName# and set the canvas size."""
258         
259         self.file = open(fileName, "w")
260         print "Outputting to: ", fileName
261
262         self.canvasSize = canvasSize
263     
264
265     # Public Methods
266     #
267     
268     def printCanvas(mesh):
269         return
270         
271     
272     # Private Methods
273     #
274     
275     def _printHeader():
276         return
277
278     def _printFooter():
279         return
280
281
282 ## SVG Writer
283
284 class SVGVectorWriter(VectorWriter):
285     """A concrete class for writing SVG output.
286
287     The class does not support animations, yet.
288     Sorry.
289     """
290
291     def __init__(self, file, canvasSize):
292         """Simply call the parent Contructor."""
293         VectorWriter.__init__(self, file, canvasSize)
294
295
296     # Public Methods
297     #
298     
299     def printCanvas(self, mesh):
300         """Convert the mesh representation to SVG."""
301
302         self._printHeader()
303         
304         for obj in mesh:
305             for face in obj:
306                 self._printPolygon(face)
307         
308         self._printFooter()
309     
310         
311     # Private Methods
312     #
313     
314     def _printHeader(self):
315         """Print SVG header."""
316
317         self.file.write("<?xml version=\"1.0\"?>\n")
318         self.file.write("<svg version=\"1.2\"\n")
319         self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
320         self.file.write("\twidth=\"%d\" height=\"%d\" streamable=\"true\">\n\n" %
321                 self.canvasSize)
322
323     def _printFooter(self):
324         """Print the SVG footer."""
325
326         self.file.write("\n</svg>\n")
327         self.file.close()
328
329     def _printPolygon(self, face):
330         """Print our primitive, finally.
331
332         There is no color Handling for now, *FIX!*
333         """
334
335         intensity = 128
336         stroke_width=1
337         
338         self.file.write("<polygon points=\"")
339
340         for v in face:
341             if face.index(v)!= 0:
342                 self.file.write(", ")
343             
344             self.file.write(`v[0]` + ", " + `v[1]`)
345
346         self.file.write("\"\n")
347         self.file.write("\tstyle=\"fill:rgb("+str(intensity)+","+str(intensity)+","+str(intensity)+");")
348         self.file.write(" stroke:rgb(0,0,0);")
349         self.file.write(" stroke-width:"+str(stroke_width)+"\"/>\n")
350
351
352 # ---------------------------------------------------------------------
353 #
354 ## Rendering Classes
355 #
356 # ---------------------------------------------------------------------
357
358 class Renderer:
359     """Render a scene viewed from a given camera.
360     
361     This class is responsible of the rendering process, hence transormation
362     and projection of the ojects in the scene are invoked by the renderer.
363
364     The user can optionally provide a specific camera for the rendering, see
365     the #doRendering# method for more informations.
366     """
367
368     def __init__(self):
369         """Set the canvas size to a defaulr value.
370         
371         The only instance attribute here is the canvas size, which can be
372         queryed to the renderer by other entities.
373         """
374         self.canvasSize = (0.0, 0.0)
375
376
377     # Public Methods
378     #
379
380     def getCanvasSize(self):
381         """Return the current canvas size read from Blender rendering context"""
382         return self.canvasSize
383         
384     def doRendering(self, scene, cameraObj=None):
385         """Control the rendering process.
386         
387         Here we control the entire rendering process invoking the operation
388         needed to transforma project the 3D scene in two dimensions.
389
390         Parameters:
391         scene --- the Blender Scene to render
392         cameraObj --- the camera object to use for the viewing processing
393         """
394
395         if cameraObj == None:
396             cameraObj = scene.getCurrentCamera()
397         
398         # TODO: given the camera get the Wold-to-camera transform and the
399         # projection matrix
400         
401         context = scene.getRenderingContext()
402         self.canvasSize = (context.imageSizeX(), context.imageSizeY())
403         
404         Objects = scene.getChildren()
405         
406         # A mesh to store the transformed geometrical structure
407         mesh = []
408         
409         for obj in Objects:
410             
411             if (obj.getType() != "Mesh"):
412                 print "Type:", obj.getType(), "\tSorry, only mesh Object supported!"
413                 continue
414
415             OBJmesh = obj.getData()           # Get the mesh data for the object
416             meshfaces = OBJmesh.faces        # The number of faces in the object
417
418             transformed_object = []
419
420             for face in meshfaces:
421
422                 # TODO: per face color calculation
423                 # TODO: add/sorting in Z' direction (per face??)
424
425                 # if the face is visible flatten it on the "picture plane"
426                 if isFaceVisible(face, obj, cameraObj):
427                     
428                     # Store transformed face
429                     transformed_face = []
430
431                     for vert in face:
432
433                         vertxyz = list(vert)
434                         
435                         p1 = flatten_new(vert, cameraObj, self.canvasSize,
436                                 obj)
437                         transformed_face.append(p1)
438                         continue
439
440                         # rotate camera
441                         vertxyz = RotatePoint(vertxyz[0], vertxyz[1], vertxyz[2],
442                                 cameraObj.RotX, cameraObj.RotY, cameraObj.RotZ)
443                                 #-cameraObj.RotX, -cameraObj.RotY, -cameraObj.RotZ)
444
445
446                         # original setting for translate
447                         vertxyz[0] -= (obj.LocX - cameraObj.LocX)
448                         vertxyz[1] -= (obj.LocY - cameraObj.LocY)
449                         vertxyz[2] -= (obj.LocZ - cameraObj.LocZ)
450
451
452                         # rotate object
453                         vertxyz = RotatePoint(vertxyz[0], vertxyz[1], vertxyz[2], obj.RotX, obj.RotY, obj.RotZ)
454
455
456
457                         p1 = flatten(vertxyz[0], vertxyz[1], vertxyz[2],
458                             cameraObj, self.canvasSize)
459
460                         transformed_face.append(p1)
461                     
462                     # just some fake lighting...
463
464                     transformed_object.append(transformed_face)
465
466             # at the end of the loop on obj
467             mesh.append(transformed_object)
468         return mesh
469
470
471     # Private Methods
472     #
473
474     def _removehiddenFaces(obj):
475         return
476
477     def _testClipping(face):
478         return
479
480
481 # ---------------------------------------------------------------------
482 #
483 ## Main Program
484 #
485 # ---------------------------------------------------------------------
486
487
488 scene   = Scene.GetCurrent()
489 renderer = Renderer()
490
491 projectedMesh = renderer.doRendering(scene)
492 canvasSize = renderer.getCanvasSize()
493
494 # hackish sorting of faces according to the max z value of a vertex
495 for o in projectedMesh:
496     o.sort(lambda f1, f2:
497             cmp(sum([v[2] for v in f1])/len(f1), sum([v[2] for v in f2])/len(f2)))
498     o.reverse()
499
500 writer = SVGVectorWriter("proba.svg", canvasSize)
501 writer.printCanvas(projectedMesh)