Match vanilla ore sphere math
This commit is contained in:
parent
c68c5c2744
commit
cb60050801
5 changed files with 110 additions and 114 deletions
|
|
@ -108,12 +108,37 @@ func flattenBlockTag(set *worldgen.FeatureSet, tag string, visiting map[string]b
|
||||||
}
|
}
|
||||||
|
|
||||||
func placeOreEllipsoid(c *Chunk, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) {
|
func placeOreEllipsoid(c *Chunk, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) {
|
||||||
|
spheres := buildOreSpheres(random, originX, originY, originZ, size)
|
||||||
|
walkOreBlocks(spheres, func(x, y, z int) {
|
||||||
|
localX := x - int(c.X)*16
|
||||||
|
if localX < 0 || localX >= 16 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if y < MinY || y >= MinY+WorldHeight {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
localZ := z - int(c.Z)*16
|
||||||
|
if localZ < 0 || localZ >= 16 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current := c.GetBlock(localX, y, localZ)
|
||||||
|
for _, target := range targets {
|
||||||
|
if !target.replaceables[current] || discard > 0 && random.NextFloat() < float32(discard) && exposedToAir(c, localX, y, localZ) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.SetBlock(localX, y, localZ, target.state)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildOreSpheres(random worldgen.RandomSource, originX, originY, originZ, size int) []oreSphere {
|
||||||
angle := random.NextFloat() * float32(math.Pi)
|
angle := random.NextFloat() * float32(math.Pi)
|
||||||
extent := float32(size) / 8.0
|
extent := float32(size) / 8.0
|
||||||
x0 := float64(originX) + math.Sin(float64(angle))*float64(extent)
|
x0 := float64(originX) + float64(worldgen.MthSin(float64(angle))*extent)
|
||||||
x1 := float64(originX) - math.Sin(float64(angle))*float64(extent)
|
x1 := float64(originX) - float64(worldgen.MthSin(float64(angle))*extent)
|
||||||
z0 := float64(originZ) + math.Cos(float64(angle))*float64(extent)
|
z0 := float64(originZ) + float64(worldgen.MthCos(float64(angle))*extent)
|
||||||
z1 := float64(originZ) - math.Cos(float64(angle))*float64(extent)
|
z1 := float64(originZ) - float64(worldgen.MthCos(float64(angle))*extent)
|
||||||
y0 := float64(originY + int(random.NextIntN(3)) - 2)
|
y0 := float64(originY + int(random.NextIntN(3)) - 2)
|
||||||
y1 := float64(originY + int(random.NextIntN(3)) - 2)
|
y1 := float64(originY + int(random.NextIntN(3)) - 2)
|
||||||
|
|
||||||
|
|
@ -121,7 +146,8 @@ func placeOreEllipsoid(c *Chunk, random worldgen.RandomSource, originX, originY,
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
t := float32(i) / float32(size)
|
t := float32(i) / float32(size)
|
||||||
randomScale := random.NextDouble() * float64(size) / 16.0
|
randomScale := random.NextDouble() * float64(size) / 16.0
|
||||||
radius := ((float64(worldgen.MthSin(float64(float32(math.Pi)*t)))+1.0)*randomScale + 1.0) / 2.0
|
wave := worldgen.MthSin(float64(float32(math.Pi)*t)) + float32(1.0)
|
||||||
|
radius := (float64(wave)*randomScale + 1.0) / 2.0
|
||||||
spheres[i] = oreSphere{
|
spheres[i] = oreSphere{
|
||||||
x: x0 + (x1-x0)*float64(t),
|
x: x0 + (x1-x0)*float64(t),
|
||||||
y: y0 + (y1-y0)*float64(t),
|
y: y0 + (y1-y0)*float64(t),
|
||||||
|
|
@ -149,35 +175,35 @@ func placeOreEllipsoid(c *Chunk, random worldgen.RandomSource, originX, originY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
walkOreBlocks(spheres, func(x, y, z int) {
|
return spheres
|
||||||
localX := x - int(c.X)*16
|
|
||||||
if localX < 0 || localX >= 16 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if y < MinY || y >= MinY+WorldHeight {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
localZ := z - int(c.Z)*16
|
|
||||||
if localZ < 0 || localZ >= 16 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
current := c.GetBlock(localX, y, localZ)
|
|
||||||
for _, target := range targets {
|
|
||||||
if !target.replaceables[current] || discard > 0 && random.NextFloat() < float32(discard) && exposedToAir(c, localX, y, localZ) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.SetBlock(localX, y, localZ, target.state)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkOreBlocks(spheres []oreSphere, visit func(x, y, z int)) {
|
func walkOreBlocks(spheres []oreSphere, visit func(x, y, z int)) {
|
||||||
minX, maxX, minY, maxY, minZ, maxZ := oreBounds(spheres)
|
visited := make(map[[3]int]bool)
|
||||||
for x := minX; x <= maxX; x++ {
|
for _, sphere := range spheres {
|
||||||
for y := minY; y <= maxY; y++ {
|
if sphere.radius < 0 {
|
||||||
for z := minZ; z <= maxZ; z++ {
|
continue
|
||||||
if oreContains(spheres, x, y, z) {
|
}
|
||||||
|
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++ {
|
||||||
|
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
|
||||||
|
position := [3]int{x, y, z}
|
||||||
|
if dx*dx+dy*dy+dz*dz >= 1 || visited[position] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
visited[position] = true
|
||||||
visit(x, y, z)
|
visit(x, y, z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -185,42 +211,6 @@ func walkOreBlocks(spheres []oreSphere, visit func(x, y, z int)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func oreBounds(spheres []oreSphere) (minX, maxX, minY, maxY, minZ, maxZ int) {
|
|
||||||
found := false
|
|
||||||
for _, sphere := range spheres {
|
|
||||||
if sphere.radius < 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
loX, hiX := int(math.Floor(sphere.x-sphere.radius)), int(math.Floor(sphere.x+sphere.radius))
|
|
||||||
loY, hiY := int(math.Floor(sphere.y-sphere.radius)), int(math.Floor(sphere.y+sphere.radius))
|
|
||||||
loZ, hiZ := int(math.Floor(sphere.z-sphere.radius)), int(math.Floor(sphere.z+sphere.radius))
|
|
||||||
if !found {
|
|
||||||
minX, maxX, minY, maxY, minZ, maxZ = loX, hiX, loY, hiY, loZ, hiZ
|
|
||||||
found = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
minX, maxX = min(minX, loX), max(maxX, hiX)
|
|
||||||
minY, maxY = min(minY, loY), max(maxY, hiY)
|
|
||||||
minZ, maxZ = min(minZ, loZ), max(maxZ, hiZ)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func oreContains(spheres []oreSphere, x, y, z int) bool {
|
|
||||||
for _, sphere := range spheres {
|
|
||||||
if sphere.radius < 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dx := (float64(x) + 0.5 - sphere.x) / sphere.radius
|
|
||||||
dy := (float64(y) + 0.5 - sphere.y) / sphere.radius
|
|
||||||
dz := (float64(z) + 0.5 - sphere.z) / sphere.radius
|
|
||||||
if dx*dx+dy*dy+dz*dz < 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func exposedToAir(c *Chunk, x, y, z int) bool {
|
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}} {
|
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]
|
nx, ny, nz := x+offset[0], y+offset[1], z+offset[2]
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func TestPlacedOresAreDeterministic(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkOreBlocksUsesVanillaCoordinateOrder(t *testing.T) {
|
func TestWalkOreBlocksUsesVanillaSphereOrder(t *testing.T) {
|
||||||
spheres := []oreSphere{
|
spheres := []oreSphere{
|
||||||
{x: 0.5, y: 0.5, z: 0.5, radius: 1.1},
|
{x: 0.5, y: 0.5, z: 0.5, radius: 1.1},
|
||||||
{x: 1.5, y: 0.5, z: 0.5, radius: 1.1},
|
{x: 1.5, y: 0.5, z: 0.5, radius: 1.1},
|
||||||
|
|
@ -59,7 +59,8 @@ func TestWalkOreBlocksUsesVanillaCoordinateOrder(t *testing.T) {
|
||||||
want := [][3]int{
|
want := [][3]int{
|
||||||
{-1, 0, 0},
|
{-1, 0, 0},
|
||||||
{0, -1, 0}, {0, 0, -1}, {0, 0, 0}, {0, 0, 1}, {0, 1, 0},
|
{0, -1, 0}, {0, 0, -1}, {0, 0, 0}, {0, 0, 1}, {0, 1, 0},
|
||||||
{1, -1, 0}, {1, 0, -1}, {1, 0, 0}, {1, 0, 1}, {1, 1, 0},
|
{1, 0, 0},
|
||||||
|
{1, -1, 0}, {1, 0, -1}, {1, 0, 1}, {1, 1, 0},
|
||||||
{2, 0, 0},
|
{2, 0, 0},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,16 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"regionio/internal/worldgen"
|
"regionio/internal/worldgen"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type oreDifference struct {
|
||||||
|
extra, missing int
|
||||||
|
}
|
||||||
|
|
||||||
func TestRegionOreReplayParityDiagnostic(t *testing.T) {
|
func TestRegionOreReplayParityDiagnostic(t *testing.T) {
|
||||||
if os.Getenv("REGIONIO_REGION_ORE_DIAGNOSTIC") != "1" {
|
if os.Getenv("REGIONIO_REGION_ORE_DIAGNOSTIC") != "1" {
|
||||||
t.Skip("set REGIONIO_REGION_ORE_DIAGNOSTIC=1 to run region ore replay parity")
|
t.Skip("set REGIONIO_REGION_ORE_DIAGNOSTIC=1 to run region ore replay parity")
|
||||||
|
|
@ -62,6 +67,9 @@ func TestRegionOreReplayParityDiagnostic(t *testing.T) {
|
||||||
}
|
}
|
||||||
initCarverReplaceable(carver.ReplaceableBlocks())
|
initCarverReplaceable(carver.ReplaceableBlocks())
|
||||||
var blockTotal, regionExact, regionOreMismatch, centerExact, centerOreMismatch, legacyExact, legacyOreMismatch int
|
var blockTotal, regionExact, regionOreMismatch, centerExact, centerOreMismatch, legacyExact, legacyOreMismatch int
|
||||||
|
regionByState := make(map[uint16]*oreDifference)
|
||||||
|
centerByState := make(map[uint16]*oreDifference)
|
||||||
|
legacyByState := make(map[uint16]*oreDifference)
|
||||||
for _, fixture := range fixtures {
|
for _, fixture := range fixtures {
|
||||||
var chunks []*Chunk
|
var chunks []*Chunk
|
||||||
for cx := fixture.x - 2; cx <= fixture.x+2; cx++ {
|
for cx := fixture.x - 2; cx <= fixture.x+2; cx++ {
|
||||||
|
|
@ -117,16 +125,19 @@ func TestRegionOreReplayParityDiagnostic(t *testing.T) {
|
||||||
regionExact++
|
regionExact++
|
||||||
} else if isOreState(regionGot) || isOreState(want) {
|
} else if isOreState(regionGot) || isOreState(want) {
|
||||||
regionOreMismatch++
|
regionOreMismatch++
|
||||||
|
countOreDifference(regionByState, regionGot, want)
|
||||||
}
|
}
|
||||||
if centerGot == want {
|
if centerGot == want {
|
||||||
centerExact++
|
centerExact++
|
||||||
} else if isOreState(centerGot) || isOreState(want) {
|
} else if isOreState(centerGot) || isOreState(want) {
|
||||||
centerOreMismatch++
|
centerOreMismatch++
|
||||||
|
countOreDifference(centerByState, centerGot, want)
|
||||||
}
|
}
|
||||||
if legacyGot == want {
|
if legacyGot == want {
|
||||||
legacyExact++
|
legacyExact++
|
||||||
} else if isOreState(legacyGot) || isOreState(want) {
|
} else if isOreState(legacyGot) || isOreState(want) {
|
||||||
legacyOreMismatch++
|
legacyOreMismatch++
|
||||||
|
countOreDifference(legacyByState, legacyGot, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,4 +146,40 @@ func TestRegionOreReplayParityDiagnostic(t *testing.T) {
|
||||||
t.Logf("region ore replay block exact %d/%d (%.3f%%), ore mismatches %d", regionExact, blockTotal, percent(regionExact, blockTotal), regionOreMismatch)
|
t.Logf("region ore replay block exact %d/%d (%.3f%%), ore mismatches %d", regionExact, blockTotal, percent(regionExact, blockTotal), regionOreMismatch)
|
||||||
t.Logf("center-only generic block exact %d/%d (%.3f%%), ore mismatches %d", centerExact, blockTotal, percent(centerExact, blockTotal), centerOreMismatch)
|
t.Logf("center-only generic block exact %d/%d (%.3f%%), ore mismatches %d", centerExact, blockTotal, percent(centerExact, blockTotal), centerOreMismatch)
|
||||||
t.Logf("legacy ore-only block exact %d/%d (%.3f%%), ore mismatches %d", legacyExact, blockTotal, percent(legacyExact, blockTotal), legacyOreMismatch)
|
t.Logf("legacy ore-only block exact %d/%d (%.3f%%), ore mismatches %d", legacyExact, blockTotal, percent(legacyExact, blockTotal), legacyOreMismatch)
|
||||||
|
logOreDifferences(t, "region", regionByState)
|
||||||
|
logOreDifferences(t, "center", centerByState)
|
||||||
|
logOreDifferences(t, "legacy", legacyByState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func countOreDifference(counts map[uint16]*oreDifference, got, want uint16) {
|
||||||
|
if isOreState(got) {
|
||||||
|
entry := counts[got]
|
||||||
|
if entry == nil {
|
||||||
|
entry = &oreDifference{}
|
||||||
|
counts[got] = entry
|
||||||
|
}
|
||||||
|
entry.extra++
|
||||||
|
}
|
||||||
|
if isOreState(want) {
|
||||||
|
entry := counts[want]
|
||||||
|
if entry == nil {
|
||||||
|
entry = &oreDifference{}
|
||||||
|
counts[want] = entry
|
||||||
|
}
|
||||||
|
entry.missing++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logOreDifferences(t *testing.T, label string, counts map[uint16]*oreDifference) {
|
||||||
|
t.Helper()
|
||||||
|
states := make([]int, 0, len(counts))
|
||||||
|
for state := range counts {
|
||||||
|
states = append(states, int(state))
|
||||||
|
}
|
||||||
|
sort.Ints(states)
|
||||||
|
for _, value := range states {
|
||||||
|
state := uint16(value)
|
||||||
|
entry := counts[state]
|
||||||
|
t.Logf("%s %s: extra=%d missing=%d", label, stateLabel(state), entry.extra, entry.missing)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
package world
|
package world
|
||||||
|
|
||||||
import (
|
import "regionio/internal/worldgen"
|
||||||
"math"
|
|
||||||
|
|
||||||
"regionio/internal/worldgen"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *decorationRegion) placeScheduledOres(seed int64) error {
|
func (r *decorationRegion) placeScheduledOres(seed int64) error {
|
||||||
set, err := worldgen.LoadFeatureSet()
|
set, err := worldgen.LoadFeatureSet()
|
||||||
|
|
@ -47,45 +43,7 @@ func (r *decorationRegion) placeScheduledOres(seed int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func placeOreEllipsoidRegion(region *decorationRegion, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) {
|
func placeOreEllipsoidRegion(region *decorationRegion, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) {
|
||||||
angle := random.NextFloat() * float32(math.Pi)
|
spheres := buildOreSpheres(random, originX, originY, originZ, size)
|
||||||
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)
|
|
||||||
|
|
||||||
spheres := make([]oreSphere, 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] = oreSphere{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walkOreBlocks(spheres, func(x, y, z int) {
|
walkOreBlocks(spheres, func(x, y, z int) {
|
||||||
if y < MinY || y >= MinY+WorldHeight {
|
if y < MinY || y >= MinY+WorldHeight {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const dataVersion26 = 4790
|
||||||
// first time it ran: chunkAt prefers the store over the generator, so the
|
// first time it ran: chunkAt prefers the store over the generator, so the
|
||||||
// already-explored area around spawn keeps its old terrain and every later fix
|
// already-explored area around spawn keeps its old terrain and every later fix
|
||||||
// looks like it did nothing in exactly the place you are standing.
|
// looks like it did nothing in exactly the place you are standing.
|
||||||
const generatorVersion = 18
|
const generatorVersion = 19
|
||||||
|
|
||||||
// generatorVersionTag is the NBT key holding generatorVersion. It is namespaced
|
// generatorVersionTag is the NBT key holding generatorVersion. It is namespaced
|
||||||
// because it is ours, not part of the vanilla chunk format.
|
// because it is ours, not part of the vanilla chunk format.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue