package org.osm2world.core.world.creation;
import java.util.ArrayList;
import java.util.List;
import org.osm2world.core.map_data.data.MapData;
import org.osm2world.core.map_data.data.MapElement;
import org.osm2world.core.map_data.data.MapNode;
import org.osm2world.core.map_data.data.MapSegment;
import org.osm2world.core.map_data.data.MapWaySegment;
import org.osm2world.core.math.VectorXZ;
import org.osm2world.core.world.data.NodeWorldObject;
import org.osm2world.core.world.network.JunctionNodeWorldObject;
import org.osm2world.core.world.network.NetworkWaySegmentWorldObject;
import org.osm2world.core.world.network.VisibleConnectorNodeWorldObject;
/**
* class that will calculate the information for those {@link MapElement}s
* that implement an interface from org.o3dw.representation.network and set it.
* Calling this is necessary for those representations to work properly!
*/
public class NetworkCalculator {
private NetworkCalculator() {}
//FIXME: must be able to handle connectors with lines of opposite direction!
private static final float ROAD_PUSHING_STEP = 0.01f;
/**
* calculates cut and offset information for all
* NetworkNode/Line/AreaRepresentations of elements in a grid.
*/
public static void calculateNetworkInformationInGrid(MapData grid) {
for (MapNode node : grid.getMapNodes()) {
//TODO: also work with nodes that aren't Network*NodeRepresentations,
// but connect two NetworkWaySegmentRep.s (invisible connectors)
final List<MapWaySegment> inboundNLines = new ArrayList<MapWaySegment>();
List<MapWaySegment> outboundNLines = new ArrayList<MapWaySegment>();
for (MapWaySegment line : node.getInboundLines()) {
if (line.getPrimaryRepresentation() instanceof NetworkWaySegmentWorldObject) {
inboundNLines.add(line);
}
}
for (MapWaySegment line : node.getOutboundLines()) {
if (line.getPrimaryRepresentation() instanceof NetworkWaySegmentWorldObject) {
outboundNLines.add(line);
}
}
if (node.getPrimaryRepresentation() instanceof JunctionNodeWorldObject) {
/* junctions */
calculateJunctionNodeEffects(node,
(JunctionNodeWorldObject)node.getPrimaryRepresentation(),
inboundNLines, outboundNLines);
} else if (inboundNLines.size() + outboundNLines.size() == 2) {
/* visible or invisible connectors */
List<MapWaySegment> connectedNLines = new ArrayList<MapWaySegment>(2);
connectedNLines.addAll(inboundNLines);
connectedNLines.addAll(outboundNLines);
MapWaySegment line1 = connectedNLines.get(0);
MapWaySegment line2 = connectedNLines.get(1);
calculateConnectorNodeEffects(node.getPrimaryRepresentation(),
line1, line2,
inboundNLines.contains(line1),
inboundNLines.contains(line2));
} else {
for (MapWaySegment outboundNLine : outboundNLines) {
setOrthogonalCutVector(outboundNLine, true);
}
for (MapWaySegment inboundNLine : inboundNLines) {
setOrthogonalCutVector(inboundNLine, false);
}
}
}
}
/**
* calculates the effects of both visible and invisible connector nodes.
*/
private static void calculateConnectorNodeEffects(
NodeWorldObject nodeRepresentation,
MapWaySegment line1, MapWaySegment line2,
boolean inbound1, boolean inbound2) {
NetworkWaySegmentWorldObject renderable1 =
((NetworkWaySegmentWorldObject)line1.getPrimaryRepresentation());
NetworkWaySegmentWorldObject renderable2 =
((NetworkWaySegmentWorldObject)line2.getPrimaryRepresentation());
VisibleConnectorNodeWorldObject visibleConnectorRep = null;
if (nodeRepresentation instanceof VisibleConnectorNodeWorldObject) {
visibleConnectorRep =
(VisibleConnectorNodeWorldObject)nodeRepresentation;
}
/* calculate cut as angle bisector between the two lines */
VectorXZ inVector = line1.getDirection();
VectorXZ outVector = line2.getDirection();
if (!inbound1) { inVector = inVector.invert(); }
if (inbound2) { outVector = outVector.invert(); }
VectorXZ cutVector;
if (inVector.equals(outVector)) { //TODO: allow for some small difference?
cutVector = outVector.rightNormal();
} else {
cutVector = outVector.subtract(inVector);
cutVector = cutVector.normalize();
}
//make sure that cutVector points to the right, which is equivalent to:
//y component of the cross product (inVector x cutVector) is positive.
//If this isn't the case, invert the cut vector.
if (inVector.z * cutVector.x - inVector.x * cutVector.z <= 0) {
cutVector = cutVector.invert();
}
/* set calculated cut vector */
if (inbound1) {
renderable1.setEndCutVector(cutVector);
} else {
renderable1.setStartCutVector(cutVector.invert());
}
if (inbound2) {
renderable2.setEndCutVector(cutVector.invert());
} else {
renderable2.setStartCutVector(cutVector);
}
/* perform calculations necessary for connectors
* whose representation requires space */
double connectorLength = 0;
if (visibleConnectorRep != null) {
connectorLength = visibleConnectorRep.getLength();
}
if (connectorLength > 0) {
/* move connected lines to make room for the node's representation */
//connected node of line1 is moved orthogonally to the cut vector
VectorXZ offset1 = cutVector.rightNormal();
offset1 = offset1.mult(connectorLength / 2);
if (inbound1) {
renderable1.setEndOffset(offset1);
} else {
renderable1.setStartOffset(offset1);
}
//node of line2 is moved into the opposite direction
VectorXZ offset2 = offset1.invert();
if (inbound2) {
renderable2.setEndOffset(offset2);
} else {
renderable2.setStartOffset(offset2);
}
/* provide information to node's representation */
if (nodeRepresentation instanceof VisibleConnectorNodeWorldObject) {
VisibleConnectorNodeWorldObject connectorRep =
(VisibleConnectorNodeWorldObject)nodeRepresentation;
VectorXZ connectedPos1;
VectorXZ connectedPos2;
if (inbound1) {
connectedPos1 = line1.getEndNode().getPos();
} else {
connectedPos1 = line1.getStartNode().getPos();
}
if (inbound2) {
connectedPos2 = line2.getEndNode().getPos();
} else {
connectedPos2 = line2.getStartNode().getPos();
}
connectorRep.setInformation(
cutVector,
connectedPos1.add(offset1),
connectedPos2.add(offset2),
renderable1.getWidth(),
renderable2.getWidth());
}
//TODO: if done properly, this might affect NOT ONLY the directly adjacent lines
}
}
private static void calculateJunctionNodeEffects(
MapNode node, JunctionNodeWorldObject nodeRepresentation,
final List<MapWaySegment> inboundNLines, List<MapWaySegment> outboundNLines) {
/* create list of all connected roads.
* Order of adds is important, it needs to match
* the order of cutVectors, coords and widths adds. */
List<MapWaySegment> connectedNSegments = new ArrayList<MapWaySegment>();
connectedNSegments.addAll(inboundNLines);
connectedNSegments.addAll(outboundNLines);
//all cut vectors in here will point to the right from the junctions pov!
List<VectorXZ> cutVectors = new ArrayList<VectorXZ>(connectedNSegments.size());
List<VectorXZ> coords = new ArrayList<VectorXZ>(connectedNSegments.size());
List<Float> widths = new ArrayList<Float>(connectedNSegments.size());
/* determine cut angles:
* always orthogonal to the connected line */
for (MapWaySegment in : inboundNLines) {
NetworkWaySegmentWorldObject inRenderable =
((NetworkWaySegmentWorldObject)in.getPrimaryRepresentation());
VectorXZ cutVector = in.getRightNormal();
inRenderable.setEndCutVector(cutVector);
cutVectors.add(cutVector.invert());
coords.add(in.getEndNode().getPos());
widths.add(inRenderable.getWidth());
}
for (MapWaySegment out : outboundNLines) {
NetworkWaySegmentWorldObject outRenderable =
((NetworkWaySegmentWorldObject)out.getPrimaryRepresentation());
VectorXZ cutVector = out.getRightNormal();
outRenderable.setStartCutVector(cutVector);
cutVectors.add(cutVector);
coords.add(out.getStartNode().getPos());
widths.add(outRenderable.getWidth());
}
/* move roads away from the intersection until they cannot overlap anymore,
* this is certain if the distance between their ends' center points
* is greater than the sum of their half-widths */
//TODO (performance) if roads were ordered by angle here already, this would be much faster -> only neighbors checked
boolean overlapPossible;
do {
overlapPossible = false;
overlapCheck:
for (int r1=0; r1 < coords.size(); r1++) {
for (int r2=r1+1; r2 < coords.size(); r2++) {
/* ignore overlapping (or almost overlapping) way segments
* as no reasonable amount of pushing would separate these */
if (VectorXZ.distance(connectedNSegments.get(r1).getDirection(),
connectedNSegments.get(r2).getDirection()) < 0.1
|| VectorXZ.distance(connectedNSegments.get(r1).getDirection(),
connectedNSegments.get(r2).getDirection().invert()) < 0.1) {
continue;
}
double distance = Math.abs(coords.get(r1).subtract(coords.get(r2)).length());
if (distance > 200) {
//TODO: proper error handling
System.err.println("distance has exceeded 200 at node " + node
+ "\n (representation: " + nodeRepresentation + ")");
// overlapCheck will remain false, no further size increase
break overlapCheck;
}
if (distance <= widths.get(r1)*0.5 + widths.get(r2)*0.5) {
overlapPossible = true;
break overlapCheck;
}
}
}
if (overlapPossible) {
/* push outwards */
coords.clear();
for (MapWaySegment in : inboundNLines) {
NetworkWaySegmentWorldObject inRenderable =
((NetworkWaySegmentWorldObject)in.getPrimaryRepresentation());
VectorXZ inVector = in.getDirection();
VectorXZ offsetModification = inVector.mult(-ROAD_PUSHING_STEP);
VectorXZ newEndOffset = inRenderable.getEndOffset().add(offsetModification);
inRenderable.setEndOffset(newEndOffset);
coords.add(in.getEndNode().getPos().add(newEndOffset));
}
for (MapWaySegment out : outboundNLines) {
NetworkWaySegmentWorldObject outRenderable =
((NetworkWaySegmentWorldObject)out.getPrimaryRepresentation());
VectorXZ outVector = out.getDirection();
VectorXZ offsetModification = outVector.mult(ROAD_PUSHING_STEP);
VectorXZ newStartOffset = outRenderable.getStartOffset().add(offsetModification);
outRenderable.setStartOffset(newStartOffset);
coords.add(out.getStartNode().getPos().add(newStartOffset));
}
}
} while(overlapPossible);
/* set calculated information using the correct order */
List<MapSegment> segments = node.getConnectedSegments();
ArrayList<VectorXZ> junctionCutCenters = new ArrayList<VectorXZ>(segments.size());
ArrayList<VectorXZ> junctionCutVectors = new ArrayList<VectorXZ>(segments.size());
ArrayList<Float> junctionWidths = new ArrayList<Float>(segments.size());
for (MapSegment segment : segments) {
if (connectedNSegments.contains(segment)) {
int index = connectedNSegments.indexOf(segment);
junctionCutCenters.add(coords.get(index));
junctionCutVectors.add(cutVectors.get(index));
junctionWidths.add(widths.get(index));
} else {
junctionCutCenters.add(null);
junctionCutVectors.add(null);
junctionWidths.add(null);
}
}
nodeRepresentation.setInformation(
junctionCutCenters, junctionCutVectors, junctionWidths);
}
/**
* @param l line with {@link NetworkWaySegmentWorldObject} as representation
*/
private static void setOrthogonalCutVector(MapWaySegment l, boolean setStartVector) {
VectorXZ cutVector = l.getRightNormal();
NetworkWaySegmentWorldObject lRepresentation =
(NetworkWaySegmentWorldObject)l.getPrimaryRepresentation();
if (setStartVector) {
lRepresentation.setStartCutVector(cutVector);
} else {
lRepresentation.setEndCutVector(cutVector);
}
}
}