起一個(gè)好名字,意味著賦予事物一個(gè)承載意義、期望與身份的符號(hào),并借此為其未來(lái)的發(fā)展鋪設(shè)一條充滿可能性的道路。它不僅僅是一個(gè)稱(chēng)呼,更是一種深遠(yuǎn)的祝福、一個(gè)無(wú)聲的預(yù)言、一個(gè)身份認(rèn)同的起點(diǎn),其象征未來(lái)的意義體現(xiàn)在以下幾個(gè)方面: 1. 承載期望與愿景: 個(gè)人: 父母給孩子取名,往往寄托著對(duì)孩子未來(lái)的期望(如“志遠(yuǎn)”、“嘉慧”、“安然”)、對(duì)品德的期許(如“仁杰”、“守信”、“思齊”)、對(duì)人生狀態(tài)的祝愿(如“樂(lè)康”、“欣悅”、“安寧”)或?qū)易鍌鞒械难永m(xù)(如特定的字輩、紀(jì)念先祖)。 企業(yè)/品牌: 一個(gè)好的公司或品牌名稱(chēng),需要體現(xiàn)其核心價(jià)值(如“誠(chéng)信”、“創(chuàng)新”)、市場(chǎng)定位(如“高端”、“親民”)、行業(yè)特性(如“迅捷”、“穩(wěn)健”)以及未來(lái)的發(fā)展藍(lán)圖(如“環(huán)球”、“未來(lái)”、“領(lǐng)航”)。 項(xiàng)目/活動(dòng): 名稱(chēng)需要清晰傳達(dá)項(xiàng)目/活動(dòng)的目標(biāo)(如“曙光計(jì)劃”、“春風(fēng)行動(dòng)”)、核心理念(如“和諧共生”、“智慧未來(lái)”)以及想要實(shí)現(xiàn)的積極影響。 2. 塑造第一印象與身份認(rèn)同: 名字是“第一張名片”: 一個(gè)恰當(dāng)、響亮、富有內(nèi)涵的名字能迅速在他人心中建立積極的初步印象,激發(fā)好奇心和好感度。這為未來(lái)的互動(dòng)和關(guān)系建立打下了基礎(chǔ)。 定義身份核心: 名字是個(gè)人、組織或事物最核心的身份標(biāo)識(shí)。它幫助確立“我是誰(shuí)”、“我們代表什么”。一個(gè)強(qiáng)大的名字能強(qiáng)化內(nèi)部成員的歸屬感和自豪感,也幫助外界快速理解其本質(zhì)。 3. 蘊(yùn)含潛力與可能性: “名正則言順”: 一個(gè)寓意積極、方向明確的名字,仿佛為未來(lái)的發(fā)展指明了一個(gè)方向。它像一個(gè)無(wú)形的燈塔,引導(dǎo)著個(gè)體或組織朝著名字所蘊(yùn)含的美好愿景努力。 激發(fā)內(nèi)在動(dòng)力: 一個(gè)充滿力量和希望的名字,本身就能對(duì)擁有者(人或組織)產(chǎn)生積極的暗示和心理激勵(lì),鼓勵(lì)其努力去“配得上”這個(gè)名字所代表的品質(zhì)和未來(lái)。 4. 象征連接與傳承: 連接過(guò)去與未來(lái): 名字常常承載著歷史(家族姓氏、文化典故)、當(dāng)下(時(shí)代特征、父母心境)和對(duì)未來(lái)的展望。它像一個(gè)紐帶,連接著起源和歸宿。 建立情感紐帶: 一個(gè)被用心賦予、飽含深情的名字,能建立起擁有者與命名者(如父母與孩子)之間深厚的情感聯(lián)系。這份情感是未來(lái)關(guān)系的重要基石。 傳承價(jià)值: 名字中蘊(yùn)含的價(jià)值觀(如勇敢、智慧、仁愛(ài))或精神(如探索、堅(jiān)韌、合作)是希望在未來(lái)得以延續(xù)和發(fā)揚(yáng)光大的。 5. 在市場(chǎng)中建立差異化與價(jià)值: 品牌資產(chǎn)的核心: 在商業(yè)領(lǐng)域,一個(gè)好的名字是品牌最核心的無(wú)形資產(chǎn)之一。它幫助在擁擠的市場(chǎng)中脫穎而出,建立獨(dú)特的品牌形象,承載品牌承諾,并最終影響消費(fèi)者未來(lái)的購(gòu)買(mǎi)決策和忠誠(chéng)度。一個(gè)有遠(yuǎn)見(jiàn)的名字能為品牌未來(lái)的價(jià)值增長(zhǎng)奠定基礎(chǔ)。 總結(jié)來(lái)說(shuō),“起一個(gè)好名字意味著什么,象征著未來(lái)”的核心在于: 意味著: 深思熟慮地注入期望、定義身份、賦予意義、建立連接、并期望其成為未來(lái)發(fā)展的重要助力。 象征著: 一個(gè)充滿希望的起點(diǎn)、一個(gè)有待實(shí)現(xiàn)的藍(lán)圖、一種無(wú)形的引導(dǎo)力量、以及一份承載著祝福與責(zé)任的傳承。 它是對(duì)未來(lái)潛力的一種具象化表達(dá)和積極召喚。 因此,起名絕非隨意之舉,而是一項(xiàng)面向未來(lái)的、充滿創(chuàng)造力和責(zé)任感的儀式。一個(gè)好的名字,如同一顆精心挑選的種子,蘊(yùn)含著破土而出、茁壯成長(zhǎng)、最終綻放出美好未來(lái)的無(wú)限可能。它既是當(dāng)下的承諾,也是通往未來(lái)的第一聲回響。

