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