-- Template: 'Template-Dialog.ms'

/*
tut_billboard_01.ms
"Billboard"
version 01
Written for 3dsmax r6
Written by swami*
07.13.03

PURPOSE:
- To realize the billboard effect: an object looks at another object, but only rotates
  about its World z-axis.
*/

--------------------------
--<< F U N C T I O N S >>
--------------------------
-- FUNCTION (makeBillboard)
-- 
-- This function realizes the billboard effect: an object looks at another object, but only rotates
-- about its World z-axis. This is accomplished by assigning a script controller to the object's rotation.
-- Through the use of a little trigonometry, the rotation is set.
-- The billboard's Local z-axis defines the front of the object. This convention is chosen to match the
-- orientation of objects created in the front view.
--
-- This function is based coded posted to the MAXScript WebBoard by Larry Minton.

fn makeBillboard
	obj		-- The billboard object.
	targ	-- The lookat object.
=
(
setWaitCursor()
-- Check to ensure the objects are defined and not deleted.
if obj!=undefined AND (NOT isDeleted obj) AND targ!=undefined AND (NOT isDeleted targ) then
(
-- Build string to be used in script controller on obj's rotation.
-- Notes:
--  - Script is in one big 'try/catch'. So, if any error occurs, the catch clause is executed,
--    and rotation is effectively set to [0,0,0].
--  - 'dependsOn' signals the controller to update when another controller changes.
--    In this case, the rotation changes when the position of the lookat object changes.
--    So, on a given frame, when the user moves the lookat object, the billboard rotates.
--  - An object name is put in single quotes (e.g. $'An Object Name') in case spaces or other
--    "problem" characters are contained in the name. Without the single quotes, an error will occur.
--  - The script controller returns a quaternion rotation value: see the lines containing
--    'eulerAngles' (pronounced "oiler angles").
--  - Basic trigonometry is used to calculate the rotation angle required to achieve the billboard effect.

local scriptStr=
"try(
dependsOn $'"+targ.name+"'.pos.controller

local d, dz, z_rot, len, ctrl

-- Get difference vector of obj and targ.
d=
try ($'"+obj.name+"'.pos-$'"+targ.name+"'.pos)
catch [0,0,0]

dz=d.z -- Store z value.
d.z=0 -- Set z to zero.
len=length d -- Vector length.

-- Calculate the z rotation (in degrees).
-- Note: len==0 => obj and targ are at same position!
z_rot=
if len!=0
	then -90+acos(d.x/len)*(if d.y!=0 then d.y/(abs d.y) else 1)
	else -90
(eulerAngles 90 0 z_rot) as quat
)catch ((eulerAngles 90 0 0) as quat)"

ctrl=obj.rotation.controller=rotation_script() -- Assign rotation script controller.
setTimeRange ctrl (interval -1000 10000) -- Set script controller's range bar to wide range, in case user expands range later.
ctrl.script=scriptStr -- Put script string into script controller.
)--end main 'if/then'.

setArrowCursor()
)
--------------------------
-- end functions.

-- If rol_BB dialog already open, then close.
global rol_BB
try (destroyDialog rol_BB) catch()

rollout rol_BB "Billboard"
(
-- VARIABLES.
local bbObjsArr -- Declare billboard objects array local to rollout.
local laObj -- Declare lookat object local to rollout.

-- UI CONTROL ITEMS.
button but_setBBEffect "Set Billboard Effect" width:125 height:40\
tooltip:"Select billboard objects and camera, then click button"

-- EVENTS.
-- Set the billboard effect.
on but_setBBEffect pressed do
(
bbObjsArr=#() -- Initialize billboard objects array to null array.
laObj=undefined -- Initialize lookat object variable to 'undefined'.

-- Note: User must manually select objects in viewport, or via 'Select by Name' dialog.
--       Selection should include the billboard objects AND one camera.
-- Loop through selected objects.
for obj in selection do
(
if superClassOf obj==camera -- Check if object is the camera.
	then laObj=obj -- Assign camera object to 'laObj' variable.
	else if classOf obj!=targetObject then append bbObjsArr obj -- Append object to billboard objects array, but exclude target objects.
)

-- Finally, set billboard effect: loop through billboard objects and call 'makeBillboard' fn.
-- This assigns a script controller to the billboard objects' rotation.
if bbObjsArr.count!=0 AND laObj!=undefined then -- Check to ensure objects have been selected.
(
for obj in bbObjsArr do makeBillboard obj laObj
)
)

)
--------------------------
-- end rollout.

-- Create dialog.
createDialog rol_BB width:250 height:100\
style:#(#style_sysmenu,#style_titlebar,#style_minimizebox,#style_maximizebox)
--------------------------
-- end script.
