package org.osm2world.core.world.modules; import static java.lang.Math.PI; import static java.util.Arrays.asList; import static org.osm2world.core.math.VectorXZ.fromAngle; import static org.osm2world.core.target.common.material.NamedTexCoordFunction.*; import static org.osm2world.core.target.common.material.TexCoordUtil.*; import static org.osm2world.core.world.modules.common.WorldModuleGeometryUtil.createTriangleStripBetween; import java.awt.Color; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapElement; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.map_data.data.overlaps.MapOverlap; import org.osm2world.core.map_elevation.data.EleConnectorGroup; import org.osm2world.core.map_elevation.data.GroundState; import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.PolygonXYZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.TriangleXYZ; import org.osm2world.core.math.TriangleXZ; import org.osm2world.core.math.VectorXYZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.math.algorithms.JTSTriangulationUtil; import org.osm2world.core.math.algorithms.Poly2TriTriangulationUtil; import org.osm2world.core.target.RenderableToAllTargets; import org.osm2world.core.target.Target; 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.util.MinMaxUtil; import org.osm2world.core.util.exception.TriangulationException; import org.osm2world.core.world.data.AbstractAreaWorldObject; import org.osm2world.core.world.data.TerrainBoundaryWorldObject; import org.osm2world.core.world.modules.SurfaceAreaModule.SurfaceArea; import org.osm2world.core.world.modules.common.AbstractModule; import com.google.common.base.Function; /** * adds golf courses to the map */ public class GolfModule extends AbstractModule { private static final int HOLE_CIRCLE_VERTICES = 8; private static final double HOLE_RADIUS = 0.108 / 2; private static final double HOLE_DEPTH = 0.102; @Override public void applyToArea(MapArea area) { if (!area.getTags().containsKey("golf")) return; if (area.getTags().contains("golf", "tee")) { area.addRepresentation(new Tee(area)); } else if (area.getTags().contains("golf", "fairway")) { area.addRepresentation(new Fairway(area)); } else if (area.getTags().contains("golf", "green")) { area.addRepresentation(new Green(area)); } } private static class Tee extends SurfaceArea { private Tee(MapArea area) { super(area, area.getTags().containsKey("surface") ? area.getTags().getValue("surface") : "grass"); } } private static class Fairway extends SurfaceArea { private Fairway(MapArea area) { super(area, area.getTags().containsKey("surface") ? area.getTags().getValue("surface") : "grass"); } } private static class Green extends AbstractAreaWorldObject implements RenderableToAllTargets, TerrainBoundaryWorldObject { private final VectorXZ pinPosition; private final SimplePolygonXZ pinHoleLoop; private final EleConnectorGroup pinConnectors; private Green(MapArea area) { super(area); /* check whether a pin has been explicitly mapped */ VectorXZ explicitPinPosition = null; for (MapOverlap<?, ?> overlap : area.getOverlaps()) { MapElement other = overlap.getOther(area); if (other.getTags().contains("golf","pin") && other instanceof MapNode) { explicitPinPosition = ((MapNode)other).getPos(); break; } } /* place an implicit pin if none has been mapped */ if (explicitPinPosition != null) { pinPosition = explicitPinPosition; } else { pinPosition = area.getOuterPolygon().getCenter(); } /* create circle around the hole */ List<VectorXZ> holeRing = new ArrayList<VectorXZ>(HOLE_CIRCLE_VERTICES); for (int i = 0; i < HOLE_CIRCLE_VERTICES; i++) { VectorXZ direction = fromAngle(2 * PI * ((double)i / HOLE_CIRCLE_VERTICES)); VectorXZ vertex = pinPosition.add(direction.mult(HOLE_RADIUS)); holeRing.add(vertex); } holeRing.add(holeRing.get(0)); pinHoleLoop = new SimplePolygonXZ(holeRing); pinConnectors = new EleConnectorGroup(); pinConnectors.addConnectorsFor(pinHoleLoop.getVertexCollection(), area, GroundState.ON); } @Override public GroundState getGroundState() { return GroundState.ON; } @Override public EleConnectorGroup getEleConnectors() { EleConnectorGroup eleConnectors = super.getEleConnectors(); if (pinConnectors != null) { eleConnectors.addAll(pinConnectors); } return eleConnectors; } @Override public void renderTo(Target<?> target) { /* render green surface */ String surfaceValue = area.getTags().getValue("surface"); Material material = Materials.GRASS; if (surfaceValue != null && !"grass".equals(surfaceValue)) { material = Materials.getSurfaceMaterial(surfaceValue, material); } Collection<TriangleXZ> trianglesXZ = getGreenTriangulation(); Collection<TriangleXYZ> triangles = getEleConnectors().getTriangulationXYZ(trianglesXZ); target.drawTriangles(material, triangles, triangleTexCoordLists(triangles , material, GLOBAL_X_Z)); /* render pin */ PolygonXYZ upperHoleRing = pinConnectors.getPosXYZ(pinHoleLoop); drawPin(target, pinPosition, upperHoleRing.getVertexLoop()); } private List<TriangleXZ> getGreenTriangulation() { List<SimplePolygonXZ> holes = area.getPolygon().getHoles(); holes.add(pinHoleLoop); try { return Poly2TriTriangulationUtil.triangulate( area.getPolygon().getOuter(), holes, Collections.<LineSegmentXZ>emptyList(), Collections.<VectorXZ>emptyList()); } catch (TriangulationException e) { System.err.println("Poly2Tri exception for " + this + ":"); e.printStackTrace(); System.err.println("... falling back to JTS triangulation."); return JTSTriangulationUtil.triangulate( area.getPolygon().getOuter(), holes, Collections.<LineSegmentXZ>emptyList(), Collections.<VectorXZ>emptyList()); } } private static void drawPin(Target<?> target, VectorXZ pos, List<VectorXYZ> upperHoleRing) { double minHoleEle = MinMaxUtil.<VectorXYZ>min(upperHoleRing, new Function<VectorXYZ, Double>() { @Override public Double apply(VectorXYZ v) { return v.y; } }).y; double holeBottomEle = minHoleEle - HOLE_DEPTH; /* draw hole */ List<VectorXYZ> lowerHoleRing = new ArrayList<VectorXYZ>(); for (VectorXYZ v : upperHoleRing) { lowerHoleRing.add(v.y(holeBottomEle)); } List<VectorXYZ> vs = createTriangleStripBetween( upperHoleRing, lowerHoleRing); Material groundMaterial = Materials.EARTH.makeSmooth(); target.drawTriangleStrip(groundMaterial, vs, texCoordLists(vs, groundMaterial, STRIP_WALL)); target.drawConvexPolygon(groundMaterial, lowerHoleRing, texCoordLists(vs, groundMaterial, GLOBAL_X_Z)); /* draw flag */ target.drawColumn(Materials.PLASTIC_GREY.makeSmooth(), null, pos.xyz(holeBottomEle), 1.5, 0.007, 0.007, false, true); ImmutableMaterial flagcloth = new ImmutableMaterial(Interpolation.SMOOTH, Color.YELLOW); List<VectorXYZ> flagVertices = asList( new VectorXYZ(pos.x, 1.5, pos.z), new VectorXYZ(pos.x, 1.2, pos.z), new VectorXYZ(pos.x + 0.4, 1.5, pos.z), new VectorXYZ(pos.x + 0.4, 1.2, pos.z)); target.drawTriangleStrip(flagcloth, flagVertices, texCoordLists(flagVertices, flagcloth, STRIP_WALL)); } } }