der_ton@Posted: Thu Oct 07, 2004 9:17 am    Post subject: : Here is a maxscript function that I used for the max->md5 exporter to print floats without scientific notation. I think the algorithm should be implementable in LScript, too.
Code:
fn printfloat flt =
--maxscript doesn't provide a way to format float output that I know of. It seems that Doom3 does not understand the <x>e<y> notation of floats
--if you plan to use this in your scripts, note that this function doesn't check against a large number passed in
(
   --if (abs flt)>10.0 then return flt -- this could be used to gain some speed.
   --else
   local numdigits = 10
   (
      str = ""
      if flt<0 then str += "-"
      flt2 = abs flt
      str += ((flt2 as integer) as string)
      flt2 -= (flt2 as integer)
      str +="."
      for i=1 to numdigits do
      (
         flt2 *=10.0
         str += ((flt2 as integer) as string)
         flt2 -= (flt2 as integer)
         if abs flt2 < (1/10^(numdigits-i+1)) then break
      )
      return str
   )
)

_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Fri Oct 08, 2004 2:22 am    Post subject: : Thanks for that code. I had nearly the same worked out when I finally got around to checking this thread again but it was still very helpful because i wasn't finished with it.

EDIT: Updated code and a shitload of problems. Details are in the comments...

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

forceDecimal: num
{
    // Lscript will convert a decimal to scientific notation after 4 consecutive zeros.

    numDigits = 10;
    strOutput = "";
    if (num<0)
    {
        strOutput += "-";
    }
    num = abs(num);
    num2 = frac(num);
    num = num - num2;
    strOutput += string(num) + ".";
    for (i=1; i<=numDigits; i++)
      {
         num2 *= 10.0;
         strOutput += string(integer(num2));
         num2 -= integer(num2);
         if (abs(num2) < (pow(1/10,(numDigits-i+1)))) { break; }
      }
    return strOutput;
}

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.previewstart;
   s_end = scn.previewend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getWorldRotation(i/s_fps);
      pos=cam.getWorldPosition(i/s_fps);

      // corrections based on 360 degree test camera rotations.

      /*
         For some reason, values over 180 or less than -180 caused the camera to switch
         directions.

         For instance, a 0 to 360 rotation around the heading would work fine until
         heading reaches a value of 181. At this point instead of continuing in a clockwise
         direction it would reverse direction. It behaves as if 181 is the same as
         179.

         I'm guessing that values in the range of -180 to 180 are the only acceptable values?

         It's driving me nuts because those are the only values that work. Anyway, I've
         corrected that issue using the conditional statements below. This way whenever a
         value is beyond that range it is looped back around into it.

         The only problem is I'm not sure how to adjust the camera orientation now. The
         camera points east at 0,0,0 when 0,0,0 should point north.

         If I adjust the conditional statments affecting rot.x to compensate for this,
         I get really funky results. For instance, a rotation around the bank would result
         in a 270 degree rotation around pitch that then stops and then rotates 360 around
         the bank.

         I'm sure the reason I'm struggling with this is because I don't understand the math
         behind quaternions.

         If i did, I could adjust for the orientation after the quaternion calculations and
         maybe that would make a world of difference.

      */

      rot=<rot.x, rot.y, rot.z>;

      // correct rot.x
      if (rot.x < -180) {
          rot.x += 360;
      }
      if (rot.x > 180) {
          rot.x -= 360;
      }

      // correct rot.y
      if (rot.y < -180) {
          rot.y += 360;
      }
      if (rot.y > 180) {
          rot.y -= 360;
      }

      // correct rot.z
      if (rot.z < -180) {
          rot.z += 360;
      }
      if (rot.z > 180) {
          rot.z -= 360;
      }

      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot.x/360*PI);
      c2=cos(rot.y/360*PI);
      c3=cos(rot.z/360*PI);
      s1=sin(rot.x/360*PI);
      s2=sin(rot.y/360*PI);
      s3=sin(rot.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3; //pitch
      q2=c1*s2*c3+s1*c2*s3; //bank
      q3=c1*c2*s3-s1*s2*c3; //heading

      // convert quaternions to strings so they can be written in decimal notation.
      v1=forceDecimal(q1);
      v2=forceDecimal(q2);
      v3=forceDecimal(q3);

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", pos_d3," ) ( ",v3, " ", v2, " ", v1, " ) ",fovarray[1]*180/PI);

   }
   exportfile.writeln("}");
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
   info("Export Complete");
}


It's like slowly pushing a nail through your skull. Mad

Everytime I think I've just about got it figured out, I find something else wrong with it.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Fri Oct 08, 2004 11:28 am    Post subject: : You should use the forceDecimal function on all seven floats that get output.

About the camera rotation issue: you should go back to the original quaternion output (don't swizzle the 3 quaternion components), and manipulate (swizzle/negate) the euler angles before or after they're wrapped into the -180;+180 interval.

And you should change the
if (rot.x < -180)
to
while (rot.x < -180), just to be really sure.
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Fri Oct 08, 2004 8:43 pm    Post subject: : Okay! Smile

I've got the orientation issues worked out. I swiveled the euler angles instead and it works great.

I placed the range value wrapping conditional statement in it's own function to optimize the code a bit.

I've tested full 360 degree heading, pitch and bank rotations. I've tested very complex camera rigs. Everything seems to be in order.

Here's the updated code...

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

wrapValues: num
{
    // takes values that exceed range and loops them back into valid values

    if (num < -180) {
      num += 360;
    }
    if (num > 180) {
      num -= 360;
    }
    return num;
}

forceDecimal: num
{
    // Lscript will convert a decimal to scientific notation after 4 consecutive zeros.

    numDigits = 10;
    strOutput = "";
    if (num<0)
    {
        strOutput += "-";
    }
    num = abs(num);
    num2 = frac(num);
    num = num - num2;
    strOutput += string(num) + ".";
    for (i=1; i<=numDigits; i++)
      {
         num2 *= 10.0;
         strOutput += string(integer(num2));
         num2 -= integer(num2);
         if (abs(num2) < (pow(1/10,(numDigits-i+1)))) { break; }
      }
    if (num2==0) {
        strOutput += "0000000000";
    }
    return strOutput;
}

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.previewstart;
   s_end = scn.previewend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getWorldRotation(i/s_fps);
      pos=cam.getWorldPosition(i/s_fps);

      // swivel camera values and adjust heading orientation
      rot_d3=<rot.y, rot.z, rot.x - 90>;

      // wrap rotational values into range -180 to 180
      rot_d3.x = wrapValues(rot_d3.x);
      rot_d3.y = wrapValues(rot_d3.y);
      rot_d3.z = wrapValues(rot_d3.z);

      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot_d3.x/360*PI);
      c2=cos(rot_d3.y/360*PI);
      c3=cos(rot_d3.z/360*PI);
      s1=sin(rot_d3.x/360*PI);
      s2=sin(rot_d3.y/360*PI);
      s3=sin(rot_d3.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3;
      q2=c1*s2*c3+s1*c2*s3;
      q3=c1*c2*s3-s1*s2*c3;

      // convert values to strings so they can be written in decimal notation
      v1=forceDecimal(pos_d3.x);
      v2=forceDecimal(pos_d3.y);
      v3=forceDecimal(pos_d3.z);
      v4=forceDecimal(q1);
      v5=forceDecimal(q2);
      v6=forceDecimal(q3);
      v7=forceDecimal(fovarray[1]*180/PI);

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", v1, " ", v2, " ", v3, " ) ( ", v4, " ", v5, " ", v6, " ) ", v7);

   }
   exportfile.writeln("}");
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
   info("Export Complete");
}


Do you have any ideas on how we could implement cuts or should we just leave that as something you hand edit in?
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



goliathvt@Posted: Fri Oct 08, 2004 8:54 pm    Post subject: : I haven't wanted to interrupt this thread because you two were working so well through the various problems and obstacles in creating this thing... but:

Thank you rich_is_bored and der_ton! Your collective work is awesome!

Very Happy

G
_________________
Staff



Rayne@Posted: Fri Oct 08, 2004 10:32 pm    Post subject: : Just tried your script. It's amazing!!! a very very gooood work!


Thanks Rich and der_ton!
_________________
theRev is coming...



rich_is_bored@Posted: Fri Oct 08, 2004 10:49 pm    Post subject: : No, that's fine. I appriciate any comments or critiques.

At this point the script supports position, rotation, and field of view. I still need to work out a way to deal with cuts.

Do you guys have any suggestions?

What I was thinking is maybe using dopesheet markers although I haven't tried seeing if that kind of data is accessible through an LScript.

Once the script is fully functional I'll start on an interface.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



Rayne@Posted: Sat Oct 09, 2004 6:15 am    Post subject: : By now i've made some tests, and I've not discovered bad issues. I'll continue this afternoon, but for now I've got no problems with the exported stuff (read: my cams works perfectly)


But like I said before, i'm a newbie in lightwave, so I can't help you with the "cuts" stuff.


Good work!
_________________
theRev is coming...



rich_is_bored@Posted: Sat Oct 09, 2004 8:16 am    Post subject: : Well, I investigated the dopesheet idea and there doesn't appear to be a way to access it.

Anyway, I've integrated an interface. It's just your standard LScript type stuff. I'm not interested in trying to make it look pretty.

I'll post the code and then I'll talk about it some more...

Code:
@version 2.0
@warnings
@script generic

exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)
numcuts = nil; // number of cuts
cuts = nil; // array of cuts
numframes = nil; // number of frames
s_fps = nil; // frames per second
s_start = nil; // starting frame
s_end = nil; // ending frame

wrapValues: num
{
    // takes values that exceed range and loops them back into valid values

    if (num < -180) {
      num += 360;
    }
    if (num > 180) {
      num -= 360;
    }
    return num;
}

forceDecimal: num
{
    // Lscript will convert a decimal to scientific notation after 4 consecutive zeros.

    numDigits = 10;
    strOutput = "";
    if (num<0)
    {
        strOutput += "-";
    }
    num = abs(num);
    num2 = frac(num);
    num = num - num2;
    strOutput += string(num) + ".";
    for (i=1; i<=numDigits; i++)
      {
         num2 *= 10.0;
         strOutput += string(integer(num2));
         num2 -= integer(num2);
         if (abs(num2) < (pow(1/10,(numDigits-i+1)))) { break; }
      }
    if (num2==0) {
        strOutput += "0000000000";
    }
    return strOutput;
}

exportmd5camera
{
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", numframes);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts "+ numcuts + "\n");
   exportfile.writeln("cuts {");
   for (i = 1; i<=numcuts; i++)
   {
         exportfile.writeln(cuts[i]);
   }
   exportfile.writeln("}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getWorldRotation(i/s_fps);
      pos=cam.getWorldPosition(i/s_fps);

      // swivel camera values and adjust heading orientation
      rot_d3=<rot.y, rot.z, rot.x - 90>;

      // wrap rotational values into range -180 to 180
      rot_d3.x = wrapValues(rot_d3.x);
      rot_d3.y = wrapValues(rot_d3.y);
      rot_d3.z = wrapValues(rot_d3.z);

      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot_d3.x/360*PI);
      c2=cos(rot_d3.y/360*PI);
      c3=cos(rot_d3.z/360*PI);
      s1=sin(rot_d3.x/360*PI);
      s2=sin(rot_d3.y/360*PI);
      s3=sin(rot_d3.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3;
      q2=c1*s2*c3+s1*c2*s3;
      q3=c1*c2*s3-s1*s2*c3;

      // convert values to strings so they can be written in decimal notation
      v1=forceDecimal(pos_d3.x);
      v2=forceDecimal(pos_d3.y);
      v3=forceDecimal(pos_d3.z);
      v4=forceDecimal(q1);
      v5=forceDecimal(q2);
      v6=forceDecimal(q3);
      v7=forceDecimal(fovarray[1]*180/PI);

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", v1, " ", v2, " ", v3, " ) ( ", v4, " ", v5, " ", v6, " ) ", v7);

   }
   exportfile.writeln("}");
}

toggleOff: value
{
    return(false);
}

generic
{

    // get info for requestor
    scn = Scene();
    userobj = scn.firstSelect();
    if (userobj == nil || userobj.genus != CAMERA)
    {
       //no camera was selected, so we export the first in the scene if there is any
       userobj = Camera();
       if (userobj == nil)
       {
          info ("No camera in the scene");
          return;
       }
    }
    cam = userobj;
    cutlist = "";
    exportfilename = "";
    s_fps = scn.fps;
    s_start = scn.previewstart;
    s_end = scn.previewend;
    numframes = s_end - s_start + 1;

    // display requester
    reqbegin("Lightwave MD5camera Exporter");
    reqsize(492,155);

    c1 = ctlinteger("Frame Rate",s_fps);
    ctlposition(c1,361,12);

    c2 = ctlstring("Cut List",cutlist);
    ctlposition(c2,10,73,476,18);

    c3 = ctlfilename("Save As",exportfilename);
    ctlposition(c3,5,104,458,19);

    c4 = ctlinteger("Start Frame",s_start);
    ctlposition(c4,85,42);

    c5 = ctlinteger("End Frame",s_end);
    ctlposition(c5,225,42);

    c6 = ctlinteger("Total Frames",numframes);
    ctlposition(c6,355,42);

    c7 = ctlcameraitems("Camera",cam);
    ctlposition(c7,3,11,343,19);

    // deactivate all unmodifiable controls
    ctlactive(c3, "toggleOff", c1, c4, c5, c6);

    return if !reqpost();

    s_fps = getvalue(c1);
    cutlist = getvalue(c2);
    exportfilename = getvalue(c3);
    s_start = getvalue(c4);
    s_end = getvalue(c5);
    numframes = getvalue(c6);
    cam = getvalue(c7);

    reqend();

    // validate values and setup file for output
    if(cam == nil)
    {
        error ("No camera selected");
        return;
    }
    if(cutlist == nil) {
        numcuts = 0;
    } else {
        cuts = parse(";",cutlist);
        numcuts = size(cuts);
        //info(string(numcuts) + " cuts were found");
        for (i=1; i<=numcuts; i++)
        {
            test = integer(cuts[i]);
            if (test == nil)
            {
                error(cuts[i]+" is not an integer");
                return;
            } else {
                cuts[i] = test - s_start;
            }
            //error(cuts[i]+" is not an integer");
            if (integer(cuts[i]) < s_start or integer(cuts[i]) > s_end)
            {
                error(cuts[i]+" is out of range");
                return;
            }
        }
    }
    exportfile = File(exportfilename,"w");
    if(exportfile == nil)
    {
       error("Unable to get writing access to ",exportfilename);
       return;
    }
    exportmd5camera();
    info("Export Complete");
}


Okay, the way I'm doing the cuts is through a single textbox. The way it works is you type the frame numbers where you want the cuts to take place.

For instance, say you want cuts at frame 10 and 20 on the timeline in Lightwave. You simply type...

Code:
10;20


... in the cuts list text box.

Keep me posted if there are any problems with this latest iteration as I haven't done much testing. I've been mainly testing the interface.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Mon Oct 25, 2004 8:22 pm    Post subject: : I'm going to package this up into a nice little zip and update the first post with a link.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Thu May 26, 2005 7:27 am    Post subject: : Long time no update but I figured I'd chime in and let you all know about a problem with the script that's been on my mind for a while but I've not been terribly bothered to integrate a solution.

There is a bug with cuts. Well, not really a bug but it more or less amounts to a bug because I'm not sure if you can move the camera in the fashion needed in order for cuts to work.

The way it works now is you manually tell the script what frames to make cuts at. The problem is that while the script is exporting what amounts to multiple clips we're working with a single camera that uses one continuous fluid motion path.

So, if I cut on one frame to start the next clip in the script, the camera in Lightwave doesn't know that this should be a completely different movement.

The problem is that you can't get a camera's motion path to abruptly end and then start up again without the previous motion carrying over into the next.

This becomes a problem because if I insert a cut at one point and try to start a new clip immediately on the next frame at a different position Lightwave will interpret the motion from the two keyframes as something that needs interpolated.

The solution is to use a separate camera for each cut but that would require major overhaul to the current script and I just don't have the time to really look into it.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Mon Oct 04, 2004 2:36 am    Post subject: MD5camera Export Project: A working LScript Plugin has been finished...

DOWNLOAD CURRENT VERSION
http://www.pcgamemods.com/8120/
http://www.geocities.com/rich_is_bored/LW_MD5camera_Export.zip

Original Thread:

Okay, to get the ball rolling on a means to export MD5cameras from Lightwave, I'm creating this thread.

First and foremost, contrary to popular belief, I am not a programmer. I took a class on Turbo Pascal in high school 6 years ago but that's about the extent of my programming knowledge.

I've got a copy of Dev-C++ and I've been toying with it so if it's absolutely nessecary I can write a program to do the conversion but, I'll be frank, it's going to suck and it's going to be a DOS mode program at best.

Now, on to the idea...

In another thread I mentioned the possibility of converting saved motion paths (*.mot) to MD5camera format. Both file formats are plain text and that should make the conversion fairly easy.

The first thing is to decypher the formats. So, on that note, here is what I believe the MD5 camera format is comprised of...

Code:
MD5Version 10                     // Header and Version
commandline ""                     // Command Line Parameters - Used to convert maya format

numFrames 452                     // The Number of Frames
frameRate 24                     // The Framerate
numCuts 3                     // The Number of Cuts

cuts {                        // A List of Frames Where Cuts Take Place
   110
   329
   429
}

camera {                     // A List of Key Frames
   ( -908.6940917969 -3417.0383300781 552.0170898438 ) ( -0.0069773775 0.0820536837 -0.084440738 ) 54.4321250916

   ...                     // The format for each line is as follows...

   continues on                  // (X_POS, Y_POS, Z_POS) (HEADING, PITCH, BANK) FOV

   ...

   ( -903.4814453125 -3417.0383300781 555.6039428711 ) ( -0.0068133976 0.0792509392 -0.0853850618 ) 54.4321250916
}


So, the values that an MD5camera needs ...


  • Number of Frames
  • Framerate
  • Number of Cuts
  • List of Cut Frames
  • List of Frames
  • Position on the X axis
  • Position on the Y axis
  • Position on the Z axis
  • Rotation for Heading
  • Rotation for Pitch
  • Rotation for Bank
  • Field of View


Now, here is what I believe the MOT motion file format is comprised of...

Code:
LWMO
3

