func viewMatrix(pos world.Position) (glm.Matrix4, glm.Matrix4) { const eyeZ = .5 Rd := glm.RotZ(float64(-90 * pos.F.Value())) Td := glm.Vector3{float64(-pos.X), float64(-pos.Y), -eyeZ}.Translation() Ri := glm.RotZ(float64(90 * pos.F.Value())) Ti := glm.Vector3{float64(pos.X), float64(pos.Y), eyeZ}.Translation() Vd := Rd.Mult(Td) // Direct. Vi := Ti.Mult(Ri) // Inverse. return Vd, Vi }
func gatherBuildingsPositions( rendererPositions map[world.ModelId]Positions, buildings world.Buildings, offsetX, offsetY float64, defaultR *glm.Matrix4, // Can be nil. worldToEye glm.Matrix4, ) { for coords, building := range buildings { position := glm.Vector3{ float64(coords.X) + offsetX, float64(coords.Y) + offsetY, 0, }.Translation() facer, ok := building.(world.Facer) if ok { // We obey the facing of the buildings that have one. r := glm.RotZ(float64(90 * facer.Facing().Value())) position = position.Mult(r) } else { // Buildings without facing receive the provided default facing. // It is given as a precalculated rotation matrix `defaultR`. position = position.Mult(*defaultR) } position = worldToEye.Mult(position) // Shaders work in view space. rendererID := building.Model() positions := rendererPositions[rendererID] positions = append(positions, position) rendererPositions[rendererID] = positions } }
func main() { var programState programState var err error glfw.SetErrorCallback(errorCallback) if !glfw.Init() { panic("GLFW initialization failed.") } defer glfw.Terminate() glfw.WindowHint(glfw.ContextVersionMajor, 3) glfw.WindowHint(glfw.ContextVersionMinor, 3) glfw.WindowHint(glfw.SrgbCapable, glfw.True) glfw.WindowHint(glfw.Resizable, glfw.False) programState.Gl.Window, err = glfw.CreateWindow(640, 480, "Daggor", nil, nil) if err != nil { panic(err) } defer programState.Gl.Window.Destroy() programState.Gl.glfwKeyEventList = makeGlfwKeyEventList() programState.Gl.Window.SetKeyCallback(programState.Gl.glfwKeyEventList.Callback) programState.Gl.Window.MakeContextCurrent() if ec := gl.Init(); ec != 0 { panic(fmt.Sprintf("OpenGL initialization failed with code %v.", ec)) } // For some reason, here, the OpenGL error flag for me contains "Invalid enum". // This is weird since I have not done anything yet. I imagine that something // goes wrong in gl.Init. Reading the error flag clears it, so I do it. // Here's the reason: // https://github.com/go-gl/glfw3/issues/50 // Maybe I should not even ask for a core profile anyway. // What are the advantages are asking for a core profile? if err := glw.CheckGlError(); err != nil { err.Description = "OpenGL has this error right after init for some reason." //fmt.Println(err) } { // Assert OpenGL >= 3.3. major := programState.Gl.Window.GetAttribute(glfw.ContextVersionMajor) minor := programState.Gl.Window.GetAttribute(glfw.ContextVersionMinor) fmt.Printf("OpenGL version %v.%v.\n", major, minor) if (major < 3) || (major == 3 && minor < 3) { panic("OpenGL version 3.3 required, your video card/driver does not seem to support it.") } } programState.Gl.context = glw.NewGlContext() programState.Gl.Shapes[floorID] = sculpt.FloorInstNorm(programState.Gl.context.Programs) programState.Gl.Shapes[ceilingID] = sculpt.CeilingInstNorm(programState.Gl.context.Programs) programState.Gl.Shapes[wallID] = sculpt.WallInstNorm(programState.Gl.context.Programs) { // I do not like the default reference frame of OpenGl. // By default, we look in the direction -z, and y points up. // I want z to point up, and I want to look in the direction +x // by default. That way, I move on an xy plane where z is the // altitude, instead of having the altitude stuffed between // the two things I use the most. And my reason for pointing // toward +x is that I use the convention for trigonometry: // an angle of 0 points to the right (east) of the trigonometric // circle. Bonus point: this matches Blender's reference frame. myFrame := glm.ZUP.Mult(glm.RotZ(90)) eye_to_clip := glm.PerspectiveProj(110, 640./480., .1, 100).Mult(myFrame) programState.Gl.context.SetEyeToClp(eye_to_clip) } gl.Enable(gl.FRAMEBUFFER_SRGB) gl.Enable(gl.DEPTH_TEST) gl.Enable(gl.CULL_FACE) gl.CullFace(gl.BACK) gl.FrontFace(gl.CCW) gl.Enable(gl.TEXTURE_CUBE_MAP_SEAMLESS) // This needs a texture server attached to the context, like for programs. glw.LoadSkybox() programState.World = world.MakeWorld() mainLoop(programState) }
func render(programState programState) { actorID := programState.World.Player_id position, ok := programState.World.Level.ActorPosition(actorID) if !ok { panic("Could not find player's character position.") } worldToEye, eyeToWorld := viewMatrix(position) programState.Gl.context.SetEyeToWld(eyeToWorld) programState.Gl.context.UpdateCamera() // Lights. { const T = 3000000000 t := programState.World.Time phase := (2 * math.Pi) * float64(t) / T lights := []glw.Light{ glw.Light{ Color: glm.Vector4{1, 0, 0, 0}, Origin: worldToEye.MultV(glm.Vector4{ 3 + 2*math.Cos(phase), 0 + 1*math.Sin(phase), .5 + .45*math.Cos(phase*6.321), 1, }), }, glw.Light{ Color: glm.Vector4{0, .5, 0, 0}, Origin: worldToEye.MultV(glm.Vector4{0, 0, .1, 1}), }, } programState.Gl.context.SetLights(lights) programState.Gl.context.UpdateLights() } // Geometry. verticalPositions := make(map[world.ModelId]Positions) horizontalPositions := make(map[world.ModelId]Positions) gatherBuildingsPositions( horizontalPositions, programState.World.Level.Floors, 0, 0, nil, worldToEye, ) gatherBuildingsPositions( horizontalPositions, programState.World.Level.Ceilings, 0, 0, nil, worldToEye, ) for i := 0; i < 4; i++ { rot := glm.RotZ(180 + 90*float64(i)) gatherBuildingsPositions( verticalPositions, programState.World.Level.Walls[i], 0, 0, &rot, worldToEye, ) } // Finally render all the things. // Reduce fill rate by drawing the closest objects first and making use of // the depth test to cull fragments before expensive lightings computations. gl.ClearColor(0.0, 0.0, 0.4, 0.0) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) for rendererID, pos := range verticalPositions { sort.Sort(pos) programState.Gl.Shapes[rendererID].Render(pos) } for rendererID, pos := range horizontalPositions { sort.Sort(pos) programState.Gl.Shapes[rendererID].Render(pos) } }