1"""Material export classes.
2
3   @author Michael Reimpell
4"""
5# Copyright (C) 2005  Michael Reimpell
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
10# version 2.1 of the License, or (at your option) any later version.
11#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21# epydoc doc format
22__docformat__ = "javadoc en"
23
24import base
25from base import *
26
27import os
28import shutil
29import Blender
30
31def clamp(value):
32	if value < 0.0:
33		value = 0.0
34	elif value > 1.0:
35		value = 1.0
36	return value
37
38class MaterialInterface:
39	def getName(self):
40		"""Returns the material name.
41
42		   @return Material name.
43		"""
44		return
45	def write(self, f):
46		"""Write material script entry.
47
48		   All used texture files are registered at the MaterialManager.
49
50		   @param f Material script file object to write into.
51		"""
52		return
53
54class DefaultMaterial(MaterialInterface):
55	def __init__(self, manager, name):
56		self.manager = manager
57		self.name = name
58		return
59	def getName(self):
60		return self.name
61	def write(self, f):
62		f.write("material %s\n" % self.getName())
63		f.write("{\n")
64		self.writeTechniques(f)
65		f.write("}\n")
66		return
67	def writeTechniques(self, f):
68		f.write(indent(1) + "technique\n" + indent(1) + "{\n")
69		f.write(indent(2) + "pass\n" + indent(2) + "{\n")
70		# empty pass
71		f.write(indent(2) + "}\n") # pass
72		f.write(indent(1) + "}\n") # technique
73		return
74
75class GameEngineMaterial(DefaultMaterial):
76	def __init__(self, manager, blenderMesh, blenderFace, colouredAmbient):
77		self.mesh = blenderMesh
78		self.face = blenderFace
79		self.colouredAmbient = colouredAmbient
80		# check if a Blender material is assigned
81		try:
82			blenderMaterial = self.mesh.materials[self.face.mat]
83		except:
84			blenderMaterial = None
85		self.material = blenderMaterial
86		DefaultMaterial.__init__(self, manager, self._createName())
87		return
88	def writeTechniques(self, f):
89		mat = self.material
90		if (not(mat)
91			and not(self.mesh.vertexColors)
92			and not(self.mesh.vertexUV or self.mesh.faceUV)):
93			# default material
94			DefaultMaterial.writeTechniques(self, f)
95		else:
96			# default material
97			# SOLID, white, no specular
98			f.write(indent(1)+"technique\n")
99			f.write(indent(1)+"{\n")
100			f.write(indent(2)+"pass\n")
101			f.write(indent(2)+"{\n")
102			# ambient
103			# (not used in Blender's game engine)
104			if mat:
105				if (not(mat.mode & Blender.Material.Modes["TEXFACE"])
106					and not(mat.mode & Blender.Material.Modes["VCOL_PAINT"])
107					and (self.colouredAmbient)):
108					ambientRGBList = mat.rgbCol
109				else:
110					ambientRGBList = [1.0, 1.0, 1.0]
111				# ambient <- amb * ambient RGB
112				ambR = clamp(mat.amb * ambientRGBList[0])
113				ambG = clamp(mat.amb * ambientRGBList[1])
114				ambB = clamp(mat.amb * ambientRGBList[2])
115				##f.write(indent(3)+"ambient %f %f %f\n" % (ambR, ambG, ambB))
116			# diffuse
117			# (Blender's game engine uses vertex colours
118			#  instead of diffuse colour.
119			#
120			#  diffuse is defined as
121			#  (mat->r, mat->g, mat->b)*(mat->emit + mat->ref)
122			#  but it's not used.)
123			if self.mesh.vertexColors:
124				#TODO: Broken in Blender 2.36.
125				# Blender does not handle "texface" mesh with vertex colours
126				f.write(indent(3)+"diffuse vertexcolour\n")
127			elif mat:
128				if (not(mat.mode & Blender.Material.Modes["TEXFACE"])
129					and not(mat.mode & Blender.Material.Modes["VCOL_PAINT"])):
130					# diffuse <- rgbCol
131					diffR = clamp(mat.rgbCol[0])
132					diffG = clamp(mat.rgbCol[1])
133					diffB = clamp(mat.rgbCol[2])
134					f.write(indent(3)+"diffuse %f %f %f\n" % (diffR, diffG, diffB))
135				elif (mat.mode & Blender.Material.Modes["VCOL_PAINT"]):
136					f.write(indent(3)+"diffuse vertexcolour\n")
137			if mat:
138				# specular <- spec * specCol, hard/4.0
139				specR = clamp(mat.spec * mat.specCol[0])
140				specG = clamp(mat.spec * mat.specCol[1])
141				specB = clamp(mat.spec * mat.specCol[2])
142				specShine = mat.hard/4.0
143				f.write(indent(3)+"specular %f %f %f %f\n" % (specR, specG, specB, specShine))
144				# emissive
145				# (not used in Blender's game engine)
146				if(not(mat.mode & Blender.Material.Modes["TEXFACE"])
147					and not(mat.mode & Blender.Material.Modes["VCOL_PAINT"])):
148					# emissive <-emit * rgbCol
149					emR = clamp(mat.emit * mat.rgbCol[0])
150					emG = clamp(mat.emit * mat.rgbCol[1])
151					emB = clamp(mat.emit * mat.rgbCol[2])
152					##f.write(indent(3)+"emissive %f %f %f\n" % (emR, emG, emB))
153			if self.mesh.faceUV:
154				# mesh has texture values, resp. tface data
155				# scene_blend <- transp
156				if (self.face.transp == Blender.Mesh.FaceTranspModes["ALPHA"]):
157					f.write(indent(3)+"scene_blend alpha_blend \n")
158				elif (self.face.transp == Blender.NMesh.FaceTranspModes["ADD"]):
159					f.write(indent(3)+"scene_blend add\n")
160				# cull_hardware/cull_software
161				if (self.face.mode & Blender.Mesh.FaceModes['TWOSIDE']):
162					f.write(indent(3) + "cull_hardware none\n")
163					f.write(indent(3) + "cull_software none\n")
164				# shading
165				# (Blender's game engine is initialized with glShadeModel(GL_FLAT))
166				##f.write(indent(3) + "shading flat\n")
167				# texture
168				if (self.face.mode & Blender.Mesh.FaceModes['TEX']) and (self.face.image):
169					f.write(indent(3)+"texture_unit\n")
170					f.write(indent(3)+"{\n")
171					f.write(indent(4)+"texture %s\n" % self.manager.registerTextureFile(self.face.image.filename))
172					f.write(indent(3)+"}\n") # texture_unit
173			f.write(indent(2)+"}\n") # pass
174			f.write(indent(1)+"}\n") # technique
175		return
176	# private
177	def _createName(self):
178		"""Create unique material name.
179
180		   The name consists of several parts:
181		   <OL>
182		   <LI>rendering material name/</LI>
183		   <LI>blend mode (ALPHA, ADD, SOLID)</LI>
184		   <LI>/TEX</LI>
185		   <LI>/texture file name</LI>
186		   <LI>/VertCol</LI>
187		   <LI>/TWOSIDE></LI>
188		   </OL>
189		"""
190		materialName = ''
191		# nonempty rendering material?
192		if self.material:
193			materialName += self.material.getName() + '/'
194		# blend mode
195		if self.mesh.faceUV and (self.face.transp == Blender.Mesh.FaceTranspModes['ALPHA']):
196			materialName += 'ALPHA'
197		elif self.mesh.faceUV and (self.face.transp == Blender.Mesh.FaceTranspModes['ADD']):
198			materialName += 'ADD'
199		else:
200			materialName += 'SOLID'
201		# TEX face mode and texture?
202		if self.mesh.faceUV and (self.face.mode & Blender.Mesh.FaceModes['TEX']):
203			materialName += '/TEX'
204			if self.face.image:
205				materialName += '/' + PathName(self.face.image.filename).basename()
206		# vertex colours?
207		if self.mesh.vertexColors:
208			materialName += '/VertCol'
209		# two sided?
210		if self.mesh.faceUV and (self.face.mode & Blender.Mesh.FaceModes['TWOSIDE']):
211			materialName += '/TWOSIDE'
212		return materialName
213
214class RenderingMaterial(DefaultMaterial):
215	def __init__(self, manager, blenderMesh, blenderFace, colouredAmbient):
216		self.mesh = blenderMesh
217		self.face = blenderFace
218		self.colouredAmbient = colouredAmbient
219		self.key = 0
220		self.mTexUVCol = None
221		self.mTexUVNor = None
222		self.mTexUVCsp = None
223		self.material = None
224		try:
225			self.material = self.mesh.materials[self.face.mat]
226		except IndexError:
227			Log.getSingleton().logWarning("Can't get material for mesh \"%s\"! Are any materials linked to object instead of linked to mesh?" % self.mesh.name)
228		if self.material:
229			self._generateKey()
230			DefaultMaterial.__init__(self, manager, self._createName())
231		else:
232			DefaultMaterial.__init__(self, manager, 'None')
233		return
234	def writeTechniques(self, f):
235		# parse material
236		if self.key:
237			if self.TECHNIQUES.has_key(self.key):
238				techniques = self.TECHNIQUES[self.key]
239				techniques(self, f)
240			else:
241				# default
242				self.writeColours(f)
243		else:
244			# Halo or empty material
245			DefaultMaterial('').writeTechniques(f)
246		return
247	def writeColours(self, f):
248		# receive_shadows
249		self.writeReceiveShadows(f, 1)
250		f.write(indent(1) + "technique\n" + indent(1) + "{\n")
251		f.write(indent(2) + "pass\n" + indent(2) + "{\n")
252		# ambient
253		if (self.colouredAmbient):
254			col = self.material.getRGBCol()
255		else:
256			col = [1.0, 1.0, 1.0]
257		self.writeAmbient(f, col, 3)
258		# diffuse
259		self.writeDiffuse(f, self.material.rgbCol, 3)
260		# specular
261		self.writeSpecular(f, 3)
262		# emissive
263		self.writeEmissive(f, self.material.rgbCol, 3)
264		# blend mode
265		self.writeSceneBlend(f,3)
266		# options
267		self.writeCommonOptions(f, 3)
268		# texture units
269		self.writeDiffuseTexture(f, 3)
270		f.write(indent(2) + "}\n") # pass
271		f.write(indent(1) + "}\n") # technique
272		return
273	def writeTexFace(self, f):
274		# preconditions: TEXFACE set
275		#
276		# Note that an additional Col texture replaces the
277		# TEXFACE texture instead of blend over according to alpha.
278		#
279		# (amb+emit)textureCol + diffuseLight*ref*textureCol + specular
280		#
281		imageFileName = None
282		if self.mTexUVCol:
283			# COL MTex replaces UV/Image Editor texture
284			imageFileName = self.manager.registerTextureFile(self.mTexUVCol.tex.getImage().getFilename())
285		elif (self.mesh.faceUV and self.face.image):
286			# UV/Image Editor texture
287			imageFileName = self.manager.registerTextureFile(self.face.image.filename)
288
289		self.writeReceiveShadows(f, 1)
290		f.write(indent(1) + "technique\n" + indent(1) + "{\n")
291		col = [1.0, 1.0, 1.0]
292		# texture pass
293		f.write(indent(2) + "pass\n" + indent(2) + "{\n")
294		self.writeAmbient(f, col, 3)
295		self.writeDiffuse(f, col, 3)
296		if not(imageFileName):
297			self.writeSpecular(f, 3)
298		self.writeEmissive(f, col, 3)
299		self.writeSceneBlend(f,3)
300		self.writeCommonOptions(f, 3)
301		if imageFileName:
302			f.write(indent(3) + "texture_unit\n")
303			f.write(indent(3) + "{\n")
304			f.write(indent(4) + "texture %s\n" % imageFileName)
305			if self.mTexUVCol:
306				self.writeTextureAddressMode(f, self.mTexUVCol, 4)
307				self.writeTextureFiltering(f, self.mTexUVCol, 4)
308			# multiply with factors
309			f.write(indent(4) + "colour_op modulate\n")
310			f.write(indent(3) + "}\n") # texture_unit
311			f.write(indent(2) + "}\n") # texture pass
312			# specular pass
313			f.write(indent(2) + "pass\n" + indent(2) + "{\n")
314			f.write(indent(3) + "ambient 0.0 0.0 0.0\n")
315			f.write(indent(3) + "diffuse 0.0 0.0 0.0\n")
316			self.writeSpecular(f, 3)
317			f.write(indent(3) + "scene_blend add\n")
318			hasAlpha = 0
319			if (self.material.getAlpha() < 1.0):
320				hasAlpha = 1
321			else:
322				for mtex in self.material.getTextures():
323					if mtex:
324						if ((mtex.tex.type == Blender.Texture.Types['IMAGE'])
325							and (mtex.mapto & Blender.Texture.MapTo['ALPHA'])):
326							hasAlpha = 1
327			if (hasAlpha):
328				f.write(indent(3) + "depth_write off\n")
329			self.writeCommonOptions(f, 3)
330		f.write(indent(2) + "}\n") # pass
331		f.write(indent(1) + "}\n") # technique
332		return
333	def writeVertexColours(self, f):
334		# preconditions: VCOL_PAINT set
335		#
336		# ambient = Amb*White resp. Amb*VCol if "Coloured Ambient"
337		# diffuse = Ref*VCol
338		# specular = Spec*SpeRGB, Hard/4.0
339		# emissive = Emit*VCol
340		# alpha = A
341		#
342		# Best match without vertex shader:
343		# ambient = Amb*white
344		# diffuse = Ref*VCol
345		# specular = Spec*SpeRGB, Hard/4.0
346		# emissive = black
347		# alpha = 1
348		#
349		self.writeReceiveShadows(f, 1)
350		f.write(indent(1) + "technique\n" + indent(1) + "{\n")
351		if (self.material.mode & Blender.Material.Modes['SHADELESS']):
352			f.write(indent(2) + "pass\n" + indent(2) + "{\n")
353			self.writeCommonOptions(f,3)
354			f.write(indent(2) + "}\n")
355		else:
356			# vertex colour pass
357			f.write(indent(2) + "pass\n" + indent(2) + "{\n")
358			f.write(indent(3) + "ambient 0.0 0.0 0.0\n")
359			f.write(indent(3) + "diffuse vertexcolour\n")
360			self.writeCommonOptions(f, 3)
361			f.write(indent(2) + "}\n") # vertex colour pass
362
363			# factor pass
364			f.write(indent(2) + "pass\n" + indent(2) + "{\n")
365			f.write(indent(3) + "ambient 0.0 0.0 0.0\n")
366			ref = self.material.getRef()
367			f.write(indent(3) + "diffuse %f %f %f\n" % (ref, ref, ref))
368			f.write(indent(3) + "scene_blend modulate\n")
369			self.writeCommonOptions(f, 3)
370			f.write(indent(2) + "}\n") # factor pass
371
372			# ambient and specular pass
373			f.write(indent(2) + "pass\n" + indent(2) + "{\n")
374			self.writeAmbient(f, [1.0, 1.0, 1.0], 3)
375			f.write(indent(3) + "diffuse 0.0 0.0 0.0\n")
376			self.writeSpecular(f, 3)
377			f.write(indent(3) + "scene_blend add\n")
378			self.writeCommonOptions(f, 3)
379			f.write(indent(2) + "}\n") # specular pass
380
381		f.write(indent(1) + "}\n") # technique
382		return
383	def writeNormalMap(self, f):
384		# preconditions COL and NOR textures
385		colImage = self.manager.registerTextureFile(self.mTexUVCol.tex.image.filename)
386		norImage = self.manager.registerTextureFile(self.mTexUVNor.tex.image.filename)
387		f.write("""	technique
388	{
389		pass
390		{
391			ambient 1 1 1
392			diffuse 0 0 0
393			specular 0 0 0 0
394			vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture
395			{
396				param_named_auto worldViewProj worldviewproj_matrix
397				param_named_auto ambient ambient_light_colour
398			}
399		}
400		pass
401		{
402			ambient 0 0 0
403			iteration once_per_light
404			scene_blend add
405			vertex_program_ref Examples/BumpMapVPSpecular
406			{
407				param_named_auto lightPosition light_position_object_space 0
408				param_named_auto eyePosition camera_position_object_space
409				param_named_auto worldViewProj worldviewproj_matrix
410			}
411			fragment_program_ref Examples/BumpMapFPSpecular
412			{
413				param_named_auto lightDiffuse light_diffuse_colour 0
414				param_named_auto lightSpecular light_specular_colour 0
415			}
416			texture_unit
417			{
418				texture %s
419				colour_op replace
420			}
421			texture_unit
422			{
423				cubic_texture nm.png combinedUVW
424				tex_coord_set 1
425				tex_address_mode clamp
426			}
427			texture_unit
428			{
429				cubic_texture nm.png combinedUVW
430				tex_coord_set 2
431				tex_address_mode clamp
432			}
433		}
434		pass
435		{
436			lighting off
437			vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture
438			{
439				param_named_auto worldViewProj worldviewproj_matrix
440				param_named ambient float4 1 1 1 1
441			}
442			scene_blend dest_colour zero
443			texture_unit
444			{
445				texture %s
446			}
447		}
448	}
449	technique
450	{
451		pass
452		{
453			ambient 1 1 1
454			diffuse 0 0 0
455			specular 0 0 0 0
456			vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture
457			{
458				param_named_auto worldViewProj worldviewproj_matrix
459				param_named_auto ambient ambient_light_colour
460			}
461		}
462		pass
463		{
464			ambient 0 0 0
465			iteration once_per_light
466			scene_blend add
467			vertex_program_ref Examples/BumpMapVP
468			{
469				param_named_auto lightPosition light_position_object_space 0
470				param_named_auto eyePosition camera_position_object_space
471				param_named_auto worldViewProj worldviewproj_matrix
472			}
473			texture_unit
474			{
475				texture %s
476				colour_op replace
477			}
478			texture_unit
479			{
480				cubic_texture nm.png combinedUVW
481				tex_coord_set 1
482				tex_address_mode clamp
483				colour_op_ex dotproduct src_texture src_current
484				colour_op_multipass_fallback dest_colour zero
485			}
486		}
487		pass
488		{
489			lighting off
490			vertex_program_ref Ogre/BasicVertexPrograms/AmbientOneTexture
491			{
492				param_named_auto worldViewProj worldviewproj_matrix
493				param_named ambient float4 1 1 1 1
494			}
495			scene_blend dest_colour zero
496			texture_unit
497			{
498				texture %s
499			}
500		}
501	}
502""" % (norImage, colImage, norImage, colImage))
503		return
504	def writeReceiveShadows(self, f, indentation=0):
505		if (self.material.mode & Blender.Material.Modes["SHADOW"]):
506			f.write(indent(indentation)+"receive_shadows on\n")
507		else:
508			f.write(indent(indentation)+"receive_shadows off\n")
509		return
510	def writeAmbient(self, f, col, indentation=0):
511		# ambient <- amb * ambient RGB
512		ambR = clamp(self.material.getAmb() * col[0])
513		ambG = clamp(self.material.getAmb() * col[1])
514		ambB = clamp(self.material.getAmb() * col[2])
515		if len(col) < 4:
516			alpha = self.material.getAlpha()
517		else:
518			alpha = col[3]
519		f.write(indent(indentation)+"ambient %f %f %f %f\n" % (ambR, ambG, ambB, alpha))
520		return
521	def writeDiffuse(self, f, col, indentation=0):
522		# diffuse = reflectivity*colour
523		diffR = clamp(col[0] * self.material.getRef())
524		diffG = clamp(col[1] * self.material.getRef())
525		diffB = clamp(col[2] * self.material.getRef())
526		if len(col) < 4:
527			alpha = self.material.getAlpha()
528		else:
529			alpha = col[3]
530		f.write(indent(indentation)+"diffuse %f %f %f %f\n" % (diffR, diffG, diffB, alpha))
531		return
532	def writeSpecular(self, f, indentation=0):
533		# specular <- spec * specCol, hard/4.0
534		specR = clamp(self.material.getSpec() * self.material.getSpecCol()[0])
535		specG = clamp(self.material.getSpec() * self.material.getSpecCol()[1])
536		specB = clamp(self.material.getSpec() * self.material.getSpecCol()[2])
537		specShine = self.material.getHardness()/4.0
538		alpha = self.material.getAlpha()
539		f.write(indent(indentation)+"specular %f %f %f %f %f\n" % (specR, specG, specB, alpha, specShine))
540		return
541	def writeEmissive(self, f, col, indentation=0):
542		# emissive <-emit * rgbCol
543		emR = clamp(self.material.getEmit() * col[0])
544		emG = clamp(self.material.getEmit() * col[1])
545		emB = clamp(self.material.getEmit() * col[2])
546		if len(col) < 4:
547			alpha = self.material.getAlpha()
548		else:
549			alpha = col[3]
550		f.write(indent(indentation)+"emissive %f %f %f %f\n" % (emR, emG, emB, alpha))
551		return
552	def writeSceneBlend(self, f, indentation=0):
553		hasAlpha = 0
554		if (self.material.getAlpha() < 1.0):
555			hasAlpha = 1
556		else:
557			for mtex in self.material.getTextures():
558				if mtex:
559					if ((mtex.tex.type == Blender.Texture.Types['IMAGE'])
560						and (mtex.mapto & Blender.Texture.MapTo['ALPHA'])):
561						hasAlpha = 1
562		if (hasAlpha):
563			f.write(indent(indentation) + "scene_blend alpha_blend\n")
564			f.write(indent(indentation) + "depth_write off\n")
565		return
566	def writeCommonOptions(self, f, indentation=0):
567		# Shadeless, ZInvert, NoMist, Env
568		# depth_func  <- ZINVERT; ENV
569		if (self.material.mode & Blender.Material.Modes['ENV']):
570			f.write(indent(indentation)+"depth_func always_fail\n")
571		elif (self.material.mode & Blender.Material.Modes['ZINVERT']):
572			f.write(indent(indentation)+"depth_func greater_equal\n")
573		# twoside
574		if self.mesh.faceUV and (self.face.mode & Blender.NMesh.FaceModes['TWOSIDE']):
575			f.write(indent(3) + "cull_hardware none\n")
576			f.write(indent(3) + "cull_software none\n")
577		# lighting <- SHADELESS
578		if (self.material.mode & Blender.Material.Modes['SHADELESS']):
579			f.write(indent(indentation)+"lighting off\n")
580		# fog_override <- NOMIST
581		if (self.material.mode & Blender.Material.Modes['NOMIST']):
582			f.write(indent(indentation)+"fog_override true\n")
583		return
584	def writeDiffuseTexture(self, f, indentation = 0):
585		if self.mTexUVCol:
586			f.write(indent(indentation)+"texture_unit\n")
587			f.write(indent(indentation)+"{\n")
588			f.write(indent(indentation + 1) + "texture %s\n" % self.manager.registerTextureFile(self.mTexUVCol.tex.getImage().getFilename()))
589			self.writeTextureAddressMode(f, self.mTexUVCol, indentation + 1)
590			self.writeTextureFiltering(f, self.mTexUVCol, indentation + 1)
591			self.writeTextureColourOp(f, self.mTexUVCol, indentation + 1)
592			f.write(indent(indentation)+"}\n") # texture_unit
593		return
594	def writeTextureAddressMode(self, f, blenderMTex, indentation = 0):
595		# tex_address_mode inside texture_unit
596		#
597		# EXTEND   | clamp
598		# CLIP     |
599		# CLIPCUBE |
600		# REPEAT   | wrap
601		#
602		if (blenderMTex.tex.extend & Blender.Texture.ExtendModes['REPEAT']):
603			f.write(indent(indentation) + "tex_address_mode wrap\n")
604		elif (blenderMTex.tex.extend & Blender.Texture.ExtendModes['EXTEND']):
605			f.write(indent(indentation) + "tex_address_mode clamp\n")
606		return
607	def writeTextureFiltering(self, f, blenderMTex, indentation = 0):
608		# filtering inside texture_unit
609		#
610		# InterPol | MidMap | filtering
611		# ---------+--------+----------
612		#    yes   |   yes  | trilinear
613		#    yes   |   no   | linear linear none
614		#    no    |   yes  | bilinear
615		#    no    |   no   | none
616		#
617		if (blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['INTERPOL']):
618			if (blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']):
619				f.write(indent(indentation) + "filtering trilinear\n")
620			else:
621				f.write(indent(indentation) + "filtering linear linear none\n")
622		else:
623			if (blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']):
624				f.write(indent(indentation) + "filtering bilinear\n")
625			else:
626				f.write(indent(indentation) + "filtering none\n")
627		return
628	def writeTextureColourOp(self, f, blenderMTex, indentation = 0):
629		# colour_op inside texture_unit
630		if ((blenderMTex.tex.imageFlags & Blender.Texture.ImageFlags['USEALPHA'])
631			and not(blenderMTex.mapto & Blender.Texture.MapTo['ALPHA'])):
632			f.write(indent(indentation) + "colour_op alpha_blend\n")
633		return
634	# private
635	def _createName(self):
636		# must be called after _generateKey()
637		materialName = self.material.getName()
638		# two sided?
639		if self.mesh.faceUV and (self.face.mode & Blender.NMesh.FaceModes['TWOSIDE']):
640			materialName += '/TWOSIDE'
641		# use UV/Image Editor texture?
642		if ((self.key & self.TEXFACE) and not(self.key & self.IMAGEUVCOL)):
643			materialName += '/TEXFACE'
644			if self.face.image:
645				materialName += '/' + PathName(self.face.image.filename).basename()
646		return materialName
647	def _generateKey(self):
648		# generates key and populates mTex fields
649		if self.material:
650			if not(self.material.mode & Blender.Material.Modes['HALO']):
651				self.key |= self.NONHALO
652				if (self.material.mode & Blender.Material.Modes['VCOL_LIGHT']):
653					self.key |= self.VCOLLIGHT
654				if (self.material.mode & Blender.Material.Modes['VCOL_PAINT']):
655					self.key |= self.VCOLPAINT
656				if (self.material.mode & Blender.Material.Modes['TEXFACE']):
657					self.key |= self.TEXFACE
658				# textures
659				for mtex in self.material.getTextures():
660					if mtex:
661						if (mtex.tex.type == Blender.Texture.Types['IMAGE']):
662							if (mtex.texco & Blender.Texture.TexCo['UV']):
663								if (mtex.mapto & Blender.Texture.MapTo['COL']):
664									self.key |= self.IMAGEUVCOL
665									self.mTexUVCol = mtex
666								if (mtex.mapto & Blender.Texture.MapTo['NOR']):
667									# Check "Normal Map" image option
668									if (mtex.tex.imageFlags & 2048):
669										self.key |= self.IMAGEUVNOR
670										self.mTexUVNor = mtex
671									# else bumpmap
672								if (mtex.mapto & Blender.Texture.MapTo['CSP']):
673									self.key |= self.IMAGEUVCSP
674									self.mTexUVCsp = mtex
675		return
676	NONHALO = 1
677	VCOLLIGHT = 2
678	VCOLPAINT = 4
679	TEXFACE = 8
680	IMAGEUVCOL = 16
681	IMAGEUVNOR = 32
682	IMAGEUVCSP = 64
683	# material techniques export methods
684	TECHNIQUES = {
685		NONHALO|IMAGEUVCOL : writeColours,
686		NONHALO|IMAGEUVCOL|IMAGEUVCSP : writeColours,
687		NONHALO|TEXFACE : writeTexFace,
688		NONHALO|TEXFACE|VCOLLIGHT : writeTexFace,
689		NONHALO|TEXFACE|IMAGEUVCOL : writeTexFace,
690		NONHALO|TEXFACE|IMAGEUVNOR : writeTexFace,
691		NONHALO|TEXFACE|IMAGEUVCSP : writeTexFace,
692		NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVCOL : writeTexFace,
693		NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVNOR : writeTexFace,
694		NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVCSP : writeTexFace,
695		NONHALO|TEXFACE|IMAGEUVCOL|IMAGEUVCSP : writeTexFace,
696		NONHALO|TEXFACE|IMAGEUVNOR|IMAGEUVCSP : writeTexFace,
697		NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVCOL|IMAGEUVCSP : writeTexFace,
698		NONHALO|TEXFACE|VCOLLIGHT|IMAGEUVNOR|IMAGEUVCSP : writeTexFace,
699		NONHALO|VCOLPAINT : writeVertexColours,
700		NONHALO|VCOLPAINT|VCOLLIGHT : writeVertexColours,
701		NONHALO|IMAGEUVCOL|IMAGEUVNOR : writeNormalMap,
702		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT : writeNormalMap,
703		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT : writeNormalMap,
704		NONHALO|IMAGEUVCOL|IMAGEUVNOR|TEXFACE : writeNormalMap,
705		NONHALO|IMAGEUVCOL|IMAGEUVNOR|IMAGEUVCSP : writeNormalMap,
706		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT : writeNormalMap,
707		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|TEXFACE : writeNormalMap,
708		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|IMAGEUVCSP : writeNormalMap,
709		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT|TEXFACE : writeNormalMap,
710		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT|IMAGEUVCSP : writeNormalMap,
711		NONHALO|IMAGEUVCOL|IMAGEUVNOR|TEXFACE|IMAGEUVCSP : writeNormalMap,
712		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLPAINT|TEXFACE|IMAGEUVCSP : writeNormalMap,
713		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|TEXFACE|IMAGEUVCSP : writeNormalMap,
714		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT|IMAGEUVCSP : writeNormalMap,
715		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT|TEXFACE : writeNormalMap,
716		NONHALO|IMAGEUVCOL|IMAGEUVNOR|VCOLLIGHT|VCOLPAINT|TEXFACE|IMAGEUVCSP : writeNormalMap
717		}
718
719class CustomMaterial(RenderingMaterial):
720	def __init__(self, manager, blenderMesh, blenderFace, colouredAmbient, customMaterialTemplate):
721		self.mesh = blenderMesh
722		self.face = blenderFace
723		self.colouredAmbient = colouredAmbient
724		self.customMaterialTemplate = customMaterialTemplate
725		self.key = 0
726		self.mTexUVCol = None
727		self.mTexUVNor = None
728		self.mTexUVCsp = None
729		self.material = None
730		self.template = None
731		try:
732			self.material = self.mesh.materials[self.face.mat]
733		except IndexError:
734			Log.getSingleton().logWarning("Can't get material for mesh \"%s\"! Are any materials linked to object instead of linked to mesh?" % self.mesh.name)
735		if self.material:
736			self._generateKey()
737			DefaultMaterial.__init__(self, manager, self._createName())
738		else:
739			DefaultMaterial.__init__(self, manager, 'None')
740		self.properties = self.material.properties
741		return
742	class Template(string.Template):
743		delimiter = '%'
744		idpattern = '[_a-z][_a-z0-9.]*'
745	def setupTemplate(self, templateString):
746		self.template = self.Template(templateString)
747		return
748	def write(self, f):
749		# fallback if we don't have a valid template set.
750		if not(self.template):
751			RenderingMaterial.write(self, f)
752			return
753
754		# do alpha check for scene blend and depth write flag.
755		self.hasAlpha = 0
756		if (self.material.alpha < 1.0):
757			self.hasAlpha = 1
758		else:
759			for mtex in self.material.getTextures():
760				if mtex:
761					if ((mtex.tex.type == Blender.Texture.Types['IMAGE'])
762						and (mtex.mapto & Blender.Texture.MapTo['ALPHA'])):
763						self.hasAlpha = 1
764
765		# setup dictionary.
766		templateDict = { \
767			'_materialName' : self.name, \
768			'_ambient' : self._getAmbient(), \
769			'_diffuse' : self._getDiffuse(), \
770			'_specular' : self._getSpecular(), \
771			'_emisive' : self._getEmisive(), \
772			'_scene_blend' : self._getSceneBlend(), \
773			'_depth_write' : self._getDepthWrite(), \
774			'_depth_func' : self._getDepthFunc(), \
775			'_receive_shadows' : self._getReceiveShadows(), \
776			'_culling' : self._getCulling(), \
777			'_lighting' : self._getLighting(), \
778			'_fog_override' : self._getFogOverride()}
779
780		textureIndex = 0
781		for mtex in self.material.getTextures():
782			if mtex:
783				if (mtex.tex.type == Blender.Texture.Types['IMAGE']):
784					textureName = mtex.tex.getName()
785					name, ext = Blender.sys.splitext(textureName)
786					if ext.lstrip('.').isdigit() : textureName = name
787
788					# alternate texture index access
789					textureIndexName = '_tex[%d]' % textureIndex
790					++textureIndex
791
792					if mtex.tex.getImage():
793						textureFilename = self.manager.registerTextureFile(mtex.tex.getImage().getFilename())
794						templateDict[textureName + '._texture'] = textureFilename
795						templateDict[textureIndexName + '._texture'] = textureFilename
796					if mtex.tex.extend & Blender.Texture.ExtendModes['REPEAT']:
797						templateDict[textureName + '._tex_address_mode'] = 'wrap'
798						templateDict[textureIndexName + '._tex_address_mode'] = 'wrap'
799					else:
800						templateDict[textureName + '._tex_address_mode'] = 'clamp'
801						templateDict[textureIndexName + '._tex_address_mode'] = 'clamp'
802					if (mtex.tex.imageFlags & Blender.Texture.ImageFlags['INTERPOL']):
803						if (mtex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']):
804							templateDict[textureName + '._filtering'] = 'trilinear'
805							templateDict[textureIndexName + '._filtering'] = 'trilinear'
806						else:
807							templateDict[textureName + '._filtering'] = 'linear linear none'
808							templateDict[textureIndexName + '._filtering'] = 'linear linear none'
809					else:
810						if (mtex.tex.imageFlags & Blender.Texture.ImageFlags['MIPMAP']):
811							templateDict[textureName + '._filtering'] = 'bilinear'
812							templateDict[textureIndexName + '._filtering'] = 'bilinear'
813						else:
814							templateDict[textureName + '._filtering'] = 'none'
815							templateDict[textureIndexName + '._filtering'] = 'none'
816					if ((mtex.tex.imageFlags & Blender.Texture.ImageFlags['USEALPHA'])
817						and not(mtex.mapto & Blender.Texture.MapTo['ALPHA'])):
818						templateDict[textureName + '._colour_op'] = 'alpha_blend'
819						templateDict[textureIndexName + '._colour_op'] = 'alpha_blend'
820					else:
821						templateDict[textureName + '._colour_op'] = 'modulate'
822						templateDict[textureIndexName + '._colour_op'] = 'modulate'
823					templateDict[textureName + '._sizeX'] = "%.6g" % mtex.size[0]
824					templateDict[textureIndexName + '._sizeX'] = "%.6g" % mtex.size[0]
825					templateDict[textureName + '._sizeY'] = "%.6g" % mtex.size[1]
826					templateDict[textureIndexName + '._sizeY'] = "%.6g" % mtex.size[1]
827					templateDict[textureName + '._sizeZ'] = "%.6g" % mtex.size[2]
828					templateDict[textureIndexName + '._sizeZ'] = "%.6g" % mtex.size[2]
829					templateDict[textureName + '._offsetX'] = "%.6g" % mtex.ofs[0]
830					templateDict[textureIndexName + '._offsetX'] = "%.6g" % mtex.ofs[0]
831					templateDict[textureName + '._offsetY'] = "%.6g" % mtex.ofs[1]
832					templateDict[textureIndexName + '._offsetY'] = "%.6g" % mtex.ofs[1]
833					templateDict[textureName + '._offsetZ'] = "%.6g" % mtex.ofs[2]
834					templateDict[textureIndexName + '._offsetZ'] = "%.6g" % mtex.ofs[2]
835		try:
836			propertyGroup = self.material.properties['properties']
837			self._parseProperties(templateDict, propertyGroup)
838		except KeyError:
839			pass
840
841		f.write(self.template.safe_substitute(templateDict))
842
843		return
844	def _getAmbient(self):
845		if self.colouredAmbient:
846			amb = self.material.amb
847			ambR = clamp(self.material.R * amb)
848			ambG = clamp(self.material.G * amb)
849			ambB = clamp(self.material.B * amb)
850			return "%.6g %.6g %.6g" % (ambR, ambG, ambB)
851		amb = self.material.amb
852		return "%.6g %.6g %.6g" % (amb, amb, amb)
853	def _getDiffuse(self):
854		return "%.6g %.6g %.6g %.6g" % (self.material.R, self.material.G, self.material.B, self.material.alpha)
855	def _getSpecular(self):
856		spec = self.material.spec
857		specR = clamp(self.material.specR * spec)
858		specG = clamp(self.material.specG * spec)
859		specB = clamp(self.material.specB * spec)
860		shininess = self.material.getHardness() / 4.0
861		return "%.6g %.6g %.6g %.6g" % (specR, specG, specB, shininess)
862	def _getEmisive(self):
863		emit = self.material.emit
864		emitR = clamp(self.material.R * emit)
865		emitG = clamp(self.material.G * emit)
866		emitB = clamp(self.material.B * emit)
867		return "%.6g %.6g %.6g" % (emitR, emitG, emitB)
868	def _getSceneBlend(self):
869		if (self.hasAlpha):
870			return 'alpha_blend'
871		return 'one zero'
872	def _getDepthWrite(self):
873		if (self.hasAlpha):
874			return 'off'
875		return 'on'
876	def _getDepthFunc(self):
877		if (self.material.mode & Blender.Material.Modes['ENV']):
878			return 'always_fail'
879		elif (self.material.mode & Blender.Material.Modes['ZINVERT']):
880			return 'greater_equal'
881		return 'less_equal'
882	def _getReceiveShadows(self):
883		if (self.material.mode & Blender.Material.Modes["SHADOW"]):
884			return 'on'
885		return 'off'
886	def _getCulling(self):
887		if self.mesh.faceUV and (self.face.mode & Blender.Mesh.FaceModes['TWOSIDE']):
888			return 'none'
889		return 'clockwise'
890	def _getLighting(self):
891		if (self.material.mode & Blender.Material.Modes['SHADELESS']):
892			return 'off'
893		return 'on'
894	def _getFogOverride(self):
895		if (self.material.mode & Blender.Material.Modes['NOMIST']):
896			return 'true'
897		return 'false'
898	def _parseProperties(self, templateDict, propertyGroup, parent=None):
899		if type(propertyGroup) is Blender.Types.IDGroupType:
900			for propName, propValue in propertyGroup.iteritems():
901				if parent : propName = parent + '.' + propName
902				propType = type(propValue)
903				if propType is Blender.Types.IDArrayType:
904					arraySize = propValue.__len__()
905					propValueString = "%.6g" % propValue.__getitem__(0)
906					i = 1
907					while i < arraySize:
908						propValueString += " %.6g" % propValue.__getitem__(i)
909						i += 1
910					templateDict[propName] = propValueString
911				elif propType is Blender.Types.IDGroupType:
912					self._parseProperties(templateDict, propValue, propName)
913				elif propType is float:
914					templateDict[propName] = "%.6g" % propValue
915				else:
916					templateDict[propName] = propValue
917		return
918
919class MaterialManager:
920	"""Manages database of material definitions.
921	"""
922	#TODO append to existing material script
923	def __init__(self, dir=None, file=None, gameEngineMaterial=False, customMaterial=False, customMaterialTplPath=None, requireFaceMats=True):
924		"""Constructor.
925
926		   @param path Directory to the material file. Default is directory of the last file read or written with Blender.
927		   @param file Material script file. Default is current scene name with ".material" prefix.
928		"""
929		self.dir = dir or Blender.sys.dirname(Blender.Get('filename'))
930		self.file = file or (Blender.Scene.GetCurrent().getName() + ".material")
931		self.gameEngineMaterial = gameEngineMaterial
932		self.customMaterial = customMaterial
933		self.customMaterialTplPath = customMaterialTplPath
934		# key=name, value=material
935		self.materialsDict = {}
936		# key=basename, value=path
937		self.textureFilesDict = {}
938		if requireFaceMats:
939			self.noMatWarned = set()
940			# We only warn once for each blender Obj X mesh
941			# This set keeps track of what we have issued warnings for already
942		else:
943			self.noMatWarned = None
944		return
945	def getMaterial(self, bMesh, bMFace, colouredAmbient, objName):
946		"""Returns material of a given face or <code>None</code>.
947		"""
948		## get name of export material for Blender's material settings of that face.
949		# The objName param provides for precise and optimally concise
950		# error messages
951		faceMaterial = None
952		if self.gameEngineMaterial:
953			if bMesh.faceUV and not(bMFace.mode & Blender.Mesh.FaceModes['INVISIBLE']):
954				if (bMFace.image and (bMFace.mode & Blender.Mesh.FaceModes['TEX'])):
955					# image texture
956					faceMaterial = GameEngineMaterial(self, bMesh, bMFace, colouredAmbient)
957			else:
958				# material only
959				if (bMesh.materials == None
960						or bMFace.mat >= len(bMesh.materials)
961						or not bMesh.materials[bMFace.mat]):
962					if self.noMatWarned != None and (
963							objName + '|' + bMesh.name) not in self.noMatWarned:
964						Log.getSingleton().logError(('Face(s) without eng. '
965								+ 'material assignment in obj "%s", mesh "%s"!')
966								% (objName, bMesh.name))
967						self.noMatWarned.add(objName + '|' + bMesh.name)
968					faceMaterial = DefaultMaterial(self, 'BaseWhite')
969				else:
970					faceMaterial = GameEngineMaterial(self, bMesh, bMFace, colouredAmbient)
971		elif self.customMaterial:
972			if (bMesh.materials == None or bMFace.mat >= len(bMesh.materials)
973					or not bMesh.materials[bMFace.mat]):
974				if self.noMatWarned != None and (
975						objName + '|' + bMesh.name) not in self.noMatWarned:
976					Log.getSingleton().logError(('Face(s) without custom '
977							+ 'material assignment in obj "%s", mesh "%s"!')
978							% (objName, bMesh.name))
979					self.noMatWarned.add(objName + '|' + bMesh.name)
980				faceMaterial = DefaultMaterial(self, 'BaseWhite')
981			else:
982				faceMaterial = CustomMaterial(self, bMesh, bMFace, colouredAmbient, self.customMaterialTplPath)
983		else:
984			# rendering material
985			if (bMesh.materials == None or bMFace.mat >= len(bMesh.materials)
986					or not bMesh.materials[bMFace.mat]):
987				if self.noMatWarned != None and (
988						objName + '|' + bMesh.name) not in self.noMatWarned:
989					Log.getSingleton().logError(('Face(s) without '
990							+ 'material assignment in obj "%s", mesh "%s"!')
991							% (objName, bMesh.name))
992					self.noMatWarned.add(objName + '|' + bMesh.name)
993				faceMaterial = DefaultMaterial(self, 'BaseWhite')
994			else:
995				faceMaterial = RenderingMaterial(self, bMesh, bMFace, colouredAmbient)
996		## return material or None
997		material = None
998		if faceMaterial:
999			if not(self.materialsDict.has_key(faceMaterial.getName())):
1000				self.materialsDict[faceMaterial.getName()] = faceMaterial
1001			material = self.materialsDict[faceMaterial.getName()]
1002		return material
1003	def registerTextureFile(self, path):
1004		"""Register texture for export.
1005
1006		   @param path Texture file path, i.e. dirname and basename.
1007		   @return Basename of texture file.
1008		"""
1009		texturePath = PathName(path)
1010		key = texturePath.basename()
1011		if self.textureFilesDict.has_key(key):
1012			if (path != self.textureFilesDict[key]):
1013				Log.getSingleton().logWarning('Texture filename conflict: \"%s\"' % key)
1014				Log.getSingleton().logWarning(' Location: \"%s\"' % path)
1015				Log.getSingleton().logWarning(' Conflicting location: \"%s\"' % self.textureFilesDict[key])
1016		self.textureFilesDict[key] = path
1017		return key
1018	def export(self, dir=None, file=None, copyTextures=False):
1019		exportDir = dir or self.dir
1020		exportFile = file or self.file
1021		Log.getSingleton().logInfo("Exporting materials \"%s\"" % exportFile)
1022		f = open(Blender.sys.join(exportDir, exportFile), "w")
1023
1024		if self.customMaterial :
1025			# set of material import lines.
1026			templateImportSet = set()
1027			# map of template strings.
1028			templateStringDict = {}
1029			for material in self.materialsDict.values():
1030				if isinstance(material, CustomMaterial):
1031					try:
1032						template = material.properties['template']
1033						if type(template) is not str:
1034							Log.getSingleton().logWarning("==========================================================")
1035							Log.getSingleton().logWarning("Material \"%s\" not assigned to a valid template!" % material.getName())
1036							Log.getSingleton().logWarning("Falling back to Rendering Material mode for this material.")
1037						else:
1038							if not(templateStringDict.has_key(template)) :
1039								Log.getSingleton().logInfo("Loading template \"%s\"" % template)
1040								templateFile = Blender.sys.join(self.customMaterialTplPath, template + '.tpl')
1041								if not(Blender.sys.exists(templateFile) == 1):
1042									Log.getSingleton().logWarning("==========================================================")
1043									Log.getSingleton().logWarning("Material \"%s\" assigned to unknown template \"%s\"!" % (material.getName(), template))
1044									Log.getSingleton().logWarning("The template file \"%s\" does not exist." % templateFile)
1045									Log.getSingleton().logWarning("Falling back to Rendering Material mode for this material.")
1046									templateStringDict[template] = None
1047								else:
1048									t = open(templateFile, 'r')
1049									templateImportSet.add(t.readline())
1050									templateStringDict[template] = t.read()
1051									t.close()
1052							if (templateStringDict[template]):
1053								material.setupTemplate(templateStringDict[template])
1054					except KeyError:
1055						Log.getSingleton().logWarning("==========================================================")
1056						Log.getSingleton().logWarning("Material \"%s\" is not assigned to a template!" % (material.getName()))
1057						Log.getSingleton().logWarning("Falling back to Rendering Material mode for this material.")
1058			# write import lines.
1059			for importString in templateImportSet:
1060				f.write(importString)
1061
1062		Log.getSingleton().logInfo("Begin writing materials:")
1063		for material in self.materialsDict.values():
1064			Log.getSingleton().logInfo("  %s" % (material.getName()))
1065			material.write(f)
1066		f.close()
1067		if copyTextures and os.path.exists(dir):
1068			baseDirname = os.path.dirname(Blender.Get("filename"))
1069			for path in self.textureFilesDict.values():
1070				# convert Blender's relative paths "//" to absolute path
1071				if (path[0:2] == "//"):
1072					Log.getSingleton().logInfo("Converting relative image name \"%s\"" % path)
1073					path = os.path.join(baseDirname, path[2:])
1074				if os.path.exists(path):
1075					# copy texture to dir
1076					Log.getSingleton().logInfo("Copying texture \"%s\"" % path)
1077					try:
1078						shutil.copy(path, dir)
1079					except (IOError, OSError), detail:
1080						Log.getSingleton().logError("Error copying \"%s\": %s" % (path, str(detail)))
1081				else:
1082					Log.getSingleton().logWarning("Can't copy texture \"%s\" because file does not exists!" % path)
1083		return
1084