NumChannels 6
Channel 0                  // X channel
{ Envelope
  1                     // Number of keys in this channel
  Key 1 0 0 0 0 0 0 0 0               // Keys (Value, Time, Incoming Curve, Tension, Continuity, Bias, NA, NA, NA)
  Behaviors 1 1                  // Pre and Post Behavior (0-Reset, 1-Constant, 2-Repeat, 3-Oscillate, 4-Offset Repeat, 5-Linear)
}
Channel 1                  // Y channel
{ Envelope
  1
  Key 2 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 2                  // Z channel
{ Envelope
  1
  Key 3 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 3                  // Heading channel
{ Envelope
  1
  Key 0.06981317 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 4                  // Pitch Channel
{ Envelope
  1
  Key 0.08726646 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 5                  // Bank Channel
{ Envelope
  1
  Key 0.10471975 0 0 0 0 0 0 0 0
  Behaviors 1 1
}


So, the values that an MOT gives us either directly or can be calculated easily ...


  • Number of Frames
  • List of Key Frames
  • Position on the X axis
  • Position on the Y axis
  • Position on the Z axis
  • Rotation for Heading
  • Rotation for Pitch
  • Rotation for Bank


Now if you compare the two you'll see that there are a couple problems.

There is no list of cuts, there is no FOV channel, and Lightwave interpolates between key frames where an MD5camera lists each frame in sequence.

You could force users to bake key frames in the graph editor. This would create the missing frames and make it easier to convert because all the calculation is already done.

The alternative isn't pretty. In order to do this manually you'd have to calculate the values for a given frame based on the curve type, tension, continuity, and bias.

Not to mention I don't know what those last three values are for and if they play a role.

As far as the cuts and FOV go, that may be do able. Is it possible to add new channels to an object in Lightwave? If so, do they get saved in a motion file?

If not, then perhaps we can use one or more of the extra fields in a MOT to fudge it if we force users to bake key frames.

The alternative is to ask the user for input and that's going to be a problem for some users.

Anyway, this is just me spilling my guts. Let's see where this goes from here.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?


Last edited by rich_is_bored on Mon Oct 25, 2004 9:14 pm; edited 1 time in total



der_ton@Posted: Mon Oct 04, 2004 1:13 pm    Post subject: : The second triple of values in the md5camera data is the X, Y and Z value of the camera's rotation quaternion, not the heading, pitch, bank format.

I'll take a look at LScript, maybe it turns out that writing such a md5camera exporter as a layout script is not that difficult.

Bozo, if you read this please tell us if you're planning to include a md5camera exporter in your LW->md5 converter, so we don't do the same work twice. Smile
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



Rayne@Posted: Mon Oct 04, 2004 1:55 pm    Post subject: : Good thing that users like Rich and der_ton are looking into this.. I really interested in a md5camera export for lightwave.. :thumbs up!:


Sadly, I really don't know anything regarding Lscripts, so I can offer help for beta testing things... Let me know... Let US know...
_________________
theRev is coming...



rich_is_bored@Posted: Mon Oct 04, 2004 6:21 pm    Post subject: : Damn it der_ton. There's that word again! "quaternion"

I guess I have to do some reading....

meh! Sad
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



weak@Posted: Mon Oct 04, 2004 6:36 pm    Post subject: : maybe that helps:

Matrix and Quaternion FAQ



rich_is_bored@Posted: Mon Oct 04, 2004 8:11 pm    Post subject: : No help at all but thanks for trying. I'm seeing a bunch of algorithms rather than explanations.

This is alot better...

http://www.gamedev.net/reference/articles/article1095.asp

But I still don't have this worked out. Here's an example I was toying with but at this point I'm stumpped...

Euler to Quaternion Conversion

Code:
Qx = [ cos(a/2), (sin(a/2), 0, 0)]
Qy = [ cos(b/2), (0, sin(b/2), 0)]
Qz = [ cos(c/2), (0, 0, sin(c/2))]


So, if...

Code:
a = 90
b = 180
c = 270


Then...

Code:
Qx = [ cos(90/2), (sin(90/2), 0, 0)]
Qy = [ cos(180/2), (0, sin(180/2), 0)]
Qz = [ cos(270/2), (0, 0, sin(270/2))]


And...

Code:
Qx = [ cos(45), (sin(45), 0, 0)]
Qy = [ cos(90), (0, sin(90), 0)]
Qz = [ cos(135), (0, 0, sin(135))]


And...

Code:
Qx = [ 0.70710678118654752440084436210485, (0.70710678118654752440084436210485, 0, 0)]
Qy = [ 0, (0, 1, 0)]
Qz = [ -0.70710678118654752440084436210485, (0, 0, 0.70710678118654752440084436210485)]


And to figure out Qw I need to...

Code:
Qw = Qx * Qy * Qz


Which means I need to multiply the unit quaternions. The formula is...

Code:
Q1 * Q2 =( w1.w2 - v1.v2, w1.v2 + w2.v1 + v1*v2)


But that's only for two unit quaternions. I need to multiply three. Does...

Code:
Qw = (Qx * Qy) * Qz


... give me the same result as if I multiplied them all at once? The reason I ask is because this statement throws me off...

Quote:
Be aware that the order of multiplication is important. Quaternion multiplication is not commutative, meaning

q1 * q2 does not equal q2 * q1

_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



weak@Posted: Mon Oct 04, 2004 8:30 pm    Post subject: : think so


rich_is_bored@Posted: Tue Oct 05, 2004 5:21 pm    Post subject: : Okay, well I'm done screwing with quaternions. Unless of course I can borrow a high school math teacher for a few days.

Anyway, I'm looking over the MOT format again and I realize that the rotation values are not in degrees. For instance here's the rotation portion of a MOT...

Code:
Channel 3
{ Envelope
  2
  Key 0 0 0 0 0 0 0 0 0
  Key 1.5707963 2.5 0 0 0 0 1.5706217 0 0
  Behaviors 1 1
}
Channel 4
{ Envelope
  2
  Key 0 0 0 0 0 0 0 0 0
  Key 3.1415925 2.5 0 0 0 0 3.1412435 0 0
  Behaviors 1 1
}
Channel 5
{ Envelope
  2
  Key 0 0 0 0 0 0 0 0 0
  Key 4.712389 2.5 0 0 0 0 4.7118654 0 0
  Behaviors 1 1
}


Now, the values we have here are...

Heading 1.5707963
Pitch 3.1415925
Bank 4.712389

And in Lightwave they are...

Heading 90
Pitch 180
Bank 270

WTF?

Anyway, before I even started this thread I was looking at the SDK in an attempt to find information on this but I couldn't find a section on motion files so I tried to decypher it myself.

I decided to read through it today and I noticed that the Scene files are also plain text and very similar. Infact they have a section in the SDK that pretty much explains the format of MOT files.

Take a look at this...

Quote:
Envelopes

An envelope defines a function of time. For any animation time, an envelope's parameters can be combined to generate a value at that time. Envelopes are used to store position coordinates, rotation angles, scale factors, camera zoom, light intensity, texture parameters, and anything else that can vary over time.

The envelope function is a piecewise polynomial curve. The function is tabulated at specific points, called keys. The curve segment between two adjacent keys is called a span, and values on the span are calculated by interpolating between the keys. The interpolation can be linear, cubic, or stepped, and it can be different for each span. The value of the function before the first key and after the last key is calculated by extrapolation.

In scene files, an envelope is stored in a block named Envelope that contains one or more nested Key blocks and one Behaviors block.

{ Envelope
nkeys
Key value time spantype p1 p2 p3 p4 p5 p6
Key ...
Behaviors pre post
}

The nkeys value is an integer, the number of Key blocks in the envelope. Envelopes must contain at least one Key block. The contents of a Key block are as follows.

value
The key value, a floating-point number. The units and limits of the value depend on what parameter the envelope represents.

time
The time in seconds, a float. This can be negative, zero or positive. Keys are listed in the envelope in increasing time order.

spantype
The curve type, an integer. This determines the kind of interpolation that will be performed on the span between this key and the previous key, and also indicates what interpolation parameters are stored for the key.

0 - TCB (Kochanek-Bartels)
1 - Hermite
2 - 1D Bezier (obsolete, equivalent to Hermite)
3 - Linear
4 - Stepped
5 - 2D Bezier

p1...p6
Curve parameters. The data depends on the span type.

TCB, Hermite, 1D Bezier
The first three parameters are tension, continuity and bias. The fourth and fifth parameters are the incoming and outgoing tangents. The sixth parameter is ignored and should be 0. Use the first three to evaluate TCB spans, and the other two to evaluate Hermite spans.
2D Bezier
The first two parameters are the incoming time and value, and the second two are the outgoing time and value.

The Behaviors block contains two integers.

pre, post
Pre- and post-behaviors. These determine how the envelope is extrapolated at times before the first key and after the last one.

0 - Reset
Sets the value to 0.0.
1 - Constant
Sets the value to the value at the nearest key.
2 - Repeat
Repeats the interval between the first and last keys (the primary interval).
3 - Oscillate
Like Repeat, but alternating copies of the primary interval are time-reversed.
4 - Offset Repeat
Like Repeat, but offset by the difference between the values of the first and last keys.
5 - Linear
Linearly extrapolates the value based on the tangent at the nearest key.


Now, the problem is that they don't explain how they are storing the value parameter other than saying it varies depending on what it represents. Well duh! No shit! But that doesn't explain how 1.5707963 = 90 degrees.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Tue Oct 05, 2004 5:53 pm    Post subject: : So the value is "3.1415925" where it should be 180? Hmm, let me think why this number seems familiar... Wink

These are radians. Convert between degrees and radians with these formulae:
deg = rad * 180 / PI
rad = deg * PI / 180
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



BNA!@Posted: Tue Oct 05, 2004 6:13 pm    Post subject: :
der_ton wrote:
So the value is "3.1415925" where it should be 180? Hmm, let me think why this number seems familiar... Wink

These are radians. Convert between degrees and radians with these formulae:
deg = rad * 180 / PI
rad = deg * PI / 180


Oh god, another "Dan Brown" style "messages hidden in numbers" guy... Smile
_________________
Staff - The world is yours, soon in 6 degrees of freedom!
Visit ModWiki



rich_is_bored@Posted: Tue Oct 05, 2004 6:18 pm    Post subject: :

Razz
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Tue Oct 05, 2004 11:06 pm    Post subject: : Here's a very simple script that dumps camera info into a file for frames 1 to 10. Unfortunately the values are all identical even for an animated camera, I'm not sure what I'm doing wrong.
Anyway, if that problem is solved, it's just a small step to a real md5camera exporter. All you need to do is convert the euler angles to quaternions, add a gui with adjustable framerange, and ofcourse format the output in md5camera syntax.

The relevant pages in the LScript reference documentation are page 115 (Camera Object Agents) and page 97 (common data members and methods for Object Agents).
Code:

@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // camera that we are going to export (Camera Object Agent)


exportmd5camera
{

   exportfile.writeln("name ", cam.name, "\n");
   for (i = 1; i<=10; i++)
   {
      fovarray = cam.fovAngles(i);
      exportfile.writeln("pos ", cam.getPosition(i));
      exportfile.writeln("rot ", cam.getRotation(i));
      exportfile.writeln("fov ", fovarray[1], " ", fovarray[2], "\n");
   }
   
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to open ",exportfilename);
      return;
   }
   exportmd5camera();
}

_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Tue Oct 05, 2004 11:34 pm    Post subject: : I was initially going to try writing the program in C++ but since I'm such a noob, and you've already started writing a LScript, I'll make the switch.

EDIT: Yeah, I see what your saying. It exports two frames on my end and then just repeats itself.

Code:
name Camera

pos 0 0 -1.99997
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -4.00005
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -5
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -5
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -5
rot 0 0 0
fov 0.789582 0.60577

...

continues


EDIT AGAIN: That parameter is time and not frame. So when you say ...

Code:
   for (i = 0; i<=10; i++)
   {
      fovarray = cam.fovAngles(i);
      exportfile.writeln("pos ", cam.getPosition(i));
      exportfile.writeln("rot ", cam.getRotation(i));
      exportfile.writeln("fov ", fovarray[1], " ", fovarray[2], "\n");
   }


... and increment i your really telling it to export the status of the camera from 1 second to 10.

So, when the animation is less than 10 seconds, the last frame is repeatedly exported.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Wed Oct 06, 2004 12:41 am    Post subject: : Okay, I sorted it out and here is the updated code...

Code:

@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // camera that we are going to export (Camera Object Agent)


exportmd5camera
{
    // Get the fps, starting, and ending frames from the scene.
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;
   exportfile.writeln("name ", cam.name, "\n");
   count = s_start;
   i = 0;
   while (count <= s_end){
      i = count / s_fps;        // Increment the time according to the fps.
      exportfile.writeln("frame ", count);
      exportfile.writeln("time ", i);
      fovarray = cam.fovAngles(i);
      exportfile.writeln("pos ", cam.getPosition(i));
      exportfile.writeln("rot ", cam.getRotation(i));
      exportfile.writeln("fov ", fovarray[1], " ", fovarray[2], "\n");
      count++;
   }
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to open ",exportfilename);
      return;
   }
   exportmd5camera();
}


I'm going to stop at this point and get back to this tommorow.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Wed Oct 06, 2004 12:38 pm    Post subject: : I've added md5-formatted output and the euler->quaternion conversion.
Now all there is to do is to get the camera rotated right, by swizzling/negating the euler angles before the quaternion is calculated. This would best be done by trial&error, because it can be confusing to try to get it right by thinking it through deterministically. That's because Doom3 uses a different coordinate system than LW, and because cameras are not oriented in the same way as in D3 (md5 cameras look down their x-axis, z is up, in LW they are looking down their z-axis, y is up).

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getRotation(i/s_fps);
      pos=cam.getPosition(i/s_fps);
      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot.x/360*PI);
      c2=cos(rot.y/360*PI);
      c3=cos(rot.z/360*PI);
      s1=sin(rot.x/360*PI);
      s2=sin(rot.y/360*PI);
      s3=sin(rot.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3;
      q2=c1*s2*c3+s1*c2*s3;
      q3=c1*c2*s3-s1*s2*c3;
      
      exportfile.writeln("\t( ", pos_d3," ) ( ",q1, " ", q2, " ", q3, " ) ",fovarray[1]*180/PI); //maybe fovarray[2] (vertical fov) should be used?
   }
   exportfile.writeln("}");
   
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
}

