/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.layers.volume; import gov.nasa.worldwind.geom.Position; import java.awt.Rectangle; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; import javax.media.opengl.GL2; import au.gov.ga.earthsci.worldwind.common.layers.Bounds; import au.gov.ga.earthsci.worldwind.common.layers.data.AbstractDataProvider; import au.gov.ga.earthsci.worldwind.common.layers.volume.btt.BinaryTriangleTree; import au.gov.ga.earthsci.worldwind.common.render.fastshape.FastShape; /** * Abstract implementation of the {@link VolumeDataProvider} interface. Provides * getter methods to the required fields, as well as curtain and surface shape * calculation. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public abstract class AbstractVolumeDataProvider extends AbstractDataProvider<VolumeLayer> implements VolumeDataProvider { /** * Number of samples in the volume data along the x-axis. */ protected int xSize; /** * Number of samples in the volume data along the y-axis; */ protected int ySize; /** * Number of samples in the volume data along the z-axis; */ protected int zSize; /** * Approximate sector containing the volume data. */ protected Bounds bounds = null; /** * Average top elevation of the volume data (in meters). */ protected double top; /** * Depth (distance between top and bottom slice) of the volume data (in * meters). */ protected double depth; /** * Value in the data that represents NODATA. */ protected float noDataValue; /** * Is the volume data reversed along the x-axis? */ protected boolean reverseX = false; /** * Is the volume data reversed along the y-axis? */ protected boolean reverseY = false; /** * Is the volume data reversed along the z-axis? */ protected boolean reverseZ = false; /** * Contains the positions of the top slice of the volume data, from the * south-west corner to the north-east corner. Longitude (x values) should * increment first. */ protected List<Position> positions; /** * Float array that contains the volume data. */ protected FloatBuffer data; /** * The minimum volume data value. */ protected float minValue; /** * The maximum volume data value. */ protected float maxValue; /** * Whether the volume data is cell-centred */ protected boolean cellCentred; protected FloatBuffer getData() { return data; } @Override public int getXSize() { return xSize; } @Override public int getYSize() { return ySize; } @Override public int getZSize() { return zSize; } @Override public double getDepth() { return depth; } @Override public double getTop() { return top; } @Override public double getSliceElevationPercent(double slice) { return slice / Math.max(zSize - 1, 1); } @Override public double getElevationPercentSlice(double elevationPercent) { return elevationPercent * Math.max(zSize - 1, 1); } @Override public int getZSubsamples() { return 1; } @Override public float getValue(int x, int y, int z) { if (reverseX) { x = xSize - x - 1; } if (reverseY) { y = ySize - y - 1; } if (reverseZ) { z = zSize - z - 1; } if (!cellCentred) { return data.get(x + y * xSize + z * xSize * ySize); } else { // Clamp cell-centred data to vertex coordinates int clampedX = Math.min(x, xSize - 2); int clampedY = Math.min(y, ySize - 2); int clampedZ = Math.min(z, zSize - 2); int index = clampedX + clampedY * (xSize - 1) + clampedZ * (xSize - 1) * (ySize - 1); return data.get(index); } } @Override public boolean isCellCentred() { return cellCentred; } @Override public float getMinValue() { return minValue; } @Override public float getMaxValue() { return maxValue; } @Override public Position getPosition(int x, int y) { return positions.get(x + y * xSize); } @Override public float getNoDataValue() { return noDataValue; } @Override public Bounds getBounds() { return bounds; } @Override public boolean isFollowTerrain() { return false; } @Override public FastShape createHorizontalSurface(float maxVariance, Rectangle rectangle) { BinaryTriangleTree btt = new BinaryTriangleTree(positions, xSize, ySize); btt.setForceGLTriangles(true); btt.setGenerateTextureCoordinates(true); return btt.buildMeshFromCenter(maxVariance, rectangle); } @Override public TopBottomFastShape createXCurtain(int x) { List<Position> positions = new ArrayList<Position>(); float[] textureCoordinateBuffer = new float[ySize * 4]; int i = 0; for (int y = 0; y < ySize; y++) { Position position = getPosition(x, y); TopBottomPosition top = new TopBottomPosition(position.latitude, position.longitude, position.elevation, false); TopBottomPosition bottom = new TopBottomPosition(position.latitude, position.longitude, position.elevation, true); positions.add(top); positions.add(bottom); float u = y / (float) Math.max(1, ySize - 1); textureCoordinateBuffer[i++] = u; textureCoordinateBuffer[i++] = 0; textureCoordinateBuffer[i++] = u; textureCoordinateBuffer[i++] = 1; } TopBottomFastShape shape = new TopBottomFastShape(positions, GL2.GL_TRIANGLE_STRIP); shape.setTextureCoordinateBuffer(textureCoordinateBuffer); return shape; } @Override public TopBottomFastShape createYCurtain(int y) { List<Position> positions = new ArrayList<Position>(); float[] textureCoordinateBuffer = new float[xSize * 4]; int i = 0; for (int x = 0; x < xSize; x++) { Position position = getPosition(x, y); TopBottomPosition top = new TopBottomPosition(position.latitude, position.longitude, position.elevation, false); TopBottomPosition bottom = new TopBottomPosition(position.latitude, position.longitude, position.elevation, true); positions.add(top); positions.add(bottom); float u = x / (float) Math.max(1, xSize - 1); textureCoordinateBuffer[i++] = u; textureCoordinateBuffer[i++] = 0; textureCoordinateBuffer[i++] = u; textureCoordinateBuffer[i++] = 1; } TopBottomFastShape shape = new TopBottomFastShape(positions, GL2.GL_TRIANGLE_STRIP); shape.setTextureCoordinateBuffer(textureCoordinateBuffer); return shape; } @Override public FastShape createBoundingBox() { List<Position> positions = new ArrayList<Position>(); for (int x = 0; x < getXSize(); x++) { Position position = getPosition(x, 0); positions.add(new TopBottomPosition(position.latitude, position.longitude, position.elevation, false)); } for (int y = 1; y < getYSize() - 1; y++) { Position position = getPosition(getXSize() - 1, y); positions.add(new TopBottomPosition(position.latitude, position.longitude, position.elevation, false)); } for (int x = getXSize() - 1; x >= 0; x--) { Position position = getPosition(x, getYSize() - 1); positions.add(new TopBottomPosition(position.latitude, position.longitude, position.elevation, false)); } for (int y = getYSize() - 2; y >= 1; y--) { Position position = getPosition(0, y); positions.add(new TopBottomPosition(position.latitude, position.longitude, position.elevation, false)); } int size = positions.size(); for (int i = 0; i < size; i++) { Position position = positions.get(i); positions.add(new TopBottomPosition(position.latitude, position.longitude, position.elevation, true)); } int squareIndexCount = ((getXSize() - 1) + (getYSize() - 1)) * 2; int[] indices = new int[2 * (squareIndexCount * 2 + 4)]; int k = 0; for (int i = 0; i < squareIndexCount; i++) { int j = (i + 1) % squareIndexCount; indices[k++] = i; indices[k++] = j; indices[k++] = i + squareIndexCount; indices[k++] = j + squareIndexCount; } int j = 0; for (int i = 0; i < 4; i++) { indices[k++] = j; indices[k++] = j + squareIndexCount; j += (i % 2 == 0 ? getXSize() : getYSize()) - 1; } TopBottomFastShape shape = new TopBottomFastShape(positions, indices, GL2.GL_LINES); shape.setBottomElevationOffset(-getDepth()); shape.setLineWidth(2.0); return shape; } @Override public boolean isSingleSliceVolume() { return xSize <= 1 || ySize <= 1 || zSize <= 1; } }