示例#1
0
/**
  @brief 获取当前时间值
*/
void StatManager::endTime()
{
    struct timeval tv;
    dure_time_end = clkTime_end = getClockTime();
    gettimeofday(&tv,NULL);
    sysTime_end = tv.tv_sec;
}
示例#2
0
  /**
   * Constructor for the Themis Vis Camera Model
   *
   * @param lab Pvl label from an Odyssey Themis VIS image.
   *
   * @throws IException::User - The image does not appear to be a Themis
   *                                  VIS image
   * @internal
   *   @history 2010-08-04 Jeannie Walldren - Added NAIF error check.
   */
  ThemisVisCamera::ThemisVisCamera(Pvl &lab) : PushFrameCamera(lab) {
    NaifStatus::CheckErrors();
    // Set up the camera characteristics
    // LoadFrameMounting("M01_SPACECRAFT","M01_THEMIS_VIS");
    // Changed Focal Length from 203.9 (millimeters????) to 202.059, per request from
    // Christopher Edwards ([email protected]) at ASU, on 2/18/11.
    SetFocalLength(202.059);
    SetPixelPitch(0.009);

    PvlGroup &inst = lab.findGroup("Instrument", Pvl::Traverse);
    // make sure it is a themis vis image
    if(inst["InstrumentId"][0] != "THEMIS_VIS") {
      string msg = "The image does not appear to be a Themis Vis Image";
      throw IException(IException::User, msg, _FILEINFO_);
    }

    // Get necessary variables
    p_exposureDur = inst["ExposureDuration"];
    p_interframeDelay = inst["InterframeDelay"];
    int sumMode = inst["SpatialSumming"];

    // Get the start and end time
    double et;
    QString stime = inst["SpacecraftClockCount"];
    et = getClockTime(stime).Et();

    double offset = inst["SpacecraftClockOffset"];
    p_etStart = et + offset - ((p_exposureDur / 1000.0) / 2.0);
    p_nframes = inst["NumFramelets"];

    // Get the keywords from labels
    PvlGroup &bandBin = lab.findGroup("BandBin", Pvl::Traverse);
    PvlKeyword &orgBand = bandBin["OriginalBand"];
    for(int i = 0; i < orgBand.size(); i++) {
      p_originalBand.push_back(toInt(orgBand[i]));
    }

    // Setup detector map
    double frameRate = p_interframeDelay;
    PushFrameCameraDetectorMap *dmap =
      new PushFrameCameraDetectorMap(this, p_etStart, frameRate, 192);
    dmap->SetDetectorSampleSumming(sumMode);
    dmap->SetDetectorLineSumming(sumMode);

    // Setup focal plane map
    CameraFocalPlaneMap *focalMap = new CameraFocalPlaneMap(this, naifIkCode());
    focalMap->SetDetectorOrigin(512.5, 512.5);

    // Setup distortion map
    new ThemisVisDistortionMap(this);

    // Setup the ground and sky map
    bool evenFramelets = (inst["Framelets"][0] == "Even");
    new PushFrameCameraGroundMap(this, evenFramelets);
    new CameraSkyMap(this);

    LoadCache();
    NaifStatus::CheckErrors();
  }
示例#3
0
/**
  @brief 提取当前系统时间和硬件时钟时钟时间并保存,以秒为单位
*/
void StatManager::beginTime()
{
    struct timeval tv;
    dure_time_start = clkTime_start = getClockTime();
    gettimeofday(&tv,NULL);
    sysTime_start = tv.tv_sec;
    power_time_start = clkTime_start;
}
示例#4
0
文件: timer.C 项目: PierFio/ball
	void Timer::dump(ostream& s, Size depth) const
	{
		BALL_DUMP_STREAM_PREFIX(s);

		BALL_DUMP_DEPTH(s, depth);
		BALL_DUMP_CLASS_HEADER(s, Timer, this);

		BALL_DUMP_DEPTH(s, depth);
		s << "CPU speed: " << cpu_speed_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "is running: " << (is_running_ ? "true" : "false") << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "last clock seconds: " << last_secs_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "last user seconds: " << last_usecs_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "last user time: " << last_user_time_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "last system time: " << last_system_time_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "current clock seconds: " << current_secs_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "current user seconds: " << current_usecs_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "current user time: " << current_user_time_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "current system time: " << current_system_time_ << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "effective clock time: " << getClockTime() << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "effective user time: " << getUserTime() << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "effective system time: " << getSystemTime() << endl;

		BALL_DUMP_DEPTH(s, depth);
		s << "effective CPU time: " << getCPUTime() << endl;

		BALL_DUMP_STREAM_SUFFIX(s);
	}
