Very often you want to find the 2d coordinates on the screen for a given 3d point. Here’s a little mel script that knows the projection math and takes care of generating the data in a “raw channel text file” format. From here, it’s easy to generate just about any ascii file format that a compositing program might be expecting.

I’ve seen people rendering out small green tracking markers on their 3d objects to later track them in 2d to get the same results. Obviously, the computer can save you these steps and get you the exact result much quicker. To quote Homer Simpson, “Computer’s can do that!

// Rob Bredow
// rob (at) 185vfx.com
// http://www.185vfx.com/
// Copyright 3/2002 Rob Bredow, All Rights Reserved
// 
// Converts currently selected point or object from 3d space to 
// screen space for use in a compositing package.
//
// Writes out "normalized" 2d coordinates so multiply the x by the
// width of your image and y by the height to get pixels
//
// Assumes your camera is called "cam_main"


// Get a matrix
proc matrix screenSpaceGetMatrix(string $attr){
  float $v[]=`getAttr $attr`;
  matrix $mat[4][4]=<<$v[0], $v[1], $v[2], $v[3]; 
             $v[4], $v[5], $v[6], $v[7];
             $v[8], $v[9], $v[10], $v[11];
             $v[12], $v[13], $v[14], $v[15]>>;
 return $mat;
}

// Multiply the vector v by the 4x4 matrix m, this is probably
// already in mel but I cant find it.
proc vector screenSpaceVecMult(vector $v, matrix $m){
  matrix $v1[1][4]=<<$v.x, $v.y, $v.z, 1>>;
  matrix $v2[1][4]=$v1*$m;
  return <<$v2[0][0], $v2[0][1],  $v2[0][2]>>;
}

global proc int screenSpace()
{
  // get the currently selected point
  string $dumpList[] = `ls -sl`;
  int $argc = size($dumpList);

  if ($argc != 1)
  {
    confirmDialog -t "screenSpace Usage" -m "To use, select a point on a geometry or a single object to write";
    return -1;
  }
  
  string $dumpPt = $dumpList[0];

  // Make $dumpPt a unix friendly name
  string $name = $dumpPt;
  while ( match("\\[",$name) != "" ) {
    $name = `substitute "\\[" $name "_"`;
  }
  while ( match("\\]",$name) != "" ) {
    $name = `substitute "\\]" $name ""`;
  }
  while ( match("\\.",$name) != "" ) {
    $name = `substitute "\\." $name "_"`;
  }

  // Find the frame range
  float $fs = `playbackOptions -q -min`;
  float $fe = `playbackOptions -q -max`;
  string $verify = "Will create " + $name + "_screenSpace.cmp\n" +
                   "For the frame range of " + $fs + "-" + $fe + "\n" +
                   "The camera used will be cam_main\n";
 
  if (`confirmDialog -t "screenSpace Verify" -m $verify -b "Dump" -b "Cancel"` == "Cancel")
    return -2;

  print ("Dumping selection...\n");

  string $pointWsFile = $name+"_screenSpace.cmp";
  int $outFileId = fopen($pointWsFile,"w");

  if ($outFileId == 0) {
    print ("Could not open output file " + $pointWsFile);
    return -1;
  }

  string $line;

  int $f;
  float $tx[],$ty[],$tz[];
  for ($f=$fs;$f<=$fe;$f++)
  {
    currentTime $f;

    // get the world space position of the point into a vector
    float $ptPosWs[] = `xform -q -ws -t $dumpPt`;
    vector $ptVecWs = <<$ptPosWs[0],$ptPosWs[1],$ptPosWs[2]>>;

    // Grab the worldInverseMatrix from cam_main
    matrix $cam_mat[4][4] = screenSpaceGetMatrix("cam_main.worldInverseMatrix");

    // Multiply the point by that matrix
    vector $ptVecCs = screenSpaceVecMult($ptVecWs,$cam_mat);

    // Adjust the point's position for the camera perspective
    float $hfv = `camera -q -hfv cam_main`;
    float $ptx = (($ptVecCs.x/(-$ptVecCs.z))/tand($hfv/2))/2.0+.5;
    float $vfv = `camera -q -vfv cam_main`;
    float $pty = (($ptVecCs.y/(-$ptVecCs.z))/tand($vfv/2))/2.0+.5;

    float $ptz = $ptVecCs.z;

    $line = $ptx + " " + $pty + " " + "\n";
    fprint $outFileId $line; 
  }
 
  fclose $outFileId;

  return 1;
}

<br/> Here’s an alternative version submitted by Andrew Roberts that looks to be more complete in every way. It builds a simple interface for the user and handles non-square pixels (NTSC) accurately.

This Post Has 4 Comments

  1. Hey Man – I’ve been racking my brains for a long time but I finally got my script working after finding your post – thank you.

    These functions of yours completed my puzzle: matrix $cam_mat[4][4] = screenSpaceGetMatrix ($thisCam + “.worldInverseMatrix”); vector $ptVecCs = screenSpaceVecMult($ptVecWs,$cam_mat);

    So little, yet so much! I’ve never used xform matrices in Maya before.. pretty cool.

    My script is for 3d object tracking (matchmoving) geometry to match live action footage. It imports a 2D track from shake and uses a 3D locator as the tracking point. It then stabilizes the image plane (by adjusting the cameras film offset). You can then parent geo to the tracked locator, and rotate it as needed to match the plate.

    Thanks to what I’ve learned from you, the camera can now have translation and rotation. Thanks again! Paul, in Vancouver.

    1. Glad to hear it was helpful for you. Thanks for writing.

  2. I’m having a bit of trouble with this. I am hoping to get the resolution gate position instead of the film gate. But I’m confused as to where that information would come into the equation. Also, would you happen to know how I could do this with orthographic set to True? I’d really appreciate any possible help, this code has already been a HUGE problem solver for me.

  3. I’ve figured out my problem. Posting here if anyone else is having the same problem and googles upon this page. To get the position in orthographic space in your camera you just take the x and y of the result of multiplying the point by the transformation matrix. As opposed to all the extra perspective work of ((x or y) / z) / tand(hvf or vfv / 2) etc.

Comments are closed.

Close Menu