# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
# $Id: ribexport.py,v 1.17 2006/04/27 16:56:46 mbaas Exp $

import sys, os, os.path, shutil, types
import protocols
import pluginmanager
import lightsource
#from cgkit import *
from cgtypes import *
from targetcamera import TargetCamera
from scene import getScene
from geomobject import *
from boxgeom import BoxGeom
from spheregeom import SphereGeom
from ccylindergeom import CCylinderGeom
from planegeom import PlaneGeom
from trimeshgeom import TriMeshGeom
from polyhedrongeom import PolyhedronGeom
from spotlight3ds import SpotLight3DS
from glpointlight import GLPointLight
from gltargetspotlight import GLTargetSpotLight
from glfreespotlight import GLFreeSpotLight
from gltargetdistantlight import GLTargetDistantLight
from glfreedistantlight import GLFreeDistantLight
from glmaterial import GLMaterial
from cgkit.Interfaces import *
from _OpenGL.GL import GL_REPLACE, GL_MODULATE, GL_DECAL, GL_BLEND
from _OpenGL.GL import *
import cmds
from ri import *
import sl
from riutil import *
from math import *

#class IObject(protocols.Interface): pass
class IGeometry(protocols.Interface): pass
class ILightSource(protocols.Interface): pass
class IMaterial(protocols.Interface): pass

# RIBImporter
class RIBExporter:
    """RIB export class.
    """

    _protocols = ["Export"]

    # extension
    def extension():
        return ["rib"]
    extension = staticmethod(extension)

    # description
    def description(self):
        """Return a short description for the file dialog."""
        return "RenderMan RIB file"
    description = staticmethod(description)

    # exportFile
    def exportFile(self,
                   filename,
                   camera = None,
                   output = None,
                   output_framebuffer = True,
                   bake = False,
                   bakemodel = None,
                   bakestvar = "st"):
        """Export a RIB file.

        \a camera is the camera object to use. If None is passed, the
        first camera found is used.
        \a output is the output file name or a list of output specifiers
                  (which contain the parameters for a RiDisplay() call). If
                  output is None, no RiDisplay() call is done.
        """

        scene = getScene()
        self.timestep = scene.timer().timestep
        self.bake = bake

        # A list with all light sources in the scene
        # (the items are the original lights, *not* the adapted lights)
        self.lights = []
        
        # A dictionary that stores lightsource shader names
        # Key is the light class, value is the shader name
        self.light_shader = {}

        # A dictionary that stores the passes created by light sources
        # Key is the original light instance, value is the passes list
        # Empty lists are not stored.
        self.light_passes = {}

        # A dictionary that stores material shader names
        # Key is the tuple (material class, surface shader name, displacement
        # shader name, interior name), value are the shader names (surface,
        # displacement, interior)
        # (the shader names in the key are not the true file names but
        # the names returned by the material)
        self.material_shaders = {}

        # A dictionary that stores the passes created by materials
        # Key is the original material instance, value is the passes list
        # Empty lists are not stored.
        self.material_passes = {}

        # A dictionary with map names used so far
        self.mapnames = {}

        # The shader names created so far (this dictionary is used to
        # create unique file names)
        self.shader_names = {}

        # Key: (geom, matid)  Value: File name
        self.geom_file = {}
        
        # The geom file names creates so far
        # (this dictionary is used to create unique file names)
        self.geom_names = {}

        # Directory names
        self.shader_path = "shaders"
        self.map_path = "maps"
        self.geom_path = "geoms"

        # The list with render passes
        passes = []

        # Search for a camera and collect the passes...
        cam = camera
        self.preprocess_called = {}
        for obj in scene.walkWorld():
            if cam==None and ICamera in obj.protocols():
                cam = obj

            # Check if the object is a light source
            try:
                explgt = protocols.adapt(obj, ILightSource)
                self.lights.append(obj)
                # Create the RenderPass objects required for this light
                lgtpasses = explgt.createPasses()
                lgtpasses = self.makePassOutputUnique(lgtpasses)
                if lgtpasses!=[]:
                    self.light_passes[obj] = lgtpasses
                    passes += lgtpasses
            except NotImplementedError:
                # TODO: Use protocols instead of isinstance()
                if isinstance(obj, lightsource.LightSource):
                    print 'Unknown light source: "%s"'%obj.name

            if obj.geom==None:
                continue

            # Collect the material passes
            if obj.visible:
                for i in range(obj.getNumMaterials()):
                    mat = obj.getMaterial(i)
                    if mat not in self.material_passes:
                        try:
                            expmat = protocols.adapt(mat, IMaterial)
                            # Create the RenderPass objects required for the material
                            ps = expmat.createPasses()
                            ps = self.makePassOutputUnique(ps)
                            if ps!=[]:
                                self.material_passes[mat] = ps
                                passes += ps
                            if mat not in self.preprocess_called:
                                expmat.preProcess(self)
                                self.preprocess_called[mat] = 1
                        except NotImplementedError:
                            pass

        if cam==None:
            cam = TargetCamera()

        # Store the camera so that Pass objects can access it
        self.cam = cam

        # Add the map path to the map names
        # (on win32, a backslash is replaced by two backslashes because
        # a renderer might interpret a single backslash as an escape character
        # (such as 3Delight))
        for ps in passes:
            tab = ps.getFilenameTable()
            for vname in tab:
                rname = tab[vname]
                rname = os.path.join(self.map_path, rname)
                if sys.platform=="win32":
                    rname = rname.replace('\\', "\\\\")
                tab[vname] = rname
            ps.setFilenameTable(tab)

        if len(passes)>0:
            self.checkMapPath()

        # Add the final image pass as last pass
#        if output==None:
#            output = scene.getGlobal("output", "out.tif")
        if bake:
            defaultdisplaymode = RI_RGBA
        else:
            defaultdisplaymode = RI_RGB
        mode = scene.getGlobal("displaymode", defaultdisplaymode)
        out = self.outputSpec(output, mode=mode, output_framebuffer=output_framebuffer)
        if bake:
            bakemodel = cmds.worldObject(bakemodel)
            bakepass = BakePass(output=out, bakemodel=bakemodel, bakestvar=bakestvar)
            passes.append(bakepass)
        else:
            imgpass = ImagePass(output=out, cam=cam)
            passes.append(imgpass)

        # Initialize the exporter attribute in the passes...
        for p in passes:
            p.exporter = self

        # Start export...
        RiBegin(filename)

        RiOption("searchpath", "shader", "%s:&"%self.shader_path)
        RiOption("searchpath", "archive", "%s:&"%self.geom_path)
        RiOption("searchpath", "texture", "%s:&"%self.map_path)
        RiHider(RI_HIDDEN, "string depthfilter", "midpoint")
        globalrib = scene.getGlobal("rib", None)
        if globalrib!=None:
            RiArchiveRecord(RI_VERBATIM, globalrib+"\n")

        # Do render passes...
        print len(passes),"passes..."
#        nr = 0
        # Tex passes first
        for rpass in passes:
            if isinstance(rpass, TexPass):
#                nr+=1
#                print "\015Pass %d..."%nr,
                rpass.doPass(0)
                rpass._done = True

        # Other passes...
        for rpass in passes:
            if isinstance(rpass, TexPass):
                continue
