How to determine UV texture coordinates for n-sided polygon - android

I have generated an n-sided polygon using the code below:
public class Vertex
{
public FloatBuffer floatBuffer; // buffer holding the vertices
public ShortBuffer indexBuffer;
public int numVertices;
public int numIndeces;
public Vertex (float[] vertex)
{
this.setVertices(vertex);
}
public Vertex (float[] vertex, short[] indices)
{
this.setVertices(vertex);
this.setIndices(indices);
}
private void setVertices(float vertex[])
{
// a float has 4 bytes so we allocate for each coordinate 4 bytes
ByteBuffer factory = ByteBuffer.allocateDirect (vertex.length * 4);
factory.order (ByteOrder.nativeOrder ());
// allocates the memory from the byte buffer
floatBuffer = factory.asFloatBuffer ();
// fill the vertexBuffer with the vertices
floatBuffer.put (vertex);
// set the cursor position to the beginning of the buffer
floatBuffer.position (0);
numVertices = vertex.length;
}
protected void setIndices(short[] indices)
{
ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
ibb.order(ByteOrder.nativeOrder());
indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
numIndeces = indices.length;
}
}
Then to create a n-sided polygon:
public class Polygon extends Mesh
{
public Polygon(int lines)
{
this(lines, 1f, 1f);
}
public Polygon(int lines, float xOffset, float yOffset)
{
float vertices[] = new float[lines*3];
float texturevertices[] = new float[lines*2];
short indices[] = new short[lines+1];
for (int i = 0; i < lines;i++)
{
vertices[i*3] = (float) (xOffset * Math.cos(2*Math.PI*i/lines));
vertices[(i*3)+1] = (float) (yOffset * Math.sin(2*Math.PI*i/lines));
vertices[(i*3)+2] = 0.0f;//z
indices[i] = (short)i;
texturevertices[i*2] =(float) (Math.cos(2*Math.PI*i/lines)/2 + 0.5f);
texturevertices[(i*2)+1] = (float) (Math.sin(2*Math.PI*i/lines)/2 + 0.5f);
}
indices[lines] = indices[0];
shape = new Vertex(vertices,indices);
texture = new Vertex(texturevertices, indices);
}
}
and as you can see I am settup up the indeces in-order so that I can render them as a line strip. Now I wish to texture the polygon. How do I do this?
I have tried implementing this:
from here: http://en.wikipedia.org/wiki/UV_mapping
But that result is really poor. How do I go through the coordinates and determine the ordering from texturing?
A related reference can be found here: How to draw a n sided regular polygon in cartesian coordinates?
EDIT I updated according to the answer given by Matic Oblak below and this is the result:
The rotation is of no concern.
This is very close... but no cigar just yet. The original texture is as follows:

If I am reading this correctly you are trying to create a circle from n polygons. There are many ways to use different types of textures and paste them to a shape, the most direct would be to have a texture with a whole shape drawn (for large 'n' it would be a circle) and texture coordinates would be the same as a circle with a center in (.5, .5) and a radius of .5:
//for your case:
u = Math.cos(2*Math.PI*i/lines)/2 + .5
v = Math.sin(2*Math.PI*i/lines)/2 + .5
//the center coordinate should be set to (.5, .5) though
The equations you posted are meant for a sphere and are a bit more complicated since it is hard to even imagine to put it as an image to a 2d surface.
EDIT (from comments):
Creating these triangles is not exactly the same as drawing the line strip. You should use a triangle fan and not triangle strip AND you need to set first point to center of the shape.
public Polygon(int lines, float xOffset, float yOffset)
{
float vertices[] = new float[(lines+1)*3]; //number of angles + center
float texturevertices[] = new float[(lines+1)*2];
short indices[] = new short[lines+2]; //number of vertices + closing
vertices[0*3] = .0f; //set 1st to center
vertices[(0*3)+1] = .0f;
vertices[(0*3)+2] = .0f;
indices[0] = 0;
texturevertices[0] = .5f;
texturevertices[1] = .5f;
for (int i = 0; i < lines;i++)
{
vertices[(i+1)*3] = (float) (xOffset * Math.cos(2*Math.PI*i/lines));
vertices[((i+1)*3)+1] = (float) (yOffset * Math.sin(2*Math.PI*i/lines));
vertices[((i+1)*3)+2] = 0.0f;//z
indices[(i+1)] = (short)i;
texturevertices[(i+1)*2] =(float) (Math.cos(2*Math.PI*i/lines)/2 + 0.5f);
texturevertices[((i+1)*2)+1] = (float) (Math.sin(2*Math.PI*i/lines)/2 + 0.5f);
}
indices[lines+1] = indices[1]; //closing part is same as for i=0
shape = new Vertex(vertices,indices);
texture = new Vertex(texturevertices, indices);
}
Now you just need to draw till index count with triangle FAN. Just a bit of note here to your "offsets", you use xOffset and yOffset as elliptic parameters and not as offsets. If you will be using them as offsets vertices[(i+1)*3] = (float) (xOffset + Math.cos(2*Math.PI*i/lines)); (note '+' instead of '*') then 1st vertex should be at offset instead of (0,0) while texture coordinates remain the same.