_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Wed Oct 06, 2004 6:25 pm    Post subject: : I've updated the code so that it is rotates the camera correctly. I've included comments to distinguish which is the heading, pitch, and bank.

I created a map with different textures on each wall so that I could gauge the orientation of the camera. From this point I exported a test camera repeatedly. Each time I tried a different rotation and used what I seen in game to determine if the rotation was correct.

Aside from the values needing to be written in the correct order, for some reason the pitch was inverted, for instance if I told the camera to rotate up it went down. I've fixed that by multipying q2 by -1 although I'm not sure if that fix is the correct one.

At any rate, it seems to be 100% and I'm going to toy with it a bit more just to be sure.

Here's the updated code...

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getRotation(i/s_fps);
      pos=cam.getPosition(i/s_fps);
      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot.x/360*PI);
      c2=cos(rot.y/360*PI);
      c3=cos(rot.z/360*PI);
      s1=sin(rot.x/360*PI);
      s2=sin(rot.y/360*PI);
      s3=sin(rot.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3; //bank
      q2=c1*s2*c3+s1*c2*s3; //pitch
      q3=c1*c2*s3-s1*s2*c3; //heading

      // invert q2 because it rotates up when it should look down
      q2=q2*-1;

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", pos_d3," ) ( ",q3, " ", q2, " ", q1, " ) ",fovarray[1]*180/PI);

   }
   exportfile.writeln("}");
   
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
}

_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?


Last edited by rich_is_bored on Wed Oct 06, 2004 6:57 pm; edited 2 times in total



der_ton@Posted: Wed Oct 06, 2004 6:44 pm    Post subject: : Hmm, I would have found it alot cleaner if you had done the manipulations to the euler angles instead of the quaternion results (negating/swizzling rot.x/y/z before they are used in the quaternion conversion), but hey, that's totally irrelevant if we end up with a working script. Wink

And q1, q2 and q3 are not bank, pitch heading. These are terms that apply to the euler angles.
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Wed Oct 06, 2004 7:08 pm    Post subject: : It works great. I can keyframe movement and rotation no problem. Smile

The only issue is that if I were to for instance, target the camera at a null, the orientation of the camera is affected in Lightwave but this does not effect the MD5camera.

This isn't a problem with the LScript though because I had the same issue trying to export motion paths. I'm sure there is a way to keyframe that kind of motion and I know if keyframed it would carry over into Doom 3.

The only thing left is to implement some way of performing cuts. Of course that isn't something you can keyframe so the only way to do it is to ask the user for a list of keys.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Wed Oct 06, 2004 7:16 pm    Post subject: : Maybe there's a way to bake constraint motion into keyframes, like you can do in Blender?

The FOV thingy is alright as it is, because Doom3's FOV denotes the horizontal FOV, too. The horizontal and vertical FOV are usually different, except if you have a camera that records a square-shaped image.
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Thu Oct 07, 2004 1:14 am    Post subject: : There is a guy who wrote a plugin called Transmotion or something that does just that. But, I'm not condoning people download it just for this.

Anyway, I've found a solution.

Instead of using getPostition and getRotation you use getWorldPosition and getWorldRotation.

Now, I can rig a camera up however I please but sometimes the values that are exported are in scientific notation (i.e -1.47128e-006).

I have to come up with a way to force the program to export decimal notation. I have a few ideas and I'll try them out and see how it goes.

On a side note there is another problem. It's not exporting the correct number of frames. It could be this isn't gathering the correct values...

Code:
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;


Anyway, I'll be sorting both of these issues out tonight and if I find solutions I'll post an update.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Mon Oct 04, 2004 2:36 am    Post subject: MD5camera Export Project: A working LScript Plugin has been finished...

DOWNLOAD CURRENT VERSION
http://www.pcgamemods.com/8120/
http://www.geocities.com/rich_is_bored/LW_MD5camera_Export.zip

Original Thread:

Okay, to get the ball rolling on a means to export MD5cameras from Lightwave, I'm creating this thread.

First and foremost, contrary to popular belief, I am not a programmer. I took a class on Turbo Pascal in high school 6 years ago but that's about the extent of my programming knowledge.

I've got a copy of Dev-C++ and I've been toying with it so if it's absolutely nessecary I can write a program to do the conversion but, I'll be frank, it's going to suck and it's going to be a DOS mode program at best.

Now, on to the idea...

In another thread I mentioned the possibility of converting saved motion paths (*.mot) to MD5camera format. Both file formats are plain text and that should make the conversion fairly easy.

The first thing is to decypher the formats. So, on that note, here is what I believe the MD5 camera format is comprised of...

