###########################################################################
# cgkit - Python Computer Graphics Kit
# Copyright (C) 2004 Matthias Baas (baas@ira.uka.de)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# cgkit homepage: http://cgkit.sourceforge.net
###########################################################################
# $Id: objmaterial.py,v 1.7 2005/08/28 19:42:43 mbaas Exp $

## \file objmaterial.py
## Contains the OBJMaterial class.

import os.path, sys
import protocols
from Interfaces import *
from slots import *
from material import Material
import ribexport
import _Image as Image
from expression import Expression
import _core
from cgtypes import *

# OBJTextureMap
class OBJTextureMap:
    """A texture map definition as it appears in OBJ/MTL files.
    """

    def __init__(self,
                 filename,
                 offset = (0,0,0),
                 scale = (1,1,1),
                 turb = (0,0,0),
                 mm = (0.0, 1.0),
                 clamp = False,
                 blendu = True,
                 blendv = True,
                 bumpsize = None,
                 refltype = None):
        
        self.filename = filename
        self.offset = offset
        self.scale = scale
        self.turb = turb
        self.mm = mm
        self.clamp = clamp
        self.blendu = blendu
        self.blendv = blendv
        if bumpsize!=None:
            self.bumpsize = bumpsize
        if refltype!=None:
            self.refltype = refltype
        
        
# OBJMaterial
#class OBJMaterial(Material):
class OBJMaterial(_core.GLMaterial):
    """This class represents a material as it appears in OBJ (or rather MTL) files.

    """

    protocols.advise(instancesProvide=[ribexport.IMaterial])
    
    def __init__(self,
                 name="OBJMaterial",
                 illum = 2,
                 Ka = (0.2, 0.2, 0.2),
                 Kd = (0.8, 0.8, 0.8),
                 Ks = (0.0, 0.0, 0.0),
                 Ke = (0.0, 0.0, 0.0),
                 Ns = 0.0,
                 Ni = 1.0,
                 d = 1.0,
                 Tr = 1.0,
                 Tf = (1.0, 1.0, 1.0),
                 sharpness = 0.0,
                 map_Ka = None,
                 map_Kd = None,
                 map_Ks = None,
                 map_Ke = None,
                 map_Ns = None,
                 map_d = None,
                 map_Bump = None,
                 refl = None,  # one single map or a list of maps
                 density = 1.0,
                 ):

#        Material.__init__(self, name=name, density=density)
        _core.GLMaterial.__init__(self, name, density)

        # Create a Kd slot and connect it with the GL diffuse slot
        self.Kd_slot = Vec3Slot(vec3(Kd))
        self.addSlot("Kd", self.Kd_slot)
        self.diffuse_adapter = Expression("(col.x,col.y,col.z,1)", col=vec3())
        self.Kd_slot.connect(self.diffuse_adapter.col_slot)
        self.diffuse_adapter.output_slot.connect(self.diffuse_slot)

        self.illum = illum
        self.Ka = Ka
