2024-07-28 22:01:15 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"image/color"
|
2024-07-28 23:10:48 +00:00
|
|
|
//"log"
|
2024-07-29 00:59:38 +00:00
|
|
|
"image"
|
2024-07-28 22:01:15 +00:00
|
|
|
"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()
|
2024-07-28 23:10:48 +00:00
|
|
|
//log.Print(r, g, b)
|
|
|
|
return uint(((r & 0xff00) << 8) | (g & 0xff00) | ((b & 0xff00) >> 8))
|
2024-07-28 22:01:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Color is in ARGB (alpha not used right now)
|
|
|
|
type Framebuffer struct {
|
|
|
|
Data []uint
|
|
|
|
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 NewFramebuffer(width uint, height uint) Framebuffer {
|
|
|
|
return Framebuffer{
|
|
|
|
Data: make([]uint, width*height),
|
|
|
|
ZBuffer: make([]float32, width*height),
|
|
|
|
Width: width,
|
|
|
|
Height: height,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-29 00:59:38 +00:00
|
|
|
func NewTexture(texture image.Image, skip int) Framebuffer {
|
|
|
|
bounds := texture.Bounds()
|
|
|
|
width := bounds.Dx() / skip
|
|
|
|
height := bounds.Dy() / skip
|
|
|
|
result := Framebuffer{
|
|
|
|
Data: make([]uint, width*height),
|
|
|
|
Width: uint(width),
|
|
|
|
Height: uint(height),
|
|
|
|
}
|
|
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y += skip {
|
|
|
|
for x := bounds.Min.X; x < bounds.Max.X; x += skip {
|
|
|
|
col := texture.At(x, y)
|
|
|
|
result.Set(uint(x/skip), uint(y/skip), Color2Uint(col))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2024-07-28 22:01:15 +00:00
|
|
|
// Fill zbuffer with pixels that are max distance away
|
|
|
|
func (fb *Framebuffer) ResetZBuffer() {
|
|
|
|
for i := range fb.ZBuffer {
|
|
|
|
fb.ZBuffer[i] = math.MaxFloat32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-29 00:59:38 +00:00
|
|
|
func (fb *Framebuffer) GetUv(u float32, v float32) uint {
|
|
|
|
x := uint(float32(fb.Width) * u)
|
|
|
|
y := uint(float32(fb.Height) * (1 - v))
|
|
|
|
return fb.Data[x+y*fb.Width]
|
|
|
|
}
|
|
|
|
|
2024-07-28 22:01:15 +00:00
|
|
|
// Sure hope this gets inlined...
|
|
|
|
func (fb *Framebuffer) Set(x uint, y uint, color uint) {
|
|
|
|
fb.Data[x+y*fb.Width] = color
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *Framebuffer) SetSafe(x uint, y uint, color uint) {
|
|
|
|
if x >= fb.Width || y >= fb.Height {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fb.Data[x+y*fb.Width] = color
|
|
|
|
}
|
|
|
|
|
|
|
|
// Given some image data, return a string that is the ppm of it
|
|
|
|
func (fb *Framebuffer) ExportPPM() string {
|
|
|
|
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 := Uint2Col(fb.Data[x+y*fb.Width])
|
|
|
|
result.WriteString(fmt.Sprintf("%d %d %d\t", r, g, b))
|
|
|
|
}
|
|
|
|
result.WriteRune('\n')
|
|
|
|
}
|
|
|
|
return result.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fb *Framebuffer) ExportPPMP6() []byte {
|
|
|
|
var result bytes.Buffer
|
|
|
|
result.WriteString(fmt.Sprintf("P6\n%d %d\n255\n", fb.Width, fb.Height))
|
|
|
|
for i := range fb.Data {
|
|
|
|
r, g, b := Uint2Col(fb.Data[i])
|
|
|
|
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 *Framebuffer) 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()
|
|
|
|
}
|