本篇内容是生成起伏的地表和生物群系,這部分應該是MC中的世界看上去變化多端的重點了
先回顧一下provideChunk:
public Chunk provideChunk(int x, int z)
{
this.rand.setSeed((long)x * L + (long)z * L);
ChunkPrimer chunkprimer = new ChunkPrimer();
// 生成基本地形(沒有洞穴等建築)
this.setBlocksInChunk(x, z, chunkprimer);
// 生成16*16的生物群系數組
this.biomesForGeneration = this.worldObj.getWorldChunkManager().loadBlockGeneratorData(this.biomesForGeneration, x * , z * , , );
// 把石頭替換成生物群系相應的方塊
this.replaceBlocksForBiome(x, z, chunkprimer, this.biomesForGeneration);
// ...
}
生成基本地形
MC不是直接生成一個高度圖,而是生成一個三維的“密度數組”,不知道為什麼這樣做。我猜是為了生成石窟這種地形,但是這樣也有可能生成浮空島
// 生成隻有石頭、水、空氣的基本地形
public void setBlocksInChunk(int xChunk, int zChunk, ChunkPrimer chunkprimer)
{
// 生成10*10的生物群系數組
this.biomesForGeneration = this.worldObj.getWorldChunkManager().getBiomesForGeneration(this.biomesForGeneration, xChunk * - , zChunk * - , , );
// 生成field_147434_q這個數組,我認為它代表密度
// 尺寸5*5*33,索引高位是x,中位是z,低位是y
this.func_147423_a(xChunk * , , zChunk * );
// xyzHigh作為field_147434_q索引,要和xyzLow組合才是方塊坐标
for (int xHigh = ; xHigh < ; ++xHigh)
{
int xIndex = xHigh * ;
int xIndex_1 = (xHigh + ) * ;
for (int zHigh = ; zHigh < ; ++zHigh)
{
int xzIndex = (xIndex + zHigh) * ;
int xz_1Index = (xIndex + zHigh + ) * ;
int x_1zIndex = (xIndex_1 + zHigh) * ;
int x_1z_1Index = (xIndex_1 + zHigh + ) * ;
for (int yHigh = ; yHigh < ; ++yHigh)
{
// 這些線性插值後得到的density3才是每個方塊的密度
double density = this.field_147434_q[xzIndex + yHigh];
double densityZ1 = this.field_147434_q[xz_1Index + yHigh];
double densityX1 = this.field_147434_q[x_1zIndex + yHigh];
double densityX1Z1 = this.field_147434_q[x_1z_1Index + yHigh];
// 步長,後同
double densityStep = (this.field_147434_q[xzIndex + yHigh + ] - density) / ;
double densityZ1Step = (this.field_147434_q[xz_1Index + yHigh + ] - densityZ1) / ;
double densityX1Step = (this.field_147434_q[x_1zIndex + yHigh + ] - densityX1) / ;
double densityX1Z1Step = (this.field_147434_q[x_1z_1Index + yHigh + ] - densityX1Z1) / ;
for (int yLow = ; yLow < ; ++yLow)
{
double density2 = density;
double density2Z1 = densityZ1;
double density2Step = (densityX1 - density) / ;
double density2Z1Step = (densityX1Z1 - densityZ1) / ;
for (int xLow = ; xLow < ; ++xLow)
{
double density3 = density2;
double density3Step = (density2Z1 - density2) / ;
for (int zLow = ; zLow < ; ++zLow)
{
if (density3 > D) // 密度>0認為是固體
{
// 設定為石頭
chunkprimer.setBlockState(xHigh * + xLow, yHigh * + yLow, zHigh * + zLow, Blocks.stone.getDefaultState());
}
else if (yHigh * + yLow < this.settings.seaLevel) // 密度<0且在海平面以下認為是液體
{
// 設定為預設液體(水或岩漿)
chunkprimer.setBlockState(xHigh * + xLow, yHigh * + yLow, zHigh * + zLow, this.field_177476_s.getDefaultState());
}
// 否則是空氣
density3 += density3Step;
}
density2 += density2Step;
density2Z1 += density2Z1Step;
}
density += densityStep;
densityZ1 += densityZ1Step;
densityX1 += densityX1Step;
densityX1Z1 += densityX1Z1Step;
}
}
}
}
}
生成密度數組
這部分應該是最重要的,然而我還是沒完全看懂,這裡把自己的了解寫出來了
// 生成field_147434_q數組(密度數組),areaXZ為區塊坐标*4,areaY恒為0
private void func_147423_a(int areaX, int areaY, int areaZ)
{
// 生成5*5*1的噪聲
this.field_147426_g = this.noiseGen6.generateNoiseOctaves(this.field_147426_g, areaX, areaZ, , , (double)this.settings.depthNoiseScaleX, (double)this.settings.depthNoiseScaleZ, (double)this.settings.depthNoiseScaleExponent);
float coordinateScale = this.settings.coordinateScale;
float heightScale = this.settings.heightScale;
// 生成3個5*5*33的噪聲
this.field_147427_d = this.field_147429_l.generateNoiseOctaves(this.field_147427_d, areaX, areaY, areaZ, , , , (double)(coordinateScale / this.settings.mainNoiseScaleX), (double)(heightScale / this.settings.mainNoiseScaleY), (double)(coordinateScale / this.settings.mainNoiseScaleZ));
this.field_147428_e = this.field_147431_j.generateNoiseOctaves(this.field_147428_e, areaX, areaY, areaZ, , , , (double)coordinateScale, (double)heightScale, (double)coordinateScale);
this.field_147425_f = this.field_147432_k.generateNoiseOctaves(this.field_147425_f, areaX, areaY, areaZ, , , , (double)coordinateScale, (double)heightScale, (double)coordinateScale);
int index = ;
int xzIndex = ;
for (int x1 = ; x1 < ; ++x1)
{
for (int z1 = ; z1 < ; ++z1)
{
float scale = ;
float groundYOffset = ;
float totalWeight = ;
// 中心點生物群系
BiomeGenBase centerBiome = this.biomesForGeneration[x1 + + (z1 + ) * ];
// 求scale和groundYOffset的權重平均值
for (int x2 = ; x2 < ; ++x2)
{
for (int z2 = ; z2 < ; ++z2)
{
BiomeGenBase biome = this.biomesForGeneration[x1 + x2 + (z1 + z2) * ];
float curGroundYOffset = this.settings.biomeDepthOffSet + biome.minHeight * this.settings.biomeDepthWeight; // biomeDepthOffSet=0
// maxHeight是反混淆錯誤吧,應該是scale
float curScale = this.settings.biomeScaleOffset + biome.maxHeight * this.settings.biomeScaleWeight; // biomeScaleOffset=0
// 世界類型是巨型生物群系
if (this.field_177475_o == WorldType.AMPLIFIED && curGroundYOffset > )
{
curGroundYOffset = + curGroundYOffset * ;
curScale = + curScale * ;
}
// parabolicField為 10 / √(該點到中心點的距離^2 + 0.2)
float weight = this.parabolicField[x2 + z2 * ] / (curGroundYOffset + );
if (biome.minHeight > centerBiome.minHeight)
{
weight /= ;
}
scale += curScale * weight;
groundYOffset += curGroundYOffset * weight;
totalWeight += weight;
}
}
scale = scale / totalWeight;
groundYOffset = groundYOffset / totalWeight;
scale = scale * + ;
groundYOffset = (groundYOffset * - ) / ;
// 這部分大概是取一個-0.36~0.125的随機數,這個随機數決定了起伏的地表
double random = this.field_147426_g[xzIndex] / D;
if (random < D)
{
random = -random * D;
}
random = random * D - D;
if (random < D)
{
random = random / D;
if (random < -D)
{
random = -D;
}
random = random / D;
random = random / D;
}
else
{
if (random > D)
{
random = D;
}
random = random / D;
}
double _groundYOffset = (double)groundYOffset;
double _scale = (double)scale;
// groundYOffset有-0.072~0.025的變動量
_groundYOffset = _groundYOffset + random * D;
/*_groundYOffset = _groundYOffset * (double)this.settings.baseSize / 8.0D;
double groundY = (double)this.settings.baseSize + _groundYOffset * 4.0D;*/
// 這個是大概的地面y坐标,實際上也沒有保證不會出現浮空島...
double groundY = (double)this.settings.baseSize * (D + _groundYOffset / D); // baseSize=8.5,應該代表了平均地表高度68
for (int y = ; y < ; ++y) // 注意這個y*8才是最終的y坐标
{
// result偏移量,這個是負數則趨向固體,是正數則趨向液體和空氣
double offset = ((double)y - groundY) * (double)this.settings.stretchY * D / D / _scale; // scale大概在0.1~0.2這樣...
if (offset < D)
{
offset *= D;
}
// 并不保證lowerLimit < upperLimit,不過沒有影響
double lowerLimit = this.field_147428_e[index] / (double)this.settings.lowerLimitScale; // lowerLimitScale=512
double upperLimit = this.field_147425_f[index] / (double)this.settings.upperLimitScale; // upperLimitScale=512
double t = (this.field_147427_d[index] / D + D) / D;
// 這個函數t < 0則取lowerLimit,t > 1則取upperLimit,否則以t為參數在上下限間線性插值
double result = MathHelper.denormalizeClamp(lowerLimit, upperLimit, t) - offset;
if (y > ) // y = 30~32
{
// 在原result和-10之間線性插值,這樣y > 240的方塊就會越來越少,最後全變成空氣
double t2 = (double)((float)(y - ) / );
result = result * (D - t2) + -D * t2;
}
this.field_147434_q[index] = result;
++index;
}
++xzIndex;
}
}
}
生成生物群系相應的方塊
GenLayerVoronoiZoom
在第二篇裡逆向GenLayer時,這層在前面沒有用到,是以我就沒有研究,但是這裡就用到了
我查了一下,Voronoi圖是用來劃分平面的,使得每個區域内的點到它所在區域的種子點的距離比到其它區域種子點的距離近
它的上一層是GenLayerRiverMix,之前用它生成了10*10的生物群系ID,這裡就要擴充成16*16的
public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight)
{
areaX = areaX - ;
areaY = areaY - ;
int parentAreaX = areaX >> ;
int parentAreaY = areaY >> ;
int parentWidth = (areaWidth >> ) + ;
int parentHeight = (areaHeight >> ) + ;
// parentRes是本層的1/4
int[] parentRes = this.parent.getInts(parentAreaX, parentAreaY, parentWidth, parentHeight);
int tmpWidth = parentWidth - << ;
int tmpHeight = parentHeight - << ;
// 臨時結果
int[] tmp = IntCache.getIntCache(tmpWidth * tmpHeight);
for (int parentY = ; parentY < parentHeight - ; ++parentY)
{
// parent目前點的值
int parentValue = parentRes[parentY * parentWidth];
// parent目前點y+1點的值
int parentValueY1 = parentRes[(parentY + ) * parentWidth];
for (int parentX = ; parentX < parentWidth - ; ++parentX)
{
// 随機取parent的4個點的坐标
this.initChunkSeed((parentX + parentAreaX) << , (parentY + parentAreaY) << );
double vertex1X = ((double)this.nextInt() / D - D) * D;
double vertex1Y = ((double)this.nextInt() / D - D) * D;
this.initChunkSeed((parentX + parentAreaX + ) << , (parentY + parentAreaY) << );
double vertex2X = ((double)this.nextInt() / D - D) * D + D;
double vertex2Y = ((double)this.nextInt() / D - D) * D;
this.initChunkSeed((parentX + parentAreaX) << , (parentY + parentAreaY + ) << );
double vertex3X = ((double)this.nextInt() / D - D) * D;
double vertex3Y = ((double)this.nextInt() / D - D) * D + D;
this.initChunkSeed((parentX + parentAreaX + ) << , (parentY + parentAreaY + ) << );
double vertex4X = ((double)this.nextInt() / D - D) * D + D;
double vertex4Y = ((double)this.nextInt() / D - D) * D + D;
int parentValueX1 = parentRes[parentX + + parentY * parentWidth] % ;
int parentValueX1Y1 = parentRes[parentX + + (parentY + ) * parentWidth] % ;
for (int yLow = ; yLow < ; ++yLow)
{
int tmpIndex = ((parentY << ) + yLow) * tmpWidth + (parentX << );
for (int xLow = ; xLow < ; ++xLow)
{
// 目前點到parent各點的距離的平方
double dist1 = ((double)yLow - vertex1Y) * ((double)yLow - vertex1Y) + ((double)xLow - vertex1X) * ((double)xLow - vertex1X);
double dist2 = ((double)yLow - vertex2Y) * ((double)yLow - vertex2Y) + ((double)xLow - vertex2X) * ((double)xLow - vertex2X);
double dist3 = ((double)yLow - vertex3Y) * ((double)yLow - vertex3Y) + ((double)xLow - vertex3X) * ((double)xLow - vertex3X);
double dist4 = ((double)yLow - vertex4Y) * ((double)yLow - vertex4Y) + ((double)xLow - vertex4X) * ((double)xLow - vertex4X);
// 取最近點的值
if (dist1 < dist2 && dist1 < dist3 && dist1 < dist4)
{
tmp[tmpIndex++] = parentValue;
}
else if (dist2 < dist1 && dist2 < dist3 && dist2 < dist4)
{
tmp[tmpIndex++] = parentValueX1;
}
else if (dist3 < dist1 && dist3 < dist2 && dist3 < dist4)
{
tmp[tmpIndex++] = parentValueY1;
}
else
{
tmp[tmpIndex++] = parentValueX1Y1;
}
}
}
// parent目前點移動x+1
parentValue = parentValueX1;
parentValueY1 = parentValueX1Y1;
}
}
int[] result = IntCache.getIntCache(areaWidth * areaHeight);
// tmp和result尺寸可能不同,這裡把tmp左上角部分複制到result
for (int resultY = ; resultY < areaHeight; ++resultY)
{
System.arraycopy(tmp, (resultY + areaY % ) * tmpWidth + (areaX % ), result, resultY * areaWidth, areaWidth);
}
return result;
}
替換方塊
public void replaceBlocksForBiome(int chunkX, int chunkZ, ChunkPrimer chunkprimer, BiomeGenBase[] biomes)
{
net.minecraftforge.event.terraingen.ChunkProviderEvent.ReplaceBiomeBlocks event = new net.minecraftforge.event.terraingen.ChunkProviderEvent.ReplaceBiomeBlocks(this, chunkX, chunkZ, chunkprimer, this.worldObj);
net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event);
if (event.getResult() == net.minecraftforge.fml.common.eventhandler.Event.Result.DENY) return;
double coordinateScale = D;
// 生成*的噪聲
this.stoneNoise = this.field_147430_m.func_151599_a(this.stoneNoise, (double)(chunkX * ), (double)(chunkZ * ), , , coordinateScale * D, coordinateScale * D, D);
for (int x = ; x < 16; ++x)
{
for (int z = ; z < 16; ++z)
{
BiomeGenBase biomegenbase = biomes[z + x * ];
biomegenbase.genTerrainBlocks(this.worldObj, this.rand, chunkprimer, chunkX * + x, chunkZ * + z, this.stoneNoise[z + x * ]);
}
}
}
類名
net.minecraft.world.biome.BiomeGenBase
public void genTerrainBlocks(World worldIn, Random rand, ChunkPrimer chunkPrimerIn, int x, int z, double noise)
{
this.generateBiomeTerrain(worldIn, rand, chunkPrimerIn, x, z, noise);
}
public final void generateBiomeTerrain(World worldIn, Random rand, ChunkPrimer chunkPrimerIn, int x, int z, double noise)
{
int seaLevel = worldIn.getSeaLevel();
IBlockState topBlock = this.topBlock; // 最頂部的方塊,比如草方塊
IBlockState fillerBlock = this.fillerBlock; // 頂部以下用來填充的方塊,比如泥土
// 還需要填充幾個方塊
int rest = -;
// 需要填充的方塊數
int fillerCount = (int)(noise / D + D + rand.nextDouble() * D);
int xLow = x % ;
int zLow = z % ;
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
for (int y = ; y >= ; --y)
{
if (y <= rand.nextInt())
{
// y <= 5随機替換為基岩
chunkPrimerIn.setBlockState(zLow, y, xLow, Blocks.bedrock.getDefaultState());
}
else
{
IBlockState curBlock = chunkPrimerIn.getBlockState(zLow, y, xLow);
if (curBlock.getBlock().getMaterial() == Material.air) // 還在空氣層,不需要填充
{
rest = -;
}
else if (curBlock.getBlock() == Blocks.stone) // 石頭層
{
if (rest == -) // 還沒開始填充
{
// 到這裡y應該在石頭與空氣和水的分界面
if (fillerCount <= ) // 不需要填充
{
topBlock = null;
fillerBlock = Blocks.stone.getDefaultState();
}
else if (y >= seaLevel - && y <= seaLevel + ) // 需要填充,且在海平面附近
{
topBlock = this.topBlock;
fillerBlock = this.fillerBlock;
} // 其他情況說明在海底,topBlock、fillerBlock不變
// 在海平面以下,且不需要填充
if (y < seaLevel && (topBlock == null || topBlock.getBlock().getMaterial() == Material.air))
{
// 根據溫度頂部替換為冰或水
if (this.getFloatTemperature(pos.set(x, y, z)) < F)
{
topBlock = Blocks.ice.getDefaultState();
}
else
{
topBlock = Blocks.water.getDefaultState();
}
}
// 開始填充頂部
rest = fillerCount;
if (y >= seaLevel - ) // 海平面以上
{
// 頂部替換為topBlock
chunkPrimerIn.setBlockState(zLow, y, xLow, topBlock);
}
else if (y < seaLevel - - fillerCount) // 海底
{
topBlock = null;
// 接下來使用石頭填充
fillerBlock = Blocks.stone.getDefaultState();
// 頂部替換為砂礫
chunkPrimerIn.setBlockState(zLow, y, xLow, Blocks.gravel.getDefaultState());
}
else // 海平面以下但不是太深的地方
{
// 沒有頂部了,直接替換為fillerBlock
chunkPrimerIn.setBlockState(zLow, y, xLow, fillerBlock);
}
}
else if (rest > ) // 正在填充
{
--rest;
// 替換為fillerBlock
chunkPrimerIn.setBlockState(zLow, y, xLow, fillerBlock);
// 如果之前填充了沙子則下面還要填充沙岩
if (rest == && fillerBlock.getBlock() == Blocks.sand)
{
rest = rand.nextInt() + Math.max(, y - );
fillerBlock = fillerBlock.getValue(BlockSand.VARIANT) == BlockSand.EnumType.RED_SAND ? Blocks.red_sandstone.getDefaultState() : Blocks.sandstone.getDefaultState();
}
} // rest = 0說明填充完畢
}
}
}
}