blob: b986825cdc5a0dba5512ae28cf895ec5422aa551 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Agence spatiale canadienne / Canadian Space Agency
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pierre Allard,
* Regent L'Archeveque - initial API and implementation
*
* SPDX-License-Identifier: EPL-1.0
*
*******************************************************************************/
package org.eclipse.apogy.addons.vehicle.impl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.vecmath.Matrix4d;
import javax.vecmath.Point2d;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import org.eclipse.apogy.addons.vehicle.ApogyAddonsVehicleFactory;
import org.eclipse.apogy.addons.vehicle.Line3d;
import org.eclipse.apogy.addons.vehicle.MeshExtent2D;
import org.eclipse.apogy.addons.vehicle.Plane;
import org.eclipse.apogy.addons.vehicle.Segment2D;
import org.eclipse.apogy.addons.vehicle.TerrainProfile;
import org.eclipse.apogy.common.geometry.data3d.CartesianPositionCoordinates;
import org.eclipse.apogy.common.geometry.data3d.CartesianTriangle;
import org.eclipse.apogy.common.geometry.data3d.CartesianTriangularMesh;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import Jama.Matrix;
public class WheelVehicleUtilitiesCustomImpl extends WheelVehicleUtilitiesImpl {
private static final Logger Logger = LoggerFactory.getLogger(WheelVehicleUtilitiesImpl.class);
public static final double COLINEAR_MINUM_ANGLE_RADIANS = Math.toRadians(20.0);
@Override
public Point2d getWheelCenterPoint(double wheelRadius, double wheelCenterX, Segment2D segment) {
Point2d p1 = segment.p1;
Point2d p2 = segment.p2;
// The wheel center point.
Point2d pc = null;
// Parameter for line going through P1 and P2.
double m = (p2.y - p1.y) / (p2.x - p1.x);
double b = p2.y - (m * p2.x);
// Intersection between line and vertical wheel center line.
Point2d pi = new Point2d(wheelCenterX, m * wheelCenterX + b);
// Computes the position of the point tangent to the circle on the line
double l = wheelRadius * m;
double theta = Math.atan(m);
double dx = l * Math.cos(theta);
double dy = l * Math.sin(theta);
Point2d pt = new Point2d(pi.x + dx, pi.y + dy);
if (isPointOnSegment(pt, segment)) {
// Wheel Center
double d = Math.sqrt(Math.pow(wheelRadius, 2.0) + Math.pow(l, 2.0));
pc = new Point2d(wheelCenterX, pi.y + d);
} else {
// Test end points.
// Test P1
Point2d pc1 = null;
double determinant = Math.pow(wheelRadius, 2.0) - Math.pow((wheelCenterX - p1.x), 2.0);
if (determinant >= 0) {
double deltaY = Math.sqrt(determinant);
pc1 = new Point2d(wheelCenterX, p1.y + deltaY);
}
// Test P2
Point2d pc2 = null;
determinant = Math.pow(wheelRadius, 2.0) - Math.pow((wheelCenterX - p2.x), 2.0);
if (determinant >= 0) {
double deltaY = Math.sqrt(determinant);
pc2 = new Point2d(wheelCenterX, p2.y + deltaY);
}
if (pc1 != null) {
if (pc2 != null) {
if (pc1.y > pc2.y) {
pc = pc1;
} else {
pc = pc2;
}
} else {
pc = pc1;
}
} else if (pc2 != null) {
pc = pc2;
}
}
return pc;
}
@Override
public Point2d getWheelCenterPoint(double wheelRadius, double wheelCenterX, TerrainProfile terrainProfile) {
Point2d center = null;
Iterator<Segment2D> it = terrainProfile.getSegments().iterator();
while (it.hasNext()) {
// Next segment.
Segment2D segment = it.next();
try {
Point2d c = getWheelCenterPoint(wheelRadius, wheelCenterX, segment);
if (c != null) {
if (center == null) {
center = c;
} else {
if (c.y > center.y) {
center = c;
}
}
}
} catch (Throwable t) {
Logger.error(t.getMessage(), t);
}
}
return center;
}
@Override
public TerrainProfile findTerrainProfile(CartesianTriangularMesh mesh, Plane plane) {
TerrainProfile terrainProfile = new TerrainProfile();
Matrix4d planePose = new Matrix4d();
planePose.setIdentity();
// Set pose of vector.
planePose.set(new Vector3d(plane.point));
// Sets the z rotation
Matrix4d planeRotation = new Matrix4d();
planeRotation.setIdentity();
double thetaZ = Math.atan2(plane.normal.x, plane.normal.y);
planeRotation.rotZ(thetaZ);
planePose.mul(planeRotation);
planePose.invert();
// Test each of the triangle in the mesh.
for (CartesianTriangle triangle : mesh.getPolygons()) {
List<Point3d> intersections = new ArrayList<Point3d>();
Line3d edge1 = new Line3d(triangle.getVertices().get(0).asPoint3d(),
triangle.getVertices().get(1).asPoint3d());
Line3d edge2 = new Line3d(triangle.getVertices().get(1).asPoint3d(),
triangle.getVertices().get(2).asPoint3d());
Line3d edge3 = new Line3d(triangle.getVertices().get(2).asPoint3d(),
triangle.getVertices().get(0).asPoint3d());
// Test each triangle side for intersection.
intersections.addAll(findIntersection(plane, edge1));
intersections.addAll(findIntersection(plane, edge2));
intersections.addAll(findIntersection(plane, edge3));
if (intersections.size() == 2) {
// Transforms pa and pb into plane reference frame.
Point3d pa = new Point3d();
planePose.transform(intersections.get(0), pa);
Point3d pb = new Point3d();
planePose.transform(intersections.get(1), pb);
Point2d p1 = new Point2d(pa.x, pa.z);
Point2d p2 = new Point2d(pb.x, pb.z);
if (p1.x < p2.x) {
Segment2D segment = new Segment2D(p1, p2);
terrainProfile.getSegments().add(segment);
} else {
Segment2D segment = new Segment2D(p2, p1);
terrainProfile.getSegments().add(segment);
}
}
}
return terrainProfile;
}
@Override
public List<Point3d> findIntersection(Plane plane, Line3d line3d) {
List<Point3d> intersections = new ArrayList<Point3d>();
Point3d pointOnPlane = plane.point;
Vector3d planeNormal = plane.normal;
Point3d p1 = line3d.p1;
Point3d p2 = line3d.p2;
Vector3d p0 = new Vector3d(pointOnPlane);
Vector3d i0 = new Vector3d(p1);
Vector3d l = new Vector3d(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
l.normalize();
p0.sub(i0);
double numerator = p0.dot(planeNormal);
double denominator = l.dot(planeNormal);
if (denominator != 0) {
if (numerator != 0) {
double d = numerator / denominator;
if (d <= p1.distance(p2)) {
Vector3d intersection = new Vector3d(i0);
Vector3d dl = new Vector3d(l);
dl.scale(d);
intersection.add(dl);
intersections.add(new Point3d(intersection));
}
} else {
// Lines is on the plane
intersections.add(p1);
intersections.add(p2);
}
} else {
// No intersections.
}
return intersections;
}
@Override
public boolean isPointOnSegment(Point2d p, Segment2D segment) {
Point2d p1 = segment.p1;
Point2d p2 = segment.p2;
if ((p1.distance(p) == 0) || (p2.distance(p) == 0)) {
return true;
} else {
double epsilon = 1E-3;
double p1p2Distance = p2.distance(p1);
double pp1Distance = p.distance(p1);
double pp2Distance = p.distance(p2);
return (pp1Distance + pp2Distance <= (p1p2Distance + epsilon));
}
}
@Override
public MeshExtent2D findMeshExtent2D(CartesianTriangularMesh mesh) {
MeshExtent2D meshExtent2D = ApogyAddonsVehicleFactory.eINSTANCE.createMeshExtent2D();
if (mesh.getPoints().size() > 0) {
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
for (CartesianPositionCoordinates point : mesh.getPoints()) {
Point3d p = point.asPoint3d();
if (p.x < minX)
minX = p.x;
if (p.x > maxX)
maxX = p.x;
if (p.y < minY)
minY = p.y;
if (p.y > maxY)
maxY = p.y;
}
meshExtent2D.setMinimumX(minX);
meshExtent2D.setMinimumY(minY);
meshExtent2D.setMaximumX(maxX);
meshExtent2D.setMaximumY(maxY);
}
return meshExtent2D;
}
@Override
public boolean isWithin(Point3d point, MeshExtent2D meshExtent) {
return ((point.x >= meshExtent.getMinimumX()) && (point.x <= meshExtent.getMaximumX())
&& (point.y >= meshExtent.getMinimumY()) && (point.y <= meshExtent.getMaximumY()));
}
@Override
public Vector3d findBestFitPlane(List<Point3d> points) {
if (points.size() >= 3 && !arePointsColinear(points)) {
try {
double[][] dataArray = new double[points.size()][3];
double[][] zArray = new double[points.size()][1];
// Fills in the arrays used for the Matrix and the Z vector.
for (int i = 0; i < points.size(); i++) {
Point3d p = points.get(i);
dataArray[i][0] = p.x;
dataArray[i][1] = p.y;
dataArray[i][2] = 1.0;
zArray[i][0] = p.z;
}
Jama.Matrix m = new Matrix(dataArray);
Jama.Matrix zVector = new Matrix(zArray);
Matrix mTranspose = m.transpose();
Matrix m1 = mTranspose.times(m);
Matrix m1Inverse = m1.inverse();
Matrix m2 = m1Inverse.times(mTranspose);
Matrix a = m2.times(zVector);
Vector3d normal = new Vector3d(a.get(0, 0), a.get(1, 0), -1);
normal.normalize();
normal.negate();
return normal;
} catch (Exception e) {
}
}
return null;
}
private boolean arePointsColinear(List<Point3d> points) {
if (points.size() >= 3) {
// Create vectors between the points.
List<Vector3d> vectors = new ArrayList<Vector3d>();
Point3d[] pointArray = points.toArray(new Point3d[] {});
Point3d p0 = pointArray[0];
for (int i = 1; i < (pointArray.length); i++) {
Point3d p1 = pointArray[i];
Vector3d v = new Vector3d(p1.x - p0.x, p1.y - p0.y, p1.z - p0.z);
v.normalize();
vectors.add(v);
}
// Compute angle between vectors.
Vector3d[] vector3dArray = vectors.toArray(new Vector3d[] {});
int colinearVectorCount = 0;
for (int i = 0; i < (vector3dArray.length - 1); i++) {
Vector3d v0 = vector3dArray[i];
Vector3d v1 = vector3dArray[i + 1];
double dotProduct = v0.dot(v1);
double angle = Math.acos(dotProduct);
if (Math.abs(angle) > Math.PI / 2) {
angle = (Math.PI - Math.abs(angle));
}
if ((Math.abs(angle) < COLINEAR_MINUM_ANGLE_RADIANS)) {
colinearVectorCount++;
}
}
return ((vector3dArray.length - colinearVectorCount) < 2);
} else {
return false;
}
}
} // WheelVehicleUtilitiesImpl