DZDrone.c

/*
 *  File:       DZDrone.c
 *
 *  Contents:   A drone object represents the position of an player or drone.
 *
 *  Copyright © 1996 Apple Computer, Inc.
 */
 
#include <assert.h>
#include <math.h>
#include <stdlib.h>
 
#include <Components.h>
#include <Dialogs.h>
#include <Events.h>
#include <QuickDraw.h>
#include <Resources.h>
#include <Sound.h>
#include <SoundComponents.h>
#include <SoundInput.h>
#include <ToolUtils.h>
#include <Types.h>
 
#include <QD3D.h>
#include <QD3DCamera.h>
#include <QD3DDrawContext.h>
#include <QD3DGeometry.h>
#include <QD3DMath.h>
#include <QD3DPick.h>
#include <QD3DSet.h>
#include <QD3DShader.h>
#include <QD3DStyle.h>
#include <QD3DTransform.h>
#include <QD3DView.h>
 
#include "SoundSprocket.h"
 
#include "DZDisplay.h"
#include "DZDrone.h"
#include "DZGame.h"
#include "DZResource.h"
#include "DZSound.h"
#include "DZThumbprint.h"
#include "DZUtils.h"
 
#ifndef SELF_DRONE_HAS_FRICTION
#define SELF_DRONE_HAS_FRICTION 1
#endif
 
 
#define IS_SELF_DRONE(inDrone)      (inDrone->thumbprint == kThumbprint_SelfDrone)
#define IS_AUTO_DRONE(inDrone)      (inDrone->thumbprint == kThumbprint_AutoDrone)
#define IS_BULLET_DRONE(inDrone)    (inDrone->thumbprint == kThumbprint_BulletDrone)
#define IS_DRONE(inDrone)           (IS_SELF_DRONE(inDrone) || IS_AUTO_DRONE(inDrone) || IS_BULLET_DRONE(inDrone))
 
#define SELF_DRONE_INITIAL_SPEED    0.50    // In units per second
#define SELF_DRONE_MAX_ACCEL        10.0        // In units per second per second
#define SELF_DRONE_FRICTION         0.80    // as a percentage (1.00 would be no friction)
 
#define AUTO_DRONE_LEAD             5.0     // Drone target leads by this distance (in units)
#define AUTO_DRONE_ACCEL            2.0     // Drone acceleration (units per second per second)
#define AUTO_DRONE_TURN_RATE        1.8     // In radians per second
#define AUTO_DRONE_BURN_TIME        3.0     // In seconds
#define AUTO_DRONE_REST_TIME        1.0     // In seconds
#define AUTO_DRONE_EXPLOSION_TIME   3.0     // In seconds
#define AUTO_DRONE_FADE_TIME        0.25    // Fraction of AUTO_DRONE_EXPLOSION_TIME for fade
#define AUTO_DRONE_MIN_SCALE        0.2     // Initial scale for explosion
 
#define AUTO_DRONE_IDLE_REF_DIST    2.0
#define AUTO_DRONE_BURN_REF_DIST    5.0
#define AUTO_DRONE_EXPL_REF_DIST    30.0
 
#define BULLET_DRONE_OFFSET         0.2     // Offset from self to bullet initial position
#define BULLET_DRONE_SPEED          50.0    // In units per second
#define BULLET_DRONE_LIMIT          50.0    // Maximum length of a bullet
 
#define HUD_SCALE                   0.02    // Scale of HUD
#define HUD_HEIGHT                  0.5     // cos(angle) at which to consider out-of-plane
 
 
enum {
    kDroneDrawContextSize           = 5     // Used for picking
};
 
 
typedef enum TDroneOrder {                  // In order of evaluation
    kDroneOrder_Self,
    kDroneOrder_Auto,
    kDroneOrder_Bullet
} TDroneOrder;
 
 
typedef void (*TDroneMoveMethod)(
    TDroneObject            inDrone);
 
typedef void (*TDroneUpdateSoundMethod)(
    TDroneObject            inDrone);
 
typedef void (*TDroneSubmitMethod)(
    TDroneObject            inDrone,
    Boolean                 inHUDVisible,
    TQ3ViewObject           inView);
 
typedef void (*TDronePickSubmitMethod)(
    TDroneObject            inDrone,
    TQ3ViewObject           inView);
 
typedef void (*TDroneHitMethod)(
    TDroneObject            inDrone);
 
 
typedef enum TAutoSound {
    kAutoSound_None,
    kAutoSound_Idle,
    kAutoSound_Burn,
    kAutoSound_Explosion
} TAutoSound;
 
typedef enum TAutoMode {
    kAutoMode_Idle,
    kAutoMode_Burn,
    kAutoMode_Rest,
    kAutoMode_Explosion
} TAutoMode;
 
 
typedef struct TDroneData {
    TThumbprint             thumbprint;         // For validation
    
    TDroneMoveMethod        moveMethod;         // Method: Drone_Move
    TDroneUpdateSoundMethod updateSoundMethod;  // Method: Drone_UpdateSound
    TDroneSubmitMethod      submitMethod;       // Method: Drone_Submit
    TDronePickSubmitMethod  pickSubmitMethod;   // Method: Drone_PickSubmit
    TDroneHitMethod         hitMethod;          // Method: Drone_Hit
    
    TDroneObject            prev;               // The global list of drones
    TDroneObject            next;
    TDroneOrder             order;              // Sort key
    
    Boolean                 mark;               // Is this drone marked to die?
    
    TQ3Point3D              position;           // Current position
    TQ3Point3D              position1;          // Previous position
    
    TQ3Vector3D             velocity;           // Change in position over time
    TQ3Vector3D             velocity1;          // Previous velocity
    
    TQ3Vector3D             acceleration;       // Change in velocity over time
    
    TQ3Vector3D             direction;          // Forward      (model X axis)
    TQ3Vector3D             up;                 // Vertical     (model Y axis)
    TQ3Vector3D             cross;              // Horizontal   (model Z axis)
    
    TQ3Object               geometry;           // The shape of the drone
    
    SndChannelPtr           autoSndChannel;     // Auto: Sound channel
    SSpSourceReference      autoSource;         // Auto: 3D sound source
    TAutoSound              autoSound;          // Auto: Sound currently playing
    TDroneObject            autoInterest;       // Auto: Drone that we're looking at
    TQ3Vector3D             autoVelocity;       // Auto: Drone velocity (instantaneous)
    TAutoMode               autoMode;           // Auto: Current state
    unsigned long           autoModeTimeout;    // Auto: When does mode expire?
    float                   autoDistance;       // Auto: Last distance from target
    float                   autoExplosion;      // Auto: 0=start; 1=end of explosion
    
    TQ3Point3D              bulletOrigin;       // Bullet: Starting position of the bullet
} TDroneData;
 
 
static TDroneObject         gDroneList                      = NULL;
static TQ3Object            gDroneAutoGeometry              = NULL;
static TQ3Object            gDroneAutoBurnGeometry          = NULL;
static TQ3Object            gDroneAutoExplosionGeometry     = NULL;
static TQ3AttributeSet      gDroneBulletColor               = NULL;
static TQ3ShaderObject      gDroneNULLIllumination          = NULL;
static TQ3ViewObject        gDroneView                      = NULL;
static TQ3DrawContextObject gDroneDrawContext               = NULL;
static TQ3CameraObject      gDroneCamera                    = NULL;
static TQ3PickObject        gDronePick                      = NULL;
 
static SndListHandle        gDroneAutoSndIdle               = NULL;
static SndListHandle        gDroneAutoSndBurn               = NULL;
static SndListHandle        gDroneAutoSndExplosion          = NULL;
 
static long                 gDroneAutoSndIdleOffset         = 0;
static long                 gDroneAutoSndBurnOffset         = 0;
static long                 gDroneAutoSndExplosionOffset    = 0;
 
static TQ3GeometryObject    gDroneAutoMarkerEqual           = NULL;
static TQ3GeometryObject    gDroneAutoMarkerAbove           = NULL;
static TQ3GeometryObject    gDroneAutoMarkerBelow           = NULL;
 
