package main import ( "image" "image/color" "log" "math" "math/rand" "renderer3/hrend" ) func Checkerboard(cols []color.Color, size int) image.Image { result := image.NewRGBA(image.Rect(0, 0, size, size)) for y := range size { for x := range size { result.Set(x, y, cols[(x+y)%len(cols)]) } } return result } // Returns a sizexsize*3 texture where the top is the top color, // bottom is bottom color, middle is gradient func Gradient(bottom color.Color, top color.Color, size int) image.Image { result := image.NewRGBA(image.Rect(0, 0, size, size*3)) br, bg, bb, ba := bottom.RGBA() tr, tg, tb, ta := top.RGBA() for y := range size { for x := range size { lerp := float32(y) / float32(size-1) result.Set(x, y, top) result.Set(x, y+size, color.RGBA{ R: byte(float32(tr>>8)*(1-lerp) + float32(br>>8)*lerp), G: byte(float32(tg>>8)*(1-lerp) + float32(bg>>8)*lerp), B: byte(float32(tb>>8)*(1-lerp) + float32(bb>>8)*lerp), A: byte(float32(ta>>8)*(1-lerp) + float32(ba>>8)*lerp), }) result.Set(x, y+size*2, bottom) } } return result } // Returns a 1px wide gradient where top is top color, bottom is bottom color func Gradient1px(bottom color.Color, top color.Color, size int) image.Image { result := image.NewRGBA(image.Rect(0, 0, 1, size)) br, bg, bb, ba := bottom.RGBA() tr, tg, tb, ta := top.RGBA() for y := range size { lerp := float32(y) / float32(size-1) result.Set(0, y, color.RGBA{ R: byte(float32(tr>>8)*(1-lerp) + float32(br>>8)*lerp), G: byte(float32(tg>>8)*(1-lerp) + float32(bg>>8)*lerp), B: byte(float32(tb>>8)*(1-lerp) + float32(bb>>8)*lerp), A: byte(float32(ta>>8)*(1-lerp) + float32(ba>>8)*lerp), }) } return result } // Skybox for now assumes a 1px gradient func Skybox() *hrend.ObjModel { v := make([]hrend.Vec3f, 8) vt := make([]hrend.Vec3f, 2) f := make([]hrend.Facef, 12) // Assuming 1px gradient, these are the only two texture points you need vt[0] = hrend.Vec3f{X: 0, Y: 0.001, Z: 0} vt[1] = hrend.Vec3f{X: 0, Y: 1, Z: 0} vvt := []hrend.Vec3f{ vt[0], vt[0], vt[0], vt[0], vt[1], vt[1], vt[1], vt[1], } // Cube faces are weird, I guess just manually do them? ugh // First 4 are the bottom vertices. We can make two faces out of these v[0] = hrend.Vec3f{X: float32(-1), Y: float32(-1), Z: float32(-1)} v[1] = hrend.Vec3f{X: float32(1), Y: float32(-1), Z: float32(-1)} v[2] = hrend.Vec3f{X: float32(1), Y: float32(-1), Z: float32(1)} v[3] = hrend.Vec3f{X: float32(-1), Y: float32(-1), Z: float32(1)} // Now the top 4 vertices, same order as bottom v[4] = hrend.Vec3f{X: float32(-1), Y: float32(1), Z: float32(-1)} v[5] = hrend.Vec3f{X: float32(1), Y: float32(1), Z: float32(-1)} v[6] = hrend.Vec3f{X: float32(1), Y: float32(1), Z: float32(1)} v[7] = hrend.Vec3f{X: float32(-1), Y: float32(1), Z: float32(1)} // These are our 12 faces fv := [][3]int{ {0, 2, 1}, // bottom {0, 3, 2}, {4, 5, 6}, // top {6, 7, 4}, {0, 1, 5}, // south {5, 4, 0}, {1, 2, 6}, // east {6, 5, 1}, {2, 3, 7}, // North {7, 6, 2}, {3, 0, 4}, // west {4, 7, 3}, } for i, face := range fv { for j := range 3 { f[i][j] = hrend.Vertex{Pos: v[face[j]], Tex: vvt[face[j]]} } } // Ugh and now the sides... so complicated return &hrend.ObjModel{ Vertices: v, VTexture: vt, Faces: f, } } // Reset all faces and regenerate them using the vertices as a square mesh func RegenerateSquareMesh(size int, obj *hrend.ObjModel) { obj.VTexture = make([]hrend.Vec3f, 4) // For the simple square terrain, there aren't a lot of texture coords... // If you want something more complicated, replace this obj.VTexture[0] = hrend.Vec3f{X: 0, Y: 0, Z: 0} obj.VTexture[1] = hrend.Vec3f{X: 1, Y: 0, Z: 0} obj.VTexture[2] = hrend.Vec3f{X: 0, Y: 1, Z: 0} obj.VTexture[3] = hrend.Vec3f{X: 1, Y: 1, Z: 0} obj.Faces = nil // Clear old faces width := size + size + 1 // Faces are slightly different; we generate two for every "cell" inside the vertices for z := 0; z < width-1; z++ { for x := 0; x < width-1; x++ { topleft := x + z*width topright := x + 1 + z*width bottomleft := x + (z+1)*width bottomright := x + 1 + (z+1)*width // remember to wind counter-clockwise obj.Faces = append(obj.Faces, hrend.Facef{ hrend.Vertex{Pos: obj.Vertices[topleft], Tex: obj.VTexture[0]}, hrend.Vertex{Pos: obj.Vertices[bottomleft], Tex: obj.VTexture[2]}, hrend.Vertex{Pos: obj.Vertices[topright], Tex: obj.VTexture[1]}, }, hrend.Facef{ hrend.Vertex{Pos: obj.Vertices[topright], Tex: obj.VTexture[1]}, hrend.Vertex{Pos: obj.Vertices[bottomleft], Tex: obj.VTexture[2]}, hrend.Vertex{Pos: obj.Vertices[bottomright], Tex: obj.VTexture[3]}, }) } } } func FlatTerrain(size int) *hrend.ObjModel { result := hrend.ObjModel{ Vertices: make([]hrend.Vec3f, 0), VTexture: make([]hrend.Vec3f, 4), Faces: make([]hrend.Facef, 0), } // Generate all the simple vertices along the plane at y=0 for z := -size; z <= size; z++ { for x := -size; x <= size; x++ { result.Vertices = append(result.Vertices, hrend.Vec3f{X: float32(x), Y: 0, Z: float32(z)}) } } RegenerateSquareMesh(size, &result) return &result } func DiamondSquareTerrain(size int, roughness float32, scale float32) *hrend.ObjModel { result := hrend.ObjModel{ Vertices: make([]hrend.Vec3f, 0), VTexture: make([]hrend.Vec3f, 4), Faces: make([]hrend.Facef, 0), } dsterra := DiamondSquare(size+size+1, float64(roughness)) // Generate all the simple vertices along the plane at y=0 for z := -size; z <= size; z++ { for x := -size; x <= size; x++ { //result.Vertices = append(result.Vertices, hrend.Vec3f{X: float32(x), Y: float32(float64(scale) * dsterra[0][0]), Z: float32(z)}) result.Vertices = append(result.Vertices, hrend.Vec3f{X: float32(x), Y: float32(float64(scale) * dsterra[z+size][x+size]), Z: float32(z)}) } } RegenerateSquareMesh(size, &result) return &result } // CHATGPT ----------------------------------------- func DiamondSquare(size int, roughness float64) [][]float64 { // Initialize the array terrain := make([][]float64, size) for i := range terrain { terrain[i] = make([]float64, size) } // Seed the corners terrain[0][0] = rand.Float64() terrain[0][size-1] = rand.Float64() terrain[size-1][0] = rand.Float64() terrain[size-1][size-1] = rand.Float64() log.Print("DS Seeded corners") // Size of the step stepSize := size - 1 for stepSize > 1 { halfStep := stepSize / 2 // Diamond step for y := halfStep; y < size; y += stepSize { for x := halfStep; x < size; x += stepSize { diamondStep(terrain, x, y, halfStep, roughness) } } // Square step for y := 0; y < size; y += halfStep { for x := (y + halfStep) % stepSize; x < size; x += stepSize { squareStep(terrain, x, y, halfStep, roughness) } } stepSize = halfStep } log.Printf("DS finished squares and diamonds") // Normalize to [0, 1] normalize(terrain) log.Printf("DS normalize (complete)") return terrain } // Diamond step of the algorithm func diamondStep(terrain [][]float64, x, y, halfStep int, roughness float64) { sum := terrain[y-halfStep][x-halfStep] + terrain[y-halfStep][x+halfStep] + terrain[y+halfStep][x-halfStep] + terrain[y+halfStep][x+halfStep] avg := sum / 4 terrain[y][x] = avg + (rand.Float64()*2-1)*roughness } // Square step of the algorithm func squareStep(terrain [][]float64, x, y, halfStep int, roughness float64) { avg := 0.0 count := 0 if x-halfStep >= 0 { avg += terrain[y][x-halfStep] count++ } if x+halfStep < len(terrain) { avg += terrain[y][x+halfStep] count++ } if y-halfStep >= 0 { avg += terrain[y-halfStep][x] count++ } if y+halfStep < len(terrain) { avg += terrain[y+halfStep][x] count++ } avg /= float64(count) terrain[y][x] = avg + (rand.Float64()*2-1)*roughness } // Normalize the array to range [0, 1] func normalize(terrain [][]float64) { minVal, maxVal := math.Inf(1), math.Inf(-1) for _, row := range terrain { for _, value := range row { if value < minVal { minVal = value } if value > maxVal { maxVal = value } } } rangeVal := maxVal - minVal for i, row := range terrain { for j := range row { terrain[i][j] = (terrain[i][j] - minVal) / rangeVal } } }