Code:
MD5Version 10                     // Header and Version
commandline ""                     // Command Line Parameters - Used to convert maya format

numFrames 452                     // The Number of Frames
frameRate 24                     // The Framerate
numCuts 3                     // The Number of Cuts

cuts {                        // A List of Frames Where Cuts Take Place
   110
   329
   429
}

camera {                     // A List of Key Frames
   ( -908.6940917969 -3417.0383300781 552.0170898438 ) ( -0.0069773775 0.0820536837 -0.084440738 ) 54.4321250916

   ...                     // The format for each line is as follows...

   continues on                  // (X_POS, Y_POS, Z_POS) (HEADING, PITCH, BANK) FOV

   ...

   ( -903.4814453125 -3417.0383300781 555.6039428711 ) ( -0.0068133976 0.0792509392 -0.0853850618 ) 54.4321250916
}


So, the values that an MD5camera needs ...


  • Number of Frames
  • Framerate
  • Number of Cuts
  • List of Cut Frames
  • List of Frames
  • Position on the X axis
  • Position on the Y axis
  • Position on the Z axis
  • Rotation for Heading
  • Rotation for Pitch
  • Rotation for Bank
  • Field of View


Now, here is what I believe the MOT motion file format is comprised of...

Code:
LWMO
3

NumChannels 6
Channel 0                  // X channel
{ Envelope
  1                     // Number of keys in this channel
  Key 1 0 0 0 0 0 0 0 0               // Keys (Value, Time, Incoming Curve, Tension, Continuity, Bias, NA, NA, NA)
  Behaviors 1 1                  // Pre and Post Behavior (0-Reset, 1-Constant, 2-Repeat, 3-Oscillate, 4-Offset Repeat, 5-Linear)
}
Channel 1                  // Y channel
{ Envelope
  1
  Key 2 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 2                  // Z channel
{ Envelope
  1
  Key 3 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 3                  // Heading channel
{ Envelope
  1
  Key 0.06981317 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 4                  // Pitch Channel
{ Envelope
  1
  Key 0.08726646 0 0 0 0 0 0 0 0
  Behaviors 1 1
}
Channel 5                  // Bank Channel
{ Envelope
  1
  Key 0.10471975 0 0 0 0 0 0 0 0
  Behaviors 1 1
}


So, the values that an MOT gives us either directly or can be calculated easily ...


  • Number of Frames
  • List of Key Frames
  • Position on the X axis
  • Position on the Y axis
  • Position on the Z axis
  • Rotation for Heading
  • Rotation for Pitch
  • Rotation for Bank


Now if you compare the two you'll see that there are a couple problems.

There is no list of cuts, there is no FOV channel, and Lightwave interpolates between key frames where an MD5camera lists each frame in sequence.

You could force users to bake key frames in the graph editor. This would create the missing frames and make it easier to convert because all the calculation is already done.

The alternative isn't pretty. In order to do this manually you'd have to calculate the values for a given frame based on the curve type, tension, continuity, and bias.

Not to mention I don't know what those last three values are for and if they play a role.

As far as the cuts and FOV go, that may be do able. Is it possible to add new channels to an object in Lightwave? If so, do they get saved in a motion file?

If not, then perhaps we can use one or more of the extra fields in a MOT to fudge it if we force users to bake key frames.

The alternative is to ask the user for input and that's going to be a problem for some users.

Anyway, this is just me spilling my guts. Let's see where this goes from here.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?


Last edited by rich_is_bored on Mon Oct 25, 2004 9:14 pm; edited 1 time in total



der_ton@Posted: Mon Oct 04, 2004 1:13 pm    Post subject: : The second triple of values in the md5camera data is the X, Y and Z value of the camera's rotation quaternion, not the heading, pitch, bank format.

I'll take a look at LScript, maybe it turns out that writing such a md5camera exporter as a layout script is not that difficult.

Bozo, if you read this please tell us if you're planning to include a md5camera exporter in your LW->md5 converter, so we don't do the same work twice. Smile
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



Rayne@Posted: Mon Oct 04, 2004 1:55 pm    Post subject: : Good thing that users like Rich and der_ton are looking into this.. I really interested in a md5camera export for lightwave.. :thumbs up!:


Sadly, I really don't know anything regarding Lscripts, so I can offer help for beta testing things... Let me know... Let US know...
_________________
theRev is coming...



rich_is_bored@Posted: Mon Oct 04, 2004 6:21 pm    Post subject: : Damn it der_ton. There's that word again! "quaternion"

I guess I have to do some reading....

meh! Sad
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



weak@Posted: Mon Oct 04, 2004 6:36 pm    Post subject: : maybe that helps:

Matrix and Quaternion FAQ



rich_is_bored@Posted: Mon Oct 04, 2004 8:11 pm    Post subject: : No help at all but thanks for trying. I'm seeing a bunch of algorithms rather than explanations.

This is alot better...

http://www.gamedev.net/reference/articles/article1095.asp

But I still don't have this worked out. Here's an example I was toying with but at this point I'm stumpped...

Euler to Quaternion Conversion

Code:
Qx = [ cos(a/2), (sin(a/2), 0, 0)]
Qy = [ cos(b/2), (0, sin(b/2), 0)]
Qz = [ cos(c/2), (0, 0, sin(c/2))]


So, if...

Code:
a = 90
b = 180
c = 270


Then...

Code:
Qx = [ cos(90/2), (sin(90/2), 0, 0)]
Qy = [ cos(180/2), (0, sin(180/2), 0)]
Qz = [ cos(270/2), (0, 0, sin(270/2))]


And...

Code:
Qx = [ cos(45), (sin(45), 0, 0)]
Qy = [ cos(90), (0, sin(90), 0)]
Qz = [ cos(135), (0, 0, sin(135))]


And...

Code:
Qx = [ 0.70710678118654752440084436210485, (0.70710678118654752440084436210485, 0, 0)]
Qy = [ 0, (0, 1, 0)]
Qz = [ -0.70710678118654752440084436210485, (0, 0, 0.70710678118654752440084436210485)]


And to figure out Qw I need to...

Code:
Qw = Qx * Qy * Qz


Which means I need to multiply the unit quaternions. The formula is...

Code:
Q1 * Q2 =( w1.w2 - v1.v2, w1.v2 + w2.v1 + v1*v2)


But that's only for two unit quaternions. I need to multiply three. Does...

Code:
Qw = (Qx * Qy) * Qz


... give me the same result as if I multiplied them all at once? The reason I ask is because this statement throws me off...

Quote:
Be aware that the order of multiplication is important. Quaternion multiplication is not commutative, meaning

q1 * q2 does not equal q2 * q1

_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



weak@Posted: Mon Oct 04, 2004 8:30 pm    Post subject: : think so


rich_is_bored@Posted: Tue Oct 05, 2004 5:21 pm    Post subject: : Okay, well I'm done screwing with quaternions. Unless of course I can borrow a high school math teacher for a few days.

Anyway, I'm looking over the MOT format again and I realize that the rotation values are not in degrees. For instance here's the rotation portion of a MOT...

Code:
Channel 3
{ Envelope
  2
  Key 0 0 0 0 0 0 0 0 0
  Key 1.5707963 2.5 0 0 0 0 1.5706217 0 0
  Behaviors 1 1
}
Channel 4
{ Envelope
  2
  Key 0 0 0 0 0 0 0 0 0
  Key 3.1415925 2.5 0 0 0 0 3.1412435 0 0
  Behaviors 1 1
}
Channel 5
{ Envelope
  2
  Key 0 0 0 0 0 0 0 0 0
  Key 4.712389 2.5 0 0 0 0 4.7118654 0 0
  Behaviors 1 1
}


Now, the values we have here are...

Heading 1.5707963
Pitch 3.1415925
Bank 4.712389

And in Lightwave they are...

Heading 90
Pitch 180
Bank 270

WTF?

Anyway, before I even started this thread I was looking at the SDK in an attempt to find information on this but I couldn't find a section on motion files so I tried to decypher it myself.

I decided to read through it today and I noticed that the Scene files are also plain text and very similar. Infact they have a section in the SDK that pretty much explains the format of MOT files.

Take a look at this...

Quote:
Envelopes

An envelope defines a function of time. For any animation time, an envelope's parameters can be combined to generate a value at that time. Envelopes are used to store position coordinates, rotation angles, scale factors, camera zoom, light intensity, texture parameters, and anything else that can vary over time.

The envelope function is a piecewise polynomial curve. The function is tabulated at specific points, called keys. The curve segment between two adjacent keys is called a span, and values on the span are calculated by interpolating between the keys. The interpolation can be linear, cubic, or stepped, and it can be different for each span. The value of the function before the first key and after the last key is calculated by extrapolation.

In scene files, an envelope is stored in a block named Envelope that contains one or more nested Key blocks and one Behaviors block.

{ Envelope
nkeys
Key value time spantype p1 p2 p3 p4 p5 p6
Key ...
Behaviors pre post
}

The nkeys value is an integer, the number of Key blocks in the envelope. Envelopes must contain at least one Key block. The contents of a Key block are as follows.

value
The key value, a floating-point number. The units and limits of the value depend on what parameter the envelope represents.

time
The time in seconds, a float. This can be negative, zero or positive. Keys are listed in the envelope in increasing time order.

spantype
The curve type, an integer. This determines the kind of interpolation that will be performed on the span between this key and the previous key, and also indicates what interpolation parameters are stored for the key.

0 - TCB (Kochanek-Bartels)
1 - Hermite
2 - 1D Bezier (obsolete, equivalent to Hermite)
3 - Linear
4 - Stepped
5 - 2D Bezier

p1...p6
Curve parameters. The data depends on the span type.

TCB, Hermite, 1D Bezier
The first three parameters are tension, continuity and bias. The fourth and fifth parameters are the incoming and outgoing tangents. The sixth parameter is ignored and should be 0. Use the first three to evaluate TCB spans, and the other two to evaluate Hermite spans.
2D Bezier
The first two parameters are the incoming time and value, and the second two are the outgoing time and value.

The Behaviors block contains two integers.

pre, post
Pre- and post-behaviors. These determine how the envelope is extrapolated at times before the first key and after the last one.

0 - Reset
Sets the value to 0.0.
1 - Constant
Sets the value to the value at the nearest key.
2 - Repeat
Repeats the interval between the first and last keys (the primary interval).
3 - Oscillate
Like Repeat, but alternating copies of the primary interval are time-reversed.
4 - Offset Repeat
Like Repeat, but offset by the difference between the values of the first and last keys.
5 - Linear
Linearly extrapolates the value based on the tangent at the nearest key.


Now, the problem is that they don't explain how they are storing the value parameter other than saying it varies depending on what it represents. Well duh! No shit! But that doesn't explain how 1.5707963 = 90 degrees.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Tue Oct 05, 2004 5:53 pm    Post subject: : So the value is "3.1415925" where it should be 180? Hmm, let me think why this number seems familiar... Wink

These are radians. Convert between degrees and radians with these formulae:
deg = rad * 180 / PI
rad = deg * PI / 180
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



BNA!@Posted: Tue Oct 05, 2004 6:13 pm    Post subject: :
der_ton wrote:
So the value is "3.1415925" where it should be 180? Hmm, let me think why this number seems familiar... Wink

These are radians. Convert between degrees and radians with these formulae:
deg = rad * 180 / PI
rad = deg * PI / 180


Oh god, another "Dan Brown" style "messages hidden in numbers" guy... Smile
_________________
Staff - The world is yours, soon in 6 degrees of freedom!
Visit ModWiki



rich_is_bored@Posted: Tue Oct 05, 2004 6:18 pm    Post subject: :

Razz
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Tue Oct 05, 2004 11:06 pm    Post subject: : Here's a very simple script that dumps camera info into a file for frames 1 to 10. Unfortunately the values are all identical even for an animated camera, I'm not sure what I'm doing wrong.
Anyway, if that problem is solved, it's just a small step to a real md5camera exporter. All you need to do is convert the euler angles to quaternions, add a gui with adjustable framerange, and ofcourse format the output in md5camera syntax.

The relevant pages in the LScript reference documentation are page 115 (Camera Object Agents) and page 97 (common data members and methods for Object Agents).
Code:

@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // camera that we are going to export (Camera Object Agent)


exportmd5camera
{

   exportfile.writeln("name ", cam.name, "\n");
   for (i = 1; i<=10; i++)
   {
      fovarray = cam.fovAngles(i);
      exportfile.writeln("pos ", cam.getPosition(i));
      exportfile.writeln("rot ", cam.getRotation(i));
      exportfile.writeln("fov ", fovarray[1], " ", fovarray[2], "\n");
   }
   
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to open ",exportfilename);
      return;
   }
   exportmd5camera();
}

_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Tue Oct 05, 2004 11:34 pm    Post subject: : I was initially going to try writing the program in C++ but since I'm such a noob, and you've already started writing a LScript, I'll make the switch.

EDIT: Yeah, I see what your saying. It exports two frames on my end and then just repeats itself.

Code:
name Camera

pos 0 0 -1.99997
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -4.00005
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -5
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -5
rot 0 0 0
fov 0.789582 0.60577

pos 0 0 -5
rot 0 0 0
fov 0.789582 0.60577

...

continues


EDIT AGAIN: That parameter is time and not frame. So when you say ...

Code:
   for (i = 0; i<=10; i++)
   {
      fovarray = cam.fovAngles(i);
      exportfile.writeln("pos ", cam.getPosition(i));
      exportfile.writeln("rot ", cam.getRotation(i));
      exportfile.writeln("fov ", fovarray[1], " ", fovarray[2], "\n");
   }


... and increment i your really telling it to export the status of the camera from 1 second to 10.

So, when the animation is less than 10 seconds, the last frame is repeatedly exported.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Wed Oct 06, 2004 12:41 am    Post subject: : Okay, I sorted it out and here is the updated code...

Code:

@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // camera that we are going to export (Camera Object Agent)


exportmd5camera
{
    // Get the fps, starting, and ending frames from the scene.
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;
   exportfile.writeln("name ", cam.name, "\n");
   count = s_start;
   i = 0;
   while (count <= s_end){
      i = count / s_fps;        // Increment the time according to the fps.
      exportfile.writeln("frame ", count);
      exportfile.writeln("time ", i);
      fovarray = cam.fovAngles(i);
      exportfile.writeln("pos ", cam.getPosition(i));
      exportfile.writeln("rot ", cam.getRotation(i));
      exportfile.writeln("fov ", fovarray[1], " ", fovarray[2], "\n");
      count++;
   }
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to open ",exportfilename);
      return;
   }
   exportmd5camera();
}


I'm going to stop at this point and get back to this tommorow.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Wed Oct 06, 2004 12:38 pm    Post subject: : I've added md5-formatted output and the euler->quaternion conversion.
Now all there is to do is to get the camera rotated right, by swizzling/negating the euler angles before the quaternion is calculated. This would best be done by trial&error, because it can be confusing to try to get it right by thinking it through deterministically. That's because Doom3 uses a different coordinate system than LW, and because cameras are not oriented in the same way as in D3 (md5 cameras look down their x-axis, z is up, in LW they are looking down their z-axis, y is up).

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getRotation(i/s_fps);
      pos=cam.getPosition(i/s_fps);
      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot.x/360*PI);
      c2=cos(rot.y/360*PI);
      c3=cos(rot.z/360*PI);
      s1=sin(rot.x/360*PI);
      s2=sin(rot.y/360*PI);
      s3=sin(rot.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3;
      q2=c1*s2*c3+s1*c2*s3;
      q3=c1*c2*s3-s1*s2*c3;
      
      exportfile.writeln("\t( ", pos_d3," ) ( ",q1, " ", q2, " ", q3, " ) ",fovarray[1]*180/PI); //maybe fovarray[2] (vertical fov) should be used?
   }
   exportfile.writeln("}");
   
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
}

