Tutorial Complete
This commit is contained in:
commit
6d09f125be
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user