60行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤(pán)采樣,比Numpy快100倍

編輯整理自 太極圖形
量子位 | 公眾號(hào) QbitAI

隨機(jī)均勻的點(diǎn)組成的圖案,在動(dòng)植物身上已經(jīng)很常見(jiàn)了。

像楊梅、草莓、荔枝、紅毛丹這樣的水果,表面都有顆?;蛘呙l(fā)狀的結(jié)構(gòu),它們隨機(jī)、均勻地散布在水果表面:

0行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤(pán)采樣,比Numpy快100倍"

類(lèi)似的圖案在動(dòng)物身上也有,比如大家都愛(ài)涮的毛肚:

0行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤(pán)采樣,比Numpy快100倍"

同樣地,在計(jì)算機(jī)模擬下,也有不少場(chǎng)景需要在空間中隨機(jī)、均勻地生成點(diǎn)。

像生成動(dòng)物毛發(fā)時(shí)的毛孔位置、多人對(duì)戰(zhàn)游戲中的玩家出生位置、生成森林時(shí)的樹(shù)木位置等等。

這些場(chǎng)景的共同特點(diǎn)是,都需要讓任何兩點(diǎn)之間的距離大于等于一個(gè)下界(這個(gè)下界是預(yù)設(shè)的,改變它就可以控制生成點(diǎn)之間的間隔)。

但如果直接使用完全隨機(jī)生成的點(diǎn),大概率會(huì)獲得一個(gè)很不均勻的分布結(jié)果,有些地方“扎堆”、有些地方稀疏:

0行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤(pán)采樣,比Numpy快100倍"

如果用這樣的點(diǎn)來(lái)模擬毛發(fā)等位置生成,效果就很差。

所以,需要在生成點(diǎn)的過(guò)程中加入一個(gè)距離判斷,來(lái)剔除那些不合要求的點(diǎn)。

此前,用numpy生成這樣一個(gè)效果,往往需要70s左右,非常不劃算。

0行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤(pán)采樣,比Numpy快100倍"

現(xiàn)在,太極圖形基于Taichi實(shí)現(xiàn)了一個(gè)超快算法,同樣的效果運(yùn)行在單個(gè)CPU線程上,只需要0.7s就能生成這樣的圖案,快了100倍左右。

一起來(lái)看看他們是怎么做的。

采用Bridson算法實(shí)現(xiàn)

此前,有一種常見(jiàn)算法dart throwing (像一個(gè)人蒙上眼睛胡亂扔飛鏢的樣子)

每次在區(qū)域內(nèi)隨機(jī)選擇一個(gè)點(diǎn),并檢查該點(diǎn)與所有已經(jīng)得到的點(diǎn)之間是否存在“沖突”。

若該點(diǎn)與某個(gè)已得到的點(diǎn)的最小距離小于指定的下界,就拋棄這個(gè)點(diǎn),否則這就是一個(gè)合格的點(diǎn),把它加入已有點(diǎn)的集合。

