示例#1
0
void WRITE(pin_t pin, bool s) {
  bool old_state = state[pin];
  uint64_t nseconds = sim_runtime_ns();
  sim_assert(pin < PIN_NB, "WRITE: Pin number out of range");

  if (direction[pin] == out) {
    state[pin] = s;
  }

  if (old_state != s) {
    record_pin(TRACE_PINS + pin, s, nseconds);
    #ifdef TRACE_ALL_PINS
      fgreen();
      for (int i = 0; i < PIN_NB; i++) {
        if (state[i]) bred(); else bblack();
        fputc('A' + i, stdout);
      }
      fbreset();
      printf("\n");
    #else
      bred();
      if (s)
        sim_tick('A' + pin);
      else
        sim_tick('a' + pin);
      fbreset();
    #endif
  }

  if (s && !old_state) { /* rising edge */
    int axis = AXIS_NONE;
    int dir;
    switch (pin) {
    case X_STEP_PIN:
      dir = state[X_DIR_PIN] ? 1 : -1;
      axis = X_AXIS;
      break;
    case Y_STEP_PIN:
      dir = state[Y_DIR_PIN] ? 1 : -1;
      axis = Y_AXIS;
      break;
    case Z_STEP_PIN:
      dir = state[Z_DIR_PIN] ? 1 : -1;
      axis = Z_AXIS;
      break;
    case E_STEP_PIN:
      dir = state[E_DIR_PIN] ? 1 : -1;
      axis = E_AXIS;
      break;
    default:
      break;
    }
    if ( axis != AXIS_NONE ) {
      pos[axis] += dir;
      record_pin(TRACE_POS + axis, pos[axis], nseconds);
      print_pos();
    }
  }
}
示例#2
0
/**
  Simulate min endstops.  "on" at -10, "off" at 0.
*/
static void sim_endstop( int axis ) {
  bool on ;

  if (axis == AXIS_NONE)        return;
  else if (pos[axis] <= -20)    on = true;
  else if (pos[axis] >= 0)      on = false;
  else                          return ;

  const char * strstate = on ? "ON" : "OFF";
  int minpin;
  switch (axis) {
    case X_AXIS:
      #ifdef X_INVERT_MIN
        on = ! on;
      #endif
      minpin = X_MIN_PIN;
      break;
    case Y_AXIS:
      #ifdef Y_INVERT_MIN
        on = ! on;
      #endif
      minpin = Y_MIN_PIN;
      break;
    case Z_AXIS:
      #ifdef Z_INVERT_MIN
        on = ! on;
      #endif
      minpin = Z_MIN_PIN;
      break;
    default:
      return;
  }

  // No change
  if (state[minpin] == on) return;

  // Change the endstop state and report it
  state[minpin] = on;
  record_pin(TRACE_PINS + minpin, on, sim_runtime_ns());
  bred();
  if (on)
    sim_tick('A' + minpin);
  else
    sim_tick('a' + minpin);
  fbreset();

  sim_info("%c-Endstop: %s", "XYZE???"[axis], strstate);
}
示例#3
0
int
main(int argv, char **argc)
{
  int i;
  sim_object_t * object;
  /*Create a sim_data object*/
  sim_data_t* test_sim = sim_new();
  sim_init(test_sim,1, 10, 192, 120);

  /*Create a display_state*/
  display_state_t * state = calloc(1,sizeof(display_state_t));
  /* memset(state, 0, sizeof(*state)); */
  bcm_egl_openvg_init(state);
  display_init(state, 0);
  state->showfps = true;
  /*
  for(i = 0; i < 1000; i++)
  */
  while(1)
  {
    sim_tick(test_sim);
    /*Pass to display and alter*/
    display_draw(state, test_sim);

    /*
    for(i = 0; i < test_sim->size; i++)
    {
      object = test_sim->objects[i];
      object->x += 1;
      object->y += 1;
    }
    */
  }
  return EXIT_SUCCESS;
}
示例#4
0
int main(int argc, char **argv, char **env)
{
	float speed;

#ifndef WITH_SERIAL_PTY
	set_conio_terminal_mode();
#endif

	Verilated::commandArgs(argc, argv);
	dut = new Vdut;

	Verilated::traceEverOn(true);
	tfp = new VerilatedVcdC;
	dut->trace(tfp, 99);
	tfp->open("dut.vcd");

	struct sim s;
	sim_init(&s);

#ifdef WITH_SERIAL_PTY
	console_init(&s);
	console_open(&s);
#endif

#ifdef WITH_ETH
	eth_init(&s, "/dev/net/tap0", "tap0"); // XXX get this from /tmp/simethernet
	eth_open(&s);
#endif

	s.run = true;
	while(s.run) {
		sim_tick(&s);
		if(SYS_CLK) {
#ifdef WITH_SERIAL
			if(console_service(&s) != 0)
				s.run = false;
#endif
#ifdef WITH_ETH
			ethernet_service(&s);
#endif
		}
	}
	s.end = clock();

	speed = (s.tick/2)/((s.end-s.start)/CLOCKS_PER_SEC);

	printf("average speed: %3.3f MHz\n\r", speed/1000000);

	tfp->close();


#ifdef WITH_SERIAL_PTY
	console_close(&s);
#endif
#ifdef WITH_ETH
	eth_close(&s);
#endif

	exit(0);
}
示例#5
0
void sim_init(struct sim *s)
{
	int i;
	s->tick = 0;
#ifdef SYS_RST
	SYS_RST = 1;
	SYS_CLK = 0;
	for (i=0; i<8; i++)
		sim_tick(s);
	SYS_RST = 0;
#endif
	s->start = clock();
}
示例#6
0
文件: gui.cpp 项目: AscendNTNU/ai-sim
bool read_history(char *filename)
{
    FILE *f = fopen(filename, "rb");
    if (!f)
    {
        Printf("Failed to open file\n");
        return false;
    }
    static FileData data;
    fread((char*)&data, sizeof(data), 1, f);
    HISTORY_LENGTH = data.length;
    STATE = sim_init(data.seed);
    for (int i = 0; i < HISTORY_LENGTH; i++)
    {
        HISTORY_CMD[i] = data.cmds[i];
        STATE = sim_tick(STATE, HISTORY_CMD[i]);
        HISTORY_STATE[i] = STATE;
    }
    fclose(f);
    return true;
}
void
update_and_render(Memory *memory, GameState *game_state, FrameBuffer *frame_buffer, Keys *keys, Mouse *mouse, u64 time_us, u32 last_frame_dt, u32 fps , u32 argc, char *argv[])
{
    if (!game_state->init)
    {
        init_render_segments(memory, game_state, frame_buffer);
        init_game(memory, game_state, keys, time_us, argc, argv);
        load_maze(memory, game_state, argc, argv);
        reset_zoom(game_state);
    }

    update_inputs(keys, &game_state->inputs, time_us);

    if (game_state->inputs.maps[SAVE].active)
    {
        serialize_maze(&game_state->maze, &game_state->functions, game_state->filename);
    }

    if (game_state->inputs.maps[RELOAD].active)
    {
        strcpy(game_state->persistent_str, "Reload!");
        load_maze(memory, game_state, argc, argv);
    }

    if (game_state->inputs.maps[RESET].active)
    {
        strcpy(game_state->persistent_str, "Reset!");
        reset_zoom(game_state);
    }

    if (game_state->inputs.maps[RESTART].active)
    {
        strcpy(game_state->persistent_str, "Restart!");
        delete_all_cars(&game_state->cars);
        reset_car_inputs(&game_state->ui);
        game_state->finish_sim_step_move = false;
        game_state->last_sim_tick = 0;
        game_state->sim_steps = 0;
    }

    if (game_state->inputs.maps[STEP_MODE_TOGGLE].active)
    {
        game_state->single_step = !game_state->single_step;
        log(L_GameLoop, "Changing stepping mode");
    }

    //
    // UPDATE VIEW
    //

    update_pan_and_zoom(game_state, mouse);

    RenderBasis render_basis;
    render_basis.world_per_pixel = game_state->world_per_pixel;
    render_basis.scale = squared(game_state->zoom / 30.0f);
    render_basis.scale_focus = game_state->scale_focus;
    render_basis.origin = game_state->maze_pos * game_state->world_per_pixel;
    render_basis.clip_region = game_state->world_render_region * game_state->world_per_pixel;

    RenderBasis orthographic_basis;
    get_orthographic_basis(&orthographic_basis, game_state->screen_render_region);

    //
    // UPDATE WORLD
    //

    ui_consume_mouse_clicks(game_state, &orthographic_basis, &game_state->ui, mouse, time_us);

    if (game_state->inputs.maps[SIM_TICKS_INC].active)
    {
        game_state->sim_ticks_per_s += .5f;
    }
    if (game_state->inputs.maps[SIM_TICKS_DEC].active)
    {
        game_state->sim_ticks_per_s -= .5f;
    }
    game_state->sim_ticks_per_s = clamp(.5, game_state->sim_ticks_per_s, 20);

    update_cells_ui_state(game_state, &render_basis, mouse, time_us);

    b32 sim = sim_tick(game_state, time_us);

    if (sim && game_state->ui.car_inputs == 0 && !game_state->finish_sim_step_move)
    {
        perform_cells_sim_tick(memory, game_state, &(game_state->maze.tree), time_us);
        perform_cars_sim_tick(memory, game_state, time_us);
    }

    if (sim && game_state->ui.car_inputs == 0)
    {
        move_cars(game_state);

        game_state->finish_sim_step_move = false;
        ++game_state->sim_steps;
    }

    annimate_cars(game_state, last_frame_dt);
    step_particles(&(game_state->particles), time_us);

    update_ui(game_state, &orthographic_basis, &game_state->ui, mouse, &game_state->inputs, time_us);

    //
    // RENDER
    //

    // Add render operations to queue

    game_state->render_operations.next_free = 0;

    add_fast_box_to_render_list(&game_state->render_operations, &orthographic_basis, (Rectangle) {
        (V2) {0,0},size(game_state->screen_render_region)
    }, (PixelColor) {
        255, 255, 255
    });

    draw_cells(game_state, &game_state->render_operations, &render_basis, &(game_state->maze.tree), time_us);
    draw_cars(game_state, &game_state->render_operations, &render_basis, &(game_state->cars), time_us);
    // render_particles(&(game_state->particles), &game_state->render_operations, &render_basis);

    r32 text_scale = 0.3;
    draw_string(&game_state->render_operations, &orthographic_basis, &game_state->bitmaps.font, size(game_state->screen_render_region) - CHAR_SIZE*text_scale*(V2) {
        strlen(game_state->persistent_str), 1
    }, game_state->persistent_str, text_scale, (V4) {
        1, 0, 0, 0
    });

    draw_ui(&game_state->render_operations, &orthographic_basis, &render_basis, &game_state->bitmaps.font, &game_state->cell_bitmaps, &game_state->ui, time_us);

    char str[4];
    fmted_str(str, 4, "%d", fps);
    draw_string(&game_state->render_operations, &orthographic_basis, &game_state->bitmaps.font, (V2) {
        0, 0
    }, str, 0.3, (V4) {
        1, 0, 0, 0
    });

    // Add render segments to render queue

    V2 ns = game_state->render_segs.n_segments;
    V2 s;
    for (s.y = 0; s.y < ns.y; ++s.y)
    {
        for (s.x = 0; s.x < ns.x; ++s.x)
        {
            RenderQueueData render_data;

            Rectangle *segment = game_state->render_segs.segments + (u32)s.y + (u32)ns.y*(u32)s.x;
            render_data.clip_region = *segment;

            render_data.frame_buffer = frame_buffer;
            render_data.render_operations = &game_state->render_operations;

#ifdef THREADED_RENDERING
            pthread_mutex_lock(&game_state->render_queue.mut);

            while (game_state->render_queue.full)
            {
                log(L_RenderQueue, "producer: queue FULL.");
                pthread_cond_wait(&game_state->render_queue.not_full, &game_state->render_queue.mut);
            }

            log(L_RenderQueue, "producer: Adding.");
            queue_add(&game_state->render_queue, render_data);

            pthread_mutex_unlock(&game_state->render_queue.mut);
            pthread_cond_signal(&game_state->render_queue.not_empty);
#else
            consume_render_operations(render_data.frame_buffer, render_data.render_operations, render_data.clip_region);
#endif
        }
    }

    // TODO: Wait for frame rendering to finish

    // TODO: Get rid of this
    game_state->last_render_basis = render_basis;
}
示例#8
0
void _WRITE(pin_t pin, bool s) {
  bool old_state = state[pin];
  uint64_t nseconds = sim_runtime_ns();
  sim_assert(pin < PIN_NB, "WRITE: Pin number out of range");

  if (direction[pin] == out) {
    state[pin] = s;
  }

  if (old_state != s) {
    record_pin(TRACE_PINS + pin, s, nseconds);
    #ifdef TRACE_ALL_PINS
      fgreen();
      for (int i = 0; i < PIN_NB; i++) {
        if (state[i]) bred(); else bblack();
        fputc('A' + i, stdout);
      }
      fbreset();
      printf("\n");
    #else
      bred();
      if (s)
        sim_tick('A' + pin);
      else
        sim_tick('a' + pin);
      fbreset();
    #endif
  }

  if (s && !old_state) { /* rising edge */
    int axis = AXIS_NONE;
    int dir;
    switch (pin) {
    case X_STEP_PIN:
      dir = state[X_DIR_PIN] ? 1 : -1;
      #ifdef X_INVERT_DIR
        dir = -dir;
      #endif
      axis = X_AXIS;
      break;
    case Y_STEP_PIN:
      dir = state[Y_DIR_PIN] ? 1 : -1;
      #ifdef Y_INVERT_DIR
        dir = -dir;
      #endif
      axis = Y_AXIS;
      break;
    case Z_STEP_PIN:
      dir = state[Z_DIR_PIN] ? 1 : -1;
      #ifdef Z_INVERT_DIR
        dir = -dir;
      #endif
      axis = Z_AXIS;
      break;
    case E_STEP_PIN:
      dir = state[E_DIR_PIN] ? 1 : -1;
      #ifdef E_INVERT_DIR
        dir = -dir;
      #endif
      axis = E_AXIS;
      break;
    default:
      break;
    }
    switch ( axis ) {
      #ifdef KINEMATICS_COREXY
        case X_AXIS:
          pos[X_AXIS] += dir;
          pos[Y_AXIS] += dir;
          break;
        case Y_AXIS:
          pos[X_AXIS] += dir;
          pos[Y_AXIS] -= dir;
          break;
      #endif
      case Z_AXIS:
      case E_AXIS:
      default:
        pos[axis] += 2 * dir;
        break;
      case AXIS_NONE:
        break;
    }
    if ( axis != AXIS_NONE ) {
      for (int a = X_AXIS; a <= E_AXIS; a++)
        record_pin(TRACE_POS + axis, pos[axis] / 2, nseconds);

      static uint64_t prev_ns = -1;
      if (prev_ns != nseconds)
        print_pos();
      prev_ns = nseconds;

      for (int a = X_AXIS; a < E_AXIS; a++)
        sim_endstop(a);
    }
  }
}
示例#9
0
文件: gui.cpp 项目: AscendNTNU/ai-sim
void gui_tick(VideoMode mode, r32 gui_time, r32 gui_dt)
{
    persist bool flag_DrawDroneGoto     = true;
    persist bool flag_DrawDrone         = true;
    persist bool flag_DrawVisibleRegion = true;
    persist bool flag_DrawTargets       = true;
    persist bool flag_DrawObstacles     = true;
    persist bool flag_Paused            = false;
    persist bool flag_Recording         = false;
    persist bool flag_SetupRecord       = false;

    persist int record_from = 0;
    persist int record_to = 0;
    persist int record_frame_skip = 1;
    persist int record_width = 0;
    persist int record_height = 0;
    persist float record_region_x = -1.0f;
    persist float record_region_y = -1.0f;
    persist float record_region_scale = 2.0f;
    persist jo_gif_t record_gif;

    persist int seek_cursor = 0;
    persist int selected_target = -1;

    persist Color color_Clear          = { 0.00f, 0.00f, 0.00f, 1.00f };
    persist Color color_Tiles          = { 0.20f, 0.35f, 0.46f, 0.66f };
    persist Color color_Grid           = { 0.00f, 0.00f, 0.00f, 1.00f };
    persist Color color_VisibleRegion  = { 0.87f, 0.93f, 0.84f, 0.50f };
    persist Color color_GreenLine      = { 0.10f, 1.00f, 0.20f, 1.00f };
    persist Color color_SelectedTarget = { 0.85f, 0.34f, 0.32f, 1.00f };
    persist Color color_Targets        = { 0.85f, 0.83f, 0.37f, 1.00f };
    persist Color color_Obstacles      = { 0.43f, 0.76f, 0.79f, 1.00f };
    persist Color color_Drone          = { 0.87f, 0.93f, 0.84f, 0.50f };
    persist Color color_DroneGoto      = { 0.87f, 0.93f, 0.84f, 0.50f };
    #define RGBA(C) C.r, C.g, C.b, C.a

    persist float send_timer = 0.0f;
    persist float send_interval = 1.0f; // In simulation time units

    NDC_SCALE_X = (mode.height / (r32)mode.width) / 12.0f;
    NDC_SCALE_Y = 1.0f / 12.0f;

    if (flag_Recording || flag_SetupRecord)
    {
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        float Ax = 2.0f / record_region_scale;
        float Bx = -1.0f - Ax*record_region_x;
        float Ay = 2.0f / record_region_scale;
        float By = -1.0f - Ay*record_region_y;
        float modelview[] = {
            Ax,   0.0f, 0.0f, 0.0f,
            0.0f, Ay,   0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            Bx,   By,   0.0f, 1.0f
        };
        glLoadMatrixf(modelview);
    }
    else
    {
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    }

    if (!flag_Paused)
    {
        if (flag_Recording)
        {
            if (seek_cursor >= record_to)
            {
                flag_Paused = true;
                flag_Recording = false;
                seek_cursor = record_from;
                jo_gif_end(&record_gif);
            }
            else if (seek_cursor + record_frame_skip >= record_to)
            {
                // clamp to end
                seek_cursor = record_to;
            }
            else
            {
                seek_cursor += record_frame_skip;
            }
        }
        else if (seek_cursor < HISTORY_LENGTH-1)
        {
            seek_cursor++;
        }
        else
        {
            sim_Command cmd;

            if (!sim_recv_cmd(&cmd))
            {
                cmd.type = sim_CommandType_NoCommand;
                cmd.x = 0.0f;
                cmd.y = 0.0f;
                cmd.i = 0;
            }

            STATE = sim_tick(STATE, cmd);
            add_history(cmd, STATE);
            seek_cursor = HISTORY_LENGTH-1;

            send_timer -= Sim_Timestep;
            if (send_timer <= 0.0f)
            {
                sim_send_state(&STATE);
                send_timer += send_interval;
            }
        }
    }

    sim_State draw_state = HISTORY_STATE[seek_cursor];
    sim_Drone drone = draw_state.drone;
    sim_Robot *robots = draw_state.robots;
    sim_Robot *targets = draw_state.robots;
    sim_Robot *obstacles = draw_state.robots + Num_Targets;

    if (flag_Recording || flag_SetupRecord)
    {
        glViewport(0, 0, record_width, record_height);
    }
    else
    {
        glViewport(0, 0, mode.width, mode.height);
    }

    glClearColor(RGBA(color_Clear));
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(2.0f);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // draw grid tiles
    glBegin(GL_TRIANGLES);
    {
        color4f(color_Tiles);
        for (int yi = 0; yi < 20; yi++)
        for (int xi = 0; xi < 20; xi++)
        {
            r32 x = xi*1.0f;
            r32 y = yi*1.0f;
            fill_square(x, y, x+1.0f, y+1.0f);
        }
    }
    glEnd();

    glBegin(GL_LINES);
    {
        // draw grid lines
        color4f(color_Grid);
        for (int i = 0; i <= 20; i++)
        {
            r32 x = (r32)i;
            draw_line(x, 0.0f, x, 20.0f);
            draw_line(0.0f, x, 20.0f, x);
        }

        // draw visible region
        if (flag_DrawVisibleRegion)
        {
            color4f(color_VisibleRegion);
            draw_circle(drone.x, drone.y, 2.5f);
        }

        // draw green line
        color4f(color_GreenLine);
        draw_line(0.0f, 20.0f, 20.0f, 20.0f);

        // draw targets
        if (flag_DrawTargets)
        {
            color4f(color_Targets);
            for (int i = 0; i < Num_Targets; i++)
                draw_robot(targets[i]);
            if (selected_target >= 0)
            {
                color4f(color_SelectedTarget);
                draw_robot(targets[selected_target]);
            }
        }

        // draw obstacles
        if (flag_DrawObstacles)
        {
            color4f(color_Obstacles);
            for (int i = 0; i < Num_Obstacles; i++)
                draw_robot(obstacles[i]);
        }

        // draw drone
        if (flag_DrawDrone)
        {
            color4f(color_Drone);
            draw_line(drone.x - 0.5f, drone.y,
                      drone.x + 0.5f, drone.y);
            draw_line(drone.x, drone.y - 0.5f,
                      drone.x, drone.y + 0.5f);
        }

        // draw drone goto
        if (flag_DrawDroneGoto)
        {
            color4f(color_DroneGoto);
            draw_circle(drone.xr, drone.yr, 0.45f);
        }

        // draw indicators of magnet or bumper activations
        for (int i = 0; i < Num_Targets; i++)
        {
            r32 x = targets[i].x;
            r32 y = targets[i].y;
            if (targets[i].action.was_bumped)
            {
                glColor4f(1.0f, 0.3f, 0.1f, 1.0f);
                draw_circle(x, y, 0.5f);
            }
            else if (targets[i].action.was_top_touched)
            {
                glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
                draw_circle(x, y, 0.5f);
            }
        }
    }
    glEnd();

    // TODO: Change capture res?
    if (flag_Recording)
    {
        static unsigned char capture_data[1024*1024*4];
        Assert(sizeof(capture_data) >=
               record_width*record_height*4);
        glReadPixels(0, 0,
                     record_width,
                     record_height,
                     GL_RGBA,
                     GL_UNSIGNED_BYTE,
                     capture_data);

        jo_gif_frame(&record_gif, capture_data,
                     2, false);
    }

    ImGui_ImplSdl_NewFrame(mode.window);

    // DRAW FLAGS
    if (ImGui::CollapsingHeader("Rendering"))
    {
        ImGui::Checkbox("Drone goto", &flag_DrawDroneGoto);
        ImGui::Checkbox("Drone", &flag_DrawDrone);
        ImGui::Checkbox("Visible region", &flag_DrawVisibleRegion);
        ImGui::Checkbox("Targets", &flag_DrawTargets);
        ImGui::Checkbox("Obstacles", &flag_DrawObstacles);
    } // END DRAW FLAGS

    // COLORS
    if (ImGui::CollapsingHeader("Colors"))
    {
        ImGui::ColorEdit4("Clear", &color_Clear.r);
        ImGui::ColorEdit4("Grid", &color_Grid.r);
        ImGui::ColorEdit4("VisibleRegion", &color_VisibleRegion.r);
        ImGui::ColorEdit4("GreenLine", &color_GreenLine.r);
        ImGui::ColorEdit4("Targets", &color_Targets.r);
        ImGui::ColorEdit4("Obstacles", &color_Obstacles.r);
        ImGui::ColorEdit4("Drone", &color_Drone.r);
        ImGui::ColorEdit4("DroneGoto", &color_DroneGoto.r);
    } // END COLORS

    // REWIND HISTORY
    if (ImGui::CollapsingHeader("Seek##header"))
    {
        ImGui::Checkbox("Paused", &flag_Paused);
        ImGui::SliderInt("Seek##bar", &seek_cursor, 0, HISTORY_LENGTH-1);
        ImGui::InputInt("Seek frame", &seek_cursor);
        if (seek_cursor < 0) seek_cursor = 0;
        if (seek_cursor > HISTORY_LENGTH-1) seek_cursor = HISTORY_LENGTH-1;
        ImGui::Text("Time: %.2f seconds", (seek_cursor+1) * Sim_Timestep);
    } // END REWIND HISTORY

    // ROBOTS
    if (ImGui::CollapsingHeader("Robots"))
    {
        ImGui::Columns(4, "RobotsColumns");
        ImGui::Separator();
        ImGui::Text("ID"); ImGui::NextColumn();
        ImGui::Text("X"); ImGui::NextColumn();
        ImGui::Text("Y"); ImGui::NextColumn();
        ImGui::Text("Angle"); ImGui::NextColumn();
        ImGui::Separator();
        for (int i = 0; i < Num_Targets; i++)
        {
            char label[32];
            sprintf(label, "%02d", i);
            if (ImGui::Selectable(label, selected_target == i, ImGuiSelectableFlags_SpanAllColumns))
                selected_target = i;
            ImGui::NextColumn();
            ImGui::Text("%.2f", robots[i].x); ImGui::NextColumn();
            ImGui::Text("%.2f", robots[i].y); ImGui::NextColumn();
            ImGui::Text("%.2f", robots[i].q); ImGui::NextColumn();
        }
        ImGui::Columns(1);
        ImGui::Separator();
    }
    else
    {
        selected_target = -1;
    } // END ROBOTS

    // COMMUNICATION
    if (ImGui::CollapsingHeader("Communication"))
    {
        ImGui::TextWrapped("The rate at which the state is "
                           "sent can be changed using this slider. "
                           "The slider value represents the time "
                           "interval (in simulation time) "
                           "between each send.");
        ImGui::SliderFloat("Send interval", &send_interval,
                           Sim_Timestep, 1.0f);
        ImGui::Separator();

        ImGui::Text("Last 10 non-trivial commands received:");
        ImGui::Columns(5, "CommunicationColumns");
        ImGui::Separator();
        ImGui::Text("Time"); ImGui::NextColumn();
        ImGui::Text("type"); ImGui::NextColumn();
        ImGui::Text("x"); ImGui::NextColumn();
        ImGui::Text("y"); ImGui::NextColumn();
        ImGui::Text("i"); ImGui::NextColumn();
        ImGui::Separator();
        int count = 0;
        for (int i = 0; count < 10 && i <= seek_cursor; i++)
        {
            sim_State state_i = HISTORY_STATE[seek_cursor-i];
            sim_Command cmd_i = HISTORY_CMD[seek_cursor-i];
            if (cmd_i.type == sim_CommandType_NoCommand)
                continue;
            char label[32];
            sprintf(label, "%.2f", state_i.elapsed_time);
            ImGui::Selectable(label, false,
                              ImGuiSelectableFlags_SpanAllColumns);
            ImGui::NextColumn();
            switch (cmd_i.type)
            {
                case sim_CommandType_NoCommand:
                {
                    ImGui::Text("Nothing"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                } break;
                case sim_CommandType_LandInFrontOf:
                {
                    ImGui::Text("Land 180"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("%d", cmd_i.i); ImGui::NextColumn();
                } break;
                case sim_CommandType_LandOnTopOf:
                {
                    ImGui::Text("Land 45"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("%d", cmd_i.i); ImGui::NextColumn();
                } break;
                case sim_CommandType_Track:
                {
                    ImGui::Text("Track"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                    ImGui::Text("%d", cmd_i.i); ImGui::NextColumn();
                } break;
                case sim_CommandType_Search:
                {
                    ImGui::Text("Search"); ImGui::NextColumn();
                    ImGui::Text("%.2f", cmd_i.x); ImGui::NextColumn();
                    ImGui::Text("%.2f", cmd_i.y); ImGui::NextColumn();
                    ImGui::Text("-"); ImGui::NextColumn();
                } break;
            }
            count++;
        }
        ImGui::Columns(1);
        ImGui::Separator();
    } // END COMMUNICATION

    // RECORDING GIFS
    if (ImGui::CollapsingHeader("Recording"))
    {
        flag_SetupRecord = true;
        if (ImGui::Button("Mark frame as begin"))
        {
            record_from = seek_cursor;
        }
        ImGui::SameLine();
        ImGui::Text("Record from: %d", record_from);
        if (ImGui::Button("Mark frame as end"))
        {
            record_to = seek_cursor;
        }
        ImGui::SameLine();
        ImGui::Text("Record to: %d", record_to);
        ImGui::InputInt("Frame skip", &record_frame_skip);

        ImGui::InputInt("Record width", &record_width);
        ImGui::InputInt("Record height", &record_height);
        if (record_width <= 0) record_width = mode.width;
        if (record_height <= 0) record_height = mode.height;

        ImGui::SliderFloat("Record x", &record_region_x, -1.0f, 1.0f);
        ImGui::SliderFloat("Record y", &record_region_y, -1.0f, 1.0f);
        ImGui::SliderFloat("Record scale", &record_region_scale, 0.0f, 2.0f);

        if (ImGui::Button("Start recording") && !flag_Recording)
            ImGui::OpenPopup("Save recording as?");
        if (ImGui::BeginPopupModal("Save recording as?", NULL, ImGuiWindowFlags_AlwaysAutoResize))
        {
            persist char filename[1024];
            persist bool init_filename = true;
            if (init_filename)
            {
                sprintf(filename, "recording%u.gif", STATE.seed);
                init_filename = false;
            }
            ImGui::InputText("Filename", filename, sizeof(filename));
            ImGui::Separator();

            if (ImGui::Button("OK", ImVec2(120,0)))
            {
                flag_Recording = true;
                flag_Paused = false;
                seek_cursor = record_from-1;
                record_gif = jo_gif_start(filename, (short)record_width, (short)record_height, 0, 32);
                ImGui::CloseCurrentPopup();
            }
            ImGui::SameLine();
            if (ImGui::Button("Cancel", ImVec2(120,0)))
            {
                ImGui::CloseCurrentPopup();
            }
            ImGui::EndPopup();
        }

        if (ImGui::Button("Stop recording") && flag_Recording)
        {
            flag_Recording = false;
            flag_Paused = true;
            jo_gif_end(&record_gif);
        }

        if (record_from < 0) record_from = 0;
        if (record_from > HISTORY_LENGTH-1) record_from = HISTORY_LENGTH-1;
        if (record_to < record_from) record_to = record_from;
        if (record_to > HISTORY_LENGTH-1) record_to = HISTORY_LENGTH-1;
        if (record_frame_skip < 1) record_frame_skip = 1;
        ImGui::Separator();
    }
    else
    {
        flag_SetupRecord = false;
    } // END RECORDING GIFS

    // RESET AND SET SEED
    persist int custom_seed = 0;
    if (ImGui::Button("Reset"))
    {
        if (custom_seed > 0)
            STATE = sim_init((u32)custom_seed);
        else
            STATE = sim_init((u32)get_tick());
        HISTORY_LENGTH = 0;
        sim_Command cmd;
        cmd.type = sim_CommandType_NoCommand;
        add_history(cmd, STATE);
    }
    ImGui::SameLine();
    ImGui::InputInt("Seed", &custom_seed);
    // END RESET AND SET SEED

    // SAVE SIMULATION
    if (ImGui::Button("Save.."))
        ImGui::OpenPopup("Save as?");
    if (ImGui::BeginPopupModal("Save as?", NULL, ImGuiWindowFlags_AlwaysAutoResize))
    {
        persist char filename[1024];
        persist bool init_filename = true;
        if (init_filename)
        {
            sprintf(filename, "simulation%u", STATE.seed);
            init_filename = false;
        }
        ImGui::InputText("Filename", filename, sizeof(filename));
        ImGui::Separator();

        if (ImGui::Button("OK", ImVec2(120,0)))
        {
            write_history(filename);
            ImGui::CloseCurrentPopup();
        }
        ImGui::SameLine();
        if (ImGui::Button("Cancel", ImVec2(120,0)))
        {
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    } // END SAVE SIMULATION

    ImGui::SameLine();

    // SAVE SINGLE SNAPSHOT
    persist bool init_snapshot_filename = true;
    if (ImGui::Button("Save snapshot.."))
        ImGui::OpenPopup("Save snapshot as?");
    if (ImGui::BeginPopupModal("Save snapshot as?", NULL, ImGuiWindowFlags_AlwaysAutoResize))
    {
        persist char filename[1024];
        ImGui::TextWrapped("The filename is relative to the executable,"
                           "unless you write an absolute path.");
        if (init_snapshot_filename)
        {
            sprintf(filename, "snapshot%u-%u", STATE.seed, seek_cursor);
            init_snapshot_filename = false;
        }
        ImGui::InputText("Filename", filename, sizeof(filename));
        ImGui::Separator();

        if (ImGui::Button("OK", ImVec2(120,0)))
        {
            sim_Observed_State snapshot =
                sim_observe_state(HISTORY_STATE[seek_cursor]);
            printf("%.2f\n", snapshot.obstacle_q[0]);
            sim_write_snapshot(filename, snapshot);
            ImGui::CloseCurrentPopup();
        }
        ImGui::SameLine();
        if (ImGui::Button("Cancel", ImVec2(120,0)))
        {
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }
    else
    {
        init_snapshot_filename = true;
    } // END SAVE SIMULATION

    ImGui::SameLine();

    // LOAD SIMULATION
    if (ImGui::Button("Load.."))
        ImGui::OpenPopup("Load file?");
    if (ImGui::BeginPopupModal("Load file?", NULL, ImGuiWindowFlags_AlwaysAutoResize))
    {
        persist char filename[1024];
        persist bool init_filename = true;
        if (init_filename)
        {
            sprintf(filename, "simulation%u", STATE.seed);
            init_filename = false;
        }
        ImGui::InputText("Filename", filename, sizeof(filename));
        ImGui::Separator();

        if (ImGui::Button("OK", ImVec2(120,0)))
        {
            read_history(filename);
            seek_cursor = 0;
            flag_Paused = true;
            ImGui::CloseCurrentPopup();
        }
        ImGui::SameLine();
        if (ImGui::Button("Cancel", ImVec2(120,0)))
        {
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    } // END LOAD SIMULATION

    ImGui::Render();
} // END gui_tick