package org.wheelmap.android.tango.mode; import android.util.Log; import android.widget.Toast; import com.vividsolutions.jts.geom.Polygon; import org.rajawali3d.Object3D; import org.rajawali3d.math.Matrix4; import org.rajawali3d.math.Plane; import org.rajawali3d.math.vector.Vector2; import org.rajawali3d.math.vector.Vector3; import org.wheelmap.android.app.WheelmapApp; import org.wheelmap.android.online.R; import org.wheelmap.android.tango.mode.math.SmallestSurroundingRectangle; import org.wheelmap.android.tango.mode.operations.CreateObjectsOperation; import org.wheelmap.android.tango.mode.operations.OperationsModeRenderer; import org.wheelmap.android.tango.renderer.objects.Polygon3D; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Stack; public abstract class MeasureAreaModeRenderer extends OperationsModeRenderer { private static final String TAG = MeasureAreaModeRenderer.class.getSimpleName(); private List<Object3D> pointObjects = new ArrayList<>(); @Override public void onClickedAt(final float[] transform) { if (isOperationRunning() || isReady()) { return; } Matrix4 matrix4 = new Matrix4(transform); final Vector3 position = matrix4.getTranslation(); if (pointObjects.size() == 3) { projectObjectToPlane(position, new Plane( pointObjects.get(0).getPosition(), pointObjects.get(1).getPosition(), pointObjects.get(2).getPosition() )); } // check if new point has a valid position and does not create a irregular polygon List<Vector3> polygonPoints = new ArrayList<>(4); for (int i = 0; i < pointObjects.size(); i++) { polygonPoints.add(pointObjects.get(i).getPosition()); } polygonPoints.add(position); if (isPolygonIrregular(polygonPoints)) { Toast.makeText(WheelmapApp.get(), R.string.tango_measurement_area_invalid_position, Toast.LENGTH_SHORT).show(); return; } addOperation(new CreateObjectsOperation() { @Override public void execute(Manipulator m) { if (isReady()) { return; } Object3D createdPoint = getObjectFactory().createMeasurePoint(); createdPoint.setPosition(position); pointObjects.add(createdPoint); m.addObject(createdPoint); int size = pointObjects.size(); if (size > 1) { String text = String.format(Locale.getDefault(), "%.2fm", getLastDistance()); getObjectFactory().measureLineBetween(m, pointObjects.get(size - 1).getPosition(), pointObjects.get(size - 2).getPosition(), text); } if (size == 4) { // close polygon by drawing line between first and last point Vector3 first = pointObjects.get(0).getPosition(); Vector3 last = pointObjects.get(size - 1).getPosition(); String text = String.format(Locale.getDefault(), "%.2fm", first.distanceTo(last)); getObjectFactory().measureLineBetween(m, first, last, text); // finish polygon by filling it Stack<Vector3> areaPoints = new Stack<>(); for (int i = 0, pointObjectsSize = pointObjects.size(); i < pointObjectsSize; i++) { Object3D pointObject = pointObjects.get(i); areaPoints.add(pointObject.getPosition()); } Polygon3D area = getObjectFactory().createAreaPolygon(areaPoints); m.addObject(area); } } @Override public void undo(Manipulator manipulator) { super.undo(manipulator); pointObjects.removeAll(getCreatedObjects()); } }); } @Override public boolean isReady() { return pointObjects.size() >= 4; } private double getLastDistance() { if (pointObjects.size() < 2) { return 0; } int lastItemPosition = pointObjects.size() - 1; Object3D lastItem = pointObjects.get(lastItemPosition); int secondLastItemPosition = pointObjects.size() - 2; Object3D secondLastItem = pointObjects.get(secondLastItemPosition); return lastItem.getPosition().distanceTo(secondLastItem.getPosition()); } private void projectObjectToPlane(Vector3 position, Plane plane) { double distance = plane.getDistanceTo(position); Vector3 normal = plane.getNormal().clone(); normal.normalize(); normal.multiply(-distance); position.add(normal); } private Object3D calculateLastPointOfPolygon() { if (pointObjects.size() != 3) { throw new IllegalStateException(); } Vector3 a = pointObjects.get(0).getPosition(); Vector3 b = pointObjects.get(1).getPosition(); Vector3 c = pointObjects.get(2).getPosition(); Vector3 ba = b.clone().subtract(a); Vector3 position = c.clone().subtract(ba); Object3D point4 = getObjectFactory().createMeasurePoint(); point4.setPosition(position); return point4; } public double getArea() { if (pointObjects.size() != 4) { return -1; } List<Vector2> points = projectPolygonTo2d(); double area = areaOfPolygon(points); Log.d(TAG, "Area: " + area); return area; } public double getWidth() { if (pointObjects.size() != 4) { return -1; } List<Vector2> vector2s = projectPolygonTo2d(); Polygon polygon = SmallestSurroundingRectangle.get(vector2s); return polygon.getCoordinates()[0].distance(polygon.getCoordinates()[1]); } public double getLength() { if (pointObjects.size() != 4) { return -1; } List<Vector2> vector2s = projectPolygonTo2d(); Polygon polygon = SmallestSurroundingRectangle.get(vector2s); return polygon.getCoordinates()[1].distance(polygon.getCoordinates()[2]); } private boolean isPolygonIrregular(List<Vector3> points) { int size = points.size(); if (size <= 3) { return false; } double polygonAngle = 0; for (int i = 0; i < size; i++) { Vector3 one = points.get(i % size); Vector3 two = points.get((i + 1) % size); Vector3 three = points.get((i + 2) % size); Vector3 directionsVector1 = one.clone().subtract(two); Vector3 directionsVector2 = two.clone().subtract(three); double angle = directionsVector1.angle(directionsVector2); polygonAngle += angle; Log.d(TAG, "Angle: " + angle); if (Math.abs(angle) >= 180) { return true; } } Log.d(TAG, "polygonAngle: " + polygonAngle); // add some calculation tolerance return polygonAngle < 355 || polygonAngle > 365; } private double areaOfPolygon(List<Vector2> polyPoints) { int i, j, n = polyPoints.size(); double area = 0; for (i = 0; i < n; i++) { j = (i + 1) % n; area += polyPoints.get(i).getX() * polyPoints.get(j).getY(); area -= polyPoints.get(j).getX() * polyPoints.get(i).getY(); } area /= 2.0; return Math.abs(area); } private List<Vector2> projectPolygonTo2d() { List<Vector3> polygon3dPoints = new ArrayList<>(4); for (int i = 0; i < pointObjects.size(); i++) { polygon3dPoints.add(pointObjects.get(i).getPosition()); } return projectPolygonTo2d(polygon3dPoints); } private List<Vector2> projectPolygonTo2d(List<Vector3> list) { // create projection matrix Plane plane = new Plane(list.get(0), list.get(1), list.get(2)); Vector3 u = list.get(0).clone().subtract(list.get(1)); Vector3 v = plane.getNormal().clone().cross(u); Vector3 w = plane.getNormal().clone(); u.normalize(); v.normalize(); w.normalize(); Matrix4 matrix4 = new Matrix4(); matrix4.setAll(u, v, w, new Vector3(0, 0, 0)); matrix4.inverse(); List<Vector2> vector2s = new ArrayList<>(list.size()); for (Vector3 vector3 : list) { Vector3 projectedVector = matrix4.projectAndCreateVector(vector3); vector2s.add(new Vector2(projectedVector.x, projectedVector.y)); } return vector2s; } @Override public void clear() { super.clear(); pointObjects.clear(); } }