重復(fù)這個(gè)操作直到獲得了足夠多的點(diǎn),或者連續(xù)失敗了N次為止(N是某個(gè)設(shè)定的正整數(shù))。

但這種算法效率很低。

因?yàn)殡S著得到的點(diǎn)的個(gè)數(shù)增加,沖突的概率越來(lái)越大,獲得新的點(diǎn)所需的時(shí)間也越來(lái)越長(zhǎng),每次比較當(dāng)前點(diǎn)和所有已有點(diǎn)之間的距離也會(huì)降低效率。

相比之下,Bridson算法則要更加高效。

這個(gè)算法的原理來(lái)自于Robert Bridson發(fā)表于2007年的論文”Fast Poisson Disk Sampling in Arbitrary Dimensions”[1](論文非常短,只有一頁(yè)A4紙),如果再去掉標(biāo)題、引言的話,真正的算法內(nèi)容只有一小段話。

開(kāi)頭這個(gè)動(dòng)圖,演示了Bridson圓盤(pán)采樣算法在一個(gè)400×400網(wǎng)格區(qū)域上的運(yùn)行效果,算法嘗試獲得100K個(gè)均勻散布的點(diǎn),實(shí)際生成了大約53.7K個(gè):

0行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤(pán)采樣,比Numpy快100倍"

這個(gè)動(dòng)畫(huà)是使用Taichi生成的,運(yùn)行在單個(gè)CPU線程上,除去編譯的時(shí)間計(jì)算,耗時(shí)僅在0.7s多一點(diǎn),而同樣的代碼翻譯成NumPy要耗時(shí)70s左右。[2]

從上面的動(dòng)畫(huà)效果可見(jiàn),Bridson算法很像包菜的生長(zhǎng)過(guò)程:我們從一個(gè)種子點(diǎn)開(kāi)始,一層一層地向外添加新的點(diǎn)。

每一次我們添加的新的點(diǎn),都位于最外層的點(diǎn)的周?chē)?/span>,并且盡可能地包住最外層。

為了避免每次都檢查和所有已有點(diǎn)之間的距離,Taichi采用了所謂網(wǎng)格的技巧:

將整個(gè)空間劃分為網(wǎng)格,對(duì)一個(gè)需要檢查的點(diǎn),只要找到它所在的網(wǎng)格,然后檢查它和臨近網(wǎng)格中的點(diǎn)之間的最小距離即可。

只要這個(gè)距離大于指定的下界,更遠(yuǎn)處的點(diǎn)就不必再檢查了。這個(gè)技巧在圖形學(xué)和物理仿真中是非常常用的。

這個(gè)采樣過(guò)程很難并行化,因?yàn)楫?dāng)一個(gè)線程“偷偷”加入一個(gè)新的點(diǎn)的時(shí)候,會(huì)改變其它所有線程對(duì)距離的判斷。所以Taichi僅使用單個(gè)CPU線程來(lái)執(zhí)行這個(gè)算法:

ti.init(arch=ti.cpu)

上面的代碼中通過(guò)指定arch=ti.cpu來(lái)讓程序運(yùn)行在CPU上。

你可能會(huì)想,既然是單線程+CPU,那為什么不直接寫(xiě)純Python呢?別著急,我們的計(jì)算部分會(huì)放在ti.kernel函數(shù)中,這種函數(shù)并不運(yùn)行在Python虛擬機(jī)中,而是會(huì)被Taichi編譯執(zhí)行,所以會(huì)比純Python的實(shí)現(xiàn)快很多倍!

在我們介紹Bridson算法的具體實(shí)現(xiàn)之前,你不妨猜猜這個(gè)Taichi程序包含多少行代碼?

安裝和導(dǎo)入Taichi

首先推薦大家使用最新的Taichi發(fā)布版本,這樣可以使用更豐富的功能,在不同平臺(tái)上的支持也更穩(wěn)定。截止本文寫(xiě)作時(shí)最新版本是1.0.3:

pip install taichi==1.0.3

然后,在代碼開(kāi)頭寫(xiě)上:

import taichi as ti
import taichi.math as tm

這樣會(huì)導(dǎo)入Taichi以及Taichi的math模塊。math模塊除了包含常用的數(shù)學(xué)函數(shù)之外,還提供了非常方便的向量運(yùn)算。

準(zhǔn)備工作

在泊松采樣算法中,采樣點(diǎn)之間的距離有一個(gè)下界r。

