@Override
 public void roomLight(int id, boolean on) {
   SimArea a = areas.get(id);
   if (a == null) {
     return;
   }
   a.setLightsOn(on);
 }
 @Override
 public void doorOpen(int id) {
   SimArea a = areas.get(id);
   if (a == null) {
     return;
   }
   a.setDoorClosed(false);
 }
 public void deserializeMap(String map) {
   String[] areaList = map.split(";");
   if (areaList.length < 3) {
     return;
   }
   try {
     float metersPerUnit = Float.valueOf(areaList[0]);
     SimArea.setMetersPerUnit(metersPerUnit);
     String[] originTokens = areaList[1].trim().split(" ");
     if (originTokens.length > 1) {
       int originX = Integer.valueOf(originTokens[0].trim());
       int originY = Integer.valueOf(originTokens[1].trim());
       Point origin = new Point(originX, originY);
       SimArea.setImageOrigin(origin);
     }
   } catch (NumberFormatException e) {
     activity.showAlert("Invalid px per meter", Toast.LENGTH_SHORT);
   }
   for (int i = 2; i < areaList.length; ++i) {
     String area = areaList[i];
     if (area.trim().length() == 0) {
       continue;
     }
     System.out.println("Parsing area:\n" + area);
     AbridgedAreaDescription aad = AbridgedAreaDescriptions.parseArea(area);
     List<Double> xywh = aad.xywh;
     int x = (int) (double) xywh.get(0);
     int y = (int) (double) xywh.get(1);
     int w = (int) (double) xywh.get(2);
     int h = (int) (double) xywh.get(3);
     int left, right, top, bottom;
     if (w >= 0) {
       left = x;
       right = x + w;
     } else {
       left = x + w;
       right = x;
     }
     if (h >= 0) {
       top = -y;
       bottom = -(y + h);
     } else {
       top = -y;
       bottom = -(y - h);
     }
     synchronized (areas) {
       areas.put(aad.id, new SimArea(aad.id, new Rect(left, top, right, bottom), aad.type));
     }
   }
   makeWalls();
 }
  @Override
  public void onDrawFrame(GL10 gl) {
    try {
      gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
      gl.glMatrixMode(GL10.GL_MODELVIEW);
      gl.glLoadIdentity();

      if (positionBuffer != null) {
        gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, positionBuffer);
      }

      synchronized (camera) {
        PointF c = getCameraLocation();
        if (c == camera) {
          GLU.gluLookAt(gl, c.x, c.y, zoom, camera.x, camera.y, 0.0f, 0.0f, 1.0f, 0.0f);
        } else {
          GLU.gluLookAt(
              gl,
              c.x,
              c.y,
              zoom / followHeightFactor,
              camera.x,
              camera.y,
              -FOLLOW_LOOK_AT_HEIGHT,
              0.0f,
              0.0f,
              -1.0f);
        }
      }

      synchronized (areas) {
        for (SimArea area : areas.values()) {
          if (!onlySeenAreas || seenAreas.contains(area.getID())) {
            area.draw(gl);
          }
        }
      }

      if (drawWalls) {
        synchronized (walls) {
          for (SimWall wall : walls) {
            wall.draw(gl);
          }
        }

        synchronized (wallTops) {
          for (SimWallTop wallTop : wallTops) {
            wallTop.draw(gl);
          }
        }
      }

      synchronized (objects) {
        for (SimObject obj : objects.values()) {
          obj.draw(gl);
        }
      }

      synchronized (robots) {
        for (SimRobot robot : robots.values()) {
          robot.draw(gl, drawRedLidar, drawBlueLidar, drawYellowWaypoint);
        }
      }

      synchronized (lastFloorTouch) {
        GLUtil.drawRect(
            gl,
            lastFloorTouch.x - 0.05f,
            lastFloorTouch.y - 0.25f,
            -0.01f,
            0.1f,
            0.5f,
            GLUtil.GRAY);
        GLUtil.drawRect(
            gl,
            lastFloorTouch.x - 0.25f,
            lastFloorTouch.y - 0.05f,
            -0.01f,
            0.5f,
            0.1f,
            GLUtil.GRAY);
      }

    } catch (ConcurrentModificationException e) {
      e.printStackTrace();
    }
  }
  private void makeWalls() {
    Point max = new Point(0, 0);
    Point min = new Point(0, 0);
    Point size;
    synchronized (areas) {
      for (SimArea area : areas.values()) {
        Rect r = area.getRect();
        if (r.top + 1 > max.y) {
          max.y = r.top + 1;
        }
        if (r.bottom - 1 < min.y) {
          min.y = r.bottom - 1;
        }
        if (r.right + 1 > max.x) {
          max.x = r.right + 1;
        }
        if (r.left - 1 < min.x) {
          min.x = r.left - 1;
        }
      }
      size = new Point(max.x - min.x, max.y - min.y);
      open = new boolean[size.x + 1][size.y + 1];
      for (SimArea area : areas.values()) {
        Rect r = area.getRect();
        for (int x = r.left; x < r.right; ++x) {
          for (int y = r.bottom; y < r.top; ++y) {
            open[x - min.x][y - min.y] = true;
          }
        }
      }
    }
    synchronized (walls) {
      walls.clear();
      for (int x_it = 0; x_it < size.x; ++x_it) {
        int x = x_it + min.x;
        for (int y_it = 0; y_it < size.y; ++y_it) {
          int y = y_it + min.y;
          // Check to the right and to the bottom.
          if ((x + 1 < size.x && open[x_it][y_it] != open[x_it + 1][y_it])
              || (x + 1 == size.x && open[x_it][y_it])) {
            walls.add(new SimWall(new Point(x + 1, y), new Point(x + 1, y + 1)));
          }
          if ((y + 1 < size.y && open[x_it][y_it] != open[x_it][y_it + 1])
              || (y + 1 == size.y && open[x_it][y_it])) {
            walls.add(new SimWall(new Point(x, y + 1), new Point(x + 1, y + 1)));
          }
        }
      }
      // Draw the walls along the edge of the map

      for (int x = min.x; x < max.x; ++x) {
        walls.add(new SimWall(new Point(x, min.y), new Point(x + 1, min.y)));
        walls.add(new SimWall(new Point(x, max.y), new Point(x + 1, max.y)));
      }
      for (int y = min.y; y < max.y; ++y) {
        walls.add(new SimWall(new Point(min.x, y), new Point(min.x, y + 1)));
        walls.add(new SimWall(new Point(max.x, y), new Point(max.x, y + 1)));
      }

      System.out.println("Made walls, size " + walls.size());
    }
    synchronized (wallTops) {
      wallTops.clear();
      boolean[][] hasTop = new boolean[size.x + 1][size.y + 1];
      for (int x = 0; x < size.x + 1; ++x) {
        for (int y = 0; y < size.y + 1; ++y) {
          hasTop[x][y] = false;
        }
      }
      for (int x_it = 0; x_it < size.x; ++x_it) {
        int x = x_it + min.x;
        for (int y_it = 0; y_it < size.y; ++y_it) {
          int y = y_it + min.y;
          // Figure out how big of a wall top to put here.
          int width = 0;
          int height = 0;
          while (true) {
            int newWidth = width + 1;
            int newHeight = height + 1;
            if (boolArrayMatches(open, false, x_it, y_it, newWidth, height)
                && boolArrayMatches(hasTop, false, x_it, y_it, newWidth, height)) {
              width = newWidth;
            }
            if (boolArrayMatches(open, false, x_it, y_it, width, newHeight)
                && boolArrayMatches(hasTop, false, x_it, y_it, width, newHeight)) {
              height = newHeight;
            }
            if (height != newHeight && width != newWidth) {
              break;
            }
            if ((width > 1 && height == 0) || (height > 1 && width == 0)) {
              break;
            }
          }
          if (width > 0 && height > 0) {
            System.out.println("Adding wall top " + x + ", " + y + ", " + width + ", " + height);
            wallTops.add(new SimWallTop(new Point(x, y), new Point(width, height)));
            for (int i = x_it; i < x_it + width; ++i) {
              for (int j = y_it; j < y_it + height; ++j) {
                hasTop[i][j] = true;
              }
            }
          }
        }
      }
      System.out.println("Made wall tops, size " + wallTops.size());
    }
  }
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    PointF screenTouch =
        new PointF(event.getX() / windowSize.x, 1.0f - event.getY() / windowSize.y);
    if (screenTouch.x < 0.0f) {
      screenTouch.x = 0.0f;
    }
    if (screenTouch.x > 1.0f) {
      screenTouch.x = 1.0f;
    }
    if (screenTouch.y < 0.0f) {
      screenTouch.y = 0.0f;
    }
    if (screenTouch.y >= 1.0f) {
      screenTouch.y = 1.0f;
    }
    PointF floorTouch = floorTouch(screenTouch);

    int action = event.getAction();
    if (action == MotionEvent.ACTION_MOVE) {
      if (fingerDown) {
        if (topDown) {
          synchronized (camera) {
            camera.x -= (floorTouch.x - lastFloorTouch.x);
            camera.y -= (floorTouch.y - lastFloorTouch.y);
            floorTouch = floorTouch(screenTouch);
          }
        } else {
          if (event.getPointerCount() > 1) {
            // followHeightFactor = 16.0f * (screenTouch.y + 0.1f);
          } else if (event.getPointerCount() == 1) {
            float deltaHeight = (screenTouch.y - lastScreenTouch.y);
            followHeightFactor += deltaHeight * followHeightFactor * 3.0f;

            if (followHeightFactor <= 1.0f) {
              followHeightFactor = 1.0f;
            } else if (followHeightFactor > 10.0f) {
              followHeightFactor = 10.0f;
            }

            // screenTouch is not being used because it is limited to 0 and 1
            // which leads to undesirable results. getX() is used instead.
            cameraOffsetX -= (event.getX() - lastX) / 200.0f;
            lastX = event.getX();
            if (cameraOffsetX > Math.PI * 2.0f) {
              cameraOffsetX -= Math.PI * 2.0f;
            } else if (cameraOffsetX < 0.0f) {
              cameraOffsetX += Math.PI * 2.0f;
            }
            // lastScreenTouch.y = screenTouch.y;
          }
        }
        if (event.getPointerCount() > 1) {
          PointF screenTouch2 =
              new PointF(event.getX(1) / windowSize.x, 1.0f - event.getY(1) / windowSize.y);
          float dx = screenTouch.x - screenTouch2.x;
          float dy = screenTouch.y - screenTouch2.y;
          zoom = -15.0f / (float) Math.sqrt(dx * dx + dy * dy) + 0.001f;
        }
      }
    } else if (action == MotionEvent.ACTION_DOWN) {
      fingerDown = true;
      lastX = event.getX();

      if (event.getEventTime() - lastTouchDownTime < 200) {
        resetCameraOffset();
      }
      lastTouchDownTime = event.getEventTime();

      if (nextObjectClass != null) {
        activity
            .getRobotSession()
            .sendMessage("object " + nextObjectClass + " " + floorTouch.x + " " + floorTouch.y);
        nextObjectClass = null;
      } else {
        try {
          boolean selected = false;
          synchronized (robots) {
            for (SimObject obj : robots.values()) {
              if (obj.intersectsPoint(floorTouch, 1.0f)) {
                activity.setSelectedObject(obj);
                selected = true;
                break;
              }
            }
          }
          if (!selected) {
            synchronized (objects) {
              for (SimObject obj : objects.values()) {
                if (obj.intersectsPoint(floorTouch, 1.0f)) {
                  activity.setSelectedObject(obj);
                  selected = true;
                  break;
                }
              }
            }
          }
          if (!selected) {
            synchronized (areas) {
              for (SimArea area : areas.values()) {
                if (area.intersectsPoint(floorTouch)) {
                  activity.setSelectedObject(area);
                  selected = true;
                  break;
                }
              }
            }
          }
          if (!selected) {
            activity.setSelectedObject(null);
          }
          draw();
        } catch (NullPointerException e) { // Don't know why this is happening
          e.printStackTrace();
        }
      }

    } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
      fingerDown = false;
    }
    synchronized (lastFloorTouch) {
      lastFloorTouch = floorTouch;
    }
    synchronized (lastScreenTouch) {
      lastScreenTouch = screenTouch;
    }
    // activity.propertiesTextView.setText(floorTouch.x + " " + floorTouch.y);
    return true;
  }