/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui.zone.vbl; import java.awt.geom.Area; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.t3.GeometryUtil; import com.t3.GeometryUtil.PointNode; public class AreaMeta { Area area; Point2D centerPoint; List<AreaFace> faceList = new ArrayList<AreaFace>(); // Only used during construction boolean isHole; PointNode pointNodeList; GeneralPath path; PointNode lastPointNode; public AreaMeta() { } public Point2D getCenterPoint() { if (centerPoint == null) { centerPoint = new Point2D.Double(area.getBounds().x + area.getBounds().width / 2, area.getBounds().y + area.getBounds().height / 2); } return centerPoint; } public Set<VisibleAreaSegment> getVisibleAreas(Point2D origin) { Set<VisibleAreaSegment> segSet = new HashSet<VisibleAreaSegment>(); VisibleAreaSegment segment = null; for (AreaFace face : faceList) { double originAngle = GeometryUtil.getAngle(origin, face.getMidPoint()); double delta = GeometryUtil.getAngleDelta(originAngle, face.getFacing()); if (Math.abs(delta) > 90) { if (segment != null) { segSet.add(segment); segment = null; } continue; } // Continuous face if (segment == null) { segment = new VisibleAreaSegment(origin); } segment.addAtEnd(face); } if (segment != null) { // We finished the list while visible, see if we can combine with the first segment // TODO: attempt to combine with the first segment somehow segSet.add(segment); } // System.out.println("Segs: " + segSet.size()); return segSet; } public Area getArea() { return new Area(area); } public boolean isHole() { return isHole; } public void addPoint(float x, float y) { // Cut out redundant points // TODO: This works ... in concept, but in practice it can create holes that pop outside of their parent bounds // for really thin diagonal lines. At some point this could be moved to a post processing step, after the // islands have been placed into their oceans. But that's an optimization for another day // if (lastPointNode != null && GeometryUtil.getDistance(lastPointNode.point, new Point2D.Float(x, y)) < 1.5) { // skippedPoints++; // return; // } PointNode pointNode = new PointNode(new Point2D.Double(x, y)); // Don't add if we haven't moved if (lastPointNode != null) { if (lastPointNode.point.equals(pointNode.point)) { return; } } if (path == null) { path = new GeneralPath(); path.moveTo(x, y); pointNodeList = pointNode; } else { path.lineTo(x, y); lastPointNode.next = pointNode; pointNode.previous = lastPointNode; } lastPointNode = pointNode; } public void close() { area = new Area(path); // Close the circle lastPointNode.next = pointNodeList; pointNodeList.previous = lastPointNode; lastPointNode = null; // For some odd reason, sometimes the first and last point are the same, which causes // bugs in the way areas are calculated if (pointNodeList.point.equals(pointNodeList.previous.point)) { // Pull out the dupe node PointNode trueLastPoint = pointNodeList.previous.previous; trueLastPoint.next = pointNodeList; pointNodeList.previous = trueLastPoint; } computeIsHole(); computeFaces(); // Don't need point list anymore pointNodeList = null; path = null; // System.out.println("AreaMeta.skippedPoints: " + skippedPoints + " h:" + isHole + " f:" + faceList.size()); } private void computeIsHole() { double angle = 0; PointNode currNode = pointNodeList.next; while (currNode != pointNodeList) { double currAngle = GeometryUtil.getAngleDelta(GeometryUtil.getAngle(currNode.previous.point, currNode.point), GeometryUtil.getAngle(currNode.point, currNode.next.point)); angle += currAngle; currNode = currNode.next; } isHole = angle < 0; } private void computeFaces() { PointNode node = pointNodeList; do { faceList.add(new AreaFace(node.point, node.next.point)); node = node.next; } while (!node.point.equals(pointNodeList.point)); } }