15号,水饺快见底了.....

这是WaveFunctionCollapse波函数坍缩算法的实现源码。

WFC算法可通过解数独游戏的过程来直观的理解,通常解数独游戏时都会先去找一个格子,这个格子可能填的数最少(即WFC中所谓的熵最小)。然后试着在这个格子中填入可能的数(即WFC中所谓的坍缩),填入数字后其它空格的可能性会发生变化(即WFC中所谓的熵传递),继续重复选择最小熵的格子做坍缩,直到所有格子都被成功坍缩后算法结束。中间有可能推导出矛盾,说明前面某个格子坍缩错误需要回退处理。

通过WFC算法来随机生成游戏场景和数独游戏稍有不同的是它们的限制条件。

首先将游戏场景分割成一系列相邻的格子(block),通常用均匀的立方体来分割比较方便,但不是必须的。每个block通过一系列固定类型的碎片(tile)来拼接,即将block坍缩到tile,限制条件是新坍缩到的tile必须和周围已坍缩到的tile是可拼接的。

2d版一个tile是一张图片,给每个tile的四条边都标记edgeID,相同的edgeID表示可以连接。另外有时可能要加一些特殊的不同edgeID的连接。

tile根据对称性可能分为 X I T L \ 等类型,对称的tile自动加入。

下面是一个示例

"wfc cfg file"
<"tiles" 11> "bridge"   ,"data/test/wavefunccollpase/castle/bridge.png"             ,     1 ,   "I""ground"   ,"data/test/wavefunccollpase/castle/ground.png"      ,     1 ,   "X""river"    ,"data/test/wavefunccollpase/castle/river.png"         ,     1 ,   "I""riverturn","data/test/wavefunccollpase/castle/riverturn.png"              ,     1 ,   "L""road"     ,"data/test/wavefunccollpase/castle/road.png"       ,     1 ,   "I""roadturn" ,"data/test/wavefunccollpase/castle/roadturn.png"          ,     1 ,   "L""t"        ,"data/test/wavefunccollpase/castle/t.png"           ,     1 ,   "T""tower"    ,"data/test/wavefunccollpase/castle/tower.png"              ,     1 ,   "L""wall"     ,"data/test/wavefunccollpase/castle/wall.png"          ,     1 ,   "I""wallriver","data/test/wavefunccollpase/castle/wallriver.png"           ,     1 ,   "I""wallroad" ,"data/test/wavefunccollpase/castle/wallroad.png"              ,     1 ,   "I""Error"   ,  "data/test/wavefunccollpase/castle/Error.png", "Unknown" ,  "data/test/wavefunccollpase/castle/Unknown.png",<"auto edges"><"special edges",13>
1 3
4 8
5 8
11 8
13 8
4 9
5 9
11 9
13 9
6 2
7 2
10 2
12 2

使用的贴片和生成结果:

3d版每个tile是一个模型,每个tile有六条边(或者叫连接面更合适)。根据上面的示例改成了3d版。

"wfc cfg file""data/test/wavefunccollpase/castle/castle.movie"(-0.5,-0.5,-0.5,) (1,1,1,)<"tiles" 12> "Clear"    ,""    ,     1,   "X""bridge"   ,""    ,     1 ,   "I""ground"   ,""    ,     1 ,   "X""river"    ,""    ,     1 ,   "I""riverturn",""    ,     1 ,   "L""road"     ,""    ,     1 ,   "I""roadturn" ,""    ,     1 ,   "L""t"        ,""    ,     1 ,   "T""tower"    ,,""   ,     1 ,   "L""wall"      ,""   ,     1 ,   "I""wallriver" ,""   ,     1 ,   "I""wallroad"  ,""   ,     1 ,   "I"<"edges" ,30> "Clear"     -1 -1 -1 -1 -1 -1"bridge"      -1 1 -1 1 0 0"ground"     -1 2 -1 2 2 2"river"      -1 2 -1 2 0 0"riverturn"      -1 2 -1 0 2 0"road"       -1 2 -1 2 3 3"roadturn"       -1 2 -1 3 2 3"t"      -1 3 -1 3 3 2"tower"      -1 5 -1 7 6 4"wall"       -1 2 -1 2 8 8"wallriver"      -1 0 -1 0 9 9"wallroad"       -1 3 -1 3 8 8"bridgeRotatedA"     -1 0 -1 0 1 1"riverRotatedA"      -1 0 -1 0 2 2"riverturnRotatedA"      -1 0 -1 2 2 0"riverturnRotatedB"      -1 0 -1 2 0 2"riverturnRotatedC"      -1 2 -1 0 0 2"roadRotatedA"       -1 3 -1 3 2 2"roadturnRotatedA"       -1 3 -1 2 2 3"roadturnRotatedB"       -1 3 -1 2 3 2"roadturnRotatedC"       -1 2 -1 3 3 2"tRotatedA"      -1 2 -1 3 3 3"tRotatedB"      -1 3 -1 3 2 3"tRotatedC"      -1 3 -1 2 3 3"towerRotatedA"      -1 4 -1 6 11 10"towerRotatedB"        -1 10 -1 11 13 12"towerRotatedC"      -1 12 -1 13 7 5"wallRotatedA"     -1 8 -1 8 2 2"wallriverRotatedA"      -1 9 -1 9 0 0"wallroadRotatedA"       -1 8 -1 8 3 3<"special edges",13>
1 3
4 8
5 8
11 8
13 8
4 9
5 9
11 9
13 9
6 2
7 2
10 2
12 2"success"

