1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.rs.vr;
18
19import android.content.Context;
20import android.graphics.Color;
21import android.graphics.Paint;
22import android.graphics.SurfaceTexture;
23import android.os.AsyncTask;
24import android.renderscript.RenderScript;
25import android.util.AttributeSet;
26import android.util.Log;
27import android.view.MotionEvent;
28import android.view.ScaleGestureDetector;
29import android.view.Surface;
30import android.view.TextureView;
31
32import com.example.android.rs.vr.engine.Cube;
33import com.example.android.rs.vr.engine.Pipeline;
34import com.example.android.rs.vr.engine.RsBrickedBitMask;
35import com.example.android.rs.vr.engine.TriData;
36import com.example.android.rs.vr.engine.VectorUtil;
37import com.example.android.rs.vr.engine.ViewMatrix;
38import com.example.android.rs.vr.engine.Volume;
39import com.example.android.rs.vr.engine.VrPipline1;
40import com.example.android.rs.vr.engine.VrState;
41
42import java.util.Arrays;
43
44/**
45 * VrView runs a volume rendering on the screen
46 */
47public class VrView extends TextureView {
48    private static final String LOGTAG = "rsexample.google.com.vrdemo";
49    private Pipeline mPipline = new VrPipline1();//BasicPipline();
50    //    private VrState mState4 = new VrState(); // for down sampled
51    private VrState mState1 = new VrState(); // for full res version
52    private VrState mStateLow = new VrState(); // for full res version
53    private VrState mLastDrawn = new VrState(); // for full res version
54    private Paint paint = new Paint();
55    private SurfaceTexture mSurfaceTexture;
56    private Surface mSurface;
57    ///private Size mImageViewSize;
58    private int refresh = 0;  // 0 is no refresh else refresh = downsample
59    int mPreviousMode = -1;
60    int last_look = 0;
61
62    //    int mDownSample = 4;
63    private final char[] looks = {
64            ViewMatrix.UP_AT,
65            ViewMatrix.DOWN_AT,
66            ViewMatrix.RIGHT_AT,
67            ViewMatrix.LEFT_AT,
68            ViewMatrix.FORWARD_AT,
69            ViewMatrix.BEHIND_AT};
70    private byte mMode = ROTATE_MODE;
71    private ScaleGestureDetector mScaleDetector;
72    private boolean mInScale;
73
74    public static final byte ROTATE_MODE = 1;
75    public static final byte CUT_X_MODE = 2;
76    public static final byte CUT_Y_MODE = 3;
77    public static final byte CUT_Z_MODE = 4;
78
79    public void setMode(byte mode) {
80        mMode = mode;
81    }
82
83    private float mDownPointX;
84    private float mDownPointY;
85    private double mDownScreenWidth;
86    private double[] mDownLookPoint = new double[3];
87    private double[] mDownEyePoint = new double[3];
88    private double[] mDownUpVector = new double[3];
89    private double[] mDownRightVector = new double[3];
90    private float[] mCurrentTrim = new float[6];
91    VrRenderTesk mRenderTesk;
92    VrBinGridTask mBinGridTask;
93
94    public VrView(Context context) {
95        super(context);
96        setup(context);
97        paint.setFilterBitmap(true);
98    }
99
100    public VrView(Context context, AttributeSet attrs) {
101        super(context, attrs);
102        setup(context);
103    }
104
105
106    public VrView(Context context, AttributeSet attrs, int defStyleAttr) {
107        super(context, attrs, defStyleAttr);
108        setup(context);
109    }
110
111
112    public VrView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
113        super(context, attrs, defStyleAttr, defStyleRes);
114        setup(context);
115    }
116
117    private void setup(Context context) {
118        setBackgroundColor(Color.BLACK);
119        if (isInEditMode()) {
120            return;
121        }
122        setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
123
124            @Override
125            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
126                mSurfaceTexture = surface;
127            }
128
129            @Override
130            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
131                mSurfaceTexture = surface;
132            }
133
134            @Override
135            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
136                return false;
137            }
138
139            @Override
140            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
141            }
142        });
143
144        mScaleDetector = new ScaleGestureDetector(context,
145                new ScaleGestureDetector.OnScaleGestureListener() {
146                    @Override
147                    public boolean onScale(ScaleGestureDetector detector) {
148                        double width = mState1.mTransform.getScreenWidth() / detector.getScaleFactor();
149                        mState1.mTransform.setScreenWidth(width);
150                        panMove(detector.getFocusX(), detector.getFocusY());
151                        render(4);
152                        return true;
153                    }
154
155                    @Override
156                    public boolean onScaleBegin(ScaleGestureDetector detector) {
157                        panDown(detector.getFocusX(), detector.getFocusY());
158                        mInScale = true;
159                        return true;
160                    }
161
162                    @Override
163                    public void onScaleEnd(ScaleGestureDetector detector) {
164                        mInScale = false;
165                    }
166                });
167    }
168
169    private void updateOutputDimensions(int width, int height) {
170    }
171
172    @Override
173    public boolean onTouchEvent(MotionEvent e) {
174        if (mPreviousMode == 1) {
175            mPipline.cancel();
176        }
177        boolean handled = mScaleDetector.onTouchEvent(e);
178        if (e.getPointerCount() > 1) {
179            return true;
180        }
181        if (mInScale) {
182            return true;
183        }
184        int action = e.getAction();
185        if (action == MotionEvent.ACTION_DOWN) {
186            actionDown(e);
187        } else if (action == MotionEvent.ACTION_MOVE) {
188            actionMove(e);
189            render(4);
190        } else if (action == MotionEvent.ACTION_UP) {
191            actionUp(e);
192            refresh = 1;
193            render(1);
194        }
195        return true;
196    }
197
198    private void panMove(float x, float y) {
199        double dist_x = (mDownPointX - x) * mDownScreenWidth / getWidth();
200        double dist_y = (y - mDownPointY) * mDownScreenWidth / getWidth();
201        double[] p;
202        p = mState1.mTransform.getEyePoint();
203        p[0] = mDownEyePoint[0] + dist_x * mDownRightVector[0] + dist_y * mDownUpVector[0];
204        p[1] = mDownEyePoint[1] + dist_x * mDownRightVector[1] + dist_y * mDownUpVector[1];
205        p[2] = mDownEyePoint[2] + dist_x * mDownRightVector[2] + dist_y * mDownUpVector[2];
206        mState1.mTransform.setEyePoint(p);
207        p = mState1.mTransform.getLookPoint();
208        p[0] = mDownLookPoint[0] + dist_x * mDownRightVector[0] + dist_y * mDownUpVector[0];
209        p[1] = mDownLookPoint[1] + dist_x * mDownRightVector[1] + dist_y * mDownUpVector[1];
210        p[2] = mDownLookPoint[2] + dist_x * mDownRightVector[2] + dist_y * mDownUpVector[2];
211        mState1.mTransform.setLookPoint(p);
212    }
213
214    private void panDown(float x, float y) {
215        mDownPointX = x;
216        mDownPointY = y;
217        mDownScreenWidth = mState1.mTransform.getScreenWidth();
218        double[] p;
219        p = mState1.mTransform.getLookPoint();
220        System.arraycopy(p, 0, mDownLookPoint, 0, 3);
221        p = mState1.mTransform.getEyePoint();
222        System.arraycopy(p, 0, mDownEyePoint, 0, 3);
223        p = mState1.mTransform.getUpVector();
224        System.arraycopy(p, 0, mDownUpVector, 0, 3);
225        mDownRightVector[0] = mDownLookPoint[0] - mDownEyePoint[0];
226        mDownRightVector[1] = mDownLookPoint[1] - mDownEyePoint[1];
227        mDownRightVector[2] = mDownLookPoint[2] - mDownEyePoint[2];
228        VectorUtil.normalize(mDownRightVector);
229        VectorUtil.cross(mDownRightVector, mDownUpVector, mDownRightVector);
230    }
231
232    private void actionDown(MotionEvent e) {
233        panDown(e.getX(), e.getY());
234
235        switch (mMode) {
236            case ROTATE_MODE:
237                mState1.mTransform.trackBallDown(e.getX(), e.getY());
238                break;
239
240            case CUT_X_MODE:
241            case CUT_Y_MODE:
242            case CUT_Z_MODE:
243                float[] trim = mState1.mCubeVolume.getTrim();
244                System.arraycopy(trim, 0, mCurrentTrim, 0, 6);
245                break;
246        }
247    }
248
249    private void actionMove(MotionEvent e) {
250        float deltax, deltay;
251
252        switch (mMode) {
253            case ROTATE_MODE:
254
255                mState1.mTransform.trackBallMove(e.getX(), e.getY());
256
257                break;
258
259            case CUT_X_MODE:
260                deltax = (float) ((mDownPointX - e.getX()) / getWidth());
261                deltay = (float) -((mDownPointY - e.getY()) / getWidth());
262                cut(0, deltax, deltay);
263                break;
264            case CUT_Y_MODE:
265                deltax = (float) ((mDownPointX - e.getX()) / getWidth());
266                deltay = (float) -((mDownPointY - e.getY()) / getWidth());
267                cut(1, deltax, deltay);
268                break;
269            case CUT_Z_MODE:
270                deltax = (float) ((mDownPointX - e.getX()) / getWidth());
271                deltay = (float) -((mDownPointY - e.getY()) / getWidth());
272                cut(2, deltax, deltay);
273                break;
274
275        }
276    }
277
278    private void actionUp(MotionEvent e) {
279    }
280
281    public void cut(int side, float fractionx, float fractiony) {
282        float[] f = Arrays.copyOf(mCurrentTrim, mCurrentTrim.length);
283        f[side] += fractionx;
284        if (f[side] < 0) f[side] = 0;
285        if (f[side] > .8) f[side] = .8f;
286        f[side + 3] += fractiony;
287        if (f[side + 3] < 0) f[side + 3] = 0;
288        if (f[side + 3] > .8) f[side + 3] = .8f;
289        mState1.mCubeVolume = new Cube(mState1.mVolume, 5f, f);
290        mState1.mCubeScreen = new TriData(mState1.mCubeVolume);
291        mState1.mCubeScreen.scale(mState1.mVolume.mVoxelDim);
292    }
293
294    public void resetCut() {
295        Arrays.fill(mCurrentTrim, 0);
296        mState1.mCubeVolume = new Cube(mState1.mVolume, 5f, mCurrentTrim);
297        mState1.mCubeScreen = new TriData(mState1.mCubeVolume);
298        mState1.mCubeScreen.scale(mState1.mVolume.mVoxelDim);
299        mState1.mTransform.look(looks[last_look], mState1.mCubeScreen, getWidth(), getHeight());
300        mState1.mTransform.setScreenWidth(.6f * mState1.mTransform.getScreenWidth());
301        last_look = (last_look + 1) % looks.length;
302        render(4);
303    }
304
305    public void setVolume(RenderScript rs, Volume v) {
306        mState1.mRs = rs;
307        mState1.mVolume = v;
308        mState1.mCubeVolume = new Cube(mState1.mVolume, 5f);
309        mState1.mCubeScreen = new TriData(mState1.mCubeVolume);
310        mState1.mCubeScreen.scale(v.mVoxelDim);
311        mState1.mTransform.setVoxelDim(v.mVoxelDim);
312        mState1.mTransform.look(ViewMatrix.DOWN_AT, mState1.mCubeScreen, getWidth(), getHeight());
313        setLook(mState1.mVolume.getLookNames()[0]);
314    }
315
316    protected void look(int k) {
317        mState1.mTransform.look(looks[k], mState1.mCubeVolume, getWidth(), getHeight());
318        render(4);
319        render(1);
320    }
321
322    void render(int downSample) {
323
324        if (mRenderTesk == null) {
325            mRenderTesk = new VrRenderTesk();
326            refresh = 0;
327            mRenderTesk.execute(downSample);
328        } else {
329            refresh = downSample;
330        }
331    }
332
333    public String[] getLooks() {
334        return mState1.mVolume.getLookNames();
335    }
336
337    public void setLook(String look) {
338        int[][] color = mState1.mVolume.getLookColor(look);
339        int[][] opacity = mState1.mVolume.getLookOpactiy(look);
340        mState1.mMaterial.setup(opacity, color);
341        if (mBinGridTask == null) {
342            mBinGridTask = new VrBinGridTask();
343            mBinGridTask.execute(mState1.mVolume);
344        }
345    }
346
347    class VrRenderTesk extends AsyncTask<Integer, String, Long> {
348
349        long m_last_time;
350
351        @Override
352        protected void onPreExecute() {
353            mStateLow.copyData(mState1);
354        }
355
356        @Override
357        protected void onCancelled() {
358            mPipline.cancel();
359        }
360
361        @Override
362        protected Long doInBackground(Integer... down) {
363            if (mState1.mRs == null) return 0L;
364            if (mSurfaceTexture == null) return 0L;
365            int sample = 4;
366            VrState state = mStateLow;
367            if (down[0] == 1) {
368                if (mPreviousMode == 4) {
369                    mState1.copyData(mLastDrawn);
370                } else {
371                    mState1.copyData(mStateLow);
372                }
373                // mStateLow.mScrAllocation.setSurface(null);
374                state = mState1;
375                sample = 1;
376                if (mStateLow.mScrAllocation != null) {
377                    mStateLow.mScrAllocation.setSurface(null);
378                }
379            } else {
380                if (mState1.mScrAllocation != null) {
381                    mState1.mScrAllocation.setSurface(null);
382                }
383            }
384
385            if (mPreviousMode != sample) {
386                if (mSurface != null) {
387                    mSurface.release();
388                }
389                mSurface = new Surface(mSurfaceTexture);
390            }
391            mPreviousMode = sample;
392
393            int img_width = getWidth() / sample;
394            int img_height = getHeight() / sample;
395            state.createOutputAllocation(mSurface, img_width, img_height);
396
397            mPipline.initBuffers(state);
398
399            if (mPipline.isCancel()) {
400                return 0L;
401            }
402            long start = System.nanoTime();
403            addTimeLine(null);
404            mPipline.setupTriangles(state);
405
406            if (mPipline.isCancel()) {
407                return 0L;
408            }
409            mPipline.rasterizeTriangles(state);
410
411            if (mPipline.isCancel()) {
412                return 0L;
413            }
414            mPipline.raycast(state);
415
416            if (mPipline.isCancel()) {
417                return 0L;
418            }
419            mLastDrawn.copyData(state);
420            state.mRs.finish();
421            state.mScrAllocation.ioSend();
422
423            long time = System.nanoTime();
424            addLine("vr(" + img_width + "," + img_height + "): " + (time - start) / 1E6f + " ms");
425            return 0L;
426        }
427
428        private void addTimeLine(String line) {
429            if (line == null) {
430                m_last_time = System.nanoTime();
431                return;
432            }
433            long time = System.nanoTime();
434            float ftime = (time - m_last_time) / 1E6f;
435            if (ftime > 100)
436                addLine(line + ": " + (ftime / 1E3f) + " sec");
437            else
438                addLine(line + ": " + (ftime) + " ms");
439            m_last_time = System.nanoTime();
440        }
441
442        private void addLine(String line) {
443            publishProgress(line);
444        }
445
446        protected void onProgressUpdate(String... progress) {
447            Log.v(LOGTAG, progress[0]);
448        }
449
450        protected void onPostExecute(Long result) {
451            invalidate();
452            mRenderTesk = null;
453            if (refresh != 0) {
454                render(refresh);
455            }
456        }
457    }
458
459    class VrBinGridTask extends AsyncTask<Volume, String, Long> {
460
461        @Override
462        protected Long doInBackground(Volume... v) {
463            mState1.mRsMask = new RsBrickedBitMask(mState1);
464            mState1.mRs.finish();
465            return 0L;
466        }
467
468        protected void onProgressUpdate(String... progress) {
469            Log.v(LOGTAG, progress[0]);
470        }
471
472        protected void onPostExecute(Long result) {
473            mBinGridTask = null;
474            render(4);
475            render(1);
476        }
477    }
478}
479