1 /*
2  * Copyright 2021 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 package org.skia.jetskidemo;
9 
10 import android.app.Activity;
11 import android.content.res.Resources;
12 import android.os.Bundle;
13 import android.view.GestureDetector;
14 import android.view.MotionEvent;
15 import android.view.ScaleGestureDetector;
16 import android.view.SurfaceView;
17 
18 import org.skia.jetski.Canvas;
19 import org.skia.jetski.Matrix;
20 import org.skia.jetski.Surface;
21 import org.skia.jetski.util.SurfaceRenderer;
22 
23 import org.skia.jetskidemo.samples.ImageShaderSample;
24 import org.skia.jetskidemo.samples.RuntimeSample;
25 import org.skia.jetskidemo.samples.Sample;
26 import org.skia.jetskidemo.samples.SkottieSample;
27 
28 import static java.lang.Math.tan;
29 
30 class Face {
31     private float rotX;
32     private float rotY;
33     public Sample sample;
34 
Face(float rotX, float rotY, Sample sample)35     Face(float rotX, float rotY, Sample sample) {
36         this.rotX = rotX;
37         this.rotY = rotY;
38         this.sample = sample;
39     }
40 
asMatrix(float scale)41     Matrix asMatrix(float scale) {
42         return new Matrix().rotateY(rotY).rotateX(rotX).translate(0, 0, scale);
43     }
44 }
45 
46 // TODO: make this public?
47 class Vec3 {
48     public float x, y, z;
49 
Vec3(float x, float y, float z)50     public Vec3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; }
51 
length()52     public float length() { return (float)Math.sqrt(x*x + y*y + z*z); }
53 
normalize()54     public Vec3 normalize() {
55         mul(1/length());
56         return this;
57     }
58 
add(float v)59     public Vec3 add(float v) {
60         x += v; y += v; z += v;
61         return this;
62     }
63 
mul(float v)64     public Vec3 mul(float v) {
65         x *= v; y *= v; z *= v;
66         return this;
67     }
68 
dot(Vec3 v)69     public float dot(Vec3 v) {
70         return x*v.x + y*v.y + z*v.z;
71     }
72 
cross(Vec3 v)73     public Vec3 cross(Vec3 v) {
74         float xx = y*v.z - z*v.y,
75               yy = z*v.x - x*v.z,
76               zz = x*v.y - y*v.x;
77         x = xx; y = yy; z = zz;
78         return this;
79     }
80 };
81 
82 class VSphereAnimator {
83     private Matrix mRotMatrix = new Matrix();
84     private Vec3   mRotAxis   = new Vec3(0, 1, 0),
85                    mCurrentDrag;
86     private float  mRotSpeed  = (float)Math.PI,
87                    mCenterX,
88                    mCenterY,
89                    mRadius;
90 
VSphereAnimator(float x, float y, float r)91     public VSphereAnimator(float x, float y, float r) {
92         mCenterX = x;
93         mCenterY = y;
94         mRadius  = r;
95     }
96 
animate(float dt)97     public void animate(float dt) {
98         final float kDecay = 0.99f;
99 
100         rotate(mRotAxis, mRotSpeed * dt);
101 
102         mRotSpeed *= kDecay;
103     }
104 
getMatrix()105     public Matrix getMatrix() {
106         return mRotMatrix;
107     }
108 
fling(float dx, float dy)109     public void fling(float dx, float dy) {
110         Vec3 u = normalVec(mCenterX, mCenterY),
111              v = normalVec(mCenterX + dx, mCenterY + dy);
112         mRotAxis = u.cross(v).normalize();
113 
114         double flingSpeed = Math.sqrt(dx*dx + dy*dy)/mRadius;
115         mRotSpeed = (float)(flingSpeed*Math.PI);
116     }
117 
startDrag(MotionEvent e)118     public void startDrag(MotionEvent e) {
119         mCurrentDrag = normalVec(e.getX(), e.getY());
120         mRotSpeed = 0;
121     }
122 
drag(MotionEvent e)123     public void drag(MotionEvent e) {
124         Vec3 u = mCurrentDrag,                  // previous drag position
125              v = normalVec(e.getX(), e.getY()); // new drag position
126 
127         float angle = (float)Math.acos(Math.max(-1, Math.min(1, u.dot(v)/u.length()/v.length())));
128         Vec3   axis = u.cross(v).normalize();
129 
130         rotate(axis, angle);
131 
132         mCurrentDrag = v;
133     }
134 
normalVec(float x, float y)135     private Vec3 normalVec(float x, float y) {
136         x =  (x - mCenterX)/mRadius;
137         y = -(y - mCenterY)/mRadius;
138         float len2 = x*x + y*y;
139 
140         if (len2 > 1) {
141             // normalize
142             float len = (float)Math.sqrt(len2);
143             x /= len;
144             y /= len;
145             len2 = 1;
146         }
147 
148         return new Vec3(x, y, (float)Math.sqrt(1 - len2));
149     }
150 
rotate(Vec3 axis, float angle)151     private void rotate(Vec3 axis, float angle) {
152         mRotMatrix = new Matrix().rotate(axis.x, axis.y, axis.z, angle)
153                                  .preConcat(mRotMatrix);
154     }
155 };
156 
157 class CubeRenderer extends SurfaceRenderer implements GestureDetector.OnGestureListener,
158                                                       ScaleGestureDetector.OnScaleGestureListener {
159     private VSphereAnimator mVSphere;
160     private Matrix          mViewMatrix;
161     private float           mCubeSideLength;
162     private long            mPrevMS;
163     private Face[]          mFaces;
164     private float           mZoom = 1;
165 
CubeRenderer(Resources res)166     public CubeRenderer(Resources res) {
167         final float rot = (float) Math.PI;
168         mFaces = new Face[] {
169             new Face(0, -rot/2, new ImageShaderSample(res, R.raw.brickwork_texture)),
170             new Face(0, 0     , new SkottieSample(res, R.raw.im_thirsty)),
171             new Face(0, rot   , new RuntimeSample(res, R.raw.runtime_shader1)),
172             new Face(rot/2, 0 , new SkottieSample(res, R.raw.permission)),
173             new Face(0, rot/2 , new RuntimeSample(res, R.raw.runtime_shader2)),
174         };
175     }
176 
177     @Override
onSurfaceInitialized(Surface surface)178     protected void onSurfaceInitialized(Surface surface) {
179         float cx = surface.getWidth()  / 2,
180               cy = surface.getHeight() / 2,
181               r  = Math.min(cx, cy);
182 
183         mVSphere = new VSphereAnimator(cx, cy, r);
184 
185         // square viewport size fitting the given surface
186         float vsz = r * 2;
187 
188         mCubeSideLength = vsz * 0.5f;
189 
190         float viewAngle = (float)Math.PI / 4f,
191               viewDistance = (float)(r / tan(viewAngle/2));
192 
193         mViewMatrix = new Matrix()
194                         // centered viewport
195                         .translate(cx, cy)
196                         // perspective
197                         .scale(vsz/2, vsz/2, 1)
198                         .preConcat(Matrix.makePerspective(0.05f, viewDistance, viewAngle))
199                         // camera
200                         .preConcat(Matrix.makeLookAt(0, 0, -viewDistance, 0, 0, 0, 0, 1, 0));
201     }
202 
203     @Override
onRenderFrame(Canvas canvas, long ms)204     protected void onRenderFrame(Canvas canvas, long ms) {
205         if (mPrevMS == 0) {
206             mPrevMS = ms;
207         }
208 
209         mVSphere.animate((ms - mPrevMS) / 1000.f);
210         mPrevMS = ms;
211 
212 
213         // clear canvas
214         canvas.drawColor(0xffffffff);
215 
216         canvas.save();
217         canvas.concat(mViewMatrix);
218         canvas.scale(mZoom, mZoom);
219         canvas.concat(mVSphere.getMatrix());
220 
221         drawFaces(canvas, ms, false);
222         drawFaces(canvas, ms, true);
223 
224         canvas.restore();
225     }
226 
drawFaces(Canvas canvas, long ms, boolean renderFront)227     private void drawFaces(Canvas canvas, long ms, boolean renderFront) {
228         for (Face f : mFaces) {
229             canvas.save();
230             canvas.concat(f.asMatrix(mCubeSideLength/2));
231 
232             if (front(canvas.getLocalToDevice()) == renderFront) {
233                 f.sample.render(canvas, ms,
234                                 -mCubeSideLength/2,
235                                 -mCubeSideLength/2,
236                                  mCubeSideLength/2,
237                                  mCubeSideLength/2);
238             }
239             canvas.restore();
240         }
241     }
242 
zoom(float z)243     private void zoom(float z) {
244         final float kMinZoom = 0.5f,
245                     kMaxZoom = 3.0f;
246         mZoom = Math.max(kMinZoom, Math.min(kMaxZoom, mZoom * z));
247     }
248 
249     @Override
onFling(MotionEvent e1, MotionEvent e2, float dx, float dy)250     public boolean onFling(MotionEvent e1, MotionEvent e2, float dx, float dy) {
251         mVSphere.fling(dx, dy);
252         return true;
253     }
254 
255     @Override
onDown(MotionEvent e)256     public boolean onDown(MotionEvent e) {
257         mVSphere.startDrag(e);
258         return true;
259     }
260 
261     @Override
onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)262     public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
263         mVSphere.drag(e2);
264         return true;
265     }
266 
267     // GestureDetector stubs
268     @Override
onSingleTapUp(MotionEvent e)269     public boolean onSingleTapUp(MotionEvent e) { return false; }
270 
271     @Override
onLongPress(MotionEvent e)272     public void onLongPress(MotionEvent e) {}
273 
274     @Override
onShowPress(MotionEvent e)275     public void onShowPress(MotionEvent e) {}
276 
front(Matrix m)277     private boolean front(Matrix m) {
278         Matrix m2;
279         try {
280             m2 = Matrix.makeInverse(m);
281         } catch (RuntimeException e) {
282             m2 = new Matrix();
283         }
284         return m2.getAtRowCol(2, 2) > 0;
285     }
286 
287     // OnScaleGestureListener
288     @Override
onScale(ScaleGestureDetector detector)289     public boolean onScale(ScaleGestureDetector detector) {
290         zoom(detector.getScaleFactor());
291         return true;
292     }
293 
294     @Override
onScaleBegin(ScaleGestureDetector detector)295     public boolean onScaleBegin(ScaleGestureDetector detector) {
296         zoom(detector.getScaleFactor());
297         return true;
298     }
299 
300     @Override
onScaleEnd(ScaleGestureDetector detector)301     public void onScaleEnd(ScaleGestureDetector detector) {
302         zoom(detector.getScaleFactor());
303     }
304 }
305 
306 public class CubeActivity extends Activity {
307     static {
308         System.loadLibrary("jetski");
309     }
310 
311     private GestureDetector      mGestureDetector;
312     private ScaleGestureDetector mScaleDetector;
313 
314     @Override
onCreate(Bundle savedInstanceState)315     protected void onCreate(Bundle savedInstanceState) {
316         super.onCreate(savedInstanceState);
317         setContentView(R.layout.activity_cube);
318 
319         SurfaceView sv = findViewById(R.id.surfaceView);
320 
321         CubeRenderer renderer = new CubeRenderer(getResources());
322         sv.getHolder().addCallback(renderer);
323 
324         mGestureDetector = new GestureDetector(this, renderer);
325         mScaleDetector = new ScaleGestureDetector(this, renderer);
326     }
327 
328     @Override
onTouchEvent(MotionEvent e)329     public boolean onTouchEvent(MotionEvent e) {
330         // always dispatch to both detectors
331         mGestureDetector.onTouchEvent(e);
332         mScaleDetector.onTouchEvent(e);
333 
334         return true;
335     }
336 }
337