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

无边无际的虚拟城市来了!能走能飞的Demo,一火再火的“波函数坍缩”开源算法... 安妮 栗子 发自 凹非寺 量子位 报道 | 公众号 QbitAI神说,要有光。于是有了光。神说,要有一座城。于是有了一座城。神说,这座城要无限大。神啊你别再说了,那是另外... 阅读详情

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

完


 

初探测量导致波函数的坍缩不是意识的参与而是信息的作用 初探测量导致波函数的坍缩不是意识的参与而是信息的作用 nam3002@163.com 摘要:波函数的理解,波函数怎么坍缩的,任何事情都可以叠加吗?测量导致波函数坍缩与信息的作用,测量引起波函数坍缩与意识有关系吗。   近来有许多文章说量子力学与佛教、神学理念相通。基督教、天主教的传教士都有较丰富的数学、物理、天文、等自然科学知识。明朝时期西方传教士利玛窦因由丰富的数学天文物理知识,受到朝廷 阅读详情

相关推荐

世界模型与薛定谔方程的三重映射:PyTorch实战指南

世界模型是一种以潜变量编码未来可能性的可微分预测器,其核心在于构建紧凑、动态、可演化的内部状态表征;它与量子力学中的波函数具有数学同构性——二者均以概率幅形式描述叠加态,并通过‘测量’(观测/采样)实现坍缩。这种对应并非哲学类比,而是源于信息压缩、演化自洽与贝叶斯更新的共通计算原理。技术价值体现在提升模型鲁棒性、可解释性与跨场景泛化能力,已成功应用于工业异常检测、具身智能仿真与教育AI助教等强不确定性场景。本文聚焦工程落地,将世界模型设计思想拆解为PyTorch可实现的损失函数约束与潜空间正则化机制。

diegouyi3472的博客 362

学习MaximGumin的“波函数坍缩”算法用来自动生成与输入有相似性的位图

目标 Maxim Gumin 在2016年开发并开源的算法(MIT许可) 步骤: 1.翻译介绍 2.研究源代码 3.搞到Unity中,然后尝试自己用一个新图案 翻译 先从效果上看,这个算法可以生成与输入位图具有局部相似性的结果: 这里 “局部相似性” 的具体含义是: 对于结果中任意一个N乘N尺寸的图案,都能在输入中找到。 N乘N尺寸图案在输入中的分布概率,应该和结果中的N乘N尺寸图案的分布概率是相近的,尽管后者图案的总数更多。换句话说:在“结果”中遇到某一个特定图案的概率,应该趋近于在“输入”中遇到这

主要记录了工作之余胡乱学习的知识,大部分是入门级别的内容。 主要是为了自己未来有用到时能快速上手,相当于是个备忘录。 当然,也很开心其他人可以从中找到对自己有用的信息。^_^ 6979

带交叉算子的量子粒子群优化:原理、参数调优与MATLAB实现

群智能优化算法中,粒子群优化(PSO)以其结构简单、收敛快速而被广泛使用,但在高维多模态函数上容易陷入局部最优和早熟收敛。量子粒子群优化(QPSO)通过波函数概率分布取代速度-位移更新,拓宽了搜索范围,却也带来收敛末期精细搜索不足的问题。交叉算子的引入为QPSO注入了显式的种群多样性维持机制,通过粒子间信息交换平衡探索与开发。围绕这一改进方案,重点阐述了QPSO的核心迭代公式、三种交叉策略(全局最优交叉、个体历史交叉、随机配对算术交叉)的作用机理,并结合MATLAB代码展示了关键参数的设置与动态调优方法,适

weixin_29053577的博客 246

在Unity中使用WFC算法无限生成城市

https://www.fgba.net/sitemap.xml

woliuqiangdong的博客 2976

波函数折叠python实现_游戏开发 _wfc_波函数坍缩_python_代码_下载

波函数折叠python实现。它支持 1D、2D、3D 样本

