package org.osm2world.core.world.modules; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.EXACT; import static org.osm2world.core.map_elevation.data.GroundState.ON; import static org.osm2world.core.world.modules.common.WorldModuleGeometryUtil.createTriangleStripBetween; import java.util.ArrayList; import java.util.List; import org.openstreetmap.josm.plugins.graphview.core.data.TagGroup; import org.osm2world.core.map_data.data.MapAreaSegment; 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.map_elevation.creation.EleConstraintEnforcer; import org.osm2world.core.map_elevation.data.EleConnector; import org.osm2world.core.map_elevation.data.EleConnectorGroup; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; import org.osm2world.core.math.PolygonXYZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.target.RenderableToAllTargets; import org.osm2world.core.target.Target; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.data.NodeWorldObject; import org.osm2world.core.world.data.TerrainBoundaryWorldObject; import org.osm2world.core.world.data.WaySegmentWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; import org.osm2world.core.world.modules.common.BridgeOrTunnel; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; import org.osm2world.core.world.network.JunctionNodeWorldObject; import org.osm2world.core.world.network.VisibleConnectorNodeWorldObject; /** * adds tunnels to the world. * * Needs to be applied <em>after</em> all the modules that generate * whatever runs through the tunnels. */ public class TunnelModule extends AbstractModule { public static final boolean isTunnel(TagGroup tags) { return tags.containsKey("tunnel") && !"no".equals(tags.getValue("tunnel")) && !"building_passage".equals(tags.getValue("tunnel")); } public static final boolean isTunnel(MapSegment segment) { if (segment instanceof MapWaySegment) { return isTunnel(((MapWaySegment)segment).getTags()); } else { return isTunnel(((MapAreaSegment)segment).getArea().getTags()); } } @Override protected void applyToWaySegment(MapWaySegment segment) { WaySegmentWorldObject primaryRepresentation = segment.getPrimaryRepresentation(); if (primaryRepresentation instanceof AbstractNetworkWaySegmentWorldObject && isTunnel(segment)) { segment.addRepresentation(new Tunnel(segment, (AbstractNetworkWaySegmentWorldObject) primaryRepresentation)); } } @Override protected void applyToNode(MapNode node) { /* entrances */ if (node.getConnectedWaySegments().size() == 2) { MapWaySegment segmentA = node.getConnectedWaySegments().get(0); MapWaySegment segmentB = node.getConnectedWaySegments().get(1); if (isTunnel(segmentA) && !isTunnel(segmentB) && segmentA.getPrimaryRepresentation() instanceof AbstractNetworkWaySegmentWorldObject) { node.addRepresentation(new TunnelEntrance(node, (AbstractNetworkWaySegmentWorldObject) segmentA.getPrimaryRepresentation())); } else if (isTunnel(segmentB) && !isTunnel(segmentA) && segmentB.getPrimaryRepresentation() instanceof AbstractNetworkWaySegmentWorldObject) { node.addRepresentation(new TunnelEntrance(node, (AbstractNetworkWaySegmentWorldObject) segmentB.getPrimaryRepresentation())); } } /* tunnel nodes and junctions */ boolean onlyTunnelConnected = true; for (MapWaySegment segment : node.getConnectedWaySegments()) { if (!isTunnel(segment)) { onlyTunnelConnected = false; break; } } if (onlyTunnelConnected) { if (node.getPrimaryRepresentation() instanceof VisibleConnectorNodeWorldObject) { //TODO: TunnelConnector } else if (node.getPrimaryRepresentation() instanceof JunctionNodeWorldObject) { node.addRepresentation(new TunnelJunction(node, (JunctionNodeWorldObject) node.getPrimaryRepresentation())); } } } public static class Tunnel extends BridgeOrTunnel implements RenderableToAllTargets { public Tunnel(MapWaySegment segment, AbstractNetworkWaySegmentWorldObject primaryWO) { super(segment, primaryWO); } @Override public GroundState getGroundState() { return GroundState.BELOW; } @Override public void renderTo(Target<?> target) { List<VectorXYZ> leftOutline = primaryRep.getOutline(false); List<VectorXYZ> rightOutline = primaryRep.getOutline(true); List<VectorXYZ> aboveLeftOutline = new ArrayList<VectorXYZ>(leftOutline.size()); List<VectorXYZ> aboveRightOutline = new ArrayList<VectorXYZ>(rightOutline.size()); for (int i=0; i < leftOutline.size(); i++) { VectorXYZ clearingOffset = VectorXYZ.Y_UNIT.mult( 10); //TODO restore clearing // primaryRep.getClearingAbove(leftOutline.get(i).xz())); aboveLeftOutline.add(leftOutline.get(i).add(clearingOffset)); aboveRightOutline.add(rightOutline.get(i).add(clearingOffset)); } List<VectorXYZ> strip1 = createTriangleStripBetween( rightOutline, aboveRightOutline); List<VectorXYZ> strip2 = createTriangleStripBetween( aboveRightOutline, aboveLeftOutline); List<VectorXYZ> strip3 = createTriangleStripBetween( aboveLeftOutline, leftOutline); target.drawTriangleStrip(Materials.TUNNEL_DEFAULT, strip1, null); target.drawTriangleStrip(Materials.TUNNEL_DEFAULT, strip2, null); target.drawTriangleStrip(Materials.TUNNEL_DEFAULT, strip3, null); } } public static class TunnelEntrance implements NodeWorldObject, TerrainBoundaryWorldObject { private final MapNode node; private final AbstractNetworkWaySegmentWorldObject tunnelContent; /** * counterclockwise outline composed of * {@link #lowerLeft}, {@link #lowerCenter}, {@link #lowerRight}, * {@link #upperRight}, {@link #upperCenter} and {@link #upperLeft}. */ private SimplePolygonXZ outline; private VectorXZ lowerLeft; private VectorXZ lowerCenter; private VectorXZ lowerRight; private VectorXZ upperLeft; private VectorXZ upperCenter; private VectorXZ upperRight; private EleConnectorGroup connectors; public TunnelEntrance(MapNode node, AbstractNetworkWaySegmentWorldObject tunnelContent) { this.node = node; this.tunnelContent = tunnelContent; } /** * creates outline as "ring" around the entrance */ private void calculateOutlineIfNecessary() { if (outline != null) return; lowerCenter = node.getPos(); VectorXZ toRight = tunnelContent.getStartCutVector() .mult(tunnelContent.getWidth() * 0.5f); lowerLeft = lowerCenter.subtract(toRight); lowerRight = lowerCenter.add(toRight); VectorXZ toBack = tunnelContent.segment.getDirection().mult(0.1); if (tunnelContent.segment.getEndNode() == node) { toBack = toBack.invert(); } upperLeft = lowerLeft.add(toBack); upperCenter = lowerCenter.add(toBack); upperRight = lowerRight.add(toBack); outline = new SimplePolygonXZ(asList( lowerLeft, lowerCenter, lowerRight, upperRight, upperCenter, upperLeft, lowerLeft)); } @Override public MapNode getPrimaryMapElement() { return node; } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public Iterable<EleConnector> getEleConnectors() { calculateOutlineIfNecessary(); if (connectors == null) { connectors = new EleConnectorGroup(); connectors.add(new EleConnector(lowerLeft, node, ON)); connectors.add(new EleConnector(lowerCenter, node, ON)); connectors.add(new EleConnector(lowerRight, node, ON)); connectors.add(new EleConnector(upperLeft, null, ON)); connectors.add(new EleConnector(upperCenter, null, ON)); connectors.add(new EleConnector(upperRight, null, ON)); } return connectors; } @Override public void defineEleConstraints(EleConstraintEnforcer enforcer) { enforcer.requireVerticalDistance( EXACT, 10, connectors.getConnector(upperLeft), connectors.getConnector(lowerLeft)); enforcer.requireVerticalDistance( EXACT, 10, connectors.getConnector(upperCenter), connectors.getConnector(lowerCenter)); enforcer.requireVerticalDistance( EXACT, 10, connectors.getConnector(upperRight), connectors.getConnector(lowerRight)); //TODO restore original clearing //tunnelPrimaryRep.getClearingAbove(node.getPos())); } @Override public AxisAlignedBoundingBoxXZ getAxisAlignedBoundingBoxXZ() { calculateOutlineIfNecessary(); return new AxisAlignedBoundingBoxXZ(getOutlinePolygon().getVertices()); } @Override public SimplePolygonXZ getOutlinePolygonXZ() { calculateOutlineIfNecessary(); return outline; } @Override public PolygonXYZ getOutlinePolygon() { calculateOutlineIfNecessary(); return connectors.getPosXYZ(getOutlinePolygonXZ()); } } public static class TunnelJunction implements NodeWorldObject, RenderableToAllTargets { private final MapNode node; private final JunctionNodeWorldObject primaryRep; public TunnelJunction(MapNode node, JunctionNodeWorldObject primaryRep) { this.node = node; this.primaryRep = primaryRep; } @Override public MapNode getPrimaryMapElement() { return node; } @Override public GroundState getGroundState() { return GroundState.BELOW; } @Override public Iterable<EleConnector> getEleConnectors() { // TODO EleConnectors for tunnels return emptyList(); } @Override public void defineEleConstraints(EleConstraintEnforcer enforcer) {} @Override public void renderTo(Target<?> target) { //TODO port to new elevation model // List<VectorXYZ> topOutline = new ArrayList<VectorXYZ>(); // // int segCount = node.getConnectedSegments().size(); // for (int i=0; i<segCount; i++) { // // List<VectorXYZ> line = primaryRep.getOutline((i+1)%segCount, i); // // List<VectorXYZ> lineTop = // new ArrayList<VectorXYZ>(line.size()); // // for (VectorXYZ lineV : line) { // // double clearing; // // if (line.indexOf(lineV) == 0) { // MapSegment segment = node.getConnectedSegments().get((i+1)%segCount); // clearing = 10; //TODO clearingAboveMapSegment(lineV, segment); // } else { // MapSegment segment = node.getConnectedSegments().get(i); // clearing = 10; //TODO clearingAboveMapSegment(lineV, segment); // } // // lineTop.add(lineV.y(lineV.y + clearing)); // // } // // // draw wall // // target.drawTriangleStrip(Materials.TUNNEL_DEFAULT, // createTriangleStripBetween(line, lineTop), null); // // //collect nodes for top outline // // topOutline.addAll(lineTop); // // } // // // draw top // // target.drawConvexPolygon(Materials.TUNNEL_DEFAULT, topOutline, // globalTexCoordLists( // topOutline, Materials.TUNNEL_DEFAULT, false)); // } //TODO update or delete // private static double clearingAboveMapSegment(VectorXYZ lineV, MapSegment segment) { // // WorldObject segmentRep; // if (segment instanceof MapWaySegment) { // segmentRep = ((MapWaySegment)segment) // .getPrimaryRepresentation(); // } else { // segmentRep = ((MapAreaSegment)segment) // .getArea().getPrimaryRepresentation(); // } // // if (segmentRep != null) { // return segmentRep.getClearingAbove(lineV.xz()); // } else { // return 0; // } // // } } }