我們假設(shè)整個(gè)區(qū)域是由N×N個(gè)同樣大小的方格組成的網(wǎng)格區(qū)域,使得每個(gè)小方格的對(duì)角線長(zhǎng)度正好是r,即網(wǎng)格的邊長(zhǎng)是r/√2。

于是任何小方格中至多包含一個(gè)點(diǎn)。如下圖所示:

0行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤(pán)采樣,比Numpy快100倍"

這就是我們前面提到的網(wǎng)格化方法,即對(duì)于任何一個(gè)點(diǎn)p,設(shè)它所在的方格是D,則任何與p的距離小于等于r的點(diǎn)必然位于以D中心的、由5×5個(gè)方格組成的正方形區(qū)域中。

在檢查距離時(shí),我們只要針對(duì)這個(gè)子區(qū)域進(jìn)行計(jì)算即可。

我們用一個(gè)一維數(shù)組samples和一個(gè)N×N的二維數(shù)組grid來(lái)記錄已經(jīng)得到的采樣點(diǎn):

  1. samples保存當(dāng)前所有已經(jīng)采樣點(diǎn)的坐標(biāo),它的每個(gè)元素是一個(gè)二維坐標(biāo)(x,y)。
  2. grid[i, j]是一個(gè)整數(shù),它存儲(chǔ)的是第(i, j)個(gè)方格中采樣點(diǎn)在數(shù)組samples中的下標(biāo)。grid[i, j] = -1表示這個(gè)方格中沒(méi)有采樣點(diǎn)。

于是我們的初始設(shè)置可以這樣寫(xiě):

grid_n = 400
res = (grid_n, grid_n)
dx = 1 / res[0]
inv_dx = res[0]
radius = dx * ti.sqrt(2)
desired_samples = 100000
grid = ti.field(dtype=int, shape=res)
samples = ti.Vector.field(2, float, shape=desired_samples)

這里網(wǎng)格大小設(shè)置為400×400,它占據(jù)的平面區(qū)域是[0,1]×[0,1],所以網(wǎng)格的步長(zhǎng)是dx = 1/400。采樣的最小間隔是每個(gè)小方格對(duì)角線的長(zhǎng)度,即radius = sqrt(2)*dx。

我們把采樣點(diǎn)的目標(biāo)個(gè)數(shù)設(shè)置為desired_examples=100000,這是一個(gè)目測(cè)值,因?yàn)?00×400的網(wǎng)格包含160000個(gè)小方格,考慮到每個(gè)方格中至多只有一個(gè)點(diǎn),我們能得到的滿足距離約束的點(diǎn)的最大數(shù)目肯定少于160000。

初始時(shí)網(wǎng)格中沒(méi)有任何點(diǎn),所以需要將grid中的值都置為-1:

grid.fill(-1)

如何生成新的點(diǎn)

在加入新的隨機(jī)點(diǎn)時(shí),我們總是從已有點(diǎn)的附近隨機(jī)選擇一個(gè)位置,然后比較它和已知點(diǎn)是否滿足最小距離約束,是的話就將其加入已有點(diǎn),否則就將其拋棄然后重新選擇點(diǎn)。

這里需要注意的是:

  1. 當(dāng)一個(gè)已有點(diǎn)附近已經(jīng)被填滿時(shí),我們后面再加入新的點(diǎn)時(shí)就不必考慮它的附近了,可以用一個(gè)下標(biāo)head來(lái)記錄這一點(diǎn)。我們約定samples數(shù)組中下標(biāo)< head的點(diǎn)附近都已經(jīng)被填滿,從而不必再考慮它們,只考慮下標(biāo)>= head的點(diǎn)即可。初始時(shí)head = 0。
  2. samples是一個(gè)長(zhǎng)度為100K的數(shù)組,這不代表我們真的能取到這么多點(diǎn),但具體個(gè)數(shù)是多少無(wú)法事先確定,所以我們還需要用一個(gè)下標(biāo)tail來(lái)記錄目前已經(jīng)獲得的點(diǎn)的個(gè)數(shù)。初始時(shí)tail = 1,因?yàn)槲覀儗⑦x擇區(qū)域中心作為第一個(gè)點(diǎn)。當(dāng)然這個(gè)初始點(diǎn)的位置可以是任意的。
  3. 正如前面提到的,當(dāng)我們檢查一個(gè)點(diǎn)p是否與已有點(diǎn)滿足最小距離約束時(shí),沒(méi)有必要遍歷檢查所有的點(diǎn)。只要檢查以p所在方格為中心,由5×5個(gè)方格組成的正方形區(qū)域即可。