static unsigned char        gDroneAutoMarkerDataEqual[8] =
                                    {0x38, 0x44, 0x82, 0x92, 0x82, 0x44, 0x38, 0x00};
 
static unsigned char        gDroneAutoMarkerDataAbove[8] =
                                    {0x38, 0x54, 0x92, 0xFE, 0x92, 0x54, 0x38, 0x00};
 
static unsigned char        gDroneAutoMarkerDataBelow[8] =
                                    {0x38, 0x44, 0x82, 0xFE, 0x82, 0x44, 0x38, 0x00};
 
static Boolean              gDroneAutoCheckFilterVersion    = true;
 
 
static TDroneObject Drone_New(
    TDroneOrder             inOrder);
 
static void SelfDrone_Move(
    TDroneObject            inDrone);
 
static void AutoDrone_Move(
    TDroneObject            inDrone);
 
static void BulletDrone_Move(
    TDroneObject            inDrone);
 
static void AutoDrone_UpdateSound(
    TDroneObject            inDrone);
 
static void AutoDrone_Submit(
    TDroneObject            inDrone,
    Boolean                 inHUDVisible,
    TQ3ViewObject           inView);
 
static void BulletDrone_Submit(
    TDroneObject            inDrone,
    Boolean                 inHUDVisible,
    TQ3ViewObject           inView);
 
static void Drone_PickSubmit(
    TDroneObject            inDrone,
    TQ3ViewObject           inView);
 
static void AutoDrone_PickSubmit(
    TDroneObject            inDrone,
    TQ3ViewObject           inView);
 
static void Drone_Hit(
    TDroneObject            inDrone);
 
static void AutoDrone_Hit(
    TDroneObject            inDrone);
 
void Drone_GetMatrix(
    TDroneObject            inDrone,
    TQ3Matrix4x4*           outMatrix);
 
 
/* =============================================================================
 *      Drone_Init (external)
 *
 *  Initializes the drone stuff.
 * ========================================================================== */
void Drone_Init(
    void)
{
    TQ3ColorRGB                     color;
    TQ3PixmapDrawContextData        pixmapDrawContextData;
    TQ3ViewAngleAspectCameraData    viewAngleCameraData;
    TQ3WindowPointPickData          windowPointPickData;
    TQ3MarkerData                   markerData;
    
    // Set up the autopilot drone geometry
    gDroneAutoGeometry = Get3DMFResource(k3DMFID_AutoDrone);
    assert(gDroneAutoGeometry != NULL);
    
    // Set up the autopilot drone burn geometry
    gDroneAutoBurnGeometry = Get3DMFResource(k3DMFID_AutoDroneBurn);
    assert(gDroneAutoBurnGeometry != NULL);
    
    // Set up the autopilot drone explosion geometry
    gDroneAutoExplosionGeometry = Get3DMFResource(k3DMFID_AutoDroneExplosion);
    assert(gDroneAutoExplosionGeometry != NULL);
    
    // Read in the autopilot drone sounds
    gDroneAutoSndIdle = (SndListHandle) GetResource('snd ', kSndID_AutoIdle);
    assert(gDroneAutoSndIdle != NULL);
    
    gDroneAutoSndBurn = (SndListHandle) GetResource('snd ', kSndID_AutoBurn);
    assert(gDroneAutoSndBurn != NULL);
    
    gDroneAutoSndExplosion = (SndListHandle) GetResource('snd ', kSndID_AutoExplosion);
    assert(gDroneAutoSndExplosion != NULL);
    
    GetSoundHeaderOffset(gDroneAutoSndIdle,         &gDroneAutoSndIdleOffset);
    GetSoundHeaderOffset(gDroneAutoSndBurn,         &gDroneAutoSndBurnOffset);
    GetSoundHeaderOffset(gDroneAutoSndExplosion,    &gDroneAutoSndExplosionOffset);
    
    // Set up the bullet drone line color
    gDroneBulletColor = Q3AttributeSet_New();
    assert(gDroneBulletColor != NULL);
    
    color.r = 1.0;
    color.g = 0.8;
    color.b = 0.1;
    
    Q3AttributeSet_Add(gDroneBulletColor, kQ3AttributeTypeDiffuseColor, &color);
    
    // Create the bullet null illum shader
    gDroneNULLIllumination = Q3NULLIllumination_New();
    assert(gDroneNULLIllumination != NULL);
    
    // Create the view that we use for bullet collision detection
    gDroneView = Q3View_New();
    assert(gDroneView != NULL);
    
    // Create its draw context
    pixmapDrawContextData.drawContextData.clearImageMethod      = kQ3ClearMethodWithColor;
    pixmapDrawContextData.drawContextData.clearImageColor.a     = 1.0;
    pixmapDrawContextData.drawContextData.clearImageColor.r     = 0.0;
    pixmapDrawContextData.drawContextData.clearImageColor.g     = 0.0;
    pixmapDrawContextData.drawContextData.clearImageColor.b     = 0.0;
    pixmapDrawContextData.drawContextData.paneState             = kQ3False;
    pixmapDrawContextData.drawContextData.maskState             = kQ3False;
    pixmapDrawContextData.drawContextData.doubleBufferState     = kQ3False;
    pixmapDrawContextData.pixmap.width                          = kDroneDrawContextSize;
    pixmapDrawContextData.pixmap.height                         = kDroneDrawContextSize;
    pixmapDrawContextData.pixmap.rowBytes                       = pixmapDrawContextData.pixmap.width*4;
    pixmapDrawContextData.pixmap.pixelSize                      = 32;
    pixmapDrawContextData.pixmap.pixelType                      = kQ3PixelTypeRGB32;
    pixmapDrawContextData.pixmap.bitOrder                       = kQ3EndianBig;
    pixmapDrawContextData.pixmap.byteOrder                      = kQ3EndianBig;
    pixmapDrawContextData.pixmap.image                          = malloc(pixmapDrawContextData.pixmap.height*pixmapDrawContextData.pixmap.rowBytes);
    
    gDroneDrawContext = Q3PixmapDrawContext_New(&pixmapDrawContextData);
    assert(gDroneDrawContext != NULL);
    
    Q3View_SetDrawContext(gDroneView, gDroneDrawContext);
    
    // Create its camera
    viewAngleCameraData.cameraData.placement.cameraLocation.x   = 0.0;
    viewAngleCameraData.cameraData.placement.cameraLocation.y   = 0.0;
    viewAngleCameraData.cameraData.placement.cameraLocation.z   = 0.0;
    viewAngleCameraData.cameraData.placement.pointOfInterest.x  = 1.0;
    viewAngleCameraData.cameraData.placement.pointOfInterest.y  = 0.0;
    viewAngleCameraData.cameraData.placement.pointOfInterest.z  = 0.0;
    viewAngleCameraData.cameraData.placement.upVector.x         = 0.0;
    viewAngleCameraData.cameraData.placement.upVector.y         = 1.0;
    viewAngleCameraData.cameraData.placement.upVector.z         = 0.0;
    viewAngleCameraData.cameraData.range.hither                 = 0.1;
    viewAngleCameraData.cameraData.range.yon                    = BULLET_DRONE_LIMIT;
    viewAngleCameraData.cameraData.viewPort.origin.x            = -1.0;
    viewAngleCameraData.cameraData.viewPort.origin.y            = 1.0;
    viewAngleCameraData.cameraData.viewPort.width               = 2.0;
    viewAngleCameraData.cameraData.viewPort.height              = 2.0;
    viewAngleCameraData.fov                                     = 0.1;
    viewAngleCameraData.aspectRatioXToY                         = pixmapDrawContextData.pixmap.width/pixmapDrawContextData.pixmap.height;
 
    gDroneCamera = Q3ViewAngleAspectCamera_New(&viewAngleCameraData);
    assert(gDroneCamera != NULL);
    
    Q3View_SetCamera(gDroneView, gDroneCamera);
    
    // Create the pick object
    windowPointPickData.data.sort               = kQ3PickSortNearToFar;
    windowPointPickData.data.mask               = kQ3PickDetailMaskPickID | kQ3PickDetailMaskDistance;
    windowPointPickData.data.numHitsToReturn    = kQ3ReturnAllHits;
    windowPointPickData.point.x                 = 0.5*pixmapDrawContextData.pixmap.width;
    windowPointPickData.point.y                 = 0.5*pixmapDrawContextData.pixmap.height;
    windowPointPickData.vertexTolerance         = 0.0;
    windowPointPickData.edgeTolerance           = 0.0;
    
    gDronePick = Q3WindowPointPick_New(&windowPointPickData);
    assert(gDronePick != NULL);
    
    // Create the autodrone markers
    markerData.location.x           = 0.0;
    markerData.location.y           = 0.0;
    markerData.location.z           = 0.0;
    markerData.xOffset              = -3;
    markerData.yOffset              = -3;
    markerData.bitmap.width         = 8;
    markerData.bitmap.height        = 8;
    markerData.bitmap.rowBytes      = 1;
    markerData.bitmap.bitOrder      = kQ3EndianBig;
    markerData.markerAttributeSet   = Q3AttributeSet_New();
    
    assert(markerData.markerAttributeSet != NULL);
    
    color.r = 1.0;
    color.g = 1.0;
    color.b = 0.4;
    
    Q3AttributeSet_Add(markerData.markerAttributeSet, kQ3AttributeTypeDiffuseColor, &color);
    
    markerData.bitmap.image = (unsigned char*) gDroneAutoMarkerDataEqual;
    gDroneAutoMarkerEqual = Q3Marker_New(&markerData);
    assert(gDroneAutoMarkerEqual != NULL);
    
    markerData.bitmap.image = (unsigned char*) gDroneAutoMarkerDataAbove;
    gDroneAutoMarkerAbove = Q3Marker_New(&markerData);
    assert(gDroneAutoMarkerAbove != NULL);
    
    markerData.bitmap.image = (unsigned char*) gDroneAutoMarkerDataBelow;
    gDroneAutoMarkerBelow = Q3Marker_New(&markerData);
    assert(gDroneAutoMarkerBelow != NULL);
    
    Q3Object_Dispose(markerData.markerAttributeSet);
    markerData.markerAttributeSet = NULL;
}
 
 
/* =============================================================================
 *      Drone_Exit (external)
 *
 *  Prepares for exit.
 * ========================================================================== */
