package hrend import ( "bytes" "fmt" "image" "image/color" "log" "math" "strings" ) // Convert rgb to uint func Col2Uint(r, g, b byte) uint { return (uint(r) << 16) | (uint(g) << 8) | uint(b) } func Color2Uint(col color.Color) uint { r, g, b, _ := col.RGBA() //log.Print(r, g, b) return uint(((r & 0xff00) << 8) | (g & 0xff00) | ((b & 0xff00) >> 8)) } // Convert uint to rgb (in that order) func Uint2Col(col uint) (byte, byte, byte) { return byte((col >> 16) & 0xFF), byte((col >> 8) & 0xFF), byte(col & 0xFF) } // A simple buffer where you can set pixels type Framebuffer interface { Set(x uint, y uint, r byte, g byte, b byte) Get(x uint, y uint) (byte, byte, byte) GetUv(u float32, v float32) (byte, byte, byte) Dims() (uint, uint) } // Turn framebuffer into image, useful for processing into individual frames func ToImage(fb Framebuffer) *image.RGBA { width, height := fb.Dims() result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) for y := range height { for x := range width { c := color.RGBA{A: 255} c.R, c.G, c.G = fb.Get(x, y) result.Set(int(x), int(y), c) } } return result } // Color is in RGB (alpha not used right now) type SimpleFramebuffer struct { Data []byte Width uint Height uint } func (fb *SimpleFramebuffer) Dims() (uint, uint) { return fb.Width, fb.Height } // Sure hope this gets inlined... func (fb *SimpleFramebuffer) Set(x uint, y uint, r byte, g byte, b byte) { if x >= fb.Width || y >= fb.Height { return } fb.Data[(x+y*fb.Width)*3] = r fb.Data[(x+y*fb.Width)*3+1] = g fb.Data[(x+y*fb.Width)*3+2] = b } func (fb *SimpleFramebuffer) Get(x uint, y uint) (byte, byte, byte) { if x >= fb.Width || y >= fb.Height { return 0, 0, 0 } return fb.Data[(x+y*fb.Width)*3], fb.Data[(x+y*fb.Width)*3+1], fb.Data[(x+y*fb.Width)*3+2] } func (fb *SimpleFramebuffer) GetUv(u float32, v float32) (byte, byte, byte) { x := uint(float32(fb.Width)*u) & (fb.Width - 1) y := uint(float32(fb.Height)*(1-v)) & (fb.Height - 1) i := (x + y*fb.Width) * 3 return fb.Data[i], fb.Data[i+1], fb.Data[i+2] // return fb.Data[(x+y*fb.Width)*3], // fb.Data[(x+y*fb.Width)*3+1], // fb.Data[(x+y*fb.Width)*3+2] } func NewSimpleFramebuffer(width uint, height uint) *SimpleFramebuffer { return &SimpleFramebuffer{ Data: make([]byte, width*height*3), Width: width, Height: height, } } type RenderBuffer struct { Data Framebuffer ZBuffer []float32 //uint16 // Apparently 16 bit z-buffers are used Width uint Height uint } // Create a new framebuffer for the given width and height. func NewRenderbuffer(d Framebuffer, width uint, height uint) RenderBuffer { return RenderBuffer{ Data: d, ZBuffer: make([]float32, width*height), Width: width, Height: height, } } func NewTexture(texture image.Image, stride int) *SimpleFramebuffer { bounds := texture.Bounds() width := bounds.Dx() / stride height := bounds.Dy() / stride result := NewSimpleFramebuffer(uint(width), uint(height)) wlog := math.Log2(float64(width)) hlog := math.Log2(float64(height)) if wlog != math.Floor(wlog) || hlog != math.Floor(hlog) { panic("Texture must be power of two") } for y := bounds.Min.Y; y < bounds.Max.Y; y += stride { for x := bounds.Min.X; x < bounds.Max.X; x += stride { col := texture.At(x, y) r, g, b, _ := col.RGBA() result.Set(uint(x/stride), uint(y/stride), byte(r>>8), byte(g>>8), byte(b>>8)) } } return result } // Fill zbuffer with pixels that are max distance away func (fb *RenderBuffer) ResetZBuffer() { for i := range fb.ZBuffer { fb.ZBuffer[i] = 0 //65535 //math.MaxFloat32 } } // Given some image data, return a string that is the ppm of it func (fb *RenderBuffer) ExportPPM() string { log.Printf("ExportPPM called for framebuffer %dx%d", fb.Width, fb.Height) var result strings.Builder result.WriteString(fmt.Sprintf("P3\n%d %d\n255\n", fb.Width, fb.Height)) for y := range fb.Height { for x := range fb.Width { r, g, b := fb.Data.Get(x, y) result.WriteString(fmt.Sprintf("%d %d %d\t", r, g, b)) } result.WriteRune('\n') } return result.String() } func (fb *RenderBuffer) ExportPPMP6() []byte { log.Printf("ExportPPM6 called for framebuffer %dx%d", fb.Width, fb.Height) var result bytes.Buffer result.WriteString(fmt.Sprintf("P6\n%d %d\n255\n", fb.Width, fb.Height)) for y := range fb.Height { for x := range fb.Width { r, g, b := fb.Data.Get(x, y) result.Write([]byte{r, g, b}) } //result.WriteString(fmt.Sprintf("%d %d %d\t", r, g, b)) } //result.WriteRune('\n') return result.Bytes() } func (fb *RenderBuffer) ZBuffer_ExportPPM() string { var result strings.Builder mini := float32(math.MaxFloat32) maxi := float32(-math.MaxFloat32) for _, f := range fb.ZBuffer { if f == math.MaxFloat32 { continue } mini = min(f, mini) maxi = max(f, maxi) } result.WriteString(fmt.Sprintf("P2\n%d %d\n255\n", fb.Width, fb.Height)) for y := range fb.Height { for x := range fb.Width { if fb.ZBuffer[x+y*fb.Width] == math.MaxFloat32 { result.WriteString("0 ") } else { zp := byte(math.Abs(float64(255 * fb.ZBuffer[x+y*fb.Width] / (maxi - mini)))) result.WriteString(fmt.Sprintf("%d ", zp)) } } result.WriteRune('\n') } return result.String() }