#!BPY

"""
Name: 'VR Object'
Blender: 240
Group: 'Animation'
Tooltip: 'Create Camera and FrameChange Script for VR Objects'
"""

__author__ = "Mitch Hughes (lobo_nz)"
__url__ = ("blender", "Author's homepage, http://blender.formworks.co.nz")
__version__ = "0.3"

__bpydoc__ = """\
"VR Object" creates cameras and a FrameChanged script to animate
them to produce the frames required to make a VR Object.

Usage:

Add the script to your blender/scripts directory
Run this script from the Scripts window, Scripts->Animation->VR Object
Choose the number of Columns, start and end angles.
Choose the number of rows, start and end angles.
Click Generate to build the rig, you can then play the animation
viewing through the active camera to make sure your object stays
in frame, you can easily move the rig by moving the Empty, which the
camera is are parented to, and scaling the empty has the effect of zooming
in/out
"""

# $Id: vr_object.py,v 0.3 2006/06/01 19:08:10$
#
# --------------------------------------------------------------------------
# VR Object by Mitch Hughes (AKA lobo_nz)
# --------------------------------------------------------------------------
# ***** BEGIN GPL LICENSE BLOCK *****
#
# 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
from Blender import Scene, Object, Scene, Camera, Window, Text, Draw, BGL
from Blender.Window import *
import math

g_identifier = Draw.Create("TEST")

g_x_steps = Draw.Create(36) # Frames in horizontal movement
g_y_steps = Draw.Create(10) # Frames in vertical movement

g_x_start = Draw.Create(0) #starting horizonatal angle
g_x_total = Draw.Create(360) #ending horizonatal angle

g_y_start = Draw.Create(-45) #starting horizonatal angle
g_y_end = Draw.Create(45) #ending horizonatal angle

g_EmptyName = Draw.Create('VR_TARGET')
g_CameraBaseName = Draw.Create('VR_CAM')
g_CameraDataName = Draw.Create('VRCAMDATA')
g_vr_scriptname = Draw.Create('VR_ROTATOR')


g_sphere_radius = Draw.Create(3) #path radius

g_scene = Scene.getCurrent()

# Events
EVENT_NOEVENT=1
EVENT_GENERATE=2
EVENT_EXIT=100

def setup_render():
	global g_x_steps, g_y_steps, g_scene
	#set the end frame of the animation
	context = g_scene.getRenderingContext()
	context.startFrame(1)
	context.endFrame(g_x_steps.val*g_y_steps.val)

def rad2deg(v):
	return round(v*180.0/math.pi,4)
def deg2rad(v):
	return (v*math.pi)/180.0;

def pos(number):
	if number < 0:
		return number * -1
	else:
		return number

def get_x(angle):
	global g_sphere_radius
	height = math.sin(deg2rad(angle)) * g_sphere_radius.val
	return height


def get_radius(height):
	global g_sphere_radius
	#calculate radius at height
	return  math.sqrt((g_sphere_radius.val*g_sphere_radius.val) - (pos(height)*pos(height)))

	
