Ejemplo n.º 1
0
func initWindow() {

	// Function called to do the re-rendering
	glut.DisplayFunc(display)
	// Called when the visibility of the program changes
	glut.VisibilityFunc(visible)
	// Called when a regular ascii character is pressed
	glut.KeyboardFunc(keyboardIn)
	// Called when any non-ascii character is pressed
	glut.SpecialFunc(specialIn)
	// Called when the size of the window changes
	glut.ReshapeFunc(reshape)

	// affect our projection matrix
	gl.MatrixMode(gl.PROJECTION)
	gl.LoadIdentity() // Load an identity matrix -> projection
	// Specify the bounds of the of our scene
	gl.Ortho(0, 40, 0, 40, 0, 40)

	// Now affect our modelview matrix
	gl.MatrixMode(gl.MODELVIEW)
	// Make points be rendererd larger
	gl.PointSize(3.0)

	currentWindow = glut.GetWindow()
}
Ejemplo n.º 2
0
func InitDisplay() {
	gl.MatrixMode(gl.PROJECTION)
	gl.LoadIdentity()
	gl.Ortho(0, 100, 0, 100, -1, 1)
	gl.MatrixMode(gl.MODELVIEW)
	gl.PointSize(3.0)
}
Ejemplo n.º 3
0
func (s *Display) drawFrame(screenData *types.Screen) {
	gl.Clear(gl.COLOR_BUFFER_BIT)
	gl.Disable(gl.DEPTH_TEST)
	gl.PointSize(float32(s.ScreenSizeMultiplier) + 1.0)
	gl.Begin(gl.POINTS)
	for y := 0; y < SCREEN_HEIGHT; y++ {
		for x := 0; x < SCREEN_WIDTH; x++ {
			var pixel types.RGB = screenData[y][x]
			gl.Color3ub(pixel.Red, pixel.Green, pixel.Blue)
			gl.Vertex2i(x*s.ScreenSizeMultiplier, y*s.ScreenSizeMultiplier)
		}
	}

	gl.End()
	glfw.SwapBuffers()
}
Ejemplo n.º 4
0
Archivo: main.go Proyecto: pwaller/mema
func main_loop(data *ProgramData) {
	start := time.Now()
	frames := 0
	lastblocks := 0

	// Frame counter
	go func() {
		for {
			time.Sleep(time.Second)
			if *verbose {
				memstats := new(runtime.MemStats)
				runtime.ReadMemStats(memstats)
				fps := float64(frames) / time.Since(start).Seconds()
				blocks := len(data.blocks)
				bps := float64(blocks-lastblocks) / time.Since(start).Seconds()
				lastblocks = blocks
				log.Printf("fps = %5.2f; blocks = %4d; bps = %5.2f sparemem = %6d MB; alloc'd = %6.6f; (+footprint = %6.6f)",
					fps, len(data.blocks), bps, SpareRAM(), float64(memstats.Alloc)/1024/1024,
					float64(memstats.Sys-memstats.Alloc)/1024/1024)

				PrintTimers(frames)
			}
			start = time.Now()
			frames = 0
		}
	}()

	// Necessary at the moment to prevent eventual OOM
	go func() {
		for {
			time.Sleep(5 * time.Second)
			if *verbose {
				log.Print("GC()")
			}
			runtime.GC()
			if *verbose {
				GCStats()
			}
		}
	}()

	var i int64 = -int64(*nback)

	// TODO(pwaller): Make this work again
	// text := glh.MakeText(data.filename, 32)

	// Location of mouse in record space
	var rec, rec_actual int64 = 0, 0

	var stacktext, dwarftext []*glh.Text
	var recordtext *glh.Text = nil

	var mousex, mousey, mousedownx, mousedowny int
	var mousepx, mousepy float64
	var lbutton bool
	escape_hit := false

	glfw.SetMouseWheelCallback(func(pos int) {
		nback_prev := *nback
		if pos < 0 {
			*nback = 40 * 1024 << uint(-pos)
		} else {
			*nback = 40 * 1024 >> uint(pos)
		}
		//log.Print("Mousewheel position: ", pos, " nback: ", *nback)

		if rec == 0 {
			return
		}

		// mouse cursor is at screen "rec_actual", and it should be after
		// the transformation

		// We need to adjust `i` to keep this value constant:
		// rec_actual == i + int64(constpart * float64(*nback))
		//   where constpart <- (-const + 2.) / 4.
		// (that way, the mouse is still pointing at the same place after scaling)

		constpart := float64(rec_actual-i) / float64(nback_prev)

		rec_actual_after := i + int64(constpart*float64(*nback))
		delta := rec_actual_after - rec_actual
		i -= delta

		// Ensure the mouse cursor position doesn't change when zooming
		rec = rec_actual - i
	})

	update_text := func() {
		if DoneThisFrame(RenderText) {
			return
		}

		if recordtext != nil {
			recordtext.Destroy()
			recordtext = nil
		}
		//r := 0 //data.GetRecord(rec_actual)
		//if r != nil {
		//log.Print(data.records[rec_actual])
		//recordtext = MakeText(r.String(), 32)
		//}

		for j := range stacktext {
			stacktext[j].Destroy()
		}
		// TODO: Load records on demand
		if false {
			stack := data.GetStackNames(rec_actual)
			stacktext = make([]*glh.Text, len(stack))
			for j := range stack {
				stacktext[j] = glh.MakeText(stack[j], 32)
			}
		}
	}

	glfw.SetMouseButtonCallback(func(button, action int) {
		switch button {
		case glfw.Mouse1:
			switch action {
			case glfw.KeyPress:
				mousedownx, mousedowny = mousex, mousey
				lbutton = true

				r := data.GetRecord(rec_actual)
				if r != nil {
					if r.Type == MEMA_ACCESS {
						log.Print(r)
						ma := r.MemAccess()
						dwarf := data.GetDwarf(ma.Pc)
						log.Print("Can has dwarf? ", len(dwarf))
						for i := range dwarf {
							log.Print("  ", dwarf[i])
							//recordtext = MakeText(data.records[rec_actual].String(), 32)

						}
						log.Print("")

						for j := range dwarftext {
							dwarftext[j].Destroy()
						}
						dwarftext = make([]*glh.Text, len(dwarf))
						for j := range dwarf {
							dwarftext[j] = glh.MakeText(fmt.Sprintf("%q", dwarf[j]), 32)
						}
					}

					//recordtext = MakeText(data.records[rec_actual].String(), 32)
				}

			case glfw.KeyRelease:
				lbutton = false
			}
		}
	})

	glfw.SetMousePosCallback(func(x, y int) {

		px, py := glh.WindowToProj(x, y)
		// Record index
		rec = int64((py+2)*float64(*nback)/4. + 0.5)
		rec_actual = rec + i

		dpy := py - mousepy
		di := int64(-dpy * float64(*nback) / 4.)

		if lbutton {
			i += di
		}

		mousepx, mousepy = px, py
		mousex, mousey = x, y

		//log.Printf("Mouse motion: (%3d, %3d), (%f, %f), (%d, %d) dpy=%f di=%d",
		//x, y, px, py, rec, rec_actual, dpy, di)

		update_text()
	})

	glfw.SetKeyCallback(func(key, state int) {
		switch key {
		case glfw.KeyEsc:
			escape_hit = true
		}
	})

	draw_mousepoint := func() {

		// Draw the mouse point
		glh.With(glh.Matrix{gl.MODELVIEW}, func() {
			gl.Translated(0, -2, 0)
			gl.Scaled(1, 4/float64(*nback), 1)
			gl.Translated(0, float64(rec), 0)

			gl.PointSize(10)
			glh.With(glh.Primitive{gl.POINTS}, func() {
				gl.Color4f(1, 1, 1, 1)
				gl.Vertex3d(mousepx, 0, 0)
			})
		})
	}

	draw_text := func() {
		// Draw any text
		glh.With(glh.WindowCoords{}, func() {
			w, h := glh.GetViewportWHD()

			glh.With(glh.Attrib{gl.ENABLE_BIT}, func() {
				gl.Enable(gl.TEXTURE_2D)
				// text.Draw(0, 0)
				for text_idx := range stacktext {
					stacktext[text_idx].Draw(int(w*0.55), int(h)-35-text_idx*16)
				}
				for text_idx := range dwarftext {
					dwarftext[text_idx].Draw(int(w*0.55), 35+text_idx*16)
				}
				if recordtext != nil {
					recordtext.Draw(int(w*0.55), 35)
				}
			})
		})
	}

	Draw = func() {

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

		// Draw the memory access/function data
		data.Draw(i, *nback)

		draw_mousepoint()
		draw_text()

		// Visible region quad
		// gl.Color4f(1, 1, 1, 0.25)
		// DrawQuadd(-2.1, -2.25, 4.4, 2.1 - -2.25)

		// StatsHUD()
	}

	interrupt := make(chan os.Signal)
	signal.Notify(interrupt, os.Interrupt)
	ctrlc_hit := false
	go func() {
		<-interrupt
		ctrlc_hit = true
	}()

	done := false
	for !done {
		done_this_frame = make(map[WorkType]bool)

		glh.With(&Timer{Name: "Draw"}, func() {
			Draw()
		})

		glfw.SwapBuffers()

		DoMainThreadWork()

		done = ctrlc_hit || escape_hit || glfw.WindowParam(glfw.Opened) == 0
		frames += 1
	}
}
Ejemplo n.º 5
0
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)
			})
		})
	})
}
Ejemplo n.º 6
0
func (block *Block) BuildTexture() {
	block.tex = glh.NewTexture(1024, 256)
	block.tex.Init()

	// TODO: use runtime.SetFinalizer() to clean up/delete the texture?
	glh.With(block.tex, func() {
		//gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, gl.TRUE)

		//gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)

		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST)
		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
		// TODO: Only try and activate anisotropic filtering if it is available

		gl.TexParameterf(gl.TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, MaxAnisotropy)
		gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 1)
	})

	for i := 0; i < 2; i++ {
		glh.With(&glh.Framebuffer{Texture: block.tex, Level: i}, func() {
			glh.With(glh.Attrib{gl.COLOR_BUFFER_BIT}, func() {
				gl.ClearColor(1, 0, 0, 0)
				gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
			})
			viewport_proj := glh.Compound(glh.Attrib{gl.VIEWPORT_BIT},
				glh.Matrix{gl.PROJECTION})

			glh.With(viewport_proj, func() {
				gl.Viewport(0, 0, block.tex.W/(1<<uint(i)), block.tex.H/(1<<uint(i)))
				gl.LoadIdentity()
				//gl.Ortho(0, float64(tex.w), 0, float64(tex.h), -1, 1)
				gl.Ortho(-2, -2+WIDTH, 2, -2, -1, 1)

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

					//gl.Hint(gl.LINES, gl.NICEST)
					//gl.LineWidth(4)
					/*
						glh.With(glh.Primitive{gl.LINES}, func() {
							gl.Color4f(1, 1, 1, 1)
							gl.Vertex2f(-2, 0)
							gl.Vertex2f(2, 0)
						})
					*/

					gl.PointSize(4)
					glh.With(glh.Matrix{gl.MODELVIEW}, func() {
						gl.Translated(0, -2, 0)
						gl.Scaled(1, 4/float64(block.nrecords), 1)

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

					/*
						gl.Color4f(0.5, 0.5, 1, 1)
						With(Primitive{gl.LINE_LOOP}, func() {
							b := 0.2
							Squared(-2.1+b, -2.1+b, 4.35-b*2, 4.2-b*2)
						})
					*/
				})
			})
		})
	}

	//block.img = block.tex.AsImage()
	if !block.detail_needed {
		block.vertex_data = nil
		runtime.GC()
	}

	blocks_rendered++
}
Ejemplo n.º 7
0
// Draw a test pattern
func TestWindowCoords(t *testing.T) {
	gltest.OnTheMainThread(func() {
		gltest.SetWindowSize(40, 40)

		w, h := GetViewportWH()
		With(WindowCoords{}, func() {
			// So that we draw in the middle of the pixel
			gl.Translated(0.5, 0.5, 0)

			// Draw stripes
			stride := 1
			internal_n := 4
			for b := 0; b < w/2-internal_n*stride; b += stride {
				if b/stride%2 == 0 {
					gl.Color4f(1, 1, 1, 1)
				} else {
					gl.Color4f(1, 0, 0, 1)
				}
				With(Primitive{gl.LINE_LOOP}, func() {
					gl.Vertex2i(b, b)
					gl.Vertex2i(w-b, b)
					gl.Vertex2i(w-b, h-b)
					gl.Vertex2i(b, h-b)
				})
			}

			// Central white, green, blue checked pattern
			gl.PointSize(2)
			With(Primitive{gl.POINTS}, func() {
				gl.Color4f(1, 1, 1, 1)
				gl.Vertex2i(w/2-2, h/2-2)
				gl.Vertex2i(w/2+2, h/2+2)

				gl.Color4f(0, 1, 0, 1)
				gl.Vertex2i(w/2+2, h/2-2)
				gl.Vertex2i(w/2-2, h/2+2)

				gl.Color4f(1, 1, 1, 1)
				gl.Vertex2i(w/2, h/2)
			})

			// Blue horizontal line to show
			With(Primitive{gl.LINE_LOOP}, func() {
				gl.Color4f(0, 0, 1, 1)
				gl.Vertex2i(0, h/2-4)
				gl.Vertex2i(w, h/2-4)

				gl.Vertex2i(w/2-4, 0)
				gl.Vertex2i(w/2-4, h)
			})

			// Remove some pixels near the boundaries
			gl.PointSize(1)
			gl.Color4f(0, 0, 0, 1)
			With(Primitive{gl.POINTS}, func() {
				// Black dot in top left
				gl.Vertex2i(0, 0)
				// Off the top right (should not be visible)
				gl.Vertex2i(w, 0)
				// Bottom left pixel (should be visible as a black dot)
				gl.Vertex2i(0, h-1)
			})
		})
	}, func() {
		CaptureToPng("TestWindowCoords.png")
	})
}
Ejemplo n.º 8
0
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()
	}
}