#            nr+=1
#            print "\015Pass %d..."%nr,
            rpass.doPass(0)
            rpass._done = True

        RiEnd()

    ## protected:

    # outputSpec
    def outputSpec(self, output, mode=RI_RGB, output_framebuffer=False):
        """Return the output specification for the final image pass.

        output is the parameter passed to exportFile().
        mode and output_framebuffer is only used when output is a string.
        The return value is a list of output specs which can be
        passed as output parameter to the ImagePass.
        """
        # No output?
        if output==None:
            return []
        # Is output a string? (i.e. the output file name)
        elif isinstance(output, types.StringTypes):
            out = [(output, RI_FILE, mode, {})]
            if output_framebuffer:
                out += [(output, RI_FRAMEBUFFER, mode, {})]
            return out
        # User specified output specs? (output must already be a list
        # of specs)
        else:
            return output
        

    # isExportable
    def isExportable(self, wobj):
        """Check if a WorldObject can be exported or not.

        ??? Geometry?
        """

        geom = wobj.geom
        if geom==None:
            return False
        
        try:
            expgeom = protocols.adapt(geom, IGeometry)
            return True
        except NotImplementedError:
            print '"%s" has unknown geometry.'%wobj.name
            return False
        
    # applyViewTransform
    def applyViewTransform(self, V):
        """Apply the view transformation V.

        This method corresponds to a RiConcatTransform() call.
        It outputs the view transformation V as a RenderMan transformation.
        The handedness of the scene is taken into account.

        \param V (\c mat4) View transformation (world -> camera)
        """
        scene = getScene()
        if scene.handedness=='r':
            RiScale(-1,1,1)                
        RiConcatTransform(V.toList())

    # applyLightSource
    def applyLightSource(self, lgt):
        """Apply the light source lgt.

        This method corresponds to a RiLightSource() call.

        \param lgt (\c WorldObject) Light source
        \return Light id or None.
        """

#        try:
        explgt = protocols.adapt(lgt, ILightSource)
#        except NotImplementedError:
#            return

        # Save the light shader if it wasn't already saved before...
        cls = "%s_%s"%(lgt.__class__, explgt.shaderName())
        if cls not in self.light_shader:
            shadername = self.writeShader(explgt.shaderName(),
                                          explgt.shaderSource())
            self.light_shader[cls] = shadername

        # Get the shader name
        shadername = self.light_shader[cls]
        if shadername==None:
            return None

        # Get the parameters for the shader
        passes = self.light_passes.get(lgt, [])
        params = explgt.shaderParams(passes)

        lid = RiLightSource(shadername, params)
        return lid

    # applyTransformation
    def applyTransformation(self, T, linearvel=None, angularvel=None):
        """Apply the transformation T.

        This method corresponds to a RiConcatTransform() call.
        It outputs T as a RenderMan transformation.

        \param T (\c mat4) Transformation
        """
        RiConcatTransform(T.toList())
        if linearvel!=None and angularvel!=None:
            try:
                axis = angularvel.normalize()
            except:
                axis = vec3(1,0,0)
            dw = 0.5*self.timestep*degrees(angularvel.length())
            dr = 0.5*self.timestep*linearvel
            if abs(dw)>1E-6:
                RiMotionBegin(0, 1)
                RiRotate(-dw, axis)
                RiRotate( dw, axis)
                RiMotionEnd()
            if dr!=vec3(0):
                RiMotionBegin(0,1)
                RiTranslate(-dr)
                RiTranslate( dr)
                RiMotionEnd()

    # applyMaterial
    def applyMaterial(self, mat):
        """Apply a material.

        This method corresponds to the calls RiColor(), RiOpacity(),
        RiSurface(), RiDisplacement() and RiInterior().

        Evtl. Flags als Parameter, die bestimmten welche der Shader
        wirklich ausgegeben werden sollen (Surface, Displacement, Volume).
        Z.B. koennte Shadow-Pass auf Surface verzichten, evtl. aber nicht
        auf Displacement.

        \param mat (\c Material) Material
        """
        if mat==None:
            return

#        try:
        expmat = protocols.adapt(mat, IMaterial)
#        except NotImplementedError:
#            return

        # Save the shaders if they weren't already saved before...
        cls = (mat.__class__,
               expmat.surfaceShaderName(),
               expmat.displacementShaderName(),
               expmat.interiorShaderName())
        if cls not in self.material_shaders:
            # Prepare the material shaders (save shader source)
            srfshader = self.writeShader(expmat.surfaceShaderName(),
                                         expmat.surfaceShaderSource())
            dispshader = self.writeShader(expmat.displacementShaderName(),
                                          expmat.displacementShaderSource())
            intrshader = self.writeShader(expmat.interiorShaderName(),
                                          expmat.interiorShaderSource())
            self.material_shaders[cls] = (srfshader, dispshader, intrshader)

        # Get the shader names
        srfshader, dispshader, intrshader = self.material_shaders[cls]

        # Get the parameters for the shader
        passes = self.material_passes.get(mat, [])
        srfparams = expmat.surfaceShaderParams(passes)
        dispparams = expmat.displacementShaderParams(passes)
        interiorparams = expmat.interiorShaderParams(passes)

        # Get the shader transforms
        srftransform = expmat.surfaceShaderTransform()
        disptransform = expmat.displacementShaderTransform()
        interiortransform = expmat.interiorShaderTransform()

        color = expmat.color()
        opacity = expmat.opacity()

        if color!=None:
            RiColor(color)
        if opacity!=None:
            RiOpacity(opacity)
        if srfshader!=None:
            if srftransform!=mat4(1):
                RiTransformBegin()
                RiConcatTransform(srftransform.toList())
            RiSurface(srfshader, srfparams)
            if srftransform!=mat4(1):
                RiTransformEnd()
        if dispshader!=None:
            if disptransform!=mat4(1):
                RiTransformBegin()
                RiConcatTransform(disptransform.toList())
            coordsys, bound = expmat.displacementBound()
            RiAttribute("displacementbound",
                        "string coordinatesystem", coordsys,
                        "float sphere", bound)
            RiDisplacement(dispshader, dispparams)
            if disptransform!=mat4(1):
                RiTransformEnd()
        if intrshader!=None:
            if interiortransform!=mat4(1):
                RiTransformBegin()
                RiConcatTransform(interiortransform.toList())
            RiInterior(intrshader, interiorparams)
            if interiortransform!=mat4(1):
                RiTransformEnd()
 

    # applyGeometry
    def applyGeometry(self, geom, matid=0):
        """Apply a geometry object.

        \param geom (\c GeomObject) Geometry or None
        """
        if geom==None:
            return

        # Was the geom already exported?
        if (geom, matid) in self.geom_file:
            filename = self.geom_file[geom, matid]
            RiReadArchive(filename)
            return

        # The geom was not exported, so do it now...

        try:
            expgeom = protocols.adapt(geom, IGeometry)
        except NotImplementedError:
            print 'Warning: Unknown geometry: "%s" (%s)'%(geom.name, geom.__class__.__name__)
            return

        # Create the geoms directory if it doesn't exist
        if not os.path.exists(self.geom_path):
            os.mkdir(self.geom_path)

        # Determine the output file name (without path)
        n = "%s_id%d_%s.rib"%(geom.__class__.__name__, matid, geom.name)
        filename = self.makeFilenameUnique(n, self.geom_names)
        self.geom_names[filename] = 1
        self.geom_file[geom, matid] = filename

        ctx = RiGetContext()
        RiBegin(os.path.join(self.geom_path, filename))
        RiOption(RI_RIBOUTPUT, RI_VERSION, 0)
        expgeom.render(matid)
        RiEnd()
        RiContext(ctx)
        RiReadArchive(filename)        


    # writeShader
    def writeShader(self, name, source):
        """Write a shader source file.

        \param name (\c str) Initial shader name or None
        \param source (\c str) Shader source code or None
        \return Shader name
        """
        # No source given? Then just use the specified shader name
        if source==None:
            return name

        if name==None:
            name = "shader"

        shadername = self.makeFilenameUnique(name, self.shader_names)
        self.shader_names[shadername]=1

        # Create the shaders directory if it doesn't exist
        if not os.path.exists(self.shader_path):
            os.mkdir(self.shader_path)

        filename = os.path.join(self.shader_path, shadername+".sl")
        f = file(filename, "wt")
        # Add the macros that are used for baking...
        if self.bake:
            f.write("#define BAKE_PASS\n")
            begin = 'varying point _P_bake = transform("world", "current", transform("camera", "object", Pref));\\\n  varying point _P_orig = P;\\\n  P = _P_bake;'
            end = 'P = _P_orig;'
            normal = 'normalize(n);'
        else:
            begin = ""
            end = ""
            normal = 'faceforward(normalize(n),I);'
        f.write("#define BAKE_BEGIN %s\n"%begin)
        f.write("#define BAKE_END %s\n"%end)
        f.write("#define BAKE_NORMAL(n) %s\n\n"%normal)
            
        f.write(source.replace("$SHADERNAME", shadername))
        f.close()
        return shadername

    # checkMapPath
    def checkMapPath(self):
        """Create the map directory if it doesn't already exist.
        """
        if not os.path.exists(self.map_path):
            os.mkdir(self.map_path)


    # makePassOutputUnique
    def makePassOutputUnique(self, passes):
        res = []
        for rpass in passes:
            tab = rpass.getFilenameTable()
            for vname in tab:
                rname = self.makeFilenameUnique(tab[vname], self.mapnames)
                tab[vname] = rname
                self.mapnames[rname] = 1
