package main import ( "flag" "fmt" "image" "image/color" "log" "math" "os" "path/filepath" "renderer2/hrend" "runtime/pprof" // For performance profiling (unnecessary) "time" _ "image/jpeg" rl "github.com/gen2brain/raylib-go/raylib" ) const ( NearClip = 0.0001 FarClip = 10 Movement = 1.0 Rotation = 0.25 LookLock = math.Pi / 32 ) func must(err error) { if err != nil { panic(err) } } type ObjectDef struct { Model *hrend.ObjModel Texture hrend.Framebuffer // This needs to go somewhere else eventually! Pos hrend.Vec3f LookVec hrend.Vec3f Scale float32 Lighting bool } func NewObjectDef(model *hrend.ObjModel, texture hrend.Framebuffer) *ObjectDef { result := ObjectDef{ Model: model, Texture: texture, LookVec: hrend.Vec3f{X: 0, Y: 0, Z: -1}, Scale: 1, Lighting: true, } return &result } 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) 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} if rl.IsKeyDown(rl.KeyD) { newcamtrans.X += Movement / Fps } if rl.IsKeyDown(rl.KeyA) { newcamtrans.X -= Movement / 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 -= Movement / Fps } if rl.IsKeyDown(rl.KeyS) { newcamtrans.Z += Movement / Fps } if rl.IsKeyDown(rl.KeySpace) { newcamtrans.Y += Movement / Fps } if rl.IsKeyDown(rl.KeyLeftShift) { newcamtrans.Y -= Movement / Fps } // translate the new camera movement based on the yaw var moverot hrend.Mat44f moverot.SetRotationY(-yaw) newcamtrans = moverot.MultiplyPoint3(newcamtrans) return yaw, pitch, newcamtrans } // -------------------------------------------- // // 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 := FlatTerrain(1) // Generate skybox skyraw := Gradient1px(color.RGBA{R: 100, G: 100, B: 255, A: 255}, color.RGBA{R: 255, G: 255, B: 255, A: 255}, 32) 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([]*ObjectDef, 0) objects = append(objects, NewObjectDef(world, wtex)) objects = append(objects, NewObjectDef(sky, skytex)) // the actual skybox skyobj := objects[len(objects)-1] skyobj.Scale = 50 skyobj.Lighting = false objects = append(objects, NewObjectDef(models[1], textures[1])) objects[len(objects)-1].Pos.Y += 1 objects[len(objects)-1].Pos.Z -= 2 // These don't really change var projection hrend.Mat44f //, viewport hrend.Mat44f projection.SetProjection(float32(*fov), float32(Width)/float32(Height), NearClip, FarClip) //viewport.SetViewportSimple(int(Width), int(Height), 1) //65535) 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) //screenmat = screenmat.Multiply(&viewport) rb.ResetZBuffer() for y := range Height { for x := range Width { fb.Set(x, y, 0, 0, 0) } } var sc [3]hrend.Vertex var modelmat hrend.Mat44f var intensity float32 var minz = float32(math.MaxFloat32) var maxz = float32(-math.MaxFloat32) 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) for _, f := range o.Model.Faces { for i := range 3 { sc[i] = f[i] sc[i].Pos = matrix3d.MultiplyPoint3(f[i].Pos) minz = min(minz, sc[i].Pos.Z) maxz = max(maxz, sc[i].Pos.Z) } log.Print(o.Model.Faces[0][0].Pos, o.Model.Faces[0][1].Pos, o.Model.Faces[0][2].Pos) log.Print(sc[0].Pos, sc[1].Pos, sc[2].Pos) log.Print(matrix3d) for i := range 3 { // Perspective divide (?) and screen coord sc[i].Pos.ViewportSelf(*width, *height) } //log.Print(sc[0].Pos, sc[1].Pos, sc[2].Pos, matrix3d) if o.Lighting { l1 := f[2].Pos.Sub(&f[0].Pos) n := l1.CrossProduct(f[1].Pos.Sub(&f[0].Pos)) n = n.Normalize() // light = lookvec // use this for weird things intensity = n.MultSimp(&light) if intensity < 0 { intensity = 0 } intensity = (intensity + float32(*minlight)) / (1 + float32(*minlight)) } else { intensity = 1.0 } hrend.TriangleTextured(&rb, o.Texture, intensity, sc[0], sc[1], sc[2]) 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() } }