From 1e2c2fe662ccd2a8b44fb4dcdd3a2bf858db075d Mon Sep 17 00:00:00 2001 From: squishy Date: Sat, 23 Mar 2024 04:15:23 +0000 Subject: [PATCH] initial commit --- .gitignore | 2 + build.bat | 5 + color/color.go | 4 + font-thin.png | Bin 0 -> 1666 bytes font.go | 32 +++++ font.png | Bin 0 -> 2428 bytes go.aseprite | Bin 0 -> 1327 bytes go.mod | 9 ++ go.png | Bin 0 -> 986 bytes go.sum | 9 ++ image.go | 356 +++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 156 ++++++++++++++++++++++ math.go | 24 ++++ rectangle.go | 7 + tilemap.go | 31 +++++ vec2.go | 62 +++++++++ 16 files changed, 697 insertions(+) create mode 100644 .gitignore create mode 100644 build.bat create mode 100644 color/color.go create mode 100644 font-thin.png create mode 100644 font.go create mode 100644 font.png create mode 100644 go.aseprite create mode 100644 go.mod create mode 100644 go.png create mode 100644 go.sum create mode 100644 image.go create mode 100644 main.go create mode 100644 math.go create mode 100644 rectangle.go create mode 100644 tilemap.go create mode 100644 vec2.go 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 0000000000000000000000000000000000000000..1b33c2b6b7b20fed1d27a79fe56eb889fa92b8ae GIT binary patch literal 1666 zcmV-|27UR7P)Px#U{Fj{MF0Q*A|NzHC_H94F^^U|-*G0{pl0sFn)~uLndMMwzA!hGZcAw|QcEN< zJUKr)khCtv7Sy)&GQ7#RQ^iaAqT}-AHZuSB(JBVQ#3oy0)}Ew?8ewXDTntTMiAi@ zb2sLE4iM)n&XoJ{sI>#csaf_407x;3eAcg6vofaOprhF&`VO0HaX99ESNGu5&QBi1 zsgZ|&lf2D@6xacvIFle0J(LguzV=8mC22fQ4hNZPYO^YQN7RVcE36etghHz^=ONQl z0uZJ;UmgJ>1Uvg(eF9PIqz}o zr8qF85sa&kx6jFqpM9DKI+tGN$QN!56~@6aZ%g}V+h*ZmQ7&$!k~u#rJvEvRKxSQ6 zVgbk?u&gcsh6|Dc;MM_HU3B`n2YZNo$~XY}=GSxJ`nT#4ZX8*3Svy!nnz)bjey1%= ztIaE!Pe_bzk?I~F09ZHIn>zrNs$Ed>K7i#Nz-)*K?B$k;J6s2h+T=M?qoq^#kw8>Rin5puCy0GcvIGSGe8?onzDCS=|Lr4fv`P|6juSAUVcJRYNRC?z|LT1c#nO0*4~ z!3RTS9!AP=oaQuh%Z`(cLe1gZ0zmj;GIOwc5Ry-`(gXhE4=5Z`Uj3`}))4&1(@@BzUHi-LUSN z2jv6T^lr5D2EUfQ>of43KLuD^T4XV7l@nEoC zxj}@b4|6KqgcpNvWI;mfj)+?)55@n@_D306; zQ3#bf*FsZ1rXo51%7LJJ50Q8v$5Q9C!)zL-IUXTedEHlzd@NsIW6@^-Z?NbOJg6^+ z`GhfhKL1w%@Y0b-YkfvT_qlFd#|@yZSg9U1Osme%$s2mG+k4}|O8}P#Snl)F+y4N- z=_sB?$U3wk)|?bUz5yT^@6B4)g}AQ(wAZ<$(FYwx5o_h-fvA?`ln1SU;p7*P4aq3F z(d%%U{x1O{zWII10Mg{X?fvb#E6jW*8BH(cMbf)u_4z!BJ#{3BG8Pv9Xb{6#o)x?- z8Iby4sEtyAa&!4-HUN-eB$EI}0Ev=2=e2MT;BaEx3*E3Fk*{wVz`U{`GId_e#20bm z8Z}r%55D$HNBb-mrd8$Z^0twZI#9RHVd{7c5Z!F3s+hUX@BS2^is7jT;6lPT_a;E) zc(dX}HtH$6O3vH*;=y*badVZ}f_69*mq)=6r_G1w%TM7x+t!|c05yIR^M+x$D*ylh M07*qoM6N<$fL7ytkO literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..01b92d8dc294cfda8676cfa88c5eab63d0eaeaca GIT binary patch literal 2428 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 BEY<)3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0808bf57fbfac0a8948c70ff5a9c28182f3c036e GIT binary patch literal 986 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>V4mXX;uumf z=k4A7*}{o3$3H$-5wWtmve0?L59Wp0|C{EizHvatd%=KAv%<=nLj}KhA!4^=y zl=Z{Q*WYK(KeO9B|A}4nop(9sa_r~vXxrX4xwzG6o=NVHZMT;%<-K2K-Nh~EVDsea zRMXA7HchLnpH^{V`FsDh-<5k7FsiiM>q@$Wv7CHZzI~VR_v3*Ajur=*Jf!~}eY*4W zN5ktMdJV(#;$2vzcdLj0%Uu*?$1GuN{ipkMakjw5H4pV~eYr4O%JD-!V`ZC6(7b~! zQgOFF|M`^|&(5(ie|{;?bwi!MRt>L@YVH!4@@HSc9TQ~he)>6Lb; zzi>bQ9UKvUkV(S7E%4sLGN2>6tv>60(n$)`kgE!A&-Z!2-&m>SAzfGWd(Uspf(j48 zKhX`kOt(KwzWw`7|IaC(6hG9nv|j&od~)KEtqn&t-bOdP-yl(D$9~&A?zT1ahT7g& z{BF-#tvnMLKcNSh9g^R^v(4iiL2)PE{N?f&t1PR^qEtu;lKJBf=*MfKYrb}9jZ>S z!GcNPI?%SMat-hQ^_MfrcC2`Da`W}L>!0`Ut&(~b!S!?R&0@P{ci*MldtUwL^D>gTe~DWM4fD%Ydr literal 0 HcmV?d00001 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 +}