#            print tab
            rpass.setFilenameTable(tab)
            res.append(rpass)
            
        return res

    # makeFilenameUnique
    def makeFilenameUnique(self, name, names):
        """Modify a file name so that it is unique.

        If \a name is already among \a names it is modified by increasing
        a trailing number so that it is unique.

        \param name (\c str) File name
        \param names A list or dictionary with existing file names
        \return Unique file name
        """
        base, ext = os.path.splitext(name)
        # Read any trailing number
        # -> base: name without number (and extension) / num: Number
        i = 1
        while i<=len(base) and base[-i:].isdigit():
            i+=1
        i-=1
        if i==0:
            num = 1
        else:
            num = int(base[-i:])
            base = base[:-i]
        
        while name in names:
            name = "%s%d%s"%(base, num, ext)
            num += 1
            
        return name
        
        

######################################################################

# RenderPass
class RenderPass:
    """RenderPass.

    A pass object can access the exporter (RIBExport) via self.exporter.
    """
    def __init__(self, output, owner=None):
        """Constructor.
        
        output is a list of output specifications that contain the
        output file name, the output type and the output mode (RI_RGB,
        RI_RGBA,...) and optional extra parameters. It's all the
        information that's necessary for a RiDisplay() call.
			
        \param owner (\c Component) The object that requires the result of this pass
        """
        self.owner = owner
        self.output = output
        # A reference to the exporter class (this is initialized in the
        # exporter after the Passes were created)
        self.exporter = None

        # done flag (this is set to True in the exporter)
        self._done = False

        # Filename table. Key: Logical file name / Value: Real filename
        self.filenametab = {}
        for name,type,mode,params in output:
            self.filenametab[name] = name

    # done
    def done(self):
        """Check if this pass is already done or not.

        This method is used when shader parameters have to be determined.
        The output of a pass may only be used once the pass is done and
        the output really exists (for example, you can't use a shadow map
        until it was created).

        \return True if the pass was already rendered (i.e. the output images exist).
        """
        return self._done

    # realFilename
    def realFilename(self, filename):
        """Translate a logical file name into a real file name.

        \param filename (\c str) Logical file name
        """
        if filename not in self.filenametab:
            raise ValueError('File name "%s" is not an output file name.'%filename)

        return self.filenametab[filename]

    # getFilenameTable
    def getFilenameTable(self):
        """Return the filename translation table.

        \return The filename translation dictionary.
        """
        return self.filenametab

    # setFilenameTable
    def setFilenameTable(self, tab):
        """Set an updated filename table.

        \param tab (\c dict) The new filename translation dictionary.
        """
        self.filenametab = tab

    # doPass
    def doPass(self, framenr):
        """Write a Frame block.

        \param framenr (\c int) Frame number to use
        """
        pass

    # initDisplays
    def initDisplays(self):
        """Call RiDisplay() for all outputs stored in the class."""
        # Do RiDisplay() calls...
        append_plus = False
        for name,type,mode,params in self.output:
            name = self.realFilename(name)
            if append_plus:
                name = "+"+name
            RiDisplay(name, type, mode, params)
            append_plus = True

        

# ImagePass
class ImagePass(RenderPass):
    def __init__(self, output, cam):
        RenderPass.__init__(self, output, None)
        self.cam = cam

    # doPass
    def doPass(self, framenr):
        scene = getScene()

        RiArchiveRecord(RI_COMMENT, "")
        RiArchiveRecord(RI_COMMENT, "Image pass")
        RiArchiveRecord(RI_COMMENT, "")
        RiFrameBegin(framenr)

        i,j = scene.getGlobal("pixelsamples", (2,2))
        RiPixelSamples(i,j)
        res = scene.getGlobal("resolution", (640,480))
        try:
            if len(res)==2:
                w,h = res
                aspect = 1
            elif len(res)==3:
                w,h,aspect = res
            else:
                raise Exception
        except:
            print >>sys.stderr, "Error: Invalid resolution setting:",res
            w,h,aspect = 640,480,1
        RiFormat(w,h,aspect)
        RiShadingRate(scene.getGlobal("shadingrate", 1.0))
        
        filterdata = scene.getGlobal("pixelfilter", None)
        if filterdata!=None:
            filter, (xwidth, ywidth) = filterdata
            RiPixelFilter(filter, xwidth, ywidth)

        # Do RiDisplay() calls...
        self.initDisplays()

        # Camera...
        cam = self.cam
        fstop = getattr(cam, "fstop", 0)
        focallength = getattr(cam, "focallength", 0)
        RiProjection(RI_PERSPECTIVE, fov=cam.fov)
        if fstop!=0 and focallength!=0:
            # XXX: The following line only works with a TargetCamera!
            focaldistance = (cam.target-cam.pos).length()
            RiDepthOfField(fstop, focallength, focaldistance)
        self.exporter.applyViewTransform(cam.viewTransformation())
        RiShutter(0,1)

        RiWorldBegin()

        # Set light sources...
        for lgt in self.exporter.lights:
            RiAttributeBegin()
            RiAttribute("identifier", "name", lgt.name)
            RiConcatTransform(lgt.worldtransform.toList())
            lid = self.exporter.applyLightSource(lgt)
            RiAttributeEnd()
            if lid!=None:
                RiIlluminate(lid, lgt.enabled)
            
