Beispiel #1
0
  // This function:
  // - is called once for each motion event
  // - does motion event interpolation
  // - paints zero, one or several dabs
  // - decides whether the stroke is finished (for undo/redo)
  // returns true if the stroke is finished or empty
  bool stroke_to (Surface * surface, float x, float y, float pressure, float xtilt, float ytilt, double dtime)
  {
    //printf("%f %f %f %f\n", (double)dtime, (double)x, (double)y, (double)pressure);

    float tilt_ascension = 0.0;
    float tilt_declination = 90.0;
    if (xtilt != 0 || ytilt != 0) {
      // shield us from insane tilt input
      xtilt = CLAMP(xtilt, -1.0, 1.0);
      ytilt = CLAMP(ytilt, -1.0, 1.0);
      assert(std::isfinite(xtilt) && std::isfinite(ytilt));

      tilt_ascension = 180.0*atan2(-xtilt, ytilt)/M_PI;
      float e;
      if (abs(xtilt) > abs(ytilt)) {
        e = sqrt(1+ytilt*ytilt);
      } else {
        e = sqrt(1+xtilt*xtilt);
      }
      float rad = hypot(xtilt, ytilt);
      float cos_alpha = rad/e;
      if (cos_alpha >= 1.0) cos_alpha = 1.0; // fix numerical inaccuracy
      tilt_declination = 180.0*acos(cos_alpha)/M_PI;

      assert(std::isfinite(tilt_ascension));
      assert(std::isfinite(tilt_declination));
    }

    // printf("xtilt %f, ytilt %f\n", (double)xtilt, (double)ytilt);
    // printf("ascension %f, declination %f\n", (double)tilt_ascension, (double)tilt_declination);
      
    pressure = CLAMP(pressure, 0.0, 1.0);
    if (!std::isfinite(x) || !std::isfinite(y) ||
        (x > 1e10 || y > 1e10 || x < -1e10 || y < -1e10)) {
      // workaround attempt for https://gna.org/bugs/?14372
      g_print("Warning: ignoring brush::stroke_to with insane inputs (x = %f, y = %f)\n", (double)x, (double)y);
      x = 0.0;
      y = 0.0;
      pressure = 0.0;
    }
    // the assertion below is better than out-of-memory later at save time
    assert(x < 1e8 && y < 1e8 && x > -1e8 && y > -1e8);

    if (dtime < 0) g_print("Time jumped backwards by dtime=%f seconds!\n", dtime);
    if (dtime <= 0) dtime = 0.0001; // protect against possible division by zero bugs
    
    if (dtime > 0.100 && pressure && states[STATE_PRESSURE] == 0) {
      // Workaround for tablets that don't report motion events without pressure.
      // This is to avoid linear interpolation of the pressure between two events.
      stroke_to (surface, x, y, 0.0, 90.0, 0.0, dtime-0.0001);
      dtime = 0.0001;
    }

    g_rand_set_seed (rng, states[STATE_RNG_SEED]);

    { // calculate the actual "virtual" cursor position

      // noise first
      if (settings[BRUSH_TRACKING_NOISE]->base_value) {
        // OPTIMIZE: expf() called too often
        float base_radius = expf(settings[BRUSH_RADIUS_LOGARITHMIC]->base_value);

        x += rand_gauss (rng) * settings[BRUSH_TRACKING_NOISE]->base_value * base_radius;
        y += rand_gauss (rng) * settings[BRUSH_TRACKING_NOISE]->base_value * base_radius;
      }

      float fac = 1.0 - exp_decay (settings[BRUSH_SLOW_TRACKING]->base_value, 100.0*dtime);
      x = states[STATE_X] + (x - states[STATE_X]) * fac;
      y = states[STATE_Y] + (y - states[STATE_Y]) * fac;
    }

    // draw many (or zero) dabs to the next position

    // see doc/stroke2dabs.png
    float dist_moved = states[STATE_DIST];
    float dist_todo = count_dabs_to (x, y, pressure, dtime);

    //if (dtime > 5 || dist_todo > 300) {
    if (dtime > 5 || reset_requested) {
      reset_requested = false;

      /*
        TODO:
        if (dist_todo > 300) {
        // this happens quite often, eg when moving the cursor back into the window
        // FIXME: bad to hardcode a distance treshold here - might look at zoomed image
        //        better detect leaving/entering the window and reset then.
        g_print ("Warning: NOT drawing %f dabs.\n", dist_todo);
        g_print ("dtime=%f, dx=%f\n", dtime, x-states[STATE_X]);
        //must_reset = 1;
        }
      */

      //printf("Brush reset.\n");
      for (int i=0; i<STATE_COUNT; i++) {
        states[i] = 0;
      }

      states[STATE_X] = x;
      states[STATE_Y] = y;
      states[STATE_PRESSURE] = pressure;

      // not resetting, because they will get overwritten below:
      //dx, dy, dpress, dtime

      states[STATE_ACTUAL_X] = states[STATE_X];
      states[STATE_ACTUAL_Y] = states[STATE_Y];
      states[STATE_STROKE] = 1.0; // start in a state as if the stroke was long finished

      return true;
    }

    //g_print("dist = %f\n", states[STATE_DIST]);
    enum { UNKNOWN, YES, NO } painted = UNKNOWN;
    double dtime_left = dtime;

    float step_dx, step_dy, step_dpressure, step_dtime;
    float step_declination, step_ascension;
    while (dist_moved + dist_todo >= 1.0) { // there are dabs pending
      { // linear interpolation (nonlinear variant was too slow, see SVN log)
        float frac; // fraction of the remaining distance to move
        if (dist_moved > 0) {
          // "move" the brush exactly to the first dab (moving less than one dab)
          frac = (1.0 - dist_moved) / dist_todo;
          dist_moved = 0;
        } else {
          // "move" the brush from one dab to the next
          frac = 1.0 / dist_todo;
        }
        step_dx        = frac * (x - states[STATE_X]);
        step_dy        = frac * (y - states[STATE_Y]);
        step_dpressure = frac * (pressure - states[STATE_PRESSURE]);
        step_dtime     = frac * (dtime_left - 0.0);
        step_declination = frac * (tilt_declination - states[STATE_DECLINATION]);
        step_ascension   = frac * (tilt_ascension - states[STATE_ASCENSION]);
        // Though it looks different, time is interpolated exactly like x/y/pressure.
      }
    
      update_states_and_setting_values (step_dx, step_dy, step_dpressure, step_declination, step_ascension, step_dtime);
      bool painted_now = prepare_and_draw_dab (surface);
      if (painted_now) {
        painted = YES;
      } else if (painted == UNKNOWN) {
        painted = NO;
      }

      dtime_left   -= step_dtime;
      dist_todo  = count_dabs_to (x, y, pressure, dtime_left);
    }

    {
      // "move" the brush to the current time (no more dab will happen)
      // Important to do this at least once every event, because
      // brush_count_dabs_to depends on the radius and the radius can
      // depend on something that changes much faster than only every
      // dab (eg speed).
    
      step_dx        = x - states[STATE_X];
      step_dy        = y - states[STATE_Y];
      step_dpressure = pressure - states[STATE_PRESSURE];
      step_declination = tilt_declination - states[STATE_DECLINATION];
      step_ascension = tilt_ascension - states[STATE_ASCENSION];
      step_dtime     = dtime_left;
    
      //dtime_left = 0; but that value is not used any more

      update_states_and_setting_values (step_dx, step_dy, step_dpressure, step_declination, step_ascension, step_dtime);
    }

    // save the fraction of a dab that is already done now
    states[STATE_DIST] = dist_moved + dist_todo;
    //g_print("dist_final = %f\n", states[STATE_DIST]);

    // next seed for the RNG (GRand has no get_state() and states[] must always contain our full state)
    states[STATE_RNG_SEED] = g_rand_int(rng);

    // stroke separation logic (for undo/redo)

    if (painted == UNKNOWN) {
      if (stroke_current_idling_time > 0 || stroke_total_painting_time == 0) {
        // still idling
        painted = NO;
      } else {
        // probably still painting (we get more events than brushdabs)
        painted = YES;
        //if (pressure == 0) g_print ("info: assuming 'still painting' while there is no pressure\n");
      }
    }
    if (painted == YES) {
      //if (stroke_current_idling_time > 0) g_print ("idling ==> painting\n");
      stroke_total_painting_time += dtime;
      stroke_current_idling_time = 0;
      // force a stroke split after some time
      if (stroke_total_painting_time > 4 + 3*pressure) {
        // but only if pressure is not being released
        // FIXME: use some smoothed state for dpressure, not the output of the interpolation code
        //        (which might easily wrongly give dpressure == 0)
        if (step_dpressure >= 0) {
          return true;
        }
      }
    } else if (painted == NO) {
      //if (stroke_current_idling_time == 0) g_print ("painting ==> idling\n");
      stroke_current_idling_time += dtime;
      if (stroke_total_painting_time == 0) {
        // not yet painted, start a new stroke if we have accumulated a lot of irrelevant motion events
        if (stroke_current_idling_time > 1.0) {
          return true;
        }
      } else {
        // Usually we have pressure==0 here. But some brushes can paint
        // nothing at full pressure (eg gappy lines, or a stroke that
        // fades out). In either case this is the prefered moment to split.
        if (stroke_total_painting_time+stroke_current_idling_time > 0.9 + 5*pressure) {
          return true;
        }
      }
    }
    return false;
  }