_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Wed Oct 06, 2004 6:25 pm    Post subject: : I've updated the code so that it is rotates the camera correctly. I've included comments to distinguish which is the heading, pitch, and bank.

I created a map with different textures on each wall so that I could gauge the orientation of the camera. From this point I exported a test camera repeatedly. Each time I tried a different rotation and used what I seen in game to determine if the rotation was correct.

Aside from the values needing to be written in the correct order, for some reason the pitch was inverted, for instance if I told the camera to rotate up it went down. I've fixed that by multipying q2 by -1 although I'm not sure if that fix is the correct one.

At any rate, it seems to be 100% and I'm going to toy with it a bit more just to be sure.

Here's the updated code...

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getRotation(i/s_fps);
      pos=cam.getPosition(i/s_fps);
      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot.x/360*PI);
      c2=cos(rot.y/360*PI);
      c3=cos(rot.z/360*PI);
      s1=sin(rot.x/360*PI);
      s2=sin(rot.y/360*PI);
      s3=sin(rot.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3; //bank
      q2=c1*s2*c3+s1*c2*s3; //pitch
      q3=c1*c2*s3-s1*s2*c3; //heading

      // invert q2 because it rotates up when it should look down
      q2=q2*-1;

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", pos_d3," ) ( ",q3, " ", q2, " ", q1, " ) ",fovarray[1]*180/PI);

   }
   exportfile.writeln("}");
   
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
}

_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?


Last edited by rich_is_bored on Wed Oct 06, 2004 6:57 pm; edited 2 times in total



der_ton@Posted: Wed Oct 06, 2004 6:44 pm    Post subject: : Hmm, I would have found it alot cleaner if you had done the manipulations to the euler angles instead of the quaternion results (negating/swizzling rot.x/y/z before they are used in the quaternion conversion), but hey, that's totally irrelevant if we end up with a working script. Wink

And q1, q2 and q3 are not bank, pitch heading. These are terms that apply to the euler angles.
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Wed Oct 06, 2004 7:08 pm    Post subject: : It works great. I can keyframe movement and rotation no problem. Smile

The only issue is that if I were to for instance, target the camera at a null, the orientation of the camera is affected in Lightwave but this does not effect the MD5camera.

This isn't a problem with the LScript though because I had the same issue trying to export motion paths. I'm sure there is a way to keyframe that kind of motion and I know if keyframed it would carry over into Doom 3.

The only thing left is to implement some way of performing cuts. Of course that isn't something you can keyframe so the only way to do it is to ask the user for a list of keys.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Wed Oct 06, 2004 7:16 pm    Post subject: : Maybe there's a way to bake constraint motion into keyframes, like you can do in Blender?

The FOV thingy is alright as it is, because Doom3's FOV denotes the horizontal FOV, too. The horizontal and vertical FOV are usually different, except if you have a camera that records a square-shaped image.
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Thu Oct 07, 2004 1:14 am    Post subject: : There is a guy who wrote a plugin called Transmotion or something that does just that. But, I'm not condoning people download it just for this.