SWAT模型非点源模拟原理

目录引言模型概述非点源污染模拟原理物质迁移内容过程营养物农药病原菌运移河道水质模型营养元素的地表水体循环河道水质模拟藻类模拟氮元素磷元素生化需氧量及溶解氧重金属的运移蓄水体水质模拟 引言 今天无意接触到这一块,就记录下来了。 模型概述 1、流域中非点源污染的来源主要是农业化肥和农药的施用。 2、氮、磷等营养物和农药的迁移和转化取决于化合物在土壤环境中经历的转化过程。 3、SWAT模型可以模拟水文相应单元内氮、磷完整的营养物循环过程和任何一种农药的降解过程。 4、SWAT模拟流域内泥沙、藻类、溶解氧、有机污染

DoWayChan的博客 4879

又一种随机赛道生成算法

之前在赛车游戏中介绍过一种利用迷宫拼接随机赛道的算法,另外之前介绍的波函数坍缩算法也很适合用来拼接随机赛道。今天介绍另外一种随机赛道算法。 路径变形算法pathdeform,熟悉3dsmax建模的应该很熟悉,有点类似loft放样。 比如将一个圆沿着一条直线路径拉伸变成圆柱的过程就是放样,但是放样法产生赛道每个路段都是一样的缺乏美观。路径变形则是将一段预先制作好的直线路段扭曲贴合到赛道路径上。预先制作好的路段可以做的很复杂可以包含建筑、树木、坑洞等所有想要的东西。 不同于直接拼接,路段将可以适用曲线。

twopointfive的专栏 1012

ARM Memory Compiler实战指南:从参数配置到GDSII生成的全流程解析

本文详细解析了ARM Memory Compiler在SRAM设计中的全流程应用,从基础参数配置到GDSII文件生成。涵盖SRAM类型选择、核心参数配置策略、高级优化技巧及TSMC 28nm工艺下的实战案例,帮助IC设计工程师高效实现存储器模块的优化设计。

game4的博客 418

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

List item 在游戏领域和人工智能领域有一个随机生成地图用的比较多的算法叫做波函数坍缩 (Wave Collapse Function)算法,这个算法可以根据自己定制的规则生成随机地图。 根据波函数坍缩算法的源码 Wave Collapse Function C#版,我将源码修改适配到了Unity上使用。 这种随机生成地图或者说图片纹理,在做Roguelike游戏地图或者生成地板纹理横版...

u012740992的专栏 1万+

伪傻瓜式安装sql server2008【附带安装包】

安装各式各样的软件无疑是学习过程中…较…为烦躁的一部分 木有醋 但还是开始吧 为什么这是伪呢? 因为刚开始天真无邪的我真的是傻瓜式安装(迷之自信),结果就B了(打开配置管理器的瞬间….一片空白) BaiduCloud分享 红领巾在何方?? Link链接:https://pan.baidu.com/s/1NZCxP0Y27dBPrgHoRWLDyQ...

EuniBoom的博客 6万+

pycharm添加Anaconda解释器显示不出来

因为我之前下载的是pycharm最新版本2024.1的,所以我抱着试一试的心态卸载重装了2021.3.3的,创建新文件的时候立刻就自己识别好了conda环境。就是图中画圈的地方,之前一直显示为python的图标,但是再Python Interpreter后面跟的路径确实是我下载anaconda的地方。另外一种方法就是使用Virtualenv Environment方式配置,但是我的pycharm还是没有识别出conda的图标。首先是这位的方法,先添加系统解释器,再重启,但是我的图标还是python。

2202_75803271的博客 4006

如何快速掌握WaveFunctionCollapse:从量子启发到无限生成的完整指南

WaveFunctionCollapse是一款基于量子力学思想的创新工具,能够从单个示例图像生成相似的位图和 tilemap。它通过模拟"波函数坍缩"过程,让计算机自动创作出局部结构与输入样本高度一致的新内容,广泛应用于游戏关卡设计、像素艺术创作和 procedural 内容生成领域。 ## 🌟 WaveFunctionCollapse的核心原理 ### 什么是波函数坍缩算法? WaveF

