/* * Copyright 2014 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.polyworld.graph; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.terasology.math.delaunay.Voronoi; import org.terasology.math.geom.BaseVector2f; import org.terasology.math.geom.ImmutableVector2f; import org.terasology.math.geom.LineSegment; import org.terasology.math.geom.Rect2f; import org.terasology.math.geom.Rect2i; import org.terasology.math.geom.Vector2f; /** * VoronoiGraph.java * */ public class VoronoiGraph implements Graph { private final List<Edge> edges = new ArrayList<>(); private final List<Corner> corners = new ArrayList<>(); private final List<Region> regions = new ArrayList<>(); private final Rect2f realBounds; private final Rect2i intBounds; /** * @param bounds bounds of the target area (points from Voronoi will be scaled and translated accordingly) * @param v the Voronoi diagram to use */ public VoronoiGraph(Rect2i bounds, Voronoi v) { intBounds = bounds; realBounds = Rect2f.createFromMinAndSize(bounds.minX(), bounds.minY(), bounds.width(), bounds.height()); final Map<Vector2f, Region> regionMap = new HashMap<>(); final Map<BaseVector2f, Corner> pointCornerMap = new HashMap<>(); for (Vector2f vorSite : v.siteCoords()) { Vector2f site = transform(v.getPlotBounds(), realBounds, vorSite); Region region = new Region(new ImmutableVector2f(site)); regions.add(region); regionMap.put(vorSite, region); for (Vector2f pt : v.region(vorSite)) { Corner c0 = makeCorner(pointCornerMap, v.getPlotBounds(), pt); region.addCorner(c0); c0.addTouches(region); } } for (org.terasology.math.delaunay.Edge libedge : v.edges()) { if (!libedge.isVisible()) { continue; } final LineSegment dEdge = libedge.delaunayLine(); final LineSegment vEdge = libedge.voronoiEdge(); Corner c0 = makeCorner(pointCornerMap, v.getPlotBounds(), vEdge.getStart()); Corner c1 = makeCorner(pointCornerMap, v.getPlotBounds(), vEdge.getEnd()); Region r0 = regionMap.get(dEdge.getStart()); Region r1 = regionMap.get(dEdge.getEnd()); final Edge edge = new Edge(c0, c1, r0, r1); edges.add(edge); // Centers point to edges. Corners point to edges. r0.addBorder(edge); r1.addBorder(edge); c0.addEdge(edge); c1.addEdge(edge); // Centers point to centers. r0.addNeigbor(r1); r1.addNeigbor(r0); // Corners point to corners c0.addAdjacent(c1); c1.addAdjacent(c0); } } /** * ensures that each corner is represented by only one corner object */ private Corner makeCorner(Map<BaseVector2f, Corner> pointCornerMap, Rect2f srcRc, BaseVector2f orgPt) { Corner exist = pointCornerMap.get(orgPt); if (exist != null) { return exist; } Vector2f p = transform(srcRc, realBounds, orgPt); Corner c = new Corner(new ImmutableVector2f(p)); corners.add(c); pointCornerMap.put(orgPt, c); float diff = 0.01f; boolean onLeft = closeEnough(p.getX(), realBounds.minX(), diff); boolean onTop = closeEnough(p.getY(), realBounds.minY(), diff); boolean onRight = closeEnough(p.getX(), realBounds.maxX(), diff); boolean onBottom = closeEnough(p.getY(), realBounds.maxY(), diff); if (onLeft || onTop || onRight || onBottom) { c.setBorder(true); } return c; } /** * @return */ @Override public List<Region> getRegions() { return Collections.unmodifiableList(regions); } /** * @return */ @Override public List<Edge> getEdges() { return Collections.unmodifiableList(edges); } /** * @return the corners */ @Override public List<Corner> getCorners() { return Collections.unmodifiableList(corners); } /** * @return the bounds */ @Override public Rect2i getBounds() { return intBounds; } /** * Transforms the given point from the source rectangle into the destination rectangle. * @param srcRc The source rectangle * @param dstRc The destination rectangle * @param pt The point to transform * @return The new, transformed point */ private static Vector2f transform(Rect2f srcRc, Rect2f dstRc, BaseVector2f pt) { // TODO: move this to a better place float x = (pt.getX() - srcRc.minX()) / srcRc.width(); float y = (pt.getY() - srcRc.minY()) / srcRc.height(); x = dstRc.minX() + x * dstRc.width(); y = dstRc.minY() + y * dstRc.height(); return new Vector2f(x, y); } private static boolean closeEnough(float d1, float d2, float diff) { return Math.abs(d1 - d2) <= diff; } }