Anyway, I've found a solution.

Instead of using getPostition and getRotation you use getWorldPosition and getWorldRotation.

Now, I can rig a camera up however I please but sometimes the values that are exported are in scientific notation (i.e -1.47128e-006).

I have to come up with a way to force the program to export decimal notation. I have a few ideas and I'll try them out and see how it goes.

On a side note there is another problem. It's not exporting the correct number of frames. It could be this isn't gathering the correct values...

Code:
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.framestart;
   s_end = scn.frameend;


Anyway, I'll be sorting both of these issues out tonight and if I find solutions I'll post an update.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Thu Oct 07, 2004 9:17 am    Post subject: : Here is a maxscript function that I used for the max->md5 exporter to print floats without scientific notation. I think the algorithm should be implementable in LScript, too.
Code:
fn printfloat flt =
--maxscript doesn't provide a way to format float output that I know of. It seems that Doom3 does not understand the <x>e<y> notation of floats
--if you plan to use this in your scripts, note that this function doesn't check against a large number passed in
(
   --if (abs flt)>10.0 then return flt -- this could be used to gain some speed.
   --else
   local numdigits = 10
   (
      str = ""
      if flt<0 then str += "-"
      flt2 = abs flt
      str += ((flt2 as integer) as string)
      flt2 -= (flt2 as integer)
      str +="."
      for i=1 to numdigits do
      (
         flt2 *=10.0
         str += ((flt2 as integer) as string)
         flt2 -= (flt2 as integer)
         if abs flt2 < (1/10^(numdigits-i+1)) then break
      )
      return str
   )
)

_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Fri Oct 08, 2004 2:22 am    Post subject: : Thanks for that code. I had nearly the same worked out when I finally got around to checking this thread again but it was still very helpful because i wasn't finished with it.

EDIT: Updated code and a shitload of problems. Details are in the comments...

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

forceDecimal: num
{
    // Lscript will convert a decimal to scientific notation after 4 consecutive zeros.

    numDigits = 10;
    strOutput = "";
    if (num<0)
    {
        strOutput += "-";
    }
    num = abs(num);
    num2 = frac(num);
    num = num - num2;
    strOutput += string(num) + ".";
    for (i=1; i<=numDigits; i++)
      {
         num2 *= 10.0;
         strOutput += string(integer(num2));
         num2 -= integer(num2);
         if (abs(num2) < (pow(1/10,(numDigits-i+1)))) { break; }
      }
    return strOutput;
}

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.previewstart;
   s_end = scn.previewend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getWorldRotation(i/s_fps);
      pos=cam.getWorldPosition(i/s_fps);

      // corrections based on 360 degree test camera rotations.

      /*
         For some reason, values over 180 or less than -180 caused the camera to switch
         directions.

         For instance, a 0 to 360 rotation around the heading would work fine until
         heading reaches a value of 181. At this point instead of continuing in a clockwise
         direction it would reverse direction. It behaves as if 181 is the same as
         179.

         I'm guessing that values in the range of -180 to 180 are the only acceptable values?

         It's driving me nuts because those are the only values that work. Anyway, I've
         corrected that issue using the conditional statements below. This way whenever a
         value is beyond that range it is looped back around into it.

         The only problem is I'm not sure how to adjust the camera orientation now. The
         camera points east at 0,0,0 when 0,0,0 should point north.

         If I adjust the conditional statments affecting rot.x to compensate for this,
         I get really funky results. For instance, a rotation around the bank would result
         in a 270 degree rotation around pitch that then stops and then rotates 360 around
         the bank.

         I'm sure the reason I'm struggling with this is because I don't understand the math
         behind quaternions.

         If i did, I could adjust for the orientation after the quaternion calculations and
         maybe that would make a world of difference.

      */

      rot=<rot.x, rot.y, rot.z>;

      // correct rot.x
      if (rot.x < -180) {
          rot.x += 360;
      }
      if (rot.x > 180) {
          rot.x -= 360;
      }

      // correct rot.y
      if (rot.y < -180) {
          rot.y += 360;
      }
      if (rot.y > 180) {
          rot.y -= 360;
      }

      // correct rot.z
      if (rot.z < -180) {
          rot.z += 360;
      }
      if (rot.z > 180) {
          rot.z -= 360;
      }

      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot.x/360*PI);
      c2=cos(rot.y/360*PI);
      c3=cos(rot.z/360*PI);
      s1=sin(rot.x/360*PI);
      s2=sin(rot.y/360*PI);
      s3=sin(rot.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3; //pitch
      q2=c1*s2*c3+s1*c2*s3; //bank
      q3=c1*c2*s3-s1*s2*c3; //heading

      // convert quaternions to strings so they can be written in decimal notation.
      v1=forceDecimal(q1);
      v2=forceDecimal(q2);
      v3=forceDecimal(q3);

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", pos_d3," ) ( ",v3, " ", v2, " ", v1, " ) ",fovarray[1]*180/PI);

   }
   exportfile.writeln("}");
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
   info("Export Complete");
}


It's like slowly pushing a nail through your skull. Mad

Everytime I think I've just about got it figured out, I find something else wrong with it.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



der_ton@Posted: Fri Oct 08, 2004 11:28 am    Post subject: : You should use the forceDecimal function on all seven floats that get output.

About the camera rotation issue: you should go back to the original quaternion output (don't swizzle the 3 quaternion components), and manipulate (swizzle/negate) the euler angles before or after they're wrapped into the -180;+180 interval.

And you should change the
if (rot.x < -180)
to
while (rot.x < -180), just to be really sure.
_________________
Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5



rich_is_bored@Posted: Fri Oct 08, 2004 8:43 pm    Post subject: : Okay! Smile

I've got the orientation issues worked out. I swiveled the euler angles instead and it works great.

I placed the range value wrapping conditional statement in it's own function to optimize the code a bit.

I've tested full 360 degree heading, pitch and bank rotations. I've tested very complex camera rigs. Everything seems to be in order.

Here's the updated code...

Code:
@version 2.0
@warnings
@script generic


exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)

wrapValues: num
{
    // takes values that exceed range and loops them back into valid values

    if (num < -180) {
      num += 360;
    }
    if (num > 180) {
      num -= 360;
    }
    return num;
}

forceDecimal: num
{
    // Lscript will convert a decimal to scientific notation after 4 consecutive zeros.

    numDigits = 10;
    strOutput = "";
    if (num<0)
    {
        strOutput += "-";
    }
    num = abs(num);
    num2 = frac(num);
    num = num - num2;
    strOutput += string(num) + ".";
    for (i=1; i<=numDigits; i++)
      {
         num2 *= 10.0;
         strOutput += string(integer(num2));
         num2 -= integer(num2);
         if (abs(num2) < (pow(1/10,(numDigits-i+1)))) { break; }
      }
    if (num2==0) {
        strOutput += "0000000000";
    }
    return strOutput;
}

exportmd5camera
{
   scn = Scene();
   s_fps = scn.fps;
   s_start = scn.previewstart;
   s_end = scn.previewend;
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", s_end-s_start+1);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts 0\n");
   exportfile.writeln("cuts {\n}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getWorldRotation(i/s_fps);
      pos=cam.getWorldPosition(i/s_fps);

      // swivel camera values and adjust heading orientation
      rot_d3=<rot.y, rot.z, rot.x - 90>;

      // wrap rotational values into range -180 to 180
      rot_d3.x = wrapValues(rot_d3.x);
      rot_d3.y = wrapValues(rot_d3.y);
      rot_d3.z = wrapValues(rot_d3.z);

      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot_d3.x/360*PI);
      c2=cos(rot_d3.y/360*PI);
      c3=cos(rot_d3.z/360*PI);
      s1=sin(rot_d3.x/360*PI);
      s2=sin(rot_d3.y/360*PI);
      s3=sin(rot_d3.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3;
      q2=c1*s2*c3+s1*c2*s3;
      q3=c1*c2*s3-s1*s2*c3;

      // convert values to strings so they can be written in decimal notation
      v1=forceDecimal(pos_d3.x);
      v2=forceDecimal(pos_d3.y);
      v3=forceDecimal(pos_d3.z);
      v4=forceDecimal(q1);
      v5=forceDecimal(q2);
      v6=forceDecimal(q3);
      v7=forceDecimal(fovarray[1]*180/PI);

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", v1, " ", v2, " ", v3, " ) ( ", v4, " ", v5, " ", v6, " ) ", v7);

   }
   exportfile.writeln("}");
}

generic {
   scn = Scene();
   userobj = scn.firstSelect();
   if (userobj == nil || userobj.genus != CAMERA)
   {
      //no camera was selected, so we export the first in the scene if there is any
      userobj = Camera();
      if (userobj == nil)
      {
         info ("No camera in the scene");
         return;
      }
   }
   cam = userobj;
   exportfilename = getfile("Export File", "*.md5camera");
   if(exportfilename == nil)
      return;
   exportfile = File(exportfilename,"w");
   if(exportfile == nil)
   {
      error("Unable to get writing access to ",exportfilename);
      return;
   }
   exportmd5camera();
   info("Export Complete");
}


Do you have any ideas on how we could implement cuts or should we just leave that as something you hand edit in?
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



