package org.osm2world.core.world.modules; import static com.google.common.collect.Iterables.any; import static java.util.Arrays.asList; import static java.util.Collections.nCopies; import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.MAX; import static org.osm2world.core.target.common.material.Materials.*; import static org.osm2world.core.target.common.material.NamedTexCoordFunction.*; import static org.osm2world.core.target.common.material.TexCoordUtil.*; import static org.osm2world.core.util.Predicates.hasType; import static org.osm2world.core.world.modules.common.WorldModuleGeometryUtil.*; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.openstreetmap.josm.plugins.graphview.core.data.Tag; import org.osm2world.core.map_data.data.MapArea; 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_data.data.overlaps.MapOverlap; import org.osm2world.core.map_elevation.creation.EleConstraintEnforcer; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.PolygonXYZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.VectorXYZ; 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.AbstractAreaWorldObject; import org.osm2world.core.world.data.TerrainBoundaryWorldObject; import org.osm2world.core.world.modules.common.ConfigurableWorldModule; import org.osm2world.core.world.modules.common.WorldModuleParseUtil; import org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject; import org.osm2world.core.world.network.JunctionNodeWorldObject; import org.osm2world.core.world.network.NetworkAreaWorldObject; /** * adds water bodies, streams, rivers and fountains to the world */ public class WaterModule extends ConfigurableWorldModule { //TODO: add canal, ditch, drain private static final Tag WATER_TAG = new Tag("natural", "water"); private static final Tag RIVERBANK_TAG = new Tag("waterway", "riverbank"); private static final Map<String, Float> WATERWAY_WIDTHS; static { WATERWAY_WIDTHS = new HashMap<String, Float>(); WATERWAY_WIDTHS.put("river", 3f); WATERWAY_WIDTHS.put("stream", 0.5f); WATERWAY_WIDTHS.put("canal", 2f); WATERWAY_WIDTHS.put("ditch", 1f); WATERWAY_WIDTHS.put("drain", 1f); } //TODO: apply to is almost always the same! create a superclass handling this! @Override public void applyTo(MapData mapData) { for (MapWaySegment line : mapData.getMapWaySegments()) { for (String value : WATERWAY_WIDTHS.keySet()) { if (line.getTags().contains("waterway", value)) { line.addRepresentation(new Waterway(line)); } } } for (MapNode node : mapData.getMapNodes()) { int connectedRivers = 0; for (MapWaySegment line : node.getConnectedWaySegments()) { if (any(line.getRepresentations(), hasType(Waterway.class))) { connectedRivers += 1; } } if (connectedRivers > 2) { node.addRepresentation(new RiverJunction(node)); } } for (MapArea area : mapData.getMapAreas()) { if (area.getTags().contains(WATER_TAG) || area.getTags().contains(RIVERBANK_TAG)) { area.addRepresentation(new Water(area)); } if (area.getTags().contains("amenity", "fountain")) { area.addRepresentation(new AreaFountain(area)); } } } public static class Waterway extends AbstractNetworkWaySegmentWorldObject implements RenderableToAllTargets, TerrainBoundaryWorldObject { public Waterway(MapWaySegment line) { super(line); } @Override public void defineEleConstraints(EleConstraintEnforcer enforcer) { super.defineEleConstraints(enforcer); /* enforce downhill flow */ if (!segment.getTags().containsKey("incline")) { enforcer.requireIncline(MAX, 0, getCenterlineEleConnectors()); } } public float getWidth() { return WorldModuleParseUtil.parseWidth(segment.getTags(), WATERWAY_WIDTHS.get(segment.getTags().getValue("waterway"))); } @Override public PolygonXYZ getOutlinePolygon() { if (isContainedWithinRiverbank()) { return null; } else { return super.getOutlinePolygon(); } } @Override public SimplePolygonXZ getOutlinePolygonXZ() { if (isContainedWithinRiverbank()) { return null; } else { return super.getOutlinePolygonXZ(); } } @Override public void renderTo(Target<?> target) { //note: simply "extending" a river cannot work - unlike streets - // because there can be islands within the riverbank polygon. // That's why rivers will be *replaced* with Water areas instead. /* only draw the river if it doesn't have a riverbank */ //TODO: handle case where a river is completely within riverbanks, but not a *single* riverbank if (! isContainedWithinRiverbank()) { List<VectorXYZ> leftOutline = getOutline(false); List<VectorXYZ> rightOutline = getOutline(true); List<VectorXYZ> leftWaterBorder = createLineBetween( leftOutline, rightOutline, 0.05f); List<VectorXYZ> rightWaterBorder = createLineBetween( leftOutline, rightOutline, 0.95f); modifyLineHeight(leftWaterBorder, -0.2f); modifyLineHeight(rightWaterBorder, -0.2f); List<VectorXYZ> leftGround = createLineBetween( leftOutline, rightOutline, 0.35f); List<VectorXYZ> rightGround = createLineBetween( leftOutline, rightOutline, 0.65f); modifyLineHeight(leftGround, -1); modifyLineHeight(rightGround, -1); /* render ground */ @SuppressWarnings("unchecked") // generic vararg is intentional List<List<VectorXYZ>> strips = asList( createTriangleStripBetween( leftOutline, leftWaterBorder), createTriangleStripBetween( leftWaterBorder, leftGround), createTriangleStripBetween( leftGround, rightGround), createTriangleStripBetween( rightGround, rightWaterBorder), createTriangleStripBetween( rightWaterBorder, rightOutline) ); for (List<VectorXYZ> strip : strips) { target.drawTriangleStrip(TERRAIN_DEFAULT, strip, texCoordLists(strip, TERRAIN_DEFAULT, GLOBAL_X_Z)); } /* render water */ List<VectorXYZ> vs = createTriangleStripBetween( leftWaterBorder, rightWaterBorder); target.drawTriangleStrip(WATER, vs, texCoordLists(vs, WATER, GLOBAL_X_Z)); } } private boolean isContainedWithinRiverbank() { boolean containedWithinRiverbank = false; for (MapOverlap<?,?> overlap : segment.getOverlaps()) { if (overlap.getOther(segment) instanceof MapArea) { MapArea area = (MapArea)overlap.getOther(segment); if (area.getPrimaryRepresentation() instanceof Water && area.getPolygon().contains(segment.getLineSegment())) { containedWithinRiverbank = true; break; } } } return containedWithinRiverbank; } private static void modifyLineHeight(List<VectorXYZ> leftWaterBorder, float yMod) { for (int i = 0; i < leftWaterBorder.size(); i++) { VectorXYZ v = leftWaterBorder.get(i); leftWaterBorder.set(i, v.y(v.y+yMod)); } } } public static class RiverJunction extends JunctionNodeWorldObject implements TerrainBoundaryWorldObject, RenderableToAllTargets { public RiverJunction(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { //TODO: check whether it's within a riverbank (as with Waterway) List<VectorXYZ> vertices = getOutlinePolygon().getVertices(); target.drawConvexPolygon(WATER, vertices, texCoordLists(vertices, WATER, GLOBAL_X_Z)); //TODO: only cover with water to 0.95 * distance to center; add land below } } public static class Water extends NetworkAreaWorldObject implements RenderableToAllTargets, TerrainBoundaryWorldObject { //TODO: only cover with water to 0.95 * distance to center; add land below. // possible algorithm: for each node of the outer polygon, check whether it // connects to another water surface. If it doesn't move it inwards, // where "inwards" is calculated based on the two adjacent polygon segments. public Water(MapArea area) { super(area); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void defineEleConstraints(EleConstraintEnforcer enforcer) { enforcer.requireSameEle(getEleConnectors()); } @Override public void renderTo(Target<?> target) { Collection<TriangleXYZ> triangles = getTriangulation(); target.drawTriangles(WATER, triangles, triangleTexCoordLists(triangles, WATER, GLOBAL_X_Z)); } } private static class AreaFountain extends AbstractAreaWorldObject implements RenderableToAllTargets, TerrainBoundaryWorldObject { public AreaFountain(MapArea area) { super(area); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { /* render water */ Collection<TriangleXYZ> triangles = getTriangulation(); target.drawTriangles(PURIFIED_WATER, triangles, triangleTexCoordLists(triangles, PURIFIED_WATER, GLOBAL_X_Z)); /* render walls */ //note: mostly copy-pasted from BarrierModule double width=0.1; double height=0.5; List<VectorXYZ> wallShape = asList( new VectorXYZ(-width/2, 0, 0), new VectorXYZ(-width/2, height, 0), new VectorXYZ(+width/2, height, 0), new VectorXYZ(+width/2, 0, 0) ); List<VectorXYZ> path = getOutlinePolygon().getVertexLoop(); List<List<VectorXYZ>> strips = createShapeExtrusionAlong( wallShape, path, nCopies(path.size(), VectorXYZ.Y_UNIT)); for (List<VectorXYZ> strip : strips) { target.drawTriangleStrip(Materials.CONCRETE, strip, texCoordLists(strip, Materials.CONCRETE, STRIP_WALL)); } } } }