• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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