home *** CD-ROM | disk | FTP | other *** search
/ Stone Design / Stone Design.iso / Stone_Friends / Wave / WavesWorld / Source / IBPalettes / WW3DKit / EveCommand.m < prev    next >
Encoding:
Text File  |  1995-05-14  |  60.6 KB  |  1,446 lines

  1. // copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
  2. // see COPYRIGHT for reuse legalities
  3. //
  4.  
  5. // EveCommands have two important pieces of state in them: a single, atomic
  6. // eve command that they are based on, and a list of samples, representing
  7. // the various values of that eve command over the course of a scene.
  8.  
  9. // They conform to the WWAnimatable protocol, and are probably the simplest
  10. // objects that conform to it.
  11.  
  12.  
  13. #import "EveCommand.h"
  14. #import "TCLCommand.h"
  15. #import "WWTCLKit.h"
  16. //#import "WWTCLClosedCmd.h"
  17.  
  18. #import "WWEveParser.h"
  19. #import "Protocol_WWSample.h"
  20. #import "WWSample.h"
  21. #import "WWSampleList.h"
  22. #import "WW3DShape.h"
  23. #import "WW3DWell.h"  // has the sceneClock protocol - formalize this!
  24.  
  25. @implementation EveCommand
  26.  
  27. + initialize { return [EveCommand setVersion:2], self; }
  28.  
  29. static char *WriteProc(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
  30. {
  31.    id  eveCmd;
  32.  
  33.  
  34.   if ([(id)clientData respondsTo:@selector(datum)])
  35.   {  eveCmd = [(id)clientData datum];
  36.      if ([eveCmd conformsTo:@protocol(WWAnimatable)])
  37.      {  [eveCmd resample];
  38.      }
  39.      else
  40.      {  NXLogError("%s doesn't conform to the WWAnimatable protocol - ", [eveCmd name]);
  41.         NXLogError("why was it stored as the datum of a WWTCLVarTrace and sent to an EveCommand's WriteProc?\n"); 
  42.      }
  43.   }
  44.   else
  45.   {  NXLogError("%s isn't a WWTCLVarTrace - why is it getting sent to an EveCommand's WriteProc?\n", [(id)clientData name]);
  46.   }
  47.   return NULL;
  48. }
  49.  
  50.  
  51. - initWithInterp:newInterp eveParser:newEveParser shape:newShape cmd:(char *)newCmd clock:newSceneClock frozen:(BOOL)frozenOrMalleable
  52. {
  53.   char            *ptr, 
  54.                   *firstCmd = NULL, 
  55.                   *cmdString;
  56.   id <WWSample>   newSample;
  57.   WWTCLClosedCmd  *tmpCmd, *closedCmd;
  58.  
  59.  
  60.   [super init];
  61.   
  62.   isTCLCommand = NO;
  63.   interp = newInterp; 
  64.   eveParser = newEveParser; 
  65.   myShape = newShape;
  66.  
  67.   // we'll use this temporarily...
  68.   tmpCmd = [[WWTCLClosedCmd alloc] initWithInterp:interp];
  69.   [tmpCmd setOriginalExpression:newCmd]; 
  70.   cmd = tmpCmd;
  71.   sceneClock = newSceneClock;
  72.   samplesList = [[WWSampleList alloc] initCount:1];  // so we don't need to grow for the default case
  73.  
  74.   // the following might not just be a simple subclass of RIBCommand,
  75.   // but rather a tcl command or an articulated procedure.  if it's an
  76.   // articulatable tcl command like "set", we need to catch that 
  77.  
  78.   ptr = cmdString = (char *)[tmpCmd cmd];
  79.   // "set yabba" would give a ptr of 3
  80.   while (*ptr && (*ptr != ' ')) { ptr++; }
  81.   firstCmd = (char *)calloc(1, (1 + (ptr - cmdString)));
  82.   strncpy(firstCmd, cmdString, (ptr - cmdString));
  83.  
  84.   if (!strcmp(firstCmd, "set"))
  85.   {  // the articulated command is the tcl command "set"
  86.      // we need to malloc up a TCLCommand object and we're done
  87.  
  88.      // WAVE: Need to deal with sampling tcl variables over time...
  89.      // need to grab this value and save it as a sample, right?
  90.  
  91.      if (!frozenOrMalleable)
  92.      {  [eveParser evaluateEveCommand:(char *)[cmd cmd]]; // go ahead and do the set now...
  93.         newSample = [[[WWSample alloc] init] setData:[[[TCLCommand alloc] init] setCmd:[tmpCmd cmd]] 
  94.                                              timestamp:[sceneClock timestamp] 
  95.                                              generator:[eveParser currentSampleGeneratorName] 
  96.                                              weight:[eveParser currentSampleWeight]];
  97.         [samplesList addSample:newSample];
  98.      }
  99.  
  100.      isTCLCommand = YES;
  101.   }
  102.   else
  103.   {  // okay, it's a regular old RIBCommand (I hope...)
  104.      // we need to evaluate the eve command at least once to get initial info from it.
  105.      if (!frozenOrMalleable)
  106.      {  firstSampleData = [self produceSampleFromCmd];
  107.         if (!firstSampleData)
  108.         {  return nil;
  109.         }
  110.         newSample = [[[WWSample alloc] init] setData:firstSampleData 
  111.                                              timestamp:[sceneClock timestamp] 
  112.                                              generator:[eveParser currentSampleGeneratorName] 
  113.                                              weight:[eveParser currentSampleWeight]];
  114.         [samplesList addSample:newSample];
  115.      }
  116.      else
  117.      {  firstSampleData = nil;
  118.      }
  119.   }
  120.  
  121.   // need to parse cmd to get the names of all the tcl variables referenced - only if malleable
  122.   if (!frozenOrMalleable)
  123.   {  closedCmd = [[eveParser tclInterp] generateClosureFrom:(char *)[cmd cmd] 
  124.                                         andForGlobalsCall:(Tcl_VarTraceProc *)WriteProc 
  125.                                         usingData:(ClientData)self];
  126.      //NXLogError("cmd:\n\t<%s>\nbecame closedCmd:\n\t<%s>\n", [cmd cmd], [closedCmd cmd]);
  127.      cmd = closedCmd;
  128.      [tmpCmd free];  // we're done with this; lose it
  129.   }
  130.   else
  131.   {  // we're gonna hold on to the tmpCmd;
  132.   }
  133.  
  134.  
  135.   // since an EveCommand also might be a "set" command, which wouldn't have a firstSampleData, we do this...
  136.   dirtyBoundingBox = YES;
  137.   hasBoundingBox = NO;
  138.   isMotionBlurrable = NO;
  139.   isCompoundCommand = NO;
  140.   if (firstSampleData)
  141.   {  hasBoundingBox = [firstSampleData hasBoundingBox];
  142.      isMotionBlurrable = [firstSampleData isMotionBlurrable];
  143.      isCompoundCommand = [firstSampleData isCompoundCommand];
  144.      pushesCTM = [firstSampleData pushesCTM];
  145.      popsCTM = [firstSampleData popsCTM];
  146.   }
  147.  
  148.   if (firstCmd)
  149.   {  int  firstCmdLen = strlen(firstCmd);
  150.      sampleName = (char *)malloc(firstCmdLen + 3);
  151.      sampleName[0] = '(';
  152.      strcpy((sampleName + 1), firstCmd);
  153.      sampleName[firstCmdLen + 1] = ')';
  154.      sampleName[firstCmdLen + 2] = '\0';
  155.      free(firstCmd);
  156.   }
  157.  
  158.   cacheIsFilled = NO;
  159.  
  160.       
  161.   return self;
  162. }
  163.  
  164. - copyFromZone:(NXZone *)zone  {  return [super copyFromZone:zone]; }
  165.  
  166. - (BOOL)isLerpable { return NO; }
  167. - lerpWith:b by:(float)uValue { return self; }
  168. - lerpSelfWith:b by:(float)uValue { return self; }
  169.  
  170. - (BOOL)pushesCTM { return pushesCTM; }
  171. - (BOOL)popsCTM { return popsCTM; }
  172. - (BOOL)pushesOrPopsCTM { return (popsCTM || pushesCTM); }
  173.  
  174. - awake
  175. {
  176.   [super awake];
  177.  
  178.   if (!eveParser)
  179.   {  NXLogError("warning!: the EveCmd <%s> has been disconnected from its eveParser.\n", cmd);
  180.      return nil;
  181.   }
  182.   if (!myShape)
  183.   {  NXLogError("warning!: the EveCmd <%s> has been disconnected from its shape.\n", cmd);
  184.      return nil;
  185.   }
  186.  
  187.   firstSampleData = [samplesList objectAt:0];
  188.   cacheIsFilled = NO;
  189.   dirtyBoundingBox = YES;
  190.  
  191.   return self;
  192. }
  193.  
  194. - free
  195. {
  196.   //NXLogError("%s %p being free'ed - freeing cmd %p, samplesList %p and its objects\n", 
  197.   //           [[self class] name], self, cmd, samplesList);
  198.   [cmd free]; // this takes care of the traces
  199.   [[samplesList freeObjects] free];
  200.  
  201.   return [super free];
  202. }
  203.  
  204. - evalAndAddAsSamples:(char *)newSamplesList
  205. {
  206.    int            argc, argc2, argc3, argc4, i, j;
  207.    char           **argv = NULL,
  208.                   **argv2 = NULL,
  209.                   **argv3 = NULL,
  210.                   **argv4 = NULL;
  211.    id <WWSample>  newSample = nil, 
  212.                   sampleData = nil;
  213.   WWTCLClosedCmd  *theCmd;
  214.   float           currentTimeStamp;
  215.  
  216.  
  217.    // we're gonna muck with the cache; don't let it think it's clean   
  218.    cacheIsFilled = NO;
  219.  
  220.    // we want to hold on to cmd...
  221.    theCmd = cmd;
  222.  
  223.    Tcl_SplitList([interp interp], newSamplesList, &argc, &argv);  // argc == how many timestamps
  224.    // okay, we've now split it into the list of time samples
  225.    // for each time sample, we want to split the list into component samples
  226.    for (i = 0; i < argc; i++) // for each timestamp
  227.    {  
  228.       Tcl_SplitList([interp interp], argv[i], &argc2, &argv2);  // argc2 == 2
  229.       if (argc2 != 2)
  230.       {  NXLogError("sample #%d of cmd <%s> was badly formed: %d arguments instead of two!\n", 
  231.             i, [cmd cmd], argc2);
  232.      if (argv) {  free(argv); }
  233.      if (argv2) {  free(argv2); }
  234.          cmd = theCmd;
  235.      return nil;
  236.       }
  237.       // okay, we've now got a time sample
  238.       // argv2[0] == timestamp
  239.       // argv2[1] == list of sample components
  240.       currentTimeStamp = (float)atof(argv2[0]);
  241.  
  242.       Tcl_SplitList([interp interp], argv2[1], &argc3, &argv3); // argc3 == number of sample components
  243.       for (j = 0; j < argc3; j++)
  244.       {  
  245.         Tcl_SplitList([interp interp], argv3[j], &argc4, &argv4); // argc4 == 3
  246.          if (argc4 != 3)
  247.          {  NXLogError("sample component #%d of sample #%d of cmd <%s> was badly formed: %d arguments instead of three!\n", 
  248.                j, i, [cmd cmd], argc4);
  249.         if (argv) {  free(argv); }
  250.         if (argv2) {  free(argv2); }
  251.         if (argv3) {  free(argv3); }
  252.         if (argv4) {  free(argv4); }
  253.             cmd = theCmd;
  254.         return nil;
  255.      }
  256.          // okay, we've now got a list of named components that comprise this sample
  257.          // argv4[0] == generatorName
  258.          // argv4[1] == weight
  259.          // argv4[2] == compiled cmd
  260.          cmd = [[WWTCLClosedCmd alloc] initWithInterp:interp];
  261.          [cmd setOriginalExpression:argv4[2]]; 
  262.          sampleData = [self produceSampleFromCmd];
  263.          if (!sampleData)
  264.          {  NXLogError("warning: the animatable expression <%s> was not able to ", [cmd cmd]);
  265.             NXLogError("generate a valid renderable object for itself when rebuilding from a sample list\n");
  266.                
  267.            if (argv) {  free(argv); }
  268.         if (argv2) {  free(argv2); }
  269.         if (argv3) {  free(argv3); }
  270.         if (argv4) {  free(argv4); }
  271.             [cmd free];
  272.             cmd = theCmd;
  273.         return nil;
  274.          }
  275.          newSample = [[[WWSample alloc] init] setData:sampleData 
  276.                                                  timestamp:currentTimeStamp
  277.                                                  generator:argv4[0] weight:(float)atof(argv4[1])];
  278.          [samplesList addSample:newSample];
  279.       }
  280.       if (argv2) {  free(argv2); argv2 = NULL; }
  281.       if (argv3) {  free(argv3); argv3 = NULL; }
  282.       if (argv4) {  free(argv4); argv4 = NULL; }
  283.    }
  284.    if (argv) {  free(argv); }
  285.    firstSampleData = [newSample data];
  286.    if (firstSampleData)
  287.    {  hasBoundingBox = [firstSampleData hasBoundingBox];
  288.       isMotionBlurrable = [firstSampleData isMotionBlurrable];
  289.       isCompoundCommand = [firstSampleData isCompoundCommand];
  290.       pushesCTM = [firstSampleData pushesCTM];
  291.       popsCTM = [firstSampleData popsCTM];
  292.    }
  293.    cacheIsFilled = NO;
  294.   
  295.    // restore cmd
  296.    [cmd free];
  297.    cmd = theCmd;
  298.    return self;
  299. }
  300.  
  301.  
  302. - produceSampleFromCmd  {  return [eveParser evaluateEveCommand:(char *)[(WWTCLClosedCmd *)cmd cmd]]; }
  303.  
  304. - (float)lastSampleIsAt {  return [samplesList lastSampleIsAt]; }
  305.  
  306. - (unsigned long int)maxSampleBandwidth  { return [samplesList maxSampleBandwidth]; }
  307.  
  308. - resample
  309. {  
  310.    id <WWSample>  newSample;
  311.    id             renderableCommand;
  312.    float          timestamp;
  313.  
  314.  
  315.    if (isTCLCommand)
  316.    {  [eveParser evaluateEveCommand:(char *)[cmd cmd]];
  317.       newSample = [[[WWSample alloc] init] setData:[[[TCLCommand alloc] init] 
  318.                                            setCmd:[cmd cmd]] 
  319.                                            timestamp:[sceneClock timestamp] 
  320.                                            generator:[eveParser currentSampleGeneratorName] 
  321.                                            weight:[eveParser currentSampleWeight]];
  322.       [samplesList addSample:newSample];
  323.    }
  324.    else 
  325.    {  // the old one is already in the list...
  326.       renderableCommand = [self produceSampleFromCmd];
  327.       if (!renderableCommand)
  328.       {  NXLogError("warning: the %s <%s> wasn't able to generate a valid renderable object for itself when resampling\n", 
  329.             [self name], [cmd cmd]);
  330.          return nil;
  331.       }
  332.       newSample = [[[WWSample alloc] init] setData:renderableCommand 
  333.                                            timestamp:[sceneClock timestamp] 
  334.                                            generator:[eveParser currentSampleGeneratorName] 
  335.                                            weight:[eveParser currentSampleWeight]];
  336.       [samplesList addSample:newSample];
  337.    }
  338.    // for now, we'll invalidate my cache and stick this new data sample into it (so boundingBox works)
  339.    cacheIsFilled = NO;
  340.    firstSampleData = [newSample data];
  341.  
  342.    // if the new sample is between bbIntervalStart and bbIntervalEnd, invalidate the boundingBox cache
  343.    timestamp = [newSample timestamp];
  344.    if ((timestamp >= bbIntervalStart) && (timestamp <= bbIntervalEnd))
  345.    {  dirtyBoundingBox = YES;
  346.       [myShape setBoundingBoxDirty];
  347.    }
  348.  
  349.    return self;
  350. }
  351.  
  352. - setMyShape:shape { myShape = shape;  return self; }
  353.  
  354. - shape { return myShape; }
  355.  
  356. - (BOOL)hasBoundingBox { return hasBoundingBox; }
  357.  
  358. - setBoundingBox:(RtBound *)newBoundingBox
  359. {
  360.   N3D_CopyBound(*newBoundingBox, boundingBox);
  361.  
  362.   return self;
  363. }
  364.  
  365.  
  366. // pre-rendering is used for generating shadows and stuff...
  367. // for right now, we don't do any performance hacks.
  368. - preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
  369. {
  370.   return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  371. }
  372.  
  373. - preRenderSelf:(WW3DCamera *)camera 
  374. {
  375.   return [self renderSelf:camera];
  376. }
  377.  
  378.  
  379. - renderCompoundCommandMaps:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
  380. {
  381.   int                howMany1 = [theFirstSampleData count];
  382.   int                howMany2 = [theLastSampleData count];
  383.   RtFloat            bogusTimeVector[2];
  384.   int                i;
  385.   id <WWRenderable>  data1, data2;
  386.  
  387.  
  388.   if (howMany1 == howMany2)  // it looks like these might be worth trying to motion blur together...
  389.   {  for (i = 0; i < howMany1; i++)
  390.      {  // check to see if the two commands are actually of the same type.  If
  391.     // they are, check to see if they're motion blurrable.  If they are,
  392.         // motion blur them.
  393.         data1 = [theFirstSampleData objectAt:i];  // cache the first data a bit
  394.         data2 = [theLastSampleData objectAt:i];  // cache the last data a bit
  395.         if ([data1 class] == [data2 class])
  396.         {  // okay, they're the same kind of command
  397.            if ([data1 isMotionBlurrable])
  398.            {  if ([data1 isCompoundCommand])  // one last check; do we go recursive?
  399.               {  [self renderCompoundCommandMaps:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  400.               }
  401.               else   // okay, it's atomic so try to motion blur it
  402.               {  // actually, I should ask the first command to
  403.                  // compare itself to the second, and only motion 
  404.                  // blur it if it thinks they're different.
  405.                  // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.)
  406.  
  407.                  // should actually check the length of the list and construct an appropriate vector, 
  408.                  // but prman 3.4 has the restriction that you can only use two samples, sigh...
  409.                  bogusTimeVector[0] = shutterOpenTime;
  410.                  bogusTimeVector[1] = shutterCloseTime;
  411.                 
  412.                  if ([data1 theSameAs:data2]) // no need for motion blur; t'aint changing...
  413.                  {  if (![data1 isMoot])  // only render it if it will actually *do* something...
  414.                     {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  415.                     }
  416.                  }  
  417.                  else
  418.                  {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  419.                     [data2 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  420.                  }
  421.               }
  422.        }
  423.            else // it's not motion blurrable; just use the first command
  424.            {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  425.            }
  426.     }
  427.         else // the two commands are different; only render the first representation
  428.         {  {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  429.        }
  430.         }
  431.      }
  432.   }
  433.   else  // it's not worth trying, just render the commands in the first sample
  434.   {  for (i = 0; i < howMany1; i++)
  435.      {  [[theFirstSampleData objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  436.      }
  437.   }
  438.  
  439.   return self;
  440. }
  441.  
  442.  
  443. - renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
  444. {
  445.   RtFloat            bogusTimeVector[2];
  446.  
  447.  
  448.   if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  449.   {  // use my cached representation!!
  450.  
  451.      // first: is the renderable command I represent even motion-blurrable?
  452.      // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
  453.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  454.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  455.         {  // use first cached renderable command to be Renderman compliant
  456.            [(id <WWRenderable>)firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  457.         }
  458.         else // same shot, but need to do it motion blurred!
  459.         {  // should actually check the length of the list and construct an appropriate vector, 
  460.            // but prman 3.4 has the restriction that you can only use two samples, sigh...
  461.            bogusTimeVector[0] = shutterOpenTime;
  462.            bogusTimeVector[1] = shutterCloseTime;
  463.  
  464.            if (isCompoundCommand) // it's a compound command; need to walk down the two 
  465.            {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  466.            }
  467.            else  // it's an atomic RIBCommand...
  468.            {  // actually, I should ask the first command to
  469.               // compare itself to the second, and only motion 
  470.               // blur it if it thinks they're different.
  471.               // also, I could check at this point if ... what?  I forgot...
  472.               if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
  473.               {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  474.               }  
  475.               else
  476.               {    [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  477.                    [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  478.               }
  479.            }
  480.         }
  481.      }
  482.      else
  483.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  484.         [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  485.      }
  486.   }
  487.   else
  488.   {  // okay, we're being asked to render a different (set of) sample(s) of myself
  489.      // we'll refill the cache and then render
  490.  
  491.      // we now need to find the sample in the samplesList that had a
  492.      // timestamp <= shutterOpenTime, where the next sample past it is <
  493.      // shutterOpenTime, and then we need to find the sample that's
  494.      //  timestamp is <= shutterCloseTime where the next sample past it is >
  495.      //  shutterCloseTime.
  496.       
  497.      // probably the best thing to do is have samples be stored in a subclass of List
  498.      // that caches the last answers to questions and searches itself accordingly...
  499.      // In this case, it's a WWSampleList, which is built to store WWSamples
  500.      firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
  501.      lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
  502.      if (!firstSampleData)
  503.      {  if (!lastSampleData)
  504.     {  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
  505.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  506.            return nil;
  507.         }
  508.         else
  509.     {  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
  510.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  511.            firstSampleData = lastSampleData;
  512.         }
  513.      }
  514.      else
  515.      {  if (!lastSampleData)
  516.     {  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
  517.               [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
  518.            lastSampleData = firstSampleData;
  519.         }
  520.      }
  521.      lastShutterOpenTime = shutterOpenTime; 
  522.      lastShutterCloseTime = shutterCloseTime; 
  523.      isMotionBlurrable = [firstSampleData isMotionBlurrable];
  524.      isCompoundCommand = [firstSampleData isCompoundCommand];
  525.      cacheIsFilled = YES;    
  526.  
  527.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  528.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  529.         {  // use first cached renderable command to be Renderman compliant
  530.            [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  531.         }
  532.         else // same shot, but need to do it motion blurred!
  533.         {  if (firstSampleData && lastSampleData)
  534.            {  bogusTimeVector[0] = shutterOpenTime;
  535.               bogusTimeVector[1] = shutterCloseTime;
  536.  
  537.               if (isCompoundCommand) // it's a compound command; need to walk down the two 
  538.               {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  539.               }
  540.               else  // it's an atomic RIBCommand...
  541.               {  // actually, I should ask the first command to
  542.                  // compare itself to the second, and only motion 
  543.                  // blur it if it thinks they're different.
  544.                  // also, I could check at this point if ... what?  I forgot...
  545.                  if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
  546.                  {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  547.                  }  
  548.                  else
  549.                  {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  550.                     [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  551.                  }
  552.               }
  553.         }
  554.            if (!firstSampleData)
  555.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  556.               if (lastSampleData)
  557.               {  [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  558.           }
  559.         }
  560.            if (!lastSampleData)
  561.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  562.               if (firstSampleData)
  563.               {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  564.           }
  565.         }
  566.         } // closes "else // same shot, but need to do it motion blurred!"
  567.      }
  568.      else
  569.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  570.         [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  571.      }
  572.   }
  573.   return self;
  574. }
  575.  
  576. - renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
  577. {
  578.   RtFloat  shutterOpenTime = [camera shutterOpenTime],
  579.            shutterCloseTime = [camera shutterCloseTime];
  580.  
  581.  
  582.   return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  583. }
  584.  
  585.  
  586. - renderCompoundCommandMaps:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
  587. {
  588.   int                howMany1 = [theFirstSampleData count];
  589.   int                howMany2 = [theLastSampleData count];
  590.   RtFloat            bogusTimeVector[2];
  591.   int                i;
  592.   id <WWRenderable>  data1, data2;
  593.  
  594.  
  595.   if (howMany1 == howMany2)  // it looks like these might be worth trying to motion blur together...
  596.   {  for (i = 0; i < howMany1; i++)
  597.      {  // check to see if the two commands are actually of the same type.  If
  598.     // they are, check to see if they're motion blurrable.  If they are,
  599.         // motion blur them.
  600.         data1 = [theFirstSampleData objectAt:i];  // cache the first data a bit
  601.         data2 = [theLastSampleData objectAt:i];  // cache the last data a bit
  602.         if ([data1 class] == [data2 class])
  603.         {  // okay, they're the same kind of command
  604.            if ([data1 isMotionBlurrable])
  605.            {  if ([data1 isCompoundCommand])  // one last check; do we go recursive?
  606.               {  [self renderCompoundCommandMaps:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  607.               }
  608.               else   // okay, it's atomic so try to motion blur it
  609.               {  // actually, I should ask the first command to
  610.                  // compare itself to the second, and only motion 
  611.                  // blur it if it thinks they're different.
  612.                  // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.)
  613.  
  614.                  // should actually check the length of the list and construct an appropriate vector, 
  615.                  // but prman 3.4 has the restriction that you can only use two samples, sigh...
  616.                  bogusTimeVector[0] = shutterOpenTime;
  617.                  bogusTimeVector[1] = shutterCloseTime;
  618.                 
  619.                  if ([data1 theSameAs:data2]) // no need for motion blur; t'aint changing...
  620.                  {  if (![data1 isMoot])  // only render it if it will actually *do* something...
  621.                     {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  622.                     }
  623.                  }  
  624.                  else
  625.                  {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  626.                     [data2 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  627.                  }
  628.               }
  629.        }
  630.            else // it's not motion blurrable; just use the first command
  631.            {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  632.            }
  633.     }
  634.         else // the two commands are different; only render the first representation
  635.         {  {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  636.        }
  637.         }
  638.      }
  639.   }
  640.   else  // it's not worth trying, just render the commands in the first sample
  641.   {  for (i = 0; i < howMany1; i++)
  642.      {  [[theFirstSampleData objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  643.      }
  644.   }
  645.  
  646.   return self;
  647. }
  648.  
  649.  
  650. - renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
  651. {
  652.   RtFloat            bogusTimeVector[2];
  653.  
  654.  
  655.   if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  656.   {  // use my cached representation!!
  657.  
  658.      // first: is the renderable command I represent even motion-blurrable?
  659.      // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
  660.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  661.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  662.         {  // use first cached renderable command to be Renderman compliant
  663.            [(id <WWRenderable>)firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  664.         }
  665.         else // same shot, but need to do it motion blurred!
  666.         {  // should actually check the length of the list and construct an appropriate vector, 
  667.            // but prman 3.4 has the restriction that you can only use two samples, sigh...
  668.            bogusTimeVector[0] = shutterOpenTime;
  669.            bogusTimeVector[1] = shutterCloseTime;
  670.  
  671.            if (isCompoundCommand) // it's a compound command; need to walk down the two 
  672.            {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  673.            }
  674.            else  // it's an atomic RIBCommand...
  675.            {  // actually, I should ask the first command to
  676.               // compare itself to the second, and only motion 
  677.               // blur it if it thinks they're different.
  678.               // also, I could check at this point if ... what?  I forgot...
  679.               if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
  680.               {  if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
  681.                  {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  682.                  }
  683.               }  
  684.               else
  685.               {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  686.                  [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  687.               }
  688.            }
  689.         }
  690.      }
  691.      else
  692.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  693.         [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  694.      }
  695.   }
  696.   else
  697.   {  // okay, we're being asked to render a different (set of) sample(s) of myself
  698.      // we'll refill the cache and then render
  699.  
  700.      // we now need to find the sample in the samplesList that had a
  701.      // timestamp <= shutterOpenTime, where the next sample past it is <
  702.      // shutterOpenTime, and then we need to find the sample that's
  703.      //  timestamp is <= shutterCloseTime where the next sample past it is >
  704.      //  shutterCloseTime.
  705.       
  706.      // probably the best thing to do is have samples be stored in a subclass of List
  707.      // that caches the last answers to questions and searches itself accordingly...
  708.      // In this case, it's a WWSampleList, which is built to store WWSamples
  709.      firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
  710.      lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
  711.      if (!firstSampleData)
  712.      {  if (!lastSampleData)
  713.     {  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
  714.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  715.            return nil;
  716.         }
  717.         else
  718.     {  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
  719.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  720.            firstSampleData = lastSampleData;
  721.         }
  722.      }
  723.      else
  724.      {  if (!lastSampleData)
  725.     {  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
  726.               [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
  727.            lastSampleData = firstSampleData;
  728.         }
  729.      }
  730.      lastShutterOpenTime = shutterOpenTime; 
  731.      lastShutterCloseTime = shutterCloseTime; 
  732.      isMotionBlurrable = [firstSampleData isMotionBlurrable];
  733.      isCompoundCommand = [firstSampleData isCompoundCommand];
  734.      cacheIsFilled = YES;    
  735.  
  736.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  737.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  738.         {  // use first cached renderable command to be Renderman compliant
  739.            [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  740.         }
  741.         else // same shot, but need to do it motion blurred!
  742.         {  if (firstSampleData && lastSampleData)
  743.            {  bogusTimeVector[0] = shutterOpenTime;
  744.               bogusTimeVector[1] = shutterCloseTime;
  745.  
  746.               if (isCompoundCommand) // it's a compound command; need to walk down the two 
  747.               {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  748.               }
  749.               else  // it's an atomic RIBCommand...
  750.               {  // actually, I should ask the first command to
  751.                  // compare itself to the second, and only motion 
  752.                  // blur it if it thinks they're different.
  753.                  // also, I could check at this point if ... what?  I forgot...
  754.                  if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
  755.                  {  if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
  756.                     {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  757.                     }
  758.                  }  
  759.                  else
  760.                  {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  761.                     [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  762.                  }
  763.               }
  764.         }
  765.            if (!firstSampleData)
  766.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  767.               if (lastSampleData)
  768.               {  [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  769.           }
  770.         }
  771.            if (!lastSampleData)
  772.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  773.               if (firstSampleData)
  774.               {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  775.           }
  776.         }
  777.         } // closes "else // same shot, but need to do it motion blurred!"
  778.      }
  779.      else
  780.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  781.         [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  782.      }
  783.   }
  784.   return self;
  785. }
  786.  
  787. - renderMaps:(WW3DCamera *)camera
  788. {
  789.   RtFloat  shutterOpenTime = [camera shutterOpenTime],
  790.            shutterCloseTime = [camera shutterCloseTime];
  791.  
  792.  
  793.   return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  794. }
  795.  
  796.  
  797. - renderCompoundCommand:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
  798. {
  799.   int                howMany1 = [theFirstSampleData count];
  800.   int                howMany2 = [theLastSampleData count];
  801.   RtFloat            bogusTimeVector[2];
  802.   int                i;
  803.   id <WWRenderable>  data1, data2;
  804.  
  805.  
  806.   if (howMany1 == howMany2)  // it looks like these might be worth trying to motion blur together...
  807.   {  for (i = 0; i < howMany1; i++)
  808.      {  // check to see if the two commands are actually of the same type.  If
  809.     // they are, check to see if they're motion blurrable.  If they are,
  810.         // motion blur them.
  811.         data1 = [theFirstSampleData objectAt:i];  // cache the first data a bit
  812.         data2 = [theLastSampleData objectAt:i];  // cache the last data a bit
  813.         if ([data1 class] == [data2 class])
  814.         {  // okay, they're the same kind of command
  815.            if ([data1 isMotionBlurrable])
  816.            {  if ([data1 isCompoundCommand])  // one last check; do we go recursive?
  817.               {  [self renderCompoundCommand:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  818.               }
  819.               else   // okay, it's atomic so try to motion blur it
  820.               {  // actually, I should ask the first command to
  821.                  // compare itself to the second, and only motion 
  822.                  // blur it if it thinks they're different.
  823.                  // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.)
  824.  
  825.                  // should actually check the length of the list and construct an appropriate vector, 
  826.                  // but prman 3.4 has the restriction that you can only use two samples, sigh...
  827.                  bogusTimeVector[0] = shutterOpenTime;
  828.                  bogusTimeVector[1] = shutterCloseTime;
  829.                 
  830.                  if ([data1 theSameAs:data2] || (![data1 similarTo:data2])) // no need for motion blur; t'aint changing or is too weird...
  831.                  {  if (![data1 isMoot])  // only render it if it will actually *do* something...
  832.                     {  [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  833.                     }
  834.                  }  
  835.                  else
  836.                  {  RiMotionBeginV((RtInt)2, bogusTimeVector);
  837.                       [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  838.                       [data2 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  839.                     RiMotionEnd();
  840.                  }
  841.               }
  842.        }
  843.            else // it's not motion blurrable; just use the first command
  844.            {  if (![data1 isMoot])  // only render it if it will actually *do* something...
  845.               {  [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  846.           }
  847.            }
  848.     }
  849.         else // the two commands are different; only render the first representation
  850.         {  if (![data1 isMoot])  // only render it if it will actually *do* something...
  851.            {  [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  852.        }
  853.         }
  854.      }
  855.   }
  856.   else  // it's not worth trying, just render the commands in the first sample
  857.   {  for (i = 0; i < howMany1; i++)
  858.      {  [[theFirstSampleData objectAt:i] renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  859.      }
  860.   }
  861.  
  862.   return self;
  863. }
  864.  
  865.  
  866. - renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
  867. {
  868.   RtFloat            bogusTimeVector[2];
  869.  
  870.  
  871.   if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  872.   {  // use my cached representation!!
  873.  
  874.      // first: is the renderable command I represent even motion-blurrable?
  875.      // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
  876.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  877.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  878.         {  // use first cached renderable command to be Renderman compliant
  879.            [(id <WWRenderable>)firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  880.         }
  881.         else // same shot, but need to do it motion blurred!
  882.         {  // should actually check the length of the list and construct an appropriate vector, 
  883.            // but prman 3.4 has the restriction that you can only use two samples, sigh...
  884.            bogusTimeVector[0] = shutterOpenTime;
  885.            bogusTimeVector[1] = shutterCloseTime;
  886.  
  887.            if (isCompoundCommand) // it's a compound command; need to walk down the two 
  888.            {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  889.            }
  890.            else  // it's an atomic RIBCommand...
  891.            {  // actually, I should ask the first command to
  892.               // compare itself to the second, and only motion 
  893.               // blur it if it thinks they're different.
  894.               // also, I could check at this point if ... what?  I forgot...
  895.               if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
  896.               {  // no need for motion blur; t'aint changing or is too weird...
  897.                  if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
  898.                  {  [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  899.                  }
  900.               }  
  901.               else
  902.               {  RiMotionBeginV((RtInt)2, bogusTimeVector);
  903.                    [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  904.                    [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  905.                  RiMotionEnd();
  906.               }
  907.            }
  908.         }
  909.      }
  910.      else
  911.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  912.         [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  913.      }
  914.   }
  915.   else
  916.   {  // okay, we're being asked to render a different (set of) sample(s) of myself
  917.      // we'll refill the cache and then render
  918.  
  919.      // we now need to find the sample in the samplesList that had a
  920.      // timestamp <= shutterOpenTime, where the next sample past it is <
  921.      // shutterOpenTime, and then we need to find the sample that's
  922.      //  timestamp is <= shutterCloseTime where the next sample past it is >
  923.      //  shutterCloseTime.
  924.       
  925.      // probably the best thing to do is have samples be stored in a subclass of List
  926.      // that caches the last answers to questions and searches itself accordingly...
  927.      // In this case, it's a WWSampleList, which is built to store WWSamples
  928.      firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
  929.      lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
  930.      if (!firstSampleData)
  931.      {  if (!lastSampleData)
  932.     {  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
  933.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  934.            return nil;
  935.         }
  936.         else
  937.     {  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
  938.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  939.            firstSampleData = lastSampleData;
  940.         }
  941.      }
  942.      else
  943.      {  if (!lastSampleData)
  944.     {  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
  945.               [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
  946.            lastSampleData = firstSampleData;
  947.         }
  948.      }
  949.      lastShutterOpenTime = shutterOpenTime; 
  950.      lastShutterCloseTime = shutterCloseTime; 
  951.      isMotionBlurrable = [firstSampleData isMotionBlurrable];
  952.      isCompoundCommand = [firstSampleData isCompoundCommand];
  953.      cacheIsFilled = YES;    
  954.  
  955.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  956.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  957.         {  // use first cached renderable command to be Renderman compliant
  958.            [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  959.         }
  960.         else // same shot, but need to do it motion blurred!
  961.         {  if (firstSampleData && lastSampleData)
  962.            {  bogusTimeVector[0] = shutterOpenTime;
  963.               bogusTimeVector[1] = shutterCloseTime;
  964.  
  965.               if (isCompoundCommand) // it's a compound command; need to walk down the two 
  966.               {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  967.               }
  968.               else  // it's an atomic RIBCommand...
  969.               {  // actually, I should ask the first command to
  970.                  // compare itself to the second, and only motion 
  971.                  // blur it if it thinks they're different.
  972.                  // also, I could check at this point if ... what?  I forgot...
  973.                  if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
  974.                  {  // no need for motion blur; t'aint changing or is too weird...
  975.                     if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
  976.                     {  [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  977.                     }
  978.                  }  
  979.                  else
  980.                  {  RiMotionBeginV((RtInt)2, bogusTimeVector);
  981.                       [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  982.                       [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  983.                     RiMotionEnd();
  984.                  }
  985.               }
  986.         }
  987.            if (!firstSampleData)
  988.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  989.               if (lastSampleData)
  990.               {  [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  991.           }
  992.         }
  993.            if (!lastSampleData)
  994.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  995.               if (firstSampleData)
  996.               {  [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  997.           }
  998.         }
  999.         } // closes "else // same shot, but need to do it motion blurred!"
  1000.      }
  1001.      else
  1002.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  1003.         [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1004.      }
  1005.   }
  1006.   return self;
  1007. }
  1008.  
  1009. - renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
  1010. {
  1011.   RtFloat            bogusTimeVector[2];
  1012.  
  1013.  
  1014.   if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  1015.   {  // use my cached representation!!
  1016.  
  1017.      // first: is the renderable command I represent even motion-blurrable?
  1018.      // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
  1019.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  1020.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  1021.         {  // use first cached renderable command to be Renderman compliant
  1022.            if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
  1023.        {  [(id <WWRenderable>)firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1024.            }
  1025.         }
  1026.         else // same shot, but need to do it motion blurred!
  1027.         {  // should actually check the length of the list and construct an appropriate vector, 
  1028.            // but prman 3.4 has the restriction that you can only use two samples, sigh...
  1029.            bogusTimeVector[0] = shutterOpenTime;
  1030.            bogusTimeVector[1] = shutterCloseTime;
  1031.  
  1032.            if (isCompoundCommand) // it's a compound command; need to walk down the two 
  1033.            {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1034.            }
  1035.            else  // it's an atomic RIBCommand...
  1036.            {  // actually, I should ask the first command to
  1037.               // compare itself to the second, and only motion 
  1038.               // blur it if it thinks they're different.
  1039.               // also, I could check at this point if ... what?  I forgot...
  1040.               if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
  1041.               {  // no need for motion blur; t'aint changing or is too weird...
  1042.                  if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
  1043.                  {  [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1044.                  }
  1045.               }  
  1046.               else
  1047.               {  RiMotionBeginV((RtInt)2, bogusTimeVector);
  1048.                    [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1049.                    [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1050.                  RiMotionEnd();
  1051.               }
  1052.            }
  1053.         }
  1054.      }
  1055.      else
  1056.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  1057.         [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1058.      }
  1059.   }
  1060.   else
  1061.   {  // okay, we're being asked to render a different (set of) sample(s) of myself
  1062.      // we'll refill the cache and then render
  1063.  
  1064.      // we now need to find the sample in the samplesList that had a
  1065.      // timestamp <= shutterOpenTime, where the next sample past it is <
  1066.      // shutterOpenTime, and then we need to find the sample that's
  1067.      //  timestamp is <= shutterCloseTime where the next sample past it is >
  1068.      //  shutterCloseTime.
  1069.       
  1070.      // probably the best thing to do is have samples be stored in a subclass of List
  1071.      // that caches the last answers to questions and searches itself accordingly...
  1072.      // In this case, it's a WWSampleList, which is built to store WWSamples
  1073.      firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
  1074.      lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
  1075.      if (!firstSampleData)
  1076.      {  if (!lastSampleData)
  1077.     {  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
  1078.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  1079.            return nil;
  1080.         }
  1081.         else
  1082.     {  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
  1083.               [cmd cmd], shutterOpenTime, shutterCloseTime);     
  1084.            firstSampleData = lastSampleData;
  1085.         }
  1086.      }
  1087.      else
  1088.      {  if (!lastSampleData)
  1089.     {  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
  1090.               [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
  1091.            lastSampleData = firstSampleData;
  1092.         }
  1093.      }
  1094.      lastShutterOpenTime = shutterOpenTime; 
  1095.      lastShutterCloseTime = shutterCloseTime; 
  1096.      isMotionBlurrable = [firstSampleData isMotionBlurrable];
  1097.      isCompoundCommand = [firstSampleData isCompoundCommand];
  1098.      cacheIsFilled = YES;    
  1099.  
  1100.      if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
  1101.      {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
  1102.         {  // use first cached renderable command to be Renderman compliant
  1103.            [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1104.         }
  1105.         else // same shot, but need to do it motion blurred!
  1106.         {  if (firstSampleData && lastSampleData)
  1107.            {  bogusTimeVector[0] = shutterOpenTime;
  1108.               bogusTimeVector[1] = shutterCloseTime;
  1109.  
  1110.               if (isCompoundCommand) // it's a compound command; need to walk down the two 
  1111.               {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1112.               }
  1113.               else  // it's an atomic RIBCommand...
  1114.               {  // actually, I should ask the first command to
  1115.                  // compare itself to the second, and only motion 
  1116.                  // blur it if it thinks they're different.
  1117.                  // also, I could check at this point if ... what?  I forgot...
  1118.                  if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
  1119.                  {  // no need for motion blur; t'aint changing or is too weird...
  1120.                     if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
  1121.                     {  [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1122.                     }
  1123.                  }  
  1124.                  else
  1125.                  {  RiMotionBeginV((RtInt)2, bogusTimeVector);
  1126.                       [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1127.                       [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1128.                     RiMotionEnd();
  1129.                  }
  1130.               }
  1131.         }
  1132.            if (!firstSampleData)
  1133.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  1134.               if (lastSampleData)
  1135.               {  [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1136.           }
  1137.         }
  1138.            if (!lastSampleData)
  1139.        {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
  1140.               if (firstSampleData)
  1141.               {  [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1142.           }
  1143.         }
  1144.         } // closes "else // same shot, but need to do it motion blurred!"
  1145.      }
  1146.      else
  1147.      {  // either we're going to the screen or this command isn't even motion-blurrable...
  1148.         [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1149.      }
  1150.   }
  1151.   return self;
  1152. }
  1153.  
  1154. - renderSelf:(WW3DCamera *)camera
  1155. {
  1156.   RtFloat  shutterOpenTime = [camera shutterOpenTime],
  1157.            shutterCloseTime = [camera shutterCloseTime];
  1158.  
  1159.  
  1160.   return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1161. }
  1162.  
  1163.  
  1164. - transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
  1165.   [(id <WWRenderable>)firstSampleData transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
  1166.   return self; 
  1167. }
  1168.  
  1169.  
  1170. - calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime  
  1171. {
  1172.   if ([self hasBoundingBox])
  1173.   {  // oy.  This is actually more complex than I'm letting on here.  
  1174.      // what I should do is get the sample at the start of the interval, 
  1175.      // get the sample at the end of the interval, and then take the max
  1176.      // of their two bounding boxes.
  1177.      // for now, fake it
  1178.      if (firstSampleData)
  1179.      {  [self setBoundingBox:[firstSampleData boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]];
  1180.      }
  1181.   }
  1182.  
  1183.   //dirtyBoundingBox = FALSE; 
  1184.   // for now, we won't cache stuff; this will force it to ask the right RIBCommand directly..  
  1185.  
  1186.   return self;
  1187. }
  1188.  
  1189. - (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStart endingAt:(RtFloat)intervalEnd
  1190.    if (!dirtyBoundingBox)
  1191.    {  // okay, this means we haven't been sent any messages that would have invalidated the boundingBox cache
  1192.       // now check intervals...
  1193.       if ((intervalStart == bbIntervalStart) && (intervalEnd == bbIntervalEnd))
  1194.       {  // cool! we can use the cached version!!
  1195.          return &boundingBox; 
  1196.       }
  1197.    }
  1198.    // damn! have to recalculate it..
  1199.    [self calculateBoundingBoxStartingAt:intervalStart endingAt:intervalEnd];
  1200.    // don't forget to refill the cache
  1201.    bbIntervalStart = intervalStart;
  1202.    bbIntervalEnd = intervalEnd;
  1203.    dirtyBoundingBox = FALSE;
  1204.  
  1205.    // so what things might invalidate the cache?  Basically if I get a
  1206.    // new sample that lies with the sample interval, the cache is
  1207.    // invalidated.
  1208.  
  1209.    return &boundingBox; 
  1210. }
  1211.  
  1212. - (BOOL)isMotionBlurrable { return isMotionBlurrable; }
  1213. - (BOOL)isCompoundCommand { return isCompoundCommand; }
  1214.  
  1215. // WavesWorld archiving:
  1216. // writeEve:(NXStream *)stream
  1217. // writeScene:(NXStream *)stream
  1218.  
  1219. - writeEve:(NXStream *)stream atTabLevel:(int)tab
  1220. {
  1221.    int  i;
  1222.  
  1223.  
  1224.    for (i = 0; i < tab; i++)
  1225.    {  NXPrintf(stream, "\t");
  1226.    }
  1227.    NXPrintf(stream, "animatable: {%s};", [(WWTCLClosedCmd *)cmd cmd]); 
  1228.    return self;
  1229. }
  1230.  
  1231. - writeScene:(NXStream *)stream atTabLevel:(int)tab
  1232. {
  1233.    int  i, j, howMany = [samplesList count];
  1234.    id   aSample;
  1235.    const char *aSampleGeneratorName;
  1236.  
  1237.  
  1238.    for (i = 0; i < tab; i++)
  1239.    {  NXPrintf(stream, "\t");
  1240.    }
  1241.    NXPrintf(stream, "animatable: {%s} {", [(WWTCLClosedCmd *)cmd cmd]); 
  1242.    NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
  1243.    for (j = 0; j < howMany; j++)
  1244.    {  aSample = [samplesList objectAt:j];
  1245.       // let's not bother if this was auto-generated...
  1246.       // need a better way of checking this, dude.
  1247.       // note that an auto-generated sample has the wrong eveCode (why is that? just think about it...)...
  1248.       aSampleGeneratorName = [aSample generatorName];
  1249.       // if the name is NULL, write it out, or if the name isn't "WWSampleList" write it out
  1250.       if (!aSampleGeneratorName || strcmp("WWSampleList", aSampleGeneratorName))
  1251.       {  for (i = 0; i < tab; i++)
  1252.          {  NXPrintf(stream, "\t");
  1253.          }
  1254.          for (i = 0; i < strlen("animatable:  "); i++)
  1255.          {  NXPrintf(stream, " ");
  1256.          }
  1257.          // okay, so how do we want this to look?
  1258.          // we have a timestamp for each sample, great
  1259.          // now, recall that a sample might be a single WWSample or 
  1260.          // it could be a WWSampleComponentList
  1261.          // {$timestamp { {$owner1 {$compiledSample1}} }
  1262.          // {$timestamp { {$owner1 {$compiledSample1}} {$owner2 {$compiledSample1}} {$owner3 {$compiledSample3}} } } 
  1263.    
  1264.          NXPrintf(stream, "{%f { ", [aSample timestamp]);
  1265.          //[aSample writeEve:stream atTabLevel:0];  // kinda gross, but we've already moved the cursor where we want...
  1266.          [aSample writeEve:stream atTabLevel:tab];
  1267.          NXPrintf(stream, "}} ");
  1268.          NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
  1269.       }
  1270.    }
  1271.    for (i = 0; i < tab; i++)
  1272.    {  NXPrintf(stream, "\t");
  1273.    }
  1274.    for (i = 0; i < strlen("animatable: "); i++)
  1275.    {  NXPrintf(stream, " ");
  1276.    }
  1277.    NXPrintf(stream, "};"); 
  1278.    return self;
  1279. }
  1280.  
  1281. - write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
  1282. {
  1283.    int  i, j, howMany = [samplesList count];
  1284.    id   aSample;
  1285.  
  1286.  
  1287.    for (i = 0; i < tab; i++)
  1288.    {  NXPrintf(stream, "\t");
  1289.    }
  1290.  
  1291.    NXPrintf(stream, "startShape (%s); ", [[[samplesList objectAt:0] class] name]);
  1292.    // need tab
  1293.    // need index (position in current list)
  1294.    NXPrintf(stream, 
  1295.         "animatable: {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
  1296.             tab, index); 
  1297.    NXPrintf(stream, "  animatable: {WW3DText $__text__(fontName) $__text__(fontSize) {");
  1298.  
  1299.      NXPrintf(stream, "{%s}", [(WWTCLClosedCmd *)cmd cmd]); 
  1300.      NXPrintf(stream, "} left;}\n");
  1301.  
  1302.      // okay, now put out my instances, pushed back in space
  1303.      // the above RIBCommands will have put the cursor in the right place, we now need to shove these guys back in Z, though
  1304.  
  1305.      for (j = 0; j < howMany; j++)
  1306.      {  aSample = [samplesList objectAt:j];
  1307.       // okay, so how do we want this to look?
  1308.       // we have a timestamp for each sample, great
  1309.       // now, recall that a sample might be a single WWSample or 
  1310.       // it could be a WWSampleComponentList
  1311.       // {$owner1 {$compiledSample1}
  1312.       // {$owner1 {$compiledSample1}} {$owner2 {$compiledSample1}} {$owner3 {$compiledSample3}}
  1313.  
  1314.       NXPrintf(stream, "    ");
  1315.       // if there are more samples in the list, we hand in the next's timestamp as the "last" time
  1316.       // otherwise we hand in this sample's timestamp again, so it knows it's the last
  1317.       if (j == (howMany - 1))
  1318.       {  [aSample write3DTextScene:stream atTabLevel:0 index:0 time:[aSample timestamp] until:[aSample timestamp]]; // we've already move the origin in X and Y
  1319.       }
  1320.       else
  1321.       {  [aSample write3DTextScene:stream atTabLevel:0 index:0 time:[aSample timestamp] until:[[samplesList objectAt:(j+1)] timestamp]]; // we've already move the origin in X and Y
  1322.       }
  1323.  
  1324.       NXPrintf(stream, "\n");
  1325.    }
  1326.  
  1327.    NXPrintf(stream, "endShape;\n");
  1328.    return self;
  1329. }
  1330.  
  1331. - writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
  1332. {
  1333.    int                i;
  1334.    id <WWRenderable>  data;
  1335.  
  1336.  
  1337.    data = [(WWSample *)[samplesList sampleAtOrJustBefore:currentTime] data];
  1338.    if ([data isMoot])
  1339.    {  return self;
  1340.    }
  1341.  
  1342.    for (i = 0; i < tab; i++)
  1343.    {  NXPrintf(stream, "\t");
  1344.    }
  1345.    NXPrintf(stream, "# animatable: {%s}\n", [(WWTCLClosedCmd *)cmd cmd]); 
  1346.    for (i = 0; i < tab; i++)
  1347.    {  NXPrintf(stream, "\t");
  1348.    }
  1349.    NXPrintf(stream, "# at time %f, this animatable expression evaluated to:\n", currentTime);
  1350.    [data writeInventorAtTime:currentTime to:stream atTabLevel:tab];
  1351.    return self;
  1352. }
  1353.  
  1354.  
  1355. // NeXTSTEP archiving:
  1356.  
  1357. #define typeVector "@@@ccc@@@c"
  1358. #define typeValues &cmd, &eveParser, &interp, \
  1359.                    &hasBoundingBox, &isMotionBlurrable, &isTCLCommand, \
  1360.                    &myShape, &samplesList, &sceneClock, \
  1361.                    &isCompoundCommand 
  1362.  
  1363. - read:(NXTypedStream *)stream 
  1364. {
  1365.   int version;
  1366.  
  1367.  
  1368.   [super read:stream];
  1369.  
  1370.   version = NXTypedStreamClassVersion(stream, "EveCommand");
  1371.   if (version == 0) NXReadTypes(stream, "i", &version), version=1;
  1372.   if (version == 1)
  1373.   {  NXReadTypes(stream, typeVector, typeValues);
  1374.      NXReadArray(stream, "f", 6, boundingBox);
  1375.   } 
  1376.   if (version == 2)
  1377.   {  NXReadTypes(stream, typeVector, typeValues);
  1378.      NXReadArray(stream, "f", 6, boundingBox);
  1379.      NXReadTypes(stream, "cc", &pushesCTM, &popsCTM);
  1380.   } 
  1381.  
  1382.   return self; 
  1383. }
  1384.  
  1385. - write:(NXTypedStream *)stream 
  1386. {
  1387.   [super write:stream];
  1388.  
  1389.   NXWriteTypes(stream, typeVector, typeValues);
  1390.   NXWriteArray(stream, "f", 6, boundingBox);
  1391.   NXWriteTypes(stream, "cc", &pushesCTM, &popsCTM);
  1392.  
  1393.   return self;
  1394. }
  1395.  
  1396. - (BOOL)theSameAs:otherRIBCommand
  1397. {
  1398.   // make the default NO...  
  1399.   return NO;
  1400. }
  1401.  
  1402. - (BOOL)similarTo:otherRIBCommand 
  1403. {
  1404.   if ([self class] != [otherRIBCommand class])
  1405.   {  return NO;
  1406.   }
  1407.   return YES;
  1408. }
  1409.  
  1410. - (BOOL)isMoot
  1411. {
  1412.   return NO;
  1413. }
  1414.  
  1415. - (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  
  1416. {  
  1417.    if ([[(WWSample *)[samplesList sampleAtOrJustBefore:startTime] data] isMoot])
  1418.    {  return YES;
  1419.    }
  1420.    if ([[(WWSample *)[samplesList sampleAtOrJustBefore:endTime] data] isMoot])
  1421.    {  return YES;
  1422.    }
  1423.    return NO;
  1424. }
  1425.  
  1426.  
  1427. - (const char *)sampleName { return sampleName; }
  1428.  
  1429. // boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
  1430. - class { return [super class]; }
  1431.  
  1432. - command
  1433. {
  1434.     return cmd;
  1435. }
  1436.  
  1437.  
  1438. @end
  1439.  
  1440.  
  1441.  
  1442.  
  1443.  
  1444.