#import <appkit/appkit.h>
#import "SimpleCamera.h"
#import "SimpleShape.h"

/* SimpleCamera -- by Bill Bumgarner 6/1/92
 *				   with assistance from Dave Springer.
 *
 * SimpleCamera demonstrates the creation of a very simple 3Dkit scene 
 * that has mouse control via the N3DRotator class, supports dumping RIB 
 * code to a file, contains light sources (ambient light and a point light), 
 * has a surface shader, supports both WireFrame and SmoothSolid rendering, 
 * and has a single custom N3DShape that generates a Torus (or teapot).
 *
 * Simple.app was created as an example of using the 3Dkit.  Parts of it
 * come from Teapot.app by Dave Springer (see SimpleShape.m).
 *
 *  You may freely copy, distribute and reuse the code in this example.
 *  NeXT disclaims any warranty of any kind, expressed or implied,
 *  as to its fitness for any particular use.
 */

@implementation SimpleCamera
- initFrame:(const NXRect *) theRect
{
  // camera position points
  RtPoint fromP = {0,0,5.0}, toP = {0,0,0};

  // light position point
  RtPoint lFromP = {0.5,0.5,0.75};

  // the various 3Dkit object id''s that we will initialize here
  id ambientLight;
  id aLight;
  id aShader;
  id aShape;

  // initialize camera and put it at (0,0,5.0) looking at the origin (0,0,0)
  // roll specifies the roll angle of the camera...
  [super initFrame:theRect];
  [self setEyeAt:fromP toward:toP roll:0.0];
  
  // create a shader that will shade surfaces with a simple matte surface.
  aShader=[[N3DShader alloc] init];
  // uncomment the following lines to generate a blue matte surface.
  // This is slow on a monochrome system.
  // [aShader setUseColor:YES];
  // [aShader setColor:NX_COLORBLUE];
  [(N3DShader *)aShader setShader:"matte"];

  // initialize the world shape and set its shader to be aShader
  aShape=[[SimpleShape alloc] init];
  [(N3DShape *) aShape  setShader:aShad@I
  [[self setWorldShape:aShape] free]; // free the default world shape

  // create an ambientlight source.
  ambientLight=[[N3DLight alloc] init];
  [ambientLight makeAmbientWithIntensity:0.1];
  [self addLight:ambientLight];
  
  // create a Point light and put it at (0.5, 0.5, 0.75) at 
  // full intensity (1.0).
  aLight=[[N3DLight alloc] init];
  [aLight makePointFrom:lFromP intensity:1.0];
  [self addLight:aLight];

  // set the surface type to generate smooth solids.  The mouseDown:
  // method automatically drops to N3D_WireFrame whenever the user manipulates
  // the scene via the mouse (see the mouseDown: implementation below).
  // This must be done after the setWorldShape: method (or after any new shape
  // is added to the hierarchy).
  [self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];

  // allocate and initialize the N3DRotator object that governs
  // rotational control via the mouseDown: method
  theRotator=[[N3DRotator alloc] initWithCamera:self];

  return self;
}

- dumpRib:sender
{
  static id savePanel=nil;
  NXStream *ts;
  char buf[MAXPATHLEN+1];

  // initialize the savePanel, if it hasn''t been done so previously
  if (!savePanel) {
    savePanel=[SavePanel new];
    [savePanel setRequiredFileType:"rib"];
  }

  // run the savepanel.  
  if([savePanel runModal]){
    // returned w/pathname, open a stream and
    ts=NXOpenMemory(NULL, 0, NX_WRITEONLY);
    // process the file name for a custom display line such that
    // "prman <<filename>>.rib" will put the resulting image somewhere
    // predictably useful.
    strcpy(buf, [savePanel filename]);
    // remove the .rib extension from the path returned by the SavePanel
    strrchr(buf,'.')[0]='\0';
    // feed to NXPrintf to put in the custom Display command
    NXPrintf(ts, "Display \"%s.tiff\" \"file\" \"rgba\"\n", buf);
    // then feed the rib code to the stream and
    [self copyRIBCode:ts];
    // save the stream to the file selected in the savepanel
    NXSaveToFile(ts, [savePanel filename]);
    // and close the stream (which also flushes it), also making sure
    // that the allocated memory is freed.
    NXCloseMemory(ts,NX_FREEBUFFER);
  }
  return self;
}

#define ACTIVEBUTTONMASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK)
- mouseDown:(NXEvent *)theEvent
{
  int                 	oldMask;
  NXPoint		oldMouse, new@Pe, dMouse;
  RtMatrix		rmat, irmat;

  // find out what axis of rotation the rotator should be constrained to
  switch([rotoMatrix selectedRow]){
  case 0: [theRotator setRotationAxis:N3D_AllAxes]; break;
  case 1: [theRotator setRotationAxis:N3D_XAxis]; break;
  case 2: [theRotator setRotationAxis:N3D_YAxis]; break;
  case 3: [theRotator setRotationAxis:N3D_ZAxis]; break;
  case 4: [theRotator setRotationAxis:N3D_XYAxes]; break;
  case 5: [theRotator setRotationAxis:N3D_XZAxes]; break;
  case 6: [theRotator setRotationAxis:N3D_YZAxes]; break;
  }

  // track the mouse until a mouseUp event occurs, updating the display
  // as tracking happens.
  [self lockFocus];
  oldMask = [window addToEventMask:ACTIVEBUTTONMASK];
  
  // switch to the N3D_WireFrame surface type
  [self setSurfaceTypeForAll:N3D_WireFrame chooseHider:YES];
  
  oldMouse = theEvent->location;
  [self convertPoint:&oldMouse fromView:nil];
  while (1)
  {
    newMouse = theEvent->location;
    [self convertPoint:&newMouse fromView:nil];
    dMouse.x = newMouse.x - oldMouse.x;
    dMouse.y = newMouse.y - oldMouse.y;
    if (dMouse.x != 0.0 || dMouse.y != 0.0) {
      [theRotator trackMouseFrom:&oldMouse to:&newMouse
       rotationMatrix:rmat andInverse:irmat];
      [worldShape concatTransformMatrix:rmat premultiply:NO];
      [self display];
    }
    theEvent = [NXApp getNextEvent:ACTIVEBUTTONMASK];
    if (theEvent->type == NX_MOUSEUP)
      break;
    oldMouse = newMouse;
  }
  // switch back to the N3D_SmoothSolids surface type
  [self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];	
  [self display];
  [self unlockFocus];

  [window setEventMask:oldMask];
  return self;
}
@end
