Ext*_*kun 88 language-agnostic algorithm graphics user-interface
这个问题实际上涉及翻滚,我将在下面概括如下:
我有一个2D视图,我在屏幕上的一个区域内有许多矩形.我如何展开这些盒子,使它们不会相互重叠,但只能用最小的移动来调整它们?
矩形的位置是动态的,取决于用户的输入,因此它们的位置可以是任何位置.
附 图像显示问题和所需的解决方案
实际上,现实问题涉及翻车.
答案中的问题
矩形的大小不固定,并且取决于翻转中文本的长度
关于屏幕尺寸,现在我认为最好假设屏幕的大小足以容纳矩形.如果有太多的矩形并且算法没有解决方案,那么我只需要调整内容.
"最小化"的要求更多的是为了美学而非绝对的工程要求.人们可以通过在两个矩形之间添加一个很大的距离来分隔两个矩形,但它作为GUI的一部分看起来不太好.我们的想法是使翻转/矩形尽可能接近其源(我将用黑线连接到源).所以要么'只为x移动一个'或'移动两个x'都可以.
Dr.*_*ius 93
我在这方面工作了一点,因为我也需要类似的东西,但我推迟了算法的开发.你帮助我得到了一些冲动:D
我还需要源代码,所以在这里.我在Mathematica中进行了研究,但由于我没有大量使用功能特性,我想它很容易翻译成任何程序语言.
首先,我决定开发圆的算法,因为交点更容易计算.它只取决于中心和半径.
我能够使用Mathematica方程求解器,并且表现很好.
只是看看:
很容易.我刚刚加载了解决方案,出现以下问题:
For each circle
Solve[
Find new coördinates for the circle
Minimizing the distance to the geometric center of the image
Taking in account that
Distance between centers > R1+R2 *for all other circles
Move the circle in a line between its center and the
geometric center of the drawing
]
Run Code Online (Sandbox Code Playgroud)
尽管如此,Mathematica完成了所有的工作.
我说"哈!这很容易,现在让我们去看矩形!".但是我错了 ...
矩形的主要问题是查询交叉点是一个令人讨厌的功能.就像是:
因此,当我试图用Matlematica为这个等式提供很多这些条件时,它的表现非常糟糕,以至于我决定做一些程序性的事情.
我的算法最终结果如下:
Expand each rectangle size by a few points to get gaps in final configuration
While There are intersections
sort list of rectangles by number of intersections
push most intersected rectangle on stack, and remove it from list
// Now all remaining rectangles doesn't intersect each other
While stack not empty
pop rectangle from stack and re-insert it into list
find the geometric center G of the chart (each time!)
find the movement vector M (from G to rectangle center)
move the rectangle incrementally in the direction of M (both sides)
until no intersections
Shrink the rectangles to its original size
Run Code Online (Sandbox Code Playgroud)
您可能会注意到"最小运动"条件并未完全满足(仅在一个方向上).但我发现在任何方向移动矩形以满足它,有时最终会为用户改变一个令人困惑的地图.
在我设计用户界面时,我选择将矩形移动一点,但是以更可预测的方式.您可以更改算法以检查所有角度及其当前位置周围的所有半径,直到找到空位,尽管它要求更高.
无论如何,这些是结果的例子(之前/之后):
编辑>更多的例子在这里
正如您所看到的,"最小运动"不满足,但结果足够好.
我会在这里发布代码,因为我的SVN存储库遇到了一些问题.当问题解决后我会删除它.
您也可以使用R-Trees来查找矩形交叉点,但处理少量矩形似乎有点过分.我还没有实现算法.也许其他人可以指向您选择的平台上的现有实现.
警告!代码是第一种方法..质量不高,肯定有一些错误.
这是Mathematica.
(*Define some functions first*)
Clear["Global`*"];
rn[x_] := RandomReal[{0, x}];
rnR[x_] := RandomReal[{1, x}];
rndCol[] := RGBColor[rn[1], rn[1], rn[1]];
minX[l_, i_] := l[[i]][[1]][[1]]; (*just for easy reading*)
maxX[l_, i_] := l[[i]][[1]][[2]];
minY[l_, i_] := l[[i]][[2]][[1]];
maxY[l_, i_] := l[[i]][[2]][[2]];
color[l_, i_]:= l[[i]][[3]];
intersectsQ[l_, i_, j_] := (* l list, (i,j) indexes,
list={{x1,x2},{y1,y2}} *)
(*A rect does intesect with itself*)
If[Max[minX[l, i], minX[l, j]] < Min[maxX[l, i], maxX[l, j]] &&
Max[minY[l, i], minY[l, j]] < Min[maxY[l, i], maxY[l, j]],
True,False];
(* Number of Intersects for a Rectangle *)
(* With i as index*)
countIntersects[l_, i_] :=
Count[Table[intersectsQ[l, i, j], {j, 1, Length[l]}], True]-1;
(*And With r as rectangle *)
countIntersectsR[l_, r_] := (
Return[Count[Table[intersectsQ[Append[l, r], Length[l] + 1, j],
{j, 1, Length[l] + 1}], True] - 2];)
(* Get the maximum intersections for all rectangles*)
findMaxIntesections[l_] := Max[Table[countIntersects[l, i],
{i, 1, Length[l]}]];
(* Get the rectangle center *)
rectCenter[l_, i_] := {1/2 (maxX[l, i] + minX[l, i] ),
1/2 (maxY[l, i] + minY[l, i] )};
(* Get the Geom center of the whole figure (list), to move aesthetically*)
geometryCenter[l_] := (* returs {x,y} *)
Mean[Table[rectCenter[l, i], {i, Length[l]}]];
(* Increment or decr. size of all rects by a bit (put/remove borders)*)
changeSize[l_, incr_] :=
Table[{{minX[l, i] - incr, maxX[l, i] + incr},
{minY[l, i] - incr, maxY[l, i] + incr},
color[l, i]},
{i, Length[l]}];
sortListByIntersections[l_] := (* Order list by most intersecting Rects*)
Module[{a, b},
a = MapIndexed[{countIntersectsR[l, #1], #2} &, l];
b = SortBy[a, -#[[1]] &];
Return[Table[l[[b[[i]][[2]][[1]]]], {i, Length[b]}]];
];
(* Utility Functions*)
deb[x_] := (Print["--------"]; Print[x]; Print["---------"];)(* for debug *)
tableForPlot[l_] := (*for plotting*)
Table[{color[l, i], Rectangle[{minX[l, i], minY[l, i]},
{maxX[l, i], maxY[l, i]}]}, {i, Length[l]}];
genList[nonOverlap_, Overlap_] := (* Generate initial lists of rects*)
Module[{alist, blist, a, b},
(alist = (* Generate non overlapping - Tabuloid *)
Table[{{Mod[i, 3], Mod[i, 3] + .8},
{Mod[i, 4], Mod[i, 4] + .8},
rndCol[]}, {i, nonOverlap}];
blist = (* Random overlapping *)
Table[{{a = rnR[3], a + rnR[2]}, {b = rnR[3], b + rnR[2]},
rndCol[]}, {Overlap}];
Return[Join[alist, blist] (* Join both *)];)
];
Run Code Online (Sandbox Code Playgroud)
主要
clist = genList[6, 4]; (* Generate a mix fixed & random set *)
incr = 0.05; (* may be some heuristics needed to determine best increment*)
clist = changeSize[clist,incr]; (* expand rects so that borders does not
touch each other*)
(* Now remove all intercepting rectangles until no more intersections *)
workList = {}; (* the stack*)
While[findMaxIntesections[clist] > 0,
(*Iterate until no intersections *)
clist = sortListByIntersections[clist];
(*Put the most intersected first*)
PrependTo[workList, First[clist]];
(* Push workList with intersected *)
clist = Delete[clist, 1]; (* and Drop it from clist *)
];
(* There are no intersections now, lets pop the stack*)
While [workList != {},
PrependTo[clist, First[workList]];
(*Push first element in front of clist*)
workList = Delete[workList, 1];
(* and Drop it from worklist *)
toMoveIndex = 1;
(*Will move the most intersected Rect*)
g = geometryCenter[clist];
(*so the geom. perception is preserved*)
vectorToMove = rectCenter[clist, toMoveIndex] - g;
If [Norm[vectorToMove] < 0.01, vectorToMove = {1,1}]; (*just in case*)
vectorToMove = vectorToMove/Norm[vectorToMove];
(*to manage step size wisely*)
(*Now iterate finding minimum move first one way, then the other*)
i = 1; (*movement quantity*)
While[countIntersects[clist, toMoveIndex] != 0,
(*If the Rect still intersects*)
(*move it alternating ways (-1)^n *)
clist[[toMoveIndex]][[1]] += (-1)^i i incr vectorToMove[[1]];(*X coords*)
clist[[toMoveIndex]][[2]] += (-1)^i i incr vectorToMove[[2]];(*Y coords*)
i++;
];
];
clist = changeSize[clist, -incr](* restore original sizes*);
Run Code Online (Sandbox Code Playgroud)
HTH!
我实现了算法的变化,允许在所有方向上搜索,但优先考虑由几何对称性强加的轴.
以更多周期为代价,这导致更紧凑的最终配置,如下所示:
这里有更多样品.
主循环的伪代码更改为:
Expand each rectangle size by a few points to get gaps in final configuration
While There are intersections
sort list of rectangles by number of intersections
push most intersected rectangle on stack, and remove it from list
// Now all remaining rectangles doesn't intersect each other
While stack not empty
find the geometric center G of the chart (each time!)
find the PREFERRED movement vector M (from G to rectangle center)
pop rectangle from stack
With the rectangle
While there are intersections (list+rectangle)
For increasing movement modulus
For increasing angle (0, Pi/4)
rotate vector M expanding the angle alongside M
(* angle, -angle, Pi + angle, Pi-angle*)
re-position the rectangle accorging to M
Re-insert modified vector into list
Shrink the rectangles to its original size
Run Code Online (Sandbox Code Playgroud)
我不是为了简洁而包含源代码,但如果您认为可以使用它,只需要它.我认为,如果你这样做,最好切换到R树(这里需要很多间隔测试)
cap*_*232 11
这是一个猜测.
找到矩形边界框的中心C.
对于与另一个重叠的每个矩形R.
这会逐渐使矩形远离彼此以及所有矩形的中心.这将终止,因为步骤4中的v的组件最终会将它们全部展开.
我认为这个解决方案非常类似于cape1232给出的解决方案,但它已经实现了,所以值得一试:)
请参阅此reddit讨论:http://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/并查看说明和实施.没有可用的源代码,所以这是我在AS3中解决这个问题的方法(工作方式完全相同,但保持矩形对齐网格的分辨率):
public class RoomSeparator extends AbstractAction {
public function RoomSeparator(name:String = "Room Separator") {
super(name);
}
override public function get finished():Boolean { return _step == 1; }
override public function step():void {
const repelDecayCoefficient:Number = 1.0;
_step = 1;
var count:int = _activeRoomContainer.children.length;
for(var i:int = 0; i < count; i++) {
var room:Room = _activeRoomContainer.children[i];
var center:Vector3D = new Vector3D(room.x + room.width / 2, room.y + room.height / 2);
var velocity:Vector3D = new Vector3D();
for(var j:int = 0; j < count; j++) {
if(i == j)
continue;
var otherRoom:Room = _activeRoomContainer.children[j];
var intersection:Rectangle = GeomUtil.rectangleIntersection(room.createRectangle(), otherRoom.createRectangle());
if(intersection == null || intersection.width == 0 || intersection.height == 0)
continue;
var otherCenter:Vector3D = new Vector3D(otherRoom.x + otherRoom.width / 2, otherRoom.y + otherRoom.height / 2);
var diff:Vector3D = center.subtract(otherCenter);
if(diff.length > 0) {
var scale:Number = repelDecayCoefficient / diff.lengthSquared;
diff.normalize();
diff.scaleBy(scale);
velocity = velocity.add(diff);
}
}
if(velocity.length > 0) {
_step = 0;
velocity.normalize();
room.x += Math.abs(velocity.x) < 0.5 ? 0 : velocity.x > 0 ? _resolution : -_resolution;
room.y += Math.abs(velocity.y) < 0.5 ? 0 : velocity.y > 0 ? _resolution : -_resolution;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我真的很喜欢b005t3r的实现!它可以在我的测试用例中工作,但是我的代表太低,无法对2个建议的修复程序发表评论。
您不应该以单个分辨率的增量来翻译房间,而应该以您辛苦计算的速度来翻译!这使得分隔更加有机,因为与不那么深的相交的房间相比,深层相交的房间在每次迭代中分隔的次数更多。
您不应该假设速度低于0.5意味着房间是分开的,因为在您从未分开的情况下,您可能会卡住。想象一下,两个房间相交,但是无法校正自身,因为无论何时有人尝试校正穿透,它们都会将所需的速度计算为<0.5,因此它们会不断迭代。
这是Java解决方案(:干杯!
do {
_separated = true;
for (Room room : getRooms()) {
// reset for iteration
Vector2 velocity = new Vector2();
Vector2 center = room.createCenter();
for (Room other_room : getRooms()) {
if (room == other_room)
continue;
if (!room.createRectangle().overlaps(other_room.createRectangle()))
continue;
Vector2 other_center = other_room.createCenter();
Vector2 diff = new Vector2(center.x - other_center.x, center.y - other_center.y);
float diff_len2 = diff.len2();
if (diff_len2 > 0f) {
final float repelDecayCoefficient = 1.0f;
float scale = repelDecayCoefficient / diff_len2;
diff.nor();
diff.scl(scale);
velocity.add(diff);
}
}
if (velocity.len2() > 0f) {
_separated = false;
velocity.nor().scl(delta * 20f);
room.getPosition().add(velocity);
}
}
} while (!_separated);
Run Code Online (Sandbox Code Playgroud)
这是一个使用 Java 编写的算法,用于处理未旋转的簇Rectangle
。它允许您指定布局的所需纵横比,并使用参数化Rectangle
作为锚点来定位集群,所有翻译都以该锚点为导向。您还可以指定任意数量的填充,您希望Rectangle
通过该填充来扩展 s。
public final class BoxxyDistribution {
/* Static Definitions. */
private static final int INDEX_BOUNDS_MINIMUM_X = 0;
private static final int INDEX_BOUNDS_MINIMUM_Y = 1;
private static final int INDEX_BOUNDS_MAXIMUM_X = 2;
private static final int INDEX_BOUNDS_MAXIMUM_Y = 3;
private static final double onCalculateMagnitude(final double pDeltaX, final double pDeltaY) {
return Math.sqrt((pDeltaX * pDeltaX) + (pDeltaY + pDeltaY));
}
/* Updates the members of EnclosingBounds to ensure the dimensions of T can be completely encapsulated. */
private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double pMinimumX, final double pMinimumY, final double pMaximumX, final double pMaximumY) {
pEnclosingBounds[0] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pMinimumX);
pEnclosingBounds[1] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pMinimumY);
pEnclosingBounds[2] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pMaximumX);
pEnclosingBounds[3] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], pMaximumY);
}
private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double[] pBounds) {
BoxxyDistribution.onEncapsulateBounds(pEnclosingBounds, pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y]);
}
private static final double onCalculateMidpoint(final double pMaximum, final double pMinimum) {
return ((pMaximum - pMinimum) * 0.5) + pMinimum;
}
/* Re-arranges a List of Rectangles into something aesthetically pleasing. */
public static final void onBoxxyDistribution(final List<Rectangle> pRectangles, final Rectangle pAnchor, final double pPadding, final double pAspectRatio, final float pRowFillPercentage) {
/* Create a safe clone of the Rectangles that we can modify as we please. */
final List<Rectangle> lRectangles = new ArrayList<Rectangle>(pRectangles);
/* Allocate a List to track the bounds of each Row. */
final List<double[]> lRowBounds = new ArrayList<double[]>(); // (MinX, MinY, MaxX, MaxY)
/* Ensure Rectangles does not contain the Anchor. */
lRectangles.remove(pAnchor);
/* Order the Rectangles via their proximity to the Anchor. */
Collections.sort(pRectangles, new Comparator<Rectangle>(){ @Override public final int compare(final Rectangle pT0, final Rectangle pT1) {
/* Calculate the Distance for pT0. */
final double lDistance0 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT0.getCenterX(), pAnchor.getCenterY() - pT0.getCenterY());
final double lDistance1 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT1.getCenterX(), pAnchor.getCenterY() - pT1.getCenterY());
/* Compare the magnitude in distance between the anchor and the Rectangles. */
return Double.compare(lDistance0, lDistance1);
} });
/* Initialize the RowBounds using the Anchor. */ /** TODO: Probably better to call getBounds() here. **/
lRowBounds.add(new double[]{ pAnchor.getX(), pAnchor.getY(), pAnchor.getX() + pAnchor.getWidth(), pAnchor.getY() + pAnchor.getHeight() });
/* Allocate a variable for tracking the TotalBounds of all rows. */
final double[] lTotalBounds = new double[]{ Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
/* Now we iterate the Rectangles to place them optimally about the Anchor. */
for(int i = 0; i < lRectangles.size(); i++) {
/* Fetch the Rectangle. */
final Rectangle lRectangle = lRectangles.get(i);
/* Iterate through each Row. */
for(final double[] lBounds : lRowBounds) {
/* Update the TotalBounds. */
BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lBounds);
}
/* Allocate a variable to state whether the Rectangle has been allocated a suitable RowBounds. */
boolean lIsBounded = false;
/* Calculate the AspectRatio. */
final double lAspectRatio = (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]) / (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
/* We will now iterate through each of the available Rows to determine if a Rectangle can be stored. */
for(int j = 0; j < lRowBounds.size() && !lIsBounded; j++) {
/* Fetch the Bounds. */
final double[] lBounds = lRowBounds.get(j);
/* Calculate the width and height of the Bounds. */
final double lWidth = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X];
final double lHeight = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y];
/* Determine whether the Rectangle is suitable to fit in the RowBounds. */
if(lRectangle.getHeight() <= lHeight && !(lAspectRatio > pAspectRatio && lWidth > pRowFillPercentage * (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]))) {
/* Register that the Rectangle IsBounded. */
lIsBounded = true;
/* Update the Rectangle's X and Y Co-ordinates. */
lRectangle.setFrame((lRectangle.getX() > BoxxyDistribution.onCalculateMidpoint(lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X])) ? lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] + pPadding : lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X] - (pPadding + lRectangle.getWidth()), lBounds[1], lRectangle.getWidth(), lRectangle.getHeight());
/* Update the Bounds. (Do not modify the vertical metrics.) */
BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lRectangle.getX(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getX() + lRectangle.getWidth(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] + lHeight);
}
}
/* Determine if the Rectangle has not been allocated a Row. */
if(!lIsBounded) {
/* Calculate the MidPoint of the TotalBounds. */
final double lCentreY = BoxxyDistribution.onCalculateMidpoint(lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
/* Determine whether to place the bounds above or below? */
final double lYPosition = lRectangle.getY() < lCentreY ? lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] - (pPadding + lRectangle.getHeight()) : (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] + pPadding);
/* Create a new RowBounds. */
final double[] lBounds = new double[]{ pAnchor.getX(), lYPosition, pAnchor.getX() + lRectangle.getWidth(), lYPosition + lRectangle.getHeight() };
/* Allocate a new row, roughly positioned about the anchor. */
lRowBounds.add(lBounds);
/* Position the Rectangle. */
lRectangle.setFrame(lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getWidth(), lRectangle.getHeight());
}
}
}
Run Code Online (Sandbox Code Playgroud)
}
AspectRatio
这是使用 an of 1.2
、 a FillPercentage
of0.8
和 a Padding
of 的示例10.0
。
这是一种确定性方法,允许在锚点周围出现间距,同时保持锚点本身的位置不变。这允许布局出现在用户兴趣点所在的任何地方。选择位置的逻辑非常简单,但我认为根据元素的初始位置对元素进行排序然后迭代它们的周围架构是实现相对可预测的分布的有用方法。另外,我们不依赖于迭代交叉测试或类似的东西,只是建立一些边界框来为我们提供在哪里对齐事物的广泛指示;之后,应用填充就变得很自然了。