1 /* 2 * Copyright 2018 Google LLC All Rights Reserved. 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 17 package com.google.skar.examples.helloskar.rendering; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.graphics.BitmapShader; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.ColorFilter; 26 import android.graphics.Paint; 27 import android.graphics.Path; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffColorFilter; 30 import android.graphics.RectF; 31 import android.graphics.Shader; 32 import android.opengl.Matrix; 33 34 import com.google.ar.core.Plane; 35 import com.google.ar.core.PointCloud; 36 import com.google.ar.core.Pose; 37 import com.google.ar.core.TrackingState; 38 import com.google.skar.CanvasMatrixUtil; 39 import com.google.skar.PaintUtil; 40 import com.google.skar.examples.helloskar.app.FingerPainting; 41 42 import java.io.IOException; 43 import java.nio.FloatBuffer; 44 import java.util.ArrayList; 45 import java.util.Collection; 46 47 /** 48 * Sample class that handles drawing different types of geometry using the matrices provided 49 * by ARCore. The matrices are handled by CanvasMatrixUtil in order to be passed to the drawing 50 * Canvas. 51 */ 52 53 public class DrawManager { 54 public enum DrawingType { 55 circle, rect, text, animation 56 } 57 58 // App defaults 59 public DrawManager.DrawingType currentDrawabletype = DrawManager.DrawingType.circle; 60 public boolean drawSmoothPainting = true; 61 62 // Camera matrices + viewport info 63 private float[] projectionMatrix = new float[16]; 64 private float[] viewMatrix = new float[16]; 65 private float viewportWidth; 66 private float viewportHeight; 67 68 // Paint modifiers 69 private ColorFilter lightFilter; 70 private BitmapShader planeShader; 71 72 // Drawables info 73 public ArrayList<float[]> modelMatrices = new ArrayList<>(); 74 public FingerPainting fingerPainting = new FingerPainting(false); 75 76 /************ Initilization calls ****************************/ initializePlaneShader(Context context, String gridDistanceTextureName)77 public void initializePlaneShader(Context context, String gridDistanceTextureName) 78 throws IOException { 79 // Read the texture. 80 Bitmap planeTexture = 81 BitmapFactory.decodeStream(context.getAssets().open(gridDistanceTextureName)); 82 // Set up the shader 83 planeShader = new BitmapShader(planeTexture, Shader.TileMode.REPEAT, 84 Shader.TileMode.REPEAT); 85 } 86 87 /************ ARCore onDrawFrame() calls ********************/ updateViewport(float width, float height)88 public void updateViewport(float width, float height) { 89 viewportWidth = width; 90 viewportHeight = height; 91 } 92 updateProjectionMatrix(float[] projectionMatrix)93 public void updateProjectionMatrix(float[] projectionMatrix) { 94 this.projectionMatrix = projectionMatrix; 95 } 96 updateViewMatrix(float[] viewMatrix)97 public void updateViewMatrix(float[] viewMatrix) { 98 this.viewMatrix = viewMatrix; 99 } 100 updateLightColorFilter(float[] colorCorr)101 public void updateLightColorFilter(float[] colorCorr) { 102 lightFilter = PaintUtil.createLightCorrectionColorFilter(colorCorr); 103 } 104 105 /********** 2D objects drawing functions **********************/ 106 107 // Sample function for drawing a circle drawCircle(Canvas canvas)108 public void drawCircle(Canvas canvas) { 109 if (modelMatrices.isEmpty()) { 110 return; 111 } 112 113 Paint p = new Paint(); 114 p.setColorFilter(lightFilter); 115 p.setARGB(180, 100, 0, 0); 116 117 canvas.save(); 118 android.graphics.Matrix m = CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), 119 viewMatrix, projectionMatrix, viewportWidth, viewportHeight); 120 canvas.setMatrix(m); 121 122 canvas.drawCircle(0, 0, 0.1f, p); 123 canvas.restore(); 124 } 125 126 // Sample function for drawing an animated round rect. 127 // Radius parameter is animated by the application drawAnimatedRoundRect(Canvas canvas, float radius)128 public void drawAnimatedRoundRect(Canvas canvas, float radius) { 129 if (modelMatrices.isEmpty()) { 130 return; 131 } 132 Paint p = new Paint(); 133 p.setColorFilter(lightFilter); 134 p.setARGB(180, 100, 0, 100); 135 136 canvas.save(); 137 canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), 138 viewMatrix, projectionMatrix, viewportWidth, viewportHeight)); 139 canvas.drawRoundRect(0,0, 0.2f, 0.2f, radius, radius, p); 140 canvas.restore(); 141 } 142 143 // Sample function for drawing a rect drawRect(Canvas canvas)144 public void drawRect(Canvas canvas) { 145 if (modelMatrices.isEmpty()) { 146 return; 147 } 148 Paint p = new Paint(); 149 p.setColorFilter(lightFilter); 150 p.setARGB(180, 0, 0, 255); 151 152 canvas.save(); 153 canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0), 154 viewMatrix, projectionMatrix, viewportWidth, viewportHeight)); 155 RectF rect = new RectF(0, 0, 0.2f, 0.2f); 156 canvas.drawRect(rect, p); 157 canvas.restore(); 158 } 159 160 // Sample function for drawing text on a canvas drawText(Canvas canvas, String text)161 public void drawText(Canvas canvas, String text) { 162 if (modelMatrices.isEmpty()) { 163 return; 164 } 165 Paint p = new Paint(); 166 float textSize = 100; 167 p.setColorFilter(lightFilter); 168 p.setARGB(255, 255, 255, 0); 169 p.setTextSize(textSize); 170 171 // TODO: Remove scale matrix and scale text directly. Potential unfixed bug in versions < P 172 float[] scaleMatrix = getTextScaleMatrix(textSize); 173 float[] rotateMatrix = CanvasMatrixUtil.createXYtoXZRotationMatrix(); 174 float[][] matrices = { scaleMatrix, rotateMatrix, modelMatrices.get(0), viewMatrix, 175 projectionMatrix, 176 CanvasMatrixUtil.createViewportMatrix(viewportWidth, 177 viewportHeight)}; 178 179 canvas.save(); 180 canvas.setMatrix(CanvasMatrixUtil.createMatrixFrom4x4( 181 CanvasMatrixUtil.multiplyMatrices4x4(matrices))); 182 canvas.drawText(text, 0, 0, p); 183 canvas.restore(); 184 } 185 186 // Sample function for drawing a built FingerPainting object drawFingerPainting(Canvas canvas)187 public void drawFingerPainting(Canvas canvas) { 188 // If path empty, return 189 if (fingerPainting.getPaths().isEmpty()) { 190 return; 191 } 192 193 // Get finger painting model matrix 194 float[] model = fingerPainting.getModelMatrix(); 195 float[] modelTranslate = new float[16]; // stores translate components of model matrix 196 Matrix.setIdentityM(modelTranslate, 0); 197 Matrix.translateM(modelTranslate, 0, model[12], model[13], model[14]); 198 199 // Rotation onto plane 200 float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix(); 201 202 // Arbitrary scale for the finger painting 203 float[] scale = new float[16]; 204 float s = 0.001f; 205 Matrix.setIdentityM(scale, 0); 206 Matrix.scaleM(scale, 0, s, s, s); 207 208 // Matrix = mvpv (model, view, projection, viewport) 209 float[][] matrices = {scale, initRot, modelTranslate, viewMatrix, projectionMatrix, 210 CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; 211 android.graphics.Matrix mvpv = 212 CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices)); 213 214 // Paint set up 215 Paint p = new Paint(); 216 p.setStyle(Paint.Style.STROKE); 217 p.setStrokeWidth(30f); 218 p.setAlpha(120); 219 220 for (FingerPainting.BuiltPath bp : fingerPainting.getPaths()) { 221 if (bp.path.isEmpty()) { 222 continue; 223 } 224 225 p.setColor(bp.color); 226 227 // Scaling issues appear to happen when drawing a Path and transforming the Canvas 228 // directly with a matrix on Android versions less than P. 229 // TODO: Ideally switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) 230 231 if (true) { 232 // Transform applied through canvas 233 canvas.save(); 234 canvas.setMatrix(mvpv); 235 canvas.drawPath(bp.path, p); 236 canvas.restore(); 237 } else { 238 // Transform path directly 239 Path pathDst = new Path(); 240 bp.path.transform(mvpv, pathDst); 241 242 // Draw dest path 243 canvas.save(); 244 canvas.setMatrix(new android.graphics.Matrix()); 245 canvas.drawPath(pathDst, p); 246 canvas.restore(); 247 } 248 } 249 250 } 251 252 /*********************** AR Environment drawing functions *************************/ 253 254 // Sample function for drawing the AR point cloud drawPointCloud(Canvas canvas, PointCloud cloud)255 public void drawPointCloud(Canvas canvas, PointCloud cloud) { 256 FloatBuffer points = cloud.getPoints(); 257 int numberOfPoints = points.remaining() / 4; 258 259 // Build vpv matrix 260 float[][] matrices = {viewMatrix, projectionMatrix, 261 CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)}; 262 float[] vpv = CanvasMatrixUtil.multiplyMatrices4x4(matrices); 263 264 // Transform points on CPU 265 float[] pointsToDraw = new float[numberOfPoints * 2]; 266 for (int i = 0; i < numberOfPoints; i++) { 267 float[] point = {points.get(i * 4), points.get(i * 4 + 1), points.get(i * 4 + 2), 1}; 268 float[] result = CanvasMatrixUtil.multiplyMatrixVector(vpv, point, true); 269 pointsToDraw[i * 2] = result[0]; 270 pointsToDraw[i * 2 + 1] = result[1]; 271 } 272 273 Paint p = new Paint(); 274 p.setARGB(220, 20, 232, 255); 275 p.setStrokeCap(Paint.Cap.SQUARE); 276 p.setStrokeWidth(6.0f); 277 278 canvas.save(); 279 canvas.setMatrix(new android.graphics.Matrix()); 280 canvas.drawPoints(pointsToDraw, p); 281 canvas.restore(); 282 } 283 284 285 // Sample function for drawing AR planes drawPlanes(Canvas canvas, Pose cameraPose, Collection<Plane> allPlanes)286 public void drawPlanes(Canvas canvas, Pose cameraPose, Collection<Plane> allPlanes) { 287 if (allPlanes.size() <= 0) { 288 return; 289 } 290 291 for (Plane plane : allPlanes) { 292 Plane subsumePlane = plane.getSubsumedBy(); 293 if (plane.getTrackingState() != TrackingState.TRACKING || subsumePlane != null) { 294 continue; 295 } 296 297 float distance = calculateDistanceToPlane(plane.getCenterPose(), cameraPose); 298 if (distance < 0) { // Plane is back-facing. 299 continue; 300 } 301 302 // Get plane model matrix 303 float[] model = new float[16]; 304 plane.getCenterPose().toMatrix(model, 0); 305 306 // Initial rotation 307 float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix(); 308 309 // Matrix = mvpv 310 float[][] matrices = {initRot, model, viewMatrix, projectionMatrix, 311 CanvasMatrixUtil.createViewportMatrix(viewportWidth, 312 viewportHeight)}; 313 android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4( 314 CanvasMatrixUtil.multiplyMatrices4x4(matrices)); 315 316 drawPlaneOutline(canvas, mvpv, plane); 317 } 318 } 319 320 // Helper function that draws an AR plane's outline using a path drawPlaneOutline(Canvas canvas, android.graphics.Matrix mvpv, Plane plane)321 private void drawPlaneOutline(Canvas canvas, android.graphics.Matrix mvpv, Plane plane) { 322 int vertsSize = plane.getPolygon().limit() / 2; 323 FloatBuffer polygon = plane.getPolygon(); 324 polygon.rewind(); 325 326 // Build source path from polygon data 327 Path pathSrc = new Path(); 328 pathSrc.moveTo(polygon.get(0), polygon.get(1)); 329 for (int i = 1; i < vertsSize; i++) { 330 pathSrc.lineTo(polygon.get(i * 2), polygon.get(i * 2 + 1)); 331 } 332 pathSrc.close(); 333 334 // Set up paint 335 Paint p = new Paint(); 336 p.setColor(Color.RED); 337 p.setAlpha(100); 338 p.setStrokeWidth(0.01f); 339 p.setStyle(Paint.Style.STROKE); 340 341 // Scaling issues appear to happen when drawing a Path and transforming the Canvas 342 // directly with a matrix on Android versions less than P. 343 // TODO: Ideally switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) 344 345 if (true) { 346 // Draw dest path 347 canvas.save(); 348 canvas.setMatrix(mvpv); 349 canvas.drawPath(pathSrc, p); 350 canvas.restore(); 351 } else { 352 // Build destination path by transforming source path 353 Path pathDst = new Path(); 354 pathSrc.transform(mvpv, pathDst); 355 356 // Draw dest path 357 canvas.save(); 358 canvas.setMatrix(new android.graphics.Matrix()); 359 canvas.drawPath(pathDst, p); 360 canvas.restore(); 361 } 362 } 363 364 // Helper function that draws an AR plane using a path + initialized shader drawPlaneWithShader(Canvas canvas, android.graphics.Matrix mvpv, Plane plane)365 private void drawPlaneWithShader(Canvas canvas, android.graphics.Matrix mvpv, Plane plane) { 366 int vertsSize = plane.getPolygon().limit() / 2; 367 FloatBuffer polygon = plane.getPolygon(); 368 polygon.rewind(); 369 370 // Build source path from polygon data 371 Path pathSrc = new Path(); 372 pathSrc.moveTo(polygon.get(0), polygon.get(1)); 373 for (int i = 1; i < vertsSize; i++) { 374 pathSrc.lineTo(polygon.get(i * 2), polygon.get(i * 2 + 1)); 375 } 376 pathSrc.close(); 377 378 // Set up paint 379 Paint p = new Paint(); 380 p.setShader(planeShader); 381 p.setColorFilter(new PorterDuffColorFilter(Color.argb(0.4f, 1, 0, 0), 382 PorterDuff.Mode.SRC_ATOP)); 383 p.setColor(Color.RED); 384 p.setAlpha(100); 385 p.setStrokeWidth(0.01f); 386 387 // Scaling issues appear to happen when drawing a Path and transforming the Canvas 388 // directly with a matrix on Android versions less than P. 389 // TODO: Ideally switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) 390 if (true) { 391 // Shader local matrix 392 android.graphics.Matrix lm = new android.graphics.Matrix(); 393 lm.setScale(0.00005f, 0.00005f); 394 planeShader.setLocalMatrix(lm); 395 396 // Draw dest path 397 canvas.save(); 398 canvas.setMatrix(mvpv); 399 canvas.drawPath(pathSrc, p); 400 canvas.restore(); 401 } else { 402 // Build destination path by transforming source path 403 Path pathDst = new Path(); 404 pathSrc.transform(mvpv, pathDst); 405 406 // Shader local matrix 407 android.graphics.Matrix lm = new android.graphics.Matrix(); 408 lm.setScale(0.00005f, 0.00005f); 409 lm.postConcat(mvpv); 410 planeShader.setLocalMatrix(lm); 411 412 // Draw dest path 413 canvas.save(); 414 canvas.setMatrix(new android.graphics.Matrix()); 415 canvas.drawPath(pathDst, p); 416 canvas.restore(); 417 } 418 } 419 420 /*************** Misc helpers *************************************/ 421 // Returns a scale matrix for drawing text given the size of the text. Workaround that solves 422 // a text size bug not fixed in Android versions < P getTextScaleMatrix(float size)423 private float[] getTextScaleMatrix(float size) { 424 float scaleFactor = 1 / (size * 10); 425 float[] initScale = new float[16]; 426 android.opengl.Matrix.setIdentityM(initScale, 0); 427 android.opengl.Matrix.scaleM(initScale, 0, scaleFactor, scaleFactor, scaleFactor); 428 return initScale; 429 } 430 calculateDistanceToPlane(Pose planePose, Pose cameraPose)431 public static float calculateDistanceToPlane(Pose planePose, Pose cameraPose) { 432 float[] normal = new float[3]; 433 float cameraX = cameraPose.tx(); 434 float cameraY = cameraPose.ty(); 435 float cameraZ = cameraPose.tz(); 436 437 // Get transformed Y axis of plane's coordinate system. 438 planePose.getTransformedAxis(1, 1.0f, normal, 0); 439 // Compute dot product of plane's normal with vector from camera to plane center. 440 return (cameraX - planePose.tx()) * normal[0] 441 + (cameraY - planePose.ty()) * normal[1] 442 + (cameraZ - planePose.tz()) * normal[2]; 443 } 444 } 445