void Drone_Exit(
    void)
{
    while (gDroneList != NULL)
    {
        Drone_Dispose(gDroneList);
    }
    
    if (gDroneAutoGeometry != NULL)
    {
        Q3Object_Dispose(gDroneAutoGeometry);
        gDroneAutoGeometry = NULL;
    }
    
    if (gDroneAutoBurnGeometry != NULL)
    {
        Q3Object_Dispose(gDroneAutoBurnGeometry);
        gDroneAutoBurnGeometry = NULL;
    }
    
    if (gDroneAutoExplosionGeometry != NULL)
    {
        Q3Object_Dispose(gDroneAutoExplosionGeometry);
        gDroneAutoExplosionGeometry = NULL;
    }
    
    if (gDroneAutoSndIdle != NULL)
    {
        ReleaseResource((Handle) gDroneAutoSndIdle);
        gDroneAutoSndIdle = NULL;
    }
    
    if (gDroneAutoSndBurn != NULL)
    {
        ReleaseResource((Handle) gDroneAutoSndBurn);
        gDroneAutoSndBurn = NULL;
    }
    
    if (gDroneAutoSndExplosion != NULL)
    {
        ReleaseResource((Handle) gDroneAutoSndExplosion);
        gDroneAutoSndExplosion = NULL;
    }
    
    if (gDroneBulletColor != NULL)
    {
        Q3Object_Dispose(gDroneBulletColor);
        gDroneBulletColor = NULL;
    }
    
    if (gDroneNULLIllumination != NULL)
    {
        Q3Object_Dispose(gDroneNULLIllumination);
        gDroneNULLIllumination = NULL;
    }
    
    if (gDroneView != NULL)
    {
        Q3Object_Dispose(gDroneView);
        gDroneView = NULL;
    }
    
    if (gDroneDrawContext != NULL)
    {
        Q3Object_Dispose(gDroneDrawContext);
        gDroneDrawContext = NULL;
    }
    
    if (gDroneCamera != NULL)
    {
        Q3Object_Dispose(gDroneCamera);
        gDroneCamera = NULL;
    }
    
    if (gDronePick != NULL)
    {
        Q3Object_Dispose(gDronePick);
        gDronePick = NULL;
    }
    
    if (gDroneAutoMarkerEqual != NULL)
    {
        Q3Object_Dispose(gDroneAutoMarkerEqual);
        gDroneAutoMarkerEqual = NULL;
    }
    
    if (gDroneAutoMarkerAbove != NULL)
    {
        Q3Object_Dispose(gDroneAutoMarkerAbove);
        gDroneAutoMarkerAbove = NULL;
    }
    
    if (gDroneAutoMarkerBelow != NULL)
    {
        Q3Object_Dispose(gDroneAutoMarkerBelow);
        gDroneAutoMarkerBelow = NULL;
    }
}
 
 
/* =============================================================================
 *      Drone_New (internal)
 *
 *  Creates a new drone.
 * ========================================================================== */
TDroneObject Drone_New(
    TDroneOrder         inOrder)
{
    TDroneObject        drone;
    TDroneObject        prev;
    TDroneObject        next;
    
    // Allocate the memory
    drone = (TDroneObject) malloc(sizeof(TDroneData));
    assert(drone != NULL);
    
    drone->thumbprint = kThumbprint_Dead;  // until finished with it
    
    // Find where to insert it
    prev = NULL;
    next = gDroneList;
    while (next != NULL && next->order < inOrder)
    {
        prev = next;
        next = next->next;
    }
    
    // Link it into the list
    drone->prev = prev;
    drone->next = next;
    
    if (prev != NULL)
    {
        prev->next = drone;
    }
    else
    {
        gDroneList = drone;
    }
    
    if (next != NULL)
    {
        next->prev = drone;
    }
    
    // Fill in the defaults
    drone->moveMethod           = NULL;
    drone->updateSoundMethod    = NULL;
    drone->submitMethod         = NULL;
    drone->pickSubmitMethod     = NULL;
    drone->hitMethod            = NULL;
    
    drone->order                = inOrder;
    
    drone->mark                 = false;
    
    drone->position.x           = 0.0;
    drone->position.y           = 0.0;
    drone->position.z           = 0.0;
    
    drone->position1.x          = 0.0;
    drone->position1.y          = 0.0;
    drone->position1.z          = 0.0;
    
    drone->velocity.x           = 0.0;
    drone->velocity.y           = 0.0;
    drone->velocity.z           = 0.0;
    
    drone->velocity1.x          = 0.0;
    drone->velocity1.y          = 0.0;
    drone->velocity1.z          = 0.0;
    
    drone->acceleration.x       = 0.0;
    drone->acceleration.y       = 0.0;
    drone->acceleration.z       = 0.0;
    
    drone->direction.x          = 1.0;
    drone->direction.y          = 0.0;
    drone->direction.z          = 0.0;
    
    drone->up.x                 = 0.0;
    drone->up.y                 = 1.0;
    drone->up.z                 = 0.0;
    
    drone->cross.x              = 0.0;
    drone->cross.y              = 0.0;
    drone->cross.z              = 1.0;
    
    drone->geometry             = NULL;
    
    drone->autoSndChannel       = NULL;
    drone->autoSource           = NULL;
    drone->autoSound            = kAutoSound_None;
    
    drone->autoInterest         = NULL;
    
    drone->autoVelocity.x       = 0.0;
    drone->autoVelocity.y       = 0.0;
    drone->autoVelocity.z       = 0.0;
    
    drone->autoMode             = kAutoMode_Idle;
    drone->autoModeTimeout      = 0;
    drone->autoDistance         = 0.0;
    drone->autoExplosion        = 0.0;
    
    drone->bulletOrigin.x       = 0.0;
    drone->bulletOrigin.y       = 0.0;
    drone->bulletOrigin.z       = 0.0;
    
    return drone;
}
 
 
/* =============================================================================
 *      SelfDrone_New (external)
 *
 *  Creates a new drone whose movement pattern is defined by user controls.
 * ========================================================================== */
