blob: d31c1a5dfea5a8a30150efab26b4d915998ae5ec [file] [log] [blame]
/*
* Copyright 2018 Google LLC All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.skar.examples.helloskar.rendering;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.graphics.Shader;
import android.opengl.Matrix;
import com.google.ar.core.Plane;
import com.google.ar.core.PointCloud;
import com.google.ar.core.Pose;
import com.google.ar.core.TrackingState;
import com.google.skar.CanvasMatrixUtil;
import com.google.skar.PaintUtil;
import com.google.skar.examples.helloskar.app.FingerPainting;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Collection;
/**
* Sample class that handles drawing different types of geometry using the matrices provided
* by ARCore. The matrices are handled by CanvasMatrixUtil in order to be passed to the drawing
* Canvas.
*/
public class DrawManager {
public enum DrawingType {
circle, rect, text, animation
}
// App defaults
public DrawManager.DrawingType currentDrawabletype = DrawManager.DrawingType.circle;
public boolean drawSmoothPainting = true;
// Camera matrices + viewport info
private float[] projectionMatrix = new float[16];
private float[] viewMatrix = new float[16];
private float viewportWidth;
private float viewportHeight;
// Paint modifiers
private ColorFilter lightFilter;
private BitmapShader planeShader;
// Drawables info
public ArrayList<float[]> modelMatrices = new ArrayList<>();
public FingerPainting fingerPainting = new FingerPainting(false);
/************ Initilization calls ****************************/
public void initializePlaneShader(Context context, String gridDistanceTextureName)
throws IOException {
// Read the texture.
Bitmap planeTexture =
BitmapFactory.decodeStream(context.getAssets().open(gridDistanceTextureName));
// Set up the shader
planeShader = new BitmapShader(planeTexture, Shader.TileMode.REPEAT,
Shader.TileMode.REPEAT);
}
/************ ARCore onDrawFrame() calls ********************/
public void updateViewport(float width, float height) {
viewportWidth = width;
viewportHeight = height;
}
public void updateProjectionMatrix(float[] projectionMatrix) {
this.projectionMatrix = projectionMatrix;
}
public void updateViewMatrix(float[] viewMatrix) {
this.viewMatrix = viewMatrix;
}
public void updateLightColorFilter(float[] colorCorr) {
lightFilter = PaintUtil.createLightCorrectionColorFilter(colorCorr);
}
/********** 2D objects drawing functions **********************/
// Sample function for drawing a circle
public void drawCircle(Canvas canvas) {
if (modelMatrices.isEmpty()) {
return;
}
Paint p = new Paint();
p.setColorFilter(lightFilter);
p.setARGB(180, 100, 0, 0);
canvas.save();
android.graphics.Matrix m = CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0),
viewMatrix, projectionMatrix, viewportWidth, viewportHeight);
canvas.setMatrix(m);
canvas.drawCircle(0, 0, 0.1f, p);
canvas.restore();
}
// Sample function for drawing an animated round rect.
// Radius parameter is animated by the application
public void drawAnimatedRoundRect(Canvas canvas, float radius) {
if (modelMatrices.isEmpty()) {
return;
}
Paint p = new Paint();
p.setColorFilter(lightFilter);
p.setARGB(180, 100, 0, 100);
canvas.save();
canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0),
viewMatrix, projectionMatrix, viewportWidth, viewportHeight));
canvas.drawRoundRect(0,0, 0.2f, 0.2f, radius, radius, p);
canvas.restore();
}
// Sample function for drawing a rect
public void drawRect(Canvas canvas) {
if (modelMatrices.isEmpty()) {
return;
}
Paint p = new Paint();
p.setColorFilter(lightFilter);
p.setARGB(180, 0, 0, 255);
canvas.save();
canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0),
viewMatrix, projectionMatrix, viewportWidth, viewportHeight));
RectF rect = new RectF(0, 0, 0.2f, 0.2f);
canvas.drawRect(rect, p);
canvas.restore();
}
// Sample function for drawing text on a canvas
public void drawText(Canvas canvas, String text) {
if (modelMatrices.isEmpty()) {
return;
}
Paint p = new Paint();
float textSize = 100;
p.setColorFilter(lightFilter);
p.setARGB(255, 255, 255, 0);
p.setTextSize(textSize);
// TODO: Remove scale matrix and scale text directly. Potential unfixed bug in versions < P
float[] scaleMatrix = getTextScaleMatrix(textSize);
float[] rotateMatrix = CanvasMatrixUtil.createXYtoXZRotationMatrix();
float[][] matrices = { scaleMatrix, rotateMatrix, modelMatrices.get(0), viewMatrix,
projectionMatrix,
CanvasMatrixUtil.createViewportMatrix(viewportWidth,
viewportHeight)};
canvas.save();
canvas.setMatrix(CanvasMatrixUtil.createMatrixFrom4x4(
CanvasMatrixUtil.multiplyMatrices4x4(matrices)));
canvas.drawText(text, 0, 0, p);
canvas.restore();
}
// Sample function for drawing a built FingerPainting object
public void drawFingerPainting(Canvas canvas) {
// If path empty, return
if (fingerPainting.getPaths().isEmpty()) {
return;
}
// Get finger painting model matrix
float[] model = fingerPainting.getModelMatrix();
float[] modelTranslate = new float[16]; // stores translate components of model matrix
Matrix.setIdentityM(modelTranslate, 0);
Matrix.translateM(modelTranslate, 0, model[12], model[13], model[14]);
// Rotation onto plane
float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix();
// Arbitrary scale for the finger painting
float[] scale = new float[16];
float s = 0.001f;
Matrix.setIdentityM(scale, 0);
Matrix.scaleM(scale, 0, s, s, s);
// Matrix = mvpv (model, view, projection, viewport)
float[][] matrices = {scale, initRot, modelTranslate, viewMatrix, projectionMatrix,
CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)};
android.graphics.Matrix mvpv =
CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices));
// Paint set up
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(30f);
p.setAlpha(120);
for (FingerPainting.BuiltPath bp : fingerPainting.getPaths()) {
if (bp.path.isEmpty()) {
continue;
}
p.setColor(bp.color);
// Scaling issues appear to happen when drawing a Path and transforming the Canvas
// directly with a matrix on Android versions less than P.
// TODO: Ideally switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
if (true) {
// Transform applied through canvas
canvas.save();
canvas.setMatrix(mvpv);
canvas.drawPath(bp.path, p);
canvas.restore();
} else {
// Transform path directly
Path pathDst = new Path();
bp.path.transform(mvpv, pathDst);
// Draw dest path
canvas.save();
canvas.setMatrix(new android.graphics.Matrix());
canvas.drawPath(pathDst, p);
canvas.restore();
}
}
}
/*********************** AR Environment drawing functions *************************/
// Sample function for drawing the AR point cloud
public void drawPointCloud(Canvas canvas, PointCloud cloud) {
FloatBuffer points = cloud.getPoints();
int numberOfPoints = points.remaining() / 4;
// Build vpv matrix
float[][] matrices = {viewMatrix, projectionMatrix,
CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)};
float[] vpv = CanvasMatrixUtil.multiplyMatrices4x4(matrices);
// Transform points on CPU
float[] pointsToDraw = new float[numberOfPoints * 2];
for (int i = 0; i < numberOfPoints; i++) {
float[] point = {points.get(i * 4), points.get(i * 4 + 1), points.get(i * 4 + 2), 1};
float[] result = CanvasMatrixUtil.multiplyMatrixVector(vpv, point, true);
pointsToDraw[i * 2] = result[0];
pointsToDraw[i * 2 + 1] = result[1];
}
Paint p = new Paint();
p.setARGB(220, 20, 232, 255);
p.setStrokeCap(Paint.Cap.SQUARE);
p.setStrokeWidth(6.0f);
canvas.save();
canvas.setMatrix(new android.graphics.Matrix());
canvas.drawPoints(pointsToDraw, p);
canvas.restore();
}
// Sample function for drawing AR planes
public void drawPlanes(Canvas canvas, Pose cameraPose, Collection<Plane> allPlanes) {
if (allPlanes.size() <= 0) {
return;
}
for (Plane plane : allPlanes) {
Plane subsumePlane = plane.getSubsumedBy();
if (plane.getTrackingState() != TrackingState.TRACKING || subsumePlane != null) {
continue;
}
float distance = calculateDistanceToPlane(plane.getCenterPose(), cameraPose);
if (distance < 0) { // Plane is back-facing.
continue;
}
// Get plane model matrix
float[] model = new float[16];
plane.getCenterPose().toMatrix(model, 0);
// Initial rotation
float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix();
// Matrix = mvpv
float[][] matrices = {initRot, model, viewMatrix, projectionMatrix,
CanvasMatrixUtil.createViewportMatrix(viewportWidth,
viewportHeight)};
android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4(
CanvasMatrixUtil.multiplyMatrices4x4(matrices));
drawPlaneOutline(canvas, mvpv, plane);
}
}
// Helper function that draws an AR plane's outline using a path
private void drawPlaneOutline(Canvas canvas, android.graphics.Matrix mvpv, Plane plane) {
int vertsSize = plane.getPolygon().limit() / 2;
FloatBuffer polygon = plane.getPolygon();
polygon.rewind();
// Build source path from polygon data
Path pathSrc = new Path();
pathSrc.moveTo(polygon.get(0), polygon.get(1));
for (int i = 1; i < vertsSize; i++) {
pathSrc.lineTo(polygon.get(i * 2), polygon.get(i * 2 + 1));
}
pathSrc.close();
// Set up paint
Paint p = new Paint();
p.setColor(Color.RED);
p.setAlpha(100);
p.setStrokeWidth(0.01f);
p.setStyle(Paint.Style.STROKE);
// Scaling issues appear to happen when drawing a Path and transforming the Canvas
// directly with a matrix on Android versions less than P.
// TODO: Ideally switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
if (true) {
// Draw dest path
canvas.save();
canvas.setMatrix(mvpv);
canvas.drawPath(pathSrc, p);
canvas.restore();
} else {
// Build destination path by transforming source path
Path pathDst = new Path();
pathSrc.transform(mvpv, pathDst);
// Draw dest path
canvas.save();
canvas.setMatrix(new android.graphics.Matrix());
canvas.drawPath(pathDst, p);
canvas.restore();
}
}
// Helper function that draws an AR plane using a path + initialized shader
private void drawPlaneWithShader(Canvas canvas, android.graphics.Matrix mvpv, Plane plane) {
int vertsSize = plane.getPolygon().limit() / 2;
FloatBuffer polygon = plane.getPolygon();
polygon.rewind();
// Build source path from polygon data
Path pathSrc = new Path();
pathSrc.moveTo(polygon.get(0), polygon.get(1));
for (int i = 1; i < vertsSize; i++) {
pathSrc.lineTo(polygon.get(i * 2), polygon.get(i * 2 + 1));
}
pathSrc.close();
// Set up paint
Paint p = new Paint();
p.setShader(planeShader);
p.setColorFilter(new PorterDuffColorFilter(Color.argb(0.4f, 1, 0, 0),
PorterDuff.Mode.SRC_ATOP));
p.setColor(Color.RED);
p.setAlpha(100);
p.setStrokeWidth(0.01f);
// Scaling issues appear to happen when drawing a Path and transforming the Canvas
// directly with a matrix on Android versions less than P.
// TODO: Ideally switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
if (true) {
// Shader local matrix
android.graphics.Matrix lm = new android.graphics.Matrix();
lm.setScale(0.00005f, 0.00005f);
planeShader.setLocalMatrix(lm);
// Draw dest path
canvas.save();
canvas.setMatrix(mvpv);
canvas.drawPath(pathSrc, p);
canvas.restore();
} else {
// Build destination path by transforming source path
Path pathDst = new Path();
pathSrc.transform(mvpv, pathDst);
// Shader local matrix
android.graphics.Matrix lm = new android.graphics.Matrix();
lm.setScale(0.00005f, 0.00005f);
lm.postConcat(mvpv);
planeShader.setLocalMatrix(lm);
// Draw dest path
canvas.save();
canvas.setMatrix(new android.graphics.Matrix());
canvas.drawPath(pathDst, p);
canvas.restore();
}
}
/*************** Misc helpers *************************************/
// Returns a scale matrix for drawing text given the size of the text. Workaround that solves
// a text size bug not fixed in Android versions < P
private float[] getTextScaleMatrix(float size) {
float scaleFactor = 1 / (size * 10);
float[] initScale = new float[16];
android.opengl.Matrix.setIdentityM(initScale, 0);
android.opengl.Matrix.scaleM(initScale, 0, scaleFactor, scaleFactor, scaleFactor);
return initScale;
}
public static float calculateDistanceToPlane(Pose planePose, Pose cameraPose) {
float[] normal = new float[3];
float cameraX = cameraPose.tx();
float cameraY = cameraPose.ty();
float cameraZ = cameraPose.tz();
// Get transformed Y axis of plane's coordinate system.
planePose.getTransformedAxis(1, 1.0f, normal, 0);
// Compute dot product of plane's normal with vector from camera to plane center.
return (cameraX - planePose.tx()) * normal[0]
+ (cameraY - planePose.ty()) * normal[1]
+ (cameraZ - planePose.tz()) * normal[2];
}
}