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
Related
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 (:
Im trying to create a sphere in opengl es 2.0 but I getting a null pointer exception. I've been stuck on this for hours. After checking the logcat it seems to find a couple of vertices but then the index buffer always hits null pointer straight after.
private void generateSphereCoords(float radius, int stacks, int slices) {
for (int stackNumber = 0; stackNumber <= stacks; stackNumber++) {
for (int sliceNumber = 0; sliceNumber <= slices; sliceNumber++) {
float theta = (float) (stackNumber * Math.PI / stacks);
float phi = (float) (sliceNumber * 2 * Math.PI / slices);
Log.i("theta", String.valueOf(theta));
Log.i("phi", String.valueOf(phi));
float sinTheta = FloatMath.sin(theta);
float sinPhi = FloatMath.sin(phi);
float cosTheta = FloatMath.cos(theta);
float cosPhi = FloatMath.cos(phi);
vertices = new float[]{radius * cosPhi * cosTheta, radius * sinPhi * cosTheta, radius * sinTheta};
Log.i("vertexX", String.valueOf(vertices[0]));
Log.i("vertexY", String.valueOf(vertices[1]));
Log.i("vertexZ", String.valueOf(vertices[2]));
// a float is 4 bytes, therefore I multiply the number of vertices by 4.
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);// (number of coordinate values * 4 bytes per float)
vbb.order(ByteOrder.nativeOrder());// use the device hardware's native byte order
vertexBuffer = vbb.asFloatBuffer();// create a floating point buffer from the ByteBuffer
vertexBuffer.put(vertices);
vertexBuffer.position(0);// set the buffer to read the first coordinate
}
}
for (int stackNumber = 0; stackNumber <= stacks; stackNumber++) {
for (int sliceNumber = 0; sliceNumber <= slices; sliceNumber++) {
ByteBuffer ibb = ByteBuffer.allocateDirect(vertexCount * 2);// (number of coordinate values * 4 bytes per float)
ibb.order(ByteOrder.nativeOrder());// use the device hardware's native byte order
indexBuffer = ibb.asShortBuffer();// create a floating point buffer from the ByteBuffer
indexBuffer.put((short) ((stackNumber * slices) + (sliceNumber % slices)));
indexBuffer.put((short) (((stackNumber + 1) * slices) + (sliceNumber % slices)));
indexBuffer.position(0);// set the buffer to read the first coordinate
}
}
}
Create your vertex buffer and index buffer outside of your loops. As it is you are recreating the vertex buffer for each vertex and the index buffer for each pair of indices.
private static final int VERTICES_PER_COORD = 3;
private void generateSphereCoords(float radius, int stacks, int slices) {
// Create vertex buffer here:
ByteBuffer vbb = ByteBuffer.allocateDirect((stacks + 1) * (slices + 1) * VERTICES_PER_COORD * 4);// (number of coordinate values * 4 bytes per float)
vbb.order(ByteOrder.nativeOrder());// use the device hardware's native byte order
vertexBuffer = vbb.asFloatBuffer();// create a floating point buffer from the ByteBuffer
for (int stackNumber = 0; stackNumber <= stacks; stackNumber++) {
for (int sliceNumber = 0; sliceNumber <= slices; sliceNumber++) {
float theta = (float) (stackNumber * Math.PI / stacks);
float phi = (float) (sliceNumber * 2 * Math.PI / slices);
Log.i("theta", String.valueOf(theta));
Log.i("phi", String.valueOf(phi));
float sinTheta = FloatMath.sin(theta);
float sinPhi = FloatMath.sin(phi);
float cosTheta = FloatMath.cos(theta);
float cosPhi = FloatMath.cos(phi);
vertices = new float[]{radius * cosPhi * cosTheta, radius * sinPhi * cosTheta, radius * sinTheta};
Log.i("vertexX", String.valueOf(vertices[0]));
Log.i("vertexY", String.valueOf(vertices[1]));
Log.i("vertexZ", String.valueOf(vertices[2]));
// a float is 4 bytes, therefore I multiply the number of vertices by 4.
// add this vertex tp the buffer:
vertexBuffer.put(vertices);
}
}
vertexBuffer.position(0);// set the buffer to read the first coordinate
// Create index buffer here:
ByteBuffer ibb = ByteBuffer.allocateDirect((stacks + 1) * (slices + 1) * 2 * 2);// (number of index values * 2 bytes per short)
ibb.order(ByteOrder.nativeOrder());// use the device hardware's native byte order
indexBuffer = ibb.asShortBuffer();// create a floating point buffer from the ByteBuffer
for (int stackNumber = 0; stackNumber <= stacks; stackNumber++) {
for (int sliceNumber = 0; sliceNumber <= slices; sliceNumber++) {
indexBuffer.put((short) ((stackNumber * slices) + (sliceNumber % slices)));
indexBuffer.put((short) (((stackNumber + 1) * slices) + (sliceNumber % slices)));
}
}
indexBuffer.position(0);// set the buffer to read the first index
}
I have created sound visualization using felixpalmer library. Now i have to modify his code to look like this.How could i achieve empty spaces between bars. like
I will handle the color scheme but don't know how to add empty spaces in bars and shadow effect.
BarGraphRender
public class BarGraphRenderer extends Renderer
{
private int mDivisions;
private Paint mPaint;
private boolean mTop;
/**
* Renders the FFT data as a series of lines, in histogram form
* #param divisions - must be a power of 2. Controls how many lines to draw
* #param paint - Paint to draw lines with
* #param top - whether to draw the lines at the top of the canvas, or the bottom
*/
public BarGraphRenderer(int divisions , Paint paint , boolean top)
{
super();
mDivisions = divisions;
mPaint = paint;
mTop = top;
}
#Override
public void onRender(Canvas canvas, AudioData data, Rect rect)
{
// Do nothing, we only display FFT data
}
#Override
public void onRender(Canvas canvas, FFTData data, Rect rect)
{
for (int i = 0; i < data.bytes.length / mDivisions; i++)
{
mFFTPoints[i * 4] = i * 4 * mDivisions;
mFFTPoints[i * 4 + 2] = i * 4 * mDivisions;
byte rfk = data.bytes[mDivisions * i];
byte ifk = data.bytes[mDivisions * i + 1];
float magnitude = (rfk * rfk + ifk * ifk);
int dbValue = (int) (10 * Math.log10(magnitude));
if(mTop)
{
mFFTPoints[i * 4 + 1] = 0;
mFFTPoints[i * 4 + 3] = (dbValue * 2 - 10);
}
else
{
mFFTPoints[i * 4 + 1] = rect.height();
mFFTPoints[i * 4 + 3] = rect.height() - (dbValue * 2 - 10);
}
}
canvas.drawLines(mFFTPoints, mPaint);
}
}
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.
I am using AndEngine.
Now the problem is that I want to draw bezier curves on the scene. I know there is not built in functionality to do this.
So I computed the points etc in a custom fashion. Now I am stack that how can I draw the lines on the scene.
I overridded the method
myScene = new Scene()
{
#Override
protected void onManagedDraw(GL10 pGL, Camera pCamera) {
log("Draw","in Draw");
super.onManagedDraw(pGL, pCamera);
}
};
The log is working fine. The code I used for line drawing.
public static void DrawQuadBezier(GL10 gl, CGPoint origin, CGPoint control,
CGPoint destination, int segments) {
FastFloatBuffer vertices = getVertices(2 * (segments + 1));
float t = 0.0f;
for(int i = 0; i < segments; i++) {
float x = (float)Math.pow(1 - t, 2) * origin.x + 2.0f * (1 - t) * t * control.x + t * t * destination.x;
float y = (float)Math.pow(1 - t, 2) * origin.y + 2.0f * (1 - t) * t * control.y + t * t * destination.y;
vertices.put(x);
vertices.put(y);
t += 1.0f / segments;
}
vertices.put(destination.x);
vertices.put(destination.y);
vertices.position(0);
gl.glDisable(GL10.GL_TEXTURE_2D);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
gl.glVertexPointer(2, GL_FLOAT, 0, vertices.bytes);
gl.glDrawArrays(GL_LINE_STRIP, 0, segments + 1);
// restore default state
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glEnable(GL10.GL_TEXTURE_2D);
}
Now when I call this method giving it the required parameters nothing is drawn on the screeen. Can any one help me with it. I will be very thankful.