Exemple #1
0
  /**
   * Compute the positions of the arrow head of the graphic's legs.
   *
   * @param dc Current draw context
   * @param base Position of the arrow's starting point.
   * @param tip Position of the arrow head tip.
   * @param arrowLength Length of the arrowhead as a fraction of the total line length.
   * @param arrowAngle Angle of the arrow head.
   * @return Positions required to draw the arrow head.
   */
  protected List<Position> computeArrowheadPositions(
      DrawContext dc, Position base, Position tip, double arrowLength, Angle arrowAngle) {
    // Build a triangle to represent the arrowhead. The triangle is built from two vectors, one
    // parallel to the
    // segment, and one perpendicular to it.

    Globe globe = dc.getGlobe();

    Vec4 ptA = globe.computePointFromPosition(base);
    Vec4 ptB = globe.computePointFromPosition(tip);

    // Compute parallel component
    Vec4 parallel = ptA.subtract3(ptB);

    Vec4 surfaceNormal = globe.computeSurfaceNormalAtPoint(ptB);

    // Compute perpendicular component
    Vec4 perpendicular = surfaceNormal.cross3(parallel);

    double finalArrowLength = arrowLength * parallel.getLength3();
    double arrowHalfWidth = finalArrowLength * arrowAngle.tanHalfAngle();

    perpendicular = perpendicular.normalize3().multiply3(arrowHalfWidth);
    parallel = parallel.normalize3().multiply3(finalArrowLength);

    // Compute geometry of direction arrow
    Vec4 vertex1 = ptB.add3(parallel).add3(perpendicular);
    Vec4 vertex2 = ptB.add3(parallel).subtract3(perpendicular);

    return TacticalGraphicUtil.asPositionList(globe, vertex1, vertex2, ptB);
  }
  protected Vec4[] calculateNormals(int width, int height, Vec4[] verts, int padding) {
    int padding2 = padding * 2;

    if (verts.length != (width + padding2) * (height + padding2))
      throw new IllegalStateException("Illegal vertices length");

    Vec4[] norms = new Vec4[width * height];
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        //   v2
        //   |
        // v1-v0-v3
        //   |
        //   v4

        Vec4 v0 =
            verts[getArrayIndex(width + padding2, height + padding2, x + padding, y + padding)];
        if (v0 != null) {
          Vec4 v1 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding - 1, y + padding)];
          Vec4 v2 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding, y + padding - 1)];
          Vec4 v3 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding + 1, y + padding)];
          Vec4 v4 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding, y + padding + 1)];

          Vec4[] normals = new Vec4[4];
          normals[0] =
              v1 != null && v2 != null
                  ? v1.subtract3(v0).cross3(v0.subtract3(v2)).normalize3()
                  : null;
          normals[1] =
              v2 != null && v3 != null
                  ? v2.subtract3(v0).cross3(v0.subtract3(v3)).normalize3()
                  : null;
          normals[2] =
              v3 != null && v4 != null
                  ? v3.subtract3(v0).cross3(v0.subtract3(v4)).normalize3()
                  : null;
          normals[3] =
              v4 != null && v1 != null
                  ? v4.subtract3(v0).cross3(v0.subtract3(v1)).normalize3()
                  : null;
          Vec4 normal = Vec4.ZERO;
          for (Vec4 n : normals) {
            if (n != null) normal = normal.add3(n);
          }
          if (normal != Vec4.ZERO) {
            norms[getArrayIndex(width, height, x, y)] = normal.normalize3();
          }
        }
      }
    }
    return norms;
  }