Beispiel #2
0
  /** 
   * mypaint_brush_stroke_to:
   * @dtime: Time since last motion event, in seconds.
   *
   * Should be called once for each motion event.
   *
   * Returns: non-0 if the stroke is finished or empty, else 0.
   */
  int mypaint_brush_stroke_to (MyPaintBrush *self, MyPaintSurface *surface,
                                float x, float y, float pressure,
                                float xtilt, float ytilt, double dtime)
  {
    //printf("%f %f %f %f\n", (double)dtime, (double)x, (double)y, (double)pressure);

    float tilt_ascension = 0.0;
    float tilt_declination = 90.0;
    if (xtilt != 0 || ytilt != 0) {
      // shield us from insane tilt input
      xtilt = CLAMP(xtilt, -1.0, 1.0);
      ytilt = CLAMP(ytilt, -1.0, 1.0);
      assert(isfinite(xtilt) && isfinite(ytilt));

      tilt_ascension = 180.0*atan2(-xtilt, ytilt)/M_PI;
      const float rad = hypot(xtilt, ytilt);
      tilt_declination = 90-(rad*60);

      assert(isfinite(tilt_ascension));
      assert(isfinite(tilt_declination));
    }

    // printf("xtilt %f, ytilt %f\n", (double)xtilt, (double)ytilt);
    // printf("ascension %f, declination %f\n", (double)tilt_ascension, (double)tilt_declination);

    if (pressure <= 0.0) pressure = 0.0;
    if (!isfinite(x) || !isfinite(y) ||
        (x > 1e10 || y > 1e10 || x < -1e10 || y < -1e10)) {
      // workaround attempt for https://gna.org/bugs/?14372
      printf("Warning: ignoring brush::stroke_to with insane inputs (x = %f, y = %f)\n", (double)x, (double)y);
      x = 0.0;
      y = 0.0;
      pressure = 0.0;
    }
    // the assertion below is better than out-of-memory later at save time
    assert(x < 1e8 && y < 1e8 && x > -1e8 && y > -1e8);

    if (dtime < 0) printf("Time jumped backwards by dtime=%f seconds!\n", dtime);
    if (dtime <= 0) dtime = 0.0001; // protect against possible division by zero bugs

    /* way too slow with the new rng, and not working any more anyway...
    rng_double_set_seed (self->rng, self->states[MYPAINT_BRUSH_STATE_RNG_SEED]*0x40000000);
    */

    if (dtime > 0.100 && pressure && self->states[MYPAINT_BRUSH_STATE_PRESSURE] == 0) {
      // Workaround for tablets that don't report motion events without pressure.
      // This is to avoid linear interpolation of the pressure between two events.
      mypaint_brush_stroke_to (self, surface, x, y, 0.0, 90.0, 0.0, dtime-0.0001);
      dtime = 0.0001;
    }

    { // calculate the actual "virtual" cursor position

      // noise first
      if (mypaint_mapping_get_base_value(self->settings[MYPAINT_BRUSH_SETTING_TRACKING_NOISE])) {
        // OPTIMIZE: expf() called too often
        const float base_radius = expf(mypaint_mapping_get_base_value(self->settings[MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC]));

        x += rand_gauss (self->rng) * mypaint_mapping_get_base_value(self->settings[MYPAINT_BRUSH_SETTING_TRACKING_NOISE]) * base_radius;
        y += rand_gauss (self->rng) * mypaint_mapping_get_base_value(self->settings[MYPAINT_BRUSH_SETTING_TRACKING_NOISE]) * base_radius;
      }

      const float fac = 1.0 - exp_decay (mypaint_mapping_get_base_value(self->settings[MYPAINT_BRUSH_SETTING_SLOW_TRACKING]), 100.0*dtime);
      x = self->states[MYPAINT_BRUSH_STATE_X] + (x - self->states[MYPAINT_BRUSH_STATE_X]) * fac;
      y = self->states[MYPAINT_BRUSH_STATE_Y] + (y - self->states[MYPAINT_BRUSH_STATE_Y]) * fac;
    }

    // draw many (or zero) dabs to the next position

    // see doc/images/stroke2dabs.png
    float dabs_moved = self->states[MYPAINT_BRUSH_STATE_PARTIAL_DABS];
    float dabs_todo = count_dabs_to (self, x, y, pressure, dtime);

    if (dtime > 5 || self->reset_requested) {
      self->reset_requested = FALSE;

      //printf("Brush reset.\n");
      int i=0;
      for (i=0; i<MYPAINT_BRUSH_STATES_COUNT; i++) {
        self->states[i] = 0;
      }

      self->states[MYPAINT_BRUSH_STATE_X] = x;
      self->states[MYPAINT_BRUSH_STATE_Y] = y;
      self->states[MYPAINT_BRUSH_STATE_PRESSURE] = pressure;

      // not resetting, because they will get overwritten below:
      //dx, dy, dpress, dtime

      self->states[MYPAINT_BRUSH_STATE_ACTUAL_X] = self->states[MYPAINT_BRUSH_STATE_X];
      self->states[MYPAINT_BRUSH_STATE_ACTUAL_Y] = self->states[MYPAINT_BRUSH_STATE_Y];
      self->states[MYPAINT_BRUSH_STATE_STROKE] = 1.0; // start in a state as if the stroke was long finished

      return TRUE;
    }

    enum { UNKNOWN, YES, NO } painted = UNKNOWN;
    double dtime_left = dtime;

    float step_ddab, step_dx, step_dy, step_dpressure, step_dtime;
    float step_declination, step_ascension;
    while (dabs_moved + dabs_todo >= 1.0) { // there are dabs pending
      { // linear interpolation (nonlinear variant was too slow, see SVN log)
        float frac; // fraction of the remaining distance to move
        if (dabs_moved > 0) {
          // "move" the brush exactly to the first dab
          step_ddab = 1.0 - dabs_moved; // the step "moves" the brush by a fraction of one dab
          dabs_moved = 0;
        } else {
          step_ddab = 1.0; // the step "moves" the brush by exactly one dab
        }
        frac = step_ddab / dabs_todo;
        step_dx        = frac * (x - self->states[MYPAINT_BRUSH_STATE_X]);
        step_dy        = frac * (y - self->states[MYPAINT_BRUSH_STATE_Y]);
        step_dpressure = frac * (pressure - self->states[MYPAINT_BRUSH_STATE_PRESSURE]);
        step_dtime     = frac * (dtime_left - 0.0);
        // Though it looks different, time is interpolated exactly like x/y/pressure.
        step_declination = frac * (tilt_declination - self->states[MYPAINT_BRUSH_STATE_DECLINATION]);
        step_ascension   = frac * smallest_angular_difference(self->states[MYPAINT_BRUSH_STATE_ASCENSION], tilt_ascension);
      }

      update_states_and_setting_values (self, step_ddab, step_dx, step_dy, step_dpressure, step_declination, step_ascension, step_dtime);
      gboolean painted_now = prepare_and_draw_dab (self, surface);
      if (painted_now) {
        painted = YES;
      } else if (painted == UNKNOWN) {
        painted = NO;
      }

      dtime_left   -= step_dtime;
      dabs_todo  = count_dabs_to (self, x, y, pressure, dtime_left);
    }

    {
      // "move" the brush to the current time (no more dab will happen)
      // Important to do this at least once every event, because
      // brush_count_dabs_to depends on the radius and the radius can
      // depend on something that changes much faster than just every
      // dab.

      step_ddab = dabs_todo; // the step "moves" the brush by a fraction of one dab
      step_dx        = x - self->states[MYPAINT_BRUSH_STATE_X];
      step_dy        = y - self->states[MYPAINT_BRUSH_STATE_Y];
      step_dpressure = pressure - self->states[MYPAINT_BRUSH_STATE_PRESSURE];
      step_declination = tilt_declination - self->states[MYPAINT_BRUSH_STATE_DECLINATION];
      step_ascension = smallest_angular_difference(self->states[MYPAINT_BRUSH_STATE_ASCENSION], tilt_ascension);
      step_dtime     = dtime_left;

      //dtime_left = 0; but that value is not used any more

      update_states_and_setting_values (self, step_ddab, step_dx, step_dy, step_dpressure, step_declination, step_ascension, step_dtime);
    }

    // save the fraction of a dab that is already done now
    self->states[MYPAINT_BRUSH_STATE_PARTIAL_DABS] = dabs_moved + dabs_todo;

    /* not working any more with the new rng...
    // next seed for the RNG (GRand has no get_state() and states[] must always contain our full state)
    self->states[MYPAINT_BRUSH_STATE_RNG_SEED] = rng_double_next(self->rng);
    */

    // stroke separation logic (for undo/redo)

    if (painted == UNKNOWN) {
      if (self->stroke_current_idling_time > 0 || self->stroke_total_painting_time == 0) {
        // still idling
        painted = NO;
      } else {
        // probably still painting (we get more events than brushdabs)
        painted = YES;
        //if (pressure == 0) g_print ("info: assuming 'still painting' while there is no pressure\n");
      }
    }
    if (painted == YES) {
      //if (stroke_current_idling_time > 0) g_print ("idling ==> painting\n");
      self->stroke_total_painting_time += dtime;
      self->stroke_current_idling_time = 0;
      // force a stroke split after some time
      if (self->stroke_total_painting_time > 4 + 3*pressure) {
        // but only if pressure is not being released
        // FIXME: use some smoothed state for dpressure, not the output of the interpolation code
        //        (which might easily wrongly give dpressure == 0)
        if (step_dpressure >= 0) {
          return TRUE;
        }
      }
    } else if (painted == NO) {
      //if (stroke_current_idling_time == 0) g_print ("painting ==> idling\n");
      self->stroke_current_idling_time += dtime;
      if (self->stroke_total_painting_time == 0) {
        // not yet painted, start a new stroke if we have accumulated a lot of irrelevant motion events
        if (self->stroke_current_idling_time > 1.0) {
          return TRUE;
        }
      } else {
        // Usually we have pressure==0 here. But some brushes can paint
        // nothing at full pressure (eg gappy lines, or a stroke that
        // fades out). In either case this is the prefered moment to split.
        if (self->stroke_total_painting_time+self->stroke_current_idling_time > 0.9 + 5*pressure) {
          return TRUE;
        }
      }
    }
    return FALSE;
  }
