/* * Copyright 2014 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cardboard.photosphere; import android.content.Context; import android.opengl.GLES20; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; public class Sphere { /** * Maximum allowed depth. */ private static final int MAXIMUM_ALLOWED_DEPTH = 5; /** * Used in vertex strip calculations, related to properties of a icosahedron. */ private static final int VERTEX_MAGIC_NUMBER = 5; /** * Each vertex is a 2D coordinate. */ private static final int NUM_FLOATS_PER_VERTEX = 3; /** * Each texture is a 2D coordinate. */ private static final int NUM_FLOATS_PER_TEXTURE = 2; /** * Each vertex is made up of 3 points, x, y, z. */ private static final int AMOUNT_OF_NUMBERS_PER_VERTEX_POINT = 3; /** * Each texture point is made up of 2 points, x, y (in reference to the texture being a 2D image). */ private static final int AMOUNT_OF_NUMBERS_PER_TEXTURE_POINT = 2; /** * Buffer holding the vertices. */ private final List<FloatBuffer> mVertexBuffer = new ArrayList<FloatBuffer>(); /** * The vertices for the sphere. */ private final List<float[]> mVertices = new ArrayList<float[]>(); /** * Buffer holding the texture coordinates. */ private final List<FloatBuffer> mTextureBuffer = new ArrayList<FloatBuffer>(); /** * Mapping texture coordinates for the vertices. */ private final List<float[]> mTexture = new ArrayList<float[]>(); /** * Total number of strips for the given depth. */ private final int mTotalNumStrips; // number of coordinates per vertex in this array static final int COORDS_PER_VERTEX = 3; // Use to access and set the view transformation private int mMVPMatrixHandle; private int mPositionHandle; private int mProgramHandle; private int mTextureCoordinateHandle; private int mTextureDataHandle0; private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex public Sphere(final Context context, final int depth, final float radius) { final String vertexShader = getVertexShader(context); final String fragmentShader = getFragmentShader(context); final int vertexShaderHandle = ShaderHelper.compileShader( GLES20.GL_VERTEX_SHADER, vertexShader); final int fragmentShaderHandle = ShaderHelper.compileShader( GLES20.GL_FRAGMENT_SHADER, fragmentShader); mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, new String[]{"a_Position", "a_TexCoordinate"}); // Set our per-vertex lighting program. GLES20.glUseProgram(mProgramHandle); mTextureDataHandle0 = TextureHelper.loadTexture(context, R.drawable.hps); // Clamp depth to the range 1 to MAXIMUM_ALLOWED_DEPTH; final int d = Math.max(1, Math.min(MAXIMUM_ALLOWED_DEPTH, depth)); // Calculate basic values for the sphere. this.mTotalNumStrips = Maths.power(2, d - 1) * VERTEX_MAGIC_NUMBER; final int numVerticesPerStrip = Maths.power(2, d) * 3; final double altitudeStepAngle = Maths.ONE_TWENTY_DEGREES / Maths.power(2, d); final double azimuthStepAngle = Maths.THREE_SIXTY_DEGREES / this.mTotalNumStrips; double x, y, z, h, altitude, azimuth; for (int stripNum = 0; stripNum < this.mTotalNumStrips; stripNum++) { // Setup arrays to hold the points for this strip. final float[] vertices = new float[numVerticesPerStrip * NUM_FLOATS_PER_VERTEX]; // NOPMD final float[] texturePoints = new float[numVerticesPerStrip * NUM_FLOATS_PER_TEXTURE]; // NOPMD int vertexPos = 0; int texturePos = 0; // Calculate position of the first vertex in this strip. altitude = Maths.NINETY_DEGREES; azimuth = stripNum * azimuthStepAngle; // Draw the rest of this strip. for (int vertexNum = 0; vertexNum < numVerticesPerStrip; vertexNum += 2) { // First point - Vertex. y = radius * Math.sin(altitude); h = radius * Math.cos(altitude); z = h * Math.sin(azimuth); x = h * Math.cos(azimuth); vertices[vertexPos++] = (float) x; vertices[vertexPos++] = (float) y; vertices[vertexPos++] = (float) z; // First point - Texture. texturePoints[texturePos++] = (float) (1 - azimuth / Maths.THREE_SIXTY_DEGREES); texturePoints[texturePos++] = (float) (1 - (altitude + Maths.NINETY_DEGREES) / Maths.ONE_EIGHTY_DEGREES); // Second point - Vertex. altitude -= altitudeStepAngle; azimuth -= azimuthStepAngle / 2.0; y = radius * Math.sin(altitude); h = radius * Math.cos(altitude); z = h * Math.sin(azimuth); x = h * Math.cos(azimuth); vertices[vertexPos++] = (float) x; vertices[vertexPos++] = (float) y; vertices[vertexPos++] = (float) z; // Second point - Texture. texturePoints[texturePos++] = (float) (1 - azimuth / Maths.THREE_SIXTY_DEGREES); texturePoints[texturePos++] = (float) (1 - (altitude + Maths.NINETY_DEGREES) / Maths.ONE_EIGHTY_DEGREES); azimuth += azimuthStepAngle; } this.mVertices.add(vertices); this.mTexture.add(texturePoints); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(numVerticesPerStrip * NUM_FLOATS_PER_VERTEX * Float.SIZE); byteBuffer.order(ByteOrder.nativeOrder()); FloatBuffer fb = byteBuffer.asFloatBuffer(); fb.put(this.mVertices.get(stripNum)); fb.position(0); this.mVertexBuffer.add(fb); // Setup texture. byteBuffer = ByteBuffer.allocateDirect(numVerticesPerStrip * NUM_FLOATS_PER_TEXTURE * Float.SIZE); byteBuffer.order(ByteOrder.nativeOrder()); fb = byteBuffer.asFloatBuffer(); fb.put(this.mTexture.get(stripNum)); fb.position(0); this.mTextureBuffer.add(fb); } } public void draw(float[] mvpMatrix) { // Add program to OpenGL ES environment // Set program handles for cube drawing. mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix"); mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position"); mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate"); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle0); for (int i = 0; i < this.mTotalNumStrips; i++) { // Prepare the triangle coordinate data GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, mVertexBuffer.get(i)); // Enable a handle to the triangle vertices GLES20.glEnableVertexAttribArray(mPositionHandle); GLES20.glVertexAttribPointer(mTextureCoordinateHandle, AMOUNT_OF_NUMBERS_PER_TEXTURE_POINT, GLES20.GL_FLOAT, false, 0, mTextureBuffer.get(i)); GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle); // Pass the projection and view transformation to the shader GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); // Draw the triangle GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, this.mVertices.get(i).length / AMOUNT_OF_NUMBERS_PER_VERTEX_POINT); } // Disable vertex array GLES20.glDisableVertexAttribArray(mPositionHandle); } protected String getVertexShader(Context context) { return RawResourceReader.readTextFileFromRawResource(context, R.raw._vertex_shader); } protected String getFragmentShader(Context context) { return RawResourceReader.readTextFileFromRawResource(context, R.raw._fragment_shader); } }