/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see .
*/
package de.hft.stuttgart.citydoctor2.datastructure;
import java.util.ArrayList;
import java.util.List;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.math.ProjectionAxis;
import de.hft.stuttgart.citydoctor2.math.Ring2d;
import de.hft.stuttgart.citydoctor2.math.UnitVector3d;
import de.hft.stuttgart.citydoctor2.math.Vector2d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.CopyHandler;
import de.hft.stuttgart.citydoctor2.utils.Copyable;
/**
* Represents a linear ring used in polygons. The ring contains the vertices.
*
* @author Matthias Betz
*
*/
public class LinearRing extends GmlElement {
private static final long serialVersionUID = -2488180830514940722L;
private LinearRingType type;
private Polygon parent;
private List vertices = new ArrayList<>();
public enum LinearRingType {
EXTERIOR, INTERIOR
}
public LinearRing(LinearRingType type) {
this.type = type;
}
/**
* Checks whether a point is inside this ring. A point on the edge does count as
* inside.
*
* @param v the point.
* @return true if the point is inside or on an edge, false if it is outside.
*/
public boolean isPointInside(Vector3d v) {
// project to 2d ring
ProjectionAxis axis = ProjectionAxis.of(this);
Vector2d point = axis.project(v);
Ring2d ring = Ring2d.of(this, axis);
int t = -1;
for (int i = 0; i < ring.getVertices().size() - 1; i++) {
t = t * crossProdTest(point, ring.getVertices().get(i), ring.getVertices().get(i + 1));
if (t == 0) {
return true;
}
}
return t >= 0;
}
private int crossProdTest(Vector2d a, Vector2d b, Vector2d c) {
if (a.getY() == b.getY() && a.getY() == c.getY()) {
if ((b.getX() <= a.getX() && a.getX() <= c.getX()) || (c.getX() <= a.getX() && a.getX() <= b.getX())) {
return 0;
} else {
return 1;
}
}
if (a.getY() == b.getY() && a.getX() == b.getX()) {
return 0;
}
if (b.getY() > c.getY()) {
Vector2d temp = b;
b = c;
c = temp;
}
if (a.getY() <= b.getY() || a.getY() > c.getY()) {
return 1;
}
return calculateDelta(a, b, c);
}
private int calculateDelta(Vector2d a, Vector2d b, Vector2d c) {
double delta = (b.getX() - a.getX()) * (c.getY() - a.getY()) - (b.getY() - a.getY()) * (c.getX() - a.getX());
if (delta > 0) {
return -1;
} else if (delta < 0) {
return 1;
} else {
return 0;
}
}
/**
* Calculates the normal vector of the ring. Method by Newell. If the Newell
* method would return a (0, 0, 0) vector a cross product is formed from the
* first 3 vertices. If there are no 3 vertices available (1, 0, 0) is returned.
*
* @return the normal as a normalized vector
*/
public UnitVector3d calculateNormalNormalized() {
return calculateNormal().normalize();
}
/**
* Calculates the normal vector of the ring. Method by Newell. If the Newell
* method would return a (0, 0, 0) vector a cross product is formed from the
* first 3 vertices. If there are no 3 vertices available (1, 0, 0) is returned.
*
* @return the normal vector
*/
public Vector3d calculateNormal() {
double[] coords = new double[3];
for (int i = 0; i < vertices.size() - 1; i++) {
Vertex current = vertices.get(i + 0);
Vertex next = vertices.get(i + 1);
coords[0] += (current.getZ() + next.getZ()) * (current.getY() - next.getY());
coords[1] += (current.getX() + next.getX()) * (current.getZ() - next.getZ());
coords[2] += (current.getY() + next.getY()) * (current.getX() - next.getX());
}
if (coords[0] == 0 && coords[1] == 0 && coords[2] == 0) {
// no valid normal vector found
if (vertices.size() < 3) {
// no three points, return x-axis
return UnitVector3d.X_AXIS;
}
Vertex v1 = vertices.get(0);
Vertex v2 = vertices.get(1);
Vertex v3 = vertices.get(2);
return calculateNormalWithCross(v1, v2, v3);
}
return new Vector3d(coords);
}
public double getArea() {
int n = vertices.size();
if (n < 3)
return 0.0; // a degenerated polygon
// prepare an vertex array with n+2 vertices:
// V[0] ... V[n-1] - the n different vertices
// V[n]=V[0]
// V[n+1]=V[1]
Vertex[] vertexArray = new Vertex[n + 2];
for (int i = 0; i < n; i++) {
vertexArray[i] = vertices.get(i);
}
vertexArray[n] = vertices.get(0);
vertexArray[n + 1] = vertices.get(1);
double area = 0;
// make sure the normal has been calculated
Vector3d normal = calculateNormal();
// select largest abs coordinate to ignore for projection
double ax = Math.abs(normal.getX()); // abs x-coord
double ay = Math.abs(normal.getY()); // abs y-coord
double az = Math.abs(normal.getZ()); // abs z-coord
// coord to ignore: 1=x, 2=y, 3=z
int coord = 3; // ignore z-coord
if (ax > ay) {
if (ax > az) {
coord = 1; // ignore x-coord
}
} else if (ay > az) {
coord = 2; // ignore y-coord
}
// compute area of the 2D projection
for (int i = 1, j = 2, k = 0; i <= vertexArray.length - 2; i++, j++, k++) {
switch (coord) {
case 1:
area += (vertexArray[i].getY() * (vertexArray[j].getZ() - vertexArray[k].getZ()));
break;
case 2:
area += (vertexArray[i].getX() * (vertexArray[j].getZ() - vertexArray[k].getZ()));
break;
case 3:
area += (vertexArray[i].getX() * (vertexArray[j].getY() - vertexArray[k].getY()));
break;
default:
throw new IllegalStateException();
}
}
// scale to get area before projection
double an = Math.sqrt(ax * ax + ay * ay + az * az); // length of normal vector
switch (coord) {
case 1:
area *= (an / (2 * ax));
break;
case 2:
area *= (an / (2 * ay));
break;
case 3:
area *= (an / (2 * az));
break;
default:
throw new IllegalStateException();
}
return Math.abs(area);
}
private UnitVector3d calculateNormalWithCross(Vertex v1, Vertex v2, Vertex v3) {
Vector3d dir1 = v2.minus(v1);
Vector3d dir2 = v3.minus(v1);
Vector3d cross = dir1.cross(dir2);
return cross.normalize();
}
@Override
public void accept(Check c) {
super.accept(c);
if (c.canExecute(this)) {
c.check(this);
}
}
@Override
public void clearAllContainedCheckResults() {
super.clearCheckResults();
}
public void setParent(Polygon polygon) {
parent = polygon;
}
public Polygon getParent() {
return parent;
}
public LinearRingType getType() {
return type;
}
public List getVertices() {
return vertices;
}
public void addVertex(Vertex v) {
vertices.add(v);
if (parent == null) {
return;
}
if (parent.isLinkedTo()) {
v.addAdjacentRing(this, parent.getLinkedFromPolygon().getParent());
}
v.addAdjacentRing(this, parent.getParent());
}
public void addVertex(int i, Vertex v) {
vertices.add(i, v);
if (parent.isLinkedTo()) {
v.addAdjacentRing(this, parent.getLinkedFromPolygon().getParent());
}
v.addAdjacentRing(this, parent.getParent());
}
public void setVertex(int i, Vertex v) {
vertices.set(i, v);
if (parent.isLinkedTo()) {
v.addAdjacentRing(this, parent.getLinkedFromPolygon().getParent());
}
v.addAdjacentRing(this, parent.getParent());
}
@Override
public String toString() {
return "LinearRing [type=" + type + ", gmlId=" + getGmlId() + "]";
}
void anonymize() {
setGmlId(GmlId.generateId());
}
public boolean isRingConnectedViaPoint(LinearRing other) {
for (Vertex v : vertices) {
if (other.getVertices().contains(v)) {
return true;
}
}
return false;
}
public boolean hasPointAsCorner(Vertex v) {
return vertices.contains(v);
}
public void setType(LinearRingType type) {
this.type = type;
}
public void addAllVertices(List extRing) {
vertices.addAll(extRing);
}
@Override
public void prepareForChecking() {
parent.getParent().prepareForChecking();
}
@Override
public void clearMetaInformation() {
parent.getParent().clearMetaInformation();
}
@Override
public void fillValues(Copyable original, CopyHandler handler) {
super.fillValues(original, handler);
LinearRing originalRing = (LinearRing) original;
type = originalRing.type;
parent = handler.getCopyInstance(originalRing.parent);
for (Vertex v : originalRing.vertices) {
vertices.add(handler.getCopyInstance(v));
}
}
@Override
public void collectInstances(CopyHandler handler) {
for (Vertex v : vertices) {
handler.addInstance(v);
}
handler.addInstance(parent);
}
@Override
public Copyable createCopyInstance() {
return new LinearRing(type);
}
}