Misc fixes and improvements
[vrm.git] / vrm.py
diff --git a/vrm.py b/vrm.py
index 35df00c..f7382a7 100755 (executable)
--- a/vrm.py
+++ b/vrm.py
@@ -1,13 +1,13 @@
 #!BPY
 """
 Name: 'VRM'
 #!BPY
 """
 Name: 'VRM'
-Blender: 241
+Blender: 242
 Group: 'Render'
 Tooltip: 'Vector Rendering Method script'
 """
 
 __author__ = "Antonio Ospite"
 Group: 'Render'
 Tooltip: 'Vector Rendering Method script'
 """
 
 __author__ = "Antonio Ospite"
-__url__ = ["http://vrm.projects.blender.org"]
+__url__ = ["http://projects.blender.org/projects/vrm"]
 __version__ = "0.3"
 
 __bpydoc__ = """\
 __version__ = "0.3"
 
 __bpydoc__ = """\
@@ -42,24 +42,24 @@ __bpydoc__ = """\
 # ---------------------------------------------------------------------
 # 
 # Things TODO for a next release:
 # ---------------------------------------------------------------------
 # 
 # Things TODO for a next release:
-#   - Switch to the Mesh structure, should be considerably faster
-#    (partially done, but with Mesh we cannot sort faces, yet)
+#   - Use multiple lighting sources in color calculation,
+#     (this is part of the "shading refactor") and use light color!
+#   - FIX the issue with negative scales in object tranformations!
 #   - Use a better depth sorting algorithm
 #   - Use a better depth sorting algorithm
+#   - Implement clipping of primitives and do handle object intersections.
+#     (for now only clipping away whole objects is supported).
 #   - Review how selections are made (this script uses selection states of
 #     primitives to represent visibility infos)
 #   - Review how selections are made (this script uses selection states of
 #     primitives to represent visibility infos)
-#   - Implement clipping of primitives and do handle object intersections.
-#     (for now only clipping for whole objects is supported).
-#   - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
-#   - Implement Edge coloring
-#   - Use multiple lighting sources in color calculation
-#   - Implement Shading Styles? (for now we use Flat Shading).
 #   - Use a data structure other than Mesh to represent the 2D image? 
 #   - Use a data structure other than Mesh to represent the 2D image? 
-#     Think to a way to merge adjacent polygons that have the same color.
+#     Think to a way to merge (adjacent) polygons that have the same color.
 #     Or a way to use paths for silhouettes and contours.
 #     Or a way to use paths for silhouettes and contours.
-#   - Add Vector Writers other that SVG.
 #   - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
 #     not support SMIL for animations)
 #   - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
 #     not support SMIL for animations)
-#   - FIX the issue with negative scales in object tranformations!
+#   - Switch to the Mesh structure, should be considerably faster
+#    (partially done, but with Mesh we cannot sort faces, yet)
+#   - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
+#   - Implement Shading Styles? (for now we use Flat Shading) (partially done).
+#   - Add Vector Writers other than SVG.
 #
 # ---------------------------------------------------------------------
 #
 #
 # ---------------------------------------------------------------------
 #
@@ -80,20 +80,24 @@ from math import *
 
 # Some global settings
 
 
 # Some global settings
 
-PRINT_POLYGONS     = True
-
-POLYGON_EXPANSION_TRICK = True # Hidden to the user for now
+class config:
+    polygons = dict()
+    polygons['SHOW'] = True
+    polygons['SHADING'] = 'TOON'
+    # Hidden to the user for now
+    polygons['EXPANSION_TRICK'] = True
 
 
-PRINT_EDGES        = False
-SHOW_HIDDEN_EDGES  = False
-EDGE_STYLE = 'silhouette'
-EDGES_WIDTH = 0.5
+    edges = dict()
+    edges['SHOW'] = True
+    edges['SHOW_HIDDEN'] = False
+    edges['STYLE'] = 'SILHOUETTE'
+    edges['WIDTH'] = 2
+    edges['COLOR'] = [0, 0, 0]
 
 
-RENDER_ANIMATION = False
-
-OPTIMIZE_FOR_SPACE = True
-
-OUTPUT_FORMAT = 'SVG'
+    output = dict()
+    output['FORMAT'] = 'SVG'
+    output['ANIMATION'] = False
+    output['JOIN_OBJECTS'] = True
 
 
 
 
 
 
@@ -117,10 +121,10 @@ class MeshUtils:
 
         return adjface_list
 
 
         return adjface_list
 
-    def isVisibleEdge(e, mesh):
-        """Normal edge selection rule.
+    def isMeshEdge(e, mesh):
+        """Mesh edge rule.
 
 
-        An edge is visible if _any_ of its adjacent faces is selected.
+        A mesh edge is visible if _any_ of its adjacent faces is selected.
         Note: if the edge has no adjacent faces we want to show it as well,
         useful for "edge only" portion of objects.
         """
         Note: if the edge has no adjacent faces we want to show it as well,
         useful for "edge only" portion of objects.
         """
@@ -155,9 +159,29 @@ class MeshUtils:
         else:
             return False
     
         else:
             return False
     
+    def toonShading(u):
+
+        levels = 2
+        texels = 2*levels - 1
+        map = [0.0] + [(i)/float(texels-1) for i in range(1, texels-1) ] + [1.0]
+        
+        v = 1.0
+        for i in range(0, len(map)-1):
+            pivot = (map[i]+map[i+1])/2.0
+            j = int(u>pivot)
+
+            v = map[i+j]
+
+            if v<map[i+1]:
+                return v
+
+        return v
+
+
     getEdgeAdjacentFaces = staticmethod(getEdgeAdjacentFaces)
     getEdgeAdjacentFaces = staticmethod(getEdgeAdjacentFaces)
-    isVisibleEdge = staticmethod(isVisibleEdge)
+    isMeshEdge = staticmethod(isMeshEdge)
     isSilhouetteEdge = staticmethod(isSilhouetteEdge)
     isSilhouetteEdge = staticmethod(isSilhouetteEdge)
+    toonShading = staticmethod(toonShading)
 
 
 
 
 
 
@@ -196,11 +220,10 @@ class Projector:
         fovy = fovy * 360.0/pi
         
         # What projection do we want?
         fovy = fovy * 360.0/pi
         
         # What projection do we want?
-        if camera.type:
-            #mP = self._calcOrthoMatrix(fovy, aspect, near, far, 17) #camera.scale) 
-            mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale) 
-        else:
+        if camera.type == 0:
             mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) 
             mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) 
+        elif camera.type == 1:
+            mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale) 
         
         # View transformation
         cam = Matrix(cameraObj.getInverseMatrix())
         
         # View transformation
         cam = Matrix(cameraObj.getInverseMatrix())
@@ -224,9 +247,11 @@ class Projector:
         # Note that we have to work on the vertex using homogeneous coordinates
         p = self.projectionMatrix * Vector(v).resize4D()
 
         # Note that we have to work on the vertex using homogeneous coordinates
         p = self.projectionMatrix * Vector(v).resize4D()
 
-        if p[3]>0:
+        # Perspective division
+        if p[3] != 0:
             p[0] = p[0]/p[3]
             p[1] = p[1]/p[3]
             p[0] = p[0]/p[3]
             p[1] = p[1]/p[3]
+            p[2] = p[2]/p[3]
 
         # restore the size
         p[3] = 1.0
 
         # restore the size
         p[3] = 1.0
@@ -234,6 +259,7 @@ class Projector:
 
         return p
 
 
         return p
 
+
     ##
     # Private methods
     #
     ##
     # Private methods
     #
@@ -509,7 +535,7 @@ class SVGVectorWriter(VectorWriter):
 
         self.file.write("\n</svg>\n")
 
 
         self.file.write("\n</svg>\n")
 
-    def _printPolygons(self, mesh):
+    def _printPolygons(self, mesh): 
         """Print the selected (visible) polygons.
         """
 
         """Print the selected (visible) polygons.
         """
 
@@ -522,9 +548,12 @@ class SVGVectorWriter(VectorWriter):
             if not face.sel:
                continue
 
             if not face.sel:
                continue
 
-            self.file.write("<polygon points=\"")
+            self.file.write("<path d=\"")
 
 
-            for v in face:
+            p = self._calcCanvasCoord(face.verts[0])
+            self.file.write("M %g,%g L " % (p[0], p[1]))
+
+            for v in face.verts[1:]:
                 p = self._calcCanvasCoord(v)
                 self.file.write("%g,%g " % (p[0], p[1]))
             
                 p = self._calcCanvasCoord(v)
                 self.file.write("%g,%g " % (p[0], p[1]))
             
@@ -540,15 +569,14 @@ class SVGVectorWriter(VectorWriter):
             else:
                 color = [255, 255, 255, 255]
 
             else:
                 color = [255, 255, 255, 255]
 
+            # Convert the color to the #RRGGBB form
+            str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
+
             # use the stroke property to alleviate the "adjacent edges" problem,
             # we simulate polygon expansion using borders,
             # see http://www.antigrain.com/svg/index.html for more info
             # use the stroke property to alleviate the "adjacent edges" problem,
             # we simulate polygon expansion using borders,
             # see http://www.antigrain.com/svg/index.html for more info
-            stroke_col = color
             stroke_width = 0.5
 
             stroke_width = 0.5
 
-            # Convert the color to the #RRGGBB form
-            str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
-
             # Handle transparent polygons
             opacity_string = ""
             if color[3] != 255:
             # Handle transparent polygons
             opacity_string = ""
             if color[3] != 255:
@@ -557,8 +585,7 @@ class SVGVectorWriter(VectorWriter):
 
             self.file.write("\tstyle=\"fill:" + str_col + ";")
             self.file.write(opacity_string)
 
             self.file.write("\tstyle=\"fill:" + str_col + ";")
             self.file.write(opacity_string)
-            if POLYGON_EXPANSION_TRICK:
-                self.file.write(" stroke:" + str_col + ";")
+            if config.polygons['EXPANSION_TRICK']:
                 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
                 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
             self.file.write("\"/>\n")
                 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
                 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
             self.file.write("\"/>\n")
@@ -569,8 +596,8 @@ class SVGVectorWriter(VectorWriter):
         """Print the wireframe using mesh edges.
         """
 
         """Print the wireframe using mesh edges.
         """
 
-        stroke_width=EDGES_WIDTH
-        stroke_col = [0, 0, 0]
+        stroke_width = config.edges['WIDTH']
+        stroke_col = config.edges['COLOR']
         
         self.file.write("<g>\n")
 
         
         self.file.write("<g>\n")
 
@@ -605,17 +632,19 @@ class SVGVectorWriter(VectorWriter):
 #
 # ---------------------------------------------------------------------
 
 #
 # ---------------------------------------------------------------------
 
-# A dictionary to collect all the different edge styles and their edge
-# selection criteria
-edgeSelectionStyles = {
-        'normal': MeshUtils.isVisibleEdge,
-        'silhouette': MeshUtils.isSilhouetteEdge
-        }
+# A dictionary to collect different shading style methods
+shadingStyles = dict()
+shadingStyles['FLAT'] = None
+shadingStyles['TOON'] = None
+
+# A dictionary to collect different edge style methods
+edgeStyles = dict()
+edgeStyles['MESH'] = MeshUtils.isMeshEdge
+edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
 
 # A dictionary to collect the supported output formats
 
 # A dictionary to collect the supported output formats
-outputWriters = {
-        'SVG': SVGVectorWriter,
-        }
+outputWriters = dict()
+outputWriters['SVG'] = SVGVectorWriter
 
 
 class Renderer:
 
 
 class Renderer:
@@ -682,11 +711,11 @@ class Renderer:
         """
         
         context = self._SCENE.getRenderingContext()
         """
         
         context = self._SCENE.getRenderingContext()
-        currentFrame = context.currentFrame()
+        origCurrentFrame = context.currentFrame()
 
         # Handle the animation case
         if not animation:
 
         # Handle the animation case
         if not animation:
-            startFrame = currentFrame
+            startFrame = origCurrentFrame
             endFrame = startFrame
             outputWriter.open()
         else:
             endFrame = startFrame
             outputWriter.open()
         else:
@@ -701,27 +730,34 @@ class Renderer:
 
             # Use some temporary workspace, a full copy of the scene
             inputScene = self._SCENE.copy(2)
 
             # Use some temporary workspace, a full copy of the scene
             inputScene = self._SCENE.copy(2)
-            
+            # And Set our camera accordingly
+            self.cameraObj = inputScene.getCurrentCamera()
+
             try:
                 renderedScene = self.doRenderScene(inputScene)
             try:
                 renderedScene = self.doRenderScene(inputScene)
-            except:
+            except :
+                print "There was an error! Aborting."
+                import traceback
+                print traceback.print_exc()
+
                 self._SCENE.makeCurrent()
                 Scene.unlink(inputScene)
                 del inputScene
                 self._SCENE.makeCurrent()
                 Scene.unlink(inputScene)
                 del inputScene
+                return
 
             outputWriter.printCanvas(renderedScene,
 
             outputWriter.printCanvas(renderedScene,
-                    doPrintPolygons = PRINT_POLYGONS,
-                    doPrintEdges    = PRINT_EDGES,
-                    showHiddenEdges = SHOW_HIDDEN_EDGES)
+                    doPrintPolygons = config.polygons['SHOW'],
+                    doPrintEdges    = config.edges['SHOW'],
+                    showHiddenEdges = config.edges['SHOW_HIDDEN'])
             
             # clear the rendered scene
             self._SCENE.makeCurrent()
             
             # clear the rendered scene
             self._SCENE.makeCurrent()
-            Scene.unlink(renderedScene)
-            del renderedScene
+            #Scene.unlink(renderedScene)
+            #del renderedScene
 
         outputWriter.close()
         print "Done!"
 
         outputWriter.close()
         print "Done!"
-        context.currentFrame(currentFrame)
+        context.currentFrame(origCurrentFrame)
 
 
     def doRenderScene(self, workScene):
 
 
     def doRenderScene(self, workScene):
@@ -733,54 +769,44 @@ class Renderer:
         
         # global processing of the scene
 
         
         # global processing of the scene
 
-        self._doConvertGeometricObjToMesh(workScene)
-
         self._doSceneClipping(workScene)
 
         self._doSceneClipping(workScene)
 
+        self._doConvertGeometricObjToMesh(workScene)
 
 
-        # XXX: Joining objects does not work in batch mode!!
-        # Do not touch the following if, please :)
-
-        global OPTIMIZE_FOR_SPACE
-        if Blender.mode == 'background':
-            print "\nWARNING! Joining objects not supported in background mode!\n"
-            OPTIMIZE_FOR_SPACE = False
-
-        if OPTIMIZE_FOR_SPACE:
+        if config.output['JOIN_OBJECTS']:
             self._joinMeshObjectsInScene(workScene)
 
             self._joinMeshObjectsInScene(workScene)
 
-
         self._doSceneDepthSorting(workScene)
         
         # Per object activities
 
         Objects = workScene.getChildren()
         for obj in Objects:
         self._doSceneDepthSorting(workScene)
         
         # Per object activities
 
         Objects = workScene.getChildren()
         for obj in Objects:
-            
+
             if obj.getType() != 'Mesh':
                 print "Only Mesh supported! - Skipping type:", obj.getType()
                 continue
 
             print "Rendering: ", obj.getName()
 
             if obj.getType() != 'Mesh':
                 print "Only Mesh supported! - Skipping type:", obj.getType()
                 continue
 
             print "Rendering: ", obj.getName()
 
-            mesh = obj.getData()
+            mesh = obj.getData(mesh=1)
 
 
-            self._doModelToWorldCoordinates(mesh, obj.matrix)
+            self._doModelingTransformation(mesh, obj.matrix)
 
 
-            self._doObjectDepthSorting(mesh)
-            
-            # We use both Mesh and NMesh because for depth sorting we change
-            # face order and Mesh class don't let us to do that.
-            mesh.update()
-            mesh = obj.getData(mesh=1)
-            
             self._doBackFaceCulling(mesh)
             self._doBackFaceCulling(mesh)
-            
-            self._doColorAndLighting(mesh)
 
 
-            self._doEdgesStyle(mesh, edgeSelectionStyles[EDGE_STYLE])
+            self._doPerVertexLighting(mesh)
 
 
+            # Do "projection" now so we perform further processing
+            # in Normalized View Coordinates
             self._doProjection(mesh, self.proj)
             self._doProjection(mesh, self.proj)
+
+            self._doViewFrustumClipping(mesh)
+
+            self._doMeshDepthSorting(mesh)
+
+            self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
+
             
             # Update the object data, important! :)
             mesh.update()
             
             # Update the object data, important! :)
             mesh.update()
@@ -799,7 +825,7 @@ class Renderer:
         """
         return obj.matrix.translationPart()
 
         """
         return obj.matrix.translationPart()
 
-    def _cameraViewDirection(self):
+    def _cameraViewVector(self):
         """Get the View Direction form the camera matrix.
         """
         return Vector(self.cameraObj.matrix[2]).resize3D()
         """Get the View Direction form the camera matrix.
         """
         return Vector(self.cameraObj.matrix[2]).resize3D()
@@ -834,7 +860,7 @@ class Renderer:
         # View Vector in orthographics projections is the view Direction of
         # the camera
         if self.cameraObj.data.getType() == 1:
         # View Vector in orthographics projections is the view Direction of
         # the camera
         if self.cameraObj.data.getType() == 1:
-            view_vect = self._cameraViewDirection()
+            view_vect = self._cameraViewVector()
 
         # View vector in perspective projections can be considered as
         # the difference between the camera position and one point of
 
         # View vector in perspective projections can be considered as
         # the difference between the camera position and one point of
@@ -843,6 +869,7 @@ class Renderer:
             vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
             view_vect = vv[1]
 
             vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
             view_vect = vv[1]
 
+
         # if d > 0 the face is visible from the camera
         d = view_vect * normal
         
         # if d > 0 the face is visible from the camera
         d = view_vect * normal
         
@@ -854,41 +881,14 @@ class Renderer:
 
     # Scene methods
 
 
     # Scene methods
 
-    def _doConvertGeometricObjToMesh(self, scene):
-        """Convert all "geometric" objects to mesh ones.
-        """
-        geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
-
-        Objects = scene.getChildren()
-        objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
-        for obj in objList:
-            old_obj = obj
-            obj = self._convertToRawMeshObj(obj)
-            scene.link(obj)
-            scene.unlink(old_obj)
-
-
-            # XXX Workaround for Text and Curve which have some normals
-            # inverted when they are converted to Mesh, REMOVE that when
-            # blender will fix that!!
-            if old_obj.getType() in ['Curve', 'Text']:
-                me = obj.getData(mesh=1)
-                for f in me.faces: f.sel = 1;
-                for v in me.verts: v.sel = 1;
-                me.remDoubles(0)
-                me.triangleToQuad()
-                me.recalcNormals()
-                me.update()
-
-
     def _doSceneClipping(self, scene):
     def _doSceneClipping(self, scene):
-        """Clip objects against the View Frustum.
+        """Clip whole objects against the View Frustum.
 
         For now clip away only objects according to their center position.
         """
 
         cpos = self._getObjPosition(self.cameraObj)
 
         For now clip away only objects according to their center position.
         """
 
         cpos = self._getObjPosition(self.cameraObj)
-        view_vect = self._cameraViewDirection()
+        view_vect = self._cameraViewVector()
 
         near = self.cameraObj.data.clipStart
         far  = self.cameraObj.data.clipEnd
 
         near = self.cameraObj.data.clipStart
         far  = self.cameraObj.data.clipEnd
@@ -910,6 +910,33 @@ class Renderer:
             if (d < near) or (d > far) or (theta > fovy):
                 scene.unlink(o)
 
             if (d < near) or (d > far) or (theta > fovy):
                 scene.unlink(o)
 
+    def _doConvertGeometricObjToMesh(self, scene):
+        """Convert all "geometric" objects to mesh ones.
+        """
+        geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
+
+        Objects = scene.getChildren()
+        objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
+        for obj in objList:
+            old_obj = obj
+            obj = self._convertToRawMeshObj(obj)
+            scene.link(obj)
+            scene.unlink(old_obj)
+
+
+            # XXX Workaround for Text and Curve which have some normals
+            # inverted when they are converted to Mesh, REMOVE that when
+            # blender will fix that!!
+            if old_obj.getType() in ['Curve', 'Text']:
+                me = obj.getData(mesh=1)
+                for f in me.faces: f.sel = 1;
+                for v in me.verts: v.sel = 1;
+                me.remDoubles(0)
+                me.triangleToQuad()
+                me.recalcNormals()
+                me.update()
+
+
     def _doSceneDepthSorting(self, scene):
         """Sort objects in the scene.
 
     def _doSceneDepthSorting(self, scene):
         """Sort objects in the scene.
 
@@ -946,12 +973,21 @@ class Renderer:
         if len(oList) == 1:
             return
 
         if len(oList) == 1:
             return
 
-        mesh = Mesh.New()
+        mesh = Mesh.New('BigOne')
         bigObj = Object.New('Mesh', 'BigOne')
         bigObj.link(mesh)
 
         bigObj = Object.New('Mesh', 'BigOne')
         bigObj.link(mesh)
 
-        bigObj.join(oList)
         scene.link(bigObj)
         scene.link(bigObj)
+
+        try:
+            bigObj.join(oList)
+        except RuntimeError:
+            print "\nCan't Join Objects\n"
+            scene.unlink(bigObj)
+            return
+        except TypeError:
+            print "Objects Type error?"
+        
         for o in oList:
             scene.unlink(o)
 
         for o in oList:
             scene.unlink(o)
 
@@ -978,7 +1014,7 @@ class Renderer:
 
         return newObject
 
 
         return newObject
 
-    def _doModelToWorldCoordinates(self, mesh, matrix):
+    def _doModelingTransformation(self, mesh, matrix):
         """Transform object coordinates to world coordinates.
 
         This step is done simply applying to the object its tranformation
         """Transform object coordinates to world coordinates.
 
         This step is done simply applying to the object its tranformation
@@ -991,34 +1027,6 @@ class Renderer:
 
         mesh.transform(matrix, True)
 
 
         mesh.transform(matrix, True)
 
-    def _doObjectDepthSorting(self, mesh):
-        """Sort faces in an object.
-
-        The faces in the object are sorted following the distance of the
-        vertices from the camera position.
-        """
-        c = self._getObjPosition(self.cameraObj)
-
-        # hackish sorting of faces
-
-        # Sort faces according to the max distance from the camera
-        by_max_vert_dist = (lambda f1, f2:
-                cmp(max([(Vector(v.co)-Vector(c)).length for v in f1]),
-                    max([(Vector(v.co)-Vector(c)).length for v in f2])))
-        
-        # Sort faces according to the min distance from the camera
-        by_min_vert_dist = (lambda f1, f2:
-                cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]),
-                    min([(Vector(v.co)-Vector(c)).length for v in f2])))
-        
-        # Sort faces according to the avg distance from the camera
-        by_avg_vert_dist = (lambda f1, f2:
-                cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1),
-                    sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2)))
-
-        mesh.faces.sort(by_max_vert_dist)
-        mesh.faces.reverse()
-
     def _doBackFaceCulling(self, mesh):
         """Simple Backface Culling routine.
         
     def _doBackFaceCulling(self, mesh):
         """Simple Backface Culling routine.
         
@@ -1038,22 +1046,10 @@ class Renderer:
             if self._isFaceVisible(f):
                 f.sel = 1
 
             if self._isFaceVisible(f):
                 f.sel = 1
 
-        # Is this the correct way to propagate the face selection info to the
-        # vertices belonging to a face ??
-        # TODO: Using the Mesh module this should come for free. Right?
-        #Mesh.Mode(Mesh.SelectModes['VERTEX'])
-        #for f in mesh.faces:
-        #    if not f.sel:
-        #        for v in f: v.sel = 0;
-
-        #for f in mesh.faces:
-        #    if f.sel:
-        #        for v in f: v.sel = 1;
+    def _doPerVertexLighting(self, mesh):
+        """Apply an Illumination ans shading model to the object.
 
 
-    def _doColorAndLighting(self, mesh):
-        """Apply an Illumination model to the object.
-
-        The Illumination model used is the Phong one, it may be inefficient,
+        The model used is the Phong one, it may be inefficient,
         but I'm just learning about rendering and starting from Phong seemed
         the most natural way.
         """
         but I'm just learning about rendering and starting from Phong seemed
         the most natural way.
         """
@@ -1072,7 +1068,7 @@ class Renderer:
         light = light_obj.data
 
         camPos = self._getObjPosition(self.cameraObj)
         light = light_obj.data
 
         camPos = self._getObjPosition(self.cameraObj)
-        
+
         # We do per-face color calculation (FLAT Shading), we can easily turn
         # to a per-vertex calculation if we want to implement some shading
         # technique. For an example see:
         # We do per-face color calculation (FLAT Shading), we can easily turn
         # to a per-vertex calculation if we want to implement some shading
         # technique. For an example see:
@@ -1091,16 +1087,16 @@ class Renderer:
             
             L = Vector(light_pos).normalize()
 
             
             L = Vector(light_pos).normalize()
 
-            V = (Vector(camPos) - Vector(f.v[0].co)).normalize()
+            V = (Vector(camPos) - Vector(f.cent)).normalize()
 
             N = Vector(f.no).normalize()
 
             R = 2 * (N*L) * N - L
 
             # TODO: Attenuation factor (not used for now)
 
             N = Vector(f.no).normalize()
 
             R = 2 * (N*L) * N - L
 
             # TODO: Attenuation factor (not used for now)
-            a0 = 1; a1 = 0.0; a2 = 0.0
+            a0 = 1.0; a1 = 0.0; a2 = 1.0
             d = (Vector(f.v[0].co) - Vector(light_pos)).length
             d = (Vector(f.v[0].co) - Vector(light_pos)).length
-            fd = min(1, 1.0/(a0 + a1*d + a2*d*d))
+            fd = min(1, 1.0/(a0 + a1*d + a2*(d*d)))
 
             # Ambient component
             Ia = 1.0
 
             # Ambient component
             Ia = 1.0
@@ -1110,17 +1106,22 @@ class Renderer:
             # Diffuse component (add light.col for kd)
             kd = mat.getRef() * Vector(mat.getRGBCol())
             Ip = light.getEnergy()
             # Diffuse component (add light.col for kd)
             kd = mat.getRef() * Vector(mat.getRGBCol())
             Ip = light.getEnergy()
-            Idiff = Ip * kd * (N*L)
             
             
+            if config.polygons['SHADING'] == 'FLAT':
+                Idiff = Ip * kd * max(0, (N*L))
+            elif config.polygons['SHADING'] == 'TOON':
+                Idiff = Ip * kd * MeshUtils.toonShading(N*L)
+
             # Specular component
             ks = mat.getSpec() * Vector(mat.getSpecCol())
             ns = mat.getHardness()
             # Specular component
             ks = mat.getSpec() * Vector(mat.getSpecCol())
             ns = mat.getHardness()
-            Ispec = Ip * ks * pow((V * R), ns)
+            Ispec = Ip * ks * pow(max(0, (V*R)), ns)
 
             # Emissive component
             ki = Vector([mat.getEmit()]*3)
 
 
             # Emissive component
             ki = Vector([mat.getEmit()]*3)
 
-            I = ki + Iamb + Idiff + Ispec
+            I = ki + Iamb + (Idiff + Ispec)
+
 
             # Set Alpha component
             I = list(I)
 
             # Set Alpha component
             I = list(I)
@@ -1129,6 +1130,8 @@ class Renderer:
             # Clamp I values between 0 and 1
             I = [ min(c, 1) for c in I]
             I = [ max(0, c) for c in I]
             # Clamp I values between 0 and 1
             I = [ min(c, 1) for c in I]
             I = [ max(0, c) for c in I]
+
+            # Convert to a value between 0 and 255
             tmp_col = [ int(c * 255.0) for c in I]
 
             for c in f.col:
             tmp_col = [ int(c * 255.0) for c in I]
 
             for c in f.col:
@@ -1137,6 +1140,93 @@ class Renderer:
                 c.b = tmp_col[2]
                 c.a = tmp_col[3]
 
                 c.b = tmp_col[2]
                 c.a = tmp_col[3]
 
+    def _doProjection(self, mesh, projector):
+        """Apply Viewing and Projection tranformations.
+        """
+
+        for v in mesh.verts:
+            p = projector.doProjection(v.co)
+            v.co[0] = p[0]
+            v.co[1] = p[1]
+            v.co[2] = p[2]
+
+        # We could reeset Camera matrix, since now
+        # we are in Normalized Viewing Coordinates,
+        # but doung that would affect World Coordinate
+        # processing for other objects
+
+        #self.cameraObj.data.type = 1
+        #self.cameraObj.data.scale = 2.0
+        #m = Matrix().identity()
+        #self.cameraObj.setMatrix(m)
+
+    def _doViewFrustumClipping(self, mesh):
+        """Clip faces against the View Frustum.
+        """
+
+    def test_extensions(self, f1, f2):
+        for v1, v2 in [ (v1, v2) for v1 in f1 for v2 in f2 ]:
+            pass
+
+    def depth_sort(self, faces):
+        return
+            
+
+    def _doMeshDepthSorting(self, mesh):
+        """Sort faces in an object.
+
+        The faces in the object are sorted following the distance of the
+        vertices from the camera position.
+        """
+        if len(mesh.faces) == 0:
+            return
+
+        #c = self._getObjPosition(self.cameraObj)
+
+        # In NVC
+        c = [0, 0, 1]
+
+        # hackish sorting of faces
+
+        # Sort faces according to the max distance from the camera
+        by_max_vert_dist = (lambda f1, f2:
+                cmp(max([(Vector(v.co)-Vector(c)).length for v in f2]),
+                    max([(Vector(v.co)-Vector(c)).length for v in f1])))
+        
+        # Sort faces according to the min distance from the camera
+        by_min_vert_dist = (lambda f1, f2:
+                cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]),
+                    min([(Vector(v.co)-Vector(c)).length for v in f2])))
+        
+        # Sort faces according to the avg distance from the camera
+        by_avg_vert_dist = (lambda f1, f2:
+                cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1),
+                    sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2)))
+
+
+        # FIXME: using NMesh to sort faces. We should avoid that!
+        nmesh = NMesh.GetRaw(mesh.name)
+        nmesh.faces.sort(by_max_vert_dist)
+        #nmesh.faces.reverse()
+
+        # Depth sort tests
+
+        self.depth_sort(nmesh.faces)
+
+        
+        mesh.faces.delete(1, range(0, len(mesh.faces)))
+
+        for i,f in enumerate(nmesh.faces):
+            fv = [v.index for v in f.v] 
+            mesh.faces.extend(fv)
+            mesh.faces[i].mat = f.mat
+            mesh.faces[i].sel = f.sel
+            for i,c in enumerate(mesh.faces[i].col):
+                c.r = f.col[i].r
+                c.g = f.col[i].g
+                c.b = f.col[i].b
+                c.a = f.col[i].a
+
     def _doEdgesStyle(self, mesh, edgestyleSelect):
         """Process Mesh Edges accroding to a given selection style.
 
     def _doEdgesStyle(self, mesh, edgestyleSelect):
         """Process Mesh Edges accroding to a given selection style.
 
@@ -1159,17 +1249,6 @@ class Renderer:
             if edgestyleSelect(e, mesh):
                 e.sel = 1
                 
             if edgestyleSelect(e, mesh):
                 e.sel = 1
                 
-    def _doProjection(self, mesh, projector):
-        """Calculate the Projection for the object.
-        """
-        # TODO: maybe using the object.transform() can be faster?
-
-        for v in mesh.verts:
-            p = projector.doProjection(v.co)
-            v.co[0] = p[0]
-            v.co[1] = p[1]
-            v.co[2] = p[2]
-
 
 
 # ---------------------------------------------------------------------
 
 
 # ---------------------------------------------------------------------
@@ -1187,40 +1266,54 @@ class GUI:
     def _init():
 
         # Output Format menu 
     def _init():
 
         # Output Format menu 
-        default_value = outputWriters.keys().index(OUTPUT_FORMAT)+1
+        output_format = config.output['FORMAT']
+        default_value = outputWriters.keys().index(output_format)+1
         GUI.outFormatMenu = Draw.Create(default_value)
         GUI.evtOutFormatMenu = 0
 
         # Animation toggle button
         GUI.outFormatMenu = Draw.Create(default_value)
         GUI.evtOutFormatMenu = 0
 
         # Animation toggle button
-        GUI.animToggle = Draw.Create(RENDER_ANIMATION)
+        GUI.animToggle = Draw.Create(config.output['ANIMATION'])
         GUI.evtAnimToggle = 1
 
         # Join Objects toggle button
         GUI.evtAnimToggle = 1
 
         # Join Objects toggle button
-        GUI.joinObjsToggle = Draw.Create(OPTIMIZE_FOR_SPACE)
+        GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
         GUI.evtJoinObjsToggle = 2
 
         # Render filled polygons
         GUI.evtJoinObjsToggle = 2
 
         # Render filled polygons
-        GUI.polygonsToggle = Draw.Create(PRINT_POLYGONS)
+        GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
+
+        # Shading Style menu 
+        shading_style = config.polygons['SHADING']
+        default_value = shadingStyles.keys().index(shading_style)+1
+        GUI.shadingStyleMenu = Draw.Create(default_value)
+        GUI.evtShadingStyleMenu = 21
+
         GUI.evtPolygonsToggle = 3
         GUI.evtPolygonsToggle = 3
-        # We hide the POLYGON_EXPANSION_TRICK, for now
+        # We hide the config.polygons['EXPANSION_TRICK'], for now
 
         # Render polygon edges
 
         # Render polygon edges
-        GUI.showEdgesToggle = Draw.Create(PRINT_EDGES)
+        GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
         GUI.evtShowEdgesToggle = 4
 
         # Render hidden edges
         GUI.evtShowEdgesToggle = 4
 
         # Render hidden edges
-        GUI.showHiddenEdgesToggle = Draw.Create(SHOW_HIDDEN_EDGES)
+        GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
         GUI.evtShowHiddenEdgesToggle = 5
 
         # Edge Style menu 
         GUI.evtShowHiddenEdgesToggle = 5
 
         # Edge Style menu 
-        default_value = edgeSelectionStyles.keys().index(EDGE_STYLE)+1
+        edge_style = config.edges['STYLE']
+        default_value = edgeStyles.keys().index(edge_style)+1
         GUI.edgeStyleMenu = Draw.Create(default_value)
         GUI.evtEdgeStyleMenu = 6
 
         # Edge Width slider
         GUI.edgeStyleMenu = Draw.Create(default_value)
         GUI.evtEdgeStyleMenu = 6
 
         # Edge Width slider
-        GUI.edgeWidthSlider = Draw.Create(EDGES_WIDTH)
+        GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
         GUI.evtEdgeWidthSlider = 7
 
         GUI.evtEdgeWidthSlider = 7
 
+        # Edge Color Picker
+        c = config.edges['COLOR']
+        GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
+        GUI.evtEdgeColorPicker = 71
+
         # Render Button
         GUI.evtRenderButton = 8
 
         # Render Button
         GUI.evtRenderButton = 8
 
@@ -1272,30 +1365,45 @@ class GUI:
                 200, 285, 160, 18, GUI.polygonsToggle.val,
                 "Render filled polygons")
 
                 200, 285, 160, 18, GUI.polygonsToggle.val,
                 "Render filled polygons")
 
+        if GUI.polygonsToggle.val == 1:
+
+            # Polygon Shading Style
+            shadingStyleMenuStruct = "Shading Style %t"
+            for t in shadingStyles.keys():
+                shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
+            GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
+                    200, 260, 160, 18, GUI.shadingStyleMenu.val,
+                    "Choose the shading style")
+
+
         # Render Edges
         GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
         # Render Edges
         GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
-                200, 260, 160, 18, GUI.showEdgesToggle.val,
+                200, 235, 160, 18, GUI.showEdgesToggle.val,
                 "Render polygon edges")
 
         if GUI.showEdgesToggle.val == 1:
             
             # Edge Style
             edgeStyleMenuStruct = "Edge Style %t"
                 "Render polygon edges")
 
         if GUI.showEdgesToggle.val == 1:
             
             # Edge Style
             edgeStyleMenuStruct = "Edge Style %t"
-            for t in edgeSelectionStyles.keys():
-               edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t
+            for t in edgeStyles.keys():
+                edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
             GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
             GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
-                    200, 235, 160, 18, GUI.edgeStyleMenu.val,
+                    200, 210, 160, 18, GUI.edgeStyleMenu.val,
                     "Choose the edge style")
 
             # Edge size
             GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
                     "Choose the edge style")
 
             # Edge size
             GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
-                    200, 210, 160, 18, GUI.edgeWidthSlider.val,
+                    200, 185, 140, 18, GUI.edgeWidthSlider.val,
                     0.0, 10.0, 0, "Change Edge Width")
 
                     0.0, 10.0, 0, "Change Edge Width")
 
+            # Edge Color
+            GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
+                    342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
+
             # Show Hidden Edges
             GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
                     GUI.evtShowHiddenEdgesToggle,
             # Show Hidden Edges
             GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
                     GUI.evtShowHiddenEdgesToggle,
-                    200, 185, 160, 18, GUI.showHiddenEdgesToggle.val,
+                    200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
                     "Render hidden edges as dashed lines")
 
         glRasterPos2i(10, 160)
                     "Render hidden edges as dashed lines")
 
         glRasterPos2i(10, 160)
@@ -1311,38 +1419,45 @@ class GUI:
         Draw.Redraw(1)
 
     def button_event(evt):
         Draw.Redraw(1)
 
     def button_event(evt):
-        global PRINT_POLYGONS
-        global POLYGON_EXPANSION_TRICK
-        global PRINT_EDGES
-        global SHOW_HIDDEN_EDGES
-        global EDGE_STYLE
-        global EDGES_WIDTH
-        global RENDER_ANIMATION
-        global OPTIMIZE_FOR_SPACE
-        global OUTPUT_FORMAT
 
         if evt == GUI.evtExitButton:
             Draw.Exit()
 
         if evt == GUI.evtExitButton:
             Draw.Exit()
+
         elif evt == GUI.evtOutFormatMenu:
             i = GUI.outFormatMenu.val - 1
         elif evt == GUI.evtOutFormatMenu:
             i = GUI.outFormatMenu.val - 1
-            OUTPUT_FORMAT = outputWriters.keys()[i]
+            config.output['FORMAT']= outputWriters.keys()[i]
+
         elif evt == GUI.evtAnimToggle:
         elif evt == GUI.evtAnimToggle:
-            RENDER_ANIMATION = bool(GUI.animToggle.val)
+            config.outpur['ANIMATION'] = bool(GUI.animToggle.val)
+
         elif evt == GUI.evtJoinObjsToggle:
         elif evt == GUI.evtJoinObjsToggle:
-            OPTIMIZE_FOR_SPACE = bool(GUI.joinObjsToggle.val)
+            config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
+
         elif evt == GUI.evtPolygonsToggle:
         elif evt == GUI.evtPolygonsToggle:
-            PRINT_POLYGONS = bool(GUI.polygonsToggle.val)
+            config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
+
+        elif evt == GUI.evtShadingStyleMenu:
+            i = GUI.shadingStyleMenu.val - 1
+            config.polygons['SHADING'] = shadingStyles.keys()[i]
+
         elif evt == GUI.evtShowEdgesToggle:
         elif evt == GUI.evtShowEdgesToggle:
-            PRINT_EDGES = bool(GUI.showEdgesToggle.val)
+            config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
+
         elif evt == GUI.evtShowHiddenEdgesToggle:
         elif evt == GUI.evtShowHiddenEdgesToggle:
-            SHOW_HIDDEN_EDGES = bool(GUI.showHiddenEdgesToggle.val)
+            config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
+
         elif evt == GUI.evtEdgeStyleMenu:
             i = GUI.edgeStyleMenu.val - 1
         elif evt == GUI.evtEdgeStyleMenu:
             i = GUI.edgeStyleMenu.val - 1
-            EDGE_STYLE = edgeSelectionStyles.keys()[i]
+            config.edges['STYLE'] = edgeStyles.keys()[i]
+
         elif evt == GUI.evtEdgeWidthSlider:
         elif evt == GUI.evtEdgeWidthSlider:
-            EDGES_WIDTH = float(GUI.edgeWidthSlider.val)
+            config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
+
+        elif evt == GUI.evtEdgeColorPicker:
+            config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
+
         elif evt == GUI.evtRenderButton:
         elif evt == GUI.evtRenderButton:
-            label = "Save %s" % OUTPUT_FORMAT
+            label = "Save %s" % config.output['FORMAT']
             # Show the File Selector
             global outputfile
             Blender.Window.FileSelector(vectorize, label, outputfile)
             # Show the File Selector
             global outputfile
             Blender.Window.FileSelector(vectorize, label, outputfile)
@@ -1355,16 +1470,11 @@ class GUI:
             #GUI.conf_debug()
 
     def conf_debug():
             #GUI.conf_debug()
 
     def conf_debug():
-        print
-        print "PRINT_POLYGONS:", PRINT_POLYGONS
-        print "POLYGON_EXPANSION_TRICK:", POLYGON_EXPANSION_TRICK
-        print "PRINT_EDGES:", PRINT_EDGES
-        print "SHOW_HIDDEN_EDGES:", SHOW_HIDDEN_EDGES
-        print "EDGE_STYLE:", EDGE_STYLE
-        print "EDGES_WIDTH:", EDGES_WIDTH
-        print "RENDER_ANIMATION:", RENDER_ANIMATION
-        print "OPTIMIZE_FOR_SPACE:", OPTIMIZE_FOR_SPACE
-        print "OUTPUT_FORMAT:", OUTPUT_FORMAT
+        from pprint import pprint
+        print "\nConfig"
+        pprint(config.output)
+        pprint(config.polygons)
+        pprint(config.edges)
 
     _init = staticmethod(_init)
     draw = staticmethod(draw)
 
     _init = staticmethod(_init)
     draw = staticmethod(draw)
@@ -1388,11 +1498,11 @@ def vectorize(filename):
     editmode = Window.EditMode()
     if editmode: Window.EditMode(0)
 
     editmode = Window.EditMode()
     if editmode: Window.EditMode(0)
 
-    actualWriter = outputWriters[OUTPUT_FORMAT]
+    actualWriter = outputWriters[config.output['FORMAT']]
     writer = actualWriter(filename)
     
     renderer = Renderer()
     writer = actualWriter(filename)
     
     renderer = Renderer()
-    renderer.doRendering(writer, RENDER_ANIMATION)
+    renderer.doRendering(writer, config.output['ANIMATION'])
 
     if editmode: Window.EditMode(1) 
 
 
     if editmode: Window.EditMode(1) 
 
@@ -1403,7 +1513,7 @@ if __name__ == "__main__":
     outputfile = ""
     basename = Blender.sys.basename(Blender.Get('filename'))
     if basename != "":
     outputfile = ""
     basename = Blender.sys.basename(Blender.Get('filename'))
     if basename != "":
-        outputfile = Blender.sys.splitext(basename)[0] + "." + str(OUTPUT_FORMAT).lower()
+        outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
 
     if Blender.mode == 'background':
         vectorize(outputfile)
 
     if Blender.mode == 'background':
         vectorize(outputfile)