/* * JaamSim Discrete Event Simulation * Copyright (C) 2012 Ausenco Engineering Canada Inc. * * 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.jaamsim.math; import java.util.List; import com.jaamsim.render.RenderUtils; /** * Some handy static methods to make life easier else where * @author Matt Chudleigh * */ public class MathUtils { final static double EPSILON = 0.000000001; // one billionth public static final boolean isSmall(double a) { return a < EPSILON; } public static boolean near(double a, double b) { double diff = Math.abs(a - b); return isSmall(diff); } /** * Checks for line segment overlap * @param a0 * @param a1 * @param b0 * @param b1 */ public static boolean segOverlap(double a0, double a1, double b0, double b1) { if (a0 == b0) return true; if (a0 < b0) { return b0 <= a1; } return a0 <= b1; } /** * Checks for line segment overlap, with a fudge factor * @param a0 * @param a1 * @param b0 * @param b1 * @param fudge - The fudge factor to allow */ public static boolean segOverlap(double a0, double a1, double b0, double b1, double fudge) { if (a0 == b0) return true; if (a0 < b0) { return b0 <= a1 + fudge; } return a0 <= b1 + fudge; } /** * Perform a bounds check on val, returns something in the range [min, max] * @param val * @param min * @param max * @return */ public static double bound(double val, double min, double max) { //assert(min <= max); if (val < min) { return min; } if (val > max) { return max; } return val; } /** * Return a matrix that rotates points and projects them onto the ray's view plane. * IE: the new coordinate system has the ray pointing in the +Z direction from the origin. * This is useful for ray-line collisions and ray-point collisions * @return */ public static Mat4d RaySpace(Ray r) { // Create a new orthonormal basis starting with the y-axis, if the ray is // nearly parallel to Y, build our new basis from X instead Vec3d t = new Vec3d(0.0d, 1.0d, 0.0d); double dist = Math.abs(t.dot3(r.getDirRef())); if (MathUtils.near(dist, 1.0d)) t.set3(1.0d, 0.0d, 0.0d); Mat4d ret = new Mat4d(); // Calculate a new basis to populate the rows of the return matrix t.cross3(r.getDirRef(), t); t.normalize3(); ret.d00 = t.x; ret.d01 = t.y; ret.d02 = t.z; t.cross3(r.getDirRef(), t); t.normalize3(); ret.d10 = t.x; ret.d11 = t.y; ret.d12 = t.z; t.set3(r.getDirRef()); ret.d20 = t.x; ret.d21 = t.y; ret.d22 = t.z; // Now use this rotation matrix to calculate the rotated translation part t.mult3(ret, r.getStartRef()); ret.d03 = -t.x; ret.d13 = -t.y; ret.d23 = -t.z; return ret; } /** * Returns a Transform representing a rotation around a non-origin point * @param rot - the rotation (in world coordinates) to apply * @param point - the point to rotate around * @return */ public static Mat4d rotateAroundPoint(Quaternion rot, Vec3d point) { Vec3d negPoint = new Vec3d(point); negPoint.scale3(-1); Transform ret = new Transform(point, rot, 1); ret.merge(ret, new Transform(negPoint)); return ret.getMat4dRef(); } public static double collisionDistPoly(Ray r, Vec3d[] points) { if (points.length < 3) { return -1; // Should this be an error? } // Check that this is actually inside the polygon, this assumes the points are co-planar Plane p = new Plane(points[0], points[1], points[2]); double dist = p.collisionDist(r); if (dist < 0) { return dist; } // Behind the start of the ray // This is the potential collision point, if it's inside the polygon Vec3d collisionPoint = r.getPointAtDist(dist); Vec3d a = new Vec3d(); Vec3d b = new Vec3d(); Vec3d cross = new Vec3d(); boolean firstPos = false; for (int i = 0; i < points.length; ++i) { // Check that the collision point is on the same winding side of all the Vec3d p0 = points[i]; Vec3d p1 = points[(i + 1) % points.length]; a.sub3(p0, collisionPoint); b.sub3(p1, p0); cross.cross3(a, b); double triple = cross.dot3(r.getDirRef()); // This point is inside the polygon if all triple products have the same sign if (i == 0) { // First iteration sets the sign firstPos = triple > 0; } if (firstPos != (triple > 0)) { return -1; } } return dist; // This must be valid then } public static double collisionDistPoly(Ray r, List<Vec3d> points) { if (points.size() < 3) { return -1; // Should this be an error? } // Check that this is actually inside the polygon, this assumes the points are co-planar Plane p = new Plane(); p.set(points.get(0), points.get(1), points.get(2)); double dist = p.collisionDist(r); if (dist < 0) { return dist; } // Behind the start of the ray // This is the potential collision point, if it's inside the polygon Vec3d collisionPoint = r.getPointAtDist(dist); Vec3d a = new Vec3d(); Vec3d b = new Vec3d(); Vec3d cross = new Vec3d(); boolean firstPos = false; for (int i = 0; i < points.size(); ++i) { // Check that the collision point is on the same winding side of all the Vec3d p0 = points.get(i); Vec3d p1 = points.get((i + 1) % points.size()); a.sub3(p0, collisionPoint); b.sub3(p1, p0); cross.cross3(a, b); double triple = cross.dot3(r.getDirRef()); // This point is inside the polygon if all triple products have the same sign if (i == 0) { // First iteration sets the sign firstPos = triple > 0; } if (firstPos != (triple > 0)) { return -1; } } return dist; // This must be valid then } /** * Determine line collision * @param rayMat - the rayspace matrix * @param lines - pairs of vertices, each pair defining a line segment (this is not a line strip or line loop) * @param collisionAngle - the angle of the collision cone in radians * @return */ public static double collisionDistLines(Mat4d rayMat, Vec4d[] lines, double collisionAngle) { double shortDist = Double.POSITIVE_INFINITY; for (int i = 0; i < lines.length; i+=2) { Vec4d nearPoint = RenderUtils.rayClosePoint(rayMat, lines[i], lines[i+1]); double angle = RenderUtils.angleToRay(rayMat, nearPoint); if (angle < 0) { continue; } Vec4d raySpaceNear = new Vec4d(0.0d, 0.0d, 0.0d, 1.0d); raySpaceNear.mult4(rayMat, nearPoint); if (angle < collisionAngle && raySpaceNear.z < shortDist) { shortDist = raySpaceNear.z; } } // Short dist is the shortest collision distance if (shortDist == Double.POSITIVE_INFINITY) { return -1; // No collision } return shortDist; } } // class