def vr_generate():
	global g_x_steps, g_y_steps, g_x_start, g_x_total, g_y_total, g_y_start, g_y_end
	global g_EmptyName, g_CameraBaseName, g_vr_scriptname, g_sphere_radius, g_scene
	
	if g_y_steps.val > 1:
		y_step = (pos(g_y_start.val) + pos(g_y_end.val))/(g_y_steps.val-1)
	else:
		y_step = 0
	
	if g_y_end.val < g_y_start.val:
		y_step = y_step * -1
	
	current_y_angle = g_y_start.val

	heights = []
	x_val = []
	
	current_y_step = 0
	#calculate heights and x_val of camera paths
	while current_y_step < g_y_steps.val:
		#print "current_y_angle = ", current_y_angle
		#print "current_y_step = ", current_y_step
		
		heights.append(get_x(current_y_angle))
		x_val.append(get_radius(heights[current_y_step]))
		
		current_y_angle = current_y_angle + y_step
		current_y_step = current_y_step + 1
	
		
	#Make empty that we use as the object centre
	try:
		#check for empty and use existing one if found
		empty = Blender.Object.Get (g_EmptyName.val) #use Existing
		empty.RotX = 0
		empty.RotY = 0
		empty.RotZ = 0
	except:
		#Make Empty
		empty = Object.New('Empty',g_EmptyName.val)  # make empty object
		g_scene.link(empty)

	#Make Cameras and Target them to the empty
	try:
		#Check for camera and use existing one if found
		theCam = Blender.Camera.Get (g_CameraDataName.val) #use Existing
	except:
		#Make New Camera
		theCam = Camera.New ('persp',g_CameraDataName.val)# make perspective camera data object 
	
	#Make Camera Object
	try:
		#Check for camera object and use existing one if found
		camera_object = Blender.Object.Get (g_CameraBaseName.val) #use Existing
	except:
		#Make a new Camera Object
		camera_object = Object.New('Camera', g_CameraBaseName.val) # make camera object
	
	camera_object.link(theCam) # link camera data with the object
	try:
		g_scene.link (camera_object) # link the object into the scene
	except:
		assume_its_allready_linked = 1
	
	camera_object.setLocation(0,-x_val[0],heights[0])
	Window.Redraw()

	#parent cameras to empty
	empty.makeParent([camera_object],0,0)\
	#set the empty to the starting angle
	empty.setEuler([0,0,deg2rad(g_x_start.val)])
	
	#write a rotator script for the empty
	try:
		#Check for rotator script text object and use existing one if found
		txt = Blender.Text.Get (g_vr_scriptname.val) #use Existing
		txt.clear()
	except:
		#Make a new Camera Object
		txt = Blender.Text.New(g_vr_scriptname.val) # make txt object

	txt.write("import Blender\nfrom Blender import Scene, Mathutils\nfrom math import *\n")
	
	txt.write("""def vecFrom2Points(a,b):
	return Mathutils.Vector(a[0]-b[0], a[1]-b[1], a[2]-b[2])

def deg2rad(v):
	return (v*pi)/180.0;

def target_obj(shooter, target):
	
	#rotate about Track axis until Up axis points to target
	#In this case Z until we make this configurable
	
	# Vector from the shooter to the targets xz plane 
	S_S_xz = vecFrom2Points(shooter.loc, [shooter.LocX, 0, shooter.LocZ])
	# Vector from the shooter to the target along xy plane
	T_S_xy = vecFrom2Points(shooter.loc, [0,0, shooter.LocZ])
	# Angle to turn to aim towards the target
	try:
		z_angle = Mathutils.AngleBetweenVecs(S_S_xz, T_S_xy)
	except:
		z_angle = 0
	
	# Corrections for calculated angle based on position
	if shooter.LocX < 0 and shooter.LocY > 0:
		z_angle = 180+z_angle
	elif shooter.LocY > 0:
		z_angle = 180-z_angle
	elif shooter.LocX < 0:
		z_angle = 360-z_angle
		
	shooter.RotZ = deg2rad(z_angle)
	
	#rotate about axis perpendicular to Track and Up axes
	#In this case X until we make this configurable
	
	# Vector from the shooter to the targets xy plane 
	S_S_xy = vecFrom2Points(shooter.loc, [shooter.LocX, shooter.LocY, 0])
	# Vector from the shooter to the target
	T_S_xy = vecFrom2Points(shooter.loc, [0,0,0])
	# Angle to turn to aim towards the target
	try:
		x_angle = Mathutils.AngleBetweenVecs(S_S_xy,T_S_xy)
	except:
		x_angle = 90
		
	# Corrections for calculated angle based on position
	if shooter.LocX < 0 and shooter.LocZ > 0:
		x_angle = 360+x_angle
	elif shooter.LocZ < 0:
		x_angle = 180-x_angle
			
	shooter.RotX = deg2rad(x_angle)
""")
	
	txt.write("scene = Scene.getCurrent()\n")
	
	txt.write("camera = Blender.Object.Get('"+g_CameraBaseName.val+"')\n")
	
	txt.write("x_val = [")
	for xs in x_val:
		txt.write( str(xs) + ", ")
	txt.write("]\n")
	
	txt.write("heights = [")
	for height in heights:
		txt.write( str(height) + ", ")
	txt.write("]\n")
	
	txt.write("frame=Blender.Get('curframe')\n")
	
	txt.write("y_step = "+str(deg2rad(y_step))+"\n")
	txt.write("y_start = "+str(deg2rad(g_y_start.val))+"\n")
	
	if g_x_steps.val > 1:
		x_step = g_x_total.val/(g_x_steps.val-1)
	else:
		x_step = 0
		g_x_total.val = 0
		
	txt.write("x_step = "+str(deg2rad(x_step))+"\n")
	txt.write("x_steps = "+str(g_x_steps.val)+"\n")
	
	txt.write("x_start = "+str(deg2rad(g_x_start.val))+"\n")
	txt.write("x_total = "+str(deg2rad(g_x_total.val))+"\n")
	
	txt.write("empty = Blender.Object.Get('"+str(g_EmptyName.val)+"') #get the vr empty\n")
	txt.write("emptyEuler = empty.getEuler()\n")
	
	txt.write("revolution = (frame-1)/x_steps\n\n")
	txt.write("frames_this_rev = (frame-1)%x_steps\n\n")
	
	txt.write("try:\n")
	txt.write("	camera.setLocation(0,x_val[revolution],heights[revolution])\n")
	txt.write("	emptyEuler[2] = (frames_this_rev * x_step) + x_start\n")
	txt.write("	empty.setEuler(emptyEuler)\n")
	txt.write("except:\n")
	txt.write("	camera.setLocation(0,x_val[-1],heights[-1])\n")
	txt.write("target_obj(camera, empty)\n") 
	
	#Link the script to the scene
	#check if scriplink allready exists
	script_link_exists = 0
	script_links = g_scene.getScriptLinks('FrameChanged')
	if script_links != None:
		for script_link in script_links:
			if script_link == g_vr_scriptname.val:
				script_link_exists = 1
	#create scriptlink if needed
	if script_link_exists == 0:
		g_scene.addScriptLink(g_vr_scriptname.val,'FrameChanged')
		
	setup_render()
	