#            shader,params,transform,name = exporter.light_shaders[lgt]
#            lid = RiLightSource(shader, params)

        self.renderChilds(scene.worldRoot())
        
        RiWorldEnd()     
        RiFrameEnd()

    def renderChilds(self, obj):
        exporter = self.exporter
        for child in obj.iterChilds():
            if not child.visible:
                continue
            # No geometry and no children? Then ignore this node
            if child.geom==None and child.lenChilds()==0:
                continue
#            if not exporter.isExportable(child):
#                continue

            RiAttributeBegin()
            
            # Store the name of the object
            RiAttribute("identifier", "name", child.name)
            # Transform the object
            exporter.applyTransformation(child.localTransform(), child.linearvel, child.angularvel)

            for i in range(child.getNumMaterials()):
                # Set shaders...
                exporter.applyMaterial(child.getMaterial(i))

                # Custom RIB
                rib = getattr(child, "rib", None)
                if rib!=None:
                    RiArchiveRecord(RI_VERBATIM, rib+"\n")

                # Output geometry
                exporter.applyGeometry(child.geom, i)
            
            # Recursively render all childs
            self.renderChilds(child)
            
            RiAttributeEnd()


# ShadowPass
class ShadowPass(RenderPass):
    def __init__(self, output, light, fov, resolution, orientoffset=mat4(1)):
        RenderPass.__init__(self, output, None)
        self.light = light
        self.fov = fov
        self.resolution = resolution
        self.orientoffset = orientoffset

    # doPass
    def doPass(self, framenr):
        scene = getScene()

        RiArchiveRecord(RI_COMMENT, "")
        RiArchiveRecord(RI_COMMENT, "Shadow pass")
        RiArchiveRecord(RI_COMMENT, "")
        RiFrameBegin(framenr)

        RiPixelSamples(1,1)
        RiPixelFilter(RiBoxFilter, 1, 1)
        RiFormat(self.resolution, self.resolution, 1)
        RiShadingRate(2)

        # Do RiDisplay() calls...
        self.initDisplays()

        # Camera...
        RiProjection(RI_PERSPECTIVE, fov=self.fov)
        V = (self.light.transform*self.orientoffset).inverse()
        self.exporter.applyViewTransform(V)
        RiShutter(0,1)

        RiWorldBegin()

        self.renderChilds(scene.worldRoot())
        
        RiWorldEnd()     
        RiFrameEnd()
        zname = self.realFilename(self.output[0][0])
        mapname = os.path.splitext(zname)[0]+".map"
        RiMakeShadow(zname, mapname)

    def renderChilds(self, obj):
        exporter = self.exporter
        for child in obj.iterChilds():
            if not child.visible:
                continue
            # No geometry and no children? Then ignore this node
            if child.geom==None and child.lenChilds()==0:
                continue
#            if not exporter.isExportable(child):
#                continue

            RiAttributeBegin()

            # Store the name of the object
            RiAttribute("identifier", "name", child.name)
            # Transform the object
            exporter.applyTransformation(child.localTransform())
            for i in range(child.getNumMaterials()):
                # Set shaders...
                exporter.applyMaterial(child.getMaterial(i))

                # Custom RIB
                rib = getattr(child, "rib", None)
                if rib!=None:
                    RiArchiveRecord(RI_VERBATIM, rib+"\n")

                # Output geometry
                exporter.applyGeometry(child.geom, i)
            
            # Recursively render all childs
            self.renderChilds(child)

            RiAttributeEnd()

# FlatReflectionPass
class FlatReflectionPass(RenderPass):
    def __init__(self, output, mirrorobj=None):
        RenderPass.__init__(self, output, None)
        self.mirrorobj = mirrorobj

    # doPass
    def doPass(self, framenr):
        scene = getScene()

        RiArchiveRecord(RI_COMMENT, "(Flat) Reflection pass")
        RiFrameBegin(framenr)

        RiPixelSamples(2,2)
        RiFormat(640,480,1)
        RiShadingRate(1)

        # Do RiDisplay() calls...
        self.initDisplays()

        # Camera...
        cam = self.exporter.cam
        RiProjection(RI_PERSPECTIVE, fov=cam.fov)
        self.exporter.applyViewTransform(cam.viewTransformation())
        RiScale(1,1,-1)
        RiShutter(0,1)
#        RiTranslate(0,0,1)

        RiWorldBegin()

        # Set light sources...
        for lgt in self.exporter.lights:
            RiAttributeBegin()
            RiAttribute("identifier", "name", lgt.name)
            RiConcatTransform(lgt.worldtransform.toList())
            lid = self.exporter.applyLightSource(lgt)
            RiAttributeEnd()
            if lid!=None:
                RiIlluminate(lid, RI_TRUE)
            
#            shader,params,transform,name = exporter.light_shaders[lgt]
#            lid = RiLightSource(shader, params)

        self.renderChilds(scene.worldRoot())
        
        RiWorldEnd()     
        RiFrameEnd()
        tifname = self.realFilename(self.output[0][0])
        texname = os.path.splitext(tifname)[0]+".tex"
        RiMakeTexture(tifname, texname, RI_CLAMP, RI_CLAMP, RiGaussianFilter, 1, 1)


    def renderChilds(self, obj):
        exporter = self.exporter
        for child in obj.iterChilds():
            if child==self.mirrorobj:
                continue
            if not child.visible:
                continue
#            if not exporter.isExportable(child):
#                continue

            RiAttributeBegin()
            
            # Store the name of the object
            RiAttribute("identifier", "name", child.name)
            # Transform the object
            exporter.applyTransformation(child.localTransform(), child.linearvel, child.angularvel)
            for i in range(child.getNumMaterials()):
                # Set shaders...
                exporter.applyMaterial(child.getMaterial(i))

                # Custom RIB
                rib = getattr(child, "rib", None)
                if rib!=None:
                    RiArchiveRecord(RI_VERBATIM, rib+"\n")

                # Output geometry
                exporter.applyGeometry(child.geom, i)
            
            # Recursively render all childs
            self.renderChilds(child)
            
            RiAttributeEnd()

# BakePass
class BakePass(RenderPass):
    """Create a bake pass.

    The class uses the technique described in the "Stupid RAT Tricks 2001":
    "The RenderMan EasyBake Oven" by Jonathan Litt and Dan Goldman.
    http://www.cs.washington.edu/homes/dgoldman/ezbake/EZ_Bake_Oven.htm

    Additionally, the original geometry is also stored so that raytracing
    can also be used.
    """
    
    def __init__(self, output, bakemodel, bakestvar="st"):
        """
        bakemodel is the WorldObject whose texture should be baked.
        bakestvar is the name of the variable that holds the texture
        coordinates for baking.
        """
        RenderPass.__init__(self, output, None)
        self.bakemodel = bakemodel
        self.bakestvar = bakestvar

    # doPass
    def doPass(self, framenr):
        scene = getScene()

        RiArchiveRecord(RI_COMMENT, "")
        RiArchiveRecord(RI_COMMENT, "Bake pass")
        RiArchiveRecord(RI_COMMENT, "")
        RiFrameBegin(framenr)

        i,j = scene.getGlobal("pixelsamples", (2,2))
        RiPixelSamples(i,j)
        res = scene.getGlobal("resolution", (256,256))
        try:
            if len(res)==2:
                w,h = res
                aspect = 1
            elif len(res)==3:
                w,h,aspect = res
            else:
                raise Exception
        except:
            print >>sys.stderr, "Error: Invalid resolution setting:",res
            w,h,aspect = 256,256,1
        RiFormat(w,h,aspect)
        RiShadingRate(scene.getGlobal("shadingrate", 1.0))

        # Do RiDisplay() calls...
        self.initDisplays()

        # Camera...
        RiProjection(RI_ORTHOGRAPHIC)
        bb = scene.boundingBox()
        bmin, bmax = bb.getBounds()
        RiScale(2,2,2)
        RiTranslate(-0.5, -0.5, -bmax.z-1)

        RiWorldBegin()

        # Set light sources...
        for lgt in self.exporter.lights:
            RiAttributeBegin()
            RiAttribute("identifier", "name", lgt.name)
            RiConcatTransform(lgt.worldtransform.toList())
            lid = self.exporter.applyLightSource(lgt)
            RiAttributeEnd()
            if lid!=None:
                RiIlluminate(lid, lgt.enabled)

        # Flattened geometry
        obj = self.bakemodel
        RiAttributeBegin()
        self.exporter.applyMaterial(obj.getMaterial(0))
