/* * Copyright (C) 2016 Google Inc. 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.android.apps.santatracker.doodles.shared.physics; import com.google.android.apps.santatracker.doodles.shared.Vector2D; import java.util.List; /** * A utility class for physics-related functions. */ public final class Util { private static final String TAG = Util.class.getSimpleName(); private static final float EPSILON = 0.0001f; private static final int COLLINEAR = 0; private static final int CLOCKWISE = 1; private static final int COUNTERCLOCKWISE = 2; private Util() { // Don't allow instantiation of this class. } /** * Return whether the point is within the axis-aligned rectangle defined by p and q. * * @param p first bounding point. * @param q second bounding point. * @param point the point to check. * @return true if point is within the bounds defined by p and q, false otherwise. */ public static boolean pointIsWithinBounds(Vector2D p, Vector2D q, Vector2D point) { return point.x >= Math.min(p.x, q.x) && point.x <= Math.max(p.x, q.x) && point.y >= Math.min(p.y, q.y) && point.y <= Math.max(p.y, q.y); } /** * Find the orientation of the ordered triplet of points. They are either clockwise, * counterclockwise, or collinear. * Implementation based on: http://goo.gl/a44iML */ private static int orientation(Vector2D p, Vector2D q, Vector2D r) { float value = (q.y - p.y) * (r.x - q.x) - (r.y - q.y) * (q.x - p.x); // Use this instead of Math.abs(value) here because it is faster. if (value < EPSILON && value > -EPSILON) { return COLLINEAR; } return value > 0 ? CLOCKWISE : COUNTERCLOCKWISE; } /** * Compute whether or not lines 1 and 2 intersect. * Implementation based on: * http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ * * @param p1 The starting point of line 1 * @param q1 The ending point of line 1 * @param p2 The starting point of line 2 * @param q2 The ending point of line 2 * @return true if 1 and 2 intersect, false otherwise. */ public static boolean lineSegmentIntersectsLineSegment( Vector2D p1, Vector2D q1, Vector2D p2, Vector2D q2) { int o1 = orientation(p1, q1, p2); int o2 = orientation(p1, q1, q2); int o3 = orientation(p2, q2, p1); int o4 = orientation(p2, q2, q1); // General case if (o1 != o2 && o3 != o4) { return true; } // Special cases if (o1 == 0 && pointIsWithinBounds(p1, q1, p2) || o2 == 0 && pointIsWithinBounds(p1, q1, q2) || o3 == 0 && pointIsWithinBounds(p2, q2, p1) || o4 == 0 && pointIsWithinBounds(p2, q2, q1)) { return true; } return false; } /** * Return whether or not two rectangles intersect. This uses a basic form of the separating axis * theorem which should be faster than running a full polygon-to-polygon check. * * @return true if the rectangles intersect, false otherwise. */ public static boolean rectIntersectsRect(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2) { float halfWidth1 = w1 / 2; float halfWidth2 = w2 / 2; float halfHeight1 = h1 / 2; float halfHeight2 = h2 / 2; float horizontalThreshold = halfWidth1 + halfWidth2; float verticalThreshold = halfHeight1 + halfHeight2; float horizontalDistance = Math.abs(x1 + halfWidth1 - (x2 + halfWidth2)); float verticalDistance = Math.abs(y1 + halfHeight1 - (y2 + halfHeight2)); return horizontalDistance < horizontalThreshold && verticalDistance < verticalThreshold; } /** * Use the separating axis theorem to determine whether or not two convex polygons intersect. * * @return true if the polygons intersect, false otherwise. */ public static boolean convexPolygonIntersectsConvexPolygon(Polygon p1, Polygon p2) { for (int i = 0; i < p1.normals.size(); i++) { Vector2D normal = p1.normals.get(i); float p1Min = getMinProjectionInDirection(normal, p1.vertices); float p1Max = getMaxProjectionInDirection(normal, p1.vertices); float p2Min = getMinProjectionInDirection(normal, p2.vertices); float p2Max = getMaxProjectionInDirection(normal, p2.vertices); if (p1Max < p2Min || p2Max < p1Min) { // If there is a separating axis, these polygons do not intersect. return false; } } for (int i = 0; i < p2.normals.size(); i++) { Vector2D normal = p2.normals.get(i); float p1Min = getMinProjectionInDirection(normal, p1.vertices); float p1Max = getMaxProjectionInDirection(normal, p1.vertices); float p2Min = getMinProjectionInDirection(normal, p2.vertices); float p2Max = getMaxProjectionInDirection(normal, p2.vertices); if (p1Max < p2Min || p2Max < p1Min) { // If there is a separating axis, these polygons do not intersect. return false; } } return true; } private static float getMaxProjectionInDirection(Vector2D direction, List<Vector2D> points) { float max = points.get(0).dot(direction); for (int i = 1; i < points.size(); i++) { max = Math.max(max, points.get(i).dot(direction)); } return max; } private static float getMinProjectionInDirection(Vector2D direction, List<Vector2D> points) { float min = points.get(0).dot(direction); for (int i = 1; i < points.size(); i++) { min = Math.min(min, points.get(i).dot(direction)); } return min; } public static Vector2D getMidpoint(Vector2D p, Vector2D q) { float deltaX = q.x - p.x; float deltaY = q.y - p.y; return Vector2D.get(p.x + deltaX / 2, p.y + deltaY / 2); } public static float clamp(float value, float lowerBound, float upperBound) { return Math.max(lowerBound, Math.min(value, upperBound)); } public static int clamp(int value, int lowerBound, int upperBound) { return Math.max(lowerBound, Math.min(value, upperBound)); } }