├── .gitignore ├── CONTRIBUTING.md ├── Examples ├── 0000_HelloWindow.c ├── 0000_Minimal.c ├── 0001_HelloRenderer.c ├── 0002_BitmapFont.c ├── 0003_RectanglePacker.c ├── 0004_SoftwareRenderer.c ├── 0005_ImgAllocator.c ├── 0006_ffi.py ├── 0007_Ecs.c └── 9999_Paint.c ├── Platform ├── LibC.c ├── OpenGL.c ├── Platform.h ├── PlatformLib.c └── X11.c ├── README.md ├── UNLICENSE ├── Utils └── SpriteEditor.c ├── WAIVER ├── WeebCore.c ├── build.sh ├── cflags.sh ├── font.wbspr └── pull_request_template.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | *.bak 3 | out.wbspr 4 | /bin 5 | /obj 6 | *.log 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Unlicensing your contributions 3 | 4 | This project is public domain. Any non-trivial contribution (more than 15 lines of code changed) 5 | should attach the disclaimer in the WAIVER file to their commit messages so that the intent to 6 | release the code into the public domain is clear. 7 | 8 | Make sure the waiver is in the COMMIT message, not just the pull request, so that the intent is 9 | preserved even if the project is moved to a different git platform. 10 | 11 | An even better way to sign your waiver is to `gpg --no-version --armor --sign WAIVER` and append 12 | that to the AUTHORS file (create it if it doesn't exist) 13 | 14 | If you're a company or your employer normally owns the code you write, you should attach a copyright 15 | disclaimer signed by your company that releases any copyright interest, such as 16 | 17 | ``` 18 | Some Company Inc. hereby disclaims all copyright interest in 19 | 20 | signature of Bob Smith 5 June 2020 21 | Bob Smith, President of Some Company 22 | ``` 23 | -------------------------------------------------------------------------------- /Examples/0000_HelloWindow.c: -------------------------------------------------------------------------------- 1 | #include "WeebCore.c" 2 | 3 | void AppInit() { 4 | SetAppName("Hello WeebCore"); 5 | SetAppClass("HelloWnd"); 6 | } 7 | 8 | #define WEEBCORE_IMPLEMENTATION 9 | #include "WeebCore.c" 10 | #include "Platform/Platform.h" 11 | -------------------------------------------------------------------------------- /Examples/0000_Minimal.c: -------------------------------------------------------------------------------- 1 | #define WEEBCORE_IMPLEMENTATION 2 | #include "WeebCore.c" 3 | #include "Platform/Platform.h" 4 | 5 | Mesh mesh; 6 | 7 | void Init() { 8 | mesh = MkMesh(); 9 | Col(mesh, 0xff3333); 10 | Quad(mesh, 10, 10, 200, 100); 11 | } 12 | 13 | void Frame() { 14 | PutMesh(mesh, 0, 0); 15 | } 16 | 17 | void AppInit() { 18 | On(INIT, Init); 19 | On(FRAME, Frame); 20 | } 21 | -------------------------------------------------------------------------------- /Examples/0001_HelloRenderer.c: -------------------------------------------------------------------------------- 1 | #include "WeebCore.c" 2 | 3 | float rot; 4 | Mesh mesh; 5 | Trans trans; 6 | 7 | void Init() { 8 | trans = MkTrans(); 9 | mesh = MkMesh(); 10 | Col(mesh, 0xffffff); 11 | Quad(mesh, -100, -50, 200, 100); 12 | Col(mesh, 0xb26633); 13 | Quad(mesh, -90, -40, 180, 80); 14 | } 15 | 16 | void Quit() { 17 | RmMesh(mesh); 18 | RmTrans(trans); 19 | } 20 | 21 | void Frame() { 22 | /* just a random time based animation */ 23 | rot = FltMod(rot + Delta() * 360, 360*2); 24 | SetRot(trans, rot); 25 | SetPos(trans, 320 + (int)(200 * Sin(rot / 2)), 240); 26 | SetScale1(trans, 0.25f + (1 + Sin(rot)) / 3); 27 | PutMesh(mesh, ToTmpMat(trans), 0); 28 | } 29 | 30 | void AppInit() { 31 | SetAppName("Hello WeebCore"); 32 | SetAppClass("HelloWnd"); 33 | On(INIT, Init); 34 | On(FRAME, Frame); 35 | On(QUIT, Quit); 36 | } 37 | 38 | #define WEEBCORE_IMPLEMENTATION 39 | #include "WeebCore.c" 40 | #include "Platform/Platform.h" 41 | -------------------------------------------------------------------------------- /Examples/0002_BitmapFont.c: -------------------------------------------------------------------------------- 1 | #include "WeebCore.c" 2 | 3 | Mesh mesh; 4 | Trans trans; 5 | Ft ft; 6 | 7 | void Init() { 8 | ft = DefFt(); 9 | 10 | trans = MkTrans(); 11 | SetPos(trans, 0, 120); 12 | SetScale1(trans, 2); 13 | 14 | mesh = MkMesh(); 15 | Col(mesh, 0xbebebe); 16 | FtMesh(mesh, ft, 10, 10, 17 | "Hello, this is a bitmap ft!\nWow! 123450123ABC\n\n" 18 | " Illegal1 = O0;\n" 19 | " int* p = &Illegal1;\n\n" 20 | " int main(int argc, char* argv[]) {\n" 21 | " puts(\"Hello World!\");\n" 22 | " }"); 23 | } 24 | 25 | void Quit() { 26 | RmMesh(mesh); 27 | RmFt(ft); 28 | RmTrans(trans); 29 | } 30 | 31 | void Frame() { 32 | PutMesh(mesh, 0, FtImg(ft)); 33 | PutMesh(mesh, ToTmpMat(trans), FtImg(ft)); 34 | } 35 | 36 | void AppInit() { 37 | SetAppClass("HelloWnd"); 38 | SetAppName("Hello Font"); 39 | On(INIT, Init); 40 | On(QUIT, Quit); 41 | On(FRAME, Frame); 42 | } 43 | 44 | #define WEEBCORE_IMPLEMENTATION 45 | #include "WeebCore.c" 46 | #include "Platform/Platform.h" 47 | -------------------------------------------------------------------------------- /Examples/0003_RectanglePacker.c: -------------------------------------------------------------------------------- 1 | #include "WeebCore.c" 2 | 3 | Wnd wnd; 4 | Ft font; 5 | Mesh help, full; 6 | Mesh packedRects; 7 | Packer pak; 8 | unsigned timeElapsed; 9 | int col; 10 | int isFull; 11 | float dragRect[4]; 12 | 13 | Mesh MkHelpText(Ft font) { 14 | Mesh mesh = MkMesh(); 15 | Col(mesh, 0xbebebe); 16 | FtMesh(mesh, font, 10, 10, "Left-click and drag to create a rectangle. Release to pack it\n" 17 | "F2 to reset"); 18 | return mesh; 19 | } 20 | 21 | Mesh MkFullText(Ft font) { 22 | Mesh mesh = MkMesh(); 23 | Col(mesh, 0x663333); 24 | FtMesh(mesh, font, 10, 32, "Rectangle didn't fit!"); 25 | return mesh; 26 | } 27 | 28 | void Init() { 29 | wnd = AppWnd(); 30 | font = DefFt(); 31 | help = MkHelpText(font); 32 | full = MkFullText(font); 33 | packedRects = MkMesh(); 34 | pak = MkPacker(WndWidth(wnd), WndHeight(wnd)); 35 | dragRect[0] = dragRect[2] = -1; 36 | } 37 | 38 | void Quit() { 39 | RmPacker(pak); 40 | RmMesh(packedRects); 41 | RmMesh(help); 42 | RmMesh(full); 43 | RmFt(font); 44 | } 45 | 46 | void KeyDown() { 47 | switch (Key(wnd)) { 48 | case MLEFT: { 49 | dragRect[0] = MouseX(wnd); 50 | dragRect[2] = MouseY(wnd); 51 | dragRect[1] = dragRect[0]; 52 | dragRect[3] = dragRect[2]; 53 | col = 0x606060 + (timeElapsed & 0x3f3f3f); /* pseudorandom col */ 54 | break; 55 | } 56 | case F2: { 57 | RmMesh(packedRects); 58 | packedRects = MkMesh(); 59 | RmPacker(pak); 60 | pak = MkPacker(WndWidth(wnd), WndHeight(wnd)); 61 | break; 62 | } 63 | } 64 | } 65 | 66 | void KeyUp() { 67 | switch (Key(wnd)) { 68 | case MLEFT: { 69 | /* ensure the coordinates are in the right order 70 | * when we drag right to left / bot to top */ 71 | float norm[4]; 72 | MemCpy(norm, dragRect, sizeof(norm)); 73 | NormRect(norm); 74 | isFull = !Pack(pak, norm); 75 | if (!isFull) { 76 | Col(packedRects, col); 77 | Quad(packedRects, norm[0], norm[2], RectWidth(norm), RectHeight(norm)); 78 | } 79 | dragRect[0] = dragRect[2] = -1; 80 | } 81 | } 82 | } 83 | 84 | void Motion() { 85 | if (dragRect[0] >= 0) { 86 | dragRect[1] += MouseDX(wnd); 87 | dragRect[3] += MouseDY(wnd); 88 | } 89 | } 90 | 91 | void Frame() { 92 | PutMesh(packedRects, 0, 0); 93 | timeElapsed += (int)(Delta(wnd) * 1000000); 94 | if (dragRect[0] >= 0) { 95 | Mesh mesh = MkMesh(); 96 | float norm[4]; 97 | MemCpy(norm, dragRect, sizeof(norm)); 98 | NormRect(norm); 99 | Col(mesh, col); 100 | Quad(mesh, norm[0], norm[2], RectWidth(norm), RectHeight(norm)); 101 | PutMesh(mesh, 0, 0); 102 | RmMesh(mesh); 103 | } 104 | PutMesh(help, 0, FtImg(font)); 105 | if (isFull) { 106 | PutMesh(full, 0, FtImg(font)); 107 | } 108 | } 109 | 110 | void AppInit() { 111 | SetAppName("WeebCore - Rectangle Packer Example"); 112 | SetAppClass("WeebCoreRectanglePacker"); 113 | On(INIT, Init); 114 | On(QUIT, Quit); 115 | On(KEYDOWN, KeyDown); 116 | On(KEYUP, KeyUp); 117 | On(MOTION, Motion); 118 | On(FRAME, Frame); 119 | } 120 | 121 | #define WEEBCORE_IMPLEMENTATION 122 | #include "WeebCore.c" 123 | #include "Platform/Platform.h" 124 | -------------------------------------------------------------------------------- /Examples/0004_SoftwareRenderer.c: -------------------------------------------------------------------------------- 1 | /* early prototype of the software renderer that will be built into WeebCore */ 2 | 3 | #include "WeebCore.c" 4 | 5 | /* passing pt's by value can generate a lot of inefficient copying but it makes code nice */ 6 | 7 | typedef struct _Pt { float x, y; } Pt; 8 | 9 | Pt SetPt(float x, float y) { 10 | Pt pt; 11 | pt.x = x; pt.y = y; 12 | return pt; 13 | } 14 | 15 | Pt AddPt(Pt a, Pt b) { return SetPt(a.x + b.x, a.y + b.y); } 16 | Pt SubPt(Pt a, Pt b) { return SetPt(a.x - b.x, a.y - b.y); } 17 | Pt MulPt(Pt a, Pt b) { return SetPt(a.x * b.x, a.y * b.y); } 18 | Pt MulPtFlt(Pt a, float x) { return SetPt(a.x * x, a.y * x); } 19 | float PtLen(Pt pt) { return Sqrt(pt.x * pt.x + pt.y * pt.y); } 20 | Pt NormPt(Pt pt) { return MulPtFlt(pt, 1 / PtLen(pt)); } 21 | 22 | typedef struct _Surf { 23 | int* pixs; 24 | int width, height; 25 | int col; 26 | }* Surf; 27 | 28 | Surf MkSurf(int width, int height) { 29 | Surf surf = Alloc(sizeof(struct _Surf)); 30 | surf->width = width; 31 | surf->height = height; 32 | surf->pixs = Alloc(width * height * sizeof(int)); 33 | surf->col = 0xffffff; 34 | return surf; 35 | } 36 | 37 | void RmSurf(Surf surf) { 38 | Free(surf->pixs); 39 | Free(surf); 40 | } 41 | 42 | void SwpPt(Pt* a, Pt* b) { 43 | Pt tmp = *b; 44 | *b = *a; 45 | *a = tmp; 46 | } 47 | 48 | void SurfScan(Surf surf, int y, int leftx, int rightx) { 49 | int x; 50 | for (x = Max(leftx, 0); x <= Min(rightx, surf->width - 1); ++x) { 51 | AlphaBlendp(&surf->pixs[y * surf->width + x], surf->col); 52 | } 53 | } 54 | 55 | void SurfTri(Surf surf, float x1, float y1, float x2, float y2, float x3, float y3) { 56 | Pt a = SetPt(x1, y1), b = SetPt(x2, y2), c = SetPt(x3, y3); 57 | Pt slope1, slope2, slope3; 58 | float startx, endx, xstep1, xstep2, xstep3; 59 | int y; 60 | 61 | /* sort points abc by y */ 62 | if (a.y > b.y) { SwpPt(&a, &b); } 63 | if (a.y > c.y) { SwpPt(&a, &c); } 64 | if (b.y > c.y) { SwpPt(&b, &c); } 65 | 66 | /* find slope of each edge and calculate how much x changes for yeach 1-pix step in y */ 67 | slope1 = SubPt(b, a); 68 | xstep1 = slope1.x / slope1.y; 69 | slope2 = SubPt(c, a); 70 | xstep2 = slope2.x / slope2.y; 71 | slope3 = SubPt(c, b); 72 | xstep3 = slope3.x / slope3.y; 73 | 74 | /* ensure the triangle is on screen at all vertically */ 75 | if (a.y >= surf->height || c.y < 0) { return; } 76 | 77 | /* ensure xstep1 is left and xstep2 is right */ 78 | if (b.x > c.x) { SwpFlts(&xstep1, &xstep2); } 79 | 80 | if (b.y >= 0 && a.y < surf->height) { 81 | /* draw scanlines from point a to b */ 82 | startx = endx = a.x; 83 | y = a.y; 84 | if (y < 0) { 85 | /* skip until vertically onscreen part */ 86 | startx += xstep1 * -y; 87 | endx += xstep2 * -y; 88 | y = 0; 89 | } 90 | for (; y < Min(b.y, surf->height - 1); ++y) { 91 | SurfScan(surf, y, startx, endx); 92 | startx += xstep1; 93 | endx += xstep2; 94 | } 95 | } 96 | 97 | if (b.y < surf->height) { 98 | /* figure out which side needs to swap x step and draw scanlines from point b to c */ 99 | if (b.x < c.x) { 100 | startx = b.x; 101 | endx = a.x + xstep2 * (b.y - a.y); 102 | xstep1 = xstep3; 103 | } else { 104 | startx = a.x + xstep1 * (b.y - a.y); 105 | endx = b.x; 106 | xstep2 = xstep3; 107 | } 108 | y = b.y; 109 | if (y < 0) { 110 | /* skip until vertically onscreen part */ 111 | startx += xstep1 * -y; 112 | endx += xstep2 * -y; 113 | y = 0; 114 | } 115 | for (; y <= Min(c.y, surf->height - 1); ++y) { 116 | SurfScan(surf, y, startx, endx); 117 | startx += xstep1; 118 | endx += xstep2; 119 | } 120 | } 121 | } 122 | 123 | int* SurfPixs(Surf surf) { return surf->pixs; } 124 | int SurfWidth(Surf surf) { return surf->width; } 125 | int SurfHeight(Surf surf) { return surf->height; } 126 | void SurfCol(Surf surf, int col) { surf->col = col; } 127 | 128 | void SurfToImg(Surf surf, ImgPtr img) { 129 | ImgCpy(img, SurfPixs(surf), SurfWidth(surf), SurfHeight(surf)); 130 | } 131 | 132 | Mesh MkFrameMesh() { 133 | Mesh mesh = MkMesh(); 134 | Quad(mesh, 0, 0, 640, 480); 135 | return mesh; 136 | } 137 | 138 | ImgPtr PutFrame() { 139 | ImgPtr img = ImgAlloc(640, 480); 140 | Surf surf = MkSurf(640, 480); 141 | SurfTri(surf, 0, -240, 320, 240, -320, 240); 142 | SurfTri(surf, 640, 240, 960, 720, 320, 720); 143 | SurfCol(surf, 0x7fff3333); 144 | SurfTri(surf, 200, 20, 620, 240, 20, 460); 145 | SurfToImg(surf, img); 146 | RmSurf(surf); 147 | return img; 148 | } 149 | 150 | Wnd wnd; 151 | Mesh mesh; 152 | ImgPtr img; 153 | 154 | void Init() { 155 | wnd = AppWnd(); 156 | mesh = MkFrameMesh(); 157 | img = PutFrame(); 158 | } 159 | 160 | void Quit() { 161 | RmMesh(mesh); 162 | } 163 | 164 | void Frame() { 165 | PutMesh(mesh, 0, img); 166 | } 167 | 168 | void AppInit() { 169 | SetAppClass("WeebCoreSoftwareRenderer"); 170 | SetAppName("WeebCore - Software Renderer Demo"); 171 | On(INIT, Init); 172 | On(QUIT, Quit); 173 | On(FRAME, Frame); 174 | } 175 | 176 | #define WEEBCORE_IMPLEMENTATION 177 | #include "WeebCore.c" 178 | #include "Platform/Platform.h" 179 | -------------------------------------------------------------------------------- /Examples/0005_ImgAllocator.c: -------------------------------------------------------------------------------- 1 | /* draft of the img alloc, which automatically packs together img's into one bigger img so the gpu 2 | * doesn't have to swap img all the time. 3 | * it uses a fixed page size and when it runs out of space it makes a new page */ 4 | 5 | #include "WeebCore.c" 6 | 7 | /* this demo just creates a bunch of random bouncing objects to fill up the atlas */ 8 | 9 | typedef struct { 10 | Trans trans; 11 | float r[4]; 12 | float vx, vy; 13 | ImgPtr img; 14 | Mesh mesh; 15 | } Ent; 16 | 17 | Wnd wnd; 18 | Ent* ents; 19 | unsigned elapsed; 20 | int frames, fps; 21 | float fpsTimer; 22 | Ft ft; 23 | Mesh text; 24 | int diagAllocator; 25 | 26 | void MkEnt() { 27 | Ent* ent = ArrAlloc(&ents, 1); 28 | int* pixs = 0; 29 | int i; 30 | /* pseudorandom */ 31 | int width = 20 + (elapsed & 31); 32 | int height = 20 + ((elapsed >> 5) & 31); 33 | int col = 0x404040 + (elapsed & 0x5f5f5f); 34 | float vx = (0xfff + (elapsed & 0xffff)) / (float)(0xffff) * 400; 35 | float vy = (0xfff + ((elapsed >> 16) & 0xffff)) / (float)(0xffff) * 400; 36 | for (i = 0; i < width * height; ++i) { 37 | float roff = (i % width) / (float)width; 38 | float goff = (i / width) / (float)height; 39 | ArrCat(&pixs, col + ((int)(roff * 0x3f) << 16) + ((int)(goff * 0x3f) << 8)); 40 | } 41 | ent->img = ImgAlloc(width, height); 42 | ImgCpy(ent->img, pixs, width, height); 43 | FlushImgs(); 44 | ent->mesh = MkMesh(); 45 | Quad(ent->mesh, 0, 0, width, height); 46 | RmArr(pixs); 47 | ent->vx = vx; 48 | ent->vy = vy; 49 | SetRect(ent->r, 0, width, 0, height); 50 | ent->trans = MkTrans(); 51 | } 52 | 53 | void UpdEnts() { 54 | int i; 55 | float delta = Delta(wnd); 56 | for (i = 0; i < ArrLen(ents); ++i) { 57 | Ent* ent = &ents[i]; 58 | SetRectPos(ent->r, 59 | RectX(ent->r) + ent->vx * delta, 60 | RectY(ent->r) + ent->vy * delta); 61 | if (RectRight(ent->r) >= WndWidth(wnd)) { 62 | ent->vx *= -1; 63 | SetRectPos(ent->r, WndWidth(wnd) - RectWidth(ent->r), RectY(ent->r)); 64 | } else if (RectLeft(ent->r) <= 0) { 65 | ent->vx *= -1; 66 | SetRectPos(ent->r, 0, RectY(ent->r)); 67 | } else if (RectBot(ent->r) >= WndHeight(wnd)) { 68 | ent->vy *= -1; 69 | SetRectPos(ent->r, RectX(ent->r), WndHeight(wnd) - RectHeight(ent->r)); 70 | } else if (RectTop(ent->r) <= 0) { 71 | ent->vy *= -1; 72 | SetRectPos(ent->r, RectX(ent->r), 0); 73 | } 74 | SetPos(ent->trans, (int)RectX(ent->r), (int)RectY(ent->r)); 75 | } 76 | } 77 | 78 | void PutEnts() { 79 | int i; 80 | for (i = 0; i < ArrLen(ents); ++i) { 81 | PutMesh(ents[i].mesh, ToTmpMat(ents[i].trans), ents[i].img); 82 | } 83 | } 84 | 85 | void RmEnt(Ent* ent) { 86 | RmTrans(ent->trans); 87 | RmMesh(ent->mesh); 88 | } 89 | 90 | void RmEnts() { 91 | int i; 92 | for (i = 0; i < ArrLen(ents); ++i) { 93 | RmEnt(&ents[i]); 94 | } 95 | RmArr(ents); 96 | ents = 0; 97 | } 98 | 99 | void RmEntAt(int i) { 100 | if (i >= 0 && i < ArrLen(ents)) { 101 | Ent* e = &ents[i]; 102 | /* for better visualization, blank out the freed img */ 103 | /* I add 0.5 because of tiny float errors which can make it round down and be off by 1 */ 104 | int width = (int)RectWidth(e->r) + 0.5f; 105 | int height = (int)RectHeight(e->r) + 0.5f; 106 | int* black = Alloc(width * height * sizeof(int)); 107 | ImgCpy(e->img, black, width, height); 108 | FlushImgs(); 109 | Free(black); 110 | ImgFree(e->img); 111 | RmEnt(e); 112 | MemMv(e, e + 1, sizeof(Ent) * (ArrLen(ents) - i - 1)); 113 | SetArrLen(ents, ArrLen(ents) - 1); 114 | } 115 | } 116 | 117 | void PutStatsText() { 118 | char* pagestr = 0; 119 | ArrStrCat(&pagestr, "fps: "); 120 | ArrStrCatI32(&pagestr, fps, 10); 121 | ArrStrCat(&pagestr, " quads: "); 122 | ArrStrCatI32(&pagestr, ArrLen(ents), 10); 123 | ArrCat(&pagestr, 0); 124 | PutFt(ft, 0xbebebe, 10, WndHeight(wnd) - 21, pagestr); 125 | RmArr(pagestr); 126 | } 127 | 128 | void Init() { 129 | wnd = AppWnd(); 130 | ft = DefFt(); 131 | text = MkMesh(); 132 | Col(text, 0xbebebe); 133 | FtMesh(text, ft, 10, 10, "space to spawn random quads\nbackspace to free the oldest quad\n" 134 | "F2 to reset the allocator\nF1 to see the texture atlas\nmouse wheel to switch pages"); 135 | } 136 | 137 | void Quit() { 138 | RmEnts(); 139 | RmMesh(text); 140 | } 141 | 142 | void KeyDown() { 143 | switch (Key(wnd)) { 144 | case SPACE: { 145 | MkEnt(); 146 | break; 147 | } 148 | case BACKSPACE: { 149 | RmEntAt(0); 150 | break; 151 | } 152 | case F1: { 153 | diagAllocator ^= 1; 154 | DiagImgAlloc(diagAllocator); 155 | break; 156 | } 157 | case F2: { 158 | RmEnts(); 159 | ClrImgs(); 160 | FlushImgs(); 161 | break; 162 | } 163 | } 164 | } 165 | 166 | void Frame() { 167 | UpdEnts(); 168 | PutEnts(); 169 | PutMesh(text, 0, FtImg(ft)); 170 | PutStatsText(); 171 | 172 | elapsed += (int)(Delta() * 1000000000); 173 | 174 | ++frames; 175 | fpsTimer += Delta(); 176 | while (fpsTimer >= 1) { 177 | fpsTimer -= 1; 178 | fps = frames; 179 | frames = 0; 180 | } 181 | } 182 | 183 | void AppInit() { 184 | SetAppName("WeebCore - Img Allocator Demo"); 185 | SetAppClass("WeebCoreImgAllocator"); 186 | On(INIT, Init); 187 | On(QUIT, Quit); 188 | On(KEYDOWN, KeyDown); 189 | On(FRAME, Frame); 190 | } 191 | 192 | #define WEEBCORE_IMPLEMENTATION 193 | #include "WeebCore.c" 194 | #include "Platform/Platform.h" 195 | -------------------------------------------------------------------------------- /Examples/0006_ffi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # run from WeebCore root dir with 4 | # ./build.sh lib && vblank_mode=0 LD_LIBRARY_PATH="$(pwd)" Examples/0006_ffi.py 5 | 6 | from ctypes import CDLL, c_void_p, c_int, c_float, CFUNCTYPE 7 | 8 | weeb = CDLL('WeebCore.so') # on windows you would change this to dll 9 | weeb.MkMesh.restype = weeb.AppWnd.restype = weeb.MkMesh.restype = c_void_p 10 | weeb.Col.argtypes = [c_void_p, c_int] 11 | weeb.Quad.argtypes = [c_void_p] + [c_float] * 4 12 | weeb.PutMesh.argtypes = [c_void_p] * 3 13 | 14 | INIT = 6 15 | FRAME = 7 16 | 17 | 18 | class Game: 19 | def __init__(self): 20 | self.mesh = None 21 | 22 | 23 | G = Game() 24 | 25 | 26 | @CFUNCTYPE(None) 27 | def init(): 28 | G.mesh = weeb.MkMesh() 29 | weeb.Col(G.mesh, 0xff3333) 30 | weeb.Quad(G.mesh, 10, 10, 200, 100) 31 | 32 | 33 | @CFUNCTYPE(None) 34 | def frame(): 35 | weeb.PutMesh(G.mesh, None, None) 36 | 37 | 38 | weeb.On(INIT, init) 39 | weeb.On(FRAME, frame) 40 | weeb.AppMain(0, None) 41 | -------------------------------------------------------------------------------- /Examples/0007_Ecs.c: -------------------------------------------------------------------------------- 1 | #include "WeebCore.c" 2 | 3 | /* prototype of the ECS that will be built into WeebCore 4 | * 5 | * Ents with the same Comps are grouped together in memory as buckets. 6 | * 7 | * buckets are laid out as structs of arrays. for example, a bucket with 10 Ents that have 8 | * a Transform and a Mesh will be laid out as 9 | * 10 | * struct { 11 | * TransformData transforms[10]; 12 | * MeshData meshes[10]; 13 | * } 14 | */ 15 | 16 | /* 17 | 18 | - Ents by themselves don't contain any data. they're just a unique integer handle 19 | - Ents are assigned to a bucket which contains the data of all the entities as an array 20 | - buckets are identified by an arbitrarily long bitmask represented as a int array. this bitmask 21 | represents which Comps the entities have. these bitmasks are also interned to save memory. that 22 | way the entity handle only needs to have a pointer to the only instance of the bitmask array 23 | - Ent handles are Ent's themselves. code that deals with handles assumes the Ent is in the bucket 24 | for Ents with only a Handle Comp. the Handle comp just contains the bitmask ptr and index within 25 | the bucket 26 | - we can simply AND bitmasks with the components we are looking for to see which buckets match and 27 | call systems on matching ones 28 | 29 | TODO: 30 | - we want some hooks on comp creation/destruction and stuff like that to be able to cleanup 31 | - for hierarchies of entities, we just create a unique Comp for each parent entity which is used 32 | to tag child entities, that way they are grouped together in a separate bucket. we will also have 33 | a built-in Orphan Comp for root Ent's. we also probably want a generic Parent comp which just 34 | contains the mask for the parent Comp bits 35 | - run a func on all Ents that have a System comp which will recursively run the System's func on all 36 | Ents that match (SystemComps | ChildOf), where ParentEnt starts as Orphan. 37 | also implement a Priority comp that lets us give an execution order. reserve negative priorities 38 | for builtin stuff that must run before everything else 39 | - these primitives should be enough to build arbitrarily complex systems on top 40 | 41 | */ 42 | 43 | /* utils for performing bit operations on Arrs of int's */ 44 | 45 | #define DefArrOp(name, op) \ 46 | void Arr##name(int** dst, int* src1, int* src2) { \ 47 | int i, len = Min(ArrLen(src1), ArrLen(src2)); \ 48 | SetArrLen(*dst, 0); \ 49 | for (i = 0; i < len; ++i) { \ 50 | ArrCat(dst, src1[i] op src2[i]); \ 51 | } \ 52 | } 53 | 54 | DefArrOp(Or, |) 55 | DefArrOp(And, &) 56 | 57 | void ArrNot(int** dst, int* arr) { 58 | int i, len = ArrLen(*dst); 59 | SetArrLen(*dst, 0); 60 | for (i = 0; i < len; ++i) { 61 | ArrCat(dst, ~arr[i]); 62 | } 63 | } 64 | 65 | /* find first bit that is equal to bit (either 1 or 0) */ 66 | int ArrBitScan(int* arr, int bit) { 67 | int i, j; 68 | for (i = 0; i < ArrLen(arr); ++i) { 69 | for (j = 0; j < 32; ++j) { 70 | if ((arr[i] & (1 << j)) == (bit << j)) { 71 | return i * 32 + j; 72 | } 73 | } 74 | } 75 | return -1; 76 | } 77 | 78 | /* count number of bits that are equal to bit (either 1 or 0) */ 79 | int ArrBitCount(int* arr, int bit) { 80 | int i, j, res = 0; 81 | for (i = 0; i < ArrLen(arr); ++i) { 82 | for (j = 0; j < 32; ++j) { 83 | if ((arr[i] & (1 << j)) == (bit << j)) { 84 | res += 1; 85 | } 86 | } 87 | } 88 | return res; 89 | } 90 | 91 | /* returns non-zero if (arr & bits) == bits */ 92 | int ArrHasBits(int* arr, int* bits) { 93 | int* and = 0; 94 | int res; 95 | ArrAnd(&and, arr, bits); 96 | res = ArrMemCmp(and, bits); 97 | RmArr(and); 98 | return res == 0; 99 | } 100 | 101 | /* ---------------------------------------------------------------------------------------------- */ 102 | 103 | typedef int Ent; 104 | 105 | typedef struct _EcsBucket { 106 | int* mask; /* interned mask so we don't store copies */ 107 | int* comps; /* comp id's */ 108 | int compsLen; /* total number of bytes occupied by 1 instance of each comp */ 109 | Map offs; /* map of comp id -> offset into data where the comp arr starts */ 110 | int* occupied; /* bitmask of occupied indices */ 111 | char* data; 112 | } EcsBucket; 113 | 114 | typedef struct _Comp { 115 | char* name; 116 | int id, len; 117 | } Comp; 118 | 119 | typedef struct _HandleComp { 120 | int* mask; /* ptr to interned mask */ 121 | int index; 122 | } HandleComp; 123 | 124 | typedef struct _NameComp { 125 | char* name; 126 | } NameComp; 127 | 128 | typedef void SystemFunc(int* mask); 129 | 130 | typedef struct _SystemComp { 131 | int prio; 132 | int* mask; 133 | SystemFunc* func; 134 | } SystemComp; 135 | 136 | Arena ecsArena; 137 | Hash ecsBuckets; 138 | Map compsById; 139 | Hash compsByName; 140 | int nextCompId; 141 | 142 | /* hardcoded archetypes for bootstrap comps */ 143 | #define HANDLE_COMP_ID 0 144 | 145 | int* nullBucket; 146 | int* handleCompBucket; 147 | 148 | static Comp* CompByName(char* name) { 149 | return HashGet(compsByName, name); 150 | } 151 | 152 | static Comp* CompById(int id) { 153 | return MapGet(compsById, id); 154 | } 155 | 156 | /* converts an Arr of Comp id's to an arbitrarily long bitmask (little endian). returns an Arr */ 157 | static int* CompsToMask(int* comps) { 158 | int* mask = 0; 159 | int i; 160 | for (i = 0; i < ArrLen(comps); ++i) { 161 | Comp* comp = CompById(comps[i]); 162 | while (comp->id / 32 >= ArrLen(mask)) { 163 | ArrCat(&mask, 0); 164 | } 165 | mask[comp->id / 32] |= 1 << (comp->id % 32); 166 | } 167 | return mask; 168 | } 169 | 170 | /* convert a string such as Comp1|Comp2 into an arr of Comp id's. spaces are ignored. 171 | * invalid comps are silently ignored */ 172 | static int* CompsStrToArr(char* s) { 173 | char* name = 0; 174 | int* comps = 0; 175 | int parsing = 1; 176 | for (; parsing; ++s) { 177 | switch (*s) { 178 | case ' ': 179 | case '\t': 180 | case '\n': 181 | case '\v': 182 | case '\f': 183 | case '\r': { 184 | continue; 185 | } 186 | case 0: { 187 | parsing = 0; 188 | } 189 | case '|': { 190 | Comp* comp; 191 | ArrCat(&name, 0); 192 | comp = CompByName(name); 193 | SetArrLen(name, 0); 194 | if (comp) { 195 | ArrCat(&comps, comp->id); 196 | } 197 | break; 198 | } 199 | default: { 200 | ArrCat(&name, *s); 201 | break; 202 | } 203 | } 204 | } 205 | RmArr(name); 206 | return comps; 207 | } 208 | 209 | /* like CompsStrToArr but returns the bitmask for the Comps */ 210 | static int* CompsStrToMask(char* s) { 211 | int* comps = CompsStrToArr(s); 212 | int* mask = CompsToMask(comps); 213 | RmArr(comps); 214 | return mask; 215 | } 216 | 217 | /* converts a bitmask of Comps to an Arr of comp id's */ 218 | static int* MaskToCompsArr(int* mask) { 219 | int i, j; 220 | int* res = 0; 221 | for (i = 0; i < ArrLen(mask); ++i) { 222 | int c = mask[i]; 223 | for (j = 0; j < 32; ++j) { 224 | if (c & (1<id = nextCompId++; 235 | comp->len = len; 236 | comp->name = name; 237 | MapSet(compsById, comp->id, comp); 238 | HashSet(compsByName, name, comp); 239 | } 240 | 241 | #define MkComp(T) MkCompEx(#T, sizeof(T)) 242 | 243 | static EcsBucket* MkEcsBucket(int* mask) { 244 | EcsBucket* bucket = ArenaAlloc(ecsArena, sizeof(EcsBucket)); 245 | if (bucket) { 246 | int i, off; 247 | bucket->mask = ArrDup(mask); 248 | bucket->comps = MaskToCompsArr(mask); 249 | bucket->data = 0; 250 | bucket->offs = MkMap(); 251 | for (off = 0, i = 0; i < ArrLen(bucket->comps); ++i) { 252 | Comp* comp = CompById(bucket->comps[i]); 253 | off += comp->len; 254 | } 255 | bucket->compsLen = off; 256 | } 257 | return bucket; 258 | } 259 | 260 | static void RmEcsBucket(EcsBucket* bucket) { 261 | if (bucket) { 262 | RmArr(bucket->comps); 263 | RmArr(bucket->data); 264 | RmMap(bucket->offs); 265 | RmArr(bucket->mask); 266 | RmArr(bucket->occupied); 267 | } 268 | } 269 | 270 | static void SetEcsBucket(int* mask, EcsBucket* bucket) { 271 | HashSetb(ecsBuckets, (char*)mask, ArrLen(mask) * 4, bucket); 272 | } 273 | 274 | static EcsBucket* GetEcsBucket(int* mask) { 275 | EcsBucket* bucket = HashGetb(ecsBuckets, (char*)mask, ArrLen(mask) * 4); 276 | if (!bucket) { 277 | bucket = MkEcsBucket(mask); 278 | SetEcsBucket(mask, bucket); 279 | } 280 | return bucket; 281 | } 282 | 283 | static int EcsBucketCap(EcsBucket* bucket) { 284 | if (bucket) { 285 | return bucket->compsLen ? ArrCap(bucket->data) / bucket->compsLen : 0; 286 | } 287 | return 0; 288 | } 289 | 290 | static int EcsBucketOccupancy(EcsBucket* bucket) { 291 | return ArrBitCount(bucket->occupied, 1); 292 | } 293 | 294 | static int EcsBucketOccupied(EcsBucket* bucket, int index) { 295 | return bucket->occupied[index / 32] & (1 << (index % 32)); 296 | } 297 | 298 | static void* GetCompsInternal(EcsBucket* bucket, int compId) { 299 | if (bucket) { 300 | int off = (int)MapGet(bucket->offs, compId); 301 | return &bucket->data[off]; 302 | } 303 | return 0; 304 | } 305 | 306 | static void* GetCompInternal(EcsBucket* bucket, int compId, int index) { 307 | char* comps = GetCompsInternal(bucket, compId); 308 | Comp* comp = CompById(compId); 309 | if (comps && comp) { 310 | return comps + comp->len * index; 311 | } 312 | return 0; 313 | } 314 | 315 | static HandleComp* EntHandle(Ent ent) { 316 | EcsBucket* bucket = GetEcsBucket(handleCompBucket); 317 | return GetCompInternal(bucket, HANDLE_COMP_ID, ent); 318 | } 319 | 320 | void* GetComps(int* mask, char* compName) { 321 | EcsBucket* bucket = GetEcsBucket(mask); 322 | Comp* comp = CompByName(compName); 323 | if (comp) { 324 | return GetCompsInternal(bucket, comp->id); 325 | } 326 | return 0; 327 | } 328 | 329 | void* GetComp(Ent ent, char* name) { 330 | HandleComp* handle = EntHandle(ent); 331 | EcsBucket* bucket = GetEcsBucket(handle->mask); 332 | Comp* comp = CompByName(name); 333 | return GetCompInternal(bucket, comp->id, handle->index); 334 | } 335 | 336 | /* we use a occupancy bitmask to avoid having to relocate all the memory every time we remove */ 337 | 338 | static int EcsBucketAlloc(int* mask) { 339 | EcsBucket* bucket = GetEcsBucket(mask); 340 | char* data = bucket->data; 341 | int cap = EcsBucketCap(bucket); 342 | int len = EcsBucketOccupancy(bucket); 343 | int compsLen = bucket->compsLen; 344 | Map offs = bucket->offs; 345 | int* comps = bucket->comps; 346 | int index, i; 347 | /* rebuild entire array to relen it (it's laid out as struct of arrays so we reloc everything) */ 348 | if (cap - len <= 0) { 349 | char* newData = 0; 350 | int off, newOff; 351 | int newCap = Max(cap * 2, 32); 352 | ArrAlloc(&newData, compsLen * newCap); 353 | for (off = 0, newOff = 0, i = 0; i < ArrLen(comps); ++i) { 354 | Comp* comp = CompById(comps[i]); 355 | if (data) { 356 | MemCpy(&newData[newOff], &data[off], comp->len * len); 357 | } 358 | MapSet(offs, comps[i], (void*)newOff); 359 | newOff += newCap * comp->len; 360 | off += cap * comp->len; 361 | } 362 | RmArr(data); 363 | bucket->data = data = newData; 364 | while (ArrLen(bucket->occupied) < newCap) { 365 | ArrCat(&bucket->occupied, 0); 366 | } 367 | cap = newCap; 368 | } 369 | index = ArrBitScan(bucket->occupied, 0); 370 | bucket->occupied[index / 32] |= 1 << (index % 32); 371 | for (i = 0; i < ArrLen(comps); ++i) { 372 | Comp* comp = CompById(comps[i]); 373 | MemSet(GetCompInternal(bucket, comp->id, index), 0, comp->len); 374 | } 375 | return index; 376 | } 377 | 378 | void RmEnt(Ent ent) { 379 | HandleComp* handle = EntHandle(ent); 380 | EcsBucket* bucket = GetEcsBucket(handle->mask); 381 | bucket->occupied[handle->index / 32] &= ~(1 << (handle->index % 32)); 382 | } 383 | 384 | static Ent MkEntHandle(int* mask, int index) { 385 | EcsBucket* bucket = GetEcsBucket(mask); 386 | Ent res = EcsBucketAlloc(handleCompBucket); 387 | HandleComp* handle = EntHandle(res); 388 | handle->index = index; 389 | handle->mask = bucket->mask; 390 | return res; 391 | } 392 | 393 | static Ent MkEntInBucket(int* mask) { 394 | return MkEntHandle(mask, EcsBucketAlloc(mask)); 395 | } 396 | 397 | Ent MkEnt() { 398 | return MkEntInBucket(nullBucket); 399 | } 400 | 401 | int EntValid(Ent ent) { 402 | EcsBucket* bucket = GetEcsBucket(handleCompBucket); 403 | return EcsBucketOccupied(bucket, ent); 404 | } 405 | 406 | int NextEnt(int* mask, int index) { 407 | EcsBucket* bucket = GetEcsBucket(mask); 408 | for (++index; index < EcsBucketCap(bucket) && !EcsBucketOccupied(bucket, index); ++index); 409 | if (index >= EcsBucketCap(bucket)) { 410 | return -1; 411 | } 412 | return index; 413 | } 414 | 415 | int FirstEnt(int* mask) { return NextEnt(mask, -1); } 416 | 417 | static void RelocateEnt(Ent ent, int* newMask) { 418 | HandleComp* handle = EntHandle(ent); 419 | if (ArrMemCmp(newMask, handle->mask)) { 420 | EcsBucket* oldBucket = GetEcsBucket(handle->mask); 421 | EcsBucket* bucket = GetEcsBucket(newMask); 422 | int index = EcsBucketAlloc(newMask); 423 | int* comps = MaskToCompsArr(handle->mask); 424 | int i; 425 | for (i = 0; i < ArrLen(comps); ++i) { 426 | Comp* comp = CompById(comps[i]); 427 | void* old = GetCompInternal(oldBucket, comp->id, handle->index); 428 | void* new = GetCompInternal(bucket, comp->id, index); 429 | MemCpy(new, old, comp->len); 430 | } 431 | RmEnt(ent); 432 | handle->mask = bucket->mask; 433 | handle->index = index; 434 | } 435 | } 436 | 437 | void* AddComp(Ent ent, char* name) { 438 | HandleComp* handle = EntHandle(ent); 439 | int* newMask = CompsStrToMask(name); 440 | ArrOr(&newMask, newMask, handle->mask); 441 | RelocateEnt(ent, newMask); 442 | RmArr(newMask); 443 | return GetComp(ent, name); 444 | } 445 | 446 | void RmComp(Ent ent, char* name) { 447 | HandleComp* handle = EntHandle(ent); 448 | int* newMask = CompsStrToMask(name); 449 | ArrNot(&newMask, newMask); 450 | ArrAnd(&newMask, newMask, handle->mask); 451 | RelocateEnt(ent, newMask); 452 | RmArr(newMask); 453 | } 454 | 455 | void MkEcs() { 456 | /* initialize hardcoded bucket masks */ 457 | ArrCat(&nullBucket, 0); 458 | ArrCat(&handleCompBucket, 1<mask, mask)) { 492 | ArrCat(&buckets, bucket); 493 | } 494 | } 495 | return buckets; 496 | } 497 | 498 | static EcsBucket** FindEcsBuckets(char* compsStr) { 499 | int* mask = CompsStrToMask(compsStr); 500 | EcsBucket** buckets = FindEcsBucketsByMask(mask); 501 | RmArr(mask); 502 | return buckets; 503 | } 504 | 505 | Ent MkSystemEx(int prio, char* compsStr, SystemFunc func, char* name) { 506 | Ent ent = MkEnt(); 507 | SystemComp* sys; 508 | NameComp* nameComp; 509 | sys = AddComp(ent, "SystemComp"); 510 | sys->prio = prio; 511 | sys->mask = CompsStrToMask(compsStr); 512 | sys->func = func; 513 | nameComp = AddComp(ent, "NameComp"); 514 | nameComp->name = name; 515 | sys = GetComp(ent, "SystemComp"); 516 | return ent; 517 | } 518 | 519 | #define MkSystem(prio, compsStr, func) MkSystemEx(prio, compsStr, func, #func) 520 | 521 | static int CmpSystems(SystemComp* a, SystemComp* b) { 522 | if (a->prio < b->prio) { 523 | return -1; 524 | } else if (a->prio > b->prio) { 525 | return 1; 526 | } 527 | return 0; 528 | } 529 | 530 | void EcsFrame() { 531 | /* TODO: be more efficient than an array of ptrs? */ 532 | SystemComp** systems = 0; 533 | EcsBucket** buckets = FindEcsBuckets("SystemComp"); 534 | int i, j; 535 | Comp* comp = CompByName("SystemComp"); 536 | for (i = 0; i < ArrLen(buckets); ++i) { 537 | EcsBucket* bucket = buckets[i]; 538 | SystemComp* comps = GetCompsInternal(bucket, comp->id); 539 | for (j = 0; j < EcsBucketCap(bucket); ++j) { 540 | if (EcsBucketOccupied(bucket, j)) { 541 | ArrCat(&systems, &comps[j]); 542 | } 543 | } 544 | } 545 | RmArr(buckets); 546 | Qsort((void**)systems, ArrLen(systems), (QsortCmp*)CmpSystems); 547 | for (i = 0; i < ArrLen(systems); ++i) { 548 | SystemComp* sys = systems[i]; 549 | buckets = FindEcsBucketsByMask(sys->mask); 550 | for (j = 0; j < ArrLen(buckets); ++j) { 551 | sys->func(buckets[j]->mask); 552 | } 553 | RmArr(buckets); 554 | } 555 | RmArr(systems); 556 | } 557 | 558 | void EcsInit() { 559 | On(INIT, MkEcs); 560 | On(QUIT, RmEcs); 561 | On(FRAME, EcsFrame); 562 | } 563 | 564 | void DiagEcsFrame() { 565 | int i; 566 | char* lines = 0; 567 | EcsBucket* handles = GetEcsBucket(handleCompBucket); 568 | for (i = 0; i < HashNumKeys(ecsBuckets); ++i) { 569 | EcsBucket* bucket = HashGetb(ecsBuckets, HashKey(ecsBuckets, i), HashKeyLen(ecsBuckets, i)); 570 | int* comps = bucket->comps; 571 | if (EcsBucketOccupancy(bucket)) { 572 | int j, start, n = 0; 573 | char* s = 0; 574 | int* nameCompMask = CompsStrToMask("NameComp"); 575 | int hasName = ArrHasBits(bucket->mask, nameCompMask); 576 | for (j = 0; j < ArrLen(comps); ++j) { 577 | Comp* comp = CompById(comps[j]); 578 | if (j != 0) { ArrStrCat(&s, "|"); } 579 | ArrStrCat(&s, comp->name); 580 | } 581 | if (!s) { 582 | ArrStrCat(&s, "(no comps)"); 583 | } 584 | ArrStrCat(&s, ": "); 585 | ArrStrCatI32(&s, EcsBucketOccupancy(bucket), 10); 586 | ArrStrCat(&s, "/"); 587 | ArrStrCatI32(&s, EcsBucketCap(bucket), 10); 588 | ArrStrCat(&s, " ents ("); 589 | ArrStrCatI32(&s, bucket->compsLen, 10); 590 | ArrStrCat(&s, "b per ent)"); 591 | ArrStrCat(&s, "\n "); 592 | start = ArrLen(s); 593 | if (hasName) { 594 | for (j = 0; j < EcsBucketCap(handles); ++j) { 595 | if (EntValid(j)) { 596 | HandleComp* handle = EntHandle(j); 597 | NameComp* name; 598 | if (handle->mask != bucket->mask) { continue; } 599 | ++n; 600 | if (j) { ArrStrCat(&s, " "); } 601 | ArrStrCatI32(&s, j, 16); 602 | ArrStrCat(&s, "/"); 603 | ArrStrCatI32(&s, handle->index, 16); 604 | name = GetComp(j, "NameComp"); 605 | ArrStrCat(&s, "'"); 606 | ArrStrCat(&s, name->name); 607 | ArrStrCat(&s, "'"); 608 | if (j < EcsBucketCap(handles) && ArrLen(s) - start >= 90) { 609 | ArrStrCat(&s, "\n "); 610 | start = ArrLen(s); 611 | } 612 | } 613 | } 614 | } 615 | if (n) { 616 | ArrStrCat(&s, "\n "); 617 | } 618 | ArrCat(&s, 0); 619 | ArrStrCat(&lines, s); 620 | ArrStrCat(&lines, "\n"); 621 | RmArr(s); 622 | RmArr(nameCompMask); 623 | } 624 | } 625 | ArrCat(&lines, 0); 626 | PutFt(DefFt(), 0xbebebe, 10, 30, lines); 627 | RmArr(lines); 628 | } 629 | 630 | void DiagEcs(int enabled) { 631 | if (enabled) { 632 | On(FRAME, DiagEcsFrame); 633 | } else { 634 | RmHandler(FRAME, DiagEcsFrame); 635 | } 636 | } 637 | 638 | /* ---------------------------------------------------------------------------------------------- */ 639 | 640 | /* TODO: actually embedding these structs in the Comp's would make more sense */ 641 | typedef struct _MeshComp { 642 | Mesh mesh; 643 | } MeshComp; 644 | 645 | typedef struct _TransComp { 646 | Trans trans; 647 | } TransComp; 648 | 649 | typedef struct _MovementComp { 650 | float x, y; 651 | float vx, vy; 652 | } MovementComp; 653 | 654 | Ent MkParticle(int x, int y) { 655 | Ent ent = MkEnt(); 656 | MovementComp* mov; 657 | MeshComp* mesh; 658 | TransComp* trans; 659 | 660 | mov = AddComp(ent, "MovementComp"); 661 | mov->x = x; 662 | mov->y = y; 663 | 664 | mesh = AddComp(ent, "MeshComp"); 665 | mesh->mesh = MkMesh(); 666 | Col(mesh->mesh, 0x993333); 667 | Quad(mesh->mesh, -2, -2, 4, 4); 668 | 669 | trans = AddComp(ent, "TransComp"); 670 | trans->trans = MkTrans(); 671 | return ent; 672 | } 673 | 674 | void MovementSystem(int* mask) { 675 | MovementComp* mov = GetComps(mask, "MovementComp"); 676 | TransComp* trans = GetComps(mask, "TransComp"); 677 | int i; 678 | Wnd wnd = AppWnd(); 679 | for (i = FirstEnt(mask); i >= 0; i = NextEnt(mask, i)) { 680 | MovementComp* m = &mov[i]; 681 | float dx = m->x - MouseX(wnd); 682 | float dy = m->y - MouseY(wnd); 683 | float mag = Sqrt(dx * dx + dy * dy); 684 | float force = 1 - Min(1, mag / 100); /* mouse repels */ 685 | m->vx += force * dx * 30 * Delta(); 686 | m->vy += force * dy * 30 * Delta(); 687 | m->vx -= m->vx * Delta(); /* friction */ 688 | m->vy -= m->vy * Delta(); 689 | m->vx -= Min(100, Max(0, m->x - WndWidth(wnd)) * Delta() * 10000); /* edges of screen repel */ 690 | m->vy -= Min(100, Max(0, m->y - WndHeight(wnd)) * Delta() * 10000); 691 | m->vx += Min(100, Max(0, -m->x) * Delta() * 10000); 692 | m->vy += Min(100, Max(0, -m->y) * Delta() * 10000); 693 | m->x += m->vx * Delta(); 694 | m->y += m->vy * Delta(); 695 | SetPos(trans[i].trans, (int)m->x, (int)m->y); 696 | } 697 | } 698 | 699 | void RenderSystem(int* mask) { 700 | MeshComp* mesh = GetComps(mask, "MeshComp"); 701 | TransComp* trans = GetComps(mask, "TransComp"); 702 | int i; 703 | for (i = FirstEnt(mask); i >= 0; i = NextEnt(mask, i)) { 704 | PutMesh(mesh[i].mesh, ToTmpMat(trans[i].trans), 0); 705 | } 706 | } 707 | 708 | void InitEcs() { 709 | MkComp(MovementComp); 710 | MkComp(MeshComp); 711 | MkComp(TransComp); 712 | MkSystem(0, "MovementComp|TransComp", MovementSystem); 713 | MkSystem(1, "MeshComp|TransComp", RenderSystem); 714 | } 715 | 716 | /* ---------------------------------------------------------------------------------------------- */ 717 | 718 | #define DEMO_DRAGGING 1 719 | #define DEMO_DIAG 2 720 | int flags = DEMO_DIAG; 721 | float spawnTimer, fpsTimer; 722 | int frames; 723 | Mesh fpsMesh; /* TODO: move fps counter to ecs */ 724 | Ft ft; 725 | 726 | void Init() { 727 | ft = DefFt(); 728 | DiagEcs(flags & DEMO_DIAG); 729 | InitEcs(); 730 | } 731 | 732 | void Quit() { 733 | RmMesh(fpsMesh); 734 | } 735 | 736 | void KeyDown() { 737 | Wnd wnd = AppWnd(); 738 | switch (Key(wnd)) { 739 | case MLEFT: 740 | flags |= DEMO_DRAGGING; 741 | break; 742 | case F1: 743 | flags ^= DEMO_DIAG; 744 | DiagEcs(flags & DEMO_DIAG); 745 | break; 746 | } 747 | } 748 | 749 | void KeyUp() { 750 | Wnd wnd = AppWnd(); 751 | if (Key(wnd) == MLEFT) { 752 | flags &= ~DEMO_DRAGGING; 753 | } 754 | } 755 | 756 | void Motion() { 757 | if (flags & DEMO_DRAGGING) { 758 | Wnd wnd = AppWnd(); 759 | spawnTimer += Delta(); 760 | while (spawnTimer >= 0.01f) { 761 | MkParticle(MouseX(wnd), MouseY(wnd)); 762 | spawnTimer -= 0.01f; 763 | } 764 | } 765 | } 766 | 767 | void Frame() { 768 | ++frames; 769 | fpsTimer += Delta(); 770 | while (!fpsMesh || fpsTimer >= 1) { 771 | char* s = 0; 772 | ArrStrCat(&s, "fps: "); 773 | if (fpsMesh) { 774 | ArrStrCatI32(&s, frames, 10); 775 | } 776 | ArrCat(&s, 0); 777 | RmMesh(fpsMesh); 778 | fpsMesh = MkMesh(); 779 | Col(fpsMesh, 0xbebebe); 780 | FtMesh(fpsMesh, ft, 10, 10, s); 781 | RmArr(s); 782 | frames = 0; 783 | fpsTimer -= 1; 784 | } 785 | PutMesh(fpsMesh, 0, FtImg(ft)); 786 | } 787 | 788 | void AppInit() { 789 | SetAppName("WeebCore - ECS Demo"); 790 | SetAppClass("WeebCoreEcsDemo"); 791 | EcsInit(); 792 | On(INIT, Init); 793 | On(QUIT, Quit); 794 | On(MOTION, Motion); 795 | On(KEYDOWN, KeyDown); 796 | On(KEYUP, KeyUp); 797 | On(FRAME, Frame); 798 | } 799 | 800 | #define WEEBCORE_IMPLEMENTATION 801 | #include "WeebCore.c" 802 | #include "Platform/Platform.h" 803 | -------------------------------------------------------------------------------- /Examples/9999_Paint.c: -------------------------------------------------------------------------------- 1 | /* very early draft of the sprite ed 2 | * middle click drag to pan 3 | * left click drag to draw 4 | * mouse wheel to zoom 5 | * 6 | * to avoid updating the img for every new pix, the pixs are batched up and temporarily 7 | * rendered as individual quads until they are flushed to the real img */ 8 | 9 | #include "WeebCore.c" 10 | 11 | #define EDIMGSIZE 1024 12 | #define EDCHECKER_SIZE 64 13 | 14 | #define EDDRAGGING (1<<1) 15 | #define EDPAINTING (1<<2) 16 | #define EDDIRTY (1<<3) 17 | 18 | typedef struct _EDUpd { 19 | int color; 20 | int x, y; 21 | } EDUpd; 22 | 23 | Wnd wnd; 24 | int flags; 25 | float oX, oY; 26 | float flushTimer; 27 | int scale; 28 | int color; 29 | Mesh bgMesh; 30 | Img checkerImg; 31 | Mesh mesh; 32 | ImgPtr img; 33 | int* imgData; 34 | EDUpd* updates; 35 | Trans trans; 36 | Mat bgMat; 37 | 38 | void EdFlushUpds() { 39 | ImgCpy(img, imgData, EDIMGSIZE, EDIMGSIZE); 40 | FlushImgs(); 41 | SetArrLen(updates, 0); 42 | flags &= ~EDDIRTY; 43 | } 44 | 45 | void EdUpdTrans() { 46 | ClrTrans(trans); 47 | SetPos(trans, oX, oY); 48 | SetScale1(trans, scale); 49 | } 50 | 51 | /* there's no way to do texture wrapping with the img allocator so for these kind of cases we have 52 | * to use a raw img and call PutMeshRaw */ 53 | Img MkCheckerImg() { 54 | static int data[4] = { 0xaaaaaa, 0x666666, 0x666666, 0xaaaaaa }; 55 | Img img = MkImg(); 56 | return Pixs(img, 2, 2, data); 57 | } 58 | 59 | int* MkPixs(int fillCol, int width, int height) { 60 | int i; 61 | int* data = 0; 62 | for (i = 0; i < width * height; ++i) { 63 | ArrCat(&data, fillCol); 64 | } 65 | return data; 66 | } 67 | 68 | void EdMapToImg(float* point) { 69 | /* un-trans mouse coordinates so they are relative to the img */ 70 | InvTransPt(ToTmpMatOrtho(trans), point); 71 | point[0] /= scale; 72 | point[1] /= scale; 73 | } 74 | 75 | void EdPaintPix() { 76 | int x, y; 77 | float point[2]; 78 | point[0] = MouseX(wnd); 79 | point[1] = MouseY(wnd); 80 | EdMapToImg(point); 81 | x = (int)point[0]; 82 | y = (int)point[1]; 83 | if (x >= 0 && x < EDIMGSIZE && y >= 0 && y < EDIMGSIZE) { 84 | int* px = &imgData[y * EDIMGSIZE + x]; 85 | if (*px != color) { 86 | EDUpd* u; 87 | *px = color; 88 | u = ArrAlloc(&updates, 1); 89 | u->x = x; 90 | u->y = y; 91 | u->color = color; 92 | flags |= EDDIRTY; 93 | } 94 | } 95 | } 96 | 97 | void EdChangeScale(int direction) { 98 | float p[2]; 99 | float newScale = scale + direction * (scale / 8 + 1); 100 | newScale = Min(32, newScale); 101 | newScale = Max(1, newScale); 102 | p[0] = MouseX(wnd); 103 | p[1] = MouseY(wnd); 104 | EdMapToImg(p); 105 | /* adjust panning so the pix we're pointing stays under the cursor */ 106 | oX -= (int)((p[0] * newScale) - (p[0] * scale) + 0.5f); 107 | oY -= (int)((p[1] * newScale) - (p[1] * scale) + 0.5f); 108 | scale = newScale; 109 | EdUpdTrans(); 110 | } 111 | 112 | void EdPutUpds() { 113 | int i; 114 | Mesh mesh = MkMesh(); 115 | for (i = 0; i < ArrLen(updates); ++i) { 116 | EDUpd* u = &updates[i]; 117 | Col(mesh, u->color); 118 | Quad(mesh, u->x, u->y, 1, 1); 119 | } 120 | PutMesh(mesh, ToTmpMat(trans), 0); 121 | RmMesh(mesh); 122 | } 123 | 124 | void Size(); 125 | 126 | void Init() { 127 | wnd = AppWnd(); 128 | scale = 4; 129 | oX = 100; 130 | oY = 100; 131 | checkerImg = MkCheckerImg(); 132 | img = ImgAlloc(EDIMGSIZE, EDIMGSIZE); 133 | imgData = MkPixs(0xffffff, EDIMGSIZE, EDIMGSIZE); 134 | trans = MkTrans(); 135 | bgMat = Scale1(MkMat(), EDCHECKER_SIZE); 136 | mesh = MkMesh(); 137 | Quad(mesh, 0, 0, EDIMGSIZE, EDIMGSIZE); 138 | EdFlushUpds(); 139 | Size(); 140 | EdUpdTrans(); 141 | } 142 | 143 | void Quit() { 144 | RmMesh(mesh); 145 | RmMesh(bgMesh); 146 | RmArr(imgData); 147 | RmArr(updates); 148 | RmImg(checkerImg); 149 | RmTrans(trans); 150 | RmMat(bgMat); 151 | } 152 | 153 | void Size() { 154 | RmMesh(bgMesh); 155 | bgMesh = MkMesh(); 156 | Quad(bgMesh, 0, 0, 157 | Ceil(WndWidth(wnd) / (float)EDCHECKER_SIZE), 158 | Ceil(WndHeight(wnd) / (float)EDCHECKER_SIZE) 159 | ); 160 | } 161 | 162 | void KeyDown() { 163 | switch (Key(wnd)) { 164 | case MMID: { flags |= EDDRAGGING; break; } 165 | case MWHEELUP: { EdChangeScale(1); break; } 166 | case MWHEELDOWN: { EdChangeScale(-1); break; } 167 | case MLEFT: { 168 | flags |= EDPAINTING; 169 | EdPaintPix(); 170 | EdFlushUpds(); 171 | break; 172 | } 173 | } 174 | } 175 | 176 | void KeyUp() { 177 | switch (Key(wnd)) { 178 | case MMID: { flags &= ~EDDRAGGING; break; } 179 | case MLEFT: { flags &= ~EDPAINTING; break; } 180 | } 181 | } 182 | 183 | void Motion() { 184 | if (flags & EDDRAGGING) { 185 | oX += MouseDX(wnd); 186 | oY += MouseDY(wnd); 187 | EdUpdTrans(); 188 | } 189 | if (flags & EDPAINTING) { 190 | EdPaintPix(); 191 | } 192 | } 193 | 194 | void Frame() { 195 | flushTimer += Delta(wnd); 196 | if ((flags & EDDIRTY) && flushTimer > 1.0f) { 197 | EdFlushUpds(); 198 | flushTimer -= 1.0f; 199 | } 200 | 201 | PutMeshRaw(bgMesh, bgMat, checkerImg); 202 | PutMesh(mesh, ToTmpMat(trans), img); 203 | EdPutUpds(); 204 | } 205 | 206 | void AppInit() { 207 | SetAppClass("HelloWnd"); 208 | SetAppName("Hello WeebCore"); 209 | On(INIT, Init); 210 | On(QUIT, Quit); 211 | On(SIZE, Size); 212 | On(KEYDOWN, KeyDown); 213 | On(KEYUP, KeyUp); 214 | On(MOTION, Motion); 215 | On(FRAME, Frame); 216 | } 217 | 218 | #define WEEBCORE_IMPLEMENTATION 219 | #include "WeebCore.c" 220 | #include "Platform/Platform.h" 221 | -------------------------------------------------------------------------------- /Platform/LibC.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct _OsTime { struct timespec t; }; 10 | 11 | float FltMod(float x, float y) { return (float)fmod(x, y); } 12 | float Sin(float degrees) { return (float)sin(ToRad(degrees)); } 13 | float Cos(float degrees) { return (float)cos(ToRad(degrees)); } 14 | int Ceil(float x) { return (int)ceil(x); } 15 | int Floor(float x) { return (int)floor(x); } 16 | float Sqrt(float x) { return (float)sqrt(x); } 17 | 18 | static void Die(char* fmt, ...) { 19 | va_list va; 20 | va_start(va, fmt); 21 | vprintf(fmt, va); 22 | va_end(va); 23 | putchar('\n'); 24 | exit(1); 25 | } 26 | 27 | void* Alloc(int n) { 28 | void* p = malloc(n); 29 | MemSet(p, 0, n); 30 | return p; 31 | } 32 | 33 | void* Realloc(void* p, int n) { 34 | return realloc(p, n); 35 | } 36 | 37 | void Free(void* p) { 38 | free(p); 39 | } 40 | 41 | void MemSet(void* p, unsigned char val, int n) { 42 | memset(p, val, n); 43 | } 44 | 45 | void MemCpy(void* dst, void* src, int n) { 46 | memcpy(dst, src, n); 47 | } 48 | 49 | void MemMv(void* dst, void* src, int n) { 50 | memmove(dst, src, n); 51 | } 52 | 53 | int WrFile(char* path, void* data, int dataLen) { 54 | FILE* f = fopen(path, "wb"); 55 | int res; 56 | if (!f) { 57 | return -1; 58 | } 59 | res = (int)fwrite(data, 1, dataLen, f); 60 | fclose(f); 61 | return res; 62 | } 63 | 64 | int RdFile(char* path, void* data, int maxSize) { 65 | FILE* f = fopen(path, "rb"); 66 | int res; 67 | if (!f) { 68 | return -1; 69 | } 70 | res = (int)fread(data, 1, maxSize, f); 71 | fclose(f); 72 | return res; 73 | } 74 | 75 | static OsTime MkOsTime() { 76 | return Alloc(sizeof(struct _OsTime)); 77 | } 78 | 79 | static void RmOsTime(OsTime t) { 80 | Free(t); 81 | } 82 | 83 | static void OsTimeCpy(OsTime dst, OsTime src) { 84 | MemCpy(&dst->t, &src->t, sizeof(dst->t)); 85 | } 86 | 87 | static void OsTimeNow(OsTime dst) { 88 | if (clock_gettime(CLOCK_MONOTONIC, &dst->t) == -1) { 89 | Die("clock_gettime failed: %s\n", strerror(errno)); 90 | } 91 | } 92 | 93 | static void TSpecDelta(struct timespec* delta, struct timespec* a, struct timespec* b) { 94 | if (a->tv_nsec > b->tv_nsec) { 95 | delta->tv_sec = b->tv_sec - a->tv_sec - 1; 96 | delta->tv_nsec = 1000000000 + b->tv_nsec - a->tv_nsec; 97 | } else { 98 | delta->tv_sec = b->tv_sec - a->tv_sec; 99 | delta->tv_nsec = b->tv_nsec - a->tv_nsec; 100 | } 101 | } 102 | 103 | static float OsTimeDelta(OsTime before, OsTime now) { 104 | struct timespec delta; 105 | TSpecDelta(&delta, &before->t, &now->t); 106 | return delta.tv_sec + delta.tv_nsec * 1e-9f; 107 | } 108 | -------------------------------------------------------------------------------- /Platform/OpenGL.c: -------------------------------------------------------------------------------- 1 | /* NOTE: this intentionally uses OpenGL 1.x for maximum compatibility. do not try to use modern 2 | * opengl features in here. make a new platform layer for optimization with modern APIs instead. */ 3 | 4 | #include 5 | 6 | typedef struct _VertData { 7 | float x, y; 8 | int color; 9 | float u, v; 10 | } VertData; 11 | 12 | struct _Mesh { 13 | int color; 14 | VertData* verts; 15 | int* indices; 16 | int istart; 17 | }; 18 | 19 | struct _Img { GLuint handle; int width, height; }; 20 | 21 | /* convert a 0xAARRGGBB integer to OpenGL's 0xAABBGGRR. 22 | * also flip alpha because we use 0 = opaque */ 23 | 24 | #define COL(argb) \ 25 | ((((argb) & 0x00FF0000) >> 16) | \ 26 | ((argb) & 0x0000FF00) | \ 27 | (((argb) & 0x000000FF) << 16) | \ 28 | (0xFF000000 - ((argb) & 0xFF000000))) 29 | 30 | static void GrInit() { 31 | glEnable(GL_TEXTURE_2D); 32 | glEnable(GL_CULL_FACE); 33 | glEnable(GL_BLEND); 34 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 35 | glEnableClientState(GL_VERTEX_ARRAY); 36 | glEnableClientState(GL_COLOR_ARRAY); 37 | glEnableClientState(GL_TEXTURE_COORD_ARRAY); 38 | } 39 | 40 | /* ---------------------------------------------------------------------------------------------- */ 41 | 42 | Mesh MkMesh() { 43 | Mesh mesh = Alloc(sizeof(struct _Mesh)); 44 | Col(mesh, 0xffffff); 45 | return mesh; 46 | } 47 | 48 | void RmMesh(Mesh mesh) { 49 | if (mesh) { 50 | RmArr(mesh->verts); 51 | RmArr(mesh->indices); 52 | } 53 | Free(mesh); 54 | } 55 | 56 | void Col(Mesh mesh, int color) { 57 | mesh->color = COL(color); 58 | } 59 | 60 | void Begin(Mesh mesh) { 61 | if (mesh->istart == -1) { 62 | mesh->istart = ArrLen(mesh->verts); 63 | } 64 | } 65 | 66 | void End(Mesh mesh) { 67 | mesh->istart = -1; 68 | } 69 | 70 | void Vert(Mesh mesh, float x, float y) { 71 | VertData* vertex = ArrAlloc(&mesh->verts, 1); 72 | vertex->x = x; 73 | vertex->y = y; 74 | vertex->color = mesh->color; 75 | } 76 | 77 | void ImgCoord(Mesh mesh, float u, float v) { 78 | int len = ArrLen(mesh->verts); 79 | if (len) { 80 | VertData* vertex = &mesh->verts[len - 1]; 81 | vertex->u = u; 82 | vertex->v = v; 83 | } 84 | } 85 | 86 | void Face(Mesh mesh, int i1, int i2, int i3) { 87 | int* indices = ArrAlloc(&mesh->indices, 3); 88 | indices[0] = mesh->istart + i1; 89 | indices[1] = mesh->istart + i2; 90 | indices[2] = mesh->istart + i3; 91 | } 92 | 93 | void PutMeshRawEx(Mesh mesh, Mat mat, Img img, float uOffs, float vOffs) { 94 | int stride = sizeof(VertData); 95 | GLuint gltex, curImg; 96 | 97 | if (!ArrLen(mesh->indices)) { 98 | return; 99 | } 100 | 101 | gltex = img ? img->handle : 0; 102 | glVertexPointer(2, GL_FLOAT, stride, &mesh->verts[0].x); 103 | glColorPointer(4, GL_UNSIGNED_BYTE, stride, &mesh->verts[0].color); 104 | if (gltex) { 105 | glTexCoordPointer(2, GL_FLOAT, stride, &mesh->verts[0].u); 106 | /* scale img coords so we can use pixs instead of texels */ 107 | glPushAttrib(GL_TRANSFORM_BIT); 108 | glMatrixMode(GL_TEXTURE); 109 | glLoadIdentity(); 110 | glScalef(1.0f / img->width, 1.0f / img->height, 1.0f); 111 | glTranslatef(uOffs, vOffs, 0); 112 | glPopAttrib(); 113 | } 114 | 115 | glGetIntegerv(GL_TEXTURE_BINDING_2D, (void*)&curImg); 116 | if (gltex != curImg) { 117 | glBindTexture(GL_TEXTURE_2D, gltex); 118 | } 119 | 120 | glLoadMatrixf(MatFlts(mat)); 121 | glDrawElements(GL_TRIANGLES, ArrLen(mesh->indices), GL_UNSIGNED_INT, mesh->indices); 122 | } 123 | 124 | /* ---------------------------------------------------------------------------------------------- */ 125 | 126 | Img MkImg() { 127 | Img img = Alloc(sizeof(struct _Img)); 128 | glGenTextures(1, &img->handle); 129 | SetImgWrapU(img, REPEAT); 130 | SetImgWrapV(img, REPEAT); 131 | SetImgMinFilter(img, NEAREST); 132 | SetImgMagFilter(img, NEAREST); 133 | return img; 134 | } 135 | 136 | void RmImg(Img img) { 137 | if (img) { 138 | glDeleteTextures(1, &img->handle); 139 | } 140 | Free(img); 141 | } 142 | 143 | static int ConvImgWrap(int mode) { 144 | switch (mode) { 145 | case CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; 146 | case MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; 147 | case REPEAT: return GL_REPEAT; 148 | } 149 | return REPEAT; 150 | } 151 | 152 | static int ConvImgFilter(int filter) { 153 | switch (filter) { 154 | case NEAREST: return GL_NEAREST; 155 | case LINEAR: return GL_LINEAR; 156 | } 157 | return NEAREST; 158 | } 159 | 160 | static void SetImgParam(Img img, int param, int value) { 161 | glBindTexture(GL_TEXTURE_2D, img->handle); 162 | glTexParameteri(GL_TEXTURE_2D, param, value); 163 | } 164 | 165 | void SetImgWrapU(Img img, int mode) { 166 | SetImgParam(img, GL_TEXTURE_WRAP_S, ConvImgWrap(mode)); 167 | } 168 | 169 | void SetImgWrapV(Img img, int mode) { 170 | SetImgParam(img, GL_TEXTURE_WRAP_T, ConvImgWrap(mode)); 171 | } 172 | 173 | void SetImgMinFilter(Img img, int filter) { 174 | SetImgParam(img, GL_TEXTURE_MIN_FILTER, ConvImgFilter(filter)); 175 | } 176 | 177 | void SetImgMagFilter(Img img, int filter) { 178 | SetImgParam(img, GL_TEXTURE_MAG_FILTER, ConvImgFilter(filter)); 179 | } 180 | 181 | Img Pixs(Img img, int width, int height, int* data) { 182 | return PixsEx(img, width, height, data, width); 183 | } 184 | 185 | Img PixsEx(Img img, int width, int height, int* data, int stride) { 186 | int* rgba = 0; 187 | int x, y; 188 | for (y = 0; y < height; ++y) { 189 | for (x = 0; x < width; ++x) { 190 | ArrCat(&rgba, COL(data[y * stride + x])); 191 | } 192 | } 193 | glBindTexture(GL_TEXTURE_2D, img->handle); 194 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); 195 | RmArr(rgba); 196 | img->width = width; 197 | img->height = height; 198 | return img; 199 | } 200 | 201 | /* ---------------------------------------------------------------------------------------------- */ 202 | 203 | void Viewport(Wnd window, int x, int y, int width, int height) { 204 | /* OpenGL uses a Y axis that starts from the bottom of the window. 205 | * I flip it because I find it most intuitive that way */ 206 | glViewport(x, WndHeight(window) - y - height, width, height); 207 | glPushAttrib(GL_TRANSFORM_BIT); 208 | glMatrixMode(GL_PROJECTION); 209 | glLoadIdentity(); 210 | glOrtho(0, width, height, 0, 0, 1); 211 | glPopAttrib(); 212 | } 213 | 214 | void ClsCol(int color) { 215 | float rgba[4]; 216 | ColToFlts(color, rgba); 217 | glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]); 218 | } 219 | 220 | void SwpBufs(Wnd window) { 221 | OsSwpBufs(window); 222 | /* this is to avoid having to have an extra function to call at the beginning of the frame */ 223 | glClear(GL_COLOR_BUFFER_BIT); 224 | } 225 | -------------------------------------------------------------------------------- /Platform/Platform.h: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) || defined(__GLIBC__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ 2 | defined(__OpenBSD__) 3 | /* TODO: actually test other *nixes */ 4 | #include "X11.c" 5 | #else 6 | #error "unknown platform" 7 | #endif 8 | -------------------------------------------------------------------------------- /Platform/PlatformLib.c: -------------------------------------------------------------------------------- 1 | /* compile this to get a shared library that contains the platform layer and WeebCore */ 2 | 3 | #define WEEBCORE_LIB 4 | #define WEEBCORE_IMPLEMENTATION 5 | #include "WeebCore.c" 6 | #include "Platform.h" 7 | -------------------------------------------------------------------------------- /Platform/X11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* render layer */ 9 | static void GrInit(); 10 | 11 | /* os layer */ 12 | typedef struct _OsTime* OsTime; 13 | 14 | static void Die(char* fmt, ...); 15 | static OsTime MkOsTime(); 16 | static void RmOsTime(OsTime t); 17 | static void OsTimeCpy(OsTime dst, OsTime src); 18 | static void OsTimeNow(OsTime dst); 19 | static float OsTimeDelta(OsTime before, OsTime now); 20 | 21 | struct _Wnd { 22 | Display* dpy; 23 | Window ptr; 24 | Colormap colorMap; 25 | GLXContext gl; 26 | int width, height; 27 | int mouseX, mouseY; 28 | float minFrameTime; 29 | OsTime lastTime, now; 30 | float delta; 31 | int msgType; 32 | union _MsgData { 33 | struct _KeyMsgData { 34 | int code; 35 | int state; 36 | } key; 37 | struct _MouseMsgData { 38 | int dx, dy; 39 | } mouse; 40 | } u; 41 | }; 42 | 43 | static void UpdateTime(Wnd wnd) { 44 | OsTimeNow(wnd->now); 45 | wnd->delta = OsTimeDelta(wnd->lastTime, wnd->now); 46 | } 47 | 48 | /* pick fb config that has the the highest samples visual */ 49 | static GLXFBConfig PickBufCfg(Display* dpy) { 50 | static int attrs[] = { 51 | GLX_X_RENDERABLE, True, 52 | GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 53 | GLX_RENDER_TYPE, GLX_RGBA_BIT, 54 | GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 55 | GLX_RED_SIZE, 8, 56 | GLX_GREEN_SIZE, 8, 57 | GLX_BLUE_SIZE, 8, 58 | GLX_ALPHA_SIZE, 8, 59 | GLX_DEPTH_SIZE, 24, 60 | GLX_STENCIL_SIZE, 8, 61 | GLX_DOUBLEBUFFER, True, 62 | None 63 | }; 64 | int n, i, bestSamples = -1, sampleBufs, samples; 65 | GLXFBConfig res = 0, *configs; 66 | 67 | configs = glXChooseFBConfig(dpy, DefaultScreen(dpy), attrs, &n); 68 | if (!n || !configs) { return 0; } 69 | for (i = 0; i < n; ++i) { 70 | glXGetFBConfigAttrib(dpy, configs[i], GLX_SAMPLE_BUFFERS, &sampleBufs); 71 | glXGetFBConfigAttrib(dpy, configs[i], GLX_SAMPLES, &samples); 72 | if (bestSamples < 0 || (sampleBufs && samples > bestSamples)) { 73 | bestSamples = samples; 74 | res = configs[i]; 75 | } 76 | } 77 | XFree(configs); 78 | return res; 79 | } 80 | 81 | static void SetAtom(Wnd wnd, char* name, char* valueName) { 82 | Atom atom = XInternAtom(wnd->dpy, name, True); 83 | Atom value = XInternAtom(wnd->dpy, valueName, True); 84 | if (atom != None && value != None) { 85 | XChangeProperty(wnd->dpy, wnd->ptr, atom, XA_ATOM, 32, 86 | PropModeReplace, (unsigned char*)&value, 1); 87 | } 88 | } 89 | 90 | static void SetWMProtocol(Wnd wnd, char* name) { 91 | Atom wmProtocol = XInternAtom(wnd->dpy, name, False); 92 | XSetWMProtocols(wnd->dpy, wnd->ptr, &wmProtocol, 1); 93 | } 94 | 95 | Wnd MkWnd(char* name, char* class) { 96 | Wnd wnd = Alloc(sizeof(struct _Wnd)); 97 | Window rootwin; 98 | XSetWindowAttributes swa; 99 | GLXFBConfig fb; 100 | XVisualInfo* vi; 101 | 102 | wnd->dpy = XOpenDisplay(0); 103 | if (!wnd->dpy) { 104 | Die("no display"); 105 | } 106 | 107 | fb = PickBufCfg(wnd->dpy); 108 | if (!fb) { 109 | Die("couldn't find appropriate fb config"); 110 | } 111 | 112 | rootwin = DefaultRootWindow(wnd->dpy); 113 | vi = glXGetVisualFromFBConfig(wnd->dpy, fb); 114 | 115 | swa.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask | ButtonPressMask | 116 | KeyReleaseMask | ButtonReleaseMask | PointerMotionMask; 117 | 118 | swa.colormap = wnd->colorMap = XCreateColormap(wnd->dpy, rootwin, vi->visual, AllocNone); 119 | 120 | wnd->width = 640; 121 | wnd->height = 480; 122 | wnd->ptr = XCreateWindow(wnd->dpy, rootwin, 0, 0, wnd->width, wnd->height, 0, 123 | vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa); 124 | 125 | /* tell wnd managers we'd like this wnd to float if possible */ 126 | SetAtom(wnd, "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_UTILITY"); 127 | 128 | /* capture delete event so closing the wnd actually stops msgloop */ 129 | SetWMProtocol(wnd, "WM_DELETE_WINDOW"); 130 | 131 | SetWndName(wnd, name ? name : "WeebCoreX11"); 132 | SetWndClass(wnd, class ? class : "WeebCore"); 133 | 134 | wnd->gl = glXCreateNewContext(wnd->dpy, fb, GLX_RGBA_TYPE, 0, True); 135 | glXMakeCurrent(wnd->dpy, wnd->ptr, wnd->gl); 136 | 137 | XMapWindow(wnd->dpy, wnd->ptr); 138 | XFree(vi); 139 | 140 | SetWndFPS(wnd, 420); 141 | wnd->lastTime = MkOsTime(); 142 | wnd->now = MkOsTime(); 143 | UpdateTime(wnd); 144 | OsTimeCpy(wnd->lastTime, wnd->now); 145 | wnd->delta = wnd->minFrameTime; 146 | 147 | GrInit(); 148 | 149 | return wnd; 150 | } 151 | 152 | void RmWnd(Wnd wnd) { 153 | glXMakeCurrent(wnd->dpy, 0, 0); 154 | glXDestroyContext(wnd->dpy, wnd->gl); 155 | XDestroyWindow(wnd->dpy, wnd->ptr); 156 | XFreeColormap(wnd->dpy, wnd->colorMap); 157 | XCloseDisplay(wnd->dpy); 158 | RmOsTime(wnd->now); 159 | RmOsTime(wnd->lastTime); 160 | Free(wnd); 161 | } 162 | 163 | void SetWndSize(Wnd wnd, int width, int height) { 164 | XResizeWindow(wnd->dpy, wnd->ptr, width, height); 165 | } 166 | 167 | void SetWndName(Wnd wnd, char* wndName) { 168 | XStoreName(wnd->dpy, wnd->ptr, wndName); 169 | } 170 | 171 | void SetWndClass(Wnd wnd, char* className) { 172 | XClassHint* classHints = XAllocClassHint(); 173 | classHints->res_name = className; 174 | classHints->res_class = "WeebCoreX11"; 175 | XSetClassHint(wnd->dpy, wnd->ptr, classHints); 176 | XFree(classHints); 177 | } 178 | 179 | void SetWndFPS(Wnd wnd, int fps) { 180 | wnd->minFrameTime = 1.0f / (fps ? fps : 10000); 181 | } 182 | 183 | float WndDelta(Wnd wnd) { 184 | return wnd->delta; 185 | } 186 | 187 | int WndWidth(Wnd wnd) { 188 | return wnd->width; 189 | } 190 | 191 | int WndHeight(Wnd wnd) { 192 | return wnd->height; 193 | } 194 | 195 | /* keycode list adapted from glfw */ 196 | /* TODO: use Xkb to handle keyboard layouts? */ 197 | static int ConvKey(Display* dpy, int xkeycode) { 198 | int sym, symsPerKey; 199 | KeySym* syms = XGetKeyboardMapping(dpy, xkeycode, 1, &symsPerKey); 200 | sym = syms[0]; 201 | XFree(syms); 202 | switch (sym) { 203 | case XK_Escape: return ESCAPE; 204 | case XK_Tab: return TAB; 205 | case XK_Shift_L: return LEFT_SHIFT; 206 | case XK_Shift_R: return RIGHT_SHIFT; 207 | case XK_Control_L: return LEFT_CONTROL; 208 | case XK_Control_R: return RIGHT_CONTROL; 209 | case XK_Meta_L: 210 | case XK_Alt_L: return LEFT_ALT; 211 | case XK_Mode_switch: /* mapped to Alt_R on many keyboards */ 212 | case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ 213 | case XK_Meta_R: 214 | case XK_Alt_R: return RIGHT_ALT; 215 | case XK_Super_L: return LEFT_SUPER; 216 | case XK_Super_R: return RIGHT_SUPER; 217 | case XK_Menu: return MENU; 218 | case XK_Num_Lock: return NUM_LOCK; 219 | case XK_Caps_Lock: return CAPS_LOCK; 220 | case XK_Print: return PRINT_SCREEN; 221 | case XK_Scroll_Lock: return SCROLL_LOCK; 222 | case XK_Pause: return PAUSE; 223 | case XK_Delete: return DELETE; 224 | case XK_BackSpace: return BACKSPACE; 225 | case XK_Return: return ENTER; 226 | case XK_Home: return HOME; 227 | case XK_End: return END; 228 | case XK_Page_Up: return PAGE_UP; 229 | case XK_Page_Down: return PAGE_DOWN; 230 | case XK_Insert: return INSERT; 231 | case XK_Left: return LEFT; 232 | case XK_Right: return RIGHT; 233 | case XK_Down: return DOWN; 234 | case XK_Up: return UP; 235 | case XK_F1: return F1; 236 | case XK_F2: return F2; 237 | case XK_F3: return F3; 238 | case XK_F4: return F4; 239 | case XK_F5: return F5; 240 | case XK_F6: return F6; 241 | case XK_F7: return F7; 242 | case XK_F8: return F8; 243 | case XK_F9: return F9; 244 | case XK_F10: return F10; 245 | case XK_F11: return F11; 246 | case XK_F12: return F12; 247 | case XK_F13: return F13; 248 | case XK_F14: return F14; 249 | case XK_F15: return F15; 250 | case XK_F16: return F16; 251 | case XK_F17: return F17; 252 | case XK_F18: return F18; 253 | case XK_F19: return F19; 254 | case XK_F20: return F20; 255 | case XK_F21: return F21; 256 | case XK_F22: return F22; 257 | case XK_F23: return F23; 258 | case XK_F24: return F24; 259 | case XK_F25: return F25; 260 | 261 | /* numeric keypad */ 262 | case XK_KP_Divide: return KP_DIVIDE; 263 | case XK_KP_Multiply: return KP_MULTIPLY; 264 | case XK_KP_Subtract: return KP_SUBTRACT; 265 | case XK_KP_Add: return KP_ADD; 266 | 267 | /* if I add xkb support, this will be a fallback to a fixed layout */ 268 | case XK_KP_Insert: return KP_0; 269 | case XK_KP_End: return KP_1; 270 | case XK_KP_Down: return KP_2; 271 | case XK_KP_Page_Down: return KP_3; 272 | case XK_KP_Left: return KP_4; 273 | case XK_KP_Right: return KP_6; 274 | case XK_KP_Home: return KP_7; 275 | case XK_KP_Up: return KP_8; 276 | case XK_KP_Page_Up: return KP_9; 277 | case XK_KP_Delete: return KP_DECIMAL; 278 | case XK_KP_Equal: return KP_EQUAL; 279 | case XK_KP_Enter: return KP_ENTER; 280 | 281 | /* printable keys */ 282 | case XK_a: return A; 283 | case XK_b: return B; 284 | case XK_c: return C; 285 | case XK_d: return D; 286 | case XK_e: return E; 287 | case XK_f: return F; 288 | case XK_g: return G; 289 | case XK_h: return H; 290 | case XK_i: return I; 291 | case XK_j: return J; 292 | case XK_k: return K; 293 | case XK_l: return L; 294 | case XK_m: return M; 295 | case XK_n: return N; 296 | case XK_o: return O; 297 | case XK_p: return P; 298 | case XK_q: return Q; 299 | case XK_r: return R; 300 | case XK_s: return S; 301 | case XK_t: return T; 302 | case XK_u: return U; 303 | case XK_v: return V; 304 | case XK_w: return W; 305 | case XK_x: return X; 306 | case XK_y: return Y; 307 | case XK_z: return Z; 308 | case XK_1: return K1; 309 | case XK_2: return K2; 310 | case XK_3: return K3; 311 | case XK_4: return K4; 312 | case XK_5: return K5; 313 | case XK_6: return K6; 314 | case XK_7: return K7; 315 | case XK_8: return K8; 316 | case XK_9: return K9; 317 | case XK_0: return K0; 318 | case XK_space: return SPACE; 319 | case XK_minus: return MINUS; 320 | case XK_equal: return EQUAL; 321 | case XK_bracketleft: return LEFT_BRACKET; 322 | case XK_bracketright: return RIGHT_BRACKET; 323 | case XK_backslash: return BACKSLASH; 324 | case XK_semicolon: return SEMICOLON; 325 | case XK_apostrophe: return APOSTROPHE; 326 | case XK_grave: return GRAVE_ACCENT; 327 | case XK_comma: return COMMA; 328 | case XK_period: return PERIOD; 329 | case XK_slash: return SLASH; 330 | case XK_less: return WORLD_1; 331 | } 332 | return -1; 333 | } 334 | 335 | static int ConvKeyState(int state) { 336 | int res = 0; 337 | if (state & ShiftMask) res |= FSHIFT; 338 | if (state & ControlMask) res |= FCONTROL; 339 | if (state & Mod1Mask) res |= FALT; 340 | if (state & Mod4Mask) res |= FSUPER; 341 | if (state & LockMask) res |= FCAPS_LOCK; 342 | if (state & Mod2Mask) res |= FNUM_LOCK; 343 | return res; 344 | } 345 | 346 | int MsgType(Wnd wnd) { 347 | return wnd->msgType; 348 | } 349 | 350 | int Key(Wnd wnd) { 351 | return wnd->u.key.code; 352 | } 353 | 354 | int KeyState(Wnd wnd) { 355 | return wnd->u.key.state; 356 | } 357 | 358 | int MouseX(Wnd wnd) { 359 | return wnd->mouseX; 360 | } 361 | 362 | int MouseY(Wnd wnd) { 363 | return wnd->mouseY; 364 | } 365 | 366 | int MouseDX(Wnd wnd) { 367 | return wnd->u.mouse.dx; 368 | } 369 | 370 | int MouseDY(Wnd wnd) { 371 | return wnd->u.mouse.dy; 372 | } 373 | 374 | int NextMsg(Wnd wnd) { 375 | XEvent xev; 376 | Atom deleteMsg = XInternAtom(wnd->dpy, "WM_DELETE_WINDOW", False); 377 | while (wnd->msgType != QUIT_REQUEST && XPending(wnd->dpy)) { 378 | XNextEvent(wnd->dpy, &xev); 379 | MemSet(&wnd->u, 0, sizeof(wnd->u)); 380 | switch (xev.type) { 381 | case ClientMessage: { 382 | if (xev.xclient.data.l[0] == deleteMsg) { 383 | PostQuitMsg(wnd); 384 | } 385 | return 1; 386 | } 387 | case ConfigureNotify: { 388 | wnd->msgType = SIZE; 389 | wnd->width = xev.xconfigure.width; 390 | wnd->height = xev.xconfigure.height; 391 | Viewport(wnd, 0, 0, xev.xconfigure.width, xev.xconfigure.height); 392 | return 1; 393 | } 394 | case KeyPress: { 395 | wnd->msgType = KEYDOWN; 396 | wnd->u.key.code = ConvKey(wnd->dpy, xev.xkey.keycode); 397 | wnd->u.key.state |= ConvKeyState(xev.xkey.state); 398 | return 1; 399 | } 400 | case KeyRelease: { 401 | XEvent nextXev; 402 | wnd->u.key.code = ConvKey(wnd->dpy, xev.xkey.keycode); 403 | wnd->u.key.state |= ConvKeyState(xev.xkey.state); 404 | /* anything released for < 20ms is a repeat */ 405 | /* TODO: Xkb can detect this without guessing */ 406 | if (XEventsQueued(wnd->dpy, QueuedAfterReading)) { 407 | int repeat; 408 | XPeekEvent(wnd->dpy, &nextXev); 409 | repeat = ( 410 | nextXev.type == KeyPress && 411 | nextXev.xkey.window == xev.xkey.window && 412 | nextXev.xkey.keycode == xev.xkey.keycode && 413 | nextXev.xkey.time - xev.xkey.time < 20 414 | ); 415 | if (repeat) { 416 | wnd->u.key.state |= FREPEAT; 417 | wnd->u.key.state |= ConvKeyState(nextXev.xkey.state); 418 | } 419 | } 420 | if (wnd->u.key.state & FREPEAT) { 421 | XNextEvent(wnd->dpy, &nextXev); /* swallow the repeat press */ 422 | wnd->msgType = KEYDOWN; 423 | } else { 424 | wnd->msgType = KEYUP; 425 | } 426 | return 1; 427 | } 428 | case ButtonPress: { 429 | wnd->msgType = KEYDOWN; 430 | wnd->u.key.code = xev.xbutton.button; 431 | wnd->u.key.state |= ConvKeyState(xev.xkey.state); 432 | return 1; 433 | } 434 | case ButtonRelease: { 435 | wnd->msgType = KEYUP; 436 | wnd->u.key.code = xev.xbutton.button; 437 | wnd->u.key.state |= ConvKeyState(xev.xkey.state); 438 | return 1; 439 | } 440 | case MotionNotify: { 441 | wnd->msgType = MOTION; 442 | wnd->u.mouse.dx = xev.xmotion.x - wnd->mouseX; 443 | wnd->u.mouse.dy = xev.xmotion.y - wnd->mouseY; 444 | wnd->mouseX = xev.xmotion.x; 445 | wnd->mouseY = xev.xmotion.y; 446 | return 1; 447 | } 448 | } 449 | } 450 | return wnd->msgType == QUIT_REQUEST; 451 | } 452 | 453 | void PostQuitMsg(Wnd wnd) { wnd->msgType = QUIT_REQUEST; } 454 | 455 | static void OsSwpBufs(Wnd wnd) { 456 | glXSwapBuffers(wnd->dpy, wnd->ptr); 457 | 458 | UpdateTime(wnd); 459 | while (WndDelta(wnd) < wnd->minFrameTime) { 460 | UpdateTime(wnd); 461 | _mm_pause(); 462 | } 463 | 464 | if (wnd->delta == 0) { 465 | Die("delta was zero, something is very wrong with the timer."); 466 | } 467 | 468 | OsTimeCpy(wnd->lastTime, wnd->now); 469 | } 470 | 471 | #include "Platform/OpenGL.c" 472 | #include "Platform/LibC.c" 473 | 474 | #ifndef WEEBCORE_LIB 475 | int main(int argc, char* argv[]) { 476 | AppInit(); 477 | return AppMain(argc, argv); 478 | } 479 | #endif 480 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tiny single-file game dev library. I made this mostly for myself, to develop my future games and to 2 | have fun writing it all from scratch. feel free to use it though, it's public domain! 3 | 4 | this is a very early work-in-progress. at its current state it's barely more than a platform layer. 5 | 6 | all of the code and assets are public domain unless stated otherwise 7 | 8 | # your first window 9 | 10 | I want it to be as painless as possible to get something on screen so you can have fun prototyping 11 | right away, while still giving a powerful interface to your gpu. drawing a rectangle is as simple as 12 | 13 | ```c 14 | #define WEEBCORE_IMPLEMENTATION 15 | #include "WeebCore.c" 16 | #include "Platform/Platform.h" 17 | 18 | Mesh mesh; 19 | 20 | void Init() { 21 | mesh = MkMesh(); 22 | Col(mesh, 0xff3333); 23 | Quad(mesh, 10, 10, 200, 100); 24 | } 25 | 26 | void Frame() { 27 | PutMesh(mesh, 0, 0); 28 | } 29 | 30 | void AppInit() { 31 | On(INIT, Init); 32 | On(FRAME, Frame); 33 | } 34 | ``` 35 | 36 | note that the platform include is absolutely optional and you can implement your own platform later. 37 | WeebCore.c does not use any external library and doesn't include anything, not even the standard C 38 | library 39 | 40 | # calling WeebCore from other languages (FFI) 41 | 42 | WeebCore is designed to be FFI friendly. most of the interfaces take basic C types. the only gotcha 43 | is that if your lang/FFI lib can't generate callbacks you have to handle msgs manually (through 44 | a simple if/else or switch statement, or by building your own msg handling around it) 45 | 46 | here's a minimal example in python with callbacks 47 | 48 | ```py 49 | #!/usr/bin/env python 50 | 51 | # run from WeebCore root dir with 52 | # ./build.sh lib && vblank_mode=0 LD_LIBRARY_PATH="$(pwd)" /path/to/script.py 53 | 54 | from ctypes import CDLL, c_void_p, c_int, c_float, CFUNCTYPE 55 | 56 | weeb = CDLL('WeebCore.so') # on windows you would change this to dll 57 | weeb.MkMesh.restype = weeb.AppWnd.restype = weeb.MkMesh.restype = c_void_p 58 | weeb.Col.argtypes = [c_void_p, c_int] 59 | weeb.Quad.argtypes = [c_void_p] + [c_float] * 4 60 | weeb.PutMesh.argtypes = [c_void_p] * 3 61 | 62 | INIT = 6 63 | FRAME = 7 64 | 65 | 66 | class Game: 67 | def __init__(self): 68 | self.mesh = None 69 | 70 | 71 | G = Game() 72 | 73 | 74 | @CFUNCTYPE(None) 75 | def init(): 76 | G.mesh = weeb.MkMesh() 77 | weeb.Col(G.mesh, 0xff3333) 78 | weeb.Quad(G.mesh, 10, 10, 200, 100) 79 | 80 | 81 | @CFUNCTYPE(None) 82 | def frame(): 83 | weeb.PutMesh(G.mesh, None, None) 84 | 85 | 86 | weeb.On(INIT, init) 87 | weeb.On(FRAME, frame) 88 | weeb.AppMain(0, None) 89 | ``` 90 | 91 | here's a minimal example in python without callbacks 92 | 93 | ```py 94 | #!/usr/bin/env python 95 | 96 | # run from WeebCore root dir with 97 | # ./build.sh lib && vblank_mode=0 LD_LIBRARY_PATH="$(pwd)" /path/to/script.py 98 | 99 | from ctypes import CDLL, c_void_p, c_int, c_float 100 | 101 | weeb = CDLL('WeebCore.so') # on windows you would change this to dll 102 | weeb.MkMesh.restype = weeb.AppWnd.restype = weeb.MkMesh.restype = c_void_p 103 | weeb.Col.argtypes = [c_void_p, c_int] 104 | weeb.Quad.argtypes = [c_void_p] + [c_float] * 4 105 | weeb.PutMesh.argtypes = [c_void_p] * 3 106 | weeb.SwpBufs.argtypes = weeb.NextMsg.argtypes = [c_void_p] 107 | 108 | weeb.MkApp(0, None) 109 | wnd = weeb.AppWnd() 110 | mesh = weeb.MkMesh() 111 | weeb.Col(mesh, 0xff3333) 112 | weeb.Quad(mesh, 10, 10, 200, 100) 113 | 114 | while weeb.AppRunning(): 115 | while weeb.NextMsg(wnd) and weeb.AppHandleMsg(): 116 | # TODO: handle msgs here 117 | pass 118 | 119 | weeb.AppFrame() 120 | weeb.PutMesh(mesh, None, None) 121 | weeb.SwpBufs(wnd) 122 | 123 | weeb.RmApp() 124 | ``` 125 | 126 | # philosophy 127 | 128 | * modular design - no platform specific code in the core code, just provide a platform interface 129 | that could be implemented for any platform 130 | * failure is an option - cutting corners to make code simpler and programming more fun is fine. 131 | not trying to cover every use/edge case 132 | * stick to C89 standard for maximum compatibility (platform specific code may use newer features) 133 | * minimal boilerplate. you should be able to get something on screen with as few lines of code as 134 | possible without writing any long winded setup code 135 | * FFI friendly: only expose functions and enums whenever possible, do not include any preprocessor 136 | conditional code in the interface. C macros should all be optional or minor features 137 | * minimal dependencies. instead of including a library, implement the minimal subset needed to do 138 | what you need to do 139 | * minimal lines of code, but prioritize simplicity over code golfing. ideally the codebase stays 140 | small enough that the entire thing should compile almost instantly 141 | * I want it to have its own tools and ecosystem to give it a bit more personality. for example I'm 142 | currently bootstrapping fonts and ui with a basic sprite editor I wrote and saving sprites to a 143 | custom wbspr format I've made. it should bootstrap itself, because it's fun. 144 | * use short but easy to type names. disregard any potential for name clashes with other libraries 145 | as the focus is on minimal dependencies 146 | * don't be afraid to break api if that means nicer/less code 147 | 148 | # portability 149 | at the moment the platform assumptions in the core logic are 150 | 151 | * signed integers use two's complement and wrap around on overflow 152 | * `int` is 32-bit 153 | * `char` and `unsigned char` are 1 byte 154 | 155 | the included platform layer implementations currently only support linux and similar unix-like OSes 156 | running X11. feel free to port it to your favorite platform 157 | 158 | see cflags.sh for recommended compiler flags 159 | 160 | # why camelCase ? why this weird naming convention? it's ugly. 161 | dunno, I guess I just got tired of typing underscores. I just felt like using this style for once. 162 | it has the advantage of being much smoother to type even with longer names 163 | 164 | # why are you using OpenGL 1 in your renderer implementation? 165 | I like the simplicity of the api and not having to mess with extensions, plus it runs on the most 166 | crippled platforms as a bonus. the renderer is modular anyway, I can always implement a OpenGL3+ or 167 | vulkan backend if performance gets too bad 168 | 169 | # yikes! global variables in the examples? 170 | this project is all about breaking "rules" for simplicity. as your project grows in complexity you 171 | will encapsulate stuff in structs as needed 172 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Utils/SpriteEditor.c: -------------------------------------------------------------------------------- 1 | /* very rough sprite will improve/rewrite as I move features into the engine. this is mainly 2 | * to bootstrap making fonts and simple sprites so I can work on text rendering and ui entirely 3 | * off my own tools */ 4 | 5 | #include "WeebCore.c" 6 | 7 | char* EdHelpString() { 8 | return ( 9 | "Controls:\n" 10 | "\n" 11 | "F1 toggle this help text\n" 12 | "Ctrl+S save\n" 13 | "Ctrl+1 encode as 1bpp and save as .txt file containing a b64 string of the data.\n" 14 | " if a selection is active, the output is cropped to the selection\n" 15 | "Middle Drag pan around\n" 16 | "Wheel Up zoom in\n" 17 | "Wheel Down zoom out\n" 18 | "E switch to pencil\n" 19 | "Left Click (pencil) draw with col 1\n" 20 | "Right Click (pencil) draw with col 2\n" 21 | "C switch to col picker. press again to expand/collapse. expanded mode lets\n" 22 | " you adjust cols with the ui, non-expanded picks from the pixels you click\n" 23 | "Left Click (col picker) set col 1\n" 24 | "Right Click (col picker) set col 2\n" 25 | "S switch to selection mode\n" 26 | "Left Drag (selection) select rectangle\n" 27 | "R crop current selection to closest power-of-two size\n" 28 | "Shift+S remove selection\n" 29 | "1 fill contents of selection with col 1\n" 30 | "2 fill contents of selection with col 2\n" 31 | "D cut the contents of the selection. this will attach them to the selection\n" 32 | "Shift+D undo cut. puts the contents of the cut back where they were\n" 33 | "T (after cutting) paste the contents of the selection at the current location\n" 34 | "N (after selecting) anim preview. this moves the selection right by its\n" 35 | " width until it finds a completely transparent frame\n" 36 | "Shift+Wheel adjust animation speed\n" 37 | "Shift+N cancel anim preview\n" 38 | "HJKL/Arrows move cursor\n" 39 | "Shift (hold) move cursor faster when using HJKL/Arrows\n" 40 | "Q left-click\n" 41 | "W right-click\n" 42 | "A middle-click\n" 43 | "[ wheel up\n" 44 | "] wheel down\n" 45 | ); 46 | } 47 | 48 | #define ED_FLUSH_TIME 1.0f 49 | #define ED_FLUSH_THRESHOLD 4096 50 | #define ED_IMGSIZE 1024 51 | #define ED_CHECKER_SIZE 8 52 | 53 | #define ED_FDRAGGING (1<<1) 54 | #define ED_FPAINTING (1<<2) 55 | #define ED_FDIRTY (1<<3) 56 | #define ED_FEXPAND_COLOR_PICKER (1<<4) 57 | #define ED_FSELECT (1<<5) 58 | #define ED_FALPHA_BLEND (1<<6) 59 | #define ED_FIGNORE_SELECTION (1<<7) 60 | #define ED_FHELP (1<<8) 61 | #define ED_FHOLDING_SHIFT (1<<9) 62 | #define ED_FSHOW_CURSOR (1<<10) 63 | #define ED_FANIM (1<<11) 64 | 65 | enum { 66 | ED_PENCIL, 67 | ED_COLOR_PICKER, 68 | ED_SELECT, 69 | ED_LAST_TOOL 70 | }; 71 | 72 | enum { 73 | ED_SELECT_NONE, 74 | ED_SELECT_MID, 75 | ED_SELECT_LEFT, /* TODO: resize selections */ 76 | ED_SELECT_RIGHT, 77 | ED_SELECT_BOTTOM, 78 | ED_SELECT_TOP 79 | }; 80 | 81 | typedef struct _EDUpd { 82 | int col; 83 | int x, y; 84 | int flags; 85 | } EDUpd; 86 | 87 | Wnd wnd; 88 | 89 | int flags; 90 | float oX, oY; 91 | float cX, cY; 92 | float dX, dY; 93 | int wishX, wishY; 94 | float flushTimer; /* TODO: built in timer events */ 95 | int scale; 96 | int cols[2]; 97 | int colIndex; 98 | Img checkerImg; 99 | Mesh mesh; 100 | Img img; 101 | int* imgData; 102 | EDUpd* updates; 103 | Trans trans; 104 | int tool; 105 | int width, height; 106 | int lastPutX, lastPutY; 107 | 108 | Ft font; 109 | Mesh helpTextMesh, helpBgMesh; 110 | 111 | float selBlinkTimer; 112 | Img selBorderImg; 113 | float selRect[4]; /* raw rectangle between the cursor and the initial drag position */ 114 | float effectiveSelRect[4]; /* normalizsnappetc selection rect */ 115 | int selAnchor; 116 | 117 | float animTimer; 118 | int animSpeed; 119 | float animRect[4]; 120 | float animStartRect[4]; 121 | 122 | Img cutImg; 123 | int* cutData; 124 | float cutPotRect[4]; /* power of two size of the cut img */ 125 | Mesh cutMesh; 126 | Trans cutTrans; 127 | float cutSourceRect[4]; /* the location from where the cut data was taken */ 128 | 129 | /* TODO: pull out col picker as a reusable widget */ 130 | Mesh colPickerMesh; 131 | Trans colPickerTrans; 132 | int colPickerSize; 133 | int grey; 134 | float colX, colY, greyY, alphaY; 135 | char* filePath; 136 | 137 | void EdMapToImg(float* point); 138 | void EdFlushUpds(); 139 | void EdClrSelection(); 140 | void EdUpdMesh(); 141 | void EdClrPanning(); 142 | void EdSelectedRect(float* rect); 143 | void EdClrPaintRateLimiter(); 144 | int EdUpdPaintRateLimiter(); 145 | int EdSampleCheckerboard(float x, float y); 146 | void EdPaintPix(int x, int y, int col, int flags); 147 | 148 | /* ---------------------------------------------------------------------------------------------- */ 149 | 150 | /* TODO: do not hardcode col picker coordinates this is so ugly */ 151 | 152 | #define CPICK_WIDTH 1.0f 153 | #define CPICK_HEIGHT 1.0f 154 | #define CPICK_MARGIN (1/32.0f) 155 | #define CPICK_BAR_WIDTH (1/16.0f) 156 | #define CPICK_BBAR_LEFT (CPICK_WIDTH + CPICK_MARGIN) 157 | #define CPICK_BBAR_RIGHT (CPICK_BBAR_LEFT + CPICK_BAR_WIDTH) 158 | #define CPICK_ABAR_LEFT (CPICK_BBAR_LEFT + CPICK_BAR_WIDTH + CPICK_MARGIN) 159 | #define CPICK_ABAR_RIGHT (CPICK_ABAR_LEFT + CPICK_BAR_WIDTH) 160 | 161 | int EdColPickerSize() { 162 | int winSize = Min(WndWidth(wnd), WndHeight(wnd)); 163 | int size = RoundUpToPowerOfTwo(winSize / 4); 164 | size = Max(size, 256); 165 | size = Min(size, winSize * 0.7); 166 | return size; 167 | } 168 | 169 | Mesh EdCreateColPickerMesh() { 170 | Mesh mesh = MkMesh(); 171 | int gradient[7] = { 0xff0000, 0xffff00, 0x00ff00, 0x00ffff, 0x0000ff, 0xff00ff, 0xff0000 }; 172 | int brightness[3] = { 0xffffff, 0xff000000, 0x000000 }; 173 | int brightnessSelect[2] = { 0xffffff, 0x000000 }; 174 | int alphaSelect[2] = { 0x00ffffff, 0xff000000 }; 175 | brightness[0] = grey; 176 | QuadGradH(mesh, 0, 0, CPICK_WIDTH, CPICK_HEIGHT, 7, gradient); 177 | QuadGradV(mesh, 0, 0, CPICK_WIDTH, CPICK_HEIGHT, 3, brightness); 178 | QuadGradV(mesh, CPICK_BBAR_LEFT, 0, CPICK_BAR_WIDTH, CPICK_HEIGHT, 2, brightnessSelect); 179 | QuadGradV(mesh, CPICK_ABAR_LEFT, 0, CPICK_BAR_WIDTH, CPICK_HEIGHT, 2, alphaSelect); 180 | return mesh; 181 | } 182 | 183 | void EdPutColPicker() { 184 | float colsY = 0; 185 | Mesh mesh = MkMesh(); 186 | if (flags & ED_FEXPAND_COLOR_PICKER) { 187 | colsY = CPICK_HEIGHT + CPICK_MARGIN; 188 | Col(mesh, Mix(grey, 0xff0000, 0.5)); 189 | Quad(mesh, CPICK_BBAR_LEFT, greyY - 1/128.0f, 1/16.0f, 1/64.0f); 190 | Col(mesh, 0x000000); 191 | Quad(mesh, CPICK_ABAR_LEFT, alphaY - 1/128.0f, 1/16.0f, 1/64.0f); 192 | Col(mesh, ~cols[colIndex] & 0xffffff); 193 | Quad(mesh, colX - 1/128.0f, colY - 1/128.0f, 1/64.0f, 1/64.0f); 194 | Col(mesh, 0x000000); 195 | Quad(mesh, colX - 1/256.0f, colY - 1/256.0f, 1/128.0f, 1/128.0f); 196 | PutMesh(colPickerMesh, ToTmpMat(colPickerTrans), 0); 197 | } 198 | Col(mesh, cols[0]); 199 | Quad(mesh, 0, colsY , 1/8.0f, 1/8.0f); 200 | Col(mesh, cols[1]); 201 | Quad(mesh, 0, colsY + 1/8.0f, 1/8.0f, 1/8.0f); 202 | PutMesh(mesh, ToTmpMat(colPickerTrans), 0); 203 | RmMesh(mesh); 204 | } 205 | 206 | void EdSizeColPicker() { 207 | Trans trans = colPickerTrans; 208 | colPickerSize = EdColPickerSize(); 209 | ClrTrans(trans); 210 | SetPos(trans, 10, 10); 211 | SetScale1(trans, colPickerSize); 212 | } 213 | 214 | void EdUpdColPickerMesh() { 215 | RmMesh(colPickerMesh); 216 | colPickerMesh = EdCreateColPickerMesh(); 217 | } 218 | 219 | void EdMixPickedCol() { 220 | float x = colX; 221 | float y = colY; 222 | int gradient[7] = { 0xff0000, 0xffff00, 0x00ff00, 0x00ffff, 0x0000ff, 0xff00ff, 0xff0000 }; 223 | int i = (int)(x * 6); 224 | int baseCol = Mix(gradient[i], gradient[i + 1], (x * 6) - i); 225 | grey = 0x010101 * (int)((1 - greyY) * 0xff); 226 | if (y <= 0.5f) { 227 | cols[colIndex] = Mix(grey, baseCol, y * 2); 228 | } else { 229 | cols[colIndex] = Mix(baseCol, 0x000000, (y - 0.5f) * 2); 230 | } 231 | cols[colIndex] |= (int)(alphaY * 0xff) << 24; 232 | EdUpdColPickerMesh(); 233 | } 234 | 235 | void EdPickCol() { 236 | /* TODO: proper inverse mat so wnd can be moved around n shit */ 237 | /* TODO: use generic rect intersect funcs */ 238 | float x = (cX - 10) / (float)colPickerSize; 239 | float y = (cY - 10) / (float)colPickerSize; 240 | if (flags & ED_FEXPAND_COLOR_PICKER) { 241 | /* clicked on the grey adjust bar */ 242 | if (y >= 0 && y <= CPICK_HEIGHT && x >= CPICK_BBAR_LEFT && x < CPICK_BBAR_RIGHT) { 243 | greyY = y; 244 | } 245 | /* clicked on the alpha adjust bar */ 246 | else if (y >= 0 && y <= CPICK_HEIGHT && x >= CPICK_ABAR_LEFT && x < CPICK_ABAR_RIGHT) { 247 | alphaY = y; 248 | } 249 | /* clicked on the col picker */ 250 | else if (y >= 0 && y <= CPICK_HEIGHT && x >= 0 && x < CPICK_WIDTH) { 251 | colX = x; 252 | colY = y; 253 | } 254 | EdMixPickedCol(); 255 | } else { 256 | float p[2]; 257 | p[0] = cX; 258 | p[1] = cY; 259 | EdMapToImg(p); 260 | if (x >= 0 && x < width && y >= 0 && y < height) { 261 | cols[colIndex] = 262 | imgData[(int)p[1] * width + (int)p[0]]; 263 | } 264 | } 265 | } 266 | 267 | /* ---------------------------------------------------------------------------------------------- */ 268 | 269 | /* NOTE: this updates rect in place to the adjusted power-of-two size */ 270 | int* EdCreatePixsFromRegion(float* rect, int powerOfTwo) { 271 | float extents[4]; 272 | int* newData = 0; 273 | int x, y; 274 | 275 | /* commit any pending changes to the current image */ 276 | if (flags & ED_FDIRTY) { 277 | EdFlushUpds(); 278 | } 279 | 280 | if (powerOfTwo) { 281 | /* snap crop rect to the closest power of two sizes */ 282 | SetRectSize(rect, RoundUpToPowerOfTwo(RectWidth(rect)), 283 | RoundUpToPowerOfTwo(RectHeight(rect))); 284 | } 285 | 286 | /* create cropped pixel data, fill any padding that goes out of the original image with transp */ 287 | SetRect(extents, 0, width, 0, height); 288 | for (y = RectTop(rect); y < RectBot(rect); ++y) { 289 | for (x = RectLeft(rect); x < RectRight(rect); ++x) { 290 | if (PtInRect(extents, x, y)) { 291 | ArrCat(&newData, imgData[y * width + x]); 292 | } else { 293 | ArrCat(&newData, 0xff000000); 294 | } 295 | } 296 | } 297 | 298 | return newData; 299 | } 300 | 301 | void EdFillRect(float* rect, int col) { 302 | int x, y; 303 | if (EdUpdPaintRateLimiter()) { 304 | for (y = RectTop(rect); y < RectBot(rect); ++y) { 305 | for (x = RectLeft(rect); x < RectRight(rect); ++x) { 306 | EdPaintPix(x, y, col, 0); 307 | } 308 | } 309 | } 310 | } 311 | 312 | void EdCrop(float* rect) { 313 | int* newData = EdCreatePixsFromRegion(rect, 0); 314 | 315 | /* refresh everything */ 316 | RmArr(imgData); 317 | imgData = newData; 318 | width = RectWidth(rect); 319 | height = RectHeight(rect); 320 | EdClrSelection(); 321 | EdUpdMesh(); 322 | EdFlushUpds(); 323 | EdClrPanning(); 324 | } 325 | 326 | /* TODO: clean up this mess, maybe use a generic ui function to draw rectangles with borders */ 327 | 328 | /* the way this works is i have a 4x4 img with a black square in the middle and a transparent 329 | * 1px border. by using img coord offsets and messing with the repeat mode, I can reuse the 330 | * img to draw all 4 sides of the dashed selection border */ 331 | 332 | void EdDashBorderH(Mesh mesh, float* rect, float off, int col) { 333 | float uSize = RectWidth(rect) * scale; 334 | float vSize = RectHeight(rect) * scale; 335 | Col(mesh, col); 336 | ImgQuad(mesh, 337 | RectX(rect) , RectY(rect) , 1 + off, 1, 338 | RectWidth(rect), RectHeight(rect), uSize , vSize * 2 339 | ); 340 | ImgQuad(mesh, 341 | RectX(rect) , RectY(rect) , 1 - off, -vSize * 2 + 2, 342 | RectWidth(rect), RectHeight(rect), uSize , vSize * 2 343 | ); 344 | } 345 | 346 | void EdDashBorderV(Mesh mesh, float* rect, float off, int col) { 347 | float uSize = RectWidth(rect) * scale; 348 | float vSize = RectHeight(rect) * scale; 349 | Col(mesh, col); 350 | ImgQuad(mesh, 351 | RectX(rect) , RectY(rect) , 1 , 1 - off, 352 | RectWidth(rect), RectHeight(rect), uSize * 2, vSize 353 | ); 354 | ImgQuad(mesh, 355 | RectX(rect) , RectY(rect) , -uSize * 2 + 2, 1 + off, 356 | RectWidth(rect), RectHeight(rect), uSize * 2 , vSize 357 | ); 358 | } 359 | 360 | void EdUpdYank() { 361 | if (cutMesh) { 362 | Trans trans = cutTrans; 363 | float* rect = effectiveSelRect; 364 | EdSelectedRect(rect); 365 | ClrTrans(trans); 366 | /* TODO: instead of multiplying by scale everywhere, keep a separate mat for scale? */ 367 | SetPos(trans, RectX(rect) * scale, RectY(rect) * scale); 368 | } 369 | } 370 | 371 | void EdClrYank() { 372 | RmMesh(cutMesh); 373 | cutMesh = 0; 374 | RmArr(cutData); 375 | cutData = 0; 376 | } 377 | 378 | void EdClrSelection() { 379 | MemSet(selRect, 0, sizeof(selRect)); 380 | flags &= ~ED_FSELECT; 381 | EdClrYank(); 382 | } 383 | 384 | void EdYank() { 385 | float* potRect = cutPotRect; 386 | float* rect = effectiveSelRect; 387 | if (cutMesh) { 388 | EdClrYank(); 389 | } 390 | MemCpy(cutSourceRect, rect, sizeof(float) * 4); 391 | MemCpy(potRect, rect, sizeof(float) * 4); 392 | cutData = EdCreatePixsFromRegion(potRect, 1); 393 | Pixs(cutImg, RectWidth(potRect), RectHeight(potRect), cutData); 394 | cutMesh = MkMesh(); 395 | Quad(cutMesh, 0, 0, RectWidth(rect), RectHeight(rect)); 396 | EdFillRect(rect, 0xff000000); 397 | EdUpdYank(); 398 | } 399 | 400 | void EdPaste(float* rect, int flags) { 401 | if (cutMesh) { 402 | int x, y; 403 | float* potRect = cutPotRect; 404 | int stride = RectWidth(potRect); 405 | for (y = 0; y < Min(RectHeight(potRect), RectHeight(rect)); ++y) { 406 | for (x = 0; x < Min(RectWidth(potRect), RectWidth(rect)); ++x) { 407 | int dx = RectLeft(rect) + x; 408 | int dy = RectTop(rect) + y; 409 | int col = cutData[y * stride + x]; 410 | EdPaintPix(dx, dy, col, flags); 411 | } 412 | } 413 | } 414 | } 415 | 416 | void EdUnyank() { 417 | /* paste at start location. no alpha blending to ensure we restore that rect to exactly the state 418 | * it had when we yanked */ 419 | EdPaste(cutSourceRect, ED_FIGNORE_SELECTION); 420 | EdClrYank(); 421 | } 422 | 423 | /* ---------------------------------------------------------------------------------------------- */ 424 | 425 | void EdBeginSelect() { 426 | switch (selAnchor) { 427 | case ED_SELECT_NONE: { 428 | float p[2]; 429 | EdClrSelection(); 430 | p[0] = cX; 431 | p[1] = cY; 432 | EdMapToImg(p); 433 | SetRectLeft(selRect, p[0]); 434 | SetRectTop(selRect, p[1]); 435 | SetRectSize(selRect, 0, 0); 436 | flags |= ED_FSELECT; 437 | break; 438 | } 439 | } 440 | } 441 | 442 | void EdSelectedRect(float* rect) { 443 | float imgRect[4]; 444 | float xoff = 1.5f, yoff = 1.5f; 445 | MemCpy(rect, selRect, sizeof(float) * 4); 446 | if (RectWidth(rect) < 0) xoff *= -1; 447 | if (RectHeight(rect) < 0) yoff *= -1; 448 | SetRectSize(rect, (int)(RectWidth(rect) + xoff), (int)(RectHeight(rect) + yoff)); 449 | NormRect(rect); 450 | SetRect(imgRect, 0, width, 0, height); 451 | ClampRect(rect, imgRect); 452 | FloorFlts(4, rect, rect); 453 | } 454 | 455 | void EdPutSelect() { 456 | Img img = selBorderImg; 457 | float* rect = effectiveSelRect; 458 | Mesh meshH = MkMesh(); 459 | Mesh meshV = MkMesh(); 460 | float off = selBlinkTimer * 2; 461 | 462 | /* draw dashed border that alternates between white and black */ 463 | EdDashBorderH(meshH, rect, off + 0, 0xffffff); 464 | EdDashBorderH(meshH, rect, off + 2, 0x000000); 465 | EdDashBorderV(meshV, rect, off + 0, 0xffffff); 466 | EdDashBorderV(meshV, rect, off + 2, 0x000000); 467 | 468 | SetImgWrapU(img, REPEAT); 469 | SetImgWrapV(img, CLAMP_TO_EDGE); 470 | PutMeshRaw(meshH, ToTmpMat(trans), img); 471 | 472 | SetImgWrapU(img, CLAMP_TO_EDGE); 473 | SetImgWrapV(img, REPEAT); 474 | PutMeshRaw(meshV, ToTmpMat(trans), img); 475 | 476 | RmMesh(meshH); 477 | RmMesh(meshV); 478 | } 479 | 480 | /* ---------------------------------------------------------------------------------------------- */ 481 | 482 | void EdBeginPainting() { 483 | switch (tool) { 484 | case ED_SELECT: { EdBeginSelect(); break; } 485 | } 486 | } 487 | 488 | void EdPainting() { 489 | switch (tool) { 490 | case ED_PENCIL: { 491 | int col = cols[colIndex]; 492 | float point[2]; 493 | point[0] = (int)cX; 494 | point[1] = (int)cY; 495 | EdMapToImg(point); 496 | if (EdUpdPaintRateLimiter()) { 497 | /* alpha blend unless we're deleting the pixel with complete transparency */ 498 | EdPaintPix((int)point[0], (int)point[1], col, 499 | ((col & 0xff000000) == 0xff000000) ? 0 : ED_FALPHA_BLEND); 500 | } 501 | break; 502 | } 503 | case ED_COLOR_PICKER: { 504 | EdPickCol(); 505 | break; 506 | } 507 | /* TODO: not sure if select dragging belongs here */ 508 | case ED_SELECT: { 509 | switch (selAnchor) { 510 | case ED_SELECT_NONE: { 511 | float p[2]; 512 | p[0] = cX; 513 | p[1] = cY; 514 | EdMapToImg(p); 515 | SetRectRight(selRect, p[0]); 516 | SetRectBot(selRect, p[1]); 517 | break; 518 | } 519 | case ED_SELECT_MID: { 520 | float* rect = selRect; 521 | SetRectPos(rect, 522 | RectX(rect) + dX / (float)scale, 523 | RectY(rect) + dY / (float)scale); 524 | if (cutMesh) { 525 | EdUpdYank(); 526 | } else { 527 | EdSelectedRect(effectiveSelRect); 528 | } 529 | break; 530 | } 531 | } 532 | } 533 | } 534 | } 535 | 536 | void EdUpdTrans() { 537 | ClrTrans(trans); 538 | SetPos(trans, (int)oX, (int)oY); 539 | SetScale1(trans, scale); 540 | EdUpdYank(); 541 | } 542 | 543 | void EdClrPanning() { 544 | oX = 100; 545 | oY = 100; 546 | EdUpdTrans(); 547 | } 548 | 549 | void EdUpdMesh() { 550 | RmMesh(mesh); 551 | mesh = MkMesh(); 552 | Quad(mesh, 0, 0, width, height); 553 | } 554 | 555 | void EdMapToImg(float* point) { 556 | /* un-mat mouse coordinates so they are relative to the img */ 557 | InvTransPt(ToTmpMatOrtho(trans), point); 558 | point[0] /= scale; 559 | point[1] /= scale; 560 | } 561 | 562 | void EdOnWheel(int direction) { 563 | if ((flags & ED_FHOLDING_SHIFT) && (flags & ED_FANIM)) { 564 | animSpeed = Max(1, Min(120, animSpeed + direction)); 565 | } else { 566 | float p[2]; 567 | float newScale = scale + direction * (scale / 8 + 1); 568 | newScale = Min(32, newScale); 569 | newScale = Max(1, newScale); 570 | p[0] = cX; 571 | p[1] = cY; 572 | EdMapToImg(p); 573 | /* adjust panning so the pixel we're pointing stays under the cursor */ 574 | oX -= (int)((p[0] * newScale) - (p[0] * scale)); 575 | oY -= (int)((p[1] * newScale) - (p[1] * scale)); 576 | scale = newScale; 577 | EdUpdTrans(); 578 | } 579 | } 580 | 581 | void EdSave() { 582 | char* spr; 583 | EdFlushUpds(); 584 | spr = ArgbToSprArr(imgData, width, height); 585 | WrFile(filePath, spr, ArrLen(spr)); 586 | RmArr(spr); 587 | } 588 | 589 | void EdSaveOneBpp() { 590 | char* data; 591 | char* b64Data; 592 | char* path = 0; 593 | if (flags & ED_FSELECT) { 594 | int x, y; 595 | float* rect = effectiveSelRect; 596 | int* crop = 0; 597 | for (y = RectTop(rect); y < RectBot(rect); ++y) { 598 | for (x = RectLeft(rect); x < RectRight(rect); ++x) { 599 | ArrCat(&crop, imgData[y * width + x]); 600 | } 601 | } 602 | data = ArgbArrToOneBpp(crop); 603 | RmArr(crop); 604 | } else { 605 | data = ArgbArrToOneBpp(imgData); 606 | } 607 | b64Data = ArrToB64(data); 608 | RmArr(data); 609 | ArrStrCat(&path, filePath); 610 | ArrStrCat(&path, ".txt"); 611 | WrFile(path, b64Data, StrLen(b64Data)); 612 | RmArr(path); 613 | Free(b64Data); 614 | } 615 | 616 | void EdLoad() { 617 | Spr spr = MkSprFromFile(filePath); 618 | if (spr) { 619 | EdFlushUpds(); 620 | RmArr(imgData); 621 | imgData = SprToArgbArr(spr); 622 | width = SprWidth(spr); 623 | height = SprHeight(spr); 624 | EdUpdMesh(); 625 | RmSpr(spr); 626 | EdFlushUpds(); 627 | } 628 | } 629 | 630 | void EdOnMotion(float dx, float dy) { 631 | dX = dx; 632 | dY = dy; 633 | cX += dx; 634 | cY += dy; 635 | if (flags & ED_FDRAGGING) { 636 | oX += dx; 637 | oY += dy; 638 | EdUpdTrans(); 639 | } 640 | if (flags & ED_FPAINTING) { EdPainting(); } 641 | } 642 | 643 | void EdHandleKeyDown(int key, int state) { 644 | switch (key) { 645 | case A: 646 | case MMID: { flags |= ED_FDRAGGING; break; } 647 | case C: { 648 | if (tool == ED_COLOR_PICKER) { 649 | flags ^= ED_FEXPAND_COLOR_PICKER; 650 | } else { 651 | tool = ED_COLOR_PICKER; 652 | } 653 | break; 654 | } 655 | case E: { tool = ED_PENCIL; break; } 656 | case S: { 657 | if (state & FCTRL) { 658 | EdSave(); 659 | } else if (state & FSHIFT) { 660 | EdPaste(effectiveSelRect, ED_FALPHA_BLEND); 661 | EdClrSelection(); 662 | } else { 663 | tool = ED_SELECT; 664 | } 665 | break; 666 | } 667 | case R: { 668 | if (flags & ED_FSELECT) { 669 | EdCrop(effectiveSelRect); 670 | } 671 | break; 672 | } 673 | case N: { 674 | if (state & FSHIFT) { 675 | flags &= ~ED_FANIM; 676 | } else if (flags & ED_FSELECT) { 677 | MemCpy(animRect, effectiveSelRect, sizeof(animRect)); 678 | MemCpy(animStartRect, effectiveSelRect, sizeof(animStartRect)); 679 | flags |= ED_FANIM; 680 | } 681 | break; 682 | } 683 | case K1: { 684 | if (state & FCTRL) { 685 | EdSaveOneBpp(); 686 | } else { 687 | EdFillRect(effectiveSelRect, cols[0]); 688 | } 689 | break; 690 | } 691 | case K2: { EdFillRect(effectiveSelRect, cols[1]); break; } 692 | case D: { 693 | if (state & FSHIFT) { 694 | EdUnyank(); 695 | } else { 696 | EdYank(); 697 | } 698 | break; 699 | } 700 | case T: { 701 | if (EdUpdPaintRateLimiter()) { 702 | EdPaste(effectiveSelRect, ED_FALPHA_BLEND); 703 | } 704 | break; 705 | } 706 | case RIGHT_BRACKET: 707 | case MWHEELUP: { EdOnWheel(1); break; } 708 | case LEFT_BRACKET: 709 | case MWHEELDOWN: { EdOnWheel(-1); break; } 710 | case Q: 711 | case W: 712 | case MLEFT: 713 | case MRIGHT: { 714 | EdClrPaintRateLimiter(); 715 | flags |= ED_FPAINTING; 716 | colIndex = key == MRIGHT || key == W ? 1 : 0; 717 | selAnchor = key == MRIGHT || key == W ? ED_SELECT_MID : ED_SELECT_NONE; 718 | EdBeginPainting(); 719 | EdPainting(); 720 | EdFlushUpds(); 721 | break; 722 | } 723 | case F1: { 724 | flags ^= ED_FHELP; 725 | break; 726 | } 727 | case H: { wishX = -1; break; } 728 | case L: { wishX = 1; break; } 729 | case K: { wishY = -1; break; } 730 | case J: { wishY = 1; break; } 731 | case LEFT_SHIFT: 732 | case RIGHT_SHIFT: { flags |= ED_FHOLDING_SHIFT; break; } 733 | } 734 | } 735 | 736 | void EdHandleKeyUp(int key) { 737 | switch (key) { 738 | case A: 739 | case MMID: { flags &= ~ED_FDRAGGING; break; } 740 | case Q: 741 | case W: 742 | case MLEFT: 743 | case MRIGHT: { flags &= ~ED_FPAINTING; break; } 744 | case LEFT_SHIFT: 745 | case RIGHT_SHIFT: { flags &= ~ED_FHOLDING_SHIFT; break; } 746 | case H: 747 | case L: { wishX = 0; break; } 748 | case K: 749 | case J: { wishY = 0; break; } 750 | } 751 | } 752 | 753 | void EdUpd() { 754 | if (flags & ED_FDIRTY) { 755 | flushTimer += Delta(wnd); 756 | /* flush updates periodically or if there's too many queued */ 757 | if (flushTimer > ED_FLUSH_TIME || ArrLen(updates) > ED_FLUSH_THRESHOLD) { 758 | EdFlushUpds(); 759 | flushTimer = Max(0, flushTimer - ED_FLUSH_TIME); 760 | } 761 | } else { 762 | flushTimer = 0; 763 | } 764 | 765 | if (wishX || wishY) { 766 | float dx = wishX * (flags & ED_FHOLDING_SHIFT ? 300 : 100) * Delta(); 767 | float dy = wishY * (flags & ED_FHOLDING_SHIFT ? 300 : 100) * Delta(); 768 | flags |= ED_FSHOW_CURSOR; 769 | EdOnMotion(dx, dy); 770 | } 771 | 772 | selBlinkTimer += Delta(); 773 | selBlinkTimer = FltMod(selBlinkTimer, 2); 774 | 775 | EdSelectedRect(effectiveSelRect); 776 | 777 | if (flags & ED_FANIM) { 778 | animTimer += Delta(); 779 | while (animTimer >= 1.0f / animSpeed) { 780 | float extents[4]; 781 | float* r = animRect; 782 | int x, y, empty = 1; 783 | animTimer -= 1.0f / animSpeed; 784 | SetRectPos(r, RectX(r) + RectWidth(r), RectY(r)); 785 | SetRect(extents, 0, width, 0, height); 786 | if (RectInRect(r, extents)) { 787 | for (y = (int)RectTop(r); y < (int)RectBot(r); ++y) { 788 | for (x = (int)RectLeft(r); x < (int)RectRight(r); ++x) { 789 | if ((imgData[y * width + x] & 0xff000000) != 0xff000000) { 790 | empty = 0; 791 | } 792 | } 793 | } 794 | } 795 | if (empty) { 796 | MemCpy(r, animStartRect, sizeof(animRect)); 797 | } 798 | } 799 | } 800 | } 801 | 802 | void EdPaintPix(int x, int y, int col, int paintFlags) { 803 | float rect[4]; 804 | int fcol = col; 805 | SetRect(rect, 0, width, 0, height); 806 | if ((flags & ED_FSELECT) && !(flags & ED_FIGNORE_SELECTION)) { 807 | ClampRect(rect, effectiveSelRect); 808 | } 809 | if (PtInRect(rect, x, y)) { 810 | int* px = &imgData[y * width + x]; 811 | if (paintFlags & ED_FALPHA_BLEND) { 812 | /* normal alpha blending with whatever is currently here */ 813 | fcol = AlphaBlend(col, *px); 814 | } 815 | if (*px != fcol) { 816 | EDUpd* u; 817 | *px = fcol; 818 | u = ArrAlloc(&updates, 1); 819 | u->x = x; 820 | u->y = y; 821 | u->col = col; 822 | u->flags = flags; 823 | flags |= ED_FDIRTY; 824 | } 825 | } 826 | } 827 | 828 | void EdFlushUpds() { 829 | Pixs(img, width, height, imgData); 830 | SetArrLen(updates, 0); 831 | flags &= ~ED_FDIRTY; 832 | } 833 | 834 | void EdClrPaintRateLimiter() { 835 | lastPutX = -1; 836 | } 837 | 838 | int EdUpdPaintRateLimiter() { 839 | float p[2]; 840 | int x, y; 841 | p[0] = cX; 842 | p[1] = cY; 843 | EdMapToImg(p); 844 | x = (int)p[0]; 845 | y = (int)p[1]; 846 | if (x != lastPutX || y != lastPutY) { 847 | lastPutX = x; 848 | lastPutY = y; 849 | return 1; 850 | } 851 | return 0; 852 | } 853 | 854 | void EdPutUpds() { 855 | int i; 856 | Mesh mesh = MkMesh(); 857 | for (i = 0; i < ArrLen(updates); ++i) { 858 | EDUpd* u = &updates[i]; 859 | if (u->flags & ED_FALPHA_BLEND) { 860 | /* normal alpha blending with whatever is currently here */ 861 | Col(mesh, u->col); 862 | } else { 863 | /* no alpha blending, just overwrite the col. 864 | * because we can have pending transparent pixels we need to do some big brain custom alpha 865 | * blending with the checkerboard col at this location */ 866 | int baseCol = EdSampleCheckerboard(u->x, u->y); 867 | float rgba[4]; 868 | ColToFlts(u->col, rgba); 869 | Col(mesh, Mix(u->col & 0xffffff, baseCol, rgba[3])); 870 | } 871 | Quad(mesh, u->x, u->y, 1, 1); 872 | } 873 | PutMesh(mesh, ToTmpMat(trans), 0); 874 | RmMesh(mesh); 875 | } 876 | 877 | /* cover rest of the area in grey to distinguish it from a transparent image */ 878 | void EdPutBorders() { 879 | /* TODO: only regenerate mesh when the mat changes */ 880 | int winWidth = WndWidth(wnd); 881 | int winHeight = WndHeight(wnd); 882 | int right, bottom; 883 | int scalEdWidth = width * scale; 884 | int scalEdHeight = height * scale; 885 | Mesh mesh = MkMesh(); 886 | Col(mesh, 0x111111); 887 | if (oX > 0) { 888 | Quad(mesh, 0, 0, oX, winHeight); 889 | } 890 | right = oX + scalEdWidth; 891 | if (right < winWidth) { 892 | Quad(mesh, right, 0, winWidth - right, winHeight); 893 | } 894 | bottom = oY + scalEdHeight; 895 | if (oY > 0) { 896 | Quad(mesh, 0, 0, winWidth, oY); 897 | } 898 | if (bottom < winHeight) { 899 | Quad(mesh, oX, bottom, scalEdWidth, winHeight - bottom); 900 | } 901 | PutMesh(mesh, 0, 0); 902 | RmMesh(mesh); 903 | } 904 | 905 | void EdPutCursor() { 906 | /* TODO: do not use a tmp mesh for the cursor */ 907 | int x = (int)cX; 908 | int y = (int)cY; 909 | Mesh mesh = MkMesh(); 910 | Col(mesh, 0xffffff); 911 | Tri(mesh, x, y, x, y + 20, x + 10, y + 17); 912 | Col(mesh, cols[colIndex]); 913 | Tri(mesh, x+1, y+3, x+1, y + 18, x + 8, y + 16); 914 | PutMesh(mesh, 0, 0); 915 | RmMesh(mesh); 916 | } 917 | 918 | void EdPut() { 919 | PutMeshRaw(mesh, ToTmpMat(trans), checkerImg); 920 | PutMeshRaw(mesh, ToTmpMat(trans), img); 921 | EdPutBorders(); 922 | EdPutUpds(); 923 | if (cutMesh) { 924 | Mat copy = ToMat(cutTrans); 925 | PutMeshRaw(cutMesh, MulMat(copy, ToTmpMat(trans)), cutImg); 926 | RmMat(copy); 927 | } 928 | if (flags & ED_FSELECT) { 929 | EdPutSelect(); 930 | } 931 | if (tool == ED_COLOR_PICKER) { 932 | EdPutColPicker(); 933 | } 934 | if (flags & ED_FSHOW_CURSOR) { 935 | EdPutCursor(); 936 | } 937 | if (flags & ED_FHELP) { 938 | PutMesh(helpBgMesh, 0, 0); 939 | PutMesh(helpTextMesh, 0, FtImg(font)); 940 | } 941 | if (flags & ED_FANIM) { 942 | Mesh mesh; 943 | char* str = 0; 944 | float* r = animRect; 945 | int x = (int)(WndWidth(wnd) - RectWidth(r) * scale); 946 | int w = RectWidth(r) * scale; 947 | int h = RectHeight(r) * scale; 948 | 949 | mesh = MkMesh(); 950 | Col(mesh, 0x7f000000); 951 | Quad(mesh, x - 10, h + 10, w + 5, 20); 952 | PutMesh(mesh, 0, 0); 953 | RmMesh(mesh); 954 | 955 | mesh = MkMesh(); 956 | ImgQuad(mesh, x, 5, RectLeft(r), RectTop(r), w, h, RectWidth(r), RectHeight(r)); 957 | PutMeshRaw(mesh, 0, img); 958 | RmMesh(mesh); 959 | 960 | ArrStrCat(&str, "anim spd: "); 961 | ArrStrCatI32(&str, animSpeed, 10); 962 | ArrStrCat(&str, " fps"); 963 | ArrCat(&str, 0); 964 | PutFt(font, 0xbebebe, x, (int)(RectHeight(r) * scale) + 15, str); 965 | RmArr(str); 966 | } 967 | } 968 | 969 | int EdSampleCheckerboard(float x, float y) { 970 | int data[4] = { 0xaaaaaa, 0x666666, 0x666666, 0xaaaaaa }; 971 | x /= ED_CHECKER_SIZE; 972 | y /= ED_CHECKER_SIZE; 973 | return data[((int)y % 2) * 2 + ((int)x % 2)]; 974 | } 975 | 976 | Img EdCreateCheckerImg() { 977 | int x, y; 978 | int* data = 0; 979 | Img img = MkImg(); 980 | for (y = 0; y < ED_CHECKER_SIZE * 2; ++y) { 981 | for (x = 0; x < ED_CHECKER_SIZE * 2; ++x) { 982 | ArrCat(&data, EdSampleCheckerboard(x, y)); 983 | } 984 | } 985 | Pixs(img, ED_CHECKER_SIZE * 2, ED_CHECKER_SIZE * 2, data); 986 | RmArr(data); 987 | return img; 988 | } 989 | 990 | Img EdCreateSelBorderImg() { 991 | Img img = MkImg(); 992 | int data[4*4] = { 993 | 0xff000000, 0xff000000, 0xff000000, 0xff000000, 994 | 0xff000000, 0x00ffffff, 0x00ffffff, 0xff000000, 995 | 0xff000000, 0x00ffffff, 0x00ffffff, 0xff000000, 996 | 0xff000000, 0xff000000, 0xff000000, 0xff000000 997 | }; 998 | return Pixs(img, 4, 4, data); 999 | } 1000 | 1001 | int* EdCreatePixs(int fillCol, int width, int height) { 1002 | int i; 1003 | int* data = 0; 1004 | for (i = 0; i < width * height; ++i) { 1005 | ArrCat(&data, fillCol); 1006 | } 1007 | return data; 1008 | } 1009 | 1010 | Mat EdCreateBgTrans() { 1011 | Mat mat = MkMat(); 1012 | return Scale1(mat, ED_CHECKER_SIZE); 1013 | } 1014 | 1015 | void Init() { 1016 | wnd = AppWnd(); 1017 | filePath = Argc() > 1 ? Argv(1) : "out.wbspr"; 1018 | scale = 4; 1019 | cX = WndWidth(wnd) / 2; 1020 | cY = WndHeight(wnd) / 2; 1021 | checkerImg = EdCreateCheckerImg(); 1022 | img = MkImg(); 1023 | cutImg = MkImg(); 1024 | width = ED_IMGSIZE; 1025 | height = ED_IMGSIZE; 1026 | imgData = EdCreatePixs(0xff000000, width, height); 1027 | trans = MkTrans(); 1028 | colPickerTrans = MkTrans(); 1029 | cutTrans = MkTrans(); 1030 | tool = ED_PENCIL; 1031 | grey = 0xffffff; 1032 | cols[1] = 0xff000000; 1033 | selBorderImg = EdCreateSelBorderImg(); 1034 | animSpeed = 5; 1035 | 1036 | flags |= ED_FHELP; 1037 | font = DefFt(); 1038 | /* TODO: scale with screen size */ 1039 | /* TODO: actual proper automatic ui layoutting */ 1040 | helpTextMesh = MkMesh(); 1041 | Col(helpTextMesh, 0xbebebe); 1042 | FtMesh(helpTextMesh, font, 15, 15, EdHelpString()); 1043 | helpBgMesh = MkMesh(); 1044 | Col(helpBgMesh, 0x33000000); 1045 | Quad(helpBgMesh, 5, 5, 610, 405); 1046 | 1047 | EdClrSelection(); 1048 | EdUpdColPickerMesh(); 1049 | EdUpdMesh(); 1050 | EdFlushUpds(); 1051 | EdSizeColPicker(); 1052 | EdClrPanning(); 1053 | EdLoad(); 1054 | } 1055 | 1056 | void Quit() { 1057 | EdClrYank(); 1058 | EdClrSelection(); 1059 | RmMesh(cutMesh); 1060 | RmMesh(mesh); 1061 | RmMesh(colPickerMesh); 1062 | RmMesh(helpTextMesh); 1063 | RmMesh(helpBgMesh); 1064 | RmArr(imgData); 1065 | RmArr(updates); 1066 | RmImg(img); 1067 | RmImg(cutImg); 1068 | RmImg(checkerImg); 1069 | RmImg(selBorderImg); 1070 | RmTrans(trans); 1071 | RmTrans(colPickerTrans); 1072 | RmTrans(cutTrans); 1073 | RmFt(font); 1074 | } 1075 | 1076 | void Size() { 1077 | EdSizeColPicker(); 1078 | } 1079 | 1080 | void KeyDown() { 1081 | EdHandleKeyDown(Key(wnd), KeyState(wnd)); 1082 | } 1083 | 1084 | void KeyUp() { 1085 | EdHandleKeyUp(Key(wnd)); 1086 | } 1087 | 1088 | void Motion() { 1089 | cX = MouseX(wnd) - MouseDX(wnd); 1090 | cY = MouseY(wnd) - MouseDY(wnd); 1091 | flags &= ~ED_FSHOW_CURSOR; 1092 | EdOnMotion(MouseDX(wnd), MouseDY(wnd)); 1093 | } 1094 | 1095 | void Frame() { 1096 | EdUpd(); 1097 | EdPut(); 1098 | } 1099 | 1100 | void AppInit() { 1101 | SetAppClass("WeebCoreSpriteEd"); 1102 | SetAppName("WeebCore Sprite Ed"); 1103 | On(INIT, Init); 1104 | On(QUIT, Quit); 1105 | On(SIZE, Size); 1106 | On(KEYDOWN, KeyDown); 1107 | On(KEYUP, KeyUp); 1108 | On(MOTION, Motion); 1109 | On(FRAME, Frame); 1110 | } 1111 | 1112 | #define WEEBCORE_IMPLEMENTATION 1113 | #include "WeebCore.c" 1114 | #include "Platform/Platform.h" 1115 | -------------------------------------------------------------------------------- /WAIVER: -------------------------------------------------------------------------------- 1 | # Copyright waiver for 2 | 3 | I dedicate any and all copyright interest in this software to the 4 | public domain. I make this dedication for the benefit of the public at 5 | large and to the detriment of my heirs and successors. I intend this 6 | dedication to be an overt act of relinquishment in perpetuity of all 7 | present and future rights to this software under copyright law. 8 | 9 | To the best of my knowledge and belief, my contributions are either 10 | originally authored by me or are derived from prior works which I have 11 | verified are also in the public domain and are not subject to claims 12 | of copyright by other parties. 13 | 14 | To the best of my knowledge and belief, no individual, business, 15 | organization, government, or other entity has any copyright interest 16 | in my contributions, and I affirm that I will not make contributions 17 | that are otherwise encumbered. 18 | -------------------------------------------------------------------------------- /WeebCore.c: -------------------------------------------------------------------------------- 1 | #ifndef WEEBCORE_HEADER 2 | #define WEEBCORE_HEADER 3 | 4 | /* opaque handles */ 5 | typedef int ImgPtr; 6 | typedef struct _Mesh* Mesh; 7 | typedef struct _Img* Img; 8 | typedef struct _Trans* Trans; 9 | typedef struct _Packer* Packer; 10 | typedef struct _Wnd* Wnd; 11 | typedef struct _Mat* Mat; 12 | typedef struct _Arena* Arena; 13 | typedef struct _Map* Map; 14 | typedef struct _Hash* Hash; 15 | 16 | /* ---------------------------------------------------------------------------------------------- */ 17 | /* APP INTERFACE */ 18 | /* */ 19 | /* the app interface is designed to minimize boilerplate when working with weebcore in C. */ 20 | /* all you are required to do is define a void AppInit() function where you can bind msg handlers */ 21 | /* as you like. */ 22 | /* */ 23 | /* NOTE: when AppInit is called, nothing is initialized yet. you should do all your init from */ 24 | /* On(INIT, MyInitFunc) and only set app parameters in AppInit */ 25 | /* ---------------------------------------------------------------------------------------------- */ 26 | 27 | #ifndef WEEBCORE_LIB 28 | void AppInit(); 29 | #endif 30 | 31 | /* these funcs can be called in AppInit to set app parameters */ 32 | 33 | void SetAppName(char* name); 34 | void SetAppClass(char* class); 35 | 36 | /* pageSize is the size of a texture page for the texture allocator. there is a tradeoff between 37 | * runtime performance and load times overhead. a big pageSize will result in less texture switches 38 | * during rendering but it will slow down flushing img allocations significantly as it will have 39 | * to update a bigger img on the gpu */ 40 | void SetAppPageSize(int pageSize); 41 | 42 | /* ---------------------------------------------------------------------------------------------- */ 43 | 44 | typedef void(* AppHandler)(); 45 | 46 | /* register handler to be called whenever msg happens. multiple handlers can be bound to same msg. 47 | * handlers are called in the order they are added. 48 | * scroll down to "ENUMS AND CONSTANTS" for a list of msgs, or check out the Examples */ 49 | void On(int msg, AppHandler handler); 50 | void RmHandler(int msg, AppHandler handler); 51 | 52 | /* time elapsed since the last frame, in seconds */ 53 | float Delta(); 54 | 55 | ImgPtr ImgFromSprFile(char* path); 56 | void PutMesh(Mesh mesh, Mat mat, ImgPtr ptr); 57 | 58 | Wnd AppWnd(); 59 | ImgPtr ImgAlloc(int width, int height); 60 | void ImgFree(ImgPtr img); 61 | 62 | /* discard EVERYTHING allocated on the img allocator. reinitialize built in textures such as the 63 | * default ft. this invalidates all ImgPtr's. 64 | * this is more optimal than freeing each ImgPtr individually if you are going to re-allocate new 65 | * stuff because freeing always leads to fragmentation over time */ 66 | void ClrImgs(); 67 | 68 | /* copy raw pixels dx, dy in the img. anything outside the img size is cropped out. 69 | * note that this replaces pixels. it doesn't do any kind of alpha blending */ 70 | void ImgCpyEx(ImgPtr ptr, int* pixs, int width, int height, int dx, int dy); 71 | 72 | /* calls ImgCpyEx with dx, dy = 0, 0 */ 73 | void ImgCpy(ImgPtr ptr, int* pixs, int width, int height); 74 | 75 | /* when you allocate img's they are not immediately sent to the gpu. they will temporarily be blank 76 | * until this is called. the engine calls this automatically every once in a while, but you can 77 | * explicitly call it to force refresh img's. 78 | * this is also automatically called after the INIT msg when using the App interface */ 79 | void FlushImgs(); 80 | 81 | int Argc(); 82 | char* Argv(int i); 83 | 84 | /* ---------------------------------------------------------------------------------------------- */ 85 | /* BUILT-IN MATH FUNCTIONS */ 86 | /* ---------------------------------------------------------------------------------------------- */ 87 | 88 | float Lerp(float a, float b, float amount); 89 | void LerpFlts(int n, float* a, float* b, float* result, float amount); 90 | void MulFltsScalar(int n, float* floats, float* result, float scalar); 91 | void FloorFlts(int n, float* floats, float* result); 92 | void ClampFlts(int n, float* floats, float* result, float min, float max); 93 | void AddFlts(int n, float* a, float* b, float* result); 94 | 95 | /* these funcs operate on a rectangle represented as an array of 4 floats (left, top, right, bot) */ 96 | void CpyRect(float* dst, float* src); 97 | void SetRect(float* rect, float left, float right, float top, float bot); 98 | void SetRectPos(float* rect, float x, float y); 99 | void SetRectSize(float* rect, float width, float height); 100 | void SetRectLeft(float* rect, float left); 101 | void SetRectRight(float* rect, float right); 102 | void SetRectTop(float* rect, float top); 103 | void SetRectBot(float* rect, float bot); 104 | void NormRect(float* rect); /* swaps values around so width/height aren't negative */ 105 | void ClampRect(float* rect, float* other); /* clamp rect to be inside of other rect */ 106 | float RectWidth(float* rect); 107 | float RectHeight(float* rect); 108 | float RectX(float* rect); 109 | float RectY(float* rect); 110 | float RectLeft(float* rect); 111 | float RectRight(float* rect); 112 | float RectTop(float* rect); 113 | float RectBot(float* rect); 114 | int PtInRect(float* rect, float x, float y); /* check if xy lies in rect. must be normalized. */ 115 | int RectSect(float* a, float* b); 116 | 117 | /* check that needle is entirely inside of haystack */ 118 | int RectInRect(float* needle, float* haystack); 119 | 120 | /* check that needle's area can entirely fit inside of haystack (ignores position) */ 121 | int RectInRectArea(float* needle, float* haystack); 122 | 123 | /* ---------------------------------------------------------------------------------------------- */ 124 | 125 | /* OpenGL-like post-multiplied mat. mat memory layout is row major */ 126 | 127 | Mat MkMat(); 128 | void RmMat(Mat mat); 129 | Mat DupMat(Mat source); 130 | 131 | /* these return mat for convienience. it's not actually a copy */ 132 | Mat SetIdentity(Mat mat); 133 | Mat SetMat(Mat mat, float* matIn); 134 | Mat GetMat(Mat mat, float* matOut); 135 | Mat Scale(Mat mat, float x, float y); 136 | Mat Scale1(Mat mat, float scale); 137 | Mat Pos(Mat mat, float x, float y); 138 | Mat Rot(Mat mat, float deg); 139 | Mat MulMat(Mat mat, Mat other); 140 | Mat MulMatFlt(Mat mat, float* matIn); 141 | 142 | /* multiply a b and store the result in a new Mat */ 143 | Mat MkMulMat(Mat matA, Mat matB); 144 | Mat MkMulMatFlt(Mat matA, float* matB); 145 | 146 | /* trans 2D point in place */ 147 | void TransPt(Mat mat, float* point); 148 | 149 | /* trans 2D point in place by inverse of mat. note that this only works if the mat is orthogonal */ 150 | void InvTransPt(Mat mat, float* point); 151 | 152 | /* note: this is a DIRECT pointer to the matrix data so if you modify it it will affect it */ 153 | float* MatFlts(Mat mat); 154 | 155 | /* ---------------------------------------------------------------------------------------------- */ 156 | /* WBSPR FORMAT */ 157 | /* this is meant as simple RLE compression for 2D sprites with a limited color palette */ 158 | /* ---------------------------------------------------------------------------------------------- */ 159 | 160 | typedef struct _Spr* Spr; 161 | 162 | Spr MkSpr(char* data, int length); 163 | Spr MkSprFromArr(char* data); 164 | Spr MkSprFromFile(char* filePath); 165 | void RmSpr(Spr spr); 166 | int SprWidth(Spr spr); 167 | int SprHeight(Spr spr); 168 | void SprToArgb(Spr spr, int* argb); 169 | int* SprToArgbArr(Spr spr); 170 | 171 | /* returns a resizable array that must be freed with RmArr */ 172 | char* ArgbToSprArr(int* argb, int width, int height); 173 | 174 | /* Format Details: 175 | * 176 | * char[4] "SP" 177 | * varint formatVersion 178 | * varint width, height 179 | * varint paletteLength 180 | * int32 palette[paletteLength] 181 | * Blob { 182 | * varint type (repeat/data) 183 | * varint size 184 | * varint paletteIndices[size] <- 1 varint for repeat 185 | * } 186 | * ... 187 | * 188 | * varint's are protobuf style encoded integers. see EncVarI32 */ 189 | 190 | /* ---------------------------------------------------------------------------------------------- */ 191 | /* RENDER UTILS */ 192 | /* ---------------------------------------------------------------------------------------------- */ 193 | 194 | 195 | /* calls PutMeshRawEx with zero u/v offset */ 196 | void PutMeshRaw(Mesh mesh, Mat mat, Img img); 197 | 198 | /* create a mat from scale, position, origin, rot applied in a fixed order */ 199 | 200 | Trans MkTrans(); 201 | void RmTrans(Trans trans); 202 | void ClrTrans(Trans trans); 203 | 204 | /* these return trans for convenience. it's not actually a copy */ 205 | Trans SetScale(Trans trans, float x, float y); 206 | Trans SetScale1(Trans trans, float scale); 207 | Trans SetPos(Trans trans, float x, float y); 208 | Trans SetOrig(Trans trans, float x, float y); 209 | Trans SetRot(Trans trans, float deg); 210 | 211 | Mat ToMat(Trans trans); 212 | 213 | /* the ortho version of To* functions produce a mat that is orthogonal (doesn't apply scale 214 | * among other things) this is useful for trivial inversion */ 215 | Mat ToMatOrtho(Trans trans); 216 | 217 | /* the mat returned by this does not need to be destroyed. it will be automatically destroyed 218 | * when RmTrans is called. 219 | * 220 | * note that subsequent calls to this function will invalidate the previously generated mat 221 | * 222 | * Ortho version does not share the same mat as the non-Ortho so it doesnt invalidate it 223 | * 224 | * it's recommended to not hold onto these Mat pointers either way. if the Trans doesn't change, 225 | * the Mat will not be recalculated, so it's cheap to call these everywhere */ 226 | Mat ToTmpMat(Trans trans); 227 | Mat ToTmpMatOrtho(Trans trans); 228 | 229 | /* add a rectangle to mesh */ 230 | void Quad(Mesh mesh, float x, float y, float width, float height); 231 | 232 | /* add a textured rectangle to mesh. note that the u/v coordinates are in pixels, since we usually 233 | * want to work pixel-perfect in 2d */ 234 | void ImgQuad(Mesh mesh, 235 | float x, float y, float u, float v, 236 | float width, float height, float uWidth, float vHeight 237 | ); 238 | 239 | /* add a triangle to mesh */ 240 | void Tri(Mesh mesh, float x1, float y1, float x2, float y2, float x3, float y3); 241 | void ImgTri(Mesh mesh, 242 | float x1, float y1, float u1, float v1, 243 | float x2, float y2, float u2, float v2, 244 | float x3, float y3, float u3, float v3 245 | ); 246 | 247 | /* draw gradients using vertex color interpolation */ 248 | 249 | void QuadGradH(Mesh mesh, float x, float y, float width, float height, int n, int* colors); 250 | void QuadGradV(Mesh mesh, float x, float y, float width, float height, int n, int* colors); 251 | 252 | /* convert Argb color to { r, g, b, a } (0.0-1.0) */ 253 | void ColToFlts(int color, float* floats); 254 | 255 | /* convert { r, g, b, a } (0.0-1.0) to Argb color */ 256 | int FltsToCol(float* f); 257 | 258 | /* linearly interpolate between colors a and b */ 259 | int Mix(int a, int b, float amount); 260 | 261 | /* multiply color's rgb values by scalar (does not touch alpha) */ 262 | int MulScalar(int color, float scalar); 263 | 264 | /* add colors together (clamps values to avoid overflow) */ 265 | int Add(int a, int b); 266 | 267 | /* alpha blend between color src and dst. alpha is not premultiplied */ 268 | int AlphaBlend(int src, int dst); 269 | 270 | /* same as alpha blend but blends in place into dst */ 271 | void AlphaBlendp(int* dst, int src); 272 | 273 | /* convert rgba pixs to 1bpp. all non-transparent colors become a 1 and transparency becomes 0. 274 | * the data is tightly packed (8 pixs per byte). 275 | * the returned data is an array and must be freed with ArrFree */ 276 | char* ArgbToOneBpp(int* pixs, int numPixs); 277 | char* ArgbArrToOneBpp(int* pixs); 278 | int* OneBppToArgb(char* data, int numBytes); 279 | int* OneBppArrToArgb(char* data); 280 | 281 | /* ---------------------------------------------------------------------------------------------- */ 282 | /* BITMAP FONTS */ 283 | /* ---------------------------------------------------------------------------------------------- */ 284 | 285 | typedef struct _Ft* Ft; 286 | 287 | /* simplest form of bitmap ft. 288 | * 289 | * this assumes pixs is a tightly packed grid of 32x3 characters that cover ascii 0x20-0x7f 290 | * (from space to the end of the ascii table). 291 | * 292 | * while this is the fastest way for the renderer to look up characters, it should only be used in 293 | * cases where you have a hardcoded ft that's never gonna change. it was created to bootstrap 294 | * the built-in ft */ 295 | Ft MkFtFromSimpleGrid(int* pixs, int width, int height, int charWidth, int charHeight); 296 | Ft MkFtFromSimpleGridFile(char* filePath, int charWidth, int charHeight); 297 | 298 | /* basic built in bitmap ft */ 299 | Ft DefFt(); 300 | 301 | void RmFt(Ft ft); 302 | ImgPtr FtImg(Ft ft); 303 | 304 | /* generate vertices and uv's for drawing string. must be rendered with the ft img from 305 | * FtImg or a img that has the same exact layout */ 306 | void FtMesh(Mesh mesh, Ft ft, int x, int y, char* string); 307 | 308 | void PutFt(Ft ft, int col, int x, int y, char* string); 309 | 310 | /* ---------------------------------------------------------------------------------------------- */ 311 | /* RESIZABLE ARRAYS */ 312 | /* */ 313 | /* make sure your initial pointer is initialized to NULL which counts as an empty array. */ 314 | /* these are special fat pointers and must be freed with RmArr */ 315 | /* ---------------------------------------------------------------------------------------------- */ 316 | 317 | void RmArr(void* array); 318 | int ArrLen(void* array); 319 | int ArrCap(void* array); 320 | void SetArrLen(void* array, int len); 321 | 322 | /* compare the raw memory of a and b using MemCmp */ 323 | int ArrMemCmp(void* a, void* b); 324 | 325 | /* join array of strs into one str where every element is separated by separator */ 326 | char* ArrStrJoin(char** array, char* separator); 327 | 328 | #define ArrDup(arr) ArrDupEx(arr, sizeof(arr[0])) 329 | 330 | /* shorthand macro to append a single element to the array */ 331 | #define ArrCat(pArr, x) { \ 332 | ArrReserve((pArr), 1); \ 333 | (*(pArr))[ArrLen(*(pArr))] = (x); \ 334 | SetArrLen(*(pArr), ArrLen(*(pArr)) + 1); \ 335 | } 336 | 337 | /* reserve memory for at least numElements extra elements. 338 | * returns a pointer to the end of the array */ 339 | #define ArrReserve(pArr, numElements) \ 340 | ArrReserveEx((void**)(pArr), sizeof((*pArr)[0]), numElements) 341 | 342 | /* reserve memory for at least numElements extra elements and set the 343 | * array length to current length + numElements. 344 | * returns a pointer to the beginning of the new elements */ 345 | #define ArrAlloc(pArr, numElements) \ 346 | ArrAllocEx((void**)(pArr), sizeof((*pArr)[0]), numElements) 347 | 348 | void ArrStrCat(char** pArr, char* str); 349 | 350 | void* ArrReserveEx(void** pArr, int elementSize, int numElements); 351 | void* ArrAllocEx(void** pArr, int elementSize, int numElements); 352 | void* ArrDupEx(void* array, int elementSize); 353 | 354 | /* ---------------------------------------------------------------------------------------------- */ 355 | /* ARENA */ 356 | /* */ 357 | /* simple allocator that pre-allocs chunks of memory for a more contiguous allocation but isn't */ 358 | /* as expensive as resizing an Arr because it doesn't realloc */ 359 | /* ---------------------------------------------------------------------------------------------- */ 360 | 361 | Arena MkArena(); 362 | Arena MkArenaEx(int chunkSize); 363 | void RmArena(Arena arena); 364 | 365 | /* if n is bigger than the chunk size, a new chunk at least as big as n will be allocated */ 366 | void* ArenaAlloc(Arena arena, int n); 367 | void* ArenaMemDup(Arena arena, void* p, int n); 368 | 369 | /* ---------------------------------------------------------------------------------------------- */ 370 | /* MAP */ 371 | /* */ 372 | /* sparse array that maps integers to values */ 373 | /* ---------------------------------------------------------------------------------------------- */ 374 | 375 | Map MkMap(); 376 | void RmMap(Map map); 377 | void MapSet(Map map, int key, void* val); 378 | void* MapGet(Map map, int key); 379 | 380 | /* return the number of collisions (keys that hashed to the same value). this is mostly used for 381 | * debugging and checking whether the map is operating as intended */ 382 | int MapColls(Map map); 383 | 384 | /* these functions can be used to iterate keys */ 385 | int MapNumKeys(Map map); 386 | int MapKey(Map map, int i); 387 | 388 | /* ---------------------------------------------------------------------------------------------- */ 389 | /* HASH */ 390 | /* */ 391 | /* sparse array that maps strings to values */ 392 | /* ---------------------------------------------------------------------------------------------- */ 393 | 394 | Hash MkHash(); 395 | void RmHash(Hash hash); 396 | /* note that Hash manages its own internal copies of the key, there's no need to keep it around */ 397 | void HashSet(Hash hash, char* key, void* val); 398 | void HashSetb(Hash hash, void* keyData, int keySize, void* val); 399 | void* HashGet(Hash hash, char* key); 400 | void* HashGetb(Hash hash, char* keyData, int keySize); 401 | int HashHas(Hash hash, char* key); 402 | int HashHasb(Hash hash, char* keyData, int keySize); 403 | 404 | int HashColls(Hash map); /* see MapColls */ 405 | 406 | /* these functions can be used to iterate keys */ 407 | int HashNumKeys(Hash hash); 408 | void* HashKey(Hash hash, int i); 409 | int HashKeyLen(Hash hash, int i); 410 | 411 | /* ---------------------------------------------------------------------------------------------- */ 412 | /* RECT PACKER */ 413 | /* */ 414 | /* attempts to efficiently pack rectangles inside a bigger rectangle. internally used to */ 415 | /* automatically stitch together all the imags and not have the renderer swap img every time. */ 416 | /* ---------------------------------------------------------------------------------------------- */ 417 | 418 | Packer MkPacker(int width, int height); 419 | void RmPacker(Packer pak); 420 | 421 | /* rect is an array of 4 floats (left, right, top, bottom) like in the Rect funcs 422 | * 423 | * NOTE: this adjusts rect in place and just returns it for convenience. make a copy if you don't 424 | * want to lose rect's original values. 425 | * 426 | * returns NULL if rect doesn't fit */ 427 | float* Pack(Packer pak, float* rect); 428 | 429 | /* mark rect as a free area. this can be used to remove already packed rects */ 430 | void PackFree(Packer pak, float* rect); 431 | 432 | /* ---------------------------------------------------------------------------------------------- */ 433 | /* MISC UTILS AND MACROS */ 434 | /* ---------------------------------------------------------------------------------------------- */ 435 | 436 | #define Min(x, y) ((x) < (y) ? (x) : (y)) 437 | #define Max(x, y) ((x) > (y) ? (x) : (y)) 438 | #define Clamp(x, min, max) Max(min, Min(max, x)) 439 | 440 | /* (a3, b3) = (a1, b1) - (a2, b2) */ 441 | #define Sub2(a1, b1, a2, b2, a3, b3) \ 442 | ((a3) = (a1) - (a2)), \ 443 | ((b3) = (b1) - (b2)) 444 | 445 | /* cross product between (x1, y1) and (x2, y2) */ 446 | #define Cross(x1, y1, x2, y2) ((x1) * (y2) - (y1) * (x2)) 447 | 448 | #define ToRad(deg) ((deg) / 360 * PI * 2) 449 | 450 | /* return the closest power of two that is higher than x */ 451 | int RoundUpToPowerOfTwo(int x); 452 | 453 | /* return the closest multiple of a that is lower than value. a must be a power of 2 */ 454 | int AlignDownToPowerOfTwo(int x, int a); 455 | 456 | /* return the closest multiple of a that is higher than value. a must be a power of 2 */ 457 | int AlignUpToPowerOfTwo(int x, int a); 458 | 459 | /* null-terminated string utils */ 460 | int StrLen(char* s); 461 | void StrCpy(char* dst, char* src); 462 | int StrCmp(char* a, char* b); 463 | 464 | /* mem utils */ 465 | int MemCmp(void* a, void* b, int n); 466 | void* MemDup(void* p, int n); 467 | 468 | /* protobuf style varints. encoded in base 128. each byte contains 7 bits of the integer and the 469 | * msb is set if there's more. byte order is little endian */ 470 | 471 | int DecVarI32(char** pData); /* increments *pData past the varint */ 472 | int EncVarI32(void* data, int x); /* returns num of bytes written */ 473 | void CatVarI32(char** pArr, int x); /* append to resizable arr */ 474 | 475 | /* encode integers in little endian */ 476 | int EncI32(void* data, int x); 477 | int DecI32(char** pData); 478 | void CatI32(char** pArr, int x); 479 | 480 | void SwpFlts(float* a, float* b); 481 | void SwpPtrs(void** a, void** b); 482 | 483 | /* encode data to a b64 string. returned string must be freed with Free */ 484 | char* ToB64(void* data, int dataSize); 485 | char* ArrToB64(char* data); 486 | char* ArrFromB64(char* b64Data); 487 | 488 | int HashStr(void* data, int len); 489 | int HashI32(int x); 490 | 491 | /* converts x to a string with the specified base and appends the characters to arr. 492 | base is clamped to 1-16 */ 493 | void ArrStrCatI32(char** arr, int x, int base); 494 | 495 | /* like ArrStrCatI32 but creates an arr on the fly and null terminates it. must be rmd with RmArr */ 496 | char* I32ToArrStr(int x, int base); 497 | 498 | typedef int QsortCmp(void*, void*); 499 | 500 | void Qsort(void** arr, int len, QsortCmp* cmp); 501 | void QsortStrs(char** strarr, int len); 502 | 503 | /* ---------------------------------------------------------------------------------------------- */ 504 | /* ENUMS AND CONSTANTS */ 505 | /* ---------------------------------------------------------------------------------------------- */ 506 | 507 | #ifndef PI 508 | #define PI 3.141592653589793238462643383279502884 509 | #endif 510 | 511 | /* message types returned by MsgType */ 512 | enum { 513 | QUIT_REQUEST = 1, 514 | KEYDOWN, 515 | KEYUP, 516 | MOTION, 517 | SIZE, 518 | INIT, 519 | FRAME, 520 | QUIT, 521 | LAST_MSG_TYPE 522 | }; 523 | 524 | /* flags for the bitfield returned by MsgKeyState */ 525 | enum { 526 | FSHIFT = 1<<1, 527 | FCONTROL = 1<<2, 528 | FALT = 1<<3, 529 | FSUPER = 1<<4, 530 | FCAPS_LOCK = 1<<5, 531 | FNUM_LOCK = 1<<6, 532 | FREPEAT = 1<<7, 533 | FLAST_STATE 534 | }; 535 | 536 | #define FCTRL FCONTROL 537 | 538 | #define MLEFT MOUSE1 539 | #define MMID MOUSE2 540 | #define MRIGHT MOUSE3 541 | #define MWHEELUP MOUSE4 542 | #define MWHEELDOWN MOUSE5 543 | 544 | /* keys returned by MsgKey */ 545 | enum { 546 | MOUSE1 = 1, 547 | MOUSE2 = 2, 548 | MOUSE3 = 3, 549 | MOUSE4 = 4, 550 | MOUSE5 = 5, 551 | SPACE = 32, 552 | APOSTROPHE = 39, 553 | COMMA = 44, 554 | MINUS = 45, 555 | PERIOD = 46, 556 | SLASH = 47, 557 | K0 = 48, 558 | K1 = 49, 559 | K2 = 50, 560 | K3 = 51, 561 | K4 = 52, 562 | K5 = 53, 563 | K6 = 54, 564 | K7 = 55, 565 | K8 = 56, 566 | K9 = 57, 567 | SEMICOLON = 59, 568 | EQUAL = 61, 569 | A = 65, 570 | B = 66, 571 | C = 67, 572 | D = 68, 573 | E = 69, 574 | F = 70, 575 | G = 71, 576 | H = 72, 577 | I = 73, 578 | J = 74, 579 | K = 75, 580 | L = 76, 581 | M = 77, 582 | N = 78, 583 | O = 79, 584 | P = 80, 585 | Q = 81, 586 | R = 82, 587 | S = 83, 588 | T = 84, 589 | U = 85, 590 | V = 86, 591 | W = 87, 592 | X = 88, 593 | Y = 89, 594 | Z = 90, 595 | LEFT_BRACKET = 91, 596 | BACKSLASH = 92, 597 | RIGHT_BRACKET = 93, 598 | GRAVE_ACCENT = 96, 599 | WORLD_1 = 161, 600 | WORLD_2 = 162, 601 | ESCAPE = 256, 602 | ENTER = 257, 603 | TAB = 258, 604 | BACKSPACE = 259, 605 | INSERT = 260, 606 | DELETE = 261, 607 | RIGHT = 262, 608 | LEFT = 263, 609 | DOWN = 264, 610 | UP = 265, 611 | PAGE_UP = 266, 612 | PAGE_DOWN = 267, 613 | HOME = 268, 614 | END = 269, 615 | CAPS_LOCK = 280, 616 | SCROLL_LOCK = 281, 617 | NUM_LOCK = 282, 618 | PRINT_SCREEN = 283, 619 | PAUSE = 284, 620 | F1 = 290, 621 | F2 = 291, 622 | F3 = 292, 623 | F4 = 293, 624 | F5 = 294, 625 | F6 = 295, 626 | F7 = 296, 627 | F8 = 297, 628 | F9 = 298, 629 | F10 = 299, 630 | F11 = 300, 631 | F12 = 301, 632 | F13 = 302, 633 | F14 = 303, 634 | F15 = 304, 635 | F16 = 305, 636 | F17 = 306, 637 | F18 = 307, 638 | F19 = 308, 639 | F20 = 309, 640 | F21 = 310, 641 | F22 = 311, 642 | F23 = 312, 643 | F24 = 313, 644 | F25 = 314, 645 | KP_0 = 320, 646 | KP_1 = 321, 647 | KP_2 = 322, 648 | KP_3 = 323, 649 | KP_4 = 324, 650 | KP_5 = 325, 651 | KP_6 = 326, 652 | KP_7 = 327, 653 | KP_8 = 328, 654 | KP_9 = 329, 655 | KP_DECIMAL = 330, 656 | KP_DIVIDE = 331, 657 | KP_MULTIPLY = 332, 658 | KP_SUBTRACT = 333, 659 | KP_ADD = 334, 660 | KP_ENTER = 335, 661 | KP_EQUAL = 336, 662 | LEFT_SHIFT = 340, 663 | LEFT_CONTROL = 341, 664 | LEFT_ALT = 342, 665 | LEFT_SUPER = 343, 666 | RIGHT_SHIFT = 344, 667 | RIGHT_CONTROL = 345, 668 | RIGHT_ALT = 346, 669 | RIGHT_SUPER = 347, 670 | MENU = 348, 671 | LAST_KEY 672 | }; 673 | 674 | /* img wrap modes */ 675 | enum { 676 | CLAMP_TO_EDGE, 677 | MIRRORED_REPEAT, 678 | REPEAT, 679 | LAST_IMG_WRAP 680 | }; 681 | 682 | /* img filters */ 683 | enum { 684 | NEAREST, 685 | LINEAR, 686 | LAST_IMG_FILTER 687 | }; 688 | 689 | /* ---------------------------------------------------------------------------------------------- */ 690 | /* MISC DEBUG AND SEMI-INTERNAL INTERFACES */ 691 | /* ---------------------------------------------------------------------------------------------- */ 692 | 693 | /* these could be used to use the app interface from FFI (minus the handlers system unless your ffi 694 | * has simple ways to pass callbacks to C). see implementation of AppMain to see how you would do 695 | * it from FFI */ 696 | 697 | /* call the built in app handlers for the current msg */ 698 | int AppHandleMsg(); 699 | 700 | /* call the built in app handlers for the FRAME msg. this should be called at the start of every 701 | * tick of the game loop. note that this not call SwpBufs, you have to call it yourself */ 702 | void AppFrame(); 703 | 704 | /* true if no QUIT_REQUEST msg has been received */ 705 | int AppRunning(); 706 | 707 | /* (automatically called by AppMain) initialize the app globals. */ 708 | void MkApp(int argc, char* argv[]); 709 | 710 | /* (automatically called by AppMain) 711 | * clean up the app globals (optional, as virtual memory cleans everything up when we exit) */ 712 | void RmApp(); 713 | 714 | /* (automatically called by the platform layer) 715 | * initializes app and runs the main loop. 716 | * this also takes care of calling pseudo msgs like INIT, FRAME and calling SwpBufs */ 717 | int AppMain(int argc, char* argv[]); 718 | 719 | /* enable debug ui for the img allocator */ 720 | void DiagImgAlloc(int enabled); 721 | 722 | /* ---------------------------------------------------------------------------------------------- */ 723 | /* PLATFORM LAYER */ 724 | /* */ 725 | /* you can either include Platform/Platform.h to use the built in sample platform layers or write */ 726 | /* your own by defining these structs and funcs */ 727 | /* ---------------------------------------------------------------------------------------------- */ 728 | 729 | Wnd MkWnd(char* name, char* class); 730 | void RmWnd(Wnd wnd); 731 | void SetWndName(Wnd wnd, char* wndName); 732 | void SetWndClass(Wnd wnd, char* className); 733 | void SetWndSize(Wnd wnd, int width, int height); 734 | int WndWidth(Wnd wnd); 735 | int WndHeight(Wnd wnd); 736 | 737 | /* get time elapsed in seconds since the last SwpBufs. guaranteed to return non-zero even if 738 | * no SwpBufs happened */ 739 | float WndDelta(Wnd wnd); 740 | 741 | /* FPS limiter, 0 for unlimited. limiting happens in SwpBufs. note that unlimited fps still 742 | * waits for the minimum timer resolution for GetTime */ 743 | void SetWndFPS(Wnd wnd, int fps); 744 | 745 | /* fetch one message. returns non-zero as long as there are more */ 746 | int NextMsg(Wnd wnd); 747 | 748 | /* sends QUIT_REQUEST message to the wnd */ 749 | void PostQuitMsg(Wnd wnd); 750 | 751 | /* these funcs get data from the last message fetched by NextMsg */ 752 | int MsgType(Wnd wnd); 753 | int Key(Wnd wnd); 754 | int KeyState(Wnd wnd); 755 | int MouseX(Wnd wnd); 756 | int MouseY(Wnd wnd); 757 | int MouseDX(Wnd wnd); 758 | int MouseDY(Wnd wnd); 759 | 760 | /* allocates n bytes and initializes memory to zero */ 761 | void* Alloc(int n); 762 | 763 | /* reallocate p to new size n. memory that wasn't initialized is not guaranteed to be zero */ 764 | void* Realloc(void* p, int n); 765 | 766 | void Free(void* p); 767 | void MemSet(void* p, unsigned char val, int n); 768 | void MemCpy(void* dst, void* src, int n); 769 | 770 | /* same as MemCpy but allows overlapping regions of memory */ 771 | void MemMv(void* dst, void* src, int n); 772 | 773 | /* write data to disk. returns number of bytes written or < 0 for errors */ 774 | int WrFile(char* path, void* data, int dataLen); 775 | 776 | /* read up to maxSize bytes from disk */ 777 | int RdFile(char* path, void* data, int maxSize); 778 | 779 | /* ---------------------------------------------------------------------------------------------- */ 780 | /* MATH FUNCTIONS */ 781 | /* */ 782 | /* you can either include Platform/Platform.h to use the built in sample implementations or write */ 783 | /* your own by defining these funcs */ 784 | /* ---------------------------------------------------------------------------------------------- */ 785 | 786 | float FltMod(float x, float y); /* returns x modulo y */ 787 | float Sin(float deg); 788 | float Cos(float deg); 789 | int Ceil(float x); 790 | int Floor(float x); 791 | float Sqrt(float x); 792 | 793 | /* ---------------------------------------------------------------------------------------------- */ 794 | /* RENDERER */ 795 | /* */ 796 | /* you can either include Platform/Platform.h to use the built in sample renderers or write your */ 797 | /* own by defining these structs and funcs */ 798 | /* ---------------------------------------------------------------------------------------------- */ 799 | 800 | /* ---------------------------------------------------------------------------------------------- */ 801 | 802 | /* Mesh is a collection of vertices that will be rendered using the same img and trans. try 803 | * to batch up as much stuff into a mesh as possible for optimal performance. */ 804 | 805 | Mesh MkMesh(); 806 | void RmMesh(Mesh mesh); 807 | 808 | /* change current color. color is Argb 32-bit int (0xAARRGGBB). 255 alpha = completely transparent, 809 | * 0 alpha = completely opaque. the default color for a mesh should be 0xffffff */ 810 | void Col(Mesh mesh, int color); 811 | 812 | /* these are for custom meshes. 813 | * wrap Vert and Face calls between Begin and End. 814 | * indices start at zero and we are indexing the vertices submitted between Begin and End. see the 815 | * implementations of Quad and Tri for examples */ 816 | void Begin(Mesh mesh); 817 | void End(Mesh mesh); 818 | void Vert(Mesh mesh, float x, float y); 819 | void ImgCoord(Mesh mesh, float u, float v); 820 | void Face(Mesh mesh, int i1, int i2, int i3); 821 | 822 | /* this is internally used to do img atlases and map relative uv's to the bigger image */ 823 | void PutMeshRawEx(Mesh mesh, Mat mat, Img img, float uOffs, float vOffs); 824 | 825 | /* ---------------------------------------------------------------------------------------------- */ 826 | 827 | Img MkImg(); 828 | void RmImg(Img img); 829 | 830 | /* set wrap mode for img coordinates. default is REPEAT */ 831 | void SetImgWrapU(Img img, int mode); 832 | void SetImgWrapV(Img img, int mode); 833 | 834 | /* set min/mag filter for img. default is NEAREST */ 835 | void SetImgMinFilter(Img img, int filter); 836 | void SetImgMagFilter(Img img, int filter); 837 | 838 | /* set the img's pix data. must be an array of 0xAARRGGBB colors as explained in Col. 839 | * pixs are laid out row major - for example a 4x4 image would be: 840 | * { px00, px10, px01, px11 } 841 | * note that this is usually an expensive call. only update the img data when it's actually 842 | * changing. 843 | * return img for convenience */ 844 | Img Pixs(Img img, int width, int height, int* data); 845 | 846 | /* same as Pixs but you can specify stride which is how many bytes are between the beginning 847 | * of each row, for cases when you have extra padding or when you're submitting a sub-region of a 848 | * bigger image */ 849 | Img PixsEx(Img img, int width, int height, int* data, int stride); 850 | 851 | /* ---------------------------------------------------------------------------------------------- */ 852 | 853 | /* flush all rendered geometry to the screen */ 854 | void SwpBufs(Wnd wnd); 855 | 856 | /* set rendering rectangle from the top left of the wnd, in pixs. this is automatically called 857 | * when the wnd is resized. it can also be called manually to render to a subregion of the 858 | * wnd. all coordinates passed to other rendering functions start from the top left corner of 859 | * this rectangle. */ 860 | void Viewport(Wnd wnd, int x, int y, int width, int height); 861 | 862 | /* the initial color of a blank frame */ 863 | void ClsCol(int color); 864 | 865 | #endif /* WEEBCORE_HEADER */ 866 | 867 | /* ############################################################################################## */ 868 | /* ############################################################################################## */ 869 | /* ############################################################################################## */ 870 | /* */ 871 | /* Hdr part ends here. This is all you need to know to use it. Implementation below. */ 872 | /* */ 873 | /* ############################################################################################## */ 874 | /* ############################################################################################## */ 875 | /* ############################################################################################## */ 876 | 877 | #if defined(WEEBCORE_IMPLEMENTATION) && !defined(WEEBCORE_OVERRIDE_MONOLITHIC_BUILD) 878 | 879 | /* ---------------------------------------------------------------------------------------------- */ 880 | 881 | /* to minimize duplication, I decided to have common flags shared by all internal stuff */ 882 | #define DIRTY (1<<0) 883 | #define ORTHO_DIRTY (1<<1) 884 | #define RUNNING (1<<2) 885 | 886 | /* ---------------------------------------------------------------------------------------------- */ 887 | 888 | typedef struct _ArrHdr { 889 | int capacity; 890 | int length; 891 | } ArrHdr; 892 | 893 | static ArrHdr* GetArrHdr(void* array) { 894 | if (!array) return 0; 895 | return (ArrHdr*)array - 1; 896 | } 897 | 898 | void RmArr(void* array) { 899 | Free(GetArrHdr(array)); 900 | } 901 | 902 | int ArrLen(void* array) { 903 | if (!array) return 0; 904 | return GetArrHdr(array)->length; 905 | } 906 | 907 | int ArrCap(void* array) { 908 | if (!array) return 0; 909 | return GetArrHdr(array)->capacity; 910 | } 911 | 912 | void SetArrLen(void* array, int length) { 913 | if (array) { 914 | GetArrHdr(array)->length = length; 915 | } 916 | } 917 | 918 | int ArrMemCmp(void* a, void* b) { 919 | int alen = ArrLen(a); 920 | int blen = ArrLen(b); 921 | if (alen > blen) { 922 | return 1; 923 | } else if (alen < blen) { 924 | return -1; 925 | } 926 | return MemCmp(a, b, alen); 927 | } 928 | 929 | void ArrStrCat(char** pArr, char* str) { 930 | for (; *str; ++str) { 931 | ArrCat(pArr, *str); 932 | } 933 | } 934 | 935 | void* ArrReserveEx(void** pArr, int elementSize, int numElements) { 936 | ArrHdr* header; 937 | if (!*pArr) { 938 | int capacity = RoundUpToPowerOfTwo(Max(numElements, 16)); 939 | header = Alloc(sizeof(ArrHdr) + elementSize * capacity); 940 | header->capacity = capacity; 941 | *pArr = header + 1; 942 | } else { 943 | int minCapacity; 944 | header = GetArrHdr(*pArr); 945 | minCapacity = header->length + numElements; 946 | if (header->capacity < minCapacity) { 947 | int newSize; 948 | while (header->capacity < minCapacity) { 949 | header->capacity *= 2; 950 | } 951 | newSize = sizeof(ArrHdr) + elementSize * header->capacity; 952 | header = Realloc(header, newSize); 953 | *pArr = header + 1; 954 | } 955 | } 956 | return (char*)*pArr + ArrLen(*pArr) * elementSize; 957 | } 958 | 959 | void* ArrAllocEx(void** pArr, int elementSize, int numElements) { 960 | void* res = ArrReserveEx(pArr, elementSize, numElements); 961 | SetArrLen(*pArr, ArrLen(*pArr) + numElements); 962 | return res; 963 | } 964 | 965 | void* ArrDupEx(void* array, int elementSize) { 966 | void* res = 0; 967 | ArrAllocEx(&res, elementSize, ArrLen(array)); 968 | if (res) { 969 | MemCpy(res, array, ArrLen(array) * elementSize); 970 | } 971 | return res; 972 | } 973 | 974 | char* ArrStrJoin(char** array, char* separator) { 975 | int i; 976 | char* res = 0; 977 | for (i = 0; i < ArrLen(array); ++i) { 978 | ArrStrCat(&res, array[i]); 979 | ArrStrCat(&res, separator); 980 | } 981 | ArrCat(&res, 0); 982 | return res; 983 | } 984 | 985 | /* ---------------------------------------------------------------------------------------------- */ 986 | 987 | struct _Arena { 988 | char** chunks; 989 | char* p; 990 | int freeBytes; 991 | int minChunkSize; 992 | }; 993 | 994 | Arena MkArena() { return MkArenaEx(1024); } 995 | 996 | Arena MkArenaEx(int chunkSize) { 997 | Arena arena = Alloc(sizeof(struct _Arena)); 998 | if (arena) { 999 | arena->minChunkSize = chunkSize; 1000 | } 1001 | return arena; 1002 | } 1003 | 1004 | void RmArena(Arena arena) { 1005 | if (arena) { 1006 | int i; 1007 | for (i = 0; i < ArrLen(arena->chunks); ++i) { 1008 | Free(arena->chunks[i]); 1009 | } 1010 | RmArr(arena->chunks); 1011 | } 1012 | Free(arena); 1013 | } 1014 | 1015 | void* ArenaAlloc(Arena arena, int n) { 1016 | void* res; 1017 | if (arena->freeBytes < n) { 1018 | int size = Max(n, arena->minChunkSize); 1019 | size = AlignUpToPowerOfTwo(size, 8); 1020 | ArrCat(&arena->chunks, Alloc(size)); 1021 | arena->p = arena->chunks[ArrLen(arena->chunks) - 1]; 1022 | arena->freeBytes = size; 1023 | } 1024 | res = arena->p; 1025 | if (!res) { 1026 | return 0; 1027 | } 1028 | arena->p += AlignUpToPowerOfTwo(n, 8); 1029 | return res; 1030 | } 1031 | 1032 | void* ArenaMemDup(Arena arena, void* p, int n) { 1033 | void* res = ArenaAlloc(arena, n); 1034 | if (res) { 1035 | MemCpy(res, p, n); 1036 | } 1037 | return res; 1038 | } 1039 | 1040 | /* ---------------------------------------------------------------------------------------------- */ 1041 | 1042 | typedef struct _MapItem { 1043 | void* val; 1044 | int key; 1045 | } MapItem; 1046 | 1047 | struct _Map { 1048 | MapItem* arr; 1049 | int* keys; 1050 | int* isset; /* bit mask of set indices into arr. this way we don't have to reserve zero kvals */ 1051 | }; 1052 | 1053 | Map MkMap() { 1054 | return Alloc(sizeof(struct _Map)); 1055 | } 1056 | 1057 | static void RmMapContents(Map map) { 1058 | if (map) { 1059 | RmArr(map->arr); 1060 | RmArr(map->keys); 1061 | RmArr(map->isset); 1062 | } 1063 | } 1064 | 1065 | static void RmMapContainer(Map map) { 1066 | Free(map); 1067 | } 1068 | 1069 | void RmMap(Map map) { 1070 | RmMapContents(map); 1071 | RmMapContainer(map); 1072 | } 1073 | 1074 | static int MapIsSet(Map map, int i) { 1075 | return map->isset[i / 32] & (0x80000000 >> (i % 32)); 1076 | } 1077 | 1078 | void MapSet(Map map, int key, void* val) { 1079 | int starti, i; 1080 | int cap = ArrCap(map->arr); 1081 | if (2 * ArrLen(map->keys) >= cap) { 1082 | Map new = MkMap(); 1083 | if (!cap) { 1084 | cap = 16; 1085 | } 1086 | cap *= 2; 1087 | ArrAlloc(&new->arr, cap); 1088 | ArrAlloc(&new->isset, Max(cap / 32, 1)); 1089 | for (i = 0; i < ArrLen(map->keys); ++i) { 1090 | MapSet(new, map->keys[i], MapGet(map, map->keys[i])); 1091 | } 1092 | RmMapContents(map); 1093 | *map = *new; 1094 | RmMapContainer(new); 1095 | } 1096 | starti = HashI32(key) % cap; 1097 | for (i = starti; MapIsSet(map, i) && map->arr[i].key != key;) { 1098 | i = (i + 1) % cap; 1099 | if (i == starti) { return; } 1100 | } 1101 | map->isset[i / 32] |= 0x80000000 >> (i % 32); 1102 | map->arr[i].key = key; 1103 | map->arr[i].val = val; 1104 | } 1105 | 1106 | void* MapGet(Map map, int key) { 1107 | int starti, i; 1108 | int cap = ArrCap(map->arr); 1109 | if (!cap) { 1110 | return 0; 1111 | } 1112 | starti = HashI32(key) % cap; 1113 | for (i = starti; map->arr[i].key != key;) { 1114 | i = (i + 1) % cap; 1115 | if (i == starti) { return 0; } 1116 | } 1117 | return map->arr[i].val; 1118 | } 1119 | 1120 | int MapColls(Map map) { 1121 | int i; 1122 | int colls = 0; 1123 | Map counts = MkMap(); 1124 | for (i = 0; i < ArrLen(map->keys); ++i) { 1125 | int hash = HashI32(map->keys[i]); 1126 | MapSet(counts, hash, (void*)((int)MapGet(counts, hash) + 1)); 1127 | } 1128 | for (i = 0; i < ArrLen(counts->keys); ++i) { 1129 | colls += (int)MapGet(counts, counts->keys[i]) - 1; 1130 | } 1131 | RmMap(counts); 1132 | return colls; 1133 | } 1134 | 1135 | int MapNumKeys(Map map) { return ArrLen(map->keys); } 1136 | int MapKey(Map map, int i) { return map->keys[i]; } 1137 | 1138 | /* ---------------------------------------------------------------------------------------------- */ 1139 | 1140 | /* we build this on top of a regular Map. we hash the keys and use the hash as the Map key. each Map 1141 | * entry is a linked list of HashItems. if there are any collisions, we walk down the list until we 1142 | * find the right key. 1143 | * going through 2 hashes (bytes -> int, int -> int) is probably unnecessary but it lets us not 1144 | * duplicate most of the Map code */ 1145 | 1146 | typedef struct _HashKeyData { 1147 | char* data; 1148 | int len; 1149 | } HashKeyData; 1150 | 1151 | static int HashKeyCmp(HashKeyData* key, void* data, int len) { 1152 | if (key->len < len) { return -1; } 1153 | if (key->len > len) { return 1; } 1154 | return MemCmp(key->data, data, len); 1155 | } 1156 | 1157 | typedef struct _HashItem { 1158 | struct _HashItem* next; 1159 | HashKeyData key; 1160 | void* val; 1161 | } HashItem; 1162 | 1163 | struct _Hash { 1164 | Arena arena; 1165 | Map map; 1166 | HashKeyData* keys; 1167 | }; 1168 | 1169 | Hash MkHash() { 1170 | Hash hash = Alloc(sizeof(struct _Hash)); 1171 | if (hash) { 1172 | hash->arena = MkArena(); 1173 | hash->map = MkMap(); 1174 | } 1175 | return hash; 1176 | } 1177 | 1178 | void RmHash(Hash hash) { 1179 | if (hash) { 1180 | RmMap(hash->map); 1181 | RmArena(hash->arena); 1182 | RmArr(hash->keys); 1183 | } 1184 | Free(hash); 1185 | } 1186 | 1187 | void HashSet(Hash hash, char* key, void* val) { 1188 | HashSetb(hash, key, StrLen(key) + 1, val); /* include null terminator just in case */ 1189 | } 1190 | 1191 | void HashSetb(Hash hash, void* keyData, int keySize, void* val) { 1192 | int strhash = HashStr(keyData, keySize); 1193 | HashItem* bucket = MapGet(hash->map, strhash); 1194 | HashItem* it; 1195 | for (it = bucket; it; it = it->next) { 1196 | if (!HashKeyCmp(&it->key, keyData, keySize)) { 1197 | it->val = val; 1198 | return; 1199 | } 1200 | } 1201 | /* key was not found, so create it */ 1202 | it = ArenaAlloc(hash->arena, sizeof(HashItem)); 1203 | it->key.data = ArenaMemDup(hash->arena, keyData, keySize); 1204 | it->key.len = keySize; 1205 | it->val = val; 1206 | it->next = bucket; 1207 | MapSet(hash->map, strhash, it); 1208 | ArrCat(&hash->keys, it->key); 1209 | } 1210 | 1211 | void* HashGet(Hash hash, char* key) { 1212 | return HashGetb(hash, key, StrLen(key) + 1); 1213 | } 1214 | 1215 | static void* HashCheckedGet(Hash hash, char* keyData, int keySize, void** pkey) { 1216 | int strhash = HashStr(keyData, keySize); 1217 | HashItem* bucket = MapGet(hash->map, strhash); 1218 | HashItem* it; 1219 | for (it = bucket; it; it = it->next) { 1220 | if (!HashKeyCmp(&it->key, keyData, keySize)) { 1221 | *pkey = it->key.data; 1222 | return it->val; 1223 | } 1224 | } 1225 | *pkey = 0; 1226 | return 0; 1227 | } 1228 | 1229 | void* HashGetb(Hash hash, char* keyData, int keySize) { 1230 | void* key; 1231 | return HashCheckedGet(hash, keyData, keySize, &key); 1232 | } 1233 | 1234 | int HashHas(Hash hash, char* key) { 1235 | return HashHasb(hash, key, StrLen(key)); 1236 | } 1237 | 1238 | int HashHasb(Hash hash, char* keyData, int keySize) { 1239 | void* key; 1240 | HashCheckedGet(hash, keyData, keySize, &key); 1241 | return key != 0; 1242 | } 1243 | 1244 | int HashColls(Hash hash) { 1245 | int i, colls = 0; 1246 | Map counts = MkMap(); 1247 | for (i = 0; i < ArrLen(hash->keys); ++i) { 1248 | HashKeyData* k = &hash->keys[i]; 1249 | int strhash = HashStr(k->data, k->len); 1250 | MapSet(counts, strhash, (void*)((int)MapGet(counts, strhash) + 1)); 1251 | } 1252 | for (i = 0; i < ArrLen(counts->keys); ++i) { 1253 | colls += (int)MapGet(counts, counts->keys[i]) - 1; 1254 | } 1255 | RmMap(counts); 1256 | return colls + MapColls(hash->map); 1257 | } 1258 | 1259 | int HashNumKeys(Hash hash) { return ArrLen(hash->keys); } 1260 | void* HashKey(Hash hash, int i) { return hash->keys[i].data; } 1261 | int HashKeyLen(Hash hash, int i) { return hash->keys[i].len; } 1262 | 1263 | /* ---------------------------------------------------------------------------------------------- */ 1264 | 1265 | typedef struct { float r[4]; } PackerRect; /* left, right, top bottom */ 1266 | 1267 | struct _Packer { 1268 | PackerRect* rects; 1269 | }; 1270 | 1271 | /* adds a free rect */ 1272 | static void ArrCatRect(PackerRect** arr, float left, float right, float top, float bottom) { 1273 | PackerRect* newRect = ArrAlloc(arr, 1); 1274 | SetRect(newRect->r, left, right, top, bottom); 1275 | } 1276 | 1277 | static void ArrCatRectFlts(PackerRect** arr, float* r) { 1278 | PackerRect* newRect = ArrAlloc(arr, 1); 1279 | MemCpy(newRect, r, sizeof(float) * 4); 1280 | } 1281 | 1282 | Packer MkPacker(int width, int height) { 1283 | Packer pak = Alloc(sizeof(struct _Packer)); 1284 | if (pak) { 1285 | /* initialize area to be one big free rect */ 1286 | ArrCatRect(&pak->rects, 0, width, 0, height); 1287 | } 1288 | return pak; 1289 | } 1290 | 1291 | void RmPacker(Packer pak) { 1292 | if (pak) { 1293 | RmArr(pak->rects); 1294 | } 1295 | Free(pak); 1296 | } 1297 | 1298 | /* find the best fit free rectangle (smallest area that can fit the rect). 1299 | * initially we will have 1 big free rect that takes the entire area */ 1300 | static int PakFindFree(Packer pak, float* rect) { 1301 | PackerRect* rects = pak->rects; 1302 | int i, bestFit = -1; 1303 | float bestFitArea = 2000000000; 1304 | for (i = 0; i < ArrLen(rects); ++i) { 1305 | float* r = rects[i].r; 1306 | if (RectInRectArea(rect, r)) { 1307 | float area = RectWidth(r) * RectHeight(r); 1308 | if (area < bestFitArea) { 1309 | bestFitArea = area; 1310 | bestFit = i; 1311 | } 1312 | } 1313 | } 1314 | return bestFit; 1315 | } 1316 | 1317 | /* once we have found a location for the rectangle, we need to split any free rectangles it 1318 | * partially intersects with. this will generate two or more smaller rects */ 1319 | static void PakSplit(Packer pak, float* rect) { 1320 | PackerRect* rects = pak->rects; 1321 | int i; 1322 | PackerRect* newRects = 0; 1323 | for (i = 0; i < ArrLen(rects); ++i) { 1324 | float* r = rects[i].r; 1325 | if (RectSect(rect, r)) { 1326 | if (rect[0] > r[0]) { ArrCatRect(&newRects, r[0], rect[0], r[2], r[3]); /* left */ } 1327 | if (rect[1] < r[1]) { ArrCatRect(&newRects, rect[1], r[1], r[2], r[3]); /* right */ } 1328 | if (rect[2] > r[2]) { ArrCatRect(&newRects, r[0], r[1], r[2], rect[2]); /* top */ } 1329 | if (rect[3] < r[3]) { ArrCatRect(&newRects, r[0], r[1], rect[3], r[3]); /* bott */ } 1330 | } else { 1331 | ArrCatRectFlts(&newRects, r); 1332 | } 1333 | } 1334 | RmArr(rects); 1335 | pak->rects = newRects; 1336 | } 1337 | 1338 | /* after the split step, there will be redundant rects because we create 1 rect for each side */ 1339 | static void PakPrune(Packer pak) { 1340 | int i, j; 1341 | PackerRect* rects = pak->rects; 1342 | PackerRect* newRects = 0; 1343 | for (i = 0; i < ArrLen(rects); ++i) { 1344 | for (j = 0; j < ArrLen(rects); ++j) { 1345 | if (i == j) { continue; } 1346 | if (RectInRect(rects[i].r, rects[j].r)) { 1347 | rects[i].r[0] = rects[i].r[1] = 0; /* make it zero size to mark for removal */ 1348 | } else if (RectInRect(rects[j].r, rects[i].r)) { 1349 | rects[j].r[0] = rects[j].r[1] = 0; 1350 | } 1351 | } 1352 | if (RectWidth(rects[i].r) > 0) { 1353 | ArrCatRectFlts(&newRects, rects[i].r); 1354 | } 1355 | } 1356 | RmArr(rects); 1357 | pak->rects = newRects; 1358 | } 1359 | 1360 | float* Pack(Packer pak, float* rect) { 1361 | float* free; 1362 | int freeRect = PakFindFree(pak, rect); 1363 | if (freeRect < 0) { /* full */ 1364 | return 0; 1365 | } 1366 | 1367 | /* move rectangle to the top left of the free area */ 1368 | free = pak->rects[freeRect].r; 1369 | SetRectPos(rect, free[0], free[2]); 1370 | 1371 | /* update free rects */ 1372 | PakSplit(pak, rect); 1373 | PakPrune(pak); 1374 | return rect; 1375 | } 1376 | 1377 | void PackFree(Packer pak, float* rect) { 1378 | /* TODO: figure out a good way to defragment the free rects */ 1379 | ArrCatRectFlts(&pak->rects, rect); 1380 | PakPrune(pak); 1381 | } 1382 | 1383 | /* ---------------------------------------------------------------------------------------------- */ 1384 | 1385 | /* https://graphics.stanford.edu/~seander/bithacks.html */ 1386 | int RoundUpToPowerOfTwo(int x) { 1387 | --x; 1388 | x |= x >> 1; 1389 | x |= x >> 2; 1390 | x |= x >> 4; 1391 | x |= x >> 8; 1392 | x |= x >> 16; 1393 | return ++x; 1394 | } 1395 | 1396 | int StrLen(char* s) { 1397 | char* p; 1398 | for (p = s; *p; ++p); 1399 | return p - s; 1400 | } 1401 | 1402 | void StrCpy(char* dst, char* src) { 1403 | while (*src) { 1404 | *dst++ = *src++; 1405 | } 1406 | } 1407 | 1408 | int StrCmp(char* a, char* b) { 1409 | while (1) { 1410 | if (*a < *b) { return -1; } 1411 | else if (*a > *b) { return 1; } 1412 | if (!*a || !*b) { break; } 1413 | ++a; ++b; 1414 | } 1415 | return 0; 1416 | } 1417 | 1418 | int DecVarI32(char** pData) { 1419 | unsigned char* p; 1420 | int res = 0, shift = 0, more = 1; 1421 | for (p = (unsigned char*)*pData; more; ++p) { 1422 | more = *p & 0x80; 1423 | res |= (*p & 0x7F) << shift; 1424 | shift += 7; 1425 | } 1426 | *pData = (char*)p; 1427 | return res; 1428 | } 1429 | 1430 | int EncVarI32(void* data, int x) { 1431 | unsigned char* p = data; 1432 | unsigned u = (unsigned)x; 1433 | while (1) { 1434 | unsigned char* byte = p++; 1435 | *byte = u & 0x7F; 1436 | u >>= 7; 1437 | if (u) { 1438 | *byte |= 0x80; 1439 | } else { 1440 | break; 1441 | } 1442 | } 1443 | return p - (unsigned char*)data; 1444 | } 1445 | 1446 | void CatVarI32(char** pArr, int x) { 1447 | char* p = ArrReserve(pArr, 4); 1448 | int n = EncVarI32(p, x); 1449 | SetArrLen(*pArr, ArrLen(*pArr) + n); 1450 | } 1451 | 1452 | int EncI32(void* data, int x) { 1453 | unsigned char* p = data; 1454 | p[0] = (x & 0x000000ff) >> 0; 1455 | p[1] = (x & 0x0000ff00) >> 8; 1456 | p[2] = (x & 0x00ff0000) >> 16; 1457 | p[3] = (x & 0xff000000) >> 24; 1458 | return 4; 1459 | } 1460 | 1461 | int DecI32(char** pData) { 1462 | unsigned char* p = (unsigned char*)*pData; 1463 | *pData += 4; 1464 | return (p[0] << 0) | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); 1465 | } 1466 | 1467 | void CatI32(char** pArr, int x) { 1468 | char* p = ArrAlloc(pArr, 4); 1469 | EncI32(p, x); 1470 | } 1471 | 1472 | int MemCmp(void* a, void* b, int n) { 1473 | unsigned char* p1 = a; 1474 | unsigned char* p2 = b; 1475 | int i; 1476 | for (i = 0; i < n; ++i) { 1477 | if (*p1 < *p2) { 1478 | return -1; 1479 | } else if (*p1 > *p2) { 1480 | return 1; 1481 | } 1482 | } 1483 | return 0; 1484 | } 1485 | 1486 | void* MemDup(void* p, int n) { 1487 | void* new = Alloc(n); 1488 | if (new) { MemCpy(new, p, n); } 1489 | return new; 1490 | } 1491 | 1492 | void SwpFlts(float* a, float* b) { 1493 | float tmp = *a; 1494 | *a = *b; 1495 | *b = tmp; 1496 | } 1497 | 1498 | void SwpPtrs(void** a, void** b) { 1499 | void* tmp = *a; 1500 | *a = *b; 1501 | *b = tmp; 1502 | } 1503 | 1504 | int HashStr(void* data, int len) { 1505 | int i; 1506 | char* p = (char*)data; 1507 | int x = 0x811c9dc5; 1508 | for (i = 0; i < len; ++i) { 1509 | x ^= p[i]; 1510 | x *= 0x1000193; 1511 | x ^= x >> 16; 1512 | } 1513 | return x; 1514 | } 1515 | 1516 | int HashI32(int x) { 1517 | x *= 0x85ebca6b; 1518 | x ^= x >> 16; 1519 | return x; 1520 | } 1521 | 1522 | int AlignDownToPowerOfTwo(int x, int a) { return x & ~(a - 1); } 1523 | int AlignUpToPowerOfTwo(int x, int a) { return AlignDownToPowerOfTwo(x + a - 1, a); } 1524 | 1525 | char* I32ToArrStr(int x, int base) { 1526 | char* res = 0; 1527 | ArrStrCatI32(&res, x, base); 1528 | ArrCat(&res, 0); 1529 | return res; 1530 | } 1531 | 1532 | void ArrStrCatI32(char** res, int x, int base) { 1533 | static char* charset = "0123456789abcdef"; 1534 | int i, tmplen; 1535 | char* tmp = 0; 1536 | base = Min(16, Max(1, base)); 1537 | if (x < 0) { 1538 | ArrCat(res, '-'); 1539 | x *= -1; 1540 | } 1541 | while (x) { 1542 | ArrCat(&tmp, charset[x % base]); 1543 | x /= base; 1544 | } 1545 | if (ArrLen(tmp) <= 0) { 1546 | ArrCat(&tmp, '0'); 1547 | } 1548 | tmplen = ArrLen(tmp); 1549 | for (i = 0; i < tmplen; ++i) { 1550 | ArrCat(res, tmp[tmplen - i - 1]); 1551 | } 1552 | RmArr(tmp); 1553 | } 1554 | 1555 | void QsortStrs(char** strarr, int len) { 1556 | Qsort((void**)strarr, len, (QsortCmp*)StrCmp); 1557 | } 1558 | 1559 | static int QsortPart(void** arr, int len, QsortCmp* cmp, int lo, int hi) { 1560 | void* pivot = arr[hi]; 1561 | int i = 0, j; 1562 | for (j = 0; j < hi; ++j) { 1563 | if (cmp(arr[j], pivot) <= 0) { 1564 | SwpPtrs(&arr[i++], &arr[j]); 1565 | } 1566 | } 1567 | SwpPtrs(&arr[i], &arr[hi]); 1568 | return i; 1569 | } 1570 | 1571 | static void QsortImpl(void** arr, int len, QsortCmp* cmp, int lo, int hi) { 1572 | if (lo < hi) { 1573 | int pivot = QsortPart(arr, len, cmp, lo, hi); 1574 | QsortImpl(arr, len, cmp, lo, pivot - 1); 1575 | QsortImpl(arr, len, cmp, pivot + 1, hi); 1576 | } 1577 | } 1578 | 1579 | void Qsort(void** arr, int len, QsortCmp* cmp) { 1580 | QsortImpl(arr, len, cmp, 0, len - 1); 1581 | } 1582 | 1583 | /* ---------------------------------------------------------------------------------------------- */ 1584 | 1585 | void PutMeshRaw(Mesh mesh, Mat mat, Img img) { 1586 | PutMeshRawEx(mesh, mat, img, 0, 0); 1587 | } 1588 | 1589 | struct _Trans { 1590 | float sX, sY; 1591 | float x, y; 1592 | float oX, oY; 1593 | float deg; 1594 | Mat tempMat, tempMatOrtho; 1595 | int dirty; 1596 | }; 1597 | 1598 | Trans MkTrans() { 1599 | Trans trans = Alloc(sizeof(struct _Trans)); 1600 | trans->tempMat = MkMat(); 1601 | trans->tempMatOrtho = MkMat(); 1602 | ClrTrans(trans); 1603 | return trans; 1604 | } 1605 | 1606 | void RmTrans(Trans trans) { 1607 | RmMat(trans->tempMat); 1608 | RmMat(trans->tempMatOrtho); 1609 | Free(trans); 1610 | } 1611 | 1612 | void ClrTrans(Trans trans) { 1613 | trans->sX = 1; 1614 | trans->sY = 1; 1615 | trans->x = trans->y = 1616 | trans->oX = trans->oY = 1617 | trans->deg = 0; 1618 | trans->dirty = ~0; 1619 | } 1620 | 1621 | Trans SetScale(Trans trans, float x, float y) { 1622 | trans->sX = x; 1623 | trans->sY = y; 1624 | trans->dirty = ~0; 1625 | return trans; 1626 | } 1627 | 1628 | Trans SetScale1(Trans trans, float scale) { 1629 | return SetScale(trans, scale, scale); 1630 | } 1631 | 1632 | Trans SetPos(Trans trans, float x, float y) { 1633 | trans->x = x; 1634 | trans->y = y; 1635 | trans->dirty = ~0; 1636 | return trans; 1637 | } 1638 | 1639 | Trans SetOrig(Trans trans, float x, float y) { 1640 | trans->oX = x; 1641 | trans->oY = y; 1642 | trans->dirty = ~0; 1643 | return trans; 1644 | } 1645 | 1646 | Trans SetRot(Trans trans, float deg) { 1647 | trans->deg = deg; 1648 | trans->dirty = ~0; 1649 | return trans; 1650 | } 1651 | 1652 | static Mat CalcTrans(Trans trans, Mat mat, int ortho) { 1653 | Pos(mat, trans->x, trans->y); 1654 | Rot(mat, trans->deg); 1655 | if (!ortho) { 1656 | Scale(mat, trans->sX, trans->sY); 1657 | } 1658 | return Pos(mat, -trans->oX, -trans->oY); 1659 | } 1660 | 1661 | Mat ToMat(Trans trans) { return CalcTrans(trans, MkMat(), 0); } 1662 | Mat ToMatOrtho(Trans trans) { return CalcTrans(trans, MkMat(), 1); } 1663 | 1664 | Mat ToTmpMat(Trans trans) { 1665 | if (trans->dirty & DIRTY) { 1666 | SetIdentity(trans->tempMat); 1667 | CalcTrans(trans, trans->tempMat, 0); 1668 | trans->dirty &= ~DIRTY; 1669 | } 1670 | return trans->tempMat; 1671 | } 1672 | 1673 | Mat ToTmpMatOrtho(Trans trans) { 1674 | if (trans->dirty & ORTHO_DIRTY) { 1675 | SetIdentity(trans->tempMatOrtho); 1676 | CalcTrans(trans, trans->tempMatOrtho, 1); 1677 | trans->dirty &= ~ORTHO_DIRTY; 1678 | } 1679 | return trans->tempMatOrtho; 1680 | } 1681 | 1682 | /* ---------------------------------------------------------------------------------------------- */ 1683 | 1684 | void ImgTri(Mesh mesh, 1685 | float x1, float y1, float u1, float v1, 1686 | float x2, float y2, float u2, float v2, 1687 | float x3, float y3, float u3, float v3 1688 | ) { 1689 | /* ensure vertex order is counter-clockwise so we can cull backfaces */ 1690 | float v1x, v1y, v2x, v2y; 1691 | float i2 = 1, i3 = 2; 1692 | Sub2(x2, y2, x1, y1, v1x, v1y); 1693 | Sub2(x3, y3, x1, y1, v2x, v2y); 1694 | if (Cross(v1x, v1y, v2x, v2y) > 0) { 1695 | i2 = 2, i3 = 1; 1696 | } 1697 | Begin(mesh); 1698 | Vert(mesh, x1, y1); ImgCoord(mesh, u1, v1); 1699 | Vert(mesh, x2, y2); ImgCoord(mesh, u2, v2); 1700 | Vert(mesh, x3, y3); ImgCoord(mesh, u3, v3); 1701 | Face(mesh, 0, i2, i3); 1702 | End(mesh); 1703 | } 1704 | 1705 | void Tri(Mesh mesh, float x1, float y1, float x2, float y2, float x3, float y3) { 1706 | ImgTri(mesh, x1, y1, 0, 0, x2, y2, 0, 0, x3, y3, 0, 0); 1707 | } 1708 | 1709 | void ImgQuad(Mesh mesh, float x, float y, float u, float v, 1710 | float width, float height, float uWidth, float vHeight) 1711 | { 1712 | Begin(mesh); 1713 | Vert(mesh, x , y ); ImgCoord(mesh, u , v ); 1714 | Vert(mesh, x , y + height); ImgCoord(mesh, u , v + vHeight); 1715 | Vert(mesh, x + width, y + height); ImgCoord(mesh, u + uWidth, v + vHeight); 1716 | Vert(mesh, x + width, y ); ImgCoord(mesh, u + uWidth, v ); 1717 | Face(mesh, 0, 1, 2); 1718 | Face(mesh, 0, 2, 3); 1719 | End(mesh); 1720 | } 1721 | 1722 | 1723 | void Quad(Mesh mesh, float x, float y, float width, float height) { 1724 | ImgQuad(mesh, x, y, 0, 0, width, height, width, height); 1725 | } 1726 | 1727 | 1728 | void QuadGradH(Mesh mesh, float x, float y, float width, float height, int n, int* colors) { 1729 | int i; 1730 | float off; 1731 | Begin(mesh); 1732 | for (i = 0; i < n; ++i) { 1733 | int s = i * 2; 1734 | off = (float)i / (n - 1) * width; 1735 | Col(mesh, colors[i]); 1736 | Vert(mesh, x + off, y); 1737 | Vert(mesh, x + off, y + height); 1738 | if (i < n - 1) { 1739 | Face(mesh, s + 0, s + 1, s + 2); 1740 | Face(mesh, s + 1, s + 3, s + 2); 1741 | } 1742 | } 1743 | End(mesh); 1744 | } 1745 | 1746 | void QuadGradV(Mesh mesh, float x, float y, float width, float height, int n, int* colors) { 1747 | int i; 1748 | float off; 1749 | Begin(mesh); 1750 | for (i = 0; i < n; ++i) { 1751 | int s = i * 2; 1752 | off = (float)i / (n - 1) * height; 1753 | Col(mesh, colors[i]); 1754 | Vert(mesh, x + width, y + off); 1755 | Vert(mesh, x, y + off); 1756 | if (i < n - 1) { 1757 | Face(mesh, s + 0, s + 1, s + 2); 1758 | Face(mesh, s + 1, s + 3, s + 2); 1759 | } 1760 | } 1761 | End(mesh); 1762 | } 1763 | 1764 | void ColToFlts(int c, float* floats) { 1765 | floats[0] = ((c & 0x00ff0000) >> 16) / 255.0f; 1766 | floats[1] = ((c & 0x0000ff00) >> 8) / 255.0f; 1767 | floats[2] = ((c & 0x000000ff) >> 0) / 255.0f; 1768 | floats[3] = ((c & 0xff000000) >> 24) / 255.0f; 1769 | } 1770 | 1771 | int FltsToCol(float* f) { 1772 | int color = 0; 1773 | color |= (int)(f[0] * 255.0f) << 16; 1774 | color |= (int)(f[1] * 255.0f) << 8; 1775 | color |= (int)(f[2] * 255.0f) << 0; 1776 | color |= (int)(f[3] * 255.0f) << 24; 1777 | return color; 1778 | } 1779 | 1780 | char* ToB64(void* vdata, int dataSize) { 1781 | static char* charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 1782 | int i, len = (dataSize / 3 + 1) * 4 + 1; 1783 | char* result = Alloc(len); 1784 | char* p = result; 1785 | unsigned char* data = vdata; 1786 | for (i = 0; i < dataSize; i += 3) { 1787 | p = &result[i / 3 * 4]; 1788 | switch (dataSize - i) { 1789 | default: /* abusing fallthrough because it's fun */ 1790 | case 3: 1791 | p[3] = charset[data[i + 2] & 0x3f]; 1792 | p[2] = ((data[i + 1] & 0x0f) << 2) | ((data[i + 2] & 0xc0) >> 6); 1793 | case 2: 1794 | p[2] = charset[p[2] | (data[i + 1] & 0x0f) << 2]; 1795 | p[1] = ((data[i] & 0x03) << 4) | ((data[i + 1] & 0xf0) >> 4); 1796 | case 1: 1797 | p[1] = charset[p[1] | (data[i] & 0x03) << 4]; 1798 | p[0] = charset[(data[i] & 0xfc) >> 2]; 1799 | } 1800 | } 1801 | if (!p[2]) p[2] = '='; 1802 | if (!p[3]) p[3] = '='; 1803 | return result; 1804 | } 1805 | 1806 | #define NLETTERS ('Z' - 'A' + 1) 1807 | 1808 | static char DecB64(char c) { 1809 | if (c >= 'A' && c <= 'Z') { 1810 | c -= 'A'; 1811 | } else if (c >= 'a' && c <= 'z') { 1812 | c = c - 'a' + NLETTERS; 1813 | } else if (c >= '0' && c <= '9') { 1814 | c = c - '0' + NLETTERS * 2; 1815 | } else if (c == '+') { 1816 | c = NLETTERS * 2 + 10; 1817 | } else if (c == '/') { 1818 | c = NLETTERS * 2 + 10 + 1; 1819 | } else { 1820 | c = 0; 1821 | } 1822 | return c; 1823 | } 1824 | 1825 | char* ArrFromB64(char* data) { 1826 | char* result = 0; 1827 | int len = StrLen(data), i; 1828 | for (i = 0; i < len; i += 4) { 1829 | int chunkLen = Min(3, len - i); 1830 | char* p = ArrAlloc(&result, chunkLen); 1831 | MemSet(p, 0, chunkLen); 1832 | switch (len - i) { 1833 | default: 1834 | case 4: 1835 | p[2] |= DecB64(data[i + 3]); /* 6 bits */ 1836 | case 3: 1837 | p[2] |= (DecB64(data[i + 2]) << 6); /* 2 bits */ 1838 | p[1] |= DecB64(data[i + 2]) >> 2; /* 4 bits */ 1839 | case 2: 1840 | p[1] |= (DecB64(data[i + 1]) << 4); /* 4 bits */ 1841 | p[0] |= DecB64(data[i + 1]) >> 4; /* 2 bits */ 1842 | case 1: 1843 | p[0] |= DecB64(data[i]) << 2; /* 6 bits */ 1844 | } 1845 | } 1846 | return result; 1847 | } 1848 | 1849 | char* ArrToB64(char* data) { 1850 | return ToB64(data, ArrLen(data)); 1851 | } 1852 | 1853 | char* ArgbToOneBpp(int* pixs, int numPixs) { 1854 | char* data = 0; 1855 | int i, j; 1856 | for (i = 0; i < numPixs;) { 1857 | char b = 0; 1858 | for (j = 0; j < 8 && i < numPixs; ++j, ++i) { 1859 | if ((pixs[i] & 0xff000000) != 0xff000000) { 1860 | b |= 1 << (7 - j); 1861 | } 1862 | } 1863 | ArrCat(&data, b); 1864 | } 1865 | return data; 1866 | } 1867 | 1868 | char* ArgbArrToOneBpp(int* pixs) { 1869 | return ArgbToOneBpp(pixs, ArrLen(pixs)); 1870 | } 1871 | 1872 | int* OneBppToArgb(char* data, int numBytes) { 1873 | int* result = 0; 1874 | int i, j; 1875 | for (i = 0; i < numBytes; ++i) { 1876 | for (j = 0; j < 8; ++j) { 1877 | if (data[i] & (1 << (7 - j))) { 1878 | ArrCat(&result, 0x00000000); 1879 | } else { 1880 | ArrCat(&result, 0xff000000); 1881 | } 1882 | } 1883 | } 1884 | return result; 1885 | } 1886 | 1887 | int* OneBppArrToArgb(char* data) { 1888 | return OneBppToArgb(data, ArrLen(data)); 1889 | } 1890 | 1891 | int Mix(int a, int b, float amount) { 1892 | float fa[4], fb[4]; 1893 | ColToFlts(a, fa); 1894 | ColToFlts(b, fb); 1895 | LerpFlts(4, fa, fb, fa, amount); 1896 | ClampFlts(4, fa, fa, 0, 1); 1897 | return FltsToCol(fa); 1898 | } 1899 | 1900 | static int MulScalarUnchecked(int color, float scalar) { 1901 | float rgba[4]; 1902 | ColToFlts(color, rgba); 1903 | MulFltsScalar(3, rgba, rgba, scalar); 1904 | return FltsToCol(rgba); 1905 | } 1906 | 1907 | int MulScalar(int color, float scalar) { 1908 | float rgba[4]; 1909 | ColToFlts(color, rgba); 1910 | MulFltsScalar(3, rgba, rgba, scalar); 1911 | ClampFlts(3, rgba, rgba, 0, 1); 1912 | return FltsToCol(rgba); 1913 | } 1914 | 1915 | int Add(int a, int b) { 1916 | float fa[4], fb[4]; 1917 | ColToFlts(a, fa); 1918 | ColToFlts(b, fb); 1919 | AddFlts(4, fa, fb, fa); 1920 | ClampFlts(4, fa, fa, 0, 1); 1921 | return FltsToCol(fa); 1922 | } 1923 | 1924 | int AlphaBlend(int src, int dst) { 1925 | int result; 1926 | float d[4], s[4], da, sa; 1927 | float outAlpha; 1928 | ColToFlts(dst, d); 1929 | ColToFlts(src, s); 1930 | sa = s[3] = 1 - s[3]; /* engine uses inverted alpha, but here we need the real alpha */ 1931 | da = d[3] = 1 - d[3]; 1932 | /* 1933 | * https://en.wikipedia.org/wiki/Alpha_compositing 1934 | * 1935 | * outAlpha = srcAlpha + dstAlpha * (1 - srcAlpha) 1936 | * finalRGB = (srcRGB * srcAlpha + dstRGB * dstAlpha * (1 - srcAlpha)) / outAlpha 1937 | */ 1938 | outAlpha = sa + da * (1 - sa); 1939 | src = MulScalarUnchecked(src & 0xffffff, sa); 1940 | dst = MulScalarUnchecked(dst & 0xffffff, da); 1941 | result = src + MulScalarUnchecked(dst, 1 - sa); 1942 | result = MulScalarUnchecked(result, 1.0f / outAlpha); 1943 | result |= (int)((1 - outAlpha) * 255) << 24; /* invert alpha back */ 1944 | return result; 1945 | } 1946 | 1947 | void AlphaBlendp(int* dst, int src) { 1948 | *dst = AlphaBlend(src, *dst); 1949 | } 1950 | 1951 | /* ---------------------------------------------------------------------------------------------- */ 1952 | 1953 | struct _Ft { 1954 | ImgPtr img; 1955 | int charWidth, charHeight; 1956 | }; 1957 | 1958 | Ft MkFtFromSimpleGrid(int* pixs, int width, int height, int charWidth, int charHeight) { 1959 | Ft ft = Alloc(sizeof(struct _Ft)); 1960 | if (ft) { 1961 | int* decolored = 0; 1962 | int i; 1963 | ft->img = ImgAlloc(width, height); 1964 | for (i = 0; i < width * height; ++i) { 1965 | /* we want the base pixs to be white so it can be colored */ 1966 | ArrCat(&decolored, pixs[i] | 0xffffff); 1967 | } 1968 | ImgCpy(ft->img, decolored, width, height); 1969 | RmArr(decolored); 1970 | ft->charWidth = charWidth; 1971 | ft->charHeight = charHeight; 1972 | } 1973 | return ft; 1974 | } 1975 | 1976 | Ft MkFtFromSimpleGridFile(char* filePath, int charWidth, int charHeight) { 1977 | Spr spr = MkSprFromFile(filePath); 1978 | if (spr) { 1979 | Ft ft; 1980 | int* imgData = SprToArgbArr(spr); 1981 | int width = SprWidth(spr), height = SprHeight(spr); 1982 | ft = MkFtFromSimpleGrid(imgData, width, height, charWidth, charHeight); 1983 | RmArr(imgData); 1984 | RmSpr(spr); 1985 | return ft; 1986 | } 1987 | return 0; 1988 | } 1989 | 1990 | void RmFt(Ft ft) { 1991 | if (ft) { 1992 | ImgFree(ft->img); 1993 | } 1994 | Free(ft); 1995 | } 1996 | 1997 | ImgPtr FtImg(Ft ft) { return ft->img; } 1998 | 1999 | void FtMesh(Mesh mesh, Ft ft, int x, int y, char* string) { 2000 | char c; 2001 | int left = x; 2002 | int width = ft->charWidth; 2003 | int height = ft->charHeight; 2004 | for (; (c = *string); ++string) { 2005 | if (c >= 0x20) { 2006 | int u = ((c - 0x20) % 0x20) * width; 2007 | int v = ((c - 0x20) / 0x20) * height; 2008 | ImgQuad(mesh, x, y, u, v, width, height, width, height); 2009 | x += width; 2010 | } else if (c == '\n') { 2011 | y += height; 2012 | x = left; 2013 | } 2014 | } 2015 | } 2016 | 2017 | void PutFt(Ft ft, int col, int x, int y, char* string) { 2018 | Mesh mesh = MkMesh(); 2019 | Col(mesh, col); 2020 | FtMesh(mesh, ft, x, y, string); 2021 | PutMesh(mesh, 0, FtImg(ft)); 2022 | RmMesh(mesh); 2023 | } 2024 | 2025 | /* ---------------------------------------------------------------------------------------------- */ 2026 | 2027 | float Lerp(float a, float b, float amount) { 2028 | return a * (1 - amount) + b * amount; 2029 | } 2030 | 2031 | void LerpFlts(int n, float* a, float* b, float* c, float amount) { 2032 | int i; 2033 | for (i = 0; i < n; ++i) { 2034 | c[i] = Lerp(a[i], b[i], amount); 2035 | } 2036 | } 2037 | 2038 | void MulFltsScalar(int n, float* floats, float* result, float scalar) { 2039 | int i; 2040 | for (i = 0; i < n; ++i) { 2041 | result[i] = floats[i] * scalar; 2042 | } 2043 | } 2044 | 2045 | void FloorFlts(int n, float* floats, float* result) { 2046 | int i; 2047 | for (i = 0; i < n; ++i) { 2048 | result[i] = Floor(floats[i]); 2049 | } 2050 | } 2051 | 2052 | void ClampFlts(int n, float* floats, float* result, float min, float max) { 2053 | int i; 2054 | for (i = 0; i < n; ++i) { 2055 | result[i] = Clamp(floats[i], min, max); 2056 | } 2057 | } 2058 | 2059 | void AddFlts(int n, float* a, float* b, float* result) { 2060 | int i; 2061 | for (i = 0; i < n; ++i) { 2062 | result[i] = a[i] + b[i]; 2063 | } 2064 | } 2065 | 2066 | void CpyRect(float* dst, float* src) { 2067 | SetRect(dst, src[0], src[1], src[2], src[3]); 2068 | } 2069 | 2070 | void SetRect(float* rect, float left, float right, float top, float bot) { 2071 | rect[0] = left; 2072 | rect[1] = right; 2073 | rect[2] = top; 2074 | rect[3] = bot; 2075 | } 2076 | 2077 | void SetRectPos(float* rect, float x, float y) { 2078 | rect[1] = x + RectWidth(rect); 2079 | rect[3] = y + RectHeight(rect); 2080 | rect[0] = x; 2081 | rect[2] = y; 2082 | } 2083 | 2084 | void SetRectSize(float* rect, float width, float height) { 2085 | rect[1] = RectX(rect) + width; 2086 | rect[3] = RectY(rect) + height; 2087 | } 2088 | 2089 | void NormRect(float* rect) { 2090 | if (rect[0] > rect[1]) { 2091 | SwpFlts(&rect[0], &rect[1]); 2092 | } 2093 | if (rect[2] > rect[3]) { 2094 | SwpFlts(&rect[2], &rect[3]); 2095 | } 2096 | } 2097 | 2098 | void ClampRect(float* rect, float* other) { 2099 | rect[0] = Min(Max(rect[0], other[0]), other[1]); 2100 | rect[1] = Min(Max(rect[1], other[0]), other[1]); 2101 | rect[2] = Min(Max(rect[2], other[2]), other[3]); 2102 | rect[3] = Min(Max(rect[3], other[2]), other[3]); 2103 | } 2104 | 2105 | int PtInRect(float* rect, float x, float y) { 2106 | return x >= rect[0] && x < rect[1] && y >= rect[2] && y < rect[3]; 2107 | } 2108 | 2109 | int RectSect(float* a, float* b) { 2110 | return a[0] < b[1] && a[1] >= b[0] && a[2] < b[3] && a[3] >= b[2]; 2111 | } 2112 | 2113 | int RectInRect(float* needle, float* haystack) { 2114 | return ( 2115 | needle[0] >= haystack[0] && needle[1] <= haystack[1] && 2116 | needle[2] >= haystack[2] && needle[3] <= haystack[3] 2117 | ); 2118 | } 2119 | 2120 | int RectInRectArea(float* needle, float* haystack) { 2121 | return ( 2122 | RectWidth(needle) <= RectWidth(haystack) && 2123 | RectHeight(needle) <= RectHeight(haystack) 2124 | ); 2125 | } 2126 | 2127 | void SetRectLeft(float* rect, float left) { rect[0] = left; } 2128 | void SetRectRight(float* rect, float right) { rect[1] = right; } 2129 | void SetRectTop(float* rect, float top) { rect[2] = top; } 2130 | void SetRectBot(float* rect, float bot) { rect[3] = bot; } 2131 | float RectWidth(float* rect) { return rect[1] - rect[0]; } 2132 | float RectHeight(float* rect) { return rect[3] - rect[2]; } 2133 | float RectX(float* rect) { return rect[0]; } 2134 | float RectY(float* rect) { return rect[2]; } 2135 | float RectLeft(float* rect) { return rect[0]; } 2136 | float RectRight(float* rect) { return rect[1]; } 2137 | float RectTop(float* rect) { return rect[2]; } 2138 | float RectBot(float* rect) { return rect[3]; } 2139 | 2140 | /* ---------------------------------------------------------------------------------------------- */ 2141 | 2142 | /* memory layout: 2143 | * float left_axis[4]; float up_axis[4]; float fwd_axis[4]; float translation[4]; */ 2144 | 2145 | struct _Mat { float m[16]; }; 2146 | 2147 | Mat MkMat() { 2148 | Mat mat = Alloc(sizeof(struct _Mat)); 2149 | if (mat) { 2150 | mat->m[0] = mat->m[5] = mat->m[10] = mat->m[15] = 1; 2151 | } 2152 | return mat; 2153 | } 2154 | 2155 | void RmMat(Mat mat) { 2156 | Free(mat); 2157 | } 2158 | 2159 | Mat DupMat(Mat source) { 2160 | return SetMat(MkMat(), source->m); 2161 | } 2162 | 2163 | /* these return mat for convienience. it's not actually a copy */ 2164 | Mat SetIdentity(Mat mat) { 2165 | MemSet(mat->m, 0, sizeof(mat->m)); 2166 | mat->m[0] = mat->m[5] = mat->m[10] = mat->m[15] = 1; 2167 | return mat; 2168 | } 2169 | 2170 | Mat SetMat(Mat mat, float* matIn) { 2171 | if (mat) { 2172 | MemCpy(mat->m, matIn, sizeof(mat->m)); 2173 | } 2174 | return mat; 2175 | } 2176 | 2177 | Mat GetMat(Mat mat, float* matOut) { 2178 | MemCpy(matOut, mat->m, sizeof(mat->m)); 2179 | return mat; 2180 | } 2181 | 2182 | #define MAT_IDENT { \ 2183 | 1, 0, 0, 0, \ 2184 | 0, 1, 0, 0, \ 2185 | 0, 0, 1, 0, \ 2186 | 0, 0, 0, 1 \ 2187 | } 2188 | 2189 | float* MatFlts(Mat mat) { 2190 | static float identity[16] = MAT_IDENT; 2191 | return mat ? mat->m : identity; 2192 | } 2193 | 2194 | Mat Scale(Mat mat, float x, float y) { 2195 | float m[16] = MAT_IDENT; 2196 | m[0] = x; 2197 | m[5] = y; 2198 | return MulMatFlt(mat, m); 2199 | } 2200 | 2201 | Mat Scale1(Mat mat, float scale) { 2202 | return Scale(mat, scale, scale); 2203 | } 2204 | 2205 | Mat Pos(Mat mat, float x, float y) { 2206 | float m[16] = MAT_IDENT; 2207 | m[12] = x; 2208 | m[13] = y; 2209 | return MulMatFlt(mat, m); 2210 | } 2211 | 2212 | Mat Rot(Mat mat, float deg) { 2213 | /* 2d rotation = z axis rotation. this means we rotate the left and up axes */ 2214 | float s = Sin(deg); 2215 | float m[16] = MAT_IDENT; 2216 | m[0] = m[5] = Cos(deg); 2217 | m[1] = s; 2218 | m[4] = -s; 2219 | return MulMatFlt(mat, m); 2220 | } 2221 | 2222 | Mat MulMat(Mat mat, Mat other) { 2223 | return MulMatFlt(mat, other->m); 2224 | } 2225 | 2226 | Mat MulMatFlt(Mat mat, float* matIn) { 2227 | Mat tmp = MkMulMatFlt(mat, matIn); 2228 | SetMat(mat, tmp->m); 2229 | RmMat(tmp); 2230 | return mat; 2231 | } 2232 | 2233 | Mat MkMulMat(Mat matA, Mat matB) { 2234 | return MkMulMatFlt(matA, matB->m); 2235 | } 2236 | 2237 | Mat MkMulMatFlt(Mat matA, float* b) { 2238 | Mat res = MkMat(); 2239 | float* a = matA->m; 2240 | float* m = res->m; 2241 | m[ 0] = b[ 0] * a[0] + b[ 1] * a[4] + b[ 2] * a[ 8] + b[ 3] * a[12]; 2242 | m[ 1] = b[ 0] * a[1] + b[ 1] * a[5] + b[ 2] * a[ 9] + b[ 3] * a[13]; 2243 | m[ 2] = b[ 0] * a[2] + b[ 1] * a[6] + b[ 2] * a[10] + b[ 3] * a[14]; 2244 | m[ 3] = b[ 0] * a[3] + b[ 1] * a[7] + b[ 2] * a[11] + b[ 3] * a[15]; 2245 | m[ 4] = b[ 4] * a[0] + b[ 5] * a[4] + b[ 6] * a[ 8] + b[ 7] * a[12]; 2246 | m[ 5] = b[ 4] * a[1] + b[ 5] * a[5] + b[ 6] * a[ 9] + b[ 7] * a[13]; 2247 | m[ 6] = b[ 4] * a[2] + b[ 5] * a[6] + b[ 6] * a[10] + b[ 7] * a[14]; 2248 | m[ 7] = b[ 4] * a[3] + b[ 5] * a[7] + b[ 6] * a[11] + b[ 7] * a[15]; 2249 | m[ 8] = b[ 8] * a[0] + b[ 9] * a[4] + b[10] * a[ 8] + b[11] * a[12]; 2250 | m[ 9] = b[ 8] * a[1] + b[ 9] * a[5] + b[10] * a[ 9] + b[11] * a[13]; 2251 | m[10] = b[ 8] * a[2] + b[ 9] * a[6] + b[10] * a[10] + b[11] * a[14]; 2252 | m[11] = b[ 8] * a[3] + b[ 9] * a[7] + b[10] * a[11] + b[11] * a[15]; 2253 | m[12] = b[12] * a[0] + b[13] * a[4] + b[14] * a[ 8] + b[15] * a[12]; 2254 | m[13] = b[12] * a[1] + b[13] * a[5] + b[14] * a[ 9] + b[15] * a[13]; 2255 | m[14] = b[12] * a[2] + b[13] * a[6] + b[14] * a[10] + b[15] * a[14]; 2256 | m[15] = b[12] * a[3] + b[13] * a[7] + b[14] * a[11] + b[15] * a[15]; 2257 | return res; 2258 | } 2259 | 2260 | void TransPt(Mat mat, float* point) { 2261 | float mPt[16]; 2262 | Mat tmp; 2263 | MemSet(mPt, 0, sizeof(mPt)); 2264 | MemCpy(mPt, point, sizeof(float) * 2); 2265 | mPt[3] = 1; 2266 | tmp = MkMulMatFlt(mat, mPt); 2267 | MemCpy(point, tmp->m, sizeof(float) * 2); 2268 | RmMat(tmp); 2269 | } 2270 | 2271 | void InvTransPt(Mat mat, float* point) { 2272 | /* https://en.wikibooks.org/wiki/GLSL_Programming/Applying_Matrix_Transformations#Transforming_Pts_with_the_Inverse_Matrix */ 2273 | Mat tmp = DupMat(mat); 2274 | float* m = tmp->m; 2275 | point[0] -= m[12]; 2276 | point[1] -= m[13]; 2277 | m[12] = m[13] = m[14] = m[15] = m[3] = m[7] = m[11] = 0; /* equivalent of taking the 3x3 mat */ 2278 | TransPt(tmp, point); 2279 | RmMat(tmp); 2280 | } 2281 | 2282 | /* ---------------------------------------------------------------------------------------------- */ 2283 | 2284 | struct _Spr { 2285 | int width, height; 2286 | int* palette; 2287 | char* data; 2288 | }; 2289 | 2290 | #define SPR_DATA 0xd 2291 | #define SPR_REPEAT 0xe 2292 | 2293 | /* TODO: maybe use hashmap to make palette lookup faster */ 2294 | static int PaletteIndex(int** palette, int color) { 2295 | int i; 2296 | for (i = 0; i < ArrLen(*palette); ++i) { 2297 | if ((*palette)[i] == color) { return i; } 2298 | } 2299 | ArrCat(palette, color); 2300 | return ArrLen(*palette) - 1; 2301 | } 2302 | 2303 | void CatSprHdr(char** pArr, int formatVersion, int width, int height, int* palette) { 2304 | int i; 2305 | ArrStrCat(pArr, "WBSP"); 2306 | CatVarI32(pArr, formatVersion); 2307 | CatVarI32(pArr, width); 2308 | CatVarI32(pArr, height); 2309 | CatVarI32(pArr, ArrLen(palette)); 2310 | for (i = 0; i < ArrLen(palette); ++i) { 2311 | CatI32(pArr, palette[i]); 2312 | } 2313 | } 2314 | 2315 | char* ArgbToSprArr(int* argb, int width, int height) { 2316 | char* spr = 0; 2317 | int* palette = 0; 2318 | int i; 2319 | for (i = 0; i < width * height; ++i) { 2320 | PaletteIndex(&palette, argb[i]); 2321 | } 2322 | CatSprHdr(&spr, 1, width, height, palette); 2323 | for (i = 0; i < width * height;) { 2324 | int start; 2325 | for (start = i; i < width * height && argb[i] == argb[start]; ++i); 2326 | if (i - start > 1) { 2327 | CatVarI32(&spr, SPR_REPEAT); 2328 | CatVarI32(&spr, i - start); 2329 | CatVarI32(&spr, PaletteIndex(&palette, argb[start])); 2330 | } else { 2331 | i = start + 1; 2332 | for (; i < width * height && argb[i] != argb[i - 1]; ++i); 2333 | CatVarI32(&spr, SPR_DATA); 2334 | CatVarI32(&spr, i - start); 2335 | for (; start < i; ++start) { 2336 | CatVarI32(&spr, PaletteIndex(&palette, argb[start])); 2337 | } 2338 | } 2339 | } 2340 | RmArr(palette); 2341 | return spr; 2342 | } 2343 | 2344 | int SprWidth(Spr spr) { 2345 | return spr->width; 2346 | } 2347 | 2348 | int SprHeight(Spr spr) { 2349 | return spr->height; 2350 | } 2351 | 2352 | Spr MkSpr(char* data, int length) { 2353 | char* p = data; 2354 | int paletteSize, i, dataLen; 2355 | Spr spr = Alloc(sizeof(struct _Spr)); 2356 | if (MemCmp(p, "WBSP", 4)) { 2357 | /* TODO: error codes or something */ 2358 | return 0; 2359 | } 2360 | p += 4; 2361 | DecVarI32(&p); /* format version */ 2362 | spr->width = DecVarI32(&p); 2363 | spr->height = DecVarI32(&p); 2364 | paletteSize = DecVarI32(&p); 2365 | for (i = 0; i < paletteSize; ++i) { 2366 | ArrCat(&spr->palette, DecI32(&p)); 2367 | } 2368 | dataLen = length - (p - data); 2369 | ArrAlloc(&spr->data, dataLen); 2370 | MemCpy(spr->data, p, dataLen); 2371 | return spr; 2372 | } 2373 | 2374 | Spr MkSprFromArr(char* data) { 2375 | return MkSpr(data, ArrLen(data)); 2376 | } 2377 | 2378 | Spr MkSprFromFile(char* filePath) { 2379 | int len = 1024000; /* TODO: handle bigger files */ 2380 | char* data = Alloc(len); 2381 | Spr res; 2382 | len = RdFile(filePath, data, len); 2383 | res = len >= 0 ? MkSpr(data, len) : 0; 2384 | Free(data); 2385 | return res; 2386 | } 2387 | 2388 | void SprToArgb(Spr spr, int* argb) { 2389 | char* p = spr->data; 2390 | int* pix = argb; 2391 | while (p - spr->data < ArrLen(spr->data)) { 2392 | int i; 2393 | int type = DecVarI32(&p); 2394 | int length = DecVarI32(&p); 2395 | switch (type) { 2396 | case SPR_DATA: { 2397 | for (i = 0; i < length; ++i) { 2398 | *pix++ = spr->palette[DecVarI32(&p)]; 2399 | } 2400 | break; 2401 | } 2402 | case SPR_REPEAT: { 2403 | int color = spr->palette[DecVarI32(&p)]; 2404 | for (i = 0; i < length; ++i) { 2405 | *pix++ = color; 2406 | } 2407 | } 2408 | } 2409 | } 2410 | } 2411 | 2412 | int* SprToArgbArr(Spr spr) { 2413 | int* res = 0; 2414 | ArrAlloc(&res, spr->width * spr->height); 2415 | SprToArgb(spr, res); 2416 | return res; 2417 | } 2418 | 2419 | void RmSpr(Spr spr) { 2420 | if (spr) { 2421 | RmArr(spr->palette); 2422 | RmArr(spr->data); 2423 | } 2424 | Free(spr); 2425 | } 2426 | 2427 | /* ---------------------------------------------------------------------------------------------- */ 2428 | 2429 | typedef struct _ImgPage { 2430 | Img img; 2431 | int* pixs; 2432 | Packer pak; 2433 | int flags; 2434 | } ImgPage; 2435 | 2436 | typedef struct _ImgRegion { 2437 | int page; 2438 | float r[4]; 2439 | } ImgRegion; 2440 | 2441 | static struct _Globals { 2442 | /* params */ 2443 | char* name; 2444 | char* class; 2445 | int pageSize; 2446 | 2447 | int argc; 2448 | char** argv; 2449 | Wnd wnd; 2450 | AppHandler* handlers[LAST_MSG_TYPE]; 2451 | int flags; 2452 | Ft ft; 2453 | 2454 | /* img allocator */ 2455 | ImgPage* pages; 2456 | ImgRegion* regions; 2457 | float flushTimer; 2458 | 2459 | int diagPage; 2460 | } app; 2461 | 2462 | Wnd AppWnd() { return app.wnd; } 2463 | float Delta() { return WndDelta(app.wnd); } 2464 | int Argc() { return app.argc; } 2465 | char* Argv(int i) { return app.argv[i]; } 2466 | 2467 | void On(int msg, AppHandler handler) { 2468 | if (msg >= 0 && msg < LAST_MSG_TYPE) { 2469 | ArrCat(&app.handlers[msg], handler); 2470 | } 2471 | } 2472 | 2473 | static void Nop() { } 2474 | 2475 | static void PruneHandlers() { 2476 | if (app.flags & DIRTY) { 2477 | int i, msg; 2478 | for (msg = 0; msg < LAST_MSG_TYPE; ++msg) { 2479 | AppHandler* handlers = app.handlers[msg]; 2480 | AppHandler* newHandlers = 0; 2481 | for (i = 0; i < ArrLen(handlers); ++i) { 2482 | if (handlers[i] != Nop) { 2483 | ArrCat(&newHandlers, handlers[i]); 2484 | } 2485 | } 2486 | RmArr(handlers); 2487 | app.handlers[msg] = newHandlers; 2488 | } 2489 | app.flags &= ~DIRTY; 2490 | } 2491 | } 2492 | 2493 | /* we cannot change the size of handlers because this could be called while iterating them. 2494 | * so we set a dirty flags that tells it to recompact the handlers array after we are done handling 2495 | * this msg */ 2496 | void RmHandler(int msg, AppHandler handler) { 2497 | if (msg >= 0 && msg < LAST_MSG_TYPE) { 2498 | int i; 2499 | AppHandler* handlers = app.handlers[msg]; 2500 | for (i = 0; i < ArrLen(handlers); ++i) { 2501 | if (handlers[i] == handler) { 2502 | handlers[i] = Nop; 2503 | app.flags |= DIRTY; 2504 | } 2505 | } 2506 | } 2507 | } 2508 | 2509 | void SetAppName(char* name) { app.name = name; } 2510 | void SetAppClass(char* class) { app.class = class; } 2511 | void SetAppPageSize(int pageSize) { app.pageSize = pageSize; } 2512 | 2513 | 2514 | static void AppHandle(int msg) { 2515 | AppHandler* handlers = app.handlers[msg]; 2516 | int i; 2517 | for (i = 0; i < ArrLen(handlers); ++i) { 2518 | handlers[i](); 2519 | } 2520 | PruneHandlers(); 2521 | } 2522 | 2523 | typedef struct _FtData { 2524 | int width, height, charWidth, charHeight; 2525 | int* pixs; 2526 | } FtData; 2527 | 2528 | static void DefFtData(FtData* ft) { 2529 | /* generated from ft.wbspr using the SpriteEditor */ 2530 | static char* b64Data = 2531 | "AIUQIQYIIIAAAAAEcIccE+c+ccAAAAAcAIUUcokIQEIIAAAEiYiiMgiCiiIIAAAiAIU0oSoAQEqIAAAImICCUggEiiAAEA" 2532 | "QCAIAecMQAQEc+AcAIqIEMk88EceAAIcIEAIA0KQqAQEqIAAAQyIICkCiIiCAAQAEIAIAe8kkAQEIIAAAQiIQC+CiIiCAA" 2533 | "IcIIAAAUIKkAQEAAEAIgiIgiECiQiiIIEAQAAIAEIEaAIIAAIAAgcc+cE8cQccAQAAAIAAAAAAAAAAAAAAAAAAAAAAAAAA" 2534 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcc8e8++cicOigiic8c8e+iii" 2535 | "ii+MQYIAmiigiggiiIEig2yiiiigIiiiiiCIQIUAqiigigggiIEkgqqiiiigIiiiUUEIIIiAq+8gi88u+IE4gimi8i8cIi" 2536 | "iqIIIIIIAAsiigiggiiIEkgiiigqiCIiiqUIQIIIAAiiigiggiiIEigiiigmiCIii2iIgIEIAAiiigiggiiIkigiiigiiC" 2537 | "IiUiiIgIEIAAci8e8+gcicYi+iicgci8IcIiiI+MEYA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 2538 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAgACAMAgIIgYAAAAAAAQAAAAAAEIIAAQAgACAQAgAAgIAAA" 2539 | "AAAAQAAAAAAIIEAAIc8eecQe8YYkI08c8e8e8iiiii+IIEAAACigii8iiIIkIqiiiiigQiiiUiEIIEYAAeigi+QiiII4Iq" 2540 | "iiiigcQiiiIiIQICqIAiigigQiiIIkIqiiiigCQiiqUiQIIEMAAiigigQiiIIiIiiiiigCQiUqiigIIEAAAe8eeeQeicIi" 2541 | "ciic8eg8MeIUie+IIEAAAAAAAAACAAIAAAAAgCAAAAAAACAEIIAAAAAAAAAcAAQAAAAAgCAAAAAAAcAAAAAAAAAAAAAAAA" 2542 | "AAAAAAAAAAAAAAAAAAAAAA"; 2543 | char* data = ArrFromB64(b64Data); 2544 | int i; 2545 | ft->pixs = OneBppArrToArgb(data); 2546 | for (i = 0; i < ArrLen(ft->pixs); ++i) { 2547 | ft->pixs[i] |= 0xffffff; /* make all pixs white */ 2548 | } 2549 | ft->charWidth = 6; 2550 | ft->charHeight = 11; 2551 | ft->width = ft->charWidth * 0x20; 2552 | ft->height = ft->charHeight * 3; 2553 | RmArr(data); 2554 | } 2555 | 2556 | static Ft MkDefFt() { 2557 | FtData ftd; 2558 | Ft ft; 2559 | DefFtData(&ftd); 2560 | ft = MkFtFromSimpleGrid(ftd.pixs, ftd.width, ftd.height, ftd.charWidth, ftd.charHeight); 2561 | RmArr(ftd.pixs); 2562 | return ft; 2563 | } 2564 | 2565 | /* for when the ImgPtr is invalidated and we don't wanna invalidate ptrs to this Ft */ 2566 | static void RefreshFt(Ft ft) { 2567 | FtData ftd; 2568 | DefFtData(&ftd); 2569 | ft->img = ImgAlloc(ftd.width, ftd.height); 2570 | ImgCpy(ft->img, ftd.pixs, ftd.width, ftd.height); 2571 | RmArr(ftd.pixs); 2572 | } 2573 | 2574 | Ft DefFt() { return app.ft; } 2575 | 2576 | static void NukeImgs() { 2577 | int i; 2578 | for (i = 0; i < ArrLen(app.pages); ++i) { 2579 | ImgPage* page = &app.pages[i]; 2580 | RmImg(page->img); 2581 | RmPacker(page->pak); 2582 | Free(page->pixs); 2583 | } 2584 | RmArr(app.pages); 2585 | RmArr(app.regions); 2586 | app.pages = 0; 2587 | app.regions = 0; 2588 | } 2589 | 2590 | void InitAppImgs() { 2591 | if (!app.ft) { 2592 | app.ft = MkDefFt(); 2593 | } else { 2594 | RefreshFt(app.ft); 2595 | } 2596 | } 2597 | 2598 | void ClrImgs() { 2599 | NukeImgs(); 2600 | InitAppImgs(); 2601 | } 2602 | 2603 | void MkApp(int argc, char* argv[]) { 2604 | app.argc = argc; 2605 | app.argv = argv; 2606 | app.pageSize = app.pageSize ? RoundUpToPowerOfTwo(app.pageSize) : 1024; 2607 | if (!app.name) { app.name = "WeebCore"; } 2608 | if (!app.class) { app.class = "WeebCore"; } 2609 | app.wnd = MkWnd(app.name, app.class); 2610 | app.flags |= RUNNING; 2611 | InitAppImgs(); 2612 | AppHandle(INIT); 2613 | FlushImgs(); 2614 | } 2615 | 2616 | void RmApp() { 2617 | int i; 2618 | AppHandle(QUIT); 2619 | NukeImgs(); 2620 | for (i = 0; i < LAST_MSG_TYPE; ++i) { 2621 | RmArr(app.handlers[i]); 2622 | } 2623 | } 2624 | 2625 | int AppHandleMsg() { 2626 | int msg = MsgType(app.wnd); 2627 | AppHandle(msg); 2628 | if (msg == QUIT_REQUEST) { 2629 | app.flags &= ~RUNNING; 2630 | return 0; 2631 | } 2632 | return 1; 2633 | } 2634 | 2635 | void AppFrame() { 2636 | AppHandle(FRAME); 2637 | app.flushTimer += Delta(); 2638 | if (app.flushTimer >= 1) { 2639 | FlushImgs(); 2640 | app.flushTimer = 0; 2641 | } 2642 | } 2643 | 2644 | int AppRunning() { return app.flags & RUNNING; } 2645 | 2646 | int AppMain(int argc, char* argv[]) { 2647 | MkApp(argc, argv); 2648 | while (AppRunning()) { 2649 | while (NextMsg(app.wnd) && AppHandleMsg()); 2650 | AppFrame(); 2651 | SwpBufs(app.wnd); 2652 | } 2653 | RmApp(); 2654 | return 0; 2655 | } 2656 | 2657 | ImgPtr ImgAlloc(int width, int height) { 2658 | ImgRegion* region = 0; 2659 | float r[4]; 2660 | int i, pageIdx; 2661 | ImgPage* page = 0; 2662 | SetRect(r, 0, width, 0, height); 2663 | for (i = 0; i < ArrLen(app.pages); ++i) { 2664 | page = &app.pages[i]; 2665 | if (Pack(page->pak, r)) { 2666 | break; 2667 | } 2668 | } 2669 | pageIdx = i; 2670 | if (pageIdx >= ArrLen(app.pages)) { 2671 | /* no free pages, make a new page */ 2672 | page = ArrAlloc(&app.pages, 1); 2673 | page->img = MkImg(); 2674 | page->pixs = Alloc(app.pageSize * app.pageSize * sizeof(int)); 2675 | page->pak = MkPacker(app.pageSize, app.pageSize); 2676 | if (!Pack(page->pak, r)) { 2677 | return 0; 2678 | } 2679 | } 2680 | for (i = 0; i < ArrLen(app.regions); ++i) { 2681 | if (app.regions[i].page < 0) { 2682 | region = &app.regions[i]; 2683 | } 2684 | } 2685 | if (!region) { 2686 | region = ArrAlloc(&app.regions, 1); 2687 | } 2688 | if (region) { 2689 | CpyRect(region->r, r); 2690 | region->page = pageIdx; 2691 | } 2692 | return region - app.regions + 1; 2693 | } 2694 | 2695 | void ImgCpyEx(ImgPtr ptr, int* pixs, int width, int height, int dx, int dy) { 2696 | if (ptr >= 1 && ptr <= ArrLen(app.regions)) { 2697 | ImgRegion* region = &app.regions[ptr - 1]; 2698 | ImgPage* page = &app.pages[region->page]; 2699 | float* r = region->r; 2700 | int pixsStride = width; 2701 | int x, y; 2702 | int left = 0, top = 0; 2703 | int right = dx + width; 2704 | int bot = dy + height; 2705 | if (dx < 0) { left = -dx; } 2706 | if (dy < 0) { top = -dy; } 2707 | if (right > RectWidth(r)) { 2708 | width -= right - (int)RectWidth(r); 2709 | } 2710 | if (bot > RectHeight(r)) { 2711 | height -= bot - (int)RectHeight(r); 2712 | } 2713 | for (y = top; y < height; ++y) { 2714 | for (x = left; x < width; ++x) { 2715 | int dstx = RectX(r) + x + dx; 2716 | int dsty = RectY(r) + y + dy; 2717 | int* dstpix = &page->pixs[dsty * app.pageSize + dstx]; 2718 | int srcpix = pixs[y * pixsStride + x]; 2719 | if (*dstpix != srcpix) { 2720 | *dstpix = srcpix; 2721 | page->flags |= DIRTY; 2722 | } 2723 | } 2724 | } 2725 | } 2726 | } 2727 | 2728 | void ImgCpy(ImgPtr ptr, int* pixs, int width, int height) { 2729 | ImgCpyEx(ptr, pixs, width, height, 0, 0); 2730 | } 2731 | 2732 | void FlushImgs() { 2733 | int i; 2734 | for (i = 0; i < ArrLen(app.pages); ++i) { 2735 | ImgPage* page = &app.pages[i]; 2736 | if (page->flags & DIRTY) { 2737 | Pixs(page->img, app.pageSize, app.pageSize, page->pixs); 2738 | page->flags &= ~DIRTY; 2739 | } 2740 | } 2741 | } 2742 | 2743 | ImgPtr ImgFromSprFile(char* path) { 2744 | ImgPtr res = 0; 2745 | Spr spr = MkSprFromFile(path); 2746 | if (spr) { 2747 | int* pixs = SprToArgbArr(spr); 2748 | res = ImgAlloc(SprWidth(spr), SprHeight(spr)); 2749 | ImgCpy(res, pixs, SprWidth(spr), SprHeight(spr)); 2750 | RmArr(pixs); 2751 | } 2752 | RmSpr(spr); 2753 | return res; 2754 | } 2755 | 2756 | void ImgFree(ImgPtr img) { 2757 | ImgRegion* region = &app.regions[img - 1]; 2758 | PackFree(app.pages[region->page].pak, region->r); 2759 | region->page = -1; 2760 | } 2761 | 2762 | void PutMesh(Mesh mesh, Mat mat, ImgPtr ptr) { 2763 | if (ptr) { 2764 | ImgRegion* region = &app.regions[ptr - 1]; 2765 | PutMeshRawEx(mesh, mat, app.pages[region->page].img, RectX(region->r), RectY(region->r)); 2766 | } else { 2767 | PutMeshRaw(mesh, mat, 0); 2768 | } 2769 | } 2770 | 2771 | /* ---------------------------------------------------------------------------------------------- */ 2772 | 2773 | static void DiagImgAllocKeyDown() { 2774 | Wnd wnd = AppWnd(); 2775 | switch (Key(wnd)) { 2776 | case MWHEELUP: { 2777 | app.diagPage = Max(0, Min(app.diagPage + 1, ArrLen(app.pages) - 1)); 2778 | break; 2779 | } 2780 | case MWHEELDOWN: { 2781 | app.diagPage = Max(app.diagPage - 1, 0); 2782 | break; 2783 | } 2784 | } 2785 | } 2786 | 2787 | static void PutPageText() { 2788 | char* pagestr = 0; 2789 | ArrStrCat(&pagestr, "ImgAllocator Diag - page "); 2790 | ArrStrCatI32(&pagestr, app.diagPage + 1, 10); 2791 | ArrCat(&pagestr, '/'); 2792 | ArrStrCatI32(&pagestr, ArrLen(app.pages), 10); 2793 | ArrStrCat(&pagestr, ", "); 2794 | ArrStrCatI32(&pagestr, app.pageSize, 10); 2795 | ArrCat(&pagestr, 'x'); 2796 | ArrStrCatI32(&pagestr, app.pageSize, 10); 2797 | ArrCat(&pagestr, 0); 2798 | PutFt(DefFt(), 0xbebebe, 10, 10, pagestr); 2799 | RmArr(pagestr); 2800 | } 2801 | 2802 | static void DiagImgAllocFrame() { 2803 | if (ArrLen(app.pages)) { 2804 | ImgPage* page = &app.pages[app.diagPage]; 2805 | int i; 2806 | /* black background */ 2807 | Mesh mesh = MkMesh(); 2808 | Col(mesh, 0x000000); 2809 | Quad(mesh, 0, 0, app.pageSize + 10, app.pageSize + 40); 2810 | PutMesh(mesh, 0, 0); 2811 | RmMesh(mesh); 2812 | /* display entire page */ 2813 | mesh = MkMesh(); 2814 | Quad(mesh, 10, 30, app.pageSize, app.pageSize); 2815 | PutMeshRaw(mesh, 0, page->img); 2816 | RmMesh(mesh); 2817 | /* rect packer region grid */ 2818 | mesh = MkMesh(); 2819 | Col(mesh, 0x00ff00); 2820 | for (i = 0; i < ArrLen(page->pak->rects); ++i) { 2821 | float* r = page->pak->rects[i].r; 2822 | Quad(mesh, 10 + r[0], 30 + r[2], 1, RectHeight(r)); 2823 | Quad(mesh, 10 + r[1], 30 + r[2], 1, RectHeight(r)); 2824 | Quad(mesh, 10 + r[0], 30 + r[2], RectWidth(r), 1); 2825 | Quad(mesh, 10 + r[0], 30 + r[3], RectWidth(r), 1); 2826 | } 2827 | PutMeshRaw(mesh, 0, 0); 2828 | RmMesh(mesh); 2829 | } 2830 | PutPageText(); 2831 | } 2832 | 2833 | void DiagImgAlloc(int enabled) { 2834 | if (enabled) { 2835 | On(KEYDOWN, DiagImgAllocKeyDown); 2836 | On(FRAME, DiagImgAllocFrame); 2837 | } else { 2838 | RmHandler(KEYDOWN, DiagImgAllocKeyDown); 2839 | RmHandler(FRAME, DiagImgAllocFrame); 2840 | } 2841 | } 2842 | 2843 | #endif /* WEEBCORE_IMPLEMENTATION */ 2844 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir="$(dirname "$0")" 4 | cd "$dir" || exit 5 | abspath="$(realpath "$dir")" 6 | 7 | mkdir -p ./bin >/dev/null 2>&1 8 | mkdir -p ./obj >/dev/null 2>&1 9 | 10 | . ./cflags.sh 11 | 12 | # /path/to/meme.c -> meme 13 | exename() { 14 | basename "$1" | rev | cut -d. -f2- | rev | cut -d_ -f2- 15 | } 16 | 17 | compile() { 18 | ${cc:-gcc} $cflags "$@" $ldflags -lGL -lX11 -lm 19 | } 20 | 21 | case "$1" in 22 | lib) 23 | echo ":: building lib" 24 | shift 25 | compile "$@" -I"$abspath" -D_XOPEN_SOURCE=600 -shared -fPIC -o WeebCore.so \ 26 | Platform/PlatformLib.c || exit 27 | exit 0 28 | ;; 29 | esac 30 | 31 | # I explicitly build core separately just to make sure it's built with the absolute minimum cflags 32 | # so that any non-portable code is more likely to generate a warning 33 | echo ":: building core" 34 | compile -DWEEBCORE_IMPLEMENTATION -c -o obj/WeebCore.o WeebCore.c || exit 35 | 36 | cflags="$cflags 37 | -I\"$abspath\" 38 | -DWEEBCORE_OVERRIDE_MONOLITHIC_BUILD=1 39 | -D_XOPEN_SOURCE=600 obj/WeebCore.o" 40 | 41 | case "$1" in 42 | *.c) 43 | compile "$@" -I"$abspath" -o "./bin/$(exename "$1")" || exit 44 | ;; 45 | *) 46 | echo ":: building examples" 47 | for x in Examples/*.c; do 48 | compile "$@" "$x" -I"$abspath" -o "./bin/$(exename "$x")"\ 49 | || exit 50 | done 51 | 52 | echo ":: building utils" 53 | for x in Utils/*.c; do 54 | compile "$@" "$x" -I"$abspath" -o "./bin/$(exename "$x")" || exit 55 | done 56 | ;; 57 | esac 58 | -------------------------------------------------------------------------------- /cflags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cflags="-std=c89 -pedantic -O3 -Wall -Wno-overlength-strings -Wno-int-to-pointer-cast 4 | -Wno-pointer-to-int-cast -ffunction-sections -fdata-sections -fwrapv" 5 | if [ -z "$DBGINFO" ]; then 6 | cflags="$cflags -g0 -fno-unwind-tables -s -fno-asynchronous-unwind-tables -fno-stack-protector 7 | -Wl,--build-id=none" 8 | else 9 | cflags="$cflags -g -fsanitize=address,undefined" 10 | fi 11 | if [ "$(uname)" = "Darwin" ]; then 12 | cc=${CC:-clang} 13 | cflags="$cflags -Wl,-dead_strip" 14 | else 15 | cc=${CC:-gcc} 16 | cflags="$cflags -Wl,--gc-sections" 17 | fi 18 | 19 | cflags="$cflags ${CFLAGS:-}" 20 | ldflags="$ldflags ${LDFLAGS:-}" 21 | 22 | { uname -a; echo "$cc"; echo "$cflags"; echo "$ldflags"; $cc --version; $cc -dumpmachine; } \ 23 | > flags.log 24 | 25 | export cflags 26 | export ldflags 27 | export cc 28 | 29 | -------------------------------------------------------------------------------- /font.wbspr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Francesco149/WeebCore/aed6f8ae3f5d26b634b1e7a026758a9c95439f27/font.wbspr -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Unlicensing your contributions 2 | - [ ] If this is a non-trivial contribution (more than 15 lines of code changed), I will attach a contributor waiver as explained in 3 | [the contributor guidelines](../blob/master/CONTRIBUTING.md) . 4 | --------------------------------------------------------------------------------