From 04a4ce42e87c143d8da30b72bb8bbdfadc331266 Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Sun, 4 Aug 2024 22:13:08 -0400 Subject: [PATCH] cool terrain --- renderer3/generation.go | 208 +++++++++++++++++++++++++++++--------- renderer3/hrend/render.go | 12 +-- renderer3/main.go | 5 +- 3 files changed, 170 insertions(+), 55 deletions(-) diff --git a/renderer3/generation.go b/renderer3/generation.go index 08822e2..dab63d4 100644 --- a/renderer3/generation.go +++ b/renderer3/generation.go @@ -3,6 +3,9 @@ package main import ( "image" "image/color" + "log" + "math" + "math/rand" "renderer3/hrend" ) @@ -61,7 +64,7 @@ func Skybox() *hrend.ObjModel { 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, Z: 0} + 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], @@ -80,7 +83,7 @@ func Skybox() *hrend.ObjModel { // These are our 12 faces fv := [][3]int{ {0, 2, 1}, // bottom - {1, 2, 3}, + {0, 3, 2}, {4, 5, 6}, // top {6, 7, 4}, {0, 1, 5}, // south @@ -97,28 +100,6 @@ func Skybox() *hrend.ObjModel { f[i][j] = hrend.Vertex{Pos: v[face[j]], Tex: vvt[face[j]]} } } - // Now the bottom 2 faces - // f[0] = hrend.Facef{ - // hrend.Vertex{Pos: v[0], Tex: vt[0]}, - // hrend.Vertex{Pos: v[1], Tex: vt[0]}, - // hrend.Vertex{Pos: v[2], Tex: vt[0]}, - // } - // f[1] = hrend.Facef{ - // hrend.Vertex{Pos: v[2], Tex: vt[0]}, - // hrend.Vertex{Pos: v[3], Tex: vt[0]}, - // hrend.Vertex{Pos: v[0], Tex: vt[0]}, - // } - // // Top 2 faces - // f[3] = hrend.Facef{ - // hrend.Vertex{Pos: v[4], Tex: vt[1]}, - // hrend.Vertex{Pos: v[5], Tex: vt[1]}, - // hrend.Vertex{Pos: v[6], Tex: vt[1]}, - // } - // f[4] = hrend.Facef{ - // hrend.Vertex{Pos: v[6], Tex: vt[1]}, - // hrend.Vertex{Pos: v[7], Tex: vt[1]}, - // hrend.Vertex{Pos: v[4], Tex: vt[1]}, - // } // Ugh and now the sides... so complicated return &hrend.ObjModel{ Vertices: v, @@ -127,23 +108,16 @@ func Skybox() *hrend.ObjModel { } } -func FlatTerrain(size int) *hrend.ObjModel { - result := hrend.ObjModel{ - Vertices: make([]hrend.Vec3f, 0), - VTexture: make([]hrend.Vec3f, 4), - Faces: make([]hrend.Facef, 0), - } +// 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... - result.VTexture[0] = hrend.Vec3f{X: 0, Y: 0, Z: 0} - result.VTexture[1] = hrend.Vec3f{X: 1, Y: 0, Z: 0} - result.VTexture[2] = hrend.Vec3f{X: 0, Y: 1, Z: 0} - result.VTexture[3] = hrend.Vec3f{X: 1, Y: 1, Z: 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)}) - } - } + // 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++ { @@ -153,16 +127,156 @@ func FlatTerrain(size int) *hrend.ObjModel { bottomleft := x + (z+1)*width bottomright := x + 1 + (z+1)*width // remember to wind counter-clockwise - result.Faces = append(result.Faces, hrend.Facef{ - hrend.Vertex{Pos: result.Vertices[topleft], Tex: result.VTexture[0]}, - hrend.Vertex{Pos: result.Vertices[bottomleft], Tex: result.VTexture[2]}, - hrend.Vertex{Pos: result.Vertices[topright], Tex: result.VTexture[1]}, + 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: result.Vertices[topright], Tex: result.VTexture[1]}, - hrend.Vertex{Pos: result.Vertices[bottomleft], Tex: result.VTexture[2]}, - hrend.Vertex{Pos: result.Vertices[bottomright], Tex: result.VTexture[3]}, + 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 + } + } +} diff --git a/renderer3/hrend/render.go b/renderer3/hrend/render.go index 8b3bdc7..c57fc70 100644 --- a/renderer3/hrend/render.go +++ b/renderer3/hrend/render.go @@ -254,6 +254,9 @@ func PerspectiveAndClip(face Facef, matrix3d *Mat44f) []Facef { // The two points that aren't a need to be the interpolated values sc[bi].Pos = LerpVec3f(sc[ai].Pos, sc[bi].Pos, tab) sc[ci].Pos = LerpVec3f(sc[ai].Pos, sc[ci].Pos, tac) + sc[bi].Tex = LerpVec3f(sc[ai].Tex, sc[bi].Tex, tab) + sc[ci].Tex = LerpVec3f(sc[ai].Tex, sc[ci].Tex, tac) + w[bi] = LerpF32(w[ai], w[bi], tab) w[ci] = LerpF32(w[ai], w[ci], tac) @@ -274,6 +277,7 @@ func PerspectiveAndClip(face Facef, matrix3d *Mat44f) []Facef { // tab and tac are the distance to that point itself, so a still needs // to be the first value here sct[ai].Pos = LerpVec3f(sc[ai].Pos, sc[bi].Pos, tab) + sct[ai].Tex = LerpVec3f(sc[ai].Tex, sc[bi].Tex, tab) w[ai] = LerpF32(w[ai], w[bi], tab) outfaces = conditionalAddTriangle(sct, w, outfaces) @@ -282,18 +286,14 @@ func PerspectiveAndClip(face Facef, matrix3d *Mat44f) []Facef { // triangle. But simply replacing it will make the triangle invisible, // since it inverts the winding order (I think) sct[bi].Pos = LerpVec3f(sc[ai].Pos, sc[ci].Pos, tac) + sct[bi].Tex = LerpVec3f(sc[ai].Tex, sc[ci].Tex, tac) w[bi] = LerpF32(wa, w[ci], tac) + // Now swap the a and b w[ai], w[bi] = w[bi], w[ai] sct[ai], sct[bi] = sct[bi], sct[ai] outfaces = conditionalAddTriangle(sct, w, outfaces) - /* - sc2[ai].Pos = LerpVec3f(sc2[ai].Pos, sc2[ci].Pos, tac) - w[ai] = LerpF32(w[ai], w[ci], tac) - //outfaces = conditionalAddTriangle(sc2, w, outfaces) - */ - } else if len(outers) != 3 { // Output the face itself, no modification outfaces = conditionalAddTriangle(sc, w, outfaces) } diff --git a/renderer3/main.go b/renderer3/main.go index 8ec2c6f..cb5921b 100644 --- a/renderer3/main.go +++ b/renderer3/main.go @@ -170,10 +170,10 @@ func main() { // Generate world wtexraw := Checkerboard([]color.Color{color.RGBA{R: 0, G: 255, B: 0, A: 255}, color.RGBA{R: 50, G: 150, B: 0, A: 255}}, 32) wtex := hrend.NewTexture(wtexraw, 1) - world := FlatTerrain(10) + world := DiamondSquareTerrain(32, 1, 9) // must be power of two // Generate skybox - skyraw := Gradient1px(color.RGBA{R: 100, G: 100, B: 255, A: 255}, color.RGBA{R: 255, G: 255, B: 255, A: 255}, 32) + skyraw := Gradient1px(color.RGBA{R: 100, G: 100, B: 255, A: 255}, color.RGBA{R: 0, G: 0, B: 25, A: 255}, 32) skytex := hrend.NewTexture(skyraw, 1) sky := Skybox() @@ -189,6 +189,7 @@ func main() { objects := make([]*hrend.ObjectDef, 0) objects = append(objects, hrend.NewObjectDef(world, wtex)) worldobj := objects[len(objects)-1] + worldobj.Pos.Y -= 3 worldobj.Color = hrend.Vec3f{0.0, 1.0, 0.0} objects = append(objects, hrend.NewObjectDef(sky, skytex)) // the actual skybox skyobj := objects[len(objects)-1]