檢查一個(gè)點(diǎn)是否和已有點(diǎn)沖突的邏輯我們單獨(dú)寫(xiě)成一個(gè)函數(shù):

@ti.func
def check_collision(p, index):
    x, y = index
    collision = False
    for i in range(max(0, x - 2), min(grid_n, x + 3)):
        for j in range(max(0, y - 2), min(grid_n, y + 3)):
            if grid[i, j] != -1:
                q = samples[grid[i, j]]
                if (q - p).norm() < radius - 1e-6:
                    collision = True
    return collision

其中p是需要檢查點(diǎn)的坐標(biāo),index=(x, y)是p所在的方格的下標(biāo)。

我們遍歷所有滿足x-2 <= i <= x+2和y-2 <= j <= y+2的下標(biāo)(i, j),檢查方格(i, j)中是否已經(jīng)有點(diǎn),即 grid[i, j]是否等于-1。有的話它與p的距離是否小于radius,然后返回對(duì)應(yīng)的判斷。

完成了準(zhǔn)備工作,我們可以開(kāi)始正式的循環(huán)了:

@ti.kernel
def poisson_disk_sample(desired_samples: int) -> int:
    samples[0] = tm.vec2(0.5)
    grid[int(grid_n * 0.5), int(grid_n * 0.5)] = 0
    head, tail = 0, 1
    while head < tail and head < desired_samples:
        source_x = samples[head]
        head += 1

        for _ in range(100):
            theta = ti.random() * 2 * tm.pi
            offset = tm.vec2(tm.cos(theta), tm.sin(theta)) * (1 + ti.random()) * radius
            new_x = source_x + offset
            new_index = int(new_x * inv_dx)

            if 0 <= new_x[0] < 1 and 0 <= new_x[1] < 1:
                collision = check_collision(new_x, new_index)
                if not collision and tail < desired_samples:
                    samples[tail] = new_x
                    grid[new_index] = tail
                    tail += 1
    return tail

首先我們把區(qū)域的中心,即坐標(biāo)為(0.5,0.5)的點(diǎn)選擇為初始點(diǎn),讓它作為“種子”將隨機(jī)點(diǎn)逐漸擴(kuò)散到整個(gè)區(qū)域。

接下來(lái)的while循環(huán)是算法的主體,這個(gè)循環(huán)是串行執(zhí)行的,只占用一個(gè)線程。

我們每次找到第一個(gè)需要考慮的點(diǎn)samples[head],然后在以它為中心,半徑為[radius, 2*raidus]的圓環(huán)中隨機(jī)選擇100個(gè)點(diǎn),逐個(gè)檢查這100個(gè)點(diǎn)是否不超出[0,1]×[0,1]的區(qū)域范圍,以及是否和已有點(diǎn)沖突。

如果都滿足的話它就是一個(gè)合格的點(diǎn),我們將它的坐標(biāo)和方格下標(biāo)更新到samples和grid中,并將已有點(diǎn)的個(gè)數(shù)tail增加1。在這100個(gè)點(diǎn)都檢查完后,可能有多個(gè)點(diǎn)會(huì)被加入已有點(diǎn)的集合。

注意在半徑為[radius, 2*raidus]的圓環(huán)中采樣可以讓我們得到的點(diǎn)在滿足最小距離約束的同時(shí)距離已有點(diǎn)也不會(huì)太遠(yuǎn)。

當(dāng)這100個(gè)點(diǎn)都檢查完畢后,我們可以認(rèn)為samples[head]這個(gè)點(diǎn)的周?chē)呀?jīng)沒(méi)有空白區(qū)域可以放置新的點(diǎn),所以將head增加1,并重新檢查下一個(gè)samples[head] 附近的區(qū)域。

當(dāng)所有的點(diǎn)周?chē)目臻g都已經(jīng)被填滿,即head = tail時(shí),或者我們已經(jīng)獲得了desired_samples個(gè)點(diǎn),即tail = desired_samples時(shí)循環(huán)結(jié)束。這時(shí)samples中下標(biāo)在0~tail-1范圍內(nèi)的點(diǎn)就是全部的已有點(diǎn)。