TDroneObject SelfDrone_New(
    void)
{
    TDroneObject        drone;
    
    // Create the basic drone
    drone = Drone_New(kDroneOrder_Self);
    assert(drone != NULL);
    
    // Fill in the fields
    drone->moveMethod       = SelfDrone_Move;
    
    Q3Vector3D_Scale(&drone->direction, SELF_DRONE_INITIAL_SPEED, &drone->autoVelocity);
    
    // Validate it
    drone->thumbprint       = kThumbprint_SelfDrone;
    
    return drone;
}
 
 
// **************************** GetSSpFilterVersion ****************************
// Finds the manufacturer and version number of the SoundSprocket filter that
// may be installed.  inManufacturer should be the manufacturer code specified
// at the installation time, which may be zero to allow any manufacturer.
// If no error is encountered, outManufacturer is set to the actual manufacturer
// code and outMajorVersion and outMinorVersion are set to the component
// specification level and manufacturer's implementation revision, respectively.
static OSStatus GetSSpFilterVersion(
    OSType                  inManufacturer,
    OSType*                 outManufacturer,
    UInt32*                 outMajorVersion,
    UInt32*                 outMinorVersion)
{
    OSStatus                err;
    ComponentDescription    description;
    Component               componentRef;
    UInt32                  vers;
    
    // Set up the component description
    description.componentType           = kSoundEffectsType;
    description.componentSubType        = kSSpLocalizationSubType;
    description.componentManufacturer   = inManufacturer;
    description.componentFlags          = 0;        
    description.componentFlagsMask      = 0;    
    
    // Find a component matching the description
    componentRef = FindNextComponent(nil, &description);
    if (componentRef == nil)  return couldntGetRequiredComponent;
    
    // Get the component description (for the manufacturer code)
    err = GetComponentInfo(componentRef, &description, nil, nil, nil);
    if (err != noErr)  return err;
    
    // Get the version composite
    vers = (UInt32) GetComponentVersion((ComponentInstance) componentRef);
    
    // Return the results
    *outManufacturer = description.componentManufacturer;
    *outMajorVersion = HiWord(vers);
    *outMinorVersion = LoWord(vers);
    
    return noErr;
}
 
 
/* =============================================================================
 *      AutoDrone_New (external)
 *
 *  Creates a new drone whose movement pattern is under automatic control.
 * ========================================================================== */
TDroneObject AutoDrone_New(
    TDroneObject            inDroneOfInterest)
{
    OSStatus            err;
    TDroneObject        drone;
    TQ3Vector3D         orientation;
    SoundComponentLink  link;
    OSType              manufacturer;
    UInt32              majorVersion;
    UInt32              minorVersion;
    
    assert(inDroneOfInterest != NULL && IS_DRONE(inDroneOfInterest));
    
    // Create the basic drone
    drone = Drone_New(kDroneOrder_Auto);
    assert(drone != NULL);
    
    // Fill in the fields
    drone->moveMethod       = AutoDrone_Move;
    drone->updateSoundMethod = AutoDrone_UpdateSound;
    drone->submitMethod     = AutoDrone_Submit;
    drone->pickSubmitMethod = AutoDrone_PickSubmit;
    drone->hitMethod        = AutoDrone_Hit;
    
    // Allocate the sound channel and set up for 3D localized sound
    drone->autoSndChannel   = NULL;
    SndNewChannel(&drone->autoSndChannel, sampledSynth, initMono, NULL);
    assert(drone->autoSndChannel != NULL);
    
    SSpSource_New(&drone->autoSource);
    assert(drone->autoSource != NULL);
    
    link.description.componentType          = kSoundEffectsType;
    link.description.componentSubType       = kSSpLocalizationSubType;
    link.description.componentManufacturer  = 0;
    link.description.componentFlags         = 0;        
    link.description.componentFlagsMask     = 0;    
    link.mixerID                            = nil;
    link.linkID                             = nil;
    
    SndSetInfo(drone->autoSndChannel, siPreMixerSoundComponent, &link);
    
    // Verify that the right version filter was installed
    if (gDroneAutoCheckFilterVersion)
    {
        err = GetSSpFilterVersion(
                link.description.componentManufacturer,
                &manufacturer,
                &majorVersion,
                &minorVersion);
        
        if (err != noErr)
        {
            // Filter must not be installed
            // Note: A normal application would bail on 3D sound here
            StopAlert(kAlrtID_FilterNotInstalled, NULL);
        }
        else
        {
            // The major version represents the component specification level
            if (majorVersion < 1)
            {
                //¥ if we couldn't handle some old version, we could bail here.
                
                StopAlert(kAlrtID_FilterVersion, NULL);
                // Note: A normal application would bail on 3D sound here
                gSoundOn = false;
            }
            else
            {
                // The minor version specifies the manufacturer's implementation revision
                
                //¥ could do something here is we needed to handle tweaks for specific
                //¥ manufacturers or versions.
            }
        }
        
        gDroneAutoCheckFilterVersion = false;
    }
 
    if (gSoundOn)
    {
        // The sound is loudest out the back of the model
        Q3Vector3D_Set(&orientation, -1.0, 0.0, 0.0);
        SSpSource_SetOrientation(drone->autoSource, &orientation);
    }
    
    drone->autoInterest     = inDroneOfInterest;
    drone->geometry         = Q3Shared_GetReference(gDroneAutoGeometry);
    
    drone->position.x       += Random()*0.0001 + AUTO_DRONE_LEAD;
    drone->position.y       += Random()*0.0001;
    drone->position.z       += Random()*0.0001;
    
    drone->autoVelocity.x   += Random()*0.0001;
    drone->autoVelocity.y   += Random()*0.0001;
    drone->autoVelocity.z   += Random()*0.0001;
    
    // Validate it
    drone->thumbprint       = kThumbprint_AutoDrone;
    
    return drone;
}
 
 
/* =============================================================================
 *      BulletDrone_New (external)
 *
 *  Creates a new drone whose behavior is projectile.
 * ========================================================================== */
TDroneObject BulletDrone_New(
    const TQ3Point3D*       inPosition,
    const TQ3Vector3D*      inDirection)
{
    TDroneObject            drone;
    
    assert(inPosition != NULL);
    assert(inDirection != NULL);
    
    // Create the basic drone
    drone = Drone_New(kDroneOrder_Bullet);
    assert(drone != NULL);
    
    // Fill in the fields
    drone->moveMethod       = BulletDrone_Move;
    drone->submitMethod     = BulletDrone_Submit;
    
    drone->position         = *inPosition;
    drone->direction        = *inDirection;
    
    drone->bulletOrigin     = *inPosition;
    
    // Validate it
    drone->thumbprint       = kThumbprint_BulletDrone;
    
    return drone;
}
 
 
/* =============================================================================
 *      Drone_Dispose (external)
 *
 *  Disposes of the drone.
 * ========================================================================== */
void Drone_Dispose(
    TDroneObject            inDrone)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    // Unlink it from the list
    if (inDrone->prev != NULL)
    {
        inDrone->prev->next = inDrone->next;
    }
    else
    {
        gDroneList = inDrone->next;
    }
    
    if (inDrone->next != NULL)
    {
        inDrone->next->prev = inDrone->prev;
    }
    
    // Dispose stuff
    if (inDrone->geometry != NULL)
    {
        Q3Object_Dispose(inDrone->geometry);
        inDrone->geometry = NULL;
    }
    
    // Free the sound channel
    //¥ This rightly belongs in AutoDrone_Dispose
    if (inDrone->autoSource != NULL)
    {
        SSpSource_Dispose(inDrone->autoSource);
        inDrone->autoSource = NULL;
    }
    
    if (inDrone->autoSndChannel != NULL)
    {
        SndDisposeChannel(inDrone->autoSndChannel, true);
        inDrone->autoSndChannel = NULL;
    }
    
    // Dispose of the memory
    inDrone->thumbprint = kThumbprint_Dead;
    free(inDrone);
}
 
 
/* =============================================================================
 *      Drone_Next (external)
 *
 *  If inDrone is NULL, then the head of the drone list is returned.  If inDrone
 *  is non-NULL, then the next drone in the list is returned.  If inDrone is the
 *  last drone, then NULL is returned.
 * ========================================================================== */