示例#5
0
  /**
   * Creates a Hirise Camera Model
   *
   * @param lab Pvl label from the iamge
   * @internal 
   *   @history 2011-05-03 Jeannie Walldren - Added NAIF error check.
   */
  HiriseCamera::HiriseCamera(Cube &cube) : LineScanCamera(cube) {
    NaifStatus::CheckErrors();
    // Setup camera characteristics from instrument and frame kernel
    SetFocalLength();
    SetPixelPitch();
    //LoadFrameMounting("MRO_SPACECRAFT", "MRO_HIRISE_OPTICAL_AXIS");
    instrumentRotation()->SetFrame(-74690);

    // Get required keywords from instrument group
    Pvl &lab = *cube.label();
    PvlGroup &inst = lab.findGroup("Instrument", Pvl::Traverse);
    int tdiMode = inst["Tdi"];
    double binMode = inst["Summing"];
    int chan = inst["ChannelNumber"];
    int cpmm = inst["CpmmNumber"];
    double deltaLineTimerCount = inst["DeltaLineTimerCount"];
    QString stime = inst["SpacecraftClockStartCount"];

    // Convert CPMM number to CCD number
    static int cpmm2ccd[] = {0, 1, 2, 3, 12, 4, 10, 11, 5, 13, 6, 7, 8, 9};
    int ccd = cpmm2ccd[cpmm];

    // Compute the line rate, convert to seconds, and multiply by the
    // downtrack summing
    double unBinnedRate = (74.0 + (deltaLineTimerCount / 16.0)) / 1000000.0;
    double lineRate = unBinnedRate * binMode;

    // Convert the spacecraft clock count to ephemeris time
    SpiceDouble et;
    // The -74999 is the code to select the transformation from
    // high-precision MRO SCLK to ET
    et = getClockTime(stime, -74999).Et();

    // Adjust the start time so that it is the effective time for
    // the first line in the image file.  Note that on 2006-03-29, this
    // time is now subtracted as opposed to adding it.  The computed start
    // time in the EDR is at the first serial line.
    et -= unBinnedRate * (((double) tdiMode / 2.0) - 0.5);
    // Effective observation
    // time for all the TDI lines used for the
    // first line before doing binning
    et += unBinnedRate * (((double) binMode / 2.0) - 0.5);
    // Effective observation time of the first line
    // in the image file, which is possibly binned

    // Compute effective line number within the CCD (in pixels) for the
    // given TDI mode.
    //   This is the "centered" 0-based line number, where line 0 is the
    //   center of the detector array and line numbers decrease going
    //   towards the serial readout.  Line number +64 sees a spot
    //   on the ground before line number 0 or -64.
    double ccdLine_c = -64.0 + ((double) tdiMode / 2.0);

    // Setup detector map for transform of image pixels to detector position
    //      CameraDetectorMap *detectorMap =
    //        new LineScanCameraDetectorMap(this,et,lineRate);
    LineScanCameraDetectorMap *detectorMap =
      new LineScanCameraDetectorMap(this, et, lineRate);
    detectorMap->SetDetectorSampleSumming(binMode);
    detectorMap->SetDetectorLineSumming(binMode);
    if(chan == 0) {
      detectorMap->SetStartingDetectorSample(1025.0);
    }

    // Setup focal plane map for transform of detector position to
    // focal plane x/y.  This will read the appropriate CCD
    // transformation coefficients from the instrument kernel
    CameraFocalPlaneMap *focalMap =
      new CameraFocalPlaneMap(this, -74600 - ccd);
    focalMap->SetDetectorOrigin(1024.5, 0.0);
    focalMap->SetDetectorOffset(0.0, ccdLine_c);

    // Setup distortion map.  This will read the optical distortion
    // coefficients from the instrument kernel
    CameraDistortionMap *distortionMap = new CameraDistortionMap(this);
    distortionMap->SetDistortion(naifIkCode());

    // Setup the ground and sky map to transform undistorted focal
    // plane x/y to lat/lon or ra/dec respectively.
    new LineScanCameraGroundMap(this);
    new LineScanCameraSkyMap(this);

    LoadCache();
    NaifStatus::CheckErrors();
  }