Exemple #3
0
  /**
   * Determine the positions that make up the arrowhead.
   *
   * @param dc Current draw context.
   * @param startPosition Position of the arrow's base.
   * @param endPosition Position of the arrow head tip.
   * @return Positions that define the arrowhead.
   */
  protected List<Position> computeArrowheadPositions(
      DrawContext dc, Position startPosition, Position endPosition) {
    Globe globe = dc.getGlobe();

    // Arrowhead looks like this:
    //                  _
    //        A\         | 1/2 width
    // ________B\       _|
    // Pt. 1    /
    //        C/
    //         | |
    //      Length

    Vec4 p1 = globe.computePointFromPosition(startPosition);
    Vec4 pB = globe.computePointFromPosition(endPosition);

    // Find vector in the direction of the arrow
    Vec4 vB1 = p1.subtract3(pB);

    double arrowLengthFraction = this.getArrowLength();

    // Find the point at the base of the arrowhead
    Vec4 arrowBase = pB.add3(vB1.multiply3(arrowLengthFraction));

    Vec4 normal = globe.computeSurfaceNormalAtPoint(arrowBase);

    // Compute the length of the arrowhead
    double arrowLength = vB1.getLength3() * arrowLengthFraction;
    double arrowHalfWidth = arrowLength * this.getArrowAngle().tanHalfAngle();

    // Compute a vector perpendicular to the segment and the normal vector
    Vec4 perpendicular = vB1.cross3(normal);
    perpendicular = perpendicular.normalize3().multiply3(arrowHalfWidth);

    // Find points A and C
    Vec4 pA = arrowBase.add3(perpendicular);
    Vec4 pC = arrowBase.subtract3(perpendicular);

    return TacticalGraphicUtil.asPositionList(globe, pA, pB, pC);
  }
  /**
   * Compute points on either side of a line segment. This method requires a point on the line, and
   * either a next point, previous point, or both.
   *
   * @param point Center point about which to compute side points.
   * @param prev Previous point on the line. May be null if {@code next} is non-null.
   * @param next Next point on the line. May be null if {@code prev} is non-null.
   * @param leftPositions Left position will be added to this list.
   * @param rightPositions Right position will be added to this list.
   * @param halfWidth Distance from the center line to the left or right lines.
   * @param globe Current globe.
   */
  protected void generateParallelPoints(
      Vec4 point,
      Vec4 prev,
      Vec4 next,
      List<Position> leftPositions,
      List<Position> rightPositions,
      double halfWidth,
      Globe globe) {
    if ((point == null) || (prev == null && next == null)) {
      String message = Logging.getMessage("nullValue.PointIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (leftPositions == null || rightPositions == null) {
      String message = Logging.getMessage("nullValue.PositionListIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (globe == null) {
      String message = Logging.getMessage("nullValue.GlobeIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    Vec4 offset;
    Vec4 normal = globe.computeSurfaceNormalAtPoint(point);

    // Compute vector in the direction backward along the line.
    Vec4 backward = (prev != null) ? prev.subtract3(point) : point.subtract3(next);

    // Compute a vector perpendicular to segment BC, and the globe normal vector.
    Vec4 perpendicular = backward.cross3(normal);

    double length;
    // If both next and previous points are supplied then calculate the angle that bisects the angle
    // current, next, prev.
    if (next != null && prev != null && !Vec4.areColinear(prev, point, next)) {
      // Compute vector in the forward direction.
      Vec4 forward = next.subtract3(point);

      // Calculate the vector that bisects angle ABC.
      offset = forward.normalize3().add3(backward.normalize3());
      offset = offset.normalize3();

      // Compute the scalar triple product of the vector BC, the normal vector, and the offset
      // vector to
      // determine if the offset points to the left or the right of the control line.
      double tripleProduct = perpendicular.dot3(offset);
      if (tripleProduct < 0) {
        offset = offset.multiply3(-1);
      }

      // Determine the length of the offset vector that will keep the left and right lines parallel
      // to the control
      // line.
      Angle theta = backward.angleBetween3(offset);
      if (!Angle.ZERO.equals(theta)) length = halfWidth / theta.sin();
      else length = halfWidth;
    } else {
      offset = perpendicular.normalize3();
      length = halfWidth;
    }
    offset = offset.multiply3(length);

    // Determine the left and right points by applying the offset.
    Vec4 ptRight = point.add3(offset);
    Vec4 ptLeft = point.subtract3(offset);

    // Convert cartesian points to geographic.
    Position posLeft = globe.computePositionFromPoint(ptLeft);
    Position posRight = globe.computePositionFromPoint(ptRight);

    leftPositions.add(posLeft);
    rightPositions.add(posRight);
  }
  protected Vec4[] calculateBentNormals(int width, int height, Vec4[] verts, int padding) {
    int padding2 = padding * 2;

    if (verts.length != (width + padding2) * (height + padding2))
      throw new IllegalStateException("Illegal vertices length");

    Vec4[] norms = new Vec4[width * height];

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        Vec4 vec =
            verts[getArrayIndex(width + padding2, height + padding2, x + padding, y + padding)];
        if (vec != null) {
          Vec4 vecnorm = vec.normalize3();
          Vec4[] maxes = new Vec4[16];
          double[] angles = new double[16];
          for (int i = 0; i < angles.length; i++) {
            angles[i] = Double.MAX_VALUE;
          }

          //  2  3  4  5  6
          //  1           7
          //  0           8
          // 15           9
          // 14 13 12 11 10

          // for (int i = 1; i <= padding; i++)
          for (int i = 2; i <= padding; i += 2) {
            Vec4[] vecs = new Vec4[16];
            vecs[0] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding - i, y + padding)];
            vecs[2] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding - i, y + padding - i)];
            vecs[4] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding, y + padding - i)];
            vecs[6] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding + i, y + padding - i)];
            vecs[8] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding + i, y + padding)];
            vecs[10] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding + i, y + padding + i)];
            vecs[12] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding, y + padding + i)];
            vecs[14] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding - i, y + padding + i)];
            if (i % 2 == 0) {
              int i2 = i / 2;
              vecs[1] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i, y + padding - i2)];
              vecs[3] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i2, y + padding - i)];
              vecs[5] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i2, y + padding - i)];
              vecs[7] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i, y + padding - i2)];
              vecs[9] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i, y + padding + i2)];
              vecs[11] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i2, y + padding + i)];
              vecs[13] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i2, y + padding + i)];
              vecs[15] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i, y + padding + i2)];
            }

            for (int j = 0; j < maxes.length; j++) {
              if (vecs[j] != null) {
                Vec4 v = vecs[j].subtract3(vec).normalize3();
                double angle = Math.acos(v.dot3(vecnorm));
                if (angle < angles[j]) {
                  angles[j] = angle;
                  maxes[j] = v;
                }
              }
            }
          }

          Vec4 normal = Vec4.ZERO;
          for (int i = 0; i < maxes.length; i++) {
            if (maxes[i] != null) {
              Vec4 n = maxes[i].cross3(vecnorm).cross3(maxes[i]);
              normal = normal.add3(n);
            }
          }
          if (normal != Vec4.ZERO) {
            norms[getArrayIndex(width, height, x, y)] = normal.normalize3();
          }
        }
      }
    }

    return norms;
  }
