我有一个二维点列表,它是一个闭合的2D凹多边形。
我想生成第二个多边形,该多边形完全位于第一个多边形的内部,并且第一个多边形的每个顶点/边缘与第二个多边形的每个顶点/边缘具有恒定的距离。
基本上,第一个多边形是“外壁”,第二个是“内壁”,两壁之间的距离是恒定的。
该怎么做?
答案 0 :(得分:3)
对于您不关心自相交的情况,其构造非常简单:
对于多边形的每个顶点:
下面是用Java / Swing实现的MCVE。实际的计算是在computeOffsetPolygonPoints
中进行的,应该很容易将其转换为其他语言和API。
对于还必须处理自相交的情况,情况可能会变得更棘手。然后,有必要定义预期的结果,尤其是对于多边形本身自相交的情况...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class InnerPolygonShape
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
InnerPolygonShapePanel innerPolygonShapePanel =
new InnerPolygonShapePanel();
JSlider offsetSlider = new JSlider(0, 100, 40);
offsetSlider.addChangeListener(e ->
{
double alpha = offsetSlider.getValue() / 100.0;
double offset = -50.0 + alpha * 100.0;
innerPolygonShapePanel.setOffset(offset);
});
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(innerPolygonShapePanel, BorderLayout.CENTER);
f.getContentPane().add(offsetSlider, BorderLayout.SOUTH);
f.setSize(800,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class InnerPolygonShapePanel extends JPanel
implements MouseListener, MouseMotionListener
{
private final List<Point2D> points;
private Point2D draggedPoint;
private double offset = -10.0;
public InnerPolygonShapePanel()
{
this.points = new ArrayList<Point2D>();
points.add(new Point2D.Double(132,532));
points.add(new Point2D.Double(375,458));
points.add(new Point2D.Double(395,267));
points.add(new Point2D.Double(595,667));
addMouseListener(this);
addMouseMotionListener(this);
}
public void setOffset(double offset)
{
this.offset = offset;
repaint();
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
paint(g, points);
List<Point2D> offsetPolygonPoints =
computeOffsetPolygonPoints(points, offset);
g.setColor(Color.BLUE);
paint(g, offsetPolygonPoints);
}
private static void paint(Graphics2D g, List<Point2D> points)
{
for (int i = 0; i < points.size(); i++)
{
int i0 = i;
int i1 = (i + 1) % points.size();
Point2D p0 = points.get(i0);
Point2D p1 = points.get(i1);
g.draw(new Line2D.Double(p0, p1));
}
g.setColor(Color.RED);
for (Point2D p : points)
{
double r = 5;
g.draw(new Ellipse2D.Double(p.getX()-r, p.getY()-r, r+r, r+r));
}
}
private static List<Point2D> computeOffsetPolygonPoints(
List<Point2D> points, double offset)
{
List<Point2D> result = new ArrayList<Point2D>();
Point2D absoluteLocation = new Point2D.Double();
for (int i = 0; i < points.size(); i++)
{
// Consider three consecutive points (previous, current, next)
int ip = (i - 1 + points.size()) % points.size();
int ic = i;
int in = (i + 1) % points.size();
Point2D pp = points.get(ip);
Point2D pc = points.get(ic);
Point2D pn = points.get(in);
// Compute the line segments between the previous and the current
// point, and the current and the next point, and compute their
// normal
Point2D line0 = difference(pc, pp);
Point2D direction0 = normalize(line0);
Point2D normal0 = rotateCw(direction0);
Point2D line1 = difference(pn, pc);
Point2D direction1 = normalize(line1);
Point2D normal1 = rotateCw(direction1);
// Shift both line segments along the normal
Point2D segment0p0 = add(pp, offset, normal0);
Point2D segment0p1 = add(pc, offset, normal0);
Point2D segment1p0 = add(pc, offset, normal1);
Point2D segment1p1 = add(pn, offset, normal1);
// Compute the intersection between the shifted line segments
intersect(
segment0p0.getX(), segment0p0.getY(),
segment0p1.getX(), segment0p1.getY(),
segment1p0.getX(), segment1p0.getY(),
segment1p1.getX(), segment1p1.getY(),
null, absoluteLocation);
result.add(new Point2D.Double(
absoluteLocation.getX(), absoluteLocation.getY()));
}
return result;
}
@Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getX(), e.getY());
repaint();
}
}
@Override
public void mousePressed(MouseEvent e)
{
final double thresholdSquared = 10 * 10;
Point2D p = e.getPoint();
Point2D closestPoint = null;
double minDistanceSquared = Double.MAX_VALUE;
for (Point2D point : points)
{
double dd = point.distanceSq(p);
if (dd < thresholdSquared && dd < minDistanceSquared)
{
minDistanceSquared = dd;
closestPoint = point;
}
}
draggedPoint = closestPoint;
}
@Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
@Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
@Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
@Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
@Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
private static Point2D difference(Point2D p0, Point2D p1)
{
double dx = p0.getX() - p1.getX();
double dy = p0.getY() - p1.getY();
return new Point2D.Double(dx, dy);
}
private static Point2D add(Point2D p0, double factor, Point2D p1)
{
double x0 = p0.getX();
double y0 = p0.getY();
double x1 = p1.getX();
double y1 = p1.getY();
return new Point2D.Double(x0 + factor * x1, y0 + factor * y1);
}
private static Point2D rotateCw(Point2D p)
{
return new Point2D.Double(p.getY(), -p.getX());
}
private static Point2D normalize(Point2D p)
{
double x = p.getX();
double y = p.getY();
double length = Math.hypot(x, y);
return new Point2D.Double(x / length, y / length);
}
// From https://github.com/javagl/Geom/blob/master/src/main/java/
// de/javagl/geom/Intersections.java
private static final double DOUBLE_EPSILON = 1e-6;
/**
* Computes the intersection of the specified lines.
*
* Ported from
* http://www.geometrictools.com/LibMathematics/Intersection/
* Wm5IntrSegment2Segment2.cpp
*
* @param s0x0 x-coordinate of point 0 of line segment 0
* @param s0y0 y-coordinate of point 0 of line segment 0
* @param s0x1 x-coordinate of point 1 of line segment 0
* @param s0y1 y-coordinate of point 1 of line segment 0
* @param s1x0 x-coordinate of point 0 of line segment 1
* @param s1y0 y-coordinate of point 0 of line segment 1
* @param s1x1 x-coordinate of point 1 of line segment 1
* @param s1y1 y-coordinate of point 1 of line segment 1
* @param relativeLocation Optional location that stores the
* relative location of the intersection point on
* the given line segments
* @param absoluteLocation Optional location that stores the
* absolute location of the intersection point
* @return Whether the lines intersect
*/
public static boolean intersect(
double s0x0, double s0y0,
double s0x1, double s0y1,
double s1x0, double s1y0,
double s1x1, double s1y1,
Point2D relativeLocation,
Point2D absoluteLocation)
{
double dx0 = s0x1 - s0x0;
double dy0 = s0y1 - s0y0;
double dx1 = s1x1 - s1x0;
double dy1 = s1y1 - s1y0;
double invLen0 = 1.0 / Math.sqrt(dx0*dx0+dy0*dy0);
double invLen1 = 1.0 / Math.sqrt(dx1*dx1+dy1*dy1);
double dir0x = dx0 * invLen0;
double dir0y = dy0 * invLen0;
double dir1x = dx1 * invLen1;
double dir1y = dy1 * invLen1;
double dot = dotPerp(dir0x, dir0y, dir1x, dir1y);
if (Math.abs(dot) > DOUBLE_EPSILON)
{
if (relativeLocation != null || absoluteLocation != null)
{
double c0x = s0x0 + dx0 * 0.5;
double c0y = s0y0 + dy0 * 0.5;
double c1x = s1x0 + dx1 * 0.5;
double c1y = s1y0 + dy1 * 0.5;
double cdx = c1x - c0x;
double cdy = c1y - c0y;
double dot0 = dotPerp(cdx, cdy, dir0x, dir0y);
double dot1 = dotPerp(cdx, cdy, dir1x, dir1y);
double invDot = 1.0/dot;
double s0 = dot1*invDot;
double s1 = dot0*invDot;
if (relativeLocation != null)
{
double n0 = (s0 * invLen0) + 0.5;
double n1 = (s1 * invLen1) + 0.5;
relativeLocation.setLocation(n0, n1);
}
if (absoluteLocation != null)
{
double x = c0x + s0 * dir0x;
double y = c0y + s0 * dir0y;
absoluteLocation.setLocation(x, y);
}
}
return true;
}
return false;
}
/**
* Returns the perpendicular dot product, i.e. the length
* of the vector (x0,y0,0)x(x1,y1,0).
*
* @param x0 Coordinate x0
* @param y0 Coordinate y0
* @param x1 Coordinate x1
* @param y1 Coordinate y1
* @return The length of the cross product vector
*/
private static double dotPerp(double x0, double y0, double x1, double y1)
{
return x0*y1 - y0*x1;
}
}