使用的贴片和生成结果:

某些时候要加入空的tile。场景的顶部和底部需要加入特殊的边,以便生成的模型边界不是空缺的。

借用townscaper的模型试了下结果,网上有个生成无限城市的例子有空再试一下。 3D贴片模型的制作有点麻烦,等找到好方法再介绍。

自动标记边的edgeID: 

对于2D版算法,一条边用边上的像素来识别(首先要将像素按x、y轴排序),依次判断像素是否相等即可。注意某些边旋转后可能逆序具有不同的edgeID。

对于3d版稍麻烦一些,一条边用连接面上的顶点位置、顶点法线、连接线等来识别(同样要先排序),有时候顶点的纹理也要考虑进去。

2D版源码:

//========================================================
//  @Date:     2016.05
//  @File:     SourceLib/Render/Maze.cpp
//  @Brief:     WaveFuncCollapse_H
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================
#ifndef WaveFuncCollapse_H
#define WaveFuncCollapse_H#include <string>
#include <vector>
#include <set>
#include <bitset>
#include <memory>
#include "Math\MathLib.h"class TextureData;
#define unordered_set setclass WFC2D
{
public://设置太大对效率有影响//static const int MaxTileNum = 64; static const int MaxTileNum = 256; typedef unsigned int rgba;//typedef std::bitset<MaxTileNum> Bitset;//邻接方向enum Dir{Top    = 0,Left   = 1,Bottom = 2,Right  = 3,DirNum = 4};//对称类型enum Symmetry { unknow = -1,X, //rot0T, //rot3I, //rot1L, //rot3backslash, P  //mirror rot3};bool StringToSymmetry(const char* str,int& type);int  PossibleRots(const Symmetry &symmetry);//连接边 具有相同ID的边是可以拼接的class TileEdgeStyle{public:TileEdgeStyle();~TileEdgeStyle();public://边的像素,从左到右 从下到上顺序存储  非对称的边旋转后边可能逆向 rgba* pixels; int   len;int   edgeID;private:TileEdgeStyle(TileEdgeStyle&);};//用于拼接的贴片类型class TileStyle{public:TileStyle();~TileStyle();bool Init(const char* name,const char* path, Symmetry sym,int weight);bool Init(TextureData* image, const char* name, Symmetry sym,int weight);public:TextureData*   image;char     name[128];int      weight;int      edges[DirNum];//???? todo 有可能某万能边可以连接十几个不匹配的其它边  参见castle中的tower(可能需要多创建N个tower?)Bitset   edgeMasks[DirNum];Symmetry sym;private:TileStyle(const TileStyle& other);};//待坍缩的块class Block{public:Bitset tileMasks;float  entropy;  //-1失败,0成功, >0weight坍缩bool   bEntropyDirty;Bitset edgeMasks[DirNum];Block* neighbours[DirNum];};int  m_iWidth;int  m_iHeight;int  m_randSeed;Block*  m_blocks;Block** m_unCollapsedBlocks;int     m_unCollapsedBlockNum;std::unordered_set<Block*> m_dirtyBlocks; //tilelibstd::vector<TileStyle*> m_tileStyles;TileStyle errorTileStyle;TileStyle unknownTileStyle;//edgelibstd::vector<TileEdgeStyle*> m_unifyEdges;public:WFC2D(int height, int width);~WFC2D();void Solute();void RendToTexture(TextureData& image);void RendTileStyles (TextureData& image);//坍缩熵最小的blockBlock*  CollapseMinBlock();//传播坍缩void    PropagateChanges(Block* block);void    InsertNeighbours(Block* block);bool    updateTileMasks(Block* block);float   checkEntropy(Block* block);void    clearCache(Block* block);inline bool  isValid(const vec2I& pos)  const;inline int   getIndex(const vec2I& pos) const;inline Dir   opDir(Dir dir) const;//tilestyle libbool GenTileStyles(const char* filename );int  AddEdgeStyle(const rgba* pixels,int len);void AutoTileEdges(TileStyle* tile);void MatchTileStyles();void AddMatch(int edgeIDA,int edgeIDB);bool AddTileStyle(const char* name,const char* path,int weight,Symmetry sym);bool AddSymmetryTileStyles(TileStyle* style);};inline bool WFC2D::isValid(const vec2I& pos) const
{return (pos.x >= 0 && pos.x < m_iWidth&& pos.y >= 0 && pos.y < m_iHeight);
}inline int WFC2D::getIndex(const vec2I& pos) const
{return pos.y * m_iWidth + pos.x;
}inline WFC2D::Dir WFC2D::opDir(Dir dir) const
{switch(dir){case Top:    return Bottom;case Left:   return Right;case Right:  return Left;case Bottom: return Top;}return DirNum;
}#endif // //========================================================
//  @Date:     2016.05
//  @File:     SourceLib/Math/WaveFuncCollapse.cpp
//  @Brief:     MarchingCubes
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================
#include "General/Pch.h"
#include "General/General.h"
#include "Render/WaveFuncCollapse.h"
#include "Render/TextureData.h"
#include "General/Pce.h"
#include "Render/RendDriver.h"
#include "General/File.h"
#include "General/StringUtil.h"
#include "Render/Font.h"WFC2D::TileEdgeStyle::TileEdgeStyle()
:pixels(NULL)
,edgeID(-1)
{}WFC2D::TileEdgeStyle::~TileEdgeStyle()
{SafeDeleteArray(pixels);
}WFC2D::TileStyle::TileStyle()
:image(NULL)
{
}WFC2D::TileStyle::~TileStyle()
{SafeDelete(image);
}bool WFC2D::TileStyle::Init(TextureData* image_, const char* name, Symmetry sym,int weight)
{strcpy(this->name,name);this->weight = weight;this->sym = sym;image = image_ ;return true;
}
bool WFC2D::TileStyle::Init(const char* name, const char* path, Symmetry sym,int weight)
{strcpy(this->name,name);this->weight = weight;this->sym = sym;image = new TextureData;bool res = image->LoadTexture(path );if (res==false){return false;}return true;
}//
WFC2D::WFC2D(int height, int width)
:m_iHeight(height)
,m_iWidth(width)
{int num = width*height;m_blocks = new Block[num];Block* it = m_blocks;for (int h=0;h<height;++h){for(int w=0;w<width;++w){it->neighbours[Left]   = (w>0)? it - 1 : NULL;it->neighbours[Right]  = (w<width-1)? it + 1 : NULL;it->neighbours[Bottom] = (h>0)? it - width : NULL;it->neighbours[Top]    = (h<height-1)? it + width : NULL;++it;}}m_unCollapsedBlocks = new Block*[num];
}
WFC2D::~WFC2D()
{SafeDeleteArray(m_blocks);SafeDeleteArray(m_unCollapsedBlocks);
}
void  WFC2D::Solute()
{PROFILEFUN("WFC2D::Solute();",0.0f,ALWAYSHIDE);Bitset bits;const int TileStyleNum = m_tileStyles.size();for (int i = 0; i < TileStyleNum; ++i){bits.set(i); //all tiles can be possible at first}m_blocks[0].tileMasks = bits;m_blocks[0].bEntropyDirty = true;float entropy = checkEntropy(&m_blocks[0]);Block* it = m_blocks;Block** pit = m_unCollapsedBlocks;int BlockNum = m_iWidth*m_iHeight;for(int i=0;i<BlockNum;++i,++it,++pit){for(int j=0;j<DirNum;j++)it->edgeMasks[j] = bits;it->bEntropyDirty = false;it->tileMasks =  bits;it->entropy =  entropy;*pit = it;}m_unCollapsedBlockNum = BlockNum;//try {Block* collapsed = CollapseMinBlock();while (collapsed != NULL){PropagateChanges(collapsed);collapsed = CollapseMinBlock();}}
}void  WFC2D::RendToTexture(TextureData& finalImage)
{if (m_tileStyles.size()==0 || m_tileStyles[0]->image->GetWidth()==0){return;}int tileW = m_tileStyles[0]->image->GetWidth();int tileH = m_tileStyles[0]->image->GetHeight();finalImage.AllocTexture(tileW * m_iWidth, tileH* m_iHeight,RS_RGBA);TileStyle* it;Block* block = m_blocks;for (int y = 0; y < m_iHeight; y++){for (int x = 0; x < m_iWidth; x++,block++){if (block->tileMasks.count() > 1){it = &unknownTileStyle;}else{int iTile = -1;const int TileStyleNum = m_tileStyles.size();for (int i = 0; i < TileStyleNum; ++i){if (block->tileMasks[i]){iTile = i;break;}} if(iTile>=0)it = m_tileStyles[iTile];elseit = &errorTileStyle;}TextureData* src    = it->image;int dy = y * tileH;int dx = x * tileW;finalImage.SubCopy(src,dx,dy,src->GetWidth(),src->GetHeight(),0,0);}}
}void  WFC2D::RendTileStyles(TextureData& finalImage)
{if (m_tileStyles.size()==0 || m_tileStyles[0]->image->GetWidth()==0){return;}int fontSize = 16;G_FontMgr->GetFontDesc().fontSize = fontSize;G_FontMgr->GetFontDesc().fontStyle = FS_OUTLINE;//FS_NORMAL;//G_FontMgr->GetFontDesc().backStyle = FB_RECT;G_FontMgr->GetFontDesc().backColor = Color(0.3f,1,0.3f,0.3f);G_FontMgr->SetColor(Color(1,0,0,1));FastWords words;int tileW = m_tileStyles[0]->image->GetWidth();int tileH = m_tileStyles[0]->image->GetHeight();int cellW = tileW+2;int cellH = tileH+2;if(cellW<48) cellW = 48;if(cellH<48) cellH = 48;int fontOffX = cellW/2 - fontSize;int fontOffY = cellH/2 - fontSize/2;int texOffX = (cellW-tileW)/2;int texOffY = (cellH-tileH)/2;int Width  = sqrt(float(m_tileStyles.size())) + 0.5f;int Height = ceil(float(m_tileStyles.size())/Width);finalImage.AllocTexture(cellW * Width, cellH* Height,RS_RGBA);TileStyle* it;int iTile = 0;for (int y = 0; y < Height; y++){for (int x = 0; x < Width; x++,iTile++){if(iTile>=m_tileStyles.size())break;it = m_tileStyles[iTile];TextureData* src    = it->image;int dy = y * cellH;int dx = x * cellW;finalImage.SubCopy(src,dx+texOffX,dy+texOffY,src->GetWidth(),src->GetHeight(),0,0);int edgeID = it->edges[Top];G_FontMgr->GenFastWords(StrFormat("%d",edgeID) ,words,true,2);words.m_textureData->FlipY();finalImage.SubCopy(words.m_textureData,dx+fontOffY,dy+cellH-fontSize,500,16, 0,0); edgeID = it->edges[Bottom];G_FontMgr->GenFastWords(StrFormat("%d",edgeID) ,words,true,2);words.m_textureData->FlipY();finalImage.SubCopy(words.m_textureData,dx+fontOffY,dy,500,16, 0,0); edgeID = it->edges[Left];G_FontMgr->GenFastWords(StrFormat("%d",edgeID) ,words,true,2);words.m_textureData->FlipY();finalImage.SubCopy(words.m_textureData,dx+3,dy+fontOffY,500,16, 0,0); edgeID = it->edges[Right];G_FontMgr->GenFastWords(StrFormat("%d",edgeID) ,words,true,2);words.m_textureData->FlipY();finalImage.SubCopy(words.m_textureData,dx+cellW-words.m_rect.width-3,dy+fontOffY,500,16, 0,0); //划线//finalImage.line();}}G_FontMgr->GetFontDesc().backStyle = FB_NULL;G_FontMgr->GetFontDesc().fontSize = 16;}float WFC2D::checkEntropy(Block* block)
{if (block->bEntropyDirty){int   count = 0;float sumWeight = 0;float sumWeightLogWeight = 0;const int TileStyleNum = m_tileStyles.size();TileStyle** pptile = &m_tileStyles[0];TileStyle*  ptile;for (int i = 0; i < TileStyleNum; ++i,++pptile){if (block->tileMasks[i]){count++;ptile = *pptile;sumWeight += ptile->weight;sumWeightLogWeight += ptile->weight * log((float)ptile->weight);}}if (count == 0){ block->entropy =  -1; //失败}else if (count == 1){ block->entropy =  0;   //成功}else{block->entropy = log(sumWeight) - sumWeightLogWeight / sumWeight;}block->bEntropyDirty = false;}return block->entropy;
}void  WFC2D::clearCache(Block* block)
{block->bEntropyDirty = true;for(int i=0;i<DirNum;i++){block->edgeMasks[i] = Bitset();}
}WFC2D::Block*  WFC2D::CollapseMinBlock()
{//选择熵(可能性)最小的blockBlock* minBlock = NULL;float  minEntropy = MaxFloat;Block** ppit = m_unCollapsedBlocks;for (int i = 0; i < m_unCollapsedBlockNum; i++,++ppit){float entropy = checkEntropy(*ppit);if (entropy<=0) {//return -1; //不选择坍缩失败的block  todo 可以返回错误 回退若干步后重新尝试 }else  if ( entropy < minEntropy){minEntropy = entropy;minBlock   = *ppit;}}//坍缩if (minBlock != NULL){int TileNum   = m_tileStyles.size();int sumWeight = 0;for (int i = 0; i < TileNum; i++){if (minBlock->tileMasks[i]){sumWeight += m_tileStyles[i]->weight;}}int rnd = rand() % sumWeight;int iTile = 0;for (int i = 0; i < TileNum; i++){if (minBlock->tileMasks[i]){if (rnd < m_tileStyles[i]->weight){iTile = i;break;}rnd -= m_tileStyles[i]->weight;}}minBlock->tileMasks.reset();minBlock->tileMasks.set(iTile);//std::cout << "collapsed to " << m_tileStyles[iTile].name;//熵发生改变 清除四边mask重新计算clearCache(minBlock);}//移除已经坍缩 或坍缩失败的blockBlock** it = m_unCollapsedBlocks;for (int i=0;i<m_unCollapsedBlockNum;){if ((*it)->entropy <=0){//swap((*it),m_unCollapsedBlocks[m_unCollapsedBlockNum-1]);(*it) = m_unCollapsedBlocks[m_unCollapsedBlockNum-1];m_unCollapsedBlockNum--;}else{++it;++i;}}return minBlock;
}void WFC2D::InsertNeighbours(Block* block)
{for(int i=0;i<DirNum;++i){if (block->neighbours[i])    m_dirtyBlocks.insert(block->neighbours[i]);}
}void WFC2D::PropagateChanges(Block* block)
{m_dirtyBlocks.clear();InsertNeighbours(block);while (!m_dirtyBlocks.empty()){std::set<Block*>::iterator it = m_dirtyBlocks.begin();Block* block = *it;m_dirtyBlocks.erase(it);if ( updateTileMasks(block)){clearCache(block);//熵发生改变 清除四边mask重新计算InsertNeighbours(block);}}
}bool WFC2D::updateTileMasks(Block* block)
{if (block->tileMasks.count() == 1){ return false; }Bitset before = block->tileMasks;for (int i=0;i<DirNum;++i){if (block->neighbours[i]){Block*  neighBlock = block->neighbours[i];int     edge = opDir((Dir)i);Bitset& edgeMask = neighBlock->edgeMasks[edge];{//edgeMask已经清除 要重新计算if (edgeMask.none()){if(neighBlock->tileMasks.none()){//如果block塌陷失败 则所有边都可以邻接 减少失败的block 而不是传递失败edgeMask.set();//全1}else{edgeMask.reset();//全0const int TileStyleNum = m_tileStyles.size();for (int i = 0; i < TileStyleNum; i++){if (neighBlock->tileMasks[i]){edgeMask |= m_tileStyles[i]->edgeMasks[edge]; }}}}}//四条边的连接限制都要满足block->tileMasks &= edgeMask;}}bool bChange  = before != block->tileMasks;//if (bChange) //  std::cout << pos << " before:\n" << before << ", after:\n" << block->tileMasks << "\n";if (block->tileMasks.none()){//std::cout << "Contradiction in TileMasks " << pos.x << "|" << pos.y << " (" << index << ")\n";}//if (block->tileMasks.count() == 1)//{//   int a = 0; //collapsed//}return bChange;
}bool WFC2D::GenTileStyles(const char* filename )
{File file;if(!file.Fopen(filename,"rt")){return false;}file.ReadString();int tileCount = file.ReadInt();char name[256];char path[256];int  weight;char symmetry[64];for (int i=0;i<tileCount;++i){file.ReadString(name,256);file.ReadString(path,256);file.ReadInt(weight);file.ReadString(symmetry,64);int sym;StringToSymmetry(symmetry,sym);if (!AddTileStyle(name,path,weight, (Symmetry)sym)){return false;}}file.ReadString(name,256);file.ReadString(path,256);errorTileStyle.Init("Error", path, X,0);file.ReadString(name,256);file.ReadString(path,256);unknownTileStyle.Init("Unknown", path, X,0);//添加对称{const int TileStyleNum = m_tileStyles.size();for (int i = 0; i < TileStyleNum; i++){TileStyle* tile = m_tileStyles[i];AddSymmetryTileStyles(tile);}}//添加连接边file.ReadString(name,256);if (stricmp(name,"auto edges")==0){{const int TileStyleNum = m_tileStyles.size();for (int i = 0; i < TileStyleNum; i++){TileStyle* tile = m_tileStyles[i];AutoTileEdges(tile);}}{//输出初稿 因为不对称的边旋转后可能逆向变成新的边,所以旋转后的tile也要写在配置文件里(虽然有可能根本不存在不对称的边)const int TileStyleNum = m_tileStyles.size();OutputDebugText(StrFormat("<\"edges\" ,%d> \n",TileStyleNum));for (int i = 0; i < TileStyleNum; i++){TileStyle* tile = m_tileStyles[i];//OutputDebugText(StrFormat("   \"%s\"        %d %d %d %d\n",tile->name,tile->edges[0],tile->edges[1],tile->edges[2],tile->edges[3]));//for 3D//第一个tile务必做成空接int emptyEdge = -1;//0;OutputDebugText(StrFormat(" \"%s\"        %d %d %d %d %d %d\n",tile->name,emptyEdge,tile->edges[Left],emptyEdge,tile->edges[Right],tile->edges[Bottom],tile->edges[Top]));}}}else if (stricmp(name,"edges")==0){int tileNum = file.ReadInt();Assert(tileNum<=m_tileStyles.size(),"");int edges[DirNum];for (int i=0;i<tileNum;++i){file.ReadString(name,256);file.ReadIntArray(m_tileStyles[i]->edges,DirNum);} }MatchTileStyles();////添加特殊连接边file.ReadString(name,256);if (stricmp(name,"special edges")==0){int cnt = file.ReadInt();for (int i=0;i<cnt;++i){int A = file.ReadInt();int B = file.ReadInt();AddMatch(A,B);}}return true;
}int WFC2D::AddEdgeStyle(const rgba* pixels_,int len)
{for (int e = 0; e < m_unifyEdges.size(); e++){if (len!=m_unifyEdges[e]->len){continue;}bool match = true;for (int i = 0; i < len; i++){if (pixels_[i] != m_unifyEdges[e]->pixels[i]){match = false;break;}}//unsigned char colorA[4];//unsigned char colorB[4];//int  dif;//unsigned int  sum = 0;//for (int i = 0; i < len; i++)//{//    (*(rgba*)colorA)  = pixels_[i];//  (*(rgba*)colorB)  = m_unifyEdges[e]->pixels[i];//   for (int j=0;j<4;j++)//   {//     dif = int(colorA[j]) - int(colorB[j]);//       if (dif<0) //            dif = -dif;//      sum += dif;// }//}//if (sum >len*30*4)//{//    match = false;//}smooth//unsigned char colorA[3][4];//unsigned char colorB[3][4];//int  dif;//unsigned int  sum = 0;//for (int i = 1; i < len-1; i++)//{//  (*(rgba*)colorA[0])  = pixels_[i-1];// (*(rgba*)colorB[0])  = m_unifyEdges[e]->pixels[i-1];//  (*(rgba*)colorA[1])  = pixels_[i];//   (*(rgba*)colorB[1])  = m_unifyEdges[e]->pixels[i];//    (*(rgba*)colorA[2])  = pixels_[i+1];//    (*(rgba*)colorB[2])  = m_unifyEdges[e]->pixels[i+1];// for (int j=0;j<4;j++)//   {//     dif = (int(colorA[0][j])+int(colorA[1][j])+int(colorA[2][j])) //         - (int(colorB[0][j])+int(colorB[1][j])+int(colorB[2][j])) ;//     if (dif<0) dif = -dif;//        sum += dif;// }//}//if (sum >len*30*4*3)//{//  match = false;//}if (match){return e;}}TileEdgeStyle* edge = new TileEdgeStyle;edge->pixels = new rgba[len];for(int i=0;i<len;i++){edge->pixels[i] = pixels_[i];}edge->len = len;edge->edgeID = m_unifyEdges.size();m_unifyEdges.push_back(edge);return edge->edgeID;
}void WFC2D::AutoTileEdges(TileStyle* style)
{TextureData* image = style->image;int W = image->GetWidth();int H = image->GetHeight();int W_1 = image->GetWidth() - 1;int H_1 = image->GetHeight() - 1;rgba edgePixels[4][256];unsigned char pixels[4];for (int x = 0; x < W; x++){image->GetPixel(x,0,pixels);edgePixels[Bottom][x] = (*(rgba*)pixels);image->GetPixel(x,H_1,pixels);edgePixels[Top][x] = (*(rgba*)pixels);}for (int y = 0; y < H; y++){image->GetPixel(0,y,pixels);edgePixels[Left][y] = (*(rgba*)pixels);image->GetPixel(W_1,y,pixels);edgePixels[Right][y] = (*(rgba*)pixels);}for (int i=0;i<DirNum;i++){style->edges[i] = AddEdgeStyle(edgePixels[i],W);}
}void WFC2D::MatchTileStyles()
{if (m_tileStyles.size() > MaxTileNum){//}for (int edge = 0; edge < DirNum; edge++){int opEdge = opDir((Dir)edge);const int TileStyleNum = m_tileStyles.size();for (int i = 0; i < TileStyleNum; ++i){for (int j = i; j < TileStyleNum; j++){if (m_tileStyles[i]->edges[edge] == m_tileStyles[j]->edges[opEdge]){m_tileStyles[i]->edgeMasks[edge].set(j);m_tileStyles[j]->edgeMasks[opEdge].set(i);}}}}
}void WFC2D::AddMatch( int edgeIDA,int edgeIDB )
{if (edgeIDA<0 //|| edgeIDA>=m_unifyEdges.size() ){//return;}int TileNum = m_tileStyles.size();TileStyle** tileI,**tileJ;for (int edge = 0; edge < DirNum; edge++){int opEdge = opDir((Dir)edge);tileI = &m_tileStyles[0];for (int i = 0; i < TileNum; i++,tileI++){if ((*tileI)->edges[edge] == edgeIDA){tileJ = &m_tileStyles[0];for (int j = 0; j < TileNum; j++,++tileJ){if ((*tileJ)->edges[opEdge]==edgeIDB){(*tileI)->edgeMasks[edge].set(j);(*tileJ)->edgeMasks[opEdge].set(i);}}} }}
}bool WFC2D::AddTileStyle(const char* name,const char* path,int weight,Symmetry sym )
{TextureData* image = new TextureData;bool res = image->LoadTexture(path );if (res==false){return false;}TileStyle* style = new TileStyle;style->Init(image, name,sym, weight);m_tileStyles.push_back(style);return true;
}bool WFC2D::AddSymmetryTileStyles(TileStyle* tileStyle )
{TextureData* image = tileStyle->image; int rot = PossibleRots(tileStyle->sym)-1;char buf[256];unsigned char pixels_[4];TextureData* lastImage = tileStyle->image;TileStyle*   lastStyle = tileStyle; for (int i = 0; i < rot; i++){int W = lastImage->GetWidth();int H = lastImage->GetHeight();TextureData* rotImage = new TextureData;rotImage->AllocTexture(H, W,RS_RGBA); //switch width and height//rot copyfor (int x = 0; x < W; x++){for (int y = 0; y < H; y++){lastImage->GetPixel(x,y,pixels_);//逆时针rotImage->SetPixel(H-y-1,x,pixels_);}}sprintf_s(buf,"%sRotated%c",tileStyle->name,char('A'+i));TileStyle* rotStyle = new TileStyle;rotStyle->Init(rotImage, buf, tileStyle->sym,tileStyle->weight);//L型三边相同//T型左右两边不对称,旋转后可能逆向,已经是新边。 上下两边旋转后不变//P型//默认所有边都是对称的rotStyle->edges[Left]  = lastStyle->edges[Top];rotStyle->edges[Right] = lastStyle->edges[Bottom];rotStyle->edges[Top]   = lastStyle->edges[Right];rotStyle->edges[Bottom] = lastStyle->edges[Left];m_tileStyles.push_back(rotStyle);lastImage = rotImage;lastStyle = rotStyle;}if (tileStyle->sym==P){int W = image->GetWidth();int H = image->GetHeight();TextureData* mirrorImage = new TextureData;mirrorImage->AllocTexture(H, W,RS_RGBA);  for (int x = 0; x < W; x++){for (int y = 0; y < H; y++){image->GetPixel(H-x,y,pixels_);mirrorImage->SetPixel(x,y,pixels_);}}sprintf_s(buf,"%sMirrored",tileStyle->name);TileStyle* mirrorStyle = new TileStyle;mirrorStyle->Init(mirrorImage, buf, tileStyle->sym,tileStyle->weight);mirrorStyle->edges[Left] = tileStyle->edges[Right];mirrorStyle->edges[Right] = tileStyle->edges[Left];m_tileStyles.push_back(mirrorStyle);TextureData* lastImage = mirrorImage;lastStyle = mirrorStyle;for (int i = 0; i < rot; i++){int W = lastImage->GetWidth();int H = lastImage->GetHeight();TextureData* rotmage = new TextureData;rotmage->AllocTexture(H, W,RS_RGBA); //switch width and heightfor (int x = 0; x < W; x++){for (int y = 0; y < H; y++){lastImage->GetPixel(x,y,pixels_);rotmage->SetPixel(H-y-1,x,pixels_);}}sprintf_s(buf,"%sMirroredRotated%c",tileStyle->name,char('A'+i));TileStyle* rotStyle = new TileStyle;rotStyle->Init(rotmage, buf, tileStyle->sym,tileStyle->weight);//默认所有边都是对称的rotStyle->edges[Left]  = lastStyle->edges[Top];rotStyle->edges[Right] = lastStyle->edges[Bottom];rotStyle->edges[Top]   = lastStyle->edges[Right];rotStyle->edges[Bottom] = lastStyle->edges[Left];m_tileStyles.push_back(rotStyle);lastImage = rotmage;lastStyle = rotStyle;}}return true;
}bool WFC2D::StringToSymmetry( const char* str,int& type )
{if      (stricmp(str,"X")==0){ type = X; return true;}else if (stricmp(str,"T")==0){ type = T; return true;}else if (stricmp(str,"I")==0){ type = I; return true;}else if (stricmp(str,"L")==0){ type = L; return true;}else if (stricmp(str,"B")==0){ type = backslash; return true;}else if (stricmp(str,"P")==0){ type = P; return true;}type = unknow;return false;
}int  WFC2D::PossibleRots( const Symmetry &symmetry )
{switch (symmetry) {case Symmetry::X:return 1;case Symmetry::I:case Symmetry::backslash:return 2;case Symmetry::T:case Symmetry::L:return 4;case Symmetry::P:return 4;default:return 0;}
}int TestWaveFuncCollapse( )
{//return 0;//const char* cfg = "data/test/wavefunccollpase/Knots/Knots.wfc";//const char* cfg = "data/test/wavefunccollpase/Circuit/circuit.wfc";const char* cfg = "data/test/wavefunccollpase/castle/castle.wfc";//const char* cfg = "data/test/wavefunccollpase/circles/circles.wfc";//const char* cfg = "data/test/wavefunccollpase/floorplan/floorplan.wfc";//const char* cfg = "data/test/wavefunccollpase/summer/summer.wfc";WFC2D wfc(30, 30);wfc.GenTileStyles(cfg);wfc.Solute();TextureData finalImage;wfc.RendTileStyles(finalImage);finalImage.SaveToFile((RemoveFileExtension(cfg)+"_tile.png").c_str());wfc.RendToTexture(finalImage);finalImage.SaveToFile(ReplaceFileExtension(cfg,"png").c_str());return true;
}

3D版源码: 和2D版大同小已,不贴了

波函数坍缩算法的实现源码相关推荐

  1. 图解kubernetes服务打散算法的实现源码

    在分布式调度中为了保证服务的高可用和容灾需求,通常都会讲服务在多个区域.机架.节点上平均分布,从而避免单点故障引起的服务不可用,在k8s中自然也实现了该算法即SelectorSpread, 本文就来学 ...

  2. 高斯正算C语言程序,一个老师给的高斯投影正、反算c++源码(最新整理)

    <一个老师给的高斯投影正.反算c++源码(最新整理)>由会员分享,可在线阅读,更多相关<一个老师给的高斯投影正.反算c++源码(最新整理)(4页珍藏版)>请在人人文库网上搜索. ...

  3. 高斯投影正反算C语言程序代码,一个老师给的高斯投影正反算c++源码.doc

    一个老师给的高斯投影正反算c源码 //高斯投影正.反算 //6度带宽?? 54年北京坐标系 //高斯投影由经纬度 Unit:DD 反算大地坐标 含带号,Unit:Metres void GaussPr ...

  4. 万字字符长文带你了解遗传算法(有几个算例源码)

    一.遗传算法的基本概念 简单而言,遗传算法使用群体搜索技术,将种群代表一组问题解, 通过对当前种群施加选择.交叉和变异等一系列遗传操作来产生新-一代的种群,并逐步使种群进化到包含近似最优解的状态.由于 ...

  5. Unity使用波函数坍缩 (Wave Collapse Function)算法生成随机地图

    在游戏领域和人工智能领域有一个随机生成地图用的比较多的算法叫做波函数坍缩 (Wave Collapse Function)算法,这个算法可以根据自己定制的规则生成随机地图. 根据波函数坍缩算法的源码 ...

  6. Blender源码探究之布料解算(一)——源码定位

    最近因为项目的安排,需要了解布料解算的源码.于是搭建了Blender的编译环境,查看里面的源码. 对源码探索了一段时间后,找到了cloth.c文件中的clothModifier_do方法.这个就是关于 ...

  7. PX4姿态解算源码原理理解

    PX4源码原理理解一.主要参考资料链接:1.1 取PX4源码一小部分姿态解算来进行讲解姿态解算源码中文注释:https://blog.csdn.net/zouxu634866/article/deta ...

  8. 【Java集合源码剖析】TreeMap源码剖析

    2019独角兽企业重金招聘Python工程师标准>>> 前言 本文不打算延续前几篇的风格(对所有的源码加入注释),因为要理解透TreeMap的所有源码,对博主来说,确实需要耗费大量的 ...

  9. ****CI框架源码阅读笔记7 配置管理组件 Config.php

    http://blog.csdn.net/ohmygirl/article/details/41041597 一个灵活可控的应用程序中,必然会存在大量的可控参数(我们称为配置),例如在CI的主配置文件 ...

最新文章

  1. python插入排序演示源码
  2. android中获取mac地址8.0,关于Android8.0以下手机获取蓝牙Mac地址的问题和扫描周围的手机蓝牙问题 下篇...
  3. ubuntu下如何设置apache的启动和重启
  4. wordpress添加html媒体文件,WordPress“添加媒体”文件时只显示上传到当前文章的附件图片...
  5. 飞鸽传书2007用户需求就是做好需求处理
  6. [深度学习NPL]word2vector总结与理解
  7. SQL Server应用程序中的高级SQL注入[转]
  8. 数据结构题及c语言版答案周桂红版,数据结构习题与答案.pdf
  9. 遗传算法框架deap简介与使用
  10. python实验原理_python实验报告5
  11. 阿里云的这群“疯子”
  12. python-tkinter使用方法——转载(二)
  13. arcgis 9.3/10.2.2/10.5版本下载
  14. C语言入门——自我介绍
  15. Python基础数据类型详解:字典(补充)
  16. 棋类对战小游戏(VS2012 MFC基于对话框)
  17. Python中出现TypeError: ‘int‘ object is not iterable的解决方法
  18. 西瓜书第一章课后习题答案
  19. 【DLT学习笔记2】-- 什么是DLT?(Diagnostic Log and Trace)
  20. SpringBoot - @RequestBody、@ResponseBody的使用场景

热门文章

  1. 微信小程序报错 net::ERR_CONNECTION_TIMED_OUT
  2. 区块链100讲:STO技术标准(ST-20,SRC20,R-TOKEN,DS Protocol,ERC1400,ERC1410)
  3. 清华山维eps提示计算机内存不足,系统提示“内存不足”的原因及解决方法
  4. 办公室局域网打印机共享设置
  5. iec-design(飞冰)一款强大的设计语言
  6. 多层FPC软板起泡与分层的原因和解决方法
  7. 威纶通MT8070iH触摸屏如何恢复出厂设置以及查看IP地址
  8. Webmin -- Command Shell
  9. 学校计算机机房辐射测试,民工们注意了,机房对人体的辐射到底有多大
  10. python儿童版ppt_python儿童编程讲解.ppt