Place scheduled ores across regions
This commit is contained in:
parent
d01e576dcc
commit
9ffd2b19ba
3 changed files with 217 additions and 0 deletions
|
|
@ -101,6 +101,23 @@ func (r *decorationRegion) getBiome(x, y, z int) (uint16, bool) {
|
|||
return chunk.GetBiome(x&15, y, z&15), true
|
||||
}
|
||||
|
||||
func (r *decorationRegion) biomeAllowsFeature(set *worldgen.FeatureSet, feature string, stage int, position worldgen.FeaturePosition) bool {
|
||||
id, ok := r.getBiome(position.X, position.Y, position.Z)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
biome, ok := set.Biomes[biomeNameByID(id)]
|
||||
if !ok || stage < 0 || stage >= len(biome.Features) {
|
||||
return false
|
||||
}
|
||||
for _, name := range biome.Features[stage] {
|
||||
if name == feature {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *decorationRegion) sourceBiomes() []string {
|
||||
seen := make(map[uint16]bool)
|
||||
var names []string
|
||||
|
|
|
|||
139
internal/world/region_ores.go
Normal file
139
internal/world/region_ores.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"regionio/internal/worldgen"
|
||||
)
|
||||
|
||||
func (r *decorationRegion) placeScheduledOres(seed int64) error {
|
||||
set, err := worldgen.LoadFeatureSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schedule, err := r.scheduledFeatures(undergroundOresStage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
random, decorationSeed := worldgen.DecorationRandom(seed, int(r.sourceX), int(r.sourceZ))
|
||||
origin := worldgen.FeaturePosition{X: int(r.sourceX) << 4, Y: MinY, Z: int(r.sourceZ) << 4}
|
||||
for _, scheduled := range schedule {
|
||||
placed := set.Placed[scheduled.Name]
|
||||
configured := set.Configured[placed.Feature]
|
||||
if configured.Type != "minecraft:ore" {
|
||||
continue
|
||||
}
|
||||
config, err := set.Ore(placed.Feature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targets, ok := resolveOreTargets(set, config)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
random.SetFeatureSeed(decorationSeed, scheduled.Index, undergroundOresStage)
|
||||
context := r.placementContext(func(position worldgen.FeaturePosition) bool {
|
||||
return r.biomeAllowsFeature(set, scheduled.Name, undergroundOresStage, position)
|
||||
})
|
||||
positions, err := set.PlacementPositions(scheduled.Name, random, origin, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, position := range positions {
|
||||
placeOreEllipsoidRegion(r, random, position.X, position.Y, position.Z, config.Size, config.DiscardAirExposure, targets)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func placeOreEllipsoidRegion(region *decorationRegion, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) {
|
||||
angle := random.NextFloat() * float32(math.Pi)
|
||||
extent := float32(size) / 8.0
|
||||
x0 := float64(originX) + math.Sin(float64(angle))*float64(extent)
|
||||
x1 := float64(originX) - math.Sin(float64(angle))*float64(extent)
|
||||
z0 := float64(originZ) + math.Cos(float64(angle))*float64(extent)
|
||||
z1 := float64(originZ) - math.Cos(float64(angle))*float64(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 := float32(i) / float32(size)
|
||||
randomScale := random.NextDouble() * float64(size) / 16.0
|
||||
radius := ((float64(worldgen.MthSin(float64(float32(math.Pi)*t)))+1.0)*randomScale + 1.0) / 2.0
|
||||
spheres[i] = sphere{
|
||||
x: x0 + (x1-x0)*float64(t), y: y0 + (y1-y0)*float64(t),
|
||||
z: z0 + (z1-z0)*float64(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++ {
|
||||
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++ {
|
||||
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 := region.getBlock(x, y, z)
|
||||
for _, target := range targets {
|
||||
if !target.replaceables[current] || discard > 0 && random.NextFloat() < float32(discard) && exposedToAirRegion(region, x, y, z) {
|
||||
continue
|
||||
}
|
||||
region.setBlock(x, y, z, target.state)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func exposedToAirRegion(region *decorationRegion, 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}} {
|
||||
if region.getBlock(x+offset[0], y+offset[1], z+offset[2]) == StateAir {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
61
internal/world/region_ores_test.go
Normal file
61
internal/world/region_ores_test.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package world
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestScheduledRegionOresAreDeterministic(t *testing.T) {
|
||||
makeRegion := func() *decorationRegion {
|
||||
var chunks []*Chunk
|
||||
for cx := int32(-1); cx <= 1; cx++ {
|
||||
for cz := int32(-1); cz <= 1; cz++ {
|
||||
chunk := NewChunk(cx, cz, BiomePlains)
|
||||
for y := MinY; y < MinY+WorldHeight; y++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
for z := 0; z < 16; z++ {
|
||||
chunk.setBlockRaw(x, y, z, StateStone)
|
||||
}
|
||||
}
|
||||
}
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
}
|
||||
region, err := newDecorationRegion(chunks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := region.setSource(0, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return region
|
||||
}
|
||||
|
||||
a, b := makeRegion(), makeRegion()
|
||||
if err := a.placeScheduledOres(12345); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.placeScheduledOres(12345); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
changed := 0
|
||||
for cx := int32(-1); cx <= 1; cx++ {
|
||||
for cz := int32(-1); cz <= 1; cz++ {
|
||||
left := a.chunks[[2]int32{cx, cz}]
|
||||
right := b.chunks[[2]int32{cx, cz}]
|
||||
for y := MinY; y < MinY+WorldHeight; y++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
for z := 0; z < 16; z++ {
|
||||
got := left.GetBlock(x, y, z)
|
||||
if got != right.GetBlock(x, y, z) {
|
||||
t.Fatalf("regions differ at chunk (%d,%d) block (%d,%d,%d)", cx, cz, x, y, z)
|
||||
}
|
||||
if got != StateStone {
|
||||
changed++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed == 0 {
|
||||
t.Fatal("scheduled ore pass changed no blocks")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue