예제 #1
0
파일: main.go 프로젝트: pwaller/debris
func createBuffer() *glh.MeshBuffer {
	const N = 128
	const f = 0.025
	pos := make([]float64, N*2)
	clr := make([]float64, N*4)
	for i := 0; i < N; i += 1 {
		pos[2*i+0] = math.Cos(f * 2 * math.Pi * 2 * float64(i) / N)
		pos[2*i+1] = math.Sin(f * 2 * math.Pi * 2 * float64(i) / N)
		clr[4*i+0] = 0.2
		clr[4*i+1] = 0.75
		clr[4*i+2] = 0.9
		clr[4*i+3] = 0.5 * (1 - float64(i)/float64(N-1))
	}

	// Create a mesh buffer with the given attributes.
	mb := glh.NewMeshBuffer(
		glh.RenderArrays,

		glh.NewPositionAttr(2, gl.DOUBLE, gl.STATIC_DRAW),
		glh.NewColorAttr(4, gl.DOUBLE, gl.STATIC_DRAW),
	)

	// Add the mesh to the buffer.
	mb.Add(pos, clr)
	return mb
}
예제 #2
0
파일: main.go 프로젝트: andrebq/exp
func prepareScene() {
	theTriangleBuf = glh.NewMeshBuffer(
		glh.RenderArrays,

		glh.NewIndexAttr(1, gl.UNSIGNED_INT, gl.STATIC_DRAW),
		glh.NewPositionAttr(3, gl.FLOAT, gl.STATIC_DRAW),
	)
	theTriangleBuf.Add(triangleIdx, triangleVertex)
}
예제 #3
0
파일: main.go 프로젝트: andrebq/exp
func createMeshBuffer() *glh.MeshBuffer {
	mb := glh.NewMeshBuffer(
		glh.RenderBuffered,

		glh.NewIndexAttr(1, gl.UNSIGNED_SHORT, gl.STATIC_DRAW),

		// 2 INTEGER values should be enough, but
		// just to keep things simpler
		// let's work with 3d vertex instead of 2d
		// it's easier to find help that way
		glh.NewPositionAttr(3, gl.FLOAT, gl.STATIC_DRAW),

		// At this moment, let's use colors instead of textures
		glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW))
	return mb
}
예제 #4
0
파일: main.go 프로젝트: jayschwa/examples
// createBuffer creates a mesh buffer and fills it with data.
func createBuffer() *glh.MeshBuffer {
	// Create a mesh buffer with the given attributes.
	mb := glh.NewMeshBuffer(
		glh.RenderBuffered,

		// 1 index per vertex.
		glh.NewIndexAttr(1, gl.UNSIGNED_SHORT, gl.STATIC_DRAW),

		// Vertex positions have 2 components (x, y).
		// These will never be changing, so mark them as static.
		glh.NewPositionAttr(2, gl.INT, gl.STATIC_DRAW),

		// Colors have 3 components (r, g, b).
		// These will be changing, so make them dynamic.
		glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW),
	)

	// Define components for a simple, coloured quad.
	idx := []uint16{0, 1, 2, 3}
	pos := []int32{0, 0, 0, 0, 0, 0, 0, 0}
	clr := []uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

	for y := 0; y < Rows; y++ {
		py := int32(CellHeight * y)

		pos[1] = py + 1
		pos[3] = py + 1
		pos[5] = py + CellHeight
		pos[7] = py + CellHeight

		for x := 0; x < Cols; x++ {
			px := int32(CellWidth * x)

			pos[0] = px + 1
			pos[2] = px + CellWidth
			pos[4] = px + CellWidth
			pos[6] = px + 1

			palette := colors[rng.Int31n(6)]
			setColor(clr, palette[0], palette[1], palette[2])

			mb.Add(idx, pos, clr)
		}
	}

	return mb
}
예제 #5
0
파일: main.go 프로젝트: nzlov/examples
func createBuffer() *glh.MeshBuffer {
	// We create as few vertices as possible.
	// Manually building a cube would require 24 vertices. Many of which
	// are duplicates. All we have to define here, is the 8 unique ones
	// necessary to construct each face of the cube.
	pos := []float32{
		1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1,
		1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1,
	}

	a := float32(0.02)

	// Each vertex comes with its own colour.
	clr := []float32{
		1, 0, 0, a, 0, 1, 0, a, 0, 0, 1, a, 1, 0, 1, a,
		1, 1, 0, a, 0, 1, 1, a, 1, 1, 1, a, 0, 0, 0, a,
	}

	// These are the indices into the position and color lists.
	// They tell the GPU which position/color pair to use in order to construct
	// the whole cube. As can be seen, all elements are repeated multiple
	// times to create the correct layout. For large meshes with many duplicate
	// vertices, this can save a sizable amount of storage space.
	idx := []byte{
		0, 1, 2, 3, 4, 5, 6, 7, 3, 2, 5, 4,
		7, 6, 1, 0, 2, 1, 6, 5, 0, 3, 4, 7,
	}

	// Create a mesh buffer with the given attributes.
	mb := glh.NewMeshBuffer(
		glh.RenderBuffered,

		// Indices.
		glh.NewIndexAttr(1, gl.UNSIGNED_BYTE, gl.STATIC_DRAW),

		// Vertex positions have 3 components (x, y, z).
		glh.NewPositionAttr(3, gl.FLOAT, gl.STATIC_DRAW),

		// Colors have 4 components (r, g, b, a).
		glh.NewColorAttr(4, gl.FLOAT, gl.STATIC_DRAW),
	)

	// Add the mesh to the buffer.
	mb.Add(idx, pos, clr)
	return mb
}
예제 #6
0
파일: main.go 프로젝트: andrebq/exp
// Create the glh.MeshBufer
func createBuffer() *glh.MeshBuffer {

	if len(scene.Mesh[0].Colors) == 0 {
		// just add some colors to it.
		assimp.RandomColor(scene.Mesh[0])
	}
	fmesh := assimp.NewFlatMesh(scene.Mesh[0])

	println("FMesh vertex count: ", len(fmesh.Vertex))

	var idxAttr *glh.Attr
	switch sz := len(fmesh.Index); {
	case sz < int(assimp.ByteSize):
		idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_BYTE, gl.STATIC_DRAW)
	case sz < int(assimp.ShortSize):
		idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_SHORT, gl.STATIC_DRAW)
	default:
		idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_INT, gl.STATIC_DRAW)
	}
	idxAttr = glh.NewIndexAttr(1, gl.UNSIGNED_INT, gl.STATIC_DRAW)

	// Create a mesh buffer with the given attributes.
	mb := glh.NewMeshBuffer(
		glh.RenderBuffered,

		// Indices.
		idxAttr,

		// Vertex positions have 3 components (x, y, z).
		glh.NewPositionAttr(3, gl.FLOAT, gl.STATIC_DRAW),

		// Colors have 4 components (r, g, b, a).
		glh.NewColorAttr(4, gl.FLOAT, gl.STATIC_DRAW),
	)

	// Add the mesh to the buffer.
	mb.Add(fmesh.Index, fmesh.Vertex, fmesh.Color)
	return mb
}
예제 #7
0
func (sg *OpenGLGraphics) Scatter(points []chart.EPoint, plotstyle chart.PlotStyle, style chart.Style) {
	//log.Panicf("Unimplemented: %s", whoami())
	//chart.GenericScatter(sg, points, plotstyle, style)

	// TODO: Implement error bars/symbols

	buffer := glh.NewMeshBuffer(glh.RenderArrays,
		glh.NewPositionAttr(2, gl.DOUBLE, gl.STATIC_DRAW),
		glh.NewColorAttr(3, gl.UNSIGNED_INT, gl.STATIC_DRAW))

	// var vertices glh.ColorVertices

	positions := make([]float64, len(points)*2)
	colors := make([]uint32, len(points)*3)

	for _, p := range points {
		r, g, b, _ := style.LineColor.RGBA()

		colors = append(colors, r, g, b)
		positions = append(positions, p.X, p.Y)

		// vertices.Add(glh.ColorVertex{
		// 	glh.MkRGBA(style.LineColor),
		// 	glh.Vertex{float32(p.X), float32(p.Y)}})
	}

	buffer.Add()

	if style.LineWidth != 0 {
		gl.LineWidth(float32(style.LineWidth))
	} else {
		gl.LineWidth(1)
	}

	glh.With(glh.Attrib{gl.ENABLE_BIT | gl.COLOR_BUFFER_BIT}, func() {
		gl.Enable(gl.BLEND)
		gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

		buffer.Render(gl.LINE_STRIP)
	})

	/***********************************************
	// First pass: Error bars
	ebs := style
	ebs.LineColor, ebs.LineWidth, ebs.LineStyle = ebs.FillColor, 1, chart.SolidLine
	if ebs.LineColor == "" {
		ebs.LineColor = "#404040"
	}
	if ebs.LineWidth == 0 {
		ebs.LineWidth = 1
	}
	for _, p := range points {
		xl, yl, xh, yh := p.BoundingBox()
		// fmt.Printf("Draw %d: %f %f-%f\n", i, p.DeltaX, xl,xh)
		if !math.IsNaN(p.DeltaX) {
			sg.Line(int(xl), int(p.Y), int(xh), int(p.Y), ebs)
		}
		if !math.IsNaN(p.DeltaY) {
			sg.Line(int(p.X), int(yl), int(p.X), int(yh), ebs)
		}
	}

	// Second pass: Line
	if (plotstyle&chart.PlotStyleLines) != 0 && len(points) > 0 {
		path := fmt.Sprintf("M %d,%d", int(points[0].X), int(points[0].Y))
		for i := 1; i < len(points); i++ {
			path += fmt.Sprintf("L %d,%d", int(points[i].X), int(points[i].Y))
		}
		st := linestyle(style)
		sg.svg.Path(path, st)
	}

	// Third pass: symbols
	if (plotstyle&chart.PlotStylePoints) != 0 && len(points) != 0 {
		for _, p := range points {
			sg.Symbol(int(p.X), int(p.Y), style)
		}
	}

	****************************************************/
}
예제 #8
0
파일: block.go 프로젝트: pwaller/mema
func (block *Block) GenerateVertices() *glh.MeshBuffer {

	width := uint64(len(block.display_active_pages)+1) * *PAGE_SIZE

	vc := glh.NewMeshBuffer(
		glh.RenderArrays,
		glh.NewPositionAttr(2, gl.FLOAT, gl.STATIC_DRAW),
		glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.STATIC_DRAW),
	)

	var stack_depth int = len(block.context_records)

	vertex := []float32{0, 0}
	colour := []uint8{0, 0, 0}
	x, y := &vertex[0], &vertex[1]
	r, g, b := &colour[0], &colour[1], &colour[2]

	vertices := make([]float32, 0, block.nrecords*2)
	colours := make([]uint8, 0, block.nrecords*3)

	for pos := int64(0); pos < int64(block.nrecords); pos++ {
		if pos < 0 {
			continue
		}

		rec := &block.records[pos]
		if rec.Type == MEMA_ACCESS {
			// take it
		} else if rec.Type == MEMA_FUNC_ENTER {
			stack_depth++

			*x = 2 + float32(stack_depth)/80.
			*y = float32(pos) //int64(len(*vc)))
			*r, *g, *b = 64, 64, 255

			vertices = append(vertices, *x, *y)
			colours = append(colours, *r, *g, *b)
			//vc.Add(vertex, colour)
			//c := color.RGBA{64, 64, 255, 255}
			//vc.Add(glh.ColorVertex{c, glh.Vertex{, y}})

			continue
		} else if rec.Type == MEMA_FUNC_EXIT {

			*x = 2 + float32(stack_depth)/80.
			*y = float32(pos) //int64(len(*vc)))
			*r, *g, *b = 255, 64, 64
			vertices = append(vertices, *x, *y)
			colours = append(colours, *r, *g, *b)
			//vc.Add(vertex, colour)
			//y := float32(int64(len(*vc)))
			//c := color.RGBA{255, 64, 64, 255}
			//vc.Add(glh.ColorVertex{c, glh.Vertex{2 + float32(stack_depth)/80., y}})

			stack_depth--

			continue
		} else {
			log.Panic("Unexpected record type: ", rec.Type)
		}
		a := rec.MemAccess()

		page := a.Addr / *PAGE_SIZE
		if _, present := block.quiet_pages[page]; present {
			continue
		}

		*x = float32((a.Addr - block.n_inactive_to_left[page]*(*PAGE_SIZE))) / float32(width)
		*x = (*x - 0.5) * 4

		if *x > 4 || *x < -4 {
			log.Panic("x has unexpected value: ", x)
		}

		*y = float32(pos) //len(*vc))
		*r, *g, *b = uint8(a.IsWrite)*255, uint8(1-a.IsWrite)*255, 0

		vertices = append(vertices, *x, *y)
		colours = append(colours, *r, *g, *b)

		//vc.Add(vertex, colour)

		//c := color.RGBA{uint8(a.IsWrite) * 255, uint8(1-a.IsWrite) * 255, 0, 255}

		//vc.Add(glh.ColorVertex{c, glh.Vertex{x, y}})

		/*
			TODO: Reintroduce 'recently hit memory locations'
			if pos > (start + N) - N / 20 {
				vc.Add(ColorVertex{c, Vertex{x, 2 + 0.1}})
				vc.Add(ColorVertex{c, Vertex{x, 2}})
			}
		*/
	}

	vc.Add(vertices, colours)
	// Don't need the record data anymore
	block.records = Records{}
	runtime.GC()

	return vc
}
예제 #9
0
파일: block.go 프로젝트: pwaller/mema
func (block *Block) Draw(start, N int64, detailed bool) {
	if block.tex == nil {
		block.RequestTexture()
	}

	switch detailed {
	case true:
		block.detail_needed = true
		if block.vertex_data == nil {
			// Hey, we need vertices but don't have them! Let's fix that..
			block.RequestVertices()
		}
	default:
		block.detail_needed = false
	}

	width := uint64(len(block.display_active_pages)) * *PAGE_SIZE
	if width == 0 {
		width = 1
	}

	vc := glh.NewMeshBuffer(glh.RenderArrays,
		glh.NewPositionAttr(2, gl.FLOAT, gl.STATIC_DRAW),
		glh.NewPositionAttr(4, gl.UNSIGNED_INT, gl.STATIC_DRAW),
	)

	colors := make([]int32, 0)
	positions := make([]float32, 0)

	// var vc glh.ColorVertices

	if *pageboundaries {
		// boundary_color := color.RGBA{64, 64, 64, 255}

		// If we try and draw too many of these, X will hang
		if width / *PAGE_SIZE < 10000 {
			for p := uint64(0); p <= width; p += *PAGE_SIZE {
				x := float32(p) / float32(width)
				x = (x - 0.5) * 4

				colors = append(colors, 64, 64, 64, 255)
				positions = append(positions, x, float32(N))

				// vc.Add(glh.ColorVertex{boundary_color, glh.Vertex{x, 0}})
				// vc.Add(glh.ColorVertex{boundary_color, glh.Vertex{x, float32(N)}})
			}
		}
	}

	var border_color [4]float64

	gl.LineWidth(1)
	glh.With(&Timer{Name: "DrawPartial"}, func() {
		var x1, y1, x2, y2 float64
		glh.With(glh.Matrix{gl.MODELVIEW}, func() {
			// TODO: A little less co-ordinate insanity?
			gl.Translated(0, -2, 0)
			gl.Scaled(1, 4/float64(*nback), 1)
			gl.Translated(0, -float64(start), 0)

			x1, y1 = glh.ProjToWindow(-2, 0)
			x2, y2 = glh.ProjToWindow(-2+WIDTH, float64(N))

		})
		border_color = [4]float64{1, 1, 1, 1}

		glh.With(glh.Matrix{gl.MODELVIEW}, func() {
			gl.Translated(0, -2, 0)
			gl.Scaled(1, 4/float64(*nback), 1)
			gl.Translated(0, -float64(start), 0)

			// Page boundaries
			// TODO: Use different blending scheme on textured quads so that the
			//       lines show through
			glh.With(glh.Attrib{gl.ENABLE_BIT}, func() {
				gl.Disable(gl.LINE_SMOOTH)
				// vc.Draw(gl.LINES)
				vc.Render(gl.LINES)
			})
		})

		if block.tex != nil && (!detailed || block.vertex_data == nil) {
			border_color = [4]float64{0, 0, 1, 1}
			glh.With(glh.WindowCoords{Invert: true}, func() {
				gl.Color4f(1, 1, 1, 1)
				// Render textured block quad
				glh.With(block.tex, func() {
					glh.DrawQuadd(x1, y1, x2-x1, y2-y1)
				})
				glh.With(glh.Primitive{gl.LINES}, func() {
					glh.Squared(x1, y1, x2-x1, y2-y1)
				})
			})
			if block.vertex_data != nil && !block.detail_needed {
				// TODO: figure out when we can unload
				// Hey, we can unload you, because you are not needed
				block.vertex_data = nil
			}

		}
		if detailed && block.vertex_data != nil {
			glh.With(glh.Matrix{gl.MODELVIEW}, func() {
				// TODO: A little less co-ordinate insanity?
				gl.Translated(0, -2, 0)
				gl.Scaled(1, 4/float64(*nback), 1)
				gl.Translated(0, -float64(start), 0)

				gl.PointSize(2)
				block.vertex_data.Render(gl.POINTS)
			})
		}

		glh.With(glh.WindowCoords{Invert: true}, func() {
			// Block boundaries
			gl.Color4dv(&border_color)

			gl.LineWidth(1)
			glh.With(glh.Primitive{gl.LINE_LOOP}, func() {
				glh.Squared(x1, y1, x2-x1, y2-y1)
			})
		})
	})
}
예제 #10
0
파일: lem1802.go 프로젝트: jteeuwen/dcpu
// Init creates gl related resources: textures, fonts, buffers, etc.
func (d *LEM1802Display) Init() (err error) {
	for _, dev := range d.cpu.Devices() {
		if dev, ok := dev.(*lem1802.Device); ok {
			d.dev = dev
			break
		}
	}

	d.createAtlas(
		(d.width-d.borderWidth*2)/lem1802.CellsPerRow,
		(d.height-d.borderHeight*2)/lem1802.CellsPerCol,
	)

	var pos [4 * 2]int16
	var clr [4 * 3]byte
	var tex [4 * 2]float32

	// Untextured quads
	d.quads_ut = glh.NewMeshBuffer(
		glh.RenderBuffered,
		glh.NewPositionAttr(2, gl.SHORT, gl.STATIC_DRAW),
		glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW),
	)

	// Textured quads
	d.quads_t = glh.NewMeshBuffer(
		glh.RenderBuffered,
		glh.NewPositionAttr(2, gl.SHORT, gl.STATIC_DRAW),
		glh.NewColorAttr(3, gl.UNSIGNED_BYTE, gl.DYNAMIC_DRAW),
		glh.NewTexCoordAttr(2, gl.FLOAT, gl.DYNAMIC_DRAW),
	)

	// Screen background/border.
	pos[2] = int16(d.width)
	pos[4] = int16(d.width)
	pos[5] = int16(d.height)
	pos[7] = int16(d.height)
	d.quads_ut.Add(pos[:], clr[:])

	gw := int16((d.width - d.borderWidth*2) / lem1802.CellsPerRow)
	gh := int16((d.height - d.borderHeight*2) / lem1802.CellsPerCol)

	// Add individual glyphs.
	//
	// These consist of two equal-sized quads stacked on top of
	// each other. The bottom one is an opaque quad with the glyph's
	// background color. The top one has the glyph texture with
	// its foreground color.
	for y := int16(0); y < lem1802.CellsPerCol; y++ {
		gy := y*gh + int16(d.borderHeight)

		for x := int16(0); x < lem1802.CellsPerRow; x++ {
			gx := x*gw + int16(d.borderWidth)

			pos[0] = gx
			pos[1] = gy
			pos[2] = gx + gw
			pos[3] = gy
			pos[4] = gx + gw
			pos[5] = gy + gh
			pos[6] = gx
			pos[7] = gy + gh

			// Glyph background.
			d.quads_ut.Add(pos[:], clr[:])

			// Glyph.
			d.quads_t.Add(pos[:], clr[:], tex[:])
		}
	}

	return
}
예제 #11
0
파일: main.go 프로젝트: pwaller/debris
func main() {
	var err error
	if err = glfw.Init(); err != nil {
		fmt.Fprintf(os.Stderr, "[e] %v\n", err)
		return
	}

	defer glfw.Terminate()

	w, h := 1980, 1080
	// w, h := 1280, 768
	if err = glfw.OpenWindow(w, h, 8, 8, 8, 16, 0, 32, glfw.Fullscreen); err != nil {
		fmt.Fprintf(os.Stderr, "[e] %v\n", err)
		return
	}

	defer glfw.CloseWindow()

	glfw.SetSwapInterval(1)
	glfw.SetWindowTitle("Debris")

	quadric = glu.NewQuadric()

	gl.Enable(gl.CULL_FACE)

	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LEQUAL)

	gl.Enable(gl.NORMALIZE)

	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

	gl.ShadeModel(gl.SMOOTH)
	gl.Enable(gl.LIGHTING)

	var (
		ambient        = []float32{0.1, 0.3, 0.6, 1}
		diffuse        = []float32{1, 1, 0.5, 1}
		specular       = []float32{0.4, 0.4, 0.4, 1}
		light_position = []float32{1, 0, 0, 0}

		// mat_specular  []float32 = []float32{1, 1, 0.5, 1}
		mat_specular  = []float32{1, 1, 0.75, 1}
		mat_shininess = float32(120)
		// light_position []float32 = []float32{0.0, 0.0, 1.0, 0.0}
	)

	const (
		fov               = 1.1 // degrees
		znear             = 145
		zfar              = 155
		camera_z_offset   = -150
		camera_x_rotation = 0 // degrees
		// camera_x_rotation = 20 // degrees

		starfield_fov = 45

		faces        = 1000
		earth_radius = 1
	)

	gl.Lightfv(gl.LIGHT1, gl.AMBIENT, ambient)
	gl.Lightfv(gl.LIGHT1, gl.DIFFUSE, diffuse)
	gl.Lightfv(gl.LIGHT1, gl.SPECULAR, specular)
	gl.Lightfv(gl.LIGHT1, gl.POSITION, light_position)
	gl.Enable(gl.LIGHT1)

	mat_emission := []float32{0, 0, 0.1, 1}
	gl.Materialfv(gl.FRONT_AND_BACK, gl.EMISSION, mat_emission)
	gl.Materialfv(gl.FRONT_AND_BACK, gl.SPECULAR, mat_specular)
	gl.Materialf(gl.FRONT_AND_BACK, gl.SHININESS, mat_shininess)

	gl.ClearColor(0.02, 0.02, 0.02, 1)
	gl.ClearDepth(1)
	gl.ClearStencil(0)
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

	b := createBuffer()

	planetoids := []*Planetoid{}
	for i := 0; i < 1000; i++ {
		p := &Planetoid{
			apogee:  1.2 + rand.Float64()*0.7,
			perigee: 1.5,
			// inclination: 45,
			inclination: rand.Float64()*20 - 10,
			// inclination: 0,
			phase0:      rand.Float64() * 360,
			rising_node: rand.Float64() * 10,
			phase:       0,
			// radius:      rand.Float32()*0.05 + 0.01, //float32(r),
			radius: rand.Float32()*0.0125 + 0.005, //float32(r),
			// quadric:     glu.NewQuadric(),
			circle: b,
		}
		planetoids = append(planetoids, p)
	}

	// Initial projection matrix:

	var aspect float64

	glfw.SetWindowSizeCallback(func(w, h int) {
		gl.Viewport(0, 0, w, h)

		gl.MatrixMode(gl.PROJECTION)
		gl.LoadIdentity()
		aspect = float64(w) / float64(h)
		glu.Perspective(fov, aspect, znear, zfar)
	})

	d := float64(0)

	wireframe := false
	atmosphere := false
	polar := false
	rotating := false
	front := false
	earth := true
	cone := true
	shadowing := true
	tilt := false
	running := true

	glfw.SetKeyCallback(func(key, state int) {
		if state != glfw.KeyPress {
			// Don't act on key coming up
			return
		}

		switch key {
		case 'A':
			atmosphere = !atmosphere
		case 'C':
			cone = !cone
		case 'E':
			earth = !earth
		case 'R':
			rotating = !rotating
		case 'F':
			front = !front
			if front {
				gl.FrontFace(gl.CW)
			} else {
				gl.FrontFace(gl.CCW)
			}
		case 'S':
			shadowing = !shadowing
		case 'T':
			tilt = !tilt
		case 'W':
			wireframe = !wireframe
			method := gl.GLenum(gl.FILL)
			if wireframe {
				method = gl.LINE
			}
			gl.PolygonMode(gl.FRONT_AND_BACK, method)

		case glfw.KeyF2:
			println("Screenshot captured")
			// glh.CaptureToPng("screenshot.png")

			w, h := glh.GetViewportWH()
			im := image.NewRGBA(image.Rect(0, 0, w, h))
			glh.ClearAlpha(1)
			gl.Flush()
			glh.CaptureRGBA(im)

			go func() {
				fd, err := os.Create("screenshot.png")
				if err != nil {
					panic("Unable to open file")
				}
				defer fd.Close()

				png.Encode(fd, im)
			}()

		case 'Q', glfw.KeyEsc:
			running = !running

		case glfw.KeySpace:
			polar = !polar
		}
	})

	_ = rand.Float64

	stars := glh.NewMeshBuffer(
		glh.RenderArrays,
		glh.NewPositionAttr(3, gl.DOUBLE, gl.STATIC_DRAW),
		glh.NewColorAttr(3, gl.DOUBLE, gl.STATIC_DRAW))

	const Nstars = 50000
	points := make([]float64, 3*Nstars)
	colors := make([]float64, 3*Nstars)

	for i := 0; i < Nstars; i++ {
		const R = 1

		phi := rand.Float64() * 2 * math.Pi
		z := R * (2*rand.Float64() - 1)
		theta := math.Asin(z / R)

		points[i*3+0] = R * math.Cos(theta) * math.Cos(phi)
		points[i*3+1] = R * math.Cos(theta) * math.Sin(phi)
		points[i*3+2] = z

		const r = 0.8
		v := rand.Float64()*r + (1 - r)
		colors[i*3+0] = v
		colors[i*3+1] = v
		colors[i*3+2] = v
	}

	stars.Add(points, colors)

	render_stars := func() {
		glh.With(glh.Attrib{gl.DEPTH_BUFFER_BIT | gl.ENABLE_BIT}, func() {
			gl.Disable(gl.LIGHTING)
			gl.PointSize(1)
			gl.Color4f(1, 1, 1, 1)

			gl.Disable(gl.DEPTH_TEST)
			gl.DepthMask(false)

			stars.Render(gl.POINTS)
		})
	}

	render_scene := func() {

		// Update light position (sensitive to current modelview matrix)
		gl.Lightfv(gl.LIGHT1, gl.POSITION, light_position)
		gl.Lightfv(gl.LIGHT2, gl.POSITION, light_position)

		if earth {
			Sphere(earth_radius, faces)
		}

		unlit_points := glh.Compound(glh.Disable(gl.LIGHTING), glh.Primitive{gl.POINTS})
		glh.With(unlit_points, func() {
			gl.Vertex3d(1, 0, 0)
		})

		for _, p := range planetoids {
			const dt = 0.1 // TODO: Frame update
			p.Render(dt)
		}

		glh.With(glh.Disable(gl.LIGHTING), func() {
			// Atmosphere
			gl.Color4f(0.25, 0.25, 1, 0.1)

			if atmosphere && earth {
				Sphere(earth_radius*1.025, 100)
			}

			gl.PointSize(10)

			glh.With(glh.Primitive{gl.POINTS}, func() {
				gl.Color4f(1.75, 0.75, 0.75, 1)
				gl.Vertex3d(-1.04, 0, 0)
			})
		})

	}

	render_shadow_volume := func() {

		glh.With(glh.Attrib{
			gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.ENABLE_BIT |
				gl.POLYGON_BIT | gl.STENCIL_BUFFER_BIT,
		}, func() {

			gl.Disable(gl.LIGHTING)

			if shadowing {
				// gl.Disable(gl.DEPTH_TEST)
				gl.DepthMask(false)
				gl.DepthFunc(gl.LEQUAL)

				gl.Enable(gl.STENCIL_TEST)
				gl.ColorMask(false, false, false, false)
				gl.StencilFunc(gl.ALWAYS, 1, 0xffffffff)
			}

			shadow_volume := func() {
				const sv_length = 2
				const sv_granularity = 100
				const sv_radius = earth_radius * 1.001

				// Shadow cone
				glh.With(glh.Matrix{gl.MODELVIEW}, func() {
					gl.Rotatef(90, 1, 0, 0)
					gl.Rotatef(90, 0, -1, 0)
					gl.Color4f(0.5, 0.5, 0.5, 1)
					glu.Cylinder(quadric, sv_radius, sv_radius*1.05,
						sv_length, sv_granularity, 1)

					glu.Disk(quadric, 0, sv_radius, sv_granularity, 1)

					glh.With(glh.Matrix{gl.MODELVIEW}, func() {
						gl.Translated(0, 0, sv_length)
						glu.Disk(quadric, 0, sv_radius*1.05, sv_granularity, 1)
					})
				})

				for _, p := range planetoids {
					p.RenderShadowVolume()
				}

			}

			if cone {
				gl.FrontFace(gl.CCW)
				gl.StencilOp(gl.KEEP, gl.KEEP, gl.INCR)

				shadow_volume()

				gl.FrontFace(gl.CW)
				gl.StencilOp(gl.KEEP, gl.KEEP, gl.DECR)

				shadow_volume()
			}

			if shadowing {
				gl.StencilFunc(gl.NOTEQUAL, 0, 0xffffffff)
				gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)

				gl.ColorMask(true, true, true, true)
				// gl.Disable(gl.STENCIL_TEST)

				gl.Disable(gl.DEPTH_TEST)

				gl.FrontFace(gl.CCW)
				// gl.Color4f(1, 0, 0, 0.75)
				gl.Color4f(0, 0, 0, 0.75)
				// gl.Color4f(1, 1, 1, 0.75)

				gl.LoadIdentity()
				gl.Translated(0, 0, camera_z_offset)
				// TODO: Figure out why this doesn't draw over the whole screen
				glh.With(glh.Disable(gl.LIGHTING), func() {
					glh.DrawQuadd(-10, -10, 20, 20)
				})

				// gl.FrontFace(gl.CW)
				// gl.Enable(gl.LIGHTING)
				// gl.Disable(gl.LIGHT1)
				// render_scene()
				// gl.Enable(gl.LIGHT1)
			}
		})
	}
	_ = render_stars

	for running {
		running = glfw.WindowParam(glfw.Opened) == 1

		glfw.SwapBuffers()

		gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)

		rotation := func() {
			if tilt {
				gl.Rotated(20, 1, 0, 0)
			}

			if polar {
				gl.Rotated(90, 1, 0, 0)
			}

			gl.Rotated(d, 0, -1, 0)
		}

		// Star field

		glh.With(glh.Matrix{gl.PROJECTION}, func() {
			gl.LoadIdentity()
			glu.Perspective(starfield_fov, aspect, 0, 1)

			glh.With(glh.Matrix{gl.MODELVIEW}, func() {
				gl.LoadIdentity()
				rotation()
				render_stars()
			})
		})

		gl.MatrixMode(gl.MODELVIEW)
		gl.LoadIdentity()
		gl.Translated(0, 0, camera_z_offset)

		rotation()
		if rotating {
			d += 0.2
		}

		_ = render_scene
		render_scene()

		_ = render_shadow_volume
		render_shadow_volume()
	}
}