89 lines
3.0 KiB
Go
89 lines
3.0 KiB
Go
package net
|
|
|
|
import "math"
|
|
|
|
// WorldConfig holds world configuration based on heightmap
|
|
type WorldConfig struct {
|
|
SamplesPerSide int // Number of heightmap samples per side
|
|
UnitsPerSample float32 // World units (meters) per sample
|
|
WorldWidth float32 // Total world width in units
|
|
WorldHeight float32 // Total world height (depth) in units
|
|
MinBounds Vec3 // Minimum world bounds
|
|
MaxBounds Vec3 // Maximum world bounds
|
|
MaxTerrainHeight float32 // Maximum terrain height
|
|
}
|
|
|
|
// NewWorldConfig creates world configuration from heightmap
|
|
func NewWorldConfig(heightmap [][]float32, unitsPerSample float32) *WorldConfig {
|
|
samplesPerSide := len(heightmap)
|
|
worldSize := float32(samplesPerSide-1) * unitsPerSample
|
|
halfSize := worldSize * 0.5
|
|
|
|
// Find max terrain height
|
|
maxHeight := float32(0)
|
|
for y := range heightmap {
|
|
for x := range heightmap[y] {
|
|
if heightmap[y][x] > maxHeight {
|
|
maxHeight = heightmap[y][x]
|
|
}
|
|
}
|
|
}
|
|
|
|
return &WorldConfig{
|
|
SamplesPerSide: samplesPerSide,
|
|
UnitsPerSample: unitsPerSample,
|
|
WorldWidth: worldSize,
|
|
WorldHeight: worldSize,
|
|
MinBounds: Vec3{X: -halfSize, Y: 0, Z: -halfSize},
|
|
MaxBounds: Vec3{X: halfSize, Y: maxHeight + 10, Z: halfSize},
|
|
MaxTerrainHeight: maxHeight,
|
|
}
|
|
}
|
|
|
|
// ClampPosition clamps a position to world bounds
|
|
func (w *WorldConfig) ClampPosition(pos Vec3) Vec3 {
|
|
return Vec3{
|
|
X: float32(math.Max(float64(w.MinBounds.X), math.Min(float64(w.MaxBounds.X), float64(pos.X)))),
|
|
Y: float32(math.Max(float64(w.MinBounds.Y), math.Min(float64(w.MaxBounds.Y), float64(pos.Y)))),
|
|
Z: float32(math.Max(float64(w.MinBounds.Z), math.Min(float64(w.MaxBounds.Z), float64(pos.Z)))),
|
|
}
|
|
}
|
|
|
|
// IsInBounds checks if a position is within world bounds
|
|
func (w *WorldConfig) IsInBounds(pos Vec3) bool {
|
|
return pos.X >= w.MinBounds.X && pos.X <= w.MaxBounds.X &&
|
|
pos.Y >= w.MinBounds.Y && pos.Y <= w.MaxBounds.Y &&
|
|
pos.Z >= w.MinBounds.Z && pos.Z <= w.MaxBounds.Z
|
|
}
|
|
|
|
// GetHeightAt gets interpolated height at world position
|
|
func (w *WorldConfig) GetHeightAt(heightmap [][]float32, worldX, worldZ float32) float32 {
|
|
// Convert world coordinates to sample coordinates
|
|
sampleX := (worldX + w.WorldWidth*0.5) / w.UnitsPerSample
|
|
sampleZ := (worldZ + w.WorldHeight*0.5) / w.UnitsPerSample
|
|
|
|
// Clamp to valid range
|
|
sampleX = float32(math.Max(0, math.Min(float64(w.SamplesPerSide-1), float64(sampleX))))
|
|
sampleZ = float32(math.Max(0, math.Min(float64(w.SamplesPerSide-1), float64(sampleZ))))
|
|
|
|
// Get integer sample indices
|
|
x0 := int(math.Floor(float64(sampleX)))
|
|
z0 := int(math.Floor(float64(sampleZ)))
|
|
x1 := int(math.Min(float64(x0+1), float64(w.SamplesPerSide-1)))
|
|
z1 := int(math.Min(float64(z0+1), float64(w.SamplesPerSide-1)))
|
|
|
|
// Get fractional parts for interpolation
|
|
fx := sampleX - float32(x0)
|
|
fz := sampleZ - float32(z0)
|
|
|
|
// Get heights at four corners
|
|
h00 := heightmap[z0][x0]
|
|
h10 := heightmap[z0][x1]
|
|
h01 := heightmap[z1][x0]
|
|
h11 := heightmap[z1][x1]
|
|
|
|
// Bilinear interpolation
|
|
h0 := h00*(1-fx) + h10*fx
|
|
h1 := h01*(1-fx) + h11*fx
|
|
return h0*(1-fz) + h1*fz
|
|
} |