gitblog_00878的博客 966

HighTec安装使用

HighTec安装使用

qq_43552564的博客 3835

AI 写的初稿一眼假?lieflat-less-ai-tone 用 520 万字语料把 AI 味逐条改出去

「真正的壁垒不是技术,而是认知」——这种句子 AI 能连写一百遍,也是它让整篇稿子一眼假的根源。lieflat-less-ai-tone 是一个**去 AI 味 skill**:基于 1,169 篇、约 520 万汉字的两轮实测语料,把「像 AI」拆成能定位、能复测的修改规则。读完后你能直接拿一篇 AI 初稿去跑,换回可核对的修改报告与前后对照。 ## 先看效果:去 AI 味前后差多少 [![

gitblog_00543的博客 118

【Coze搞钱实战】24. 从零实现精准用户画像:Coze+企业微信打造智能导购推送系统(附完整代码)

电商运营中,“客服重复询问用户需求、无法精准推荐”是转化率低的核心痛点——日均200人次咨询,73%的问题是重复的,首单转化率仅14%。本文基于Coze平台sys_uuid用户识别功能、企业微信API与多维表格,打造“用户行为→自动打标→标签存储→智能推送”全链路方案:通过分类分层标签体系捕捉用户偏好,Coze自动识别用户并打标,飞书多维表格存储标签数据,企业微信触发个性化推送。实操后可使客服重复询问率下降62%,意向客户转化率提升81%,首单转化率提升64%。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 543

Claude Code Cli 源码泄露事件全解析:从51万行代码中拆解AI Agent的记忆护城河

2026年3月,Anthropic的AI编程助手Claude Code意外泄露51万行核心代码,暴露了其多Agent架构、六级权限验证系统及创新的记忆机制。记忆系统采用四层设计:用户自定义框架、自动记忆提取、智能上下文压缩和纯文件存储。最独特的是"自动做梦"功能,AI会定期整理记忆,类似人类睡眠记忆巩固。代码还揭示未发布功能如电子宠物系统,但也暴露权限系统漏洞和代码质量问题。这次泄露展现了AI助手核心技术的复杂性,特别是其记忆系统如何通过透明可编辑的设计建立用户信任。

program哲学 469

终极对比:WaveFunctionCollapse与传统纹理合成算法的7大核心差异

WaveFunctionCollapse(波函数坍缩)算法是一种革命性的纹理生成技术,它从量子力学中汲取灵感,能够从单个示例图像生成无限多样的位图和瓦片地图。与传统纹理合成算法相比,WFC算法在游戏开发、数字艺术和建筑设计领域展现出了独特的优势。 ## 🔍 算法原理对比:从根本差异说起 ### WaveFunctionCollapse:基于约束的智能生成 WFC算法通过分析输入图像的局部模式

gitblog_00963的博客 1103

【某音电商】protobuf聊天协议逆向

某音电商私信、聊天消息protobuf协议逆向分析与解析

吴秋霖的博客 2315

【unity实战】使用Unity程序化生成3D随机地牢(附项目源码)

程序生成技术通过预设规则系统创造内容,其核心在于确定性而非随机性,种子值确保生成结果可重复。文章介绍了程序生成在游戏开发中的应用,包括墙面生成等技术实现,通过Unity代码演示了如何创建和渲染随机变化的墙壁效果。关键点包括:程序生成与随机生成的区别、种子值的重要性、多种生成技术的应用场景,以及基于变换矩阵的高效批量渲染方法。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 2249

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

天赐范式第16天:【硬核物理】哥本哈根学派沉默了:用纯经典混沌模拟出量子双缝干涉,量子力学统计特性可能是高维相空间混沌投影的观点(附源码)