#        RiCoordSysTransform("camera")
#        RiScale(2,2,2)
#        RiTranslate(-0.5,-0.5,1)
        RiTranslate(0,0, bmax.z+1.5)
        self.bakeModel(obj, self.bakestvar)
        RiAttributeEnd()

        # 3Delight attribute...
        RiAttribute("visibility", "string transmission", "opaque")
        # Pixie attribute...
        RiAttribute("visibility", "int trace", 1)
        
        self.renderChilds(scene.worldRoot())

        RiWorldEnd()     
        RiFrameEnd()

    def bakeModel(self, obj, stvarname="st"):
        """
        obj is the model that should be baked. stvarname is the name
        of the variable that holds the texture coordinates for baking.
        """

        # Check texture coordinates...
        info = obj.geom.findVariable(stvarname)
        if info==None:
            raise RuntimeError, "No variable '%s' found"%stvarname
            
        name, storage, type, mult = info

        if type!=FLOAT or mult!=2:
            raise TypeError, "variable '%s' is of wrong type"%stvarname

        if storage==FACEVARYING:
            geom = cmds.convFaceVarToVar(obj.geom)
        elif storage==VARYING:
            geom = obj.geom
        else:
            raise TypeError, "'%s' storage class %s not supported for baking"%(stvarname,storage)

        # Convert the model into a trimesh...
        if not isinstance(geom, TriMeshGeom):
            tm = TriMeshGeom()
            geom.convert(tm)
            geom = tm
        
        # Convert the tex coords into vec3s...
        st = geom.slot(stvarname)
#        print "sizes:",geom.verts.size(), st.size()
        stcoords = map(lambda x: vec3(x), st)

        # Transform the verts...
#        verts = []
#        W = obj.worldtransform
#        for i,j,k in geom.faces:
#            verts.append(W*geom.verts[i])
#            verts.append(W*geom.verts[j])
#            verts.append(W*geom.verts[k])

#        verts = 156*[(0,0,0)]

        # Create parameter list...
        W = obj.worldtransform
        params = {"P":stcoords,
                  "Pref":map(lambda x: W*x, geom.verts)}
        RiDeclare("Pref", "vertex point")
        clss = ["constant", "uniform", "varying", "vertex", "facevarying", "facevertex", "user"]
        typs = ["integer", "float", "string", "color", "point", "vector",
                "normal", "matrix", "hpoint"]
        for name, storage, type, multiplicity in geom.iterVariables():
            cls = clss[abs(storage)]
            typ = typs[abs(type)]
            if cls!="user":
                s = geom.slot(name)
                params[name] = list(s)
                if multiplicity==1:
                    decl = "%s %s"%(cls, typ)
                else:
                    decl = "%s %s[%d]"%(cls, typ, multiplicity)
                RiDeclare(name, decl)

#        RiCoordSysTransform("camera")
        RiPointsPolygons(len(geom.faces)*[3], list(geom.faces), params)

    def renderChilds(self, obj):
        exporter = self.exporter
        for child in obj.iterChilds():
            if not child.visible:
                continue
            # No geometry and no children? Then ignore this node
            if child.geom==None and child.lenChilds()==0:
                continue
#            if not exporter.isExportable(child):
#                continue

            RiAttributeBegin()
            
            # Store the name of the object
            RiAttribute("identifier", "name", child.name)
            # Transform the object
            exporter.applyTransformation(child.localTransform(), child.linearvel, child.angularvel)

            for i in range(child.getNumMaterials()):
                # Set shaders...
                exporter.applyMaterial(child.getMaterial(i))

                # Custom RIB
                rib = getattr(child, "rib", None)
                if rib!=None:
                    RiArchiveRecord(RI_VERBATIM, rib+"\n")

                # Output geometry
                exporter.applyGeometry(child.geom, i)
            
            # Recursively render all childs
            self.renderChilds(child)
            
            RiAttributeEnd()


# TexPass
class TexPass(RenderPass):
    def __init__(self, maps, output=[]):
        """Constructor.

        The map definition list contains one tuple for every map.
        The tuple is a 7-tuple with the following entries:

        - Map name (incl. path)
        - swrap ("periodic", "clamp", "black")
        - twrap ("periodic", "clamp", "black")
        - filterfunc ("gaussian", "box", "triangle", "sinc", "catmullrom", ...)
        - swidth
        - twidth
        - params - A dictionary with additional parameters

        These are the parameters for the \c RiMakeTexture() call.
        The output file name is generated from the input map name by
        replacing the suffix with "*.tex".
        
        \param maps A list with map defintions
        """
        RenderPass.__init__(self, output)
        self.maps = maps

    def doPass(self, framenr):
        
        for map,swrap,twrap,filterfunc,swidth,twidth,params in self.maps:

            # Copy the original image into the map directory and convert
            # to tif if necessary
            self.copyImageMap(map)

            # Call RiMakeTexture() to convert the .tif into .tex
            mapbase = os.path.basename(map)
            name, ext = os.path.splitext(mapbase)
            tifname = os.path.join(self.exporter.map_path, name)+".tif"
            texname = os.path.join(self.exporter.map_path, name)+".tex"
            if sys.platform=="win32":
                tifname = tifname.replace("\\", "\\\\")
                texname = texname.replace("\\", "\\\\")
            RiMakeTexture(tifname, texname,
                          swrap, twrap,
                          filterfunc, swidth, twidth, **params)

    ## protected:

    def copyImageMap(self, texmap):
        """Copy the texture map image into the map folder.

        \param texmap (\c str) Texture map name (incl. path)
        """

        self.exporter.checkMapPath()
        texname = os.path.basename(texmap)
        name, ext = os.path.splitext(texname)
        if ext.lower()!=".tif":
            print 'Converting "%s"'%texmap
            tifname = os.path.join(self.exporter.map_path, name+".tif")
            # Read original map
            try:
                img = Image.open(texmap)
            except IOError, e:
                print e
                return
            # Save map as TIF file
            img.save(tifname)
        else:
            print 'Copying "%s"'%texmap
            dest = os.path.join(self.exporter.map_path, os.path.basename(texmap))
            shutil.copyfile(texmap, dest)


######################################################################
####################### Geometry adapters ############################
######################################################################

