package org.osm2world.core.world.modules; import static java.lang.Math.PI; import static java.util.Arrays.asList; import static org.osm2world.core.math.VectorXYZ.X_UNIT; import static org.osm2world.core.target.common.material.Materials.*; import static org.osm2world.core.target.common.material.NamedTexCoordFunction.STRIP_FIT; import static org.osm2world.core.target.common.material.TexCoordUtil.texCoordLists; import static org.osm2world.core.world.modules.common.WorldModuleParseUtil.*; import java.util.ArrayList; import java.util.List; 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.target.RenderableToAllTargets; import org.osm2world.core.target.Target; import org.osm2world.core.target.common.TextureData; import org.osm2world.core.target.common.material.Material; import org.osm2world.core.world.data.NoOutlineNodeWorldObject; import org.osm2world.core.world.modules.common.AbstractModule; /** * adds traffic signs to the world */ public class TrafficSignModule extends AbstractModule { @Override protected void applyToNode(MapNode node) { if (!node.getTags().containsKey("traffic_sign")) return; if (isInHighway(node)) return; //only exact positions (next to highway) /* split the traffic sign value into its components */ String tagValue = node.getTags().getValue("traffic_sign"); String[] countryAndSigns = tagValue.split(":", 2); if (countryAndSigns.length != 2) return; String country = countryAndSigns[0]; String[] signs = countryAndSigns[1].split("[;,]"); /* match each individual sign to the list of supported types */ List<TrafficSignType> types = new ArrayList<TrafficSignType>(signs.length); for (String sign : signs) { sign = sign.trim(); sign = sign.replace('-', '_'); try { types.add(TrafficSignType.valueOf(country + '_' + sign)); } catch (IllegalArgumentException e) { // not a supported traffic sign type } } /* create a visual representation for the traffic sign */ if (types.size() > 0) { node.addRepresentation(new TrafficSign(node, types)); } } private enum TrafficSignType { STOP(SIGN_DE_206, 1, 2), DE_206(SIGN_DE_206, 1, 2), DE_250(SIGN_DE_250, 1, 2), DE_625(SIGN_DE_625_11, 2, .80), DE_625_11(SIGN_DE_625_11, 2, .80), DE_625_21(SIGN_DE_625_21, 2, .80); public final Material material; public final int numPosts; private final double defaultHeight; TrafficSignType(Material material, int numPosts, double defaultHeight) { this.material = material; this.numPosts = numPosts; this.defaultHeight = defaultHeight; } } 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 TrafficSign extends NoOutlineNodeWorldObject implements RenderableToAllTargets { private final List<TrafficSignType> types; public TrafficSign(MapNode node, List<TrafficSignType> types) { super(node); this.types = types; } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public void renderTo(Target<?> target) { /* get basic parameters */ double height = parseHeight(node.getTags(), (float)types.get(0).defaultHeight); double postRadius = 0.05; double[] signHeights = new double[types.size()]; double[] signWidths = new double[types.size()]; for (int sign = 0; sign < types.size(); sign++) { TextureData textureData = null; if (types.get(sign).material.getNumTextureLayers() != 0) { textureData = types.get(sign).material.getTextureDataList().get(0); } if (textureData == null) { signHeights[sign] = 0.6; signWidths[sign] = 0.6; } else { signHeights[sign] = textureData.height; signWidths[sign] = textureData.width; } } /* position the post(s) */ int numPosts = types.get(0).numPosts; List<VectorXYZ> positions = new ArrayList<VectorXYZ>(numPosts); for (int i = 0; i < numPosts; i++) { double relativePosition = 0.5 - (i+1)/(double)(numPosts+1); positions.add(getBase().add(X_UNIT.mult(relativePosition * signWidths[0]))); } /* create the front and back side of the sign */ List<List<VectorXYZ>> signGeometries = new ArrayList<List<VectorXYZ>>(); double distanceBetweenSigns = 0.1; double upperHeight = height; for (int sign = 0; sign < types.size(); sign++) { double signHeight = signHeights[sign]; double signWidth = signWidths[sign]; List<VectorXYZ> vs = asList( getBase().add(+signWidth/2, upperHeight, postRadius), getBase().add(+signWidth/2, upperHeight-signHeight, postRadius), getBase().add(-signWidth/2, upperHeight, postRadius), getBase().add(-signWidth/2, upperHeight-signHeight, postRadius) ); signGeometries.add(vs); upperHeight -= signHeight + distanceBetweenSigns; } /* rotate the sign around the base to match the direction tag */ double direction = parseDirection(node.getTags(), PI); for (List<VectorXYZ> vs : signGeometries) { for (int i = 0; i < vs.size(); i++) { VectorXYZ v = vs.get(i); v = v.rotateVec(direction, getBase(), VectorXYZ.Y_UNIT); vs.set(i, v); } } if (positions.size() > 1) { // if 1, the post is exactly on the base for (int i = 0; i < positions.size(); i++) { VectorXYZ v = positions.get(i); v = v.rotateVec(direction, getBase(), VectorXYZ.Y_UNIT); positions.set(i, v); } } /* render the post(s) */ for (VectorXYZ position : positions) { target.drawColumn(STEEL, null, position, height, postRadius, postRadius, false, true); } /* render the sign (front, then back) */ for (int sign = 0; sign < types.size(); sign++) { TrafficSignType type = types.get(sign); List<VectorXYZ> vs = signGeometries.get(sign); target.drawTriangleStrip(type.material, vs, texCoordLists(vs, type.material, STRIP_FIT)); vs = asList(vs.get(2), vs.get(3), vs.get(0), vs.get(1)); target.drawTriangleStrip(STEEL, vs, texCoordLists(vs, STEEL, STRIP_FIT)); } } } }