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        if (isInEditMode()) {
119            return;
120        }
121        setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
122
123            @Override
124            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
125                mSurfaceTexture = surface;
126            }
127
128            @Override
129            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
130                mSurfaceTexture = surface;
131            }
132
133            @Override
134            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
135                return false;
136            }
137
138            @Override
139            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
140            }
141        });
142
143        mScaleDetector = new ScaleGestureDetector(context,
144                new ScaleGestureDetector.OnScaleGestureListener() {
145                    @Override
146                    public boolean onScale(ScaleGestureDetector detector) {
147                        double width = mState1.mTransform.getScreenWidth() / detector.getScaleFactor();
148                        mState1.mTransform.setScreenWidth(width);
149                        panMove(detector.getFocusX(), detector.getFocusY());
150                        render(4);
151                        return true;
152                    }
153
154                    @Override
155                    public boolean onScaleBegin(ScaleGestureDetector detector) {
156                        panDown(detector.getFocusX(), detector.getFocusY());
157                        mInScale = true;
158                        return true;
159                    }
160
161                    @Override
162                    public void onScaleEnd(ScaleGestureDetector detector) {
163                        mInScale = false;
164                    }
165                });
166    }
167
168    private void updateOutputDimensions(int width, int height) {
169    }
170
171    @Override
172    public boolean onTouchEvent(MotionEvent e) {
173        if (mPreviousMode == 1) {
174            mPipline.cancel();
175        }
176        boolean handled = mScaleDetector.onTouchEvent(e);
177        if (e.getPointerCount() > 1) {
178            return true;
179        }
180        if (mInScale) {
181            return true;
182        }
183        int action = e.getAction();
184        if (action == MotionEvent.ACTION_DOWN) {
185            actionDown(e);
186        } else if (action == MotionEvent.ACTION_MOVE) {
187            actionMove(e);
188            render(4);
189        } else if (action == MotionEvent.ACTION_UP) {
190            actionUp(e);
191            refresh = 1;
192            render(1);
193        }
194        return true;
195    }
196
197    private void panMove(float x, float y) {
198        double dist_x = (mDownPointX - x) * mDownScreenWidth / getWidth();
199        double dist_y = (y - mDownPointY) * mDownScreenWidth / getWidth();
200        double[] p;
201        p = mState1.mTransform.getEyePoint();
202        p[0] = mDownEyePoint[0] + dist_x * mDownRightVector[0] + dist_y * mDownUpVector[0];
203        p[1] = mDownEyePoint[1] + dist_x * mDownRightVector[1] + dist_y * mDownUpVector[1];
204        p[2] = mDownEyePoint[2] + dist_x * mDownRightVector[2] + dist_y * mDownUpVector[2];
205        mState1.mTransform.setEyePoint(p);
206        p = mState1.mTransform.getLookPoint();
207        p[0] = mDownLookPoint[0] + dist_x * mDownRightVector[0] + dist_y * mDownUpVector[0];
208        p[1] = mDownLookPoint[1] + dist_x * mDownRightVector[1] + dist_y * mDownUpVector[1];
209        p[2] = mDownLookPoint[2] + dist_x * mDownRightVector[2] + dist_y * mDownUpVector[2];
210        mState1.mTransform.setLookPoint(p);
211    }
212
213    private void panDown(float x, float y) {
214        mDownPointX = x;
215        mDownPointY = y;
216        mDownScreenWidth = mState1.mTransform.getScreenWidth();
217        double[] p;
218        p = mState1.mTransform.getLookPoint();
219        System.arraycopy(p, 0, mDownLookPoint, 0, 3);
220        p = mState1.mTransform.getEyePoint();
221        System.arraycopy(p, 0, mDownEyePoint, 0, 3);
222        p = mState1.mTransform.getUpVector();
223        System.arraycopy(p, 0, mDownUpVector, 0, 3);
224        mDownRightVector[0] = mDownLookPoint[0] - mDownEyePoint[0];
225        mDownRightVector[1] = mDownLookPoint[1] - mDownEyePoint[1];
226        mDownRightVector[2] = mDownLookPoint[2] - mDownEyePoint[2];
227        VectorUtil.normalize(mDownRightVector);
228        VectorUtil.cross(mDownRightVector, mDownUpVector, mDownRightVector);
229    }
230
231    private void actionDown(MotionEvent e) {
232        panDown(e.getX(), e.getY());
233
234        switch (mMode) {
235            case ROTATE_MODE:
236                mState1.mTransform.trackBallDown(e.getX(), e.getY());
237                break;
238
239            case CUT_X_MODE:
240            case CUT_Y_MODE:
241            case CUT_Z_MODE:
242                float[] trim = mState1.mCubeVolume.getTrim();
243                System.arraycopy(trim, 0, mCurrentTrim, 0, 6);
244                break;
245        }
246    }
247
248    private void actionMove(MotionEvent e) {
249        float deltax, deltay;
250
251        switch (mMode) {
252            case ROTATE_MODE:
253
254                mState1.mTransform.trackBallMove(e.getX(), e.getY());
255
256                break;
257
258            case CUT_X_MODE:
259                deltax = (float) ((mDownPointX - e.getX()) / getWidth());
260                deltay = (float) -((mDownPointY - e.getY()) / getWidth());
261                cut(0, deltax, deltay);
262                break;
263            case CUT_Y_MODE:
264                deltax = (float) ((mDownPointX - e.getX()) / getWidth());
265                deltay = (float) -((mDownPointY - e.getY()) / getWidth());
266                cut(1, deltax, deltay);
267                break;
268            case CUT_Z_MODE:
269                deltax = (float) ((mDownPointX - e.getX()) / getWidth());
270                deltay = (float) -((mDownPointY - e.getY()) / getWidth());
271                cut(2, deltax, deltay);
272                break;
273
274        }
275    }
276
277    private void actionUp(MotionEvent e) {
278    }
279
280    public void cut(int side, float fractionx, float fractiony) {
281        float[] f = Arrays.copyOf(mCurrentTrim, mCurrentTrim.length);
282        f[side] += fractionx;
283        if (f[side] < 0) f[side] = 0;
284        if (f[side] > .8) f[side] = .8f;
285        f[side + 3] += fractiony;
286        if (f[side + 3] < 0) f[side + 3] = 0;
287        if (f[side + 3] > .8) f[side + 3] = .8f;
288        mState1.mCubeVolume = new Cube(mState1.mVolume, 5f, f);
289        mState1.mCubeScreen = new TriData(mState1.mCubeVolume);
290        mState1.mCubeScreen.scale(mState1.mVolume.mVoxelDim);
291    }
292
293    public void resetCut() {
294        Arrays.fill(mCurrentTrim, 0);
295        mState1.mCubeVolume = new Cube(mState1.mVolume, 5f, mCurrentTrim);
296        mState1.mCubeScreen = new TriData(mState1.mCubeVolume);
297        mState1.mCubeScreen.scale(mState1.mVolume.mVoxelDim);
298        mState1.mTransform.look(looks[last_look], mState1.mCubeScreen, getWidth(), getHeight());
299        mState1.mTransform.setScreenWidth(.6f * mState1.mTransform.getScreenWidth());
300        last_look = (last_look + 1) % looks.length;
301        render(4);
302    }
303
304    public void setVolume(RenderScript rs, Volume v) {
305        mState1.mRs = rs;
306        mState1.mVolume = v;
307        mState1.mCubeVolume = new Cube(mState1.mVolume, 5f);
308        mState1.mCubeScreen = new TriData(mState1.mCubeVolume);
309        mState1.mCubeScreen.scale(v.mVoxelDim);
310        mState1.mTransform.setVoxelDim(v.mVoxelDim);
311        mState1.mTransform.look(ViewMatrix.DOWN_AT, mState1.mCubeScreen, getWidth(), getHeight());
312        setLook(mState1.mVolume.getLookNames()[0]);
313    }
314
315    protected void look(int k) {
316        mState1.mTransform.look(looks[k], mState1.mCubeVolume, getWidth(), getHeight());
317        render(4);
318        render(1);
319    }
320
321    void render(int downSample) {
322
323        if (mRenderTesk == null) {
324            mRenderTesk = new VrRenderTesk();
325            refresh = 0;
326            mRenderTesk.execute(downSample);
327        } else {
328            refresh = downSample;
329        }
330    }
331
332    public String[] getLooks() {
333        return mState1.mVolume.getLookNames();
334    }
335
336    public void setLook(String look) {
337        int[][] color = mState1.mVolume.getLookColor(look);
338        int[][] opacity = mState1.mVolume.getLookOpactiy(look);
339        mState1.mMaterial.setup(opacity, color);
340        if (mBinGridTask == null) {
341            mBinGridTask = new VrBinGridTask();
342            mBinGridTask.execute(mState1.mVolume);
343        }
344    }
345
346    class VrRenderTesk extends AsyncTask<Integer, String, Long> {
347
348        long m_last_time;
349
350        @Override
351        protected void onPreExecute() {
352            mStateLow.copyData(mState1);
353        }
354
355        @Override
356        protected void onCancelled() {
357            mPipline.cancel();
358        }
359
360        @Override
361        protected Long doInBackground(Integer... down) {
362            if (mState1.mRs == null) return 0L;
363            if (mSurfaceTexture == null) return 0L;
364            int sample = 4;
365            VrState state = mStateLow;
366            if (down[0] == 1) {
367                if (mPreviousMode == 4) {
368                    mState1.copyData(mLastDrawn);
369                } else {
370                    mState1.copyData(mStateLow);
371                }
372                // mStateLow.mScrAllocation.setSurface(null);
373                state = mState1;
374                sample = 1;
375                if (mStateLow.mScrAllocation != null) {
376                    mStateLow.mScrAllocation.setSurface(null);
377                }
378            } else {
379                if (mState1.mScrAllocation != null) {
380                    mState1.mScrAllocation.setSurface(null);
381                }
382            }
383
384            if (mPreviousMode != sample) {
385                if (mSurface != null) {
386                    mSurface.release();
387                }
388                mSurface = new Surface(mSurfaceTexture);
389            }
390            mPreviousMode = sample;
391
392            int img_width = getWidth() / sample;
393            int img_height = getHeight() / sample;
394            state.createOutputAllocation(mSurface, img_width, img_height);
395
396            mPipline.initBuffers(state);
397
398            if (mPipline.isCancel()) {
399                return 0L;
400            }
401            long start = System.nanoTime();
402            addTimeLine(null);
403            mPipline.setupTriangles(state);
404
405            if (mPipline.isCancel()) {
406                return 0L;
407            }
408            mPipline.rasterizeTriangles(state);
409
410            if (mPipline.isCancel()) {
411                return 0L;
412            }
413            mPipline.raycast(state);
414
415            if (mPipline.isCancel()) {
416                return 0L;
417            }
418            mLastDrawn.copyData(state);
419            state.mRs.finish();
420            state.mScrAllocation.ioSend();
421
422            long time = System.nanoTime();
423            addLine("vr(" + img_width + "," + img_height + "): " + (time - start) / 1E6f + " ms");
424            return 0L;
425        }
426
427        private void addTimeLine(String line) {
428            if (line == null) {
429                m_last_time = System.nanoTime();
430                return;
431            }
432            long time = System.nanoTime();
433            float ftime = (time - m_last_time) / 1E6f;
434            if (ftime > 100)
435                addLine(line + ": " + (ftime / 1E3f) + " sec");
436            else
437                addLine(line + ": " + (ftime) + " ms");
438            m_last_time = System.nanoTime();
439        }
440
441        private void addLine(String line) {
442            publishProgress(line);
443        }
444
445        protected void onProgressUpdate(String... progress) {
446            Log.v(LOGTAG, progress[0]);
447        }
448
449        protected void onPostExecute(Long result) {
450            invalidate();
451            mRenderTesk = null;
452            if (refresh != 0) {
453                render(refresh);
454            }
455        }
456    }
457
458    class VrBinGridTask extends AsyncTask<Volume, String, Long> {
459
460        @Override
461        protected Long doInBackground(Volume... v) {
462            mState1.mRsMask = new RsBrickedBitMask(mState1);
463            mState1.mRs.finish();
464            return 0L;
465        }
466
467        protected void onProgressUpdate(String... progress) {
468            Log.v(LOGTAG, progress[0]);
469        }
470
471        protected void onPostExecute(Long result) {
472            mBinGridTask = null;
473            render(4);
474            render(1);
475        }
476    }
477}
478