/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2015, 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
package android.view;
-import com.android.layoutlib.bridge.impl.GcSnapshot;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-
+import android.annotation.NonNull;
import android.graphics.Canvas;
-import android.graphics.Canvas_Delegate;
-import android.graphics.LinearGradient;
import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.Path;
-import android.graphics.Path.FillType;
-import android.graphics.RadialGradient;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region.Op;
-import android.graphics.Shader.TileMode;
-
-import java.awt.Rectangle;
+import com.android.layoutlib.bridge.shadowutil.SpotShadow;
+import com.android.layoutlib.bridge.shadowutil.ShadowBuffer;
-/**
- * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly,
- * since it modifies the size of the content, that we can't do.
- */
public class RectShadowPainter {
+ private static final float SHADOW_STRENGTH = 0.1f;
+ private static final int LIGHT_POINTS = 8;
- private static final int START_COLOR = ResourceHelper.getColor("#37000000");
- private static final int END_COLOR = ResourceHelper.getColor("#03000000");
- private static final float PERPENDICULAR_ANGLE = 90f;
+ private static final int QUADRANT_DIVIDED_COUNT = 8;
- public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+ private static final int RAY_TRACING_RAYS = 180;
+ private static final int RAY_TRACING_LAYERS = 10;
+
+ public static void paintShadow(@NonNull Outline viewOutline, float elevation,
+ @NonNull Canvas canvas) {
Rect outline = new Rect();
if (!viewOutline.getRect(outline)) {
throw new IllegalArgumentException("Outline is not a rect shadow");
}
- // TODO replacing the algorithm here to create better shadow
-
- float shadowSize = elevationToShadow(elevation);
- int saved = modifyCanvas(canvas, shadowSize);
+ Rect originCanvasRect = canvas.getClipBounds();
+ int saved = modifyCanvas(canvas);
if (saved == -1) {
return;
}
-
- float radius = viewOutline.getRadius();
- if (radius <= 0) {
- // We can not paint a shadow with radius 0
- return;
- }
-
try {
- Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
- cornerPaint.setStyle(Style.FILL);
- Paint edgePaint = new Paint(cornerPaint);
- edgePaint.setAntiAlias(false);
- float outerArcRadius = radius + shadowSize;
- int[] colors = {START_COLOR, START_COLOR, END_COLOR};
- cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
- new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
- edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR,
- TileMode.CLAMP));
- Path path = new Path();
- path.setFillType(FillType.EVEN_ODD);
- // A rectangle bounding the complete shadow.
- RectF shadowRect = new RectF(outline);
- shadowRect.inset(-shadowSize, -shadowSize);
- // A rectangle with edges corresponding to the straight edges of the outline.
- RectF inset = new RectF(outline);
- inset.inset(radius, radius);
- // A rectangle used to represent the edge shadow.
- RectF edgeShadowRect = new RectF();
-
-
- // left and right sides.
- edgeShadowRect.set(-shadowSize, 0f, 0f, inset.height());
- // Left shadow
- sideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0);
- // Right shadow
- sideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2);
- // Top shadow
- edgeShadowRect.set(-shadowSize, 0, 0, inset.width());
- sideShadow(canvas, edgePaint, edgeShadowRect, inset.right, outline.top, 1);
- // bottom shadow. This needs an inset so that blank doesn't appear when the content is
- // moved up.
- edgeShadowRect.set(-shadowSize, 0, shadowSize / 2f, inset.width());
- edgePaint.setShader(new LinearGradient(edgeShadowRect.right, 0, edgeShadowRect.left, 0,
- colors, new float[]{0f, 1 / 3f, 1f}, TileMode.CLAMP));
- sideShadow(canvas, edgePaint, edgeShadowRect, inset.left, outline.bottom, 3);
-
- // Draw corners.
- drawCorner(canvas, cornerPaint, path, inset.right, inset.bottom, outerArcRadius, 0);
- drawCorner(canvas, cornerPaint, path, inset.left, inset.bottom, outerArcRadius, 1);
- drawCorner(canvas, cornerPaint, path, inset.left, inset.top, outerArcRadius, 2);
- drawCorner(canvas, cornerPaint, path, inset.right, inset.top, outerArcRadius, 3);
+ float radius = viewOutline.getRadius();
+ if (radius <= 0) {
+ // We can not paint a shadow with radius 0
+ return;
+ }
+
+ // view's absolute position in this canvas.
+ int viewLeft = -originCanvasRect.left + outline.left;
+ int viewTop = -originCanvasRect.top + outline.top;
+ int viewRight = viewLeft + outline.width();
+ int viewBottom = viewTop + outline.height();
+
+ float[][] rectangleCoordinators = generateRectangleCoordinates(viewLeft, viewTop,
+ viewRight, viewBottom, radius, elevation);
+
+ // TODO: get these values from resources.
+ float lightPosX = canvas.getWidth() / 2;
+ float lightPosY = 0;
+ float lightHeight = 1800;
+ float lightSize = 200;
+
+ paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
+ lightSize, canvas);
} finally {
canvas.restoreToCount(saved);
}
}
- private static float elevationToShadow(float elevation) {
- // The factor is chosen by eyeballing the shadow size on device and preview.
- return elevation * 0.5f;
+ private static int modifyCanvas(@NonNull Canvas canvas) {
+ Rect rect = canvas.getClipBounds();
+ canvas.translate(rect.left, rect.top);
+ return canvas.save();
}
- /**
- * Translate canvas by half of shadow size up, so that it appears that light is coming
- * slightly from above. Also, remove clipping, so that shadow is not clipped.
- */
- private static int modifyCanvas(Canvas canvas, float shadowSize) {
- Rect clipBounds = canvas.getClipBounds();
- if (clipBounds.isEmpty()) {
- return -1;
+ @NonNull
+ private static float[][] generateRectangleCoordinates(float left, float top, float right,
+ float bottom, float radius, float elevation) {
+ left = left + radius;
+ top = top + radius;
+ right = right - radius;
+ bottom = bottom - radius;
+
+ final double RADIANS_STEP = 2 * Math.PI / 4 / QUADRANT_DIVIDED_COUNT;
+
+ float[][] ret = new float[QUADRANT_DIVIDED_COUNT * 4][3];
+
+ int points = 0;
+ // left-bottom points
+ for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+ ret[points][0] = (float) (left - radius + radius * Math.cos(RADIANS_STEP * i));
+ ret[points][1] = (float) (bottom + radius - radius * Math.cos(RADIANS_STEP * i));
+ ret[points][2] = elevation;
+ points++;
+ }
+ // left-top points
+ for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+ ret[points][0] = (float) (left + radius - radius * Math.cos(RADIANS_STEP * i));
+ ret[points][1] = (float) (top + radius - radius * Math.cos(RADIANS_STEP * i));
+ ret[points][2] = elevation;
+ points++;
}
- int saved = canvas.save();
- // Usually canvas has been translated to the top left corner of the view when this is
- // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow.
- // Thus, we just expand in each direction by width and height of the canvas, while staying
- // inside the original drawing region.
- GcSnapshot snapshot = Canvas_Delegate.getDelegate(canvas).getSnapshot();
- Rectangle originalClip = snapshot.getOriginalClip();
- if (originalClip != null) {
- canvas.clipRect(originalClip.x, originalClip.y, originalClip.x + originalClip.width,
- originalClip.y + originalClip.height, Op.REPLACE);
- canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
- canvas.getHeight(), Op.INTERSECT);
+ // right-top points
+ for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+ ret[points][0] = (float) (right + radius - radius * Math.cos(RADIANS_STEP * i));
+ ret[points][1] = (float) (top + radius + radius * Math.cos(RADIANS_STEP * i));
+ ret[points][2] = elevation;
+ points++;
}
- canvas.translate(0, shadowSize / 2f);
- return saved;
+ // right-bottom point
+ for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+ ret[points][0] = (float) (right - radius + radius * Math.cos(RADIANS_STEP * i));
+ ret[points][1] = (float) (bottom - radius + radius * Math.cos(RADIANS_STEP * i));
+ ret[points][2] = elevation;
+ points++;
+ }
+
+ return ret;
}
- private static void sideShadow(Canvas canvas, Paint edgePaint,
- RectF edgeShadowRect, float dx, float dy, int rotations) {
- if (isRectEmpty(edgeShadowRect)) {
- return;
+ private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
+ float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
+
+ // The polygon of shadow (same as the original item)
+ float[] shadowPoly = new float[coordinates.length * 3];
+ for (int i = 0; i < coordinates.length; i++) {
+ shadowPoly[i * 3 + 0] = coordinates[i][0];
+ shadowPoly[i * 3 + 1] = coordinates[i][1];
+ shadowPoly[i * 3 + 2] = coordinates[i][2];
}
- int saved = canvas.save();
- canvas.translate(dx, dy);
- canvas.rotate(rotations * PERPENDICULAR_ANGLE);
- canvas.drawRect(edgeShadowRect, edgePaint);
- canvas.restoreToCount(saved);
- }
- /**
- * @param canvas Canvas to draw the rectangle on.
- * @param paint Paint to use when drawing the corner.
- * @param path A path to reuse. Prevents allocating memory for each path.
- * @param x Center of circle, which this corner is a part of.
- * @param y Center of circle, which this corner is a part of.
- * @param radius radius of the arc
- * @param rotations number of quarter rotations before starting to paint the arc.
- */
- private static void drawCorner(Canvas canvas, Paint paint, Path path, float x, float y,
- float radius, int rotations) {
- int saved = canvas.save();
- canvas.translate(x, y);
- path.reset();
- path.arcTo(-radius, -radius, radius, radius, rotations * PERPENDICULAR_ANGLE,
- PERPENDICULAR_ANGLE, false);
- path.lineTo(0, 0);
- path.close();
- canvas.drawPath(path, paint);
- canvas.restoreToCount(saved);
- }
+ // TODO: calculate the ambient shadow and mix with Spot shadow.
+
+ // Calculate the shadow of SpotLight
+ float[] light = SpotShadow.calculateLight(lightSize, LIGHT_POINTS, lightPosX,
+ lightPosY, lightHeight);
+
+ int stripSize = 3 * SpotShadow.getStripSize(RAY_TRACING_RAYS, RAY_TRACING_LAYERS);
+ if (stripSize < 9) {
+ return;
+ }
+ float[] strip = new float[stripSize];
+ SpotShadow.calcShadow(light, LIGHT_POINTS, shadowPoly, coordinates.length, RAY_TRACING_RAYS,
+ RAY_TRACING_LAYERS, 1f, strip);
- /**
- * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
- * <p/>
- * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float,
- * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
- * drawing empty rectangles, which results in IllegalArgumentException.
- */
- private static boolean isRectEmpty(RectF rect) {
- return (int) rect.left >= (int) rect.right || (int) rect.top >= (int) rect.bottom;
+ ShadowBuffer buff = new ShadowBuffer(canvas.getWidth(), canvas.getHeight());
+ buff.generateTriangles(strip, SHADOW_STRENGTH);
+ buff.draw(canvas);
}
}
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.layoutlib.bridge.shadowutil;
+
+import android.annotation.NonNull;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+
+public class ShadowBuffer {
+
+ private int mWidth;
+ private int mHeight;
+ private Bitmap mBitmap;
+ private int[] mData;
+
+ public ShadowBuffer(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ mData = new int[mBitmap.getWidth() * mBitmap.getHeight()];
+ mBitmap.getPixels(mData, 0, mBitmap.getWidth(), 0, 0, mBitmap.getWidth(),
+ mBitmap.getHeight());
+ }
+
+ public void generateTriangles(@NonNull float[] strip, float scale) {
+ for (int i = 0; i < strip.length - 8; i += 3) {
+ float fx3 = strip[i];
+ float fy3 = strip[i + 1];
+ float fz3 = scale * strip[i + 2];
+
+ float fx2 = strip[i + 3];
+ float fy2 = strip[i + 4];
+ float fz2 = scale * strip[i + 5];
+
+ float fx1 = strip[i + 6];
+ float fy1 = strip[i + 7];
+ float fz1 = scale * strip[i + 8];
+
+ if (fx1 * (fy2 - fy3) + fx2 * (fy3 - fy1) + fx3 * (fy1 - fy2) == 0) {
+ continue;
+ }
+
+ triangleZBuffMin(mData, mWidth, mHeight, fx3, fy3, fz3, fx2, fy2, fz2, fx1, fy1, fz1);
+ triangleZBuffMin(mData, mWidth, mHeight, fx1, fy1, fz1, fx2, fy2, fz2, fx3, fy3, fz3);
+ }
+ mBitmap.setPixels(mData, 0, mBitmap.getWidth(), 0, 0, mBitmap.getWidth(),
+ mBitmap.getHeight());
+ }
+
+ private void triangleZBuffMin(@NonNull int[] buff, int w, int h, float fx3, float fy3,
+ float fz3, float fx2, float fy2, float fz2, float fx1, float fy1, float fz1) {
+ if (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0) {
+ float tmpX = fx1;
+ float tmpY = fy1;
+ float tmpZ = fz1;
+ fx1 = fx2;
+ fy1 = fy2;
+ fz1 = fz2;
+ fx2 = tmpX;
+ fy2 = tmpY;
+ fz2 = tmpZ;
+ }
+ double d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + (fx2 - fx3) * fy1);
+
+ if (d == 0) {
+ return;
+ }
+ float dx = (float) (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + (fy2 - fy3) * fz1) / d);
+ float dy = (float) ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + (fx2 - fx3) * fz1) / d);
+ float zOff = (float) ((fx1 * (fy3 * fz2 - fy2 * fz3) + fy1 * (fx2 * fz3 - fx3 * fz2) +
+ (fx3 * fy2 - fx2 * fy3) * fz1) / d);
+
+ int Y1 = (int) (16.0f * fy1 + .5f);
+ int Y2 = (int) (16.0f * fy2 + .5f);
+ int Y3 = (int) (16.0f * fy3 + .5f);
+
+ int X1 = (int) (16.0f * fx1 + .5f);
+ int X2 = (int) (16.0f * fx2 + .5f);
+ int X3 = (int) (16.0f * fx3 + .5f);
+
+ int DX12 = X1 - X2;
+ int DX23 = X2 - X3;
+ int DX31 = X3 - X1;
+
+ int DY12 = Y1 - Y2;
+ int DY23 = Y2 - Y3;
+ int DY31 = Y3 - Y1;
+
+ int FDX12 = DX12 << 4;
+ int FDX23 = DX23 << 4;
+ int FDX31 = DX31 << 4;
+
+ int FDY12 = DY12 << 4;
+ int FDY23 = DY23 << 4;
+ int FDY31 = DY31 << 4;
+
+ int minX = (min(X1, X2, X3) + 0xF) >> 4;
+ int maxX = (max(X1, X2, X3) + 0xF) >> 4;
+ int minY = (min(Y1, Y2, Y3) + 0xF) >> 4;
+ int maxY = (max(Y1, Y2, Y3) + 0xF) >> 4;
+
+ if (minY < 0) {
+ minY = 0;
+ }
+ if (minX < 0) {
+ minX = 0;
+ }
+ if (maxX > w) {
+ maxX = w;
+ }
+ if (maxY > h) {
+ maxY = h;
+ }
+ int off = minY * w;
+
+ int C1 = DY12 * X1 - DX12 * Y1;
+ int C2 = DY23 * X2 - DX23 * Y2;
+ int C3 = DY31 * X3 - DX31 * Y3;
+
+ if (DY12 < 0 || (DY12 == 0 && DX12 > 0)) {
+ C1++;
+ }
+ if (DY23 < 0 || (DY23 == 0 && DX23 > 0)) {
+ C2++;
+ }
+ if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) {
+ C3++;
+ }
+ int CY1 = C1 + DX12 * (minY << 4) - DY12 * (minX << 4);
+ int CY2 = C2 + DX23 * (minY << 4) - DY23 * (minX << 4);
+ int CY3 = C3 + DX31 * (minY << 4) - DY31 * (minX << 4);
+
+ for (int y = minY; y < maxY; y++) {
+ int CX1 = CY1;
+ int CX2 = CY2;
+ int CX3 = CY3;
+ float p = zOff + dy * y;
+ for (int x = minX; x < maxX; x++) {
+ if (CX1 > 0 && CX2 > 0 && CX3 > 0) {
+ int point = x + off;
+ float zVal = p + dx * x;
+ buff[point] |= ((int) (zVal * 255)) << 24;
+ }
+ CX1 -= FDY12;
+ CX2 -= FDY23;
+ CX3 -= FDY31;
+ }
+ CY1 += FDX12;
+ CY2 += FDX23;
+ CY3 += FDX31;
+ off += w;
+ }
+ }
+
+ private int min(int x1, int x2, int x3) {
+ return (x1 > x2) ? ((x2 > x3) ? x3 : x2) : ((x1 > x3) ? x3 : x1);
+ }
+
+ private int max(int x1, int x2, int x3) {
+ return (x1 < x2) ? ((x2 < x3) ? x3 : x2) : ((x1 < x3) ? x3 : x1);
+ }
+
+ public void draw(@NonNull Canvas c) {
+ c.drawBitmap(mBitmap, 0, 0, null);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.android.layoutlib.bridge.shadowutil;
+
+import android.annotation.NonNull;
+
+public class SpotShadow {
+
+ private static float rayIntersectPoly(@NonNull float[] poly, int polyLength, float px, float py,
+ float dx, float dy) {
+ int p1 = polyLength - 1;
+ for (int p2 = 0; p2 < polyLength; p2++) {
+ float p1x = poly[p1 * 2 + 0];
+ float p1y = poly[p1 * 2 + 1];
+ float p2x = poly[p2 * 2 + 0];
+ float p2y = poly[p2 * 2 + 1];
+ float div = (dx * (p1y - p2y) + dy * p2x - dy * p1x);
+ if (div != 0) {
+ float t = (dx * (p1y - py) + dy * px - dy * p1x) / (div);
+ if (t >= 0 && t <= 1) {
+ float t2 = (p1x * (py - p2y) + p2x * (p1y - py) + px * (p2y - p1y)) / div;
+ if (t2 > 0) {
+ return t2;
+ }
+ }
+ }
+ p1 = p2;
+ }
+ return Float.NaN;
+ }
+
+ private static void centroid2d(@NonNull float[] poly, int len, @NonNull float[] ret) {
+ float sumX = 0;
+ float sumY = 0;
+ int p1 = len - 1;
+ float area = 0;
+ for (int p2 = 0; p2 < len; p2++) {
+ float x1 = poly[p1 * 2 + 0];
+ float y1 = poly[p1 * 2 + 1];
+ float x2 = poly[p2 * 2 + 0];
+ float y2 = poly[p2 * 2 + 1];
+ float a = (x1 * y2 - x2 * y1);
+ sumX += (x1 + x2) * a;
+ sumY += (y1 + y2) * a;
+ area += a;
+ p1 = p2;
+ }
+
+ float centroidX = sumX / (3 * area);
+ float centroidY = sumY / (3 * area);
+ ret[0] = centroidX;
+ ret[1] = centroidY;
+ }
+
+ /**
+ * calculates the Centroid of a 3d polygon
+ * @param poly The flatten 3d vertices coordinates of polygon, the format is like
+ * [x0, y0, z0, x1, y1, z1, x2, ...]
+ * @param len The number of polygon vertices. So the length of poly should be len * 3.
+ * @param ret The array used to sotre the result. The length should be 3.
+ */
+ private static void centroid3d(@NonNull float[] poly, int len, @NonNull float[] ret) {
+ int n = len - 1;
+ double area = 0;
+ double cx = 0, cy = 0, cz = 0;
+ for (int i = 1; i < n; i++) {
+ int k = i + 1;
+ float a0 = poly[i * 3 + 0] - poly[0 * 3 + 0];
+ float a1 = poly[i * 3 + 1] - poly[0 * 3 + 1];
+ float a2 = poly[i * 3 + 2] - poly[0 * 3 + 2];
+ float b0 = poly[k * 3 + 0] - poly[0 * 3 + 0];
+ float b1 = poly[k * 3 + 1] - poly[0 * 3 + 1];
+ float b2 = poly[k * 3 + 2] - poly[0 * 3 + 2];
+ float c0 = a1 * b2 - b1 * a2;
+ float c1 = a2 * b0 - b2 * a0;
+ float c2 = a0 * b1 - b0 * a1;
+ double areaOfTriangle = Math.sqrt(c0 * c0 + c1 * c1 + c2 * c2);
+ area += areaOfTriangle;
+ cx += areaOfTriangle * (poly[i * 3 + 0] + poly[k * 3 + 0] + poly[0 * 3 + 0]);
+ cy += areaOfTriangle * (poly[i * 3 + 1] + poly[k * 3 + 1] + poly[0 * 3 + 1]);
+ cz += areaOfTriangle * (poly[i * 3 + 2] + poly[k * 3 + 2] + poly[0 * 3 + 2]);
+ }
+ ret[0] = (float) (cx / (3 * area));
+ ret[1] = (float) (cy / (3 * area));
+ ret[2] = (float) (cz / (3 * area));
+ }
+
+ /**
+ * Extracts the convex hull of a polygon.
+ * @param points The vertices coordinates of polygon
+ * @param pointsLength The number of polygon vertices. So the length of poly should be len * 3.
+ * @param retPoly retPoly is at most the size of the input polygon
+ * @return The number of points in the retPolygon
+ */
+ private static int hull(@NonNull float[] points, int pointsLength, @NonNull float[] retPoly) {
+ quicksortX(points, 0, pointsLength - 1);
+ int n = pointsLength;
+ float[] lUpper = new float[n * 2];
+ lUpper[0] = points[0];
+ lUpper[1] = points[1];
+ lUpper[2] = points[2];
+ lUpper[3] = points[3];
+
+ int lUpperSize = 2;
+
+ for (int i = 2; i < n; i++) {
+ lUpper[lUpperSize * 2 + 0] = points[i * 2 + 0];
+ lUpper[lUpperSize * 2 + 1] = points[i * 2 + 1];
+ lUpperSize++;
+
+ while (lUpperSize > 2 &&
+ !rightTurn(lUpper[(lUpperSize - 3) * 2], lUpper[(lUpperSize - 3) * 2 + 1],
+ lUpper[(lUpperSize - 2) * 2], lUpper[(lUpperSize - 2) * 2 + 1],
+ lUpper[(lUpperSize - 1) * 2], lUpper[(lUpperSize - 1) * 2 + 1])) {
+ // Remove the middle point of the three last
+ lUpper[(lUpperSize - 2) * 2 + 0] = lUpper[(lUpperSize - 1) * 2 + 0];
+ lUpper[(lUpperSize - 2) * 2 + 1] = lUpper[(lUpperSize - 1) * 2 + 1];
+ lUpperSize--;
+ }
+ }
+
+ float[] lLower = new float[n * 2];
+ lLower[0] = points[(n - 1) * 2 + 0];
+ lLower[1] = points[(n - 1) * 2 + 1];
+ lLower[2] = points[(n - 2) * 2 + 0];
+ lLower[3] = points[(n - 2) * 2 + 1];
+
+ int lLowerSize = 2;
+
+ for (int i = n - 3; i >= 0; i--) {
+ lLower[lLowerSize * 2 + 0] = points[i * 2 + 0];
+ lLower[lLowerSize * 2 + 1] = points[i * 2 + 1];
+ lLowerSize++;
+
+ while (lLowerSize > 2 &&
+ !rightTurn(lLower[(lLowerSize - 3) * 2], lLower[(lLowerSize - 3) * 2 + 1],
+ lLower[(lLowerSize - 2) * 2], lLower[(lLowerSize - 2) * 2 + 1],
+ lLower[(lLowerSize - 1) * 2], lLower[(lLowerSize - 1) * 2 + 1])) {
+ // Remove the middle point of the three last
+ lLower[(lLowerSize - 2) * 2 + 0] = lLower[(lLowerSize - 1) * 2 + 0];
+ lLower[(lLowerSize - 2) * 2 + 1] = lLower[(lLowerSize - 1) * 2 + 1];
+ lLowerSize--;
+ }
+ }
+
+ int count = 0;
+ for (int i = 0; i < lUpperSize; i++) {
+ retPoly[count * 2 + 0] = lUpper[i * 2 + 0];
+ retPoly[count * 2 + 1] = lUpper[i * 2 + 1];
+ count++;
+ }
+ for (int i = 1; i < lLowerSize - 1; i++) {
+ retPoly[count * 2 + 0] = lLower[i * 2 + 0];
+ retPoly[count * 2 + 1] = lLower[i * 2 + 1];
+ count++;
+ }
+ return count;
+ }
+
+ private static boolean rightTurn(float ax, float ay, float bx, float by, float cx, float cy) {
+ return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) > 0.00001;
+ }
+
+ /**
+ * calculates the intersection of poly1 with poly2 and put in poly2
+ * @param poly1 The flatten 2d coordinates of polygon
+ * @param poly1length The vertices number of poly1
+ * @param poly2 The flatten 2d coordinates of polygon
+ * @param poly2length The vertices number of poly2
+ * @return number of vertices in poly2
+ */
+ private static int intersection(@NonNull float[] poly1, int poly1length, @NonNull float[] poly2,
+ int poly2length) {
+ makeClockwise(poly1, poly1length);
+ makeClockwise(poly2, poly2length);
+ float[] poly = new float[(poly1length * poly2length + 2) * 2];
+ int count = 0;
+ int pCount = 0;
+ for (int i = 0; i < poly1length; i++) {
+ if (pointInsidePolygon(poly1[i * 2 + 0], poly1[i * 2 + 1], poly2, poly2length)) {
+ poly[count * 2 + 0] = poly1[i * 2 + 0];
+ poly[count * 2 + 1] = poly1[i * 2 + 1];
+ count++;
+ pCount++;
+ }
+ }
+ int fromP1 = pCount;
+ for (int i = 0; i < poly2length; i++) {
+ if (pointInsidePolygon(poly2[i * 2 + 0], poly2[i * 2 + 1], poly1, poly1length)) {
+ poly[count * 2 + 0] = poly2[i * 2 + 0];
+ poly[count * 2 + 1] = poly2[i * 2 + 1];
+ count++;
+ }
+ }
+ int fromP2 = count - fromP1;
+ if (fromP1 == poly1length) { // use p1
+ for (int i = 0; i < poly1length; i++) {
+ poly2[i * 2 + 0] = poly1[i * 2 + 0];
+ poly2[i * 2 + 1] = poly1[i * 2 + 1];
+ }
+ return poly1length;
+ }
+ if (fromP2 == poly2length) { // use p2
+ return poly2length;
+ }
+ float[] intersection = new float[2];
+ for (int i = 0; i < poly2length; i++) {
+ for (int j = 0; j < poly1length; j++) {
+ int i1_by_2 = i * 2;
+ int i2_by_2 = ((i + 1) % poly2length) * 2;
+ int j1_by_2 = j * 2;
+ int j2_by_2 = ((j + 1) % poly1length) * 2;
+ boolean found =
+ lineIntersection(poly2[i1_by_2 + 0], poly2[i1_by_2 + 1], poly2[i2_by_2 + 0],
+ poly2[i2_by_2 + 1], poly1[j1_by_2 + 0], poly1[j1_by_2 + 1],
+ poly1[j2_by_2 + 0], poly1[j2_by_2 + 1], intersection);
+ if (found) {
+ poly[count * 2 + 0] = intersection[0];
+ poly[count * 2 + 1] = intersection[1];
+ count++;
+ } else {
+ float dx = poly2[i * 2 + 0] - poly1[j * 2 + 0];
+ float dy = poly2[i * 2 + 1] - poly1[j * 2 + 1];
+
+ if (dx * dx + dy * dy < 0.01) {
+ poly[count * 2 + 0] = poly2[i * 2 + 0];
+ poly[count * 2 + 1] = poly2[i * 2 + 1];
+ count++;
+ }
+ }
+ }
+ }
+ if (count == 0) {
+ return 0;
+ }
+ float avgX = 0;
+ float avgY = 0;
+ for (int i = 0; i < count; i++) {
+ avgX += poly[i * 2 + 0];
+ avgY += poly[i * 2 + 1];
+ }
+ avgX /= count;
+ avgY /= count;
+
+ float[] ctr = new float[]{avgX, avgY};
+ sort(poly, count, ctr);
+ int size = count;
+
+ poly2[0] = poly[0];
+ poly2[1] = poly[1];
+
+ count = 1;
+ for (int i = 1; i < size; i++) {
+ float dx = poly[i * 2 + 0] - poly[(i - 1) * 2 + 0];
+ float dy = poly[i * 2 + 1] - poly[(i - 1) * 2 + 1];
+ if (dx * dx + dy * dy >= 0.01) {
+ poly2[count * 2 + 0] = poly[i * 2 + 0];
+ poly2[count * 2 + 1] = poly[i * 2 + 1];
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public static void sort(@NonNull float[] poly, int polyLength, @NonNull float[] ctr) {
+ quicksortCircle(poly, 0, polyLength - 1, ctr);
+ }
+
+ public static float angle(float x1, float y1, @NonNull float[] ctr) {
+ return -(float) Math.atan2(x1 - ctr[0], y1 - ctr[1]);
+ }
+
+ private static void swapPair(@NonNull float[] points, int i, int j) {
+ float x = points[i * 2 + 0];
+ float y = points[i * 2 + 1];
+ points[i * 2 + 0] = points[j * 2 + 0];
+ points[i * 2 + 1] = points[j * 2 + 1];
+ points[j * 2 + 0] = x;
+ points[j * 2 + 1] = y;
+ }
+
+ private static void quicksortCircle(@NonNull float[] points, int low, int high,
+ @NonNull float[] ctr) {
+ int i = low, j = high;
+ int p = low + (high - low) / 2;
+ float pivot = angle(points[p * 2], points[p * 2 + 1], ctr);
+ while (i <= j) {
+ while (angle(points[i * 2 + 0], points[i * 2 + 1], ctr) < pivot) {
+ i++;
+ }
+ while (angle(points[j * 2 + 0], points[j * 2 + 1], ctr) > pivot) {
+ j--;
+ }
+ if (i <= j) {
+ swapPair(points, i, j);
+ i++;
+ j--;
+ }
+ }
+ if (low < j) {
+ quicksortCircle(points, low, j, ctr);
+ }
+ if (i < high) {
+ quicksortCircle(points, i, high, ctr);
+ }
+ }
+
+ /**
+ * This function do Quick Sort by comparing X axis only.<br>
+ * Note that the input values of points are paired coordinates, e.g. {@code [x0, y0, x1, y1, x2,
+ * y2, ...]).}
+ * @param points The input point pairs. Every {@code (2 * i, 2 * i + 1)} points are pairs.
+ * @param low lowest index used to do quick sort sort
+ * @param high highest index used to do quick sort
+ */
+ private static void quicksortX(@NonNull float[] points, int low, int high) {
+ int i = low, j = high;
+ int p = low + (high - low) / 2;
+ float pivot = points[p * 2];
+ while (i <= j) {
+ while (points[i * 2 + 0] < pivot) {
+ i++;
+ }
+ while (points[j * 2 + 0] > pivot) {
+ j--;
+ }
+
+ if (i <= j) {
+ swapPair(points, i, j);
+ i++;
+ j--;
+ }
+ }
+ if (low < j) {
+ quicksortX(points, low, j);
+ }
+ if (i < high) {
+ quicksortX(points, i, high);
+ }
+ }
+
+ private static boolean pointInsidePolygon(float x, float y, @NonNull float[] poly, int len) {
+ boolean c = false;
+ float testX = x;
+ float testY = y;
+ for (int i = 0, j = len - 1; i < len; j = i++) {
+ if (((poly[i * 2 + 1] > testY) != (poly[j * 2 + 1] > testY)) && (testX <
+ (poly[j * 2 + 0] - poly[i * 2 + 0]) * (testY - poly[i * 2 + 1]) /
+ (poly[j * 2 + 1] - poly[i * 2 + 1]) + poly[i * 2 + 0])) {
+ c = !c;
+ }
+ }
+ return c;
+ }
+
+ private static void makeClockwise(@NonNull float[] polygon, int len) {
+ if (polygon == null || len == 0) {
+ return;
+ }
+ if (!isClockwise(polygon, len)) {
+ reverse(polygon, len);
+ }
+ }
+
+ private static boolean isClockwise(@NonNull float[] polygon, int len) {
+ float sum = 0;
+ float p1x = polygon[(len - 1) * 2 + 0];
+ float p1y = polygon[(len - 1) * 2 + 1];
+ for (int i = 0; i < len; i++) {
+ float p2x = polygon[i * 2 + 0];
+ float p2y = polygon[i * 2 + 1];
+ sum += p1x * p2y - p2x * p1y;
+ p1x = p2x;
+ p1y = p2y;
+ }
+ return sum < 0;
+ }
+
+ private static void reverse(@NonNull float[] polygon, int len) {
+ int n = len / 2;
+ for (int i = 0; i < n; i++) {
+ float tmp0 = polygon[i * 2 + 0];
+ float tmp1 = polygon[i * 2 + 1];
+ int k = len - 1 - i;
+ polygon[i * 2 + 0] = polygon[k * 2 + 0];
+ polygon[i * 2 + 1] = polygon[k * 2 + 1];
+ polygon[k * 2 + 0] = tmp0;
+ polygon[k * 2 + 1] = tmp1;
+ }
+ }
+
+ /**
+ * Intersects two lines in parametric form.
+ */
+ private static final boolean lineIntersection(float x1, float y1, float x2, float y2, float x3,
+ float y3, float x4, float y4, @NonNull float[] ret) {
+ float d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ if (d == 0.000f) {
+ return false;
+ }
+
+ float dx = (x1 * y2 - y1 * x2);
+ float dy = (x3 * y4 - y3 * x4);
+ float x = (dx * (x3 - x4) - (x1 - x2) * dy) / d;
+ float y = (dx * (y3 - y4) - (y1 - y2) * dy) / d;
+
+ if (((x - x1) * (x - x2) > 0.0000001) || ((x - x3) * (x - x4) > 0.0000001) ||
+ ((y - y1) * (y - y2) > 0.0000001) || ((y - y3) * (y - y4) > 0.0000001)) {
+ return false;
+ }
+ ret[0] = x;
+ ret[1] = y;
+ return true;
+ }
+
+ @NonNull
+ public static float[] calculateLight(float size, int points, float x, float y, float height) {
+ float[] ret = new float[points * 3];
+ for (int i = 0; i < points; i++) {
+ double angle = 2 * i * Math.PI / points;
+ ret[i * 3 + 0] = (float) Math.cos(angle) * size + x;
+ ret[i * 3 + 1] = (float) Math.sin(angle) * size + y;
+ ret[i * 3 + 2] = (height);
+ }
+ return ret;
+ }
+
+ /**
+ layers indicates the number of circular strips to generate.
+ Strength is how dark a shadow to generate.
+
+ /**
+ * This calculates a collection of triangles that represent the shadow cast by a polygonal
+ * light source (lightPoly) hitting a convex polygon (poly).
+ * @param lightPoly The flatten 3d coordinates of light
+ * @param lightPolyLength The vertices number of light polygon.
+ * @param poly The flatten 3d coordinates of item
+ * @param polyLength The vertices number of light polygon.
+ * @param rays defines the number of points around the perimeter of the shadow to generate
+ * @param layers The number of shadow's fading.
+ * @param strength The factor of the black color of shadow.
+ * @param retStrips Used to store the calculated shadow strength
+ * @return true if the params is able to calculate a shadow, else false.
+ */
+ public static boolean calcShadow(@NonNull float[] lightPoly, int lightPolyLength,
+ @NonNull float[] poly, int polyLength, int rays, int layers, float strength,
+ @NonNull float[] retStrips) {
+ float[] shadowRegion = new float[lightPolyLength * polyLength * 2];
+ float[] outline = new float[polyLength * 2];
+ float[] umbra = new float[polyLength * lightPolyLength * 2];
+ int umbraLength = 0;
+
+ int k = 0;
+ for (int j = 0; j < lightPolyLength; j++) {
+ int m = 0;
+ for (int i = 0; i < polyLength; i++) {
+ float t = lightPoly[j * 3 + 2] - poly[i * 3 + 2];
+ if (t == 0) {
+ return false;
+ }
+ t = lightPoly[j * 3 + 2] / t;
+ float x = lightPoly[j * 3 + 0] - t * (lightPoly[j * 3 + 0] - poly[i * 3 + 0]);
+ float y = lightPoly[j * 3 + 1] - t * (lightPoly[j * 3 + 1] - poly[i * 3 + 1]);
+
+ shadowRegion[k * 2 + 0] = x;
+ shadowRegion[k * 2 + 1] = y;
+ outline[m * 2 + 0] = x;
+ outline[m * 2 + 1] = y;
+
+ k++;
+ m++;
+ }
+ if (umbraLength == 0) {
+ System.arraycopy(outline, 0, umbra, 0, polyLength);
+ umbraLength = polyLength;
+ } else {
+ umbraLength = intersection(outline, polyLength, umbra, umbraLength);
+ if (umbraLength == 0) {
+ break;
+ }
+ }
+ }
+ int shadowRegionLength = k;
+
+ float[] penumbra = new float[k * 2];
+ int penumbraLength = hull(shadowRegion, shadowRegionLength, penumbra);
+ if (umbraLength < 3) {// no real umbra make a fake one
+ float[] p = new float[3];
+ centroid3d(lightPoly, lightPolyLength, p);
+ float[] centShadow = new float[polyLength * 2];
+ for (int i = 0; i < polyLength; i++) {
+ float t = p[2] - poly[i * 3 + 2];
+ if (t == 0) {
+ return false;
+ }
+ t = p[2] / t;
+ float x = p[0] - t * (p[0] - poly[i * 3 + 0]);
+ float y = p[1] - t * (p[1] - poly[i * 3 + 1]);
+
+ centShadow[i * 2 + 0] = x;
+ centShadow[i * 2 + 1] = y;
+ }
+ float[] c = new float[2];
+ centroid2d(centShadow, polyLength, c);
+ for (int i = 0; i < polyLength; i++) {
+ centShadow[i * 2 + 0] = (c[0] * 9 + centShadow[i * 2 + 0]) / 10;
+ centShadow[i * 2 + 1] = (c[1] * 9 + centShadow[i * 2 + 1]) / 10;
+ }
+ umbra = centShadow; // fake umbra
+ umbraLength = polyLength; // same size as the original polygon
+ }
+
+ triangulateConcentricPolygon(penumbra, penumbraLength, umbra, umbraLength, rays, layers,
+ strength, retStrips);
+ return true;
+ }
+
+ /**
+ * triangulate concentric circles.
+ * This takes the inner and outer polygons of the umbra and penumbra and triangulates it.
+ * @param penumbra The 2d flatten vertices of penumbra polygons.
+ * @param penumbraLength The number of vertices in penumbra.
+ * @param umbra The 2d flatten vertices of umbra polygons.
+ * @param umbraLength The number of vertices in umbra.
+ * @param rays defines the number of points around the perimeter of the shadow to generate
+ * @param layers The number of shadow's fading.
+ * @param strength The factor of the black color of shadow.
+ * @param retStrips Used to store the calculated shadow strength.
+ */
+ private static void triangulateConcentricPolygon(@NonNull float[] penumbra, int penumbraLength,
+ @NonNull float[] umbra, int umbraLength, int rays, int layers, float strength,
+ @NonNull float[] retStrips) {
+ int rings = layers + 1;
+ double step = Math.PI * 2 / rays;
+ float[] retXY = new float[2];
+ centroid2d(umbra, umbraLength, retXY);
+ float cx = retXY[0];
+ float cy = retXY[1];
+
+ float[] t1 = new float[rays];
+ float[] t2 = new float[rays];
+
+ for (int i = 0; i < rays; i++) {
+ float dx = (float) Math.cos(Math.PI / 4 + step * i);
+ float dy = (float) Math.sin(Math.PI / 4 + step * i);
+ t2[i] = rayIntersectPoly(umbra, umbraLength, cx, cy, dx, dy);
+ t1[i] = rayIntersectPoly(penumbra, penumbraLength, cx, cy, dx, dy);
+ }
+
+ int p = 0;
+ // Calculate the vertex
+ for (int r = 0; r < layers; r++) {
+ int startP = p;
+ for (int i = 0; i < rays; i++) {
+ float dx = (float) Math.cos(Math.PI / 4 + step * i);
+ float dy = (float) Math.sin(Math.PI / 4 + step * i);
+
+ for (int j = r; j < (r + 2); j++) {
+ float jf = j / (float) (rings - 1);
+ float t = t1[i] + jf * (t2[i] - t1[i]);
+ float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
+ retStrips[p * 3 + 0] = dx * t + cx;
+ retStrips[p * 3 + 1] = dy * t + cy;
+ retStrips[p * 3 + 2] = jf * op * strength;
+ p++;
+
+ }
+ }
+ retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
+ retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
+ retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
+ p++;
+ startP++;
+ retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
+ retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
+ retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
+ p++;
+ }
+ int oldP = p - 1;
+ retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
+ retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
+ retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
+ p++;
+
+ // Skip the first point here, then make it same as last point later.
+ oldP = p;
+ p++;
+ for (int k = 0; k < rays; k++) {
+ int i = k / 2;
+ if ((k & 1) == 1) { // traverse the inside in a zig zag pattern
+ // for strips
+ i = rays - i - 1;
+ }
+ float dx = (float) Math.cos(Math.PI / 4 + step * i);
+ float dy = (float) Math.sin(Math.PI / 4 + step * i);
+
+ float jf = 1;
+
+ float t = t1[i] + jf * (t2[i] - t1[i]);
+ float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
+
+ retStrips[p * 3 + 0] = dx * t + cx;
+ retStrips[p * 3 + 1] = dy * t + cy;
+ retStrips[p * 3 + 2] = jf * op * strength;
+ p++;
+ }
+ p = oldP;
+ retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
+ retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
+ retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
+ }
+
+ public static int getStripSize(int rays, int layers) {
+ return (2 + rays + ((layers) * 2 * (rays + 1)));
+ }
+}