Related

Android OpenGL ES textured half sphere

I have to develop an equirectangular image viewer, like the one of the Ricoh Theta app.
I'm doing it on Android, with Open GL ES (1.0, but I can change to 2.0 if needed).
For now, I have managed to create the half sphere (based on this answer), with this code:
public class HalfSphere {
// ---------------------------------------------------------------------------------------------
// region Attributes
private final int[] mTextures = new int[1];
float[][] mVertices;
int mNbStrips;
int mNbVerticesPerStrips;
private final List<FloatBuffer> mVerticesBuffer = new ArrayList<>();
private final List<ByteBuffer> mIndicesBuffer = new ArrayList<>();
private final List<FloatBuffer> mTextureBuffer = new ArrayList<>();
// endregion
// ---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
// region Constructor
public HalfSphere(int nbStrips, int nbVerticesPerStrips, float radius) {
// Generate the vertices:
mNbStrips = nbStrips;
mNbVerticesPerStrips = nbVerticesPerStrips;
mVertices = new float[mNbStrips * mNbVerticesPerStrips][3];
for (int i = 0; i < mNbStrips; i++) {
for (int j = 0; j < mNbVerticesPerStrips; j++) {
mVertices[i * mNbVerticesPerStrips + j][0] = (float) (radius * Math.cos(j * 2 * Math.PI / mNbVerticesPerStrips) * Math.cos(i * Math.PI / mNbStrips));
mVertices[i * mNbVerticesPerStrips + j][1] = (float) (radius * Math.sin(i * Math.PI / mNbStrips));
mVertices[i * mNbVerticesPerStrips + j][2] = (float) (radius * Math.sin(j * 2 * Math.PI / mNbVerticesPerStrips) * Math.cos(i * Math.PI / mNbStrips));
}
}
// Populate the buffers:
for(int i = 0; i < mNbStrips - 1; i++) {
for(int j = 0; j < mNbVerticesPerStrips; j++) {
byte[] indices = {
0, 1, 2, // first triangle (bottom left - top left - top right)
0, 2, 3 // second triangle (bottom left - top right - bottom right)
};
float[] p1 = mVertices[i * mNbVerticesPerStrips + j];
float[] p2 = mVertices[i * mNbVerticesPerStrips + (j + 1) % mNbVerticesPerStrips];
float[] p3 = mVertices[(i + 1) * mNbVerticesPerStrips + (j + 1) % mNbVerticesPerStrips];
float[] p4 = mVertices[(i + 1) * mNbVerticesPerStrips + j];
float[] quad = {
p1[0], p1[1], p1[2],
p2[0], p2[1], p2[2],
p3[0], p3[1], p3[2],
p4[0], p4[1], p4[2]
};
mVerticesBuffer.add(floatArrayToFloatBuffer(quad));
mTextureBuffer.add(floatArrayToFloatBuffer(quad));
mIndicesBuffer.add(byteArrayToByteBuffer(indices));
}
}
}
// endregion
// ---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
// region Draw
public void draw(final GL10 gl) {
// bind the previously generated texture.
gl.glBindTexture(GL10.GL_TEXTURE_2D, this.mTextures[0]);
// Point to our buffers.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Set the face rotation, clockwise in this case.
gl.glFrontFace(GL10.GL_CW);
for(int i = 0; i < mVerticesBuffer.size(); i++) {
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVerticesBuffer.get(i));
gl.glTexCoordPointer(3, GL10.GL_FLOAT, 0, mTextureBuffer.get(i));
gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 6, GL10.GL_UNSIGNED_BYTE, mIndicesBuffer.get(i)); // GL_TRIANGLE_STRIP / GL_LINE_LOOP
}
// Disable the client state before leaving.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
// endregion
// ---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
// region Utils
public void loadGLTexture(GL10 gl, Bitmap texture) {
// Generate one texture pointer, and bind it to the texture array.
gl.glGenTextures(1, this.mTextures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, this.mTextures[0]);
// Create nearest filtered texture.
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
// Use Android GLUtils to specify a two-dimensional texture image from our bitmap.
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0);
texture.recycle();
}
public FloatBuffer floatArrayToFloatBuffer(float[] array) {
ByteBuffer vbb = ByteBuffer.allocateDirect(array.length * 4);
vbb.order(ByteOrder.nativeOrder()); // use the device hardware's native byte order
FloatBuffer fb = vbb.asFloatBuffer(); // create a floating point buffer from the ByteBuffer
fb.put(array); // add the coordinates to the FloatBuffer
fb.position(0); // set the buffer to read the first coordinate
return fb;
}
public ByteBuffer byteArrayToByteBuffer(byte[] array) {
ByteBuffer vbb = ByteBuffer.allocateDirect(array.length * 4);
vbb.order(ByteOrder.nativeOrder()); // use the device hardware's native byte order
vbb.put(array); // add the coordinates to the FloatBuffer
vbb.position(0); // set the buffer to read the first coordinate
return vbb;
}
// endregion
// ---------------------------------------------------------------------------------------------
}
Of course, the texture is not applied correctly, as I'm using the coordinates of my vertices. Does someone see how to do it correctly? I'll also need to be able to "move" the texture when the user pan.
EDIT: as suggested by codetiger, doing lat/180 and lon/360, and then normalizing to [0..1] worked. Now, I'm trying to add the panning. It works when panning on longitude (horizontally):
But not when panning on latitude (vertically):
I'm simply adding values between 0..1 when the user pans. I tried to use the formula given here with no success. Any idea?
If it helps, that's what I want (obtained with the Ricoh Theta app):
In order to make the sphere a full 360 degree sphere, you can replace the lines below.
mVertices[i * mNbVerticesPerStrips + j][0] = (float) (radius * Math.cos(j * 2 * Math.PI / mNbVerticesPerStrips) * Math.cos(2 * i * Math.PI / mNbStrips));
mVertices[i * mNbVerticesPerStrips + j][1] = (float) (radius * Math.sin(2 * i * Math.PI / mNbStrips));
mVertices[i * mNbVerticesPerStrips + j][2] = (float) (radius * Math.sin(j * 2 * Math.PI / mNbVerticesPerStrips) * Math.cos(2 * i * Math.PI / mNbStrips));
The only change is using 2 * Math.PI / mNbStrips for second angle instead of Math.PI / mNbStrips
And to rotate the image, you can rotate the sphere by using
gl.glRotatef(angle, 1.0f, 0.0f, 0.0f);
Update:
To get correct Texture Coordinates for the sphere, for standard distortion sphere texture you can use (lat/180, lon/360) and normalise it to get [0..1]. As mentioned here https://stackoverflow.com/a/10395141/409315