#        self.Kd = Kd
        self.Ks = Ks
        self.Ke = Ke
        self.Ns = Ns
        self.Ni = Ni
        self.d = d
        self.Tr = Tr
        self.Tf = vec3(Tf)
        self.sharpness = sharpness
        self.map_Ka = map_Ka
        self.map_Kd = map_Kd
        self.map_Ks = map_Ks
        self.map_Ke = map_Ke
        self.map_Ns = map_Ns
        self.map_d = map_d
        self.map_Bump = map_Bump

        if refl==None:
            refl = []
        else:
            # Turn refl into a list
            try:
                refl = list(refl)
            except:
                refl = [refl]
        self.refl = refl

        # "Hide" the GLMaterial slots
        self.removeSlot("ambient")
        self.removeSlot("diffuse")
        self.removeSlot("specular")
        self.removeSlot("shininess")
        self.removeSlot("emission")

    exec slotPropertyCode("Kd")

    def mtlDefinition(self):
        s = """illum %d
Ka %f %f %f
Kd %f %f %f
Ks %f %f %f
Ke %f %f %f
Ns %f
Ni %f
"""%(self.illum,
     self.Ka[0], self.Ka[1], self.Ka[2],
     self.Kd[0], self.Kd[1], self.Kd[2],
     self.Ks[0], self.Ks[1], self.Ks[2],
     self.Ke[0], self.Ke[1], self.Ke[2],
     self.Ns,
     self.Ni)
        if self.d!=1.0:
            s += "d %f\n"%self.d
        if self.Tr!=1.0:
            s += "Tr %f\n"%self.d
        if self.Tf[0]!=1.0 or self.Tf[1]!=1.0 or self.Tf[2]!=1.0:
            s += "Tf %f %f %f\n"%(self.Tf[0], self.Tf[1], self.Tf[2])
        s += self.mapDefinition("map_Ka", self.map_Ka)
        s += self.mapDefinition("map_Kd", self.map_Kd)
        s += self.mapDefinition("map_Ks", self.map_Ks)
        s += self.mapDefinition("map_Ke", self.map_Ke)
        s += self.mapDefinition("map_Ns", self.map_Ns)
        s += self.mapDefinition("map_d", self.map_d)
        s += self.mapDefinition("map_Bump", self.map_Bump)
        for map in self.refl:
            s += self.mapDefinition("refl", map)
        return s

    # mapDefinition
    def mapDefinition(self, mapname, map):
        """Return the map defintion line or an empty string.
        """
        if map==None:
            return ""

        onoff = {True:"on", False:"off"}

        res = mapname
        # -o
        u,v,w = map.offset
        if u!=0 or v!=0 or w!=0:
            res += " -o %f %f %f"%(u,v,w)
        # -s
        u,v,w = map.scale
        if u!=1 or v!=1 or w!=1:
            res += " -s %f %f %f"%(u,v,w)
        # -t
        u,v,w = map.turb
        if u!=0 or v!=0 or w!=0:
            res += " -t %f %f %f"%(u,v,w)
        # -mm
        base, gain = map.mm
        if base!=0 or gain!=1:
            res += " -mm %f %f"%(base, gain)
        # -clamp
        if map.clamp:
            res += " -clamp %s"%onoff[map.clamp]
        # -blendu
        if not map.blendu:
            res += " -blendu %s"%onoff[map.blendu]
        # -blendv
        if not map.blendv:
            res += " -blendv %s"%onoff[map.blendv]
        # -bm
        if mapname=="map_Bump" and getattr(map, "bumpsize", 1)!=1:
            res += " -bm %f"%map.bumpsize
        # -type
        if mapname=="refl":
            res += " -type %s"%getattr(map, "refltype", "sphere")

        return res+" %s\n"%map.filename        
        
        
    def createPasses(self):
        """Returns a list of RenderPass objects."""
        texdefs = []
        if self.map_Ka!=None:
            texdefs.append((self.map_Ka.filename, "periodic", "periodic", "gaussian", 0.01, 0.01, {}))
        if self.map_Kd!=None:
            texdefs.append((self.map_Kd.filename, "periodic", "periodic", "gaussian", 0.01, 0.01, {}))
        if self.map_Ks!=None:
            texdefs.append((self.map_Ks.filename, "periodic", "periodic", "gaussian", 0.01, 0.01, {}))
        if self.map_d!=None:
            texdefs.append((self.map_d.filename, "periodic", "periodic", "gaussian", 0.01, 0.01, {}))
        if self.map_Bump!=None:
            texdefs.append((self.map_Bump.filename, "periodic", "periodic", "gaussian", 0.01, 0.01, {}))
        
        if texdefs!=[]:
            return [ribexport.TexPass(maps=texdefs)]
        else:
            return []

    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 None
#        return self.Kd

    def opacity(self):
        """Return the opacity for the RiOpacity() call or None.
        """
        return None
#        a = self.Tr*self.d
#        return (a,a,a)

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

    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.
        """
        return """// OBJ/MTL material shader
        