摘要:本文通过经典混沌系统模拟量子双缝干涉现象,提出量子力学统计特性可能是高维相空间混沌投影的观点。研究采用受驱摆+高斯势垒模型,使用RK45积分器对2000个粒子进行模拟,成功复现双缝干涉统计特征(分布重叠度49.7%)。结果表明,确定性混沌系统在引入初值扰动后,可涌现出类似量子概率分布的行为。该发现支持超决定论观点,认为量子随机性可能源于底层混沌系统的复杂性。研究提供了完整Python复现代码,为经典-量子对应关系提供了新证据。

snowoftheworld的博客 572

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

仅限高级开发者掌握的秘技:C语言实现多量子比特门操作(附完整源码)

掌握量子计算核心技能,本文深入解析量子门的 C 语言实现方法,适用于多量子比特系统仿真与量子算法开发。采用高效矩阵运算与位操作优化,提升计算性能,代码简洁可扩展,适合高性能场景。附完整源码,值得收藏。

FuncTide的博客 745

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【量子计算实战必备】:Top 5量子编程调试工具全面测评

掌握量子编程工具的调试功能,高效定位量子电路错误。本文测评5款主流工具,涵盖Qiskit、Cirq等适用场景与断点追踪、模拟验证等核心方法,提升开发效率。量子计算实战必备,值得收藏

Instrulink的博客 860

揭秘VSCode中Azure QDK断点失效之谜:3步精准定位量子程序Bug

解决VSCode Azure QDK断点失效难题,3步精准定位量子程序Bug。适用于量子算法调试场景,通过检查调试配置、量子模拟器兼容性与代码注入逻辑,快速修复断点问题。提升调试效率,保障开发流畅性,值得收藏。

InstrIsle的博客 439

探索量子力学:从零开始理解量子世界

量子力学是现代物理学中一门神秘且具有深远意义的学科,它揭示了微观世界的规律和性质,颠覆了我们对自然的常识认知。虽然量子力学的数学形式看起来有些抽象复杂,但只要我们有足够的好奇心和探索欲望,从零开始理解量子力学并非难事。本文将带领读者逐步探索量子力学的奥秘,无需具备过多的数学和物理背景,我们将以通俗易懂的方式解释基本概念和原理。通过一些简单的代码示例,读者将有机会体验到量子计算的神奇之处。让我们开始这段奇妙的量子之旅吧!量子力学作为一门极具挑战性和深远意义的学科,通过本文我们初步探索了其基本原理和数学形式。

努力是为了站在万人之中,成为别人的光 5280

从像素到利润:探索WaveFunctionCollapse的开源商业模式与无限创意

WaveFunctionCollapse是一款基于量子力学思想的位图和瓦片地图生成工具,能够从单个示例图像出发,通过算法自动生成具有相似风格和结构的无限纹理与地图。这款开源项目不仅为游戏开发、视觉设计等领域提供了强大的创意工具,更为开发者和创业者开辟了多元化的商业变现路径。 ## 什么是WaveFunctionCollapse?核心技术解析 WaveFunctionCollapse的核心原理源

gitblog_00474的博客 634

用量子纠缠通灵:呼叫已故架构师改bug

《量子测试通灵术:破解遗留系统的时空密码》摘要: 本文创新性提出"量子测试通灵"方法论,针对无文档遗留系统构建逆向工程测试体系。通过量子纠缠隐喻,建立API调用链分析(Swagger反编译)和覆盖率热力图(Jacoco)的缺陷定位技术,结合AI代码意图推测(CodeBERT)与Git考古学,破解已故架构师的"量子锁"。实战案例展示如何用Docker封装历史环境、设计量子态用例矩阵,通过热修复和蓝绿部署实现"灵魂补丁"。文章最终构建测试通灵师能力模型

2501_94445272的博客 339
上一篇: 两款扫雷游戏3D版源码
下一篇: 流体类游戏实现
娄磊lorry
博客等级 码龄19年 95粉丝 47原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值