How to fill LineDataSet in MPAndroidChart with LinearGradiant or Array of colors?

I am making one app in which i need to display visible spectrum (400nm to 780nm). I am using MPAndroidchart. I converted wavelength to color and rendered the spectrum. Below is the screenshot of the app. I can display the rendered spectrum on the background grid but how can i display in the lineDataset. LineDataSet only have one function SetFillColor(int). I want to fill the lineDataset with this Paint. This is my code.
Paint paint = new Paint();// = chart.setPaint();
int[] colors =new int[7];
float[] pos = {0.0f, 0.15f, 0.275f, 0.325f, 0.5f,0.6625f,1};
final float[] bands = { 380, 440, 490, 510, 580, 645, 780};
for(int i =0;i<bands.length;i++) {
colors[i]=Wavelength.wvColor(bands[i], gamma);
//Wavelength.wvColor is the function which returns the `int`.
}
paint.setShader(new LinearGradient(0, 0, chart.getWidht(), 0, colors, pos, Shader.TileMode.CLAMP));
In chart, i can easily display it with the below code
chart.setPaint(paint, Chart.PAINT_GRID_BACKGROUND);
Question: How can i fill my LineDataset with the linearGradient or fill with array of colors?
You can find the solution here: https://github.com/PhilJay/MPAndroidChart/issues/1076
public class LineChartRenderer extends com.github.mikephil.charting.renderer.LineChartRenderer {
public LineChartRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
#Override
protected void drawLinearFill(Canvas c, LineDataSet dataSet, List<Entry> entries, int minx, int maxx, Transformer trans) {
mRenderPaint.setStyle(Paint.Style.FILL);
mRenderPaint.setColor(dataSet.getFillColor());
// filled is drawn with less alpha
mRenderPaint.setAlpha(dataSet.getFillAlpha());
Path filled = generateFilledPath(entries, dataSet.getFillFormatter().getFillLinePosition(dataSet, mChart), minx, maxx);
trans.pathValueToPixel(filled);
// GRADIENT BG - SET SHADER
ALog.d(this, "drawLinearFill #LineChartRenderer - c.getHeight()=" + c.getHeight());
mRenderPaint.setShader(new LinearGradient(0, 0, 0, c.getHeight(), AConstant.COLOR_CHART_LINE, AConstant.COLOR_CHART_BG,
Shader.TileMode.CLAMP));
c.drawPath(filled, mRenderPaint);
// restore alpha
mRenderPaint.setAlpha(255);
// GRADIENT BG - REMOVE SHADER
mRenderPaint.setShader(null);
}
/**
* Generates the path that is used for filled drawing.
*
* #param entries
* #return
*/
private Path generateFilledPath(List<Entry> entries, float fillMin, int from, int to) {
ALog.d(this, "generateFilledPath #LineChartRenderer");
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
Path filled = new Path();
filled.moveTo(entries.get(from).getXIndex(), fillMin);
filled.lineTo(entries.get(from).getXIndex(), entries.get(from).getVal() * phaseY);
// create a new path
for (int x = from + 1, count = (int) Math.ceil((to - from) * phaseX + from); x < count; x++) {
Entry e = entries.get(x);
filled.lineTo(e.getXIndex(), e.getVal() * phaseY);
}
// close up
filled.lineTo(entries.get(Math.max(Math.min((int) Math.ceil((to - from) * phaseX + from) - 1, entries.size() - 1), 0))
.getXIndex(), fillMin);
filled.close();
return filled;
}
}

