package org.osm2world.core.world.modules; import static com.google.common.collect.Iterables.any; import static java.util.Arrays.asList; import static org.osm2world.core.target.common.material.NamedTexCoordFunction.GLOBAL_X_Z; import static org.osm2world.core.target.common.material.TexCoordUtil.texCoordLists; import static org.osm2world.core.util.Predicates.hasType; import java.util.Collections; import java.util.List; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.map_data.data.MapWaySegment; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.GeometryUtil; 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.Material; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.data.TerrainBoundaryWorldObject; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.modules.common.WorldModuleGeometryUtil; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; import org.osm2world.core.world.network.JunctionNodeWorldObject; /** * adds rails to the world */ public class RailwayModule extends ConfigurableWorldModule { /** accepted values of the railway key */ private static final List<String> RAILWAY_VALUES = asList( "rail", "light_rail", "tram", "subway", "disused"); @Override public void applyTo(MapData grid) { for (MapWaySegment segment : grid.getMapWaySegments()) { if (segment.getTags().containsAny("railway", RAILWAY_VALUES)) { segment.addRepresentation(new Rail(segment)); } } //TODO: the following for loop is copied from water module and should be in a common superclass for (MapNode node : grid.getMapNodes()) { int connectedRails = 0; for (MapWaySegment line : node.getConnectedWaySegments()) { if (any(line.getRepresentations(), hasType(Rail.class))) { connectedRails += 1; } } if (connectedRails > 2) { // node.addRepresentation(new RailJunction(node)); // TODO: reactivate after implementing proper rendering for rail junctions } } } private static class Rail extends AbstractNetworkWaySegmentWorldObject implements RenderableToAllTargets, TerrainBoundaryWorldObject { private static final float GROUND_WIDTH = 2.25f; private static final float RAIL_DIST = 1.5f; private static final float SLEEPER_WIDTH = 2.0f; private static final float SLEEPER_LENGTH = 0.75f; private static final float SLEEPER_HEIGHT = 0.125f; private static final List<VectorXYZ> RAIL_SHAPE = asList( new VectorXYZ(-0.45f, 0, 0), new VectorXYZ(-0.1f, 0.1f, 0), new VectorXYZ(-0.1f, 0.5f, 0), new VectorXYZ(-0.25f, 0.55f, 0), new VectorXYZ(-0.25f, 0.75f, 0), new VectorXYZ(+0.25f, 0.75f, 0), new VectorXYZ(+0.25f, 0.55f, 0), new VectorXYZ(+0.1f, 0.5f, 0), new VectorXYZ(+0.1f, 0.1f, 0), new VectorXYZ(+0.45f, 0, 0) ); static { for (int i=0; i < RAIL_SHAPE.size(); i++) { VectorXYZ v = RAIL_SHAPE.get(i); v = v.mult(0.25f); v = v.y(v.y + SLEEPER_HEIGHT); RAIL_SHAPE.set(i, v); } } public Rail(MapWaySegment segment) { super(segment); } @Override public GroundState getGroundState() { if (segment.getTags().contains("railway", "subway") && !segment.getTags().contains("tunnel", "no")){ return GroundState.BELOW; } return super.getGroundState(); } @Override public void renderTo(Target<?> target) { /* draw ground */ List<VectorXYZ> groundVs = WorldModuleGeometryUtil.createTriangleStripBetween( getOutline(false), getOutline(true)); target.drawTriangleStrip(Materials.RAIL_BALLAST_DEFAULT, groundVs, texCoordLists(groundVs, Materials.RAIL_BALLAST_DEFAULT, GLOBAL_X_Z)); /* draw rails */ @SuppressWarnings("unchecked") List<VectorXYZ>[] railLines = new List[2]; railLines[0] = WorldModuleGeometryUtil.createLineBetween( getOutline(false), getOutline(true), ((GROUND_WIDTH - RAIL_DIST) / GROUND_WIDTH) / 2); railLines[1] = WorldModuleGeometryUtil.createLineBetween( getOutline(false), getOutline(true), 1 - ((GROUND_WIDTH - RAIL_DIST) / GROUND_WIDTH) / 2); for (List<VectorXYZ> railLine : railLines) { List<List<VectorXYZ>> stripVectors = WorldModuleGeometryUtil.createShapeExtrusionAlong( RAIL_SHAPE, railLine, Collections.nCopies(railLine.size(), VectorXYZ.Y_UNIT)); for (List<VectorXYZ> stripVector : stripVectors) { target.drawTriangleStrip(Materials.RAIL_DEFAULT, stripVector, null); } } /* draw railway ties/sleepers */ List<VectorXZ> sleeperPositions = GeometryUtil.equallyDistributePointsAlong(3, false, getStartWithOffset(), getEndWithOffset()); for (VectorXZ sleeperPosition : sleeperPositions) { //TODO interpolate ele, also using additional points inbetween // VectorXYZ sleeperPositionXYZ = // segment.getElevationProfile().getWithEle(sleeperPosition); // // target.drawBox(Materials.RAIL_SLEEPER_DEFAULT, // sleeperPositionXYZ, segment.getDirection(), // SLEEPER_HEIGHT, SLEEPER_WIDTH, SLEEPER_LENGTH); } } @Override public float getWidth() { return GROUND_WIDTH; } } public static class RailJunction extends JunctionNodeWorldObject implements RenderableToAllTargets, TerrainBoundaryWorldObject { public RailJunction(MapNode node) { super(node); } @Override public GroundState getGroundState() { //TODO (code duplication): copied from RoadModule GroundState currentGroundState = null; checkEachLine: { for (MapWaySegment line : this.node.getConnectedWaySegments()) { if (line.getPrimaryRepresentation() == null) continue; GroundState lineGroundState = line.getPrimaryRepresentation().getGroundState(); if (currentGroundState == null) { currentGroundState = lineGroundState; } else if (currentGroundState != lineGroundState) { currentGroundState = GroundState.ON; break checkEachLine; } } } return currentGroundState; } @Override public void renderTo(Target<?> target) { if (getOutlinePolygon() == null) return; /* draw ground */ List<VectorXYZ> vectors = getOutlinePolygon().getVertexLoop(); Material material = Materials.RAIL_BALLAST_DEFAULT; target.drawConvexPolygon(material, vectors, texCoordLists(vectors, material, GLOBAL_X_Z)); /* draw connection between each pair of rails */ /* TODO: use node.getConnectedLines() instead? * (allows access to information from there, * such as getOutline!) */ for (int i=0; i<cutCenters.size(); i++) { for (int j=0; j<i; j++) { /* connect those rails with an obtuse angle between them */ } } } } }