3dtrial/renderer3/generation.go

283 lines
8.1 KiB
Go
Raw Permalink Normal View History

2024-08-04 13:35:47 +00:00
package main
import (
"image"
"image/color"
2024-08-05 02:13:08 +00:00
"log"
"math"
"math/rand"
2024-08-04 13:35:47 +00:00
"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
2024-08-05 02:13:08 +00:00
vt[0] = hrend.Vec3f{X: 0, Y: 0.001, Z: 0}
2024-08-04 13:35:47 +00:00
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
2024-08-05 02:13:08 +00:00
{0, 3, 2},
2024-08-04 13:35:47 +00:00
{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,
}
}
2024-08-05 02:13:08 +00:00
// 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)
2024-08-04 13:35:47 +00:00
// For the simple square terrain, there aren't a lot of texture coords...
2024-08-05 02:13:08 +00:00
// 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
2024-08-04 13:35:47 +00:00
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
2024-08-05 02:13:08 +00:00
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]},
2024-08-04 13:35:47 +00:00
}, hrend.Facef{
2024-08-05 02:13:08 +00:00
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]},
2024-08-04 13:35:47 +00:00
})
}
}
2024-08-05 02:13:08 +00:00
}
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)
2024-08-04 13:35:47 +00:00
return &result
}
2024-08-05 02:13:08 +00:00
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
}
}
}