package org.osm2world.core.world.modules; import static java.lang.Math.*; import static java.util.Arrays.asList; import static java.util.Arrays.fill; import static org.openstreetmap.josm.plugins.graphview.core.util.ValueStringParser.parseOsmDecimal; 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.texCoordLists; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*; import java.awt.Color; import java.util.ArrayList; import java.util.List; import java.util.function.IntToDoubleFunction; 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.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.ConfMaterial; import org.osm2world.core.target.common.material.ImmutableMaterial; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.target.common.material.Material.Interpolation; import org.osm2world.core.target.common.material.Materials; import org.osm2world.core.world.data.NoOutlineNodeWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; import javax.print.attribute.HashPrintServiceAttributeSet; /** * adds various types of street furniture to the world */ public class StreetFurnitureModule extends AbstractModule { @Override protected void applyToNode(MapNode node) { if (node.getTags().contains("man_made", "flagpole")) { node.addRepresentation(new Flagpole(node)); } if (node.getTags().contains("advertising", "column")) { node.addRepresentation(new AdvertisingColumn(node)); } if (node.getTags().contains("advertising", "billboard")) { node.addRepresentation(new Billboard(node)); } if (node.getTags().contains("amenity", "bench")) { node.addRepresentation(new Bench(node)); } if (node.getTags().contains("amenity", "table") || node.getTags().contains("leisure", "picnic_table")) { node.addRepresentation(new Table(node)); } if (node.getTags().contains("highway", "bus_stop") || node.getTags().contains("public_transport", "platform") && node.getTags().contains("bus", "yes")) { node.addRepresentation(new BusStop(node)); } if (node.getTags().contains("man_made", "cross") || node.getTags().contains("summit:cross", "yes") || node.getTags().contains("historic", "wayside_cross")) { node.addRepresentation(new Cross(node)); } if (node.getTags().contains("amenity", "waste_basket")) { node.addRepresentation(new WasteBasket(node)); } if (node.getTags().contains("amenity", "grit_bin")) { node.addRepresentation(new GritBin(node)); } if (node.getTags().contains("amenity", "post_box") && (node.getTags().containsAnyKey(asList("operator", "brand")))) { node.addRepresentation(new PostBox(node)); } if (node.getTags().contains("amenity", "telephone") && (node.getTags().containsAnyKey(asList("operator", "brand")))) { node.addRepresentation(new Phone(node)); } if (node.getTags().contains("amenity", "vending_machine") && (node.getTags().containsAny("vending", asList("parcel_pickup;parcel_mail_in", "parcel_mail_in")))) { node.addRepresentation(new ParcelMachine(node)); } if (node.getTags().contains("amenity", "vending_machine") && (node.getTags().containsAny("vending", asList("bicycle_tube", "cigarettes", "condoms")))) { node.addRepresentation(new VendingMachineVice(node)); } if (node.getTags().contains("amenity", "recycling") && (node.getTags().contains("recycling_type", "container"))) { node.addRepresentation(new RecyclingContainer(node)); } if (node.getTags().contains("emergency", "fire_hydrant") && node.getTags().contains("fire_hydrant:type", "pillar")) { node.addRepresentation(new FireHydrant(node)); } if (node.getTags().contains("highway", "street_lamp")) { node.addRepresentation(new StreetLamp(node)); } if (node.getTags().contains("tourism", "information") && node.getTags().contains("information", "board")) { node.addRepresentation(new Board(node)); } } private static boolean isInWall(MapNode node) { if (node.getAdjacentAreas().size() > 0) { return true; } else { return false; } } private static boolean isInHighway(MapNode node) { if (node.getConnectedWaySegments().size() > 0) { for (MapWaySegment way : node.getConnectedWaySegments()) { if (way.getTags().containsKey("highway") && !way.getTags().containsAny("highway", asList("path", "footway", "platform"))) { return true; } } } return false; } private static final class Flagpole extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public Flagpole(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { target.drawColumn(STEEL, null, getBase(), parseHeight(node.getTags(), 10f), 0.15, 0.15, false, true); } } private static final class AdvertisingColumn extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public AdvertisingColumn(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { float height = parseHeight(node.getTags(), 3f); /* draw socket, poster and cap */ target.drawColumn(CONCRETE, null, getBase(), 0.15 * height, 0.5, 0.5, false, false); target.drawColumn(ADVERTISING_POSTER, null, getBase(), 0.98 * height, 0.48, 0.48, false, false); target.drawColumn(CONCRETE, null, getBase().add(0, 0.95 * height, 0), 0.05 * height, 0.5, 0.5, false, true); } } private static final class Billboard extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public Billboard(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { float width = parseWidth(node.getTags(), 4); float height = parseHeight(node.getTags(), 3.5f); float minHeight = height / 5; double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); VectorXZ boardVector = faceVector.rightNormal(); /* draw board */ VectorXYZ[] vsPoster = { getBase().add(boardVector.mult(width / 2)).addY(height), getBase().add(boardVector.mult(width / 2)).addY(minHeight), getBase().add(boardVector.mult(-width / 2)).addY(height), getBase().add(boardVector.mult(-width / 2)).addY(minHeight) }; List<VectorXYZ> vsListPoster = asList(vsPoster); target.drawTriangleStrip(ADVERTISING_POSTER, vsListPoster, texCoordLists(vsListPoster, ADVERTISING_POSTER, STRIP_FIT)); VectorXYZ[] vsBoard = { vsPoster[2], vsPoster[3], vsPoster[0], vsPoster[1] }; List<VectorXYZ> vsListBoard = asList(vsBoard); target.drawTriangleStrip(CONCRETE, vsListBoard, texCoordLists(vsListBoard, CONCRETE, STRIP_WALL)); /* draw poles */ VectorXZ[] poles = { node.getPos().add(boardVector.mult(-width / 4)), node.getPos().add(boardVector.mult(+width / 4)) }; for (VectorXZ pole : poles) { target.drawBox(CONCRETE, pole.xyz(getBase().y), faceVector, minHeight, 0.2, 0.1); } } } private static final class Bench extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public Bench(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { float width = parseWidth(node.getTags(), 1.5f); /* determine material */ Material material = null; //TODO parse color if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("material")); } if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("surface"), Materials.WOOD); } /* calculate vectors and corners */ double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); VectorXZ boardVector = faceVector.rightNormal(); List<VectorXZ> cornerOffsets = new ArrayList<VectorXZ>(4); cornerOffsets.add(faceVector.mult(+0.25).add(boardVector.mult(+width / 2))); cornerOffsets.add(faceVector.mult(+0.25).add(boardVector.mult(-width / 2))); cornerOffsets.add(faceVector.mult(-0.25).add(boardVector.mult(+width / 2))); cornerOffsets.add(faceVector.mult(-0.25).add(boardVector.mult(-width / 2))); /* draw seat and backrest */ target.drawBox(material, getBase().addY(0.5), faceVector, 0.05, width, 0.5); if (!node.getTags().contains("backrest", "no")) { target.drawBox(material, getBase().add(faceVector.mult(-0.23)).addY(0.5), faceVector, 0.5, width, 0.04); } /* draw poles */ for (VectorXZ cornerOffset : cornerOffsets) { VectorXZ polePos = node.getPos().add(cornerOffset.mult(0.8)); target.drawBox(material, polePos.xyz(getBase().y), faceVector, 0.5, 0.08, 0.08); } } } private static final class Table extends NoOutlineNodeWorldObject implements RenderableToAllTargets { private final ConfMaterial defaultMaterial; public Table(MapNode node) { super(node); if (node.getTags().contains("leisure", "picnic_table")) { defaultMaterial = Materials.WOOD; } else { defaultMaterial = Materials.STEEL; } } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { int seats = parseInt(node.getTags(), 4, "seats"); // All default values are bound to the hight value. This allows to chose any table size. float height = parseHeight(node.getTags(), 0.75f); float width = parseWidth(node.getTags(), height * 1.2f); float length = parseLength(node.getTags(), ((seats + 1) / 2) * height / 1.25f); float seatHeight = height / 1.5f; /* determine material */ Material material = null; //TODO parse color if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("material")); } if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("surface"), defaultMaterial); } /* calculate vectors and corners */ double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); VectorXZ boardVector = faceVector.rightNormal(); float poleCenterOffset = height / 15f; List<VectorXZ> cornerOffsets = new ArrayList<VectorXZ>(4); cornerOffsets.add(faceVector.mult(+length / 2 - poleCenterOffset).add(boardVector.mult(+width / 2 - poleCenterOffset))); cornerOffsets.add(faceVector.mult(+length / 2 - poleCenterOffset).add(boardVector.mult(-width / 2 + poleCenterOffset))); cornerOffsets.add(faceVector.mult(-length / 2 + poleCenterOffset).add(boardVector.mult(+width / 2 - poleCenterOffset))); cornerOffsets.add(faceVector.mult(-length / 2 + poleCenterOffset).add(boardVector.mult(-width / 2 + poleCenterOffset))); /* draw poles */ float poleThickness = poleCenterOffset * 1.6f; for (VectorXZ cornerOffset : cornerOffsets) { VectorXZ polePos = node.getPos().add(cornerOffset); target.drawBox(material, polePos.xyz(getBase().y + 0.001), faceVector, height, poleThickness, poleThickness); } /* draw table */ target.drawBox(material, getBase().addY(height * 14f / 15f), faceVector, height / 15f, width, length); /* draw seats */ int leftSeats = seats / 2; int rightSeats = (seats + 1) / 2; renderSeatSide(target, material, boardVector.mult(+width / 2 + seatHeight / 2.5f), length, leftSeats, seatHeight); renderSeatSide(target, material, boardVector.mult(-width / 2 - seatHeight / 2.5f), length, rightSeats, seatHeight); } private void renderSeatSide(Target<?> target, Material material, VectorXZ rowPos, float length, int seats, float seatHeight) { VectorXZ boardVector = rowPos.rightNormal(); VectorXZ faceVector = rowPos.normalize(); float seatWidth = seatHeight / 1.25f; float seatLength = seatHeight / 1.25f; VectorXYZ seatBase = getBase().add(rowPos).addY(seatHeight * 0.94f); VectorXYZ backrestBase = getBase().add(rowPos).add(faceVector.mult(seatLength * 0.45f)).addY(seatHeight); for (int i = 0; i < seats; i++) { float seatBoardPos = length / seats * ((seats - 1) / 2.0f - i); VectorXZ seatPos = rowPos.add(boardVector.mult(seatBoardPos)); List<VectorXZ> cornerOffsets = new ArrayList<VectorXZ>(4); cornerOffsets.add(seatPos.add(boardVector.mult(+seatWidth * 0.45f).add(faceVector.mult(+seatLength * 0.45f)))); cornerOffsets.add(seatPos.add(boardVector.mult(+seatWidth * 0.45f).add(faceVector.mult(-seatLength * 0.45f)))); cornerOffsets.add(seatPos.add(boardVector.mult(-seatWidth * 0.45f).add(faceVector.mult(-seatLength * 0.45f)))); cornerOffsets.add(seatPos.add(boardVector.mult(-seatWidth * 0.45f).add(faceVector.mult(+seatLength * 0.45f)))); /* draw poles */ for (VectorXZ cornerOffset : cornerOffsets) { VectorXZ polePos = node.getPos().add(cornerOffset); target.drawBox(material, polePos.xyz(getBase().y + 0.001), faceVector, seatHeight, seatWidth / 10f, seatLength / 10f); } /* draw seat */ target.drawBox(material, seatBase.add(boardVector.mult(seatBoardPos)), faceVector, seatHeight * 0.06f, seatWidth, seatLength); /* draw backrest */ target.drawBox(material, backrestBase.add(boardVector.mult(seatBoardPos)), faceVector, seatHeight, seatWidth, seatLength / 10f); } } } /** * a summit cross or wayside cross */ private static final class Cross extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public Cross(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { boolean summit = node.getTags().containsKey("summit:cross") || node.getTags().contains("natural", "peak"); float height = parseHeight(node.getTags(), summit ? 4f : 2f); float width = parseHeight(node.getTags(), height * 2 / 3); double thickness = min(height, width) / 8; /* determine material and direction */ Material material = null; //TODO parse color if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("material")); } if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("surface"), Materials.WOOD); } double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); /* draw cross */ target.drawBox(material, getBase(), faceVector, height, thickness, thickness); target.drawBox(material, getBase().addY(height - width / 2 - thickness / 2), faceVector, thickness, width, thickness); } } private static final class RecyclingContainer extends NoOutlineNodeWorldObject implements RenderableToAllTargets { double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); public RecyclingContainer(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { float distanceX = 3f; float distanceZ = 1.6f; int n = -1; int m = 0; if (node.getTags().containsAny(asList("recycling:glass_bottles", "recycling:glass"), "yes")) { n++; } if (node.getTags().contains("recycling:paper", "yes")) { n++; } if (node.getTags().contains("recycling:clothes", "yes")) { n++; } if (node.getTags().contains("recycling:paper", "yes")) { drawContainer(target, "paper", getBase().add(new VectorXYZ((distanceX * (-n / 2 + m)), 0f, (distanceZ / 2)).rotateY(faceVector.angle()))); drawContainer(target, "paper", getBase().add(new VectorXYZ((distanceX * (-n / 2 + m)), 0f, -(distanceZ / 2)).rotateY(faceVector.angle()))); m++; } if (node.getTags().containsAny(asList("recycling:glass_bottles", "recycling:glass"), "yes")) { drawContainer(target, "white_glass", getBase().add(new VectorXYZ((distanceX * (-n / 2 + m)), 0f, (distanceZ / 2)).rotateY(faceVector.angle()))); drawContainer(target, "coloured_glass", getBase().add(new VectorXYZ((distanceX * (-n / 2 + m)), 0f, -(distanceZ / 2)).rotateY(faceVector.angle()))); m++; } if (node.getTags().contains("recycling:clothes", "yes")) { drawContainer(target, "clothes", getBase().add(new VectorXYZ((distanceX * (-n / 2 + m)), 0f, 0).rotateY(faceVector.angle()))); } } private void drawContainer(Target<?> target, String trash, VectorXYZ pos) { if ("clothes".equals(trash)) { target.drawBox(new ImmutableMaterial(Interpolation.FLAT, new Color(0.82f, 0.784f, 0.75f)), pos, faceVector, 2, 1, 1); } else { // "paper" || "white_glass" || "coloured_glass" float width = 1.5f; float height = 1.6f; Material colourFront = null; Material colourBack = null; if ("paper".equals(trash)) { colourFront = new ImmutableMaterial(Interpolation.FLAT, Color.BLUE); colourBack = new ImmutableMaterial(Interpolation.FLAT, Color.BLUE); } else if ("white_glass".equals(trash)) { colourFront = new ImmutableMaterial(Interpolation.FLAT, Color.WHITE); colourBack = new ImmutableMaterial(Interpolation.FLAT, Color.WHITE); } else { // "coloured_glass" colourFront = new ImmutableMaterial(Interpolation.FLAT, new Color(0.18f, 0.32f, 0.14f)); colourBack = new ImmutableMaterial(Interpolation.FLAT, new Color(0.39f, 0.15f, 0.11f)); } target.drawBox(STEEL, pos, faceVector, height, width, width); target.drawBox(colourFront, pos.add(new VectorXYZ((width / 2 - 0.10), 0.1f, (width / 2 - 0.1)).rotateY(directionAngle)), faceVector, height - 0.2, 0.202, 0.202); target.drawBox(colourBack, pos.add(new VectorXYZ(-(width / 2 - 0.10), 0.1f, (width / 2 - 0.1)).rotateY(directionAngle)), faceVector, height - 0.2, 0.202, 0.202); target.drawBox(colourFront, pos.add(new VectorXYZ((width / 2 - 0.10), 0.1f, -(width / 2 - 0.1)).rotateY(directionAngle)), faceVector, height - 0.2, 0.202, 0.202); target.drawBox(colourBack, pos.add(new VectorXYZ(-(width / 2 - 0.10), 0.1f, -(width / 2 - 0.1)).rotateY(directionAngle)), faceVector, height - 0.2, 0.202, 0.202); } } } private static final class WasteBasket extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public WasteBasket(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { /* determine material */ Material material = null; //TODO parse color if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("material")); } if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("surface"), STEEL); } /* draw pole */ target.drawColumn(material, null, getBase(), 1.2, 0.06, 0.06, false, true); /* draw basket */ target.drawColumn(material, null, getBase().addY(0.5).add(0.25, 0f, 0f), 0.5, 0.2, 0.2, true, true); } } private static final class GritBin extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public GritBin(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { float height = parseHeight(node.getTags(), 0.5f); float width = parseWidth(node.getTags(), 1); float depth = width / 2f; /* determine material */ Material material = null; //TODO parse color if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("material")); } if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("surface"), Materials.GRITBIN_DEFAULT); } double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); VectorXZ boardVector = faceVector.rightNormal(); /* draw box */ target.drawBox(material, getBase(), faceVector, height, width, depth); /* draw lid */ List<VectorXYZ> vs = new ArrayList<VectorXYZ>(); vs.add(getBase().addY(height + 0.2)); vs.add(getBase().add(boardVector.mult(width / 2)).add(faceVector.mult(depth / 2)).addY(height)); vs.add(getBase().add(boardVector.mult(-width / 2)).add(faceVector.mult(depth / 2)).addY(height)); vs.add(getBase().add(boardVector.mult(-width / 2)).add(faceVector.mult(-depth / 2)).addY(height)); vs.add(getBase().add(boardVector.mult(width / 2)).add(faceVector.mult(-depth / 2)).addY(height)); vs.add(getBase().add(boardVector.mult(width / 2)).add(faceVector.mult(depth / 2)).addY(height)); target.drawTriangleFan(material.brighter(), vs, null); } } private static final class Phone extends NoOutlineNodeWorldObject implements RenderableToAllTargets { private static enum Type {WALL, PILLAR, CELL, HALFCELL} public Phone(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); Material roofMaterial = null; Material poleMaterial = null; Type type = null; // get Type of Phone if (isInWall(node)) { type = Type.WALL; } else { type = Type.CELL; } // Phones differ widely in appearance, hence we draw them only for known operators or brands if (node.getTags().containsAny(asList("operator", "brand"), asList("Deutsche Telekom AG", "Deutsche Telekom", "Telekom"))) { roofMaterial = TELEKOM_MANGENTA; poleMaterial = STEEL; } else if (node.getTags().containsAny(asList("operator", "brand"), "British Telecom")) { roofMaterial = POSTBOX_ROYALMAIL; poleMaterial = POSTBOX_ROYALMAIL; } else { //no rendering, unknown operator or brand //TODO log info return; } // default dimensions may differ depending on the phone type float height = 0f; float width = 0f; switch (type) { case WALL: break; case CELL: height = parseHeight(node.getTags(), 2.1f); width = parseWidth(node.getTags(), 0.8f); target.drawBox(GLASS, getBase(), faceVector, height - 0.2, width - 0.06, width - 0.06); target.drawBox(roofMaterial, getBase().addY(height - 0.2), faceVector, 0.2, width, width); target.drawBox(poleMaterial, getBase().add(new VectorXYZ((width / 2 - 0.05), 0, (width / 2 - 0.05)).rotateY(directionAngle)), faceVector, height - 0.2, 0.1, 0.1); target.drawBox(poleMaterial, getBase().add(new VectorXYZ(-(width / 2 - 0.05), 0, (width / 2 - 0.05)).rotateY(directionAngle)), faceVector, height - 0.2, 0.1, 0.1); target.drawBox(poleMaterial, getBase().add(new VectorXYZ(0, 0, -(width / 2 - 0.05)).rotateY(directionAngle)), faceVector, height - 0.2, width, 0.1); break; default: assert false : "unknown or unsupported phone type"; } } } private static final class VendingMachineVice extends NoOutlineNodeWorldObject implements RenderableToAllTargets { private static enum Type {WALL, PILLAR} public VendingMachineVice(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); Material machineMaterial = null; Material poleMaterial = STEEL; Type type = null; if (node.getTags().contains("vending", "bicycle_tube") && node.getTags().containsAny("operator", asList("Continental", "continental"))) { machineMaterial = new ImmutableMaterial(Interpolation.FLAT, Color.ORANGE); } else if (node.getTags().contains("vending", "bicycle_tube")) { machineMaterial = new ImmutableMaterial(Interpolation.FLAT, Color.BLUE); } else if (node.getTags().contains("vending", "cigarettes")) { machineMaterial = new ImmutableMaterial(Interpolation.FLAT, new Color(0.8f, 0.73f, 0.5f)); } else if (node.getTags().contains("vending", "condoms")) { machineMaterial = new ImmutableMaterial(Interpolation.FLAT, new Color(0.39f, 0.15f, 0.11f)); } // get Type of vending machine if (isInWall(node)) { type = Type.WALL; } else { type = Type.PILLAR; } // default dimensions will differ depending on the post box type float height = 0f; switch (type) { case WALL: break; case PILLAR: height = parseHeight(node.getTags(), 1.8f); target.drawBox(poleMaterial, getBase().add(new VectorXYZ(0, 0, -0.05).rotateY(faceVector.angle())), faceVector, height - 0.3, 0.1, 0.1); target.drawBox(machineMaterial, getBase().addY(height - 1).add(new VectorXYZ(0, 0, 0.1).rotateY(directionAngle)), faceVector, 1, 1, 0.2); break; default: assert false : "unknown or unsupported Vending machine Type"; } } } private static final class PostBox extends NoOutlineNodeWorldObject implements RenderableToAllTargets { private static enum Type {WALL, PILLAR} public PostBox(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); Material boxMaterial = null; Material poleMaterial = null; Type type = null; // post boxes differ widely in appearance, hence we draw them only for known operators or brands if (node.getTags().containsAny(asList("operator", "brand"), asList("Deutsche Post AG", "Deutsche Post"))) { boxMaterial = POSTBOX_DEUTSCHEPOST; poleMaterial = STEEL; type = Type.WALL; } else if (node.getTags().contains("operator", "Royal Mail")) { boxMaterial = POSTBOX_ROYALMAIL; type = Type.PILLAR; } else { //no rendering, unknown operator or brand for post box //TODO log info return; } assert (type != Type.WALL || poleMaterial != null) : "post box of type wall requires a pole material"; // default dimensions will differ depending on the post box type float height = 0f; float width = 0f; switch (type) { case WALL: height = parseHeight(node.getTags(), 0.8f); width = parseWidth(node.getTags(), 0.3f); target.drawBox(poleMaterial, getBase(), faceVector, height, 0.08, 0.08); target.drawBox(boxMaterial, getBase().add(faceVector.mult(width / 2 - 0.08 / 2)).addY(height), faceVector, width, width, width); break; case PILLAR: height = parseHeight(node.getTags(), 2f); width = parseWidth(node.getTags(), 0.5f); target.drawColumn(boxMaterial, null, getBase(), height - 0.1, width, width, false, false); target.drawColumn(boxMaterial, null, getBase().addY(height - 0.1), 0.1, width + 0.1, 0, true, true); break; default: assert false : "unknown post box type"; } } } private static final class BusStop extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public BusStop(MapNode node) { super(node); if (node.getTags().contains("bin", "yes")) { node.addRepresentation(new WasteBasket(node)); } } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { if (!isInHighway(node)) { float height = parseHeight(node.getTags(), 3f); float signHeight = 0.7f; float signWidth = 0.4f; Material poleMaterial = STEEL; double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); target.drawColumn(poleMaterial, null, getBase(), height - signHeight, 0.05, 0.05, true, true); /* draw sign */ target.drawBox(BUS_STOP_SIGN, getBase().addY(height - signHeight), faceVector, signHeight, signWidth, 0.02); /* draw timetable */ target.drawBox(poleMaterial, getBase().addY(1.2f).add(new VectorXYZ(0.055f, 0, 0f).rotateY(directionAngle)), faceVector, 0.31, 0.01, 0.43); //TODO Add Shelter and bench } } } private static final class ParcelMachine extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public ParcelMachine(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { double ele = getBase().y; double directionAngle = parseDirection(node.getTags(), PI); Material boxMaterial = POSTBOX_DEUTSCHEPOST; Material otherMaterial = STEEL; VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); VectorXZ rightVector = faceVector.rightNormal(); // shape depends on type if (node.getTags().contains("type", "Rondell")) { float height = parseHeight(node.getTags(), 2.2f); float width = parseWidth(node.getTags(), 3f); float rondelWidth = width * 2 / 3; float boxWidth = width * 1 / 3; float roofOverhang = 0.3f; /* draw rondel */ target.drawColumn(boxMaterial, null, getBase().add(rightVector.mult(-rondelWidth / 2)), height, rondelWidth / 2, rondelWidth / 2, false, true); /* draw box */ target.drawBox(boxMaterial, getBase().add(rightVector.mult(boxWidth / 2)).add(faceVector.mult(-boxWidth / 2)), faceVector, height, boxWidth, boxWidth); /* draw roof */ target.drawColumn(otherMaterial, null, getBase().addY(height), 0.1, rondelWidth / 2 + roofOverhang / 2, rondelWidth / 2 + roofOverhang / 2, true, true); } else if (node.getTags().contains("type", "Paketbox")) { float height = parseHeight(node.getTags(), 1.5f); float width = parseHeight(node.getTags(), 1.0f); float depth = width; target.drawBox(boxMaterial, getBase(), faceVector, height, width * 2, depth * 2); } else { // type=Schrank or type=24/7 Station (they look roughly the same) or no type (fallback) float height = parseHeight(node.getTags(), 2.2f); float width = parseWidth(node.getTags(), 3.5f); float depth = width / 3; float roofOverhang = 0.3f; /* draw box */ target.drawBox(boxMaterial, getBase(), faceVector, height, width, depth); /* draw small roof */ target.drawBox(otherMaterial, node.getPos().add(faceVector.mult(roofOverhang)).xyz(ele + height), faceVector, 0.1, width, depth + roofOverhang * 2); } } } private static final class FireHydrant extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public FireHydrant(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { float height = parseHeight(node.getTags(), 1f); /* draw main pole */ target.drawColumn(FIREHYDRANT, null, getBase(), height, 0.15, 0.15, false, true); /* draw two small and one large valve */ VectorXYZ valveBaseVector = getBase().addY(height - 0.3); VectorXZ smallValveVector = VectorXZ.X_UNIT; VectorXZ largeValveVector = VectorXZ.Z_UNIT; target.drawBox(FIREHYDRANT, valveBaseVector, smallValveVector, 0.1f, 0.5f, 0.1f); target.drawBox(FIREHYDRANT, valveBaseVector.add(0.2f, -0.1f, 0f), largeValveVector, 0.15f, 0.15f, 0.15f); } } private static final class StreetLamp extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public StreetLamp(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { float lampHeight = 0.8f; float lampHalfWidth = 0.4f; float poleHeight = parseHeight(node.getTags(), 5f) - lampHeight; /* determine material */ Material material = null; if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("material")); } if (material == null) { material = Materials.getSurfaceMaterial( node.getTags().getValue("surface"), STEEL); } /* draw pole */ target.drawColumn(material, null, getBase(), 0.5, 0.16, 0.08, false, false); target.drawColumn(material, null, getBase().addY(0.5), poleHeight, 0.08, 0.08, false, false); /* draw lamp */ // lower part List<VectorXYZ> vs = new ArrayList<VectorXYZ>(); vs.add(getBase().addY(poleHeight)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(lampHalfWidth, 0, lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(lampHalfWidth, 0, -lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(-lampHalfWidth, 0, -lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(-lampHalfWidth, 0, lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(lampHalfWidth, 0, lampHalfWidth)); target.drawTriangleFan(material, vs, null); // upper part vs = new ArrayList<VectorXYZ>(); vs.add(getBase().addY(poleHeight + lampHeight)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(lampHalfWidth, 0, lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(-lampHalfWidth, 0, lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(-lampHalfWidth, 0, -lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(lampHalfWidth, 0, -lampHalfWidth)); vs.add(getBase().addY(poleHeight + lampHeight * 0.8).add(lampHalfWidth, 0, lampHalfWidth)); target.drawTriangleFan(material, vs, null); } } private static final class Board extends NoOutlineNodeWorldObject implements RenderableToAllTargets { public Board(MapNode node) { super(node); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { double directionAngle = parseDirection(node.getTags(), PI); VectorXZ faceVector = VectorXZ.fromAngle(directionAngle); target.drawColumn(WOOD, null, getBase(), 1.5, 0.05, 0.05, false, true); target.drawBox(WOOD, getBase().addY(1.2), faceVector, 0.4, 0.4, 0.1); } } }