public class ShadedElevationLayer extends ElevationLayer {
  protected static final String CACHE_NAME_PREFIX = "Elevation Layer/";
  protected static final String FORMAT_SUFFIX = ".elev";

  protected double exaggeration = 1.0;
  protected Vec4 sunPosition = new Vec4(1, 1, 1);
  protected Vec4 sunPositionNormalized = sunPosition.normalize3();
  protected double bakedExaggeration = 100.0;
  protected double shaderMinElevation;
  protected double shaderMaxElevation;

  private int shaderProgram = -1;
  private int minElevationUniform;
  private int maxElevationUniform;
  private int minTexElevationUniform;
  private int maxTexElevationUniform;
  private int exaggerationUniform;
  private int bakedExaggerationUniform;
  private int opacityUniform;
  private int eyePositionUniform;
  private int sunPositionUniform;
  private int oldModelViewInverseUniform;

  public ShadedElevationLayer(ExtendedElevationModel elevationModel) {
    this(elevationModel, null);
  }

  public ShadedElevationLayer(ExtendedElevationModel elevationModel, Sector sector) {
    super(elevationModel, CACHE_NAME_PREFIX, FORMAT_SUFFIX, sector);
    shaderMinElevation = elevationModel.getMinElevation();
    shaderMaxElevation = elevationModel.getMaxElevation();
  }

