Tutorial Complete
This commit is contained in:
		
							
								
								
									
										311
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,311 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
/* OpenGL & Go Tutorial
 | 
			
		||||
 * https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
 | 
			
		||||
 *
 | 
			
		||||
 * Vertex/Fragment Shader Explanation
 | 
			
		||||
 * https://www.quora.com/What-is-a-vertex-shader-and-what-is-a-fragment-shader/answer/Harold-Serrano?srid=aVb
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-gl/gl/v2.1/gl"
 | 
			
		||||
	"github.com/go-gl/glfw/v3.1/glfw"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	width              = 500
 | 
			
		||||
	height             = 500
 | 
			
		||||
	vertexShaderSource = `
 | 
			
		||||
		#version 410
 | 
			
		||||
		in vec3 vp;
 | 
			
		||||
		void main() {
 | 
			
		||||
			gl_Position = vec4(vp, 1.0);
 | 
			
		||||
		}
 | 
			
		||||
	` + "\x00"
 | 
			
		||||
	fragmentShaderSource = `
 | 
			
		||||
		#version 410
 | 
			
		||||
		out vec4 frag_colour;
 | 
			
		||||
		void main() {
 | 
			
		||||
			frag_colour = vec4(1, 1, 1, 1);
 | 
			
		||||
		}
 | 
			
		||||
	` + "\x00"
 | 
			
		||||
 | 
			
		||||
	rows = 10
 | 
			
		||||
	cols = 10
 | 
			
		||||
	fps  = 10
 | 
			
		||||
 | 
			
		||||
	threshold = 0.15
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	square = []float32{
 | 
			
		||||
		-0.5, 0.5, 0,
 | 
			
		||||
		-0.5, -0.5, 0,
 | 
			
		||||
		0.5, -0.5, 0,
 | 
			
		||||
 | 
			
		||||
		-0.5, 0.5, 0,
 | 
			
		||||
		0.5, 0.5, 0,
 | 
			
		||||
		0.5, -0.5, 0,
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	runtime.LockOSThread()
 | 
			
		||||
 | 
			
		||||
	window := initGlfw()
 | 
			
		||||
	defer glfw.Terminate()
 | 
			
		||||
 | 
			
		||||
	program := initOpenGL()
 | 
			
		||||
 | 
			
		||||
	cells := makeCells()
 | 
			
		||||
	for !window.ShouldClose() {
 | 
			
		||||
		t := time.Now()
 | 
			
		||||
 | 
			
		||||
		for x := range cells {
 | 
			
		||||
			for _, c := range cells[x] {
 | 
			
		||||
				c.checkState(cells)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		draw(cells, window, program)
 | 
			
		||||
 | 
			
		||||
		time.Sleep(time.Second/time.Duration(fps) - time.Since(t))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// initGlfw initializes glfw and returns a Window to use.
 | 
			
		||||
func initGlfw() *glfw.Window {
 | 
			
		||||
	if err := glfw.Init(); err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	glfw.WindowHint(glfw.Resizable, glfw.False)
 | 
			
		||||
	glfw.WindowHint(glfw.ContextVersionMajor, 4)
 | 
			
		||||
	glfw.WindowHint(glfw.ContextVersionMinor, 1)
 | 
			
		||||
	glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
 | 
			
		||||
	glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
 | 
			
		||||
 | 
			
		||||
	window, err := glfw.CreateWindow(width, height, "Conway's Game of Life", nil, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	window.MakeContextCurrent()
 | 
			
		||||
 | 
			
		||||
	return window
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// initOpenGL initializes OpenGL and returns an initialized program.
 | 
			
		||||
func initOpenGL() uint32 {
 | 
			
		||||
	if err := gl.Init(); err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	version := gl.GoStr(gl.GetString(gl.VERSION))
 | 
			
		||||
	log.Println("OpenGL version", version)
 | 
			
		||||
 | 
			
		||||
	vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prog := gl.CreateProgram()
 | 
			
		||||
	gl.AttachShader(prog, vertexShader)
 | 
			
		||||
	gl.AttachShader(prog, fragmentShader)
 | 
			
		||||
	gl.LinkProgram(prog)
 | 
			
		||||
	return prog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func draw(cells [][]*cell, window *glfw.Window, program uint32) {
 | 
			
		||||
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
 | 
			
		||||
	gl.UseProgram(program)
 | 
			
		||||
 | 
			
		||||
	for x := range cells {
 | 
			
		||||
		for _, c := range cells[x] {
 | 
			
		||||
			c.draw()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	glfw.PollEvents()
 | 
			
		||||
	window.SwapBuffers()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// makeVao initializes and returns a vertex array from the points provided.
 | 
			
		||||
func makeVao(points []float32) uint32 {
 | 
			
		||||
	var vbo uint32
 | 
			
		||||
	gl.GenBuffers(1, &vbo)
 | 
			
		||||
	gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
 | 
			
		||||
	gl.BufferData(gl.ARRAY_BUFFER, 4*len(points), gl.Ptr(points), gl.STATIC_DRAW)
 | 
			
		||||
 | 
			
		||||
	var vao uint32
 | 
			
		||||
	gl.GenVertexArrays(1, &vao)
 | 
			
		||||
	gl.BindVertexArray(vao)
 | 
			
		||||
	gl.EnableVertexAttribArray(0)
 | 
			
		||||
	gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
 | 
			
		||||
	gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil)
 | 
			
		||||
 | 
			
		||||
	return vao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compileShader(source string, shaderType uint32) (uint32, error) {
 | 
			
		||||
	shader := gl.CreateShader(shaderType)
 | 
			
		||||
 | 
			
		||||
	csources, free := gl.Strs(source)
 | 
			
		||||
	gl.ShaderSource(shader, 1, csources, nil)
 | 
			
		||||
	free()
 | 
			
		||||
	gl.CompileShader(shader)
 | 
			
		||||
 | 
			
		||||
	var status int32
 | 
			
		||||
	gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
 | 
			
		||||
	if status == gl.FALSE {
 | 
			
		||||
		var logLength int32
 | 
			
		||||
		gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)
 | 
			
		||||
 | 
			
		||||
		log := strings.Repeat("\x00", int(logLength+1))
 | 
			
		||||
		gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))
 | 
			
		||||
 | 
			
		||||
		return 0, fmt.Errorf("failed to compile %v: %v", source, log)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return shader, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func makeCells() [][]*cell {
 | 
			
		||||
	rand.Seed(time.Now().UnixNano())
 | 
			
		||||
 | 
			
		||||
	cells := make([][]*cell, rows, rows)
 | 
			
		||||
	for x := 0; x < rows; x++ {
 | 
			
		||||
		for y := 0; y < cols; y++ {
 | 
			
		||||
			c := newCell(x, y)
 | 
			
		||||
 | 
			
		||||
			c.alive = rand.Float64() < threshold
 | 
			
		||||
			c.aliveNext = c.alive
 | 
			
		||||
 | 
			
		||||
			cells[x] = append(cells[x], c)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cells
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type cell struct {
 | 
			
		||||
	drawable uint32
 | 
			
		||||
 | 
			
		||||
	alive     bool
 | 
			
		||||
	aliveNext bool
 | 
			
		||||
 | 
			
		||||
	x int
 | 
			
		||||
	y int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newCell(x, y int) *cell {
 | 
			
		||||
	points := make([]float32, len(square), len(square))
 | 
			
		||||
	copy(points, square)
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(points); i++ {
 | 
			
		||||
		var position float32
 | 
			
		||||
		var size float32
 | 
			
		||||
		switch i % 3 {
 | 
			
		||||
		case 0:
 | 
			
		||||
			size = 1.0 / float32(cols)
 | 
			
		||||
			position = float32(x) * size
 | 
			
		||||
		case 1:
 | 
			
		||||
			size = 1.0 / float32(rows)
 | 
			
		||||
			position = float32(y) * size
 | 
			
		||||
		default:
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if points[i] < 0 {
 | 
			
		||||
			points[i] = (position * 2) - 1
 | 
			
		||||
		} else {
 | 
			
		||||
			points[i] = ((position + size) * 2) - 1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &cell{
 | 
			
		||||
		drawable: makeVao(points),
 | 
			
		||||
		x:        x,
 | 
			
		||||
		y:        y,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *cell) draw() {
 | 
			
		||||
	if !c.alive {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gl.BindVertexArray(c.drawable)
 | 
			
		||||
	gl.DrawArrays(gl.TRIANGLES, 0, int32(len(square)/3))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkState determines the state of the cell for the next tick of the game
 | 
			
		||||
func (c *cell) checkState(cells [][]*cell) {
 | 
			
		||||
	c.alive = c.aliveNext
 | 
			
		||||
	c.aliveNext = c.alive
 | 
			
		||||
 | 
			
		||||
	liveCount := c.liveNeighbors(cells)
 | 
			
		||||
	if c.alive {
 | 
			
		||||
		// 1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation
 | 
			
		||||
		if liveCount < 2 {
 | 
			
		||||
			c.alive = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 2. Any live cell with two or three live neighbors lives on to the next generation
 | 
			
		||||
		if liveCount == 2 || liveCount == 3 {
 | 
			
		||||
			c.aliveNext = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 3. Any live cell with more than three live neighbors dies, as if by overpopulation
 | 
			
		||||
		if liveCount > 3 {
 | 
			
		||||
			c.aliveNext = false
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// 4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction
 | 
			
		||||
		if liveCount == 3 {
 | 
			
		||||
			c.aliveNext = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// liveNeighbors returns the number of live neighbors for a cell
 | 
			
		||||
func (c *cell) liveNeighbors(cells [][]*cell) int {
 | 
			
		||||
	var liveCount int
 | 
			
		||||
	add := func(x, y int) {
 | 
			
		||||
		// If we're at an edge, check the other side of the board.
 | 
			
		||||
		if x == len(cells) {
 | 
			
		||||
			x = 0
 | 
			
		||||
		} else if x == -1 {
 | 
			
		||||
			x = len(cells) - 1
 | 
			
		||||
		}
 | 
			
		||||
		if y == len(cells[x]) {
 | 
			
		||||
			y = 0
 | 
			
		||||
		} else if y == -1 {
 | 
			
		||||
			y = len(cells[x]) - 1
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if cells[x][y].alive {
 | 
			
		||||
			liveCount++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	add(c.x-1, c.y)   // To the left
 | 
			
		||||
	add(c.x+1, c.y)   // To the right
 | 
			
		||||
	add(c.x, c.y+1)   // Up
 | 
			
		||||
	add(c.x, c.y-1)   // Down
 | 
			
		||||
	add(c.x-1, c.y+1) // Top-Left
 | 
			
		||||
	add(c.x+1, c.y+1) // Top-Right
 | 
			
		||||
	add(c.x-1, c.y-1) // Bottom-Left
 | 
			
		||||
	add(c.x+1, c.y-1) // Bottom-Right
 | 
			
		||||
 | 
			
		||||
	return liveCount
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user