TDroneObject Drone_Next(
    TDroneObject            inDrone)
{
    TDroneObject            result;
    
    if (inDrone != NULL)
    {
        assert(IS_DRONE(inDrone));
        
        result = inDrone->next;
    }
    else
    {
        result = gDroneList;
    }
    
    return result;
}
 
 
/* =============================================================================
 *      Drone_Move (external)
 *
 *  Moves the drone forward one time step.  This may mark the drone for death.
 * ========================================================================== */
void Drone_Move(
    TDroneObject            inDrone)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    // Move the drone by its own rules
    assert(inDrone->moveMethod != NULL);
    (inDrone->moveMethod)(inDrone);
    
    // Drone is still alive -- compute the velocity and acceleration
    Q3Point3D_Subtract(&inDrone->position, &inDrone->position1, &inDrone->velocity);
    Q3Vector3D_Scale(&inDrone->velocity, gGameFramesPerSecond, &inDrone->velocity);
    inDrone->position1 = inDrone->position;
    
    Q3Vector3D_Subtract(&inDrone->velocity, &inDrone->velocity1, &inDrone->acceleration);
    Q3Vector3D_Scale(&inDrone->acceleration, gGameFramesPerSecond, &inDrone->acceleration);
    inDrone->velocity1 = inDrone->velocity;
    
    // Reorthogonalize the up and cross vectors
    assert(fabs(Q3Vector3D_Length(&inDrone->direction) - 1.0) < 0.05);
    
    Q3Vector3D_Cross(&inDrone->direction, &inDrone->up, &inDrone->cross);
    Q3Vector3D_Normalize(&inDrone->cross, &inDrone->cross);
    
    Q3Vector3D_Cross(&inDrone->cross, &inDrone->direction, &inDrone->up);
}
 
 
 
/* =============================================================================
 *      AutoDrone_Move (internal)
 *
 *  Moves the drone forward one time step.  The autopilot drone has a constant
 *  thrust along its direction of orientation.  The direction is controlled to
 *  point just in front of the drone of interest.
 * ========================================================================== */
void AutoDrone_Move(
    TDroneObject            inDrone)
{
    TQ3Vector3D             newDirection;
    TQ3Vector3D             v1;
    TQ3Vector3D             v2;
    TQ3Point3D              target;
    float                   distance;
    float                   turnRate;
    float                   limit;
    TAutoSound              newSound;
    SndCommand              sndCommand;
    long                    base;
    TQ3Matrix4x4            matrix;
    
    assert(inDrone != NULL && IS_AUTO_DRONE(inDrone));
    
    // Figure out new direction
    if (inDrone->autoMode == kAutoMode_Explosion)
    {
        // Point the explosion geometry at the camera
        //¥ NOTE: For now, we assume that the camera is at the drone of interest
        //¥ This is wrong for two reasons.  First, the camera could actually be
        //¥ elsewhere.  Second, this assumes that the drone of interest moves
        //¥ before this drone.
        Q3Point3D_Subtract(
                &inDrone->autoInterest->position,
                &inDrone->position,
                &newDirection);
        
        Q3Vector3D_Normalize(&newDirection, &inDrone->direction);
    }
    else
    {
        // Find a point in front of the drone of interest
        assert(IS_DRONE(inDrone->autoInterest));
        
        Q3Vector3D_Scale(&inDrone->autoInterest->direction, AUTO_DRONE_LEAD, &v1);
        Q3Point3D_Vector3D_Add(&inDrone->autoInterest->position, &v1, &target);
        
        // Point toward the target
        Q3Point3D_Subtract(&target, &inDrone->position, &newDirection);
        Q3Vector3D_Normalize(&newDirection, &newDirection);
        
        // Limit the turn rate
        turnRate = acosf(Q3Vector3D_Dot(&inDrone->direction, &newDirection));
        limit = AUTO_DRONE_TURN_RATE*gGameInterval;
        if (turnRate > limit)
        {
            // Limit the turn
            // Note: this should actually be spherical interpolation -- but linear is close enough
            turnRate = limit/turnRate;
            Q3Vector3D_Scale(&inDrone->direction, 1.0-turnRate, &v1);
            Q3Vector3D_Scale(&newDirection, turnRate, &v2);
            Q3Vector3D_Add(&v1, &v2, &inDrone->direction);
            Q3Vector3D_Normalize(&inDrone->direction, &inDrone->direction);
        }
        else
        {
            // It's OK to make the desired turn
            inDrone->direction = newDirection;
        }
    }
    
    // Figure out new position
    switch (inDrone->autoMode)
    {
        case kAutoMode_Idle: // Start a burn if we are getting farther from the target
            // Start burn if we are getting farther from the target
            distance = Q3Point3D_Distance(&inDrone->position, &target);
            if (inDrone->autoDistance > distance)
            {
                // Getting closer to target -- don't change
                inDrone->autoDistance = distance;
            }
            else
            {
                // Getting farther from target -- start a burn
                inDrone->autoMode = kAutoMode_Burn;
                inDrone->autoModeTimeout = TickCount() + (unsigned long) (AUTO_DRONE_BURN_TIME*60);
            }
        break;
        
        case kAutoMode_Burn: // Accelerate toward the target
            // Continue the burn?
            if (TickCount() <= inDrone->autoModeTimeout)
            {
                // Still burning -- change the velocity by accelerating toward the target
                Q3Vector3D_Scale(&inDrone->direction, gGameInterval*AUTO_DRONE_ACCEL, &v1);
                Q3Vector3D_Add(&inDrone->autoVelocity, &v1, &inDrone->autoVelocity);
            }
            else
            {
                // Switch to rest mode
                inDrone->autoMode = kAutoMode_Rest;
                inDrone->autoModeTimeout = TickCount() + (unsigned long) (AUTO_DRONE_REST_TIME*60);
            }
        break;
        
        case kAutoMode_Rest: // Don't use the engine for a while
            // Continue the rest?
            if (TickCount() <= inDrone->autoModeTimeout)
            {
                // Still resting
                // (do nothing)
            }
            else
            {
                // Switch to idle mode
                inDrone->autoMode = kAutoMode_Idle;
                inDrone->autoDistance = Q3Point3D_Distance(&inDrone->position, &target);
            }
        break;
        
        case kAutoMode_Explosion: // Show the explosion for a while
            // Continue the explosion?
            inDrone->autoExplosion += gGameInterval/AUTO_DRONE_EXPLOSION_TIME;
            if (inDrone->autoExplosion <= 1.0)
            {
                // Still exploding
                inDrone->autoVelocity.x =
                inDrone->autoVelocity.y =
                inDrone->autoVelocity.z = 0.0;
            }
            else
            {
                // Kill the drone
                Drone_SetMark(inDrone, true);
            }
        break;
        
        default:
            assert(0);
    }
    
    // Move along the new velocity vector
    Q3Vector3D_Scale(&inDrone->autoVelocity, gGameInterval, &v1);
    Q3Point3D_Vector3D_Add(&inDrone->position, &v1, &inDrone->position);
    
    if (gSoundOn)
    {
        // Choose the next sound to play
        switch (inDrone->autoMode)
        {
            case kAutoMode_Idle:
            case kAutoMode_Rest:
                newSound = kAutoSound_Idle;
            break;
            
            case kAutoMode_Burn:
                newSound = kAutoSound_Burn;
            break;
            
            break;
            
            case kAutoMode_Explosion:
                newSound = kAutoSound_Explosion;
            break;
            
            default:
                assert(0);
        }
        
        // Change the sound
        if (inDrone->autoSound != newSound)
        {
            // Stop the old sound
            if (inDrone->autoSound != kAutoSound_None)
            {
                sndCommand.cmd = quietCmd;
                sndCommand.param1 = 0;
                sndCommand.param2 = 0;
                SndDoImmediate(inDrone->autoSndChannel, &sndCommand);
            }
            
            inDrone->autoSound = newSound;
            
            // Play the new sound
            switch (inDrone->autoSound)
            {
                case kAutoSound_None:
                    base = 0;
                break;
                
                case kAutoSound_Idle:
                    base = (long) *gDroneAutoSndIdle + gDroneAutoSndIdleOffset;
                    SSpSource_SetAngularAttenuation(inDrone->autoSource, 0.0, 0.0);
                    SSpSource_SetReferenceDistance(inDrone->autoSource, AUTO_DRONE_IDLE_REF_DIST);
                break;
                
                case kAutoSound_Burn:
                    base = (long) *gDroneAutoSndBurn + gDroneAutoSndBurnOffset;
                    SSpSource_SetAngularAttenuation(inDrone->autoSource, 1.5, -12.0);
                    SSpSource_SetReferenceDistance(inDrone->autoSource, AUTO_DRONE_BURN_REF_DIST);
                break;
                
                case kAutoSound_Explosion:
                    base = (long) *gDroneAutoSndExplosion + gDroneAutoSndExplosionOffset;
                    SSpSource_SetAngularAttenuation(inDrone->autoSource, 0.0, 0.0);
                    SSpSource_SetReferenceDistance(inDrone->autoSource, AUTO_DRONE_EXPL_REF_DIST);
                break;
                
                default:
                    assert(0);
            }
            
            if (base != 0)
            {
                // Install the sound
                sndCommand.cmd = soundCmd;
                sndCommand.param1 = 0;
                sndCommand.param2 = base;
                SndDoImmediate(inDrone->autoSndChannel, &sndCommand);
                
                // Play it indefinitely
                sndCommand.cmd = freqCmd;
                sndCommand.param1 = 0;
                sndCommand.param2 = 60;
                SndDoImmediate(inDrone->autoSndChannel, &sndCommand);
            }
        }
        
        // Change the sound source location
        Drone_GetMatrix(inDrone, &matrix);
        SSpSource_SetTransform(inDrone->autoSource, &matrix);
    }
}
 
 
/* =============================================================================
 *      BulletDrone_Move (internal)
 *
 *  Moves the drone forward one time step.
 * ========================================================================== */
