Files
ngzz-mc/internal/worldgen/gen.go

310 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package worldgen 确定性世界生成(场景/世界生成.md §3 管线)。
//
// 管线(pass 顺序固定):高度图 → 生物群系地表 → 洞穴 → 矿物 → 树(后处理)。
// 确定性铁律:同 seed 同参数 → 逐字节相同结果(噪声实例按 seed 派生、遍历顺序固定)。
package worldgen
import (
"fmt"
"mc/internal/block"
"mc/internal/chunk"
"github.com/ojrac/opensimplex-go"
)
// 生物群系(简化版:4 种,世界生态.md §2 双噪声判定)。
type biome uint8
const (
biomePlains biome = iota
biomeDesert
biomeForest
biomeSnow
)
// 海平面与地形常量。
const (
seaLevel = 62
dirtDepth = 3 // 地表下泥土厚度
)
// Generator 世界生成器。
type Generator struct {
seed int64
height opensimplex.Noise // 高度图(2D,fBm 近似:双 octave)
temp opensimplex.Noise // 温度噪声(生物群系)
humid opensimplex.Noise // 湿度噪声(生物群系)
cave opensimplex.Noise // 洞穴 3D 密度场
ore opensimplex.Noise // 矿物分布 3D
biomeScale float64
heightScale float64
caveDensity float64
// 常用方块状态(启动时从注册表解析一次)
stone, grass, dirt, bedrock, sand, water, snowB, log, leaves block.State
coal, iron, gold, diamond, redstone block.State
planks, cobble, torch block.State // 村庄建筑用
}
// New 创建生成器;必须的方块缺注册时报错。
func New(reg *block.Registry, seed int64, biomeScale, heightScale, caveDensity float64) (*Generator, error) {
g := &Generator{
seed: seed,
height: opensimplex.New(seed),
temp: opensimplex.New(seed + 101),
humid: opensimplex.New(seed + 202),
cave: opensimplex.New(seed + 303),
ore: opensimplex.New(seed + 404),
biomeScale: biomeScale,
heightScale: heightScale,
caveDensity: caveDensity,
}
// 解析必须方块 ID(缺失直接报错,避免生成到一半失败)
var err error
if g.stone, err = must(reg, "stone"); err != nil {
return nil, err
}
if g.grass, err = must(reg, "grass_block"); err != nil {
return nil, err
}
if g.dirt, err = must(reg, "dirt"); err != nil {
return nil, err
}
if g.bedrock, err = must(reg, "bedrock"); err != nil {
return nil, err
}
if g.sand, err = must(reg, "sand"); err != nil {
return nil, err
}
if g.water, err = must(reg, "water"); err != nil {
return nil, err
}
if g.snowB, err = must(reg, "snow_block"); err != nil {
return nil, err
}
if g.log, err = must(reg, "oak_log"); err != nil {
return nil, err
}
if g.leaves, err = must(reg, "oak_leaves"); err != nil {
return nil, err
}
if g.coal, err = must(reg, "coal_ore"); err != nil {
return nil, err
}
if g.iron, err = must(reg, "iron_ore"); err != nil {
return nil, err
}
if g.gold, err = must(reg, "gold_ore"); err != nil {
return nil, err
}
if g.diamond, err = must(reg, "diamond_ore"); err != nil {
return nil, err
}
if g.redstone, err = must(reg, "redstone_ore"); err != nil {
return nil, err
}
if g.planks, err = must(reg, "oak_planks"); err != nil {
return nil, err
}
if g.cobble, err = must(reg, "cobblestone"); err != nil {
return nil, err
}
if g.torch, err = must(reg, "torch"); err != nil {
return nil, err
}
return g, nil
}
// must 从注册表取方块状态,缺注册返回错误。
func must(reg *block.Registry, name string) (block.State, error) {
id, ok := reg.ID(name)
if !ok {
return block.Air, fmt.Errorf("worldgen: 方块 %q 未注册", name)
}
return block.NewState(id, 0), nil
}
// heightAt 返回世界列 (wx, wz) 的地形高度(y)。
func (g *Generator) heightAt(wx, wz int32) int {
h := g.height.Eval2(float64(wx)*g.heightScale, float64(wz)*g.heightScale)
h += 0.35 * g.height.Eval2(float64(wx)*g.heightScale*2.5+100, float64(wz)*g.heightScale*2.5+100)
y := 64 + int(h*24)
if y < 8 {
y = 8
}
if y > chunk.Height-16 {
y = chunk.Height - 16
}
return y
}
// biomeAt 温度 + 湿度双噪声 → 生物群系(世界生态.md §2.1)。
func (g *Generator) biomeAt(wx, wz int32) biome {
t := g.temp.Eval2(float64(wx)*g.biomeScale, float64(wz)*g.biomeScale)
h := g.humid.Eval2(float64(wx)*g.biomeScale*0.9+50, float64(wz)*g.biomeScale*0.9+50)
switch {
case t > 0.35 && h < -0.1:
return biomeDesert
case t < -0.35:
return biomeSnow
case h > 0.3:
return biomeForest
default:
return biomePlains
}
}
// surfaceID 群系地表方块。
func (g *Generator) surfaceID(b biome) block.State {
switch b {
case biomeDesert:
return g.sand
case biomeSnow:
return g.snowB
default:
return g.grass
}
}
// isCave 洞穴判定(3D 密度场阈值;基岩层与高空不挖,世界生成.md §6 踩坑)。
func (g *Generator) isCave(wx, wy, wz int32) bool {
if wy > 56 || wy < 6 {
return false
}
n := g.cave.Eval3(float64(wx)*0.08, float64(wy)*0.12, float64(wz)*0.08)
n += 0.5 * g.cave.Eval3(float64(wx)*0.16+31, float64(wy)*0.24+17, float64(wz)*0.16+71)
return n > g.caveDensity
}
// oreAt 矿物分布(按深度区间,挖矿与矿物.md §2)。
func (g *Generator) oreAt(wx, wy, wz int32) block.State {
if wy > 128 {
return g.stone
}
n := g.ore.Eval3(float64(wx)*0.12, float64(wy)*0.12, float64(wz)*0.12)
switch {
case wy <= 16 && n > 0.86:
return g.diamond
case wy <= 16 && n > 0.78:
return g.redstone
case wy <= 32 && n > 0.74:
return g.gold
case wy <= 64 && n > 0.70:
return g.iron
case n > 0.64:
return g.coal
}
return g.stone
}
// Generate 生成整个区块(确定性;结果不标存档脏——生成属纯函数可重算)。
func (g *Generator) Generate(cx, cz int32) *chunk.Chunk {
c := chunk.New(cx, cz)
for lz := 0; lz < 16; lz++ {
for lx := 0; lx < 16; lx++ {
wx := int32(lx) + cx*16
wz := int32(lz) + cz*16
h := g.heightAt(wx, wz)
b := g.biomeAt(wx, wz)
surface := g.surfaceID(b)
// 海平面以下但高于地形:灌水
for y := h + 1; y < seaLevel; y++ {
c.Fill(lx, y, lz, g.water)
}
// 地形柱:基岩 → 石头/矿物 → 泥土 → 地表
for y := 0; y <= h; y++ {
wy := int32(y)
var s block.State
switch {
case y == 0:
s = g.bedrock
case y <= 4:
s = g.stone
case y == h:
s = surface
case y >= h-dirtDepth:
s = g.dirt
default:
s = g.stone
}
// 洞穴掏空(海平面以下洞内灌水)
if y > 4 && g.isCave(wx, wy, wz) {
if y <= seaLevel {
s = g.water
} else {
s = block.Air
}
} else if s == g.stone {
s = g.oreAt(wx, wy, wz)
}
c.Fill(lx, y, lz, s)
}
}
}
// 树后处理 pass(世界生态.md §3.1:边缘留白避免跨区块,树冠不出界)
g.placeTrees(c)
// 村庄结构 pass(村民系统.md §2:网格撒点 + 水井 + 房屋)
g.placeVillage(c, cx, cz)
return c
}
// placeTrees 在森林/平原撒树;方块边缘 3 格内不撒(跨区块安全的最小方案)。
func (g *Generator) placeTrees(c *chunk.Chunk) {
for lz := 3; lz <= 12; lz++ {
for lx := 3; lx <= 12; lx++ {
wx := int32(lx) + c.CX*16
wz := int32(lz) + c.CZ*16
h := g.heightAt(wx, wz)
if h+7 >= chunk.Height {
continue
}
b := g.biomeAt(wx, wz)
prob := 0.80
if b == biomePlains {
prob = 0.95
}
if b != biomeForest && b != biomePlains {
continue
}
n := g.ore.Eval2(float64(wx)*0.2+777, float64(wz)*0.2+777)
if n < prob {
continue
}
// 只长在草上
if c.Block(lx, h, lz) != g.grass {
continue
}
g.growTree(c, lx, h, lz)
}
}
}
// growTree 生成一棵橡树:树干 5 格 + 树冠 5×5×3。
func (g *Generator) growTree(c *chunk.Chunk, lx, h, lz int) {
for y := h + 1; y <= h+5; y++ {
c.Fill(lx, y, lz, g.log)
}
for y := h + 3; y <= h+5; y++ {
for dx := -2; dx <= 2; dx++ {
for dz := -2; dz <= 2; dz++ {
x, z := lx+dx, lz+dz
if x < 0 || x >= 16 || z < 0 || z >= 16 {
continue // 树冠不出区块(边缘留白已保证不越界)
}
if dx == 0 && dz == 0 && y <= h+5 {
continue // 树干位置
}
// 角部修剪,更接近原版树冠形状
if y == h+5 && (dx*dx+dz*dz >= 5) {
continue
}
c.Fill(x, y, z, g.leaves)
}
}
}
}