波函数坍缩算法的实现源码

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版大同小已,不贴了


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值