surface $SHADERNAME(float illum = 2;
           color Ka = color "rgb" (0.2, 0.2, 0.2);
           color Kd = color "rgb" (0.8, 0.8, 0.8);
           color Ks = color "rgb" (0.8, 0.8, 0.8);
           color Ke = color "rgb" (0, 0, 0);
           float Ni = 0.0;
           float Ns = 0.0;
           float d = 1.0;
           float Tr = 1.0;
           color Tf = color "rgb" (1.0, 1.0, 1.0);
           
           string map_Ka = "";
           float map_Ka_offset[3] = {0, 0, 0};
           float map_Ka_scale[3] = {1, 1, 1};
           
           string map_Kd = "";
           float map_Kd_offset[3] = {0, 0, 0};
           float map_Kd_scale[3] = {1, 1, 1};
           
           string map_Ks = "";
           float map_Ks_offset[3] = {0, 0, 0};
           float map_Ks_scale[3] = {1, 1, 1};

           string map_Ke = "";
           float map_Ke_offset[3] = {0, 0, 0};
           float map_Ke_scale[3] = {1, 1, 1};

           string map_Ns = "";
           float map_Ns_offset[3] = {0, 0, 0};
           float map_Ns_scale[3] = {1, 1, 1};

           string map_d = "";
           float map_d_offset[3] = {0, 0, 0};
           float map_d_scale[3] = {1, 1, 1};
           )
{
  normal Nf = faceforward(normalize(N),I);
  color _Ka = Ka;
  color _Kd = Kd;
  color _Ks = Ks;
  color _Ke = Ke;
  float _Ns = Ns;
  float _d = d;

  if (map_Ka!="")
  {
    _Ka = texture(map_Ka,
                  s*map_Ka_scale[0]+map_Ka_offset[0],
                  (1-t)*map_Ka_scale[1]+map_Ka_offset[1]);
  }

  if (map_Kd!="")
  {
    _Kd = texture(map_Kd,
                  s*map_Kd_scale[0]+map_Kd_offset[0],
                  (1-t)*map_Kd_scale[1]+map_Kd_offset[1]);
  }

  if (map_Ks!="")
  {
    _Ks = texture(map_Ks,
                  s*map_Ks_scale[0]+map_Ks_offset[0],
                  (1-t)*map_Ks_scale[1]+map_Ks_offset[1]);
  }

  if (map_Ke!="")
  {
    _Ke = texture(map_Ke,
                  s*map_Ke_scale[0]+map_Ke_offset[0],
                  (1-t)*map_Ke_scale[1]+map_Ke_offset[1]);
  }

  if (map_Ns!="")
  {
    _Ns = texture(map_Ns,
                  s*map_Ns_scale[0]+map_Ns_offset[0],
                  (1-t)*map_Ns_scale[1]+map_Ns_offset[1]);
  }

  if (map_d!="")
  {
    _d = texture(map_d,
                 s*map_d_scale[0]+map_d_offset[0],
                 (1-t)*map_d_scale[1]+map_d_offset[1]);
  }

  if (illum==0)
  {
    Ci = _Kd;
  }
  else if (illum==1)
  {
    Ci = _Ka + _Kd*diffuse(Nf);
  }
  else
  {
    // Ns==0? then disable specular part
    if (_Ns<1E-6)
    {
      _Ks = color "rgb" (0,0,0);
    }
    Ci = _Ka*ambient() + _Kd*diffuse(Nf) + _Ks*specular(Nf,-normalize(I),1/_Ns) + _Ke;
  }  

  Oi = _d*Tr*Tf;
  Ci *= Oi;
}        
        """

    def surfaceShaderParams(self, passes):
        """Return a dictionary with shader parameters and their values."""
        res = {"uniform float illum" : self.illum,
               "uniform color Ka" : self.Ka,
               "uniform color Kd" : self.Kd,
               "uniform color Ks" : self.Ks,
               "uniform color Ke" : self.Ke,
               "uniform float Ns" : self.Ns,
               "uniform float Ni" : self.Ni,
               "uniform float d" : self.d,
               "uniform float Tr" : self.Tr,
               "uniform color Tf" : self.Tf
               }

        if self.map_Ka!=None:
            texname = os.path.basename(self.map_Ka.filename)
            name, ext = os.path.splitext(texname)
            res["uniform string map_Ka"] = name+".tex"       
            res["uniform float[3] map_Ka_offset"] = self.map_Ka.offset
            res["uniform float[3] map_Ka_scale"] = self.map_Ka.scale

        if self.map_Kd!=None:
            texname = os.path.basename(self.map_Kd.filename)
            name, ext = os.path.splitext(texname)
            res["uniform string map_Kd"] = name+".tex"       
            res["uniform float[3] map_Kd_offset"] = self.map_Kd.offset
            res["uniform float[3] map_Kd_scale"] = self.map_Kd.scale

        if self.map_Ks!=None:
            texname = os.path.basename(self.map_Ks.filename)
            name, ext = os.path.splitext(texname)
            res["uniform string map_Ks"] = name+".tex"       
            res["uniform float[3] map_Ks_offset"] = self.map_Ks.offset
            res["uniform float[3] map_Ks_scale"] = self.map_Ks.scale

        if self.map_Ke!=None:
            texname = os.path.basename(self.map_Ke.filename)
            name, ext = os.path.splitext(texname)
            res["uniform string map_Ke"] = name+".tex"  
            res["uniform float[3] map_Ke_offset"] = self.map_Ke.offset
            res["uniform float[3] map_Ke_scale"] = self.map_Ke.scale

        if self.map_Ns!=None:
            texname = os.path.basename(self.map_Ns.filename)
            name, ext = os.path.splitext(texname)
            res["uniform string map_Ns"] = name+".tex"
            res["uniform float[3] map_Ns_offset"] = self.map_Ns.offset
            res["uniform float[3] map_Ns_scale"] = self.map_Ns.scale

        if self.map_d!=None:
            texname = os.path.basename(self.map_d.filename)
            name, ext = os.path.splitext(texname)
            res["uniform string map_d"] = name+".tex"       
            res["uniform float[3] map_d_offset"] = self.map_d.offset
            res["uniform float[3] map_d_scale"] = self.map_d.scale

        return res
        
    def surfaceShaderTransform(self):
        return mat4(1)

    def displacementShaderName(self):
        if self.map_Bump==None:
            return None
        
        return "matobj_bump"

   
    def displacementShaderSource(self):
        if self.map_Bump==None:
            return None
        
        return """// OBJ/MTL material shader (bump)
        
