OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.chromoting.cardboard; | |
6 | |
7 import static org.chromium.chromoting.cardboard.CardboardUtil.makeFloatBuffer; | |
8 import static org.chromium.chromoting.cardboard.CardboardUtil.makeShortBuffer; | |
9 | |
10 import android.app.Activity; | |
11 import android.graphics.Bitmap; | |
12 import android.graphics.BitmapFactory; | |
13 import android.opengl.GLES20; | |
14 | |
15 import com.google.vrtoolkit.cardboard.Eye; | |
16 | |
17 import org.chromium.base.Log; | |
18 import org.chromium.chromoting.ChromotingDownloadManager; | |
19 | |
20 import java.nio.FloatBuffer; | |
21 import java.nio.ShortBuffer; | |
22 | |
23 /** | |
24 * Cardboard Activity photosphere, which is used to draw the activity environmen
t. | |
25 */ | |
26 public class Photosphere { | |
27 private static final String TAG = "Cardboard"; | |
28 | |
29 private static final String VERTEX_SHADER = | |
30 "uniform mat4 u_CombinedMatrix;" | |
31 + "attribute vec4 a_Position;" | |
32 + "attribute vec2 a_TexCoordinate;" | |
33 + "varying vec2 v_TexCoordinate;" | |
34 + "void main() {" | |
35 + " v_TexCoordinate = a_TexCoordinate;" | |
36 + " gl_Position = u_CombinedMatrix * a_Position;" | |
37 + "}"; | |
38 | |
39 private static final String FRAGMENT_SHADER = | |
40 "precision mediump float;" | |
41 + "uniform sampler2D u_Texture;" | |
42 + "varying vec2 v_TexCoordinate;" | |
43 + "void main() {" | |
44 + " gl_FragColor = texture2D(u_Texture, v_TexCoordinate);" | |
45 + "}"; | |
46 | |
47 private int mVertexShaderHandle; | |
48 private int mFragmentShaderHandle; | |
49 private int mProgramHandle; | |
50 private int mCombinedMatrixHandle; | |
51 private int mPositionHandle; | |
52 private int mLeftEyeTextureDataHandle; | |
53 private int mRightEyeTextureDataHandle; | |
54 private int mTextureCoordinateHandle; | |
55 private int mTextureUniformHandle; | |
56 | |
57 private static final float RADIUS = 100.0f; | |
58 | |
59 // Subdivision of the texture. | |
60 // Note: (COLUMS + 1) * (ROWS + 1), which is the number of vertices, should
be within the | |
61 // range of short. | |
62 private static final int COLUMNS = 100; | |
63 private static final int ROWS = 50; | |
64 | |
65 private static final int POSITION_DATA_SIZE = 3; | |
66 private static final int TEXTURE_COORDINATE_DATA_SIZE = 2; | |
67 | |
68 // Number of drawing vertices needed to draw a rectangle surface. | |
69 private static final int NUM_DRAWING_VERTICES_PER_RECT = 6; | |
70 | |
71 private static final int NUM_VERTICES = (ROWS + 1) * (COLUMNS + 1); | |
72 | |
73 // Number of drawing vertices passed to glDrawElements(). | |
74 private static final int NUM_DRAWING_VERTICES = ROWS * COLUMNS * NUM_DRAWING
_VERTICES_PER_RECT; | |
75 | |
76 // TODO(shichengfeng): Find a photosphere image and upload it. | |
77 private static final String IMAGE_URI = | |
78 "https://dl.google.com/chrome-remote-desktop/android-assets/photosph
ere.jpg"; | |
79 private static final String IMAGE_NAME = "photosphere"; | |
80 | |
81 private static final ShortBuffer INDICES_BUFFER = calculateIndicesBuffer(); | |
82 | |
83 // Maximum resolution for decoded image. | |
84 private static final int MAX_WIDTH = 4096; | |
85 private static final int MAX_HEIGHT = 4096; | |
86 | |
87 private FloatBuffer mPositionBuffer; | |
88 private FloatBuffer mTextureCoordinatesBuffer; | |
89 | |
90 private Activity mActivity; | |
91 | |
92 private String mDownloadDirectory; | |
93 | |
94 // Lock to allow multithreaded access to mDownloadDirectory. | |
95 private final Object mDownloadDirectoryLock = new Object(); | |
96 | |
97 // Flag to signal that the image is fully decoded and should be loaded | |
98 // into the OpenGL textures. | |
99 private boolean mLoadTexture; | |
100 | |
101 // Lock to allow multithreaded access to mLoadTexture. | |
102 private final Object mLoadTextureLock = new Object(); | |
103 | |
104 ChromotingDownloadManager mDownloadManager; | |
105 | |
106 public Photosphere(Activity activity) { | |
107 mActivity = activity; | |
108 | |
109 calculatePosition(); | |
110 calculateIndicesBuffer(); | |
111 | |
112 // Set handlers for eye point drawing. | |
113 mVertexShaderHandle = | |
114 ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADE
R); | |
115 mFragmentShaderHandle = | |
116 ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_S
HADER); | |
117 mProgramHandle = ShaderHelper.createAndLinkProgram(mVertexShaderHandle, | |
118 mFragmentShaderHandle, new String[] {"a_Position", "u_CombinedMa
trix", | |
119 "a_TexCoordinate", "u_Texture"}); | |
120 mPositionHandle = | |
121 GLES20.glGetAttribLocation(mProgramHandle, "a_Position"); | |
122 mCombinedMatrixHandle = | |
123 GLES20.glGetUniformLocation(mProgramHandle, "u_CombinedMatrix"); | |
124 mTextureCoordinateHandle = | |
125 GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate"); | |
126 mTextureUniformHandle = | |
127 GLES20.glGetUniformLocation(mProgramHandle, "u_TextureUnit"); | |
128 mLeftEyeTextureDataHandle = TextureHelper.createTextureHandle(); | |
129 mRightEyeTextureDataHandle = TextureHelper.createTextureHandle(); | |
130 | |
131 mActivity.runOnUiThread(new Runnable() { | |
132 public void run() { | |
133 // Download the phtosphere image. | |
134 mDownloadManager = new ChromotingDownloadManager(mActivity, IMAG
E_NAME, | |
135 IMAGE_URI, new ChromotingDownloadManager.Callback() { | |
136 @Override | |
137 public void onBatchDownloadComplete() { | |
138 synchronized (mLoadTextureLock) { | |
139 mLoadTexture = true; | |
140 } | |
141 } | |
142 }); | |
143 synchronized (mDownloadDirectoryLock) { | |
144 mDownloadDirectory = mDownloadManager.getDownloadDirectory()
; | |
145 } | |
146 mDownloadManager.download(); | |
147 } | |
148 }); | |
149 } | |
150 | |
151 /** | |
152 * Set the texture for photosphere and clean temporary decoded image at the
end. | |
153 * Only call this method when we have completely downloaded photosphere imag
e. | |
154 */ | |
155 public void maybeLoadTextureAndCleanImage() { | |
156 synchronized (mLoadTextureLock) { | |
157 if (!mLoadTexture) { | |
158 return; | |
159 } | |
160 mLoadTexture = false; | |
161 } | |
162 | |
163 Bitmap image; | |
164 synchronized (mDownloadDirectoryLock) { | |
165 // This will only be executed when download finishes, which runs on
main thread. | |
166 // On the main thread, we first initialize mDownloadDirectory and th
en start download, | |
167 // so it is safe to use mDownloadDirectory here. | |
168 // First decode with inJustDecodeBounds = true to check dimensions. | |
169 final BitmapFactory.Options options = new BitmapFactory.Options(); | |
170 options.inJustDecodeBounds = true; | |
171 BitmapFactory.decodeFile(mDownloadDirectory + "/" + IMAGE_NAME, opti
ons); | |
172 | |
173 calculateInSampleSize(options); | |
174 | |
175 // Decode bitmap with inSampleSize set | |
176 options.inJustDecodeBounds = false; | |
177 image = BitmapFactory.decodeFile(mDownloadDirectory + "/" + IMAGE_NA
ME, options); | |
178 } | |
179 | |
180 if (image == null) { | |
181 Log.i(TAG, "Failed to decode image files."); | |
182 return; | |
183 } | |
184 | |
185 Bitmap leftEyeImage = Bitmap.createBitmap(image, 0, 0, image.getWidth(), | |
186 image.getHeight() / 2); | |
187 TextureHelper.linkTexture(mLeftEyeTextureDataHandle, leftEyeImage); | |
188 leftEyeImage.recycle(); | |
189 Bitmap rightEyeImage = Bitmap.createBitmap(image, 0, image.getHeight() /
2, | |
190 image.getWidth(), image.getHeight() / 2); | |
191 TextureHelper.linkTexture(mRightEyeTextureDataHandle, rightEyeImage); | |
192 rightEyeImage.recycle(); | |
193 | |
194 image.recycle(); | |
195 } | |
196 | |
197 public void draw(float[] combinedMatrix, int eyeType) { | |
198 GLES20.glUseProgram(mProgramHandle); | |
199 | |
200 // Pass in model view project matrix. | |
201 GLES20.glUniformMatrix4fv(mCombinedMatrixHandle, 1, false, combinedMatri
x, 0); | |
202 | |
203 GLES20.glVertexAttribPointer(mPositionHandle, POSITION_DATA_SIZE, GLES20
.GL_FLOAT, | |
204 false, 0, mPositionBuffer); | |
205 GLES20.glEnableVertexAttribArray(mPositionHandle); | |
206 | |
207 // Pass in texture coordinate. | |
208 GLES20.glVertexAttribPointer(mTextureCoordinateHandle, TEXTURE_COORDINAT
E_DATA_SIZE, | |
209 GLES20.GL_FLOAT, false, 0, mTextureCoordinatesBuffer); | |
210 GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle); | |
211 | |
212 // Link texture data with texture unit. | |
213 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); | |
214 if (eyeType == Eye.Type.LEFT) { | |
215 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mLeftEyeTextureDataHandle
); | |
216 } else if (eyeType == Eye.Type.RIGHT) { | |
217 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mRightEyeTextureDataHandl
e); | |
218 } | |
219 GLES20.glUniform1i(mTextureUniformHandle, 0); | |
220 | |
221 GLES20.glDrawElements(GLES20.GL_TRIANGLES, NUM_DRAWING_VERTICES, GLES20.
GL_UNSIGNED_SHORT, | |
222 INDICES_BUFFER); | |
223 | |
224 GLES20.glDisableVertexAttribArray(mPositionHandle); | |
225 GLES20.glDisableVertexAttribArray(mTextureCoordinateHandle); | |
226 } | |
227 | |
228 /** | |
229 * Calculate the sphere position coordinates and texture coordinates. | |
230 */ | |
231 private void calculatePosition() { | |
232 float[] vertexCoordinates = new float[NUM_VERTICES * POSITION_DATA_SIZE]
; | |
233 float[] textureCoordinates = new float[NUM_VERTICES * TEXTURE_COORDINATE
_DATA_SIZE]; | |
234 | |
235 int vertexIndex = 0; | |
236 int textureCoordinateIndex = 0; | |
237 for (int row = 0; row <= ROWS; row++) { | |
238 for (int column = 0; column <= COLUMNS; column++) { | |
239 double theta = (ROWS / 2.0 - row) / (ROWS / 2.0) * Math.PI / 2; | |
240 double phi = 2 * Math.PI * column / COLUMNS; | |
241 float x = (float) (RADIUS * Math.cos(theta) * Math.cos(phi)); | |
242 float y = (float) (RADIUS * Math.sin(theta)); | |
243 float z = (float) (RADIUS * Math.cos(theta) * Math.sin(phi)); | |
244 | |
245 vertexCoordinates[vertexIndex++] = x; | |
246 vertexCoordinates[vertexIndex++] = y; | |
247 vertexCoordinates[vertexIndex++] = z; | |
248 | |
249 textureCoordinates[textureCoordinateIndex++] = (float) column /
COLUMNS; | |
250 textureCoordinates[textureCoordinateIndex++] = (float) row / ROW
S; | |
251 } | |
252 } | |
253 | |
254 mPositionBuffer = makeFloatBuffer(vertexCoordinates); | |
255 mTextureCoordinatesBuffer = makeFloatBuffer(textureCoordinates); | |
256 } | |
257 | |
258 /** | |
259 * Calculate the drawing indices buffer. | |
260 */ | |
261 private static ShortBuffer calculateIndicesBuffer() { | |
262 short[] indices = new short[NUM_DRAWING_VERTICES]; | |
263 int currentIndex = 0; | |
264 for (int row = 0; row < ROWS; row++) { | |
265 for (int column = 0; column < COLUMNS; column++) { | |
266 int topLeft = row * (COLUMNS + 1) + column; | |
267 int topRight = topLeft + 1; | |
268 int bottomLeft = topLeft + (COLUMNS + 1); | |
269 int bottomRight = bottomLeft + 1; | |
270 indices[currentIndex++] = (short) topLeft; | |
271 indices[currentIndex++] = (short) bottomLeft; | |
272 indices[currentIndex++] = (short) topRight; | |
273 indices[currentIndex++] = (short) bottomLeft; | |
274 indices[currentIndex++] = (short) bottomRight; | |
275 indices[currentIndex++] = (short) topRight; | |
276 } | |
277 } | |
278 return makeShortBuffer(indices); | |
279 } | |
280 | |
281 /** | |
282 * Clean up opengl data. | |
283 */ | |
284 public void cleanup() { | |
285 GLES20.glDeleteShader(mVertexShaderHandle); | |
286 GLES20.glDeleteShader(mFragmentShaderHandle); | |
287 GLES20.glDeleteTextures(2, new int[] {mLeftEyeTextureDataHandle, | |
288 mRightEyeTextureDataHandle}, 0); | |
289 | |
290 mActivity.runOnUiThread(new Runnable() { | |
291 public void run() { | |
292 mDownloadManager.close(); | |
293 } | |
294 }); | |
295 } | |
296 | |
297 // Calculate InSampleSize, which is the number of pixels in either dimension
that correspond | |
298 // to a single pixel in the decoded bitmap. | |
299 private static void calculateInSampleSize(BitmapFactory.Options options) { | |
300 // Raw height and width of image | |
301 int height = options.outHeight; | |
302 int width = options.outWidth; | |
303 int inSampleSize = 1; | |
304 | |
305 while (height > MAX_HEIGHT || width > MAX_WIDTH) { | |
306 inSampleSize *= 2; | |
307 height /= 2; | |
308 width /= 2; | |
309 } | |
310 | |
311 options.inSampleSize = inSampleSize; | |
312 } | |
313 } | |
OLD | NEW |