void BulletDrone_Move(
    TDroneObject            inDrone)
{
    TQ3Vector3D             v;
    float                   prevDistance;
    float                   currDistance;
    TQ3CameraPlacement      placement;
    TDroneObject            target;
    unsigned long           count;
    unsigned long           index;
    
    assert(inDrone != NULL && IS_BULLET_DRONE(inDrone));
    
    // Move the bullet
    prevDistance = Q3Point3D_Distance(&inDrone->position, &inDrone->bulletOrigin);
    
    Q3Vector3D_Scale(&inDrone->direction, gGameInterval*BULLET_DRONE_SPEED, &v);
    Q3Point3D_Vector3D_Add(&inDrone->position, &v, &inDrone->position);
    
    currDistance = Q3Point3D_Distance(&inDrone->position, &inDrone->bulletOrigin);
    
    // Time to expire?
    if (currDistance > BULLET_DRONE_LIMIT)
    {
        // Mark the drone to die
        Drone_SetMark(inDrone, true);
    }
    else
    {
        // Set up for collision detection
        placement.cameraLocation    = inDrone->bulletOrigin;
        placement.upVector          = inDrone->up;
        
        Q3Point3D_Vector3D_Add(&inDrone->bulletOrigin, &inDrone->direction, &placement.pointOfInterest);
        
        Q3Camera_SetPlacement(gDroneCamera, &placement);
        
        // Collision detection with all target drones
        Q3View_StartPicking(gDroneView, gDronePick);
        do
        {
            for (target = Drone_Next(NULL); target != NULL; target = Drone_Next(target))
            {
                // Submit the drone geometry, along with a PickID that is the object reference
                Q3PickIDStyle_Submit((unsigned long) target, gDroneView);
                Drone_PickSubmit(target, gDroneView);
            }
        }
        while (Q3View_EndPicking(gDroneView) == kQ3ViewStatusRetraverse);
        
        // Check the hit list
        Q3Pick_GetNumHits(gDronePick, &count);
        for (index = 0; index < count; index++)
        {
        UInt32  pickGood, maskGood;
        
            Q3Pick_GetPickDetailData(gDronePick, index, kQ3PickDetailMaskPickID, &pickGood);
            Q3Pick_GetPickDetailData(gDronePick, index, kQ3PickDetailMaskDistance, &maskGood);
            
            if (pickGood && maskGood)
            {
                Q3Pick_GetPickDetailData(gDronePick, index, kQ3PickDetailMaskPickID, &target);
                
                if (target != NULL && IS_DRONE(target))
                {
                    // Got a valid hit -- check its range
                    //¥ Should it be a bullet or a laser?
//                  if ((1 || prevDistance <= hitData.distance) && hitData.distance <= currDistance)
                    if (true)
                    {
                        // Hit it!
                        Drone_Hit(target);
                        
                        // Kill the bullet
                        Drone_SetMark(inDrone, true);
                        break;
                    }
                }
            }
            
//          Q3Hit_EmptyData(&hitData);
        }
        
        // Empty out the pick hits
        Q3Pick_EmptyHitList(gDronePick);
    }
}
 
 
/* =============================================================================
 *      Drone_UpdateSound (external)
 *
 *  Updates localized sounds for this drone.  The default method does nothing.
 * ========================================================================== */
void Drone_UpdateSound(
    TDroneObject            inDrone)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    if (inDrone->updateSoundMethod != NULL)
    {
        (inDrone->updateSoundMethod)(inDrone);
    }
}
 
 
/* =============================================================================
 *      AutoDrone_UpdateSound (external)
 *
 *  Updates localized sounds for this autopilot drone.
 * ========================================================================== */
void AutoDrone_UpdateSound(
    TDroneObject            inDrone)
{
    SSpLocalizationData             snd3DInfo;
    
    assert(inDrone != NULL && IS_AUTO_DRONE(inDrone));
    
    if (gSoundOn)
    {
        SSpSource_CalcLocalization(inDrone->autoSource, Sound_GetListener(), &snd3DInfo);
        
        SndSetInfo(inDrone->autoSndChannel, siSSpLocalization, &snd3DInfo);
    }
}
 
 
/* =============================================================================
 *      Drone_Submit (external)
 *
 *  Submits the drone for drawing.
 * ========================================================================== */
void Drone_Submit(
    TDroneObject            inDrone,
    Boolean                 inHUDVisible,
    TQ3ViewObject           inView)
{
    TQ3Matrix4x4            matrix;
    
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    if (inDrone->submitMethod != NULL)
    {
        // Use the submit method
        (inDrone->submitMethod)(inDrone, inHUDVisible, inView);
    }
    else if (inDrone->geometry != NULL)
    {
        // Submit the geometry
        Drone_GetMatrix(inDrone, &matrix);
        
        Q3Push_Submit(inView);
        Q3MatrixTransform_Submit(&matrix, inView);
        Q3Object_Submit(inDrone->geometry, inView);
        Q3Pop_Submit(inView);
    }
}
 
 
/* =============================================================================
 *      AutoDrone_Submit (internal)
 *
 *  Submits the bullet drone for drawing.
 * ========================================================================== */
