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