- fixed MapViewPosition scaleMap
- fixed GLRender isVisible() for proxy tiles - smoother scale animation
This commit is contained in:
parent
8d57bc7531
commit
7d7cf10d89
@ -44,10 +44,12 @@ public final class MercatorProjection {
|
||||
public static final double LONGITUDE_MIN = -LONGITUDE_MAX;
|
||||
|
||||
/**
|
||||
* Calculates the distance on the ground that is represented by a single pixel on the map.
|
||||
* Calculates the distance on the ground that is represented by a single
|
||||
* pixel on the map.
|
||||
*
|
||||
* @param latitude
|
||||
* the latitude coordinate at which the resolution should be calculated.
|
||||
* the latitude coordinate at which the resolution should be
|
||||
* calculated.
|
||||
* @param zoomLevel
|
||||
* the zoom level at which the resolution should be calculated.
|
||||
* @return the ground resolution at the given latitude and zoom level.
|
||||
@ -58,7 +60,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a latitude coordinate (in degrees) to a pixel Y coordinate at a certain zoom level.
|
||||
* Converts a latitude coordinate (in degrees) to a pixel Y coordinate at a
|
||||
* certain zoom level.
|
||||
*
|
||||
* @param latitude
|
||||
* the latitude coordinate that should be converted.
|
||||
@ -79,7 +82,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a latitude coordinate (in degrees) to a tile Y number at a certain zoom level.
|
||||
* Converts a latitude coordinate (in degrees) to a tile Y number at a
|
||||
* certain zoom level.
|
||||
*
|
||||
* @param latitude
|
||||
* the latitude coordinate that should be converted.
|
||||
@ -103,7 +107,8 @@ public final class MercatorProjection {
|
||||
/**
|
||||
* @param longitude
|
||||
* the longitude value which should be checked.
|
||||
* @return the given longitude value, limited to the possible longitude range.
|
||||
* @return the given longitude value, limited to the possible longitude
|
||||
* range.
|
||||
*/
|
||||
public static double limitLongitude(double longitude) {
|
||||
return Math.max(Math.min(longitude, LONGITUDE_MAX), LONGITUDE_MIN);
|
||||
@ -120,7 +125,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a longitude coordinate (in degrees) to a pixel X coordinate at a certain zoom level.
|
||||
* Converts a longitude coordinate (in degrees) to a pixel X coordinate at a
|
||||
* certain zoom level.
|
||||
*
|
||||
* @param longitude
|
||||
* the longitude coordinate that should be converted.
|
||||
@ -138,7 +144,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a longitude coordinate (in degrees) to the tile X number at a certain zoom level.
|
||||
* Converts a longitude coordinate (in degrees) to the tile X number at a
|
||||
* certain zoom level.
|
||||
*
|
||||
* @param longitude
|
||||
* the longitude coordinate that should be converted.
|
||||
@ -151,7 +158,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a pixel X coordinate at a certain zoom level to a longitude coordinate.
|
||||
* Converts a pixel X coordinate at a certain zoom level to a longitude
|
||||
* coordinate.
|
||||
*
|
||||
* @param pixelX
|
||||
* the pixel X coordinate that should be converted.
|
||||
@ -178,7 +186,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a pixel Y coordinate at a certain zoom level to a latitude coordinate.
|
||||
* Converts a pixel Y coordinate at a certain zoom level to a latitude
|
||||
* coordinate.
|
||||
*
|
||||
* @param pixelY
|
||||
* the pixel Y coordinate that should be converted.
|
||||
@ -206,7 +215,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a tile X number at a certain zoom level to a longitude coordinate.
|
||||
* Converts a tile X number at a certain zoom level to a longitude
|
||||
* coordinate.
|
||||
*
|
||||
* @param tileX
|
||||
* the tile X number that should be converted.
|
||||
@ -219,7 +229,8 @@ public final class MercatorProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a tile Y number at a certain zoom level to a latitude coordinate.
|
||||
* Converts a tile Y number at a certain zoom level to a latitude
|
||||
* coordinate.
|
||||
*
|
||||
* @param tileY
|
||||
* the tile Y number that should be converted.
|
||||
|
50
src/org/oscim/utils/FastMath.java
Normal file
50
src/org/oscim/utils/FastMath.java
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012 Hannes Janetzek
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.utils;
|
||||
|
||||
public class FastMath {
|
||||
/**
|
||||
* from http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
|
||||
*
|
||||
* @param v
|
||||
* ...
|
||||
* @return ...
|
||||
*/
|
||||
public static int log2(int v) {
|
||||
|
||||
int r = 0; // result of log2(v) will go here
|
||||
|
||||
if ((v & 0xFFFF0000) != 0) {
|
||||
v >>= 16;
|
||||
r |= 16;
|
||||
}
|
||||
if ((v & 0xFF00) != 0) {
|
||||
v >>= 8;
|
||||
r |= 8;
|
||||
}
|
||||
if ((v & 0xF0) != 0) {
|
||||
v >>= 4;
|
||||
r |= 4;
|
||||
}
|
||||
if ((v & 0xC) != 0) {
|
||||
v >>= 2;
|
||||
r |= 2;
|
||||
}
|
||||
if ((v & 0x2) != 0) {
|
||||
r |= 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
32
src/org/oscim/view/DelayedTaskHandler.java
Normal file
32
src/org/oscim/view/DelayedTaskHandler.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012 Hannes Janetzek
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.view;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
|
||||
public class DelayedTaskHandler extends Handler {
|
||||
public final int MESSAGE_UPDATE_POSITION = 1;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MESSAGE_UPDATE_POSITION:
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -83,6 +83,8 @@ public class MapView extends FrameLayout {
|
||||
private String mRenderTheme;
|
||||
private Map<String, String> mMapOptions;
|
||||
|
||||
// private final Handler mHandler;
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* the enclosing MapActivity instance.
|
||||
@ -124,6 +126,8 @@ public class MapView extends FrameLayout {
|
||||
|
||||
MapActivity mapActivity = (MapActivity) context;
|
||||
|
||||
// mHandler = new DelayedTaskHandler();
|
||||
|
||||
debugSettings = new DebugSettings(false, false, false, false);
|
||||
|
||||
mMapDatabaseType = mapDatabaseType;
|
||||
@ -205,6 +209,29 @@ public class MapView extends FrameLayout {
|
||||
return mMapViewPosition;
|
||||
}
|
||||
|
||||
public void enableRotation(boolean enable) {
|
||||
enableRotation = enable;
|
||||
|
||||
if (enable) {
|
||||
enableCompass(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableCompass(boolean enable) {
|
||||
if (enable == this.enableCompass)
|
||||
return;
|
||||
|
||||
this.enableCompass = enable;
|
||||
|
||||
if (enable)
|
||||
enableRotation(false);
|
||||
|
||||
if (enable)
|
||||
mCompass.enable();
|
||||
else
|
||||
mCompass.disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent motionEvent) {
|
||||
// mMapZoomControls.onMapViewTouchEvent(motionEvent.getAction()
|
||||
@ -601,30 +628,6 @@ public class MapView extends FrameLayout {
|
||||
mapWorker.proceed();
|
||||
}
|
||||
|
||||
public void enableRotation(boolean enable) {
|
||||
enableRotation = enable;
|
||||
|
||||
if (enable) {
|
||||
enableCompass(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableCompass(boolean enable) {
|
||||
if (enable == this.enableCompass)
|
||||
return;
|
||||
|
||||
this.enableCompass = enable;
|
||||
|
||||
if (enable)
|
||||
enableRotation(false);
|
||||
|
||||
if (enable)
|
||||
mCompass.enable();
|
||||
else
|
||||
mCompass.disable();
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Sets the visibility of the zoom controls.
|
||||
// *
|
||||
@ -647,18 +650,4 @@ public class MapView extends FrameLayout {
|
||||
// mJobParameters = new JobParameters(mJobParameters.theme, textScale);
|
||||
// clearAndRedrawMapView();
|
||||
// }
|
||||
|
||||
// public final int
|
||||
// public Handler messageHandler = new Handler() {
|
||||
//
|
||||
// @Override
|
||||
// public void handleMessage(Message msg) {
|
||||
// switch (msg.what) {
|
||||
// // handle update
|
||||
// // .....
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// };
|
||||
|
||||
}
|
||||
|
@ -17,25 +17,35 @@ package org.oscim.view;
|
||||
import org.oscim.core.GeoPoint;
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.MercatorProjection;
|
||||
|
||||
import android.util.FloatMath;
|
||||
import org.oscim.utils.FastMath;
|
||||
|
||||
/**
|
||||
* A MapPosition stores the latitude and longitude coordinate of a MapView together with its zoom level.
|
||||
* A MapPosition stores the latitude and longitude coordinate of a MapView
|
||||
* together with its zoom level.
|
||||
*/
|
||||
public class MapViewPosition {
|
||||
private static float MAX_SCALE = 2.0f;
|
||||
private static float MIN_SCALE = 1.0f;
|
||||
public static int MAX_ZOOMLEVEL = 16;
|
||||
private static final String TAG = "MapViewPosition";
|
||||
|
||||
public final static int MAX_ZOOMLEVEL = 16;
|
||||
|
||||
private final static float MAX_SCALE = 2.0f;
|
||||
private final static float MIN_SCALE = 1.0f;
|
||||
|
||||
private final MapView mMapView;
|
||||
|
||||
private double mLatitude;
|
||||
private double mLongitude;
|
||||
private final MapView mMapView;
|
||||
private byte mZoomLevel;
|
||||
private float mScale;
|
||||
private float mRotation;
|
||||
private float mTilt;
|
||||
|
||||
// 2^mZoomLevel * mScale;
|
||||
private float mMapScale;
|
||||
|
||||
// private final static float MAP_SIZE = 1000000;
|
||||
// private final static float MAP_SIZE2 = 1000000 >> 1;
|
||||
|
||||
MapViewPosition(MapView mapView) {
|
||||
mMapView = mapView;
|
||||
|
||||
@ -45,8 +55,24 @@ public class MapViewPosition {
|
||||
mScale = 1;
|
||||
mRotation = 0.0f;
|
||||
mTilt = 0;
|
||||
mMapScale = 1;
|
||||
}
|
||||
|
||||
// private static double latitudeToMapView(double latitude) {
|
||||
// double sinLatitude = Math.sin(latitude * (Math.PI / 180));
|
||||
// return (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 *
|
||||
// Math.PI))
|
||||
// * MAP_SIZE;
|
||||
// }
|
||||
//
|
||||
// public static double longitudeToMapView(double longitude) {
|
||||
// return (longitude + 180) / 360 * MAP_SIZE;
|
||||
// }
|
||||
//
|
||||
// private static double pixelXToLongitude(double pixelX, byte zoomLevel) {
|
||||
// return 360 * ((pixelX / ((long) Tile.TILE_SIZE << zoomLevel)) - 0.5);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @return the current center point of the MapView.
|
||||
*/
|
||||
@ -55,14 +81,16 @@ public class MapViewPosition {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an immutable MapPosition or null, if this map position is not valid.
|
||||
* @return an immutable MapPosition or null, if this map position is not
|
||||
* valid.
|
||||
* @see #isValid()
|
||||
*/
|
||||
public synchronized MapPosition getMapPosition() {
|
||||
if (!isValid()) {
|
||||
return null;
|
||||
}
|
||||
// Log.d("MapViewPosition", "lat: " + mLatitude + " lon: " + mLongitude);
|
||||
// Log.d("MapViewPosition", "lat: " + mLatitude + " lon: " +
|
||||
// mLongitude);
|
||||
return new MapPosition(mLatitude, mLongitude, mZoomLevel, mScale, mRotation);
|
||||
}
|
||||
|
||||
@ -178,7 +206,8 @@ public class MapViewPosition {
|
||||
|
||||
public synchronized void rotateMap(float angle, float cx, float cy) {
|
||||
moveMap(cx, cy);
|
||||
// Log.d("MapViewPosition", "rotate:" + angle + " " + (mRotation - angle));
|
||||
// Log.d("MapViewPosition", "rotate:" + angle + " " + (mRotation -
|
||||
// angle));
|
||||
mRotation -= angle;
|
||||
}
|
||||
|
||||
@ -186,7 +215,7 @@ public class MapViewPosition {
|
||||
mRotation = f;
|
||||
}
|
||||
|
||||
public void setTile(float f) {
|
||||
public void setTilt(float f) {
|
||||
mTilt = f;
|
||||
}
|
||||
|
||||
@ -199,10 +228,12 @@ public class MapViewPosition {
|
||||
mLatitude = MercatorProjection.limitLatitude(mapPosition.lat);
|
||||
mLongitude = MercatorProjection.limitLongitude(mapPosition.lon);
|
||||
mZoomLevel = mMapView.limitZoomLevel(mapPosition.zoomLevel);
|
||||
mMapScale = 1 << mZoomLevel;
|
||||
}
|
||||
|
||||
synchronized void setZoomLevel(byte zoomLevel) {
|
||||
mZoomLevel = mMapView.limitZoomLevel(zoomLevel);
|
||||
mMapScale = 1 << mZoomLevel;
|
||||
}
|
||||
|
||||
synchronized void setScale(float scale) {
|
||||
@ -226,79 +257,25 @@ public class MapViewPosition {
|
||||
moveMap(pivotX * (1.0f - scale),
|
||||
pivotY * (1.0f - scale));
|
||||
|
||||
float s = mScale * scale;
|
||||
float newScale = mMapScale * scale;
|
||||
|
||||
if (s >= MAX_SCALE) {
|
||||
if (s > 8)
|
||||
int z = FastMath.log2((int) newScale);
|
||||
|
||||
if (z <= 0 || (z >= MAX_ZOOMLEVEL && mScale >= 8))
|
||||
return;
|
||||
|
||||
if (z > MAX_ZOOMLEVEL) {
|
||||
// z16 shows everything, just increase scaling
|
||||
if (mScale * scale > 8)
|
||||
return;
|
||||
|
||||
if (mZoomLevel <= MAX_ZOOMLEVEL) {
|
||||
byte z = (byte) FloatMath.sqrt(s);
|
||||
mZoomLevel += z;
|
||||
s *= 1.0f / (1 << z);
|
||||
}
|
||||
} else if (s < MIN_SCALE) {
|
||||
byte z = (byte) FloatMath.sqrt(1 / s);
|
||||
if (z != 0 && mZoomLevel == 1)
|
||||
return;
|
||||
mZoomLevel -= z;
|
||||
s *= 1 << z;
|
||||
mScale *= scale;
|
||||
mMapScale = newScale;
|
||||
return;
|
||||
}
|
||||
|
||||
mScale = s;
|
||||
mZoomLevel = (byte) z;
|
||||
mScale = newScale / (1 << z);
|
||||
mMapScale = newScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms in or out by the given amount of zoom levels.
|
||||
*
|
||||
* @param zoomLevelDiff
|
||||
* the difference to the current zoom level.
|
||||
* @param s
|
||||
* scale between min/max zoom
|
||||
* @return true if the zoom level was changed, false otherwise.
|
||||
*/
|
||||
// public boolean zoom(byte zoomLevelDiff, float s) {
|
||||
// float scale = s;
|
||||
//
|
||||
// if (zoomLevelDiff > 0) {
|
||||
// // check if zoom in is possible
|
||||
// if (mMapViewPosition.getZoomLevel() + zoomLevelDiff > getMaximumPossibleZoomLevel()) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// scale *= 1.0f / (1 << zoomLevelDiff);
|
||||
// } else if (zoomLevelDiff < 0) {
|
||||
// // check if zoom out is possible
|
||||
// if (mMapViewPosition.getZoomLevel() + zoomLevelDiff < mMapZoomControls.getZoomLevelMin()) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// scale *= 1 << -zoomLevelDiff;
|
||||
// }
|
||||
//
|
||||
// if (scale == 0)
|
||||
// scale = 1;
|
||||
// // else
|
||||
// // scale = Math.round(256.0f * scale) / 256.0f;
|
||||
//
|
||||
// mMapViewPosition.setZoomLevel((byte) (mMapViewPosition.getZoomLevel() + zoomLevelDiff));
|
||||
//
|
||||
// // mapZoomControls.onZoomLevelChange(mapViewPosition.getZoomLevel());
|
||||
//
|
||||
// // zoomAnimator.setParameters(zoomStart, matrixScaleFactor,
|
||||
// // getWidth() >> 1, getHeight() >> 1);
|
||||
// // zoomAnimator.startAnimation();
|
||||
//
|
||||
// // if (scale > MAX_ZOOM) {
|
||||
// // scale = MAX_ZOOM;
|
||||
// // }
|
||||
//
|
||||
// if (zoomLevelDiff != 0 || mZoomFactor != scale) {
|
||||
// mZoomFactor = scale;
|
||||
// redrawTiles();
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -29,7 +29,8 @@ import android.view.animation.DecelerateInterpolator;
|
||||
import android.widget.Scroller;
|
||||
|
||||
/**
|
||||
* Implementation for multi-touch capable devices. TODO write a AnimationTimer instead of using CountDownTimer
|
||||
* Implementation for multi-touch capable devices. TODO write a AnimationTimer
|
||||
* instead of using CountDownTimer
|
||||
*/
|
||||
public class TouchHandler {
|
||||
private static final int INVALID_POINTER_ID = -1;
|
||||
@ -440,7 +441,8 @@ public class TouchHandler {
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector gd) {
|
||||
// Log.d("ScaleListener", "Sum " + mSumScale + " " + (mTimeEnd - mTimeStart));
|
||||
// Log.d("ScaleListener", "Sum " + mSumScale + " " + (mTimeEnd -
|
||||
// mTimeStart));
|
||||
|
||||
if (mTimer == null && mTimeEnd - mTimeStart < 150
|
||||
&& (mSumScale < 0.99 || mSumScale > 1.01)) {
|
||||
@ -449,7 +451,7 @@ public class TouchHandler {
|
||||
|
||||
mZooutOut = mSumScale < 0.99;
|
||||
|
||||
mTimer = new CountDownTimer((int) mScaleDuration, 30) {
|
||||
mTimer = new CountDownTimer((int) mScaleDuration, 15) {
|
||||
@Override
|
||||
public void onTick(long tick) {
|
||||
scale(tick);
|
||||
@ -468,7 +470,7 @@ public class TouchHandler {
|
||||
private float mPrevScale;
|
||||
private CountDownTimer mTimer;
|
||||
boolean mZooutOut;
|
||||
private final float mScaleDuration = 350;
|
||||
private final float mScaleDuration = 450;
|
||||
|
||||
boolean scale(long tick) {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011, 2012 mapsforge.org
|
||||
* Copyright 2012, Hannes Janetzek
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
|
@ -16,19 +16,12 @@ package org.oscim.view.renderer;
|
||||
|
||||
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
|
||||
import static android.opengl.GLES20.GL_BLEND;
|
||||
import static android.opengl.GLES20.GL_DEPTH_TEST;
|
||||
import static android.opengl.GLES20.GL_DYNAMIC_DRAW;
|
||||
import static android.opengl.GLES20.GL_ONE;
|
||||
import static android.opengl.GLES20.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static android.opengl.GLES20.GL_POLYGON_OFFSET_FILL;
|
||||
import static android.opengl.GLES20.GL_STENCIL_BUFFER_BIT;
|
||||
import static android.opengl.GLES20.glBindBuffer;
|
||||
import static android.opengl.GLES20.glBlendFunc;
|
||||
import static android.opengl.GLES20.glBufferData;
|
||||
import static android.opengl.GLES20.glClear;
|
||||
import static android.opengl.GLES20.glClearColor;
|
||||
import static android.opengl.GLES20.glClearStencil;
|
||||
import static android.opengl.GLES20.glDisable;
|
||||
import static android.opengl.GLES20.glEnable;
|
||||
import static android.opengl.GLES20.glGenBuffers;
|
||||
import static android.opengl.GLES20.glViewport;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
@ -69,7 +62,6 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
private static ArrayList<VertexBufferObject> mVBOs;
|
||||
|
||||
private static int mWidth, mHeight;
|
||||
private static float mAspect;
|
||||
|
||||
private static int rotateBuffers = 2;
|
||||
private static ShortBuffer shortBuffer[];
|
||||
@ -86,12 +78,12 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
private static float[] mProjMatrix = new float[16];
|
||||
private static float[] mProjMatrixI = new float[16];
|
||||
|
||||
// curTiles is set by TileLoader and swapped with
|
||||
// drawTiles in onDrawFrame in GL thread.
|
||||
private static TilesData curTiles, drawTiles;
|
||||
// mNextTiles is set by TileLoader and swapped with
|
||||
// mDrawTiles in onDrawFrame in GL thread.
|
||||
private static TilesData mNextTiles, mDrawTiles;
|
||||
|
||||
// flag set by updateVisibleList when current visible tiles
|
||||
// changed. used in onDrawFrame to flip curTiles/drawTiles
|
||||
// changed. used in onDrawFrame to flip mNextTiles/mDrawTiles
|
||||
private static boolean mUpdateTiles;
|
||||
|
||||
private static MapPosition mCurPosition;
|
||||
@ -147,7 +139,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
* current MapPosition
|
||||
* @param tiles
|
||||
* active tiles
|
||||
* @return curTiles (the previously active tiles)
|
||||
* @return mNextTiles (the previously active tiles)
|
||||
*/
|
||||
static TilesData updateTiles(MapPosition mapPosition, TilesData tiles) {
|
||||
GLRenderer.tilelock.lock();
|
||||
@ -155,8 +147,8 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
mCurPosition = mapPosition;
|
||||
|
||||
// unlock previously active tiles
|
||||
for (int i = 0; i < curTiles.cnt; i++) {
|
||||
MapTile t = curTiles.tiles[i];
|
||||
for (int i = 0; i < mNextTiles.cnt; i++) {
|
||||
MapTile t = mNextTiles.tiles[i];
|
||||
boolean found = false;
|
||||
|
||||
for (int j = 0; j < tiles.cnt; j++) {
|
||||
@ -168,8 +160,8 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < drawTiles.cnt; j++) {
|
||||
if (drawTiles.tiles[j] == t) {
|
||||
for (int j = 0; j < mDrawTiles.cnt; j++) {
|
||||
if (mDrawTiles.tiles[j] == t) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@ -180,22 +172,23 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
t.unlock();
|
||||
}
|
||||
|
||||
TilesData tmp = curTiles;
|
||||
curTiles = tiles;
|
||||
TilesData tmp = mNextTiles;
|
||||
mNextTiles = tiles;
|
||||
|
||||
// lock tiles (and their proxies) to not be removed from cache
|
||||
for (int i = 0; i < curTiles.cnt; i++) {
|
||||
MapTile t = curTiles.tiles[i];
|
||||
for (int i = 0; i < mNextTiles.cnt; i++) {
|
||||
MapTile t = mNextTiles.tiles[i];
|
||||
if (!t.isActive)
|
||||
t.lock();
|
||||
}
|
||||
|
||||
for (int j = 0; j < drawTiles.cnt; j++) {
|
||||
MapTile t = drawTiles.tiles[j];
|
||||
for (int j = 0; j < mDrawTiles.cnt; j++) {
|
||||
MapTile t = mDrawTiles.tiles[j];
|
||||
if (!t.isActive)
|
||||
t.lock();
|
||||
}
|
||||
|
||||
// GLThread flips mNextTiles with mDrawTiles
|
||||
mUpdateTiles = true;
|
||||
|
||||
GLRenderer.tilelock.unlock();
|
||||
@ -236,46 +229,6 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
mUpdateColor = true;
|
||||
}
|
||||
|
||||
private static boolean isVisible(MapPosition mapPosition, MapTile tile, float div) {
|
||||
double dx, dy, scale;
|
||||
|
||||
if (div == 0) {
|
||||
dx = tile.pixelX - mapPosition.x;
|
||||
dy = tile.pixelY - mapPosition.y;
|
||||
scale = mapPosition.scale;
|
||||
} else {
|
||||
dx = tile.pixelX - mapPosition.x * div;
|
||||
dy = tile.pixelY - mapPosition.y * div;
|
||||
scale = mapPosition.scale / div;
|
||||
}
|
||||
int size = Tile.TILE_SIZE;
|
||||
int sx = (int) (dx * scale);
|
||||
int sy = (int) (dy * scale);
|
||||
|
||||
// FIXME little hack, need to do scanline check or sth
|
||||
// this kindof works for typical screen aspect
|
||||
if (mRotate) {
|
||||
int ssize = mWidth > mHeight ? mWidth : mHeight;
|
||||
|
||||
if (sy > ssize / 2 || sx > ssize / 2
|
||||
|| sx + size * scale < -ssize / 2
|
||||
|| sy + size * scale < -ssize / 2) {
|
||||
tile.isVisible = false;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (sy > mHeight / 2 || sx > mWidth / 2
|
||||
|| sx + size * scale < -mWidth / 2
|
||||
|| sy + size * scale < -mHeight / 2) {
|
||||
tile.isVisible = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
tile.isVisible = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int uploadCnt = 0;
|
||||
|
||||
private boolean uploadTileData(MapTile tile) {
|
||||
@ -308,7 +261,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
}
|
||||
|
||||
// Log.d(TAG, "uploadTileData, " + tile);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tile.vbo.id);
|
||||
GLES20.glBindBuffer(GL_ARRAY_BUFFER, tile.vbo.id);
|
||||
|
||||
sbuf = shortBuffer[uploadCnt];
|
||||
|
||||
@ -317,8 +270,8 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
// FIXME probably not a good idea to do this in gl thread...
|
||||
if (sbuf == null || sbuf.capacity() < newSize) {
|
||||
ByteBuffer bbuf = ByteBuffer.allocateDirect(newSize * SHORT_BYTES).order(
|
||||
ByteOrder.nativeOrder());
|
||||
ByteBuffer bbuf = ByteBuffer.allocateDirect(newSize * SHORT_BYTES)
|
||||
.order(ByteOrder.nativeOrder());
|
||||
sbuf = bbuf.asShortBuffer();
|
||||
shortBuffer[uploadCnt] = sbuf;
|
||||
sbuf.put(mFillCoords, 0, 8);
|
||||
@ -346,7 +299,6 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
sbuf.flip();
|
||||
|
||||
if (newSize != sbuf.remaining()) {
|
||||
|
||||
Log.d(TAG, "tiles wrong: " + tile + " "
|
||||
+ newSize + " "
|
||||
+ sbuf.position() + " "
|
||||
@ -361,13 +313,16 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
}
|
||||
newSize *= SHORT_BYTES;
|
||||
|
||||
// reuse memory allocated for vbo when possible and allocated
|
||||
// memory is less then four times the new data
|
||||
if (tile.vbo.size > newSize && tile.vbo.size < newSize * 4
|
||||
&& mBufferMemoryUsage < LIMIT_BUFFERS) {
|
||||
GLES20.glBufferSubData(GL_ARRAY_BUFFER, 0, newSize, sbuf);
|
||||
// Log.d(TAG, "reuse buffer " + tile.vbo.size + " " + newSize);
|
||||
} else {
|
||||
mBufferMemoryUsage -= tile.vbo.size;
|
||||
tile.vbo.size = newSize;
|
||||
glBufferData(GL_ARRAY_BUFFER, tile.vbo.size, sbuf, GL_DYNAMIC_DRAW);
|
||||
GLES20.glBufferData(GL_ARRAY_BUFFER, tile.vbo.size, sbuf, GL_DYNAMIC_DRAW);
|
||||
mBufferMemoryUsage += tile.vbo.size;
|
||||
}
|
||||
|
||||
@ -380,47 +335,96 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
}
|
||||
|
||||
private static void checkBufferUsage() {
|
||||
// try to clear some unused vbo when exceding limit
|
||||
if (mBufferMemoryUsage > LIMIT_BUFFERS) {
|
||||
Log.d(TAG, "buffer object usage: " + mBufferMemoryUsage / MB + "MB");
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
int buf[] = new int[1];
|
||||
|
||||
synchronized (mVBOs) {
|
||||
for (VertexBufferObject vbo : mVBOs) {
|
||||
|
||||
if (vbo.size == 0)
|
||||
continue;
|
||||
|
||||
mBufferMemoryUsage -= vbo.size;
|
||||
|
||||
// this should free allocated memory but it does not.
|
||||
// on HTC it causes oom exception?!
|
||||
|
||||
// glBindBuffer(GL_ARRAY_BUFFER, vbo.id);
|
||||
// glBufferData(GL_ARRAY_BUFFER, 0, null,
|
||||
// GLES20.GL_STATIC_DRAW);
|
||||
|
||||
// recreate vbo instead
|
||||
buf[0] = vbo.id;
|
||||
GLES20.glDeleteBuffers(1, buf, 0);
|
||||
GLES20.glGenBuffers(1, buf, 0);
|
||||
vbo.id = buf[0];
|
||||
|
||||
vbo.size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
Log.d(TAG, " > " + mBufferMemoryUsage / MB + "MB");
|
||||
|
||||
if (mBufferMemoryUsage > LIMIT_BUFFERS && CACHE_TILES > 100)
|
||||
CACHE_TILES -= 50;
|
||||
|
||||
} else if (CACHE_TILES < CACHE_TILES_MAX) {
|
||||
CACHE_TILES += 50;
|
||||
if (mBufferMemoryUsage < LIMIT_BUFFERS) {
|
||||
if (CACHE_TILES < CACHE_TILES_MAX)
|
||||
CACHE_TILES += 50;
|
||||
return;
|
||||
}
|
||||
|
||||
// try to clear some unused vbo when exceding limit
|
||||
Log.d(TAG, "buffer object usage: " + mBufferMemoryUsage / MB + "MB");
|
||||
|
||||
int vboIds[] = new int[10];
|
||||
VertexBufferObject[] tmp = new VertexBufferObject[10];
|
||||
|
||||
int removed = 0;
|
||||
synchronized (mVBOs) {
|
||||
for (VertexBufferObject vbo : mVBOs) {
|
||||
|
||||
if (vbo.size == 0)
|
||||
continue;
|
||||
|
||||
mBufferMemoryUsage -= vbo.size;
|
||||
vbo.size = 0;
|
||||
|
||||
// this should free allocated memory but it does not.
|
||||
// on HTC it causes OOM exception?!
|
||||
// glBindBuffer(GL_ARRAY_BUFFER, vbo.id);
|
||||
// glBufferData(GL_ARRAY_BUFFER, 0, null,
|
||||
// GLES20.GL_STATIC_DRAW);
|
||||
|
||||
// recreate vbo instead
|
||||
vboIds[removed] = vbo.id;
|
||||
tmp[removed++] = vbo;
|
||||
|
||||
if (removed == 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
GLES20.glDeleteBuffers(removed, vboIds, 0);
|
||||
GLES20.glGenBuffers(removed, vboIds, 0);
|
||||
|
||||
for (int i = 0; i < removed; i++)
|
||||
tmp[i].id = vboIds[i];
|
||||
|
||||
Log.d(TAG, "now: " + mBufferMemoryUsage / MB + "MB");
|
||||
}
|
||||
|
||||
if (mBufferMemoryUsage > LIMIT_BUFFERS && CACHE_TILES > 100)
|
||||
CACHE_TILES -= 50;
|
||||
}
|
||||
|
||||
private static boolean isVisible(MapPosition mapPosition, MapTile tile) {
|
||||
float dx, dy, scale, div = 1;
|
||||
int diff = mapPosition.zoomLevel - tile.zoomLevel;
|
||||
|
||||
if (diff < 0)
|
||||
div = (1 << -diff);
|
||||
else if (diff > 0)
|
||||
div = (1.0f / (1 << diff));
|
||||
|
||||
scale = mapPosition.scale / div;
|
||||
dx = (float) (tile.pixelX - mapPosition.x * div);
|
||||
dy = (float) (tile.pixelY - mapPosition.y * div);
|
||||
|
||||
int size = Tile.TILE_SIZE;
|
||||
int sx = (int) (dx * scale);
|
||||
int sy = (int) (dy * scale);
|
||||
|
||||
// FIXME little hack, need to do scanline check or sth
|
||||
// this kindof works for typical screen aspect
|
||||
if (mRotate) {
|
||||
int ssize = mWidth > mHeight ? mWidth : mHeight;
|
||||
|
||||
if (sy > ssize / 2 || sx > ssize / 2
|
||||
|| sx + size * scale < -ssize / 2
|
||||
|| sy + size * scale < -ssize / 2) {
|
||||
tile.isVisible = false;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (sy > mHeight / 2 || sx > mWidth / 2
|
||||
|| sx + size * scale < -mWidth / 2
|
||||
|| sy + size * scale < -mHeight / 2) {
|
||||
tile.isVisible = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
tile.isVisible = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean mRotate = false;
|
||||
@ -429,17 +433,16 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
float div, boolean project) {
|
||||
float x, y, scale;
|
||||
|
||||
// if (mRotate) {
|
||||
scale = mapPosition.scale / (div * COORD_MULTIPLIER);
|
||||
x = (float) (tile.pixelX - mapPosition.x * div);
|
||||
y = (float) (tile.pixelY - mapPosition.y * div);
|
||||
|
||||
Matrix.setIdentityM(matrix, 0);
|
||||
|
||||
// scale to tile coordinates
|
||||
// scale to tile to world coordinates
|
||||
Matrix.scaleM(matrix, 0, scale, scale, 1);
|
||||
|
||||
// translate relative to center
|
||||
// translate relative to map center
|
||||
Matrix.translateM(matrix, 0,
|
||||
x * COORD_MULTIPLIER,
|
||||
-(y + Tile.TILE_SIZE) * COORD_MULTIPLIER,
|
||||
@ -450,44 +453,31 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
if (project)
|
||||
Matrix.multiplyMM(matrix, 0, mProjMatrix, 0, matrix, 0);
|
||||
|
||||
// } else {
|
||||
// scale = (float) (2.0 * mapPosition.scale / (mHeight * div));
|
||||
// x = (float) (tile.pixelX - mapPosition.x * div);
|
||||
// y = (float) (tile.pixelY - mapPosition.y * div);
|
||||
//
|
||||
// matrix[12] = x * scale * mAspect;
|
||||
// matrix[13] = -(y + Tile.TILE_SIZE) * scale;
|
||||
// // increase the 'distance' with each tile drawn.
|
||||
// matrix[14] = -0.99f + offset * 0.01f;
|
||||
// matrix[0] = scale * mAspect / COORD_MULTIPLIER;
|
||||
// matrix[5] = scale / COORD_MULTIPLIER;
|
||||
// }
|
||||
}
|
||||
|
||||
private float[] mv = new float[4];
|
||||
private float[] mu = { 1, 1, -1, 1 };
|
||||
|
||||
private float[] mUnprojMatrix = new float[16];
|
||||
|
||||
private void unproject(MapPosition pos, float x, float y) {
|
||||
mu[0] = x;
|
||||
mu[1] = y;
|
||||
mu[2] = -1;
|
||||
|
||||
// add tilt
|
||||
Matrix.multiplyMV(mv, 0, mTmpMatrix, 0, mu, 0);
|
||||
// Log.d(TAG, ">> " + mv[0] + " " + mv[1] + " " + mv[2]);
|
||||
|
||||
Matrix.multiplyMV(mv, 0, mUnprojMatrix, 0, mv, 0);
|
||||
float size = Tile.TILE_SIZE * pos.scale;
|
||||
if (mv[3] != 0) {
|
||||
float w = 1 / mv[3];
|
||||
float xx = Math.round(((mv[0] * w) / size) * 100) / 100f;
|
||||
float yy = Math.round(((mv[1] * w) / size) * 100) / 100f;
|
||||
Log.d(TAG, " " + xx + " " + yy);
|
||||
}
|
||||
}
|
||||
// private float[] mv = new float[4];
|
||||
// private float[] mu = { 1, 1, -1, 1 };
|
||||
//
|
||||
// private float[] mUnprojMatrix = new float[16];
|
||||
//
|
||||
// private void unproject(MapPosition pos, float x, float y) {
|
||||
// mu[0] = x;
|
||||
// mu[1] = y;
|
||||
// mu[2] = -1;
|
||||
//
|
||||
// // add tilt
|
||||
// Matrix.multiplyMV(mv, 0, mTmpMatrix, 0, mu, 0);
|
||||
// // Log.d(TAG, ">> " + mv[0] + " " + mv[1] + " " + mv[2]);
|
||||
//
|
||||
// Matrix.multiplyMV(mv, 0, mUnprojMatrix, 0, mv, 0);
|
||||
// float size = Tile.TILE_SIZE * pos.scale;
|
||||
// if (mv[3] != 0) {
|
||||
// float w = 1 / mv[3];
|
||||
// float xx = Math.round(((mv[0] * w) / size) * 100) / 100f;
|
||||
// float yy = Math.round(((mv[1] * w) / size) * 100) / 100f;
|
||||
// Log.d(TAG, " " + xx + " " + yy);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 glUnused) {
|
||||
@ -498,30 +488,27 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
if (MapView.debugFrameTime)
|
||||
start = SystemClock.uptimeMillis();
|
||||
|
||||
// Note: it seems faster to also clear the stencil buffer even
|
||||
// when not needed. probaly otherwise it is masked out from the
|
||||
// depth buffer as they share the same memory region afaik
|
||||
GLES20.glDepthMask(true);
|
||||
|
||||
// Note: having the impression it is faster to also clear the
|
||||
// stencil buffer even when not needed. probaly otherwise it
|
||||
// is masked out from the depth buffer as they share the same
|
||||
// memory region afaik
|
||||
glClear(GLES20.GL_COLOR_BUFFER_BIT
|
||||
GLES20.glStencilMask(0xFF);
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT
|
||||
| GLES20.GL_DEPTH_BUFFER_BIT
|
||||
// | GLES20.GL_STENCIL_BUFFER_BIT
|
||||
);
|
||||
| GLES20.GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
// get position and current tiles to draw
|
||||
GLRenderer.tilelock.lock();
|
||||
mapPosition = mCurPosition;
|
||||
|
||||
if (mUpdateTiles) {
|
||||
TilesData tmp = drawTiles;
|
||||
drawTiles = curTiles;
|
||||
curTiles = tmp;
|
||||
TilesData tmp = mDrawTiles;
|
||||
mDrawTiles = mNextTiles;
|
||||
mNextTiles = tmp;
|
||||
mUpdateTiles = false;
|
||||
}
|
||||
GLRenderer.tilelock.unlock();
|
||||
|
||||
if (drawTiles == null)
|
||||
if (mDrawTiles == null)
|
||||
return;
|
||||
|
||||
// if (mRotate != (mMapView.enableRotation || mMapView.enableCompass)) {
|
||||
@ -572,12 +559,13 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
}
|
||||
|
||||
if (mUpdateColor && mClearColor != null) {
|
||||
glClearColor(mClearColor[0], mClearColor[1], mClearColor[2], mClearColor[3]);
|
||||
GLES20.glClearColor(mClearColor[0], mClearColor[1], mClearColor[2],
|
||||
mClearColor[3]);
|
||||
mUpdateColor = false;
|
||||
}
|
||||
|
||||
int tileCnt = drawTiles.cnt;
|
||||
MapTile[] tiles = drawTiles.tiles;
|
||||
int tileCnt = mDrawTiles.cnt;
|
||||
MapTile[] tiles = mDrawTiles.tiles;
|
||||
|
||||
uploadCnt = 0;
|
||||
int updateTextures = 0;
|
||||
@ -586,7 +574,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
for (int i = 0; i < tileCnt; i++) {
|
||||
MapTile tile = tiles[i];
|
||||
|
||||
if (!isVisible(mapPosition, tile, 1))
|
||||
if (!isVisible(mapPosition, tile))
|
||||
continue;
|
||||
|
||||
if (tile.texture == null && TextRenderer.drawToTexture(tile))
|
||||
@ -621,32 +609,29 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
if (updateTextures > 0)
|
||||
TextRenderer.compileTextures();
|
||||
|
||||
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
|
||||
GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL);
|
||||
GLES20.glEnable(GL_DEPTH_TEST);
|
||||
GLES20.glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
|
||||
for (int i = 0; i < tileCnt; i++) {
|
||||
if (tiles[i].isVisible && tiles[i].isReady) {
|
||||
drawTile(mapPosition, tiles[i], 1);
|
||||
}
|
||||
if (tiles[i].isVisible && tiles[i].isReady)
|
||||
drawTile(mapPosition, tiles[i]);
|
||||
}
|
||||
|
||||
// proxies are clipped to the region where nothing was drawn to depth
|
||||
// buffer
|
||||
// TODO draw all parent before grandparent
|
||||
// buffer. TODO draw all parent before grandparent
|
||||
for (int i = 0; i < tileCnt; i++) {
|
||||
if (tiles[i].isVisible && !tiles[i].isReady) {
|
||||
if (tiles[i].isVisible && !tiles[i].isReady)
|
||||
drawProxyTile(mapPosition, tiles[i]);
|
||||
}
|
||||
}
|
||||
// GlUtils.checkGlError("end draw");
|
||||
|
||||
GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL);
|
||||
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
|
||||
GLES20.glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
GLES20.glDisable(GL_DEPTH_TEST);
|
||||
|
||||
mDrawCount = 0;
|
||||
mDrawSerial++;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
GLES20.glEnable(GL_BLEND);
|
||||
int z = mapPosition.zoomLevel;
|
||||
float s = mapPosition.scale;
|
||||
|
||||
@ -683,11 +668,20 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
// used to not draw a tile twice per frame...
|
||||
private static byte mDrawSerial = 0;
|
||||
|
||||
private static void drawTile(MapPosition mapPosition, MapTile tile, float div) {
|
||||
private static void drawTile(MapPosition mapPosition, MapTile tile) {
|
||||
// draw parents only once
|
||||
if (tile.lastDraw == mDrawSerial)
|
||||
return;
|
||||
|
||||
float div = 1;
|
||||
|
||||
int diff = mapPosition.zoomLevel - tile.zoomLevel;
|
||||
|
||||
if (diff < 0)
|
||||
div = (1 << -diff);
|
||||
else if (diff > 0)
|
||||
div = (1.0f / (1 << diff));
|
||||
|
||||
tile.lastDraw = mDrawSerial;
|
||||
|
||||
int z = mapPosition.zoomLevel;
|
||||
@ -698,7 +692,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
GLES20.glPolygonOffset(0, mDrawCount++);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tile.vbo.id);
|
||||
GLES20.glBindBuffer(GL_ARRAY_BUFFER, tile.vbo.id);
|
||||
|
||||
LineLayer ll = tile.lineLayers;
|
||||
PolygonLayer pl = tile.polygonLayers;
|
||||
@ -716,10 +710,9 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
pnext = pl.layer;
|
||||
|
||||
if (pl != null && pnext < lnext) {
|
||||
glDisable(GL_BLEND);
|
||||
GLES20.glDisable(GL_BLEND);
|
||||
|
||||
pl = PolygonRenderer.drawPolygons(pl, lnext, mvp, z, s,
|
||||
!clipped);
|
||||
pl = PolygonRenderer.drawPolygons(pl, lnext, mvp, z, s, !clipped);
|
||||
|
||||
clipped = true;
|
||||
|
||||
@ -730,7 +723,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
clipped = true;
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
GLES20.glEnable(GL_BLEND);
|
||||
ll = LineRenderer.drawLines(tile, ll, pnext, mvp, div, z, s);
|
||||
}
|
||||
}
|
||||
@ -747,13 +740,13 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
if (c == null)
|
||||
continue;
|
||||
|
||||
if (!isVisible(mapPosition, c, 2)) {
|
||||
if (!isVisible(mapPosition, c)) {
|
||||
drawn++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c.isReady) {
|
||||
drawTile(mapPosition, c, 2);
|
||||
drawTile(mapPosition, c);
|
||||
drawn++;
|
||||
}
|
||||
}
|
||||
@ -762,36 +755,38 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
// TODO could use tile.proxies here
|
||||
private static void drawProxyTile(MapPosition mapPosition, MapTile tile) {
|
||||
|
||||
boolean drawn = false;
|
||||
if (mapPosition.scale > 1.5f) {
|
||||
// prefer drawing children
|
||||
if (!drawProxyChild(mapPosition, tile)) {
|
||||
MapTile t = tile.rel.parent.tile;
|
||||
if (t != null) {
|
||||
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
|
||||
MapTile t = tile.rel.parent.tile;
|
||||
if (t.isReady) {
|
||||
drawTile(mapPosition, t, 0.5f);
|
||||
} else {
|
||||
MapTile p = t.rel.parent.tile;
|
||||
if (p != null && p.isReady)
|
||||
drawTile(mapPosition, p, 0.25f);
|
||||
drawTile(mapPosition, t);
|
||||
drawn = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!drawn && (tile.proxies & MapTile.PROXY_GRAMPA) != 0) {
|
||||
MapTile t = tile.rel.parent.parent.tile;
|
||||
if (t.isReady)
|
||||
drawTile(mapPosition, t);
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// prefer drawing parent
|
||||
MapTile t = tile.rel.parent.tile;
|
||||
|
||||
if (t != null && t.isReady) {
|
||||
drawTile(mapPosition, t, 0.5f);
|
||||
drawTile(mapPosition, t);
|
||||
|
||||
} else if (!drawProxyChild(mapPosition, tile)) {
|
||||
|
||||
// need to check rel.parent here, t could alread be root
|
||||
if (t != null) {
|
||||
t = t.rel.parent.tile;
|
||||
|
||||
if (t != null && t.isReady)
|
||||
drawTile(mapPosition, t, 0.25f);
|
||||
if ((tile.proxies & MapTile.PROXY_GRAMPA) != 0) {
|
||||
t = tile.rel.parent.parent.tile;
|
||||
if (t.isReady)
|
||||
drawTile(mapPosition, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -811,8 +806,6 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
|
||||
mAspect = (float) height / width;
|
||||
|
||||
// Matrix.orthoM(mProjMatrix, 0, -0.5f / mAspect, 0.5f / mAspect, -0.5f,
|
||||
// 0.5f, -1, 1);
|
||||
|
||||
@ -828,7 +821,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
mProjMatrix[10] = 0;
|
||||
mProjMatrix[14] = 0;
|
||||
|
||||
glViewport(0, 0, width, height);
|
||||
GLES20.glViewport(0, 0, width, height);
|
||||
|
||||
if (!changed && !mNewSurface) {
|
||||
mMapView.redrawMap();
|
||||
@ -845,7 +838,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
// Set up vertex buffer objects
|
||||
int numVBO = (CACHE_TILES + (numTiles * 2));
|
||||
int[] mVboIds = new int[numVBO];
|
||||
glGenBuffers(numVBO, mVboIds, 0);
|
||||
GLES20.glGenBuffers(numVBO, mVboIds, 0);
|
||||
GlUtils.checkGlError("glGenBuffers");
|
||||
|
||||
mVBOs = new ArrayList<VertexBufferObject>(numVBO);
|
||||
@ -857,22 +850,22 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
TextRenderer.setup(numTiles);
|
||||
|
||||
if (mClearColor != null) {
|
||||
glClearColor(mClearColor[0], mClearColor[1],
|
||||
GLES20.glClearColor(mClearColor[0], mClearColor[1],
|
||||
mClearColor[2], mClearColor[3]);
|
||||
} else {
|
||||
glClearColor(0.98f, 0.98f, 0.97f, 1.0f);
|
||||
GLES20.glClearColor(0.98f, 0.98f, 0.97f, 1.0f);
|
||||
}
|
||||
|
||||
GlUtils.checkGlError("onSurfaceChanged");
|
||||
|
||||
glClear(GL_STENCIL_BUFFER_BIT);
|
||||
GLES20.glClear(GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
mMapView.redrawMap();
|
||||
}
|
||||
|
||||
void clearTiles(int numTiles) {
|
||||
drawTiles = new TilesData(numTiles);
|
||||
curTiles = new TilesData(numTiles);
|
||||
mDrawTiles = new TilesData(numTiles);
|
||||
mNextTiles = new TilesData(numTiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -904,9 +897,9 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
// glEnable(GL_SCISSOR_TEST);
|
||||
// glScissor(0, 0, mWidth, mHeight);
|
||||
glClearStencil(0);
|
||||
glDisable(GLES20.GL_CULL_FACE);
|
||||
glBlendFunc(GLES20.GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLES20.glClearStencil(0);
|
||||
GLES20.glDisable(GLES20.GL_CULL_FACE);
|
||||
GLES20.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,8 @@ class LineLayer {
|
||||
private static final float COORD_SCALE = GLRenderer.COORD_MULTIPLIER;
|
||||
// scale factor mapping extrusion vector to short values
|
||||
private static final float DIR_SCALE = 2048;
|
||||
// mask for packing last two bits of extrusion vector with texture coordinates
|
||||
// mask for packing last two bits of extrusion vector with texture
|
||||
// coordinates
|
||||
private static final int DIR_MASK = 0xFFFFFFFC;
|
||||
|
||||
// next layer
|
||||
@ -64,7 +65,8 @@ class LineLayer {
|
||||
}
|
||||
|
||||
/*
|
||||
* line extrusion is based on code from GLMap (https://github.com/olofsj/GLMap/) by olofsj
|
||||
* line extrusion is based on code from GLMap
|
||||
* (https://github.com/olofsj/GLMap/) by olofsj
|
||||
*/
|
||||
|
||||
void addLine(float[] points, short[] index, boolean closed) {
|
||||
|
@ -62,11 +62,10 @@ class LineRenderer {
|
||||
static LineLayer drawLines(MapTile tile, LineLayer layer, int next, float[] matrix,
|
||||
float div, double zoom, float scale) {
|
||||
|
||||
float z = 1 / div;
|
||||
|
||||
if (layer == null)
|
||||
return null;
|
||||
|
||||
// TODO should use fast line program when view is not tilted
|
||||
GLES20.glUseProgram(lineProgram);
|
||||
|
||||
GLES20.glEnableVertexAttribArray(hLineVertexPosition);
|
||||
@ -81,7 +80,9 @@ class LineRenderer {
|
||||
GLES20.glUniformMatrix4fv(hLineMatrix, 1, false, matrix, 0);
|
||||
|
||||
// scale factor to map one pixel on tile to one pixel on screen:
|
||||
float pixel = 2.0f / (scale * z);
|
||||
// only works with orthographic projection
|
||||
float s = scale / div;
|
||||
float pixel = 2.0f / s;
|
||||
|
||||
if (mFast)
|
||||
GLES20.glUniform1f(hLineScale, pixel);
|
||||
@ -89,7 +90,7 @@ class LineRenderer {
|
||||
GLES20.glUniform1f(hLineScale, 0);
|
||||
|
||||
// line scale factor (for non fixed lines)
|
||||
float s = FloatMath.sqrt(scale * z);
|
||||
float lineScale = FloatMath.sqrt(s);
|
||||
boolean blur = false;
|
||||
|
||||
LineLayer l = layer;
|
||||
@ -117,33 +118,31 @@ class LineRenderer {
|
||||
for (LineLayer o = l.outlines; o != null; o = o.outlines) {
|
||||
|
||||
if (line.blur != 0) {
|
||||
GLES20.glUniform1f(hLineScale, (l.width + o.width) / (scale * z)
|
||||
- (line.blur / (scale * z)));
|
||||
GLES20.glUniform1f(hLineScale, (l.width + o.width) / s
|
||||
- (line.blur / s));
|
||||
blur = true;
|
||||
}
|
||||
|
||||
if (zoom > TileGenerator.STROKE_MAX_ZOOM_LEVEL)
|
||||
GLES20.glUniform1f(hLineWidth,
|
||||
(l.width + o.width) / (scale * z));
|
||||
GLES20.glUniform1f(hLineWidth, (l.width + o.width) / s);
|
||||
else
|
||||
GLES20.glUniform1f(hLineWidth, l.width / (scale * z)
|
||||
+ o.width / s);
|
||||
GLES20.glUniform1f(hLineWidth, l.width / s + o.width / lineScale);
|
||||
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, o.offset, o.verticesCnt);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (line.blur != 0) {
|
||||
GLES20.glUniform1f(hLineScale, (l.width / s) * line.blur);
|
||||
GLES20.glUniform1f(hLineScale, (l.width / lineScale) * line.blur);
|
||||
blur = true;
|
||||
}
|
||||
|
||||
if (line.fixed || zoom > TileGenerator.STROKE_MAX_ZOOM_LEVEL) {
|
||||
// invert scaling of extrusion vectors so that line width
|
||||
// stays the same
|
||||
GLES20.glUniform1f(hLineWidth, (l.width / (scale * z)));
|
||||
// stays the same.
|
||||
GLES20.glUniform1f(hLineWidth, l.width / s);
|
||||
} else {
|
||||
GLES20.glUniform1f(hLineWidth, (l.width / s));
|
||||
GLES20.glUniform1f(hLineWidth, l.width / lineScale);
|
||||
}
|
||||
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, l.offset, l.verticesCnt);
|
||||
|
@ -91,13 +91,10 @@ public class MapRenderer extends GLSurfaceView {
|
||||
mInitial = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* called by MapView when position or map settings changes
|
||||
*/
|
||||
/**
|
||||
* Update list of visible tiles and passes them to MapRenderer, when not
|
||||
* available tiles are created and added to JobQueue (mapView.addJobs) for
|
||||
* loading by MapGenerator class
|
||||
* loading by TileGenerator class
|
||||
*
|
||||
* @param clear
|
||||
* ...
|
||||
@ -108,7 +105,7 @@ public class MapRenderer extends GLSurfaceView {
|
||||
boolean changedPos = false;
|
||||
boolean changedZoom = false;
|
||||
|
||||
if (mMapView == null || mMapView.getMapPosition() == null)
|
||||
if (mMapView == null)
|
||||
return;
|
||||
|
||||
MapPosition mapPosition = mMapView.getMapPosition().getMapPosition();
|
||||
@ -121,14 +118,17 @@ public class MapRenderer extends GLSurfaceView {
|
||||
if (clear) {
|
||||
// remove all tiles references
|
||||
Log.d(TAG, "CLEAR");
|
||||
synchronized (GLRenderer.lock) {
|
||||
for (MapTile t : mTiles)
|
||||
clearTile(t);
|
||||
|
||||
mTiles.clear();
|
||||
mTilesLoaded.clear();
|
||||
QuadTree.init();
|
||||
}
|
||||
GLRenderer.tilelock.lock();
|
||||
|
||||
for (MapTile t : mTiles)
|
||||
clearTile(t);
|
||||
|
||||
mTiles.clear();
|
||||
mTilesLoaded.clear();
|
||||
QuadTree.init();
|
||||
GLRenderer.tilelock.unlock();
|
||||
|
||||
mInitial = true;
|
||||
}
|
||||
|
||||
@ -180,21 +180,29 @@ public class MapRenderer extends GLSurfaceView {
|
||||
mTileY = tileY;
|
||||
mPrevZoom = zoomLevel;
|
||||
|
||||
if (changedZoom) {
|
||||
// need to update visible list first when zoom level changes
|
||||
// as scaling is relative to the tiles of current zoom-level
|
||||
updateVisibleList(mapPosition, 0);
|
||||
} else {
|
||||
// pass new position to glThread
|
||||
GLRenderer.updatePosition(mapPosition);
|
||||
}
|
||||
|
||||
GLRenderer.updatePosition(mapPosition);
|
||||
if (!MapView.debugFrameTime)
|
||||
requestRender();
|
||||
|
||||
if (changedPos)
|
||||
if (changedZoom || changedPos) {
|
||||
// need to update visible list first when zoom level changes
|
||||
// as scaling is relative to the tiles of current zoom-level
|
||||
updateVisibleList(mapPosition, zdir);
|
||||
|
||||
if (!MapView.debugFrameTime)
|
||||
requestRender();
|
||||
}
|
||||
// else {
|
||||
// // pass new position to glThread
|
||||
// GLRenderer.updatePosition(mapPosition);
|
||||
// }
|
||||
|
||||
// if (!MapView.debugFrameTime)
|
||||
// requestRender();
|
||||
|
||||
// if (changedPos)
|
||||
// updateVisibleList(mapPosition, zdir);
|
||||
|
||||
if (changedPos || changedZoom) {
|
||||
int remove = mTiles.size() - GLRenderer.CACHE_TILES;
|
||||
if (remove > 50)
|
||||
@ -202,7 +210,6 @@ public class MapRenderer extends GLSurfaceView {
|
||||
}
|
||||
|
||||
limitLoadQueue();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,8 @@ import org.oscim.view.generator.JobTile;
|
||||
class MapTile extends JobTile {
|
||||
|
||||
/**
|
||||
* VBO layout: - 16 bytes fill coordinates, n bytes polygon vertices, m bytes lines vertices
|
||||
* VBO layout: - 16 bytes fill coordinates, n bytes polygon vertices, m
|
||||
* bytes lines vertices
|
||||
*/
|
||||
VertexBufferObject vbo;
|
||||
|
||||
@ -65,8 +66,8 @@ class MapTile extends JobTile {
|
||||
byte lastDraw = 0;
|
||||
|
||||
// keep track which tiles are locked as proxy for this tile
|
||||
// 16: parent
|
||||
// 32: grandparent
|
||||
final static int PROXY_PARENT = 16;
|
||||
final static int PROXY_GRAMPA = 32;
|
||||
// 1-8: children
|
||||
byte proxies;
|
||||
|
||||
@ -99,12 +100,12 @@ class MapTile extends JobTile {
|
||||
MapTile p = rel.parent.tile;
|
||||
|
||||
if (p != null && (p.isReady || p.newData || p.isLoading)) {
|
||||
proxies |= (1 << 4);
|
||||
proxies |= PROXY_PARENT;
|
||||
p.refs++;
|
||||
} else {
|
||||
p = rel.parent.parent.tile;
|
||||
if (p != null && (p.isReady || p.newData || p.isLoading)) {
|
||||
proxies |= (1 << 5);
|
||||
proxies |= PROXY_GRAMPA;
|
||||
p.refs++;
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ import org.oscim.utils.GlUtils;
|
||||
import android.opengl.GLES20;
|
||||
|
||||
class PolygonRenderer {
|
||||
private static final String TAG = "PolygonRenderer";
|
||||
|
||||
private static final int NUM_VERTEX_SHORTS = 2;
|
||||
private static final int POLYGON_VERTICES_DATA_POS_OFFSET = 0;
|
||||
private static int STENCIL_BITS = 8;
|
||||
@ -71,7 +73,7 @@ class PolygonRenderer {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void fillPolygons(int count, double zoom, float scale) {
|
||||
private static void fillPolygons(double zoom, float scale) {
|
||||
boolean blend = false;
|
||||
|
||||
/* draw to framebuffer */
|
||||
@ -83,7 +85,7 @@ class PolygonRenderer {
|
||||
/* only draw where nothing was drawn yet */
|
||||
glEnable(GLES20.GL_DEPTH_TEST);
|
||||
|
||||
for (int c = 0; c < count; c++) {
|
||||
for (int c = mStart; c < mCount; c++) {
|
||||
PolygonLayer l = mFillPolys[c];
|
||||
|
||||
float f = 1.0f;
|
||||
@ -149,8 +151,13 @@ class PolygonRenderer {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
static PolygonLayer drawPolygons(PolygonLayer layer, int next,
|
||||
float[] matrix, double zoom, float scale, boolean clip) {
|
||||
// layers to fill
|
||||
private static int mCount;
|
||||
// stencil buffer index to start fill
|
||||
private static int mStart;
|
||||
|
||||
static PolygonLayer drawPolygons(final PolygonLayer layer, final int next,
|
||||
final float[] matrix, final double zoom, final float scale, boolean first) {
|
||||
|
||||
glUseProgram(polygonProgram);
|
||||
GLES20.glEnableVertexAttribArray(hPolygonVertexPosition);
|
||||
@ -160,33 +167,42 @@ class PolygonRenderer {
|
||||
|
||||
glUniformMatrix4fv(hPolygonMatrix, 1, false, matrix, 0);
|
||||
|
||||
// use stencilbuffer method for polygon drawing
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
|
||||
PolygonLayer l = layer;
|
||||
|
||||
boolean first = clip;
|
||||
int cnt = 0;
|
||||
if (first) {
|
||||
mCount = 0;
|
||||
mStart = 0;
|
||||
} else {
|
||||
mStart = mCount;
|
||||
}
|
||||
|
||||
for (; l != null && l.layer < next; l = l.next) {
|
||||
// fade out polygon layers (set in RederTheme)
|
||||
if (l.area.fade > 0 && l.area.fade > zoom)
|
||||
continue;
|
||||
|
||||
if (cnt == 0) {
|
||||
if (mCount == 0) {
|
||||
// clear stencilbuffer (tile region)
|
||||
|
||||
// disable drawing to framebuffer
|
||||
glColorMask(false, false, false, false);
|
||||
|
||||
// never pass the test: always apply fail op
|
||||
glStencilFunc(GLES20.GL_ALWAYS, 0, 0xFF);
|
||||
glStencilMask(0xFF);
|
||||
// glClear(GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
// clear stencilbuffer (tile region)
|
||||
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
|
||||
|
||||
if (first) {
|
||||
// draw clip-region into depth buffer
|
||||
// draw clip-region into depth buffer:
|
||||
// this is used for lines and polygons
|
||||
|
||||
// write to depth buffer
|
||||
GLES20.glDepthMask(true);
|
||||
|
||||
// to prevent overdraw gl_less restricts
|
||||
// the clip to the area where no other
|
||||
// tile was drawn
|
||||
@ -197,6 +213,7 @@ class PolygonRenderer {
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
// dont modify depth buffer
|
||||
GLES20.glDepthMask(false);
|
||||
// only draw to this tile
|
||||
GLES20.glDepthFunc(GLES20.GL_EQUAL);
|
||||
@ -205,29 +222,50 @@ class PolygonRenderer {
|
||||
// stencil op for stencil method polygon drawing
|
||||
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
|
||||
|
||||
// no need for depth test while drawing stencil
|
||||
glDisable(GLES20.GL_DEPTH_TEST);
|
||||
|
||||
} else if (mCount == mStart) {
|
||||
// disable drawing to framebuffer
|
||||
glColorMask(false, false, false, false);
|
||||
|
||||
// never pass the test: always apply fail op
|
||||
glStencilFunc(GLES20.GL_ALWAYS, 0, 0xFF);
|
||||
|
||||
// stencil op for stencil method polygon drawing
|
||||
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
|
||||
|
||||
// no need for depth test while drawing stencil
|
||||
glDisable(GLES20.GL_DEPTH_TEST);
|
||||
}
|
||||
mFillPolys[cnt] = l;
|
||||
|
||||
mFillPolys[mCount] = l;
|
||||
|
||||
// set stencil mask to draw to
|
||||
glStencilMask(1 << cnt++);
|
||||
glStencilMask(1 << mCount++);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_FAN, l.offset, l.verticesCnt);
|
||||
|
||||
// draw up to 8 layers into stencil buffer
|
||||
if (cnt == STENCIL_BITS) {
|
||||
fillPolygons(cnt, zoom, scale);
|
||||
cnt = 0;
|
||||
if (mCount == STENCIL_BITS) {
|
||||
fillPolygons(zoom, scale);
|
||||
mCount = 0;
|
||||
mStart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cnt > 0)
|
||||
fillPolygons(cnt, zoom, scale);
|
||||
if (mCount > 0)
|
||||
fillPolygons(zoom, scale);
|
||||
|
||||
//
|
||||
// if (mCount > 5){
|
||||
// mCount = 0;
|
||||
// mStart = 0;
|
||||
// }
|
||||
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
if (clip && first)
|
||||
if (first)
|
||||
drawDepthClip();
|
||||
|
||||
GLES20.glDisableVertexAttribArray(hPolygonVertexPosition);
|
||||
|
Loading…
x
Reference in New Issue
Block a user