void AutoDrone_Submit(
    TDroneObject            inDrone,
    Boolean                 inHUDVisible,
    TQ3ViewObject           inView)
{
    TQ3Matrix4x4            matrix;
    TQ3ColorRGB             transparency;
    TQ3Point3D              position;
    TQ3Vector3D             direction;
    TQ3Vector3D             up;
    TQ3Vector3D             right;
    TQ3Vector3D             v;
    TQ3Vector3D             v1;
    TQ3Vector3D             v2;
    float                   height;
    TQ3GeometryObject       marker;
    
    assert(inDrone != NULL && IS_AUTO_DRONE(inDrone));
    assert(inView != NULL);
    
    // Draw the drone
    Q3Push_Submit(inView);
    
    Drone_GetMatrix(inDrone, &matrix);
    Q3MatrixTransform_Submit(&matrix, inView);
    
    switch (inDrone->autoMode)
    {
        case kAutoMode_Burn:
            Q3Object_Submit(gDroneAutoBurnGeometry, inView);
            /* FALL THROUGH TO SUBMIT DRONE GEOMETRY */
        
        case kAutoMode_Rest:
        case kAutoMode_Idle:
            Q3Object_Submit(inDrone->geometry, inView);
        break;
        
        case kAutoMode_Explosion:
            // Grow the explosion
            v.x = v.y = v.z = (1.0-AUTO_DRONE_MIN_SCALE)*inDrone->autoExplosion + AUTO_DRONE_MIN_SCALE;
            Q3ScaleTransform_Submit(&v, inView);
            
            // Fade at the end
            if (inDrone->autoExplosion > (1.0-AUTO_DRONE_FADE_TIME))
            {
                transparency.r =
                transparency.g =
                transparency.b = (-1.0/AUTO_DRONE_FADE_TIME)*inDrone->autoExplosion + (1.0/AUTO_DRONE_FADE_TIME);
                
                Q3Attribute_Submit(kQ3AttributeTypeTransparencyColor, &transparency, inView);
            }
            
            // Submit the explosion geometry
            Q3Object_Submit(gDroneAutoExplosionGeometry, inView);
        break;
        
        default:
            assert(0);
    }
    
    Q3Pop_Submit(inView);
    
    // Draw the HUD marker for the drone
    if (inHUDVisible && inDrone->autoMode != kAutoMode_Explosion)
    {
        Display_GetViewerPosition(&position, &direction, &up);
        Q3Vector3D_Cross(&direction, &up, &right);
        
        Q3Point3D_Subtract(&inDrone->position, &position, &v);
        
        Q3Vector3D_Scale(&up,    HUD_SCALE*Q3Vector3D_Dot(&direction, &v), &v1);
        Q3Vector3D_Scale(&right, HUD_SCALE*Q3Vector3D_Dot(&right,     &v), &v2);
        
        Q3Point3D_Vector3D_Add(&position, &direction, &position);
        Q3Point3D_Vector3D_Add(&position, &v1,        &position);
        Q3Point3D_Vector3D_Add(&position, &v2,        &position);
        
        Q3Vector3D_Normalize(&v, &v);
        height = Q3Vector3D_Dot(&up, &v);
        if (height >= HUD_HEIGHT)
        {
            marker = gDroneAutoMarkerAbove;
        }
        else if (height <= -HUD_HEIGHT)
        {
            marker = gDroneAutoMarkerBelow;
        }
        else
        {
            marker = gDroneAutoMarkerEqual;
        }
        
        Q3Marker_SetPosition(marker, &position);
        Q3Object_Submit(marker, inView);
    }
}
 
 
/* =============================================================================
 *      BulletDrone_Submit (internal)
 *
 *  Submits the bullet drone for drawing.
 * ========================================================================== */
void BulletDrone_Submit(
    TDroneObject            inDrone,
    Boolean                 inHUDVisible,
    TQ3ViewObject           inView)
{
    TQ3LineData             lineData;
    
    assert(inDrone != NULL && IS_BULLET_DRONE(inDrone));
    assert(inView != NULL);
    
    lineData.vertices[0].point          = inDrone->bulletOrigin;
    lineData.vertices[0].attributeSet   = NULL;
    lineData.vertices[1].point          = inDrone->position;
    lineData.vertices[1].attributeSet   = NULL;
    lineData.lineAttributeSet           = NULL;
    
    Q3Push_Submit(inView);
    Q3Object_Submit(gDroneNULLIllumination, inView);
    Q3Object_Submit(gDroneBulletColor, inView);
    Q3Line_Submit(&lineData, inView);
    Q3Pop_Submit(inView);
}
 
 
/* =============================================================================
 *      Drone_PickSubmit (internal)
 *
 *  Submits the drone for picking against a bullet.  It forwards to the actual
 *  drone hit method, if any.
 * ========================================================================== */
void Drone_PickSubmit(
    TDroneObject            inDrone,
    TQ3ViewObject           inView)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    if (inDrone->pickSubmitMethod != NULL)
    {
        (*inDrone->pickSubmitMethod)(inDrone, inView);
    }
}
 
 
/* =============================================================================
 *      AutoDrone_PickSubmit (internal)
 *
 *  Submits the autopilot drone for picking against a bullet.
 * ========================================================================== */
void AutoDrone_PickSubmit(
    TDroneObject            inDrone,
    TQ3ViewObject           inView)
{
    TQ3Matrix4x4            matrix;
    
    assert(inDrone != NULL && IS_AUTO_DRONE(inDrone));
    assert(inView != NULL);
    
    // Draw the drone
    if (inDrone->autoMode != kAutoMode_Explosion)
    {
        Q3Push_Submit(inView);
        
        Drone_GetMatrix(inDrone, &matrix);
        Q3MatrixTransform_Submit(&matrix, inView);
        
        Q3Object_Submit(gDroneAutoBurnGeometry, inView);
        
        Q3Pop_Submit(inView);
    }
}
 
 
/* =============================================================================
 *      Drone_Hit (internal)
 *
 *  Called when this drone is hit.  It forwards to the actual drone hit method,
 *  if any.
 * ========================================================================== */
void Drone_Hit(
    TDroneObject            inDrone)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    if (inDrone->hitMethod != NULL)
    {
        (*inDrone->hitMethod)(inDrone);
    }
}
 
 
/* =============================================================================
 *      AutoDrone_Hit (internal)
 *
 *  Called when this autopilot drone is hit.  It puts the drone into
 *  explosion mode.
 * ========================================================================== */
void AutoDrone_Hit(
    TDroneObject            inDrone)
{
    assert(inDrone != NULL && IS_AUTO_DRONE(inDrone));
    
    inDrone->autoMode = kAutoMode_Explosion;
    inDrone->autoExplosion = 0.0;
}
 
 
/* =============================================================================
 *      Drone_SetMark (external)
 *
 *  Changes the drone's mark to the given value.  The mark is used to indicate
 *  which drones should die.
 * ========================================================================== */
void Drone_SetMark(
    TDroneObject            inDrone,
    Boolean                 inMark)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    inDrone->mark = inMark;
}
 
 
/* =============================================================================
 *      Drone_GetMark (external)
 *
 *  Returns the drone's mark.
 * ========================================================================== */
Boolean Drone_GetMark(
    TDroneObject            inDrone)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    return inDrone->mark;
}
 
 
/* =============================================================================
 *      Drone_GetPosition (external)
 *
 *  Returns the current position in outPosition.
 * ========================================================================== */
void Drone_GetPosition(
    TDroneObject            inDrone,
    TQ3Point3D*             outPosition)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    assert(outPosition != NULL);
    
    *outPosition = inDrone->position;
}
 
 
/* =============================================================================
 *      Drone_GetVelocity (external)
 *
 *  Returns the current velocity in outVelocity
 * ========================================================================== */
void Drone_GetVelocity(
    TDroneObject            inDrone,
    TQ3Vector3D*            outVelocity)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    assert(outVelocity != NULL);
    
    *outVelocity = inDrone->velocity;
}
 
 
/* =============================================================================
 *      Drone_GetDirection (external)
 *
 *  Returns the current direction in outDirection.
 * ========================================================================== */
void Drone_GetDirection(
    TDroneObject            inDrone,
    TQ3Vector3D*            outDirection)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    assert(outDirection != NULL);
    
    *outDirection = inDrone->direction;
}
 
 
/* =============================================================================
 *      Drone_GetUp (external)
 *
 *  Returns the current up vector in outUp.
 * ========================================================================== */
void Drone_GetUp(
    TDroneObject            inDrone,
    TQ3Vector3D*            outUp)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    assert(outUp != NULL);
    
    *outUp = inDrone->up;
}
 
 
/* =============================================================================
 *      Drone_GetMatrix (external)
 *
 *  Returns the matrix that transforms to the drone position and orientation.
 * ========================================================================== */
