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 }