  @Override
  protected void setupShader(DrawContext dc) {
    if (shaderProgram == -1) {
      initShader(dc);
    }

    GL gl = dc.getGL().getGL2();
    gl.glUseProgram(shaderProgram);

    // double minElevation = ((ElevationTesselator)
    // dc.getGlobe().getTessellator()).getMinElevation();
    // double maxElevation = ((ElevationTesselator)
    // dc.getGlobe().getTessellator()).getMaxElevation();
    double minElevation = clamp(shaderMinElevation, minElevationClamp, maxElevationClamp);
    double maxElevation = clamp(shaderMaxElevation, minElevationClamp, maxElevationClamp);
    gl.glUniform1f(minElevationUniform, (float) minElevation);
    gl.glUniform1f(maxElevationUniform, (float) maxElevation);
    gl.glUniform1f(exaggerationUniform, (float) exaggeration);
    gl.glUniform1f(bakedExaggerationUniform, (float) bakedExaggeration);
    gl.glUniform1f(opacityUniform, (float) getOpacity());

    Matrix modelViewInv = dc.getView().getModelviewMatrix().getInverse();
    float[] modelViewInvArray =
        new float[] {
          (float) modelViewInv.m11,
          (float) modelViewInv.m21,
          (float) modelViewInv.m31,
          (float) modelViewInv.m41,
          (float) modelViewInv.m12,
          (float) modelViewInv.m22,
          (float) modelViewInv.m32,
          (float) modelViewInv.m42,
          (float) modelViewInv.m13,
          (float) modelViewInv.m23,
          (float) modelViewInv.m33,
          (float) modelViewInv.m43,
          (float) modelViewInv.m14,
          (float) modelViewInv.m24,
          (float) modelViewInv.m34,
          (float) modelViewInv.m44
        };
    gl.glUniformMatrix4fv(oldModelViewInverseUniform, 1, false, modelViewInvArray, 0);

    Vec4 eye = dc.getView().getEyePoint();
    gl.glUniform3f(eyePositionUniform, (float) eye.x, (float) eye.y, (float) eye.z);
    gl.glUniform3f(
        sunPositionUniform,
        (float) sunPositionNormalized.x,
        (float) sunPositionNormalized.y,
        (float) sunPositionNormalized.z);
  }

