#!BPY
"""
Name: 'Billboard Render on Active'
Blender: 242
Group: 'Image'
Tooltip: 'Selected objects and lamps to rendered faces on the act mesh'
""" 
__author__= "Campbell Barton"
__url__= ["blender", "blenderartist"]
__version__= "1.0"

__bpydoc__= """\
Render Billboard Script
This can texture a simple billboard mesh from any number of selected objects.

Renders objects in the selection to quad faces on the active mesh.

Usage
* Light your model or enable the shadless flag so it is visible
* Make a low poly mesh out of quads with 90d corners. (this will be you billboard mesh)
* Select the model and any lamps that light it
* Select the billboard mesh so that it is active
* Run this script, Adjust settings such as image size or oversampling.
* Select a place to save the PNG image.
* Once the script has finished running return to the 3d view by pressing Shift+F5
* To see the newly applied textures change the drawtype to 'Textured Solid'
"""
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C) Campbell J Barton 2006
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------

import Blender as B
import BPyMathutils
# reload(BPyMathutils)
import BPyRender
reload(BPyRender)
import boxpack2d
# reload(boxpack2d) # for developing.
from Blender.Scene import Render

import os
Vector= B.Mathutils.Vector

def alpha_mat(image):
	# returns a material useable for 
	mtl= B.Material.New()
	mtl.mode |= (B.Material.Modes.SHADELESS | B.Material.Modes.ZTRANSP | B.Material.Modes.FULLOSA )
	mtl.alpha= 0.0 # so image sets the alpha
	
	tex= B.Texture.New()
	tex.type= B.Texture.Types.IMAGE
	image.antialias = True
	tex.setImageFlags('InterPol', 'UseAlpha')
	tex.setExtend('Clip')
	tex.image= image
	
	mtl.setTexture(0, tex, B.Texture.TexCo.UV, B.Texture.MapTo.COL | B.Texture.MapTo.ALPHA)
	
	return mtl

# PupBlock Settings
GLOBALS= {}
PREF_RES= B.Draw.Create(512)
PREF_TILE_RES= B.Draw.Create(256)
PREF_AA = B.Draw.Create(1)
PREF_ALPHA= B.Draw.Create(1)
PREF_Z_OFFSET = B.Draw.Create(10.0)
PREF_IMG_PACK= B.Draw.Create(1)