void Drone_GetMatrix(
    TDroneObject            inDrone,
    TQ3Matrix4x4*           outMatrix)
{
    assert(inDrone != NULL && IS_DRONE(inDrone));
    assert(outMatrix != NULL);
    
    outMatrix->value[0][0] = inDrone->direction.x;
    outMatrix->value[0][1] = inDrone->direction.y;
    outMatrix->value[0][2] = inDrone->direction.z;
    
    outMatrix->value[1][0] = inDrone->up.x;
    outMatrix->value[1][1] = inDrone->up.y;
    outMatrix->value[1][2] = inDrone->up.z;
    
    outMatrix->value[2][0] = inDrone->cross.x;
    outMatrix->value[2][1] = inDrone->cross.y;
    outMatrix->value[2][2] = inDrone->cross.z;
    
    outMatrix->value[3][0] = inDrone->position.x;
    outMatrix->value[3][1] = inDrone->position.y;
    outMatrix->value[3][2] = inDrone->position.z;
    
    outMatrix->value[0][3] = 0.0;
    outMatrix->value[1][3] = 0.0;
    outMatrix->value[2][3] = 0.0;
    outMatrix->value[3][3] = 1.0;
}
 
 
/* =============================================================================
 *      Drone_Fire (external)
 *
 *  Called each time the fire button is pressed.
 * ========================================================================== */
void Drone_Fire(
    TDroneObject            inDrone)
{
    TQ3Vector3D             up;
    TQ3Vector3D             cross;
    TQ3Point3D              origin;
    
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    // Find the offsets
    Q3Vector3D_Scale(&inDrone->up,      BULLET_DRONE_OFFSET, &up);
    Q3Vector3D_Scale(&inDrone->cross,   BULLET_DRONE_OFFSET, &cross);
    
    // Fire one
    Q3Point3D_Vector3D_Subtract(&inDrone->position, &up, &origin);
    Q3Point3D_Vector3D_Subtract(&origin, &cross, &origin);
    BulletDrone_New(&origin, &inDrone->direction);
    
    // Fire two
    Q3Point3D_Vector3D_Subtract(&inDrone->position, &up, &origin);
    Q3Point3D_Vector3D_Add(&origin, &cross, &origin);
    BulletDrone_New(&origin, &inDrone->direction);
}
 
 
/* =============================================================================
 *      Drone_Silence (external)
 *
 *  Silences any drone sounds.
 * ========================================================================== */
void Drone_Silence(
    TDroneObject            inDrone)
{
    SndCommand              sndCommand;
    
    assert(inDrone != NULL && IS_DRONE(inDrone));
    
    //¥ We should really do this as AutoDrone_Silence, but whatever...
    
    if (inDrone->autoSndChannel != NULL)
    {
        // Purge any pending commands
        sndCommand.cmd = flushCmd;
        sndCommand.param1 = 0;
        sndCommand.param2 = 0;
        SndDoImmediate(inDrone->autoSndChannel, &sndCommand);
            
        // Quiet the current sound
        sndCommand.cmd = quietCmd;
        sndCommand.param1 = 0;
        sndCommand.param2 = 0;
        SndDoImmediate(inDrone->autoSndChannel, &sndCommand);
    }
}
 
/* =============================================================================
 *      SelfDrone_Move (internal)
 *
 *  Moves the drone forward one time step.
 * ========================================================================== */
void SelfDrone_Move(
    TDroneObject            inDrone)
{
    TQ3Vector3D             v;
    
    assert(inDrone != NULL && IS_SELF_DRONE(inDrone));
    
    // Move along the velocity vector
    Q3Vector3D_Scale(&inDrone->autoVelocity, gGameInterval, &v);
    Q3Point3D_Vector3D_Add(&inDrone->position, &v, &inDrone->position);
 
#if SELF_DRONE_HAS_FRICTION
    // automatically slow ship down if no thrust (like friction)
    Q3Vector3D_Scale(&inDrone->autoVelocity, SELF_DRONE_FRICTION, &inDrone->autoVelocity);
#endif
}
 
/* =============================================================================
 *      SelfDrone_Thrust (external)
 *
 *  Increases the velocity of the drone.
 * ========================================================================== */
void SelfDrone_Thrust(
    TDroneObject            inDrone,
    float                   inThrust)
{
    TQ3Vector3D             v;
    
    assert(inDrone != NULL && IS_SELF_DRONE(inDrone));
    
    // change the velocity by accelerating inThrust amount
    Q3Vector3D_Scale(&inDrone->direction, inThrust*SELF_DRONE_MAX_ACCEL, &v);
    Q3Vector3D_Add(&inDrone->autoVelocity, &v, &inDrone->autoVelocity);
}
 
/* =============================================================================
 *      SelfDrone_DampVelocity (external)
 *
 *  Increases the velocity of the drone.
 * ========================================================================== */
void SelfDrone_DampVelocity(
    TDroneObject            inDrone,
    float                   inDampingPercentage)
{
    assert(inDrone != NULL && IS_SELF_DRONE(inDrone));
    
    // automatically slow ship down if no thrust (like friction)
    Q3Vector3D_Scale(&inDrone->autoVelocity, inDampingPercentage, &inDrone->autoVelocity);
}
 
/* =============================================================================
 *      SelfDrone_AllStop (external)
 *
 *  Increases the velocity of the drone.
 * ========================================================================== */
void SelfDrone_InstantStop(
    TDroneObject            inDrone)
{
    assert(inDrone != NULL && IS_SELF_DRONE(inDrone));
    
    // automatically slow ship down if no thrust (like friction)
    Q3Vector3D_Scale(&inDrone->autoVelocity, 0.0, &inDrone->autoVelocity);
}
 
 
 
/* =============================================================================
 *      SelfDrone_Pitch (external)
 *
 *  Changes the direction of the ship by the given angles, in radians.
 * ========================================================================== */
void SelfDrone_Pitch(
    TDroneObject            inDrone,
    float                   inPitchAngle)
{
    TQ3Vector3D             v1;
    TQ3Vector3D             v2;
    
    assert(inDrone != NULL && IS_SELF_DRONE(inDrone));
    
    if (inPitchAngle != 0.0)
    {
        Q3Vector3D_Scale(&inDrone->direction, cosf(inPitchAngle), &v1);
        Q3Vector3D_Scale(&inDrone->up,        sinf(inPitchAngle), &v2);
        Q3Vector3D_Add(&v1, &v2, &inDrone->direction);
    }
}
 
 
/* =============================================================================
 *      SelfDrone_Yaw (external)
 *
 *  Changes the direction of the ship by the given angles, in radians.
 * ========================================================================== */
void SelfDrone_Yaw(
    TDroneObject            inDrone,
    float                   inYawAngle)
{
    TQ3Vector3D             v1;
    TQ3Vector3D             v2;
    
    assert(inDrone != NULL && IS_SELF_DRONE(inDrone));
    
    if (inYawAngle != 0.0)
    {
        Q3Vector3D_Scale(&inDrone->direction, cosf(inYawAngle), &v1);
        Q3Vector3D_Scale(&inDrone->cross,     sinf(inYawAngle), &v2);
        Q3Vector3D_Add(&v1, &v2, &inDrone->direction);
    }
}
 
 
/* =============================================================================
 *      SelfDrone_Roll (external)
 *
 *  Changes the direction of the ship by the given angles, in radians.
 * ========================================================================== */
void SelfDrone_Roll(
    TDroneObject            inDrone,
    float                   inRollAngle)
{
    TQ3Vector3D             v1;
    TQ3Vector3D             v2;
    
    assert(inDrone != NULL && IS_SELF_DRONE(inDrone));
    
    if (inRollAngle != 0.0)
    {
        Q3Vector3D_Scale(&inDrone->up,      cosf(inRollAngle), &v1);
        Q3Vector3D_Scale(&inDrone->cross,   sinf(inRollAngle), &v2);
        Q3Vector3D_Add(&v1, &v2, &inDrone->up);
    }
}
 
/*
{
    matrix = BuildRotationMatrix (angle, x, y, z)
    
    up = Multiply (up, matrix);
    
    normalize?
}
*/