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 set
class 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, //rot0
T, //rot3
I, //rot1
L, //rot3
backslash,
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;
//tilelib
std::vector<TileStyle*> m_tileStyles;
TileStyle errorTileStyle;
TileStyle unknownTileStyle;
//edgelib
std::vector<TileEdgeStyle*> m_unifyEdges;
public:
WFC2D(int height, int width);
~WFC2D();
void Solute();
void RendToTexture(TextureData& image);
void RendTileStyles (TextureData& image);
//坍缩熵最小的block
Block* 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 lib
bool 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];
else
it = &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()
{
//选择熵(可能性)最小的block
Block* 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);
}
//移除已经坍缩 或坍缩失败的block
Block** 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();//全0
const 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 copy
for (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 height
for (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版大同小已,不贴了
完