goliathvt@Posted: Fri Oct 08, 2004 8:54 pm    Post subject: : I haven't wanted to interrupt this thread because you two were working so well through the various problems and obstacles in creating this thing... but:

Thank you rich_is_bored and der_ton! Your collective work is awesome!

Very Happy

G
_________________
Staff



Rayne@Posted: Fri Oct 08, 2004 10:32 pm    Post subject: : Just tried your script. It's amazing!!! a very very gooood work!


Thanks Rich and der_ton!
_________________
theRev is coming...



rich_is_bored@Posted: Fri Oct 08, 2004 10:49 pm    Post subject: : No, that's fine. I appriciate any comments or critiques.

At this point the script supports position, rotation, and field of view. I still need to work out a way to deal with cuts.

Do you guys have any suggestions?

What I was thinking is maybe using dopesheet markers although I haven't tried seeing if that kind of data is accessible through an LScript.

Once the script is fully functional I'll start on an interface.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



Rayne@Posted: Sat Oct 09, 2004 6:15 am    Post subject: : By now i've made some tests, and I've not discovered bad issues. I'll continue this afternoon, but for now I've got no problems with the exported stuff (read: my cams works perfectly)


But like I said before, i'm a newbie in lightwave, so I can't help you with the "cuts" stuff.


Good work!
_________________
theRev is coming...



rich_is_bored@Posted: Sat Oct 09, 2004 8:16 am    Post subject: : Well, I investigated the dopesheet idea and there doesn't appear to be a way to access it.

Anyway, I've integrated an interface. It's just your standard LScript type stuff. I'm not interested in trying to make it look pretty.

I'll post the code and then I'll talk about it some more...

Code:
@version 2.0
@warnings
@script generic

exportfile = nil; // exportfile
cam = nil; // cam that we are going to export (Camera Object Agent)
numcuts = nil; // number of cuts
cuts = nil; // array of cuts
numframes = nil; // number of frames
s_fps = nil; // frames per second
s_start = nil; // starting frame
s_end = nil; // ending frame

wrapValues: num
{
    // takes values that exceed range and loops them back into valid values

    if (num < -180) {
      num += 360;
    }
    if (num > 180) {
      num -= 360;
    }
    return num;
}

forceDecimal: num
{
    // Lscript will convert a decimal to scientific notation after 4 consecutive zeros.

    numDigits = 10;
    strOutput = "";
    if (num<0)
    {
        strOutput += "-";
    }
    num = abs(num);
    num2 = frac(num);
    num = num - num2;
    strOutput += string(num) + ".";
    for (i=1; i<=numDigits; i++)
      {
         num2 *= 10.0;
         strOutput += string(integer(num2));
         num2 -= integer(num2);
         if (abs(num2) < (pow(1/10,(numDigits-i+1)))) { break; }
      }
    if (num2==0) {
        strOutput += "0000000000";
    }
    return strOutput;
}

exportmd5camera
{
   
   exportfile.writeln("MD5Version 10");
   exportfile.writeln("commandline \"\"\n");
   exportfile.writeln("numFrames ", numframes);
   exportfile.writeln("frameRate ", s_fps);
   exportfile.writeln("numCuts "+ numcuts + "\n");
   exportfile.writeln("cuts {");
   for (i = 1; i<=numcuts; i++)
   {
         exportfile.writeln(cuts[i]);
   }
   exportfile.writeln("}\n");
   exportfile.writeln("camera {\n");
   
   for (i = s_start; i<=s_end; i++)
   {
      fovarray = cam.fovAngles(i/s_fps);
      rot=cam.getWorldRotation(i/s_fps);
      pos=cam.getWorldPosition(i/s_fps);

      // swivel camera values and adjust heading orientation
      rot_d3=<rot.y, rot.z, rot.x - 90>;

      // wrap rotational values into range -180 to 180
      rot_d3.x = wrapValues(rot_d3.x);
      rot_d3.y = wrapValues(rot_d3.y);
      rot_d3.z = wrapValues(rot_d3.z);

      pos_d3=<pos.x, pos.z, pos.y>;
      c1=cos(rot_d3.x/360*PI);
      c2=cos(rot_d3.y/360*PI);
      c3=cos(rot_d3.z/360*PI);
      s1=sin(rot_d3.x/360*PI);
      s2=sin(rot_d3.y/360*PI);
      s3=sin(rot_d3.z/360*PI);
      q1=s1*c2*c3-c1*s2*s3;
      q2=c1*s2*c3+s1*c2*s3;
      q3=c1*c2*s3-s1*s2*c3;

      // convert values to strings so they can be written in decimal notation
      v1=forceDecimal(pos_d3.x);
      v2=forceDecimal(pos_d3.y);
      v3=forceDecimal(pos_d3.z);
      v4=forceDecimal(q1);
      v5=forceDecimal(q2);
      v6=forceDecimal(q3);
      v7=forceDecimal(fovarray[1]*180/PI);

      // User should set camera aspect ratio to 1:1 so that fovarray[1] and fovarray[2] are the same?
      exportfile.writeln("\t( ", v1, " ", v2, " ", v3, " ) ( ", v4, " ", v5, " ", v6, " ) ", v7);

   }
   exportfile.writeln("}");
}

toggleOff: value
{
    return(false);
}

generic
{

    // get info for requestor
    scn = Scene();
    userobj = scn.firstSelect();
    if (userobj == nil || userobj.genus != CAMERA)
    {
       //no camera was selected, so we export the first in the scene if there is any
       userobj = Camera();
       if (userobj == nil)
       {
          info ("No camera in the scene");
          return;
       }
    }
    cam = userobj;
    cutlist = "";
    exportfilename = "";
    s_fps = scn.fps;
    s_start = scn.previewstart;
    s_end = scn.previewend;
    numframes = s_end - s_start + 1;

    // display requester
    reqbegin("Lightwave MD5camera Exporter");
    reqsize(492,155);

    c1 = ctlinteger("Frame Rate",s_fps);
    ctlposition(c1,361,12);

    c2 = ctlstring("Cut List",cutlist);
    ctlposition(c2,10,73,476,18);

    c3 = ctlfilename("Save As",exportfilename);
    ctlposition(c3,5,104,458,19);

    c4 = ctlinteger("Start Frame",s_start);
    ctlposition(c4,85,42);

    c5 = ctlinteger("End Frame",s_end);
    ctlposition(c5,225,42);

    c6 = ctlinteger("Total Frames",numframes);
    ctlposition(c6,355,42);

    c7 = ctlcameraitems("Camera",cam);
    ctlposition(c7,3,11,343,19);

    // deactivate all unmodifiable controls
    ctlactive(c3, "toggleOff", c1, c4, c5, c6);

    return if !reqpost();

    s_fps = getvalue(c1);
    cutlist = getvalue(c2);
    exportfilename = getvalue(c3);
    s_start = getvalue(c4);
    s_end = getvalue(c5);
    numframes = getvalue(c6);
    cam = getvalue(c7);

    reqend();

    // validate values and setup file for output
    if(cam == nil)
    {
        error ("No camera selected");
        return;
    }
    if(cutlist == nil) {
        numcuts = 0;
    } else {
        cuts = parse(";",cutlist);
        numcuts = size(cuts);
        //info(string(numcuts) + " cuts were found");
        for (i=1; i<=numcuts; i++)
        {
            test = integer(cuts[i]);
            if (test == nil)
            {
                error(cuts[i]+" is not an integer");
                return;
            } else {
                cuts[i] = test - s_start;
            }
            //error(cuts[i]+" is not an integer");
            if (integer(cuts[i]) < s_start or integer(cuts[i]) > s_end)
            {
                error(cuts[i]+" is out of range");
                return;
            }
        }
    }
    exportfile = File(exportfilename,"w");
    if(exportfile == nil)
    {
       error("Unable to get writing access to ",exportfilename);
       return;
    }
    exportmd5camera();
    info("Export Complete");
}


Okay, the way I'm doing the cuts is through a single textbox. The way it works is you type the frame numbers where you want the cuts to take place.

For instance, say you want cuts at frame 10 and 20 on the timeline in Lightwave. You simply type...

Code:
10;20


... in the cuts list text box.

Keep me posted if there are any problems with this latest iteration as I haven't done much testing. I've been mainly testing the interface.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Mon Oct 25, 2004 8:22 pm    Post subject: : I'm going to package this up into a nice little zip and update the first post with a link.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?



rich_is_bored@Posted: Thu May 26, 2005 7:27 am    Post subject: : Long time no update but I figured I'd chime in and let you all know about a problem with the script that's been on my mind for a while but I've not been terribly bothered to integrate a solution.

There is a bug with cuts. Well, not really a bug but it more or less amounts to a bug because I'm not sure if you can move the camera in the fashion needed in order for cuts to work.

The way it works now is you manually tell the script what frames to make cuts at. The problem is that while the script is exporting what amounts to multiple clips we're working with a single camera that uses one continuous fluid motion path.

So, if I cut on one frame to start the next clip in the script, the camera in Lightwave doesn't know that this should be a completely different movement.

The problem is that you can't get a camera's motion path to abruptly end and then start up again without the previous motion carrying over into the next.

This becomes a problem because if I insert a cut at one point and try to start a new clip immediately on the next frame at a different position Lightwave will interpret the motion from the two keyframes as something that needs interpolated.

The solution is to use a separate camera for each cut but that would require major overhaul to the current script and I just don't have the time to really look into it.
_________________
Staff
Learn something today? Why not write an article about it on modwiki.net?