The idea behind this script was to create a tool that will allow to animate a lights position by actually animating the position of the specular highlight, instead of the the lights position. This could be interesting for pack shots or simply as tool to easily adjust the specular at exact position that you want it to be.

hiliteMove1.0 Version: 1.0 Author: Vladislav Bakic Filesize: 6.29K License: Apache 2.0 License

The script uses a locator that can but does not have to be geometry constrained to a model, and then looks for the closes point on the model to get the normal, then mirrors the angle between the camera and the normal and positions the light at the opposite side, with optional distance attribute.

The locator does not have to be constrained so it could be animated on a path that is close to the surface to accommodate for when you have more then one object that you want the highlight to span across. 

Once the locator is deleted, all of the expressions and nodes associated with the tool are removed from the scene, so that you can easily use it as a tool to just position your light.

Here is the commented code to explain how it works:


 author:	Vladislav Bakic
 email:		This email address is being protected from spambots. You need JavaScript enabled to view it.
 license:	Apache 2.0 License

//String array index utility, to be able to return only one element from a command
global proc string _si(string $array[], int $index){
    string $return = "";
	$index = ($index%size($array));
    if($index < 0){
    	$index = size($array) + $index;
    	if($index < 0)
    		$index = 0;

    if($index < size($array) && $index >= 0)
        $return = $array[$index];
    return $return;
// Get the transfomr node from the shape
proc string getTransform( string $shape )
   if ( "transform" != `nodeType $shape` ){
      string $parents[] = `listRelatives -fullPath -parent $shape`;
      $shape = $parents[0];

   return $shape;
//Get current viewport camera, else assign the persp1 camera
global proc string getCurrentCamera(){
  string $camera;
  string $panel = `getPanel -wf`;
  if ( "modelPanel" == `getPanel -to $panel` )
    $camera = `modelEditor -q -camera $panel`;
  if($camera == "")
    return "persp";
  if(`nodeType $camera` == "camera")
    $camera = getTransform( $camera );
  return $camera;
//Create the locator, closest point on mesh or surface, aim constraint, geo constraint and the expression
global proc hiliteMove()
	//get the selection
    string $sel[2] = stringArrayRemoveDuplicates(stringArrayCatenate(`ls -sl`, `ls -sl -dag -lf`));
    string $light, $mesh, $type, $geo, $class = "";
    string $cpm;
	//load nearestPointOnMesh
	if (!`pluginInfo -q -l "nearestPointOnMesh"`){
		loadPlugin "nearestPointOnMesh";
	//find the light and the geo
    for ($i=0;$i<size($sel);$i++){
        $type = `nodeType $sel[$i]`;
        $class = _si(`getClassification $type`,0);
        if(size(`match "light" $class`)){
            $light = getTransform($sel[$i]);
        else if($type == "mesh" || $type == "nurbsSurface"){
            $mesh = $sel[$i];
            $geo = $type;
        if(size($mesh) && size($light))
	//if we have a light and a mesh (or a surface) do the stuff
    if(size($light) && size($mesh))
		//create the locator and add attributes
        string $loc = _si(`spaceLocator -p 0 0 0 -name "hiliteControl"`, 0);
        setAttr ($loc+".displayRotatePivot") 1;
        addAttr -ln "alwaysActive" -at bool $loc;
        addAttr -ln "distance" -at double -dv 10 $loc;
        addAttr -ln "geometryConstaint"  -at "enum" -en "On:Off:"  $loc;

        setAttr -e -keyable true ($loc+".alwaysActive");
        setAttr -e -keyable true ($loc+".distance");
        setAttr -e -keyable true ($loc+".geometryConstaint");

		//create the geo constraint
        string $geoConst = _si(`geometryConstraint -weight 1 $mesh $loc`,0);
        connectAttr ($loc+".geometryConstaint") ($geoConst+".nodeState");
		//create the aim constraint
        string $aimConst = _si(`aimConstraint -offset 0 0 0 -weight 1 -aimVector 0 0 -1 -upVector 0 1 0 -worldUpType "vector" -worldUpVector 0 1 0 $loc $light`,0);
        parent $aimConst $loc;

		//create the closest point on mesh/surface and the corresponding expression line to get the normal
		string $normStr = "";
        if ($geo == "mesh"){
            $cpm = `shadingNode -asUtility nearestPointOnMesh`;
            connectAttr ($mesh+".worldMesh[0]") ($cpm+".inMesh");
			$normStr = "$normalSurface[0] = "+$cpm+".normalX;\n$normalSurface[1] = "+$cpm+".normalY;\n$normalSurface[2] = "+$cpm+".normalZ;";
            $cpm = `shadingNode -asUtility closestPointOnSurface`;
            connectAttr ($mesh+".worldSpace[0]") ($cpm+".inputSurface");
			$normStr = "$normalSurface = `pointOnSurface -no -u (" +$cpm + ".parameterU) -v (" + $cpm + ".parameterV) \"" + getTransform($mesh) + "\"`;";
        connectAttr ($loc+".translate") ($cpm+".inPosition");
		string $cam = getCurrentCamera();
		//assemble the expression
		//here is a short explanation: We get the position of the camera, the postion of the locator and the normal on the point closest to the surface
		//then we subtract the locator pos from the camera pos to get the vector pointing to the closest point on surface from the camera
		//next we retrieve the normal and put into the most important part of the script, the formula to reverse the normal
		//vector = inVector - 2 * (inVector.x-normal.x + inVector.x-normal.y + inVector.x-normal.z) * normal
		//next we divide the vector by its intensity to normalize it, and we multiply it then by the distance attribute
		//then we add the position of the locator to get the final position of the light and we assign it
        string $exp = `expression -s ("float $hilite[3];\nstring $selection[1] = `ls -sl`;\n$hilite[0] = "+$loc+".translateX;\n$hilite[1] = "+$loc+".translateY;\n$hilite[2] = "+$loc+".translateZ;\nfloat $distance = "+$loc+".distance;\nint $active = "+$loc+".alwaysActive;\n\nvector $specular = <<$hilite[0], $hilite[1], $hilite[2]>>;\nfloat $camera[3];\n$camera[0] = "+$cam+".translateX;\n$camera[1] = "+$cam+".translateY;\n$camera[2] = "+$cam+".translateZ;\n\nvector $in = <<$camera[0]-$hilite[0],$camera[1]-$hilite[1],$camera[2]-$hilite[2]>>;\nfloat $ii = $in;\n$in = <<($in.x)/$ii*$distance,($in.y)/$ii*$distance,($in.z)/$ii*$distance>>;\nfloat $normalSurface[3];\n"+$normStr+"\n\nvector $normal = <<$normalSurface[0], $normalSurface[1], $normalSurface[2]>>;\n\nvector $result = $in - 2 * (($in.x*$normal.x + $in.y*$normal.y + $in.z*$normal.z)) * $normal;\n\n$result = <<((-$result.x)+$hilite[0]), ((-$result.y)+$hilite[1]), ((-$result.z)+$hilite[2])>>;\nfloat $intesity = $result;\n if($selection[0]==\""+$loc+"\" || $active){\n\t"+$light+".translateX = ($result.x);\n\t"+$light+".translateY = ($result.y);\n\t"+$light+".translateZ = ($result.z);}")  -o "" -ae 1 -uc all`;
		//script job to remove the closest point on surface and the expression if we delete the locator
		int $job = `scriptJob -ro true -attributeDeleted ($loc+".tx") ("delete "+$exp+" "+$cpm)`;
        select -r $loc;
	//throw an error
        error("You must select a geometry and a light!");
//invoke the script