Beispiel #3
0
  // This function runs a brush "simulation" step. Usually it is
  // called once or twice per dab. In theory the precision of the
  // "simulation" gets better when it is called more often. In
  // practice this only matters if there are some highly nonlinear
  // mappings in critical places or extremely few events per second.
  //
  // note: parameters are is dx/ddab, ..., dtime/ddab (dab is the number, 5.0 = 5th dab)
  void update_states_and_setting_values (float step_dx, float step_dy, float step_dpressure, float step_declination, float step_ascension, float step_dtime)
  {
    float pressure;
    float inputs[INPUT_COUNT];

    if (step_dtime < 0.0) {
      printf("Time is running backwards!\n");
      step_dtime = 0.001;
    } else if (step_dtime == 0.0) {
      // FIXME: happens about every 10th start, workaround (against division by zero)
      step_dtime = 0.001;
    }

    states[STATE_X]        += step_dx;
    states[STATE_Y]        += step_dy;
    states[STATE_PRESSURE] += step_dpressure;

    states[STATE_DECLINATION] += step_declination;
    states[STATE_ASCENSION] += step_ascension;

    float base_radius = expf(settings[BRUSH_RADIUS_LOGARITHMIC]->base_value);

    // FIXME: does happen (interpolation problem?)
    states[STATE_PRESSURE] = CLAMP(states[STATE_PRESSURE], 0.0, 1.0);
    pressure = states[STATE_PRESSURE];

    { // start / end stroke (for "stroke" input only)
      if (!states[STATE_STROKE_STARTED]) {
        if (pressure > settings[BRUSH_STROKE_THRESHOLD]->base_value + 0.0001) {
          // start new stroke
          //printf("stroke start %f\n", pressure);
          states[STATE_STROKE_STARTED] = 1;
          states[STATE_STROKE] = 0.0;
        }
      } else {
        if (pressure <= settings[BRUSH_STROKE_THRESHOLD]->base_value * 0.9 + 0.0001) {
          // end stroke
          //printf("stroke end\n");
          states[STATE_STROKE_STARTED] = 0;
        }
      }
    }

    // now follows input handling

    float norm_dx, norm_dy, norm_dist, norm_speed;
    norm_dx = step_dx / step_dtime / base_radius;
    norm_dy = step_dy / step_dtime / base_radius;
    norm_speed = sqrt(SQR(norm_dx) + SQR(norm_dy));
    norm_dist = norm_speed * step_dtime;

    inputs[INPUT_PRESSURE] = pressure;
    inputs[INPUT_SPEED1] = log(speed_mapping_gamma[0] + states[STATE_NORM_SPEED1_SLOW])*speed_mapping_m[0] + speed_mapping_q[0];
    inputs[INPUT_SPEED2] = log(speed_mapping_gamma[1] + states[STATE_NORM_SPEED2_SLOW])*speed_mapping_m[1] + speed_mapping_q[1];
    inputs[INPUT_RANDOM] = g_rand_double (rng);
    inputs[INPUT_STROKE] = MIN(states[STATE_STROKE], 1.0);
    inputs[INPUT_DIRECTION] = fmodf (atan2f (states[STATE_DIRECTION_DY], states[STATE_DIRECTION_DX])/(2*M_PI)*360 + 180.0, 180.0);
    inputs[INPUT_TILT_DECLINATION] = states[STATE_DECLINATION];
    inputs[INPUT_TILT_ASCENSION] = states[STATE_ASCENSION];
    inputs[INPUT_CUSTOM] = states[STATE_CUSTOM_INPUT];
    if (print_inputs) {
      g_print("press=% 4.3f, speed1=% 4.4f\tspeed2=% 4.4f\tstroke=% 4.3f\tcustom=% 4.3f\n", (double)inputs[INPUT_PRESSURE], (double)inputs[INPUT_SPEED1], (double)inputs[INPUT_SPEED2], (double)inputs[INPUT_STROKE], (double)inputs[INPUT_CUSTOM]);
    }
    // FIXME: this one fails!!!
    //assert(inputs[INPUT_SPEED1] >= 0.0 && inputs[INPUT_SPEED1] < 1e8); // checking for inf

    for (int i=0; i<BRUSH_SETTINGS_COUNT; i++) {
      settings_value[i] = settings[i]->calculate (inputs);
    }

    {
      float fac = 1.0 - exp_decay (settings_value[BRUSH_SLOW_TRACKING_PER_DAB], 1.0);
      states[STATE_ACTUAL_X] += (states[STATE_X] - states[STATE_ACTUAL_X]) * fac; // FIXME: should this depend on base radius?
      states[STATE_ACTUAL_Y] += (states[STATE_Y] - states[STATE_ACTUAL_Y]) * fac;
    }

    { // slow speed
      float fac;
      fac = 1.0 - exp_decay (settings_value[BRUSH_SPEED1_SLOWNESS], step_dtime);
      states[STATE_NORM_SPEED1_SLOW] += (norm_speed - states[STATE_NORM_SPEED1_SLOW]) * fac;
      fac = 1.0 - exp_decay (settings_value[BRUSH_SPEED2_SLOWNESS], step_dtime);
      states[STATE_NORM_SPEED2_SLOW] += (norm_speed - states[STATE_NORM_SPEED2_SLOW]) * fac;
    }
  
    { // slow speed, but as vector this time

      // FIXME: offset_by_speed should be removed.
      //   Is it broken, non-smooth, system-dependent math?!
      //   A replacement could be a directed random offset.

      float time_constant = exp(settings_value[BRUSH_OFFSET_BY_SPEED_SLOWNESS]*0.01)-1.0;
      // Workaround for a bug that happens mainly on Windows, causing
      // individual dabs to be placed far far away. Using the speed
      // with zero filtering is just asking for trouble anyway.
      if (time_constant < 0.002) time_constant = 0.002;
      float fac = 1.0 - exp_decay (time_constant, step_dtime);
      states[STATE_NORM_DX_SLOW] += (norm_dx - states[STATE_NORM_DX_SLOW]) * fac;
      states[STATE_NORM_DY_SLOW] += (norm_dy - states[STATE_NORM_DY_SLOW]) * fac;
    }

    { // orientation (similar lowpass filter as above, but use dabtime instead of wallclock time)
      float dx = step_dx / base_radius;
      float dy = step_dy / base_radius;
      float step_in_dabtime = hypotf(dx, dy); // FIXME: are we recalculating something here that we already have?
      float fac = 1.0 - exp_decay (exp(settings_value[BRUSH_DIRECTION_FILTER]*0.5)-1.0, step_in_dabtime);

      float dx_old = states[STATE_DIRECTION_DX];
      float dy_old = states[STATE_DIRECTION_DY];
      // use the opposite speed vector if it is closer (we don't care about 180 degree turns)
      if (SQR(dx_old-dx) + SQR(dy_old-dy) > SQR(dx_old-(-dx)) + SQR(dy_old-(-dy))) {
        dx = -dx;
        dy = -dy;
      }
      states[STATE_DIRECTION_DX] += (dx - states[STATE_DIRECTION_DX]) * fac;
      states[STATE_DIRECTION_DY] += (dy - states[STATE_DIRECTION_DY]) * fac;
    }

    { // custom input
      float fac;
      fac = 1.0 - exp_decay (settings_value[BRUSH_CUSTOM_INPUT_SLOWNESS], 0.1);
      states[STATE_CUSTOM_INPUT] += (settings_value[BRUSH_CUSTOM_INPUT] - states[STATE_CUSTOM_INPUT]) * fac;
    }

    { // stroke length
      float frequency;
      float wrap;
      frequency = expf(-settings_value[BRUSH_STROKE_DURATION_LOGARITHMIC]);
      states[STATE_STROKE] += norm_dist * frequency;
      // can happen, probably caused by rounding
      if (states[STATE_STROKE] < 0) states[STATE_STROKE] = 0;
      wrap = 1.0 + settings_value[BRUSH_STROKE_HOLDTIME];
      if (states[STATE_STROKE] > wrap) {
        if (wrap > 9.9 + 1.0) {
          // "inifinity", just hold stroke somewhere >= 1.0
          states[STATE_STROKE] = 1.0;
        } else {
          states[STATE_STROKE] = fmodf(states[STATE_STROKE], wrap);
          // just in case
          if (states[STATE_STROKE] < 0) states[STATE_STROKE] = 0;
        }
      }
    }

    // calculate final radius
    float radius_log;
    radius_log = settings_value[BRUSH_RADIUS_LOGARITHMIC];
    states[STATE_ACTUAL_RADIUS] = expf(radius_log);
    if (states[STATE_ACTUAL_RADIUS] < ACTUAL_RADIUS_MIN) states[STATE_ACTUAL_RADIUS] = ACTUAL_RADIUS_MIN;
    if (states[STATE_ACTUAL_RADIUS] > ACTUAL_RADIUS_MAX) states[STATE_ACTUAL_RADIUS] = ACTUAL_RADIUS_MAX;

    // aspect ratio (needs to be caluclated here because it can affect the dab spacing)
    states[STATE_ACTUAL_ELLIPTICAL_DAB_RATIO] = settings_value[BRUSH_ELLIPTICAL_DAB_RATIO];
    states[STATE_ACTUAL_ELLIPTICAL_DAB_ANGLE] = settings_value[BRUSH_ELLIPTICAL_DAB_ANGLE];
  }
