我想用或多或少均匀且随机分布的圆圈填充图片的一部分,这些圆圈不应重叠。如果圆圈只有部分位于形状内,那就没问题了。密度应该像您在此页面上看到的那样http://zoada.com/html5/brute.html值为 80-100。(但我不需要那么多圆圈,30 个就足够了,我可以重复这个图案)。
我不能使用网格并稍微“摆动”圆圈:圆圈太大,而且看起来根本不随机。
目前,我正在考虑一种随机尝试:获取某个随机点,测量其与现有圆的距离,绘制或丢弃该点。尝试 100 次,并希望您获得正确数量的圆(并且不会花费太长时间)。但也许有人有更好的主意。
该示例简单地手动绘制圆圈:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{backgrounds}
\begin{document}
\begin{tikzpicture}[framed,gridded,radius=0.5cm]
\draw[red](0,0) rectangle (5,5);
\draw (1,1) circle;
\draw (2.3,1.1) circle ;
\draw (4.5,0.8) circle ;
\draw (5.1,1.8) circle ;
\draw (0.4,3.3) circle ;
\draw (2.1,2.8) circle ;
\draw (3.8,3.5) circle ;
\draw (4.8,4.2) circle ;
\draw (0.8,4.9) circle ;
\draw (2.1,4.1) circle ;
\draw (3.8,2.0) circle ;
\draw (3.5,0.6) circle ;
\draw (3.0,5.0) circle ;
\draw (4.1,5.1) circle ;
\draw (0.9,2.1) circle ;
\end{tikzpicture}
\end{document}
答案1
这是一个实现您所描述的宏。它需要四个参数:要填充的矩形的宽度和高度、圆的半径以及尝试次数。
每次尝试都会生成一个随机位置。计算此位置与所有现有圆之间的距离,如果发生碰撞,则不绘制圆。如果圆不发生碰撞,则绘制圆,并将其坐标添加到现有圆的列表中。
这种方法效率不高,但是确实有效:
\fillrandomly{3}{2}{0.2}{50}
\pgfmathsetseed{2}
\fillrandomly{5}{5}{0.5}{300}
\documentclass{article}
\usepackage{tikz}
\begin{document}
\def\xlist{4}
\def\ylist{4}
\newcommand{\fillrandomly}[4]{
\pgfmathsetmacro\diameter{#3*2}
\draw (0,0) rectangle (#1,#2);
\foreach \i in {1,...,#4}{
\pgfmathsetmacro\x{rnd*#1}
\pgfmathsetmacro\y{rnd*#2}
\xdef\collision{0}
\foreach \element [count=\i] in \xlist{
\pgfmathtruncatemacro\j{\i-1}
\pgfmathsetmacro\checkdistance{ sqrt( ({\xlist}[\j]-(\x))^2 + ({\ylist}[\j]-(\y))^2 ) }
\ifdim\checkdistance pt<\diameter pt
\xdef\collision{1}
\breakforeach
\fi
}
\ifnum\collision=0
\xdef\xlist{\xlist,\x}
\xdef\ylist{\ylist,\y}
\draw [red, thick] (\x,\y) circle [radius=#3];
\fi
}
}
\begin{tikzpicture}
\pgfmathsetseed{2}
\fillrandomly{5}{5}{0.5}{300}
\end{tikzpicture}
\end{document}
答案2
这是不同的变体。其想法是生成一堆圆,然后剔除相交的圆。对于剔除阶段,我使用了半成品实现清扫和修剪就大型碰撞检测而言,这会加快速度n
。
- 随机生成点
- 对他们进行排序
x-coordinates
- 保存
x-coordinates
重叠的对以进行进一步测试,丢弃其余的 - 对于已保存的对,确定是否
y-coordinates
重叠 - 如果是这样,那么它们可能会发生碰撞,因此对它们进行实际碰撞测试
- 如果它们发生碰撞,则丢弃其中之一
宏是\circles{n}{rad}{width}{height}
并\circles{500}{.1}{10}{5}
给出:
\documentclass{article}
\usepackage{luacode}
\usepackage{tikz}
\usetikzlibrary{backgrounds}
\begin{luacode*}
local rand = math.random
local abs = math.abs
local pts = {}
local tstpairs = {}
-- generate the points
local function genpts(n,x,y)
for i = 1,n do
pts[i]={}
pts[i][1] = rand()*x
pts[i][2] = rand()*y
end
end
-- for sorted pairs, check if x-coords overlap
-- if so, store the pair in table tstpairs
local function getpairs(t,r)
for i = 1,#t do
tstpairs[i] = {}
for j = 1,#t-i do
if t[i+j][1]-t[i][1]<2*r then
tstpairs[i][#tstpairs[i]+1]=i+j
else
break
end
end
end
end
-- this is the actual collision test
-- it's less expensive to use x^2+y^2<d^2 than sqrt(x^2+y^2)<d
local function testcol(a,b,r)
local x = pts[b][1]-pts[a][1]
local y = pts[b][2]-pts[a][2]
if x*x+y*y<4*r*r then
return true
end
end
-- this is a bit of a mess, deleting pairs on the fly was causing some
-- problems, so I had to include some checks "if pts[k1]..." etc.
local function delpairs(r)
for k1,v1 in pairs(tstpairs) do
if pts[k1] then
for k2,v2 in pairs(v1) do
if pts[v2] then
if abs(pts[v2][2]-pts[k1][2])<2*r then
if testcol(k1,v2,r) then
pts[v2]=nil
end
end
end
end
end
end
end
-- quickSort helper
local function partition(array, p, r)
local x = array[r][1]
local i = p - 1
for j = p, r - 1 do
if array[j][1] <= x then
i = i + 1
local temp = array[i][1]
array[i][1] = array[j][1]
array[j][1] = temp
end
end
local temp = array[i + 1][1]
array[i + 1][1] = array[r][1]
array[r][1] = temp
return i + 1
end
-- quickSort to sort by x
-- taken from https://github.com/akosma/CodeaSort/blob/master/QuickSort.lua
local function quickSort(array, p, r)
p = p or 1
r = r or #array
if p < r then
q = partition(array, p, r)
quickSort(array, p, q - 1)
quickSort(array, q + 1, r)
end
end
-- draw output
local function showout(n,r,x,y)
tex.print("\\begin{tikzpicture}[radius="..r.."cm]")
tex.print("\\draw[red](0,0) rectangle ("..x..","..y..");")
for k,v in pairs(pts) do
tex.print("\\draw ("..pts[k][1]..","..pts[k][2]..") circle ;")
end
tex.print("\\end{tikzpicture}")
end
-- wrapper
function circles(n,r,x,y)
genpts(n,x,y)
quickSort(pts)
getpairs(pts,r)
delpairs(r)
showout(n,r,x,y)
end
\end{luacode*}
\def\circles#1#2#3#4{\directlua{circles(#1,#2,#3,#4)}}
\begin{document}
\circles{500}{.1}{10}{5}
\end{document}
答案3
刚刚发现这个老问题,为了完整起见,我决定添加以下答案。
泊松圆盘采样似乎是创建此类模式的正确方法。这个问题/答案在纯 lualatex 中提供了该算法的实现,允许从 pgf/tikz 中使用它。
假设您有poisson.sty
和poisson.lua
(您可以从提到的答案中获取),以及可以正常工作的安装lualatex
,则解决方案将是:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{backgrounds}
\usepackage{poisson}
\begin{document}
\edef\mylist{\poissonpointslist{5}{5}{1}{20}}
% 5 x 5 is the size of the area to fill
% 1 is the minimum distance between centers
\begin{tikzpicture}[framed,gridded,radius=0.5cm]
\draw[red](0,0) rectangle (5,5);
\foreach \x/\y in \mylist \draw (\x,\y) circle;
\end{tikzpicture}
\end{document}
结果(用 编译lualatex
)是:
答案4
我知道这是一个很老的问题,但我一直在寻找类似的东西。杰克的代码如下几乎对我来说是可行的,但我希望更精细地控制实际出现的圆圈数量。所以我修改了 Jake 的代码,如下所示(我还通过比较平方距离而不是绝对距离进行了轻微优化):
\newcommand{\fillrandomly}[4]{
\xdef\xlist{4}
\xdef\ylist{4}
\pgfmathsetmacro\diametersqr{(#3*2)^2}
\draw (0,0) rectangle (#1,#2);
\foreach \i in {1,...,#4}{
\foreach \k in {1,...,20}{
\pgfmathsetmacro\x{rnd*#1}
\pgfmathsetmacro\y{rnd*#2}
\xdef\collision{0}
\foreach \element [count=\i] in \xlist{
\pgfmathtruncatemacro\j{\i-1}
\pgfmathsetmacro\checkdistancesqr{ ( ({\xlist}[\j]-(\x))^2 + ({\ylist}[\j]-(\y))^2 ) }
\ifdim\checkdistancesqr pt<\diametersqr pt
\xdef\collision{1}
\breakforeach
\fi
}
\ifnum\collision=0
\xdef\xlist{\xlist,\x}
\xdef\ylist{\ylist,\y}
\draw [red, thick] (\x,\y) circle [radius=#3];
\breakforeach
\fi
}
}
}
这循环尝试放置n圆圈,对于每个圆圈,它会尝试放置最多 20 次,直到放弃。如果你有紧密排列的圆圈,情况就不会好多少,但对于相对松散排列的圆圈,你可以相当肯定地在相对较短的时间内获得所需数量的圆圈。