class BoxAdapter:
    protocols.advise(instancesProvide=[IGeometry], asAdapterForTypes=[BoxGeom])
    
    def __init__(self, boxgeom, proto):
        self.geom = boxgeom

    def render(self, matid):
        if matid!=0:
            return
        
        lx2 = self.geom.lx/2
        ly2 = self.geom.ly/2
        lz2 = self.geom.lz/2
        A = vec3(-lx2,-ly2,-lz2)
        B = vec3( lx2,-ly2,-lz2)
        C = vec3( lx2, ly2,-lz2)
        D = vec3(-lx2, ly2,-lz2)
        E = vec3(-lx2,-ly2, lz2)
        F = vec3( lx2,-ly2, lz2)
        G = vec3( lx2, ly2, lz2)
        H = vec3(-lx2, ly2, lz2)
        RiPatch(RI_BILINEAR, P=[D,C,A,B])
        RiPatch(RI_BILINEAR, P=[A,B,E,F])
        RiPatch(RI_BILINEAR, P=[D,A,H,E])
        RiPatch(RI_BILINEAR, P=[C,D,G,H])
        RiPatch(RI_BILINEAR, P=[B,C,F,G])
        RiPatch(RI_BILINEAR, P=[E,F,H,G])


class SphereAdapter:
    protocols.advise(instancesProvide=[IGeometry], asAdapterForTypes=[SphereGeom])
    
    def __init__(self, spheregeom, proto):
        self.geom = spheregeom

    def render(self, matid):
        if matid==0:
            r = self.geom.radius
            RiSphere(r, -r, r, 360)
        

class CCylinderAdapter:
    protocols.advise(instancesProvide=[IGeometry], asAdapterForTypes=[CCylinderGeom])
    
    def __init__(self, ccylgeom, proto):
        self.geom = ccylgeom

    def render(self, matid):
        if matid!=0:
            return
        
        r = self.geom.radius
        l = self.geom.length
        l2 = l/2
        silhouette_len = pi*r+l
        cap_len = pi/2*r;
        v1 = cap_len/silhouette_len
        v2 = v1+l/silhouette_len
        
        RiCylinder(r, -l2, l2, 360, st=[0,v1, 1,v1, 0,v2, 1,v2])
        RiTransformBegin()
        RiTranslate(0,0,l2)
        RiSphere(r, 0, r, 360, st=[0,v2, 1,v2, 0,1, 1,1])
        RiTransformEnd()
        RiTransformBegin()
        RiTranslate(0,0,-l2)
        RiSphere(r, -r, 0, 360, st=[0,0, 1,0, 0,v1, 1,v1])
        RiTransformEnd()

class PlaneAdapter:
    protocols.advise(instancesProvide=[IGeometry], asAdapterForTypes=[PlaneGeom])
    
    def __init__(self, planegeom, proto):
        self.geom = planegeom

    def render(self, matid):
        if matid!=0:
            return
        
        lx2 = self.geom.lx/2
        ly2 = self.geom.ly/2
        A = vec3(-lx2,-ly2,0)
        B = vec3( lx2,-ly2,0)
        C = vec3( lx2, ly2,0)
        D = vec3(-lx2, ly2,0)
        RiPatch(RI_BILINEAR, P=[A,B,D,C])

class TriMeshAdapter:
    protocols.advise(instancesProvide=[IGeometry], asAdapterForTypes=[TriMeshGeom])
    
    def __init__(self, meshgeom, proto):
        self.geom = meshgeom

    def render(self, matid):
        if self.geom.findVariable("matid")!=None:
            tm = cmds.extractUniform(self.geom, "matid", matid)
            tm = cmds.removeIsolatedVertices(tm)
        else:
            if matid!=0:
                return
            tm = self.geom
                    
        if len(tm.faces)==0:
            return

        if tm.findVariable("N")!=None and tm.findVariable("Nfaces")!=None:
            tm = cmds.convMeshAttr(tm, "N", "Nfaces")

        # Create parameter list...
        params = {"P":list(tm.verts)}
        clss = ["constant", "uniform", "varying", "vertex", "facevarying", "facevertex", "user"]
        typs = ["integer", "float", "string", "color", "point", "vector",
               "normal", "matrix", "hpoint"]
        for name, storage, type, multiplicity in tm.iterVariables():
            cls = clss[abs(storage)]
            typ = typs[abs(type)]
            if cls!="user":
                s = tm.slot(name)
                params[name] = list(s)
                if multiplicity==1:
                    decl = "%s %s"%(cls, typ)
                else:
                    decl = "%s %s[%d]"%(cls, typ, multiplicity)
                RiDeclare(name, decl)
                
        if hasattr(self.geom, "subdiv"):
            RiSubdivisionMesh("catmull-clark", len(tm.faces)*[3],
                              list(tm.faces), [],0,[],[], params)
        else:
            RiPointsPolygons(len(tm.faces)*[3], list(tm.faces), params)

class PolyhedronAdapter:
    protocols.advise(instancesProvide=[IGeometry], asAdapterForTypes=[PolyhedronGeom])
    
    def __init__(self, polyhedrongeom, proto):
        self.geom = polyhedrongeom

    def render(self, matid):
        if matid!=0:
            return

        pg = self.geom

        # Create parameter list...
        params = {"P":list(pg.verts)}
        clss = ["constant", "uniform", "varying", "vertex", "facevarying", "facevertex", "user"]
        typs = ["integer", "float", "string", "color", "point", "vector",
               "normal", "matrix", "hpoint"]
        for name, storage, type, multiplicity in pg.iterVariables():
            cls = clss[abs(storage)]
            typ = typs[abs(type)]
            if cls!="user":
                s = pg.slot(name)
                params[name] = list(s)
                if multiplicity==1:
                    decl = "%s %s"%(cls, typ)
                else:
                    decl = "%s %s[%d]"%(cls, typ, multiplicity)
                RiDeclare(name, decl)

        nloops = []
        nverts = []
        vertids = []
        for i in range(pg.getNumPolys()):
            poly = pg.getPoly(i)
            nloops.append(len(poly))
            for loop in poly:
                nverts.append(len(loop))
                vertids.append(loop)

        if hasattr(self.geom, "subdiv"):
            if nloops==len(nloops)*[1]:
                RiSubdivisionMesh("catmull-clark", nverts, vertids,
                                  [], 0, [], [], params)
            else:
                print >>sys.stderr, "Warning: Polyhedron with holes in its polys can't be used as subdiv"
        else:
            RiPointsGeneralPolygons(nloops, nverts, vertids, params)


######################################################################
######################### Light adapters #############################
######################################################################

