void paint_the_world()

in coinrun/coinrun.cpp [1506:1673]


void paint_the_world(
  QPainter& p, const QRect& rect,
  const std::shared_ptr<State>& state, const Agent* agent,
  bool recon, bool lasers)
{
  const_cast<Agent*>(agent)->zoom = 0.9*agent->zoom + 0.1*agent->target_zoom;
  double zoom = agent->zoom;
  const double bgzoom = 0.4;

  bool lowres = rect.height() < 200;
  GroundTheme* ground_theme = choose_ground_theme(state->ground_n, lowres);

  std::shared_ptr<Maze> maze = agent->maze;

  bool maze_render = maze->game_type == CoinRunMaze_v0;

  double kx = zoom * rect.width()  / double(maze->h);  // not w!
  double ky = zoom * rect.height() / double(maze->h);
  double dx = (-agent->x) * kx + rect.center().x()  - 0.5*kx;
  double dy = (agent->y) * ky - rect.center().y()   - 0.5*ky;

  p.setRenderHint(QPainter::Antialiasing, true);
  p.setRenderHint(QPainter::SmoothPixmapTransform, true);
  p.setRenderHint(QPainter::HighQualityAntialiasing, true);

  for (int tile_x=-1; tile_x<=2; tile_x++) {
    for (int tile_y=-1; tile_y<=1; tile_y++) {
      double zx = rect.width()*zoom;   // / bgzoom;
      double zy = rect.height()*zoom;  // / bgzoom);
      QRectF bg_image = QRectF(0, 0, zx, zy);
      bg_image.moveCenter(QPointF(
        zx*tile_x + rect.center().x() + bgzoom*(dx + kx*maze->h/2),
        zy*tile_y + rect.center().y() + bgzoom*(dy - ky*maze->h/2)
        ));

      if (maze_render) {
        p.fillRect(bg_image, QColor(30, 30, 30));
      } else {
        p.drawImage(bg_image, bg_images[state->bg_n]);
      }
    }
  }

  int radius = int(1 + maze->h / zoom);  // actually /2 works except near scroll limits
  int ix = int(agent->x + .5);
  int iy = int(agent->y + .5);
  int x_start = max(ix - radius, 0);
  int x_end = min(ix + radius + 1, maze->w);
  int y_start = max(iy - radius, 0);
  int y_end = min(iy + radius + 1, maze->h);
  double WINH = rect.height();

  for (int y=y_start; y<y_end; ++y) {
    for (int x=x_start; x<x_end; x++) {
      int wkey = maze->get_elem(x, y);
      if (wkey==SPACE) continue;

      auto f = ground_theme->walls.find(wkey);
      QImage img = f == ground_theme->walls.end() ? ground_theme->default_wall : f->second;
      QRectF dst = QRectF(kx*x + dx, WINH - ky*y + dy, kx + .5, ky + .5);
      dst.adjust(-0.1, -0.1, +0.1, +0.1); // here an attempt to fix subpixel seams that appear on the image, especially lowres
      
      if (maze_render) {
        if (is_coin(wkey)) {
          p.fillRect(dst, QColor(255, 255, 0));
        } else if (wkey == WALL_MIDDLE || wkey == WALL_SURFACE) {
          p.fillRect(dst, QColor(150, 150, 150));
        }
      } else if (wkey==LAVA_MIDDLE || wkey==LAVA_SURFACE) {
        QRectF d1 = dst;
        QRectF d2 = dst;
        QRectF sr(QPointF(0,0), img.size());
        QRectF sr1 = sr;
        QRectF sr2 = sr;
        float tr = state->time*0.1;
        tr -= int(tr);
        tr *= -1;
        d1.translate(tr*dst.width(), 0);
        d2.translate(dst.width() + tr*dst.width(), 0);
        sr1.translate(-tr*img.width(), 0);
        sr2.translate(-img.width() - tr*img.width(), 0);
        d1 &= dst;
        d2 &= dst;
        d1.adjust(0, 0, +0.5, 0);
        d2.adjust(-0.5, 0, 0, 0);
        sr1 &= sr;
        sr2 &= sr;
        if (!sr1.isEmpty())
          p.drawImage(d1, img, sr1);
        if (!sr2.isEmpty())
          p.drawImage(d2, img, sr2);
      } else {
        p.drawImage(dst, img);
      }
    }
  }

  PlayerTheme* active_theme = choose_player_theme(agent->theme_n, agent->is_facing_right, lowres);

  if (maze_render) {
    QRectF dst = QRectF(kx * agent->x + dx, WINH - ky * (agent->y+1) + dy + ky, kx, ky);
    p.fillRect(dst, QColor(0, 255, 199));
  } else {
    QImage img = agent->picture(active_theme);
    QRectF dst = QRectF(kx * agent->x + dx, WINH - ky * (agent->y+1) + dy, kx, 2 * ky);
    p.drawImage(dst, img);  
  }

  int monsters_count = maze->monsters.size();
  for (int i=0; i<monsters_count; ++i) {
    const std::shared_ptr<Monster>& m = maze->monsters[i];
    QRectF dst = QRectF(kx*m->x + dx, WINH - ky*m->y + dy, kx, ky);

    EnemyTheme* theme = choose_enemy_theme(m, i, lowres);
    if (m->is_flying || m->is_walking) {
      for (int t=2; t<MONSTER_TRAIL; t+=2) {
        QRectF dst = QRectF(kx*m->prev_x[t] + dx, WINH - ky*m->prev_y[t] + dy, kx, ky);
        float ft = 1 - float(t)/MONSTER_TRAIL;
        float smaller = 0.20;
        float lower = -0.22;
        float soar = -0.4;
        dst.adjust(
          (smaller-0.2*ft)*kx, (soar*ft-0.2*ft-lower+smaller)*ky,
          (-smaller+0.2*ft)*kx, (soar*ft+0.2*ft-lower-smaller)*ky);
        p.setBrush(QColor(255,255,255, t*127/MONSTER_TRAIL));
        p.setPen(Qt::NoPen);
        p.drawEllipse(dst);
      }
    }
    p.drawImage(dst, state->time / theme->anim_freq % 2 == 0 ? theme->walk1 : theme->walk2);
  }

  if (USE_DATA_AUGMENTATION) {
    float max_rand_dim = .25;
    float min_rand_dim = .1;
    int num_blotches = global_rand_gen.randint(0, 6);

    bool hard_blotches = false;

    if (hard_blotches) {
      max_rand_dim = .3;
      min_rand_dim = .2;
      num_blotches = global_rand_gen.randint(0, 10);
    }

    for (int j = 0; j < num_blotches; j++) {
      float rx = global_rand_gen.rand01() * rect.width();
      float ry = global_rand_gen.rand01() * rect.height();
      float rdx = (global_rand_gen.rand01() * max_rand_dim + min_rand_dim) * rect.width();
      float rdy = (global_rand_gen.rand01() * max_rand_dim + min_rand_dim) * rect.height();

      QRectF dst3 = QRectF(rx, ry, rdx, rdy);
      p.fillRect(dst3, QColor(global_rand_gen.randint(0, 255), global_rand_gen.randint(0, 255), global_rand_gen.randint(0, 255)));
    }
  }

  if (PAINT_VEL_INFO) {
    float infodim = rect.height() * .2;
    QRectF dst2 = QRectF(0, 0, infodim, infodim);
    int s0 = to_shade(agent->spring / maze->max_jump);
    int s1 = to_shade(.5 * agent->vx / maze->max_speed + .5);
    int s2 = to_shade(.5 * agent->vy / maze->max_jump + .5);
    p.fillRect(dst2, QColor(s1, s1, s1));

    QRectF dst3 = QRectF(infodim, 0, infodim, infodim);
    p.fillRect(dst3, QColor(s2, s2, s2));
  }
}