displacement $SHADERNAME(
           string map_Bump = "";
           float bump_size = 1.0;
           float map_Bump_offset[3] = {0, 0, 0};
           float map_Bump_scale[3] = {1, 1, 1};
           )
{
  float amount = 0;
  
  if (map_Bump!="")
  {
    color bc = texture(map_Bump,
                       s*map_Bump_scale[0]+map_Bump_offset[0],
                      (1-t)*map_Bump_scale[1]+map_Bump_offset[1]);
    amount = (comp(bc,0)+comp(bc,1)+comp(bc,2))/3;
  }

  P = P + bump_size*amount*normalize(N);
  N = calculatenormal(P);
}
        """
    
    def displacementShaderParams(self, passes):
        if self.map_Bump==None:
            return {}

        res = {}
        texname = os.path.basename(self.map_Bump.filename)
        name, ext = os.path.splitext(texname)
        res["uniform string map_Bump"] = name+".tex"
        res["uniform float bump_size"] = getattr(self.map_Bump, "bumpsize", 1.0)
        res["uniform float[3] map_Bump_offset"] = self.map_Bump.offset
        res["uniform float[3] map_Bump_scale"] = self.map_Bump.scale
        return res

    def displacementShaderTransform(self):
        return mat4(1)

    def displacementBound(self):
        if self.map_Bump==None:
            return "current", 0
        else:
            return "current", abs(getattr(self.map_Bump, "bumpsize", 1.0))
	
    def interiorShaderName(self):
        return None
    
    def interiorShaderSource(self):
        return None
    
    def interiorShaderParams(self, passes):
        return {}

    def interiorShaderTransform(self):
        return mat4(1)
      
        