# SpotLight3DSAdapter
class SpotLight3DSAdapter:
    protocols.advise(instancesProvide=[ILightSource], asAdapterForTypes=[SpotLight3DS])
    
    def __init__(self, lgt, proto):
        self.obj = lgt

    def createPasses(self):
        """Returns a list of RenderPass objects."""
        if self.obj.shadowed:
            shadpass = ShadowPass(output=[("shadow.z", "zfile", RI_Z, {})],
                                  light=self.obj, fov=self.obj.falloff,
                                  resolution = self.obj.shadow_size)
            return [shadpass]

        return []

    def shaderName(self):
        """Returns the name of the corresponding light shader or None.
        """
        return "spotlight3ds"

    def shaderSource(self):
        """Returns surface shader source code as a string or None.
        """
        return """// SpotLight3DS shader
        
light $SHADERNAME(float intensity = 1.0;
         uniform color lightcolor = color "rgb" (1, 1, 1);
         float falloff = 45.0;
         float hotspot = 43.0;
         float attenuation = 0;
         float inner_range = 0;
         float outer_range = 999;
         float overshoot = 0;
         float shadow_filter = 4.0;
         float shadow_bias = 0.01;
         float shadow_samples = 16;
         string shadowname = "")
{
  uniform float cosfalloff = cos(radians(0.5*falloff));
  uniform float coshotspot = cos(radians(0.5*hotspot));
  uniform vector axis = normalize(vector "shader" (0,0,1));
  float illuminate_angle;

  if (overshoot==0)
  {
    illuminate_angle = falloff*PI/360.0;
  }
  else
  {
    illuminate_angle = PI;
  }
  
  illuminate(point "shader" (0,0,0), axis, illuminate_angle)
  {
    vector L0 = normalize(L);
    float dist = length(L);
    float att = 1.0;

    // Attenuation due to hotspot/falloff
    if (overshoot==0)
    {
      float ca = L0.axis;
      att = smoothstep(cosfalloff, coshotspot, ca);
    }

    // Attenuation due to distance
    if (attenuation!=0)
      att *= 1.0-smoothstep(inner_range, outer_range, dist);
    
    Cl = att * intensity * lightcolor;

    if (shadowname!="")
      Cl *= 1 - shadow(shadowname, Ps, "width", shadow_filter, "bias", shadow_bias, "samples", shadow_samples);
    
  }
}        
        """

    def shaderParams(self, passes):
        """Return a dictionary with shader parameters and their values."""
        params = {"intensity":self.obj.intensity,
                "uniform color lightcolor":self.obj.color,
                "uniform float falloff":self.obj.falloff,
                "uniform float attenuation":self.obj.attenuation,
                "uniform float inner_range":self.obj.inner_range,
                "uniform float outer_range":self.obj.outer_range,
                "uniform float hotspot":self.obj.hotspot,
                "uniform float overshoot":int(self.obj.overshoot)
                  }
        if self.obj.shadowed and passes[0].done():
            zfile = passes[0].realFilename(passes[0].output[0][0])
            mapname = os.path.splitext(zfile)[0]+".map"
            params["uniform string shadowname"] = mapname
            params["uniform float shadow_filter"] = self.obj.shadow_filter
            params["uniform float shadow_bias"] = self.obj.shadow_bias
        return params



# GLPointLightAdapter
class GLPointLightAdapter:
    protocols.advise(instancesProvide=[ILightSource], asAdapterForTypes=[GLPointLight])
    
    def __init__(self, lgt, proto):
        self.obj = lgt

    def createPasses(self):
        """Returns a list of RenderPass objects."""
        return []

    def shaderName(self):
        """Returns the name of the corresponding light shader or None.
        """
        return "glpointlight"

    def shaderSource(self):
        """Returns surface shader source code as a string or None.
        """
        return """// GLPointLight shader
light $SHADERNAME(float intensity = 1.0;
         color diffuse = color "rgb" (1, 1, 1);
         color specular = color "rgb" (1, 1, 1);
         float constant_attenuation = 1;
         float linear_attenuation = 0;
         float quadratic_attenuation = 0)         
{
  illuminate(point "shader" (0,0,0))
  {
    float d = length(L);
    float att = 1.0/(constant_attenuation + linear_attenuation*d + quadratic_attenuation*d*d);
    
    Cl = att*intensity*color "rgb" (1,1,1);
  }
}        
        """

    def shaderParams(self, passes):
        """Return a dictionary with shader parameters and their values."""
        return {"intensity":self.obj.intensity,
                "uniform color diffuse":self.obj.diffuse,
                "uniform color specular":self.obj.specular,
                "uniform float constant_attenuation":self.obj.constant_attenuation,
                "uniform float linear_attenuation":self.obj.linear_attenuation,
                "uniform float quadratic_attenuation":self.obj.quadratic_attenuation}


# GLSpotLightAdapter
class GLSpotLightAdapter:
    protocols.advise(instancesProvide=[ILightSource], asAdapterForTypes=[GLTargetSpotLight, GLFreeSpotLight])
    
    def __init__(self, lgt, proto):
        self.obj = lgt

    def createPasses(self):
        """Returns a list of RenderPass objects."""
        if hasattr(self.obj, "shadowmap"):
            res,width,blur,bias,samples = self.obj.shadowmap
            shadpass = ShadowPass(output=[("shadow.z", "zfile", RI_Z, {})],
                                  light=self.obj, fov=self.obj.cutoff,
                                  resolution = res)
            return [shadpass]

        return []

    def shaderName(self):
        """Returns the name of the corresponding light shader or None.
        """
        return "glspotlight"

    def shaderSource(self):
        """Returns surface shader source code as a string or None.
        """
        return """// GLSpotLight shader
light $SHADERNAME(float intensity = 1.0;
         uniform color diffuse = color "rgb" (1, 1, 1);
         uniform color specular = color "rgb" (1, 1, 1);
         float constant_attenuation = 1;
         float linear_attenuation = 0;
         float quadratic_attenuation = 0;
         float exponent = 0.0;
         float cutoff = 45.0;
         string shadowname = "";
         float width = 1.0;
         float blur = 0.0;
         float bias = 0.1;
         float samples = 16;)
{
  float cutoff_rad = cutoff*PI/180.0;
  
  illuminate(point "shader" (0,0,0), vector "shader" (0,0,1), cutoff_rad)
  {
    float d = length(L);
    float att = 1.0/(constant_attenuation + linear_attenuation*d + quadratic_attenuation*d*d);
    
    vector Lshad = normalize(vtransform("shader", L));
    float sp = max(zcomp(Lshad), 0);
    float spot = 0;
    if (sp>=cos(cutoff_rad))
    {
      spot = pow(sp, exponent);
    }
    
    Cl = att * spot * intensity * color "rgb" (1,1,1);

    if (shadowname!="")
      Cl *= 1 - shadow(shadowname, Ps, "blur", blur, "width", width, "bias", bias, "samples", samples);
    
  }
}        
        """

    def shaderParams(self, passes):
        """Return a dictionary with shader parameters and their values."""
        params = {"intensity":self.obj.intensity,
                "uniform color diffuse":self.obj.diffuse,
                "uniform color specular":self.obj.specular,
                "uniform float constant_attenuation":self.obj.constant_attenuation,
                "uniform float linear_attenuation":self.obj.linear_attenuation,
                "uniform float quadratic_attenuation":self.obj.quadratic_attenuation,
                "uniform float exponent":self.obj.exponent,
                "uniform float cutoff":self.obj.cutoff}
        if hasattr(self.obj, "shadowmap") and passes[0].done():
            res,width,blur,bias,samples = self.obj.shadowmap
            zfile = passes[0].realFilename(passes[0].output[0][0])
            mapname = os.path.splitext(zfile)[0]+".map"
            params["uniform string shadowname"] = mapname
            params["uniform float width"] = width
            params["uniform float blur"] = blur
            params["uniform float bias"] = bias
            params["uniform float samples"] = samples
        return params

# GLDistantLightAdapter
class GLDistantLightAdapter:
    protocols.advise(instancesProvide=[ILightSource], asAdapterForTypes=[GLTargetDistantLight, GLFreeDistantLight])
    
    def __init__(self, lgt, proto):
        self.obj = lgt

    def createPasses(self):
        """Returns a list of RenderPass objects."""
        return []

    def shaderName(self):
        """Returns the name of the corresponding light shader or None.
        """
        return "gldistantlight"

    def shaderSource(self):
        """Returns surface shader source code as a string or None.
        """
        return """// GLDistantLight shader
light $SHADERNAME(float intensity = 1.0;
         uniform color diffuse = color "rgb" (1, 1, 1);
         uniform color specular = color "rgb" (1, 1, 1))
{
  solar(vector "shader" (0,0,1), PI/2)
  {
    Cl = intensity * color "rgb" (1,1,1);
  }
}        
        """

    def shaderParams(self, passes):
        """Return a dictionary with shader parameters and their values."""
        return {"intensity":self.obj.intensity,
                "uniform color diffuse":self.obj.diffuse,
                "uniform color specular":self.obj.specular}