def save_billboard(PREF_IMAGE_PATH):
	B.Window.WaitCursor(1)
	# remove png, add it later
	PREF_IMAGE_PATH= PREF_IMAGE_PATH.replace('.png', '')
	
	ob_sel= GLOBALS['ob_sel']
	me_ob = GLOBALS['me_ob']
	me_data = GLOBALS['me_data']
	
	time= B.sys.time()
	
	me_mat= me_ob.matrixWorld
	
	# Render images for all faces
	face_data= [] # Store faces, images etc
	boxes2Pack= []
	me_data.faceUV= True
		
	for i, f in enumerate(me_data.faces):
		no= f.no
		# Offset the plane by the zoffset on the faces normal
		plane= [v.co * me_mat for v in f]
		
		# Horizontal stacking, make sure 0,1 and 2,3 are the longest
		if\
		(plane[0]-plane[1]).length + (plane[2]-plane[3]).length < \
		(plane[1]-plane[2]).length + (plane[3]-plane[0]).length:
			plane.append(plane.pop(0))
			rot90= True
		else:
			rot90= False
		
		
		#plane.reverse()
		no= B.Mathutils.QuadNormal(*plane)
		plane= [v + no*PREF_Z_OFFSET.val for v in plane]
		
		cent= (plane[0]+plane[1]+plane[2]+plane[3] ) /4.0
		camera_matrix= BPyMathutils.plane2mat(plane)
		tmp_path= '%s_%d' % (PREF_IMAGE_PATH, i)
		img= BPyRender.imageFromObjectsOrtho(ob_sel, tmp_path, PREF_TILE_RES.val, PREF_TILE_RES.val, PREF_AA.val, PREF_ALPHA.val, camera_matrix)
		# img.reload()
		#img.pack() # se we can keep overwriting the path
		#img.filename= ""
		
		
		if not PREF_IMG_PACK.val:
			f.mode |= B.Mesh.FaceModes.TEX
			f.image = img
			f.uv=Vector(0,1), Vector(0,0), Vector(1,0), Vector(1,1)
			
			if PREF_ALPHA.val:
				f.transp |= B.Mesh.FaceTranspModes.ALPHA
		else:
			w= ((plane[0]-plane[1]).length + (plane[2]-plane[3]).length)/2
			h= ((plane[1]-plane[2]).length + (plane[3]-plane[0]).length)/2
			
			face_data.append( (f, img, rot90) )
			boxes2Pack.append( (i, h, w) )
	
	if PREF_IMG_PACK.val:
		# pack the quads into a square
		
		packWidth, packHeight, packedLs = boxpack2d.boxPackIter(boxes2Pack)
		render_obs= []
		
		# Add geometry to the mesh
		for box in packedLs:
			i= box[0]
			
			orig_f, img, rot90= face_data[i]
			
			# New Mesh and Object
			render_mat= alpha_mat(img)
			
			render_me= B.Mesh.New()
			render_ob= B.Object.New('Mesh')
			render_me.materials= [render_mat]
			render_ob.link(render_me)
			
			render_obs.append(render_ob)
			
			# Add verts clockwise from the bottom left.
			_x= box[1] / packWidth
			_y= box[2] / packHeight
			_w= box[3] / packWidth
			_h= box[4] / packHeight
			
			
			render_me.verts.extend([\
			Vector(_x, _y, 0),\
			Vector(_x, _y +_h, 0),\
			Vector(_x + _w, _y +_h, 0),\
			Vector(_x + _w, _y, 0),\
			])
			
			render_me.faces.extend(list(render_me.verts))
			render_me.faceUV= True
			
			# target_face= render_me.faces[-1]
			# TEXFACE isnt used because of the renderign engine cant to alpha's for texdface.
			#target_face.image= img
			#target_face.mode |= B.Mesh.FaceModes.TEX
			
			# Set the UV's, we need to flip them HOZ?
			uv1, uv2, uv3, uv4= orig_f.uv
			uv3.x= uv4.x= _x+_w
			uv1.x= uv2.x= _x
			
			uv2.y= uv3.y= _y+_h
			uv1.y= uv4.y= _y
			
			if rot90:
				orig_f.uv= Vector(uv4), Vector(uv1), Vector(uv2), Vector(uv3)
			
		target_image= BPyRender.imageFromObjectsOrtho(render_obs, PREF_IMAGE_PATH, PREF_RES.val, PREF_RES.val, PREF_AA.val, PREF_ALPHA.val, None)
		
		# Set to the 1 image.
		for f in me_data.faces:
			f.image= target_image
			if PREF_ALPHA.val:
				f.transp |= B.Mesh.FaceTranspModes.ALPHA
		
		# Free the images data and remove
		for data in face_data:
			img= data[1]
			os.remove(img.filename)
			img.reload()
	# Finish pack
	
	me_data.update()
	me_ob.makeDisplayList()
	B.Window.WaitCursor(0)
	print '%.2f secs taken' % (B.sys.time()-time)
	



def main():
	scn= B.Scene.GetCurrent()
	ob_sel= list(scn.objects.context)
	
	PREF_KEEP_ASPECT= False
	
	
	# Error Checking
	if len(ob_sel) < 2:
		B.Draw.PupMenu("Error%t|Select 2 mesh objects")
		return
		
	me_ob= scn.objects.active
	
	if not me_ob:
		B.Draw.PupMenu("Error%t|No active mesh selected.")
	
	try:
		ob_sel.remove(me_ob)
	except:
		pass
	
	if me_ob.type != 'Mesh':
		B.Draw.PupMenu("Error%t|Active Object must be a mesh to write billboard images too")
		return
	
	me_data= me_ob.getData(mesh=1)
	
	for f in me_data.faces:
		if len(f) != 4:
			B.Draw.PupMenu("Error%t|Active mesh must have only quads")
			return
	
	
	
	
	# Get user input
	block = [\
	'Image Pixel Size',\
	("Packed Size: ", PREF_RES, 128, 2048, "Pixel width and height to render the billboard to"),\
	("Tile Size: ", PREF_TILE_RES, 64, 1024, "Pixel  width and height for each tile to render to"),\
	'Render Settings',\
	("Pack Final", PREF_IMG_PACK , "Pack all images into 1 image"),\
	("Oversampling", PREF_AA , "Higher quality woth extra sampling"),\
	("Alpha Clipping", PREF_ALPHA , "Render empty areas as transparent"),\
	("Cam ZOffset: ", PREF_Z_OFFSET, 0.1, 100, "Distance to place the camera away from the quad when rendering")\
	]
	
	if not B.Draw.PupBlock("Billboard Render", block):
		return
	
	# Set globals
	GLOBALS['ob_sel'] = ob_sel
	GLOBALS['me_ob'] = me_ob
	GLOBALS['me_data'] = me_data
	
	B.Window.FileSelector(save_billboard, 'SAVE BILLBOARD', B.sys.makename(ext='.png'))

if __name__=='__main__':
	main()
