// 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) } } } }