diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00f6589 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.fleet +.idea \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..4cf5237 --- /dev/null +++ b/build.bat @@ -0,0 +1,5 @@ +set CGO_ENABLED=1 +set CC=x86_64-w64-mingw32-gcc +set GOOS=windows +set GOARCH=amd64 +go build -tags static -ldflags "-s -w" \ No newline at end of file diff --git a/color/color.go b/color/color.go new file mode 100644 index 0000000..31159c4 --- /dev/null +++ b/color/color.go @@ -0,0 +1,4 @@ +package color + +const White = 0xffffffff +const Black = 0xff000000 diff --git a/font-thin.png b/font-thin.png new file mode 100644 index 0000000..1b33c2b Binary files /dev/null and b/font-thin.png differ diff --git a/font.go b/font.go new file mode 100644 index 0000000..c5f38b1 --- /dev/null +++ b/font.go @@ -0,0 +1,32 @@ +package bloob + +import _ "embed" + +type ch struct { + xForward int + image *Image +} + +type Font struct { + chars []ch + lineHeight int +} + +//go:embed "font.png" +var fontdata []byte +var DefaultFont = LoadTilesetFontBytes(fontdata, Vec2i{X: 8, Y: 14}) + +//go:embed "font-thin.png" +var fontdataThin []byte +var DefaultFontThin = LoadTilesetFontBytes(fontdataThin, Vec2i{X: 6, Y: 8}) + +func LoadTilesetFontBytes(data []byte, tilesize Vec2i) *Font { + tiles := LoadImageBytes(data).MakeTileset(tilesize) + var font Font + for i := 0; i < len(tiles); i += 1 { + c := ch{xForward: tilesize.X, image: tiles[i]} + font.chars = append(font.chars, c) + } + font.lineHeight = tilesize.Y + 2 + return &font +} diff --git a/font.png b/font.png new file mode 100644 index 0000000..01b92d8 Binary files /dev/null and b/font.png differ diff --git a/go.aseprite b/go.aseprite new file mode 100644 index 0000000..57c616e Binary files /dev/null and b/go.aseprite differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c085d46 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module git.danitheskunk.com/squishy/blooblib + +go 1.22.1 + +require ( + github.com/askeladdk/aseprite v0.0.4 + github.com/veandco/go-sdl2 v0.4.38 + golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 +) diff --git a/go.png b/go.png new file mode 100644 index 0000000..0808bf5 Binary files /dev/null and b/go.png differ diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..663b411 --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +github.com/askeladdk/aseprite v0.0.4 h1:/k1VTiDkPORnrzonUUV5oXWwdHBoYjIIYJ1K/PupNMU= +github.com/askeladdk/aseprite v0.0.4/go.mod h1:lVW4EwZ7lgQjeHp7MhYj1NII5a/yLYyvAo7COPIn3WY= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/veandco/go-sdl2 v0.4.38 h1:lx8syOA2ccXlgViYkQe2Kn/4xt+p9mdd1Qc/yYMrmSo= +github.com/veandco/go-sdl2 v0.4.38/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= diff --git a/image.go b/image.go new file mode 100644 index 0000000..03cdcac --- /dev/null +++ b/image.go @@ -0,0 +1,356 @@ +package bloob + +import ( + "bytes" + _ "github.com/askeladdk/aseprite" + "image" + _ "image/png" + "math" + "os" + "runtime/pprof" + "sync" + "unsafe" +) + +type Image struct { + Data []uint32 + Size Vec2i + Alpha bool +} + +var wg sync.WaitGroup +var threadProfile = pprof.Lookup("threadcreate") + +func NewImage(size Vec2i) *Image { + return &Image{Data: make([]uint32, size.Size()), Size: Vec2i{X: size.X, Y: size.Y}, Alpha: true} +} + +func NewImageFromPointer(pointer unsafe.Pointer, size Vec2i) *Image { + return &Image{Data: unsafe.Slice((*uint32)(pointer), size.Size()), Size: Vec2i{X: size.X, Y: size.Y}, Alpha: true} +} + +func LoadImage(path string) *Image { + data, err := os.ReadFile(path) + if err != nil { + panic(err) + } + return LoadImageBytes(data) +} + +func LoadImageBytes(data []byte) *Image { + file, _, _ := image.Decode(bytes.NewReader(data)) + + width := file.Bounds().Max.X + height := file.Bounds().Max.Y + img := NewImage(Vec2i{X: width, Y: height}) + for y := 0; y < height; y += 1 { + for x := 0; x < width; x += 1 { + r, g, b, a := file.At(x, y).RGBA() + r = r >> 8 + g = g >> 8 + b = b >> 8 + a = a >> 8 + img.Data[x+y*width] = r<<16 | g<<8 | b | a<<24 + } + } + return img +} + +func (image *Image) Clear(color uint32) { + for i := 0; i < image.Size.X*image.Size.Y; i += 1 { + image.Data[i] = color + } +} + +func CropToArea(sizeDst Vec2i, rectSrc Recti, posDst Vec2i) (start, end, dst Vec2i) { + if posDst.X+rectSrc.Pos.X < 0 { + start.X = -posDst.X + } else { + start.X = rectSrc.Pos.X + } + if posDst.X+rectSrc.Size.X-rectSrc.Pos.X >= sizeDst.X { + end.X = sizeDst.X - posDst.X + } else { + end.X = rectSrc.Pos.X + rectSrc.Size.X + } + + if posDst.Y+rectSrc.Pos.Y < 0 { + start.Y = -posDst.Y + } else { + start.Y = rectSrc.Pos.Y + } + if posDst.Y+rectSrc.Size.Y-rectSrc.Pos.Y >= sizeDst.Y { + end.Y = sizeDst.Y - posDst.Y + } else { + end.Y = rectSrc.Pos.Y + rectSrc.Size.Y + } + dst = Sub(posDst, rectSrc.Pos) + //println(dst.X, posDst.X, rectSrc.Pos.X) + return +} + +func (image *Image) Draw(other *Image, dst Vec2i) { + image.DrawSub(other, dst, Recti{Size: other.Size}) +} + +func (image *Image) DrawSub(other *Image, pos Vec2i, rect Recti) { + start, end, dst := CropToArea(image.Size, rect, pos) + //println(start.X, "|", end.X, "|", dst.X, "|", pos.X) + //println(end.Y) + if other.Alpha { + for y := start.Y; y < end.Y; y += 1 { + for x := start.X; x < end.X; x += 1 { + //println(y) + val := other.Data[x+y*other.Size.X] + if val&0xff000000 > 0 { + image.Data[(x+dst.X)+(y+dst.Y)*image.Size.X] = val + } + } + } + } else { + for y := start.Y; y < end.Y; y += 1 { + for x := start.X; x < end.X; x += 1 { + image.Data[(x+dst.X)+(y+dst.Y)*image.Size.X] = other.Data[x+y*other.Size.X] + } + } + } +} + +func (image *Image) DrawColor(other *Image, dst Vec2i, color uint32) { + image.DrawSubColor(other, dst, Recti{Size: other.Size}, color) +} + +func (image *Image) DrawSubColor(other *Image, pos Vec2i, rect Recti, color uint32) { + start, end, dst := CropToArea(image.Size, rect, pos) + for y := start.Y; y < end.Y; y += 1 { + for x := start.X; x < end.X; x += 1 { + //println(y) + val := other.Data[x+y*other.Size.X] + if val&0xff000000 > 0 { + image.Data[(x+dst.X)+(y+dst.Y)*image.Size.X] = color + } + } + } +} + +func (image *Image) DrawUpscale(other *Image) { + var scale int + if image.Size.X/other.Size.X < image.Size.Y/other.Size.Y { + scale = image.Size.X / other.Size.X + } else { + scale = image.Size.Y / other.Size.Y + } + + switch scale { + case 1: + wg.Add(other.Size.Y) + for y := 0; y < other.Size.Y; y += 1 { + go func(image, other *Image, y int) { + defer wg.Done() + for x := 0; x < other.Size.X; x += 1 { + val := other.Data[x+y*other.Size.X] + image.Data[(x*scale)+(y*scale)*image.Size.X] = val + } + }(image, other, y) + } + wg.Wait() + break + case 2: + wg.Add(other.Size.Y) + for y := 0; y < other.Size.Y; y += 1 { + go func(image, other *Image, y int) { + defer wg.Done() + for x := 0; x < other.Size.X; x += 1 { + val := other.Data[x+y*other.Size.X] + val2 := (val & 0xfefefe) >> 1 + dx := x * 2 + dy := y * 2 + image.Data[(dx+0)+(dy+0)*image.Size.X] = val + image.Data[(dx+1)+(dy+0)*image.Size.X] = val + image.Data[(dx+0)+(dy+1)*image.Size.X] = val2 + image.Data[(dx+1)+(dy+1)*image.Size.X] = val2 + } + }(image, other, y) + } + wg.Wait() + break + case 3: + wg.Add(other.Size.Y) + //println("Before thread count : ", threadProfile.Count()) + for y := 0; y < other.Size.Y; y += 1 { + go func(image, other *Image, y int) { + defer wg.Done() + for x := 0; x < other.Size.X; x += 1 { + val := other.Data[x+y*other.Size.X] + val2 := (val & 0xfefefe) >> 1 + dx := x * 3 + dy := y * 3 + image.Data[(dx+0)+(dy+0)*image.Size.X] = val + image.Data[(dx+1)+(dy+0)*image.Size.X] = val + image.Data[(dx+2)+(dy+0)*image.Size.X] = val2 + image.Data[(dx+0)+(dy+1)*image.Size.X] = val + image.Data[(dx+1)+(dy+1)*image.Size.X] = val + image.Data[(dx+2)+(dy+1)*image.Size.X] = val2 + image.Data[(dx+0)+(dy+2)*image.Size.X] = val2 + image.Data[(dx+1)+(dy+2)*image.Size.X] = val2 + image.Data[(dx+2)+(dy+2)*image.Size.X] = val2 + } + }(image, other, y) + } + //println("After thread count : ", threadProfile.Count()) + wg.Wait() + break + case 4: + wg.Add(other.Size.Y) + for y := 0; y < other.Size.Y; y += 1 { + go func(image, other *Image, y int) { + defer wg.Done() + for x := 0; x < other.Size.X; x += 1 { + val := other.Data[x+y*other.Size.X] + dx := x * 4 + dy := y * 4 + image.Data[(dx+0)+(dy+0)*image.Size.X] = val + image.Data[(dx+1)+(dy+0)*image.Size.X] = val + image.Data[(dx+2)+(dy+0)*image.Size.X] = val + image.Data[(dx+3)+(dy+0)*image.Size.X] = val + image.Data[(dx+0)+(dy+1)*image.Size.X] = val + image.Data[(dx+1)+(dy+1)*image.Size.X] = val + image.Data[(dx+2)+(dy+1)*image.Size.X] = val + image.Data[(dx+3)+(dy+1)*image.Size.X] = val + image.Data[(dx+0)+(dy+2)*image.Size.X] = val + image.Data[(dx+1)+(dy+2)*image.Size.X] = val + image.Data[(dx+2)+(dy+2)*image.Size.X] = val + image.Data[(dx+3)+(dy+2)*image.Size.X] = val + image.Data[(dx+0)+(dy+3)*image.Size.X] = val + image.Data[(dx+1)+(dy+3)*image.Size.X] = val + image.Data[(dx+2)+(dy+3)*image.Size.X] = val + image.Data[(dx+3)+(dy+3)*image.Size.X] = val + } + }(image, other, y) + } + wg.Wait() + break + default: + wg.Add(other.Size.Y) + for y := 0; y < other.Size.Y; y += 1 { + go func(image, other *Image, y int) { + defer wg.Done() + for x := 0; x < other.Size.X; x += 1 { + val := other.Data[x+y*other.Size.X] + for sy := 0; sy < scale; sy += 1 { + for sx := 0; sx < scale; sx += 1 { + image.Data[(x*scale+sx)+(y*scale+sy)*image.Size.X] = val + } + } + } + }(image, other, y) + } + wg.Wait() + break + } +} + +func (image *Image) DrawText(str string, font *Font, pos Vec2i, color uint32) { + origX := pos.X + for _, ch := range str { + c := font.chars[ch] + if ch == 10 { + pos.X = origX + pos.Y += font.lineHeight + continue + } + image.DrawColor(c.image, pos, color) + pos.X += c.xForward + } +} + +func (image *Image) DrawTextSin(str string, font *Font, pos Vec2i, color uint32, freq, amplitude, phase float64) { + origX := pos.X + for _, ch := range str { + c := font.chars[ch] + if ch == 10 { + pos.X = origX + pos.Y += font.lineHeight + continue + } + image.DrawColor(c.image, Vec2i{X: pos.X, Y: int(math.Sin(float64(pos.X)*math.Pi/4/freq+phase*math.Pi*2)*amplitude) + pos.Y}, color) + pos.X += c.xForward + } +} + +func (image *Image) DrawTextOutline(str string, font *Font, pos Vec2i, color, outlineColor uint32) { + origX := pos.X + for _, ch := range str { + c := font.chars[ch] + if ch == 10 { + pos.X = origX + pos.Y += font.lineHeight + continue + } + x := pos.X + y := pos.Y + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x, Y: y - 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x, Y: y + 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y - 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y - 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x, Y: y + 2}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 2}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 2}, outlineColor) + image.DrawColor(c.image, pos, color) + pos.X += c.xForward + } +} + +func (image *Image) DrawTextOutlineSin(str string, font *Font, pos Vec2i, color, outlineColor uint32, freq, amplitude, phase float64) { + origX := pos.X + for _, ch := range str { + c := font.chars[ch] + if ch == 10 { + pos.X = origX + pos.Y += font.lineHeight + continue + } + x := pos.X + y := int(math.Sin(float64(pos.X)*math.Pi/4/freq+phase*math.Pi*2)*amplitude) + pos.Y + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x, Y: y - 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x, Y: y + 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y - 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y - 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 1}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x, Y: y + 2}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 2}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 2}, outlineColor) + image.DrawColor(c.image, Vec2i{X: x, Y: y}, color) + pos.X += c.xForward + } +} + +func (image *Image) DrawTilemap(tilemap *Tilemap, pos Vec2i) { + tilesize := tilemap.Tileset[0].Size + for y := 0; y < tilemap.Size.Y; y += 1 { + for x := 0; x < tilemap.Size.X; x += 1 { + tpos := Vec2i{X: x, Y: y} + image.Draw(tilemap.Tileset[tilemap.Get(tpos)], Add(Mul(tpos, tilesize), pos)) + } + } +} + +func (image *Image) MakeTileset(tileSize Vec2i) []*Image { + tileCount := Div(image.Size, tileSize) + var tiles []*Image + for y := 0; y < tileCount.Y; y += 1 { + for x := 0; x < tileCount.X; x += 1 { + tile := NewImage(tileSize) + tile.DrawSub(image, Vec2i{}, Recti{Pos: Vec2i{X: x * tileSize.X, Y: y * tileSize.Y}, Size: tileSize}) + tiles = append(tiles, tile) + } + } + return tiles +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..5c677c7 --- /dev/null +++ b/main.go @@ -0,0 +1,156 @@ +package main + +import ( + _ "embed" + "fmt" + "git.danitheskunk.com/squishy/blooblib/color" + . "git.danitheskunk.com/squishy/blooblib/src" + "github.com/veandco/go-sdl2/sdl" + "time" +) + +const Width = 640 +const Height = 360 + +//test + +func main() { + _ = sdl.Init(sdl.INIT_EVERYTHING) + defer sdl.Quit() + + scale := 3 + + window, _ := sdl.CreateWindow( + "test", + sdl.WINDOWPOS_CENTERED, + sdl.WINDOWPOS_CENTERED, + int32(Width*scale), + int32(Height*scale), + sdl.WINDOW_SHOWN, + ) + surface, _ := window.GetSurface() + //data := unsafe.Slice((*uint32)(surface.Data()), Width*Height) + windowImage := NewImageFromPointer(surface.Data(), Vec2i{X: Width * scale, Y: Height * scale}) + screen := NewImage(Vec2i{X: Width, Y: Height}) + screen.Alpha = false + //sprite := NewImage(Vec2i{X: 32, Y: 32}) + //sprite.Clear(0xabcdef) + fmt.Printf("%d\n", surface.Format.BitsPerPixel) + println("\n"[0]) + + tiles := LoadImage("go.png").MakeTileset(Vec2i{X: 16, Y: 16}) + tilemap := NewTilemap(Vec2i{X: 40, Y: 23}, tiles) + tilemap.Clear(16) + for y := 0; y < 19; y += 1 { + for x := 0; x < 19; x += 1 { + tilemap.Set(Vec2i{X: 10 + x, Y: 1 + y}, 3) + } + //tilemap.Set(Vec2i{X: 9, Y: 1 + y}, 17) + tilemap.Set(Vec2i{X: 10, Y: 1 + y}, 11) + tilemap.Set(Vec2i{X: 28, Y: 1 + y}, 12) + } + + for x := 0; x < 19; x += 1 { + //tilemap.Set(Vec2i{X: 10 + x, Y: 0}, 18) + tilemap.Set(Vec2i{X: 10 + x, Y: 1}, 9) + tilemap.Set(Vec2i{X: 10 + x, Y: 19}, 10) + tilemap.Set(Vec2i{X: 10 + x, Y: 20}, 17) + tilemap.Set(Vec2i{X: 10 + x, Y: 21}, 18) + } + + for y := 0; y < 21; y += 1 { + tilemap.Set(Vec2i{X: 29, Y: 1 + y}, 19) + } + tilemap.Set(Vec2i{X: 10, Y: 21}, 20) + tilemap.Set(Vec2i{X: 29, Y: 1}, 21) + + tilemap.Set(Vec2i{X: 10, Y: 1}, 5) + tilemap.Set(Vec2i{X: 28, Y: 1}, 6) + tilemap.Set(Vec2i{X: 10, Y: 19}, 7) + tilemap.Set(Vec2i{X: 28, Y: 19}, 8) + + tilemap.Set(Vec2i{X: 13, Y: 4}, 4) + tilemap.Set(Vec2i{X: 25, Y: 4}, 4) + tilemap.Set(Vec2i{X: 13, Y: 16}, 4) + tilemap.Set(Vec2i{X: 25, Y: 16}, 4) + tilemap.Set(Vec2i{X: 13, Y: 10}, 4) + tilemap.Set(Vec2i{X: 13, Y: 16}, 4) + tilemap.Set(Vec2i{X: 25, Y: 10}, 4) + tilemap.Set(Vec2i{X: 25, Y: 16}, 4) + tilemap.Set(Vec2i{X: 19, Y: 4}, 4) + tilemap.Set(Vec2i{X: 19, Y: 10}, 4) + tilemap.Set(Vec2i{X: 19, Y: 16}, 4) + + _ = window.UpdateSurface() + + lastSecond := time.Now() + framesSinceLastSecond := 0 + + targetFPS := 60 + frameDuration := time.Second / time.Duration(targetFPS) + lastFrame := lastSecond + + running := true + tick := 0 + for running { + for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { + switch event.(type) { + case *sdl.QuitEvent: + running = false + break + } + } + now := time.Now() + delta := now.Sub(lastSecond) + //println(delta.Seconds()) + //println(now.Second()) + if delta.Seconds() >= 1.0 { + fmt.Printf("FPS: %5d | mspf: %f\n", framesSinceLastSecond, 1000.0/float32(framesSinceLastSecond)) + framesSinceLastSecond = 0 + lastSecond = lastSecond.Add(time.Second) + } + + delta2 := now.Sub(lastFrame) + if delta2 >= frameDuration { + + //t := (float64(tick)) / 1000 + screen.Clear(0x123456) + screen.DrawTextOutline( + // "Hello World, this is a test,\nand now for another line :)", + // "Hello World, this is a test", + // "Vigilance, deathtouch, haste\n\nQuesting Beast can't be blocked by creatures\nwith power 2 or less.\n\nCombat damage that would be dealt by\ncreatures you control can't be prevented.\n\nWhenever Questing Beast deals combat damage\nto an opponent, it deals that much damage to\ntarget planeswalker that player controls.", + "Lorem ipsum dolor sit amet,\n"+ + "consectetur adipiscing elit,\n"+ + "sed do eiusmod tempor incididunt\n"+ + "ut labore et dolore magna aliqua.\n"+ + "Ut enim ad minim veniam, quis\n"+ + "nostrud exercitation ullamco\n"+ + "laboris nisi ut aliquip ex ea\n"+ + "commodo consequat. Duis aute\n"+ + "irure dolor in reprehenderit in\n"+ + "voluptate velit esse cillum dolore\n"+ + "eu fugiat nulla pariatur.\n"+ + "Excepteur sint occaecat cupidatat\n"+ + "non proident, sunt in culpa qui\n"+ + "officia deserunt mollit anim id\n"+ + "est laborum.", + DefaultFont, + Vec2i{X: 10, Y: 10}, + 0xffdddd, + color.Black, + // 50, + // 3.0, + // float64(tick) / 60.0, + ) + screen.DrawTilemap(tilemap, Vec2i{}) + screen.Draw(tilemap.Tileset[2], Vec2i{X: 16 * 25, Y: 16 * 4}) + screen.Draw(tilemap.Tileset[1], Vec2i{X: 16 * 13, Y: 16 * 16}) + windowImage.DrawUpscale(screen) + //windowImage.Draw(screen, Vec2i{}) + tick += 1 + framesSinceLastSecond += 1 + lastFrame = lastFrame.Add(frameDuration) + } + _ = window.UpdateSurface() + } +} diff --git a/math.go b/math.go new file mode 100644 index 0000000..7a7f7c3 --- /dev/null +++ b/math.go @@ -0,0 +1,24 @@ +package bloob + +import "math" + +func FastSin(x float64) float64 { + const PI = 3.14159265358979323846264338327950288 + const INVPI = 0.31830988618379067153776752674502872 + const A = 0.00735246819687011731341356165096815 + const B = -0.16528911397014738207016302002888890 + const C = 0.99969198629596757779830113868360584 + + k := int32(math.Round(INVPI * x)) + x -= float64(k) * PI + x2 := x * x + x = x * (C + x2*(B+A*x2)) + if k%2 != 0 { + x = -x + } + return x +} + +func FastCos(x float64) float64 { + return FastSin(x + math.Pi/2) +} diff --git a/rectangle.go b/rectangle.go new file mode 100644 index 0000000..3750d2f --- /dev/null +++ b/rectangle.go @@ -0,0 +1,7 @@ +package bloob + +type Rect[T Number] struct { + Pos, Size Vec2[T] +} + +type Recti = Rect[int] diff --git a/tilemap.go b/tilemap.go new file mode 100644 index 0000000..32573c8 --- /dev/null +++ b/tilemap.go @@ -0,0 +1,31 @@ +package bloob + +type Tilemap struct { + tiles []int + Size Vec2i + Tileset []*Image +} + +func NewTilemap(size Vec2i, tileset []*Image) *Tilemap { + return &Tilemap{tiles: make([]int, size.Size()), Size: size, Tileset: tileset} +} + +func (tilemap *Tilemap) Set(pos Vec2i, val int) { + if pos.X >= 0 && pos.Y >= 0 && pos.X < tilemap.Size.X && pos.Y < tilemap.Size.Y { + tilemap.tiles[pos.X+pos.Y*tilemap.Size.X] = val + } +} + +func (tilemap *Tilemap) Get(pos Vec2i) int { + if pos.X >= 0 && pos.Y >= 0 && pos.X < tilemap.Size.X && pos.Y < tilemap.Size.Y { + return tilemap.tiles[pos.X+pos.Y*tilemap.Size.X] + } else { + return 0 + } +} + +func (tilemap *Tilemap) Clear(val int) { + for i := 0; i < len(tilemap.tiles); i += 1 { + tilemap.tiles[i] = val + } +} diff --git a/vec2.go b/vec2.go new file mode 100644 index 0000000..d46825c --- /dev/null +++ b/vec2.go @@ -0,0 +1,62 @@ +package bloob + +import ( + "golang.org/x/exp/constraints" + "math" +) + +type Number interface { + constraints.Integer | constraints.Float +} +type Vec2[T Number] struct { + X, Y T +} +type Vec2i = Vec2[int] + +func Add[T Number](a, b Vec2[T]) Vec2[T] { + return Vec2[T]{X: a.X + b.X, Y: a.Y + b.Y} +} + +func AddScalar[T Number](a Vec2[T], b T) Vec2[T] { + return Vec2[T]{X: a.X + b, Y: a.Y + b} +} + +func Sub[T Number](a, b Vec2[T]) Vec2[T] { + return Vec2[T]{X: a.X - b.X, Y: a.Y - b.Y} +} + +func SubScalar[T Number](a Vec2[T], b T) Vec2[T] { + return Vec2[T]{X: a.X - b, Y: a.Y - b} +} + +func Mul[T Number](a, b Vec2[T]) Vec2[T] { + return Vec2[T]{X: a.X * b.X, Y: a.Y * b.Y} +} + +func MulScalar[T Number](a Vec2[T], b T) Vec2[T] { + return Vec2[T]{X: a.X * b, Y: a.Y * b} +} + +func Div[T Number](a, b Vec2[T]) Vec2[T] { + return Vec2[T]{X: a.X / b.X, Y: a.Y / b.Y} +} + +func DivScalar[T Number](a Vec2[T], b T) Vec2[T] { + return Vec2[T]{X: a.X / b, Y: a.Y / b} +} + +func (a Vec2[T]) Size() T { + return a.X * a.Y +} + +func Dot[T Number](a, b Vec2[T]) T { + return a.X*b.X + a.Y*b.Y +} + +func (a Vec2[T]) Mag() float64 { + return math.Sqrt(float64(a.MagSqr())) +} + +func (a Vec2[T]) MagSqr() T { + return a.X*a.X + a.Y*a.Y +}