package main import ( "flag" "fmt" "image" "image/color" "log" "math" "os" "path/filepath" "renderer4/hrend" "runtime/pprof" // For performance profiling (unnecessary) "time" _ "image/jpeg" rl "github.com/gen2brain/raylib-go/raylib" ) const ( NearClip = 0.01 FarClip = 100 Movement = 1.0 Rotation = 0.25 LookLock = math.Pi / 32 ) func must(err error) { if err != nil { panic(err) } } func loadObject(name string) (*hrend.ObjModel, hrend.Framebuffer) { ofile := filepath.Join("../../", name+".obj") tfile := filepath.Join("../../", name+".jpg") log.Printf("Loading obj %s, texture %s", ofile, tfile) of, err := os.Open(ofile) must(err) defer of.Close() o, err := hrend.ParseObj(of) must(err) // We also get rid of cached vertex info from the file //o.ClearCachedVertexInfo() jf, err := os.Open(tfile) must(err) defer jf.Close() timg, _, err := image.Decode(jf) must(err) texture := hrend.NewTexture(timg, 4) return o, texture } // However flag works... idk var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") var width = flag.Int("width", 640, "width of window or frame") var height = flag.Int("height", 480, "height of window or frame") var renderout = flag.String("renderout", "", "If set, rendering is done to a file instead of realtime") var renderinput = flag.String("renderinput", "", "If not realtime, the inputs are taken from here.") var xofs = flag.Float64("xofs", 0, "starting x-offset") var zofs = flag.Float64("zofs", 0, "starting z-offset") var yofs = flag.Float64("yofs", 0.5, "starting y-offset") var fov = flag.Float64("fov", 90, "the horizontal fov") var fps = flag.Int("fps", 60, "fps to run (realtime only)") var minlight = flag.Float64("minlight", 0.5, "Minimum light level") // var renderconfig = flag.String("renderconfig", "", "if set, rendering is written out") func IsRealtime() bool { return *renderout == "" } // Do next inputs, whether they come from raylib or a file func CameraInput(yaw, pitch float32) (float32, float32, hrend.Vec3f) { Fps := float32(*fps) mouse := rl.GetMouseDelta() pitch += Rotation * mouse.Y / Fps yaw += Rotation * mouse.X / Fps pitch = hrend.Clamp(pitch, LookLock, math.Pi-LookLock) newcamtrans := hrend.Vec3f{X: 0, Y: 0, Z: 0} move := float32(Movement) if rl.IsMouseButtonDown(rl.MouseButtonLeft) { move *= 6 } if rl.IsKeyDown(rl.KeyD) { newcamtrans.X += move / Fps } if rl.IsKeyDown(rl.KeyA) { newcamtrans.X -= move / Fps } // Moving forward moves in the negative z direction, since we FACE // the -z axis (the camera does anyway) if rl.IsKeyDown(rl.KeyW) { newcamtrans.Z -= move / Fps } if rl.IsKeyDown(rl.KeyS) { newcamtrans.Z += move / Fps } if rl.IsKeyDown(rl.KeySpace) { newcamtrans.Y += move / Fps } if rl.IsKeyDown(rl.KeyLeftShift) { newcamtrans.Y -= move / Fps } // translate the new camera movement based on the yaw var moverot hrend.Mat44f moverot.SetRotationY(-yaw) hnewcamtrans := moverot.MultiplyPoint3(newcamtrans) return yaw, pitch, hnewcamtrans.MakeConventional() } // -------------------------------------------- // // MAIN // // -------------------------------------------- func main() { log.Printf("Program start") flag.Parse() if *cpuprofile != "" { log.Printf("CPU profiling requested, write to %s", *cpuprofile) f, err := os.Create(*cpuprofile) must(err) defer f.Close() err = pprof.StartCPUProfile(f) must(err) defer pprof.StopCPUProfile() } Width := uint(*width) Height := uint(*height) var timer hrend.FrameTimer var fb hrend.Framebuffer var drawFunc func() if IsRealtime() { rl.InitWindow(int32(Width), int32(Height), "Simple renderer with raylib") defer rl.CloseWindow() rl.SetTargetFPS(int32(*fps)) rl.DisableCursor() rfb := NewRaylibBuffer(Width, Height) defer rl.UnloadTexture(rfb.Texture) defer rl.UnloadImageColors(rfb.Data) defer rl.UnloadImage(rfb.Image) fb = rfb drawFunc = func() { rl.UpdateTexture(rfb.Texture, rfb.Data) rl.BeginDrawing() rl.ClearBackground(rl.RayWhite) rl.DrawTexture(rfb.Texture, 0, 0, rl.White) rl.DrawText(fmt.Sprintf("Frame: %.2fms", timer.LastAverage.Seconds()*1000), 5, 5, 20, rl.Red) rl.EndDrawing() } } else { } rb := hrend.NewRenderbuffer(fb, Width, Height) // Generate world wtexraw := Checkerboard([]color.Color{color.RGBA{R: 0, G: 255, B: 0, A: 255}, color.RGBA{R: 50, G: 150, B: 0, A: 255}}, 32) wtex := hrend.NewTexture(wtexraw, 1) world := DiamondSquareTerrain(32, 0.05, 9) // must be power of two // Generate skybox skyraw := Gradient1px(color.RGBA{R: 100, G: 100, B: 255, A: 255}, color.RGBA{R: 0, G: 0, B: 25, A: 255}, 128) skytex := hrend.NewTexture(skyraw, 1) sky := Skybox() // Some static models we could put in the scene modnames := []string{"head", "diablo"} models := make([]*hrend.ObjModel, len(modnames)) textures := make([]hrend.Framebuffer, len(modnames)) for i, name := range modnames { models[i], textures[i] = loadObject(name) } // And the actual objects for the scene. We also put the world in there objects := make([]*hrend.ObjectDef, 0) objects = append(objects, hrend.NewObjectDef(world, wtex)) worldobj := objects[len(objects)-1] worldobj.Pos.Y -= 5 worldobj.Color = hrend.Vec3f{X: 0.0, Y: 1.0, Z: 0.0} objects = append(objects, hrend.NewObjectDef(sky, skytex)) // the actual skybox skyobj := objects[len(objects)-1] skyobj.Scale = 50 skyobj.Lighting = false skyobj.Color = hrend.Vec3f{X: 0.5, Y: 0.5, Z: 1.0} objects = append(objects, hrend.NewObjectDef(models[1], textures[1])) diabloobj := objects[len(objects)-1] diabloobj.Pos.Y += 1 diabloobj.Pos.Z -= 2 diabloobj.Color = hrend.Vec3f{X: 1.0, Y: 0.0, Z: 0.0} //diabloobj.Lighting = false // These don't really change var projection hrend.Mat44f projection.SetProjection(float32(*fov), float32(Width)/float32(Height), NearClip, FarClip) var camera hrend.Mat44f var newcamtrans hrend.Vec3f camtrans := hrend.Vec3f{X: float32(*xofs), Y: float32(*yofs), Z: float32(*zofs)} camup := hrend.Vec3f{X: 0, Y: 1, Z: 0} lightang := -math.Pi / 4 // Angle offset from "straight down" light := hrend.Vec3f{X: 0, Y: float32(-math.Cos(lightang)), Z: float32(math.Sin(lightang))} // In our system, 0 degree yaw is facing -Z, into the scene yaw := float32(0) pitch := float32(math.Pi / 2) // Start looking flat log.Printf("Starting render loop") for !rl.WindowShouldClose() { start := time.Now() yaw, pitch, newcamtrans = CameraInput(yaw, pitch) camtrans = *camtrans.Add(&newcamtrans) _ = camera.SetCamera(&camtrans, yaw, pitch, &camup) screenmat := camera.Inverse().Multiply(&projection) rb.ResetZBuffer() for y := range Height { for x := range Width { fb.Set(x, y, 0, 0, 0) } } var modelmat hrend.Mat44f var intensity float32 outvecs := make([]hrend.HVec3f, 0, 65536) for _, o := range objects { // Create the final matrix modelmat.SetLookAt(&o.Pos, o.Pos.Add(&o.LookVec), &camup) modelmat.ScaleSelf(o.Scale) matrix3d := modelmat.Multiply(screenmat) outvecs = hrend.PerspectiveAll(o.Model.Vertices, matrix3d, outvecs) for _, f := range o.Model.Faces { // Generate the new amount of triangles for each face (could be clipped) //func ClipFace(face Facei, vecs []HVec3f, texs []Vec3f) []Facef { for _, sc := range hrend.ClipFace(f, outvecs, o.Model.VTexture) { for i := range 3 { // Screen coord mapping sc[i].Pos.ViewportSelf(*width, *height) } //log.Print(sc[0].Pos, sc[1].Pos, sc[2].Pos, matrix3d) if o.Lighting { l1 := o.FV(&f, 2).Sub(o.FV(&f, 0)) n := l1.CrossProduct(o.FV(&f, 1).Sub(o.FV(&f, 0))) n = n.Normalize() // light = lookvec // use this for weird things intensity = n.MultSimp(&light) if intensity < 0 { intensity = 0 // Don't just not draw the triangle: it should be black } intensity = (intensity + float32(*minlight)) / (1 + float32(*minlight)) } else { intensity = 1.0 } hrend.TriangleTextured(&rb, o.Texture, intensity, &sc) //sc[0], sc[1], sc[2]) //hrend.TriangleFlat(&rb, o.Color.Scale(intensity), sc[0].Pos, sc[1].Pos, sc[2].Pos) } //break // only render one face //hrend.TriangleFlat(&rb, hrend.Col2Uint(byte(255*intensity), byte(255*intensity), byte(255*intensity)), sc[0].Pos, sc[1].Pos, sc[2].Pos) } //break // only render one object } //log.Print(minz, maxz) timer.Add(time.Since(start), 10) drawFunc() } }