######################################################################
####################### Material adapters ############################
######################################################################


# GLMaterialAdapter
class GLMaterialAdapter:
    """
    """

    protocols.advise(instancesProvide=[IMaterial], asAdapterForTypes=[GLMaterial])

    def __init__(self, material, proto):
        self.mat = material

    def createPasses(self):
        """Returns a list of RenderPass objects."""
        if self.mat.getTexture(0)==None:
            return []
        
        tex = self.mat.getTexture(0)
        return [TexPass(maps=[(tex.imagename, "periodic", "periodic", "gaussian", 1, 1, {})])]

    def preProcess(self, exporter):
        """Preprocessing method.

        This method is called before the image is rendered and can be used
        to create or copy image maps.
        """
        pass

    def color(self):
        """Return the color for the RiColor() call or None.
        """
        return self.mat.diffuse

    def opacity(self):
        """Return the opacity for the RiOpacity() call or None.
        """
        return None
#        a = self.mat.diffuse.w
#        return (a,a,a)

    def surfaceShaderName(self):
        """Returns the name of the corresponding surface shader or None.
        """
        return "glmaterial"

    def surfaceShaderSource(self):
        """Returns surface shader source code as a string or None.

        If the return value is None, then shaderName() must return
        the name of the shader to use.
        """
        ambient = self.mat.ambient
        emission = self.mat.emission
        return """// GLMaterial shader
surface $SHADERNAME(color ambient = color "rgb" (0.2, 0.2, 0.2);
           color specular = color "rgb" (0, 0, 0);
           float shininess = 0.0;
           color emission = color "rgb" (0, 0, 0);
           float diffuse_alpha = 1.0;
           string texname = "";
           float T[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
           float mode = 0;
           color texenvcolor = color "rgb" (1,1,1);
           float texenvcolor_alpha = 1;
           float blend_sfactor = -1;
           float blend_dfactor = -1;
           varying point Pref = point(0,0,0);
           )
{
  BAKE_BEGIN
  normal Nf = BAKE_NORMAL(N);
  vector V = normalize(-I);
  color diffuse = Cs;

  Ci = emission + 0*ambient;
  illuminance(P, Nf, PI/2)
  {
    vector L0 = normalize(L);
    vector S = normalize(L0+V);
    float NL = max(Nf.L0, 0);
    float fi = (NL!=0)? 1 : 0;

    color diffuse_lgt = color "rgb" (1,1,1);
    color specular_lgt = color "rgb" (1,1,1);
    lightsource("diffuse", diffuse_lgt);
    lightsource("specular", specular_lgt);
    
    Ci += Cl*(diffuse*diffuse_lgt*NL +
              fi*specular*specular_lgt*pow(max(Nf.S, 0), shininess) );
  }

  if (texname!="")
  {
    float ss = T[0]*s + T[1]*t + T[3];
    float tt = T[4]*s + T[5]*t + T[7];
    float dn = T[12]*s + T[13]*t + T[15];
    color texcolor = texture(texname, ss/dn, tt/dn);
    if (mode==0)  // GL_REPLACE
    {
      Ci = texcolor;
    }
    else if (mode==1)  // GL_MODULATE
    {
      Ci = texcolor*Ci;
    }
    else if (mode==2)  // GL_DECAL
    {
      Ci = texcolor;
    }
    else  // GL_BLEND
    {
      Ci = Ci*(color "rgb" (1,1,1)-texcolor) + texenvcolor*texcolor;
    }
  }

  Oi = 1;

  if (blend_sfactor!=-1 && blend_dfactor!=-1)
  {
    color S = 0;
    color D = 0;

    if (blend_sfactor==1)
      S = 1;
    else if (blend_sfactor==3)
      S = Ci;
    else if (blend_sfactor==5)
      S = 1-Ci;
    else if (blend_sfactor==6)
      S = diffuse_alpha;
    else if (blend_sfactor==7)
      S = 1-diffuse_alpha;

    if (blend_dfactor==1)
      D = 1;
    else if (blend_dfactor==3)
      D = Ci;
    else if (blend_dfactor==5)
      D = 1-Ci;
    else if (blend_dfactor==6)
      D = diffuse_alpha;
    else if (blend_dfactor==7)
      D = 1-diffuse_alpha;
    
    Ci = S*Ci;
    Oi = 1-D;
  }
  BAKE_END
}        
        """

    def surfaceShaderParams(self, passes):
        """Return a dictionary with shader parameters and their values."""
        blend_dict = { GL_ZERO : 0,
                       GL_ONE : 1,
                       GL_DST_COLOR : 2,
                       GL_SRC_COLOR : 3,
                       GL_ONE_MINUS_DST_COLOR : 4,
                       GL_ONE_MINUS_SRC_COLOR : 5,
                       GL_SRC_ALPHA : 6,
                       GL_ONE_MINUS_SRC_ALPHA : 7,
                       GL_DST_ALPHA : 8,
                       GL_ONE_MINUS_DST_ALPHA : 9,
                       GL_SRC_ALPHA_SATURATE : 10
                     }
        sfactor = blend_dict.get(self.mat.blend_sfactor, -1)
        dfactor = blend_dict.get(self.mat.blend_dfactor, -1)
        res = {"uniform float shininess" : self.mat.shininess,
               "uniform color specular" : tuple(self.mat.specular)[:3],
               "uniform color ambient" : tuple(self.mat.ambient)[:3],
               "uniform color emission" : tuple(self.mat.emission)[:3],
               "uniform float blend_sfactor" : sfactor,
               "uniform float blend_dfactor" : dfactor,
               "uniform float diffuse_alpha" : self.mat.diffuse.w
               }

        if self.mat.getTexture(0)!=None:
            tex = self.mat.getTexture(0)
            # Image name
            n = os.path.basename(tex.imagename)
            name,ext = os.path.splitext(n)
            res["uniform string texname"] = name+".tex"
            # Mode
            try:
                res["uniform float mode"] = [GL_REPLACE, GL_MODULATE, GL_DECAL, GL_BLEND].index(tex.mode)
            except:
                pass
            res["uniform color texenvcolor"] = tuple(tex.texenvcolor)[:3]
            res["uniform float texenvcolor_alpha"] = tex.texenvcolor.w
            res["uniform float [16] T"] = tex.transform.transpose()
      
        return res

    def surfaceShaderTransform(self):
        return mat4(1)

    def displacementShaderName(self):
        return None
    
    def displacementShaderSource(self):
        return None
    
    def displacementShaderParams(self, passes):
        return {}

    def displacementShaderTransform(self):
        return mat4(1)

    def displacementBound(self):
        return "current", 0
	
    def interiorShaderName(self):
        return None
    
    def interiorShaderSource(self):
        return None
    
    def interiorShaderParams(self, passes):
        return {}

    def interiorShaderTransform(self):
        return mat4(1)


######################################################################

# Register the OffImporter class as a plugin class
pluginmanager.register(RIBExporter)