展示動(dòng)畫(huà)效果

我們可以只用幾行代碼,就把整個(gè)采樣的過(guò)程用動(dòng)畫(huà)的形式顯示出來(lái):

num_samples = poisson_disk_sample(desired_samples)
gui = ti.GUI("Poisson Disk Sampling", res=800, background_color=0xFFFFFF)
count = 0
speed = 300
while gui.running:
    gui.circles(samples.to_numpy()[:min(count * speed, num_samples)],
                color=0x000000,
                radius=1.5)
    count += 1
    gui.show()

這里我們控制動(dòng)畫(huà)的速度為每生成300個(gè)點(diǎn)就繪制一幀。

至此我們已經(jīng)介紹完了程序的所有要點(diǎn),把各部分組合起來(lái):

import taichi as ti
import taichi.math as tm
ti.init(arch=ti.cpu)

grid_n = 400
res = (grid_n, grid_n)
dx = 1 / res[0]
inv_dx = res[0]
radius = dx * ti.sqrt(2)
desired_samples = 100000
grid = ti.field(dtype=int, shape=res)
samples = ti.Vector.field(2, float, shape=desired_samples)

grid.fill(-1)

@ti.func
def check_collision(p, index):
    x, y = index
    collision = False
    for i in range(max(0, x - 2), min(grid_n, x + 3)):
        for j in range(max(0, y - 2), min(grid_n, y + 3)):
            if grid[i, j] != -1:
                q = samples[grid[i, j]]
                if (q - p).norm() < radius - 1e-6:
                    collision = True
    return collision

@ti.kernel
def poisson_disk_sample(desired_samples: int) -> int:
    samples[0] = tm.vec2(0.5)
    grid[int(grid_n * 0.5), int(grid_n * 0.5)] = 0
    head, tail = 0, 1
    while head < tail and head < desired_samples:
        source_x = samples[head]
        head += 1

        for _ in range(100):
            theta = ti.random() * 2 * tm.pi
            offset = tm.vec2(tm.cos(theta), tm.sin(theta)) * (1 + ti.random()) * radius
            new_x = source_x + offset
            new_index = int(new_x * inv_dx)

            if 0 <= new_x[0] < 1 and 0 <= new_x[1] < 1:
                collision = check_collision(new_x, new_index)
                if not collision and tail < desired_samples:
                    samples[tail] = new_x
                    grid[new_index] = tail
                    tail += 1
    return tail

num_samples = poisson_disk_sample(desired_samples)
gui = ti.GUI("Poisson Disk Sampling", res=800, background_color=0xFFFFFF)
count = 0
speed = 300
while gui.running:
    gui.circles(samples.to_numpy()[:min(count * speed, num_samples)],
                color=0x000000,
                radius=1.5)
    count += 1
    gui.show()

代碼總行數(shù):60。

One More Thing

具體來(lái)說(shuō),這篇代碼實(shí)現(xiàn)了兩個(gè)操作

  1. 60行代碼中實(shí)現(xiàn)了一個(gè)完整的泊松采樣動(dòng)畫(huà)。
  2. 在一個(gè)400×400的網(wǎng)格中采集了53k個(gè)點(diǎn),但耗時(shí)不到1秒。

相關(guān)代碼可以在文末的原文鏈接中找到。

嚴(yán)格來(lái)說(shuō),本文實(shí)現(xiàn)的算法和Bridson論文里描述的算法有一點(diǎn)點(diǎn)不一樣的地方(更簡(jiǎn)單一些),但是效果卻差不多。

你能看出是哪里不一樣嗎?(TIP:可以關(guān)注一下原論文Step 2中“active list”的處理方式)

項(xiàng)目地址:
https://github.com/taichi-dev/poisson-sampling-homework

參考資料:
[1]Robert Bridson的原論文見(jiàn)Fast Poisson Disk Sampling in Arbitrary Dimensions.
[2]Poisson采樣用Taichi, Numpy, Numba實(shí)現(xiàn)的benchmark比較見(jiàn)GitHub

— 完 —

量子位 QbitAI · 頭條號(hào)簽約

關(guān)注我們,第一時(shí)間獲知前沿科技動(dòng)態(tài)

本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 673862431@qq.com 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。
如若轉(zhuǎn)載,請(qǐng)注明出處:http://www.builtinbookshelves.com/archives/19343