Android OpenGL Circle Texture Doubled

I am trying to draw Circle with texture on it which should be stretched on all vertices.
The problem is that the result i get looks like this:
http://s14.postimg.org/3wyb74469/image.png
I have tried to draw triangle fan as it need to be , first coordinates at 0,0,0
And rest as needed:
http://escience.anu.edu.au/lecture/cg/surfaceModeling/image/surfaceModeling015.png
Also here is the same question and I couldn't get answer for my problem from it :
OpenGL ES, add texture to circle
Loading circle vertices coordinates function:
private final int mVerticesDataSize = 3;
private final int mNumberOfVertices = 180;
private final int mBytesPerFloat = 4;
private float[] vertices;
private FloatBuffer mVerticesBuff;
public void loadCircleVerticesBuff(Context mActivityContext){
mVerticesBuff = ByteBuffer.allocateDirect(mNumberOfVertices * mVerticesDataSize * mBytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertices = new float[mNumberOfVertices * mVerticesDataSize];
float theta = 0;
for (int i = 0; i < (mNumberOfVertices * mVerticesDataSize); i += 3) {
vertices[i] = (float) (((float) 5*Math.cos(theta)));
vertices[i + 1] = (float) ((float) 5*Math.sin(theta));
vertices[i + 2] = 0;
theta += Math.PI / 90;
}
mVerticesBuff.put(vertices);
mVerticesBuff.position(0);
}
Loading circle texture coordinates function:
private final int mTextureCoordinateDataSize = 3;
public void loadCircleTextureBuff(){
mCircleTextureCoordinatesBuff = ByteBuffer.allocateDirect(mNumberOfVertices * mTextureCoordinateDataSize * mBytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
mCircleTextureCoordinatesBuff.put(vertices);
mCircleTextureCoordinatesBuff.position(0);
}
The opengl function used to draw is :
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, mNumberOfVertices);
So solution found (: 1 very very importent thing i missed up / or openGL missed up.
Texture coordinates can be only in range of 0 to 1 .
So here is the trick/solution :
//Build vertices :
vertices[i] = (float) (((float) raduis*Math.cos(theta)))+raduis;
vertices[i + 1] = (float) ((float) raduis*Math.sin(theta))+raduis;
// Build texture :
for (int i = 0; i < (mNumberOfVertices * mVerticesDataSize); i += 3) {
vertices[i] = (vertices[i])/(raduis*2);
vertices[i + 1] = (vertices[i+1])/(raduis*2);
vertices[i + 2] = 0;
}
And here is the res :
http://s2.postimg.org/tno4jr4y1/image.png
Dont forget to flip texture vertices as i forgot (:

colourful polylines in android maps api v2

I want to draw polyline in android maps api version 2. I want it to have many colors, preferably with gradients. It seems to me though, that polylines are allowed to have only single color.
How can I do that? I already have api-v1 overlay drawing what I like, so presumably I can reuse some code
public class RouteOverlayGoogle extends Overlay {
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
//(...) draws line with color representing speed
}
I know it's been a pretty long time since this has been asked, but there are still no gradient polylines (as of writing, ~may 2015) and drawing multiple polylines really doesn't cut it (jagged edges, quite a bit of lag when dealing with several hundred of points, just not very visually appealing).
When I had to implement gradient polylines, what I ended up doing was implementing a TileOverlay that would render the polyline to a canvas and then rasterize it (see this gist for the specific code I wrote to do it https://gist.github.com/Dagothig/5f9cf0a4a7a42901a7b2).
The implementation doesn't try to do any sort of viewport culling because I ended up not needing it to reach the performance I wanted (I'm not sure about the numbers, but it was under a second per tiles, and multiple tiles will be rendered at the same time).
Rendering the gradient polyline can be pretty tricky to get properly however since you're dealing with varying viewports (positions and size): more than that, I hit a few issues with the limit on float precision at high zoom levels (20+). In the end I didn't use the scale and translate functions from the canvas because I would get weird corruption issues.
Something else to watch out for if you use a similar data structure to what I had (latitudes, longitudes and timestamps) is that you need multiple segments to render the gradient properly (I ended up working with 3 points at a time).
For posterity's sake, I'm going to also leave the code from the gist here:
(the projections are done using https://github.com/googlemaps/android-maps-utils if you're wondering where com.google.maps.android.projection.SphericalMercatorProjection comes from)
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileProvider;
import com.google.maps.android.SphericalUtil;
import com.google.maps.android.geometry.Point;
import com.google.maps.android.projection.SphericalMercatorProjection;
import java.io.ByteArrayOutputStream;
import java.util.List;
/**
* Tile overlay used to display a colored polyline as a replacement for the non-existence of gradient
* polylines for google maps
*/
public class ColoredPolylineTileOverlay<T extends ColoredPolylineTileOverlay.PointHolder> implements TileProvider {
public static final double LOW_SPEED_CLAMP_KMpH = 0;
public static final double LOW_SPEED_CLAMP_MpS = 0;
// TODO: calculate speed as highest speed of pointsCollection
public static final double HIGH_SPEED_CLAMP_KMpH = 50;
public static final double HIGH_SPEED_CLAMP_MpS = HIGH_SPEED_CLAMP_KMpH * 1000 / (60 * 60);
public static final int BASE_TILE_SIZE = 256;
public static int[] getSpeedColors(Context context) {
return new int[] {
context.getResources().getColor(R.color.polyline_low_speed),
context.getResources().getColor(R.color.polyline_med_speed),
context.getResources().getColor(R.color.polyline_high_speed)
};
}
public static float getSpeedProportion(double metersPerSecond) {
return (float)(Math.max(Math.min(metersPerSecond, HIGH_SPEED_CLAMP_MpS), LOW_SPEED_CLAMP_MpS) / HIGH_SPEED_CLAMP_MpS);
}
public static int interpolateColor(int[] colors, float proportion) {
int rTotal = 0, gTotal = 0, bTotal = 0;
// We correct the ratio to colors.length - 1 so that
// for i == colors.length - 1 and p == 1, then the final ratio is 1 (see below)
float p = proportion * (colors.length - 1);
for (int i = 0; i < colors.length; i++) {
// The ratio mostly resides on the 1 - Math.abs(p - i) calculation :
// Since for p == i, then the ratio is 1 and for p == i + 1 or p == i -1, then the ratio is 0
// This calculation works BECAUSE p lies within [0, length - 1] and i lies within [0, length - 1] as well
float iRatio = Math.max(1 - Math.abs(p - i), 0.0f);
rTotal += (int)(Color.red(colors[i]) * iRatio);
gTotal += (int)(Color.green(colors[i]) * iRatio);
bTotal += (int)(Color.blue(colors[i]) * iRatio);
}
return Color.rgb(rTotal, gTotal, bTotal);
}
protected final Context context;
protected final PointCollection<T> pointsCollection;
protected final int[] speedColors;
protected final float density;
protected final int tileDimension;
protected final SphericalMercatorProjection projection;
// Caching calculation-related stuff
protected LatLng[] trailLatLngs;
protected Point[] projectedPts;
protected Point[] projectedPtMids;
protected double[] speeds;
public ColoredPolylineTileOverlay(Context context, PointCollection pointsCollection) {
super();
this.context = context;
this.pointsCollection = pointsCollection;
speedColors = getSpeedColors(context);
density = context.getResources().getDisplayMetrics().density;
tileDimension = (int)(BASE_TILE_SIZE * density);
projection = new SphericalMercatorProjection(BASE_TILE_SIZE);
calculatePointsAndSpeeds();
}
public void calculatePointsAndSpeeds() {
trailLatLngs = new LatLng[pointsCollection.getPoints().size()];
projectedPts = new Point[pointsCollection.getPoints().size()];
projectedPtMids = new Point[Math.max(pointsCollection.getPoints().size() - 1, 0)];
speeds = new double[Math.max(pointsCollection.getPoints().size() - 1, 0)];
List<T> points = pointsCollection.getPoints();
for (int i = 0; i < points.size(); i++) {
T point = points.get(i);
LatLng latLng = point.getLatLng();
trailLatLngs[i] = latLng;
projectedPts[i] = projection.toPoint(latLng);
// Mids
if (i > 0) {
LatLng previousLatLng = points.get(i - 1).getLatLng();
LatLng latLngMid = SphericalUtil.interpolate(previousLatLng, latLng, 0.5);
projectedPtMids[i - 1] = projection.toPoint(latLngMid);
T previousPoint = points.get(i - 1);
double speed = SphericalUtil.computeDistanceBetween(latLng, previousLatLng) / ((point.getTime() - previousPoint.getTime()) / 1000.0);
speeds[i - 1] = speed;
}
}
}
#Override
public Tile getTile(int x, int y, int zoom) {
// Because getTile can be called asynchronously by multiple threads, none of the info we keep in the class will be modified
// (getTile is essentially side-effect-less) :
// Instead, we create the bitmap, the canvas and the paints specifically for the call to getTile
Bitmap bitmap = Bitmap.createBitmap(tileDimension, tileDimension, Bitmap.Config.ARGB_8888);
// Normally, instead of the later calls for drawing being offset, we would offset them using scale() and translate() right here
// However, there seems to be funky issues related to float imprecisions that happen at large scales when using this method, so instead
// The points are offset properly when drawing
Canvas canvas = new Canvas(bitmap);
Matrix shaderMat = new Matrix();
Paint gradientPaint = new Paint();
gradientPaint.setStyle(Paint.Style.STROKE);
gradientPaint.setStrokeWidth(3f * density);
gradientPaint.setStrokeCap(Paint.Cap.BUTT);
gradientPaint.setStrokeJoin(Paint.Join.ROUND);
gradientPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
gradientPaint.setShader(new LinearGradient(0, 0, 1, 0, speedColors, null, Shader.TileMode.CLAMP));
gradientPaint.getShader().setLocalMatrix(shaderMat);
Paint colorPaint = new Paint();
colorPaint.setStyle(Paint.Style.STROKE);
colorPaint.setStrokeWidth(3f * density);
colorPaint.setStrokeCap(Paint.Cap.BUTT);
colorPaint.setStrokeJoin(Paint.Join.ROUND);
colorPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
// See https://developers.google.com/maps/documentation/android/views#zoom for handy info regarding what zoom is
float scale = (float)(Math.pow(2, zoom) * density);
renderTrail(canvas, shaderMat, gradientPaint, colorPaint, scale, x, y);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
return new Tile(tileDimension, tileDimension, baos.toByteArray());
}
public void renderTrail(Canvas canvas, Matrix shaderMat, Paint gradientPaint, Paint colorPaint, float scale, int x, int y) {
List<T> points = pointsCollection.getPoints();
double speed1, speed2;
MutPoint pt1 = new MutPoint(), pt2 = new MutPoint(), pt3 = new MutPoint(), pt1mid2 = new MutPoint(), pt2mid3 = new MutPoint();
// Guard statement: if the trail is only 1 point, just render the point by itself as a speed of 0
if (points.size() == 1) {
pt1.set(projectedPts[0], scale, x, y, tileDimension);
speed1 = 0;
float speedProp = getSpeedProportion(speed1);
colorPaint.setStyle(Paint.Style.FILL);
colorPaint.setColor(interpolateColor(speedColors, speedProp));
canvas.drawCircle((float) pt1.x, (float) pt1.y, colorPaint.getStrokeWidth() / 2f, colorPaint);
colorPaint.setStyle(Paint.Style.STROKE);
return;
}
// Guard statement: if the trail is exactly 2 points long, just render a line from A to B at d(A, B) / t speed
if (points.size() == 2) {
pt1.set(projectedPts[0], scale, x, y, tileDimension);
pt2.set(projectedPts[1], scale, x, y, tileDimension);
speed1 = speeds[0];
float speedProp = getSpeedProportion(speed1);
drawLine(canvas, colorPaint, pt1, pt2, speedProp);
return;
}
// Because we want to be displaying speeds as color ratios, we need multiple points to do it properly:
// Since we use calculate the speed using the distance and the time, we need at least 2 points to calculate the distance;
// this means we know the speed for a segment, not a point.
// Furthermore, since we want to be easing the color changes between every segment, we have to use 3 points to do the easing;
// every line is split into two, and we ease over the corners
// This also means the first and last corners need to be extended to include the first and last points respectively
// Finally (you can see about that in getTile()) we need to offset the point projections based on the scale and x, y because
// weird display behaviour occurs
for (int i = 2; i < points.size(); i++) {
pt1.set(projectedPts[i - 2], scale, x, y, tileDimension);
pt2.set(projectedPts[i - 1], scale, x, y, tileDimension);
pt3.set(projectedPts[i], scale, x, y, tileDimension);
// Because we want to split the lines in two to ease over the corners, we need the middle points
pt1mid2.set(projectedPtMids[i - 2], scale, x, y, tileDimension);
pt2mid3.set(projectedPtMids[i - 1], scale, x, y, tileDimension);
// The speed is calculated in meters per second (same format as the speed clamps); because getTime() is in millis, we need to correct for that
speed1 = speeds[i - 2];
speed2 = speeds[i - 1];
float speed1Prop = getSpeedProportion(speed1);
float speed1to2Prop = getSpeedProportion((speed1 + speed2) / 2);
float speed2Prop = getSpeedProportion(speed2);
// Circle for the corner (removes the weird empty corners that occur otherwise)
colorPaint.setStyle(Paint.Style.FILL);
colorPaint.setColor(interpolateColor(speedColors, speed1to2Prop));
canvas.drawCircle((float)pt2.x, (float)pt2.y, colorPaint.getStrokeWidth() / 2f, colorPaint);
colorPaint.setStyle(Paint.Style.STROKE);
// Corner
// Note that since for the very first point and the very last point we don't split it in two, we used them instead.
drawLine(canvas, shaderMat, gradientPaint, colorPaint, i - 2 == 0 ? pt1 : pt1mid2, pt2, speed1Prop, speed1to2Prop);
drawLine(canvas, shaderMat, gradientPaint, colorPaint, pt2, i == points.size() - 1 ? pt3 : pt2mid3, speed1to2Prop, speed2Prop);
}
}
/**
* Note: it is assumed the shader is 0, 0, 1, 0 (horizontal) so that it lines up with the rotation
* (rotations are usually setup so that the angle 0 points right)
*/
public void drawLine(Canvas canvas, Matrix shaderMat, Paint gradientPaint, Paint colorPaint, MutPoint pt1, MutPoint pt2, float ratio1, float ratio2) {
// Degenerate case: both ratios are the same; we just handle it using the colorPaint (handling it using the shader is just messy and ineffective)
if (ratio1 == ratio2) {
drawLine(canvas, colorPaint, pt1, pt2, ratio1);
return;
}
shaderMat.reset();
// PS: don't ask me why this specfic orders for calls works but other orders will mess up
// Since every call is pre, this is essentially ordered as (or my understanding is that it is):
// ratio translate -> ratio scale -> scale to pt length -> translate to pt start -> rotate
// (my initial intuition was to use only post calls and to order as above, but it resulted in odd corruptions)
// Setup based on points:
// We translate the shader so that it is based on the first point, rotated towards the second and since the length of the
// gradient is 1, then scaling to the length of the distance between the points makes it exactly as long as needed
shaderMat.preRotate((float) Math.toDegrees(Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x)), (float)pt1.x, (float)pt1.y);
shaderMat.preTranslate((float)pt1.x, (float)pt1.y);
float scale = (float)Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
shaderMat.preScale(scale, scale);
// Setup based on ratio
// By basing the shader to the first ratio, we ensure that the start of the gradient corresponds to it
// The inverse scaling of the shader means that it takes the full length of the call to go to the second ratio
// For instance; if d(ratio1, ratio2) is 0.5, then the shader needs to be twice as long so that an entire call (1)
// Results in only half of the gradient being used
shaderMat.preScale(1f / (ratio2 - ratio1), 1f / (ratio2 - ratio1));
shaderMat.preTranslate(-ratio1, 0);
gradientPaint.getShader().setLocalMatrix(shaderMat);
canvas.drawLine(
(float)pt1.x,
(float)pt1.y,
(float)pt2.x,
(float)pt2.y,
gradientPaint
);
}
public void drawLine(Canvas canvas, Paint colorPaint, MutPoint pt1, MutPoint pt2, float ratio) {
colorPaint.setColor(interpolateColor(speedColors, ratio));
canvas.drawLine(
(float)pt1.x,
(float)pt1.y,
(float)pt2.x,
(float)pt2.y,
colorPaint
);
}
public interface PointCollection<T extends PointHolder> {
List<T> getPoints();
}
public interface PointHolder {
LatLng getLatLng();
long getTime();
}
public static class MutPoint {
public double x, y;
public MutPoint set(Point point, float scale, int x, int y, int tileDimension) {
this.x = point.x * scale - x * tileDimension;
this.y = point.y * scale - y * tileDimension;
return this;
}
}
}
Note that this implementation assumes two relatively large things:
the polyline is already complete
that there is only one polyline.
I would assume handling (1) would not be very difficult. However, if you intend to draw multiple polylines this way, you may need to look at some ways to enhance performance (keeping a bounding box of the polylines to be able to easily discard those that do not fit the viewport for one).
One more thing to remember regarding using a TileOverlay is that it is rendered after movements are done, not during; so you may want to back up the overlay with an actual monochrome polyline underneath it to give it some continuity.
PS: this is the first time I try to answer a question, so if there's anything I should fix or do differently please tell me.
One simple solution: draw multiple polylines and individually set the color.

OpenGL ES Texture Mapping Overflow

I want to animate a 2d Sprite sheet. I hava a sprite sheet with a lot of character animation with different frame size. For a single animation, I scale a vertex to fit one frame and then change Texture position for animation. Works pretty well for one animation, but when switching to another animation with different frame size and scale vertex and fitting texture again, I get side effect where texture is stretcht and not fitting, it is just on one animation frame, but make the change between two animations look very bad.
I think, that is because of the vertex-size change. So my idea ist, to have a fixed vertex size and fit the texture without strechting it to the full vertex (height for every animation is fixed).
Maybe a image will help, so I created one:
Here is my code, hope it is enough:
public boolean nextFrame() {
float textureWidth = textureMap()[currentAnimation][0];
float frameCount = textureMap()[currentAnimation][1];
float frameWidth = textureWidth / frameCount;
if (loop) {
if (currentFrame == frameCount)
currentFrame = 0;
} else {
if (currentFrame == frameCount) {
setAnimation(AnimationConstants.IDLE);
loop = true;
return false;
}
}
float x_left = (float) currentFrame * frameWidth / textureWidth;
float x_right = (float) (currentFrame * frameWidth + frameWidth)
/ textureWidth;
texture[0] = x_left; // top left x
texture[1] = 1.0f; // top left y
texture[2] = x_left; // bottom left x
texture[3] = 0.0f; // bottom left y
texture[4] = x_right; // top right x
texture[5] = 1.0f; // top right y
texture[6] = x_right; // bottom right x
texture[7] = 0.0f; // bottom right y
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
currentFrame++;
return true;
}
private void newVertex() {
float textureWidth = textureMap()[currentAnimation][0];
float frameCount = textureMap()[currentAnimation][1];
float frameWidth = textureWidth / frameCount;
float width = (float) frameWidth / (float) frameHeight;
vertices[0] = pos_x; // bottom left x
vertices[1] = pos_y; // bottom left y
vertices[3] = pos_x; // top left x
vertices[4] = pos_y + (1.0f * scale); // top left y
vertices[6] = pos_x + (width * scale); // bottom right x
vertices[7] = pos_y; // bottom right y
vertices[9] = pos_x + (width * scale); // top right x
vertices[10] = pos_y + (1.0f * scale); // top right y
// z values
vertices[2] = -0.2f; // bottom left z
vertices[5] = -0.2f; // top left z
vertices[8] = -0.2f; // bottom right z
vertices[11] = -0.2f; // top right z
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
}
so for every new animation, I call newVertex().
Check this out.
I suppose you should use the same general idea. If needed I can describe in detail how to place texture correctly in your case.

Categories

Resources