RegionIO/internal/world/placed_features.go

190 lines
5.4 KiB
Go

package world
import (
"math"
"regionio/internal/worldgen"
)
const undergroundOresStage = 6
type resolvedOreTarget struct {
state uint16
replaceables map[uint16]bool
}
func placeVanillaOres(c *Chunk, seed int64, cx, cz int32, biomes *[16][16]string) {
set, err := worldgen.LoadFeatureSet()
if err != nil {
panic("world: loading feature datapack: " + err.Error())
}
random, decorationSeed := worldgen.DecorationRandom(seed, int(cx), int(cz))
seen := make(map[string]bool)
for bx := 0; bx < 16; bx += 4 {
for bz := 0; bz < 16; bz += 4 {
biome := set.Biomes[biomes[bx][bz]]
if len(biome.Features) <= undergroundOresStage {
continue
}
for featureIndex, name := range biome.Features[undergroundOresStage] {
if seen[name] {
continue
}
seen[name] = true
placed := set.Placed[name]
configured := set.Configured[placed.Feature]
if configured.Type != "minecraft:ore" {
continue
}
config, err := set.Ore(placed.Feature)
if err != nil {
panic(err)
}
plan, err := set.Placement(name)
if err != nil {
panic(err)
}
targets, ok := resolveOreTargets(set, config)
if !ok {
continue
}
random.SetFeatureSeed(decorationSeed, featureIndex, undergroundOresStage)
if plan.RarityChance > 0 && random.NextIntN(int32(plan.RarityChance)) != 0 {
continue
}
attempts := plan.Count.Sample(random)
for attempt := 0; attempt < attempts; attempt++ {
x := int(random.NextIntN(16))
z := int(random.NextIntN(16))
y := plan.SampleY(random, MinY, WorldHeight)
placeOreEllipsoid(c, random, x, y, z, config.Size, config.DiscardAirExposure, targets)
}
}
}
}
}
func resolveOreTargets(set *worldgen.FeatureSet, config worldgen.OreFeatureConfig) ([]resolvedOreTarget, bool) {
targets := make([]resolvedOreTarget, 0, len(config.Targets))
for _, target := range config.Targets {
state, ok := nameToStateID(target.State.Name, nil)
if !ok || target.Target.PredicateType != "minecraft:tag_match" {
return nil, false
}
replaceables := make(map[uint16]bool)
for _, name := range set.BlockTags[target.Target.Tag] {
id, ok := nameToStateID(name, nil)
if ok {
replaceables[id] = true
}
}
if len(replaceables) == 0 {
return nil, false
}
targets = append(targets, resolvedOreTarget{state: state, replaceables: replaceables})
}
return targets, true
}
func placeOreEllipsoid(c *Chunk, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) {
angle := float64(random.NextFloat()) * math.Pi
extent := float64(size) / 8.0
x0 := float64(originX+8) + math.Sin(angle)*extent
x1 := float64(originX+8) - math.Sin(angle)*extent
z0 := float64(originZ+8) + math.Cos(angle)*extent
z1 := float64(originZ+8) - math.Cos(angle)*extent
y0 := float64(originY + int(random.NextIntN(3)) - 2)
y1 := float64(originY + int(random.NextIntN(3)) - 2)
type sphere struct{ x, y, z, radius float64 }
spheres := make([]sphere, size)
for i := 0; i < size; i++ {
t := float64(i) / float64(size)
radius := (math.Sin(math.Pi*t) + 1.0) * (float64(random.NextDouble())*float64(size)/16.0 + 1.0) / 2.0
spheres[i] = sphere{
x: x0 + (x1-x0)*t,
y: y0 + (y1-y0)*t,
z: z0 + (z1-z0)*t,
radius: radius,
}
}
for i := range spheres {
if spheres[i].radius < 0 {
continue
}
for j := i + 1; j < len(spheres); j++ {
if spheres[j].radius < 0 {
continue
}
dx, dy, dz := spheres[i].x-spheres[j].x, spheres[i].y-spheres[j].y, spheres[i].z-spheres[j].z
dr := spheres[i].radius - spheres[j].radius
if dr*dr > dx*dx+dy*dy+dz*dz {
if dr > 0 {
spheres[j].radius = -1
} else {
spheres[i].radius = -1
break
}
}
}
}
visited := make(map[[3]int]bool)
for _, sphere := range spheres {
if sphere.radius < 0 {
continue
}
minX, maxX := int(math.Floor(sphere.x-sphere.radius)), int(math.Floor(sphere.x+sphere.radius))
minY, maxY := int(math.Floor(sphere.y-sphere.radius)), int(math.Floor(sphere.y+sphere.radius))
minZ, maxZ := int(math.Floor(sphere.z-sphere.radius)), int(math.Floor(sphere.z+sphere.radius))
for x := minX; x <= maxX; x++ {
if x < 0 || x >= 16 {
continue
}
dx := (float64(x) + 0.5 - sphere.x) / sphere.radius
if dx*dx >= 1 {
continue
}
for y := minY; y <= maxY; y++ {
if y < MinY || y >= MinY+WorldHeight {
continue
}
dy := (float64(y) + 0.5 - sphere.y) / sphere.radius
if dx*dx+dy*dy >= 1 {
continue
}
for z := minZ; z <= maxZ; z++ {
if z < 0 || z >= 16 {
continue
}
dz := (float64(z) + 0.5 - sphere.z) / sphere.radius
pos := [3]int{x, y, z}
if dx*dx+dy*dy+dz*dz >= 1 || visited[pos] {
continue
}
visited[pos] = true
current := c.GetBlock(x, y, z)
for _, target := range targets {
if !target.replaceables[current] || discard > 0 && random.NextFloat() < float32(discard) && exposedToAir(c, x, y, z) {
continue
}
c.SetBlock(x, y, z, target.state)
break
}
}
}
}
}
}
func exposedToAir(c *Chunk, x, y, z int) bool {
for _, offset := range [][3]int{{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}} {
nx, ny, nz := x+offset[0], y+offset[1], z+offset[2]
if nx < 0 || nx >= 16 || nz < 0 || nz >= 16 {
continue
}
if c.GetBlock(nx, ny, nz) == StateAir {
return true
}
}
return false
}