示例#6
0
int main(int argc, char** argv) {
    (void) argc;
    (void) argv;
    /*srand(time(NULL));*/
#ifdef WIN32
    {
        LARGE_INTEGER divisor;
        QueryPerformanceFrequency(&divisor);
        clockDivisor = divisor.QuadPart;
    }
#else
    clockDivisor = CLOCKS_PER_SEC;
#endif
    printf("Starting\nClock divisor: %lu\n", clockDivisor);

    struct graphics g = {
#ifdef SHOWKEYS
        .width = 100, .height = 100,
#else
        .width = 1200,
        .height = 900,
#endif
    };
    initiateGraphics(&g, "Test window");

    if(!g.window) goto CLEANUP;
    /*thread t;*/
    /*newThread(test, NULL, &t);*/

    clockType frameStart, frameEnd, gameStart;
    getClockTime(&frameStart);

    initLogic();
    loadEntities();
    loadTileTextures();
    initializeWorld();
    initializeBullets();
    initializeItems();
    initializeItemFunctions();
    luaStart();

    getClockTime(&gameStart);

    while(1){
        cameraTick();
        renderGraphics(&g);

        oldMouseState = mouseState;
        mouseState = SDL_GetMouseState(&mouseX, &mouseY);
        if(oldMouseState != mouseState){
            mouseEvent();
        }
        for(SDL_Event event; SDL_PollEvent(&event);){
            switch(event.type){
            case SDL_QUIT:
                goto CLEANUP;
            case SDL_KEYDOWN:
            case SDL_KEYUP:
                if(event.key.keysym.scancode == 0x14) goto CLEANUP; //TODO
                keyEvent(event.key);
            break;
            }
        }

        getClockTime(&frameEnd);
        frameTime = getDiffClock(frameStart, frameEnd);
        appTime = getDiffClock(gameStart, frameEnd);
        frameStart = frameEnd;
        gameUpdate();
    }
CLEANUP:

    luaEnd();
    uninitializeItems();
    uninitializeItemFunctions();
    uninitializeBullets();
    uninitializeWorld();
    unloadTileTextures();
    unloadEntities();
    destroyGraphics(&g);

    return 0;
}

float normalRandomFloat(){
    return ((float) rand()) / ((float) RAND_MAX);
}

float randomFloat(float a, float b){
    float diff = b - a;
    return a + normalRandomFloat() * diff;
}
示例#7
0
  /**
   * Constructor for the LRO NAC Camera Model
   *
   * @param lab Pvl Label to create the camera model from
   *
   * @internal 
   *   @history 2011-05-03 Jeannie Walldren - Added NAIF error check.
   */
  LroNarrowAngleCamera::LroNarrowAngleCamera(Cube &cube) : LineScanCamera(cube) {
    NaifStatus::CheckErrors();
    // Set up the camera info from ik/iak kernels
    SetFocalLength();
    SetPixelPitch();

    double constantTimeOffset = 0.0,
           additionalPreroll = 0.0,
           additiveLineTimeError = 0.0,
           multiplicativeLineTimeError = 0.0;

    QString ikernKey = "INS" + toString(naifIkCode()) + "_CONSTANT_TIME_OFFSET";
    constantTimeOffset = getDouble(ikernKey);

    ikernKey = "INS" + toString(naifIkCode()) + "_ADDITIONAL_PREROLL";
    additionalPreroll = getDouble(ikernKey);

    ikernKey = "INS" + toString(naifIkCode()) + "_ADDITIVE_LINE_ERROR";
    additiveLineTimeError = getDouble(ikernKey);

    ikernKey = "INS" + toString(naifIkCode()) + "_MULTIPLI_LINE_ERROR";
    multiplicativeLineTimeError = getDouble(ikernKey);

    // Get the start time from labels
    Pvl &lab = *cube.label();
    PvlGroup &inst = lab.findGroup("Instrument", Pvl::Traverse);
    QString stime = inst["SpacecraftClockPrerollCount"];
    SpiceDouble etStart;

    if(stime != "NULL") {
      etStart = getClockTime(stime).Et();
    }
    else {
      etStart = iTime((QString)inst["PrerollTime"]).Et();
    }

    // Get other info from labels
    double csum = inst["SpatialSumming"];
    double lineRate = (double) inst["LineExposureDuration"] / 1000.0;
    double ss = inst["SampleFirstPixel"];
    ss += 1.0;

    lineRate *= 1.0 + multiplicativeLineTimeError;
    lineRate += additiveLineTimeError;
    etStart += additionalPreroll * lineRate;
    etStart += constantTimeOffset;

    setTime(etStart);

    // Setup detector map
    LineScanCameraDetectorMap *detectorMap = new LineScanCameraDetectorMap(this, etStart, lineRate);
    detectorMap->SetDetectorSampleSumming(csum);
    detectorMap->SetStartingDetectorSample(ss);

    // Setup focal plane map
    CameraFocalPlaneMap *focalMap = new CameraFocalPlaneMap(this, naifIkCode());

    //  Retrieve boresight location from instrument kernel (IK) (addendum?)
    ikernKey = "INS" + toString(naifIkCode()) + "_BORESIGHT_SAMPLE";
    double sampleBoreSight = getDouble(ikernKey);

    ikernKey = "INS" + toString(naifIkCode()) + "_BORESIGHT_LINE";
    double lineBoreSight = getDouble(ikernKey);

    focalMap->SetDetectorOrigin(sampleBoreSight, lineBoreSight);
    focalMap->SetDetectorOffset(0.0, 0.0);

    // Setup distortion map
    LroNarrowAngleDistortionMap *distMap = new LroNarrowAngleDistortionMap(this);
    distMap->SetDistortion(naifIkCode());

    // Setup the ground and sky map
    new LineScanCameraGroundMap(this);
    new LineScanCameraSkyMap(this);

    LoadCache();
    NaifStatus::CheckErrors();
  }