def DRAWX(val):
	global g_drawx
	if val >= 0:
		g_drawx=g_drawx-val
		return g_drawx
	else:
		g_drawx = val*-1
		return g_drawx
######################################################
# GUI Loader
######################################################
def draw_gui():
	global EVENT_NOEVENT,EVENT_GENERATE,EVENT_EXIT
	global g_x_steps, g_y_steps, g_x_start, g_x_total, g_y_start, g_y_end
	global g_EmptyName, g_CameraBaseName, g_vr_scriptname, g_sphere_radius, g_scene
	
	DRAWX(-280)
	
	########## Titles
	BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
	BGL.glRasterPos2d(8, DRAWX(20))
	Draw.Text("			VR Object Camera Generator		 ")
	BGL.glRasterPos2d(8, DRAWX(20))
	Draw.Text("1) Specify Rows, Columns and Angle Limits")
	#BGL.glRasterPos2d(8, DRAWX(0))
	
	#Slider(name, event, x, y, width, height, initial, min, max, realtime=1, tooltip=None)
	g_x_steps = Draw.Slider("Columns: ", EVENT_NOEVENT, 10, DRAWX(30), 250, 18, g_x_steps.val, 1, 60, 1, "Number of frames per revolution")
	g_x_start = Draw.Slider("Angle Start: ", EVENT_NOEVENT, 10, DRAWX(20), 250, 18, g_x_start.val, -180, 180, 1, "Start angle for revolution")
	g_x_total = Draw.Slider("Angle Total: ", EVENT_NOEVENT, 10, DRAWX(20), 250, 18,   g_x_total.val, -360, 360, 1, "Total angle of revolution")
	
	g_y_steps = Draw.Slider("Rows: ", EVENT_NOEVENT, 10, DRAWX(30), 250, 18, g_y_steps.val, 1, 60, 1, "Number of frames in Vertical movement")
	g_y_start = Draw.Slider("Angle Start: ", EVENT_NOEVENT, 10, DRAWX(20), 250, 18, g_y_start.val, -90, 90, 1, "Start angle for Vertical movement (0 is horizontal)")
	g_y_end   = Draw.Slider("Angle End: ", EVENT_NOEVENT, 10, DRAWX(20), 250, 18,   g_y_end.val, -90, 90, 1, "End angle for Vertical movement (0 is horizontal)")

	g_sphere_radius   = Draw.Slider("Sphere Radius: ", EVENT_NOEVENT, 10, DRAWX(40), 250, 18,   g_sphere_radius.val, 1, 10, 1, "Radius of the sphere the cameras sit on")
	
	BGL.glRasterPos2d(8, DRAWX(25))
	Draw.Text("2) Press Generate")

	######### Draw and Exit Buttons
	Draw.Button("Generate",EVENT_GENERATE , 10, DRAWX(30), 80, 18)
	Draw.Button("Exit",EVENT_EXIT , 180, DRAWX(0), 80, 18)

def event(evt, val):
	if (evt == Draw.QKEY and not val):
		Draw.Exit()

def bevent(evt):
	global EVENT_NOEVENT,EVENT_GENERATE,EVENT_EXIT

	######### Manages GUI events
	if (evt==EVENT_EXIT):
		Draw.Exit()
	elif (evt==EVENT_GENERATE):
		vr_generate()
		Blender.Run(g_vr_scriptname.val)
		Draw.Exit()

Draw.Register(draw_gui, event, bevent)