  protected void initShader(DrawContext dc) {
    GL gl = dc.getGL().getGL2();
    int v = gl.glCreateShader(GL.GL_VERTEX_SHADER);
    int f = gl.glCreateShader(GL.GL_FRAGMENT_SHADER);
    String vsrc = "", fsrc = "", line;

    try {
      BufferedReader brv =
          new BufferedReader(
              new InputStreamReader(this.getClass().getResourceAsStream("vertexshader.glsl")));
      while ((line = brv.readLine()) != null) {
        vsrc += line + "\n";
      }

      BufferedReader brf =
          new BufferedReader(
              new InputStreamReader(this.getClass().getResourceAsStream("fragmentshader.glsl")));
      while ((line = brf.readLine()) != null) {
        fsrc += line + "\n";
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    gl.glShaderSource(v, 1, new String[] {vsrc}, new int[] {vsrc.length()}, 0);
    gl.glCompileShader(v);
    gl.glShaderSource(f, 1, new String[] {fsrc}, new int[] {fsrc.length()}, 0);
    gl.glCompileShader(f);

    shaderProgram = gl.glCreateProgram();
    gl.glAttachShader(shaderProgram, v);
    gl.glAttachShader(shaderProgram, f);
    gl.glLinkProgram(shaderProgram);
    gl.glValidateProgram(shaderProgram);

    gl.glUseProgram(shaderProgram);
    gl.glUniform1i(gl.glGetUniformLocation(shaderProgram, "tex0"), 0);
    gl.glUniform1i(gl.glGetUniformLocation(shaderProgram, "tex1"), 1);
    minElevationUniform = gl.glGetUniformLocation(shaderProgram, "minElevation");
    maxElevationUniform = gl.glGetUniformLocation(shaderProgram, "maxElevation");
    minTexElevationUniform = gl.glGetUniformLocation(shaderProgram, "minTexElevation");
    maxTexElevationUniform = gl.glGetUniformLocation(shaderProgram, "maxTexElevation");
    exaggerationUniform = gl.glGetUniformLocation(shaderProgram, "exaggeration");
    bakedExaggerationUniform = gl.glGetUniformLocation(shaderProgram, "bakedExaggeration");
    opacityUniform = gl.glGetUniformLocation(shaderProgram, "opacity");
    eyePositionUniform = gl.glGetUniformLocation(shaderProgram, "eyePosition");
    sunPositionUniform = gl.glGetUniformLocation(shaderProgram, "sunPosition");
    oldModelViewInverseUniform = gl.glGetUniformLocation(shaderProgram, "oldModelViewInverse");
  }

  @Override
  protected void packupShader(DrawContext dc) {
    dc.getGL().getGL2().glUseProgram(0);
  }

  public double getExaggeration() {
    return exaggeration;
  }

  public void setExaggeration(double exaggeration) {
    this.exaggeration = exaggeration;
  }

  public Vec4 getSunPosition() {
    return sunPosition;
  }

  public void setSunPosition(Vec4 sunPosition) {
    this.sunPosition = sunPosition;
    this.sunPositionNormalized = sunPosition.normalize3();
  }

  public double getShaderMinElevation() {
    return shaderMinElevation;
  }

  public void setShaderMinElevation(double shaderMinElevation) {
    this.shaderMinElevation = shaderMinElevation;
  }

  public double getShaderMaxElevation() {
    return shaderMaxElevation;
  }

  public void setShaderMaxElevation(double shaderMaxElevation) {
    this.shaderMaxElevation = shaderMaxElevation;
  }

  @Override
  protected boolean handleElevations(
      Globe globe, TextureTile tile, Sector sector, BufferWrapper[] elevations) {
    int width = tile.getLevel().getTileWidth();
    int height = tile.getLevel().getTileHeight();

    double[] minmax = getMinMax(elevations[4], elevationModel.getMissingDataSignal());
    byte[][] bytes =
        elevationsToTexture(width, height, globe, sector, elevations, minmax[0], minmax[1]);

    File file = WorldWind.getDataFileStore().newFile(tile.getPath());
    return saveTexture(file, bytes, width, height, minmax[0], minmax[1]);
  }

  protected boolean saveTexture(
      File file, byte[][] bytes, int width, int height, double minElevation, double maxElevation) {
    synchronized (fileLock) {
      OutputStream os = null;
      try {
        os = new FileOutputStream(file);
        DataOutputStream dos = new DataOutputStream(os);
        int bands = bytes.length;
        dos.writeInt(width);
        dos.writeInt(height);
        dos.writeInt(bands);
        dos.writeDouble(minElevation);
        dos.writeDouble(maxElevation);
        for (int i = 0; i < bands; i++) {
          os.write(bytes[i]);
        }
        return true;
      } catch (IOException e) {
        e.printStackTrace();
        return false;
      } finally {
        if (os != null) {
          try {
            os.close();
          } catch (IOException e) {
          }
        }
      }
    }
  }

  @Override
  protected boolean loadTexture(TextureTile tile, URL textureURL) {
    if (!(tile instanceof MinMaxTextureTile)) {
      Logging.logger().severe("Tile is not instance of " + MinMaxTextureTile.class);
      getLevels().markResourceAbsent(tile);
      return false;
    }

    synchronized (fileLock) {
      InputStream is = null;
      try {
        is = textureURL.openStream();
        DataInputStream dis = new DataInputStream(is);

        int width = dis.readInt();
        int height = dis.readInt();
        int bands = dis.readInt();
        double minElevation = dis.readDouble();
        double maxElevation = dis.readDouble();
        byte[][] bytes = new byte[bands][];
        for (int i = 0; i < bands; i++) {
          bytes[i] = new byte[width * height];
          is.read(bytes[i]);
        }

        DataBuffer db = new DataBufferByte(bytes, width * height);
        SampleModel sm = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, bands);
        Raster raster = Raster.createRaster(sm, db, null);
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
        image.setData(raster);

        TextureData textureData = TextureIO.newTextureData(image, isUseMipMaps());
        if (textureData == null) {
          throw new Exception("Could not create texture data for " + textureURL);
        }

        ((MinMaxTextureTile) tile).setMinElevation(minElevation);
        ((MinMaxTextureTile) tile).setMaxElevation(maxElevation);
        tile.setTextureData(textureData);

        // if (tile.getLevelNumber() != 0 || !this.isRetainLevelZeroTiles())
        addTileToCache(tile);

        getLevels().unmarkResourceAbsent(tile);
        firePropertyChange(AVKey.LAYER, null, this);
        return true;
      } catch (Exception e) {
        // Assume that something's wrong with the file and delete it.
        gov.nasa.worldwind.WorldWind.getDataFileStore().removeFile(textureURL);
        getLevels().markResourceAbsent(tile);
        String message = Logging.getMessage("generic.DeletedCorruptDataFile", textureURL);
        Logging.logger().info(message + ":" + e.getLocalizedMessage());
        return false;
      } finally {
        if (is != null) {
          try {
            is.close();
          } catch (IOException e) {
          }
        }
      }
    }
  }

  protected double[] getMinMax(BufferWrapper elevations, double missingDataSignal) {
    double min = Double.MAX_VALUE;
    double max = -Double.MAX_VALUE;
    for (int i = 0; i < elevations.length(); i++) {
      double value = elevations.getDouble(i);
      if (value != missingDataSignal) {
        min = Math.min(min, elevations.getDouble(i));
        max = Math.max(max, elevations.getDouble(i));
      }
    }
    if (min < minElevationClamp) min = minElevationClamp;
    if (max > maxElevationClamp) max = maxElevationClamp;
    /*if (min == Double.MAX_VALUE || max == -Double.MAX_VALUE)
    {
    	Logging.logger().warning("No elevations found in tile");
    }*/
    return new double[] {min, max};
  }

  protected byte[][] elevationsToTexture(
      int width,
      int height,
      Globe globe,
      Sector sector,
      BufferWrapper[] elevations,
      double minElevation,
      double maxElevation) {
    // elevation tile index configuration:
    // +-+-+-+
    // |6|7|8|
    // +-+-+-+
    // |3|4|5|
    // +-+-+-+
    // |0|1|2|
    // +-+-+-+

    int padding = 1;
    double missingDataSignal = elevationModel.getMissingDataSignal();
    double[] paddedElevations =
        calculatePaddedElevations(width, height, padding, elevations, missingDataSignal);
    Vec4[] verts =
        calculateTileVerts(
            width,
            height,
            globe,
            sector,
            paddedElevations,
            missingDataSignal,
            padding,
            bakedExaggeration);
    Vec4[] normals = calculateNormals(width, height, verts, padding);
    // Vec4[] bentNormals = calculateBentNormals(width, height, verts, padding);
    // normals = mixNormals(normals, bentNormals, 0.5);

    byte[] red = new byte[width * height];
    byte[] blue = new byte[width * height];
    byte[] green = new byte[width * height];
    byte[] alpha = new byte[width * height];

    for (int i = 0; i < width * height; i++) {
      Vec4 normal = normals[i];
      if (normal == null) normal = Vec4.ZERO;

      red[i] = (byte) (255.0 * (normal.x + 1) / 2);
      green[i] = (byte) (255.0 * (normal.y + 1) / 2);
      blue[i] = (byte) (255.0 * (normal.z + 1) / 2);
      double elevation = elevations[4].getDouble(i);
      elevation = clamp(elevation, minElevation, maxElevation);
      alpha[i] = (byte) (255.0 * (elevation - minElevation) / (maxElevation - minElevation));
    }

    byte[][] bytes = new byte[4][];
    bytes[0] = red;
    bytes[1] = green;
    bytes[2] = blue;
    bytes[3] = alpha;
    return bytes;
  }

  protected Vec4[] calculateNormals(int width, int height, Vec4[] verts, int padding) {
    int padding2 = padding * 2;

    if (verts.length != (width + padding2) * (height + padding2))
      throw new IllegalStateException("Illegal vertices length");

    Vec4[] norms = new Vec4[width * height];
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        //   v2
        //   |
        // v1-v0-v3
        //   |
        //   v4

        Vec4 v0 =
            verts[getArrayIndex(width + padding2, height + padding2, x + padding, y + padding)];
        if (v0 != null) {
          Vec4 v1 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding - 1, y + padding)];
          Vec4 v2 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding, y + padding - 1)];
          Vec4 v3 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding + 1, y + padding)];
          Vec4 v4 =
              verts[
                  getArrayIndex(width + padding2, height + padding2, x + padding, y + padding + 1)];

          Vec4[] normals = new Vec4[4];
          normals[0] =
              v1 != null && v2 != null
                  ? v1.subtract3(v0).cross3(v0.subtract3(v2)).normalize3()
                  : null;
          normals[1] =
              v2 != null && v3 != null
                  ? v2.subtract3(v0).cross3(v0.subtract3(v3)).normalize3()
                  : null;
          normals[2] =
              v3 != null && v4 != null
                  ? v3.subtract3(v0).cross3(v0.subtract3(v4)).normalize3()
                  : null;
          normals[3] =
              v4 != null && v1 != null
                  ? v4.subtract3(v0).cross3(v0.subtract3(v1)).normalize3()
                  : null;
          Vec4 normal = Vec4.ZERO;
          for (Vec4 n : normals) {
            if (n != null) normal = normal.add3(n);
          }
          if (normal != Vec4.ZERO) {
            norms[getArrayIndex(width, height, x, y)] = normal.normalize3();
          }
        }
      }
    }
    return norms;
  }

  protected Vec4[] calculateBentNormals(int width, int height, Vec4[] verts, int padding) {
    int padding2 = padding * 2;

    if (verts.length != (width + padding2) * (height + padding2))
      throw new IllegalStateException("Illegal vertices length");

    Vec4[] norms = new Vec4[width * height];

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        Vec4 vec =
            verts[getArrayIndex(width + padding2, height + padding2, x + padding, y + padding)];
        if (vec != null) {
          Vec4 vecnorm = vec.normalize3();
          Vec4[] maxes = new Vec4[16];
          double[] angles = new double[16];
          for (int i = 0; i < angles.length; i++) {
            angles[i] = Double.MAX_VALUE;
          }

          //  2  3  4  5  6
          //  1           7
          //  0           8
          // 15           9
          // 14 13 12 11 10

          // for (int i = 1; i <= padding; i++)
          for (int i = 2; i <= padding; i += 2) {
            Vec4[] vecs = new Vec4[16];
            vecs[0] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding - i, y + padding)];
            vecs[2] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding - i, y + padding - i)];
            vecs[4] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding, y + padding - i)];
            vecs[6] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding + i, y + padding - i)];
            vecs[8] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding + i, y + padding)];
            vecs[10] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding + i, y + padding + i)];
            vecs[12] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding, y + padding + i)];
            vecs[14] =
                verts[
                    getArrayIndex(
                        width + padding2, height + padding2, x + padding - i, y + padding + i)];
            if (i % 2 == 0) {
              int i2 = i / 2;
              vecs[1] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i, y + padding - i2)];
              vecs[3] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i2, y + padding - i)];
              vecs[5] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i2, y + padding - i)];
              vecs[7] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i, y + padding - i2)];
              vecs[9] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i, y + padding + i2)];
              vecs[11] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding + i2, y + padding + i)];
              vecs[13] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i2, y + padding + i)];
              vecs[15] =
                  verts[
                      getArrayIndex(
                          width + padding2, height + padding2, x + padding - i, y + padding + i2)];
            }

            for (int j = 0; j < maxes.length; j++) {
              if (vecs[j] != null) {
                Vec4 v = vecs[j].subtract3(vec).normalize3();
                double angle = Math.acos(v.dot3(vecnorm));
                if (angle < angles[j]) {
                  angles[j] = angle;
                  maxes[j] = v;
                }
              }
            }
          }

          Vec4 normal = Vec4.ZERO;
          for (int i = 0; i < maxes.length; i++) {
            if (maxes[i] != null) {
              Vec4 n = maxes[i].cross3(vecnorm).cross3(maxes[i]);
              normal = normal.add3(n);
            }
          }
          if (normal != Vec4.ZERO) {
            norms[getArrayIndex(width, height, x, y)] = normal.normalize3();
          }
        }
      }
    }

    return norms;
  }

  @Override
  protected TextureTile createTile(Sector sector, Level level, int row, int col) {
    return new MinMaxTextureTile(this, sector, level, row, col);
  }

  @Override
  protected Texture createTexture(TextureTile tile) {
    if (!(tile instanceof MinMaxTextureTile)) {
      String message = "Tile is not instance of " + MinMaxTextureTile.class;
      Logging.logger().severe(message);
      throw new IllegalStateException(message);
    }

    MinMaxTextureTile mmtt = (MinMaxTextureTile) tile;
    return new ElevationTexture(
        tile.getTextureData(), mmtt.getMinElevation(), mmtt.getMaxElevation());
  }

  @Override
  protected void setupTexture(DrawContext dc, Texture texture) {
    super.setupTexture(dc, texture);

    if (!(texture instanceof ElevationTexture)) {
      String message = "Texture is not instance of " + ElevationTexture.class;
      Logging.logger().severe(message);
      throw new IllegalStateException(message);
    }

    GL gl = dc.getGL().getGL2();
    gl.glUniform1f(minTexElevationUniform, (float) ((ElevationTexture) texture).getMinElevation());
    gl.glUniform1f(maxTexElevationUniform, (float) ((ElevationTexture) texture).getMaxElevation());
  }

  protected static class MinMaxTextureTile extends ElevationTextureTile {
    private double minElevation = Double.MAX_VALUE;
    private double maxElevation = -Double.MAX_VALUE;

    public MinMaxTextureTile(ElevationLayer layer, Sector sector, Level level, int row, int col) {
      super(layer, sector, level, row, col);
    }

    public double getMinElevation() {
      return minElevation;
    }

    public void setMinElevation(double minElevation) {
      this.minElevation = minElevation;
    }

    public double getMaxElevation() {
      return maxElevation;
    }

    public void setMaxElevation(double maxElevation) {
      this.maxElevation = maxElevation;
    }
  }

  public static class ElevationTexture extends SubclassableTexture {
    private final double minElevation;
    private final double maxElevation;

    public ElevationTexture(TextureData data, double minElevation, double maxElevation)
        throws GLException {
      super(data);
      this.minElevation = minElevation;
      this.maxElevation = maxElevation;
    }

    public double getMinElevation() {
      return minElevation;
    }

    public double getMaxElevation() {
      return maxElevation;
    }
  }
}
 public void setSunPosition(Vec4 sunPosition) {
   this.sunPosition = sunPosition;
   this.sunPositionNormalized = sunPosition.normalize3();
 }