示例#8
0
  /**
   * @brief Initialize the MDIS camera model for NAC and WAC
   *
   * This constructor reads the Messenger/MDIS instrument addendum for many of
   * its default parameters.
   *
   * This camera model does not support subframes of jailbar imaging modes.
   * An exception is thrown in those cases.
   *
   * @param lab Pvl label from a Messenger MDIS image.
   *
   * @throws iException::User - "Subframe imaging mode is not supported"
   * @throws iException::User - "Jail bar observations are not supported"
   * @throws iException::User - "New MDIS/NAC distortion model invalidates
   *                 previous SPICE - you must rerun spiceinit to get new
   *                 kernels"
   * @internal
   *   @history 2011-05-03 Jeannie Walldren - Added NAIF error check.
   *
   */
  MdisCamera::MdisCamera(Pvl &lab) : FramingCamera(lab) {
    NaifStatus::CheckErrors();
    // Set up detector constants
    const int MdisWac(-236800);
    // const int MdisNac(-236820);

    PvlGroup &inst = lab.findGroup("Instrument", Pvl::Traverse);

    // Clarification on MDIS subframe image mode provides us the ability to
    // support this mode now.  The entire MDIS frame is geometrically valid
    // but only portions of the full frame actually contain image data.  The
    // portions outside subframes should be NULL and not interfere in
    // downstream processing, such as mosaics.
#if defined(MDIS_SUBFRAMES_UNSUPPORTED)
    int subFrameMode = inst["SubFrameMode"];
    if(subFrameMode != 0) {
      string msg = "Subframe imaging mode is not supported!";
      throw iException::Message(iException::User, msg, _FILEINFO_);
    }
#endif

    //  According to the MDIS team, this is nothing to be concerned with and
    //  should be treated as other normal observations.  So the test to
    // disallow it has been effectively removed 2007-09-05 (KJB).
#if defined(MDIS_JAILBARS_UNSUPPORTED)
    int jailBars = inst["JailBars"];
    if(jailBars != 0) {
      string msg = "Jail bar observations are not currently supported!";
      throw iException::Message(iException::Programmer, msg, _FILEINFO_);
    }
#endif

    //  Determine filter number.  Only conditional code required for
    //  NAC and WAC support!
    int filterNumber(0);    //  Default appropriate for MDIS-NAC
    if(naifIkCode() == MdisWac) {
      PvlGroup &bandBin = lab.findGroup("BandBin", Pvl::Traverse);
      filterNumber = bandBin["Number"];
    }

    //  Set up instrument and filter code strings
    QString ikCode = toString(naifIkCode());
    int fnCode(naifIkCode() - filterNumber);
    QString filterCode = toString(fnCode);
    QString ikernKey;

    // Fetch the frame translations from the instrument kernels
    ikernKey = "INS" + ikCode + "_REFERENCE_FRAME";
    QString baseFrame = getString(ikernKey);

    ikernKey = "INS" + filterCode + "_FRAME";
    QString ikFrame = getString(ikernKey);

    // Set up the camera info from ik/iak kernels

    //  Turns out (2008-01-17) the WAC has different focal lengths for
    // each filter.  Added to the instrument kernel (IAK) on this date.
    //  Add temperature dependant focal length
    SetFocalLength(computeFocalLength(filterCode, lab));

    SetPixelPitch();

    // Removed by Jeff Anderson.  The refactor of the SPICE class
    // uses frames always so this is no longer needed
    //      LoadFrameMounting(baseFrame, ikFrame, false);

    // Get the start time from labels as the starting image time plus half
    // the exposure duration (in <MS>) to get pointing attitude.
    //  !!NOTE:  The ephemeris time MUST be set prior to creating the
    //           cache (CreateCache) because the kernels are all unloaded
    //           after the cache is done and this operation will fail!!
    QString stime = inst["SpacecraftClockCount"];
    double exposureDuration = ((double) inst["ExposureDuration"]) / 1000.0;// divide by 1000 to convert to seconds

    iTime etStart = getClockTime(stime);

    //  Setup camera detector map
    CameraDetectorMap *detMap = new CameraDetectorMap(this);

    // Setup focal plane map, and detector origin for the instrument that
    // may have a filter (WAC only!).
    CameraFocalPlaneMap *focalMap = new CameraFocalPlaneMap(this, fnCode);

    //  Retrieve boresight location from instrument kernel (IK) (addendum?)
    ikernKey = "INS" + ikCode + "_BORESIGHT_SAMPLE";
    double sampleBoreSight = getDouble(ikernKey);

    ikernKey = "INS" + ikCode + "_BORESIGHT_LINE";
    double lineBoreSight = getDouble(ikernKey);

    //  Apply the boresight
    focalMap->SetDetectorOrigin(sampleBoreSight, lineBoreSight);

    // Determine summing.  MDIS has two sources of summing or binning.
    // One is performed in the FPU and the in the MP, post-observation,
    // on-board after coming out of the FPGAs, where the FPU binning is
    // performed.  The FPU binning was programmed incorrectly and the
    // actual pixels from the detector are peculiar.  Hence, I have
    // designed this camera model such that the offsets can be managed
    // external to the code.  See the MDIS instrument kernel addendum
    // in $ISIS3DATA/messenger/kernels/iak/mdisAddendum???.ti for the
    // offsets for *each* detector.  Note that an offset is only applied
    // when FPU binning is performed.
    int fpuBinMode   = inst["FpuBinningMode"];
    int pixelBinMode = inst["PixelBinningMode"];

    int summing = ((pixelBinMode == 0) ? 1 : pixelBinMode);
    //  FPU binning was performed, retrieve the FPU binning offsets and
    //  apply them to the focal plane mapping.
    if(fpuBinMode == 1) {
#if defined(USE_FPU_BINNING_OFFSETS)
      ikernKey = "INS" + ikCode + "_FPUBIN_START_SAMPLE";
      double fpuStartingSample = getDouble(ikernKey);
      detMap->SetStartingDetectorSample(fpuStartingSample);

      ikernKey = "INS" + ikCode + "_FPUBIN_START_LINE";
      double fpuStartingLine = getDouble(ikernKey);
      detMap->SetStartingDetectorLine(fpuStartingLine);
#endif
      summing *= 2;
    }

    //  Set summing/binning modes as an accumulation of FPU and MP binning.
    detMap->SetDetectorLineSumming(summing);
    detMap->SetDetectorSampleSumming(summing);

    // Setup distortion map.  As of 2007/12/06, we now have an actual model.
    // Note that this model supports distinct distortion for each WAC filter.
    // See $ISIS3DATA/messenger/kernels/iak/mdisAddendumXXX.ti or possibly
    // $ISIS3DATA/messenger/kernels/ik/msgr_mdis_vXXX.ti for the *_OD_K
    // parameters.
    // NAC has a new implementation of its distortion contributed by
    // Scott Turner and Lillian Nguyen at JHUAPL.
    // (2010/10/06) The WAC now uses the same disortion model implementation.
    // Valid Taylor Series parameters are in versions msgr_mdis_v120.ti IK
    // and above.   Note fnCode works for NAC as well as long as
    // filterNumber stays at 0 for the NAC only!
    try {
      TaylorCameraDistortionMap *distortionMap = new TaylorCameraDistortionMap(this);
      distortionMap->SetDistortion(fnCode);
    }
    catch(IException &ie) {
      string msg = "New MDIS NAC/WAC distortion models will invalidate previous "
                   "SPICE - you may need to rerun spiceinit to get new kernels";
      throw IException(ie, IException::User, msg, _FILEINFO_);
    }

    // Setup the ground and sky map
    new CameraGroundMap(this);
    new CameraSkyMap(this);

    // Create a cache and grab spice info since it does not change for
    // a framing camera (fixed spacecraft position and pointing) after,
    // of course applying the gimble offset which is handled in the SPICE
    // kernels (thank you!).  Note this was done automagically in the
    // SetEpheremisTime call above.  IMPORTANT that it be done prior to
    // creating the cache since all kernels are unloaded, essentially
    // clearing the pool and whacking the frames definitions, required to
    iTime centerTime = etStart + (exposureDuration / 2.0);
    setTime(centerTime);
    LoadCache();
    NaifStatus::CheckErrors();
  }