Beispiel #4
0
    // This function runs a brush "simulation" step. Usually it is
    // called once or twice per dab. In theory the precision of the
    // "simulation" gets better when it is called more often. In
    // practice this only matters if there are some highly nonlinear
    // mappings in critical places or extremely few events per second.
    //
    // note: parameters are is dx/ddab, ..., dtime/ddab (dab is the number, 5.0 = 5th dab)
    void update_states_and_setting_values (float step_dx, float step_dy, float step_dpressure, float step_dtime)
    {
        float pressure;
        float inputs[INPUT_COUNT];

        if (step_dtime < 0.0) {
            printf("Time is running backwards!\n");
            step_dtime = 0.001;
        } else if (step_dtime == 0.0) {
            // FIXME: happens about every 10th start, workaround (against division by zero)
            step_dtime = 0.001;
        }

        states[STATE_X]        += step_dx;
        states[STATE_Y]        += step_dy;
        states[STATE_PRESSURE] += step_dpressure;

        float base_radius = expf(settings[BRUSH_RADIUS_LOGARITHMIC]->base_value);

        // FIXME: does happen (interpolation problem?)
        states[STATE_PRESSURE] = CLAMP(states[STATE_PRESSURE], 0.0, 1.0);
        pressure = states[STATE_PRESSURE];

        { // start / end stroke (for "stroke" input only)
            if (!states[STATE_STROKE_STARTED]) {
                if (pressure > settings[BRUSH_STROKE_THRESHOLD]->base_value + 0.0001) {
                    // start new stroke
                    //printf("stroke start %f\n", pressure);
                    states[STATE_STROKE_STARTED] = 1;
                    states[STATE_STROKE] = 0.0;
                }
            } else {
                if (pressure <= settings[BRUSH_STROKE_THRESHOLD]->base_value * 0.9 + 0.0001) {
                    // end stroke
                    //printf("stroke end\n");
                    states[STATE_STROKE_STARTED] = 0;
                }
            }
        }

        // now follows input handling

        float norm_dx, norm_dy, norm_dist, norm_speed;
        norm_dx = step_dx / step_dtime / base_radius;
        norm_dy = step_dy / step_dtime / base_radius;
        norm_speed = sqrt(SQR(norm_dx) + SQR(norm_dy));
        norm_dist = norm_speed * step_dtime;

        inputs[INPUT_PRESSURE] = pressure;
        inputs[INPUT_SPEED1] = log(speed_mapping_gamma[0] + states[STATE_NORM_SPEED1_SLOW])*speed_mapping_m[0] + speed_mapping_q[0];
        inputs[INPUT_SPEED2] = log(speed_mapping_gamma[1] + states[STATE_NORM_SPEED2_SLOW])*speed_mapping_m[1] + speed_mapping_q[1];
        inputs[INPUT_RANDOM] = g_rand_double (rng);
        inputs[INPUT_STROKE] = MIN(states[STATE_STROKE], 1.0);
        //if (states[STATE_NORM_DY_SLOW] == 0 && states[STATE_NORM_DX_SLOW] == 0) {
        //inputs[INPUT_ANGLE] = fmodf(atan2f (states[STATE_NORM_DY_SLOW], states[STATE_NORM_DX_SLOW])/(M_PI) + 1.0, 1.0);
        inputs[INPUT_CUSTOM] = states[STATE_CUSTOM_INPUT];
        if (print_inputs) {
            g_print("press=% 4.3f, speed1=% 4.4f\tspeed2=% 4.4f\tstroke=% 4.3f\tcustom=% 4.3f\n", (double)inputs[INPUT_PRESSURE], (double)inputs[INPUT_SPEED1], (double)inputs[INPUT_SPEED2], (double)inputs[INPUT_STROKE], (double)inputs[INPUT_CUSTOM]);
        }
        // FIXME: this one fails!!!
        //assert(inputs[INPUT_SPEED1] >= 0.0 && inputs[INPUT_SPEED1] < 1e8); // checking for inf

        for (int i=0; i<BRUSH_SETTINGS_COUNT; i++) {
            settings_value[i] = settings[i]->calculate (inputs);
        }

        {
            float fac = 1.0 - exp_decay (settings_value[BRUSH_SLOW_TRACKING_PER_DAB], 1.0);
            states[STATE_ACTUAL_X] += (states[STATE_X] - states[STATE_ACTUAL_X]) * fac; // FIXME: should this depend on base radius?
            states[STATE_ACTUAL_Y] += (states[STATE_Y] - states[STATE_ACTUAL_Y]) * fac;
        }

        { // slow speed
            float fac;
            fac = 1.0 - exp_decay (settings_value[BRUSH_SPEED1_SLOWNESS], step_dtime);
            states[STATE_NORM_SPEED1_SLOW] += (norm_speed - states[STATE_NORM_SPEED1_SLOW]) * fac;
            fac = 1.0 - exp_decay (settings_value[BRUSH_SPEED2_SLOWNESS], step_dtime);
            states[STATE_NORM_SPEED2_SLOW] += (norm_speed - states[STATE_NORM_SPEED2_SLOW]) * fac;
        }

        { // slow speed, but as vector this time
            float fac = 1.0 - exp_decay (exp(settings_value[BRUSH_OFFSET_BY_SPEED_SLOWNESS]*0.01)-1.0, step_dtime);
            states[STATE_NORM_DX_SLOW] += (norm_dx - states[STATE_NORM_DX_SLOW]) * fac;
            states[STATE_NORM_DY_SLOW] += (norm_dy - states[STATE_NORM_DY_SLOW]) * fac;
        }

        { // custom input
            float fac;
            fac = 1.0 - exp_decay (settings_value[BRUSH_CUSTOM_INPUT_SLOWNESS], 0.1);
            states[STATE_CUSTOM_INPUT] += (settings_value[BRUSH_CUSTOM_INPUT] - states[STATE_CUSTOM_INPUT]) * fac;
        }

        { // stroke length
            float frequency;
            float wrap;
            frequency = expf(-settings_value[BRUSH_STROKE_DURATION_LOGARITHMIC]);
            states[STATE_STROKE] += norm_dist * frequency;
            // can happen, probably caused by rounding
            if (states[STATE_STROKE] < 0) states[STATE_STROKE] = 0;
            wrap = 1.0 + settings_value[BRUSH_STROKE_HOLDTIME];
            if (states[STATE_STROKE] > wrap) {
                if (wrap > 9.9 + 1.0) {
                    // "inifinity", just hold stroke somewhere >= 1.0
                    states[STATE_STROKE] = 1.0;
                } else {
                    states[STATE_STROKE] = fmodf(states[STATE_STROKE], wrap);
                    // just in case
                    if (states[STATE_STROKE] < 0) states[STATE_STROKE] = 0;
                }
            }
        }

        // calculate final radius
        float radius_log;
        radius_log = settings_value[BRUSH_RADIUS_LOGARITHMIC];
        states[STATE_ACTUAL_RADIUS] = expf(radius_log);
        if (states[STATE_ACTUAL_RADIUS] < ACTUAL_RADIUS_MIN) states[STATE_ACTUAL_RADIUS] = ACTUAL_RADIUS_MIN;
        if (states[STATE_ACTUAL_RADIUS] > ACTUAL_RADIUS_MAX) states[STATE_ACTUAL_RADIUS] = ACTUAL_RADIUS_MAX;

        // aspect ratio (needs to be caluclated here because it can affect the dab spacing)

        float ratio = settings_value[BRUSH_ELLIPTICAL_DAB_RATIO];
        //float angle = atan2(states[STATE_NORM_DY_SLOW], -states[STATE_NORM_DX_SLOW]) / M_PI * 180;
        float angle = settings_value[BRUSH_ELLIPTICAL_DAB_ANGLE];
        states[STATE_ACTUAL_ELLIPTICAL_DAB_RATIO] = ratio;
        states[STATE_ACTUAL_ELLIPTICAL_DAB_ANGLE] = angle;
    }