示例#9
0
void update_ui_snow( double time_step, bool windy )
{
    pp::Vec2d *v, f;
    pp::Vec2d *pt;
    double size;
    double dist_from_push, p_dist;
    pp::Vec2d push_vector;
    int i;
    double push_timestep, time;

    time = getClockTime();

    push_vector.x = 0;
    push_vector.y = 0;
    push_timestep = 0;
	
    if ( push_position_initialized ) {
	push_vector.x = push_position.x - last_push_position.x;
	push_vector.y = push_position.y - last_push_position.y;
	push_timestep = time - last_update_time;
    }
    last_push_position = push_position;
    last_update_time = time;

    for ( i=0; i<num_particles; i++) {
	pt = &particles[i].pt;
	v = &particles[i].vel;
	size = particles[i].size;

	f.x = 0;
	f.y = 0;

	/* Mouse push and gravity */
	dist_from_push = (pow((pt->x - push_position.x), 2) +
			  pow((pt->y - push_position.y), 2));
	if ( push_timestep > 0 ) {
	    f.x = PUSH_FACTOR * push_vector.x / push_timestep; 
	    
	    f.y = PUSH_FACTOR * push_vector.y / push_timestep; 

	    f.x = min(MAX_PUSH_FORCE,f.x);
	    f.x = max(-MAX_PUSH_FORCE,f.x);
	    f.y = min(MAX_PUSH_FORCE,f.y);
	    f.y = max(-MAX_PUSH_FORCE,f.y);

	    f.x *= 1.0/(PUSH_DIST_DECAY*dist_from_push + 1) * 
		size/PARTICLE_SIZE_RANGE;
	    f.y *= 1.0/(PUSH_DIST_DECAY*dist_from_push + 1) *
		size/PARTICLE_SIZE_RANGE;
	}

	/* Update velocity */
	v->x += ( f.x + ( windy ? WIND_FORCE : 0.0 ) - v->x * AIR_DRAG ) * 
	    time_step;
	v->y += ( f.y - GRAVITY_FACTOR - v->y * AIR_DRAG ) * 
	    time_step;

	/* Update position */
        pt->x += v->x * time_step * ( size / PARTICLE_SIZE_RANGE ); 
        pt->y += v->y * time_step * ( size / PARTICLE_SIZE_RANGE );

	if ( pt->x < 0 ) {
	    pt->x = 1;
	} else if ( pt->x > 1 ) {
	    pt->x = 0.0;
	}
    }

    /* Kill off & regenerate particles */
    for (i=0; i<num_particles; i++) {
	particle_t *p = &particles[i];

	if (p->pt.y < -0.05) {
	    /* If we have an excess of particles, kill off with
	       50% probability */
	    if ( num_particles > BASE_NUM_PARTICLES && frand() > 0.5 ) {
		/* Delete the particle */
		*p = particles[num_particles-1];
		num_particles -= 1;
	    } else {
		p->pt.x = frand();
		p->pt.y = 1+frand()*BASE_VELOCITY;
		p_dist = frand();
		p->size = PARTICLE_MIN_SIZE + 
		    ( 1.0 - p_dist ) * PARTICLE_SIZE_RANGE;
		p->vel.x = 0;
		p->vel.y = -BASE_VELOCITY-p_dist*VELOCITY_RANGE;
	    }
	}
    }

    if ( time_step < PUSH_DECAY_TIME_CONSTANT ) {
	push_vector.x *= 1.0 - time_step/PUSH_DECAY_TIME_CONSTANT;
	push_vector.y *= 1.0 - time_step/PUSH_DECAY_TIME_CONSTANT;
    } else {
	push_vector.x = 0.0;
	push_vector.y = 0.0;
    }
}